Commit Graph
694 Commits
Author SHA1 Message Date
sabrilandCursor Agent 51a2dbf1b9 E2E/Playwright: Complete Rainforest browser test migration (batch 7, 121-142) (#37453)
* E2E/Playwright: Migrate RFQA browser tests (batch 4, 61-80)

Migrate the fourth batch of Rainforest QA browser tests into Playwright
(migration plan None-core #34-53, keys MM-T480..MM-T1257): 17 new/covered
specs across notifications, search, channel_settings, sidebar_left,
keyboard_shortcuts, and direct_messages_modal, plus 3 duplicate keys folded
into existing tests (MM-T480, MM-T1256, MM-T1257).

POM/infra: add a ChannelMenu page object for the channel header dropdown, an
EditChannelHeaderModal, a header field on the channel-settings info tab, a
channel-wide-mentions checkbox on NotificationsSettings, a getConfirmModal
helper, and a clickNotification browser-API mock helper. Give the channel
header menu and the confirm modal proper accessible names so they can be
targeted semantically. Notification specs run on all browsers now that the
Notification API stub works headless on Chromium and Firefox.

* E2E/Playwright: Migrate RFQA browser tests (batch 5, 81-100)

Migrate the fifth batch of Rainforest QA browser tests into Playwright across
keyboard_shortcuts, search, archived_channels, channel_settings,
plugin_marketplace, messaging, and emoji_picker (keys MM-T1247..MM-T2365),
folding duplicate coverage into existing keyboard-shortcut specs and
documenting skipped cases inline.

POM/infra: add AddPeopleToChannelModal and MarketplaceModal page objects,
manage/add member buttons on the channel members RHS, and an App Marketplace
entry on the global header; target the search "Jump" link through the existing
SearchResultsPanel POM.

Also fix a webapp bug where a post body disappeared when an inline edit was
cancelled mid-animation (found via the up-arrow edit shortcut spec).

* E2E/Playwright: Migrate RFQA browser tests (batch 6, 101-120)

Migrate the sixth batch of Rainforest QA browser tests into Playwright
(migration plan None-core #74-93, keys MM-T2547..MM-T5604) across multi_team,
channel_settings, system_console, keyboard_shortcuts, search,
plugin_marketplace, messaging, channel_bookmarks, and custom_groups.

Fold duplicate coverage into existing specs (MM-T3680 into the marketplace
spec; MM-T4872 is already covered by the batch-5 search-prefill spec) and
document the plugin-install skip (MM-T4023, same external-download limitation
as MM-T1987).

POM/infra: add a ChannelBookmarksCreateModal page object and Bookmarks Bar
submenu actions on the ChannelMenu, plus a channel-bookmarks-bar locator and
bookmark-create-modal on ChannelsPage.

* E2E/Playwright: Fail on missing scroll bounding boxes

* E2E/Playwright: Locate user group dialog semantically

* E2E/Playwright: Address batch 6 review feedback

* E2E/Playwright: Locate user group rows semantically

* E2E/Playwright: Complete Rainforest migration batch 7

* CI: Rerun flaky webapp unit test

* E2E/Playwright: Keep batch 7 test data isolated

* E2E/Playwright: Group related batch 7 specs

* E2E/Playwright: Address batch 7 review feedback

* E2E/Playwright: Use bookmark bar POM in grouped spec

* add method to retrieve all bookmark links and update tests for bookmark order verification

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-07-14 03:57:32 +00:00
sabril bd0d8866d6 Remove deprecated Playwright/Cypress v1 dispatch (#37413)
* Remove deprecated Playwright/Cypress v1 e2e CI dispatch

Deletes the legacy in-job execution templates and their calculate-results
actions, and unconditionally routes both wrappers to the v2 (Test System IO)
templates. Test suites and local-dev tooling are untouched.

* remove v2 references

* remove aws dependencies in e2e tests, remove deprecated workflows
2026-07-14 10:55:18 +08:00
sabrilandCursor Agent d628dbc0ec E2E/Playwright: Migrate RFQA browser tests (batch 6, 101-120) (#37431)
* E2E/Playwright: Migrate RFQA browser tests (batch 4, 61-80)

Migrate the fourth batch of Rainforest QA browser tests into Playwright
(migration plan None-core #34-53, keys MM-T480..MM-T1257): 17 new/covered
specs across notifications, search, channel_settings, sidebar_left,
keyboard_shortcuts, and direct_messages_modal, plus 3 duplicate keys folded
into existing tests (MM-T480, MM-T1256, MM-T1257).

POM/infra: add a ChannelMenu page object for the channel header dropdown, an
EditChannelHeaderModal, a header field on the channel-settings info tab, a
channel-wide-mentions checkbox on NotificationsSettings, a getConfirmModal
helper, and a clickNotification browser-API mock helper. Give the channel
header menu and the confirm modal proper accessible names so they can be
targeted semantically. Notification specs run on all browsers now that the
Notification API stub works headless on Chromium and Firefox.

* E2E/Playwright: Migrate RFQA browser tests (batch 5, 81-100)

Migrate the fifth batch of Rainforest QA browser tests into Playwright across
keyboard_shortcuts, search, archived_channels, channel_settings,
plugin_marketplace, messaging, and emoji_picker (keys MM-T1247..MM-T2365),
folding duplicate coverage into existing keyboard-shortcut specs and
documenting skipped cases inline.

POM/infra: add AddPeopleToChannelModal and MarketplaceModal page objects,
manage/add member buttons on the channel members RHS, and an App Marketplace
entry on the global header; target the search "Jump" link through the existing
SearchResultsPanel POM.

Also fix a webapp bug where a post body disappeared when an inline edit was
cancelled mid-animation (found via the up-arrow edit shortcut spec).

* E2E/Playwright: Migrate RFQA browser tests (batch 6, 101-120)

Migrate the sixth batch of Rainforest QA browser tests into Playwright
(migration plan None-core #74-93, keys MM-T2547..MM-T5604) across multi_team,
channel_settings, system_console, keyboard_shortcuts, search,
plugin_marketplace, messaging, channel_bookmarks, and custom_groups.

Fold duplicate coverage into existing specs (MM-T3680 into the marketplace
spec; MM-T4872 is already covered by the batch-5 search-prefill spec) and
document the plugin-install skip (MM-T4023, same external-download limitation
as MM-T1987).

POM/infra: add a ChannelBookmarksCreateModal page object and Bookmarks Bar
submenu actions on the ChannelMenu, plus a channel-bookmarks-bar locator and
bookmark-create-modal on ChannelsPage.

* E2E/Playwright: Fail on missing scroll bounding boxes

* E2E/Playwright: Locate user group dialog semantically

* E2E/Playwright: Address batch 6 review feedback

* E2E/Playwright: Locate user group rows semantically

* E2E/Playwright: Group related batch 6 specs

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-07-13 19:03:56 +08:00
sabrilandCursor Agent 7748bf749e E2E/Playwright: Migrate RFQA browser tests (batch 5, 81-100) (#37417)
* E2E/Playwright: Migrate RFQA browser tests (batch 4, 61-80)

Migrate the fourth batch of Rainforest QA browser tests into Playwright
(migration plan None-core #34-53, keys MM-T480..MM-T1257): 17 new/covered
specs across notifications, search, channel_settings, sidebar_left,
keyboard_shortcuts, and direct_messages_modal, plus 3 duplicate keys folded
into existing tests (MM-T480, MM-T1256, MM-T1257).

POM/infra: add a ChannelMenu page object for the channel header dropdown, an
EditChannelHeaderModal, a header field on the channel-settings info tab, a
channel-wide-mentions checkbox on NotificationsSettings, a getConfirmModal
helper, and a clickNotification browser-API mock helper. Give the channel
header menu and the confirm modal proper accessible names so they can be
targeted semantically. Notification specs run on all browsers now that the
Notification API stub works headless on Chromium and Firefox.

* E2E/Playwright: Migrate RFQA browser tests (batch 5, 81-100)

Migrate the fifth batch of Rainforest QA browser tests into Playwright across
keyboard_shortcuts, search, archived_channels, channel_settings,
plugin_marketplace, messaging, and emoji_picker (keys MM-T1247..MM-T2365),
folding duplicate coverage into existing keyboard-shortcut specs and
documenting skipped cases inline.

POM/infra: add AddPeopleToChannelModal and MarketplaceModal page objects,
manage/add member buttons on the channel members RHS, and an App Marketplace
entry on the global header; target the search "Jump" link through the existing
SearchResultsPanel POM.

Also fix a webapp bug where a post body disappeared when an inline edit was
cancelled mid-animation (found via the up-arrow edit shortcut spec).

* E2E/Playwright: Fail on missing scroll bounding boxes

* E2E/Playwright: Group related batch 5 specs

* E2E/Playwright: Avoid duplicate creator membership

* E2E/Playwright: Group Rainforest batches 1 through 4

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-07-13 17:14:42 +08:00
sabrilandCursor Agent 17466f3638 E2E/Playwright: Migrate RFQA browser tests (batch 4, 61-80) (#37411)
Migrate the fourth batch of Rainforest QA browser tests into Playwright
(migration plan None-core #34-53, keys MM-T480..MM-T1257): 17 new/covered
specs across notifications, search, channel_settings, sidebar_left,
keyboard_shortcuts, and direct_messages_modal, plus 3 duplicate keys folded
into existing tests (MM-T480, MM-T1256, MM-T1257).

POM/infra: add a ChannelMenu page object for the channel header dropdown, an
EditChannelHeaderModal, a header field on the channel-settings info tab, a
channel-wide-mentions checkbox on NotificationsSettings, a getConfirmModal
helper, and a clickNotification browser-API mock helper. Give the channel
header menu and the confirm modal proper accessible names so they can be
targeted semantically. Notification specs run on all browsers now that the
Notification API stub works headless on Chromium and Firefox.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-07-13 15:37:20 +08:00
Ben SchumacherandClaude Sonnet 5 cce485f605 [MM-69561] Add ability to rotate (regenerate) Personal Access Tokens (#37295)
* MM-69561: Add ability to rotate (regenerate) Personal Access Tokens

- Add UpdateTokenRotate to UserAccessTokenStore interface and implement
  in sqlstore: deletes sessions on the old secret, then updates the
  token row with the new secret and expiry in one transaction
- Regenerate store retrylayer, timerlayer, and mocks
- Add RotateUserAccessToken app method: validates expiry (bot-exempt),
  captures old session for cache eviction, generates new secret, and
  sends a notification email
- Register POST /api/v4/users/tokens/rotate handler with full permission
  checks (create_user_access_token + edit_other_users + manage_system
  for sysadmin targets); rejects OAuth sessions and disabled tokens
- Add RotateUserAccessToken to the Go client (client4.go)
- Add storetest covering secret rotation and old-session cleanup
- Add API4 tests: happy path, permission denials, OAuth rejection, and
  max-lifetime enforcement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69561: Add dedicated rotate email and i18n strings

- Add SendUserAccessTokenRotatedEmail (subject/body distinct from the
  'added' email so users aren't confused by a rotation event)
- Add SendUserAccessTokenRotatedEmail to ServiceInterface + mock
- Add en.json strings for the rotate email and the two new error ids
  (rotate.app_error, disabled_token.app_error)
- Switch RotateUserAccessToken to call SendUserAccessTokenRotatedEmail

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69561: Add mmctl token rotate subcommand

- Add RotateUserAccessToken to the mmctl Client interface and mock
- Add 'mmctl token rotate <token-id> [--expires-in <duration>]' command
  reusing the existing resolveTokenExpiry/parseExpiresIn helpers from
  'generate'; prints the new secret once on success
- Add unit tests: happy path, --expires-in passed through, server error,
  invalid --expires-in

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69561: Regenerate mmctl docs for token rotate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69561: Add API docs for POST /users/tokens/rotate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69561: Fix i18n string ordering after extract

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69561: Add missing API4 test cases for token rotate

Cover the three untested access-control branches flagged by the test
analysis bot:
- Rotating a disabled token returns 400
- Non-system-admin rotating a sysadmin's token returns 403
- Rotating a remote user's token returns 403

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69561: Address review comments

- Fix handler authorization order: check SessionHasPermissionToUserOrBot
  and manage_system before IsRemote/IsActive to avoid leaking token state
  to unauthorized callers; matches revokeUserAccessToken/disableUserAccessToken
- Fix API docs minimum server version: 10.8 -> 10.10

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69561: Restore i18n strings accidentally deleted by extract

The earlier i18n-extract run stripped ~164 unrelated translation keys
(mostly enterprise-only strings like app.pap.* and api.ldap.*) because
the enterprise codebase isn't present in this checkout, so the
extractor treated them as unused. Restore them while keeping the 5
new keys added for token rotation.

* comment

* [MM-69561] Add webapp Regenerate option for Personal Access Tokens

Adds a "Regenerate" link to Account Settings > Security > Personal
Access Tokens that calls the POST /users/tokens/rotate endpoint added
in the server-side rotate PAT work. Regenerating shows a confirmation
modal naming the token, then reveals the new secret via the existing
one-time-copy flow used for token creation.

- webapp Client4.rotateUserAccessToken
- mattermost-redux rotateUserAccessToken action
- Regenerate link/confirm modal/reveal flow in user_access_token_section
- i18n strings
- Playwright e2e coverage

* Fix regenerate PAT e2e test: confirm modal is not nested in the Profile dialog

ConfirmModal renders via react-bootstrap's Modal, which portals to
document.body as a sibling of the Profile dialog rather than a
descendant, so it must be located via #confirmModal on the page
instead of scoped to the Profile dialog locator.

* Let users pick a new expiry when regenerating a Personal Access Token

Previously, regenerating a token always called rotateUserAccessToken
with no expiresAt, so the rotated secret never expired even if the
original token did. The Regenerate confirmation modal now includes the
same expiry picker used by token creation (extracted into a shared
renderExpiryPicker helper), enforces MaximumPersonalAccessTokenLifetimeDays
the same way, and disables the confirm button until a valid expiry is
selected.

* Scope the red background in the Regenerate modal to the warning text only

The confirmation question, expiry picker, and its hints were sitting
inside the same alert-danger box as the warning, making the whole
modal read as an error. Only the warning paragraph keeps the red
background now.

* Move the regenerate confirmation question below the expiry picker

* Align rotate-token wording with UI: use 'regenerate/regenerated'

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* [MM-69561] rename pat_expiry_notify job to notify_expiring_access_tokens

Unifies naming with the sibling cleanup_expired_access_tokens job and
fixes the "expiry" vs "expiring" ambiguity: this job warns about tokens
approaching expiry, not ones that have already expired. Safe to rename
outright since the job hasn't shipped yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* [MM-69561] remove dead session lookup from EnableUserAccessToken

The GetSessionContext call and its result were never used: both branches
returned nil regardless. Leftover from mirroring DisableUserAccessToken's
shape, which does use its session (to revoke it) unlike Enable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* [MM-69561] rename remaining PAT identifiers to match AccessToken convention

The job-level rename (pat_expiry_notify -> notify_expiring_access_tokens)
left the app-layer function and its helpers using the old PAT/
PersonalAccessToken naming. Rename them to match:

- NotifyPersonalAccessTokensExpiring -> NotifyExpiringAccessTokens
- patExpiryBucket -> accessTokenExpiryBucket
- sendPATExpiryNotification -> sendAccessTokenExpiryNotification
- patExpiryNotifyBatchLimit -> expiringAccessTokenBatchLimit
- patExpiryThresholds -> expiringAccessTokenThresholds
- maxPersonalAccessTokenExpiry -> maxUserAccessTokenExpiry

Also reword the package doc on notify_expiring_access_tokens, which no
longer needs to explain a PAT/UserAccessToken naming split now that the
app-layer method matches the job name.

Pure rename, no behavior change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-10 23:08:19 +02:00
Jesse Hallam 09bda773d8 MM-69585: remove AttributeBasedAccessControl feature flag (#37366)
* MM-69585: remove AttributeBasedAccessControl feature flag

* Gate channel ABAC UI on EnableAttributeBasedAccessControl config
2026-07-10 16:24:25 -03:00
cursor[bot] 6619448fec [MM-69589] Remove ExperimentalAuditSettingsSystemConsoleUI feature flag (#37385) 2026-07-10 07:11:14 -03:00
Scott BishelandScott Bishel 0d7ae8e58d Add file upload element to interactive dialogs (#36881)
* Add file upload element to interactive dialogs

  Adds a new file element type to interactive dialogs, letting users
  upload files in a dialog and forward the file IDs to the integration.

  Server:
  - model: new file DialogElement type with validation, AllowMultiple
    field, and SubmitDialogRequest.FileIds.
  - SubmitInteractiveDialog validates submitted file IDs (existence +
    ownership) from both file_ids and any file IDs referenced in
    submission values; bounded and batched.
  - client4.getFileInfo / Client4.GetFileInfo.

  Webapp:
  - AppsFormFileUpload component: upload, progress, removal, and
    hydration of pre-set file IDs; single vs allow_multiple selection.
  - Wired into the dialog -> apps-form conversion (file field type).
  - Submit is blocked while any field has an upload in progress
    (per-field pending tracking).

  Tests:
  - Go unit tests for model + submit-time file-ID validation.
  - Jest tests for the upload component.
  - Cypress e2e spec (file_upload_spec.js) + webhook fixtures.

  Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* coderabbit review fixes, linter fixes

* update openAPI spec for file upload

* revert changes to package-lock.json

* review fixes, add correct ids to E2Etests

* fix dryrun security issue

* lint fixes

* Address dialog file upload review feedback and UI file cap

* test fixes

* add several unit tests

* lint fix

---------

Co-authored-by: Scott Bishel <sbishel@ScottsFderalMac.home.local>
2026-07-09 21:44:53 -06:00
Scott BishelandMattermost Build ca25611b0c MM-69219: Add multiple concurrent dialogs via action_button element type (#37119)
* Add multiple concurrent dialogs support with action_button element type

  Enable plugins to open child dialogs from within an existing dialog via a new
  action_button dialog element and POST /actions/dialogs/execute endpoint. Parent
  dialogs stay open while children stack (cap of 3). Dialog handlers derive the
  team permission and forwarded team from the server-loaded channel, not the
  client-supplied TeamId.

* update docs, code rabbit review fixes

* update comment per coderabbit

* fix tests

* increase test coverage

* fix bad merge

* Replace PostActionAPIResponse with ExecuteDialogActionResponse; replace single-dialog Redux slot with dialogs map

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-09 17:03:14 -06:00
Scott BishelandMattermost Build a6060f5d05 MM-68754: Add silent post delivery for bots and integrations (#36771)
* MM-68754: Add silent post delivery for bots and integrations

  Bot, OAuth, incoming webhook, and plugin posts can request silent
  delivery via ?silent=true (REST), silent:true (webhook payload), or
  silent_notification:true (plugin Props). Silent posts are visible in
  the channel but produce no notifications, unread, or New Messages
  line. force_notification overrides silent. Non-integration senders
  requesting silent receive HTTP 403.

  Also tightens SanitizeProps to strip from_webhook, from_bot,
  from_oauth_app, from_plugin, force_notification, and
  silent_notification from client input. These props are now set
  server-side based on session and entry-point flags, preventing
  identity spoofing. Legitimate callers see no behavior change since
  the server already controlled these props at the response shape;
  the hardening only affects callers attempting to forge them.

* Drop redundant prop-bag silent_notification path from webhooks

* Address CodeRabbit feedback + rebase test fixup

* Update tests to use CreatePostFlags.FromIncomingWebhook instead of forging from_webhook prop

* update api documentation

* test fixes

* added additional tests

* review fixes, update comments

* code review changes, fix test

* fix formatting issues

* fix lint errors

* avoid backward compatibility issues, by no longer stripping the  Post properties.
will add them back in v12 when breaking changes are allowed

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-09 17:02:22 -06:00
Vishal Kumar SinghandMattermost Build befdb0175a Stop leaking message body via the Notifications API tag (#36364)
* Stop leaking message body via the Notifications API tag

showNotification was passing the rendered chat body as the Web Notifications
API tag option. On Chromium-based browsers (Chrome, Edge, Brave), the tag is
serialised into the notification-activation command line via the
--notification-launch-id argument, where endpoint detection tooling such as
CrowdStrike Falcon FDR, Microsoft Defender for Endpoint, and Sysmon Event ID 1
captures the full process-start command line and forwards it to the customer's
SIEM. That meant private message content (including incident-response messages,
credentials accidentally pasted into chat, and customer PII) was being copied
into telemetry pipelines that were never in scope to receive it.

Use the title - which already carries only the sender / channel context - as
the tag instead. As a side benefit this is closer to the spec-intended use of
tag: subsequent notifications from the same conversation now replace the prior
one rather than stacking.

Add a regression test covering the leak: the test pushes a body with a
plausible secret pattern (token=AKIA-...) and asserts the tag never echoes
any of it.

Signed-off-by: Vishal Kumar Singh <vishal.kr.singh2021@gmail.com>

* Allow callers to pass an explicit notification tag

Threads channelId through dispatchNotification so per-conversation
notifications coalesce by a stable opaque id rather than the user-visible
title. The title remains as a safe fallback when callers do not supply a
tag, preserving the existing behaviour for the session-expired notification
emitted from login.tsx where no channel context exists.

Signed-off-by: Vishal Kumar Singh <vishal.kr.singh2021@gmail.com>

* fix: explain desktop notification path

* Avoid title fallback for notification tags

Signed-off-by: Vishal Kumar Singh <vishal.kr.singh2021@gmail.com>

* test: align notification action payload expectations

Signed-off-by: Vishal Kumar Singh <vishal.kr.singh2021@gmail.com>

* test: align notification tag e2e expectation

---------

Signed-off-by: Vishal Kumar Singh <vishal.kr.singh2021@gmail.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-09 13:30:26 +02:00
49b7406ad7 Add Playwright E2E test for maximum login attempts lockout (#36932)
Tests that a user account is locked after exceeding the configured
MaximumLoginAttempts limit and can be unlocked via the admin API.

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-09 08:41:53 +08:00
f0abe8a596 [MM-69591] Remove WebSocketEventScope feature flag (#37384)
* Remove WebSocketEventScope feature flag gating on server

Leave the typing/reaction WebSocket event scoping permanently enabled.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Remove WebSocketEventScope feature flag usage in webapp

Always update the active channel/team/thread over the WebSocket so the
event scoping stays permanently enabled.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Remove WebSocketEventScope feature flag from e2e default config

Co-authored-by: mattermost-code <matty-code@mattermost.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
2026-07-08 21:18:18 +00:00
af43bfdbb0 [MM-69593] Remove StreamlinedMarketplace feature flag (#37390)
* Remove StreamlinedMarketplace feature flag definition

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Remove StreamlinedMarketplace feature flag from webapp

The streamlined marketplace UI is now the permanent behavior. Remove the
selector, the legacy tabbed/search branch of the marketplace modal, and
update the affected tests and snapshots.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Remove obsolete StreamlinedMarketplace feature flag guard in cypress spec

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Remove unused marketplace i18n strings after flag removal

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Clamp marketplace pagination when listing shrinks

When fetchListing() or plugin-status refetches reduce the listing size,
keep the current page within the last valid page so users are not left
on an empty view with no pagination footer.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-07-08 16:03:57 -03:00
7870605fb1 [MM-69590] Remove NotificationMonitoring feature flag (#37386)
* [MM-69590] Remove NotificationMonitoring feature flag

The NotificationMonitoring feature flag shipped and defaulted true in
v9.9. Remove the flag and all conditional gating so notification
delivery metrics collection is permanently enabled, gated only by the
MetricsSettings.EnableNotificationMetrics admin setting.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-69590] Cover notification metrics client config

Assert EnableNotificationMetrics client config prop tracks the
MetricsSettings.EnableNotificationMetrics admin setting now that the
NotificationMonitoring feature flag gate is removed.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-69590] Cover notification counter gating

Add app-layer coverage asserting CountNotification increments the
notification counter only when MetricsSettings.EnableNotificationMetrics
is set, exercising the un-gated notificationMetricsDisabled path.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-69590] Cover websocket notification counter gating

Assert the websocket notification counter increments via the posted-ack
broadcast hook only when MetricsSettings.EnableNotificationMetrics is
set, exercising the un-gated incrementWebsocketCounter path.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Address PR feedback: 0 answered, 1 resolved, 0 declined

Document that MetricsSettings.EnableNotificationMetrics must be set to
true for notification monitoring, matching the code gating and the
push-notification-health-targets doc.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-07-08 16:00:49 -03:00
sabril fe7fd4ebec SEC-10721 E2E/Playwright: Migrate RFQA browser tests (batch 3, 41-60) (#37380) 2026-07-09 01:48:18 +08:00
sabril 147777a8c6 SEC-10588 E2E/Playwright: Migrate RFQA browser tests (batch 2, 21-40) (#37375) 2026-07-09 00:26:48 +08:00
sabril 02d770d64b SEC-10587 E2E/Playwright: Migrate RFQA browser tests (batch 1, 1-20) (#37352) 2026-07-08 15:20:29 +08:00
sabril 8fa7e72a6e E2E/Playwright: Reorg POM using accessibility locators (#37315)
* Use semantic locators in Playwright POM

* Refactor test IDs for badges and modals to use more descriptive names

* remove unused field-with-error
2026-07-06 03:45:22 +00:00
sabril 87b7433d2d E2E/Cypress: Upgrade cypress to 15.18 and its deps (#37278)
* chore: cypress upgrade to 15.18 and its deps

* handle non-zero exit
2026-07-06 10:25:08 +08:00
Jesse Hallam 68389fabbb MM-69466: re-fetch admin config on config_changed to prevent concurrent save clobber (#37338) 2026-07-03 08:48:07 -04:00
076370e690 MM-67412: System Console — Board Attributes screen (PSAv2-based) (#36518)
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-02 21:41:39 +03:00
Ben SchumacherandClaude Sonnet 5 ef60931ab9 [MM-69075] Revoke non-compliant personal access tokens (#37030)
* [MM-68421] Frontend: PAT creation UI — expiry picker and status display

Wires the webapp UI to the server-side support added in PR #36706 (and
the MM-68419 model changes):

- Extends UserAccessToken type with expires_at, and ClientConfig with
  EnforcePersonalAccessTokenExpiry / MaximumPersonalAccessTokenLifetimeDays.
- Threads expires_at through Client4.createUserAccessToken and the
  mattermost-redux createUserAccessToken action.
- Adds a date/time picker to the PAT creation form (reuses
  DateTimePickerModal). Honors enforcement and clamps to the
  max-lifetime setting; maps server error ids
  (expires_at_required/in_past/too_far) to localized messages.
- Displays expiry, derived status badge (active/expired/disabled), and
  an approaching-expiry warning (<7 days) in the account settings token
  list. Mirrors expiry + status in the admin Manage Tokens modal.
- Adds the supporting i18n keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Address review: i18n, ServerError type, scss, explicit guards

- Inject intl so the "Expires in N days" tooltip and the picker
  ariaLabel are localized instead of hard-coded English.
- Use the canonical ServerError type from @mattermost/types/errors
  instead of an ad-hoc inline cast.
- Dedupe the new i18n ids — keep a single "Expires: " string per
  namespace and drop the duplicates introduced in the first pass.
- Add scss for setting-box__token-expiry / __token-status /
  __token-expiry-warning so the new status pill and warning render
  with the expected styling.
- Replace truthy checks on the numeric expiresAt with explicit
  > 0 comparisons for readability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Replace expiry datetime picker with preset chooser + custom date

Per UX discussion: PATs are long-lived so sub-day precision is noise.
Swap the DateTimePickerModal for a native <select> of presets
(No expiry, 7d, 30d, 90d, 1 year, Custom date) with a date-only
<input type="date"> revealed when Custom is selected. Effective time
is end-of-local-day, which matches how users think about expiry.

- Drops the DateTimePickerModal import and the picker open/close
  handlers; removes the moment-timezone import.
- Adds isPresetAllowed() that hides presets exceeding
  MaximumPersonalAccessTokenLifetimeDays, and a defaultExpiryPreset()
  that picks 30d (then 7d, then Custom) when enforcement is on,
  No expiry otherwise.
- The custom <input type="date"> uses min=today and max=now+maxDays
  for native bounds; submit-time validation still maps server error
  ids if the user bypasses the bounds.
- i18n: adds preset labels; drops the now-unused picker/clear/change
  strings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Fix CI: playwright config and ESLint blank line

- Add EnforcePersonalAccessTokenExpiry / MaximumPersonalAccessTokenLifetimeDays
  to e2e-tests/playwright/lib/src/server/default_config.ts so its tsc -b
  matches the updated ServiceSettings shape.
- Drop a stray double blank line in user_access_token_section.tsx that
  tripped no-multiple-empty-lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Sort i18n keys to match formatjs extract output

ci/i18n-extract diffs en.json against the formatjs extractor's sorted
output and fails on any difference. Re-run the extract so the new
PAT-expiry keys land in alphabetical position.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Address CodeRabbit findings

- Clamp the default custom-expiry date to maxLifetimeDays when set
  so the form doesn't open in an invalid state when
  defaultExpiryPreset() falls back to 'custom'.
- Reject a cleared/empty custom date with expires_at_required
  instead of silently submitting without an expiry; only forward
  expiresAt to the action when it's > 0.
- Use an explicit undefined check in Client4.createUserAccessToken
  so an intentional 0 isn't dropped from the request body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Update manage_tokens_modal snapshot for expiry + status row

The admin token list now renders an Expires row and a status badge per
token; refresh the jest snapshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Add unit tests for PAT expiry helpers

Per the PR Test Analysis advisory: export the pure helpers from
user_access_token_section.tsx and add unit tests covering them.

Coverage:
- deriveTokenStatus: active / expired / inactive branches.
- mapServerErrorIdToMessage: all three server error ids (short and
  api.user.create_user_access_token.*.app_error variants) and the
  default null path.
- endOfLocalDayPlusDays: end-of-day on the Nth future day, 0 days.
- endOfLocalDayFromIsoDate: valid ISO date and malformed inputs.
- PRESET_DAYS: snapshot of the preset durations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Freeze time in PAT helper tests to avoid midnight flake

The date arithmetic helpers (Date.now, new Date()) could disagree
across a midnight boundary, making the tests theoretically flaky.
Pin system time to a stable mid-day in 2026 via jest.useFakeTimers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Add component tests for PAT expiry creation UI

Covers the create-form validation branches (missing description, empty
custom date, past date, beyond maxLifetimeDays), enforceExpiry rendering
(no-expiry option hidden + enforced hint), maxLifetimeDays preset
filtering, and token-list status display (active/expired/disabled, never,
and the "expires soon" warning).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Add behavioral status/expiry tests for manage_tokens_modal

Covers the admin token modal's derived status display: Active + "Never"
for an active token without expiry, Expired for an active token past its
expiry, Disabled for an inactive token regardless of expiry, and Active
with a rendered date (not "Never") for a token expiring in the future.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Align PAT expiry UI with shipped server contract

The server (#36706) did not ship a separate EnforcePersonalAccessTokenExpiry
flag — expiry enforcement is implied by MaximumPersonalAccessTokenLifetimeDays
> 0. Two webapp gaps surfaced once the server side merged:

- enforceExpiry was read from the never-sent config.EnforcePersonalAccessToken
  Expiry, so the "hide No-expiry / require expiry" path was dead. Derive it from
  maxLifetimeDays > 0 instead and drop the dead config flag from the component,
  redux props, ClientConfig/ServiceSettings types, and the e2e default config.
- The server returns app.user_access_token.expires_at_{required,in_past,too_far}
  .app_error, but mapServerErrorIdToMessage matched the api.user.create_user_
  access_token.* namespace, so the localized errors never fired. Map the actual
  ids.

Unit tests updated accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Add Playwright e2e for PAT expiry UI

Covers, against a real server, the personal access token expiry surfaces in
Account Settings > Security:

- the expiry picker renders all presets and reveals the custom-date input
- a custom expiry with no date is blocked client-side
- MaximumPersonalAccessTokenLifetimeDays > 0 hides "No expiry" and oversized
  presets, shows the enforced hint, and rejects an over-the-limit custom date
- the token list shows Active/Never, an "expires in N days" warning, and the
  Disabled badge (seeded via the API)

The expired-status badge is left to the component unit tests since the server
rejects creating a token whose expiry is already in the past.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Use locator('option') for native select assertions

getByRole('option') does not reliably match the options of a closed native
<select>, which would make the absence assertions (toHaveCount(0)) pass
vacuously. Query option elements by DOM instead, matching the repo's house
pattern for native selects.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Fix expiry overshoot vs server cap and review nits

Review found that presets/custom dates resolve to end-of-local-day, which can
sit up to ~24h beyond the server cap of "now + MaximumPersonalAccessTokenLife
timeDays" (an exact duration from creation time). With a max configured, the
default preset equals the cap, so accepting the default and saving was rejected
server-side with expires_at_too_far for most of the day.

- Clamp the submitted expiry to the server cap when a maximum lifetime is set.
  Validation still runs on the raw end-of-day value so an explicitly out-of-range
  custom date is still rejected; only the in-range end-of-day overshoot is clamped.
- Count "expires in N days" from the start of today and floor it, so an end-of-day
  expiry no longer over-reports by one (a 7-day token reads "7 days", not "8").
- Add an aria-label to the custom expiry date input.

Tests: unit coverage for clampExpiresAtToMaxLifetime; a Playwright spec that
creates a token with the default preset under a 30-day cap and asserts success.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Apply prettier formatting to PAT e2e spec

The ci/playwright/npm-check job (lint + prettier + tsc + lint:test-docs) failed
on prettier formatting. eslint and tsc were clean; reformat the spec to satisfy
prettier as well.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Add PAT max-lifetime System Console setting and creation-form UX fixes

- Add ServiceSettings.MaximumPersonalAccessTokenLifetimeDays number field to
  System Console > Integrations > Integration Management, after Enable Personal
  Access Tokens (disabled when tokens are disabled).
- Disable the token creation Save button until a non-empty description is
  entered (description input is now controlled; whitespace-only rejected).
- Render the token creation form as a distinct "Create New Token" card so it no
  longer blends into the existing token list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fix stylelint property order in new-token card

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [MM-68421] Validate PAT expiry inline before submit

Surface the expiry validation error in the create-token form and disable
Save while the selection is invalid, instead of only failing inside the
create-confirmation flow. Previously a system admin had to click Save then
"Yes, Create" before seeing "An expiry date is required." for an empty
custom date.

Extracts the expiry checks into getExpiryValidationError(), reuses it as
the handleCreateToken guard, and renders the result inline + in the Save
button's disabled condition.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Fix PAT expiry e2e specs for inline validation

The "blocks submitting a custom expiry with no date chosen" and "enforces
expiry when a maximum lifetime is configured" specs clicked the Save button
and expected an inline error afterward. Since 7ccd65ea surfaces the expiry
error inline and disables Save while the selection is invalid, the click
timed out on a disabled button.

Assert the inline error is visible and that Save is disabled, instead of
clicking it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* MM-69075: server-side revoke non-compliant PATs

Adds store queries (GetNonCompliantExpiry/CountNonCompliantExpiry),
app methods to count and bulk-revoke (hard-delete) non-compliant PATs in
batches with session-cache invalidation, sysadmin-gated api4 endpoints
with audit logging, Client4 methods, and the OpenAPI spec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* MM-69075: System Console button to revoke non-compliant PATs

Adds a 'Revoke non-compliant tokens' control under Integrations >
Integration Management (next to Maximum Personal Access Token Lifetime).
It shows the current non-compliant count (refreshed on load, save, and
revoke), disables when there is nothing to revoke, and confirms the
irreversible delete with the blast radius. Wires up the TS Client4
methods and i18n strings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* MM-69075: Address PR review feedback

- Rename route from /tokens/revoke_non_compliant to /tokens/non_compliant/revoke for consistency with /tokens/non_compliant/count
- Remove redundant c.LogAudit("") before permission check
- Remove redundant c.LogAudit on success (structured audit record is sufficient)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69075: Simplify non-compliant token revocation with single store call

- Add DeleteNonCompliantExpiry store method: atomically deletes non-compliant
  tokens and their sessions in a single Postgres CTE, returning affected user
  IDs for session cache clearing
- Replace the get->extract IDs->delete dance in RevokeNonCompliantUserAccessTokens
  with the new single store call per batch
- Switch post-loop partial completion check to CountNonCompliantExpiry
- Add partial completion error i18n string
- Clear stale error banner in refreshCount on successful fetch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69075: Remove GetNonCompliantExpiry, migrate tests to DeleteNonCompliantExpiry

GetNonCompliantExpiry is now unused — DeleteNonCompliantExpiry supersedes it.
Remove it from the store interface, sqlstore, retrylayer, timerlayer, and mock.
Migrate the store test to exercise DeleteNonCompliantExpiry instead, adding
session-deletion verification.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69075: Fix DISTINCT undercounting bug and add multi-token-per-user test

- Remove SELECT DISTINCT from DeleteNonCompliantExpiry CTE so each deleted
  token row is returned, not collapsed per user; totalRevoked now counts
  tokens, not users, and batch-continuation is correct
- Deduplicate userIDs in app layer before ClearSessionCacheForUser calls
- Add multi-token-per-user fixture to store test: two non-compliant tokens
  sharing a UserId verify len(userIDs)==4 and catch any future DISTINCT regression

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69075: Fix generated store layers and mock ordering

Regenerate retrylayer/timerlayer via make store-layers and fix
DeleteNonCompliantExpiry alphabetical position in mock file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69075: Inline nonCompliantExpiryWhere into its sole caller

The helper was extracted to share the predicate between GetNonCompliantExpiry
and CountNonCompliantExpiry. GetNonCompliantExpiry is gone; with a single
call site the extracted function adds no value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69075: Fix flaky test — relax global count assertions

Global count assertions (require.Equal against baseline) are fragile when
other concurrent tests hold non-compliant tokens. Replace exact equality
with GreaterOrEqual/LessOrEqual for global counts. The DISTINCT regression
is still caught precisely: sharedUserID must appear exactly twice in the
returned slice (once per token, not once per user).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69075: Fix false partial-revoke error caused by read-replica lag

After DeleteNonCompliantExpiry writes to master, the post-delete
CountNonCompliantExpiry read targets the replica, which may not have
caught up yet. This made the first revoke call return a spurious HTTP
500 even though all tokens were actually deleted, and a second click
then showed 'Revoked 0' because nothing remained.

Fix: track whether the batch loop exited via a natural break (all done)
vs. exhausted revokeNonCompliantMaxBatches (genuinely incomplete), and
signal the partial-revoke error only in the latter case. This removes
the racy replica read entirely — the loop's own exit conditions prove
completion on master.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* MM-69075: Add Playwright e2e coverage for revoke non-compliant tokens UI

Covers the gap flagged in PR review (#37030): button/disabled states,
AlertBanner states, confirmation modal open/cancel/confirm, count
refresh after policy save, and token auth invalidation (compliant and
bot tokens survive a revoke).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* MM-69075: Fix testid for number-type Maximum PAT Lifetime field

Number-type TextSetting inputs use ${id}number as their test id, not
${id}input (only text-type inputs get the 'input' suffix). Found by
actually running the spec locally against a server built from this
branch - all 4 tests now pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* MM-69075: Fix prettier formatting in e2e spec

CI's prettier --check flagged this file; ran prettier --write to match
project style. The other CI lint warnings (max-lines, no-warning-comments)
are pre-existing, in files this branch never touched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* MM-69075: Fix TS2345 - UserAccessToken.token is optional

The token secret field is only populated on the object returned from
CreateUserAccessToken, so its type is string | undefined. Guard for
that in tokenIsUsable instead of asserting non-null at every call site.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-02 18:19:40 +02:00
6332bc948d [MM-64430] Allow editing team name and description from the System Console (#37206)
* [MM-64430] Allow editing team name and description in System Console

Make the Team Profile panel on the System Console team detail page editable
so admins can rename a team and edit its description without switching to the
chat-facing team settings. Changes are saved through the existing
SaveChangesPanel flow via patchTeam.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-64430] Widen team profile edit inputs to fit full team name

The Team Name and Team Description inputs inherited the narrow team-desc-col
width, making long values appear visually truncated. Let the edit column grow
to fill the panel so the full value is visible.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-64430] Add tests for editing team name and description in System Console

Cover the new editable Team Profile fields and TeamDetails save flow:
team_profile renders/edits the name and description inputs and surfaces the
name validation error; team_details saves the trimmed name and description
via patchTeam and blocks the save when the name is too short.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-64430] Strengthen team profile edit tests per review

Scope the name validation error assertion to the name field, and cover error
recovery, whitespace-only names, navigation blocking on edit, and resetting
the fields when a different team is loaded.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-64430] Pre-commit fixes: i18n sync, lint and stylelint

Run i18n-extract to drop the now-unused team profile strings, reorder the new
SCSS rule properties, and reformat the new tests to satisfy JSX lint rules.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Remove stray comment in TeamProfile cloud guard

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Retrigger CodeRabbit review after rate limit

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Address CodeRabbit feedback on team profile save flow

- Only reset editable fields when the selected team changes, not when
  totalGroups updates
- Validate team name before archive/restore operations
- Patch profile fields before archiving so Save and Archive persists edits

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Retrigger Enterprise CI after transient npm ECONNRESET

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Validate team name before opening Save and Archive modal

When the team name is invalid (too short) and the team is toggled to be
archived, clicking Save opened the Save and Archive confirm modal and the
validation error rendered behind it, so confirming appeared to do nothing.
Validate the name in onSave before opening the modal so the inline error
fires immediately, and defensively close the archive modal in handleSubmit.

* Add e2e test for editing team name and description in System Console

* Address review: exact URL assertion + archive-invalid-name e2e coverage

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-07-02 12:39:08 +02:00
Caleb Roseland cb136ac81e MM-61199: Remove channelBookmarks feature flag (#37120) 2026-06-30 18:36:27 -05:00
0fa2713b59 MM-67755: WYSIWYG editor for message composition (#36143)
* MM-67755: WYSIWYG editor for message composition

- Add TipTap-based WYSIWYG editor as an opt-in setting
- Implement formatting bar with text styles dropdown, link popover
- Add custom list extension for Mattermost markdown compatibility
- Add suggestion list integration for mentions and emoji
- Add feature flag and user preference toggle
- Update e2e tests for WYSIWYG editor compatibility

Made-with: Cursor

* Fix package versions to match current master (11.7.0)

Made-with: Cursor

* Add missing PermissionPolicies feature flag from master

Made-with: Cursor

* Fix remaining WYSIWYG UX issues: toolbar overlap, autocomplete, emoji, tables

Made-with: Cursor

* Address CodeRabbit review: null guards, XSS fix, e2e tests, constants alignment

Made-with: Cursor

* UX fixes required by Matt

* linter

* fix e2e

* Fix CI failures: resolve merge conflicts with master

- Fix TS errors from useRewrite hook refactor (additionalControl → rewriteMenuProps)
- Fix onComposition → onCompositionUpdate prop rename on Textbox
- Remove accidentally re-added enzyme-to-json/serializer from jest config
- Extract new i18n strings for WYSIWYG editor components

Made-with: Cursor

* Fix lint: remove unused aiRewriteEnabled and rewriteMenuProps vars

Made-with: Cursor

* Fix jest and E2E test failures

- Add lowlight and ESM deps to jest transformIgnorePatterns
- Replace have.value with have.text for contenteditable elements
- Remove selectionStart/selectionEnd assertions for WYSIWYG editor

Made-with: Cursor

* e2e test

* Expose focus()/blur() on WysiwygEditor handle so focus hooks work

useTextboxFocus drives focus into the composer for many flows (channel
switch, RHS open, post-submit refocus, Reply shortcut, "type to focus
main input"). It calls textboxRef.current?.focus(), but textboxRef is
the legacy <Textbox> ref and is null when the WYSIWYG editor is the
mounted composer, so every focus call becomes a silent no-op.

Add focus() and blur() to WysiwygEditorHandle, backed by editor.commands
.focus()/blur(), and pass wysiwygRef into useTextboxFocus so it picks
whichever ref is mounted. The hook keeps its existing signature for
legacy callers; wysiwygRef is optional.

Verified locally on the WYSIWYG branch:
- messaging/focus_move_spec (3/6 failing -> 6/6 passing)
- messaging/message_edit_post_clear_text_spec (0/1 -> 1/1)
- messaging/message_edit_post_history_spec (0/3 -> 1/3)
- keyboard_shortcuts/dot_menu_spec gets past the Reply-focus assertion

Co-authored-by: Cursor <cursoragent@cursor.com>

* e2e: coerce postMessage args to string in WYSIWYG branch

The legacy textarea path used cy.invoke('val', message) which silently
coerced numbers/undefined to strings. The WYSIWYG branch added in the
previous refactor calls message.replace() and crashes with
"TypeError: message.replace is not a function" whenever a spec passes a
non-string value, e.g. cy.uiPostMessageQuickly(0) or before-each hooks
that pass undefined.

Funnel everything through asPostMessage() so the WYSIWYG branch is just
as forgiving as the textarea branch. Loosen postMessage /
postMessageReplyInRHS / uiPostMessageQuickly typings to `unknown` to
match what callers actually pass.

Fixes failures in: copy_post_text_spec, save_post_spec,
search_filter/{after,before,future_date}_spec, and
scroll/default_images_collapsed_spec.

Co-authored-by: Cursor <cursoragent@cursor.com>

* filtetr by feature flag for now

* coderabbit ai feedback

* coderabbitai comments

* align with code conventions

* fix e2e test

* PR comments

* fix i18n

* anchor link popover to editor selectio

* UX issues

* regression

* Final UX feedback

* linter

* e2e test rename

* Final UX

* Fix playwright lint

* Align e2e mocks with master

* Disable feature flag

* Sanitize urls before handling actions with link popove

---------

Co-authored-by: Nevyana Angelova <nevyangelova@192.168.100.47>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-06-30 15:11:30 +00:00
sabril d85da5ce2c E2E/Playwright: Upgrade Playwright to 1.61 and its deps (#37277)
* chore: upgrade playwright to 1.61 and its deps

* fix failed tests

* address comments

* use assignTeamsToPolicy helper method
2026-06-30 04:38:05 +00:00
1c801690a0 Mattermost Blocks (#36338)
* Mattermost Blocks

* Rethink some block props and styles

* Remove obsolete comment

* Style fixes and add examples

* Add mention, search, and image metadata support

* Several UI improvements

* Validate blocks by translating them

* Add initial e2e tests

* Fix lint

* Fix tests and lint

* Fix lint

* Improve e2e and simplify doPostAction

* server side cleanup

* Fix typo

* Fix style

* Fix some tests

* Fix more tests

* Fix cypress tests

* Fix playwhright tests

* Fix flaky system console test

* Frontend cleanup

* Add blocks editor

* Fix CI

* Limit all blocks width

* Fix test

* Enable webhook service on playwhright CI

* fix flaky test

* Fix style

* Cleanups

* Address coderabbit comments

* Address coderabbit comments

* Address CI errors

* Fix tests

* Address changes in valid actions

* UX fixes

* Fix test

* Fix danger buttons and add allow emojis in buttons

* style tweaks

use semantic color tokens for buttons and accents, update max-heights for scollables,  adjustments to containers, removed btn-sm (using default button size for mm blocks with height and padding adjustments for use within blocks. removed hover selector from utils to prevent hover state issue with primary button, refined semantic-color-warning.

* Address feedback

* Add interactive content to post previews

* Add collapse transitions

* Address autocomplete position

* Handle columns within column sets

* Fix css lint

* Fix tests

* Use feature flag

* Address feedback

* Fix lint and add missing tests

* Fix e2e tests

* Fix e2e test

* Allow interactive posts without a message

* Fix lint

* Address feedback

* Fix ci

* Fix lint

* Add limits to block translations

* Fix lint

* Address feedback

* Address feedback

* Update snapshot

* Fix collapsible not defaulting to the right value

* Add total text limit

* Sync translations with mobile

* Fix snapshot

---------

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-06-29 20:33:46 +02:00
nang2049andNevyana Angelova f4f69470d3 MM-24208: Improve multiple image previews and add video preview (#36922)
* MM-24208: Improve multiple image previews and add video preview

* linter

* fix lint and e2

* PR fixes

* UX feedback

* coderabbit ai feedback

* UX feedback

* Refactor useContainerDimensions hook

* Refactor useContainerdimentions hook to desctructure width

* UI feedback final

* lint

* Remove redundant video previe

* Add caching for thumbnails

---------

Co-authored-by: Nevyana Angelova <nevyangelova@192.168.100.47>
2026-06-29 12:34:35 +00:00
Jesse Hallam 7b10409141 [MM-69229] Remove CloudAnnualRenewals feature flag and dead code (#37151)
* [MM-69229] Remove CloudAnnualRenewals feature flag and dead code

The CloudAnnualRenewals flag (default false, shipped v9.4) gated whether
the cloud subscription's WillRenew/CancelAt timing was exposed to
clients. The feature never launched — the flag has been off since 2023,
so those fields have always been blanked and the webapp banner that
would have consumed them was already removed.

- Server: blank WillRenew/CancelAt unconditionally in getSubscription
  (locking in the long-standing behavior) and drop the now-dead CancelAt
  assignment in the end-user subscription branch.
- Webapp: remove the orphaned CLOUD_ANNUAL_RENEWAL_BANNER and
  ANNUAL_RENEWAL_60_DAY/30_DAY constants (defined but unused).
- Remove the struct field, its SetDefaults entry, and the e2e mirror.

* gofmt feature_flags.go
2026-06-24 17:00:06 +00:00
Jesse Hallam ba033eae47 [MM-69229] Promote ConsumePostHook: remove the feature flag (#37148)
* perf: skip ConsumePostHook clones when no plugin implements it

applyPostsWillBeConsumedHook deep-cloned every returned post via
Post.ForPlugin() on each batch read path before dispatching the
MessagesWillBeConsumed hook, even when no plugin implements it. Add
Environment.HasPluginImplementing(hookId) — the same cheap
supervisor.Implements scan RunMultiPluginHook does internally, minus the
clone, closure, and metrics observe — and guard both apply helpers on it
so stock servers pay nothing for the gate.

* [MM-69229] Promote ConsumePostHook: remove the feature flag

The ConsumePostHook flag (default false, shipped v9.3) gated the public
MessagesWillBeConsumed plugin hook. With the per-read clone now skipped
when no plugin implements the hook, the gate is safe to remove and the
hook becomes always-on (still subject to HasPluginImplementing and the
burn-on-read carve-out).

Drops the struct field, its SetDefaults entry, the e2e default_config
mirror, and the flag-toggling tests that no longer have a flag to toggle.

* Fix early return to also check MessagesWillBeConsumedWithContext
2026-06-23 20:04:46 +00:00
Jesse Hallam dbcd904cc2 [MM-69229] Promote CloudIPFiltering: remove the feature flag (#37150)
The CloudIPFiltering flag (default false, shipped v9.4) gated the
cloud-only IP filtering admin feature. The feature stays cloud-only
independent of the flag:

- Server: ensureIPFilteringInterface still requires license.IsCloud()
  and MinimumEnterpriseLicense, so non-cloud servers continue to get
  501 Not Implemented. Added not-cloud coverage to applyIPFilters and
  getMyIP to match the existing getIPFilters case.
- Webapp: the admin console section now gates on a Cloud license +
  Enterprise tier (it.licensedForFeature('Cloud')) instead of the flag,
  preserving cloud-only visibility.

Drops the struct field, its SetDefaults entry, the e2e default_config
mirror, the admin_sidebar test fixtures, and the flag-toggling test
cases.
2026-06-23 15:26:28 -03:00
Jesse Hallam c55282c738 [MM-69229] Remove orphaned PermalinkPreviews feature flag (#37147)
The PermalinkPreviews feature flag had no consumers and no SetDefaults
entry (zero-value false). The feature is gated by the separate
ServiceSettings.EnablePermalinkPreviews config setting (defaults true).
2026-06-23 10:54:09 +02:00
Jesse Hallam 86732989e8 [MM-69229] Remove orphaned DeprecateCloudFree feature flag (#37149)
DeprecateCloudFree (shipped v8.0.0) had no consumers anywhere in the
server or webapp — the Cloud Free deprecation-banner code that once read
it is long gone, leaving only the struct field, its SetDefaults entry,
and the e2e default_config mirror. Removing it changes no behavior.
2026-06-22 21:17:32 -04:00
159fe5502b Fix MM-T643 long URL embedded image E2E test (#37073)
* Fix long URL embedded image E2E test by raising MaximumURLLength

The test posts an incoming webhook containing an embedded image with a
very long URL. With the image proxy enabled, the rendered image src points
to /api/v4/image?url=<encoded>, whose request URI (~3165 chars) exceeds the
default MaximumURLLength of 2048 and is rejected with
'basicSecurityChecks: URL is too long'. Raise the limit in the test config
so the long URL is accepted, restoring the test's original intent.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Make long URL embedded image E2E test self-contained and robust

via.placeholder.com is no longer a live image host (it now returns an HTML
redirect), so the embedded image never loaded and the 'file thumbnail'
assertion failed. Point the long image URL at a static asset served by the
Mattermost server itself (allowed via AllowedUntrustedInternalConnections),
keeping the URL long while removing the external dependency.

Also drive the post past the Show more overflow threshold via the message
text instead of relying on the image height, and target the stable
#showMoreButton id rather than matching the localized 'Show more' text, which
resolves to a non-visible span.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Scope assertions to the post and verify the proxied image loads

Address test-quality review: scope the thumbnail and Show more/less queries
to the specific post via #post_<id> (the #showMoreButton id repeats per
overflowing post), and assert the embedded image actually decoded
(naturalWidth > 0) so the test proves the long proxied URL was served, not
just that an <img> element exists.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Restore MaximumURLLength config after incoming webhook spec

Back up ServiceSettings via cy.apiGetConfig() in before() and restore
the original values in after() to prevent test-order leakage.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-06-22 11:09:27 -04:00
017a7102f8 [MM-69055] Add rank property field type (#36809)
* Add rank property field type and migrate classification field to use it

Introduces a new 'rank' property field type that behaves identically to
'select' across validation, access-control masking, options handling, and
rendering. The classification markings admin panel now creates its
template, system, and channel fields as 'rank' instead of 'select', with
a paired DB migration to flip any existing classification rows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Enforce rank validation on ranked property field options

For PropertyFieldTypeRank fields, every option must carry a non-negative,
unique rank. Adds the Rank field to CustomProfileAttributesSelectOption
and strips stray Rank values from options on non-rank field types so
they cannot drift into persisted attrs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Materialize rank options in AttributeView and signal field updates to access control

Extend the AttributeView materialized view so rank-typed property values
are exposed as {"name", "rank"} objects, enabling downstream ABAC SQL and
CEL machinery to compare a user's rank against a named option without
baking rank integers into policy expressions.

Add OnPropertyFieldOptionsChanged on the access control service interface
and call it after every property field update so the service can drop any
per-field metadata it caches (such as the rank-by-name map) and invalidate
compiled-policy cache entries that reference the field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Show rank-and-lower options/values for shared_only rank fields

shared_only masking on a rank field previously required an exact option
match, the same as select/multiselect. For rank fields the intended
semantics are clearance-style: a caller sees every option and every
target value at or below their own rank.

filterSharedOnlyFieldOptions and filterSharedOnlyValue now branch rank
fields to filterSharedOnlyRankFieldOptions / filterSharedOnlyRankValue,
which compare against the caller's rank instead of intersecting option
IDs. Select, multiselect, and scalar (text/date/user) masking are
unchanged. A caller with no value of their own has no rank and sees
nothing; the source plugin still sees everything.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Render rank property field type like select in webapp

Treat the new 'rank' custom profile attribute type as a single-select
everywhere it is editable, and make it fully usable from the system
console:

- user_settings/general: render rank fields as a single-select dropdown
  (resolving option IDs to names) instead of a free-text input.
- system_user_detail: resolve rank option names and render the value
  input as a native single-select.
- user_properties_type_menu: add a selectable "Rank" field type
  (reusing the Select icon) so existing rank fields display correctly
  and admins can create new rank fields from the UI.
- en.json: add the "Rank" type label string.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Make rank cache invalidation cluster-aware and cover field deletion

OnPropertyFieldOptionsChanged previously dropped the access control
service's per-field rank cache only on the node handling the property
field update, so peer nodes kept serving stale name->rank maps until
restart. It was also wired into the update path only, leaving deleted
rank fields cached indefinitely.

Call OnPropertyFieldOptionsChanged from DeletePropertyField so a removed
rank field's cached options are dropped too. The access control service
now broadcasts the invalidation cluster-wide (see companion enterprise
change).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Backfill option ranks when migrating classification fields to rank

The select->rank conversion previously only flipped the field type. On
upgrades the existing classification options carry no rank (master's
select option struct has no rank field, so the UI-sent rank was dropped
on save), which left an invalid rank field: the matview projected null
ranks, shared_only masking hid every value, and the validation hook
rejected any later edit.

Backfill a rank onto each option from its 1-based array position. The
classification UI keeps levels in severity order and rewrites the full
options array on every save, so position is the authoritative ordering;
1-based matches both the UI's `opt.rank ?? (i+1)` fallback and the
presets' ranks, so a configured preset is still recognized after upgrade.
The flip and backfill share one migration (one transaction), so the field
is never observable in the invalid (rank, null-rank) state, and the down
migration strips the rank key to restore the prior select shape.

Only the three known classification fields with a non-empty options array
are touched; the updates match at most three rows and add no meaningful
lock footprint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add System Console UI for ranked property fields

Implements the admin UI for the ranked property field type across three
System Console surfaces:

- User Attributes: the "Ranked" attribute type, numbered value chips in
  ascending rank order, a per-chip popover (rename / change rank / remove),
  and an "Edit ranking" modal with drag-reorder, arrow steppers, numeric
  rank inputs, auto-assigned next rank, and inline duplicate rejection.
- Membership Policy editor: ranked comparison operators (is exactly,
  is not, is at least, is greater than, is at most, is less than) shown for
  ranked attributes in place of the standard set, with CEL build/parse and
  default-operator wiring.
- User detail: a ranked-value picker rendering options highest-rank-first
  with numbered badges and a checkmark on the assigned value.

Adds Playwright e2e coverage for all three surfaces (plus page-object
helpers) and webapp unit tests for the operator wiring and rank utilities.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fix enterprise CI: regenerate access-control mocks and add rank i18n key

The enterprise CI lane fails on two pre-existing backend gaps on this
branch:

- go vet (check-style): OnPropertyFieldOptionsChanged was added to
  PolicyAdministrationPointInterface but the generated einterfaces mocks
  were never regenerated, so AccessControlServiceInterface /
  PolicyAdministrationPointInterface mocks no longer satisfy the
  interface (access_control_test.go, access_control_masking_test.go).
  Add the missing method to both mocks.

- Check i18n: enterprise access_control/administration.go references the
  app.pap.rehydrate_rank.app_error key, which was never added to
  server/i18n/en.json. Add it so `make i18n-extract` produces a clean
  diff.

Verified: both affected test packages compile, `make i18n-extract`
yields no diff, and `make i18n-check` passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Retrigger enterprise CI

Pick up enterprise fix 15914c11 (require.NoError in rank cel_utils tests)
in the combined Enterprise CI/tests lane, which pins the enterprise SHA at
mattermost-side dispatch time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Retrigger enterprise CI

Pick up enterprise fix 48db14db (golangci-lint findings in rank
access-control code) in the combined Enterprise CI/tests lane.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Clamp shared_only rank values to the caller's rank instead of hiding

For a shared_only rank field, filterSharedOnlyRankValue previously hid a
target's value entirely when the target outranked the caller, returning
nil. That answered "is the target at or below me?" but not the question
the field exists for: "what can we talk about, and at what level?" A
higher-ranked target simply disappeared.

Now the value is clamped to the highest rank the caller shares with the
target — the target's own value when it is at or below the caller's rank,
otherwise the option at the caller's own rank. The caller always learns
their shared ceiling and never sees a rank above their own. Ranks are
unique per field, so the clamp target is unambiguous.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Renumber rank migrations to 194-197 (after property_groups 193)

000193_add_property_groups_schema_version was already on master, so
our rank migrations should follow it: 194-197.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix e2e classification helper field names after rename migration

Migration 000196 renamed channel_classification → classification and
system_classification → classification. Update the e2e helpers to
create and clean up fields with the new canonical names so the frontend
(which looks for 'classification') can find them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Remove unused i18n key and fix e2e prettier formatting

app.pap.save_policy.advanced_expression_blocked was added to en.json
but never used as an AppError key in Go code — remove it so i18n-check
passes. Also fix prettier formatting in ranked_operators.spec.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix 000194 down migration: drop AttributeView before enum rebuild

TestUpAndDownMigrations/Should_be_reversible_for_postgres failed because
the down migration rebuilds the property_field_type enum (Postgres can't
drop an enum value in place), which requires ALTER COLUMN on
PropertyFields.Type. The AttributeView materialized view reads that
column, so Postgres rejects the alter: "cannot alter type of a column
used by a view or rule".

The up path is unaffected because it uses ADD VALUE (no column rewrite);
only the down rebuild trips the dependency. Mirror the canonical
drop-view / alter-column / recreate-view pattern: drop AttributeView,
rebuild the enum, then recreate the no-rank view (the same definition
000197's down restores, which 000177's down later replaces).

Verified end-to-end against PostgreSQL 16: full up-then-down sequence
now passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add unit tests for rank validation and field-change signalling

Fills two gaps flagged by PR test analysis:
- access_control_attribute_validation_test.go: rank option validation
  (valid ranks persist; missing/negative/duplicate rank rejected; zero
  rank allowed; non-rank fields strip stray rank values).
- property_field_test.go: UpdatePropertyFields/DeletePropertyField
  signal the access control service via OnPropertyFieldOptionsChanged
  for each affected field, and stay nil-safe when no AC service exists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add api4 tests for rank-field permission branching

Fills the API gap flagged by PR test analysis: rank fields participate
in the isOptionsOnly permission branch alongside select/multiselect.
- properties_test.go: an options-only PATCH on a rank field uses the
  narrower manage-options permission (member succeeds), while a
  structural PATCH (name change) requires the full edit-field
  permission (member forbidden, admin succeeds).
- custom_profile_attributes_test.go: an options-only PATCH on a rank
  CPA field routes through the options path and round-trips ranks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add mmctl unit tests for rank attribute resolution

Fills the mmctl gap flagged by PR test analysis. Both resolution
directions for rank-typed attributes were untested:
- user_attributes_test.go (TestResolveDisplayValue): a stored rank
  option ID resolves to its option name for display; unknown IDs and
  option-less fields fall back to the raw value.
- user_attributes_value_test.go (TestResolveOptionNamesToIDs): setting
  a rank value by option name resolves to the option ID; already-an-ID
  and unknown names pass through.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Refine ranked attribute editing UI

Edit ranking modal:
- Derive ranks from row position so reordering or removing a value always
  keeps them contiguous (1..N) with no gaps or duplicates
- Drop the arrow steppers in favor of drag-only reordering; render the drag
  clone through a body portal so it isn't offset by the modal dialog transform
- Show values as read-only chips (lowest-first) with Lowest/Highest labels
- "Add value" toggles into an inline row (fake handle + borderless field);
  Enter commits the value instead of closing the modal
- Title shows the field name plus "Ranked attribute"

Inline rank values:
- Rank badge fills the left of the chip; add an inline remove (X) that reuses
  react-select's CrossIcon so it matches select fields

Also give the rank attribute type its own SortAscendingIcon and trim a couple
of stale comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fix SCSS property order lint errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix e2e rank modal tests to match drag-and-drop UX

The ranked schema modal uses position-based ranks (drag to reorder) — there
are no editable numeric rank inputs per row and no per-row name inputs.  The
two failing tests were written against an earlier design that had those inputs:

- "rejects a duplicate rank inline" expected .ranked-schema-modal__rank-input
  and .ranked-schema-modal__error (neither exists).  Replaced with a test that
  covers the actual duplicate-label guard on the add-value inline input.

- "adds a value via the Edit ranking modal" expected .ranked-schema-modal__name-input
  (doesn't exist) and that save is disabled while the add input is open (it
  isn't — save is disabled only when rows is empty).  Updated to use the real
  add-value flow: click "Add value", fill .ranked-schema-modal__add-input, blur
  to commit.

Also removes the three broken page-object helpers (rankedModalRankInputs,
rankedModalError, rankedModalNameInputs) whose CSS selectors never existed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce positive ranks and backfill them on select-to-rank conversion

Rank values must be positive integers (>= 1), matching the webapp's
isValidRank helper and rankForIndex which both require rank >= 1. The
server was accepting rank = 0 (rank < 0 check), allowing invalid data
that would cause filtering bugs: a caller with a rank-0 option would
see nothing in a shared-only field since no option would satisfy
rank <= 0. Change the guard to rank <= 0 and update the error message
and tests accordingly.

When converting a field from select/multiselect to rank via the type
dropdown, the existing options had no ranks. The server's option
validation then rejected the save (or the UI showed stale local state
appearing valid). Auto-assign contiguous 1-based ranks in array order
at the point of type change so the conversion is immediately valid and
the user does not need to open the rank-ordering modal just to commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Show an inline error when a ranked option is renamed to a duplicate

Renaming a ranked option to a name already used by another option was
silently ignored: the draft reverted with no feedback, leaving the user
unsure why their edit didn't stick. The add-value flow already warns on
duplicates; the rename path now matches it.

The chip popover surfaces "Values must be unique." beneath the label
input while the typed name collides, and the rename stays blocked so a
duplicate is never committed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Scroll the ranked schema list so the modal footer stays in view

A ranked attribute with many values (e.g. 30) grew the edit modal taller
than the viewport, pushing the Save/Cancel footer off the bottom of the
screen where it couldn't be reached.

Cap the value list at 50vh and scroll it internally, keeping the header
and footer anchored. The list is the droppable's own scroll container, so
react-beautiful-dnd auto-scrolls it while a row is being dragged, and a
small right padding keeps the scrollbar clear of the remove buttons.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add a shared allow-list for option-bearing property field types

The "is this a select/multiselect/rank field?" check was open-coded as a
three-way type-negation chain in the model, both PSAv2 patch handlers, and
the access-control shared-options filter. Each copy had to be kept in sync
by hand as field types were added.

Introduce an optionFieldTypes allow-list in the model with a
PropertyFieldType.SupportsOptions() helper (mirroring the webapp's
supportsOptions) and route every call site through it, so adding a future
option-bearing type is a one-line change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Guard the rank options filter before its store lookup

filterSharedOnlyRankFieldOptions built the option-rank map and ran the
caller-rank store lookup before checking whether the field had a usable
options array at all. A field with no options (or a malformed attrs blob)
has nothing to filter, yet still paid for a database query.

Move the cheap nil/shape guards to the top so an optionless field returns
immediately, and reuse the extracted options slice in the filter loop
instead of pulling it out of attrs a second time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Memoize the ranked attribute editors' event handlers

The rank schema modal and the inline rank-values cell recreated every
handler (drag-end, move, remove, confirm, rename, add) on each render.
These are passed down to the modal footer, the drag-and-drop context, and
per-chip popovers, so the fresh identities defeated memoization downstream.

Wrap them in useCallback with explicit dependencies. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add tests for the classification select-to-rank migration

The 000195 conversion is non-trivial: it flips specific (Name, ObjectType)
fields in the access_control group from select to rank, then backfills a
1-based rank onto each option from its array position, guarding against
empty or absent options arrays.

Cover the behavior end to end against a real Postgres schema: the type
flip with position-derived ranks in order, the empty- and absent-options
guards (type flips without fabricating options), the name/object-type/group
mismatches that must stay select, and the down round-trip that strips the
ranks and reverts the type.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Gate the rank property field type behind a feature flag

Add a PropertyFieldRank feature flag (off by default) that gates the
"rank" custom profile attribute type.

The enforcement is a single app-layer gate, rankPropertyFieldGate, shared
by both CreatePropertyField (blocks creating a rank field) and
UpdatePropertyFields (blocks converting an existing field to rank). When
the flag is off it returns app.property_field.rank_disabled.app_error.

The admin console CPA type menu hides the rank option unless the flag is
on, read via useGetFeatureFlagValue.

Existing app/api4 tests that exercise rank fields now enable the flag in
their setup so they continue to pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Scope the rank feature-flag gate to user-object fields

The PropertyFieldRank gate rejected any rank-typed field create/update
when the flag was off. But classification markings now uses the rank
type internally (template/system/channel object types) and ships GA
behind the separate ClassificationMarkings flag, which is on by default
while PropertyFieldRank is off by default. The migration also converts
existing classification fields to rank unconditionally. Together this
broke the classification admin panel in the default configuration: both
creating new classification fields and editing existing (migrated) ones
returned app.property_field.rank_disabled.app_error.

Scope the gate to ObjectType == user, which is the only origin of the
user-facing rank CPA type (createCPAField forces ObjectType=user). Rank
fields on other object types are exempt, so classification keeps working
regardless of the flag while the user-facing CPA rank type stays gated.

Add regression cases proving a non-user (classification-style) rank
field is creatable and convertible-to with the flag off, and enable
PropertyFieldRank in the e2e default config so the user-facing rank
specs pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Update webapp/channels/src/components/admin_console/system_properties/rank_badge.scss

Co-authored-by: abhijit-singh <abhijitsingh0702@gmail.com>

* Enable the rank feature flag in the e2e CI server env

The new rank e2e tests failed in playwright-full because the CI docker
server gets feature flags from MM_FEATUREFLAGS_* env vars (config patches
don't stick without a SplitKey), and PropertyFieldRank was only set in the
local default_config.ts. Add the matching env var so the flag is on in CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fix stale migration-number references in classification rank down migrations

The 000195 and 000196 down migrations carried comment references to their
development-era numbers (000191/000192). Correct them to the current numbering
(000195 reverses itself; 000196's down restores distinct names before 000195's
down matches on them). Addresses review feedback from @mgdelacroix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Use PropertyFieldType.SupportsOptions() for option-field type checks

Replace the repeated `Select || Multiselect || Rank` comparisons with the
existing SupportsOptions() helper across the access-control masking, validation,
and option-filtering paths, and in mmctl value resolution. Behavior is unchanged
(SupportsOptions covers exactly those three types). Addresses review feedback
from @mgdelacroix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* webapp: use supportsOptions() helper for option-field type checks

Replace the repeated `type === 'select' || 'multiselect' || 'rank'` checks with
the existing supportsOptions() helper from @mattermost/types/properties, matching
the server-side SupportsOptions() usage. Addresses review feedback from @mgdelacroix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Include rank in extractOptionIDsFromValue error message

The function handles select, multiselect and rank, but the error message only
listed select and multiselect. Addresses review feedback from @mgdelacroix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Validate option rank in CustomProfileAttributesSelectOption.IsValid

When an option carries a rank, enforce that it is a positive integer at the
model layer, mirroring the field-level option validation. Rank stays optional
so select/multiselect options (which carry none) remain valid. Addresses review
feedback from @mgdelacroix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Invalidate access-control cache before broadcasting field update

In UpdatePropertyFields, move the OnPropertyFieldOptionsChanged notification
above the websocket broadcast so a client reacting to the update event never
re-reads stale cached field metadata. This matches the ordering already used in
DeletePropertyField. Addresses review feedback from @mgdelacroix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Assert stored options in options-only patch permission tests

The select/multiselect/rank options-only update tests only checked that the
request succeeded; also assert the returned field's options (id, name, and rank
for the rank case) so the tests actually verify the change was persisted.
Addresses review feedback from @mgdelacroix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add FeatureFlagPropertyFieldRank to webapp ClientConfig type

The server-side PropertyFieldRank feature flag and user_properties_type_menu.test.tsx
reference FeatureFlagPropertyFieldRank, but it was never added to the webapp
ClientConfig FeatureFlags type, leaving tsc -b red. Add the missing field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Tighten the ranked option popover spacing

Drop MUI's default 8px MuiList top/bottom padding on the per-option popover and
reduce the label input's bottom padding to 4px, so the popover reads tighter
without jamming the label input against the top edge. Addresses design feedback
from @abhijit-singh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add header and footer dividers to the Edit ranking modal

Enable GenericModal's bodyDivider and footerDivider so the ranked schema modal
shows a divider under the header and above the Save/Cancel footer, which also
delineates the scroll area when the value list is long. Addresses design
feedback from @abhijit-singh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Match ranked 'Add values…' affordance to select/multiselect

The ranked values cell now mirrors the select/multiselect CreatableSelect
cell for consistency across field types:

- Tab commits a pending value (keeping focus in the input for the next
  one) when it's non-empty and not a duplicate, matching the select cell;
  a blank/duplicate input lets Tab move focus away normally.
- The empty-state placeholder uses the shared 'Add values… (required)'
  text and matches react-select's placeholder color (full-opacity
  neutral50) and 10px content inset, so size, color, and left alignment
  line up with the select cell.
- The placeholder is hidden once values exist, mirroring react-select.
- The chips well gets the same hover/focus background tint and text
  cursor as the select control.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Auto-size the ranked 'Add values…' input like react-select

The bare <input> scrolled its overflow in a fixed-width box, while the
select/multiselect cell grows its input and wraps to a new row as you
type. Replicate react-select's sizer technique: wrap the input in an
inline-grid whose hidden ::after mirrors the live text (content:
attr(data-value), white-space: pre, font: inherit) and sizes the grid
column the input fills. The input now grows with its content and, as a
flex child of the values well, wraps to a new row when it no longer fits.

Both the input and the sizer use 'font: inherit' (matching react-select)
so the measured and rendered text line up and the font matches the select
cell. Since the auto-sized input no longer spans the whole cell, a
mousedown handler on the well forwards focus to the input, mirroring
react-select's clickable control.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fix consistent-type-imports lint errors in Playwright e2e specs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enable PropertyFieldRank feature flag in rank e2e test setup

The Rank type option in the attribute type menu is gated behind the
PropertyFieldRank feature flag. initSetup resets config, so the flag
must be explicitly re-enabled before the test exercises the UI selector.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Remove broken feature-flag patchConfig from rank e2e setup

setupTest called patchConfig({FeatureFlags: {PropertyFieldRank: 'true'}})
with the string 'true'. PropertyFieldRank is a Go bool, so the server's
patchConfig handler (which does json.Decode(&cfg) into *model.Config before
any filtering) failed to unmarshal the string into a bool and returned 400
"Invalid or missing config in request body." This hard-failed setupTest and
all four specs (CI run 27387310039: 4/4 failed at spec line 36).

The call was both broken and unnecessary:

- Without a SplitKey the config store marks FeatureFlags read-only
  (PlatformService.SetupFeatureFlags -> SetReadOnlyFF(!splitConfigured)), so
  no patchConfig can toggle a flag. The flag is enabled at the server level:
  the MM_FEATUREFLAGS_PROPERTYFIELDRANK env var in CI (added to
  e2e-tests/.ci/server.generate.sh) and the server config locally.
- With the env var in place, all four specs passed at commit 8fca57bf3b; the
  later commit that added this line is what regressed them.

Remove the call and document where the flag actually comes from.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Locate ranked add-value input by class, not placeholder

The "creates a ranked attribute" spec timed out at addRankValuesToLast
waiting for getByPlaceholder('Add value…') on the inline add-value input.

Commit 3c12f30008 ("Match ranked 'Add values…' affordance to
select/multiselect") changed that input in two ways the page object never
caught up with:

- the placeholder text became 'Add values… (required)' (plural, suffixed),
  so 'Add value…' no longer matches; and
- the placeholder now renders only in the empty state
  (placeholder={showPlaceholder ? ... : undefined}), so it disappears after
  the first value is added — a placeholder lookup can never add 3 values.

Locate the input by its stable class .user-property-rank-values__add-input
instead, which is present regardless of options count or placeholder text.

This is the real failure the reverted patchConfig change had misdiagnosed as
a feature-flag issue. The spec passed at 8fca57bf3b because that predates
3c12f30008 (placeholder was then 'Add value…' and always shown).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Empty commit to retrigger CI after enterprise branch update

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

* Fix migration test after renumbering 000194 to 000198

During conflict resolution, migration 000194_add_type_id_index_to_access_control_policies
was renumbered to 000198 to maintain sequential ordering after the new rank migrations.
This commit updates the corresponding test file to match:
- Renamed migration_000194_test.go to migration_000198_test.go
- Updated test function name from TestMigration000194 to TestMigration000198
- Updated migration file references to point to 000198 instead of 000194

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

* Renumber migrations: restore 000194 from master, move rank migrations to 000196-000199

The 000194_add_type_id_index_to_access_control_policies migration came from master and should keep its original number. Our rank migrations have been renumbered:
- 000194 -> 000196: add_rank_to_property_field_type
- 000195 -> 000197: convert_classification_fields_to_rank
- 000196 -> 000198: rename_classification_linked_fields
- 000197 -> 000199: add_rank_to_attribute_view

This leaves 000195 available for the threadmemberships_cleanup_v2 migration from master.

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

* Rename migration test files to match renumbered migrations

After renumbering rank migrations from 000194-000197 to 000196-000199, the test file migration_000195_test.go (which tests the classification->rank conversion) needed to be renamed to migration_000197_test.go to match the new migration number. Updated the test function name and migration file references accordingly.

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

* Filter policy field autocomplete to user-type fields only

Add ObjectType: user filter to GetAccessControlFieldsAutocomplete so
the attribute picker in the Membership Policies editor no longer
surfaces template, system, and channel classification-marking fields
as ghost entries alongside real user CPAs.

MM-69366

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: abhijit-singh <abhijitsingh0702@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-06-18 16:43:51 -04:00
Ben SchumacherandClaude Opus 4.8 5b132e2230 [MM-68421] Frontend: PAT creation UI: expiry picker and status display (#36707)
* [MM-68421] Frontend: PAT creation UI — expiry picker and status display

Wires the webapp UI to the server-side support added in PR #36706 (and
the MM-68419 model changes):

- Extends UserAccessToken type with expires_at, and ClientConfig with
  EnforcePersonalAccessTokenExpiry / MaximumPersonalAccessTokenLifetimeDays.
- Threads expires_at through Client4.createUserAccessToken and the
  mattermost-redux createUserAccessToken action.
- Adds a date/time picker to the PAT creation form (reuses
  DateTimePickerModal). Honors enforcement and clamps to the
  max-lifetime setting; maps server error ids
  (expires_at_required/in_past/too_far) to localized messages.
- Displays expiry, derived status badge (active/expired/disabled), and
  an approaching-expiry warning (<7 days) in the account settings token
  list. Mirrors expiry + status in the admin Manage Tokens modal.
- Adds the supporting i18n keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Address review: i18n, ServerError type, scss, explicit guards

- Inject intl so the "Expires in N days" tooltip and the picker
  ariaLabel are localized instead of hard-coded English.
- Use the canonical ServerError type from @mattermost/types/errors
  instead of an ad-hoc inline cast.
- Dedupe the new i18n ids — keep a single "Expires: " string per
  namespace and drop the duplicates introduced in the first pass.
- Add scss for setting-box__token-expiry / __token-status /
  __token-expiry-warning so the new status pill and warning render
  with the expected styling.
- Replace truthy checks on the numeric expiresAt with explicit
  > 0 comparisons for readability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Replace expiry datetime picker with preset chooser + custom date

Per UX discussion: PATs are long-lived so sub-day precision is noise.
Swap the DateTimePickerModal for a native <select> of presets
(No expiry, 7d, 30d, 90d, 1 year, Custom date) with a date-only
<input type="date"> revealed when Custom is selected. Effective time
is end-of-local-day, which matches how users think about expiry.

- Drops the DateTimePickerModal import and the picker open/close
  handlers; removes the moment-timezone import.
- Adds isPresetAllowed() that hides presets exceeding
  MaximumPersonalAccessTokenLifetimeDays, and a defaultExpiryPreset()
  that picks 30d (then 7d, then Custom) when enforcement is on,
  No expiry otherwise.
- The custom <input type="date"> uses min=today and max=now+maxDays
  for native bounds; submit-time validation still maps server error
  ids if the user bypasses the bounds.
- i18n: adds preset labels; drops the now-unused picker/clear/change
  strings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Fix CI: playwright config and ESLint blank line

- Add EnforcePersonalAccessTokenExpiry / MaximumPersonalAccessTokenLifetimeDays
  to e2e-tests/playwright/lib/src/server/default_config.ts so its tsc -b
  matches the updated ServiceSettings shape.
- Drop a stray double blank line in user_access_token_section.tsx that
  tripped no-multiple-empty-lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Sort i18n keys to match formatjs extract output

ci/i18n-extract diffs en.json against the formatjs extractor's sorted
output and fails on any difference. Re-run the extract so the new
PAT-expiry keys land in alphabetical position.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Address CodeRabbit findings

- Clamp the default custom-expiry date to maxLifetimeDays when set
  so the form doesn't open in an invalid state when
  defaultExpiryPreset() falls back to 'custom'.
- Reject a cleared/empty custom date with expires_at_required
  instead of silently submitting without an expiry; only forward
  expiresAt to the action when it's > 0.
- Use an explicit undefined check in Client4.createUserAccessToken
  so an intentional 0 isn't dropped from the request body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Update manage_tokens_modal snapshot for expiry + status row

The admin token list now renders an Expires row and a status badge per
token; refresh the jest snapshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Add unit tests for PAT expiry helpers

Per the PR Test Analysis advisory: export the pure helpers from
user_access_token_section.tsx and add unit tests covering them.

Coverage:
- deriveTokenStatus: active / expired / inactive branches.
- mapServerErrorIdToMessage: all three server error ids (short and
  api.user.create_user_access_token.*.app_error variants) and the
  default null path.
- endOfLocalDayPlusDays: end-of-day on the Nth future day, 0 days.
- endOfLocalDayFromIsoDate: valid ISO date and malformed inputs.
- PRESET_DAYS: snapshot of the preset durations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Freeze time in PAT helper tests to avoid midnight flake

The date arithmetic helpers (Date.now, new Date()) could disagree
across a midnight boundary, making the tests theoretically flaky.
Pin system time to a stable mid-day in 2026 via jest.useFakeTimers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68421] Add component tests for PAT expiry creation UI

Covers the create-form validation branches (missing description, empty
custom date, past date, beyond maxLifetimeDays), enforceExpiry rendering
(no-expiry option hidden + enforced hint), maxLifetimeDays preset
filtering, and token-list status display (active/expired/disabled, never,
and the "expires soon" warning).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Add behavioral status/expiry tests for manage_tokens_modal

Covers the admin token modal's derived status display: Active + "Never"
for an active token without expiry, Expired for an active token past its
expiry, Disabled for an inactive token regardless of expiry, and Active
with a rendered date (not "Never") for a token expiring in the future.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Align PAT expiry UI with shipped server contract

The server (#36706) did not ship a separate EnforcePersonalAccessTokenExpiry
flag — expiry enforcement is implied by MaximumPersonalAccessTokenLifetimeDays
> 0. Two webapp gaps surfaced once the server side merged:

- enforceExpiry was read from the never-sent config.EnforcePersonalAccessToken
  Expiry, so the "hide No-expiry / require expiry" path was dead. Derive it from
  maxLifetimeDays > 0 instead and drop the dead config flag from the component,
  redux props, ClientConfig/ServiceSettings types, and the e2e default config.
- The server returns app.user_access_token.expires_at_{required,in_past,too_far}
  .app_error, but mapServerErrorIdToMessage matched the api.user.create_user_
  access_token.* namespace, so the localized errors never fired. Map the actual
  ids.

Unit tests updated accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Add Playwright e2e for PAT expiry UI

Covers, against a real server, the personal access token expiry surfaces in
Account Settings > Security:

- the expiry picker renders all presets and reveals the custom-date input
- a custom expiry with no date is blocked client-side
- MaximumPersonalAccessTokenLifetimeDays > 0 hides "No expiry" and oversized
  presets, shows the enforced hint, and rejects an over-the-limit custom date
- the token list shows Active/Never, an "expires in N days" warning, and the
  Disabled badge (seeded via the API)

The expired-status badge is left to the component unit tests since the server
rejects creating a token whose expiry is already in the past.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Use locator('option') for native select assertions

getByRole('option') does not reliably match the options of a closed native
<select>, which would make the absence assertions (toHaveCount(0)) pass
vacuously. Query option elements by DOM instead, matching the repo's house
pattern for native selects.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Fix expiry overshoot vs server cap and review nits

Review found that presets/custom dates resolve to end-of-local-day, which can
sit up to ~24h beyond the server cap of "now + MaximumPersonalAccessTokenLife
timeDays" (an exact duration from creation time). With a max configured, the
default preset equals the cap, so accepting the default and saving was rejected
server-side with expires_at_too_far for most of the day.

- Clamp the submitted expiry to the server cap when a maximum lifetime is set.
  Validation still runs on the raw end-of-day value so an explicitly out-of-range
  custom date is still rejected; only the in-range end-of-day overshoot is clamped.
- Count "expires in N days" from the start of today and floor it, so an end-of-day
  expiry no longer over-reports by one (a 7-day token reads "7 days", not "8").
- Add an aria-label to the custom expiry date input.

Tests: unit coverage for clampExpiresAtToMaxLifetime; a Playwright spec that
creates a token with the default preset under a 30-day cap and asserts success.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Apply prettier formatting to PAT e2e spec

The ci/playwright/npm-check job (lint + prettier + tsc + lint:test-docs) failed
on prettier formatting. eslint and tsc were clean; reformat the spec to satisfy
prettier as well.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Add PAT max-lifetime System Console setting and creation-form UX fixes

- Add ServiceSettings.MaximumPersonalAccessTokenLifetimeDays number field to
  System Console > Integrations > Integration Management, after Enable Personal
  Access Tokens (disabled when tokens are disabled).
- Disable the token creation Save button until a non-empty description is
  entered (description input is now controlled; whitespace-only rejected).
- Render the token creation form as a distinct "Create New Token" card so it no
  longer blends into the existing token list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fix stylelint property order in new-token card

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [MM-68421] Validate PAT expiry inline before submit

Surface the expiry validation error in the create-token form and disable
Save while the selection is invalid, instead of only failing inside the
create-confirmation flow. Previously a system admin had to click Save then
"Yes, Create" before seeing "An expiry date is required." for an empty
custom date.

Extracts the expiry checks into getExpiryValidationError(), reuses it as
the handleCreateToken guard, and renders the result inline + in the Save
button's disabled condition.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [MM-68421] Fix PAT expiry e2e specs for inline validation

The "blocks submitting a custom expiry with no date chosen" and "enforces
expiry when a maximum lifetime is configured" specs clicked the Save button
and expected an inline error afterward. Since 7ccd65ea surfaces the expiry
error inline and disables Save while the selection is invalid, the click
timed out on a disabled button.

Assert the inline error is visible and that Save is disabled, instead of
clicking it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 14:27:34 +02:00
Pablo Vélez 6a3b21eb8f Mm 68846 masking from visual to canonical walker (#36772)
* MM-68846 - add canonical CEL AST masking walker and model resolver interface

* add canonical masking methods to PAP einterface and update mock

* Migrate app-layer masking to canonical CEL AST walker

* Remove Visual AST masking dead code and obsolete i18n key

* Update simulation masking tests to mock MaskExpressionForCaller

* reject persisted tokens and enforce merge shape match and add corresponding tests

* MM-68900 - abac masking add e2e back

* add missing config values and split the tests

* implement coderabbit feedback

* adjust timeout and disable button logic

* enhance masking logic and tests for access control policies

* refactor masking tests and database setup for improved field deletion handling

* Enhance error handling in TestMergeStoredPolicyExpressions to verify error ID and status code

* Refactor error handling in access control policy methods for clarity

* Refactor masking logic and improve clarity in access control methods

* Allow deny-all 'false' policies by dropping the redundant sentinel check in rejectMaskedTokens; add tests

* Add masking-related error messages and update test descriptions with tags

* Add error handling for uninitialized Policy Administration Point in access control
2026-06-16 17:09:11 +02:00
Harrison Healey d90ea343bc MM-69003 Update ESLint and related dependencies (#37039)
* Remove everything

* Re-add most ESLint plugins and run --fix

* Additional fixes

* Fix or disable linter warnings caused by require imports

* Fix how prop types for SearchResults are defined

* More additional fixes

* Update deprecated stylistic rule names

* Remove deprecated .eslintignore

* Update eslint-plugin-react-hooks

* Enable react-hooks/error-boundaries

* Fix react-hooks/static-components wherever possible

* Fix react-hooks/use-memo wherever possible

* Disable warnings for new rules and update comment

* Update eslint-plugin-no-only-tests

* Update eslint dependencies in E2E test packages

* Remove now-uneeded patch

* Address Coderabbit feedback

* Actually fix inverted tooltip logic
2026-06-15 13:57:41 -03:00
Pablo VélezandMattermost Build 4641761122 MM - 69063 - team abac backend and security gate (#36903)
* MM-69063 - Add team ABAC model and constants foundation

* Add team ABAC store EXISTS, channel Type retrofit, policy count split, and index migration

* Add team ABAC app layer: access gate, hydrators, assign/unassign, cleanup, and  GetTeamMembersToRemove store

* Enforce team membership ABAC on join and hide policy governed teams from  non-qualifying users in the directory

* Add team_ids to access policy assign/unassign, expose per-team policy GET,  and support abac_match_only for not_in_team user listing

* Add team ABAC client methods, websocket handler, per-team System Console policy UI, and hide policy-governed teams from non-qualifying users

* Make team ABAC mode-aware: advisory on public teams, strict on private, and surface governed private teams to qualifying users in directory listings

* Flag-gate team ABAC mutation/read APIs and fix policy-save error handling, member-removal limit, team-id  validation, export, and audit cleanup

* coderabbit feedback; Broadcast team policy enforcement updates on policy create/update and activation, not only on delete

* Update team access control policy schema to allow nullable policies and enhance test cases with channel counts

* Enhance access control policy tests to include team policy search alongside channel policy search

* Add team membership access control feature flag to docker-compose generation

* Implement team access control policy checks and refactor related components

* Audit-log team ABAC policy removal on team archive and delete

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-06-12 23:39:16 +02:00
Jesse Hallam aad6c8afe8 [MM-69228] Default the CJKSearch feature flag to true (#37032) 2026-06-12 17:03:42 +02:00
Harrison Healey 5cbbb76b7b MM-69003 Switch to using @stylistic/eslint-plugin for deprecated ESLint rules (#36770)
* MM-69003 Switch to using @stylistic/eslint-plugin for deprecated ESLint rules

* Run --fix on all web app packages

These changes are mostly around TypeScript linting where the @stylistic
versions are more accurate and therefore stricter about spacing and
semicolons in TS types.

* Run --fix on Cypress tests
2026-06-11 17:31:15 -04:00
6583982b26 Fix stale channel members RHS list after ABAC access-rule member removal (#36964)
* Fix stale channel members RHS list after ABAC member removal

The channel members RHS list is built from the profilesInChannel and
membersInChannel Redux stores, which are only pruned by the user_removed
websocket handler when the affected channel is currently focused. When a
removal is not handled over the websocket (e.g. an ABAC access-rule change
processed while another channel is focused, or a missed event), reopening
the members list performs an additive reload that never prunes removed
members, so the list stays stale even though the member count is refreshed
separately via getChannelStats.

Make the first-page members reload authoritative: reconcile the channel
member stores against the server response and prune members the server no
longer returns. Scoped to the initial full-membership page to avoid
pruning members that belong to later pages.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Add tests for channel members RHS reconcile on reload

Covers pruning members the server no longer returns on the first page,
and the guards that prevent pruning when reconcile is disabled, on a full
page (more pages may follow), or on subsequent pages.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Strengthen channel members reconcile tests

Cover pruning across both the member and profile stores, multiple stale
members, the no-false-positive case (present members are never pruned),
and assert the members RHS passes reconcile=true on its first-page mount
reload (but not on pagination).

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Tighten reconcile tests with exact-set and perPage guard

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Tighten reconcile comment

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Add ABAC channel members RHS e2e coverage

* Stabilize ABAC RHS e2e sync job wait

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-06-11 07:38:18 -04:00
471fd8d1dd Bound document content extraction time and decouple it from uploads (MM-69098) (#36856)
* Bound document content extraction to prevent upload DoS (MM-69098)

Extraction of uploaded Office documents ran with no time or size bound and
acquired a shared worker-pool slot on the request goroutine, so a low-privilege
user could keep cheap-to-upload but expensive-to-extract documents in flight and
degrade file uploads for every user on the server.

- Enforce a configurable per-extraction timeout (FileSettings.ExtractContentTimeout,
  default 10s) and honor the previously-ignored size argument in the document and
  PDF extractors to bound the work performed.
- Run extraction on a dedicated, bounded worker pool with non-blocking submit
  (GoExtraction) so saturating it can never block the request goroutines that
  dispatch uploads; overflow is skipped and backfilled by the ExtractContent job.
- Route the file and resumable-upload extraction dispatch sites through the new
  pool instead of the shared GoBuffered/Go pools.

Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>

* Use require.Fail instead of t.Fatal in extraction pool test

The mattermost-govet tFatal analyzer (run by check-style) forbids t.Fatal
in tests in favor of testify assertions.

Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>

* ci: retrigger workflows (transient container init failure)

Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>

* Use sync.WaitGroup.Go for extraction workers

Satisfies the golangci-lint waitgroupgo/modernize analyzer flagged by
check-style at goroutines.go:58.

Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>

* Fix file close race with detached extraction goroutine

On timeout, extractWithTimeout returns while the converter may still be
reading the input on a detached goroutine. ExtractContentFromFileInfo
previously closed the file via defer as soon as Extract returned, racing
with (and closing the file out from under) that goroutine.

Transfer close ownership to docextractor via ExtractSettings.ReaderCloser:
the reader is now closed only after extraction actually finishes - in the
detached goroutine on the timeout path, or after Extract returns on the
synchronous path. ExtractContentFromFileInfo no longer closes the file
itself.

Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>

* Document that extraction timeout bounds wait time, not CPU

Clarify in ExtractSettings.Timeout and extractWithTimeout that on timeout
the docconv converter keeps running on a detached goroutine (docconv is
not context-aware), so the timeout bounds how long an extraction occupies
a worker slot, not the CPU it consumes. MaxFileSize is the primary bound
on per-extraction work. Note load-shedding as a possible future
improvement.

Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>

* Add tests for PDF size cap and ExtractContentTimeout validation

- TestPdfMaxFileSize exercises the LimitedReaderWithError branch in
  pdf.go: a tight MaxFileSize errors out before extraction, while zero
  and generous limits extract normally.
- TestFileSettingsExtractContentTimeout asserts ExtractContentTimeout=-1
  fails IsValid while 0 (disabled) and 10 (default) pass.

Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>

* Harden extraction shutdown, panic safety, and timeout input validation

- stopExtractionWorkers now drains the queue after signaling stop so a
  worker cannot dequeue and run buffered tasks during shutdown.
- The detached extraction goroutine recovers panics and converts them to
  errors, so a panic in an extractor cannot crash the server.
- Admin console enforces a client-side minimum of 0 for
  ExtractContentTimeout (validators.minValue).
- Test hardening: bounded wait for the detached extractor to start, plus
  a test that a panic is surfaced as an error.

Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>

* ci: retrigger workflows (transient Docker Hub image pull timeout)

Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>

* Correct misleading extraction-skip logs and comments

The queue-full warning logs and GoExtraction doc comments claimed skipped
content would be backfilled by a 'periodic ExtractContent job'. That job is
registered with a nil scheduler, so it never runs automatically -- it only
runs when an admin manually triggers a content extraction job (mmctl extract).

Reword the logs and comments to state that skipped files stay unsearchable
until an admin runs a content extraction job, so we don't imply automatic
recovery that doesn't exist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 23:12:51 +00:00
Harrison Healey f3836530b7 MM-69003 Mostly share ESLint config between web app and E2E tests (#36767)
* Switch Cypress to use shared ESLint config

* Run --fix in Cypress

* Manually fix remaining lint issues in Cypress

* Switch Playwright to use shared ESLint config

* Run --fix in Playwright

* Manually fix remaining lint issues in Playwright

* Install and cache web app deps during Cypress CI builds

This also caches the types and client package. That isn't needed currently
since it uses prepackaged versions of those, but I imagine we might change
that at some point.

* Run e2e-tests-check when ESLint plugin is updated

* Change E2E test GHA caching to cache all of web app node_modules

* Fix mismatch between cache save and restore

* Try bumping cache keys

* Copy step to install dependencies to server.run_cypress.sh

I don't know how this must've worked before, but if this fixes the issue,
it seems like neither Cypress nor Playwright actually use the cached
depenendencies.

* Try disabling caching entirely for Cypress tests

* Try bypassing makefile?

* Try also manually building dependencies in run_specs.sh

I don't know why this appears to duplicate run_cypress.sh and
run_playwright.sh, both of which are called run_test.sh which
might not be used any more as best I can tell.

* Try installing the web app dependencies in yet another place

* Disable the extra steps in server.prepare.sh specifically for Cypress

* Revert changes to update cache key and disable web app depenedency cache on Cypress builds
2026-06-09 20:50:58 +00:00
Devin Binnie 684ddb32a9 [MM-68988][MM-68989][MM-68990][MM-68991][MM-68997][MM-68998] Session Attributes MVF - Server-work (#36934)
* [MM-68988][MM-68989][MM-68990][MM-68991][MM-68997] Session Attributes MVF - Server-work

* PR feedback

* [MM-68998] Add web app hooks for Desktop App to signal a refresh of attributes/manifest

* Fix types

* Adjust the test to test the license first

* PR feedback

* Coderabbit feedback

* More tests
2026-06-09 13:10:53 -04:00
Maria A NunezandCursor Agent f6e7e71695 Migrate Zephyr manual tests to Cypress E2E (#36971)
* MM-T1814: add 'Add a BOT to a team' Cypress E2E test

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

* MM-T1335: invite guests to public and private channels

Extend invitePeople() helper to accept multiple channels and add a spec
verifying both a public and a private channel are added to the guest
invite list.

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

* MM-T1340: verify guest invite email and join flow (not_cloud)

Add a non-cloud spec covering step 2 of MM-T1340: read the invitation
email, open the join link, complete guest signup, and verify the guest
is added to the team.

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

* MM-T1335: assert channel chips via icons and count

Verified against a licensed E2E server: assert exactly two channel chips
with public and private channel icons rather than an exact text match,
which failed due to extra text in the chip label.

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

* MM-T1340: accept terms and use valid password in guest signup

Verified against a licensed E2E server: the signup Create account button
is gated on a 14+ char password and the terms/privacy checkbox. Use
newTestPassword(), check the agreement, and assert the app loads.

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-06-09 10:33:37 +08:00
Maria A NunezandCursor Agent ffd4814940 MM-T3436: add Cypress E2E for Actiance XML compliance export download (#36970)
Add a Cypress test covering Zephyr case MM-T3436, verifying that with
DownloadExportResults enabled and the Actiance XML export format, a
compliance export provides a Download link and the downloaded archive
contains the posted message content and the file attachment.

Reuses existing compliance helpers (uiEnableComplianceExport,
uiExportCompliance, gotoTeamAndPostImage, downloadAndUnzipExportFile,
verifyActianceXMLFile); modeled on MM-T1173 without the delete step.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-06-09 10:23:20 +08:00
Ben SchumacherandClaude Opus 4.7 aa03fae744 [MM-69026] Add zoom and pan to the image file preview (#36775)
* [MM-69026] Add zoom and pan to the image file preview

Enables the existing file-preview-modal zoom controls for image previews
(previously PDF-only) and adds cursor-aware wheel zoom, drag-to-pan,
keyboard shortcuts, and overflow clipping so the panned image can't
escape behind the modal header.

- Per-file default scale (1.0 for images, 1.75 stays for PDFs).
- Translate state alongside scale; auto-snaps to the origin at default scale.
- Native non-passive wheel listener so preventDefault actually fires.
- Wheel step scaled by deltaY magnitude for trackpad pinch.
- Keyboard: +/=, -, 0; skipped when an input/textarea/contentEditable is focused.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix eslint no-mixed-operators and lines-around-comment

Parenthesise mixed +/* and -/ arithmetic to satisfy
eslint(no-mixed-operators) and add the blank line before the
inline-input guard comment for lines-around-comment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Cap image zoom at 2x and harden lifecycle / drag-pan

- Add ZoomSettings.MAX_SCALE_IMAGE = 2.0 and route image-path clamp
  sites through a new FilePreviewModal.getMaxScaleForFile. PDFs keep
  the original 3.0 ceiling.
- Reconcile scale/translate in getDerivedStateFromProps when
  props.fileInfos changes so newly appearing indexes get seeded with
  the file's default instead of reading as undefined.
- Gate drag-to-pan on currentScale > defaultScale so dragging at
  default scale (image fits the viewport) doesn't slide the image
  around in empty space.
- Tighten the e2e style-match regex so it only accepts scale values
  strictly greater than 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Reset image zoom state on same-length file swaps

A websocket post update can replace an attachment at the same index
without changing the array length, which previously bypassed the
length-based reconciliation in getDerivedStateFromProps and let the
old file's zoom/translate state apply to the new file.

Track a per-index identity (file id, falling back to link) and trigger
the same reconciliation when any identity differs at its index;
indexes whose identity is unchanged keep their existing scale/translate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add unit tests for zoom-related instance behavior and helpers

Covers the gaps flagged by Tests/analysis:

- Static helpers getDefaultScaleForFile, getMaxScaleForFile, and
  getFileIdentity (incl. namespace separation for file vs link
  identities).
- handleKeyDown: +/= zoom in, - zoom out, 0 reset, modifier-key
  bailout, and the INPUT-focus guard.
- handleImageMouseDown drag gate: ignored at default scale and on
  non-left-button mousedowns; sets isDragging when zoomed.
- handleImageWheel clamping at MAX_SCALE_IMAGE (2.0) and MIN_SCALE
  (0.25), plus deltaY=0 short-circuit.
- getDerivedStateFromProps identity reconciliation: same-length swap
  resets the affected index while preserving others; list growth
  seeds the new index with the file's default.

68 tests pass (was 50).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 11:45:00 +02:00