From 5162845c1d95aa009b78c8dcc41dac848a1e1491 Mon Sep 17 00:00:00 2001 From: Scott Bishel Date: Wed, 24 Jun 2026 15:29:57 -0600 Subject: [PATCH 1/5] add file upload samples --- go.mod | 2 + server/command_hooks.go | 35 ++++++++++++++++ server/dialog_samples.go | 41 ++++++++++++++++++- server/http_hooks.go | 86 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index ce1ce1e..b37c874 100644 --- a/go.mod +++ b/go.mod @@ -67,3 +67,5 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/mattermost/mattermost/server/public => ../mattermost-interactive-file-upload/server/public diff --git a/server/command_hooks.go b/server/command_hooks.go index 6d0d125..4bed7a2 100644 --- a/server/command_hooks.go +++ b/server/command_hooks.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "strings" "time" @@ -50,6 +51,9 @@ const ( "- `/dialog error-no-elements` - Open an Interactive Dialog with no elements which always returns an general error.\n" + "- `/dialog field-refresh` - Open an Interactive Dialog with field refresh functionality.\n" + "- `/dialog multistep` - Open a multi-step Interactive Dialog demonstrating form refresh on submit.\n" + + // "- `/dialog action-buttons` - Open an Interactive Dialog demonstrating an incident response board with triage and timeline notes.\n" + + "- `/dialog file-upload` - Open an Interactive Dialog with file upload fields (single and multiple).\n" + + "- `/dialog file-upload-clear` - Clear persisted file upload data and start fresh.\n" + "- `/dialog help` - Show this help text" ) @@ -209,6 +213,12 @@ func getCommandDialogAutocompleteData() *model.AutocompleteData { multiSelect := model.NewAutocompleteData("multi-select", "", "Open an Interactive Dialog with multi-select fields.") command.AddCommand(multiSelect) + fileUpload := model.NewAutocompleteData("file-upload", "", "Open an Interactive Dialog with file upload fields.") + command.AddCommand(fileUpload) + + fileUploadClear := model.NewAutocompleteData("file-upload-clear", "", "Clear persisted file upload data.") + command.AddCommand(fileUploadClear) + help := model.NewAutocompleteData("help", "", "") command.AddCommand(help) @@ -525,6 +535,31 @@ func (p *Plugin) executeCommandDialog(args *model.CommandArgs) *model.CommandRes URL: fmt.Sprintf("%s/plugins/%s/dialog/multistep", *serverConfig.ServiceSettings.SiteURL, manifest.Id), Dialog: getDialogStep1(), } + case "file-upload": + dialog := getDialogWithFileUpload() + kvKey := "file_upload_" + args.UserId + if data, appErr := p.API.KVGet(kvKey); appErr == nil && len(data) > 0 { + var stored map[string]string + if json.Unmarshal(data, &stored) == nil { + for i := range dialog.Elements { + if val, ok := stored[dialog.Elements[i].Name]; ok { + dialog.Elements[i].Default = val + } + } + } + } + dialogRequest = model.OpenDialogRequest{ + TriggerId: args.TriggerId, + URL: fmt.Sprintf("%s/plugins/%s/dialog/file-upload", *serverConfig.ServiceSettings.SiteURL, manifest.Id), + Dialog: dialog, + } + case "file-upload-clear": + kvKey := "file_upload_" + args.UserId + p.API.KVDelete(kvKey) + return &model.CommandResponse{ + ResponseType: model.CommandResponseTypeEphemeral, + Text: "File upload data cleared.", + } default: return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, diff --git a/server/dialog_samples.go b/server/dialog_samples.go index 88b398c..7e62998 100644 --- a/server/dialog_samples.go +++ b/server/dialog_samples.go @@ -157,6 +157,7 @@ func getDialogWithSampleElements() model.Dialog { Text: "Option3", Value: "opt3", }}, + Default: "opt3", }}, SubmitLabel: "Submit", NotifyOnCancel: true, @@ -413,7 +414,12 @@ func getDialogWithDynamicSelectElements() model.Dialog { DataSource: "dynamic", DataSourceURL: fmt.Sprintf("/plugins/%s/dialog/countries", manifest.Id), Optional: true, - }}, + }, { + DisplayName: "Text", + Name: "text_elem", + Type: "text", + }, + }, SubmitLabel: "Submit Dynamic Select", NotifyOnCancel: true, State: dialogStateSome, @@ -1000,6 +1006,39 @@ func getDialogWithMultiSelectElements() model.Dialog { } } +func getDialogWithFileUpload() model.Dialog { + return model.Dialog{ + CallbackId: "file_upload_demo", + Title: "File Upload Dialog Demo", + IconURL: "http://www.mattermost.org/wp-content/uploads/2016/04/icon.png", + Elements: []model.DialogElement{{ + DisplayName: "Single File Upload", + Name: "single_file", + Type: "file", + HelpText: "Upload a single file.", + Optional: true, + }, { + DisplayName: "Multiple File Upload", + Name: "multi_file", + Type: "file", + HelpText: "Upload multiple files at once.", + Optional: true, + AllowMultiple: true, + }, { + DisplayName: "Description", + Name: "description", + Type: "textarea", + Placeholder: "Describe the uploaded files...", + HelpText: "Optional description of the uploaded files.", + Optional: true, + MaxLength: 500, + }}, + SubmitLabel: "Submit Files", + NotifyOnCancel: true, + State: dialogStateSome, + } +} + // Helper function to convert interface{} to string safely func interfaceToString(value interface{}) string { switch v := value.(type) { diff --git a/server/http_hooks.go b/server/http_hooks.go index 420f735..d81d166 100644 --- a/server/http_hooks.go +++ b/server/http_hooks.go @@ -54,6 +54,7 @@ func (p *Plugin) initializeAPI() { dialogRouter.HandleFunc("/error", p.handleDialogWithError) dialogRouter.HandleFunc("/field-refresh", p.handleDialogFieldRefresh) dialogRouter.HandleFunc("/multistep", p.handleDialogMultistep) + dialogRouter.HandleFunc("/file-upload", p.handleDialogFileUpload) dialogRouter.HandleFunc("/products", p.handleDynamicProducts).Methods(http.MethodPost) dialogRouter.HandleFunc("/companies", p.handleDynamicCompanies).Methods(http.MethodPost) @@ -927,3 +928,88 @@ func (p *Plugin) handleDynamicCountries(w http.ResponseWriter, r *http.Request) response := model.LookupDialogResponse{Items: filteredCountries} p.writeJSON(w, response) } + +func (p *Plugin) handleDialogFileUpload(w http.ResponseWriter, r *http.Request) { + var request model.SubmitDialogRequest + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + p.API.LogError("Failed to decode SubmitDialogRequest", "err", err) + w.WriteHeader(http.StatusBadRequest) + return + } + defer r.Body.Close() + + user, appErr := p.API.GetUser(request.UserId) + if appErr != nil { + p.API.LogError("Failed to get user for dialog", "err", appErr.Error()) + w.WriteHeader(http.StatusOK) + return + } + + if request.Cancelled { + if _, appErr = p.API.CreatePost(&model.Post{ + UserId: p.botID, + ChannelId: request.ChannelId, + Message: fmt.Sprintf("@%v canceled the file upload dialog", user.Username), + }); appErr != nil { + p.API.LogError("Failed to post file upload cancel message", "err", appErr.Error()) + } + w.WriteHeader(http.StatusOK) + return + } + + // Build a summary of uploaded files + msg := fmt.Sprintf("@%v submitted a file upload dialog\n", user.Username) + + if desc, ok := request.Submission["description"].(string); ok && desc != "" { + msg += fmt.Sprintf("**Description:** %s\n", desc) + } + + // File IDs come in both request.FileIds and in submission values as comma-separated strings + fileIds := request.FileIds + if len(fileIds) == 0 { + // Fallback: extract from submission values for file-type fields + for _, key := range []string{"single_file", "multi_file"} { + if val, ok := request.Submission[key].(string); ok && val != "" { + for _, id := range strings.Split(val, ",") { + if id = strings.TrimSpace(id); id != "" { + fileIds = append(fileIds, id) + } + } + } + } + } + + if len(fileIds) > 0 { + msg += fmt.Sprintf("**File IDs (%d):** %s\n", len(fileIds), strings.Join(fileIds, ", ")) + } else { + msg += "**Files:** none\n" + } + + // Post with the uploaded files attached + post := &model.Post{ + UserId: p.botID, + ChannelId: request.ChannelId, + Message: msg, + FileIds: fileIds, + } + + if _, appErr = p.API.CreatePost(post); appErr != nil { + p.API.LogError("Failed to post file upload dialog message", "err", appErr.Error()) + return + } + + // Persist per-element file IDs so the dialog can be re-opened with previously uploaded files + kvKey := "file_upload_" + request.UserId + stored := map[string]string{} + for _, key := range []string{"single_file", "multi_file"} { + if val, ok := request.Submission[key].(string); ok && val != "" { + stored[key] = val + } + } + if len(stored) > 0 { + data, _ := json.Marshal(stored) + p.API.KVSet(kvKey, data) + } + + w.WriteHeader(http.StatusOK) +} From eb2c0f291d44c30233c5abd290ac1322dc3a3174 Mon Sep 17 00:00:00 2001 From: Scott Bishel Date: Thu, 13 Aug 2026 13:28:49 -0600 Subject: [PATCH 2/5] update go.mod --- go.mod | 24 +++++++++++------------- go.sum | 46 ++++++++++++++++++++++------------------------ 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/go.mod b/go.mod index b37c874..1264c8d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/mattermost/mattermost-plugin-demo -go 1.26.2 +go 1.26.4 require ( github.com/gorilla/mux v1.8.1 @@ -12,7 +12,7 @@ require ( ) require ( - github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/beevik/etree v1.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect @@ -27,10 +27,10 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.7.0 // indirect + github.com/hashicorp/go-plugin v1.8.0 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect - github.com/lib/pq v1.12.0 // indirect + github.com/lib/pq v1.12.3 // indirect github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect github.com/mattermost/gosaml2 v0.10.0 // indirect github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 // indirect @@ -48,24 +48,22 @@ require ( github.com/segmentio/encoding v0.5.4 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/stretchr/objx v0.5.3 // indirect - github.com/tinylib/msgp v1.6.3 // indirect + github.com/tinylib/msgp v1.6.4 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/wiggin77/merror v1.0.5 // indirect github.com/wiggin77/srslog v1.0.1 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect - golang.org/x/crypto v0.50.0 // indirect - golang.org/x/mod v0.34.0 // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/net v0.54.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.36.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.37.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 // indirect google.golang.org/grpc v1.81.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/mattermost/mattermost/server/public => ../mattermost-interactive-file-upload/server/public diff --git a/go.sum b/go.sum index 4c14b86..45f7a88 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beevik/etree v1.6.0 h1:u8Kwy8pp9D9XeITj2Z0XtA5qqZEmtJtuXZRQi+j03eE= @@ -89,8 +89,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1 github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= -github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= +github.com/hashicorp/go-plugin v1.8.0 h1:ie8S6RRY8RvB2usYZv+AAZ/wBvx2AU5p5QeP5j/FORs= +github.com/hashicorp/go-plugin v1.8.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= @@ -112,8 +112,8 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo= -github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8= @@ -126,8 +126,6 @@ github.com/mattermost/logr/v2 v2.0.22 h1:npFkXlkAWR9J8payh8ftPcCZvLbHSI125mAM5/r github.com/mattermost/logr/v2 v2.0.22/go.mod h1:0sUKpO+XNMZApeumaid7PYaUZPBIydfuWZ0dqixXo+s= github.com/mattermost/mattermost-plugin-agents v1.14.1-0.20260519141957-379b06b188d9 h1:ovbzO370Fw8rzDRaQhZdY2HMan8QG4l0Gd1HaNEBKkY= github.com/mattermost/mattermost-plugin-agents v1.14.1-0.20260519141957-379b06b188d9/go.mod h1:Ca1M+q6C0EwPEbDBZyPyqKlRdwv3NXbo+vd19B6MOgU= -github.com/mattermost/mattermost/server/public v0.3.1-0.20260402155910-d9d71af83e3f h1:FXDfzbDTk86bKEgBATCTAb3AWsQVzJMn9ruLY72nmQk= -github.com/mattermost/mattermost/server/public v0.3.1-0.20260402155910-d9d71af83e3f/go.mod h1:QnF/1Evlh7e3G8ifwut7Q5Joy/t4oHYNcDoyBTYuXho= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -215,8 +213,8 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s= -github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= +github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ= +github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= @@ -247,14 +245,14 @@ golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -264,8 +262,8 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -289,14 +287,14 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -304,8 +302,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= @@ -320,8 +318,8 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 h1:pfIbyB44sWzHiCpRqIen67ZQnVXSfIxWrqUMk1qwODE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= From 46985ec145ef79a6c1df2415d63d5842de22fb9b Mon Sep 17 00:00:00 2001 From: Scott Bishel Date: Thu, 13 Aug 2026 13:36:25 -0600 Subject: [PATCH 3/5] remove unrelated changes --- server/command_hooks.go | 1 - server/dialog_samples.go | 8 +------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/server/command_hooks.go b/server/command_hooks.go index f80ce49..0f45f69 100644 --- a/server/command_hooks.go +++ b/server/command_hooks.go @@ -52,7 +52,6 @@ const ( "- `/dialog error-no-elements` - Open an Interactive Dialog with no elements which always returns an general error.\n" + "- `/dialog field-refresh` - Open an Interactive Dialog with field refresh functionality.\n" + "- `/dialog multistep` - Open a multi-step Interactive Dialog demonstrating form refresh on submit.\n" + - // "- `/dialog action-buttons` - Open an Interactive Dialog demonstrating an incident response board with triage and timeline notes.\n" + "- `/dialog file-upload` - Open an Interactive Dialog with file upload fields (single and multiple).\n" + "- `/dialog file-upload-clear` - Clear persisted file upload data and start fresh.\n" + "- `/dialog help` - Show this help text" diff --git a/server/dialog_samples.go b/server/dialog_samples.go index 7e62998..4e098c0 100644 --- a/server/dialog_samples.go +++ b/server/dialog_samples.go @@ -157,7 +157,6 @@ func getDialogWithSampleElements() model.Dialog { Text: "Option3", Value: "opt3", }}, - Default: "opt3", }}, SubmitLabel: "Submit", NotifyOnCancel: true, @@ -414,12 +413,7 @@ func getDialogWithDynamicSelectElements() model.Dialog { DataSource: "dynamic", DataSourceURL: fmt.Sprintf("/plugins/%s/dialog/countries", manifest.Id), Optional: true, - }, { - DisplayName: "Text", - Name: "text_elem", - Type: "text", - }, - }, + }}, SubmitLabel: "Submit Dynamic Select", NotifyOnCancel: true, State: dialogStateSome, From 00a0dea47e66b38c4fc12a3af0d7af33cc0d336c Mon Sep 17 00:00:00 2001 From: Scott Bishel Date: Thu, 13 Aug 2026 14:55:51 -0600 Subject: [PATCH 4/5] coderabbit review fixes --- server/command_hooks.go | 8 +++++++- server/http_hooks.go | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/server/command_hooks.go b/server/command_hooks.go index 0f45f69..eb1f41a 100644 --- a/server/command_hooks.go +++ b/server/command_hooks.go @@ -566,7 +566,13 @@ func (p *Plugin) executeCommandDialog(args *model.CommandArgs) *model.CommandRes } case "file-upload-clear": kvKey := "file_upload_" + args.UserId - p.API.KVDelete(kvKey) + if appErr := p.API.KVDelete(kvKey); appErr != nil { + p.API.LogError("Failed to clear file upload data", "err", appErr.Error()) + return &model.CommandResponse{ + ResponseType: model.CommandResponseTypeEphemeral, + Text: "Failed to clear file upload data.", + } + } return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, Text: "File upload data cleared.", diff --git a/server/http_hooks.go b/server/http_hooks.go index d8dd033..1ef7989 100644 --- a/server/http_hooks.go +++ b/server/http_hooks.go @@ -1030,7 +1030,8 @@ func (p *Plugin) handleDialogFileUpload(w http.ResponseWriter, r *http.Request) user, appErr := p.API.GetUser(request.UserId) if appErr != nil { p.API.LogError("Failed to get user for dialog", "err", appErr.Error()) - w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(model.SubmitDialogResponse{Error: "Failed to process dialog submission."}) return } @@ -1084,10 +1085,13 @@ func (p *Plugin) handleDialogFileUpload(w http.ResponseWriter, r *http.Request) if _, appErr = p.API.CreatePost(post); appErr != nil { p.API.LogError("Failed to post file upload dialog message", "err", appErr.Error()) + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(model.SubmitDialogResponse{Error: "Failed to post the uploaded files."}) return } - // Persist per-element file IDs so the dialog can be re-opened with previously uploaded files + // Persist per-element file IDs so the dialog can be re-opened with previously uploaded files. + // Always overwrite to avoid returning stale IDs on the next open. kvKey := "file_upload_" + request.UserId stored := map[string]string{} for _, key := range []string{"single_file", "multi_file"} { @@ -1098,6 +1102,8 @@ func (p *Plugin) handleDialogFileUpload(w http.ResponseWriter, r *http.Request) if len(stored) > 0 { data, _ := json.Marshal(stored) p.API.KVSet(kvKey, data) + } else { + p.API.KVDelete(kvKey) } w.WriteHeader(http.StatusOK) From 3fe5e28ed2c27bceb02964390876ab851a090e78 Mon Sep 17 00:00:00 2001 From: Scott Bishel Date: Mon, 17 Aug 2026 11:56:46 -0600 Subject: [PATCH 5/5] update server/public version --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1264c8d..23f700f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.26.4 require ( github.com/gorilla/mux v1.8.1 github.com/mattermost/mattermost-plugin-agents v1.14.1-0.20260519141957-379b06b188d9 - github.com/mattermost/mattermost/server/public v0.3.1-0.20260402155910-d9d71af83e3f + github.com/mattermost/mattermost/server/public v0.4.4-0.20260710034453-0d7ae8e58d6a github.com/modelcontextprotocol/go-sdk v1.4.1 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index 45f7a88..11cfdbc 100644 --- a/go.sum +++ b/go.sum @@ -126,6 +126,8 @@ github.com/mattermost/logr/v2 v2.0.22 h1:npFkXlkAWR9J8payh8ftPcCZvLbHSI125mAM5/r github.com/mattermost/logr/v2 v2.0.22/go.mod h1:0sUKpO+XNMZApeumaid7PYaUZPBIydfuWZ0dqixXo+s= github.com/mattermost/mattermost-plugin-agents v1.14.1-0.20260519141957-379b06b188d9 h1:ovbzO370Fw8rzDRaQhZdY2HMan8QG4l0Gd1HaNEBKkY= github.com/mattermost/mattermost-plugin-agents v1.14.1-0.20260519141957-379b06b188d9/go.mod h1:Ca1M+q6C0EwPEbDBZyPyqKlRdwv3NXbo+vd19B6MOgU= +github.com/mattermost/mattermost/server/public v0.4.4-0.20260710034453-0d7ae8e58d6a h1:l7IyGE+1tMjj/ymV1JH0Gq0WG7Ln/kCLya3FA1U+tag= +github.com/mattermost/mattermost/server/public v0.4.4-0.20260710034453-0d7ae8e58d6a/go.mod h1:rHFKFSnyNmyk1qieL00Fv+YuLb093Q7y8VTwBu/43ic= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=