diff --git a/.github/workflows/docs-preview-fork.yml b/.github/workflows/docs-preview-fork.yml index fe25d6652bae..16ddfd6ab300 100644 --- a/.github/workflows/docs-preview-fork.yml +++ b/.github/workflows/docs-preview-fork.yml @@ -7,10 +7,6 @@ on: type: string required: true description: "PR number (fork PR to build preview for)" - TRIGGERING_ACTOR: - type: string - required: true - description: "GitHub login of the fork PR author" COMMIT_SHA: type: string required: true @@ -31,11 +27,11 @@ jobs: with: repository_full_name: ${{ github.repository }} commit_sha: ${{ inputs.COMMIT_SHA }} - context: "docs-preview-fork / preview" + context: docs-preview description: "Docs preview build for ${{ inputs.COMMIT_SHA }} is running" status: pending - preview: + deploy: uses: ./.github/workflows/docs-preview-template.yml secrets: AWS_DOCS_PR_PREVIEW_KEY_ID: ${{ secrets.AWS_DOCS_PR_PREVIEW_KEY_ID }} @@ -45,36 +41,4 @@ jobs: with: PR_NUMBER: ${{ inputs.PR_NUMBER }} COMMIT_SHA: ${{ inputs.COMMIT_SHA }} - - update-failure-status: - runs-on: ubuntu-latest - if: failure() || cancelled() - needs: - - preview - steps: - - uses: mattermost/actions/delivery/update-commit-status@fec7b836001c9380d4bfaf28d443945c103a098c - env: - GITHUB_TOKEN: ${{ github.token }} - with: - repository_full_name: ${{ github.repository }} - commit_sha: ${{ inputs.COMMIT_SHA }} - context: "docs-preview-fork / preview" - description: "Docs preview build for ${{ inputs.COMMIT_SHA }} failed" - status: failure - - update-success-status: - runs-on: ubuntu-latest - if: success() - needs: - - preview - steps: - - uses: mattermost/actions/delivery/update-commit-status@fec7b836001c9380d4bfaf28d443945c103a098c - env: - GITHUB_TOKEN: ${{ github.token }} - with: - repository_full_name: ${{ github.repository }} - commit_sha: ${{ inputs.COMMIT_SHA }} - context: "docs-preview-fork / preview" - description: "Docs preview build for ${{ inputs.COMMIT_SHA }} succeeded" - status: success - target_url: "http://${{ vars.DOCS_PREVIEW_BUCKET_NAME }}.s3-website-us-east-1.amazonaws.com/mattermost/pr-${{ inputs.PR_NUMBER }}/" + STATUS_CONTEXT: docs-preview diff --git a/.github/workflows/docs-preview-template.yml b/.github/workflows/docs-preview-template.yml index 5efcd28d72e8..6033d5260458 100644 --- a/.github/workflows/docs-preview-template.yml +++ b/.github/workflows/docs-preview-template.yml @@ -9,6 +9,12 @@ on: COMMIT_SHA: type: string required: true + # Commit status context shown on the PR. Details (⋯) links to the preview + # when status is success. Empty skips status updates. + STATUS_CONTEXT: + type: string + required: false + default: docs-preview secrets: AWS_DOCS_PR_PREVIEW_KEY_ID: required: true @@ -23,10 +29,10 @@ concurrency: permissions: contents: read + statuses: write jobs: - preview: - name: Build and deploy preview + deploy: runs-on: ubuntu-24.04 steps: - name: Checkout @@ -89,3 +95,37 @@ jobs: "s3://${BUCKET_NAME}/mattermost/pr-${PR_NUMBER}/" \ --delete \ --no-progress + + - name: Set preview URL + id: preview + env: + BUCKET_NAME: ${{ vars.DOCS_PREVIEW_BUCKET_NAME }} + PR_NUMBER: ${{ inputs.PR_NUMBER }} + run: | + echo "url=http://${BUCKET_NAME}.s3-website-us-east-1.amazonaws.com/mattermost/pr-${PR_NUMBER}/" >> "$GITHUB_OUTPUT" + + # Details (⋯) on this check links to the preview URL via target_url. + - name: Set success commit status + if: ${{ inputs.STATUS_CONTEXT != '' }} + uses: mattermost/actions/delivery/update-commit-status@fec7b836001c9380d4bfaf28d443945c103a098c + env: + GITHUB_TOKEN: ${{ github.token }} + with: + repository_full_name: ${{ github.repository }} + commit_sha: ${{ inputs.COMMIT_SHA }} + context: ${{ inputs.STATUS_CONTEXT }} + description: "Docs preview build for ${{ inputs.COMMIT_SHA }} succeeded" + status: success + target_url: ${{ steps.preview.outputs.url }} + + - name: Set failure commit status + if: ${{ always() && inputs.STATUS_CONTEXT != '' && (failure() || cancelled()) }} + uses: mattermost/actions/delivery/update-commit-status@fec7b836001c9380d4bfaf28d443945c103a098c + env: + GITHUB_TOKEN: ${{ github.token }} + with: + repository_full_name: ${{ github.repository }} + commit_sha: ${{ inputs.COMMIT_SHA }} + context: ${{ inputs.STATUS_CONTEXT }} + description: "Docs preview build for ${{ inputs.COMMIT_SHA }} failed" + status: failure diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml index c9697ac4db9f..6e8134195f81 100644 --- a/.github/workflows/docs-preview.yml +++ b/.github/workflows/docs-preview.yml @@ -12,62 +12,13 @@ permissions: statuses: write jobs: - update-initial-status: - runs-on: ubuntu-latest - if: github.event.pull_request.head.repo.full_name == github.repository - steps: - - name: Set pending commit status - uses: mattermost/actions/delivery/update-commit-status@fec7b836001c9380d4bfaf28d443945c103a098c - env: - GITHUB_TOKEN: ${{ github.token }} - with: - repository_full_name: ${{ github.repository }} - commit_sha: ${{ github.event.pull_request.head.sha }} - context: "docs-preview / preview" - description: "Docs preview build for ${{ github.event.pull_request.head.sha }} is running" - status: pending - deploy: uses: ./.github/workflows/docs-preview-template.yml if: github.event.pull_request.head.repo.full_name == github.repository - needs: - - update-initial-status secrets: AWS_DOCS_PR_PREVIEW_KEY_ID: ${{ secrets.AWS_DOCS_PR_PREVIEW_KEY_ID }} AWS_DOCS_PR_PREVIEW_SECRET_ACCESS_KEY: ${{ secrets.AWS_DOCS_PR_PREVIEW_SECRET_ACCESS_KEY }} with: PR_NUMBER: ${{ github.event.number }} COMMIT_SHA: ${{ github.event.pull_request.head.sha }} - - update-failure-status: - runs-on: ubuntu-latest - if: failure() || cancelled() - needs: - - deploy - steps: - - uses: mattermost/actions/delivery/update-commit-status@fec7b836001c9380d4bfaf28d443945c103a098c - env: - GITHUB_TOKEN: ${{ github.token }} - with: - repository_full_name: ${{ github.repository }} - commit_sha: ${{ github.event.pull_request.head.sha }} - context: "docs-preview / preview" - description: "Docs preview build for ${{ github.event.pull_request.head.sha }} failed" - status: failure - - update-success-status: - runs-on: ubuntu-latest - if: success() - needs: - - deploy - steps: - - uses: mattermost/actions/delivery/update-commit-status@fec7b836001c9380d4bfaf28d443945c103a098c - env: - GITHUB_TOKEN: ${{ github.token }} - with: - repository_full_name: ${{ github.repository }} - commit_sha: ${{ github.event.pull_request.head.sha }} - context: "docs-preview / preview" - description: "Docs preview build for ${{ github.event.pull_request.head.sha }} succeeded" - status: success - target_url: "http://${{ vars.DOCS_PREVIEW_BUCKET_NAME }}.s3-website-us-east-1.amazonaws.com/mattermost/pr-${{ github.event.number }}/" + STATUS_CONTEXT: docs-preview diff --git a/docs/develop/integrate/plugins/interactive-messages/index.md b/docs/develop/integrate/plugins/interactive-messages/index.md index fddd692dfd20..ec6f9bfd2f4e 100644 --- a/docs/develop/integrate/plugins/interactive-messages/index.md +++ b/docs/develop/integrate/plugins/interactive-messages/index.md @@ -17,6 +17,12 @@ To try it out, you can use this [Matterpoll plugin](https://github.com/matterpol ![image](poll.png) +## Markdown action buttons + +In addition to message-attachment buttons and menus, you can embed interactive affordances directly in a post's markdown body using `mmaction://` links backed by a `mm_blocks_actions` post prop. This is useful when a short message reads naturally with an inline "Approve" or "Reject" link and a full message attachment isn't warranted. + +See [markdown action buttons](/developers/integrate/reference/markdown-actions) for the full schema, limits, and end-to-end flow. + ## Message buttons Add message buttons as `actions` in your integration [message attachments](https://docs.mattermost.com/developer/message-attachments.html). diff --git a/docs/develop/integrate/reference/markdown-actions/index.md b/docs/develop/integrate/reference/markdown-actions/index.md new file mode 100644 index 000000000000..51af5547f7c2 --- /dev/null +++ b/docs/develop/integrate/reference/markdown-actions/index.md @@ -0,0 +1,227 @@ +--- +title: "Markdown action buttons" +description: "Markdown action buttons let an integration turn an inline post-markdown link into an action affordance. Clicking the link dispatches a post action to the integration's endpoint instead of navigating away, expanding interactivity beyond message attachments." +sidebar_label: "Markdown action buttons" +sidebar_position: 45 +--- + +:::note Part of a broader framework +Markdown action buttons are one binding surface in a broader Interactive Messages framework under active development. Additional binding surfaces and action types are planned for future iterations. +::: + +Use markdown action buttons to add inline, in-text affordances to a post — without using a message attachment. They're useful when: + +- A short message reads naturally with an "Approve" or "Reject" inline link. +- An integration wants to mix narrative text and action affordances in the same post body. +- The visual weight of a full message attachment isn't warranted. + +For attachment-style buttons and menus, see [interactive messages](/developers/integrate/plugins/interactive-messages). + +## How it works + +A markdown action has two parts: + +1. A markdown link in the post body using the `mmaction://` scheme, where the link host is the action ID: + + ```text + [Approve](mmaction://approve?ticket=ISS-101) + ``` + +2. A matching entry in the post's `props.mm_blocks_actions` registry that tells the server what to do when the link is clicked: + + ```json + { + "mm_blocks_actions": { + "approve": { + "type": "external", + "url": "https://integration.example.com/hook/approve", + "context": {"project": "Demo Project"} + } + } + } + ``` + +The client renders the link as a button. Clicking it dispatches a request to the Mattermost server, which forwards the call to the integration's `url` along with merged query parameters and any server-side context. + +## Example post payload + +The following payload posts a message with two markdown action buttons. The body markdown references action IDs defined in `mm_blocks_actions`. + +```json +{ + "channel_id": "qmd5oqtwoibz8cuzxzg5ekshgr", + "message": "Ticket ISS-101 needs review: [Approve](mmaction://approve?ticket=ISS-101) [Reject](mmaction://reject?ticket=ISS-101)", + "props": { + "mm_blocks_actions": { + "approve": { + "type": "external", + "url": "https://integration.example.com/hook/approve", + "context": {"project": "Demo Project"} + }, + "reject": { + "type": "external", + "url": "https://integration.example.com/hook/reject", + "context": {"project": "Demo Project"} + } + } + } +} +``` + +You can send this payload using the [create post REST API](https://api.mattermost.com/#operation/CreatePost), an [incoming webhook](/developers/integrate/webhooks/incoming), or from a [plugin](/developers/integrate/plugins/components/server). + +### Submit using the REST API + +```bash +curl -X POST $MM_URL/api/v4/posts \ + -H "Authorization: Bearer $BOT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "channel_id": "'$CHANNEL_ID'", + "message": "Ticket ISS-101 needs review: [Approve](mmaction://approve?ticket=ISS-101) [Reject](mmaction://reject?ticket=ISS-101)", + "props": { + "mm_blocks_actions": { + "approve": { + "type": "external", + "url": "https://integration.example.com/hook/approve", + "context": {"project": "Demo Project"} + }, + "reject": { + "type": "external", + "url": "https://integration.example.com/hook/reject", + "context": {"project": "Demo Project"} + } + } + } + }' +``` + +### Submit from a plugin + +A server-side plugin creates posts with markdown actions through the existing `API.CreatePost` / `API.UpdatePost` interfaces. The action `url` typically points at the plugin's own HTTP handler. + +```go +post := &model.Post{ + ChannelId: channelID, + UserId: p.botID, + Message: "Ticket ISS-101 needs review: " + + "[Approve](mmaction://approve?ticket=ISS-101) " + + "[Reject](mmaction://reject?ticket=ISS-101)", + Props: model.StringInterface{ + "mm_blocks_actions": map[string]any{ + "approve": map[string]any{ + "type": "external", + "url": fmt.Sprintf("/plugins/%s/inline_action/approve", manifest.Id), + "context": map[string]any{"project": "Demo Project"}, + }, + "reject": map[string]any{ + "type": "external", + "url": fmt.Sprintf("/plugins/%s/inline_action/reject", manifest.Id), + "context": map[string]any{"project": "Demo Project"}, + }, + }, + }, +} +_, err := p.API.CreatePost(post) +``` + +Plugin updates to `mm_blocks_actions` via `UpdatePost` are accepted only when the updated value passes validation. Removal of the `mm_blocks_actions` prop by non-integration sessions is restricted to prevent dropping or corrupting actions on posts owned by another integration. + +## Link syntax + +```text +[