Skip to content

Add channel settings tab demo#204

Open
nickmisasi wants to merge 8 commits into
masterfrom
channel-settings-pluggable
Open

Add channel settings tab demo#204
nickmisasi wants to merge 8 commits into
masterfrom
channel-settings-pluggable

Conversation

@nickmisasi

@nickmisasi nickmisasi commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Register a ChannelSettingsTab pluggable to smoke-test the new channel settings tab plugin API
  • Add a minimal ChannelSettingsSmokeTest component that renders channel info, a dirty-state input, and a tab-switch error banner
  • Make GetTeams() calls non-fatal in ensureDemoUser, ensureDemoChannels, OnActivate, and OnDeactivate so the plugin can start even when the Teams table has NULL string columns (core server bug where CompanyName is NULL)
    Needs Support pluggable channel settings tabs mattermost#35591

Test plan

  • Build and deploy the plugin to a Mattermost instance with channel settings pluggable support
  • Open Channel Settings on a public/private channel and confirm the Smoke Test tab appears
  • Click the tab and verify it renders the channel display name, name, and ID
  • Type into the input to mark the tab dirty, then try switching tabs to confirm the unsaved-change warning
  • Verify the plugin starts cleanly even if GetTeams() returns an error (check server logs for warn-level messages instead of crashes)

🤖 Generated with Claude Code

Register a ChannelSettingsTab pluggable for smoke-testing the new
channel settings tab API. The component renders channel info, a
dirty-state input, and a tab-switch error banner.

Make GetTeams() calls non-fatal in ensureDemoUser, ensureDemoChannels,
OnActivate, and OnDeactivate so the plugin can start even when the
Teams table has NULL string columns (core server bug).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@nickmisasi
nickmisasi requested a review from hanzei as a code owner March 13, 2026 14:50
@coderabbitai

coderabbitai Bot commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Too much diff to scan? Review this PR in Change Stack to start with the highest-impact changes.

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request introduces a complete channel settings feature that allows per-channel customization through two UI tabs (schema-driven and custom). The server exposes four HTTP endpoints (GET/POST schema and custom state) with authorization guards. The client module provides typed fetch helpers, and React components render both predefined settings (accent colour, channel info) and a custom tab for channel notes and greeting configuration.

Changes

Channel Settings Capability

Layer / File(s) Summary
Type definitions and shared contracts
webapp/src/channel_settings/types.ts, go.mod
Type-only mirror of the host channel settings API defining ChannelSettingsValues, radio/custom settings, sections, tab variants, and handler contracts. Adds indirect test-support dependency github.com/stretchr/objx.
Server API endpoints and routing
server/channel_settings_api.go, server/http_hooks.go
Four HTTP handlers (GET/POST schema, GET/POST custom) with three authorization guards (requireUser, requireChannelReader, requireChannelManager) that persist per-channel KV data and route /channel_settings/{channel_id} endpoints.
Server API tests
server/channel_settings_api_test.go
Tests schema and custom state round-trip persistence, authorization validation (missing user, insufficient permissions), and malformed request handling via mocked Mattermost API.
Client communication module
webapp/src/channel_settings/client.ts
Typed fetch helpers (fetchSchemaValues, saveSchemaValues, fetchCustomState, saveCustomState) that construct plugin-scoped URLs, include CSRF credentials via Client4.getOptions, and parse JSON responses.
Schema-based settings components
webapp/src/channel_settings/accent_color_setting.tsx, webapp/src/channel_settings/channel_info_section.tsx, webapp/src/channel_settings/schema_tab.tsx
AccentColorSetting (colour swatch selector with hydration), ChannelInfoSection (read-only channel metadata), and buildSchemaTab (schema definition with Posting and Channel info sections).
Custom settings tab component
webapp/src/channel_settings/custom_tab.tsx
Stateful React component managing channel note (textarea) and pin-greeting (checkbox) with fetch/save/reset handlers, dirty tracking, loading/error states, and theme-aware styling via CSS variables.
Registration and plugin integration
webapp/src/channel_settings/register.ts, webapp/src/plugin.jsx, webapp/i18n/en.json
Exports registerChannelSettings helper that conditionally registers schema-based and custom tabs; wires registration into plugin initialization; provides English i18n strings for all channel settings labels and messages.

Sequence Diagram(s)

sequenceDiagram
    participant UIComponent as UI Component
    participant ClientModule as client.ts
    participant HTTPHandler as HTTP Handler
    participant KVStore as KV Store
    UIComponent->>ClientModule: fetchSchemaValues(channelId)
    ClientModule->>ClientModule: Build plugin URL
    ClientModule->>ClientModule: Get CSRF token via Client4.getOptions
    ClientModule->>HTTPHandler: GET /channel_settings/{channel_id}/schema
    HTTPHandler->>HTTPHandler: Authorize (requireChannelReader)
    HTTPHandler->>KVStore: Get schema data
    KVStore-->>HTTPHandler: Channel data map
    HTTPHandler-->>ClientModule: 200 JSON response
    ClientModule-->>UIComponent: Promise<ChannelSettingsValues>
    UIComponent->>ClientModule: saveSchemaValues(channelId, newValues)
    ClientModule->>HTTPHandler: POST /channel_settings/{channel_id}/schema
    HTTPHandler->>HTTPHandler: Authorize (requireChannelManager)
    HTTPHandler->>KVStore: Set schema data
    KVStore-->>HTTPHandler: Success
    HTTPHandler-->>ClientModule: 200 JSON response
    ClientModule-->>UIComponent: Promise<ChannelSettingsValues>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% 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
Title check ✅ Passed The title clearly and concisely describes the main change: adding a channel settings tab demo to test the new plugin API.
Description check ✅ Passed The description is directly related to the changeset, detailing the registration of channel settings tabs, component additions, and making GetTeams() calls non-fatal.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch channel-settings-pluggable

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
webapp/src/components/channel_settings_smoke_test.jsx (1)

17-24: Consider defensive access for channel properties.

While channel is marked as required in PropTypes, accessing nested properties without defensive checks could cause runtime errors if the channel object has an unexpected shape. For a smoke test component this is likely acceptable, but consider using optional chaining for robustness.

♻️ Optional defensive access
             <div style={{marginTop: '12px'}}>
-                <strong>{'Display Name: '}</strong>{channel.display_name}
+                <strong>{'Display Name: '}</strong>{channel?.display_name}
             </div>
             <div style={{marginTop: '4px'}}>
-                <strong>{'Channel Name: '}</strong>{channel.name}
+                <strong>{'Channel Name: '}</strong>{channel?.name}
             </div>
             <div style={{marginTop: '4px'}}>
-                <strong>{'Channel ID: '}</strong>{channel.id}
+                <strong>{'Channel ID: '}</strong>{channel?.id}
             </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/src/components/channel_settings_smoke_test.jsx` around lines 17 - 24,
Accessing channel.display_name, channel.name and channel.id without defensive
checks can throw if channel has an unexpected shape; update the JSX to use
optional chaining and sensible fallbacks (e.g. channel?.display_name ?? '' ,
channel?.name ?? '' , channel?.id ?? '') so missing properties render safely.
Locate the render usage of channel.display_name / channel.name / channel.id in
the channel settings smoke test component and replace direct property access
with the optional-chaining + fallback pattern to make the component robust.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@webapp/src/components/channel_settings_smoke_test.jsx`:
- Around line 17-24: Accessing channel.display_name, channel.name and channel.id
without defensive checks can throw if channel has an unexpected shape; update
the JSX to use optional chaining and sensible fallbacks (e.g.
channel?.display_name ?? '' , channel?.name ?? '' , channel?.id ?? '') so
missing properties render safely. Locate the render usage of
channel.display_name / channel.name / channel.id in the channel settings smoke
test component and replace direct property access with the optional-chaining +
fallback pattern to make the component robust.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6ff8b33f-4acf-4e51-a81e-8586416deed2

📥 Commits

Reviewing files that changed from the base of the PR and between 9c80775 and b82d856.

📒 Files selected for processing (4)
  • server/activate_hooks.go
  • server/configuration.go
  • webapp/src/components/channel_settings_smoke_test.jsx
  • webapp/src/plugin.jsx

@nickmisasi nickmisasi changed the title Add channel settings tab smoke test Add channel settings tab demo Mar 13, 2026

@hanzei hanzei left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure I agree with the non-fatal changes

Comment thread server/activate_hooks.go Outdated
msg := fmt.Sprintf("OnActivate: %s", manifest.Id)
if err := p.postPluginMessage(team.Id, msg); err != nil {
return errors.Wrap(err, "failed to post OnActivate message")
p.API.LogWarn("Failed to query teams OnActivate, skipping activation messages", "error", err.Error())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's strange that GetTeams fails. I wonder if we should still fail loudly as that makes things easier for QA to debug.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would love to see a typescript file. not a blocker

@hanzei hanzei added the 2: Dev Review Requires review by a core committer label Mar 19, 2026
nickmisasi and others added 3 commits March 25, 2026 14:22
- Register save/reset via registerSaveBarHandlers; remove inline tab-switch banner
- Set icon to fa fa-plug to match other demo plugin surfaces (MainMenu, channel header, etc.)

Made-with: Cursor
* feat: rewrite channel settings to schema-based webapp API

Migrate the demo plugin from the old custom-only channel settings API
(registerSaveBarHandlers) to the new schema-based registry API so the
plugin serves as a reference exemplar for both registration styles.

Register two channel settings tabs:
- A declarative schema tab ("Demo Channel Settings") with a Posting
  section (two radio settings + a custom accent-color setting) and a
  custom Channel info section. shouldRender hides it in DM/GM, the host
  owns the save bar, and persistence flows through onSave/loadValues.
- A fully custom tab ("Demo Advanced (Custom)") where the plugin renders
  the entire body, registers its own save/reset handlers, and reports
  dirty state via setUnsaved.

Persistence is backed by new plugin HTTP endpoints over the KV store
(GET/POST /channel_settings/{channel_id}/schema and /custom) guarded by
read (PermissionReadChannel) and write (Manage{Public,Private}
ChannelProperties) authorization. Removes the old smoke-test component.

Co-authored-by: Cursor <[email protected]>

* fix: make custom channel settings textarea theme-aware

Theme the "Channel note" textarea via Mattermost CSS variables so it
renders correctly on any theme, and add a visible translatable
placeholder (injectIntl) so it's readable on dark themes.

Co-authored-by: Cursor <[email protected]>

---------

Co-authored-by: Cursor <[email protected]>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
webapp/src/channel_settings/client.ts (1)

22-28: 💤 Low value

Consider error response handling.

The request<T> function uses a type assertion (as Promise<T>) without validating the response shape. If the server returns an error with an unexpected JSON structure, the typed result could cause runtime issues in consuming code.

For a production plugin, consider validating critical fields or wrapping the response:

async function request<T>(url: string, options: Options): Promise<T> {
    const res = await fetch(url, Client4.getOptions(options));
    if (!res.ok) {
        // Try to extract error message from response
        const errorText = await res.text().catch(() => 'Unknown error');
        throw new Error(`channel settings request failed: ${res.status} - ${errorText}`);
    }
    return res.json() as Promise<T>;
}

However, for a demo plugin this is acceptable as-is.

🤖 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 `@webapp/src/channel_settings/client.ts` around lines 22 - 28, The request<T>
function currently asserts the JSON response shape without handling error
bodies; update request<T> to attempt to read error details when res.ok is false
(e.g., call res.text() or parse res.json() safely) and include that detail in
the thrown Error, and optionally validate or guard the successful res.json()
before casting to T so callers don't get a misleading type assertion; modify the
function named request<T> in client.ts to extract and include response error
text/JSON in the error message and add a basic runtime check or explicit parsing
before returning the typed result.
server/channel_settings_api.go (1)

102-107: ⚡ Quick win

Move defer r.Body.Close() before decoding.

The defer statement should be placed immediately after you begin using the body, not after Decode. While this works in practice because Decode consumes the body, the conventional pattern is:

var values map[string]string
defer r.Body.Close()
if err := json.NewDecoder(r.Body).Decode(&values); err != nil {

This makes the resource cleanup intent clearer and matches idiomatic Go.

📋 Proposed fix
 func (p *Plugin) handleSaveChannelSettingsSchema(w http.ResponseWriter, r *http.Request) {
 	channelID := mux.Vars(r)["channel_id"]
 	if _, ok := p.requireChannelManager(w, r, channelID); !ok {
 		return
 	}
 
 	var values map[string]string
+	defer r.Body.Close()
 	if err := json.NewDecoder(r.Body).Decode(&values); err != nil {
 		http.Error(w, "Invalid request body", http.StatusBadRequest)
 		return
 	}
-	defer r.Body.Close()

Apply the same pattern to handleSaveChannelSettingsCustom at line 145.

🤖 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/channel_settings_api.go` around lines 102 - 107, Move the defer
r.Body.Close() to immediately after the request body is first used in the
handlers to follow idiomatic Go: in handleSaveChannelSettings (the block that
currently does json.NewDecoder(r.Body).Decode(&values)) place defer
r.Body.Close() before calling Decode so the body is scheduled to be closed as
soon as it’s referenced; apply the same change in
handleSaveChannelSettingsCustom to ensure both handlers defer closing r.Body
before decoding the JSON.
🤖 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/channel_settings_api.go`:
- Around line 84-88: In handleGetChannelSettingsSchema: stop pre-initializing
values to an empty map; declare it as var values map[string]string so
p.client.KV.Get(csSchemaKey(channelID), &values) can return nil without creating
an empty map, then after the GET check err (log and 500 on error) and explicitly
handle values == nil as the missing-key case (e.g., respond with 404 or an
empty-schema response as your API requires) instead of relying on the prior
redundant empty-map check; reference p.client.KV.Get, csSchemaKey, and the
values variable when making this change.

---

Nitpick comments:
In `@server/channel_settings_api.go`:
- Around line 102-107: Move the defer r.Body.Close() to immediately after the
request body is first used in the handlers to follow idiomatic Go: in
handleSaveChannelSettings (the block that currently does
json.NewDecoder(r.Body).Decode(&values)) place defer r.Body.Close() before
calling Decode so the body is scheduled to be closed as soon as it’s referenced;
apply the same change in handleSaveChannelSettingsCustom to ensure both handlers
defer closing r.Body before decoding the JSON.

In `@webapp/src/channel_settings/client.ts`:
- Around line 22-28: The request<T> function currently asserts the JSON response
shape without handling error bodies; update request<T> to attempt to read error
details when res.ok is false (e.g., call res.text() or parse res.json() safely)
and include that detail in the thrown Error, and optionally validate or guard
the successful res.json() before casting to T so callers don't get a misleading
type assertion; modify the function named request<T> in client.ts to extract and
include response error text/JSON in the error message and add a basic runtime
check or explicit parsing before returning the typed result.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 09d28ce8-462b-4d78-bdda-4b1181a6160c

📥 Commits

Reviewing files that changed from the base of the PR and between 0728245 and 0db03b5.

⛔ Files ignored due to path filters (1)
  • webapp/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • go.mod
  • server/channel_settings_api.go
  • server/channel_settings_api_test.go
  • server/http_hooks.go
  • webapp/i18n/en.json
  • webapp/src/channel_settings/accent_color_setting.tsx
  • webapp/src/channel_settings/channel_info_section.tsx
  • webapp/src/channel_settings/client.ts
  • webapp/src/channel_settings/custom_tab.tsx
  • webapp/src/channel_settings/register.ts
  • webapp/src/channel_settings/schema_tab.tsx
  • webapp/src/channel_settings/types.ts
  • webapp/src/plugin.jsx
✅ Files skipped from review due to trivial changes (1)
  • webapp/i18n/en.json

Comment thread server/channel_settings_api.go
nickmisasi and others added 3 commits June 9, 2026 10:35
Address @hanzei's PR #204 review feedback: GetTeams failing during
OnActivate should fail loudly (return the error and abort activation)
rather than LogWarn-and-continue, so QA can debug activation problems.

Scoped to OnActivate per the review comment; OnDeactivate and the
ensureDemo* helpers keep their resilient behavior.

Co-authored-by: Cursor <[email protected]>
… tab

The host no longer injects theme/webSocketClient into custom channel
settings tab bodies, so the reference implementation reads colors from
the Mattermost theme CSS variables instead of the theme prop and drops
the now-unneeded component cast.

Co-authored-by: nick.misasi <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2: Dev Review Requires review by a core committer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants