mirror of
https://github.com/mattermost/mattermost.git
synced 2026-11-01 17:33:12 -06:00
master
1615
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f619e1e3e0 |
Remove dead self-serve purchase code (#39528)
* Remove dead self-serve purchase code Remove the unused ChangeSubscription, GetSelfHostedProducts and InvalidateCaches methods from CloudInterface, the always-501 /api/v4/hosted_customer/signup_available endpoint, the unused NotifySelfHostedSignupProgress app method, the hosted purchase screening job, and Client4.GetSelfHostedProducts. Deprecate the related public model types and constants. Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Remove hosted_customer/signup_available from the API reference Co-authored-by: Nick Misasi <nick13misasi@gmail.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
bd81219306 |
Remove Cloud file storage limit (#39526)
* Remove Cloud file storage limit The Cloud product limits no longer define a file storage limit, so the last accessible file job, filtering and header are inert. Add a one-time migration that clears any leftover LastAccessibleFileTime marker. Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Mock the last accessible file time migration key in the test store Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Delete leftover last accessible file jobs in the cleanup migration Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Retry the cleanup migration when deleting a leftover job fails Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Remove the last accessible file time cleanup migration Co-authored-by: Nick Misasi <nick13misasi@gmail.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
4df6bbc764 |
Remove unused cloud invoice endpoints (#39523)
* Remove unused cloud invoice endpoints
Remove GET /api/v4/cloud/subscription/invoices and
/invoices/{invoice_id}/pdf, their CloudInterface methods and the
Client4.GetInvoicesForSubscription method. Mark the invoice model types
as deprecated since server/public is a published module.
Co-authored-by: Nick Misasi <nick13misasi@gmail.com>
* Clarify deprecation notes on cloud invoice model types
Co-authored-by: Nick Misasi <nick13misasi@gmail.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
|
||
|
|
a29df1bb0f |
Remove cloud customer update endpoints (#39519)
Removes the unused PUT /api/v4/cloud/customer and PUT /api/v4/cloud/customer/address routes, their CloudInterface methods, Client4 methods and API documentation. Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
51e826eae1 |
Remove AdjustInProductLimits and the Cloud usage API (#39527)
The Cloud product limits no longer define a teams limit, so AdjustInProductLimits never archives or restores teams. The /api/v4/usage endpoints only served the removed Cloud limits UI. Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
6d6fc9b0c6 |
[MM-71390] Tolerate failed nodes in plain-text logs and fix log viewer filters (#39476)
Server.GetLogs now gets other nodes' logs through ClusterInterface.QueryLogs, so a node that can't be reached gets its usual separator followed by the failed-node error line instead of failing the whole request. Remote logs are only requested in a licensed, enabled cluster, matching QueryLogs, and ClusterInterface.GetLogs is removed. The failed-node entry now carries "failed_node": true, which the structured viewer keys off instead of the message text. queryLogs returns 400 rather than 500 for a filter body that can't be decoded or is null. In the structured viewer, an active time range now counts as a filter for the empty state, "Clear filters" also clears it, and changing it resets the page, expanded row and selection. Claude-Session: https://claude.ai/code/session_01FpCfx5vEzDnjuW2q3TTD5t Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
a275dd6b12 |
Add the checkbox property field type (#39460)
* Add the number property field type A number field holds a JSON number; null or an absent value is empty. - Migration 000236 adds 'number' to the property_field_type enum. - A new ValueTypeValidationHook checks every value write in every group: a number field's value must be a finite JSON number. A value whose field is not in its group is left to the property service, as before. - The access control group refuses number fields, since access policies and the attribute views they read cannot compare numbers. - PropertyFieldType gains IsValid and SupportsAccessControl, and the webapp's FieldType lists 'number'. Split out of mattermost/mattermost#39458 (a port of #39373 from the Boards core branch). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add the checkbox property field type A checkbox field holds a JSON boolean; false, null or an absent value is unchecked. - Migration 000237 adds 'checkbox' to the property_field_type enum. - The value type validation hook refuses a checkbox value that isn't true or false. - The access control group refuses checkbox fields, like number fields. - The webapp's FieldType lists 'checkbox'. Builds on the number field type. Split out of mattermost/mattermost#39458 (a port of #39373 from the Boards core branch). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add number to the property field type enum in the API docs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add checkbox to the property field type enum in the API docs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read field types from the master; refuse trailing text after a number - The value type validation hook reads the fields it checks from the master. A replica that hadn't caught up with a field just created as a number, or changed to one, let a value of the wrong shape through. - A number value must end after the number. Decoder.More only looks for another value, so "1 ]" passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * List rank and graph in the property field type enum in the API docs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Check value updates against the group they write to An update names its group twice: the method's groupID, which the store uses to select the row, and each payload's GroupID. The value type hook read the field from the payload's group, so a payload naming another group skipped validation while the store still wrote the number field's row. Updates are now checked against the method's groupID when it is set, using copies, so the payload isn't changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Check value updates against the field of the row they write An update writes Value to the row its ID names and leaves the row's FieldID as it is. The value type hook checked the field the payload named, so a payload naming a text field could write a string into a number field's row. Updates are now checked against the stored rows' fields and groups. A row that can't be read yet, such as one a replica hasn't caught up with, keeps the payload's field, in the update's group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read the rows an update writes from the master The value type hook read the stored rows of an update from a replica, and a row the replica hadn't caught up with fell back to the field the payload named, which let the bypass through. The rows are now read from the master, so every row the update can write is found and checked against its own field. A value with no row at all writes nothing. PropertyValueSearchOpts gains IDs, so the search, which honors a master request context, can look values up by ID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5923d963fe |
Add the number property field type (#39459)
* Add the number property field type A number field holds a JSON number; null or an absent value is empty. - Migration 000236 adds 'number' to the property_field_type enum. - A new ValueTypeValidationHook checks every value write in every group: a number field's value must be a finite JSON number. A value whose field is not in its group is left to the property service, as before. - The access control group refuses number fields, since access policies and the attribute views they read cannot compare numbers. - PropertyFieldType gains IsValid and SupportsAccessControl, and the webapp's FieldType lists 'number'. Split out of mattermost/mattermost#39458 (a port of #39373 from the Boards core branch). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add number to the property field type enum in the API docs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read field types from the master; refuse trailing text after a number - The value type validation hook reads the fields it checks from the master. A replica that hadn't caught up with a field just created as a number, or changed to one, let a value of the wrong shape through. - A number value must end after the number. Decoder.More only looks for another value, so "1 ]" passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * List rank and graph in the property field type enum in the API docs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Check value updates against the group they write to An update names its group twice: the method's groupID, which the store uses to select the row, and each payload's GroupID. The value type hook read the field from the payload's group, so a payload naming another group skipped validation while the store still wrote the number field's row. Updates are now checked against the method's groupID when it is set, using copies, so the payload isn't changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Check value updates against the field of the row they write An update writes Value to the row its ID names and leaves the row's FieldID as it is. The value type hook checked the field the payload named, so a payload naming a text field could write a string into a number field's row. Updates are now checked against the stored rows' fields and groups. A row that can't be read yet, such as one a replica hasn't caught up with, keeps the payload's field, in the update's group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read the rows an update writes from the master The value type hook read the stored rows of an update from a replica, and a row the replica hadn't caught up with fell back to the field the payload named, which let the bypass through. The rows are now read from the master, so every row the update can write is found and checked against its own field. A value with no row at all writes nothing. PropertyValueSearchOpts gains IDs, so the search, which honors a master request context, can look values up by ID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5e4e72fb34 |
[MM-71290] Apply log filters on all cluster nodes (#39319)
* [MM-71290] Apply log filters on all cluster nodes Pass the logs query filter through ClusterInterface.QueryLogs so remote nodes filter by level and date themselves, and document that a node whose logs can't be fetched is reported as a single error entry instead of failing the request. * Trim QueryLogs API docs Drop the paragraph restating that filters apply on every node (already covered by the release note) and note that nodes the cluster has marked as failed are omitted from the response. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AYnv2eNo1pjDXsHdZgwwQ6 * Return per-node errors from ClusterInterface.QueryLogs QueryLogs now returns an einterfaces.NodeLogs per node, with Err set when a node's logs couldn't be fetched. Server.QueryLogs turns that error into the synthetic error log entry, so the API response is unchanged while the cluster layer no longer formats log lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AYnv2eNo1pjDXsHdZgwwQ6 --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
692c8eb745 |
[MM-71289] Fix server_names filter in logs query (#39318)
Return local logs when server_names names this node by its cluster hostname (or "default"), skip the cluster round-trip when only the local node is requested, and stop emitting null entries for names that don't match a node. Remote nodes are only queried on a licensed, enabled cluster, matching how the local node name is chosen. |
||
|
|
9c92a61a56 |
Bump Go version to v1.26.10 (#39328)
* Bump Go version to v1.26.10 * Bump go-msft-fips to 1.26.10.1 --------- Co-authored-by: Jesse Hallam <jesse@mattermost.com> |
||
|
|
e7e0ee99d2 |
Docs: interactive dialog checkbox elements and per-channel join/leave messages (#39260)
* update documentation for checkbox group/matrix and join/leave channel settings * update docs from coderabbit review |
||
|
|
3d291f7fd0 | Include deleted replies in thread updatesOnly responses (#39026) | ||
|
|
85b6fc1ac7 |
[MM-71079] Health Dashboard PR28: System Console page (read-only) (#38943)
* [MM-71079] Add health finding types and Client4.getHealthFindings Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-71079] Add health findings redux action, reducer and selectors Severity counts are derived from the fetched list and count firing findings only, so unknowns never inflate or deflate them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-71079] Add the read-only Site health System Console page Lists firing findings by area, with unknown findings in their own collapsed section that shows each one's cause. Registered under Reporting and gated like the API: HealthDashboard feature flag, Enterprise license and system admin. The sidebar now passes the system admin flag to isHidden checks, as route rendering already does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-71079] Read last-evaluated time from the findings API GET /api/v4/health/findings now returns {evaluated_at, findings}. Store evaluated_at alongside the findings and read it directly instead of deriving it from max(last_seen_at), which was 0 whenever every finding was muted and the page wrongly showed "Not evaluated yet". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * [MM-71079] Add Playwright smoke test for the Site health page Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * [MM-71079] Return docs_url and console_path on rendered health findings HealthFinding.Render copies the rule's DocsURL and ConsolePath so the System Console page can link to the setting and the documentation. Like title, remediation and message, they are render-only and empty in a store result. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-71079] Filter Site health findings by tab and group them by severity or category The summary pills become one segmented tab group: Open (firing plus unknown), Critical, Warning, Info, Recently resolved (last 7 days) and Unknown, each with a count. Severity counts are firing only. A Group by control switches between severity sections and per-area accordions; areas with a firing critical start expanded. Rows sort by severity then newest first, show how long they have been firing, unevaluated or since they resolved, and only one row is expanded at a time. The page title is shown once, in the admin header. Selectors return every finding; tab filtering, counting and grouping are pure helpers that take now, so the resolved window is testable. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-71079] Link Site health findings to their setting and documentation An expanded finding gains a Resolve here section with link cards for console_path (in-app navigation to the System Console setting) and docs_url (opens in a new tab), each shown only when set. Details lead with the finding's state, and firing or unknown findings with a console_path get a primary Go to setting button. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-71079] Find the Site health title in the admin header in the smoke test The page no longer renders a second "Site health" heading; the title lives only in the admin console header. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-71079] Move SiteURL health rules to the platform area SITE_URL_EMPTY and SITE_URL_HTTP were in the cluster area, which the System Console shows as "High availability". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * [MM-71079] Simplify Site health page helpers Reuse getHealthFindingSection and countHealthFindingsByTab instead of local copies, gate on the FeatureFlags admin config like other flags, replace label switches with lookup tables, and memoize finding rows. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * [MM-71079] Fix Site health resolved tab, accordion and row state Keep checks that passed on their first run out of Recently resolved, expand every area on filtered tabs, collapse the expanded row when the tab or grouping changes, and stop the e2e test depending on whether a health check has already run. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * [MM-71079] Hide collapsed Site health panels The display rules on the findings list overrode the hidden attribute, so collapsing a category accordion left its findings visible. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
f396b69a35 |
[MM-71090] Health Dashboard: don't persist never-fired checks as resolved (#38982)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
60328d1ed0 |
[MM-70984] Health Dashboard PR14: REST API + mmctl health check (#38808)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
bb72eeb7b2 |
Allow to hydrate posts with their attributes (#38134)
* Updates post metadata and PreparePostForClient opts, adds tests
* Add per-target property field cap and apply it to post attributes
* Allows SearchPropertyValues to target master with the context mechanism
* Add opt-in post property value hydration to GET /posts/{id}
* Add opt-in post property value hydration to GetPostsForChannel and GetPostThread
* Add opt-in post property value hydration to the webapp client
* Update include_property_groups to be includePropertyGroups
* Updates API docs to match the implementation
* Update query parameter naming and remove multiple group support
* Fix property value type in the docs
* Add tests for the post property value hydration bounds
* Fix e2e flaky test
---------
Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
|
||
|
|
b2a68c93cc |
Add the creator property field permission level (#38129)
* Adds the creator permission level, migration and updates the model and tests around them * Adds the authorization layer and API tests * Adds authorization channel test cases * Adds authorization test cases for the creator permission level * Update API docs * Improve test case name and comment * Document the creator object-type restriction and cover guest authors * Update comments to reference generic use cases * Require scope access for the creator permission level * Improve readability --------- Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es> |
||
|
|
c6175dfd82 |
[MM-70617] Extend PAT expiry policy to user-owned bots (#37599)
* Extend PAT expiry policy to user-owned bots Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Add bot token expiry flag to mmctl Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Fix expired bot token API assertion Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Fix generated mmctl bot documentation Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Address bot token review feedback Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Validate bot token expiry before creation Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Fix request.CTX build error and trim API doc wording Pass rctx instead of rctx.Context() to UserStore.Get, which expects request.CTX not context.Context. Also drop the bot-ownership detail from the non-compliant token API descriptions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Deduplicate bot-owner PAT policy logic and test fixtures Merge access_token_notification.go into session.go and factor a shared resolveBotOwner helper so the "is this bot's owner still a real, active user" check has one implementation instead of two nearly identical ones. Also collapse the repeated policy-check-then-validate sequence in CreateUserAccessToken/ RotateUserAccessToken into enforceUserAccessTokenExpiryPolicy, and extract a saveBotWithOwner test helper to remove duplicated bot/owner fixture setup across three storetest functions. No behavior change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Fix revoke_non_compliant_tokens spec for user-owned bot exemption change This PR narrows bot exemption from the PAT expiry policy to plugin-owned and system bots only; user-owned bots (like the one this test creates via the REST API) are now subject to the same policy as their owner. Update the "revokes non-compliant tokens" test to expect the bot token to be revoked along with the non-compliant user token, instead of asserting it survives. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Move resolveBotOwner from session.go to bot.go resolveBotOwner is a bot-domain helper (GetBot + resolve OwnerId to a user), not session-specific. Group it with the other bot helpers and generalize its doc comment away from PAT-policy framing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Fix GetUser call missing rctx argument after master merge Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Rename includeAllTokens to includeUserOwnedTokens for clarity The old name implied "all tokens" when the flag actually only toggles whether non-bot tokens are included; user-owned bot tokens are always included regardless of its value. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Drop token-scope parameter from GetExpiredBefore/cleanupExpired Cleanup of already-expired tokens shouldn't depend on whether new PAT creation is currently enabled: session expiry already enforces token.ExpiresAt unconditionally, so gating deletion on EnableUserAccessTokens just let expired human-owned tokens pile up whenever the setting was off. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Exclude all protected system bots from PAT expiry queries GetExpiringBefore, CountNonCompliantExpiry, and DeleteNonCompliantExpiry only excluded model.BotSystemBotUsername, missing other bots in model.ProtectedBotUsernames such as the content-flagging bot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Skip stale at-deletion DMs and defer the bot-owner lookup The cleanup job no longer scopes GetExpiredBefore by config, so the first run on a server where the job was previously gated off drains the whole accumulated backlog and DMs owners about tokens that stopped working months ago. Cap the at-deletion notice at 7 days past expiry; deletion is unaffected. Also reorder enforceUserAccessTokenExpiryPolicy to validate first and resolve the bot owner only when validation fails, so a compliant bot token no longer pays an uncached Bot().Get plus a user read on every create and rotate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Align revoke spec with the webapp PR to avoid a rebase conflict PR #37604 rewrites the same regions of this spec. Adopting its wording, naming and assertion order leaves that PR with no delta on this file, so it rebases cleanly once this lands. Its plugin-owned bot case is deliberately not adopted: createBot always sets OwnerId from the session and BotPatch carries no OwnerId, so a bot created over REST is user-owned and would be revoked. That branch is covered by the app and store tests instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Use IsSystemOwned() instead of hardcoded system-bot username check Both resolveBotOwner and IsBotExemptFromDMRestrictions only special-cased BotSystemBotUsername, missing other protected system bots like the content-flagging bot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Reject protected bot usernames in CreateBot and PatchBot An admin could create or rename a bot to a ProtectedBotUsername (e.g. system-bot) before the system creates it. GetOrCreateSystemOwnedBot adopts an existing bot by username without verifying provenance, so the squatted bot inherits IsSystemOwned() exemptions this PR extends to the PAT lifetime policy, letting the credential persist unbounded. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Require the target user to exist when assigning a bot The assignBot handler fetched the target user only to check whether it was itself a bot, silently ignoring a not-found error. A caller could assign a bot to a non-existent user ID, leaving the bot with an owner that resolves to nothing. Treat a missing target user as invalid input. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Close two bypasses of the protected bot username guard IsSystemOwned() checked bot.Username before User.PreUpdate() lowercases it, so PatchBot("System-Bot") committed the rename to "system-bot" while validation still saw the mixed-case value. Normalize the comparison in IsSystemOwned(). PatchUser (and other App.UpdateUser callers: the plugin API and bulk import) had no protected-username check at all, so a bot's user record could be renamed to a reserved name like "system-bot" without going through PatchBot's guard. Add the same check to App.UpdateUser. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Apply the PAT expiry policy to system-owned and orphaned bot tokens Two bot-ownership states escaped the policy because both resolve to no owner user, which the code read as "plugin-owned, exempt". System-owned bots carry a real system admin as OwnerId; their exemption was inherited from ProtectedBotUsernames, whose rationale is that disabling these bots breaks core features. Token expiry disables nothing, no core feature authenticates as a system bot with a PAT (they are only used as a poster identity), and the System Console does offer "Create New Token" for them - so the carve-out was a hole in a compliance control rather than a safeguard. This reverts most of the protected-bot filtering added to the expiry queries. A bot whose owning account was permanently deleted keeps a dangling OwnerId that the owner lookup cannot tell apart from a plugin id. model.Bot.HasUserOwner separates the two by id shape, mirrored in SQL for the revoke queries. The notification query keeps its owner-row join: with no live owner there is genuinely nobody to warn, which already does not exempt a token from revocation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Warn when an imported bot owner does not resolve to a user importBot assumed an unresolvable owner was a plugin id and said so in a comment, but the value is equally a user id whose account is gone, or a username that was never imported. Keep resolving it the same way - the value has to survive the export/import round trip for both plugin-owned and orphaned bots - and log the case instead of asserting a cause. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
1e0a76ee9a |
MM-70575: Support paid add-on plugin licenses (#38327)
MM-70575: Gate add-on plugins on a license entitlement A license can now grant add-ons alongside its SKU tier (License.AddOns), and a plugin declares the one it needs in its manifest (required_add_on). The server enables and activates such a plugin only while the license grants that add-on, including when it is enabled directly in config, and granting or revoking the entitlement takes effect with no config change. The System Console shows an explanation in place of the enable control on both the plugin list and the plugin's settings page. Also keeps the current license when the database read in LoadLicense fails, instead of treating the error as "no license". It runs on a 24h timer, so one failed read dropped every entitlement until the next successful one. |
||
|
|
46bfb71725 |
add new ephemeral mode audit logging endpoints (#37715)
* add new ephemeral mode audit logging endpoints * add license requirement and server timestamps to ephemeral mode audit endpoints * adds openapi specs for new ephemeral mode audit api endpoints * support for providing errors when adding audit logs related to ephemeral mode events * address PR comments * change spec to reflect on nullable fields * adds missing context argument |
||
|
|
5c2992443c |
Add checkbox_group and checkbox_matrix interactive dialog elements (#37656)
* Add checkbox_group and checkbox_matrix interactive dialog elements Introduce two new interactive dialog element types plus a label_position option, with client rendering, model validation, and API documentation. |
||
|
|
0019ecc531 |
(1) - Feature post delivery audit setting (#37882)
* Added server and client side frameowkr for configuring settings * Type fixes * Added test mocks * Review fixes * Restored gotool go modules * CI * (2) - Post tracking audit event implementation (#37919) * Initial implementation for generating delivery tracking * Initial implementation for generating delivery tracking * Some performance improvements * Some performance improvements * Record list.Posts instead of list.Order * Fixed email batch delivery recording * Fixed a couple of pending delivery mechanisms * Caching fixes * another few fixes * Stub DeliveryTracking in store mocks used by cache purge InvalidateAllCachesSkipSend now also clears the delivery tracking store's caches. The partial store mocks in these tests mirror the store list that function touches, so the added call panicked with an unexpected DeliveryTracking() during test cleanup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Recorded post delivery event after JSON encoding succeeds * Updated decisionmkaing point in email delivery tracking * Plugin hook handling * Added the Learn More button on delivery tracking section * Added in inline code comment for explaining a decision * Restored go modules * Updated indirect status of a go dependency * Removed learn more button * fixed a test --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0d1ce90d72 |
[MM-70250] Remove unused GET bulk reactions endpoint (#38174)
Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
c8b1cc0046 |
MM-68283 - Add render-time ABAC permission decisions for file upload/download (#36820)
* Add Action Search render-decision API for ABAC file actions * Add render-permission store, client method, and useRenderPermission hook * Wire upload/download render decisions into UI with invalidation and e2e coverage * Work around false-literal bug in ABAC file-access e2e specs * add related schemas and fix format * remove redundant comment in upload file enforcement test * Fix stale post-list ETags and missing live updates after ABAC policy or attribute changes * Enhance channel access control event handling by marking non-current channels stale for lazy reconciliation * Refactor ETag handling and improve permission policy checks in channel and post APIs * Remove unnecessary blank line in access control policy update function * Align action search API with AuthZEN spec: add results list, discovery mode, reserve subject/page fields * Nest ActionSearchResult to AuthZEN canonical shape and apply code review fixes * Refactor action search request type formatting and update test loop for clarity * Implement ABAC permission checks for channel access in searchAccessControlDecisionActions * Scope ABAC render ETag to target channel, cache policy and CPA epochs in localcachelayer, and fail-closed on unknown resource types * add stub for ETag cache invalidation in access control policy tests * Jitter render reconcile on system-scoped permission policy updates to avoid a thundering herd * Make the ABAC ETag epochs deletion-sensitive by folding a row count into each * Address web app review: reuse RESET_POSTS_IN_CHANNEL, batch decision fetches, and simplify the upload affordance * Split the render-decision reducer per key and scope invalidation stamps per resource * MM-68283 - Use aria-disabled on the policy-restricted upload button so its tooltip still opens * MM-68283 - Cover live post redaction without reload and use native Playwright assertions * MM-68283 - Trim comments to the load-bearing reasons and drop dead test fixture fields * MM-68283 - Refetch the channel in view after a policy change instead of relying on a remount * MM-68283 - Apply a refetched post whose file metadata was redacted or restored * MM-68283 - Require metadata on both sides before treating a post's redaction state as changed * Keep the ABAC render caches and materialized attribute view in step with every attribute write * Restructure the ABAC file-permission e2e specs and cover upload enforcement server-side --------- Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com> |
||
|
|
2d6fc01907 | Bump Go version in missed go.mod files (#38102) | ||
|
|
fb87397dba |
Data spillage exposure radius report generation (#37809)
* WIP: * WIP: * Added API integration * Removed some unneeded functions and cleaned up unnecessery comments * CI * Test improvements * Coderabit fixes * Report data updates * Handled commas and few other chaaracters in channel name * Data spillage exposure radius UI integration (#37820) * UI implementation and integration of exposure report APIs * Minor cleanup * Coderabit fixes * Allowed generating exposure report irrespective of status * Used the new button component * fixed lint error |
||
|
|
44d12bef80 |
[MM-66243] Omit sanitized last_viewed_at/last_update_at instead of returning -1 for other users (#37505)
* Omit sanitized channel member timestamps from JSON The channel member sanitization introduced in #33835 replaced other users' LastViewedAt and LastUpdateAt with -1, which clients decode as Dec 31 1969. Serialize the sanitized sentinel as an absent field instead so the API no longer returns an invalid timestamp for other users. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Add tests and API docs for omitted sanitized member timestamps Verify at the JSON layer that last_viewed_at and last_update_at are omitted for other users' memberships (across the channel and user endpoints) while remaining present for the requester, including a legitimate zero timestamp. Document the omission in the API spec. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Strengthen sanitized-timestamp test coverage Cover the NDJSON streaming branch of getChannelMembersForUser and the getChannelMembersForTeamForUser endpoint, assert the requester's own timestamps are valid (not the sentinel), use the sanitizedTimestamp constant, and note the ChannelMemberForExport marshaling footgun. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Marshal team data via a typed struct in ChannelMemberWithTeamData Co-authored-by: mattermost-code <matty-code@mattermost.com> * Avoid shadowing err in ChannelMemberWithTeamData.MarshalJSON Co-authored-by: mattermost-code <matty-code@mattermost.com> * Use omitzero tags to omit sanitized member timestamps Replace the custom ChannelMember/ChannelMemberWithTeamData MarshalJSON round-trip with the Go 1.24 omitzero tag on LastViewedAt/LastUpdateAt. SanitizeForCurrentUser now zeroes another user's timestamps so they are omitted from API responses, per reviewer feedback. * Give current user a real last_viewed_at in sanitization test With omitzero, a zero last_viewed_at is legitimately omitted. Have user2 post an unread message and the current user view the channel so the current-user assertions verify a genuine timestamp survives sanitization. * Use -1 sentinel for sanitized member timestamps with single-pass marshal A last_viewed_at of 0 legitimately means "never viewed", so it cannot double as the sanitization sentinel. Restore the -1 sentinel and omit it during serialization via shadowing pointer fields, avoiding the previous marshal/unmarshal/marshal round-trip. * Clarify ChannelMember.MarshalJSON doc comment per review feedback * Address PR feedback: 0 answered, 4 resolved, 0 declined - Simplify sanitizedTimestamp and SanitizeForCurrentUser doc comments per review - Document that new ChannelMemberWithTeamData fields must be added to MarshalJSON - Add round-trip test guarding against fields dropped by MarshalJSON * Address PR feedback: remove round-trip MarshalJSON test The round-trip test did not guard against forgetting to add a new field to MarshalJSON, since the same field would also be missing from the test. * Address PR feedback: assert legitimate zero last_update_at is serialized * Mark sanitized channel member timestamp fields as nullable in OpenAPI spec --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: mattermost-code <matty-code@mattermost.com> Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
f112b9a715 |
Remove deprecated built-in Slack import API and CLI (#37999)
The webapp Slack import path was deprecated in v6.0 in favor of mmetl and Mattermost bulk import; remove the leftover API, importer package, CLI command, and import_team permission. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
ea183fab48 |
[MM-67157] Remove format parameter requirement from client license endpoint (#37167)
* MM-67157: Remove unused format flag from /license/client endpoint The `format` query parameter on the `/license/client` (and local variant) endpoint was effectively dead: it was required but only ever accepted the single value `old`, returning an error otherwise. This mirrors the earlier removal of the same flag from `/config/client`, where the server now ignores the parameter while clients continue to send `format=old` for compatibility with pre-v11 servers. The server no longer inspects the `format` parameter, so requests with no format, `format=old`, or any other value all succeed. The unused i18n string and the parameter/response documentation in the OpenAPI spec are removed accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Audz4JLNQN5SJxcPwyQBd * MM-67157: document format=old retention in webapp client Mirror the getClientConfig comment so the format=old query param on getClientLicenseOld is not mistakenly removed; clients keep sending it for compatibility with pre-v11 servers even though current servers now ignore it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Audz4JLNQN5SJxcPwyQBd * MM-67157: stop sending format=old from webapp client Now that the server ignores the format parameter on /license/client, drop format=old from the @mattermost/client getClientLicenseOld call and update the e2e intercepts/helpers that matched the old query string. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Audz4JLNQN5SJxcPwyQBd * Drop unneeded wildcard from license/client cy.intercept path The format query param is gone from GET /license/client requests, so the trailing * used to match it is no longer needed. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
9dfbaeca99 |
Add weekly recurring scheduled posts (#37746)
* Add weekly recurring scheduled posts. Extend scheduled posts so users can schedule weekly repeats and keep the series healthy across sends, reschedules, and UI updates instead of falling back to one-shot behavior. Made-with: Cursor * Add Playwright coverage for recurring scheduled posts. Cover weekly recurring scheduled messages in the scheduled-messages spec so the recurring UI and reschedule flow stay protected without adding a separate test surface. Made-with: Cursor * Fix recurring scheduled post CI failures. Resolve the initial lint and formatting issues and renumber the new scheduled-post migration so it no longer collides with master during Postgres-backed test setup. Made-with: Cursor * Sync recurring scheduled post translation files. Regenerate the affected English translation catalogs so the recurring scheduled post strings match the source extraction order expected by CI. Made-with: Cursor * Fix recurring scheduled post review follow-ups. Preserve overdue cleanup behavior during weekly catch-up, defer delete websocket events until deletion succeeds, and address the remaining migration and UI review nits. Made-with: Cursor * Address recurring scheduled post review feedback Made-with: Cursor * Fix recurring scheduled post CI checks Made-with: Cursor * Make pending scheduled post keyset cursor index-scannable EXPLAIN ANALYZE on a 5M-row ScheduledPosts table showed the pure OR-form cursor forced Postgres to scan idx_scheduledposts_pending_scheduled_at_id from the top on every page (~383ms/page, ~2M rows filtered). Keeping the ScheduledAt <= beforeTime bound outside the tie-break restores the index boundary (~0.12ms/page). Adds storetest coverage for cursor pagination. Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Address code quality review findings for recurring scheduled posts Server: - Replace silent-fallback AdvanceWeeklyScheduledNextOccurrence with error-returning ScheduledPost.ComputeNextScheduledAt; a recurring post whose timezone fails to load is now routed through the failed-post path instead of being reposted every job run or silently deleted - Add ScheduledPost.IsRecurring and partition batches in processScheduledPostBatch; advance/delete now run independently so a store failure in one path can't cause reposts in the other - Drop dead generality in UpdateRecurringScheduledPosts (only ScheduledAt varies per row; ErrorCode/ProcessedAt are constants) - Simplify redundant repeat-type condition in GetPendingScheduledPosts Webapp: - Add shared isRecurringScheduledPost helper, replacing six scattered repeat_type === 'weekly' literals - Recurrence timezone is now simply the scheduler's current timezone; removes initialRepeatTimezone/effectiveTimezone plumbing and the modal-label/picker timezone mismatch - Single enforcement point for hiding send-now on recurring posts - Use canonical getTeamIdByChannelId at both SCHEDULED_POST_UPDATED dispatch sites; rewrite errorsByTeamId update case as remove-then-add and add reducer tests Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Simplify recurring scheduled post code per review - End a recurring series when its channel no longer exists instead of advancing it forever (matches the one-shot channel-not-found handling) - Collapse the errorsByTeamId SCHEDULED_POST_UPDATED case into the identical SINGLE_SCHEDULED_POST_RECEIVED case (a scheduled post's team can't change) and combine duplicate byId cases; preserves state references on no-op updates - Drop no-op timezone conversions in ComputeNextScheduledAt - Remove redundant checkbox aria-label (label htmlFor already names it) - Schedule new job tests in the past instead of sleeping a real second - Remove redundant test assignment and e2e positional boolean Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Remove slop from recurring scheduled post changes - Drop the business-rule CHECK constraint from the recurrence migration; no other migration enforces model-layer validation in the database, and BaseIsValid plus the job's failure handling already own it - Fold the standalone repeat-validation test file into the existing TestScheduledPostBaseIsValid, matching its conventions - Revert unrelated benchmark modernization in utils_test.go - Drop an unneeded cast, unused fixture fields, and naming/assertion inconsistencies in tests Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Regenerate ScheduledPostStore mock with mockery ordering Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Address CodeRabbit review feedback - Reject the host-dependent 'Local' value for RepeatTimezone; recurring schedules need a fixed zone (UTC or IANA name) - Hide reschedule for deactivated DMs, matching send-now eligibility Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Resolve scheduled post team bucket from existing state when channel is unloaded An admin can reschedule before fetchMissingChannels resolves, in which case deriving the team from the channel returns undefined and the update was misfiled under directChannels. getScheduledPostTeamId falls back to the byTeamId bucket that already holds the post. Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Retrigger CI after transient enterprise npm network failure Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Clear hover/focus before asserting scheduled post header details in e2e The drafts panel hides its timestamp/tag info section while hovered or focus-within; after the reschedule modal closes, focus returns to the row and the 'Repeats weekly' tag assertion saw a hidden element. Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Move recurrence columns into baseColumns and dispatch ComputeNextScheduledAt on repeat type Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Preserve recurrence when scheduled post updates omit repeat fields Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Disallow file attachments on recurring scheduled posts Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Stop pinning the channel indicator for recurring-only scheduled posts Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Re-home nullable-Type comment onto baseColumns Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Gate recurring scheduled posts behind a default-off feature flag Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Move presence-preservation rationale to the copy site Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Extract draftHasAttachments helper and require allowRecurring at single-caller layers Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Return null from the indicator selector when nothing should show Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Gate only recurrence transitions and preserve existing series in the modal Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Avoid err shadowing in scheduled post update handler Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Disable the repeat weekly checkbox with a tooltip when the message has attachments Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Return an explicit disposition from postScheduledPost The batch loop inferred 'channel permanently gone' from the one return path with a nil error and an error code set - an invariant a future change could silently break, deleting recurring series by accident. postScheduledPost now returns posted/failed/unsendable explicitly, the switch refuses to delete on an unhandled disposition, and a test pins that both recurring and one-shot posts in a nonexistent channel are permanently deleted. Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Fix Repeat weekly attachments tooltip centering on the modal row (#37927) Shrink-wrap the repeat checkbox row so WithTooltip anchors to the control/label instead of the full modal body width. Co-authored-by: Cursor Agent <cursoragent@cursor.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
6242bc3e2f |
[MM-70140] Remove experimental AD/LDAP login button color settings (#37855)
* [MM-70140] Remove experimental AD/LDAP login button color settings Remove the dead-code LdapSettings.LoginButtonColor / LoginButtonBorderColor / LoginButtonTextColor experimental settings. These values were plumbed into the client config but never consumed by the web or mobile clients, so no AD/LDAP login button was ever rendered or colored. Removes the fields from the server config struct and defaults, the client config payload, the Admin Console Experimental Features section, the webapp config type, related en.json strings, API definitions, docs, and test/default config fixtures. Co-authored-by: mattermost-code <matty-code@mattermost.com> * [MM-70140] Add test guarding removal of LDAP login button color keys Assert that GenerateLimitedClientConfig still emits LdapLoginFieldName under an LDAP license but never emits the removed LdapLoginButtonColor/BorderColor/ TextColor keys, guarding against reintroduction of the dead settings. Co-authored-by: mattermost-code <matty-code@mattermost.com> * [MM-70140] Remove LDAP login button colors from webapp LdapSettings type Keep AdminConfig LdapSettings in sync with the server model after the experimental AD/LDAP login button color settings were removed. Co-authored-by: mattermost-code <matty-code@mattermost.com> * [MM-70140] Update admin console index test after LDAP color removal Drop experimental/features from the ldap search expectation now that the AD/LDAP login button color settings are no longer under Experimental. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Address PR feedback: remove test for removed LDAP login button color settings Per @lieut-data's review, drop TestGenerateLimitedClientConfigOmitsLdapLoginButtonColors; there's no need to perpetually test that a removed feature stays removed. * chore: retrigger CI after GitHub Actions service outage Co-authored-by: mattermost-code <matty-code@mattermost.com> * chore: retrigger CI after Actions service recovery 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: mattermost-build <mattermost-build@users.noreply.github.com> |
||
|
|
68535c13bc |
[MM-70130] Add permanent banner for non-production developer license keys (#37846)
* [MM-70130] Add permanent banner for non-production developer license keys Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * [MM-70130] Update non-production license banner text Co-authored-by: Nick Misasi <nick13misasi@gmail.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
25f3a75cb7 |
[MM-67163] Scheduled Recaps (#35495)
* docs: map existing codebase with focus on recaps
- STACK.md - Technologies and dependencies
- ARCHITECTURE.md - System design and patterns
- STRUCTURE.md - Directory layout
- CONVENTIONS.md - Code style and patterns
- TESTING.md - Test structure
- INTEGRATIONS.md - External services
- CONCERNS.md - Technical debt and issues
* docs: initialize project
Scheduled AI Recaps — enable recurring scheduled recaps instead of only manual one-off summaries
* chore: add project config
Mode: yolo
Depth: standard
Parallelization: enabled
* docs: complete domain research for scheduled recaps
Research files:
- STACK.md: Job server patterns, ScheduledPosts reference implementation
- FEATURES.md: Table stakes vs differentiators, anti-features to avoid
- ARCHITECTURE.md: Component boundaries, data flow, 5-phase build order
- PITFALLS.md: Timezone, cluster, schema critical pitfalls
- SUMMARY.md: Synthesized findings and roadmap implications
Key findings:
- Use ScheduledPosts polling pattern, not JobServer schedulers
- Store user intent + pre-computed NextRunAt for efficient polling
- Leverage existing recap worker, extend with time_period support
- Critical: Get timezone/schema right in Phase 1 to avoid migrations
* docs: define v1 requirements
35 requirements across 5 categories:
- Scheduling (7)
- Management (8)
- Backend Infrastructure (10)
- Frontend Wizard (7)
- Frontend Scheduled Tab (7)
Topic-based recaps deferred to v2
* docs: create roadmap (5 phases)
* docs(01): capture phase context
Phase 01: Database Foundation
- OpenCode discretion on implementation patterns
- Implicit requirements from downstream phases noted
* docs(01-database-foundation): research phase domain
Phase 1: Database Foundation
- Analyzed existing Recap and ScheduledPost patterns
- Documented schema design for recurring schedules
- Identified timezone/DST handling approach using IANA zones
- Catalogued store interface patterns
- Documented common pitfalls and anti-patterns
* docs(01): create phase plan for database foundation
Phase 01: Database Foundation
- 2 plans in 2 waves
- Wave 1: Model + migration (01-01-PLAN.md)
- Wave 2: Store layer + tests (01-02-PLAN.md)
- Ready for execution
* feat(01-01): create ScheduledRecap model with DST-aware NextRunAt computation
- Add ScheduledRecap struct with all required fields for schedule configuration
- Add day-of-week bitmask constants matching Go's time.Weekday (Sunday=0)
- Add channel mode constants (specific, all_unreads)
- Add time period constants (last_24h, last_week, since_last_read)
- Implement ComputeNextRunAt with timezone-aware scheduling using time.LoadLocation
- Implement IsValid for input validation
- Add PreSave/PreUpdate lifecycle methods
- Add Auditable method for audit logging
* feat(01-01): create database migration for ScheduledRecaps table
- Create ScheduledRecaps table with all required columns
- Add index for user queries (idx_scheduled_recaps_user_id)
- Add index for scheduler polling (idx_scheduled_recaps_next_run_at)
- Add composite index for efficient scheduler query (idx_scheduled_recaps_enabled_next_run)
- Add index for user + soft delete queries (idx_scheduled_recaps_user_delete)
- Add down migration to drop all indexes and table
* test(01-01): add unit tests for ScheduledRecap with DST edge cases
- Test day-of-week bitmask constants and operations
- Test ComputeNextRunAt for Monday-only, weekday, and every-day schedules
- Test timezone handling - different timezones produce different UTC millis
- Test DST spring forward edge case (March 2024) - Go normalizes non-existent times
- Test DST fall back edge case (November 2024) - Go uses first occurrence
- Test error cases: invalid timezone, invalid time format, zero days
- Test IsValid method for all validation rules
- Test PreSave and PreUpdate lifecycle methods
- Test Auditable method returns expected fields
- Fix ComputeNextRunAt to validate time format using regex before parsing
* docs(01-01): complete ScheduledRecap model and migration plan
Tasks completed: 3/3
- Task 1: Create ScheduledRecap model with constants and NextRunAt computation
- Task 2: Create database migration for ScheduledRecaps table
- Task 3: Add unit tests for ComputeNextRunAt with DST edge cases
SUMMARY: .planning/phases/01-database-foundation/01-01-SUMMARY.md
* feat(01-02): add ScheduledRecapStore interface to store.go
- Add ScheduledRecapStore interface with CRUD operations (Save, Get, Update, Delete)
- Add query operations (GetForUser, GetDueBefore)
- Add state update methods (UpdateNextRunAt, MarkExecuted, SetEnabled)
- Register ScheduledRecap() method in main Store interface
* feat(01-02): create SqlScheduledRecapStore implementation
- Implement CRUD operations (Save, Get, Update, Delete with soft delete)
- Implement GetForUser with pagination for user's scheduled recaps
- Implement GetDueBefore for scheduler polling query
- Implement efficient state updates (UpdateNextRunAt, MarkExecuted, SetEnabled)
- Handle JSON serialization/deserialization of ChannelIds array
- Follow existing patterns from recap_store.go
* feat(01-02): register ScheduledRecapStore in SqlStore
- Add scheduledRecap field to SqlStoreStores struct
- Initialize newSqlScheduledRecapStore in NewSqlStore
- Add ScheduledRecap() accessor method to SqlStore
* test(01-02): add comprehensive tests for ScheduledRecapStore
- Test CRUD operations (Save, Get, Update, Delete)
- Test GetForUser with pagination
- Test GetDueBefore scheduler query filtering
- Test state update methods (UpdateNextRunAt, MarkExecuted, SetEnabled)
- Test ChannelIds JSON serialization (array, empty, nil)
- All 13 test cases pass
* docs(01-02): complete ScheduledRecapStore plan
Tasks completed: 4/4
- Add ScheduledRecapStore interface to store.go
- Create SqlScheduledRecapStore implementation
- Register ScheduledRecapStore in SqlStore
- Create comprehensive store tests
SUMMARY: .planning/phases/01-database-foundation/01-02-SUMMARY.md
* fix(01): regenerate store mocks for ScheduledRecapStore
* docs(01): complete Database Foundation phase
Phase 1: Database Foundation
- 2 plans executed (model + store)
- 3 requirements complete (INFRA-01, INFRA-02, INFRA-10)
- Goal verified ✓
* docs(03): research phase scheduler integration domain
Phase 03: Scheduler Integration
- Standard stack identified (Mattermost job system)
- Architecture patterns documented (Scheduler + Worker pattern)
- Cluster-safe execution via leader-only scheduling
- Pitfalls catalogued (duplicate jobs, race conditions)
- Code examples from existing codebase patterns
* docs(02): create phase 2 API layer plans
Phase 02: API Layer
- 2 plans in 2 waves
- Plan 01 (Wave 1): App layer methods for CRUD + pause/resume
- Plan 02 (Wave 2): API handlers, routes, params, audit events
- Ready for execution
* feat(02-01): create App layer CRUD methods for ScheduledRecap
- Add CreateScheduledRecap with session-based userId, validation, NextRunAt computation
- Add GetScheduledRecap to retrieve by ID
- Add GetScheduledRecapsForUser with pagination
- Add UpdateScheduledRecap with NextRunAt recomputation when enabled
- Add DeleteScheduledRecap for soft delete
- Add PauseScheduledRecap to disable without deleting
- Add ResumeScheduledRecap with NextRunAt recomputation before enabling
- Regenerate store layer files for ScheduledRecapStore interface
* docs(02-01): complete App layer CRUD methods plan
Tasks completed: 3/3
- Regenerate store mocks (already complete from Phase 1)
- Create App layer file with CRUD methods
- Verify app layer interfaces (no interface file exists)
SUMMARY: .planning/phases/02-api-layer/02-01-SUMMARY.md
* feat(02-02): add audit event constants for scheduled recaps
- AuditEventCreateScheduledRecap for recap configuration creation
- AuditEventGetScheduledRecap for viewing single recap
- AuditEventGetScheduledRecaps for listing user recaps
- AuditEventUpdateScheduledRecap for configuration updates
- AuditEventDeleteScheduledRecap for recap deletion
- AuditEventPauseScheduledRecap for pausing execution
- AuditEventResumeScheduledRecap for resuming execution
* feat(02-02): add ScheduledRecapId to params and context
- Add ScheduledRecapId field to Params struct
- Parse scheduled_recap_id from URL path variables
- Add RequireScheduledRecapId validation method
* feat(02-02): add route registration for scheduled recaps
- Add ScheduledRecaps and ScheduledRecap routes to Routes struct
- Initialize route prefixes in Init function
- Add InitScheduledRecap call in initialization
* feat(02-02): create API handlers for scheduled recaps
- InitScheduledRecap registers all 7 API routes
- createScheduledRecap validates required fields and creates recap
- getScheduledRecap retrieves with authorization check
- getScheduledRecaps lists user's recaps with pagination
- updateScheduledRecap updates with ownership verification
- deleteScheduledRecap soft deletes with authorization
- pauseScheduledRecap disables execution with ownership check
- resumeScheduledRecap re-enables with NextRunAt recomputation
- All handlers include audit logging and feature flag check
* docs(02-02): complete API handlers plan
Tasks completed: 4/4
- Add audit event constants for scheduled recaps
- Add ScheduledRecapId to params and context
- Add route registration in api.go
- Create API handler file
SUMMARY: .planning/phases/02-api-layer/02-02-SUMMARY.md
* docs(02): complete API Layer phase
Phase 2: API Layer
- 2 plans executed in 2 waves
- 16/16 must-haves verified
- INFRA-05 through INFRA-09 complete
- Ready for Phase 3: Scheduler Integration
* docs(03): create phase plan for scheduler integration
Phase 03: Scheduler Integration
- 2 plans in 2 waves
- Wave 1: Job constant, scheduler, worker
- Wave 2: Job registration, App method
- Ready for execution
* fix(03): revise plan 03-02 Task 2 for worker context compatibility
- CreateRecapFromSchedule now creates recap directly via store
- Uses sr.UserId instead of rctx.Session().UserId (unavailable in worker)
- Creates JobTypeRecap job directly instead of delegating to CreateRecap
- Updated key_links to reflect store and job linkage
* docs(04): capture phase context
Phase 04: Scheduled Tab
- Implementation decisions documented
- Phase boundary established
- Figma references captured (123:62940, 123:19772)
* feat(03-01): add JobTypeScheduledRecap constant
- Add JobTypeScheduledRecap constant with value 'scheduled_recap'
- Add to AllJobTypes slice for job type validation
* feat(03-01): create ScheduledRecap scheduler
- Add Scheduler struct wrapping PeriodicScheduler
- 1-minute polling interval (SchedulerPollingInterval constant)
- Enabled when cfg.FeatureFlags.EnableAIRecaps is true
- ScheduleJob polls GetDueBefore for due recaps
- Creates job with CreateJobOnce for deduplication
- Job data: scheduled_recap_id, user_id, channel_ids, agent_id
* feat(03-01): create ScheduledRecap worker
- Define AppIface interface with CreateRecapFromSchedule method
- Use SimpleWorker pattern following recap/worker.go pattern
- Enabled when cfg.FeatureFlags.EnableAIRecaps is true
- Extract job data: scheduled_recap_id, user_id, channel_ids, agent_id
- Verify ScheduledRecap exists and is enabled before execution
- Call app.CreateRecapFromSchedule to create the actual recap
- Compute next run time using sr.ComputeNextRunAt
- Call MarkExecuted to update LastRunAt, NextRunAt, RunCount atomically
- Disable non-recurring schedules after execution
* docs(03-01): complete job system components plan
Tasks completed: 3/3
- Add JobTypeScheduledRecap constant
- Create scheduler implementation
- Create worker implementation
SUMMARY: .planning/phases/03-scheduler-integration/03-01-SUMMARY.md
* docs(04): create phase 4 plans - Scheduled Tab UI
Phase 04: Frontend - Scheduled Tab
- 4 plans in 4 waves
- Wave 1: TypeScript types + Client4 API methods
- Wave 2: Redux layer (action types, actions, reducer, selectors)
- Wave 3: ScheduledRecapItem component (card UI with toggle, menu)
- Wave 4: Scheduled tab integration with human verification
Covers requirements TAB-01 through TAB-07 and MGMT-01 through MGMT-08
* feat(03-02): add ScheduledRecap job registration in initJobs
- Register JobTypeScheduledRecap with worker and scheduler
- Import scheduled_recap package for job components
- Worker uses App interface for CreateRecapFromSchedule
- Scheduler polls for due recaps at 1-minute intervals
* feat(03-02): implement CreateRecapFromSchedule App method
- Create Recap from ScheduledRecap configuration
- Use sr.UserId instead of session (worker context has no session)
- Create recap record directly via store
- Create JobTypeRecap job to trigger processing
- Handle both specific channels and all_unreads mode
* docs(03-02): complete app integration plan
Tasks completed: 3/3
- Add job registration in initJobs
- Implement CreateRecapFromSchedule App method
- Verify full integration compiles
SUMMARY: .planning/phases/03-scheduler-integration/03-02-SUMMARY.md
* docs(phase-3): complete scheduler integration phase
* feat(04-01): add ScheduledRecap TypeScript types
- Add ScheduledRecap type matching Go model fields
- Add ScheduledRecapInput type for create/update operations
- Types exported via @mattermost/types/recaps
* feat(04-01): add Client4 scheduled recap route and imports
- Add getScheduledRecapsRoute() method returning /scheduled_recaps endpoint
- Import ScheduledRecap and ScheduledRecapInput types
* feat(04-01): add Client4 scheduled recap API methods
- createScheduledRecap: POST /scheduled_recaps
- getScheduledRecaps: GET /scheduled_recaps (paginated)
- getScheduledRecap: GET /scheduled_recaps/:id
- updateScheduledRecap: PUT /scheduled_recaps/:id
- deleteScheduledRecap: DELETE /scheduled_recaps/:id
- pauseScheduledRecap: POST /scheduled_recaps/:id/pause
- resumeScheduledRecap: POST /scheduled_recaps/:id/resume
* docs(04-01): complete TypeScript types and Client4 methods plan
Tasks completed: 3/3
- Add ScheduledRecap TypeScript type
- Add Client4 scheduled recap route helper
- Add Client4 scheduled recap API methods
SUMMARY: .planning/phases/04-scheduled-tab/04-01-SUMMARY.md
* feat(04-02): add scheduled recap action types
- GET_SCHEDULED_RECAPS_REQUEST/SUCCESS/FAILURE
- RECEIVED_SCHEDULED_RECAP and RECEIVED_SCHEDULED_RECAPS
- PAUSE_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE
- RESUME_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE
- DELETE_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE
* feat(04-02): add scheduled recap Redux actions
- getScheduledRecaps: fetches paginated scheduled recaps
- pauseScheduledRecap: pauses a scheduled recap
- resumeScheduledRecap: resumes a paused scheduled recap
- deleteScheduledRecap: deletes a scheduled recap
* feat(04-02): add scheduled recaps to reducer
- Add scheduledRecaps to RecapsState type
- Handle RECEIVED_SCHEDULED_RECAP for single recap
- Handle RECEIVED_SCHEDULED_RECAPS for bulk updates
- Handle DELETE_SCHEDULED_RECAP_SUCCESS for removal
* feat(04-02): add scheduled recap selectors
- getScheduledRecapsState: base selector for raw state
- getAllScheduledRecaps: returns all scheduled recaps as array
- getActiveScheduledRecaps: filters enabled, non-deleted recaps
- getPausedScheduledRecaps: filters disabled, non-deleted recaps
- getScheduledRecapById: returns single recap by ID
* feat(04-02): update GlobalState type for scheduled recaps
- Import ScheduledRecap type from recaps
- Add scheduledRecaps field to recaps entity state
* docs(04-02): complete Redux store and actions plan
Tasks completed: 5/5
- Add scheduled recap action types
- Add scheduled recap Redux actions
- Add scheduled recaps to reducer
- Add scheduled recap selectors
- Update GlobalState type for scheduled recaps
SUMMARY: .planning/phases/04-scheduled-tab/04-02-SUMMARY.md
* feat(04-03): add i18n strings for scheduled recap UI
- Add scheduled tab label
- Add active/paused toggle states
- Add run stats strings (last run, run count, never run, next run)
- Add toast messages for pause/resume/delete
- Add kebab menu labels (edit, delete)
- Add delete confirmation modal strings
- Add empty state strings (title, description, cta)
- Add day formatting strings (weekdays, weekend, everyday, individual days)
- Add schedule format string
* feat(04-03): create useScheduleDisplay hook for schedule formatting
- Add bitmask constants matching Go model (Sun=1, Mon=2, etc.)
- formatDaysOfWeek: smart groupings (Every day, Weekdays, Weekends) or comma-separated
- formatTimeOfDay: locale-appropriate 12/24hr time from HH:MM
- formatSchedule: combines days and time with i18n format string
- formatNextRun: smart relative formatting (Today, Tomorrow, Day name, Date)
- formatLastRun: formatted date or 'Never run'
- formatRunCount: pluralized run count
* feat(04-03): create ScheduledRecapItem component
- Render card with title and schedule pattern subtitle
- Show next run time when schedule is active
- Toggle between Active/Paused states with pause/resume actions
- Run stats (last run, run count) appear on hover
- Kebab menu with Edit and Delete options
- Delete confirmation modal with FormattedMessage
- Use useScheduleDisplay hook for all formatting
* feat(04-03): add ScheduledRecapItem styles
- Card with border, radius, and hover state
- Flexbox layout with title/subtitle and actions
- Title with truncation (ellipsis) for long names
- Subtitle with metadata separator styling
- Run stats with opacity transition on hover
- Toggle button min-width for consistent sizing
- Kebab menu button hover state
* docs(04-03): complete ScheduledRecapItem component plan
Tasks completed: 4/4
- Add i18n strings for scheduled recap UI
- Create useScheduleDisplay hook for schedule formatting
- Create ScheduledRecapItem component
- Add ScheduledRecapItem styles
SUMMARY: .planning/phases/04-scheduled-tab/04-03-SUMMARY.md
* feat(04-04): create ScheduledRecapsEmptyState component
- Empty state displays when no scheduled recaps exist
- Shows illustration with icons, title, description
- CTA button to create first recap
- Supports disabled state when agents bridge is disabled
* feat(04-04): create ScheduledRecapsList component
- Renders empty state when no scheduled recaps exist
- Maps over scheduled recaps to render ScheduledRecapItem
- Passes edit and create handlers through to children
* feat(04-04): add Scheduled tab to main Recaps component
- Add Scheduled tab after Unread and Read tabs
- Fetch scheduled recaps on mount with getScheduledRecaps
- Display ScheduledRecapsList when on scheduled tab
- Wire up edit handler (opens create modal - Phase 5 adds pre-fill)
- Import scheduled_recap_item.scss for styling
* style(04-04): add SCSS styles for scheduled recaps
- Add .scheduled-recaps-list styles (flex column, centered, gap)
- Add .scheduled-recaps-empty-state styles (centered, illustration, text)
- Consistent with existing recap UI styling patterns
* docs(04-04): complete Scheduled tab integration plan
* docs(04): update STATE.md for phase 4 completion
* docs(04): complete Scheduled Tab phase
Phase 4: Scheduled Tab
- 4 plans executed across 4 waves
- 15/15 requirements verified
- Human verified UI works correctly
* docs(05): capture phase context
Phase 05: Enhanced Wizard
- Implementation decisions documented
- Phase boundary established
* docs(05): add research hints for component discovery
* docs(05): research phase domain for enhanced wizard
Phase 05: Enhanced Wizard - Frontend Implementation
- Standard stack identified (existing codebase components)
- Architecture patterns documented (multi-step modal, bitmask days)
- Pitfalls catalogued (timezone, validation, edit mode)
- Code examples from codebase referenced
* docs(05): create phase plan for enhanced wizard
Phase 05: Frontend - Enhanced Wizard
- 6 plans in 3 waves
- Wave 1: Redux actions, DayOfWeekSelector
- Wave 2: ScheduleConfiguration, Run once toggle
- Wave 3: Modal integration, Edit wiring
- Ready for execution
* feat(05-01): add action type constants for create/update scheduled recap
- CREATE_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE
- UPDATE_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE
* feat(05-02): create DayOfWeekSelector component
- Bitmask-based day selection matching server model
- Monday-first ordering for work schedule intuition
- XOR toggle for clean state management
- aria-pressed accessibility support
* feat(05-01): add createScheduledRecap async action
- Takes ScheduledRecapInput parameter
- Calls Client4.createScheduledRecap
- Dispatches RECEIVED_SCHEDULED_RECAP on success
- Follows existing pauseScheduledRecap pattern
* feat(05-02): add DayOfWeekSelector styles
- Flexbox layout with 8px gap between buttons
- 40x40px day buttons with hover states
- Selected state uses button-bg color
- Error and disabled state styling
* feat(05-01): add updateScheduledRecap async action
- Takes id and ScheduledRecapInput parameters
- Calls Client4.updateScheduledRecap
- Dispatches RECEIVED_SCHEDULED_RECAP on success
- Follows existing action patterns
* docs(05-02): complete DayOfWeekSelector plan
Tasks completed: 2/2
- DayOfWeekSelector component with bitmask state
- Styled button group with toggle/hover/error states
SUMMARY: .planning/phases/05-enhanced-wizard/05-02-SUMMARY.md
* docs(05-01): complete Redux actions for scheduled recaps plan
Tasks completed: 3/3
- Add action type constants for create/update scheduled recap
- Add createScheduledRecap async action
- Add updateScheduledRecap async action
SUMMARY: .planning/phases/05-enhanced-wizard/05-01-SUMMARY.md
* feat(05-04): add run once toggle to RecapConfiguration
- Add runOnce, setRunOnce, and isEditMode props to Props type
- Import Toggle component
- Add run once toggle section at bottom of Step 1
- Toggle hidden when isEditMode is true
- Include descriptive text below toggle
* feat(05-03): create ScheduleConfiguration component for Step 3
- Add day-of-week selection using DayOfWeekSelector
- Add time picker with 30-minute intervals and locale-aware formatting
- Add time period dropdown (Previous day, Last 3 days, Last 7 days)
- Add custom instructions textarea with 500 char limit
- Add next run preview with timezone support
- Use getCurrentTimezone selector for user timezone
* feat(05-04): add run once toggle styles
- Add .run-once-group with top separator border
- Style toggle and label with proper alignment
- Add description text with left margin for alignment
- Use consistent spacing and typography
* feat(05-03): add Step 3 schedule configuration styles
- Add step-three layout with vertical flex and gap
- Add form-group styling with label and error states
- Add next-run-preview styling with background and subtle text
- Add textarea overrides for custom instructions input
* docs(05-04): complete run once toggle plan
Tasks completed: 2/2
- Add run once toggle to RecapConfiguration
- Add run once toggle styles
SUMMARY: .planning/phases/05-enhanced-wizard/05-04-SUMMARY.md
* docs(05-03): complete ScheduleConfiguration plan
Tasks completed: 2/2
- Create ScheduleConfiguration component
- Add Step 3 styles to SCSS
SUMMARY: .planning/phases/05-enhanced-wizard/05-03-SUMMARY.md
* feat(05-05): add schedule state and edit mode props to modal
- Add editScheduledRecap prop for edit mode detection
- Add schedule state (daysOfWeek, timeOfDay, timePeriod, customInstructions)
- Add runOnce state and validation state (daysError, timeError)
- Add useEffect to pre-fill form in edit mode
- Import createScheduledRecap, updateScheduledRecap actions
- Import ScheduleConfiguration component and getCurrentTimezone selector
* feat(05-06): update handleEditScheduledRecap to pass scheduled recap to modal
- Find scheduled recap by ID from scheduledRecaps array
- Pass editScheduledRecap via dialogProps to CreateRecapModal
- Early return if scheduled recap not found
* feat(05-05): update step navigation for run once and schedule flows
- Update handleNext to clear validation errors on navigation
- Update handlePrevious to clear validation errors on navigation
- Update getTotalSteps for run once (2-3 steps) vs scheduled (always 3)
- Update getActualStep for proper step indicator mapping
* feat(05-05): update renderStep for schedule vs run once flows
- Pass runOnce, setRunOnce, and isEditMode props to RecapConfiguration
- Show ChannelSummary for run once mode at step 3
- Show ScheduleConfiguration for scheduled mode at step 3
- Pass all schedule state props to ScheduleConfiguration component
* feat(05-05): update handleSubmit for immediate and scheduled recaps
- Add schedule field validation for non-run-once mode
- Dispatch createRecap for run once mode (existing behavior)
- Dispatch updateScheduledRecap for edit mode
- Dispatch createScheduledRecap for new scheduled recaps
- Navigate to ?tab=scheduled after creating/editing scheduled recap
- Add proper error messages for schedule validation failures
* feat(05-05): update modal header and button text for edit mode
- Update canProceed to validate schedule fields in step 3
- Add getConfirmButtonText helper for context-aware button text
- Show 'Start recap' for run once, 'Save changes' for edit mode
- Show 'Create schedule' for new scheduled recaps
- Update headerText to show 'Edit your recap' in edit mode
* docs(05-05): complete wizard integration plan
Tasks completed: 5/5
- Add schedule state and edit mode props
- Update step navigation for run once and schedule flows
- Update renderStep for schedule vs run once flows
- Update handleSubmit for immediate and scheduled recaps
- Update modal header and button text for edit mode
SUMMARY: .planning/phases/05-enhanced-wizard/05-05-SUMMARY.md
* fix(05-06): JSON.stringify body in scheduled recap API calls
createScheduledRecap and updateScheduledRecap were passing objects
directly to doFetch body, causing '[object Object]' to be sent instead
of JSON. Fixed to match createRecap pattern.
* fix(05-06): align time period values with server model
Frontend was using 'last_3_days' and 'last_7_days' but server expects
'last_24h', 'last_week', and 'since_last_read'. Updated options to match.
* fix(05-06): remove duplicate border on custom instructions textarea
- GenericModal adds a border to all .form-control elements
- Input widget's Input_fieldset already provides a border container
- This caused a double-border visual glitch on the textarea
- Added border: none to the inner textarea to fix the issue
* fix(05-06): reserve space for next run preview to prevent modal height jump
- Always render next-run-preview container (previously conditionally rendered)
- Use visibility:hidden instead of not rendering when no preview available
- Add non-breaking space placeholder to maintain consistent element height
- Prevents jarring visual jump when user selects a day of the week
* fix(05-06): remove border/background from next-run-preview
- Remove padding, border-radius, and background-color from .next-run-preview
- Style as plain text with subtle color and smaller font size
- Keep margin for appropriate spacing from time selector
* fix(05-06): use abbreviated timezone in next recap preview
- Use Intl.DateTimeFormat with timeZoneName: 'short' to get timezone
abbreviation (e.g., EST, PST, EDT) instead of full label
- Remove unused getCurrentTimezoneLabel selector import
- Preview now shows 'Monday at 9:00 AM (EST)' instead of
'Monday at 9:00 AM ((UTC-05:00) Eastern Time (US & Canada))'
* fix(05-06): prevent modal height jump when next run preview appears
- Move next-run-preview inside time-selection-group as helper text
- Add min-height: 16px to reserve space when preview is hidden
- Use visibility: hidden instead of display: none for consistent height
- Reduce step-three gap from 20px to 16px for better spacing
- Add margin-bottom: 0 to form-group to override default spacing
* fix(05-06): add section titles and fix spacing in schedule configuration
- Add section titles per Figma design (Heading 100 style):
- 'When would you like your summary sent?' as main header
- 'On which days should your recap run?' for days section
- 'At what time?' for time section
- 'Select a time period for your recap to cover' for time period
- 'Additional instructions for {agentName}' for custom instructions
- Pass agentName prop from parent to show selected agent name
- Fix spacing: reserve space for next-run preview with container
to prevent time period section from jumping when preview appears
- Update SCSS with schedule-section groups and proper spacing
* fix(05-06): remove duplicate title and fix subtitle-dropdown spacing
- Remove 'When would you like your summary sent?' duplicate title
- Add scoped CSS rule for 12px total spacing between subtitle and dropdown
* fix(05-06): use standard Toggle without text labels for active/paused state
- Remove onText/offText props from Toggle component
- Add ariaLabel for accessibility (describes toggle state and action)
- Update SCSS to remove min-width constraint that was for text display
- Navigation to scheduled tab after creating scheduled recap already works correctly
* fix(05-06): toggle color and tab navigation after creating scheduled recap
- Use btn-toggle-primary class for scheduled recap toggle to display proper button-bg color
- Add useQuery hook to read tab query parameter from URL
- Sync activeTab state with URL tab parameter to enable navigation after modal close
* fix(05-06): sync tab state with URL bidirectionally
- Add handleTabChange callback that updates both state and URL
- Use history.replace() to update URL without polluting browser history
- Remove tab param from URL when switching to 'unread' (default tab)
- Simplify URL sync useEffect to always update from tabParam
- This enables proper navigation after creating scheduled recaps
* fix(05-06): fix navigation to scheduled tab after creating scheduled recap
- Replace useRouteMatch() with getCurrentRelativeTeamUrl selector
- Modal was using route match which returned wrong URL context (modal is rendered at root level)
- Use team selector to get correct team URL for navigation
- Update test to remove unnecessary useRouteMatch mock
* fix(05-06): sort scheduled recaps by newest first
- Update getAllScheduledRecaps selector to sort by create_at descending
- Follows same pattern as other recap selectors (getUnreadRecaps, getReadRecaps)
- Derived selectors (getActiveScheduledRecaps, getPausedScheduledRecaps) inherit sort order
* docs(05-06): complete edit wiring and UI polish plan
* docs(phase-05): complete Enhanced Wizard phase
Phase 5: Enhanced Wizard
- 6 plans executed across 3 waves
- 13 requirements verified
- Multi-step wizard for creating/editing scheduled recaps
- Run once and scheduled flows
- Full edit mode with pre-fill
- Extensive UI polish based on human feedback
All 39 requirements complete. Milestone ready for audit.
* docs(v1): milestone audit complete - all requirements satisfied
- 39/39 requirements verified
- 5/5 phases passed
- 100% cross-phase integration
- 5/5 E2E flows complete
- 2 minor tech debt items (non-blocking)
* chore: remove .planning from git tracking
- Add .planning to .gitignore
- Remove .planning files from git index (kept locally)
- Planning files are for local development only
* feat(06-01): add AIRecapSettings and RecapLimitSettings structs
- RecapLimitSettings with 7 limit fields (recaps/day, scheduled, channels, posts, tokens, posts/day, cooldown)
- AIRecapSettings with master toggle and per-limit enforcement toggles
- SetDefaults methods with sensible defaults (10 recaps/day, 5 scheduled, etc.)
- isValid/IsValid validation methods enforcing natural minimums
* feat(06-01): integrate AIRecapSettings into Config struct
- Add AIRecapSettings field to Config struct
- Call AIRecapSettings.SetDefaults() in Config.SetDefaults()
- Call AIRecapSettings.IsValid() in Config.IsValid()
* test(06-01): add tests for AIRecapSettings and RecapLimitSettings
- TestAIRecapSettingsSetDefaults: verifies all defaults match spec
- TestRecapLimitSettingsValidation: verifies validation rejects invalid values
- TestAIRecapSettingsPreservesExistingValues: verifies SetDefaults preserves existing
- TestAIRecapSettingsIsValid: verifies IsValid delegates to DefaultLimits
* feat(06-02): create EffectiveRecapLimits struct
- Add EffectiveRecapLimits struct with 7 resolved limit fields
- Add LimitSource type with system/group/user constants
- Add UnlimitedValue constant (-1) for disabled limits
- Add IsLimitEnabled helper function for enforcement code
* feat(06-02): create GetEffectiveLimits resolution function
- Add GetEffectiveLimits(userID) returning resolved limits for any user
- Resolve limits from AIRecapSettings.DefaultLimits config
- Apply per-limit enforcement toggles (disabled = -1 unlimited)
- Add helper functions getValueOrDefault and getBoolOrDefault
- Structure for Phase 8 group/user resolution with TODOs
* test(06-02): add tests for GetEffectiveLimits function
- TestGetEffectiveLimitsDefaults verifies system defaults returned
- TestGetEffectiveLimitsWithDisabledToggle verifies -1 returned for disabled limits
- TestGetEffectiveLimitsWithCustomDefaults verifies custom config honored
- TestGetEffectiveLimitsAllTogglesDisabled verifies all -1 when all disabled
- TestGetEffectiveLimitsUnlimitedConfigValue verifies -1 config value honored
- TestIsLimitEnabled verifies helper correctly identifies enabled limits
* feat(07-01): add RecapStatusSkipped constant and SkipReason field
- Add RecapStatusSkipped constant for recaps skipped due to limit violations
- Add SkipReasonDailyLimit and SkipReasonCooldown skip reason constants
- Add ScheduledRecapId field to Recap struct for tracking scheduled recaps
- Add SkipReason field to Recap struct for tracking why recap was skipped
- Update Auditable() method to include new fields
- Update recapColumns and recapToMap to include new fields
* feat(07-01): add store interface methods for limit enforcement
- Add CountForUserSince to RecapStore for daily limit enforcement
- Add GetLastCompletedManualRecap to RecapStore for cooldown checking
- Add CountForUser to ScheduledRecapStore for max scheduled recaps limit
- Update mock implementations for both stores
* feat(07-01): implement store methods in SQL stores
- Implement CountForUser in SqlScheduledRecapStore
- Counts active (non-deleted, enabled) scheduled recaps for a user
- Implement CountForUserSince in SqlRecapStore
- Counts recaps since timestamp, excluding skipped recaps
- Implement GetLastCompletedManualRecap in SqlRecapStore
- Returns most recent completed manual recap (no ScheduledRecapId)
- Returns nil, nil when no manual recap exists
* feat(07-03): add daily limit check to scheduled recap worker
- Add GetEffectiveLimits and GetUser to AppIface for limit checking
- Check MaxRecapsPerDay before executing scheduled recap
- Create skipped recap record when daily limit exceeded
- Use user's timezone for midnight calculation
- Update next run time even when skipping (scheduler moves on)
* feat(07-03): add cooldown check to manual recap creation
- Check CooldownMinutes before allowing manual recap creation
- Return HTTP 429 with retry-after info when cooldown active
- Only checks against completed manual recaps (per CONTEXT.md)
- Failed recaps don't consume cooldown (checks completed only)
* feat(07-03): add i18n messages for cooldown errors
- Add cooldown_active error with retry info template
- Add cooldown_check_failed error message
- Uses "Your organization's policy limits..." pattern
* feat(07-02): add limit checks to CreateScheduledRecap
- Add max scheduled recaps limit check using CountForUser store method
- Add max channels per recap limit check against ChannelIds length
- Return HTTP 400 with clear error messages when limits exceeded
- Uses GetEffectiveLimits for limit resolution (ENF-01, ENF-02, ENF-08)
* feat(07-02): add i18n error messages for scheduled recap limits
- Add max_scheduled_reached message with "Your organization's policy limits..." pattern
- Add max_channels_exceeded message with limit and requested count
- Add count_failed internal error message
* test(07-04): add unit tests for post/token truncation
- Test proportional post distribution across channels
- Test minimum 1 post per channel guarantee
- Test empty channel handling
- Test token estimation (4 chars/token heuristic)
- Test token limit truncation removes from largest channels
Verifies ENF-05, ENF-06 truncation implementation.
* test(07-05): add ENF-07 permission preservation tests
- Add tests verifying over-limit users can view/edit/delete existing recaps
- Tests confirm management operations do NOT check limits (grandfathering)
- Tests confirm creation IS still blocked when over limit
- Add migration 000151 for missing ScheduledRecapId/SkipReason columns
ENF-07: Limits only block creation, not management of existing resources
* feat(08-01): create UnlimitedNumberSetting component
- Number input with Unlimited checkbox for admin console settings
- When checked: disables input and sets value to -1 (unlimited)
- When unchecked: enables input and sets value to defaultValue
- Supports disabled state and setByEnv footer
* test(08-01): add unit tests for UnlimitedNumberSetting
- Tests rendering with numeric and unlimited values
- Tests checkbox toggle behavior (check/uncheck)
- Tests number input changes
- Tests disabled state and setByEnv footer
- Tests custom unlimited label and placeholder
- 11 test cases covering core functionality
* feat(08-02): add Recaps subsection to admin_definition.tsx
- Import UnlimitedNumberSetting component
- Add 'recaps' subsection under site configuration section
- Include master enable toggle for AI Recap Limits
- Add 3 grouped sections: Quota Limits, Content Limits, Time Limits
- Configure all 7 limit settings with proper config keys
- Add AIRecapSettings and RecapLimitSettings TypeScript types
- All settings disabled when master toggle is off
* feat(08-02): add i18n strings for Recaps admin section
- Add admin.sidebar.recaps for navigation
- Add admin.site.recaps for section title
- Add admin.recaps.enable.* for master toggle
- Add admin.recaps.sections.* for section descriptions
- Add admin.recaps.max*.* for all limit field labels/descriptions
- Add admin.recaps.cooldownMinutes.* for time limit settings
- Add admin.recaps.unlimited for checkbox label
- Total: 24 new i18n strings
* style(08-02): remove section comments to fix lint errors
Remove inline comments that triggered lines-around-comment lint rule
* feat(09-01): add RecapLimitStatus model and App layer logic
- Add RecapLimitStatus, DailyUsageStatus, CooldownStatus structs
- Implement App.GetRecapLimitStatus with daily usage count and cooldown calculation
* feat(09-01): add GET /api/v4/recaps/limit_status endpoint
- Register route and handler
- Return structured limit status
- Add error translation
* feat(09-01): add TypeScript types for limit status
- Export RecapLimitStatus and related types
* feat(09-02): add recap limit status redux integration
- Add Client4.getRecapLimitStatus method
- Add Redux action, reducer, and selector for limit status
- Update GlobalState and initial state to include limitStatus
* fix(09-02): update CreateRecapModal error handling and tests
- Check dispatch result.error instead of try/catch to handle server errors
- Display server error message (e.g. policy limits) inline
- Fix TypeScript errors: displayName property and missing props in tests
* feat(09-03): implement user-facing limit status UI
* Verify Phase 9: User-Facing UX
* Remove UAT artifact
* fix: UI/UX issues (badge, toggle, input, tooltip)
* fix: Increase RecapUsageBadge tooltip z-index
* blank lines
* [MM-67163] checkpoint: scheduled recaps feature working
All phases (01-09) complete and verified:
- Database foundation with DST-aware scheduling
- API layer for CRUD operations
- Job scheduler and worker
- Scheduled tab UI with list/create/edit/delete
- Enhanced wizard with schedule configuration
- Config settings and admin console section
- Limit enforcement (daily, cooldown, token, post)
- User-facing limit status badge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [MM-67163] simplify: reduce duplication and fix issues across scheduled recaps
- Extract requireScheduledRecapOwnership helper for 5 API handlers
- Consolidate ResumeScheduledRecap from 4 store calls to 2
- Remove redundant Get() in PauseScheduledRecap
- Extract advanceSchedule helper in worker to deduplicate skip/success paths
- Remove double PreSave() in store Save method
- Remove dead code fallback in GetEffectiveLimits
- Deduplicate ScheduledRecapInput construction in create modal
- Fix missing fetchRecapLimitStatus import (TS error)
- Extract day-of-week bitmask constants to @mattermost/types/recaps
- Fix hardcoded English strings in schedule_display formatNextRun
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address CodeRabbit review feedback on scheduled recaps
Server fixes:
- Gate getRecapLimitStatus with requireRecapsEnabled guard
- Normalize error mapping (404 vs 500) in scheduled recap handlers
- Enforce MaxChannelsPerRecap limit in UpdateScheduledRecap
- Return explicit error for unsupported all_unreads mode in scheduled recaps
- Add compensation logic to clean up orphan recaps on job creation failure
- Fix proportional post truncation to strictly enforce maxPosts cap
- Add missing CountForUser retry wrapper in RetryLayerScheduledRecapStore
- Handle NULL ScheduledRecapId in manual recap cooldown lookup
- Exclude soft-deleted rows in scheduled recap Get query
Frontend fixes:
- Pass isCreationBlocked to ScheduledRecapsList empty state
- Fix same-day nextRunAt mislabeled as "Tomorrow" in schedule display
- Handle thunk error results in scheduled recap item actions
- Replace scheduled recaps map on full refresh instead of merging
Made-with: Cursor
* Handle thunk error results in scheduled recap toggle handler
Check dispatch result for errors in handleToggle to prevent
false-success UI flows when pause/resume operations fail.
Made-with: Cursor
* Fix CI failures: mock store, permissions, migrations, lint, and Playwright config
- Add ScheduledRecap() to storetest.Store mock and retrylayer test setup
- Register sysconsole_read_ai_recaps / sysconsole_write_ai_recaps permissions
- Renumber scheduled_recaps migration from 150→156 and recap_skip_fields from 151→157 to resolve version conflicts
- Fix ESLint errors in recap components (operator-linebreak, import order, labels, headers, etc.)
- Add AIRecapSettings to Playwright default_config.ts
Made-with: Cursor
* Address CodeRabbit Round 2 review feedback
- Add OpenAPI spec for GET /api/v4/recaps/limit_status with schema
definitions for RecapLimitStatus, EffectiveRecapLimits,
DailyUsageStatus, and CooldownStatus
- Set ScheduledRecapId when creating recaps from schedules to prevent
cooldown logic from treating scheduled recaps as manual
- Handle past nextRunAt timestamps in schedule display: show "Yesterday"
for -1 day and full date for older past dates
Made-with: Cursor
* Add missing variable declarations for AI recaps permissions
Made-with: Cursor
* Fix jsx-max-props-per-line lint errors in schedule_configuration.tsx
Made-with: Cursor
* Fix stylelint property order in recap SCSS files
Made-with: Cursor
* Update admin sidebar snapshots to include Recaps section
Made-with: Cursor
* Fix Go lint issues and add OpenAPI specs for scheduled_recap endpoints
- Use max/min builtins instead of if-statements (modernize/minmax)
- Use range-over-int syntax for for-loops (modernize/rangeint)
- Fix tautological Monday&Monday test (staticcheck/SA4000)
- Add OpenAPI specs for all 7 scheduled_recap API routes
- Add ScheduledRecap model definition to definitions.yaml
Made-with: Cursor
* Regenerate i18n en.json for recap-related strings
Made-with: Cursor
* Fix gofmt indentation in recap.go
Made-with: Cursor
* Fix scheduled recap CI regressions
Align the scheduled recap soft-delete store test with the current Get behavior and add the missing scheduled recap i18n strings so server and enterprise checks stay in sync.
Made-with: Cursor
* Address CodeRabbit review feedback on scheduled recaps
- Use session user ID instead of client-controlled recap.UserId for
limit enforcement in UpdateScheduledRecap (security hardening)
- Add missing i18n entry for app.recap.fetch_posts.app_error
- All other review comments were already addressed in prior commits
Made-with: Cursor
* Fix schedule configuration import order
Reorder the moment import so the webapp lint job passes again on the scheduled recap PR.
Made-with: Cursor
* Allow selecting "all unreads" recap type when no current unreads exist
With scheduled recaps, users should be able to select "all unreads" even
without current unread channels since unreads will exist when the schedule
runs. The "run once" toggle is now disabled when all unreads is selected
with no current unreads, preserving the pre-scheduling behavior of
preventing an immediate recap with nothing to summarize.
Made-with: Cursor
* Fix indentation in recap_configuration.tsx to satisfy eslint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix Recaps test mocks for scheduled recap state.
Keep the webapp test shard green by mirroring the new selectors and mount-time actions used by the Recaps page.
Made-with: Cursor
* Update server/channels/app/recap.go
* Update server/channels/app/recap.go
* Update server/channels/app/recap_limits.go
* Update server/channels/app/recap_limits.go
* Update server/channels/app/scheduled_recap.go
* Update server/channels/app/scheduled_recap.go
* Update webapp/channels/src/components/recaps/scheduled_recaps_empty_state.tsx
* Update server/channels/app/scheduled_recap.go
* Fix gofmt formatting in recap limits
Made-with: Cursor
* Fix recap limits for soft-deleted recaps
Keep deleted recaps in quota and cooldown checks so soft deletion cannot bypass AI usage enforcement.
Made-with: Cursor
* Fix translation
* Fix server check-style: concurrent indexes and lint cleanups
- Use CREATE/DROP INDEX CONCURRENTLY in 000168 scheduled recaps migrations
(required by mattermost-govet concurrentIndex check).
- gofmt validation constants in scheduled_recap.go.
- Replace string += loops in recap tests with strings.Repeat for modernize linter.
Made-with: Cursor
* Fix 000168 migration: run CONCURRENTLY indexes outside transaction
PostgreSQL rejects CREATE/DROP INDEX CONCURRENTLY inside a transaction.
Morph requires -- morph:nontransactional for these migrations, matching
other index migrations in the repo.
Made-with: Cursor
* Stabilize scheduled Recaps for review
Bring the scheduled Recaps work back into a shippable state by tightening backend scheduling and limit semantics, cleaning up the UI flows, and adding focused Recaps E2E coverage.
Made-with: Cursor
* Fix scheduled recaps lint failures
Made-with: Cursor
* Fix scheduled recap Go lint
Made-with: Cursor
* Fix scheduled recaps Playwright check
Made-with: Cursor
* Sync scheduled recaps i18n catalog
Made-with: Cursor
* Fix server recaps CI checks
Made-with: Cursor
* Stabilize recap server CI setup
Made-with: Cursor
* Recaps: enforce token limit, remove dead truncation code, simplify scheduled worker
- Delete unused multi-channel truncation subsystem (FetchAndTruncatePostsForRecap, truncatePostsProportionally)
- Enforce MaxTokensPerRecap in the live recap path (previously a silent no-op)
- Drop redundant non-atomic daily-limit pre-check in the scheduled worker; rely on the atomic check in CreateRecapFromSchedule
- Disable non-recurring schedules on all terminal paths (including daily-limit skips) via finalizeSchedule
- Trim scheduled-recap job payload to scheduled_recap_id
- Exclude skipped recaps from GetRecapsForUser
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* Recaps webapp: format schedule times in the schedule timezone, static day i18n, typed schedule fields
- Display next-run and schedule times using the scheduled recap's timezone (shared schedule_time_format helper) instead of browser-local time
- Replace dynamic day-of-week i18n message IDs with static descriptors so strings are extractable/translatable
- Add ScheduledRecapTimePeriod/ScheduledRecapChannelMode union types
- Use shared Button in the scheduled recaps empty state
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* Recaps e2e: cover scheduled edit/delete/all-unreads/empty-state and token-limit enforcement
- Add UI coverage for editing, deleting, and the empty state of scheduled recaps
- Add all-unreads scheduled recap modal flow
- Add immediate-recap token-limit enforcement tests (single and per-channel), validating MaxTokensPerRecap truncation end-to-end
- Extend recaps page object and helpers
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* Recaps: regenerate store layers/mocks and i18n after cleanup; fix lint
- Regenerate timerlayer/retrylayer/RecapStore mock to canonical order
- Drop orphaned app.recap.fetch_posts.app_error i18n key (removed with dead fetch helper)
- Add missing semicolon in UnlimitedNumberSetting props
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* Recaps: regenerate default roles permissions and admin sidebar snapshot
- Regenerate Cypress default_roles_permissions fixture to include AI Recaps sysconsole permissions
- Update admin sidebar snapshot for the new Recaps section
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* Recaps: stabilize flaky cooldown round-up test
The cooldown round-up subtest placed the prior recap 30s before a 2-minute
cooldown, leaving only ~30s of slack before the rounded remaining time would
flip from 2 to 1 minute. Under heavily loaded CI this could intermittently fail
the exact-minute assertion. Move the prior recap to 1s ago so the remaining time
sits near the top of the 2-minute band (~59s slack) while still exercising
ceiling rounding.
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* Recaps: store ScheduledRecap.ChannelIds as jsonb
Postgres is the only supported database, so the prior TEXT+JSON-string
workaround for MySQL compatibility is unnecessary. Store ChannelIds in a
jsonb column and type the model field as model.StringArray, which removes the
bespoke marshal/unmarshal and intermediate scan struct in the store.
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* Recaps: gate recap limit settings on the ai_recaps permission
Add access:"ai_recaps" to DefaultLimits and all RecapLimitSettings fields so a
delegated admin with sysconsole_write_ai_recaps can save the limit values,
instead of falling back to requiring manage_system.
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* Recaps: drop bespoke Users row lock for limit enforcement
The recap limit savers were the only SELECT ... FOR UPDATE in the sqlstore.
Conform to the prevailing pattern (e.g. channel_bookmark_store.Save): enforce
MaxScheduledRecaps / MaxRecapsPerDay with a transactional count + insert and no
row lock, accepting the same best-effort behavior under concurrency as channels,
team members, and bookmarks.
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* Recaps: document SaveOnceByTypeAndData dedup semantics
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* Recaps: use SERIALIZABLE isolation for limit-check inserts
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* Recaps: mark orphaned recap skipped when job enqueue fails
When CreateJob fails after the recap row is committed, flag the recap
skipped with reason job_creation_failed instead of leaving it pending.
Skipped recaps are excluded from the daily-limit count, so this frees
the quota slot for a recap that will never run, and keeps CreateRecap
consistent with CreateRecapFromSchedule.
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* ci: retrigger CI (flaky enterprise npm cache EEXIST)
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* ci: retrigger CI (flaky Vet API container init)
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* Recaps: re-check channel read permission at recap execution time
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* fix scheduled recap job server test setup
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* ci: retry flaky webapp test
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* ci: retry documentation impact review
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* MM-67163: Address review feedback: remove unused userID param, return AppError from GetRecapLimitStatus
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* MM-67163: Bulk channel permission check for recap creation, bounded by channel limit
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* chore: rerun CI
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* chore: rerun CI after network failure
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
|
||
|
|
2851af059d |
Add admin-locked profile fields for email users and pre-provisioned names on invites (#37458)
* Add TeamSettings.LockProfileFieldsForEmailUsers with server-side enforcement Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Add API tests for LockProfileFieldsForEmailUsers enforcement Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Hide admin-locked profile fields in user settings and add System Console dropdown Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Support pre-set username and name on team email invites Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Add tests for invite profiles; fix resend worker channel-list parsing Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Add pre-set profile inputs to member invite modal Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Prefill and lock pre-set username on signup page Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Add first/last name editing to System Console user detail and document new setting Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Fix lint issues in invite modal profile inputs Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Fix double outline on invite modal profile inputs inside GenericModal Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Refactor invite emails to InviteEmailData struct and harden invite profile validation Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Centralize profile-lock permission exemption in app layer and add config coverage Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Per-field name locking in profile settings, typed lock setting, and shared invite profile helpers Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Add Playwright E2E coverage for locked profile fields and pre-set invite profiles Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Sync playwright package-lock with merged workspace versions Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Restore upstream playwright package-lock (fix npm ci drift) Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Assert invite input cleared instead of chip text after adding email Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Fix invite modal scroll, username error layout, and clipped autocomplete Keep the footer pinned while tall profile rows scroll, show username validation full-width after blur, and portal select menus so they are not clipped by the scroll container. Co-authored-by: Cursor <cursoragent@cursor.com> * fixes for autocomplete items not aligning properly * Make invite autocomplete menu portal opt-in and fix modal chrome Confine document.body menu portaling to the invite modal via a menuPortal prop, and restore click-away, slide-in animation, and header alignment for the scrolling invite modal layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix eslint lines-around-comment on menuPortal props Co-authored-by: Cursor <cursoragent@cursor.com> * Fix stylelint property order in invitation modal SCSS. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix stylelint property order in invitation_modal.scss Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Adapt invite modal E2E to portaled autocomplete menus Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Simplify locked profile invite implementation Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Fix invite modal review and E2E feedback Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Retry flaky enterprise CI Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Retry Docker image export CI Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Minimize locked profile fields diff Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Fix locked profile E2E documentation Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Address minimized test review feedback Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Fix email test whitespace Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Retry OpenSearch download CI Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Retry flaky Cypress thread navigation Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Preserve legacy invite behavior without profiles Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Clarify invite profile validation Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Retry flaky enterprise E2E Co-authored-by: nick.misasi <nick.misasi@mattermost.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Matthew Birtch <2040554+matthewbirtch@users.noreply.github.com> |
||
|
|
3a820143a1 |
MM 69100 - Team ABAC Membership - members sync and end user surfaces (#37054)
* 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 * MM-69100 - add team membership ABAC sync worker with mode-aware removal and auto-add * Add team ABAC removal/auto-add notifications, cascade audit, and team custom-rules save backend * Add Team Settings team membership tab with custom-rules editor, auto-add toggle, save confirmation, and mode-flip sync trigger * Add team access control policy panels, job details team list, and e2e coverage for membership tab and discoverability cards * ix save double-submit with loading state, team privacy via updateTeamPrivacy, mode-flip count accuracy, sync trigger logic, i18n keys, and e2e test cleanup * Add team membership policy notices to invite and add-to-team flows, team access control system messages, and a team policy attributes endpoint * Add team membership recommended tag for qualifying users and team requirements notice in the members modal * temp * adjust styling, add new e2e, fix team admin job permissions issues * e2e clean items created * fix directory and team integration e2e tests * Surface team sync results in the Sync Job Details modal via a Teams tab linked from the chained channel job * code clean up, adjust styling * Add access control attributes endpoint and enhance E2E tests for team membership * Enhance team access control by updating permission checks and syncing jobs * Add API-level enforcement gate tests for ABAC team membership * Fix formatting in team channel settings component by adding missing semicolon * Fix formatting in Client4 class by adjusting type annotations for clarity * revert unwanted changes in package-lock * Refactor job permission test and simplify error handling in team details component * Update test assertions and enhance auto-add functionality in team membership tab * Update onboarding tests to use 'Public Team' card for team access settings * Refactor AccessSettings and TeamPolicyEditor components to replace allowOpenInviteCheckbox with public and private team buttons, and update access control job dispatching logic. * Remove redundant state updates for channel assignments in access control policy reducer * Implement email suppression for team membership notifications and add related tests * Authorize ABAC team self-join by attribute match instead of the join_private_teams role * comments clean up * Gate team privacy type normalization behind active ABAC, keep legacy allow_open_invite-only path otherwise * adjust job details styles * Add team membership policy disconnect confirmation, fix linked-policy affected-member count, and polish job details modal * Combine custom rules with system policy expressions for accurate confirm counts * Implement child resource count stamping and deletion gating for access control policies * Enhance team sync job triggering logic for membership changes without auto-add * Refactor team privacy handling to align with open-directory model; update comments for clarity on allow_open_invite logic. * Deduplicate team ABAC sync jobs per policy to prevent concurrent runs emitting duplicate DMs and audit records on HA clusters * fix linter warnings * Add confirmation for policy removal in TeamDetails tests * Update delete policy message to include Teams in the warning * Add linked teams warning to policy deletion and update translations * Add Auto-add feature for policy selection and team management - Enhanced PolicySelectionModal to include an Auto-add checkbox for each policy. - Updated PolicyList to manage Auto-add state and reflect changes in the UI. - Modified TeamAccessControl to handle Auto-add functionality and persist changes. - Added tests to ensure Auto-add behavior works as expected in various scenarios. - Updated translations for Auto-add related strings. * Add team membership sync footer and enhance job fetching with policy ID * Refactor team membership policy handling and sync footer implementation * Add team sync channel cascade test for private channel membership --------- Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
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> |
||
|
|
af49e8dc80 |
Preserve 429/503 retry status codes through DoActionRequest (#36700)
* Preserve 429/503 retry status codes through DoActionRequest DoActionRequest collapsed all plugin non-200 responses to 400, losing the retry semantics carried by 429 and 503 (RFC 6585, RFC 7231). This preserves 429/503 verbatim, maps other 5xx to 502 Bad Gateway, and leaves other non-200 responses wrapped as 400. * update api documentation for new errors * fix tests affected by change --------- Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Scott Bishel <scott.bishel@mattermost.com> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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
|
||
|
|
78decd39ce | chore: bump Go version to 1.26.4 (#37287) | ||
|
|
1eb4c62cf9 |
PSAv2 endpoint improvements (#36782)
* Updates search opts and cursor for searching property fields * Adds store changes and tests to accomodate the new search options * Add an index for improving delta query efficiency * Enhances the get property fields endpoint to support deltas and hierarchical searches * Adds new fields search endpoint * Adds since to the property values endpoints * Adds property value endpoint documentation * Fix linter * Address coderabbit comments * Complete i18n * Minor improvements * Consistently apply DWIM semantics around object_type system * Add support for DM/GMs * Move auditRec to the top of the searchPropertyFieldsCore method * Update since mechanics to use greater or equal * Update API docs * Updated tests to filter after the semantics changes --------- Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es> |
||
|
|
c45a675553 |
MM-68976 Preserve PluginSettings.SignaturePublicKeyFiles on config patch endpoint (#36868)
* MM-68976 Preserve PluginSettings.SignaturePublicKeyFiles on config patch endpoint The full PUT /api/v4/config endpoint silently preserves PluginSettings.SignaturePublicKeyFiles (added in #13682), but the sparse PUT /api/v4/config/patch endpoint had no equivalent guard, so a session with sysconsole_write_plugins could modify the field through it. Mirror the full update endpoint's behavior by silently preserving the existing value in patchConfig, and add a regression test equivalent to the one in TestUpdateConfig. * MM-68976 Document SignaturePublicKeyFiles as non-modifiable in config API spec Both the update and patch config endpoints preserve PluginSettings.SignaturePublicKeyFiles; note this in the OpenAPI descriptions alongside the existing PluginSettings.EnableUploads note. |
||
|
|
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> |
||
|
|
93d0e6198d |
Route Calls pushes through a VoIP token if present (#36726)
* Route Calls pushes through a VoIP token if present
Add VoIP device id support so calls notifications can be delivered as iOS
PushKit pushes, while standard pushes continue using the regular device
token.
* New SessionPropVoIPDeviceId + IsValidVoIPDeviceID validator
* PushNotifyAppleReactNativeVoIP platform constant
* voip_device_id accepted on PUT /users/sessions/device and on login
* DoLogin refactored to accept LoginOptions struct; all four call sites
(api4/user, channels/web/{saml,magic_link,oauth}) converted
* sendPushNotificationToAllSessions: when SubType=calls, prefer
Session.Props[voip_device_id]; fall back to standard DeviceId
Tests cover the validator, the device-props endpoint (accept/reject), and
the SubType=calls routing matrix.
* Skip session per Calls plugin "answered_elsewhere" convention
When the Calls plugin fans out a cancel-ring push to a user's other
VoIP devices after they answer a call from one of them, it can't tell
core which session to skip because the public plugin API doesn't expose
a skip-session-id parameter. It encodes the auth-session-id on SenderId
together with Category="answered_elsewhere". Core honors the convention
and clears SenderId on the per-session copy before forwarding so the
session id never reaches the proxy or device. Category is preserved on
the wire so the mobile client can pick CXCallEndedReason.answeredElsewhere.
The matching side lives in mattermost-plugin-calls' push_notifications.go;
both sides cross-reference the constant.
* delay voip token audit until session props are set
* capture voip audit failed case
* Promote VoIP token to a column; dispatch by Transport; harden revocation
* ai feedback review
* feedback review
* feedback review
* fix tests
* update openAPI
* update serialized session model
---------
Co-authored-by: Jesse Hallam <jesse@mattermost.com>
|
||
|
|
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 |
||
|
|
6ef5d58b7f |
Board channel bookmarks with target_id and readonly bookmark API (#36572)
Automatic Merge |
||
|
|
5283ca54b4 |
add PushNotification.Transport and related const (#36829)
* add PushNotification.Transport and related const Supporting change for https://github.com/mattermost/mattermost-plugin-calls/pull/1189, originally in https://github.com/mattermost/mattermost/pull/36726. * updated API docs to match |