snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
This commit is contained in:
10
docs/develop/integrate/plugins/components/index.md
Normal file
10
docs/develop/integrate/plugins/components/index.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: "Components"
|
||||
sidebar_position: 40
|
||||
---
|
||||
|
||||
A Mattermost plugin consists of several components:
|
||||
|
||||
- [Server](/server): subprocesses invoked by the Mattermost Server that communicate using RPC
|
||||
- [Webapp](/webapp): extensions/modifications to the Mattermost web and desktop apps
|
||||
- [Mobile](/mobile): mobile device support
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "Mobile plugins"
|
||||
sidebar_position: 30
|
||||
---
|
||||
|
||||
[Server plugins](/developers/integrate/plugins/components/server) may use [interactive dialogs](/developers/integrate/plugins/interactive-dialogs) and [interactive messages](/developers/integrate/plugins/interactive-messages) to build cross platform interactions that are compatible with mobile.
|
||||
|
||||
It isn't possible to customize the mobile user experience in the same way the [webapp](/developers/integrate/plugins/components/webapp) can be extended.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: "Best practices"
|
||||
sidebar_position: 0
|
||||
---
|
||||
|
||||
## How should plugins serve publicly available static files?
|
||||
|
||||
Add all static files under a file directory named `public` within the plugin directory, and include the files in the plugin bundle using the Makefile.
|
||||
|
||||
## How do plugins make sure http requests are authentic?
|
||||
|
||||
Plugins can implement the [`ServeHTTP`](/developers/integrate/reference/server#Hooks.ServeHTTP) to listen to http requests. This can be used to receive post action requests when [Interactive Messages Buttons and Menus](https://docs.mattermost.com/developer/interactive-messages.html) are triggered by users.
|
||||
|
||||
When plugins act as an HTTP server, they serve requests from Mattermost clients (which are authenticated in a Mattermost sense), but may also serve HTTP requests from external services like webhooks. These requests from external services might use the Authorization header to authorize themselves against the plugin.
|
||||
|
||||
Since these requests are just HTTP requests, anyone can send them to the plugin. Hence, the plugin must make sure the requests are authentic. The Mattermost Server sets the HTTP header ``Mattermost-User-Id`` when the request is made by an authenticated client. If the plugin is expecting the request to come from an authenticated Mattermost user, and the ``Mattermost-User-Id`` is blank, it's the plugin's responsibility to reject the request.
|
||||
|
||||
From Mattermost v9.4, external systems can use the ``Authorization`` header to authenticate with the plugin. HTTP requests to server-side plugins can use an ``Authorization`` header in the request for the plugin to use, and the header value will be present in the request received by the plugin, as long as the token provided in the header is not a user token issued by the Mattermost server. This is useful for connecting external systems that require authentication through an Authorization header with their own token.
|
||||
|
||||
## How should plugins handle request cancellation?
|
||||
|
||||
Long-running `ServeHTTP` handlers should pass `r.Context()` to downstream operations and stop work when it is canceled. Mattermost v12.0 and later propagates deadlines and explicit cancellation for server-to-plugin and inter-plugin HTTP requests when the destination plugin is built with a context-aware plugin runtime.
|
||||
|
||||
Inter-plugin callers can opt in with `http.NewRequestWithContext` or `req.WithContext`. If cancellation happens before response headers arrive, `PluginHTTP` returns `nil` and the caller can inspect `request.Context().Err()`. If it happens after headers arrive, reading the response body returns `context.Canceled` or `context.DeadlineExceeded`. Callers must close response bodies as usual.
|
||||
|
||||
Context values are not propagated. Use headers or `plugin.Context` for supported cross-process metadata. Plugins that require end-to-end cancellation should declare Mattermost v12.0 as their minimum server version. Older servers retain legacy behavior: they continue to process the request without context propagation, and the call may not return promptly after cancellation.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: "Debug Server plugins"
|
||||
---
|
||||
|
||||
Plugins communicate with the main Mattermost Server by [RPC](https://github.com/hashicorp/go-plugin). In order to debug them with Delve, a few steps are necessary.
|
||||
### macOS
|
||||
1. After starting the main Mattermost application, run `ps aux | grep name.of.your.plugin`. This will print a list of running processes that match that name, as such: `username 78836 0.0 0.1 4397696 12492 s006 S 7:07AM 0:00.03 plugins/name.of.your.plugin/server/dist/plugin-darwin-amd64`.
|
||||
2. Grab the `pid`, which is the second number after your username in the output above. Run `dlv attach pid`, where `pid` is that number. let the plugin continue executing soon after connecting; Otherwise, the server will detect it as stopped and attempt to restart it.
|
||||
3. You're done. You should have access to your plugin's code through Delve and be able to set breakpoints, etc.
|
||||
42
docs/develop/integrate/plugins/components/server/ha.md
Normal file
42
docs/develop/integrate/plugins/components/server/ha.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: "High availability"
|
||||
sidebar_position: 60
|
||||
---
|
||||
|
||||
Mattermost Enterprise Edition servers with an E20 license have the ability to run in [High Availability (HA)](https://docs.mattermost.com/deployment/cluster.html) mode, meaning a cluster of Mattermost app servers running together as a single Mattermost deployment.
|
||||
|
||||
It is important that all plugins consider HA environments when being built.
|
||||
|
||||
Plugins are started as subprocesses of the main Mattermost process on each app server. This means a Mattermost deployment that has three app servers will have three separate copies of the same plugin running. Each running copy of the plugin will be isolated from one another on different servers. Therefore, to run properly in HA the plugin's server-side code must be stateless.
|
||||
|
||||
To be stateless, the plugin must not retain any information or status in memory that may be needed across multiple events (e.g. HTTP requests or in other hooks). This data should instead be stored in a place that all running copies of the plugin have access to. For example, the [key-value store](/developers/integrate/reference/server#API.KVSet) the plugin API provides.
|
||||
|
||||
To better explain the problem with having a plugin store data in-memory, consider this case:
|
||||
|
||||
Let's say we have two Mattermost app servers running together in HA and an installed plugin with a feature that alerts a user any time a trigger word is posted in a channel.
|
||||
|
||||
1. The user uses the web app side of the plugin to set the word `hello` as the trigger word
|
||||
2. An HTTP request is made to the server-side of the plugin to set `hello`
|
||||
3. The plugin process running on app server 1 handles the request and stores `hello` in a variable
|
||||
|
||||
In this scenario, the trigger word is now only set for the plugin process running on app server 1. The plugin process running on app server 2 is unaware that the trigger word is `hello`. This means when someone posts a message containing `hello` and the request is load balanced to app server 2, the plugin is not going to alert our user when it should.
|
||||
|
||||
The proper way to deal with this case would be for the plugin to store the trigger word in a global store, such as the KV store. Then any time a user posts the plugin can pull the trigger word from the store and properly alert the user, regardless of which app server handles the request.
|
||||
|
||||
## Run a scheduled job in high availability mode
|
||||
|
||||
Using the [mattermost/public/pluginapi/cluster](https://github.com/mattermost/mattermost/blob/d2c3710265c293281c2b445d4f72f27871c2e127/server/public/pluginapi/cluster/job.go#L115-L116) package, we can schedule jobs to perform background activity at regular intervals, without having to explicitly coordinate with other instances of the same plugin. Here's an example from the [Demo Plugin](https://github.com/mattermost/mattermost-plugin-demo/blob/d647f1ed7fdc384f5bc163a6bba689ab4293704e/server/activate_hooks.go#L72):
|
||||
|
||||
```go
|
||||
job, cronErr := cluster.Schedule(
|
||||
p.API,
|
||||
"BackgroundJob",
|
||||
cluster.MakeWaitForRoundedInterval(15*time.Minute),
|
||||
p.BackgroundJobFunc,
|
||||
)
|
||||
if cronErr != nil {
|
||||
return errors.Wrap(cronErr, "failed to schedule background job")
|
||||
}
|
||||
|
||||
p.backgroundJob = job
|
||||
```
|
||||
113
docs/develop/integrate/plugins/components/server/hello-world.md
Normal file
113
docs/develop/integrate/plugins/components/server/hello-world.md
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
title: "Plugin quick start"
|
||||
sidebar_position: -10
|
||||
---
|
||||
|
||||
This tutorial will walk you through the basics of writing a Mattermost plugin with a server component.
|
||||
|
||||
Note that the steps below are intentionally very manual to explain all of the pieces fitting together. In practice, we recommend referencing [mattermost-plugin-starter-template](https://github.com/mattermost/mattermost-plugin-starter-template) for helpful build scripts. Also, the plugin API changed in Mattermost 5.2. Consult the [migration](/developers/integrate/plugins/migration) document to upgrade older plugins.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Mattermost plugins extend the server using a Go API. In the future, gRPC may be supported, allowing you to write plugins in any language. For now, you'll need a functioning Go environment, so follow [Go's Getting Started](https://golang.org/doc/install) guide if needed.
|
||||
|
||||
You'll also need a Mattermost server to install and test the plugin. This server must have [Enable](https://docs.mattermost.com/administration/config-settings.html#enable-plugins) set to true in the [PluginSettings](https://docs.mattermost.com/administration/config-settings.html#plugins-beta) section of its config file. If you want to upload plugins via the System Console or API, you'll also need to set [EnableUploads](https://docs.mattermost.com/administration/config-settings.html#enable-plugin-uploads) to true in the same section.
|
||||
|
||||
## Build the plugin
|
||||
|
||||
The process that will communicate with the Mattermost server is built using a set of APIs provided by the source code for the Mattermost server.
|
||||
|
||||
Download the source code for the Mattermost server:
|
||||
|
||||
```bash
|
||||
go get -u github.com/mattermost/mattermost/server/v8
|
||||
```
|
||||
|
||||
Define `$GOPATH`. By default, this is already `$HOME/go`, but it's helpful to make this explicit:
|
||||
```shell
|
||||
export GOPATH=$HOME/go
|
||||
```
|
||||
|
||||
Now, create a directory to act as your workspace:
|
||||
|
||||
```bash
|
||||
mkdir -p $GOPATH/src/my-plugin
|
||||
cd $GOPATH/src/my-plugin
|
||||
```
|
||||
|
||||
Create a file named `plugin.go` with the following contents:
|
||||
|
||||
<PluginGoExample name="_helloWorld" />
|
||||
|
||||
This plugin will register an HTTP handler that will respond with "Hello, world!" when requested.
|
||||
|
||||
Build the executable that will be distributed with your plugin:
|
||||
|
||||
```bash
|
||||
go build -o plugin.exe plugin.go
|
||||
```
|
||||
|
||||
|
||||
<Note title="Note">
|
||||
Your executable is platform specific! If you're building the plugin for a server running on a different operating system, you'll need to use a slightly different command. For example, if you're developing the plugin from MacOS and deploying to a Linux server, you'll need to use this command:
|
||||
</Note>
|
||||
|
||||
|
||||
```bash
|
||||
GOOS=linux GOARCH=amd64 go build -o plugin.exe plugin.go
|
||||
```
|
||||
|
||||
Also note that the ".exe" extension is required if you'd like your plugin to run on Windows, but is otherwise optional. Consider referencing [mattermost-plugin-starter-template](https://github.com/mattermost/mattermost-plugin-starter-template) for helpful build scripts.
|
||||
|
||||
Now, we'll need to define the required manifest describing your plugin's entry point. Create a file named `plugin.json` with the following contents:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "com.mattermost.server-hello-world",
|
||||
"name": "Hello World",
|
||||
"server": {
|
||||
"executable": "plugin.exe"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This manifest gives the server the location of your executable within your plugin bundle. Consult the [manifest reference](/developers/integrate/plugins/manifest-reference) for more details, including how to define a cross-platform bundle by defining multiple executables, and how to define a minimum required server version for your plugin.
|
||||
|
||||
Note that you may also use `plugin.yaml` to define the manifest.
|
||||
|
||||
Bundle the manifest and executable into a tar file:
|
||||
|
||||
```bash
|
||||
tar -czvf plugin.tar.gz plugin.exe plugin.json
|
||||
```
|
||||
|
||||
You should now have a file named `plugin.tar.gz` in your workspace. Congratulations! This is your first server plugin!
|
||||
|
||||
## Install the plugin
|
||||
|
||||
Install the plugin in one of the following ways:
|
||||
|
||||
1) Through System Console UI:
|
||||
|
||||
```text
|
||||
- Log in to Mattermost as a System Admin.
|
||||
- Open the System Console at `/admin_console`
|
||||
- Navigate to *Plugins > Plugin Management** and upload the `plugin.tar.gz` you generated above.
|
||||
- Click **Enable** under the plugin after it has uploaded.
|
||||
```
|
||||
|
||||
2) Or, manually:
|
||||
|
||||
- Extract `plugin.tar.gz` to a folder with the same name as the plugin id you specified in ``plugin.json``, in this case `com.mattermost.server-hello-world/`.
|
||||
- Add the plugin to the directory set by **PluginSettings > Directory** in your ``config.json`` file. If none is set, defaults to `./plugins` relative to your Mattermost installation directory. The resulting directory structure should look something like:
|
||||
|
||||
```
|
||||
mattermost/
|
||||
plugins/
|
||||
com.mattermost.server-hello-world/
|
||||
plugin.json
|
||||
plugin.exe
|
||||
```
|
||||
- Restart the Mattermost server.
|
||||
|
||||
Once you've installed the plugin in one of the ways above, browse to `https://<your-mattermost-server>/plugins/com.mattermost.server-hello-world`, and you'll be greeted by your plugin.
|
||||
48
docs/develop/integrate/plugins/components/server/index.md
Normal file
48
docs/develop/integrate/plugins/components/server/index.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: "Server plugins"
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
Server plugins are subprocesses invoked by the server that communicate with Mattermost using remote procedure calls (RPC).
|
||||
|
||||
Looking for a quick start? [See our "Hello, world!" tutorial](/developers/integrate/plugins/components/server/hello-world).
|
||||
|
||||
Want the Server SDK reference doc? [Find it here](/developers/integrate/reference/server).
|
||||
|
||||
## Features
|
||||
|
||||
#### RPC API
|
||||
|
||||
Use the [RPC API](/developers/integrate/reference/server#API) to execute create, read, update and delete (CRUD) operations on server data models.
|
||||
|
||||
For example, your plugin can consume events from a third-party webhook and create corresponding posts in Mattermost, without having to host your code outside Mattermost.
|
||||
|
||||
#### Hooks
|
||||
|
||||
Register for [hooks](/developers/integrate/reference/server#Hooks) and get alerted when certain events occur.
|
||||
|
||||
For example, consume the [OnConfigurationChange](/developers/integrate/reference/server#Hooks.OnConfigurationChange) hook to respond to server configuration changes, or the [MessageHasBeenPosted](/developers/integrate/reference/server#Hooks.MessageHasBeenPosted) hook to respond to posts.
|
||||
|
||||
#### REST API
|
||||
|
||||
Implement the [ServeHTTP](/developers/integrate/) hook to extend the existing Mattermost REST API.
|
||||
|
||||
Plugins with both a web app and server component can leverage this REST API to exchange data. Alternatively, expose your REST API to services and developers connecting from outside Mattermost.
|
||||
|
||||
## How it works
|
||||
|
||||
When starting a plugin, the server consults the [plugin's manifest](/developers/integrate/plugins/manifest-reference) to determine if a server component was included. If found, the server launches a new process using the executable included with the plugin.
|
||||
|
||||
The server will trigger the [OnActivate](/developers/integrate/reference/server#Hooks.OnActivate) hook if the plugin is successfully started, allowing you to perform startup events. If the plugin is disabled, the server will trigger the [OnDeactivate](/developers/integrate/reference/server#Hooks.OnDeactivate) hook. While running, the server plugin can consume hook events, make API calls, launch threads or subprocesses of its own, interact with third-party services or do anything else a regular program can do.
|
||||
|
||||
## High availability
|
||||
|
||||
Considerations for plugins in a high availability configuration are documented here: [High Availability](/developers/integrate/plugins/components/server/ha)
|
||||
|
||||
## Best practices
|
||||
|
||||
Some best practices for working with the server component of a plugin are documented here: [Best Practices](/developers/integrate/plugins/components/server/best-practices)
|
||||
|
||||
## Debug Server plugins
|
||||
|
||||
Guidelines for debugging the server-side of plugins are documented here: [Debug Server plugins](/developers/integrate/plugins/components/server/debugging)
|
||||
282
docs/develop/integrate/plugins/components/webapp/actions.md
Normal file
282
docs/develop/integrate/plugins/components/webapp/actions.md
Normal file
@@ -0,0 +1,282 @@
|
||||
---
|
||||
title: "Redux actions"
|
||||
sidebar_position: 11
|
||||
---
|
||||
|
||||
When building web app plugins, it's common to perform actions or access the state that web and mobile apps already support. The majority of these actions exist in [mattermost-redux](https://github.com/mattermost/mattermost-redux), which is our library of shared code between Mattermost JavaScript clients. The `mattermost-redux` library exports types and functions that are imported by the web application. These functions can be imported by plugins and used the same way. There are a few different kinds of functions exported by the library:
|
||||
|
||||
* [actions](https://github.com/mattermost/mattermost-redux/tree/master/src/actions): Actions perform API requests and can change the state of Mattermost.
|
||||
* [client](https://github.com/mattermost/mattermost-redux/tree/master/src/client): The client package can be used to instantiate a Client4 object to interact with the Mattermost API directly. This is useful in plugins as well as JavaScript server applications communicating with Mattermost.
|
||||
* [constants](https://github.com/mattermost/mattermost-redux/tree/master/src/constants): An assortment of constants within Mattermost's data model.
|
||||
* [selectors](https://github.com/mattermost/mattermost-redux/tree/master/src/selectors): Selectors return certain data from the Redux store, such as getPost which allows you get a post by id.
|
||||
* [store](https://github.com/mattermost/mattermost-redux/tree/master/src/store): Functions related to the Redux store itself.
|
||||
* [types](https://github.com/mattermost/mattermost-redux/tree/master/src/types): Various types of objects in Mattermost's data model. These are useful for plugins written in Typescript.
|
||||
* [utils](https://github.com/mattermost/mattermost-redux/tree/master/src/utils): Various utility functions shared across the web application.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
It's assumed you have already set up your plugin development environment for web app plugins to match [mattermost-plugin-starter-template](https://github.com/mattermost/mattermost-plugin-starter-template). If not, follow the README instructions of that repository first, or [see the Hello, World! guide](/developers/integrate/plugins/components/webapp/hello-world).
|
||||
|
||||
## Basic example
|
||||
|
||||
Here's an example of a web app plugin making use of [mattermost-redux's](https://github.com/mattermost/mattermost-redux) selectors:
|
||||
|
||||
```ts
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import {getPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
const MyComponent = ({postId}) => {
|
||||
const post = useSelector((state) => getPost(state, postId));
|
||||
const currentUser = useSelector(getCurrentUser);
|
||||
const currentChannel = useSelector(getCurrentChannel);
|
||||
const currentTeam = useSelector(getCurrentTeam);
|
||||
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
## Some common actions
|
||||
|
||||
We've listed out some of the commonly-used actions that you can use in your web app plugin. You can find all the actions are available for your plugin to import [in the source code for mattermost-redux](https://github.com/mattermost/mattermost-redux/tree/master/src/actions).
|
||||
|
||||
* ### [createChannel(channel: Channel, userId: string)](https://github.com/mattermost/mattermost-redux/blob/3d1028034d7677adfda58e91b9a5dcaf1bc0ff99/src/actions/channels.ts#L47)
|
||||
|
||||
Dispatch this action to create a new channel.
|
||||
|
||||
|
||||
* ### [getCustomEmoji(emojiId: string)](https://github.com/mattermost/mattermost-redux/blob/3d1028034d7677adfda58e91b9a5dcaf1bc0ff99/src/actions/emojis.ts#L32)
|
||||
|
||||
Dispatch this action to fetch a specific emoji associated with the `emojiId` provided.
|
||||
|
||||
|
||||
* ### [createPost(post: Post, files: any\[\] = \[\])](https://github.com/mattermost/mattermost-redux/blob/3d1028034d7677adfda58e91b9a5dcaf1bc0ff99/src/actions/posts.ts#L162)
|
||||
|
||||
Dispatch this action to create a new post.
|
||||
|
||||
|
||||
* ### [getMyTeams()](https://github.com/mattermost/mattermost-redux/blob/3d1028034d7677adfda58e91b9a5dcaf1bc0ff99/src/actions/teams.ts#L67)
|
||||
|
||||
Dispatch this action to fetch all the team types associated with the current user.
|
||||
|
||||
|
||||
* ### [createUser(user: UserProfile, token: string, inviteId: string, redirect: string)](https://github.com/mattermost/mattermost-redux/blob/3d1028034d7677adfda58e91b9a5dcaf1bc0ff99/src/actions/users.ts#L53)
|
||||
|
||||
Dispatch this action to create a new user profile.
|
||||
|
||||
|
||||
## Some common selectors
|
||||
|
||||
Here are some examples of commonly-used selectors you can use in your web app plugin. You can find all the selectors that are available for your plugin to import [in the source code for mattermost-redux](https://github.com/mattermost/mattermost-redux/tree/master/src/selectors).
|
||||
|
||||
* ### [getCurrentUserId(state)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/common.ts#L46)
|
||||
|
||||
Retrieves the `userId` of the current user from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getCurrentUser(state: GlobalState): UserProfile](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/common.ts#L42)
|
||||
|
||||
Retrieves the user profile of the current user from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getUsers(state: GlobalState): IDMappedObjects<UserProfile>](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/common.ts#L50)
|
||||
|
||||
Retrieves all user profiles from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getChannel(state: GlobalState, id: string)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/channels.ts#L218)
|
||||
|
||||
Retrieves a channel as it exists in the store without filling in any additional details such as the `display_name` for Direct Messages/Group Messages.
|
||||
|
||||
|
||||
* ### [getCurrentChannelId(state: GlobalState)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/common.ts#L14)
|
||||
|
||||
Retrieves the channel ID of the current channel from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getCurrentChannel: (state: GlobalState)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/channels.ts#L235)
|
||||
|
||||
Retrieves the complete channel info of the current channel from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getPost(state: GlobalState, postId: $ID<Post>)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/posts.ts#L46)
|
||||
|
||||
Retrieves the specific post associated with the supplied `postID` from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getCurrentTeamId(state: GlobalState)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/teams.ts#L20)
|
||||
|
||||
Retrieves the `teamId` of the current team from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getCurrentTeam: (state: GlobalState)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/teams.ts#L57)
|
||||
|
||||
Retrieves the team info of the current team from the `Redux store`.
|
||||
|
||||
|
||||
* ### [getCustomEmojisByName: (state: GlobalState)](https://github.com/mattermost/mattermost-redux/blob/master/src/selectors/entities/emojis.ts#L37)
|
||||
|
||||
Retrieves the the specific emoji associated with the supplied `customEmojiName` from the `Redux store`.
|
||||
|
||||
|
||||
## Some common client functions
|
||||
|
||||
We've listed out some of the commonly-used client functions you can use in your web app plugin. You can find all the client functions that are available for your plugin to import [in the source code for mattermost-redux](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts).
|
||||
|
||||
* ### [getUser = (userId: string)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L846)
|
||||
|
||||
Routes to the user profile of the specified `userId` from the `Mattermost Server`.
|
||||
|
||||
|
||||
* ### [getUserByUsername = (username: string)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L853)
|
||||
|
||||
Routes to the user profile of the specified `username` from the `Mattermost Server`.
|
||||
|
||||
|
||||
* ### [getChannel = (channelId: string)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L1600)
|
||||
|
||||
Routes to the channel of the specified `channelId` from the `Mattermost Server`.
|
||||
|
||||
|
||||
* ### [getChannelByName = (teamId: string, channelName: string, includeDeleted = false)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L1609)
|
||||
|
||||
Routes to the channel of the specified `channelName` from the `Mattermost Server`.
|
||||
|
||||
|
||||
* ### [getTeam = (teamId: string)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L1192)
|
||||
|
||||
Routes to the team of the specified `teamId` from the `Mattermost Server`.
|
||||
|
||||
|
||||
* ### [getTeamByName = (teamName: string)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L1199)
|
||||
|
||||
Routes to the team of the specified `teamName` from the `Mattermost Server`.
|
||||
|
||||
|
||||
* ### [executeCommand = (command: string, commandArgs: CommandArgs)](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L2463)
|
||||
|
||||
Executes the specified command with the arguments provided and fetches the response.
|
||||
|
||||
|
||||
* ### [getOptions(options: Options) \{const newOptions: Options = \{...options\}](https://github.com/mattermost/mattermost-redux/blob/master/src/client/client4.ts#L440)
|
||||
|
||||
Get the client options to make requests to the server. Use this to create your own custom requests.
|
||||
|
||||
## Custom reducers and actions
|
||||
|
||||
Reducers in Redux are pure functions that describe how the data in the store changes after any given action. Reducers will always produce the same resulting state for a given state and action. You can register a custom reducer for your plugin against the Redux store with the `registerReducer` function.
|
||||
|
||||
### [registerReducer(reducer)](/developers/integrate/reference/webapp#registerReducer)
|
||||
|
||||
Registers a reducer against the Redux store. It will be accessible in Redux state under `state['plugins-<yourpluginid>']`. It generally accepts a reducer and returns undefined.
|
||||
|
||||
When building web app plugins, it is common to perform actions that web and mobile apps already support. The majority of these actions exist in [mattermost-redux](https://github.com/mattermost/mattermost-redux), our library of shared code between Mattermost JavaScript clients.
|
||||
|
||||
Here we'll show how to use Redux actions with a plugin. To learn more about these actions, see the [contributor documentation](/developers/contribute/more-info/webapp/redux/actions).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This guide assumes you have already set up your plugin development environment for web app plugins to match [mattermost-plugin-starter-template](https://github.com/mattermost/mattermost-plugin-starter-template). If not, follow the README instructions of that repository first, or [see the Hello, World! guide](/developers/integrate/plugins/components/webapp/hello-world).
|
||||
|
||||
## Import mattermost-redux
|
||||
|
||||
First, you'll need to add `mattermost-redux` as a dependency of your web app plugin.
|
||||
|
||||
```bash
|
||||
cd /path/to/plugin/webapp
|
||||
npm install mattermost-redux
|
||||
```
|
||||
|
||||
That will add `mattermost-redux` as a dependency in your `package.json` file, allowing it to be imported into any of your plugin's JavaScript files.
|
||||
|
||||
## Use an action
|
||||
|
||||
Actions are used as part of components. To give components access to these actions, we pass them in as React props from the component's container `index.js` file. To demonstrate this, we'll create a new component.
|
||||
|
||||
In the `webapp` directory, let's create a component folder called `action_example` and switch into it.
|
||||
|
||||
```bash
|
||||
mkdir -p src/components/action_example
|
||||
cd src/components/action_example
|
||||
```
|
||||
|
||||
In there, create two files: `index.js` and `action_example.jsx`. If you're not familiar with why we're creating these directories and files, [read the contributor documentation on using React with Redux](/developers/contribute/more-info/webapp/redux/react-redux).
|
||||
|
||||
Open up `action_example.jsx` and add the following:
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class ActionExample extends React.PureComponent {
|
||||
static propTypes = {
|
||||
user: PropTypes.object.isRequired,
|
||||
patchUser: PropTypes.func.isRequired, // here we define the action as a prop
|
||||
}
|
||||
|
||||
updateFirstName = () => {
|
||||
const patchedUser = {
|
||||
id: this.props.user.id,
|
||||
first_name: 'Jim',
|
||||
};
|
||||
|
||||
this.props.patchUser(patchedUser); // here we use the action
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{'First name: ' + this.props.user.first_name}
|
||||
<a
|
||||
href='#'
|
||||
onClick={this.updateFirstName}
|
||||
>
|
||||
Click me to update the first name!
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This component will display a user's first name and then, when the link is clicked, use an action to update that user's first name to "Jim".
|
||||
|
||||
The action `patchUser` is from mattermost-redux. It takes in a subset of a user object and updates the user on the server, [using the `PUT /users/\{user_id\}/patch` endpoint](https://api.mattermost.com/#tag/users%2Fpaths%2F~1users~1%7Buser_id%7D~1patch%2Fput).
|
||||
|
||||
We must now use our container to import this action and pass it our component. Open up the `index.js` file and add:
|
||||
|
||||
```javascript
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import {patchUser} from 'mattermost-redux/actions'; // importing the action
|
||||
|
||||
import ActionExample from './action_example.jsx';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const currentUserId = state.entities.users.currentUserId;
|
||||
|
||||
return {
|
||||
user: state.entities.users.profiles[currentUserId],
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => bindActionCreators({
|
||||
patchUser, // passing the action as a prop
|
||||
}, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ActionExample);
|
||||
```
|
||||
|
||||
The container is doing two things. First, it's grabbing the current (logged in) user from the Redux state and passing it in as a prop. Anytime the Redux state updates, for example when we use the `patchUser` action, our component will get the updated copy of the current user. Second, the container is importing the `patchUser` action from mattermost-redux and passing it in as an action prop to our component.
|
||||
|
||||
Now we can use `this.props.patchUser()` to update a user. The example component we made uses it to patch the current user's first name.
|
||||
|
||||
To use our component in our plugin we would then use the registry in the initialization function of the plugin to register the component somewhere in the Mattermost UI. That is beyond the scope of this guide, but you can [read more about that here](/developers/integrate/reference/webapp).
|
||||
|
||||
## Available actions
|
||||
|
||||
The actions that are available for your plugin to import can be [found in the source code for mattermost-redux](https://github.com/mattermost/mattermost-redux/tree/master/src/actions).
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: "Best practices"
|
||||
sidebar_position: 0
|
||||
---
|
||||
|
||||
## Design best practices
|
||||
|
||||
### Actions that apply to specific Channels
|
||||
- Recommendation: Have your plugin register the actions to [the channel header](/developers/integrate/reference/webapp#registerChannelHeaderButtonAction). This makes it quickly accessible for users and the actions apply on the channel they're viewing.
|
||||
- Example: Zoom meeting posts to a channel
|
||||
|
||||

|
||||
|
||||
You can additionally [register a slash command](/developers/integrate/reference/server#API.RegisterCommand) on the server-side to take channel-specific actions.
|
||||
- Example: Jira project actions
|
||||
|
||||

|
||||
|
||||
### Actions that apply to specific messages
|
||||
- Recommendation: Have your plugin register a [post dropdown menu component](/developers/integrate/reference/webapp#registerPostDropdownMenuComponent) with some text, icon and an action function. This adds your action to the "More Actions" post menu dropdown for easy discovery.
|
||||
- Examples: Create or attach to Jira issue from a message; copy a message to another channel; report an inappropriate message
|
||||
- Sample code: [mattermost/mattermost-plugin-todo/webapp/src/index.js](https://github.com/mattermost/mattermost-plugin-todo/blob/0c4dbfb58a72f8392ea66e101996afd06fdb2913/webapp/src/index.js#L30-L37)
|
||||
|
||||

|
||||
|
||||
### Actions related to files or images
|
||||
- Recommendation: Have your plugin register a [file upload method](/developers/integrate/reference/webapp#registerFileUploadMethod) with some text, icon and an action function. This adds your new action to the file upload menu.
|
||||
- Examples: File sharing from OneDrive or GDrive; Draw plugin for sketches
|
||||
|
||||

|
||||
|
||||
### Actions that apply to specific teams
|
||||
- Recommendation: Have your plugin register [left sidebar header component](/developers/integrate/reference/webapp#registerLeftSidebarHeaderComponent) with some text, icon and an action function. This adds your action above your team's channels in the sidebar.
|
||||
- Examples: Trello kanban board plugin, GitHub Plugin
|
||||
|
||||

|
||||
|
||||
### Quick links or status summaries of workflows
|
||||
- Recommendation: Have your plugin register a [bottom team sidebar component](/developers/integrate/reference/webapp#registerBottomTeamSidebarComponent). This adds icons to the lower left corner of the UI.
|
||||
- Examples: GitHub sidebar links with summary of outstanding reviews or unread messages; ServiceNow incident status summary
|
||||
|
||||

|
||||
|
||||
### Global actions that can be taken anywhere in the server and not directly related to teams, channels or users
|
||||
- Recommendation: Have your plugin register [main menu action](/developers/integrate/reference/webapp#registerMainMenuAction) with some text, icon for mobile, and an action function. This adds your action to the Main Menu. You can additionally [register a slash command](/developers/integrate/reference/server#API.RegisterCommand) on the server-side.
|
||||
- Examples: Share feedback plugin in Main Menu; /jira slash commands for quick actions
|
||||
|
||||

|
||||
|
||||
### Actions that apply to specific users
|
||||
- Recommendation: Have your plugin register a [popover user actions component](/developers/integrate/reference/webapp#registerPopoverUserActionsComponent). This adds your action button to the user profile popover.
|
||||
- Examples: Report User plugin; Display extra information about the user from an LDAP server
|
||||
|
||||

|
||||
|
||||
### Extra information on a user profile
|
||||
- Recommendation: Have your plugin register a [popover user attribute component](/developers/integrate/reference/webapp#registerPopoverUserAttributesComponent). This adds your custom attributes to the user profile popover.
|
||||
- Examples: Custom User Attributes plugin
|
||||
|
||||

|
||||
|
||||
### Actions related to emoji and GIFs
|
||||
- Recommendation: Have your plugin add a component to the emoji picker. This is not yet supported, but some [work had previously started](https://github.com/mattermost/mattermost/issues/10412#issuecomment-481776595) with the issue currently opened as Help Wanted.
|
||||
- Examples: Bitmoji plugin; GIFs via Giphy or Gfycat
|
||||
|
||||
## Set up the plugin to properly communicate with the Mattermost Server
|
||||
|
||||
### Use the Mattermost Server's `SiteURL` in your web app plugin
|
||||
|
||||
In order to make sure your plugin has full compatibility with your Mattermost server, you should use the server's configured SiteURL in each API call you send to the server from the webapp. Here's an [example](https://github.com/mattermost/mattermost-plugin-jira/blob/19a9c2442817132b4eee5c77e259b80a40188a6a/webapp/src/selectors/index.js#L13-L26) of how to compute the SiteURL:
|
||||
|
||||
```js
|
||||
export const getPluginServerRoute = (state) => {
|
||||
const config = getConfig(state);
|
||||
|
||||
let basePath = '';
|
||||
if (config && config.SiteURL) {
|
||||
basePath = new URL(config.SiteURL).pathname;
|
||||
|
||||
if (basePath && basePath[basePath.length - 1] === '/') {
|
||||
basePath = basePath.substr(0, basePath.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return basePath + '/plugins/' + PluginId;
|
||||
};
|
||||
```
|
||||
|
||||
### Include the server's CSRF token in your web app plugin's requests
|
||||
|
||||
The Mattermost server can be configured to require a CSRF token to be present in HTTP requests sent from the webapp. In order to include the token in each request, you can use the `mattermost-redux` library's `Client4.getOptions` function to add the token to your `fetch` request. Here's an [example](https://github.com/mattermost/mattermost-plugin-jira/blob/19a9c2442817132b4eee5c77e259b80a40188a6a/webapp/src/client/index.js#L14) of how to include the CSRF token.
|
||||
|
||||
```js
|
||||
const response = await fetch(url, Client4.getOptions(options));
|
||||
```
|
||||
178
docs/develop/integrate/plugins/components/webapp/hello-world.md
Normal file
178
docs/develop/integrate/plugins/components/webapp/hello-world.md
Normal file
@@ -0,0 +1,178 @@
|
||||
---
|
||||
title: "Web app quick start"
|
||||
sidebar_position: -10
|
||||
---
|
||||
|
||||
This tutorial will walk you through the basics of extending the Mattermost web app.
|
||||
|
||||
Note that the steps below are intentionally very manual to explain all of the pieces fitting together. In practice, we recommend referencing [mattermost-plugin-starter-template](https://github.com/mattermost/mattermost-plugin-starter-template) for helpful build scripts. Also, the plugin API changed in Mattermost 5.2. Consult the [migration](/developers/integrate/plugins/migration) document to upgrade older plugins.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Plugins, just like the Mattermost web app itself, are built using [ReactJS](https://react.dev/) with [Redux](https://redux.js.org/). Make sure to install [npm](https://www.npmjs.com/get-npm) to manage your JavaScript dependencies.
|
||||
|
||||
You'll also need a Mattermost server to install and test the plugin. This server must have [Enable](https://docs.mattermost.com/administration/config-settings.html#enable-plugins) set to true in the [PluginSettings](https://docs.mattermost.com/administration/config-settings.html#plugins-beta) section of its config file. If you want to upload plugins via the System Console or API, you'll also need to set [EnableUploads](https://docs.mattermost.com/administration/config-settings.html#enable-plugin-uploads) to true in the same section.
|
||||
|
||||
## Set up the workspace
|
||||
|
||||
Create a directory to act as your plugin workspace. With that directory, create and switch to a `webapp` directory:
|
||||
|
||||
```bash
|
||||
mkdir webapp
|
||||
cd webapp
|
||||
```
|
||||
|
||||
Install the necessary NPM dependencies:
|
||||
|
||||
```bash
|
||||
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader webpack webpack-cli
|
||||
npm install --save react
|
||||
```
|
||||
|
||||
Configure Webpack by creating a `webpack.config.js` file:
|
||||
|
||||
```js
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: [
|
||||
'./src/index.jsx',
|
||||
],
|
||||
resolve: {
|
||||
modules: [
|
||||
'src',
|
||||
'node_modules',
|
||||
],
|
||||
extensions: ['*', '.js', '.jsx'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-react',
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": "commonjs",
|
||||
"targets": {
|
||||
"node": "current"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
externals: {
|
||||
react: 'React',
|
||||
},
|
||||
output: {
|
||||
path: path.join(__dirname, '/dist'),
|
||||
publicPath: '/',
|
||||
filename: 'main.js',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Observe that `react` is specified as an external library. This allows you to test your code locally (e.g. with [jest](https://jestjs.io/) and snapshots) but leverage the version of React shipped with Mattermost to avoid bloating your plugin.
|
||||
|
||||
Now create the entry point file and output directory:
|
||||
```bash
|
||||
mkdir src dist
|
||||
touch src/index.jsx
|
||||
```
|
||||
|
||||
Then populate `src/index.jsx` with the following:
|
||||
```jsx
|
||||
import React from 'react';
|
||||
|
||||
// Courtesy of https://feathericons.com/
|
||||
const Icon = () => <i className='icon fa fa-plug'/>;
|
||||
|
||||
class HelloWorldPlugin {
|
||||
initialize(registry, store) {
|
||||
registry.registerChannelHeaderButtonAction(
|
||||
// icon - JSX element to use as the button's icon
|
||||
<Icon />,
|
||||
// action - a function called when the button is clicked, passed the channel and channel member as arguments
|
||||
// null,
|
||||
() => {
|
||||
alert("Hello World!");
|
||||
},
|
||||
// dropdown_text - string or JSX element shown for the dropdown button description
|
||||
"Hello World",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
window.registerPlugin('com.mattermost.webapp-hello-world', new HelloWorldPlugin());
|
||||
```
|
||||
|
||||
Generate a minified bundle ready to install as a web app plugin:
|
||||
|
||||
```bash
|
||||
./node_modules/.bin/webpack --mode=production
|
||||
```
|
||||
|
||||
Now, we'll need to define the required manifest describing your plugin's entry point. Create a file named `plugin.json` with the following contents:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "com.mattermost.webapp-hello-world",
|
||||
"name": "Hello World",
|
||||
"webapp": {
|
||||
"bundle_path": "main.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This manifest gives the server the location of your components within your plugin bundle. Consult the [manifest reference](/developers/integrate/plugins/manifest-reference) for more details, including how to define a minimum required server version for your plugin.
|
||||
|
||||
Note that you may also use `plugin.yaml` to define the manifest.
|
||||
|
||||
Bundle the manifest and entry point into a tar file:
|
||||
|
||||
```bash
|
||||
mkdir -p com.mattermost.webapp-hello-world
|
||||
cp -r dist/main.js com.mattermost.webapp-hello-world/
|
||||
cp plugin.json com.mattermost.webapp-hello-world/
|
||||
tar -czvf plugin.tar.gz com.mattermost.webapp-hello-world
|
||||
```
|
||||
|
||||
You should now have a file named `plugin.tar.gz` in your workspace. Congratulations! This is your first web app plugin!
|
||||
|
||||
## Install the plugin
|
||||
|
||||
Install the plugin in one of the following ways:
|
||||
|
||||
1) Through System Console UI:
|
||||
|
||||
```text
|
||||
- Log in to Mattermost as a System Admin.
|
||||
- Open the System Console at `/admin_console`
|
||||
- Navigate to **Plugins > Plugin Management** and upload the `plugin.tar.gz` you generated above.
|
||||
- Click **Enable** under the plugin after it has uploaded.
|
||||
```
|
||||
|
||||
2) Or, manually:
|
||||
|
||||
- Extract `plugin.tar.gz` to a folder with the same name as the plugin id you specified in ``plugin.json``, in this case `com.mattermost.server-hello-world/`.
|
||||
- Add the plugin to the directory set by **PluginSettings > Directory** in your ``config.json`` file. If none is set, defaults to `./plugins` relative to your Mattermost installation directory. The resulting directory structure should look something like:
|
||||
|
||||
```
|
||||
mattermost/
|
||||
plugins/
|
||||
com.mattermost.webapp-hello-world/
|
||||
plugin.json
|
||||
main.js
|
||||
```
|
||||
- Restart the Mattermost server.
|
||||
- Enable the plugin in **System Console > Plugins > Plugin Management**.
|
||||
|
||||
Navigate to a regular Mattermost page and observe the new icon in the channel header. Click the icon and observe the alert dialog.
|
||||
54
docs/develop/integrate/plugins/components/webapp/index.md
Normal file
54
docs/develop/integrate/plugins/components/webapp/index.md
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: "Web app plugins"
|
||||
sidebar_position: 20
|
||||
---
|
||||
|
||||
Web app plugins extend and modify the Mattermost web and desktop apps, without having to fork and rebase on every Mattermost release.
|
||||
|
||||
Looking for a quick start? [See our "Hello, world!" tutorial](/developers/integrate/plugins/components/webapp/hello-world).
|
||||
|
||||
Want the web app SDK reference doc? [Find it here](/developers/integrate/reference/webapp).
|
||||
|
||||
## Features
|
||||
|
||||
#### Extend existing components
|
||||
|
||||
Register your own React components to be added to the channel header, sidebars, user details popover, main menu and other supported components. Multiple plugins can add to the same component simultaneously. This API focuses on ease of use and maintaining compatibility with future Mattermost releases.
|
||||
|
||||
For example, the [Mattermost Zoom Plugin](https://github.com/mattermost/mattermost-plugin-zoom) registers a button in the channel header to trigger a Zoom call and post a link to the call in the current channel.
|
||||
|
||||
#### Add new root components
|
||||
|
||||
Register your own React components alongside other root components like the sidebars and modals. This enables whole new interactions that aren't constrained by the set of existing components. This API is geared towards power and flexibility, but may require fine tuning with future Mattermost releases.
|
||||
|
||||
#### Custom post type components
|
||||
|
||||
Web app plugins can also render different post components based on the post's type. Any time the web app encounters a post with this post type, it replaces the default rendering of the post component with your own custom implementation. Only one plugin can own the rendering for a given custom post type at a time: the last plugin to register will own the rendering for that custom post type.
|
||||
|
||||
For example, you can register a custom post type `custom_poll` using [registerPostTypeComponent](/developers/integrate/reference/webapp#registerPostTypeComponent). Then, any time the web app sees that post type, it replaces the regular rendering of the post component with your own custom implementation.
|
||||
|
||||
Use this in conjunction with setting the post type in webhooks or slash commands, through the REST API or with a server plugin, and you can deeply integrate or extend Mattermost posts to fit your needs.
|
||||
|
||||
## How it works
|
||||
|
||||
When a plugin is uploaded to a Mattermost server and activated, the server checks to see if there is a webapp portion included as part of the plugin by looking at the [plugin's manifest](/developers/integrate/plugins/manifest-reference). If one is found, the server copies the bundled JavaScript included with the plugin into the static directory for serving. A WebSocket event is then fired off to the connected clients signalling the activation of a new plugin.
|
||||
|
||||
On web app launch, a request is made to the server to get a list of plugins that contain web app components. The web app then proceeds to download and execute the JavaScript bundles for each plugin. A similar process happens if an already launched web app receives a WebSocket event for a newly activated plugin.
|
||||
|
||||
Once downloaded and executed, each plugin should have registered itself via the global [registerPlugin](/developers/integrate/reference/webapp#registerPlugin). The web app then invokes the `initialize` function defined on the plugin class, passing a registry and store. The registry passed allows the plugin to register (and unregister) components, event callbacks and Redux reducers to track plugin state. The store passed is the same Redux store used by the web app, giving the plugin access to the full state of the web app.
|
||||
|
||||
Components registered by the plugin via the registry are tracked in the Redux store and used by `Pluggable` components throughout the web app. `Pluggable` components with a `pluggableName` attribute can render multiple such components registered by plugins.
|
||||
|
||||
Custom post types work similarly, but are registered slightly differently, use a separate reducer and have more of a custom implementation.
|
||||
|
||||
## Redux actions
|
||||
|
||||
Further information on the available Redux Actions is documented here: [Redux Actions](/developers/integrate/plugins/components/webapp/actions)
|
||||
|
||||
## Best practices
|
||||
|
||||
Some best practices for working with the webapp component of a plugin are documented here: [Best Practices](/developers/integrate/plugins/components/webapp/best-practices)
|
||||
|
||||
## React and JSX runtime compatibility
|
||||
|
||||
Plugins that render inside Mattermost must use the host's React, ReactDOM, and JSX runtime. See [React JSX runtime compatibility](/developers/integrate/plugins/components/webapp/react-jsx-runtime) to configure the host exports and require Mattermost 12.0 or later.
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: "React JSX runtime compatibility"
|
||||
sidebar_position: 30
|
||||
---
|
||||
|
||||
Mattermost v12 uses React 19. Web app plugins must use the host's React, ReactDOM, and JSX runtime. Externalizing only `react` can leave an older JSX runtime bundled in the plugin or a dependency, causing `ReactCurrentOwner` or incompatible-element errors.
|
||||
|
||||
## 1. Require Mattermost 12.0
|
||||
|
||||
Set `"min_server_version": "12.0.0"` in the plugin manifest, or keep a higher minimum if your plugin already requires one.
|
||||
|
||||
## 2. Use the host's JSX runtime
|
||||
|
||||
Add all five module paths below to your plugin's Webpack `externals` configuration:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
// ...
|
||||
externals: {
|
||||
react: 'React',
|
||||
'react-dom': 'ReactDOM',
|
||||
'react-dom/client': 'ReactDOM',
|
||||
'react/jsx-runtime': 'ReactJSXRuntime',
|
||||
'react/jsx-dev-runtime': 'ReactJSXDevRuntime',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Continue writing JSX normally. With the automatic JSX transform, your compiler imports helpers from `react/jsx-runtime` or `react/jsx-dev-runtime`; this configuration resolves those imports to Mattermost's runtime. Include `react-dom/client` because dependencies may import it directly.
|
||||
|
||||
Mattermost provides these exports before plugin code runs. Development plugin builds also work on production hosts, but development-only JSX diagnostics are unavailable there.
|
||||
|
||||
## 3. Rebuild and test
|
||||
|
||||
Rebuild and release the plugin after updating its configuration. Existing published bundles are not changed by the host exports.
|
||||
|
||||
Check dependencies for embedded JSX runtimes. Webpack externals handle module imports, but cannot replace a runtime already embedded in a dependency's code. Update affected dependencies to versions that import the runtime separately.
|
||||
|
||||
Test the rebuilt plugin on Mattermost 12.0, including modals, menus, settings, and other UI that appears after startup. Exercise production and development plugin builds, including a development plugin on a production host. If your plugin requires a higher minimum server version, test on that version.
|
||||
|
||||
The shared JSX runtime does not restore APIs removed by React 19. Replace `findDOMNode` with DOM refs and legacy ReactDOM rendering calls with root APIs. See the [React 19 upgrade guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide) for the remaining migration steps.
|
||||
Reference in New Issue
Block a user