* Allow syncing any CPA field with LDAP/SAML and disable editable toggle when synced
A custom profile attribute field could only be linked to LDAP/SAML sync
when it was user-editable, and the editable toggle stayed enabled for
synced fields. Toggling editable off silently stripped the link on save.
Allow admin-managed fields to be synced (sync and admin-managed are no
longer mutually exclusive on the server) and disable the editable toggle
in the dot menu while a field is synced, since synced values come from
the IdP and are never user-editable.
* Add tests for syncable admin-managed CPA fields and disabled editable toggle
* Strengthen sync test coverage: combined admin-managed+synced and SAML update path
* ci: re-trigger Enterprise CI after transient npm network failure
* Address PR feedback: 1 answered, 1 resolved, 0 declined
---------
Co-authored-by: cursor[bot] <206951365+cursor[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
* MM-69042 Add user setting to experimentally enable concurrent React
* Change collapsed label to always use store value
(cherry picked from commit 8c8f28f943)
Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>
* [MM-68693] Resource level permission policies and new simulation (#36472)
(cherry picked from commit ba1cec51a5)
---------
Co-authored-by: Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>
* MM-68762: Add Postgres migrations for discoverable private channels
Three online-safe migrations introduce the schema that supports the
Discoverable Private Channels feature (PRs 2-5 of MM-68430 will land
behind it):
- 000175 adds Channels.Discoverable BOOLEAN NOT NULL DEFAULT FALSE.
Metadata-only on Postgres >= 11; no table rewrite.
- 000176 creates a partial index on
(TeamId) WHERE Discoverable AND Type='P' AND DeleteAt=0
using CREATE INDEX CONCURRENTLY (-- morph:nontransactional) so the
build never blocks writes on the populated Channels table.
- 000177 creates the ChannelJoinRequests table with three indexes, the
important one being the partial unique index on (ChannelId, UserId)
WHERE Status = 'pending'. That keeps the full audit history intact
while still enforcing at-most-one active pending request per
(channel, user).
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Add FeatureFlagDiscoverableChannels (default false)
Gates the per-channel Discoverable toggle and the channel-join-request
flow. Default-OFF so all PRs in the MM-68430 series can land on master
without exposing partial UX.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Add Discoverable + ChannelJoinRequest models
- Channel gains a Discoverable bool, ChannelPatch a *bool, both serialized
as 'discoverable'. Patch() applies it, Auditable() logs it, and IsValid()
rejects Discoverable=true on any non-private channel so a misconfigured
patch can never produce a public discoverable channel.
- New ChannelJoinRequest type captures the per-row state of a non-member's
request: pending -> approved | denied | withdrawn. Rows are append-only
with reviewer and timestamps so the table is also the audit trail.
IsValid() enforces:
* recognized status,
* Message and DenialReason rune limits,
* DenialReason only on denied rows (no orphan reasons),
* reviewer + reviewed_at present for any terminal review (approved /
denied) but not for self-service withdrawal.
- Two new WebSocket event constants -- channel_join_request_created and
channel_join_request_updated -- that later PRs broadcast on the admin
queue and the requester's My Pending Requests panel.
Unit tests cover Patch(), the new IsValid() rule on Discoverable, the
PreSave/PreUpdate timestamp behavior on ChannelJoinRequest, and every
IsValid branch including the reviewer-required-on-review invariant.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Add discoverable-channel permissions
Two new channel-scoped permissions, each independently rebindable from
the System Console:
- manage_private_channel_discoverability gates the per-channel toggle so
admins can restrict who can flip discoverability without also handing
out manage_private_channel_properties.
- manage_channel_join_requests gates the queue list / approve / deny /
count endpoints (added in PR 2).
Both are added to the channel_admin role bootstrap so new deployments
get them by default, and a new permissions migration
(add_discoverable_channel_permissions) grants them to channel_admin,
team_admin and system_admin scheme roles on existing deployments.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Add ChannelJoinRequestStore and wire Discoverable into channel store
- channelSliceColumns / channelToSlice / updateChannelT now include the
new Discoverable column so Save() and Update() round-trip the field.
Existing select paths inherit the column automatically because every
read goes through channelSliceColumns.
- New ChannelJoinRequestStore interface and SQL implementation:
Save / Get / GetPendingForChannelAndUser / GetForChannel / GetForUser
/ Update / CountPending. Save translates the
idx_channeljoinrequests_pending_unique partial unique index violation
into store.ErrConflict so the app layer (PR 2) can return 409 without
re-parsing pq errors.
- Storetest suite at storetest/channel_join_request_store.go is invoked
from sqlstore via the existing StoreTest harness; covers insert /
partial-unique conflict / re-insert after withdrawal / NotFound /
status filtering / pagination with TotalCount / Update / CountPending.
- Mocks and retrylayer / timerlayer are regenerated via make store-mocks
and go generate ./channels/store -- no hand-written generator output.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Add TS types for Discoverable channels + join requests
webapp/platform/types:
- Channel.discoverable?: boolean alongside existing policy_enforced /
policy_is_active so the web client sees the same wire shape the server
emits.
- ChannelJoinRequest, ChannelJoinRequestStatus, ChannelJoinRequestList,
GetChannelJoinRequestsOptions for the API contract surfaced in PR 2.
webapp/platform/client:
- WebSocketEvents enum gains ChannelJoinRequestCreated and
ChannelJoinRequestUpdated so PR 3 can hang WS handlers off them
without redeclaring constants.
These are model-only updates with no UI consumer yet; PR 3 introduces
the toggle, request flow, and admin queue surfaces.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Split ChannelJoinRequests indexes into concurrent migrations
The mattermost-govet concurrentIndex lint check enforces CREATE INDEX
CONCURRENTLY on every CREATE INDEX statement, even on an empty
freshly-created table where it would be a no-op. The original 000177
file inlined three CREATE INDEX statements; that failed check-style.
Mirror the convention used by 000166_create_views +
000167_create_views_channel_id_delete_at_index: keep the CREATE TABLE
in its own (transactional) file, and move each index into a separate
nontransactional file that runs CREATE INDEX CONCURRENTLY. Verified
locally against Postgres 15 that all four new migrations apply in
order and the storetest suite (partial unique constraint + paged
list + count) still passes.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Wire new permission migration into test fixtures
Two CI test surfaces missed when the channel_admin role and the
permission-migration list gained the new
manage_private_channel_discoverability and manage_channel_join_requests
entries:
- testlib/store.go: the shared mocked SystemStore used by
SetupWithStoreMock / SetupEnterpriseWithStoreMock needs an explicit
GetByName expectation for every migration key (because the mock
panics on unexpected calls). Add the new
MigrationKeyAddDiscoverableChannelPermissions key so
TestCreateOrUpdateAccessControlPolicy, the elasticsearch
aggregation_job_test, and every other mock-store test stop panicking
on server bootstrap.
- cmd/mmctl/commands/permissions_test.go: TestResetPermissionsCmd
hard-codes the channel_admin default permission list and expects
PatchRole to be called with exactly that slice. Extend the expected
slice with the two new permission ids so the mmctl reset path stays
in sync with the role bootstrap.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Register new idx_channels_discoverable_team in TestGetSchemaDefinition
The schema-dump test asserts an exact index count and definition map
for the channels table. Migration 000176 added
idx_channels_discoverable_team — a partial btree on (teamid) gated by
discoverable=true AND type='P' AND deleteat=0. Bump the expected count
from 12 to 13 and add the index's CREATE INDEX definition as produced
by pg_indexes (note: type is cast to channel_type, the existing
domain). Verified locally against Postgres 15.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Fix golangci-lint findings in ChannelJoinRequest store
Two golangci-lint findings on the freshly-added files:
- sqlstore/channel_join_request_store.go:133 (modernize): collapse the
'if page < 0 { page = 0 }' clamp into max(opts.Page, 0).
- storetest/channel_join_request_store.go:243 (govet shadow): the
inner Save loop redeclared err with :=, shadowing the outer err
captured from the first CountPending call. Switch to plain
assignment so the same err is reused.
Verified locally with golangci-lint v2.11.4 across public/...,
channels/app/..., channels/store/..., channels/testlib/... and
cmd/mmctl/commands/... — 0 issues.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Sync channel_admin bootstrap with TestDoAdvancedPermissionsMigration
app_test.go pins the exact list of permissions the channel_admin role
is expected to hold after DoAdvancedPermissionsMigration completes.
The role bootstrap in role.go grew two entries
(manage_private_channel_discoverability and manage_channel_join_requests),
so the test's expected slice needs the same two entries appended in
the same order, otherwise assert.Equal fails on slice ordering.
This is the same class of fix as the mmctl/permissions_test.go change
in a previous commit -- two parallel test fixtures encode the
channel_admin defaults and have to be updated in lockstep with the
bootstrap.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Add English translations for new model error keys
12 keys were emitted by the new Discoverable + ChannelJoinRequest
validation paths but had no en.json entry, which trips i18n-check on
CI. Add the missing entries with one-line English copy that mirrors
adjacent model errors (Invalid <field>., Create at must be a valid
time., etc.). The new entries are:
- model.channel.is_valid.discoverable.app_error
- model.channel_join_request.is_valid.channel_id.app_error
- model.channel_join_request.is_valid.create_at.app_error
- model.channel_join_request.is_valid.denial_reason.app_error
- model.channel_join_request.is_valid.denial_reason_status.app_error
- model.channel_join_request.is_valid.id.app_error
- model.channel_join_request.is_valid.message.app_error
- model.channel_join_request.is_valid.reviewed_by.app_error
- model.channel_join_request.is_valid.reviewer.app_error
- model.channel_join_request.is_valid.status.app_error
- model.channel_join_request.is_valid.update_at.app_error
- model.channel_join_request.is_valid.user_id.app_error
Generated through 'make i18n-extract'; verified clean with
'make i18n-check'. Per the workspace rule, only en.json was modified --
no other locale files.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Address CodeRabbit review: stable pagination + redact denial reason from audit log
Two production-code findings from CodeRabbit on the freshly-added
ChannelJoinRequest server code:
- sqlstore/channel_join_request_store.go (GetForChannel / GetForUser):
OrderBy("CreateAt DESC") alone is unstable when two rows share a
millisecond (NewId is monotonic-ish but CreateAt is millisecond
resolution), so offset paging could duplicate or skip rows between
pages. Add Id DESC as a deterministic tie-breaker on both list
queries.
- model/channel_join_request.Auditable: the denial reason is admin-typed
free text and could carry sensitive content. Mirror the existing
has_message pattern by emitting has_denial_reason as a boolean
presence flag instead of the raw value. Reviewer id, review timestamp,
and status are still logged, so the audit trail keeps every piece
needed for compliance review.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Tighten model tests per CodeRabbit review
Two test-only findings from CodeRabbit:
- TestChannelJoinRequestPreUpdateAdvancesUpdateAt previously asserted
GreaterOrEqual(r.UpdateAt, originalCreate). Because validRequest
initialises UpdateAt to GetMillis() (same call site as CreateAt), a
no-op PreUpdate would still pass that check. Seed r.UpdateAt = 1
before calling PreUpdate() and assert Greater(r.UpdateAt, int64(1))
so any regression that drops the GetMillis assignment fails the test.
- TestChannelIsValidDiscoverable did not cover ChannelTypeGroup. Add the
case alongside ChannelTypeOpen and ChannelTypeDirect so the contract
that 'only ChannelTypePrivate accepts Discoverable=true' is fully
pinned across all four channel types.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Mock ChannelJoinRequest accessor in retrylayer test
retrylayer_test.go's genStore() helper mocks every Store() accessor
because retrylayer.New() wraps the entire surface. The new
ChannelJoinRequest() method I added on Store was missing from the
mock, so TestRetry/on_regular_error_should_not_retry panicked with
'Unexpected Method Call ChannelJoinRequest()' on Postgres shard 0.
Add the mock alongside the other accessors. No production code
change.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* Phase 1: CPA display_name + CEL-safe name validation (server)
- Add typed DisplayName field to CPAAttrs + display_name attr key constant.
- Add ValidateCPAFieldName helper enforcing CEL IDENTIFIER + reserved-word blacklist.
- Wire validation into App.CreateCPAField (always) and App.PatchCPAField (lenient grandfather: skip when Name unchanged).
- Trim + 255-rune cap DisplayName in CPAField.SanitizeAndValidate.
- Developer-facing godoc note documenting rule, sources of truth, and Option C scoping.
- Asserting test for documented Option C plugin-API bypass (closed by PR #36173).
Spec: planner/projects/property-display-name/ideas/001-cpa-display-name/spec.md
Plan: .planning/phase-1/PLAN.md
Made-with: Cursor
* Phase 1 (review): address Reza's Major + Minor findings
- Rename misleading subtest "empty DisplayName is omitted from attrs"
to "empty DisplayName round-trips as empty string" (Major #1).
- Add TestCPAAttrs_JSONOmitEmpty pinning the omitempty wire-format
contract that PR #36173's typed-attrs strategy relies on (Major #1).
- Extend TestValidateCPAFieldName: case-sensitivity (IN/In ok),
single-character names (a/_/A ok), missing "as" reserved word
(Minor #2). Add whitespace-only DisplayName case (Minor #2).
- Document PropertyFieldNameMaxRunes reuse in SanitizeAndValidate
to prevent drift (Minor #3).
- Replace broken PLAN-server.md reference in bypass-test docstring
with in-tree CPAAttrs godoc reference (Minor #4).
- Document omitempty semantics on CPAAttrs.DisplayName field to
prevent the same misreading caught in review (Minor #5).
- Document grouping intent above CPAFieldNameReservedWords (Minor #8).
Review: .planning/phase-1/REVIEW.md
Made-with: Cursor
* Phase 2: in-app backfill migration for CPA display_name
- Add cpaDisplayNameBackfillKey + cpaDisplayNameBackfillVersion constants.
- Implement (*Server).doSetupCPADisplayNameBackfill: idempotent, cursor-paged
scan over CPA group fields; backfill attrs.display_name = name when empty.
- Register in m1 migration slice in doAppMigrations (mlog.Fatal on error,
matching existing convention).
- Three migration tests: NoExistingFields, BackfillsMissing, Idempotent.
System-key idempotency + per-field DisplayName-empty check together provide
HA-safe behavior on rolling deploys (last-write-wins on the System key;
data-level idempotency from the per-field check).
Spec: planner/projects/property-display-name/ideas/001-cpa-display-name/spec.md
Plan: .planning/phase-2/PLAN.md
Made-with: Cursor
* Phase 2 (review): document race + harden idempotency test
- Document SearchPropertyFields→UpdatePropertyFields rolling-deploy
race: stale snapshot can revert concurrent admin CPA rename. Pre-
existing systemic shape (no UpdateAt optimistic-lock); narrow
window; bounded blast radius (admin re-rename, ABAC ID-keyed).
Accepted limitation per spec Out of Scope (Major #1, Option C).
- Tighten TestCPADisplayNameBackfill_Idempotent: snapshot UpdateAt
before second run; assert no DB write on the System key or the
field row (Major #2).
- Extract clearCPABackfillMarker helper with explanatory godoc to
centralize the 3x-repeated test precondition (Minor #1).
- Comment fieldA seed as the "key-present-as-empty-string" idempotency
boundary case (Minor #6).
- Add godoc to doSetupCPADisplayNameBackfill (Minor #10).
Review: .planning/phase-2/REVIEW.md
Made-with: Cursor
* Linting
* Removing unnecessary comments
* Clean up tests
* Linting
* Fix tests
* Updated API doc
* Phase 3: webapp helper + render-site migration for CPA display_name
- Add display_name?: string to UserPropertyField.attrs type.
- New getUserPropertyFieldLabel(field) helper: returns
attrs.display_name?.trim() || name. Defensive against missing attrs.
- Migrate ~10 user-facing CPA-name render sites to the helper:
profile popover, user settings general (4 usages incl. line 1673
missed by high-level plan), admin user detail, admin CPA list (2
usages), and ABAC editor's selected-attribute UI (3 usages incl.
the button label found in planning-stage research).
- CEL paths (table_editor, attribute_selector_menu user.attributes
expression construction, ABAC search filters) keep using `name`
per spec — display_name is label-only.
- Phase 4 boundary marker: TODOs in admin table + delete modal for
follow-up admin-edit UX + client-side validator.
Spec: planner/projects/property-display-name/ideas/001-cpa-display-name/spec.md
Plan: .planning/phase-3/PLAN.md
* Phase 3 (review): add Unicode test + correct helper docblock scope
Address Reza's Phase 3 review:
- Major #1: add missing test case for non-ASCII display_name
(Latin-extended + CJK), pinning the trim/passthrough contract.
- Nitpick #3: correct the helper's JSDoc to reflect that the
delete modal is intentionally not migrated until Phase 4.
No production behavior change. No new dependencies.
Made-with: Cursor
* Phase 4: admin CPA edit UX + client-side identifier validation
Made-with: Cursor
* docs: append Phase 4 implementation summary
Made-with: Cursor
* Phase 4: admin CPA edit UX + client-side identifier validation
Complete the Phase 4 takeover from the existing dirty worktree and record the verified Stage 2 scope for admin CPA display-name editing, client-side identifier validation, and the required grandfather regression follow-ups.
Document the targeted Jest, typecheck, and lint-equivalent validation results in the Phase 4 plan without widening the implementation scope or rewriting the prior in-scope work.
Made-with: Cursor
* docs: finalize Phase 4 implementation summary
Made-with: Cursor
* docs: correct Phase 4 summary commit reference
Made-with: Cursor
* Phase 4 (review): fix empty-name warning precedence
Required-name validation now short-circuits before uniqueness checks so empty identifiers keep the correct warning. Add duplicate collision regression coverage for the dot-menu flow and add a stable validation-error testid for Phase 5 automation.
Made-with: Cursor
* Test updates
* Fix merge issue
* Fix tests
* PR Feedback
* Move migration to PropertyService
* Updates to UX
* Comment cleanup
* Add webapp tests for CPA display_name and fix CEL-affected specs
Update E2E seeds to use CEL-safe identifiers with display_name,
add ABAC selector spec, and extend Jest coverage for label-rendering
sites, auto-fill guard rails, and required-warning suppression.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Remove .planning/phase-4/PLAN.md
This planning artifact was committed inadvertently and should not be
part of the codebase.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address CodeRabbit review comments
- Fix e2e test to use display_name in label assertions
- Make getIncrementedCELName case-insensitive to prevent collisions
- Update tooltip to mention reserved CEL words
- Replace hasSpaces check with full CEL identifier validation
- Use CPA_FIELD_NAME_MAX_RUNES for consistent maxLength
- Fix race condition by removing global cleanupAllFields
- Enable IntegratedBoards flag for legacy field seeding
- Replace fixed sleeps with state-based waits in tests
Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>
* Fix linting errors in getIncrementedCELName
- Use camelCase for destructured delete_at parameter
- Place dots on same line for method chaining
Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>
* Remove unused imports in user_attributes_display_name.spec.ts
- Remove unused deleteCustomProfileAttributes import
- Remove unused getFieldsMap function
- Remove unused FieldsMap type
Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>
* Fix webapp test failure - remove htmlFor assertion
The htmlFor attribute assertion was failing in the test environment,
likely due to a testing library issue. The important functionality
(displaying display_name in labels) is still properly tested.
Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>
* Fix post-merge CI failures: i18n drift and Playwright Prettier
- Re-extract webapp en.json so the identifier tooltip string matches
user_properties_table.tsx (source of truth was already shortened in
Phase 4; en.json was not regenerated).
- Apply Prettier formatting to three CPA display_name Playwright specs
(whitespace and import/expression collapsing only). No test logic
changes.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address CodeRabbit feedback: use stable locators, add reserved words to tooltip, remove regex from hasText
Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>
* Fix i18n drift: align defaultMessage with en.json for identifier tooltip
Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>
* Comment cleanup
* Slugify CPA duplicate names to snake_case
slugifyForCEL now lowercases and inserts underscores at camel/PascalCase
boundaries (e.g. MyField -> my_field, XMLParser -> xml_parser) so
duplicated CPA fields get conventional snake_case names instead of
preserving the source casing.
Co-authored-by: Cursor <cursoragent@cursor.com>
* UX improvements: CEL identifier tooltip, validation, and attribute picker dual-name display
- Add info tooltip to the Attribute column header explaining CEL identifier rules
- Add client-side CEL identifier validation (pattern + reserved words) with a descriptive error message
- Show both display name and unique identifier in the policy attribute picker
- Filter attribute picker search by both display name and unique name
- Add display_name to UserPropertyField attrs TypeScript type
- Expand "CEL" to "Common Expression Language (CEL)" in the attribute-spaces tooltip
Co-authored-by: Cursor <cursoragent@cursor.com>
* Linting
* PR Feedback
* Restore name limit
* Fix tests
* Revert stray comment block above TestCPADisplayNameBackfill_BackfillsProtectedSourceOnlyField
Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>
* Revert extended fieldA comment in TestCPADisplayNameBackfill_BackfillsMissing
Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>
* Fix E2E tests
* Fix test
---------
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* MM-67771 Update Report a Problem to email flow for licensed servers
Change the default "Report a Problem" behavior for licensed servers to
open a mailto link to reportaproblem@mattermost.com with pre-filled
metadata instead of redirecting to the support portal. Unlicensed servers
continue to redirect to the troubleshooting forums. Admin console help
text is now license-aware with separate descriptions for each plan type.
* Add isFreeEdition check for Report a Problem flow
Treat both unlicensed servers and licensed servers with entry SKU as
free edition. This affects the Report a Problem default behavior
(forum redirect vs mailto) and the admin console help text shown.
- Add isFreeEdition to general.ts selectors and admin_definition_helpers
- Add SKUEntry constant to general constants
- Reuse isFreeEdition in product_menu.tsx
- Add entry SKU test case for report_a_problem
* Add the link to forums for free edition
* Add permission and restricted-mode guards to ReportAProblemType dropdown
The ReportAProblemType dropdown was missing the write-permission check
and RestrictSystemAdmin guard that all other fields in the section have.
* Changed batchGetProfilesInChannel to batchGetProfilesInGroupChannel and have it use bulk API
* MM-65058 Add useUserIdsInGroupChannel and use to populate Direct Messages modal
* Address feedback
* Run Prettier
* Fix types
* feat(webapp): added keyboard shortcut for Mark All As Read (MM-2541)
- Added shortcut (within sidebar) for Shift+ESC to mark _all_ messages, teams as read
- Desktop only
- Added feature toasts for new features and localStorage support
- Added feature toast for mark-all-as-read feature
- Should decide when/how people want this shown, I just followed designs
- Will only show if the user has not clicked 'Got it' before, and is not on mobile
- Added confirmation modal for mark all as read shortcut
- Contains option to not show again, saved in localStorage
- Added English translations for read shortcut
- Will need i18n aid on other languages
This is a draft version of this feature update that still needs testing and i18n support, along with a11y validation.
* feat(webapp): feature flags and fixes for mark all as read shortcut
- Added feature flags surrounding rollout of mark-all-as-read shortcut
- Added shortcut to list of shortcuts in help section
- Extended tests for new components
- Updated snapshot for sidebar_list, keyboard_shortcuts_modal
- Fixed styling and CSS issues
Still in draft, needs documentation and e2e support.
* fix(webapp): fixed some issues with new mark-all-read feature
- Scoped persistent storage to current user ID
so that subsequent new logins also get the notification
- Replaced LocalStorage calls with useGlobalState calls, sad
that I missed that this updated call was being used.
- Fixed an issue that would have caused the new shortcut to
show up in the Help menu's shortcuts without being enabled.
* Fixed a snapshot test and a missing i18n member
* Replaced useGlobalState with backend-ready usePreference. Previous version was just a mistake as we didnt know about the supported API
* fix(server): fix lint issue with gofmt
* feat(server,webapp): added cleaner and more effective method with which to mark-all-read
- Added 2 new routes to the API (need to find docs to update those):
- `PUT /api/v4/channels/members/<userId>/direct/read` will mark a user's non-team DMs and GMs as read
- `PUT /api/v4/users/<userId>/teams/<teamId>/read` will do a similar action as the multi-channel mark_read action, but with a teamId signifier. Because this is using a teamId, it will _not_ handle DMs or GMs.
- Updated sidebar_list.tsx to use these new routes for the new shortcut
- Added extensive testing, including feature flag assurance.
* fix from upstream changes
* fix: eslint errors in teams actions
* document new API endpoints
* fix i18n
* fix err id
* remove unused localhost methods
* use ShortcutKey and ShortcutSequence
* feature_enhancements, mark as read toast enchancements
* read all modal mount point, use openModal
* use handler
* fix style
* fix: fix refactoring typo
* Merge fix: realign branch with upstream changes
Upstream MM-67319/MM-67320 (#36037) moved ShortcutKey and
WithTooltip into the shared package and rewrote the keyboard
shortcuts test to snapshot real DOM instead of a
react-test-renderer tree. The merge resolution missed several
follow-on consequences; clean them up so the branch builds, type
checks, lints, passes i18n-extract-check and runs without
throwing at mount.
- Port the inline-content variant from the deleted channels-side
shortcut_key.scss to the new shared shortcut_key.css.
- Refresh the keyboard_shortcuts_sequence snapshot so it matches
Testing Library's container output (DOM only, no component
nodes, class= not className=).
- Repoint mark_all_as_read_modal and mark_all_as_read_toast at
components/shortcut_key for ShortcutKeys and use
ShortcutKeys.escape; the channels-side with_tooltip is now a
thin re-export and the field was renamed in the shared keys
map. Without this both consumers threw "Cannot read properties
of undefined" at mount.
- Switch mark_all_as_read_toast's UserAgent import to
@mattermost/shared/utils/user_agent; the channels-local
utils/user_agent path no longer resolves.
- Drop the orphan mark_all_threads_as_read_modal.cancel string
from en.json so formatjs extraction is in sync.
* Clean up TestReadAllInTeam
Drop four lines left from debugging and replace them with a real
assertion: LastViewedAtTimes must contain the test channel with a
value at or after the most recent post.
Update three client.GetChannel calls to the (ctx, id) signature;
the prior etag argument no longer compiles after upstream removed
it.
* Use SelectBuilder for team channels query
GetTeamChannelsWithUnreadAndMentions built a squirrel query and
then manually called ToSql before handing the string+args to
GetReplica().Select. SelectBuilder accepts the builder directly
and removes the intermediate dance, matching the pattern used
elsewhere in this store.
* Mark all team-channel threads on team read
MarkTeamChannelsAndThreadsViewed used Thread().MarkAllAsReadByTeam
unconditionally, writing every thread membership in the team for
the user even when nothing was stale. Scoping the call to
channelsToView (channels with unread channel-level messages) would
have closed the perf concern but introduced a regression: in CRT
mode a thread reply does not bump the channel's TotalMsgCount, so
a channel can be read at the channel level while still having
unread thread replies, and those would have been silently skipped.
Build the channel-id list from the keys of the times map instead.
GetTeamChannelsWithUnreadAndMentions already populates that map
for every team channel the user belongs to, so no extra query is
needed. MarkAllAsReadByChannels then filters the actual UPDATE
through its LastReplyAt > LastViewed clause, keeping writes
bounded to genuinely stale rows.
Gate the channel-level work (UpdateLastViewedAt, push clearing,
the MultipleChannelsViewed event) on channelsToView being
non-empty, but always run the thread mark and broadcast
ThreadReadChanged for every team channel so CRT clients refresh
thread state in channels that had no channel-level change.
* Mark mark-read audit records as success
The handlers for mark all DM/GM and mark team read created an
audit record with status Fail and never updated it on success,
so successful calls were always logged as failures.
* Mark all DM/GM threads on full read
MarkAllDirectAndGroupMessagesViewed early-returned when no
channel had unreads, so followed threads in DMs/GMs whose
channel-level counters were already current stayed unread under
CRT. Mirror MarkTeamChannelsAndThreadsViewed and call
MarkAllAsReadByChannels for every DM/GM in times.
* Polish DM/GM channels-with-unreads query
Use model.ChannelTypeDirect/Group constants instead of bare
"D"/"G" literals, and update the error wrap to mention DM/GM
channels (it was copied from the team variant).
* Fix stale ReadAllMessages godoc
* Type last_viewed_at_times as int64 map in OpenAPI
The response field was declared as a generic object. Add
additionalProperties so generated clients see it as a
channelId -> int64 timestamp map.
* Gate MarkAllAsReadToast mount on feature flag
The toast was mounted unconditionally, so its async chunk loaded
even when EnableShiftEscapeToMarkAllRead was off. Gate the mount
with the flag so the chunk only loads when the feature is on.
* Return data from markAllInTeamAsRead thunk
Match the {data: response} shape used by adjacent thunks instead
of returning {}, so callers can read the API payload.
* Coerce undefined suffix in createStoredKey
createStoredKey('foo') returned 'fooundefined' when the suffix
arg was omitted. Coerce a missing suffix to ''.
* Refactor mark-read websocket events
* Polish DM/GM channels-with-unreads query
* Fix import order in shortcut_key consumers
* Fix CI
---------
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Jesse Hallam <jesse@mattermost.com>
Co-authored-by: Caleb Roseland <caleb@calebroseland.com>
Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
* [MM-68588] Add notice in System Console when policy has mixed channel types
When a membership policy in the System Console policy editor has both
public and private channels assigned, the implications differ by type:
private channels restrict access while public channels are advisory.
Add an informational SectionNotice below the assigned-channels list
explaining the distinction so admins understand the behavior before
saving. The notice only appears when the effective channel set
(saved - removed + added) contains at least one channel of each type.
Refactored the public/private counting that was previously inlined in
the confirmation modal block into a useMemo so the same calculation
drives both the new notice and the existing confirmation modal.
Made-with: Cursor
* Add mixed channel notice to team policy editor
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* Add Classification Markings admin console page
Adds a new admin console page under Site Configuration for managing
classification markings. This allows system administrators to define
classification levels (e.g., UNCLASSIFIED, SECRET, TOP SECRET) with
associated colors and rank ordering, which will be used for system-wide
and per-channel classification banners.
The page includes:
- Enable/disable toggle backed by the property field system (field
existence = enabled)
- Country preset dropdown (US DoD, NATO, UK GSCP, Canada, Australia
PSPF) that auto-fills standard classification levels
- Editable classification levels table with drag-and-drop reorder,
inline text editing, color picker, and delete
- Auto-switch to "Custom" preset when levels are manually modified
- Confirmation dialog when switching presets would overwrite custom data
Also adds:
- ClassificationMarkings feature flag (default off)
- Generic property field client methods (get/create/patch/delete) for
the /api/v4/properties/ endpoints
- Enterprise license + feature flag gating on the admin page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix classification markings: add validation, error handling, and system object type
- Add "system" as a valid property field object type so the
classification markings API calls succeed
- Surface load errors instead of silently swallowing them (only
suppress 404 for unconfigured state)
- Validate before save: require at least one level, non-empty names,
and no duplicates
- Default to custom preset with empty levels on first open
- Add section strings to searchableStrings for admin console search
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Move classification field to CPA group targeting users
Store the classification markings property field in the
custom_profile_attributes group with object_type 'user' instead of the
attributes group with object_type 'system'. Clear target_id for PSAv2
system target compliance and mark the field as admin-managed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Stabilize preset option IDs and add danger warning on preset switch
Hardcode deterministic IDs for all preset classification levels so
switching away and back preserves option IDs, preventing orphaned
property values. Compare only level data (not preset label) for change
detection so cosmetic preset switches don't trigger false save states.
Show a danger modal with red confirm button when changing presets on an
existing field, warning about system-wide impact on classified resources.
The warning appears once per session then allows frictionless switching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove system object type from property fields
Not needed yet — will be added when system/channel banners are implemented.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix ESLint errors in classification markings admin page
Fix import ordering and remove unused generateId import.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address CodeRabbit review feedback for classification markings
- Register property field API endpoints when ClassificationMarkings flag
is enabled (not just IntegratedBoards) to prevent 404s
- Preserve preset option IDs when creating a new classification field
instead of blanking them with empty strings
- Add sysconsole read/write permission constants for classification
markings across server and webapp, and wire up resource-level
permission checks in the admin definition
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add rank attribute to classification marking options
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add classification markings permissions migration and read-only support
Add a permissions migration to grant classification markings sysconsole
permissions to existing roles on upgrade. Wire up the disabled prop so
read-only users can view but not edit classification settings. Register
the permission in the Delegated Granular Administration UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Paginate loadField to find classification field beyond first page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix lint errors and warnings in classification markings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove classification markings sysconsole permissions; gate on sysadmin instead
Classification markings admin page no longer uses feature-specific
read/write permissions. Visibility is gated on license + feature flag,
editing is gated on system admin role. This avoids coupling
feature-specific permissions to the generic property service.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Set sysadmin-level permissions on classification markings field creation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use stable IDs instead of array indices for classification level operations
Switch updateLevel/deleteLevel to identify levels by ID rather than
index, sort levels by rank on load, and extract i18n strings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Refactor classification markings into extracted helper functions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add tests for classification markings admin console feature
Add unit and component tests covering:
- Pure function tests for detectPreset, optionsToLevels, levelsToOptions,
processClassificationField, and fetchClassificationField pagination logic
- React component tests for rendering states, validation, and user interactions
- Client4 property field method tests for URL construction and HTTP verbs
- Server routing test verifying routes register with ClassificationMarkings flag
- Feature flag default and serialization test
Export pure functions from classification_markings.tsx to enable direct testing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix lint errors in classification markings tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix test compilation error
* Fix color input auto-filling after 3 hex characters in classification markings
Buffer ColorInput onChange in a LevelColorCell wrapper so the table
doesn't re-render mid-typing, preventing the input from losing its
focus-guarded local state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fixing style issues with color picker z-index
* Added fix to prevent immediate dismissal when clicking inside color picker
* Adding E2E test suite for configuration
* Removing duplicates
* Fixing unrelated linter error
* Fixing test linting issues
* Updating tests to skip appropriately
* Matching configuration to UX specs
* Fixing style lint
* Added informational banner for presentational nature of markings
* Enabling the markings flag on playwright server
* Added missing feature flag to e2e test environment in ci
* Reverting changes to color_input
- Not needed as we're using a custom component
* Added and polished global banner configuration
* Refactoring webapp for readability
- Separating components
- Adding unit tests
- Isolating helper methods into utilities
* Fixing linter errors
* linter fix
* Manually fixing linter issues
* Separating global classification component
* Added persistence of classification marking configuration
* Changing LevelID with LevelName
* Making changes for PR reviews
* Changing property object of classification field to template
* syncing i18n file
* Removing inaccurate note from comments
* PR fixes for UX review
* Cleaning up unused value
* Added GlobalClassificationBanner component
- Made sure it syncs on change by using normal configuration values on it
- Works with "top" and "top_and_bottom"
- Renders on both root and admin_console
* Adding E2E test cases for global classification
* Linter fixes, i18n extract
* PR Fixes
* Linter fix
* Matching default messages
* Fixing type errors
* Fixing pipeline and runtime errors
* Fixing announcementbar rendering on top of global classifications
* Increasing banner & font sizes
* Fixing font size to 12px instead of 16px
- I read it wrong
* Replacing config values with property
* Test linter fixes
* Fixing type errors and go format error
* Making changes needed to align with specs
- Ensuring system_classification is a separate linked property that differs from the template
- Saving the global classification banner values as a propertyvalue
* Added missing arguments in e2e tests
* Added missing conditions for useEffect
- Also fixing E2E error in pipeline
* Fixing issues with V1 and V2 group mismatch
* Fixes for linter errors and coderabbit review
* Addressing more issues found by coderabbit
* Fixing issues found by coderabbit
* Migrating to use system properties
* Ran all linters and prettier
- Resolving coding style drift that happened from not running prettier on the webapp (even though CI doesn't check for this)
* Undoing the prettier changes in webapp
* Cleaning up unwanted autoformatted changes
* Reverting prettier changes to clean diff
* Fixing E2E test
* Import fixes in test
* Applying changes for PR feedback
* Fixing issues with failing e2e tests
* Changing key of selection from name to id
* Replacing field setup in E2E tests to use levelId instead of levelName
---------
Co-authored-by: David Krauser <david@krauser.org>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: David Krauser <david@kruser.org>
Co-authored-by: Mattermost Build <build@mattermost.com>
* fix(channels): update sidebar icon when channel converted via mmctl
The handleChannelConvertedEvent WebSocket handler hardcoded
type as PRIVATE_CHANNEL, so private-to-public conversions were
silently ignored. Now the server includes channel_type in the
channel_converted WS event and the frontend reads it.
Ref: MM-68233
* test(channels): add tests for channel_converted WS event
Add server-side tests verifying the WebSocket event payload includes
channel_type for both private→public and public→private conversions.
Add frontend tests for handleChannelConvertedEvent covering both
conversion directions, backwards compatibility fallback when
channel_type is absent, and edge cases.
Ref: MM-68233
* test(channels): add E2E test for channel privacy WS icon update
Playwright E2E tests verify that the sidebar channel icon updates
in real-time when channel privacy is changed via the API (simulating
mmctl). Tests both public→private and private→public directions.
Ref: MM-68233
* refactor: review feedback on channel_converted fix
Narrow channel_type WS field to 'O' | 'P' union type instead of
string. Drop hardcoded channel names in E2E tests to let
pw.random.channel() generate unique names and avoid collisions.
Ref: MM-68233
* fix(e2e): provide name + unique flag for channel creation
pw.random.channel() requires a name field — server rejects channels
without a valid lowercase alphanumeric name. Use unique: true to
append a random suffix for test isolation.
Ref: MM-68233
* refactor(channels): use channel type constants in channel_converted code
Address review feedback: replace inlined 'O'/'P' string literals with
predefined constants. websocket_messages.ts now types channel_type as
ChannelType (already imported); websocket_actions tests use
Constants.OPEN_CHANNEL / Constants.PRIVATE_CHANNEL.
---------
Co-authored-by: Mattermost Build <build@mattermost.com>
* feat: include connection id in the plugin context
* refactor: group ConnectionId next to SessionId in plugin Context
Addresses review feedback to keep related identifier fields adjacent.
* fix(files): forward Connection-Id on file uploads to plugin hooks
The webapp uploadFile XHR didn't attach the Connection-Id header, so
FileWillBeUploaded plugin hooks always received an empty ConnectionId.
Read it from the websocket selector and set it on the request, matching
how drafts and channel bookmarks already do it. Adds a server-side test
asserting the connection id propagates through pluginContext.
* fix(lint): reorder file_actions imports to satisfy import/order
* Document ConnectionId on request.Context
* Fix compact mode: consecutive bot reply header floating incorrectly in RHS
MM-67419: In compact display mode, when a bot sends consecutive replies in
the RHS thread view, the .post__header floats incorrectly because the
global CSS rule in _post.scss gains higher specificity (7 classes) than
the ThreadViewer override (6 classes) when the post also carries .post--bot
and .same--user classes.
Fix: add an explicit .same--user.post--bot sub-selector inside the
.post-right__container / .ThreadViewer compact-reply block, raising the
override specificity to 8 classes so it correctly beats the global rule
and resets float to none.
Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>
* Remove redundant height/margin declarations from bot post-header override
The enclosing .post__header rule already sets height: auto and margin-left: 0;
the only property the global compact bot rule overrides is float: left, so
the new sub-selector only needs float: none to win the specificity contest.
Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>
* MM-68433 - Fix DM/GM menu gating and header save in Channel Settings
* fix linter
* Avoid global role mutation in autotranslation DM e2e tests
* adjust menu item display based on config
* fix e2e tests
* Stabilize DM autotranslation Playwright menu/settings tests
---------
Co-authored-by: Mattermost Build <build@mattermost.com>
* Fix modal title line-height regression introduced in MM-66442
The current inflated value for the modal's line-height causes
multi-line modal titles to overflow or be clipped. Restores the
original value and adds an e2e test to prevent future regressions.
* Improvements
---------
Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
When navigator.sendBeacon() is called with a plain string body, the
browser defaults to Content-Type: text/plain;charset=UTF-8. This
causes legitimate WAF alerts because the payload is JSON, not plain
text. Many deployments have been blocking users because of this
mismatch (MM-61530 / mattermost/mattermost#29101).
Fix: wrap the JSON string in a Blob with type 'application/json'
before passing it to sendBeacon. Also add the matching
Content-Type: application/json header to the fallback fetch call
that fires when sendBeacon returns false.
Tests: add three new focused tests in the
PerformanceReporter.sendReport content-type suite that verify:
1. sendBeacon receives a Blob with type application/json
2. The Blob's text content is valid JSON matching the report
3. The fallback fetch includes Content-Type: application/json
Also update the existing (currently skipped) tests to parse the
Blob body via FileReader instead of JSON.parse(string).
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>
* MM-67904 Fix inflated count in search results Messages tab
The "Messages" counter in the search results RHS was rendering
`results.length`, but `results` is the array produced by
`makeAddDateSeparatorsForSearchResults`, which interleaves
date-line strings between posts (one per date group). A search
returning a single post therefore showed "2", and N posts spread
across D dates showed N+D.
Filter date-line strings out of `results` before computing the
counter so it reflects only the actual matching posts. File
results are unaffected.
Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>
* Address review feedback: simplify count predicate, drop unnecessary type casts in tests
- Count non-string entries in results directly. The Props type already
declares results as Array<Post | string>, so any string entry is a
separator; this is clearer than checking isDateLine and is more
defensive against future marker strings.
- Drop the as any casts on the new test results props. The literals are
assignable to Array<Post | string>, so the casts only suppressed
type checking.
Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>