Skip to content

MM-67455: DM connected users when mentioned in confluence#229

Merged
nang2049 merged 6 commits into
MM-68853-forge-cloud-migrationfrom
MM-67455-at-mentions-for-connected-user
Jun 4, 2026
Merged

MM-67455: DM connected users when mentioned in confluence#229
nang2049 merged 6 commits into
MM-68853-forge-cloud-migrationfrom
MM-67455-at-mentions-for-connected-user

Conversation

@nang2049

@nang2049 nang2049 commented May 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacked on top of #228

When a Mattermost user is mentioned in a Confluence page or comment the plugin now sends them a direct message from the bot linking back to the page. Works on both Confluence Cloud via the Forge bridge from MM-68853 and Confluence Server.

  • Cloud fetches ADF body via GET /wiki/api/v2/{pages,footer-comments,inline-comments}/{id}?body-format=atlas_doc_format and wait for {type: "mention", attrs: {id}} nodes.

  • DC fetches storage-format body via GET /rest/api/content/{id}?expand=body.storage and extracts user ID from <ri:user ri:account-id="…"/> and legacy <ri:user ri:userkey="…"/>.

  • New slash subcommand: /confluence notifications on|off

  • Setting is stored in KV per Mattermost user

  • Existing slash command UX fixes from thread in Mattermost https://hub.mattermost.com/pde/pl/tfzomf7r33dffn5artkq645wno install hidden from non-admins

  • Added granular scopes read:page:confluence and read:comment:confluence to the OAuth consent in instance_cloud.go. The v2 endpoints we now call don't accept the legacy read:confluence-content.all scope.

  • Existing connected users have tokens without these scopes so their DMs will not fire until they reconnect. To handle this
    the Cloud client returns ErrCloudInsufficientScope on 403. When the dispatcher sees it it DMs the user telling them to reconnect.

  • When the user isn't a connected Mattermost user the existing webhook handler already falls back to AdminAPIToken for channel notifications. We now do the same for this mention dispatch. This dose not work on Cloud today. It would require shipping a Forge admin scoped token and I will open a ticket to follow up.

Manual QA on Cloud:

  • Connected user mentions another connected user in a page: recipient gets DM with page link
  • Connected user mentions in a footer comment: recipient gets DM
  • Connected user mentions in an inline comment: recipient gets DM
  • User mentions themself: no DM
  • User runs /confluence notifications off: no DM
  • Pre upgrade user mentioned: no DM, but user gets reconnect prompt once

Manual QA on DC:

  • Same as above
  • Mention from non connected user with AdminAPIToken: recipient gets DM

Ticket Link

https://mattermost.atlassian.net/browse/MM-67455

Change Impact: 🔴 High

Reasoning: This PR introduces cross-cutting mention-based DM notifications that touch authentication (new OAuth scopes), event ingestion (Forge bridge), HTTP clients for Cloud/DC, parsing logic (ADF/XHTML), command UX, KV persistence, and DM dispatch—affecting core notification flows and connection resolution across multiple modules.

Regression Risk: High — changes modify connection storage semantics (reverse mapping skip for admin), add asynchronous DM dispatch paths, and introduce multiple mention-extraction implementations and OAuth-scope-dependent behaviors (ErrCloudInsufficientScope) that can break existing notification delivery or require user re-authentication; these touch widely-used flows and have numerous integration points.

** QA Recommendation:** Do not skip manual QA. Recommended tests: (1) Cloud ADF mention extraction (pages, footer comments, inline comments) including actor self-mentions and deduplication; (2) DC storage-format extraction including legacy ri:user keys; (3) Forge enrichment and drain behavior with storage limits and failure modes; (4) DM dispatch end-to-end with per-user notifications enabled/disabled and AdminAPIToken fallback on DC (and confirm Cloud fallback behavior); (5) behavior when Cloud tokens lack new scopes and the reconnect prompt flow; (6) regression tests for existing notification flows and StoreConnection reverse-mapping change; (7) slash-command UX and settings persistence; (8) concurrent/async dispatch error handling and logging. Prioritize integration tests and staged rollout to catch cross-layer regressions.

Generated by CodeRabbitAI

@nang2049
nang2049 requested a review from a team as a code owner May 26, 2026 14:11
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 758dffae-900c-4c9c-b9c6-a252535ba0bf

📥 Commits

Reviewing files that changed from the base of the PR and between 05204b6 and d34498b.

📒 Files selected for processing (2)
  • forge/README.md
  • forge/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • forge/README.md
  • forge/src/index.ts

📝 Walkthrough

Walkthrough

Adds mention-DM notifications: extract account IDs from Cloud ADF and Server XHTML, per-user opt settings in KV, SendMentionDMs service, REST client surface for mention resolution, webhook/poller integration to dispatch DMs, slash commands to control preferences, and supporting docs/config updates.

Changes

Mention Notification System

Layer / File(s) Summary
Mention extraction utilities
server/service/adf.go, server/service/adf_test.go, server/service/storage_xhtml.go, server/service/storage_xhtml_test.go
ADF and XHTML storage parsing with recursive node walking and bot mention filtering; comprehensive tests for nil/empty/nested/deduplicated and bot-excluded mentions.
Mention settings and DM dispatch service
server/service/mention_settings.go, server/service/mentions.go, server/service/mentions_test.go
KV-backed per-user settings defaulting to enabled; SendMentionDMs deduplicates recipients, skips actor/blank IDs, resolves Mattermost users, filters by opt-in, opens DM channels and posts formatted messages; tests cover dedupe and opt-out.
REST client interface and implementations
server/client.go, server/client_cloud.go, server/client_server.go
RESTService interface extended with MentionAccountIDsInPage and MentionAccountIDsInComment; Cloud client stubs return unsupported (mentions from Forge events); Server client fetches expanded storage and extracts via storage utility.
Forge event model and enrichment
server/serializer/confluence_forge.go, forge/src/index.ts
ForgeContent adds Extensions.location; ForgeEvent gains ActorAccountID() and MentionPageContext(); enqueue enriches events with ADF body, enforces storage limits, and register/drain gained logging and force re-registration behavior.
Cloud poller dispatch
server/forge_poller.go, forge/src/index.ts
Poller/drain logging and gating; dispatchMentionDMs parses enriched ADF, extracts mention IDs, determines page/comment context, and calls SendMentionDMs for recipients.
Server webhook mention dispatch
server/confluence_server.go
Webhook handler now starts asynchronous dispatchServerMentionDMs and dispatchServerMentionDMsWithAPIToken after notifications; includes GetMentionAccountIDsWithAPIToken helper to fetch body.storage and extract mention IDs.
Slash command interface
server/command.go
Adds `/confluence settings notifications [on
Store, tests, flow, docs, manifest
server/store/store.go, server/service/notification_test.go, server/flow.go, plugin.json, forge/manifest.yml, forge/README.md
Store skips admin sentinel reverse mapping; test mocks allow log calls; flow step simplified; README expanded; manifest adds read:page/comment scopes; plugin help text reformatted.

Sequence Diagram(s)

sequenceDiagram
  participant ConfluenceWebhook
  participant ConfluenceClient
  participant MentionExtraction
  participant SendMentionDMs
  participant MattermostDM
  ConfluenceWebhook->>ConfluenceClient: Fetch page/comment with expanded storage
  ConfluenceClient->>MentionExtraction: Extract mention account IDs
  MentionExtraction-->>SendMentionDMs: []accountID
  SendMentionDMs->>SendMentionDMs: De-duplicate & filter actor
  SendMentionDMs->>SendMentionDMs: Check KV opt-in
  SendMentionDMs->>MattermostDM: Create DM post for enabled recipients
  MattermostDM-->>SendMentionDMs: success/error
Loading
sequenceDiagram
  participant ForgePoller
  participant ForgeQueue
  participant ADFParsing
  participant SendMentionDMs
  participant MattermostDM
  ForgePoller->>ForgeQueue: Drain queued events
  ForgeQueue-->>ForgePoller: events (with enriched ADF body)
  ForgePoller->>ADFParsing: Extract mention account IDs from ADF
  ADFParsing-->>SendMentionDMs: []accountID
  SendMentionDMs->>MattermostDM: Create DM posts for enabled recipients
  MattermostDM-->>SendMentionDMs: success/error
Loading

🎯 4 (Complex) | ⏱️ ~60 minutes

🐰 A rabbit hops through mention threads so fine,
Extracting IDs from storage, ADF, and line,
Settings and DMs with opt-in control true,
From Confluence to Mattermost, mentions slip through! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main feature: sending direct messages to Mattermost users when they are mentioned in Confluence pages or comments.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch MM-67455-at-mentions-for-connected-user

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
server/service/mentions_test.go (1)

68-69: ⚡ Quick win

Assert exact DM count to prevent duplicate-send regressions.

AssertCalled is too weak here; use an exact call count so duplicate DM bugs fail the test.

Proposed fix
-	api.AssertCalled(t, "CreatePost", mock.Anything)
+	api.AssertNumberOfCalls(t, "CreatePost", 1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/service/mentions_test.go` around lines 68 - 69, Replace the weak
api.AssertCalled(t, "CreatePost", mock.Anything) with an exact-call assertion to
prevent duplicate DM regressions: call api.AssertNumberOfCalls(t, "CreatePost",
1) (or the equivalent AssertNumberOfCalls method on the mock) so the test fails
if CreatePost is invoked more than once.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/command.go`:
- Around line 63-76: The handlers map on ConfluenceCommandHandler is missing the
"edit" entry advertised elsewhere; add an "edit" key to the handlers map and
point it to the existing edit handler function (e.g., editSubscription or
executeEdit — use whatever function implements the edit flow), or implement that
handler if it doesn't exist, then register it as "edit": editSubscription (or
"edit": executeEdit) so the /confluence edit command routes to the edit flow
instead of falling through to invalidCommand.

In `@server/confluence_server.go`:
- Around line 288-292: GetMentionAccountIDsWithAPIToken currently returns nil
error for non-200 responses because it only returns the err from
MakeHTTPCallWithAPIToken, which is nil for 403/404; change it to treat any
statusCode != http.StatusOK as an error by returning a constructed error (e.g.
fmt.Errorf("failed to fetch content %s: status %d", contentID, statusCode)) so
callers don’t silently proceed; update the call sites that use
GetMentionAccountIDsWithAPIToken (the API-token path around the other
mention-fetching logic) to handle and propagate this error instead of treating a
nil error as success.

In `@server/forge_poller.go`:
- Line 196: The reconnect-notice dedupe check in the KVGet → CreatePost → KVSet
sequence is not atomic and can race when dispatchMentionDMs is invoked
concurrently; serialize that critical section by adding per-actor
synchronization (e.g., a per-actor mutex or a single map of locks) and acquire
the lock around the check/send/set sequence inside the function that currently
does KVGet, CreatePost, KVSet so only one goroutine for a given actor can
perform the read→create→write path at a time; ensure the lock is released after
KVSet (or on error) and reference dispatchMentionDMs and the
KVGet/CreatePost/KVSet calls when updating the code.

In `@server/service/mentions.go`:
- Around line 36-65: The loop over p.AccountIDs can send duplicate DMs when the
same accountID appears multiple times; add a local seen set
(map[string]struct{}) at the start of the dispatch to skip duplicates: before
resolving store.GetMattermostUserIDFromConfluenceID for an accountID, check and
mark it in seen so each accountID is processed at most once per invocation; keep
the existing checks (skip when accountID == p.ActorAccountID, when mmUserIDPtr
nil/empty, when !IsMentionNotificationEnabled, and when
GetDirectChannel/CreatePost fail) and use buildMentionMessage/preserving the
same usage of config.Mattermost.GetDirectChannel and CreatePost.

---

Nitpick comments:
In `@server/service/mentions_test.go`:
- Around line 68-69: Replace the weak api.AssertCalled(t, "CreatePost",
mock.Anything) with an exact-call assertion to prevent duplicate DM regressions:
call api.AssertNumberOfCalls(t, "CreatePost", 1) (or the equivalent
AssertNumberOfCalls method on the mock) so the test fails if CreatePost is
invoked more than once.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6235fd94-b6f0-4c07-8f67-c9175058113b

📥 Commits

Reviewing files that changed from the base of the PR and between e8194d5 and de13583.

📒 Files selected for processing (15)
  • server/client.go
  • server/client_cloud.go
  • server/client_server.go
  • server/command.go
  • server/confluence_server.go
  • server/forge_poller.go
  • server/instance_cloud.go
  • server/serializer/confluence_forge.go
  • server/service/adf.go
  • server/service/adf_test.go
  • server/service/mention_settings.go
  • server/service/mentions.go
  • server/service/mentions_test.go
  • server/service/storage_xhtml.go
  • server/service/storage_xhtml_test.go

Comment thread server/command.go
Comment on lines 63 to 76
var ConfluenceCommandHandler = Handler{
handlers: map[string]HandlerFunc{
"list": listChannelSubscription,
"unsubscribe": deleteSubscription,
"install": showInstallEditionPrompt,
"install/cloud": showInstallCloudHelp,
"install/server": showInstallServerHelp,
"connect": executeConnect,
"disconnect": executeDisconnect,
"help": confluenceHelpCommand,
"list": listChannelSubscription,
"unsubscribe": deleteSubscription,
"install": showInstallEditionPrompt,
"install/cloud": showInstallCloudHelp,
"install/server": showInstallServerHelp,
"connect": executeConnect,
"disconnect": executeDisconnect,
"help": confluenceHelpCommand,
"notifications": executeNotificationsStatus,
"notifications/on": executeNotificationsOn,
"notifications/off": executeNotificationsOff,
},

@coderabbitai coderabbitai Bot May 27, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Register the edit subcommand in the router.

Line 40 and the autocomplete entries below now advertise /confluence edit, but this map still has no "edit" entry, so that command will fall through to invalidCommand instead of opening the edit flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/command.go` around lines 63 - 76, The handlers map on
ConfluenceCommandHandler is missing the "edit" entry advertised elsewhere; add
an "edit" key to the handlers map and point it to the existing edit handler
function (e.g., editSubscription or executeEdit — use whatever function
implements the edit flow), or implement that handler if it doesn't exist, then
register it as "edit": editSubscription (or "edit": executeEdit) so the
/confluence edit command routes to the edit flow instead of falling through to
invalidCommand.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed valid but pre-existing on master, I can flag as a follow up rather than expanding scope here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nang2049, understood — keeping this PR focused makes sense. Would you like me to open a GitHub issue to track the missing "edit" handler registration in ConfluenceCommandHandler so it doesn't get lost?

Comment thread server/confluence_server.go
Comment thread server/forge_poller.go
Comment thread server/service/mentions.go

@avasconcelos114 avasconcelos114 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome improvement! I bet this will come in handy to a lot of people

Just leaving a suggestion for nesting the notification slash command for future-proofing and consistency with other plugins

Comment thread server/serializer/confluence_forge.go Outdated
if e.Content == nil {
return ""
}
return e.Content.Extensions.Location

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any chance we end up trying to read a nil value since both Extensions and Location have omitempty marked on them? (not entirely familiar with the shape of Forge event bodies and what defaults it may send so not sure if this is a valid concern or not)

Comment thread server/command.go Outdated
"* `/confluence list` - List all subscriptions for the current channel.\n" +
"* `/confluence edit \"<name>\"` - Edit the subscription settings associated with the given subscription name.\n"
"* `/confluence edit \"<name>\"` - Edit the subscription settings associated with the given subscription name.\n" +
"* `/confluence notifications [on|off]` - Show or change whether you get a direct message when you are @-mentioned in Confluence.\n"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if it would be good to follow GitHub's pattern and nest this under a settings command family in case we want to add other types of personalised features

Image

@nang2049

nang2049 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

@avasconcelos114 ready for review

@nang2049
nang2049 requested a review from avasconcelos114 June 3, 2026 13:16
@nang2049

nang2049 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
server/serializer/confluence_forge.go (1)

55-61: 💤 Low value

Clarify/remove unused ForgeExtensions.Location

  • server/serializer/confluence_forge.go defines ForgeExtensions.Location, but there are no Go references to Extensions.Location beyond the inline comment stating it’s "footer" for comment-related payloads.
  • No nil-deref risk: Location is a non-pointer string with omitempty (missing fields deserialize to "").
  • Keep this field only if it’s required to mirror the Forge event payload; otherwise remove it or add a stronger rationale for why it must remain.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/serializer/confluence_forge.go` around lines 55 - 61, The
ForgeExtensions.Location field in type ForgeExtensions (used by the parent
struct field Extensions ForgeExtensions) appears unused; either remove the
Location string field to avoid unnecessary struct noise or add a clear comment
explaining it’s required to mirror the incoming Forge event payload (and keep
json:"location,omitempty") so future readers know it’s intentionally preserved;
update any related tests or JSON examples accordingly and ensure the parent
struct still serializes/deserializes correctly after the change.
forge/src/index.ts (1)

119-121: 💤 Low value

Update the stale register comment.

The comment still states registration is a one-shot that is "refused" once mm.registered is set, but the handler now allows overwriting via {"force": true}.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@forge/src/index.ts` around lines 119 - 121, Update the stale comment for
register to reflect that registration is no longer strictly one-shot: mention
that while mm.registered indicates prior registration, the handler accepts an
overwrite when the request includes {"force": true}; reference the register
handler and mm.registered flag and note the new behavior and how to re-register
(standard flow vs forced overwrite) so the comment matches the current
implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@forge/src/index.ts`:
- Around line 30-38: The storage.set call in enqueue can fail for values >240
KiB and currently rethrows (dropping the event); modify the enqueue flow around
enrichWithBody and storage.set to detect serialized size before write and fall
back to a trimmed payload: after calling enrichWithBody(evt) compute the byte
size of the object you intend to store (e.g., Buffer.byteLength(JSON.stringify({
event: enriched, context, enqueuedAt: Date.now() }))), and if it exceeds
~240*1024 bytes, remove or replace enriched.content.body with a lightweight
marker/summary or move only metadata (e.g., title, url, contentSnippet) and
optionally set a flag like bodyStripped=true so the consumer can fetch the full
body lazily; then attempt storage.set with the trimmed object and only throw if
that fails for non-size reasons. Ensure you reference and update the same
variables used in this block (enrichWithBody, enriched, storage.set, key,
context) so the retry/fallback uses the trimmed payload and preserves delivery.

In `@server/flow.go`:
- Around line 785-790: stepCloudForgeBridge currently renders
[{{.ForgeInstallURL}}]({{.ForgeInstallURL}}) unconditionally which produces a
broken link when GetForgeInstallURL() returns an empty string (ForgeInstallURL
not validated by Configuration.IsValid()). Fix by adding a guard or fallback: in
stepCloudForgeBridge (or the template data preparation) check
GetForgeInstallURL() / c.ForgeInstallURL and if empty render a non-link fallback
text like "Install the bridge on your site (installation URL not available)" or
omit the markdown link entirely; alternatively add validation in
Configuration.IsValid() to ensure ForgeInstallURL is set for Cloud installs so
the template never receives an empty value. Ensure references to
GetForgeInstallURL(), stepCloudForgeBridge, Configuration.IsValid(), and the
ForgeInstallURL field are updated accordingly.

---

Nitpick comments:
In `@forge/src/index.ts`:
- Around line 119-121: Update the stale comment for register to reflect that
registration is no longer strictly one-shot: mention that while mm.registered
indicates prior registration, the handler accepts an overwrite when the request
includes {"force": true}; reference the register handler and mm.registered flag
and note the new behavior and how to re-register (standard flow vs forced
overwrite) so the comment matches the current implementation.

In `@server/serializer/confluence_forge.go`:
- Around line 55-61: The ForgeExtensions.Location field in type ForgeExtensions
(used by the parent struct field Extensions ForgeExtensions) appears unused;
either remove the Location string field to avoid unnecessary struct noise or add
a clear comment explaining it’s required to mirror the incoming Forge event
payload (and keep json:"location,omitempty") so future readers know it’s
intentionally preserved; update any related tests or JSON examples accordingly
and ensure the parent struct still serializes/deserializes correctly after the
change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3a451414-5e75-4d6d-8483-4ce345b45d09

📥 Commits

Reviewing files that changed from the base of the PR and between de13583 and 05204b6.

📒 Files selected for processing (14)
  • forge/README.md
  • forge/manifest.yml
  • forge/src/index.ts
  • plugin.json
  • server/client_cloud.go
  • server/command.go
  • server/confluence_server.go
  • server/flow.go
  • server/forge_poller.go
  • server/serializer/confluence_forge.go
  • server/service/mentions.go
  • server/service/mentions_test.go
  • server/service/notification_test.go
  • server/store/store.go
✅ Files skipped from review due to trivial changes (3)
  • forge/manifest.yml
  • plugin.json
  • forge/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • server/service/mentions_test.go
  • server/service/mentions.go
  • server/confluence_server.go

Comment thread forge/src/index.ts
Comment thread server/flow.go
Comment on lines +785 to +790
text := "##### :white_check_mark: Install the Forge bridge for event delivery\n\n" +
"Event subscriptions on Confluence Cloud run through a small Forge app we publish.\n\n" +
"1. Install the bridge on your site: [{{.ForgeInstallURL}}]({{.ForgeInstallURL}}).\n" +
"2. After installation, open the install log (or run `forge webtrigger` against your tenant) and copy the **drain URL** and **register URL**.\n" +
"3. Click **Register bridge** below and paste both URLs. The plugin will register itself with the bridge and start polling for events.\n\n" +
":lock: The shared secret never leaves the plugin — it is sent directly from the server to your bridge's register endpoint."

@coderabbitai coderabbitai Bot Jun 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect GetForgeInstallURL to confirm it can never be empty.
ast-grep --pattern 'func ($_ $_) GetForgeInstallURL() $_ { $$$ }'
rg -nP -C3 'GetForgeInstallURL' --type=go

Repository: mattermost/mattermost-plugin-confluence

Length of output: 1206


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "ForgeInstallURL" --type=go

Repository: mattermost/mattermost-plugin-confluence

Length of output: 632


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "ForgeInstallURL" --type=go

Repository: mattermost/mattermost-plugin-confluence

Length of output: 632


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "ForgeInstallURL" --type=go

Repository: mattermost/mattermost-plugin-confluence

Length of output: 632


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all config/UI/default references to ForgeInstallURL (including non-Go files)
rg -n --hidden --glob '!**/node_modules/**' -S "forgeinstallurl|ForgeInstallURL" .

# Inspect the GetForgeInstallURL implementation and surrounding config wiring
sed -n '1,140p' server/config/main.go
sed -n '140,260p' server/config/main.go

# Inspect the flow state/template wiring around ForgeInstallURL usage
sed -n '130,260p' server/flow.go
sed -n '740,830p' server/flow.go

Repository: mattermost/mattermost-plugin-confluence

Length of output: 11110


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for where forgeinstallurl / ForgeInstallURL is referenced besides the getter/flow text.
rg -n --hidden --glob '!**/node_modules/**' -S "forgeinstallurl|ForgeInstallURL" .

# Inspect plugin.json around the ForgeInstallURL config definition
sed -n '1,120p' plugin.json
sed -n '120,220p' plugin.json

# Inspect any config pages / handlers that might populate default values
ls -la server/config
rg -n --hidden --glob '!**/node_modules/**' "ForgeDrainURL|ForgeSharedSecret|forgedrainurl|forgesharedsecret" server/config server -S

Repository: mattermost/mattermost-plugin-confluence

Length of output: 5911


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find any template usage for ForgeInstallURL
rg -n "{{\\.ForgeInstallURL}}" -S server

# Inspect the rest of server/flow.go around the Forge bridge registration step for any validations
sed -n '820,930p' server/flow.go

# Look for any explicit config validation for ForgeInstallURL
rg -n "ForgeInstallURL" server/config server/flow.go server/forge_poller.go server/plugin.go -S

Repository: mattermost/mattermost-plugin-confluence

Length of output: 180


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find template usage for ForgeInstallURL (literal)
rg -n -F "{{.ForgeInstallURL}}" server || true
rg -n "ForgeInstallURL" server/config server/flow.go server/plugin.go server/forge_poller.go || true

# Show the full cloud-forge-bridge and submitCloudOAuthConfig sections
sed -n '650,860p' server/flow.go

Repository: mattermost/mattermost-plugin-confluence

Length of output: 8716


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find template usage for ForgeInstallURL (literal)
rg -n -F "{{.ForgeInstallURL}}" server || true
rg -n "ForgeInstallURL" server/config server/flow.go server/plugin.go server/forge_poller.go || true

# Show the full cloud-forge-bridge and submitCloudOAuthConfig sections
sed -n '650,860p' server/flow.go

Repository: mattermost/mattermost-plugin-confluence

Length of output: 8716


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Where IsValid is used
rg -n "IsValid\\(\\) error" -S server || true
rg -n "\\.IsValid\\(\\)" -S . || true

# Inspect server/config/main.go and plugin startup around config loading/validation
rg -n "ProcessConfiguration\\(|Sanitize\\(|GetConfig\\(" -S server || true

# Inspect server/plugin.go which likely loads/validates config
sed -n '1,220p' server/plugin.go
sed -n '220,520p' server/plugin.go

Repository: mattermost/mattermost-plugin-confluence

Length of output: 9579


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Where IsValid is used
rg -n "IsValid\\(\\) error" -S server || true
rg -n "\\.IsValid\\(\\)" -S . || true

# Inspect server/config/main.go and plugin startup around config loading/validation
rg -n "ProcessConfiguration\\(|Sanitize\\(|GetConfig\\(" -S server || true

# Inspect server/plugin.go which likely loads/validates config
sed -n '1,220p' server/plugin.go
sed -n '220,520p' server/plugin.go

Repository: mattermost/mattermost-plugin-confluence

Length of output: 9579


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect config validation logic
sed -n '1,120p' server/config/main.go

# Confirm there are no other assignments/defaults for ForgeInstallURL
rg -n "ForgeInstallURL\\s*=" server || true
rg -n "\"forgeinstallurl\"|ForgeInstallURL" server/config server || true

Repository: mattermost/mattermost-plugin-confluence

Length of output: 3727


Prevent broken “Forge Bridge Install URL” link in the Cloud wizard.

stepCloudForgeBridge renders [{{.ForgeInstallURL}}]({{.ForgeInstallURL}}) unconditionally. GetForgeInstallURL() only TrimSpaces c.ForgeInstallURL, and Configuration.IsValid() does not validate ForgeInstallURL, so it can be empty for Cloud installs—producing a broken/empty link while still prompting the user to install. Restore a fallback/guard (or validate ForgeInstallURL for Cloud setup) to avoid this.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/flow.go` around lines 785 - 790, stepCloudForgeBridge currently
renders [{{.ForgeInstallURL}}]({{.ForgeInstallURL}}) unconditionally which
produces a broken link when GetForgeInstallURL() returns an empty string
(ForgeInstallURL not validated by Configuration.IsValid()). Fix by adding a
guard or fallback: in stepCloudForgeBridge (or the template data preparation)
check GetForgeInstallURL() / c.ForgeInstallURL and if empty render a non-link
fallback text like "Install the bridge on your site (installation URL not
available)" or omit the markdown link entirely; alternatively add validation in
Configuration.IsValid() to ensure ForgeInstallURL is set for Cloud installs so
the template never receives an empty value. Ensure references to
GetForgeInstallURL(), stepCloudForgeBridge, Configuration.IsValid(), and the
ForgeInstallURL field are updated accordingly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Operator workflow assumes the System Console level Forge Bridge Install URL is populated before the install-cloud wizard is run since the field is secret: true and surfaced in System Console > Plugins > Confluence. If it's empty the wizard renders an empty markdown link

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, something went wrong! Please try again later. 🐰 💔

@nang2049

nang2049 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nang2049
nang2049 merged commit 836a6dc into MM-68853-forge-cloud-migration Jun 4, 2026
9 checks passed
@nang2049
nang2049 deleted the MM-67455-at-mentions-for-connected-user branch June 4, 2026 09:17
nang2049 added a commit that referenced this pull request Jun 12, 2026
…ridge POC (#228)

* MM-68853: Confluence Cloud install path via OAuth 2.0 (3LO) + Forge bridge POC

* MM-68853: Confluence Cloud install path via OAuth 2.0 (3LO) + Forge bridge POC

* coderabbit comments

* MM-68853: deliver Forge events to subscribed channels end-to-end

* coderabbit feedbadck

* PR feedback

* MM-67455: DM connected users when mentioned in confluence (#229)

* MM-67455: DM connected users when @-mentioned in Confluence

* PR feedback

* Linter

* coderabbitai feedback

* README.md

---------

Co-authored-by: Nevyana Angelova <nevyangelova@192.168.100.47>

* Update README.md

* fix broen link

* Send a DM notifying admins when the secret was regenerated and access was lost

* fix spam

* fix forge docs

* switch app to production

---------

Co-authored-by: Nevyana Angelova <nevyangelova@192.168.100.47>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants