diff --git a/go.mod b/go.mod index ce1ce1e..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,19 +48,19 @@ 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 diff --git a/go.sum b/go.sum index 4c14b86..e279c33 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= @@ -215,8 +215,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 +247,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 +264,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 +289,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 +304,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.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= 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 +320,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= diff --git a/server/command_hooks.go b/server/command_hooks.go index f6daf9e..146f678 100644 --- a/server/command_hooks.go +++ b/server/command_hooks.go @@ -51,6 +51,8 @@ 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 checkbox-group` - Open an Interactive Dialog with checkbox_group fields and label_position variants.\n" + + "- `/dialog checkbox-matrix` - Open an Interactive Dialog with checkbox_matrix fields in both row_selection modes.\n" + "- `/dialog help` - Show this help text" ) @@ -219,6 +221,12 @@ func getCommandDialogAutocompleteData() *model.AutocompleteData { multiSelect := model.NewAutocompleteData("multi-select", "", "Open an Interactive Dialog with multi-select fields.") command.AddCommand(multiSelect) + checkboxGroup := model.NewAutocompleteData("checkbox-group", "", "Open an Interactive Dialog with checkbox_group fields and label_position variants.") + command.AddCommand(checkboxGroup) + + checkboxMatrix := model.NewAutocompleteData("checkbox-matrix", "", "Open an Interactive Dialog with checkbox_matrix fields in both row_selection modes.") + command.AddCommand(checkboxMatrix) + help := model.NewAutocompleteData("help", "", "") command.AddCommand(help) @@ -537,6 +545,18 @@ 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 "checkbox-group": + dialogRequest = model.OpenDialogRequest{ + TriggerId: args.TriggerId, + URL: fmt.Sprintf("%s/plugins/%s/dialog/checkboxes", *serverConfig.ServiceSettings.SiteURL, manifest.Id), + Dialog: getDialogCheckboxGroup(), + } + case "checkbox-matrix": + dialogRequest = model.OpenDialogRequest{ + TriggerId: args.TriggerId, + URL: fmt.Sprintf("%s/plugins/%s/dialog/checkboxes", *serverConfig.ServiceSettings.SiteURL, manifest.Id), + Dialog: getDialogCheckboxMatrix(), + } default: return &model.CommandResponse{ ResponseType: model.CommandResponseTypeEphemeral, diff --git a/server/dialog_checkboxes_test.go b/server/dialog_checkboxes_test.go new file mode 100644 index 0000000..4bcf6f4 --- /dev/null +++ b/server/dialog_checkboxes_test.go @@ -0,0 +1,90 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// The checkbox dialogs are consumed by the mobile e2e suite, so they must pass +// the same server-side validation the real /dialogs/open endpoint applies. +// Without this, a bad default or a label_position on the wrong element type only +// surfaces as an opaque "Failed to open Interactive Dialog" at test time. +func TestCheckboxDialogsAreValid(t *testing.T) { + t.Run("checkbox_group", func(t *testing.T) { + d := getDialogCheckboxGroup() + require.NoError(t, d.IsValid()) + }) + + t.Run("checkbox_matrix", func(t *testing.T) { + d := getDialogCheckboxMatrix() + require.NoError(t, d.IsValid()) + }) +} + +// Field names and option values are part of the e2e contract — the spec builds +// testIDs like AppFormElement..checkbox..button from them. +func TestCheckboxDialogContract(t *testing.T) { + group := getDialogCheckboxGroup() + + byName := map[string]int{} + for i, e := range group.Elements { + byName[e.Name] = i + } + for _, name := range []string{"services", "regions", "notify_before", "notify_after", "priority"} { + require.Contains(t, byName, name, "e2e spec depends on this field name") + } + + services := group.Elements[byName["services"]] + assert.False(t, services.Optional, "services must stay required for the validation test") + assert.Empty(t, services.Default, "services must have no default so submit is blocked") + + regions := group.Elements[byName["regions"]] + assert.Equal(t, "us,apac", regions.Default, "defaults test asserts these are pre-checked") + + assert.Equal(t, "before", group.Elements[byName["notify_before"]].LabelPosition) + assert.Equal(t, "after", group.Elements[byName["notify_after"]].LabelPosition) + + priority := group.Elements[byName["priority"]] + assert.Equal(t, "radio", priority.Type) + assert.True(t, priority.Optional, "Clear selection only renders for optional radios") + + matrix := getDialogCheckboxMatrix() + matrixByName := map[string]int{} + for i, e := range matrix.Elements { + matrixByName[e.Name] = i + } + require.Contains(t, matrixByName, "permissions") + require.Contains(t, matrixByName, "environment_owner") + + permissions := matrix.Elements[matrixByName["permissions"]] + assert.Equal(t, "multiple", permissions.MatrixConfig.RowSelection) + assert.Equal(t, "posts:view,edit", permissions.Default) + assert.Greater(t, len(permissions.MatrixConfig.Columns), 4, "needs enough columns to force horizontal scroll") + + owner := matrix.Elements[matrixByName["environment_owner"]] + assert.Equal(t, "single", owner.MatrixConfig.RowSelection) +} + +func TestFormatCheckboxValue(t *testing.T) { + for name, tc := range map[string]struct { + value any + expected string + }{ + "checkbox_group sorted": {[]any{"web", "api"}, "api,web"}, + "checkbox_group single": {[]any{"api"}, "api"}, + "empty array": {[]any{}, "(none)"}, + "nil": {nil, "(none)"}, + "empty string": {"", "(none)"}, + "radio string": {"p2", "p2"}, + "matrix single row": {[]any{"posts:edit,view"}, "posts:edit,view"}, + "matrix sorts columns": {[]any{"posts:view,edit"}, "posts:edit,view"}, + "matrix sorts rows": {[]any{"files:delete", "posts:view"}, "files:delete;posts:view"}, + "matrix trims column spaces": {[]any{"posts: view , edit "}, "posts:edit,view"}, + } { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.expected, formatCheckboxValue(tc.value)) + }) + } +} diff --git a/server/dialog_samples.go b/server/dialog_samples.go index 88b398c..01d5506 100644 --- a/server/dialog_samples.go +++ b/server/dialog_samples.go @@ -1150,3 +1150,148 @@ func getDialogDateTimeTimezone() model.Dialog { "This dialog demonstrates timezone support and manual time entry features.", } } + +// ============================================================================ +// checkbox_group / checkbox_matrix Dialogs +// Covers: MM-T5115 - MM-T5122 +// ============================================================================ + +// Dialog: checkbox_group elements plus a radio field for the optional "Clear +// selection" affordance. Field names are stable — the e2e spec keys off them. +func getDialogCheckboxGroup() model.Dialog { + return model.Dialog{ + CallbackId: "checkbox_group", + Title: "Checkbox Group Demo", + SubmitLabel: "Submit", + State: "checkbox_group", + + // Keep this as ordinary dialog copy. Do not repeat the title here: the title + // already renders in the modal header, and a second copy makes + // by.text() match twice, which fails Detox with "multiple elements + // matched". + IntroductionText: "Select the services affected by this incident, then adjust regions, notifications, and priority as needed.", + Elements: []model.DialogElement{ + { + DisplayName: "Affected Services", + Name: "services", + Type: "checkbox_group", + HelpText: "Select at least one service.", + Options: []*model.PostActionOptions{ + {Text: "API", Value: "api"}, + {Text: "Web", Value: "web"}, + {Text: "Database", Value: "db"}, + {Text: "Cache", Value: "cache"}, + }, + }, + { + DisplayName: "Regions", + Name: "regions", + Type: "checkbox_group", + Optional: true, + Default: "us,apac", + HelpText: "Defaults to US and APAC.", + Options: []*model.PostActionOptions{ + {Text: "US", Value: "us"}, + {Text: "EU", Value: "eu"}, + {Text: "APAC", Value: "apac"}, + }, + }, + { + DisplayName: "Notify (label before)", + Name: "notify_before", + Type: "checkbox_group", + Optional: true, + LabelPosition: "before", + Options: []*model.PostActionOptions{ + {Text: "Email", Value: "email"}, + {Text: "Push", Value: "push"}, + }, + }, + { + DisplayName: "Notify (label after)", + Name: "notify_after", + Type: "checkbox_group", + Optional: true, + LabelPosition: "after", + Options: []*model.PostActionOptions{ + {Text: "Email", Value: "email"}, + {Text: "Push", Value: "push"}, + }, + }, + { + DisplayName: "Priority", + Name: "priority", + Type: "radio", + Optional: true, + HelpText: "Optional — you can clear your selection.", + Options: []*model.PostActionOptions{ + {Text: "P1", Value: "p1"}, + {Text: "P2", Value: "p2"}, + {Text: "P3", Value: "p3"}, + }, + }, + }, + } +} + +// Dialog: checkbox_matrix in both row_selection modes. `permissions` uses seven +// columns so the grid must scroll horizontally while row labels stay pinned. +func getDialogCheckboxMatrix() model.Dialog { + return model.Dialog{ + CallbackId: "checkbox_matrix", + Title: "Checkbox Matrix Demo", + SubmitLabel: "Submit", + State: "checkbox_matrix", + + // Ordinary dialog copy, and deliberately not repeating the title — see the + // note in getDialogCheckboxGroup. + IntroductionText: "Grant permissions for each resource, then choose a single owner per environment.", + Elements: []model.DialogElement{ + { + DisplayName: "Permissions", + Name: "permissions", + Type: "checkbox_matrix", + Optional: true, + Default: "posts:view,edit", + HelpText: "Scroll right to see more columns.", + MatrixConfig: &model.DialogMatrixConfig{ + RowSelection: "multiple", + Rows: []*model.PostActionOptions{ + {Text: "Posts", Value: "posts"}, + {Text: "Files", Value: "files"}, + {Text: "Channels", Value: "channels"}, + }, + Columns: []*model.PostActionOptions{ + {Text: "View", Value: "view"}, + {Text: "Edit", Value: "edit"}, + {Text: "Delete", Value: "delete"}, + {Text: "Share", Value: "share"}, + {Text: "Export", Value: "export"}, + {Text: "Archive", Value: "archive"}, + {Text: "Restore", Value: "restore"}, + }, + }, + }, + { + DisplayName: "Environment Owner", + Name: "environment_owner", + Type: "checkbox_matrix", + Optional: true, + HelpText: "One owner per environment.", + MatrixConfig: &model.DialogMatrixConfig{ + RowSelection: "single", + Rows: []*model.PostActionOptions{ + {Text: "Dev", Value: "dev"}, + {Text: "Staging", Value: "staging"}, + {Text: "Prod", Value: "prod"}, + }, + Columns: []*model.PostActionOptions{ + {Text: "Alice", Value: "alice"}, + {Text: "Bob", Value: "bob"}, + {Text: "Carol", Value: "carol"}, + }, + }, + }, + }, + } +} diff --git a/server/http_hooks.go b/server/http_hooks.go index b54cfd3..eccf893 100644 --- a/server/http_hooks.go +++ b/server/http_hooks.go @@ -5,6 +5,7 @@ import ( "fmt" "html" "net/http" + "sort" "strings" "time" @@ -57,6 +58,7 @@ func (p *Plugin) initializeAPI() { dialogRouter.HandleFunc("/error", p.handleDialogWithError) dialogRouter.HandleFunc("/field-refresh", p.handleDialogFieldRefresh) dialogRouter.HandleFunc("/multistep", p.handleDialogMultistep) + dialogRouter.HandleFunc("/checkboxes", p.handleDialogCheckboxes) dialogRouter.HandleFunc("/products", p.handleDynamicProducts).Methods(http.MethodPost) dialogRouter.HandleFunc("/companies", p.handleDynamicCompanies).Methods(http.MethodPost) @@ -498,6 +500,48 @@ func (p *Plugin) handleInteractiveAction(w http.ResponseWriter, r *http.Request) p.writeJSON(w, resp) } +// handleDialogCheckboxes echoes the submission of the checkbox_group / +// checkbox_matrix dialogs back as readable message text so e2e tests can assert +// the exact wire encoding (post Props, used by handleDialog1, isn't visible +// on screen). +func (p *Plugin) handleDialogCheckboxes(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 checkbox SubmitDialogRequest", "err", err) + w.WriteHeader(http.StatusBadRequest) + return + } + defer r.Body.Close() + + if request.Cancelled { + p.writeJSON(w, &model.SubmitDialogResponse{}) + return + } + + names := make([]string, 0, len(request.Submission)) + for name := range request.Submission { + names = append(names, name) + } + sort.Strings(names) + + lines := make([]string, 0, len(names)) + for _, name := range names { + lines = append(lines, fmt.Sprintf("%s=%s", name, formatCheckboxValue(request.Submission[name]))) + } + + if _, appErr := p.API.CreatePost(&model.Post{ + UserId: p.botID, + ChannelId: request.ChannelId, + Message: "Checkboxes Submitted:\n" + strings.Join(lines, "\n"), + }); appErr != nil { + p.API.LogError("Failed to post checkbox submission", "err", appErr.Error()) + w.WriteHeader(http.StatusInternalServerError) + return + } + + p.writeJSON(w, &model.SubmitDialogResponse{}) +} + func (p *Plugin) handleInlineActionTriage(w http.ResponseWriter, r *http.Request) { var request model.PostActionIntegrationRequest err := json.NewDecoder(r.Body).Decode(&request) @@ -576,6 +620,60 @@ func (p *Plugin) handleInlineActionTriage(w http.ResponseWriter, r *http.Request p.writeJSON(w, resp) } +// formatCheckboxValue renders a submission value canonically. checkbox_group and +// checkbox_matrix both arrive as JSON arrays of strings. +// +// Entries are sorted deliberately: neither the group's selection order nor the +// matrix's row order is semantically meaningful, and asserting on either would +// make e2e expectations depend on the order the test happened to tap cells in. +// The row/column *structure* is still asserted verbatim. +func formatCheckboxValue(value any) string { + switch v := value.(type) { + case []any: + if len(v) == 0 { + return "(none)" + } + parts := make([]string, 0, len(v)) + isMatrix := false + for _, item := range v { + entry := canonicalMatrixEntry(fmt.Sprintf("%v", item)) + if strings.Contains(entry, ":") { + isMatrix = true + } + parts = append(parts, entry) + } + sort.Strings(parts) + + // Matrix entries already contain "," between columns, so they are joined + // with ";" to keep the two nesting levels distinguishable. + if isMatrix { + return strings.Join(parts, ";") + } + return strings.Join(parts, ",") + case nil: + return "(none)" + default: + if s := fmt.Sprintf("%v", v); s != "" { + return s + } + return "(none)" + } +} + +// canonicalMatrixEntry sorts the column list inside a single "row:col1,col2" entry. +func canonicalMatrixEntry(entry string) string { + rowValue, columnsPart, ok := strings.Cut(entry, ":") + if !ok { + return entry + } + columns := strings.Split(columnsPart, ",") + for i := range columns { + columns[i] = strings.TrimSpace(columns[i]) + } + sort.Strings(columns) + return rowValue + ":" + strings.Join(columns, ",") +} + func formatJSON(v any) string { b, err := json.MarshalIndent(v, "", " ") if err != nil {