Files
upstream-mirror 240db4e2a7
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 00:29:55 +05:00

271 lines
6.8 KiB
Go

package flow
import (
"fmt"
"net/url"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/mattermost/mattermost/server/public/model"
)
type Color string
const (
ColorDefault Color = "default"
ColorPrimary Color = "primary"
ColorSuccess Color = "success"
ColorGood Color = "good"
ColorWarning Color = "warning"
ColorDanger Color = "danger"
)
type Step struct {
name Name
template *model.MessageAttachment
forwardTo Name
autoForward bool
terminal bool
onRender func(f *Flow)
buttons []Button
}
type Button struct {
Name string
Disabled bool
Color Color
// OnClick is called when the button is clicked. It returns the next step's
// name and the state updates to apply.
//
// If Dialog is also specified, OnClick is executed first.
OnClick func(f *Flow) (Name, State, error)
// Dialog is the interactive dialog to display if the button is clicked
// (OnClick is executed first). OnDialogSubmit must be provided.
Dialog *model.Dialog
// Function that is called when the dialog box is submitted. It can return a
// general error, or field-specific errors. On success it returns the name
// of the next step, and the state updates to apply.
OnDialogSubmit func(f *Flow, submitted map[string]any) (Name, State, map[string]string, error)
}
func NewStep(name Name) Step {
return Step{
name: name,
template: &model.MessageAttachment{},
}
}
func (s Step) WithButton(buttons ...Button) Step {
s.buttons = append(s.buttons, buttons...)
return s
}
func (s Step) Terminal() Step {
s.terminal = true
return s
}
func (s Step) OnRender(f func(*Flow)) Step {
s.onRender = f
return s
}
func (s Step) Next(name Name) Step {
s.forwardTo = name
s.autoForward = true
return s
}
func (s Step) WithImage(imageURL string) Step {
if u, err := url.Parse(imageURL); err == nil {
if u.Host != "" && (u.Scheme == "http" || u.Scheme == "https") {
s.template.ImageURL = imageURL
} else {
s.template.ImageURL = u.Path
}
}
return s
}
func (s Step) WithColor(color Color) Step {
s.template.Color = string(color)
return s
}
func (s Step) WithPretext(text string) Step {
s.template.Pretext = text
return s
}
func (s Step) WithField(title, value string) Step {
s.template.Fields = append(s.template.Fields, &model.MessageAttachmentField{
Title: title,
Value: value,
})
return s
}
func (s Step) WithTitle(text string) Step {
s.template.Title = text
return s
}
func (s Step) WithText(text string) Step {
s.template.Text = text
return s
}
func (s Step) do(f *Flow) (*model.Post, bool, error) {
if s.onRender != nil {
s.onRender(f)
}
return s.render(f, false, 0)
}
func (s Step) done(f *Flow, selectedButton int) (*model.Post, error) {
post, _, err := s.render(f, true, selectedButton)
return post, err
}
func (s Step) render(f *Flow, done bool, selectedButton int) (*model.Post, bool, error) {
sa := f.processAttachment(s.template)
post := model.Post{}
model.ParseMessageAttachment(&post, []*model.MessageAttachment{sa})
if s.terminal {
// Nothing else to do, do not display buttons on terminal posts.
return &post, true, nil
}
buttons := processButtons(s.buttons, f.state.AppState)
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.MessageAttachment)
if !ok || len(attachments) != 1 {
return nil, false, errors.New("expected 1 message attachment")
}
var actions []*model.PostAction
if done {
if selectedButton > 0 {
action := renderButton(buttons[selectedButton-1], s.name, selectedButton, f.state.AppState)
action.Disabled = true
actions = append(actions, action)
}
} else {
for i, b := range buttons {
actions = append(actions, renderButton(b, s.name, i+1, f.state.AppState))
}
}
attachments[0].Actions = actions
return &post, false, nil
}
func (f *Flow) processAttachment(attachment *model.MessageAttachment) *model.MessageAttachment {
if attachment == nil {
return &model.MessageAttachment{Text: "ERROR"}
}
a := *attachment
a.Pretext = formatState(attachment.Pretext, f.state.AppState)
a.Title = formatState(attachment.Title, f.state.AppState)
a.Text = formatState(attachment.Text, f.state.AppState)
for _, field := range a.Fields {
field.Title = formatState(field.Title, f.state.AppState)
v := field.Value.(string)
if v != "" {
field.Value = formatState(v, f.state.AppState)
}
}
a.Fallback = fmt.Sprintf("%s: %s", a.Title, a.Text)
if attachment.ImageURL != "" {
if u, err := url.Parse(attachment.ImageURL); err == nil {
if u.Host != "" && (u.Scheme == "http" || u.Scheme == "https") {
a.ImageURL = attachment.ImageURL
} else {
a.ImageURL = fmt.Sprintf("%s/plugins/%s/%s", f.siteURL, f.pluginID, strings.TrimPrefix(attachment.ImageURL, "/"))
}
}
}
return &a
}
func processButtons(in []Button, state State) []Button {
var out []Button
for _, b := range in {
button := b
button.Name = formatState(b.Name, state)
out = append(out, button)
}
return out
}
func processDialog(in *model.Dialog, state State) model.Dialog {
d := *in
d.Title = formatState(d.Title, state)
d.IntroductionText = formatState(d.IntroductionText, state)
d.SubmitLabel = formatState(d.SubmitLabel, state)
for i := range d.Elements {
d.Elements[i].DisplayName = formatState(d.Elements[i].DisplayName, state)
d.Elements[i].Name = formatState(d.Elements[i].Name, state)
d.Elements[i].Default = formatState(d.Elements[i].Default, state)
d.Elements[i].Placeholder = formatState(d.Elements[i].Placeholder, state)
d.Elements[i].HelpText = formatState(d.Elements[i].HelpText, state)
}
return d
}
func renderButton(b Button, stepName Name, i int, state State) *model.PostAction {
return &model.PostAction{
Type: model.PostActionTypeButton,
Name: formatState(b.Name, state),
Disabled: b.Disabled,
Style: string(b.Color),
Integration: &model.PostActionIntegration{
Context: map[string]any{
contextStepKey: string(stepName),
contextButtonKey: strconv.Itoa(i),
},
},
}
}
func buttonContext(request *model.PostActionIntegrationRequest) (Name, int, error) {
fromString, ok := request.Context[contextStepKey].(string)
if !ok {
return "", 0, errors.New("missing step name")
}
fromName := Name(fromString)
buttonStr, ok := request.Context[contextButtonKey].(string)
if !ok {
return "", 0, errors.New("missing button id")
}
buttonIndex, err := strconv.Atoi(buttonStr)
if err != nil {
return "", 0, errors.Wrap(err, "invalid button number")
}
return fromName, buttonIndex, nil
}
func dialogContext(request *model.SubmitDialogRequest) (Name, int, error) {
data := strings.Split(request.State, ",")
if len(data) != 2 {
return "", 0, errors.New("invalid request")
}
fromName := Name(data[0])
buttonIndex, err := strconv.Atoi(data[1])
if err != nil {
return "", 0, errors.Wrap(err, "malformed button number")
}
return fromName, buttonIndex, nil
}