mirror of
https://github.com/mattermost/mattermost.git
synced 2026-11-01 09:28:09 -06:00
master
23754
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2b3fb21fe3 |
Add KVListWithOptions for server-side prefix filtering in plugin KV store (#35441)
* Add KVListWithOptions to push prefix filtering into SQL Add server-side prefix filtering for plugin KV store key listing via KVListWithOptions across the full stack: store interface, SQL implementation, platform service, app layer, plugin API, pluginapi helper, and memory store. Previously, WithPrefix in pluginapi.ListKeys performed client-side filtering after fetching all keys. This pushes the LIKE filter into the SQL query for efficiency, using sanitizeSearchTerm to escape SQL wildcards. * Add KVListWithOptions backward-compat fallback for old servers Manually implement the KVListWithOptions RPC glue instead of generating it so the client can detect "rpc: can't find method" errors from pre-11.5 servers and return a proper AppError with StatusNotImplemented. The generated version silently returns (nil, nil) on RPC errors, which is indistinguishable from an empty result due to encoding/gob conflating nil and empty slices. The pluginapi helper already checks for StatusNotImplemented and falls back to KVList with client-side prefix filtering. * Address review findings on KVListWithOptions - Escape backslashes in the SQL prefix instead of stripping them. - Return an AppError for every RPC failure in the KVListWithOptions client. - AND multiple WithPrefix options again; extra prefixes become checkers. - Make the old-server fallback page over prefix-filtered keys and trigger only on StatusNotImplemented. - Set the minimum server version to 12.2 and fix the WithChecker doc. - Remove the unused PlatformService.KVListWithOptions. - Route List and ListPluginKeys through their WithOptions variants. * Tighten KV list tests from CodeRabbit nitpicks - Assert exact keys per page in the prefix pagination store test. - Drop unused pgHost/pgPort lookups in the KV integration test. - Include compiler output in the plugin build failure message. * Use snake_case mlog field key in ListPluginKeysWithOptions * Move KVListWithOptions integration tests into regular test suites - Cover the plugin-to-SQL path with a test_kv_list_with_options plugin run by TestBasicAPIPlugins. - Cover the old-server RPC error mapping with a unit test against an RPC server that lacks the method. - Remove server/integration/pluginapi and the testcontainers dependency it pulled into server/go.mod. * Combine WithPrefix prefixes so all prefix filtering stays server-side A second prefix ran as a checker after page and count were applied, so pages came back short. Nested prefixes now collapse to the longest one, and prefixes that cannot both match return no keys. * Use a fixed batch size for the ListKeys prefix fallback scan The scan read the caller's count keys per KVList call, so a small count made many RPCs. It now reads 1000 keys per call, independent of count. * Use an explicit ESCAPE '*' for the plugin KV prefix LIKE Matches the other LIKE queries in the store and does not depend on the default backslash escape of Postgres. * Rename the KVListWithOptions RPC error ids to plugin.api.kv_list_with_options.* Matches the naming of the other plugin API error ids. * Add a benchmark for listing plugin keys with a prefix Compares paging through KVList and filtering in Go with KVListWithOptions, for 10k, 100k and 1M keys. * Document KVListWithOptions and prefix listing for plugins Expands the doc comments that generate the server plugin reference, and adds a best-practices section on listing KV store keys with a prefix. |
||
|
|
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> |
||
|
|
18fb0968a4 |
[MM-71476] Fix timeout when deleting the last reply in a thread on large Posts tables (#39579)
* [MM-71476] Compute thread reply stats in one query to avoid full index scan on reply delete Deleting the last live reply in a thread could time out on large Posts tables: the MAX-only LastReplyAt subquery let Postgres use a backward scan of idx_posts_create_at, which walks the entire index when no live reply remains. Selecting COUNT and MAX together prevents that rewrite, so the query reads the thread through idx_posts_root_id_delete_at. Also return the thread cleanup error before updating the root post's UpdateAt, and return an error from that update instead of only logging it, so the real cause is no longer masked by "driver: bad connection". Co-authored-by: mattermost-code <matty-code@mattermost.com> * Address PR feedback: 0 answered, 1 resolved, 0 declined Add a Postgres EXPLAIN test for the thread reply stats query, rendered from a shared threadReplyStatsQuery helper, with a MAX-only control case. * Address PR feedback: 0 answered, 1 resolved, 0 declined --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: mattermost-code <matty-code@mattermost.com> |
||
|
|
3c0d58fcb6 |
Remove unused archived file UI (#39532)
Removes the archived file tooltip, preview and attachment states, the archived flag on FileInfo, their strings, and the Cypress Cloud file limits spec. 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> |
||
|
|
f9f65b7604 |
MM-71482: Update login token handling (#39578)
* Add CWS login test cases Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Update token cleanup and CWS login handling Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Extend CWS login test cases Co-authored-by: nick.misasi <nick.misasi@mattermost.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
89f34ef2ad |
Remove unused billing history page (#39524)
Remove the admin console Billing History page, its invoice table and modals, the cloud invoice preview, the invoice redux state and the Client4 invoice methods, plus the unreachable invoice view in BillingSummary. Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
7605721bd7 |
Remove unused Cloud email templates (#39517)
Delete the Cloud arrears, payment failed, upgrade confirmation and renewal notification email templates, which are no longer sent by the server, along with the mjml partials that were only used by them. Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
9aa8140f86 |
Fix openApiSync gaps for nested, helper, and multi-method routes (#39577)
* Fix openApiSync gaps for nested and multi-method routes * Follow nested init helpers in openApiSync |
||
|
|
b4fb9d16bd |
Remove Cloud Starter billing UI (#39529)
Removes the Cloud trial ended announcement bar, which only rendered for the Cloud Starter plan, and the Starter-only cases on the billing plan details, feature list and contact sales card. 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> |
||
|
|
414ca16c11 |
Remove company information edit page (#39518)
* Remove company information edit page Removes the unused Company Information edit page from the System Console, its route, the add/edit links on the read-only page, the related redux actions and Client4 methods, orphaned strings and Cypress edit cases. Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Leave after_subscription_spec.js to the stale billing specs removal Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Remove MM-T4165 company information edit Cypress case Co-authored-by: Nick Misasi <nick13misasi@gmail.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
a8f8a6b523 | Documentation updates for some missed areas. (#39511) | ||
|
|
e8d166d8fc |
Compact the websocket channel membership structures (#38993)
* Make WebConn.ShouldSendEvent allocation-free with an immutable session bundle WebConn.ShouldSendEvent runs once per connection for every event the hub delivers. With channel-scoped iteration off (the default) that is every connection on the node for every channel-scoped event. Each call allocated 336 bytes in two places unrelated to the decision itself: - IsMFAAuthenticated built a fresh request.Context from the session on every call, and the clone escaped into the MFARequired interface call (320 bytes). - The session's roles string was split with strings.Fields on every call, whether or not a permission check followed (16 bytes). At 15k connections per node that is about 5 MB of garbage per channel-scoped event. Store the session as an immutable bundle behind the existing atomic pointer: a deep copy of the session, its roles parsed once, and a request context built once. SetSession builds the bundle in one store; GetSession keeps its signature. The hot path reads the bundle and passes the cached context to MFARequired. The cached context is shared read-only by the hub and read-pump goroutines; comments on the bundle, the session getter, and the roles accessor say so. The trade is about 320 bytes resident per connection. No decision changes. Measured end to end with BenchmarkHubChannelBroadcast (one posted event through the production hubs, one per CPU, to 5000 connections with 200 memberships each; benchstat, n=10, interleaved), against master: iteration members time per event p99 latency allocs per event off 50 780us -> 717us 2.32ms -> 798us 10349 -> 348 off 5000 1.11ms -> 1.05ms 3.20ms -> 1.19ms 10349 -> 348 on 50 37.2us -> 34.2us 60us -> 53us 472 -> 372 on 5000 1.12ms -> 1.05ms 2.70ms -> 1.20ms 10372 -> 372 on, churn 50 unchanged unchanged 894 -> 802 Bytes per event drop from 1.6 MB to 19 KB. With the work spread over one hub per CPU the mean time gains 6 to 8%; the tail gains most, p99 down 56 to 66% where every connection is checked, because the collector no longer has 1.6 MB of garbage per event to chase. The allocations left are the per-hub event preparation (29 per hub). The bundle adds about 400 bytes per connection (live heap +1.4 to 1.5%, 1.9 MiB at 5000 connections). The churn case is bound by membership reloads on the hub goroutine, which this change does not touch. Per call (BenchmarkShouldSendEvent, 200 memberships, mock suite; the real App suite also measures zero allocations on the channel and user-scoped paths): 102 to 149 ns and 2 allocations before, 37 to 83 ns and none after. Events flagged as sanitized or sensitive still allocate inside RolesGrantPermission, which is out of scope here. Tests: TestWebConnShouldSendEventDecisions pins 23 decision cases with a denying mock suite; TestWebConnShouldSendEventAllocs asserts zero allocations for six cases and is stable under the race detector; reintroducing either removed allocation fails all six. Release Note: NONE Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Use an admin session for the sensitive-data allocation case The platform test helper's suite grants every permission, so the case passed with a plain user session, but a reader has to know that to see why. Give the session the admin role so the case reads as what it is. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Hold small channel sets in the hub index as slices With EnableWebHubChannelIteration on, each hub keeps, per channel, the set of its connections in that channel as a Go map. Most channels on a hub have only a few connections there: every DM and small group is its own channel. A map costs about 165 bytes even when it holds one connection, so with many small channels the index costs far more than the connections it holds. The set is now a slice while a channel has at most 64 connections on the hub, and a map beyond that. Adding appends; removing scans the slice and moves the last element into the gap; iterating walks the slice from the end, so the hub can still remove the connection it is visiting when its send queue is full. A set that empties is deleted. BenchmarkHubChannelBroadcast with the setting on, 5000 connections in 200 channels each, where h is how many connections on the hub share each channel on average (set with the benchmark's sharing knob); live heap of the index on top of the setting-off baseline, n=6: h before after 2 172 MB 50 MB 3 165 MB 50 MB 5 152 MB 50 MB 10 112 MB 22 MB 50 10 MB -14 MB Live objects drop about 30% and a full collection about 15 to 30%. Delivery time is unchanged within noise. Tests: TestConnSet covers duplicate adds, the switch to a map past 64 connections, and removing members while iterating. Release Note: NONE Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Key the channel membership structures by the decoded channel ID A channel ID is 16 random bytes that model.NewId writes as 26 base32 characters, and the server keeps it as that string everywhere. The websocket membership structures hold one copy per membership: the per-connection cache with the setting off, and the hub index's channel lists and sets with it on. Each copy is a 16-byte string header plus its own 32-byte allocation, and the collector marks every one. model.DecodeId turns an ID back into its 16 bytes and rejects any string NewId could not have produced; the decoder alone would accept four strings per ID, since it ignores the last character's two unused bits. The per-connection cache and the index now key by those bytes, stored inline with no pointers, and keep any ID that does not decode in a string-keyed side map, which stays empty in practice. The hub decodes each event's channel once per broadcast and passes the key to ShouldSendEvent, which now takes it as a parameter; its only callers in this repository are the hub and tests. BenchmarkHubChannelBroadcast, 5000 connections in 200 channels each, live heap once memberships are loaded, where h is how many connections on the hub share each channel on average, n=6: h setting off: before -> after setting on: before -> after 2 136 MB -> 65 MB 186 MB -> 153 MB 5 143 MB -> 72 MB 193 MB -> 160 MB 10 145 MB -> 74 MB 167 MB -> 134 MB 50 146 MB -> 75 MB 132 MB -> 98 MB Live objects with the setting off drop from about 2.1 million to under 100 thousand: each connection's cache is now one pointer-free map. Delivery time is unchanged within noise. Tests: TestDecodeId (the unused-bits string the round-trip check rejects), TestChannelSet (keys, undecodable IDs, the zero key) and TestHubConnIndexChannelsThatDoNotDecode. Release Note: NONE Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Let the hub broadcast benchmark vary how widely channels are shared The benchmark draws each user's 200 channels from a pool of 20000, so each channel is held by about 50 of the 5000 connections. Memory results depend on that number: DMs and small groups are held by one or two connections on a node, large channels by hundreds. MM_BENCH_CHANNEL_SHARING=h sizes the pool so each channel is held by about h connections. Unset, the pool stays at 20000 and the results are unchanged. Release Note: NONE Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Walk small channel sets with a plain loop The linter asked for slices.Backward here. That is a second closure on every channel event, on top of the iterator connSet.all returns, and BenchmarkHubChannelBroadcast counts it: 408 allocations per event instead of 372 with the plain loop, about 1.2 KB more per event. Back to the plain loop, with the reason beside the nolint directive. Release Note: NONE Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Move the cached request context lookup into a helper getRCtx returns the request context built in SetSession, or an empty one when there is no session, so IsMFAAuthenticated reads like getSessionRoles. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * PR feedback: Build the connections inside each TestConnSet subtest Each subtest now makes its own connections, so the subtests are independent and could run in parallel. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * PR feedback: Return an explicit zero value from every DecodeId failure The length check returned the named result, which is zero there, while the two later returns spell out a zero value because Decode may have written part of b before failing. Make the first one explicit too and say why. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * PR feedback: Remove from a small connection set with slices.Delete The slice is walked from the end, so shifting down the elements after the removed one only moves connections that were already visited. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * PR feedback: Drop the side maps for channel IDs that do not decode Every channel ID the server makes decodes: NewId has produced them since the first release, and the store refuses a caller-supplied ID unless the channel is shared, so only a shared-channel invite from a plugin remote could store one that does not. Such an ID is now skipped, with a warning, when a connection's membership loads, and the hub drops a broadcast whose channel ID does not decode, since no connection can be a member of it. Before, those IDs were kept in string-keyed side maps next to the decoded keys. channelSet and channelList are plain decoded keys, ForChannel and has take the key alone, and the decodeChannelID helper lives only in the tests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
4bf16a6a10 |
MM-71424: Make Elasticsearch/OpenSearch TestConfig side-effect-free (#39512)
* MM-71424: Make Elasticsearch/OpenSearch TestConfig side-effect-free TestConfig marked a stopped engine ready and swapped in a new client, skipping Start(): no index templates, no bulk processors, no healthy flag. The watcher then never called Start(), and SyncBulkIndexChannels could hit a nil sync bulk processor in an unrecovered goroutine. It also overwrote the running engine's version and plugins, without the mutex, from a possibly unsaved candidate config. - TestConfig now only builds a client and checks the version. - App.TestElasticsearch wakes the search engine watcher after a successful test on an inactive engine, so it runs a full Start() right away instead of waiting out its backoff. - SyncBulkIndexChannels returns an error instead of panicking when the sync bulk processor is missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FpCfx3XMX5sfS3hGN7Ln1M * MM-71424: Only wake the watcher when the tested connection is the saved one The watcher starts the engine with the saved config, and each wake resets its backoff. Testing an unsaved candidate config (for example a different URL) must not make it retry the saved cluster immediately. Also fold the duplicated watcher wake subtest into a loop over both wake functions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FpCfx3XMX5sfS3hGN7Ln1M * MM-71424: Simplify the TestConfig follow-up - Move the "wake the watcher after a successful test" decision into PlatformService.SearchEngineConnectionTested, next to the watcher, instead of exposing a raw reevaluate signal to the app layer. - Close the probe client in TestConfig. It is now always discarded, and with sniffing on it would leave a discovery goroutine running. - Drop the syncBulkProcessor nil guards: with TestConfig no longer setting ready, ready implies both processors exist. - Restore the original watcher wake subtest and test the new platform method directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FpCfx3XMX5sfS3hGN7Ln1M * MM-71424: Use the caller's context for the TestConfig version probe Lets callers that carry a deadline or cancellation stop the version request. The probe client is still closed with a background context so cleanup runs even when the caller's context is already done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FpCfx3XMX5sfS3hGN7Ln1M * MM-71424: Add watcher lifecycle tests for both search engines Adapted from #37931 as requested in review: - TestTestConfigThenSavingConfigStartsEngine: TestConfig leaves a stopped engine alone; saving the tested config wakes the watcher, which runs the real Start() and creates the sync bulk processor. - TestTestElasticsearchStartsEngineWithoutWaitingForBackoff: the saved cluster comes back while the watcher waits out its backoff, and a successful App.TestElasticsearch starts the engine right away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FpCfx3XMX5sfS3hGN7Ln1M --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
cdf79fef30 | ci: update the test-system-io actions to 18a0baa (#39564) | ||
|
|
f7f54c0968 | Use compass-ui Icon in Button icon slots and fix follow-ups from the Button migration (#39559) | ||
|
|
caaec8cda5 |
Add a button to quote text selected in a message (#39237)
* Add a button to quote text selected in a message Selecting text in a post in the center channel, RHS or Threads view shows a floating "Quote selection" button at the end of the selection. Clicking it appends the selection as a Markdown blockquote to the message box of the same channel or thread and leaves the caret on an empty line below it, in both the plain text and the WYSIWYG editors. Fixes https://github.com/mattermost/mattermost/issues/39236 * Address review feedback on quoting selected text - Determine the selection direction from its boundary points so a left to right selection that ends on an element containing its start isn't treated as right to left. - Keep a quote queued while the message box is busy rewriting the draft and add it once it's done, but still drop it when the message box can't be used. * Add E2E tests for quoting selected text Cover quoting a selection into the channel message box and posting it with a reply, a right to left selection added after an existing draft, quoting from the RHS into the thread reply box, and typing the reply below the quote in the WYSIWYG editor. Text is selected by dragging the mouse like a user does. * Address UX review feedback on quoting selected text - Keep the Markdown formatting of the quoted text (headings, lists, emphasis, code blocks, links, mentions, hashtags) by converting the selected HTML back to Markdown, in both the plain text and WYSIWYG editors. - Place the quote button next to the first selected character instead of the caret, which browsers report at the end of the previous line when a selection starts at the beginning of a wrapped line. - Don't offer to quote when the target message box can't be used: archived channels, DMs with deactivated users, threads whose root was deleted, or without permission to post. - Make the button reachable with Tab and dismissable with Escape, returning the focus where it was, and show it for selections made without the mouse. - Mix the hover color with the background instead of using transparency. * Update the button visibility ref in a layout effect Writing a ref during render isn't allowed, since React can discard a render without rolling the write back. * Use the shared Turndown converter for quotes Convert quoted selections with utils/turndown.ts, which already handles the markup of rendered messages for pasting, instead of a separate converter, so quoting and copy-pasting a message produce the same Markdown. - Add a rule for user mentions to the shared converter so that they keep the username rather than the display name, for pasting too. - Drop the data-language attribute on code blocks. When only part of a code block is selected, its language label is copied along with the selection instead. * Address dev review feedback on quoting selected text - Don't offer to quote burn-on-read posts, which can't be copied either, so that they aren't kept around after they're deleted. - Leave the user's selection as is instead of narrowing it to the message text, so a selection running into the author name, attachments or reactions can still be copied as a whole. Only the message text is quoted. - Support quoting in channel and thread pop-out windows. --------- Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
926a4c8a83 |
Remove Cloud team limit checks on team create and restore (#39525)
The Cloud product limits no longer define a teams limit, so these checks never block anything; they only fail the request when fetching limits fails. Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
9a78a71c28 |
Remove Cypress specs for removed billing flows (#39516)
* Remove Cypress specs for removed billing flows Delete specs for the pricing modal, purchase and payment flows, downgrade feedback, invoice and card details, self-hosted purchase and workspace deletion, and drop the purchase modal cases from the subscriptions spec. Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Remove Cypress billing helpers that are no longer used Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Remove MM-T4122 from subscriptions spec It exercises the removed purchase modal. Co-authored-by: Nick Misasi <nick13misasi@gmail.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
68b173ab79 |
Keep the team sidebar and current team in global products with the product rail (#39552)
* Keep the team sidebar and a selected team in global products With the rail on screen, products that hide the team sidebar (Agents) keep it, and loading one of their pages restores the user's last team, so the rail and its team menu don't disappear on a refresh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Restore a team only where the rail shows, and survive blocked storage Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Leave the product rail out of backstage pages Integrations and Custom Emoji take the whole page with their own way back, so the rail stays off there, and the header and team sidebar behave as they do without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Remember a team picked inside a global product Switching teams on a global product's page selects the team without a team URL, so the team controller never recorded it and a refresh restored the earlier one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Record the last team only where the product rail shows With the flag off or on mobile, switching teams in a global product leaves user_prev_team as before. The blocked-storage test now has a current user, so it reaches the storage write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Avoid an import cycle when recording the last team team_actions importing the product rail selectors closed a cycle through utils/utils, which broke channel_header_title.test.tsx. Check the flag and mobile view directly instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test when the team sidebar shows alongside the product rail Cover global products with and without the rail, the hidden sidebar, the hide button, and backstage pages bringing the sidebar back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e93c098703 |
Plugin API additions for Boards: Create new board modal, Channel Settings tab, tooltips (#39457)
* Plugin API additions for Boards: Create new board modal, Channel Settings tab, tooltips
Ports mattermost/mattermost#39108 from the Boards core branch
(cursor/boards-plugin-core-changes-eade) to master. It applied cleanly.
* Let plugins open the new channel modal for their own channel types
- registerSidebarBrowseOrAddChannelMenuAction takes an optional placement;
'afterCreateChannel' lists the item right below "Create new channel".
- registerChannelTypeOption takes showInNewChannelModal; false keeps the
option out of "Create a new channel".
- new_channel_modal is a published modal. With channelTypePluginId it offers
only that plugin's channel types, without Public and Private Channel, with
the first selected, and takes wording for that kind of channel (title, name
label and placeholder, purpose label and help text, category help text).
Boards uses these for "Create new board".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Let plugin sidebars add their own header buttons
registerRightHandSidebarComponent takes headerControls, rendered first among
the sidebar header's buttons, before expand and close, as the thread header
shows Follow. Boards uses it for Follow on a card.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Expose WithTooltip to plugins on window.Components
So plugin UI can show core's tooltips (Boards uses it for the attribute lock
and the card's header buttons).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Revert "Let plugin sidebars add their own header buttons"
This reverts commit
|
||
|
|
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> |
||
|
|
718b00b337 |
Remove unused Cloud usage limits UI (#39530)
Removes the Cloud usage modal, file limit sticky banner, product switcher limits footer, admin limit-reached effects, the usage-delta hooks and HOC, the teams-limit gating that read usage deltas, the limits branch of the cloud_subscription_changed websocket handler, and their strings. Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
c0b8b4348d |
drip: use errors.AsType for sidebar category store errors (#39553)
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: jesse <jesse@mattermost.com> |
||
|
|
57002e487c |
drip: AppError -> error: login attempts (#39539)
Migrate checkUserLoginAttempts to return error so a nil result cannot become a non-nil error interface at the caller. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: jesse <jesse@mattermost.com> |
||
|
|
19cd4917f6 |
Remove unused hosted customer state (#39520)
Drop the hostedCustomer reducer, action types, state type, unused cloud action types, the hosted customer signup websocket event and the unused Client4 hosted customer route. Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
540a5d7b5c |
Add post priority and acknowledgements to bulk import and export (#39551)
* Add post priority and acknowledgements to bulk import data and validation Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Import post priority and acknowledgements Co-authored-by: nick.misasi <nick.misasi@mattermost.com> * Export post priority and acknowledgements Co-authored-by: nick.misasi <nick.misasi@mattermost.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
49a923f5c2 |
Remove orphaned billing and purchase components (#39522)
Delete Cloud billing/purchase components, hooks, selectors, utils, modal identifiers, assets and i18n strings that are no longer referenced. Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
1cf0caf2d9 |
Remove unused payment announcement bar (#39521)
Drop the Cloud payment-failed / card-expired banner, which links to a console page that no longer exists, along with its helpers, strings and the unused announcement bar placeholders. Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
3525e8faa8 |
[MM-70033] Replace property permission mechanisms with a typed permissions model (#39081)
* Replace property permission mechanisms with a typed permissions model Convert every PSAv2 property field to a permissions object at startup and on every create, and remove the legacy fallback, so the permissions object is the only rule. The property hook covers every PSAv2 and v3 group, the access control group moves to version 3, the sync lock is restored as a grant, and the legacy permission columns are still written so the change can be rolled back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> * Require a system admin to submit permissions on an access_control create The access_control group's hook pins field.write and option.write to sysadmin on every create, but a submitted permissions object is stored as sent and decides alone, so it slipped past that pin. Only a system administrator may now submit one there; anyone else gets a 403, and can still create the field without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Convert fields missing a permissions object on every startup The backfill ran once, behind a System key. A release that predates the permissions object writes fields without one, from an old node during a rolling upgrade or while rolled back, and the hook refuses such a field to everyone, so those fields could never be read, written or deleted again. The backfill now runs on every startup. It only writes fields still missing an object, so a startup with nothing to convert writes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Report a linked field masked when its template can't be read effectiveAccessModeUsing fell back to a linked field's own access mode when the template read failed. A linked field never carries masking of its own, so that mode is usually public, and the callers that strip attribute values or shield fields from a channel admin then let the field through. A failed or missing template read now reports shared_only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cover plugin access to builtin groups outside access_control The conversion now gives fields outside access_control a wildcard plugin grant, so a plugin keeps the access it had before the hook covered every v2 group. Add a test that a plugin can still read a boards field's options and write a value, and update the tests and the comment that expected no grant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fit the lower PRs' newer tests to the cutover Two tests added below this PR after it was cut ran here for the first time. The masking fixtures updated with the rows a create or update returned, which still hold the legacy columns as submitted, so the cutover read the update as a column change and kept the stored permissions; they now re-read the stored rows. A member's create without a permissions object now gets one converted from the default levels rather than none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Skip and log a field the permissions backfill can't convert Any error converting a field stopped the backfill, and with it the server, since app migrations are fatal. A template switched to shared_only over member-writable linked fields, a soft-deleted or missing template, or a concurrent write could each leave a deployment unable to start. A field that fails is now logged by ID and left without a permissions object, which the hook already treats as refusing everyone, and the run carries on. A group batch that fails is retried one field at a time, so one bad field doesn't hold back the others. The backfill runs on every startup, so a skipped field is retried on the next one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Return a field's legacy columns as stored from create and update The store writes the Protected and Permission* columns from the permissions object but returned the field with the columns as submitted. The hook defaults those columns before the save, so a caller's own stricter object could come back next to a looser column: a CPA field created with value.write none returned PermissionValues member. A plugin that renamed that copy sent the stale column back, which an update reads as a deliberate legacy edit, and value.write became member. Create and Update now return the columns as stored, the same view a read gives, so sending a returned field back changes only what the caller changed. The masking fixtures no longer need to re-read the rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Expect the stored legacy column from create and update in two tests Two tests read PermissionValues off the field a create or update returned and expected the column as submitted. Those now return it as stored: an unset column on an unmanaged group's field is stored as value.write none, and an owner-managed field's value.write is none, left to the owner's grant, whatever the hook pinned the column to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Correct two doc comments in the permissions backfill tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Refuse an update that changes both permissions and a legacy permission key Translating a legacy column or attr change rebuilds the permissions object from the stored one, so a changed object submitted in the same update was silently dropped, keeping any grant or wider tier it had just removed. Neither shape says which of the two should win, so the update is now a 400. An echoed object, or none, translates as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cover an unowned field's v2 read having no owners attr Clients lock any field whose attrs.owners is non-empty, so a field an admin creates must reach users without one, through both the CPA and the generic endpoints, and users must still be able to set their own value. Also drops the plugin wildcard from two comments that described it as projected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Write the permissions column in the masking e2e DB helpers The server now decides a field's access from its permissions column alone, so setting access_mode and protected in attrs no longer masked anything and every spec that relied on it saw plain values. The helpers now write the permissions object the conversion would produce for each mode, and keep the grants index in step, alongside the legacy keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fit two owner tests to the unprojected plugin wildcard Removing a field's last owner now reads as no owners, with the plugin wildcard kept as a stored grant. The max-owners echo test relied on the wildcard as the synthetic owner past the limit, so it uses a sync lock's grant instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Follow the model's ApplySyncLock and attr-protected projection ApplySyncLock now takes the field it locks rather than its sync source, since the grant it gives the sync depends on the field's owners, protection and link. And ProjectLegacyPermissions now reports an attr-protected access_control field with the Protected column unset and the protection in the attr, as master stores it. The link step read only the column, so a linked field of such a template would no longer copy its protection and access mode for an older release to read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Leave the template's sync grant off its linked fields A synced template now carries a grant for its sync over its options, which the sync provisions there. A linked field copied every template grant but option.read, so a channel field linked to a synced template took a sync grant over option.write. The conversion of the same field from its own attrs gives it none, and the v2 projection reported it as an owner, which locks the field to people in the webapp. The link step now leaves the template's sync grant out. A linked user field still gets the sync's grant from the sync lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fit the bulk value delete tests to the field and value write check Deleting all of a field's values now needs field.write and value.write, and fails closed on a field with no permissions object. Five tests assumed the old check: a human caller always passed, and a plugin needed value.write alone. They now delete as a caller holding both, or expect the refusal, and create the linked fixture directly so its template's grants don't gate the link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fit tests to the conversion's owner, source plugin and sync grants The model now converts a user or role owner to no grant, gives the source plugin a grant only on a protected field (and only its reads once the field has owners), leaves derived grants out of the projected owners, and gives the sync the reads and option writes master gives it. Tests that assumed the old grants now check the new ones: - the sync's grant on a linked user field holds every action but option.read; - a user owner added through a v2 update stays in the v2 view without a grant; - a field with the most owners allowed projects no synthetic owner, so an unchanged echo needs no allowance for one; - the linked-field backfill test uses a plugin owner, which still converts to a grant; - an unprotected field a plugin creates carries only the plugin wildcard; - adding an owner to an owned, protected field is done by its listed owner, and the source plugin keeps its reads; - the v2 view test reads the legacy access_mode attr, since GetAccessMode now reports the mode from the permissions object. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fit master's newer sync and bot attribute tests to the cutover Three tests master added since the last rebase assumed access was decided from attrs and the legacy hook: - the linked sync attrs test seeded its template with no permissions object, which the link step now refuses; it seeds a converted one, as the tests around it do; - the syncer's broadcast test restricted its field through the access_mode attr alone; it restricts the permissions object too; - the bot attribute lock test expected the hook's protected and sync lock errors, but the API's value.write check now refuses first, as for every other locked field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Convert a linked field's own grants on create A linked field created without a permissions object took its template's grants wholesale and never converted its own settings. A field with owners its template lacked was then writable by any plugin, and showed no owners in the v2 view, where master lets only those owners and the sync write it. A field without owners on a template that had them could be written only by the template's owner, where master lets any plugin write it. The restrictions still come from the template, as master copied the template's permission levels. The grants are now converted from the field's own attrs, as the startup backfill converts a linked field, so create and backfill give the same grants for the same row. The conversion already gives a synced user field the sync's grant and takes value.write off every other grant, so the sync lock applied afterwards is dropped: on an owned synced field it also took value.write from the owners, which master and the backfill let write. A template's explicit grants, such as a role grant authored through v3, no longer reach a linked field created without a permissions object. Grants are per field; a caller that wants them sends its own object. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Let the sync follow a late sync link on a linked user field's values A user field linked to its template before the template got its sync link carries no copy of the link, so it has neither the sync lock nor the sync's grant, and the hook refused the sync its values. Master leaves such a field open to every machine caller, the sync included, and its syncer follows the template's link for it. The sync is now let at such a field's values, reads and writes, when the template is synced from its own source and the field's catch-all plugin grant allows the action, which is where master lets any machine caller in. An owned or protected field has no such grant for a value write, and only a user field takes a sync link, so the sync is still refused wherever master refuses it, and no other writer is locked out. The reads are needed too: the syncer compares what it reads with what it last wrote, and a hidden value never gets cleared. isDefinitionSync moves to the property service so both hooks share the template lookup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tell a sync its template was unlinked before judging its grants When an admin unlinks a template from the sync while a sync run is in progress, the sync's next option change on that template has to fail with the 409 sync_source_unlinked, which tells the syncer to keep the user's stored value and stop syncing the field. Unlinking also takes the sync's option grant off the template, and the access control hook runs before the validation hook that raises the 409, so the sync got a 403 instead. The syncer treats that as an ordinary failure and clears the stored value. The access control hook now gives a sync changing a field's options the validation hook's answers first, in the same order: its own field goes on to the grants, a field synced from another source gets the 403, and a field synced from nothing gets the 409. Only the options endpoints answer this way, as on master, which never refuses a value write or a field update with the 409. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Narrow a linked field's restrictions by its own locks on create A linked field created without a permissions object took its restrictions wholesale from its template. A plugin that created a protected linked field on an unprotected template left sysadmins able to edit and delete it, and one that also set source_only left its values readable by anyone, where master lets only the source plugin do either. The template's restrictions are now narrowed by the field's own protection and access mode, never widened: a protected field takes the converted field.write (and in access_control option.write), and a non-public access mode takes the converted reads where they are narrower. Create and backfill now give the same restrictions for these rows. A shared_only mode the field sets for itself still can't be expressed, since a linked field carries no masking of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read a late-linked field's template once per hook call Letting the sync at a late-linked user field's values reads the field's template from the master database to see whether it is synced from the caller's source, and the read and write paths asked once per value. A sync run reads every user's values on each field it maps, so that was one master read per user per field. The answer is now kept in the call's masking context, per template and sync source, so one call reads each template once however many values it judges. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Allow unlinking a field whose template does not exist Unlinking reads the stored field's template to work out the legacy view a caller was shown, both in the access control hook and when reconverting legacy permission keys. When the template row is gone entirely, those reads failed with its not-found error, which the API reports as a 500, so the holdings guard's allowance for a missing template was never reached. The read path already projects such a field with no template, so these reads now do the same for a template that does not exist. Any other read error still fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Describe the legacy permission update error strings for translators Every en.json entry carries a description for translators, and the two errors refusing a legacy-shaped update that would lose permission settings had none. They now say when each is shown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Convert system-owned fields on every page of a group ConvertSystemOwnedFields read a single page of the group's fields, so in a group with more fields than one page holds, a system-owned field past the first page was left with no permissions object and without its subsystem's grant. It now pages through the whole group with a cursor, the way the permissions backfill does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name the Local-session denial cases after their unconverted fixtures These cases deny a Local session only because their field is left with no permissions object, a state the backfill does not leave behind. Once a field is converted, a Local session may edit it, protected or not. The names now say the field is unconverted, so they no longer read as saying a Local session is refused on such a field in general. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fit two tests to the field.write floor under the cutover The access_control create test submitted a policy at field.write none as a system admin and expected it stored as sent. The floor now refuses that through the API, so the test checks the refusal and then submits the same policy at field.write sysadmin. The lockout test that keeps a partial restrictions object's meaning for callers outside the HTTP API used an uninstalled plugin, which the cutover's enforcement hook refuses. The LDAP sync, granted field.write, stands in for it, since it too reaches the app layer without coming through the handlers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Judge a permissions patch's lockout checks by its effect The patch handler gates a permissions object on what it changes, so an unchanged echo riding along on an options patch is judged on option.write alone. The floor and the anti-lockout check were keyed on the key being present, so that same echo was refused: with a 403 when the caller holds option.write but not field.write, and with a 400 when the field is one a plugin keeps at field.write none. Both now run only when the permissions actually change, which takes nothing away from either check, since an unchanged object can lock out nobody new. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read null grants and the served [] as the same when comparing permissions A field created without grants stores them as null, and the API serves them as [], so every caller sees the same shape. A client that sends the served permissions back alongside an options change then looked, to both the patch handler's echo check and the enforcement hook's options-only comparison, as if it had changed the grants. A caller who holds option.write but not field.write got a 403 on such a field, which is common: linked fields and fields created without grants. Both layers now compare with one helper, model.PermissionsEqual, which is reflect.DeepEqual except that a nil and an empty list are the same list in grants, a grant's scopes and allow, and masking's except. Those are the lists the wire cannot keep apart, and they mean the same thing wherever a policy is read. Comparing this way rather than normalizing the stored object at one point also covers fields already stored with null grants. Any other difference still counts, masking absent against masking {} included, so a real grant change is still held to field.write. The handler's resolution of the permissions patch, including the drop of a linked field's served masking: {}, moves into a small helper so the test that checks the handler and the hook agree runs the same code, and gains cases for a null grants echo, an added grant and a linked field's served permissions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bump the managed category group before converting its fields An install that still carries the legacy "true" marker has its managed category group at version 1, and its field has no permissions object. The setup registered the group and converted the field before bumping the version, so the converted field failed the group version check and server startup aborted, and the service kept the version 1 group cached. Bump the version and save the marker first, as before, then register the group so the cache holds the bumped row, then convert. A conversion that fails after the marker is saved is retried on the next boot without a second bump. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Leave fields in unversioned property groups out of the permissions backfill A group that existed on 11.7 is still at version 1 after the upgrade, but a plugin could have put fields with an ObjectType in it. The backfill converted those, the write failed the group version check, and every startup logged them as unconverted fields that stay inaccessible. They don't: the access control hook and the API only enforce version 2 and 3 groups, so these fields behave as they did before. Skip them instead. ConvertSystemOwnedFields now skips a version 1 group too. Without that, a managed category group left at version 1 with no setup flag, by a server that stopped between creating the field and saving the flag, failed startup; now that boot saves the flag and the next one bumps the group and converts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Reconvert a field's permissions when its OpenID Connect sync link changes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cover the OpenID Connect sync's service grant on value writes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name every sync source in a comment and an error description Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test that local mode can't write sync- or owner-locked values Check local mode against LDAP- and SAML-synced and owner-managed CPA fields through the production ladder, where the conversion gives them a value.write tier of none, and restore the empty and local-mode callers to the OpenID sync case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Describe local mode's value-write rule by the field's locks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Report a linked field masked in the v2 view when its template can't be read A linked field never carries masking of its own, so falling back to its own access mode told a v2 reader a masked linked field was public. Report shared_only instead, as the v3 masking presence and effectiveAccessModeUsing already do; a field whose own tiers say source_only keeps that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tag the plugin as caller when a plugin creates a channel PluginAPI.CreateChannel passed the plugin API's untagged context, so the access control hook refused the managed category write and the channel came back without its category. Add a test helper that builds the plugin API on the context a real plugin gets, and use it in the managed category test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tag local-mode sessions as the local admin on post and field reads Post property value hydration and the access control field autocomplete tagged the session's UserId, which is empty for a local-mode session, so local mode got posts without property values and fields without options. Tag such a session as the local admin, as the other property routes do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Warn when property access control refuses a call with no caller ID The access control hook logs a warning, once per hook call, naming the operation and the group and field IDs, when a call on an enforced group carries no caller ID and has a write refused or values hidden. A field read that only hides options is not logged. App tests install an observer on the hook that sees every such call, field reads included, whatever logger the context carries, and fail unless they opt out with AllowUntaggedPropertyCaller, so an internal call that forgets to tag its caller shows up in a test instead of failing silently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tag the caller on channel creation, policy masking and redaction reads Channel creation's attribute field lookup, the policy simulation's CPA redaction, the field autocomplete's linked-template reads, and the policy masking calls ran untagged or with a local-mode session's empty UserId, so the access control hook hid options from them. Tag the session user, or the local admin for local mode. The plugin API's policy read, policy save and access evaluation passed the plugin API's untagged context; tag the plugin. Expose the hook's no-caller observer on Server so api4 tests can check the calls they make are tagged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Report a refused linked-field create with no caller ID A linked field create that its template refuses returns an AppError rather than ErrAccessDenied, so the hook's deferred report never saw it, and a caller that named nobody was refused without a warning or a call to the test observer. Report it where the refusal is made. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Withhold broadcast values that not everyone can read Websocket broadcasts carried a property value, an option list or an attribute whenever the field's access mode said public, which a read tier between everyone and none still reports, and which a linked field reports for itself whatever its template's masking. Add model.PropertyFieldReadableByEveryone: a permissions object is present, nothing masks the field (the template's masking, for a linked field), and the read tier is everyone. Value broadcasts, option stripping on field broadcasts, the channel admin redaction set and the members sidebar attribute filter all decide on it. The redaction set and the sidebar filter show option names for a field with options, so for those fields they require both value.read and option.read to be readable by everyone. Each path now gates only on the tier of what it exposes: value.read for values, option.read for options, and both for option-type fields on the redaction set and the sidebar filter. A none tier on the other action no longer withholds, which matches the hook's HTTP read; backfilled fields always carry equal read tiers, so none of them is affected. model.BroadcastValue keeps its signature but now withholds more: it withholds the value of any field with no permissions object, so for fields of plugin-registered v1 groups it always withholds, and it withholds a linked field's value, having no template to read. The core callers use the new model.BroadcastValueWithTemplate through App.PropertyValueBroadcaster, which reads each template once per broadcast and withholds on a failed read; the property syncer uses the template it already loaded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fall back to the single linked user field for editor masking When a channel field's template names no mask_by_field_id, policy masking now takes the caller's holdings from the one user field linked to the same template, as the editor did before mask_by_field_id existed. Two or more such fields fail the reference closed with ambiguous_sibling, as before. A declared mask_by_field_id still wins. The property read hook is unchanged and keeps resolving holdings from mask_by_field_id only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Backfill only fields with no permissions object The permissions backfill runs on every startup and paged through every property field to find the ones it converts. Add an internal store search, GetFieldsMissingPermissions, that returns only live PSAv2 fields in version 2 and 3 groups whose Permissions column is NULL, paged by (CreateAt, Id), and use it in MigrateBackfillPropertyPermissions. A startup with nothing to convert now reads one empty page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Skip a backfilled field that changed since the scan The backfill converted the copy of each field its page read, but updatePropertyFields took the expected UpdateAt from its own re-read, so a legacy edit from an old node landing in between passed the check and was overwritten. Add updatePropertyFieldsIfUnchanged, which takes the UpdateAt each field was read at and returns store.ErrConflict if the row has moved on, and use it in the backfill with the UpdateAts its page read. A field that conflicts is logged, counted as skipped and left unconverted for the next startup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Report the stricter of a linked field's own and template access mode EffectiveAccessMode returned a linked field's template mode alone, so a master shared_only field linked to an unmasked template, which the conversion narrows to none reads instead of masking, reported public. Policy masking then showed every literal and the v2 view reported the field public. A linked field now reports the stricter of its own mode and its template's, and a linked field whose template can't be read keeps its own source_only rather than dropping to shared_only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Apply golangci-lint v2.15.0 modernize fixes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the managed category group at V2 instead of bumping it The managed category setup now registers the group, pins it at V2 with ensureManagedCategoryGroupVersion, and only then converts its fields, on every startup whether or not the setup flag is saved. A converted field doesn't validate against a version 1 group, and the pin re-caches the group that registering has just cached. This replaces the increment that ran only for the legacy marker, which the store no longer offers, and also repairs a group that drifted past V2. An install that stopped between creating the field and saving the flag now finishes in one boot and saves the current flag, rather than saving the legacy marker and converting on the next boot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Describe the managed category group as pinned, not bumped Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
18b992d329 |
Make WebConn.ShouldSendEvent allocation-free with an immutable session bundle (#38992)
* Make WebConn.ShouldSendEvent allocation-free with an immutable session bundle WebConn.ShouldSendEvent runs once per connection for every event the hub delivers. With channel-scoped iteration off (the default) that is every connection on the node for every channel-scoped event. Each call allocated 336 bytes in two places unrelated to the decision itself: - IsMFAAuthenticated built a fresh request.Context from the session on every call, and the clone escaped into the MFARequired interface call (320 bytes). - The session's roles string was split with strings.Fields on every call, whether or not a permission check followed (16 bytes). At 15k connections per node that is about 5 MB of garbage per channel-scoped event. Store the session as an immutable bundle behind the existing atomic pointer: a deep copy of the session, its roles parsed once, and a request context built once. SetSession builds the bundle in one store; GetSession keeps its signature. The hot path reads the bundle and passes the cached context to MFARequired. The cached context is shared read-only by the hub and read-pump goroutines; comments on the bundle, the session getter, and the roles accessor say so. The trade is about 320 bytes resident per connection. No decision changes. Measured end to end with BenchmarkHubChannelBroadcast (one posted event through the production hubs, one per CPU, to 5000 connections with 200 memberships each; benchstat, n=10, interleaved), against master: iteration members time per event p99 latency allocs per event off 50 780us -> 717us 2.32ms -> 798us 10349 -> 348 off 5000 1.11ms -> 1.05ms 3.20ms -> 1.19ms 10349 -> 348 on 50 37.2us -> 34.2us 60us -> 53us 472 -> 372 on 5000 1.12ms -> 1.05ms 2.70ms -> 1.20ms 10372 -> 372 on, churn 50 unchanged unchanged 894 -> 802 Bytes per event drop from 1.6 MB to 19 KB. With the work spread over one hub per CPU the mean time gains 6 to 8%; the tail gains most, p99 down 56 to 66% where every connection is checked, because the collector no longer has 1.6 MB of garbage per event to chase. The allocations left are the per-hub event preparation (29 per hub). The bundle adds about 400 bytes per connection (live heap +1.4 to 1.5%, 1.9 MiB at 5000 connections). The churn case is bound by membership reloads on the hub goroutine, which this change does not touch. Per call (BenchmarkShouldSendEvent, 200 memberships, mock suite; the real App suite also measures zero allocations on the channel and user-scoped paths): 102 to 149 ns and 2 allocations before, 37 to 83 ns and none after. Events flagged as sanitized or sensitive still allocate inside RolesGrantPermission, which is out of scope here. Tests: TestWebConnShouldSendEventDecisions pins 23 decision cases with a denying mock suite; TestWebConnShouldSendEventAllocs asserts zero allocations for six cases and is stable under the race detector; reintroducing either removed allocation fails all six. Release Note: NONE Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Use an admin session for the sensitive-data allocation case The platform test helper's suite grants every permission, so the case passed with a plain user session, but a reader has to know that to see why. Give the session the admin role so the case reads as what it is. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Move the cached request context lookup into a helper getRCtx returns the request context built in SetSession, or an empty one when there is no session, so IsMFAAuthenticated reads like getSessionRoles. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
0cfb204f04 |
Bump github.com/andybalholm/brotli to v1.2.7 (#39543)
Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
0eb2eb7ebc | drip: AppError -> error: login attempts (#39539) | ||
|
|
ea90a1c2d5 |
docs: add Ubuntu 26.04 LTS to supported APT install targets (#39498)
Mattermost .deb packages are now published to the `resolute` suite of the APT repository. List Ubuntu 26.04 LTS alongside 22.04 and 24.04 on the Ubuntu/Debian deployment pages. Scope is host APT installs only; the Docker image base is unchanged. |
||
|
|
7823260826 |
Let plugin shared channel receive APIs run on any cluster node (#39504)
The shared channel service is only active on the cluster leader, but plugins run on every node. ReceiveSharedChannelSyncMsg, ReceiveSharedChannelAttachmentSyncMsg and ReceiveSharedChannelProfileImageSyncMsg required an active service, so a plugin on a follower node got a service_inactive error and could not ingest inbound data. These APIs now only require the service to exist, the same as the HTTP remote cluster receive path, which has no leader check. Received content is written to the database on the receiving node. Posts, reactions and acknowledgements reach other remotes through cluster websocket events handled by the leader. Sync tasks queued as a side effect on a follower (membership changes, statuses) wait in that node's inactive queue. Outbound APIs such as SyncSharedChannel stay leader-only. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e22bcdd331 |
fix(e2e): wait for a search-engine answer in the Elasticsearch/OpenSearch search specs (#39445)
postAndFindMessage polled one search and asserted on a second. The server answers from the database whenever the engine errors (a just-purged index has no create_at mapping for a few hundred ms), so the poll could be satisfied by the fallback while the next search, answered by Elasticsearch, returned nothing. Engine answers carry highlights in `matches`; database answers don't. With fromSearchEngine the poll now waits for an engine answer containing the post, and the assertions run on that same response. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: yasser.khan <yasser.khan@mattermost.com> Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
fcafb9b89c |
fix(e2e): repair deletion-report.spec.ts on master (#39376)
* fix(e2e): stop the deletion report and removal notice racing on reviewer threads Removing a flagged post posted the deletion report and the "removed by" notice from two goroutines at once. Both are often the first replies on a reviewer's thread, and two first replies saved concurrently both try to insert the Threads row: one fails on threads_pkey and its post is dropped. That made deletion-report.spec.ts flaky on master, and reviewers could silently miss either message. The removal notice now waits for the deletion report to be posted. Co-authored-by: yasser.khan <yasser.khan@mattermost.com> * Fix shadowed variable lint in deletion report test Co-authored-by: mattermost-code <matty-code@mattermost.com> * fix(app): bound deletion-report delivery in PermanentDeleteFlaggedPost The removal notification waits for the deletion report to be delivered first, but report delivery uploads a file through a filestore write that takes no context. A blocked write never closed deletionReportSent, leaking the notification goroutine and dropping the notice. Give delivery a one-minute context deadline, and close the signal on timeout even if the write ignores the context, logging the timeout. Co-authored-by: yasser khan <attitude3cena.yf@gmail.com> * fix(app): do not post the deletion report after its context is done A file write that resumes after the delivery deadline would otherwise still reach CreatePost in postReviewerMessage, racing the removal notification that the timeout released. Abort the reviewer post when the request context is done before starting the save. Co-authored-by: yasser khan <attitude3cena.yf@gmail.com> * basic improvement * restored accidently altered files --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: mattermost-code <matty-code@mattermost.com> Co-authored-by: yasser khan <attitude3cena.yf@gmail.com> Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com> |
||
|
|
b9fd23e542 | MM-70703: Align file attachment permission checks (#39413) | ||
|
|
8a53d645de |
MM-71401: Rebuild the scrambled authentication settings tables (#39482)
* MM-71401: Rebuild the email domain restriction settings table Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the MFA settings tables The Enforce table's System Console path was written as Markdown bold inside HTML, so it rendered with literal asterisks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the AD/LDAP guest filter and name attribute tables Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the SAML enable and guest attribute tables Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the Google OAuth 2.0 settings tables Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the GitLab OpenID preferred username table Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the Google OpenID settings tables The discovery endpoint table called the setting a string input with Google's URL as its default. Its config default is empty; the System Console fills in Google's URL in a read-only field. A blockquote above the Enable table repeated its true and false values, and is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Remove the Google OpenID preferred username setting GoogleSettings.UsePreferredUsername exists only because GoogleSettings shares the SSOSettings struct with GitLab and OpenID. The System Console offers the setting for GitLab, Entra ID and other OpenID providers, but not Google, and the Google provider ignores it: GetUserFromJSON discards the SSO settings and always derives the username from the email address. Nothing links to the section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the Entra ID OpenID settings tables The discovery endpoint table gave the common endpoint as the default. The System Console builds the endpoint from the Directory (tenant) ID in a read-only field, and only falls back to the common endpoint when no directory ID is set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the other OpenID Connect provider settings tables The preferred username table was already two columns but kept stray grid characters in its config cell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2515ee2b33 |
MM-71401: Rebuild the scrambled site settings tables (#39483)
* MM-71401: Rebuild the custom brand text and minimum desktop version tables Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the max users per team table The table said the limit counts activated and deactivated users. It counts active team members only: GetActiveMemberCount skips users removed from the team and deactivated users. The wording follows the MM-71123 docs update held for its release, minus the parts that only apply once that ships, so that update reduces to its own changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the threaded discussions table Name the two "Enabled" options as the System Console does, say which ones users can change, and give each option's config.json value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the message link previews table Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the LaTeX rendering tables EnableInlineLatex defaults to true, as it has since it was added, not false. The web and desktop apps render inline LaTeX only when EnableLatex is also true, and EnableLatex defaults to false, so inline LaTeX is off by default anyway. The old text implied the reverse dependency. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the public file links table Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Rebuild the admin and end user notices tables Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * MM-71401: Qualify the EnableLatex default by deployment age Config.SetDefaults gives EnableLatex true when the saved config already has a SiteURL but no EnableLatex, as a config from before the setting existed does, and false otherwise. Label the defaults the way the session revocation setting labels its own isUpdate-dependent default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cf7496d135 |
Fix the secure file preview and PDF link navigation settings tables (#39475)
* Fix the secure file preview and PDF link navigation settings tables The "Enable secure file preview on mobile" table came out of the RST conversion as a single scrambled cell, with the config path, config.json key and environment variable spliced into the description. Rebuild it as the usual two-column table on both pages that document it. Its config details, and those of "Allow PDF link navigation on mobile", were also wrong. Both settings have lived in NativeAppSettings on the Environment > Mobile Security page since they were added in v10.11, not in FileSettings under Site Configuration > File sharing and downloads, and MobileAllowPdfLinkNavigation defaults to false, not true. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Point the Site Configuration copies to Mobile Security Both settings live under Environment > Mobile Security, yet the Site Configuration page documented them in full under a section that says its settings are in Site Configuration > File Sharing and Downloads. Replace the duplicated tables there with a pointer to the Mobile security entries, and keep the headings so existing links to these anchors still land on the page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5e9feb818b |
Mark mobile file upload and download settings as Enterprise (#39473)
The System Console shows "Allow file uploads on mobile" and "Allow file downloads on mobile" only when the license includes the Compliance feature, and the server only sends their client config values under that license. Add the Enterprise and Enterprise Advanced plan badge to both settings, as "Enable secure file preview on mobile" already has for its plan. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1bd58a1746 |
Add an experimental product rail with Home, Channels and Boards views (#39383)
* Add a product rail beside the team sidebar A Slack-style navigation rail, behind the ProductRail feature flag, for switching between Channels, Threads and registered products. The channel sidebar drops its Threads link and the product switcher drops the items the rail now shows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the product rail into Home, Channels and Boards, and fold in the team sidebar Builds on the product rail: - Home shows the whole channel sidebar; Channels and Boards narrow it to channels (and DMs) or to boards, the channels a plugin registers with registerChannelKind. Categories left empty by the split are hidden. - Each rail item returns to where the user last was in it, per team and across reloads: the last channel, board, thread or product page. - The team sidebar can be hidden from its bottom; the current team then tops the rail as a stack that opens a team menu. - Threads takes the place of the channel sidebar, in its colors. - Docs is shown as a placeholder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name the site instead of the product in the header while the product rail is on The rail already shows which product is open, so the product menu button shows the site name and a chevron instead of "Channels". The menu holds site-wide items, and the label keeps the button easy to hit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop the chevron after the site name in the header Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Center the site name on the grid icon in the header Metropolis sits its capitals about 1px above the middle of its line box, so the site name read high next to the grid icon. Nudge it down to line them up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add the ProductRail flag to the generated defaults and support packet golden Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read board channel kinds without depending on the Boards plugin API The Home/Boards split relies on plugins registering board channels with registerChannelKind, which comes with the Boards work and isn't on master yet. Read those registrations directly instead: until the API lands there are none, and the rail shows Channels, Threads and products without the split; once it lands, Home and Boards appear with no further change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Address review: menu fallback, saved paths, and dragging in filtered sections - Keep Channels and the products in the product menu when the rail can't show for want of a current team. - Accept only a plain object of team-to-path strings from localStorage. - While Channels or Boards hides categories, number the shown ones without gaps for dragging, and translate category and channel drop indices from the shown list to the full one. The hidden categories come from one selector the list and each category share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep no-op drops in place, and the unread filter in the rail's section - A drop at the item's own visible index keeps its place in the full list instead of moving it past hidden neighbours. - With the unread filter on, the current channel is added only when it belongs in the product rail's current section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop Home and the rail dividers; let the filter menu show boards in Channels - The rail lists Channels, Threads, Boards, Docs and products, with no Home view and no dividers. - While the rail splits boards from channels, the filter button above the channel list opens a menu: Unreads only, and in Channels, Show boards (a sidebar_settings preference). With boards shown, Channels lists them too, stays put when a board is opened, and counts their mentions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Even out the space around the team stack at the top of the rail With the divider gone, give the team stack 12px above and below, rather than 8px above and 16px below. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Let the rail sit on the header background, with more room below the team The rail no longer has a background of its own, so it reads as one with the team sidebar; badges ring themselves in the header colour. The team stack gets 24px below it, clearing the Channels badge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Join the rail to the main panel beside the team sidebar; fix thread tags - With the team sidebar showing, the rail takes the sidebar background and the panel's outer border and corners, with a divider before the channel list, so the two read as one panel. - In Threads, remap --semantic-color-general too, so the channel tags in the thread list take the sidebar's text colour instead of the light theme's dark grey. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Square the thread list's left corners in Threads With the rail on, the thread list takes the channel sidebar's place against the panel's left edge, so its corners there are square, like the sidebar's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Round the thread pane, and restyle the active thread-list tab - Beside the thread list, round the thread pane like the center channel beside the channel sidebar, with the sidebar colour behind its corners. - Give the active thread-list tab the sidebar's colours, so it reads on the list's background instead of as blue on blue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Turn the rail off on mobile; use Compass checkboxes in the filter menu - On mobile the rail is hidden, so treat it as off there: no Channels or Boards split, the team sidebar shows whatever the preference, and the product menu keeps Channels and the products. - The filter menu's checkboxes are Compass Checkboxes, shown for the item's state only (out of the tab order and accessibility tree, and not taking clicks), instead of grey icon glyphs that read as disabled. - Lighten the active thread-list tab's background to 8%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Lead the rail with Threads; widen and mark the thread list - Threads is the first item in the rail, ahead of Channels. - Beside the rail, the thread list is a fixed 368px wide, and the open thread is marked with the sidebar text colour at 10%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tighten the space below the team stack to 8px Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Let Show boards only toggle what Channels lists; plain unread toggle in Boards - Turning Show boards off while a board is open no longer moves the rail to Boards (and so drops the option from the menu): the setting only changes what Channels lists. A channel or board opened still moves the rail to its section. - In Boards, the filter button is the one-click unread toggle again; the menu, with Show boards, is only in Channels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the default products at the top of the rail, others at the bottom The rail leads with Threads, Channels, Boards, Agents and Playbooks; any other product a plugin registers is pinned to the bottom. Drop the Docs placeholder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Reset the product rail's state on logout Logging out doesn't reload the page, so the next user to log in on it would inherit the previous user's last paths and save them as their own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tie the rail's side effects to the rail being on screen The header's site name, the hidden Threads link and the rail's layout CSS now apply only when the rail is shown (a team, desktop width), and adding a channel to a category no longer translates its index as if it were a drop into a filtered list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hide the team sidebar's hide button where the rail isn't shown On mobile the button could save the preference, which then hid the team sidebar on returning to desktop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bfc9583942 |
drip: use errors.AsType for OAuth store errors (#39479)
Rewrite errors.As switches in app/oauth.go to errors.AsType where each target stays inside its own branch, and cover those branches with a focused test. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: jesse <jesse@mattermost.com> |
||
|
|
76c56ef772 | Replace @deanwhillier/jest-matchmedia-mock with a local matchMedia mock (#39496) | ||
|
|
25f5f13a63 |
Bump github.com/mattermost/logr/v2 to v2.0.25 (#39486)
v2.0.25 tags the commit that the server already pins as a pseudo-version. |
||
|
|
0629253243 |
[MM-71391] Read server logs in blocks and stream log downloads (#39477)
* [MM-71390] Tolerate failed nodes in plain-text logs and fix log viewer filters 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. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FpCfx5vEzDnjuW2q3TTD5t * [MM-71391] Read server logs in blocks and stream log downloads GetLogsSkipSend scanned mattermost.log backwards one byte per ReadAt, so a default request cost millions of syscalls per node, and a selective filter read to the start of the file. It now reads 64 KiB blocks from the end and splits lines from the buffer, returning the same lines as before. With date_from set, the scan stops after 100 consecutive entries older than it, so a single out-of-order entry does not cut it short. A line decoding to JSON null is now kept as unparseable instead of panicking under a filter. downloadLogs streams the open log file through WriteFileResponse instead of loading it into memory; the support packet still uses GetLogFile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FpCfx5vEzDnjuW2q3TTD5t * [MM-71391] Use < for the log page size loop bound --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
40575a0b58 |
Update maximize-microsoft-investments.mdx (#39434)
* Update maximize-microsoft-investments.mdx * Update maximize-microsoft-investments.mdx * Update maximize-microsoft-investments.mdx * Remove redundant note on connected workspaces Removed note about connected workspaces and their limitations with Microsoft Teams. * Update sign-in instructions for Mattermost responders --------- Co-authored-by: Eric Sethna <14333569+esethna@users.noreply.github.com> |