From 376ea7e1f5457967bf7521fe65f3fb527df9c024 Mon Sep 17 00:00:00 2001 From: Michael Kochell <6913320+mickmister@users.noreply.github.com> Date: Mon, 20 Nov 2023 10:57:33 -0500 Subject: [PATCH 01/10] Disable unused-parameter golangci check (#191) * add lint rule exclusion for unused-parameter * re-introduce the unused parameters in plugin.go --- .golangci.yml | 3 +++ server/plugin.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 5682ada..5987c1c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -46,3 +46,6 @@ issues: linters: - bodyclose - scopelint # https://github.com/kyoh86/scopelint/issues/4 + - linters: + - revive + text: unused-parameter diff --git a/server/plugin.go b/server/plugin.go index be623f3..a5753a6 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -21,7 +21,7 @@ type Plugin struct { } // ServeHTTP demonstrates a plugin that handles HTTP requests by greeting the world. -func (p *Plugin) ServeHTTP(_ *plugin.Context, w http.ResponseWriter, _ *http.Request) { +func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, world!") } From de0b31b48a2bbc1f990422abbdce2ce1a0948f8d Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Tue, 23 Jan 2024 10:02:37 -0400 Subject: [PATCH 02/10] Sync with playbooks: install-go-tools, gotestsum, and dynamic versions (#192) * Revert "Update main.go (#154)" This reverts commit be4a281d0cc791d10e6e5ae917b325b2f054e475. * Revert "[MM-33506] Use embed package to include plugin manifest (#145)" This reverts commit ca9ee3c17c6920a636a33f378e17395afd6f329f. * Revert "Don't generate manifest.ts (#127)" This reverts commit 18d30b50bc7ba800c9f05bfd82970781db0aea3e. * install-go-tools target, adopt gotestsum * bring back make apply + automatic versioning * Update build/manifest/main.go Co-authored-by: Michael Kochell <6913320+mickmister@users.noreply.github.com> * suppress git describe error when no tags match * make version/release notes opt-in * fix whitespace in Makefile * document version management options --------- Co-authored-by: Michael Kochell <6913320+mickmister@users.noreply.github.com> --- .gitattributes | 2 + .gitignore | 6 ++ Makefile | 53 ++++++++++---- README.md | 9 +++ build/legacy.mk | 3 - build/manifest/main.go | 135 +++++++++++++++++++++++++++++++++++ build/setup.mk | 7 +- plugin.go | 18 ----- plugin.json | 2 - webapp/src/index.tsx | 2 +- webapp/src/manifest.test.tsx | 8 +-- webapp/src/manifest.ts | 3 - 12 files changed, 198 insertions(+), 50 deletions(-) create mode 100644 .gitattributes delete mode 100644 build/legacy.mk delete mode 100644 plugin.go delete mode 100644 webapp/src/manifest.ts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4bd338f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +server/manifest.go linguist-generated=true +webapp/src/manifest.js linguist-generated=true diff --git a/.gitignore b/.gitignore index dc25e90..1d478b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,13 @@ +bin/ dist/ +webapp/src/manifest.ts +server/manifest.go # Mac .DS_Store # Jetbrains .idea/ + +# VS Code +.vscode diff --git a/Makefile b/Makefile index fc2c58c..5342f19 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,6 @@ GO ?= $(shell command -v go 2> /dev/null) NPM ?= $(shell command -v npm 2> /dev/null) CURL ?= $(shell command -v curl 2> /dev/null) MM_DEBUG ?= -MANIFEST_FILE ?= plugin.json GOPATH ?= $(shell go env GOPATH) GO_TEST_FLAGS ?= -race GO_BUILD_FLAGS ?= @@ -13,6 +12,10 @@ DEFAULT_GOARCH := $(shell go env GOARCH) export GO111MODULE=on +# We need to export GOBIN to allow it to be set +# for processes spawned from the Makefile +export GOBIN ?= $(PWD)/bin + # You can include assets this directory into the bundle. This can be e.g. used to include profile pictures. ASSETS_DIR ?= assets @@ -22,7 +25,6 @@ default: all # Verify environment, and define PLUGIN_ID, PLUGIN_VERSION, HAS_SERVER and HAS_WEBAPP as needed. include build/setup.mk -include build/legacy.mk BUNDLE_NAME ?= $(PLUGIN_ID)-$(PLUGIN_VERSION).tar.gz @@ -41,9 +43,20 @@ endif .PHONY: all all: check-style test dist +## Propagates plugin manifest information into the server/ and webapp/ folders. +.PHONY: apply +apply: + ./build/bin/manifest apply + +## Install go tools +install-go-tools: + @echo Installing go tools + $(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.1 + $(GO) install gotest.tools/gotestsum@v1.7.0 + ## Runs eslint and golangci-lint .PHONY: check-style -check-style: webapp/node_modules +check-style: apply webapp/node_modules install-go-tools @echo Checking for style guide compliance ifneq ($(HAS_WEBAPP),) @@ -51,14 +64,13 @@ ifneq ($(HAS_WEBAPP),) cd webapp && npm run check-types endif +# It's highly recommended to run go-vet first +# to find potential compile errors that could introduce +# weird reports at golangci-lint step ifneq ($(HAS_SERVER),) - @if ! [ -x "$$(command -v golangci-lint)" ]; then \ - echo "golangci-lint is not installed. Please see https://github.com/golangci/golangci-lint#install for installation instructions."; \ - exit 1; \ - fi; \ - @echo Running golangci-lint - golangci-lint run ./... + $(GO) vet ./... + $(GOBIN)/golangci-lint run ./... endif ## Builds the server, if it exists, for all supported architectures, unless MM_SERVICESETTINGS_ENABLEDEVELOPER is set. @@ -104,7 +116,7 @@ endif bundle: rm -rf dist/ mkdir -p dist/$(PLUGIN_ID) - cp $(MANIFEST_FILE) dist/$(PLUGIN_ID)/ + ./build/bin/manifest dist ifneq ($(wildcard $(ASSETS_DIR)/.),) cp -r $(ASSETS_DIR) dist/$(PLUGIN_ID)/ endif @@ -125,7 +137,7 @@ endif ## Builds and bundles the plugin. .PHONY: dist -dist: server webapp bundle +dist: apply server webapp bundle ## Builds and installs the plugin to a server. .PHONY: deploy @@ -134,7 +146,7 @@ deploy: dist ## Builds and installs the plugin to a server, updating the webapp automatically when changed. .PHONY: watch -watch: server bundle +watch: apply server bundle ifeq ($(MM_DEBUG),) cd webapp && $(NPM) run build:watch else @@ -188,9 +200,20 @@ detach: setup-attach ## Runs any lints and unit tests defined for the server and webapp, if they exist. .PHONY: test -test: webapp/node_modules +test: apply webapp/node_modules install-go-tools ifneq ($(HAS_SERVER),) - $(GO) test -v $(GO_TEST_FLAGS) ./server/... + $(GOBIN)/gotestsum -- -v ./... +endif +ifneq ($(HAS_WEBAPP),) + cd webapp && $(NPM) run test; +endif + +## Runs any lints and unit tests defined for the server and webapp, if they exist, optimized +## for a CI environment. +.PHONY: test-ci +test-ci: apply webapp/node_modules install-go-tools +ifneq ($(HAS_SERVER),) + $(GOBIN)/gotestsum --format standard-verbose --junitfile report.xml -- ./... endif ifneq ($(HAS_WEBAPP),) cd webapp && $(NPM) run test; @@ -198,7 +221,7 @@ endif ## Creates a coverage report for the server code. .PHONY: coverage -coverage: webapp/node_modules +coverage: apply webapp/node_modules ifneq ($(HAS_SERVER),) $(GO) test $(GO_TEST_FLAGS) -coverprofile=server/coverage.txt ./server/... $(GO) tool cover -html=server/coverage.txt diff --git a/README.md b/README.md index 3b8b91e..a1ac099 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,15 @@ export MM_ADMIN_TOKEN=j44acwd8obn78cdcx7koid4jkr make deploy ``` +### Releasing new versions + +The version of a plugin is determined at compile time, automatically populating a `version` field in the [plugin manifest](plugin.json): +* If the current commit matches a tag, the version will match after stripping any leading `v`, e.g. `1.3.1`. +* Otherwise, the version will combine the nearest tag with `git rev-parse --short HEAD`, e.g. `1.3.1+d06e53e1`. +* If there is no version tag, an empty version will be combined with the short hash, e.g. `0.0.0+76081421`. + +To disable this behaviour, manually populate and maintain the `version` field. + ## Q&A ### How do I make a server-only or web app-only plugin? diff --git a/build/legacy.mk b/build/legacy.mk deleted file mode 100644 index 0c32733..0000000 --- a/build/legacy.mk +++ /dev/null @@ -1,3 +0,0 @@ -.PHONY: apply -apply: - @echo make apply is deprecated and has no effect. diff --git a/build/manifest/main.go b/build/manifest/main.go index 957a500..c57fe10 100644 --- a/build/manifest/main.go +++ b/build/manifest/main.go @@ -4,11 +4,50 @@ import ( "encoding/json" "fmt" "os" + "strings" "github.com/mattermost/mattermost/server/public/model" "github.com/pkg/errors" ) +const pluginIDGoFileTemplate = `// This file is automatically generated. Do not modify it manually. + +package main + +import ( + "encoding/json" + "strings" + + "github.com/mattermost/mattermost/server/public/model" +) + +var manifest *model.Manifest + +const manifestStr = ` + "`" + ` +%s +` + "`" + ` + +func init() { + _ = json.NewDecoder(strings.NewReader(manifestStr)).Decode(&manifest) +} +` + +const pluginIDJSFileTemplate = `// This file is automatically generated. Do not modify it manually. + +const manifest = JSON.parse(` + "`" + ` +%s +` + "`" + `); + +export default manifest; +` + +// These build-time vars are read from shell commands and populated in ../setup.mk +var ( + BuildHashShort string + BuildTagLatest string + BuildTagCurrent string +) + func main() { if len(os.Args) <= 1 { panic("no cmd specified") @@ -37,6 +76,16 @@ func main() { fmt.Printf("true") } + case "apply": + if err := applyManifest(manifest); err != nil { + panic("failed to apply manifest: " + err.Error()) + } + + case "dist": + if err := distManifest(manifest); err != nil { + panic("failed to write manifest to dist directory: " + err.Error()) + } + default: panic("unrecognized command: " + cmd) } @@ -62,6 +111,32 @@ func findManifest() (*model.Manifest, error) { return nil, errors.Wrap(err, "failed to parse manifest") } + // If no version is listed in the manifest, generate one based on the state of the current + // commit, and use the first version we find (to prevent causing errors) + if manifest.Version == "" { + var version string + tags := strings.Fields(BuildTagCurrent) + for _, t := range tags { + if strings.HasPrefix(t, "v") { + version = t + break + } + } + if version == "" { + if BuildTagLatest != "" { + version = BuildTagLatest + "+" + BuildHashShort + } else { + version = "v0.0.0+" + BuildHashShort + } + } + manifest.Version = strings.TrimPrefix(version, "v") + } + + // If no release notes specified, generate one from the latest tag, if present. + if manifest.ReleaseNotesURL == "" && BuildTagLatest != "" { + manifest.ReleaseNotesURL = manifest.HomepageURL + "releases/tag/" + BuildTagLatest + } + return &manifest, nil } @@ -74,3 +149,63 @@ func dumpPluginID(manifest *model.Manifest) { func dumpPluginVersion(manifest *model.Manifest) { fmt.Printf("%s", manifest.Version) } + +// applyManifest propagates the plugin_id into the server and webapp folders, as necessary +func applyManifest(manifest *model.Manifest) error { + if manifest.HasServer() { + // generate JSON representation of Manifest. + manifestBytes, err := json.MarshalIndent(manifest, "", " ") + if err != nil { + return err + } + manifestStr := string(manifestBytes) + + // write generated code to file by using Go file template. + if err := os.WriteFile( + "server/manifest.go", + []byte(fmt.Sprintf(pluginIDGoFileTemplate, manifestStr)), + 0600, + ); err != nil { + return errors.Wrap(err, "failed to write server/manifest.go") + } + } + + if manifest.HasWebapp() { + // generate JSON representation of Manifest. + // JSON is very similar and compatible with JS's object literals. so, what we do here + // is actually JS code generation. + manifestBytes, err := json.MarshalIndent(manifest, "", " ") + if err != nil { + return err + } + manifestStr := string(manifestBytes) + + // Escape newlines + manifestStr = strings.ReplaceAll(manifestStr, `\n`, `\\n`) + + // write generated code to file by using JS file template. + if err := os.WriteFile( + "webapp/src/manifest.ts", + []byte(fmt.Sprintf(pluginIDJSFileTemplate, manifestStr)), + 0600, + ); err != nil { + return errors.Wrap(err, "failed to open webapp/src/manifest.ts") + } + } + + return nil +} + +// distManifest writes the manifest file to the dist directory +func distManifest(manifest *model.Manifest) error { + manifestBytes, err := json.MarshalIndent(manifest, "", " ") + if err != nil { + return err + } + + if err := os.WriteFile(fmt.Sprintf("dist/%s/plugin.json", manifest.Id), manifestBytes, 0600); err != nil { + return errors.Wrap(err, "failed to write plugin.json") + } + + return nil +} diff --git a/build/setup.mk b/build/setup.mk index 493b06f..b90963a 100644 --- a/build/setup.mk +++ b/build/setup.mk @@ -4,8 +4,13 @@ ifeq ($(GO),) $(error "go is not available: see https://golang.org/doc/install") endif +# Gather build variables to inject into the manifest tool +BUILD_HASH_SHORT = $(shell git rev-parse --short HEAD) +BUILD_TAG_LATEST = $(shell git describe --tags --match 'v*' --abbrev=0 2>/dev/null) +BUILD_TAG_CURRENT = $(shell git tag --points-at HEAD) + # Ensure that the build tools are compiled. Go's caching makes this quick. -$(shell cd build/manifest && $(GO) build -o ../bin/manifest) +$(shell cd build/manifest && $(GO) build -ldflags '-X "main.BuildHashShort=$(BUILD_HASH_SHORT)" -X "main.BuildTagLatest=$(BUILD_TAG_LATEST)" -X "main.BuildTagCurrent=$(BUILD_TAG_CURRENT)"' -o ../bin/manifest) # Ensure that the deployment tools are compiled. Go's caching makes this quick. $(shell cd build/pluginctl && $(GO) build -o ../bin/pluginctl) diff --git a/plugin.go b/plugin.go deleted file mode 100644 index c12d7bd..0000000 --- a/plugin.go +++ /dev/null @@ -1,18 +0,0 @@ -package root - -import ( - _ "embed" // Need to embed manifest file - "encoding/json" - "strings" - - "github.com/mattermost/mattermost/server/public/model" -) - -//go:embed plugin.json -var manifestString string - -var Manifest model.Manifest - -func init() { - _ = json.NewDecoder(strings.NewReader(manifestString)).Decode(&Manifest) -} diff --git a/plugin.json b/plugin.json index 7f3cd0a..a479d11 100644 --- a/plugin.json +++ b/plugin.json @@ -4,9 +4,7 @@ "description": "This plugin serves as a starting point for writing a Mattermost plugin.", "homepage_url": "https://github.com/mattermost/mattermost-plugin-starter-template", "support_url": "https://github.com/mattermost/mattermost-plugin-starter-template/issues", - "release_notes_url": "https://github.com/mattermost/mattermost-plugin-starter-template/releases/tag/v0.1.0", "icon_path": "assets/starter-template-icon.svg", - "version": "0.1.0", "min_server_version": "6.2.1", "server": { "executables": { diff --git a/webapp/src/index.tsx b/webapp/src/index.tsx index b86550d..c3fc933 100644 --- a/webapp/src/index.tsx +++ b/webapp/src/index.tsx @@ -2,7 +2,7 @@ import {Store, Action} from 'redux'; import {GlobalState} from '@mattermost/types/lib/store'; -import {manifest} from '@/manifest'; +import manifest from '@/manifest'; import {PluginRegistry} from '@/types/mattermost-webapp'; diff --git a/webapp/src/manifest.test.tsx b/webapp/src/manifest.test.tsx index 212a765..d71f2d4 100644 --- a/webapp/src/manifest.test.tsx +++ b/webapp/src/manifest.test.tsx @@ -1,13 +1,7 @@ -import {manifest} from './manifest'; +import manifest from './manifest'; test('Plugin manifest, id and version are defined', () => { expect(manifest).toBeDefined(); expect(manifest.id).toBeDefined(); expect(manifest.version).toBeDefined(); }); - -// To ease migration, verify separate export of id and version. -test('Plugin id and version are defined', () => { - expect(manifest.id).toBeDefined(); - expect(manifest.version).toBeDefined(); -}); diff --git a/webapp/src/manifest.ts b/webapp/src/manifest.ts deleted file mode 100644 index 314911b..0000000 --- a/webapp/src/manifest.ts +++ /dev/null @@ -1,3 +0,0 @@ -import manifest from '@/../../plugin.json'; - -export {manifest}; From 8dd6e7c187f8c26f8aeb4865e5b2fb3807f4326a Mon Sep 17 00:00:00 2001 From: Ben Schumacher Date: Tue, 30 Jan 2024 16:49:59 +0100 Subject: [PATCH 03/10] Fetch plugin logs from server (#193) Co-authored-by: Jesse Hallam --- Makefile | 8 ++ build/pluginctl/logs.go | 185 ++++++++++++++++++++++++++++++++ build/pluginctl/logs_test.go | 202 +++++++++++++++++++++++++++++++++++ build/pluginctl/main.go | 4 + go.mod | 2 +- go.sum | 4 + 6 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 build/pluginctl/logs.go create mode 100644 build/pluginctl/logs_test.go diff --git a/Makefile b/Makefile index 5342f19..dff7546 100644 --- a/Makefile +++ b/Makefile @@ -278,6 +278,14 @@ ifneq ($(HAS_WEBAPP),) endif rm -fr build/bin/ +.PHONY: logs +logs: + ./build/bin/pluginctl logs $(PLUGIN_ID) + +.PHONY: logs-watch +logs-watch: + ./build/bin/pluginctl logs-watch $(PLUGIN_ID) + # Help documentation à la https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html help: @cat Makefile build/*.mk | grep -v '\.PHONY' | grep -v '\help:' | grep -B1 -E '^[a-zA-Z0-9_.-]+:.*' | sed -e "s/:.*//" | sed -e "s/^## //" | grep -v '\-\-' | sed '1!G;h;$$!d' | awk 'NR%2{printf "\033[36m%-30s\033[0m",$$0;next;}1' | sort diff --git a/build/pluginctl/logs.go b/build/pluginctl/logs.go new file mode 100644 index 0000000..f20e8bb --- /dev/null +++ b/build/pluginctl/logs.go @@ -0,0 +1,185 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "slices" + "strings" + "time" + + "github.com/mattermost/mattermost/server/public/model" +) + +const ( + logsPerPage = 100 // logsPerPage is the number of log entries to fetch per API call + timeStampFormat = "2006-01-02 15:04:05.000 Z07:00" +) + +// logs fetches the latest 500 log entries from Mattermost, +// and prints only the ones related to the plugin to stdout. +func logs(ctx context.Context, client *model.Client4, pluginID string) error { + err := checkJSONLogsSetting(ctx, client) + if err != nil { + return err + } + + logs, err := fetchLogs(ctx, client, 0, 500, pluginID, time.Unix(0, 0)) + if err != nil { + return fmt.Errorf("failed to fetch log entries: %w", err) + } + + err = printLogEntries(logs) + if err != nil { + return fmt.Errorf("failed to print logs entries: %w", err) + } + + return nil +} + +// watchLogs fetches log entries from Mattermost and print them to stdout. +// It will return without an error when ctx is canceled. +func watchLogs(ctx context.Context, client *model.Client4, pluginID string) error { + err := checkJSONLogsSetting(ctx, client) + if err != nil { + return err + } + + now := time.Now() + var oldestEntry string + + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return nil + case <-ticker.C: + var page int + for { + logs, err := fetchLogs(ctx, client, page, logsPerPage, pluginID, now) + if err != nil { + return fmt.Errorf("failed to fetch log entries: %w", err) + } + + var allNew bool + logs, oldestEntry, allNew = checkOldestEntry(logs, oldestEntry) + + err = printLogEntries(logs) + if err != nil { + return fmt.Errorf("failed to print logs entries: %w", err) + } + + if !allNew { + // No more logs to fetch + break + } + page++ + } + } + } +} + +// checkOldestEntry check a if logs contains new log entries. +// It returns the filtered slice of log entries, the new oldest entry and whether or not all entries were new. +func checkOldestEntry(logs []string, oldest string) ([]string, string, bool) { + if len(logs) == 0 { + return nil, oldest, false + } + + newOldestEntry := logs[(len(logs) - 1)] + + i := slices.Index(logs, oldest) + switch i { + case -1: + // Every log entry is new + return logs, newOldestEntry, true + case len(logs) - 1: + // No new log entries + return nil, oldest, false + default: + // Filter out oldest log entry + return logs[i+1:], newOldestEntry, false + } +} + +// fetchLogs fetches log entries from Mattermost +// and filters them based on pluginID and timestamp. +func fetchLogs(ctx context.Context, client *model.Client4, page, perPage int, pluginID string, since time.Time) ([]string, error) { + logs, _, err := client.GetLogs(ctx, page, perPage) + if err != nil { + return nil, fmt.Errorf("failed to get logs from Mattermost: %w", err) + } + + logs, err = filterLogEntries(logs, pluginID, since) + if err != nil { + return nil, fmt.Errorf("failed to filter log entries: %w", err) + } + + return logs, nil +} + +// filterLogEntries filters a given slice of log entries by pluginID. +// It also filters out any entries which timestamps are older then since. +func filterLogEntries(logs []string, pluginID string, since time.Time) ([]string, error) { + type logEntry struct { + PluginID string `json:"plugin_id"` + Timestamp string `json:"timestamp"` + } + + var ret []string + + for _, e := range logs { + var le logEntry + err := json.Unmarshal([]byte(e), &le) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal log entry into JSON: %w", err) + } + if le.PluginID != pluginID { + continue + } + + let, err := time.Parse(timeStampFormat, le.Timestamp) + if err != nil { + return nil, fmt.Errorf("unknown timestamp format: %w", err) + } + if let.Before(since) { + continue + } + + // Log entries returned by the API have a newline a prefix. + // Remove that to make printing consistent. + e = strings.TrimPrefix(e, "\n") + + ret = append(ret, e) + } + + return ret, nil +} + +// printLogEntries prints a slice of log entries to stdout. +func printLogEntries(entries []string) error { + for _, e := range entries { + _, err := io.WriteString(os.Stdout, e+"\n") + if err != nil { + return fmt.Errorf("failed to write log entry to stdout: %w", err) + } + } + + return nil +} + +func checkJSONLogsSetting(ctx context.Context, client *model.Client4) error { + cfg, _, err := client.GetConfig(ctx) + if err != nil { + return fmt.Errorf("failed to fetch config: %w", err) + } + if cfg.LogSettings.FileJson == nil || !*cfg.LogSettings.FileJson { + return errors.New("JSON output for file logs are disabled. Please enable LogSettings.FileJson via the configration in Mattermost.") //nolint:revive,stylecheck + } + + return nil +} diff --git a/build/pluginctl/logs_test.go b/build/pluginctl/logs_test.go new file mode 100644 index 0000000..687d8c2 --- /dev/null +++ b/build/pluginctl/logs_test.go @@ -0,0 +1,202 @@ +package main + +import ( + "fmt" + "testing" + "time" +) + +func TestCheckOldestEntry(t *testing.T) { + for name, tc := range map[string]struct { + logs []string + oldest string + expectedLogs []string + expectedOldest string + expectedAllNew bool + }{ + "nil logs": { + logs: nil, + oldest: "oldest", + expectedLogs: nil, + expectedOldest: "oldest", + expectedAllNew: false, + }, + "empty logs": { + logs: []string{}, + oldest: "oldest", + expectedLogs: nil, + expectedOldest: "oldest", + expectedAllNew: false, + }, + "no new entries, one old entry": { + logs: []string{"old"}, + oldest: "old", + expectedLogs: []string{}, + expectedOldest: "old", + expectedAllNew: false, + }, + "no new entries, multipile old entries": { + logs: []string{"old1", "old2", "old3"}, + oldest: "old3", + expectedLogs: []string{}, + expectedOldest: "old3", + expectedAllNew: false, + }, + "one new entry, no old entry": { + logs: []string{"new"}, + oldest: "old", + expectedLogs: []string{"new"}, + expectedOldest: "new", + expectedAllNew: true, + }, + "multipile new entries, no old entry": { + logs: []string{"new1", "new2", "new3"}, + oldest: "old", + expectedLogs: []string{"new1", "new2", "new3"}, + expectedOldest: "new3", + expectedAllNew: true, + }, + "one new entry, one old entry": { + logs: []string{"old", "new"}, + oldest: "old", + expectedLogs: []string{"new"}, + expectedOldest: "new", + expectedAllNew: false, + }, + "one new entry, multipile old entries": { + logs: []string{"old1", "old2", "old3", "new"}, + oldest: "old3", + expectedLogs: []string{"new"}, + expectedOldest: "new", + expectedAllNew: false, + }, + "multipile new entries, ultipile old entries": { + logs: []string{"old1", "old2", "old3", "new1", "new2", "new3"}, + oldest: "old3", + expectedLogs: []string{"new1", "new2", "new3"}, + expectedOldest: "new3", + expectedAllNew: false, + }, + } { + t.Run(name, func(t *testing.T) { + logs, oldest, allNew := checkOldestEntry(tc.logs, tc.oldest) + + if allNew != tc.expectedAllNew { + t.Logf("expected allNew: %v, got %v", tc.expectedAllNew, allNew) + t.Fail() + } + if oldest != tc.expectedOldest { + t.Logf("expected oldest: %v, got %v", tc.expectedOldest, oldest) + t.Fail() + } + + compareSlice(t, tc.expectedLogs, logs) + }) + } +} + +func TestFilterLogEntries(t *testing.T) { + now := time.Now() + + for name, tc := range map[string]struct { + logs []string + pluginID string + since time.Time + expectedLogs []string + expectedErr bool + }{ + "nil slice": { + logs: nil, + expectedLogs: nil, + expectedErr: false, + }, + "empty slice": { + logs: []string{}, + expectedLogs: nil, + expectedErr: false, + }, + "no JSON": { + logs: []string{ + `{"foo"`, + }, + expectedLogs: nil, + expectedErr: true, + }, + "unknown time format": { + logs: []string{ + `{"message":"foo", "plugin_id": "some.plugin.id", "timestamp": "2023-12-18 10:58:53"}`, + }, + pluginID: "some.plugin.id", + expectedLogs: nil, + expectedErr: true, + }, + "one matching entry": { + logs: []string{ + `{"message":"foo", "plugin_id": "some.plugin.id", "timestamp": "2023-12-18 10:58:53.091 +01:00"}`, + }, + pluginID: "some.plugin.id", + expectedLogs: []string{ + `{"message":"foo", "plugin_id": "some.plugin.id", "timestamp": "2023-12-18 10:58:53.091 +01:00"}`, + }, + expectedErr: false, + }, + "filter out non plugin entries": { + logs: []string{ + `{"message":"bar1", "timestamp": "2023-12-18 10:58:52.091 +01:00"}`, + `{"message":"foo", "plugin_id": "some.plugin.id", "timestamp": "2023-12-18 10:58:53.091 +01:00"}`, + `{"message":"bar2", "timestamp": "2023-12-18 10:58:54.091 +01:00"}`, + }, + pluginID: "some.plugin.id", + expectedLogs: []string{ + `{"message":"foo", "plugin_id": "some.plugin.id", "timestamp": "2023-12-18 10:58:53.091 +01:00"}`, + }, + expectedErr: false, + }, + "filter out old entries": { + logs: []string{ + fmt.Sprintf(`{"message":"old2", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Add(-2*time.Second).Format(timeStampFormat)), + fmt.Sprintf(`{"message":"old1", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Add(-1*time.Second).Format(timeStampFormat)), + fmt.Sprintf(`{"message":"now", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Format(timeStampFormat)), + fmt.Sprintf(`{"message":"new1", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Add(1*time.Second).Format(timeStampFormat)), + fmt.Sprintf(`{"message":"new2", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Add(2*time.Second).Format(timeStampFormat)), + }, + pluginID: "some.plugin.id", + since: now, + expectedLogs: []string{ + fmt.Sprintf(`{"message":"new1", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Add(1*time.Second).Format(timeStampFormat)), + fmt.Sprintf(`{"message":"new2", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Add(2*time.Second).Format(timeStampFormat)), + }, + expectedErr: false, + }, + } { + t.Run(name, func(t *testing.T) { + logs, err := filterLogEntries(tc.logs, tc.pluginID, tc.since) + if tc.expectedErr { + if err == nil { + t.Logf("expected error, got nil") + t.Fail() + } + } else { + if err != nil { + t.Logf("expected no error, got %v", err) + t.Fail() + } + } + compareSlice(t, tc.expectedLogs, logs) + }) + } +} + +func compareSlice[S ~[]E, E comparable](t *testing.T, expected, got S) { + if len(expected) != len(got) { + t.Logf("expected len: %v, got %v", len(expected), len(got)) + t.FailNow() + } + + for i := 0; i < len(expected); i++ { + if expected[i] != got[i] { + t.Logf("expected [%d]: %v, got %v", i, expected[i], got[i]) + t.Fail() + } + } +} diff --git a/build/pluginctl/main.go b/build/pluginctl/main.go index 54572fc..2f80af5 100644 --- a/build/pluginctl/main.go +++ b/build/pluginctl/main.go @@ -57,6 +57,10 @@ func pluginctl() error { return enablePlugin(ctx, client, os.Args[2]) case "reset": return resetPlugin(ctx, client, os.Args[2]) + case "logs": + return logs(ctx, client, os.Args[2]) + case "logs-watch": + return watchLogs(context.WithoutCancel(ctx), client, os.Args[2]) // Keep watching forever default: return errors.New("invalid second argument") } diff --git a/go.mod b/go.mod index e192a30..3724fd4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/mattermost/mattermost-plugin-starter-template -go 1.19 +go 1.21 require ( github.com/mattermost/mattermost/server/public v0.0.6 diff --git a/go.sum b/go.sum index 4d6d740..1ca95a9 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,7 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -79,11 +80,13 @@ github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= @@ -299,6 +302,7 @@ google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= From 495541e9651cbe35120bdb0ee499972ba9aaea8c Mon Sep 17 00:00:00 2001 From: Ben Schumacher Date: Mon, 12 Feb 2024 11:29:58 +0100 Subject: [PATCH 04/10] Improve error message when plugin bundle is too large (#195) --- go.mod | 39 ++++++++++--------- go.sum | 117 +++++++++++++++++++++------------------------------------ 2 files changed, 61 insertions(+), 95 deletions(-) diff --git a/go.mod b/go.mod index 3724fd4..81a84b2 100644 --- a/go.mod +++ b/go.mod @@ -3,32 +3,31 @@ module github.com/mattermost/mattermost-plugin-starter-template go 1.21 require ( - github.com/mattermost/mattermost/server/public v0.0.6 + github.com/mattermost/mattermost/server/public v0.0.14 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.4 ) require ( - github.com/blang/semver v3.5.1+incompatible // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect - github.com/fatih/color v1.15.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a // indirect - github.com/hashicorp/go-hclog v1.5.0 // indirect - github.com/hashicorp/go-plugin v1.4.10 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/gorilla/websocket v1.5.1 // indirect + github.com/hashicorp/go-hclog v1.6.2 // indirect + github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/lib/pq v1.10.9 // indirect github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect - github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect - github.com/mattermost/logr/v2 v2.0.16 // indirect + github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 // indirect + github.com/mattermost/logr/v2 v2.0.21 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/oklog/run v1.1.0 // indirect github.com/pborman/uuid v1.2.1 // indirect @@ -36,17 +35,17 @@ require ( github.com/philhofer/fwd v1.1.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect - github.com/tinylib/msgp v1.1.8 // indirect - github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/tinylib/msgp v1.1.9 // 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 - golang.org/x/crypto v0.10.0 // indirect - golang.org/x/net v0.11.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 // indirect - google.golang.org/grpc v1.56.1 // indirect + golang.org/x/crypto v0.16.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect + google.golang.org/grpc v1.60.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 1ca95a9..e39a361 100644 --- a/go.sum +++ b/go.sum @@ -10,9 +10,11 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -23,21 +25,17 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a h1:etIrTD8BQqzColk9nKRusM9um5+1q0iOEJLqfBMIK64= github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a/go.mod h1:emQhSYTXqB0xxjLITTw4EaWZ+8IIQYw+kx9GqNUKdLg= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-asn1-ber/asn1-ber v1.3.2-0.20191121212151-29be175fc3a3/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= -github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= +github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -53,7 +51,6 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= @@ -61,26 +58,24 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a h1:i0+Se9S+2zL5CBxJouqn2Ej6UQMwH1c57ZB6DVnqck4= -github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-plugin v1.4.10 h1:xUbmA4jC6Dq163/fWcp8P3JuHilrHHMLNRxzGQJ9hNk= -github.com/hashicorp/go-plugin v1.4.10/go.mod h1:6/1TEzT0eQznvI/gV2CM29DLSkAK/e58mUWKVsPaph0= +github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= +github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= +github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -97,12 +92,12 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm 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= github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34= -github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d h1:/RJ/UV7M5c7L2TQ0KNm4yZxxFvC1nvRz/gY/Daa35aI= -github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ= -github.com/mattermost/logr/v2 v2.0.16 h1:jnePX4cPskC3WDFvUardh/xZfxNdsFXbEERJQ1kUEDE= -github.com/mattermost/logr/v2 v2.0.16/go.mod h1:1dm/YhTpozsqANXxo5Pi5zYLBsal2xY0pX+JZNbzYJY= -github.com/mattermost/mattermost/server/public v0.0.6 h1:FUaJ+P36E3Tt12Umdm8p1h7sZNUeObDk3p3aFTaBkCo= -github.com/mattermost/mattermost/server/public v0.0.6/go.mod h1:Y7Ht1haGGrsuYzX73HhpSe2VnbGLuZj2/tsQslHd2/M= +github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 h1:Y1Tu/swM31pVwwb2BTCsOdamENjjWCI6qmfHLbk6OZI= +github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956/go.mod h1:SRl30Lb7/QoYyohYeVBuqYvvmXSZJxZgiV3Zf6VbxjI= +github.com/mattermost/logr/v2 v2.0.21 h1:CMHsP+nrbRlEC4g7BwOk1GAnMtHkniFhlSQPXy52be4= +github.com/mattermost/logr/v2 v2.0.21/go.mod h1:kZkB/zqKL9e+RY5gB3vGpsyenC+TpuiOenjMkvJJbzc= +github.com/mattermost/mattermost/server/public v0.0.14 h1:YPV7zzr2GzeCwvtpsPZagFInnMGGPqgbIRyWV9yEuHg= +github.com/mattermost/mattermost/server/public v0.0.14/go.mod h1:Bk+atJcELCIk9Yeq5FoqTr+gra9704+X4amrlwlTgSc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -110,8 +105,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= @@ -122,7 +117,6 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= @@ -170,43 +164,35 @@ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:Udh github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= -github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU= +github.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k= 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.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= -github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/wiggin77/merror v1.0.5 h1:P+lzicsn4vPMycAf2mFf7Zk6G9eco5N+jB1qJ2XW3ME= github.com/wiggin77/merror v1.0.5/go.mod h1:H2ETSu7/bPE0Ymf4bEwdUoo73OOEkdClnoRisfw0Nm0= github.com/wiggin77/srslog v1.0.1 h1:gA2XjSMy3DrRdX9UqLuDtuVAAshb8bE1NhX1YK0Qe+8= github.com/wiggin77/srslog v1.0.1/go.mod h1:fehkyYDq1QfuYn60TDPu9YdY2bB85VUW2mvN1WynEls= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= -go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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= @@ -215,13 +201,9 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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= @@ -231,9 +213,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -241,29 +220,21 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= 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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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= @@ -271,10 +242,6 @@ 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.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= @@ -288,14 +255,14 @@ 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-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= 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= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= -google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.60.0 h1:6FQAR0kM31P6MRdeluor2w2gPaS4SVNrD/DNTxrQ15k= +google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= From cb94a72744462b2618eb4045a1377b9c22855929 Mon Sep 17 00:00:00 2001 From: Ben Schumacher Date: Wed, 20 Mar 2024 12:45:34 +0100 Subject: [PATCH 05/10] Fix flaky TestFilterLogEntries (#197) --- build/pluginctl/logs_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build/pluginctl/logs_test.go b/build/pluginctl/logs_test.go index 687d8c2..2917a27 100644 --- a/build/pluginctl/logs_test.go +++ b/build/pluginctl/logs_test.go @@ -154,17 +154,17 @@ func TestFilterLogEntries(t *testing.T) { }, "filter out old entries": { logs: []string{ - fmt.Sprintf(`{"message":"old2", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Add(-2*time.Second).Format(timeStampFormat)), - fmt.Sprintf(`{"message":"old1", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Add(-1*time.Second).Format(timeStampFormat)), - fmt.Sprintf(`{"message":"now", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Format(timeStampFormat)), - fmt.Sprintf(`{"message":"new1", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Add(1*time.Second).Format(timeStampFormat)), - fmt.Sprintf(`{"message":"new2", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Add(2*time.Second).Format(timeStampFormat)), + fmt.Sprintf(`{"message":"old2", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Add(-2*time.Second).Format(timeStampFormat)), + fmt.Sprintf(`{"message":"old1", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Add(-1*time.Second).Format(timeStampFormat)), + fmt.Sprintf(`{"message":"now", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Format(timeStampFormat)), + fmt.Sprintf(`{"message":"new1", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Add(1*time.Second).Format(timeStampFormat)), + fmt.Sprintf(`{"message":"new2", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Add(2*time.Second).Format(timeStampFormat)), }, pluginID: "some.plugin.id", since: now, expectedLogs: []string{ - fmt.Sprintf(`{"message":"new1", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Add(1*time.Second).Format(timeStampFormat)), - fmt.Sprintf(`{"message":"new2", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, time.Now().Add(2*time.Second).Format(timeStampFormat)), + fmt.Sprintf(`{"message":"new1", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Add(1*time.Second).Format(timeStampFormat)), + fmt.Sprintf(`{"message":"new2", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Add(2*time.Second).Format(timeStampFormat)), }, expectedErr: false, }, From 4e15acf566d1d74c91d432b11e1a72178665d3a8 Mon Sep 17 00:00:00 2001 From: Christopher Poile Date: Mon, 22 Apr 2024 11:03:18 -0400 Subject: [PATCH 06/10] [MM-57855] Add manifest validation step (#198) --- Makefile | 7 ++++++- build/manifest/main.go | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index dff7546..18e745a 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,11 @@ endif .PHONY: all all: check-style test dist +## Ensures the plugin manifest is valid +.PHONY: manifest-check +manifest-check: + ./build/bin/manifest check + ## Propagates plugin manifest information into the server/ and webapp/ folders. .PHONY: apply apply: @@ -56,7 +61,7 @@ install-go-tools: ## Runs eslint and golangci-lint .PHONY: check-style -check-style: apply webapp/node_modules install-go-tools +check-style: manifest-check apply webapp/node_modules install-go-tools @echo Checking for style guide compliance ifneq ($(HAS_WEBAPP),) diff --git a/build/manifest/main.go b/build/manifest/main.go index c57fe10..890414a 100644 --- a/build/manifest/main.go +++ b/build/manifest/main.go @@ -86,6 +86,11 @@ func main() { panic("failed to write manifest to dist directory: " + err.Error()) } + case "check": + if err := manifest.IsValid(); err != nil { + panic("failed to check manifest: " + err.Error()) + } + default: panic("unrecognized command: " + cmd) } From bd0e6f287b44b37dc4a7c984a308fae120f7d3fe Mon Sep 17 00:00:00 2001 From: Akis Maziotis Date: Thu, 6 Jun 2024 11:27:19 +0300 Subject: [PATCH 07/10] [feat] Implementing new release process (#200) Streamlining the release process for mattermost-plugins by integrating release and signing pipelines under the delivery platform. This update automates signing and releasing by simply pushing a semver tag(e.g: v0.0.1) or by using the newly introduced Makefile targets. ``` make patch make minor make major ``` For Release Candidades(RC): ``` make patch-rc make minor-rc make major-rc ``` Signed-off-by: Akis Maziotis --- .github/workflows/cd.yml | 18 -------- Makefile | 94 ++++++++++++++++++++++++++++++++++++++++ README.md | 40 +++++++++++++++++ 3 files changed, 134 insertions(+), 18 deletions(-) delete mode 100644 .github/workflows/cd.yml diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml deleted file mode 100644 index c929fd9..0000000 --- a/.github/workflows/cd.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: cd -on: - workflow_run: - workflows: ["ci"] - branches-ignore: ["*"] - types: - - completed - push: - tags: - - "v*" - -permissions: - contents: read - -jobs: - plugin-cd: - uses: mattermost/actions-workflows/.github/workflows/plugin-cd.yml@main - secrets: inherit diff --git a/Makefile b/Makefile index 18e745a..f22c81d 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,100 @@ else GO_BUILD_GCFLAGS = endif + +# ==================================================================================== +# Used for semver bumping +PROTECTED_BRANCH := master +APP_NAME := $(shell basename -s .git `git config --get remote.origin.url`) +CURRENT_VERSION := $(shell git describe --abbrev=0 --tags) +VERSION_PARTS := $(subst ., ,$(subst v,,$(subst -rc, ,$(CURRENT_VERSION)))) +MAJOR := $(word 1,$(VERSION_PARTS)) +MINOR := $(word 2,$(VERSION_PARTS)) +PATCH := $(word 3,$(VERSION_PARTS)) +RC := $(shell echo $(CURRENT_VERSION) | grep -oE 'rc[0-9]+' | sed 's/rc//') +# Check if current branch is protected +define check_protected_branch + @current_branch=$$(git rev-parse --abbrev-ref HEAD); \ + if ! echo "$(PROTECTED_BRANCH)" | grep -wq "$$current_branch"; then \ + echo "Error: Tagging is only allowed from $(PROTECTED_BRANCH) branch. You are on $$current_branch branch."; \ + exit 1; \ + fi +endef +# Check if there are pending pulls +define check_pending_pulls + @git fetch; \ + current_branch=$$(git rev-parse --abbrev-ref HEAD); \ + if [ "$$(git rev-parse HEAD)" != "$$(git rev-parse origin/$$current_branch)" ]; then \ + echo "Error: Your branch is not up to date with upstream. Please pull the latest changes before performing a release"; \ + exit 1; \ + fi +endef +# ==================================================================================== + +.PHONY: patch minor major patch-rc minor-rc major-rc + +patch: ## to bump patch version (semver) + $(call check_protected_branch) + $(call check_pending_pulls) + @$(eval PATCH := $(shell echo $$(($(PATCH)+1)))) + @echo Bumping $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH) + git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH)" + git push origin v$(MAJOR).$(MINOR).$(PATCH) + @echo Bumped $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH) + +minor: ## to bump minor version (semver) + $(call check_protected_branch) + $(call check_pending_pulls) + @$(eval MINOR := $(shell echo $$(($(MINOR)+1)))) + @$(eval PATCH := 0) + @echo Bumping $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH) + git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH)" + git push origin v$(MAJOR).$(MINOR).$(PATCH) + @echo Bumped $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH) + +major: ## to bump major version (semver) + $(call check_protected_branch) + $(call check_pending_pulls) + $(eval MAJOR := $(shell echo $$(($(MAJOR)+1)))) + $(eval MINOR := 0) + $(eval PATCH := 0) + @echo Bumping $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH) + git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH)" + git push origin v$(MAJOR).$(MINOR).$(PATCH) + @echo Bumped $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH) + +patch-rc: ## to bump patch release candidate version (semver) + $(call check_protected_branch) + $(call check_pending_pulls) + @$(eval RC := $(shell echo $$(($(RC)+1)))) + @echo Bumping $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)" + git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + @echo Bumped $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + +minor-rc: ## to bump minor release candidate version (semver) + $(call check_protected_branch) + $(call check_pending_pulls) + @$(eval MINOR := $(shell echo $$(($(MINOR)+1)))) + @$(eval PATCH := 0) + @$(eval RC := 1) + @echo Bumping $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)" + git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + @echo Bumped $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + +major-rc: ## to bump major release candidate version (semver) + $(call check_protected_branch) + $(call check_pending_pulls) + @$(eval MAJOR := $(shell echo $$(($(MAJOR)+1)))) + @$(eval MINOR := 0) + @$(eval PATCH := 0) + @$(eval RC := 1) + @echo Bumping $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)" + git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + @echo Bumped $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) + ## Checks the code style, tests, builds and bundles the plugin. .PHONY: all all: check-style test dist diff --git a/README.md b/README.md index a1ac099..f83aada 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,46 @@ The version of a plugin is determined at compile time, automatically populating To disable this behaviour, manually populate and maintain the `version` field. +## How to Release + +To trigger a release, follow these steps: + +1. **For Patch Release:** Run the following command: + ``` + make patch + ``` + This will release a patch change. + +2. **For Minor Release:** Run the following command: + ``` + make minor + ``` + This will release a minor change. + +3. **For Major Release:** Run the following command: + ``` + make major + ``` + This will release a major change. + +4. **For Patch Release Candidate (RC):** Run the following command: + ``` + make patch-rc + ``` + This will release a patch release candidate. + +5. **For Minor Release Candidate (RC):** Run the following command: + ``` + make minor-rc + ``` + This will release a minor release candidate. + +6. **For Major Release Candidate (RC):** Run the following command: + ``` + make major-rc + ``` + This will release a major release candidate. + ## Q&A ### How do I make a server-only or web app-only plugin? From 9805832b2f774602bd460d05e6b7d633d00becef Mon Sep 17 00:00:00 2001 From: Akis Maziotis Date: Fri, 7 Jun 2024 13:44:29 +0300 Subject: [PATCH 08/10] [chore] Makefile - Allow releasing also from release.* branches (#201) Rationale: https://hub.mattermost.com/private-core/pl/yim6z9us9fdtfrjkdf8i16tczw --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f22c81d..f7548ae 100644 --- a/Makefile +++ b/Makefile @@ -53,8 +53,8 @@ RC := $(shell echo $(CURRENT_VERSION) | grep -oE 'rc[0-9]+' | sed 's/rc//') # Check if current branch is protected define check_protected_branch @current_branch=$$(git rev-parse --abbrev-ref HEAD); \ - if ! echo "$(PROTECTED_BRANCH)" | grep -wq "$$current_branch"; then \ - echo "Error: Tagging is only allowed from $(PROTECTED_BRANCH) branch. You are on $$current_branch branch."; \ + if ! echo "$(PROTECTED_BRANCH)" | grep -wq "$$current_branch" && ! echo "$$current_branch" | grep -q "^release"; then \ + echo "Error: Tagging is only allowed from $(PROTECTED_BRANCH) or release branches. You are on $$current_branch branch."; \ exit 1; \ fi endef From 09c4f13f2c850bf7ccbbaa71407f0835d23cb26d Mon Sep 17 00:00:00 2001 From: Akis Maziotis Date: Wed, 26 Jun 2024 10:55:54 +0300 Subject: [PATCH 09/10] [feat] makefile adding an aprooval step for version bump targets (#202) Ticket: https://mattermost.atlassian.net/browse/CLD-7957 --- Makefile | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Makefile b/Makefile index f7548ae..0eed2fe 100644 --- a/Makefile +++ b/Makefile @@ -67,6 +67,14 @@ define check_pending_pulls exit 1; \ fi endef +# Prompt for approval +define prompt_approval + @read -p "About to bump $(APP_NAME) to version $(1), approve? (y/n) " userinput; \ + if [ "$$userinput" != "y" ]; then \ + echo "Bump aborted."; \ + exit 1; \ + fi +endef # ==================================================================================== .PHONY: patch minor major patch-rc minor-rc major-rc @@ -75,6 +83,7 @@ patch: ## to bump patch version (semver) $(call check_protected_branch) $(call check_pending_pulls) @$(eval PATCH := $(shell echo $$(($(PATCH)+1)))) + $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)) @echo Bumping $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH) git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH)" git push origin v$(MAJOR).$(MINOR).$(PATCH) @@ -85,6 +94,7 @@ minor: ## to bump minor version (semver) $(call check_pending_pulls) @$(eval MINOR := $(shell echo $$(($(MINOR)+1)))) @$(eval PATCH := 0) + $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)) @echo Bumping $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH) git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH)" git push origin v$(MAJOR).$(MINOR).$(PATCH) @@ -96,6 +106,7 @@ major: ## to bump major version (semver) $(eval MAJOR := $(shell echo $$(($(MAJOR)+1)))) $(eval MINOR := 0) $(eval PATCH := 0) + $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)) @echo Bumping $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH) git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH)" git push origin v$(MAJOR).$(MINOR).$(PATCH) @@ -105,6 +116,7 @@ patch-rc: ## to bump patch release candidate version (semver) $(call check_protected_branch) $(call check_pending_pulls) @$(eval RC := $(shell echo $$(($(RC)+1)))) + $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)-rc$(RC)) @echo Bumping $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)" git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) @@ -116,6 +128,7 @@ minor-rc: ## to bump minor release candidate version (semver) @$(eval MINOR := $(shell echo $$(($(MINOR)+1)))) @$(eval PATCH := 0) @$(eval RC := 1) + $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)-rc$(RC)) @echo Bumping $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)" git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) @@ -128,6 +141,7 @@ major-rc: ## to bump major release candidate version (semver) @$(eval MINOR := 0) @$(eval PATCH := 0) @$(eval RC := 1) + $(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)-rc$(RC)) @echo Bumping $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC) git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)" git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) From 2d962d18d234dd5db309333c4f8a8ed048170709 Mon Sep 17 00:00:00 2001 From: Felipe Martin Date: Thu, 8 Aug 2024 15:52:29 +0200 Subject: [PATCH 10/10] added command with interactive dialog --- .golangci.yml | 3 +- Makefile | 2 +- README.md | 76 +-------------- go.mod | 11 ++- go.sum | 24 ++++- plugin.json | 10 +- server/api.go | 93 +++++++++++++++++++ server/commands.go | 66 +++++++++++++ server/permissions.go | 37 ++++++++ server/plugin.go | 37 +++++++- server/sqlstore/file.go | 53 +++++++++++ server/sqlstore/post.go | 51 ++++++++++ server/sqlstore/store.go | 79 ++++++++++++++++ webapp/src/actions.tsx | 45 +++++++++ webapp/src/index.tsx | 18 +++- webapp/src/types/mattermost-webapp/index.d.ts | 4 + 16 files changed, 520 insertions(+), 89 deletions(-) create mode 100644 server/api.go create mode 100644 server/commands.go create mode 100644 server/permissions.go create mode 100644 server/sqlstore/file.go create mode 100644 server/sqlstore/post.go create mode 100644 server/sqlstore/store.go create mode 100644 webapp/src/actions.tsx diff --git a/.golangci.yml b/.golangci.yml index 5987c1c..7c3e6f4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -6,9 +6,8 @@ linters-settings: gofmt: simplify: true goimports: - local-prefixes: github.com/mattermost/mattermost-starter-template + local-prefixes: github.com/mattermost/mattermost-plugin-attachments-remover govet: - check-shadowing: true enable-all: true disable: - fieldalignment diff --git a/Makefile b/Makefile index 0eed2fe..05c50bd 100644 --- a/Makefile +++ b/Makefile @@ -164,7 +164,7 @@ apply: ## Install go tools install-go-tools: @echo Installing go tools - $(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.1 + $(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.1 $(GO) install gotest.tools/gotestsum@v1.7.0 ## Runs eslint and golangci-lint diff --git a/README.md b/README.md index f83aada..0f6b68e 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,8 @@ -# Plugin Starter Template [![CircleCI branch](https://img.shields.io/circleci/project/github/mattermost/mattermost-plugin-starter-template/master.svg)](https://circleci.com/gh/mattermost/mattermost-plugin-starter-template) +# Plugin Starter Template [![CircleCI branch](https://img.shields.io/circleci/project/github/mattermost/mattermost-plugin-attachments-remover/master.svg)](https://circleci.com/gh/mattermost/mattermost-plugin-attachments-remover) -This plugin serves as a starting point for writing a Mattermost plugin. Feel free to base your own plugin off this repository. +## Features -To learn more about plugins, see [our plugin documentation](https://developers.mattermost.com/extend/plugins/). - -This template requires node v16 and npm v8. You can download and install nvm to manage your node versions by following the instructions [here](https://github.com/nvm-sh/nvm). Once you've setup the project simply run `nvm i` within the root folder to use the suggested version of node. - -## Getting Started -Use GitHub's template feature to make a copy of this repository by clicking the "Use this template" button. - -Alternatively shallow clone the repository matching your plugin name: -``` -git clone --depth 1 https://github.com/mattermost/mattermost-plugin-starter-template com.example.my-plugin -``` - -Note that this project uses [Go modules](https://github.com/golang/go/wiki/Modules). Be sure to locate the project outside of `$GOPATH`. - -Edit the following files: -1. `plugin.json` with your `id`, `name`, and `description`: -```json -{ - "id": "com.example.my-plugin", - "name": "My Plugin", - "description": "A plugin to enhance Mattermost." -} -``` - -2. `go.mod` with your Go module path, following the `//` convention: -``` -module github.com/example/my-plugin -``` - -3. `.golangci.yml` with your Go module path: -```yml -linters-settings: - # [...] - goimports: - local-prefixes: github.com/example/my-plugin -``` - -Build your plugin: -``` -make -``` - -This will produce a single plugin file (with support for multiple architectures) for upload to your Mattermost server: - -``` -dist/com.example.my-plugin.tar.gz -``` +- Allows users and sysadmins to delete attachments from posts via a context menu option ## Development @@ -161,29 +115,5 @@ To trigger a release, follow these steps: ## Q&A -### How do I make a server-only or web app-only plugin? - -Simply delete the `server` or `webapp` folders and remove the corresponding sections from `plugin.json`. The build scripts will skip the missing portions automatically. - -### How do I include assets in the plugin bundle? - -Place them into the `assets` directory. To use an asset at runtime, build the path to your asset and open as a regular file: - -```go -bundlePath, err := p.API.GetBundlePath() -if err != nil { - return errors.Wrap(err, "failed to get bundle path") -} - -profileImage, err := ioutil.ReadFile(filepath.Join(bundlePath, "assets", "profile_image.png")) -if err != nil { - return errors.Wrap(err, "failed to read profile image") -} - -if appErr := p.API.SetProfileImage(userID, profileImage); appErr != nil { - return errors.Wrap(err, "failed to set profile image") -} -``` - ### How do I build the plugin with unminified JavaScript? Setting the `MM_DEBUG` environment variable will invoke the debug builds. The simplist way to do this is to simply include this variable in your calls to `make` (e.g. `make dist MM_DEBUG=1`). diff --git a/go.mod b/go.mod index 81a84b2..16ed41e 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,33 @@ -module github.com/mattermost/mattermost-plugin-starter-template +module github.com/mattermost/mattermost-plugin-attachments-remover go 1.21 require ( + github.com/Masterminds/squirrel v1.5.4 + github.com/gorilla/mux v1.8.1 + github.com/jmoiron/sqlx v1.4.0 github.com/mattermost/mattermost/server/public v0.0.14 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.4 ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect github.com/fatih/color v1.16.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect - github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.5.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect github.com/hashicorp/go-hclog v1.6.2 // indirect github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 // indirect @@ -35,6 +41,7 @@ require ( github.com/philhofer/fwd v1.1.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/tinylib/msgp v1.1.9 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index e39a361..95875da 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,12 @@ dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 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/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -36,8 +40,8 @@ github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= @@ -63,6 +67,8 @@ github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -76,6 +82,8 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -86,6 +94,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= @@ -107,6 +119,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= @@ -160,11 +174,16 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1l github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -225,6 +244,7 @@ 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.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= diff --git a/plugin.json b/plugin.json index a479d11..7d17ee9 100644 --- a/plugin.json +++ b/plugin.json @@ -1,9 +1,9 @@ { - "id": "com.mattermost.plugin-starter-template", - "name": "Plugin Starter Template", - "description": "This plugin serves as a starting point for writing a Mattermost plugin.", - "homepage_url": "https://github.com/mattermost/mattermost-plugin-starter-template", - "support_url": "https://github.com/mattermost/mattermost-plugin-starter-template/issues", + "id": "com.mattermost.attachments-remover", + "name": "Attachments manager", + "description": "This plugin allows you more ways to manage attachments in Mattermost.", + "homepage_url": "https://github.com/mattermost/mattermost-plugin-attachments-remover", + "support_url": "https://github.com/mattermost/mattermost-plugin-attachments-remover/issues", "icon_path": "assets/starter-template-icon.svg", "min_server_version": "6.2.1", "server": { diff --git a/server/api.go b/server/api.go new file mode 100644 index 0000000..ac15989 --- /dev/null +++ b/server/api.go @@ -0,0 +1,93 @@ +package main + +import ( + "net/http" + + "github.com/gorilla/mux" +) + +type API struct { + plugin *Plugin + router *mux.Router +} + +func (a *API) ServeHTTP(w http.ResponseWriter, r *http.Request) { + a.router.ServeHTTP(w, r) +} + +func (a *API) handlerRemoveAttachments(w http.ResponseWriter, r *http.Request) { + // Get post_id from the query parameters + err := r.ParseForm() + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + postID := r.FormValue("post_id") + + // Check if the user is the post author or a system admin + userID := r.Header.Get("Mattermost-User-ID") + post, appErr := a.plugin.API.GetPost(postID) + if appErr != nil { + http.Error(w, appErr.Error(), appErr.StatusCode) + return + } + + if errReason := a.plugin.userHasRemovePermissionsToPost(userID, post.ChannelId, postID); errReason != "" { + http.Error(w, errReason, http.StatusForbidden) + return + } + + // Soft-delete the attachments + for _, fileID := range post.FileIds { + if err := a.plugin.SQLStore.DetatchAttachmentFromChannel(fileID); err != nil { + a.plugin.API.LogError("error detaching attachment from channel", "err", err) + http.Error(w, "Internal server error, check logs", http.StatusInternalServerError) + return + } + } + + originalFileIDs := post.FileIds + + // Edit the post to remove the attachments + post.FileIds = []string{} + newPost, appErr := a.plugin.API.UpdatePost(post) + if appErr != nil { + http.Error(w, appErr.Error(), appErr.StatusCode) + return + } + + // Attach the original file IDs to the original post so history is not lost + if err := a.plugin.SQLStore.AttachFileIDsToPost(newPost.OriginalId, originalFileIDs); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) +} + +// setupAPI sets up the API for the plugin. +func setupAPI(plugin *Plugin) (*API, error) { + api := &API{ + plugin: plugin, + router: mux.NewRouter(), + } + + group := api.router.PathPrefix("/api/v1").Subrouter() + group.Use(authorizationRequiredMiddleware) + group.HandleFunc("/remove_attachments", api.handlerRemoveAttachments) + + return api, nil +} + +func authorizationRequiredMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + userID := r.Header.Get("Mattermost-User-ID") + if userID != "" { + next.ServeHTTP(w, r) + return + } + + http.Error(w, "Not authorized", http.StatusUnauthorized) + }) +} diff --git a/server/commands.go b/server/commands.go new file mode 100644 index 0000000..5f1fc67 --- /dev/null +++ b/server/commands.go @@ -0,0 +1,66 @@ +package main + +import ( + "strings" + + "github.com/mattermost/mattermost/server/public/model" + "github.com/mattermost/mattermost/server/public/plugin" +) + +func (p *Plugin) getCommand() *model.Command { + return &model.Command{ + Trigger: "removeattachments", + DisplayName: "Remove Attachments", + Description: "Remove all attachments from a post", + AutoComplete: false, // Hide from autocomplete + } +} + +func (p *Plugin) createCommandResponse(message string) *model.CommandResponse { + return &model.CommandResponse{ + Text: message, + } +} + +func (p *Plugin) createErrorCommandResponse(errorMessage string) *model.CommandResponse { + return &model.CommandResponse{ + Text: "Can't remove attachments: " + errorMessage, + } +} + +func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { + commandSplit := strings.Split(args.Command, " ") + + // Do not provide command output since it's going to be triggered from the frontend + if len(commandSplit) != 2 { + return p.createErrorCommandResponse("Invalid number of arguments, use `/removeattachments [postID]`."), nil + } + + postID := commandSplit[1] + + // Check if the post ID is a valid ID + if !model.IsValidId(postID) { + return p.createErrorCommandResponse("Invalid post ID"), nil + } + + // Check if the user has permissions to remove attachments from the post + if errReason := p.userHasRemovePermissionsToPost(args.UserId, args.ChannelId, postID); errReason != "" { + return p.createErrorCommandResponse(errReason), nil + } + + // Create an interactive dialog to confirm the action + if err := p.API.OpenInteractiveDialog(model.OpenDialogRequest{ + TriggerId: args.TriggerId, + URL: "/plugins/" + manifest.Id + "/api/v1/remove_attachments?post_id=" + postID, + Dialog: model.Dialog{ + Title: "Remove Attachments", + IntroductionText: "Are you sure you want to remove all attachments from this post?", + SubmitLabel: "Remove", + }, + }); err != nil { + return p.createCommandResponse(err.Error()), nil + } + + // Return nothing, let the dialog/api handle the response + return &model.CommandResponse{}, nil +} diff --git a/server/permissions.go b/server/permissions.go new file mode 100644 index 0000000..7bbef50 --- /dev/null +++ b/server/permissions.go @@ -0,0 +1,37 @@ +package main + +import "github.com/mattermost/mattermost/server/public/model" + +// userHasRemovePermissionsToPost checks if the user has permissions to remove attachments from a post +// based on the post ID, the user ID, and the channel ID. +// Returns an error message if the user does not have permissions, or an empty string if the user has permissions. +func (p *Plugin) userHasRemovePermissionsToPost(userID, channelID, postID string) string { + // Check if the post exists + post, appErr := p.API.GetPost(postID) + if appErr != nil { + return "Post does not exist" + } + + // Check if the post has attachments + if len(post.FileIds) == 0 { + return "Post has no attachments" + } + + // Check if the user is the post author or has permissions to edit others posts + user, appErr := p.API.GetUser(userID) + if appErr != nil { + return "Internal error, check with your system administrator for assistance" + } + + if post.UserId != user.Id && !p.API.HasPermissionToChannel(userID, channelID, model.PermissionEditOthersPosts) { + return "Not authorized" + } + + // Check if the post is editable at this point in time + config := p.API.GetConfig() + if config.ServiceSettings.PostEditTimeLimit != nil && *config.ServiceSettings.PostEditTimeLimit > 0 && model.GetMillis() > post.CreateAt+int64(*config.ServiceSettings.PostEditTimeLimit*1000) { + return "Post is too old to edit" + } + + return "" +} diff --git a/server/plugin.go b/server/plugin.go index a5753a6..2136e44 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -6,6 +6,9 @@ import ( "sync" "github.com/mattermost/mattermost/server/public/plugin" + "github.com/mattermost/mattermost/server/public/pluginapi" + + "github.com/mattermost/mattermost-plugin-attachments-remover/server/sqlstore" ) // Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes. @@ -18,11 +21,39 @@ type Plugin struct { // configuration is the active plugin configuration. Consult getConfiguration and // setConfiguration for usage. configuration *configuration + + // api is the instance of the API struct that handles the plugin's API endpoints. + api *API + + // sqlStore is the instance of the SQLStore struct that handles the plugin's database interactions. + SQLStore *sqlstore.SQLStore } -// ServeHTTP demonstrates a plugin that handles HTTP requests by greeting the world. func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "Hello, world!") + p.api.ServeHTTP(w, r) } -// See https://developers.mattermost.com/extend/plugins/server/reference/ +func (p *Plugin) OnActivate() error { + var err error + p.api, err = setupAPI(p) + if err != nil { + return fmt.Errorf("error setting up the API: %w", err) + } + + // Setup direct SQL Store access via the plugin api + papi := pluginapi.NewClient(p.API, p.Driver) + SQLStore, err := sqlstore.New(papi.Store, &papi.Log) + if err != nil { + p.API.LogError("cannot create SQLStore", "err", err) + return err + } + p.SQLStore = SQLStore + + // Register command + if err := p.API.RegisterCommand(p.getCommand()); err != nil { + p.API.LogError("failed to register command", "err", err) + return fmt.Errorf("failed to register command: %w", err) + } + + return nil +} diff --git a/server/sqlstore/file.go b/server/sqlstore/file.go new file mode 100644 index 0000000..5a7334f --- /dev/null +++ b/server/sqlstore/file.go @@ -0,0 +1,53 @@ +package sqlstore + +import ( + "context" + "fmt" + "time" + + sq "github.com/Masterminds/squirrel" + + "github.com/mattermost/mattermost/server/public/model" +) + +const ( + fileInfoTable = "FileInfo" +) + +func (s *SQLStore) DetatchAttachmentFromChannel(fileID string) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + update := map[string]interface{}{ + "ChannelId": "", + "PostId": "", + "DeleteAt": model.GetMillis(), + } + + query := s.masterBuilder. + Update(fileInfoTable). + SetMap(update). + Where(sq.Eq{"Id": fileID}) + + tx, err := s.master.Begin() + if err != nil { + return fmt.Errorf("error starting transaction: %w", err) + } + + q, args, err := query.ToSql() + if err != nil { + return fmt.Errorf("error creating query: %w", err) + } + + _, err = tx.ExecContext(ctx, q, args...) + if err != nil { + s.logger.Error("error detaching attachment from channel", "fileId", fileID, "err", err) + return tx.Rollback() + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("error committing transaction: %w", err) + } + + return nil +} diff --git a/server/sqlstore/post.go b/server/sqlstore/post.go new file mode 100644 index 0000000..c217aa0 --- /dev/null +++ b/server/sqlstore/post.go @@ -0,0 +1,51 @@ +package sqlstore + +import ( + "context" + "encoding/json" + "fmt" + "time" + + sq "github.com/Masterminds/squirrel" +) + +const ( + postsTable = "Posts" +) + +func (s *SQLStore) AttachFileIDsToPost(postID string, fileIDs []string) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + fileIDsJSON, err := json.Marshal(fileIDs) + if err != nil { + return fmt.Errorf("error marshaling fileIds: %w", err) + } + + query := s.masterBuilder. + Update(postsTable). + Set("FileIds", fileIDsJSON). + Where(sq.Eq{"Id": postID}) + + tx, err := s.master.Begin() + if err != nil { + return fmt.Errorf("error starting transaction: %w", err) + } + + q, args, err := query.ToSql() + if err != nil { + return fmt.Errorf("error creating query: %w", err) + } + + _, err = tx.ExecContext(ctx, q, args...) + if err != nil { + s.logger.Error("error attaching file to post", "postId", postID, "err", err) + return tx.Rollback() + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("error committing transaction: %w", err) + } + + return nil +} diff --git a/server/sqlstore/store.go b/server/sqlstore/store.go new file mode 100644 index 0000000..4a26b0f --- /dev/null +++ b/server/sqlstore/store.go @@ -0,0 +1,79 @@ +package sqlstore + +import ( + "database/sql" + + sq "github.com/Masterminds/squirrel" + "github.com/jmoiron/sqlx" + "github.com/mattermost/mattermost/server/public/model" +) + +type Source interface { + GetMasterDB() (*sql.DB, error) + GetReplicaDB() (*sql.DB, error) + DriverName() string +} + +type Logger interface { + Error(message string, keyValuePairs ...interface{}) + Warn(message string, keyValuePairs ...interface{}) + Info(message string, keyValuePairs ...interface{}) + Debug(message string, keyValuePairs ...interface{}) +} + +type SQLStore struct { + src Source + master *sqlx.DB + replica *sqlx.DB + masterBuilder sq.StatementBuilderType + replicaBuilder sq.StatementBuilderType + logger Logger +} + +// New constructs a new instance of SQLStore. +func New(src Source, logger Logger) (*SQLStore, error) { + var master, replica *sqlx.DB + + masterDB, err := src.GetMasterDB() + if err != nil { + return nil, err + } + master = sqlx.NewDb(masterDB, src.DriverName()) + + replicaDB, err := src.GetReplicaDB() + if err != nil { + return nil, err + } + replica = sqlx.NewDb(replicaDB, src.DriverName()) + + masterBuilder := sq.StatementBuilder.PlaceholderFormat(sq.Question) + if src.DriverName() == model.DatabaseDriverPostgres { + masterBuilder = masterBuilder.PlaceholderFormat(sq.Dollar) + } + + if src.DriverName() == model.DatabaseDriverMysql { + master.MapperFunc(func(s string) string { return s }) + } + + masterBuilder = masterBuilder.RunWith(master) + + replicaBuilder := sq.StatementBuilder.PlaceholderFormat(sq.Question) + if src.DriverName() == model.DatabaseDriverPostgres { + replicaBuilder = replicaBuilder.PlaceholderFormat(sq.Dollar) + } + + if src.DriverName() == model.DatabaseDriverMysql { + replica.MapperFunc(func(s string) string { return s }) + } + + replicaBuilder = replicaBuilder.RunWith(replica) + + return &SQLStore{ + src, + master, + replica, + masterBuilder, + replicaBuilder, + logger, + }, nil +} diff --git a/webapp/src/actions.tsx b/webapp/src/actions.tsx new file mode 100644 index 0000000..635877e --- /dev/null +++ b/webapp/src/actions.tsx @@ -0,0 +1,45 @@ +import {AnyAction, Dispatch} from 'redux'; +import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels'; +import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; +import {GetStateFunc} from 'mattermost-redux/types/actions'; +import {Client4} from 'mattermost-redux/client'; +import {IntegrationTypes} from 'mattermost-redux/action_types'; + +export function setTriggerId(triggerId: string) { + return { + type: IntegrationTypes.RECEIVED_DIALOG_TRIGGER_ID, + data: triggerId, + }; +} + +export function triggerRemoveAttachmentsCommand(postID: string) { + return (dispatch: Dispatch, getState: GetStateFunc) => { + const command = '/removeattachments ' + postID; + clientExecuteCommand(dispatch, getState, command); + + return {data: true}; + }; +} + +export async function clientExecuteCommand(dispatch: Dispatch, getState: GetStateFunc, command: string) { + let currentChannel = getCurrentChannel(getState()); + const currentTeamId = getCurrentTeamId(getState()); + + // Default to town square if there is no current channel (i.e., if Mattermost has not yet loaded) + if (!currentChannel) { + currentChannel = await Client4.getChannelByName(currentTeamId, 'town-square'); + } + + const args = { + channel_id: currentChannel?.id, + team_id: currentTeamId, + }; + + try { + //@ts-ignore Typing in mattermost-redux is wrong + const data = await Client4.executeCommand(command, args); + dispatch(setTriggerId(data?.trigger_id)); + } catch (error) { + console.error(error); //eslint-disable-line no-console + } +} diff --git a/webapp/src/index.tsx b/webapp/src/index.tsx index c3fc933..2cc4ba1 100644 --- a/webapp/src/index.tsx +++ b/webapp/src/index.tsx @@ -1,15 +1,31 @@ import {Store, Action} from 'redux'; -import {GlobalState} from '@mattermost/types/lib/store'; +import {GlobalState} from 'mattermost-redux/types/store'; +import {getPost} from 'mattermost-redux/selectors/entities/posts'; import manifest from '@/manifest'; import {PluginRegistry} from '@/types/mattermost-webapp'; +import {triggerRemoveAttachmentsCommand} from './actions'; + export default class Plugin { // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function public async initialize(registry: PluginRegistry, store: Store>>) { // @see https://developers.mattermost.com/extend/plugins/webapp/reference/ + registry.registerPostDropdownMenuAction( + 'Remove attachments', + async (postID) => { + store.dispatch(triggerRemoveAttachmentsCommand(postID) as any); + }, + (postID) => { + const state = store.getState(); + const post = getPost(state, postID); + + // Don't show up if the post has no attachments. Permissions are checked server-side. + return typeof post.file_ids?.length !== 'undefined' && post.file_ids?.length > 0; + }, + ); } } diff --git a/webapp/src/types/mattermost-webapp/index.d.ts b/webapp/src/types/mattermost-webapp/index.d.ts index 906abe1..dcbe2c3 100644 --- a/webapp/src/types/mattermost-webapp/index.d.ts +++ b/webapp/src/types/mattermost-webapp/index.d.ts @@ -1,5 +1,9 @@ +type PostMenuAction = (postID: string) => void; +type PostMenuFilter = (postID: string) => boolean; + export interface PluginRegistry { registerPostTypeComponent(typeName: string, component: React.ElementType) + registerPostDropdownMenuAction(text: string, action?: PostMenuAction, filter?: PostMenuFilter) // Add more if needed from https://developers.mattermost.com/extend/plugins/webapp/reference }