Commit Graph
3108 Commits
Author SHA1 Message Date
Mattermost Build fb50e5ff84 MM-68702: Reject demoting bot accounts to guest (#36487) (#36621)
Automatic Merge
2026-05-19 08:23:40 +02:00
Mattermost Build d577e6a441 Mm 68282 admin ephemeral mode (#36194) (#36615)
Automatic Merge
2026-05-19 07:23:40 +02:00
Mattermost Build 8b00e80b49 Mm 68503 be abac mask save path masking (#36513) (#36613)
Automatic Merge
2026-05-18 18:53:42 +02:00
Mattermost Build 0cec0687d7 Data spillage report generation UI (#36340) (#36612)
Automatic Merge
2026-05-18 18:23:41 +02:00
Ben Cooke 02023f0328 [MM-68463] New endpoint to GET user by auth_data (#36352) 2026-05-15 15:26:03 -04:00
deafd88fd5 MM-68762: Discoverable Private Channels — Server data layer (#36539)
* MM-68762: Add Postgres migrations for discoverable private channels

Three online-safe migrations introduce the schema that supports the
Discoverable Private Channels feature (PRs 2-5 of MM-68430 will land
behind it):

- 000175 adds Channels.Discoverable BOOLEAN NOT NULL DEFAULT FALSE.
  Metadata-only on Postgres >= 11; no table rewrite.
- 000176 creates a partial index on
  (TeamId) WHERE Discoverable AND Type='P' AND DeleteAt=0
  using CREATE INDEX CONCURRENTLY (-- morph:nontransactional) so the
  build never blocks writes on the populated Channels table.
- 000177 creates the ChannelJoinRequests table with three indexes, the
  important one being the partial unique index on (ChannelId, UserId)
  WHERE Status = 'pending'. That keeps the full audit history intact
  while still enforcing at-most-one active pending request per
  (channel, user).

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Add FeatureFlagDiscoverableChannels (default false)

Gates the per-channel Discoverable toggle and the channel-join-request
flow. Default-OFF so all PRs in the MM-68430 series can land on master
without exposing partial UX.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Add Discoverable + ChannelJoinRequest models

- Channel gains a Discoverable bool, ChannelPatch a *bool, both serialized
  as 'discoverable'. Patch() applies it, Auditable() logs it, and IsValid()
  rejects Discoverable=true on any non-private channel so a misconfigured
  patch can never produce a public discoverable channel.
- New ChannelJoinRequest type captures the per-row state of a non-member's
  request: pending -> approved | denied | withdrawn. Rows are append-only
  with reviewer and timestamps so the table is also the audit trail.
  IsValid() enforces:
  * recognized status,
  * Message and DenialReason rune limits,
  * DenialReason only on denied rows (no orphan reasons),
  * reviewer + reviewed_at present for any terminal review (approved /
    denied) but not for self-service withdrawal.
- Two new WebSocket event constants -- channel_join_request_created and
  channel_join_request_updated -- that later PRs broadcast on the admin
  queue and the requester's My Pending Requests panel.

Unit tests cover Patch(), the new IsValid() rule on Discoverable, the
PreSave/PreUpdate timestamp behavior on ChannelJoinRequest, and every
IsValid branch including the reviewer-required-on-review invariant.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Add discoverable-channel permissions

Two new channel-scoped permissions, each independently rebindable from
the System Console:

- manage_private_channel_discoverability gates the per-channel toggle so
  admins can restrict who can flip discoverability without also handing
  out manage_private_channel_properties.
- manage_channel_join_requests gates the queue list / approve / deny /
  count endpoints (added in PR 2).

Both are added to the channel_admin role bootstrap so new deployments
get them by default, and a new permissions migration
(add_discoverable_channel_permissions) grants them to channel_admin,
team_admin and system_admin scheme roles on existing deployments.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Add ChannelJoinRequestStore and wire Discoverable into channel store

- channelSliceColumns / channelToSlice / updateChannelT now include the
  new Discoverable column so Save() and Update() round-trip the field.
  Existing select paths inherit the column automatically because every
  read goes through channelSliceColumns.
- New ChannelJoinRequestStore interface and SQL implementation:
  Save / Get / GetPendingForChannelAndUser / GetForChannel / GetForUser
  / Update / CountPending. Save translates the
  idx_channeljoinrequests_pending_unique partial unique index violation
  into store.ErrConflict so the app layer (PR 2) can return 409 without
  re-parsing pq errors.
- Storetest suite at storetest/channel_join_request_store.go is invoked
  from sqlstore via the existing StoreTest harness; covers insert /
  partial-unique conflict / re-insert after withdrawal / NotFound /
  status filtering / pagination with TotalCount / Update / CountPending.
- Mocks and retrylayer / timerlayer are regenerated via make store-mocks
  and go generate ./channels/store -- no hand-written generator output.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Add TS types for Discoverable channels + join requests

webapp/platform/types:
- Channel.discoverable?: boolean alongside existing policy_enforced /
  policy_is_active so the web client sees the same wire shape the server
  emits.
- ChannelJoinRequest, ChannelJoinRequestStatus, ChannelJoinRequestList,
  GetChannelJoinRequestsOptions for the API contract surfaced in PR 2.

webapp/platform/client:
- WebSocketEvents enum gains ChannelJoinRequestCreated and
  ChannelJoinRequestUpdated so PR 3 can hang WS handlers off them
  without redeclaring constants.

These are model-only updates with no UI consumer yet; PR 3 introduces
the toggle, request flow, and admin queue surfaces.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Split ChannelJoinRequests indexes into concurrent migrations

The mattermost-govet concurrentIndex lint check enforces CREATE INDEX
CONCURRENTLY on every CREATE INDEX statement, even on an empty
freshly-created table where it would be a no-op. The original 000177
file inlined three CREATE INDEX statements; that failed check-style.

Mirror the convention used by 000166_create_views +
000167_create_views_channel_id_delete_at_index: keep the CREATE TABLE
in its own (transactional) file, and move each index into a separate
nontransactional file that runs CREATE INDEX CONCURRENTLY. Verified
locally against Postgres 15 that all four new migrations apply in
order and the storetest suite (partial unique constraint + paged
list + count) still passes.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Wire new permission migration into test fixtures

Two CI test surfaces missed when the channel_admin role and the
permission-migration list gained the new
manage_private_channel_discoverability and manage_channel_join_requests
entries:

- testlib/store.go: the shared mocked SystemStore used by
  SetupWithStoreMock / SetupEnterpriseWithStoreMock needs an explicit
  GetByName expectation for every migration key (because the mock
  panics on unexpected calls). Add the new
  MigrationKeyAddDiscoverableChannelPermissions key so
  TestCreateOrUpdateAccessControlPolicy, the elasticsearch
  aggregation_job_test, and every other mock-store test stop panicking
  on server bootstrap.
- cmd/mmctl/commands/permissions_test.go: TestResetPermissionsCmd
  hard-codes the channel_admin default permission list and expects
  PatchRole to be called with exactly that slice. Extend the expected
  slice with the two new permission ids so the mmctl reset path stays
  in sync with the role bootstrap.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Register new idx_channels_discoverable_team in TestGetSchemaDefinition

The schema-dump test asserts an exact index count and definition map
for the channels table. Migration 000176 added
idx_channels_discoverable_team — a partial btree on (teamid) gated by
discoverable=true AND type='P' AND deleteat=0. Bump the expected count
from 12 to 13 and add the index's CREATE INDEX definition as produced
by pg_indexes (note: type is cast to channel_type, the existing
domain). Verified locally against Postgres 15.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Fix golangci-lint findings in ChannelJoinRequest store

Two golangci-lint findings on the freshly-added files:

- sqlstore/channel_join_request_store.go:133 (modernize): collapse the
  'if page < 0 { page = 0 }' clamp into max(opts.Page, 0).
- storetest/channel_join_request_store.go:243 (govet shadow): the
  inner Save loop redeclared err with :=, shadowing the outer err
  captured from the first CountPending call. Switch to plain
  assignment so the same err is reused.

Verified locally with golangci-lint v2.11.4 across public/...,
channels/app/..., channels/store/..., channels/testlib/... and
cmd/mmctl/commands/... — 0 issues.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Sync channel_admin bootstrap with TestDoAdvancedPermissionsMigration

app_test.go pins the exact list of permissions the channel_admin role
is expected to hold after DoAdvancedPermissionsMigration completes.
The role bootstrap in role.go grew two entries
(manage_private_channel_discoverability and manage_channel_join_requests),
so the test's expected slice needs the same two entries appended in
the same order, otherwise assert.Equal fails on slice ordering.

This is the same class of fix as the mmctl/permissions_test.go change
in a previous commit -- two parallel test fixtures encode the
channel_admin defaults and have to be updated in lockstep with the
bootstrap.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Add English translations for new model error keys

12 keys were emitted by the new Discoverable + ChannelJoinRequest
validation paths but had no en.json entry, which trips i18n-check on
CI. Add the missing entries with one-line English copy that mirrors
adjacent model errors (Invalid <field>., Create at must be a valid
time., etc.). The new entries are:

- model.channel.is_valid.discoverable.app_error
- model.channel_join_request.is_valid.channel_id.app_error
- model.channel_join_request.is_valid.create_at.app_error
- model.channel_join_request.is_valid.denial_reason.app_error
- model.channel_join_request.is_valid.denial_reason_status.app_error
- model.channel_join_request.is_valid.id.app_error
- model.channel_join_request.is_valid.message.app_error
- model.channel_join_request.is_valid.reviewed_by.app_error
- model.channel_join_request.is_valid.reviewer.app_error
- model.channel_join_request.is_valid.status.app_error
- model.channel_join_request.is_valid.update_at.app_error
- model.channel_join_request.is_valid.user_id.app_error

Generated through 'make i18n-extract'; verified clean with
'make i18n-check'. Per the workspace rule, only en.json was modified --
no other locale files.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Address CodeRabbit review: stable pagination + redact denial reason from audit log

Two production-code findings from CodeRabbit on the freshly-added
ChannelJoinRequest server code:

- sqlstore/channel_join_request_store.go (GetForChannel / GetForUser):
  OrderBy("CreateAt DESC") alone is unstable when two rows share a
  millisecond (NewId is monotonic-ish but CreateAt is millisecond
  resolution), so offset paging could duplicate or skip rows between
  pages. Add Id DESC as a deterministic tie-breaker on both list
  queries.
- model/channel_join_request.Auditable: the denial reason is admin-typed
  free text and could carry sensitive content. Mirror the existing
  has_message pattern by emitting has_denial_reason as a boolean
  presence flag instead of the raw value. Reviewer id, review timestamp,
  and status are still logged, so the audit trail keeps every piece
  needed for compliance review.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Tighten model tests per CodeRabbit review

Two test-only findings from CodeRabbit:

- TestChannelJoinRequestPreUpdateAdvancesUpdateAt previously asserted
  GreaterOrEqual(r.UpdateAt, originalCreate). Because validRequest
  initialises UpdateAt to GetMillis() (same call site as CreateAt), a
  no-op PreUpdate would still pass that check. Seed r.UpdateAt = 1
  before calling PreUpdate() and assert Greater(r.UpdateAt, int64(1))
  so any regression that drops the GetMillis assignment fails the test.
- TestChannelIsValidDiscoverable did not cover ChannelTypeGroup. Add the
  case alongside ChannelTypeOpen and ChannelTypeDirect so the contract
  that 'only ChannelTypePrivate accepts Discoverable=true' is fully
  pinned across all four channel types.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Mock ChannelJoinRequest accessor in retrylayer test

retrylayer_test.go's genStore() helper mocks every Store() accessor
because retrylayer.New() wraps the entire surface. The new
ChannelJoinRequest() method I added on Store was missing from the
mock, so TestRetry/on_regular_error_should_not_retry panicked with
'Unexpected Method Call ChannelJoinRequest()' on Postgres shard 0.

Add the mock alongside the other accessors. No production code
change.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
2026-05-15 21:04:32 +02:00
Jesse HallamandMattermost Build 3f3d8408b2 Return descriptive errors from Role.IsValid and Role.IsValidWithoutId (#36582)
* Return descriptive errors from Role.IsValid and Role.IsValidWithoutId

Previously both methods returned bool, leaving callers with no context
about which validation check failed. Now both return error with a
message identifying the specific constraint that was violated.

* Add tests for Role.IsValid and Role.IsValidWithoutId

* Log migration key on doPermissionsMigration failure

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-15 18:40:25 +00:00
Bill Gardner fa1255f149 Update Calls to v1.11.5 (#36574) 2026-05-15 09:22:08 -04:00
Jesse HallamandMattermost Build 54bee00622 MM-68332: consistently enforce query timeouts (#36522)
* Remove QueryRowx (no-timeout) from sqlxDBWrapper; migrate caller to QueryRowX

QueryRowx forwarded to context.Background() with no timeout, while QueryRowX
(uppercase) already enforces the wrapper timeout. Removing the no-timeout
variant eliminates an accidental footgun and migrates the one sqlstore caller
(plugin_store) to the timeout-enforcing method.

* sqlxRow, with timeout cancel after Scan

Introduce sqlxRow, which pairs *sqlx.Row with its context cancel function.
QueryRowX (on both sqlxDBWrapper and sqlxTxWrapper) now returns *sqlxRow;
Scan calls cancel immediately after the row is consumed, releasing the
timeout context as soon as possible rather than waiting for the timer.

* sqlxRows, with timeout cancel on Close

Introduce sqlxRows, which embeds *sqlx.Rows and holds the timeout context's
cancel function. Close() cancels the context immediately after the rows are
done, releasing timeout resources as soon as iteration completes rather than
waiting for the timer to fire.

Introduce rowScanner interface (Next/Scan/Err) so the internal helpers
scanRowsIntoMap and scanRetentionIdsForDeletion accept any row iterator
rather than the concrete *sql.Rows, accommodating the new return types
without threading *sqlxRows through every caller.

* abolish the X suffix altogether

* delete unused NamedQuery

* add timeout tests for Query and QueryRow on db and tx wrappers

* fix tx/timeout tests: handle pq driver.ErrBadConn on killed connection

* fixup! fix tx/timeout tests: handle pq driver.ErrBadConn on killed connection

* override Next() on sqlxRows to cancel on EOF

* rm redundant row.Err(), handled by Scan

* apply timeout to context unless deadline set

* rebind consistently

* address review feedback: rename, trace, and QueryContext fix

- Rename withQueryTimeout → ensureQueryTimeout to better convey that it
  respects existing deadlines rather than overriding them.
- Add missing w.trace blocks to QueryRowContext and ExecContext.
- Change QueryContext to use ensureQueryTimeout and return *sqlxRows
  (instead of *sql.Rows) so the cancel is deferred to Close/Next
  rather than released prematurely.

* fix Beginx → Begin after master merge

* fix golangci-lint inline warning in sqlx_wrapper_test

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-15 13:02:13 +00:00
Jesse HallamandMattermost Build d4fc0ecb1c MM-68150: Upgrade golangci-lint to v2.12.2 (#36554)
* Simplify invite_people email parsing

Replace backwards in-place mutation loop with a straightforward forward
filter into a new slice. Extract into parseEmailList so the logic can be
unit tested directly.

* MM-68150: Upgrade golangci-lint to v2.12.2

Remove //go:fix inline from NewPointer, which is a generic function not
yet supported by the inline analyzer, and fix 11 slicesbackward
modernize issues flagged by the new version.

* MM-68150: Enable all linters by default; disable those with >20 existing issues

Switch from opt-in (default: none) to opt-out (default: all) so new
linters added to golangci-lint are evaluated automatically. Explicitly
disable every linter that has more than 20 pre-existing violations,
deferring those for later cleanup. Also disable a handful of linters
whose violations are intentional patterns in this codebase (nilerr,
dogsled, sqlclosecheck, iotamixing, predeclared, containedctx, iface,
gocheckcompilerdirectives, promlinter, goprintffuncname, gomoddirectives).

* MM-68150: Fix mirror linter issues

Replace Write([]byte(s)) with WriteString(s), and FindIndex([]byte(s))
with FindStringIndex(s), to avoid unnecessary allocations.

* MM-68150: Fix nosprintfhostport linter issue

Use net.JoinHostPort to construct host:port strings instead of
fmt.Sprintf with a manually formatted pattern.

* MM-68150: Fix rowserrcheck and sqlclosecheck linter issues

Check rows.Err() after iteration loops in schema_dump.go. In the
sqlx_wrapper test, defer rows.Close() rather than closing inline.

* MM-68150: Fix nilnesserr linter issues — wrong variable in error handlers

In 11 places, a stale variable (often the outer err from a prior
assignment) was used instead of the freshly-checked error variable
(appErr, rowErr, jsonErr, writeErr, esErr). Each produces a typed-nil
wrapped in a non-nil interface, silently discarding the real error.

* MM-68150: Add i18n string for app.compile_csv_chunks.write_error

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-14 17:29:37 -04:00
Julien Tant d43dbe972e Update Playbooks plugin to v2.9.0 (incl. FIPS) (#36570) 2026-05-14 12:37:14 -07:00
Alejandro García Montoro f604ec7a5c MM-68662: Add Azure Blob Storage filestore backend (#36498)
* Generalize file backend error types

Replace S3FileBackendAuthError and S3FileBackendNoBucketError with
backend-agnostic FileBackendAuthError and FileBackendNoBucketError so
non-S3 drivers can return them and the admin "Test Connection" flow
keeps surfacing useful messages.

The old S3-prefixed names are kept as type aliases of the generic
types so external code (plugins, historical consumers) continues to
compile, and so existing S3 construction sites stay untouched.

The type switch in connectionTestErrorToAppError now matches the
generic types, with new i18n keys (test_connection_auth.app_error
and test_connection_no_bucket.app_error) whose wording does not name
S3. The old S3-specific i18n keys are dropped via `make i18n-extract`
since they are no longer referenced from code; the api4 test that
asserted on those keys is updated, and the Cypress
`MM-T996 Amazon S3 connection error messaging` spec that asserted
on the old user-facing string is updated to the new wording.

------
AI assisted commit

* Pull in Azure SDK and uuid dependencies

Bring in github.com/Azure/azure-sdk-for-go/sdk/azcore and
.../sdk/storage/azblob (with .../sdk/internal as their indirect
dependency). The two are needed by the upcoming Azure Blob Storage
filestore backend and its lazy-Range-backed reader. The bump of
golang.org/x/{crypto,net,sys,term,text} comes transitively from
azblob's minimum versions.

Also promotes github.com/google/uuid from indirect to direct,
since the Azure backend uses it to generate block IDs that share
the same wire format the SDK itself produces in UploadStream.

------
AI assisted commit

* Add azureRangeReader, a seekable Range-backed blob reader

A small standalone type that satisfies the FileBackend interface's
ReadCloseSeeker + the broader io.ReaderAt contract on top of Azure
Blob Storage HTTP Range requests. Lands as its own commit because
the upcoming Azure FileBackend driver builds on it, and the reader
itself is independently useful — and independently testable against
a fake downloader without standing up an Azure client.

Design notes:

* Read opens an HTTP Range stream lazily at the current offset and
  reuses it for sequential reads. Seek to a different offset closes
  the open stream; the next Read re-opens it.
* Seek to the same offset is a no-op and does not close the open
  stream, so callers like zip.NewReader that probe with redundant
  seeks don't kick off a fresh download.
* ReadAt issues a dedicated ranged DownloadStream per call and does
  not touch the streaming cursor — matches the io.ReaderAt contract
  the bulk-import worker's zip.NewReader path relies on.
* Close cancels the context (which any in-flight Azure call will
  observe and abort), stops the deadline timer, and closes the
  current body if any. It is safe to call when no body was ever
  opened.
* CancelTimeout lets long-running consumers like the import worker
  opt out of the per-operation deadline that would otherwise kill
  multi-minute downloads partway through.

The implementation talks to a small blobDownloader interface rather
than *blob.Client directly so the unit tests can substitute a fake
downloader that records every requested Range and tracks Close
calls on the bodies it hands out.

------
AI assisted commit

* Add Azure Blob Storage filestore driver

Implements the FileBackend interface against Azure Blob Storage in
a new azurestore.go (~520 LOC). The driver is not yet selectable
via NewFileBackend's switch — that wiring lands in the next commit
together with the admin config surface — but the driver itself is
complete and self-contained behind the FileBackendSettings struct.

Filesstore.go grows three pieces of supporting infrastructure that
the driver consumes:

* a `driverAzure = "azureblob"` constant alongside the existing
  driverS3 and driverLocal,
* an Azure-specific block on FileBackendSettings (storage account,
  access key, container, path prefix, endpoint, SSL flag, request
  timeout),
* a CheckMandatoryAzureFields validator that mirrors
  CheckMandatoryS3Fields.

Behavioural notes that warrant calling out:

* Reader returns the previously-added azureRangeReader, so reads
  stream lazily over HTTP Range and ReadAt is available for the
  bulk-import worker's zip.NewReader path. The deadline timer is
  armed before the initial GetProperties call so the HEAD itself
  is bounded.
* WriteFile and AppendFile both go through StageBlock +
  CommitBlockList via a shared stageBlocks helper, never the SDK's
  UploadStream. UploadStream's small-payload fast path falls back
  to single-shot PutBlob, which leaves the resulting blob with no
  committed block list; a subsequent AppendFile that calls
  CommitBlockList on that blob would then clobber its content.
  Routing every write through the block-list mechanism keeps
  AppendFile correct regardless of payload size.
* AppendFile stages the new chunk as one or more blocks and commits
  the existing committed block list plus the newly staged IDs.
  The new bytes go up exactly once — no re-download, no
  re-concatenate, no re-upload of the prior contents.
* WriteFileContext does not wrap the caller-supplied context with
  its own timeout — that timeout is applied in WriteFile only,
  matching the S3 driver, so long-running TryWriteFileContext
  callers (like message-export bulk writes) opt out of the
  per-operation timeout the way the abstraction documents.

Authentication is shared-key only for this drop; Microsoft Entra
ID / managed identity is deferred to a follow-up. The endpoint is
configurable so the same code targets the production Azure host
(vhost style — {account}.blob.core.windows.net) or Azurite /
Azure Government / sovereign clouds (path style —
host[:port]/{account}).

------
AI assisted commit

* Wire Azure backend into config, validation, and driver selection

This commit registers the previously-added AzureFileBackend driver
with the rest of the system. Until now the driver was usable only
via direct construction; after this commit, `DriverName: "azureblob"`
in config.json is a fully-supported deployment configuration.

Five integration sites are touched:

* `newFileBackend` in filesstore.go now dispatches `driverAzure` to
  NewAzureFileBackend, alongside the existing s3 and local cases.
  NewFileBackendSettingsFromConfig (and its export counterpart) gain
  an Azure branch that maps the model.FileSettings fields onto the
  Azure-specific FileBackendSettings fields.
* `model.FileSettings` grows the user-facing Azure config schema:
  storage account, access key, container, path prefix, endpoint,
  SSL flag, request timeout, plus matching Export* fields for the
  dedicated export store. SetDefaults populates them so deployments
  that never opted into Azure don't carry nil pointers. `isValid`
  accepts the new ImageDriverAzure constant.
* `Config.Sanitize()` masks AzureAccessKey and ExportAzureAccessKey
  the same way it masks AmazonS3SecretAccessKey, so the shared key
  never reaches an API consumer in plain text.
* `desanitize()` restores the masked keys on a config write so a
  PATCH that doesn't touch the key doesn't clobber it with the
  FakeSetting placeholder.
* `configSensitivePaths` covers both Azure key paths so audit
  diffs don't include them either.
* `ConfigToFileBackendSettings` in the `mattermost db` CLI helper
  gets the Azure branch its production counterpart already has —
  without it, `mattermost db migrate` / `db downgrade` would fail
  on Azure-configured deployments with "missing azure storage
  account setting".

Finally, the shared FileBackendTestSuite is now wired against
Azurite via TestAzureFileBackendTestSuite, which skips when
CI_AZURITE_HOST is unreachable. The test-infra wiring (the docker
service, the env vars, the start_dependencies entry) landed in a
previous PR; this commit is what makes the suite actually exercise
the Azure driver end to end.

------
AI assisted commit

* Validate Azure timeout and path prefix in Config.IsValid

Parity with the S3-side checks that already cover
AmazonS3RequestTimeoutMilliseconds and AmazonS3PathPrefix. Without
these, a zero/negative AzureRequestTimeoutMilliseconds passes
validation and later creates immediately-expired request contexts,
and leading/trailing whitespace in AzurePathPrefix produces blob
keys that don't match what the admin configured.

Same checks added for the Export* counterparts. The
file_driver.app_error translation is updated to mention the new
'azureblob' option alongside 'local' and 'amazons3'.

------
AI assisted commit

* Stream zip entries from the Azure backend

writeZipEntry was calling ReadFile, which loads the entire blob
into memory before writing it to the archive. For large blobs or
deep directories this spikes RSS or OOMs the goroutine. Switch to
Reader (the streaming azureRangeReader) and io.Copy into the zip
entry so memory stays bounded regardless of blob size.

------
AI assisted commit

* Use a backend-agnostic fallback for FileBackendNoBucketError

The fallback Error() message was "no such bucket", which leaks S3
terminology when an Azure caller returns the type with no wrapped
Err. Use "no such bucket or container" so logs and external error
handling stay neutral across backends.

------
AI assisted commit

* Defend Azure path prefix against directory traversal

Reject ".." in AzurePathPrefix and ExportAzurePathPrefix at config
validation time, since path.Join collapses traversal segments and a
prefix like "../other-tenant" would otherwise escape the configured
isolation boundary.

Harden the prefix helper as a second line of defense: if the joined
path no longer sits inside pathPrefix, fall back to joining the prefix
with the base name of the caller-supplied path. That preserves the
prefix invariant for plugin and import paths that the upload code does
not sanitize uniformly.

------
AI assisted commit

* Honor SkipVerify when constructing the Azure client

FileBackendSettings.SkipVerify is plumbed through from the System Console
the same way it is for S3, so admins toggling the flag for self-signed
endpoints (Azurite, sovereign clouds) get the behavior they expect
without having to drop SSL entirely and send the shared key in clear
text.

------
AI assisted commit

* Warn when the Azure request timeout falls back to its default

Config.IsValid already rejects non-positive AzureRequestTimeoutMilliseconds
for any path that goes through config validation, so this warn only fires
for direct callers that bypass validation (tests, helpers). Logging the
substitution turns a silent coercion into something an operator can
correlate against unexpected request behavior.

------
AI assisted commit

* Cap Azure request timeout at 10 minutes

Reject AzureRequestTimeoutMilliseconds values above the ceiling so an
operator (or someone who has admin access) cannot effectively disable
timeouts by setting the value to math.MaxInt64. A hung Azure call then
holds a goroutine open until the OS gives up.

Applies the same bound to ExportAzureRequestTimeoutMilliseconds. S3 has
the same gap; treating it is out of scope here but worth a follow-up.

------
AI assisted commit

* Refuse AppendFile on blobs without a committed block list

A blob written by another tool (Azure portal, azcopy, a migration script,
a plugin using Put Blob) has its content in the blob but an empty
committed-block list. Committing a new block list against such a blob
silently replaces the existing content with only the appended bytes.

Check the blob's properties before staging when the committed-block list
is empty, and refuse with a clear error if the blob has content. Same
hazard for an admin pointing the backend at an existing container with
pre-existing files.

Adds an integration test against Azurite to lock the behavior in.

------
AI assisted commit

* Surface truncated reads from azureRangeReader

Read closed the body cleanly and returned io.EOF even when the remote
stream terminated before the blob's content length. Callers (and any
retry layer above) then accepted a partial blob as complete.

ReadAt unconditionally rewrote io.ErrUnexpectedEOF to io.EOF, which made
truncated downloads indistinguishable from clean reads. That is exactly
what zip.NewReader consumes for archive readers, so the bulk-import
worker would silently import partial archives.

Read now closes the body, nils it, and returns io.ErrUnexpectedEOF when
EOF arrives before offset reaches size. ReadAt only collapses
ErrUnexpectedEOF to EOF when the full count was delivered and the stream
was consumed to the end of the blob. Otherwise the truncation
propagates with context.

Both code paths are exercised by new fakeDownloader-backed tests.

------
AI assisted commit

* Move container provisioning out of Azure TestConnection

Auto-creating the container inside TestConnection meant a typo in the
System Console (mattermosst instead of mattermost) silently provisioned
an unwanted container in the admin's Azure subscription, with no audit
log and no warning. They'd discover it later when uploads landed
somewhere unexpected.

TestConnection now returns FileBackendNoBucketError when the container
is missing, mirroring the S3 contract. A new MakeContainer method
mirrors S3FileBackend.MakeBucket, and Server.Start dispatches via two
capability interfaces (bucketMaker / containerMaker) instead of a hard
S3 type assertion — so the NoBucket error is no longer silently
swallowed for backends Server.Start has not been taught about.

------
AI assisted commit

* Carry file backend auth detail through to AppError

The Test Connection button collapsed every typed backend failure into
the same generic i18n message. Operators trying to debug bad credentials
or a missing bucket only saw "Unable to authenticate against the file
storage backend" with no SDK code to grep for in their logs.

Use errors.As so the typed checks survive future wrapping, and pass the
underlying error string through the NewAppError details argument. The
AppError serializer surfaces that detail to the admin console alongside
the translated message, so a bad S3 InvalidAccessKeyId or an Azure
AuthenticationFailed shows up in the toast without an i18n schema
change.

------
AI assisted commit

* Remove non-ascii characters from comments

------
AI assisted commit

* Make linter happy

------
AI assisted commit

* Harden Azure prefix boundary check

strings.HasPrefix on the joined path is a string-level check, not a
path-level one, so a configured prefix of "mattermost" accepts a joined
result of "mattermost-evil/...". A crafted caller path like
"../mattermost-evil/secrets" would collapse via path.Join to that exact
sibling and slip through the boundary check, escaping the configured
prefix scope.

Require the joined path to be the cleaned prefix itself or to start with
the prefix followed by a path separator. The fallback path.Join uses the
same cleaned prefix for consistency.

------
AI assisted commit

* Provision Azurite container in standalone test setup

The shared FileBackendTestSuite's SetupTest already handles a missing
container by detecting FileBackendNoBucketError from TestConnection and
calling MakeContainer, but TestAzureFileBackendAppendRefusesNonBlockBlob
bypasses SetupTest and calls TestConnection directly. On a fresh Azurite
instance the test would fail before exercising the append-refusal logic.

Extract a newAzuriteBackend(t) helper alongside azuriteSettings(t) that
builds the backend and ensures the container exists, mirroring the
suite's setup. Use errors.As for forward compatibility with future
wrapping.

------
AI assisted commit

* Fix grammar in email-settings i18n string

"Email settings has unset values." -> "Email settings have unset values."

------
AI assisted commit

* Make Azure MakeContainer idempotent

Treat a ContainerAlreadyExists response as success so that two nodes
racing through TestConnection plus MakeContainer at boot both converge
instead of having the loser fail. Mirrors how the S3 backend handles
the equivalent BucketAlreadyOwnedByYou case.

------
AI assisted commit

* Narrow AzureEndpoint comment to path-style only

The setting only builds path-style URLs, so it cannot reach sovereign
clouds like Azure Government or Azure China, which require vhost-style
endpoints. Update the comment to reflect what the code actually does
and document that sovereign-cloud support is out of scope.

------
AI assisted commit
2026-05-14 16:59:18 +00:00
David Krauser 9f1fe90b69 Migrate CPA to the v2 Property System (#36180) 2026-05-14 12:46:07 -04:00
David Krauser 4aa1c58e37 ci: invalidate poisoned shard-timing cache and guard future saves (#36568) 2026-05-14 16:01:49 +00:00
Julien TantandClaude Opus 4.6 323841e9c5 Add board channel types (BO/BP) for Integrated Boards (#35887)
* Add board channel types (BO/BP) with POST /boards API

Introduces board channel types as a new channel variant that reuses the
Channels table but is fully isolated from all /channels endpoints.

Model:
- Add ChannelTypeOpenBoard ("BO") and ChannelTypePrivateBoard ("BP")
- Add IsBoard(), IsOpenBoard(), IsPrivateBoard() helpers
- Add board-specific websocket events (board_created/updated/deleted/restored)

Store:
- SaveBoardChannel: atomic channel + view creation in a single transaction
- Save() rejects board types (forces use of SaveBoardChannel)
- Exclude boards from all channel listing/search queries (GetTeamChannels,
  GetAll, GetChannels, GetChannelsByUser, GetDeleted, autocomplete, search)

API:
- POST /boards: create board channel (feature-flagged behind IntegratedBoards)
- All /channels write endpoints reject board types with 400
- All /channels read endpoints reject or exclude board types
- Open boards get same public-read semantics as open channels

Tests:
- 15 rejection tests covering every /channels write + read endpoint
- 9 exclusion tests covering every listing/search endpoint
- 8 store tests for SaveBoardChannel + Save rejection
- 4 board creation API tests (create, private, flag off, sidebar exclusion)
- 3 authorization tests for board permission semantics

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

* Update generated files: i18n, go.mod, migrations list

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

* Add i18n translations for board channel error strings

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

* Fix board guard ordering in getChannelMembers and getChannelStats

Move the board rejection check after the permission check so that
nonexistent channel IDs still return 403 (not 404) matching the
original behavior expected by TestGetChannelMembers.

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

* Filter boards at store level instead of API guards

Store.Get() now excludes board types via WHERE clause, making boards
invisible to all /channels endpoints. Added GetBoardChannel() for
/boards endpoints. Removed redundant API-level rejectBoardChannel
guards from 10 handlers that already call GetChannel(). Kept explicit
guards only on 3 handlers that don't fetch the channel.

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

* Fix empty i18n translation for app.channel.save_member.app_error

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

* Add board system properties, kanban column config, and audit logging

Migration:
- Register "boards" property group with system-wide Assignee (user) and
  Status (select: Todo/In Progress/Complete) fields, both protected
- Idempotent migration following content flagging pattern

Board creation:
- Look up boards fields by name, set board:linked_properties on channel
- Build kanban view props with group_by mapping status options to columns
- Add typed KanbanProps/KanbanColumn/KanbanGroupBy structs with
  ToProps()/KanbanPropsFromProps() for round-tripping
- Add audit record logging for POST /boards
- Add early team_id validation in API handler
- Error on missing status options instead of silent empty columns

Tests:
- Migration test: field creation + idempotent re-run
- Board creation test: verify kanban props + linked_properties
- Fix updateChannelMemberRoles test to use valid role string

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

* Add boards migration mock to testlib store setup

The boards property migration calls System().GetByName() which needs
a matching mock expectation, same pattern as content_flagging_setup_done.

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

* Add kanban view props validation and tests

Validate kanban View.Props in IsValid(): group_by required with valid
field_id, 1-100 columns, each column needs id, name, and at least one
option_id. Update all test helpers to produce valid kanban props.

11 dedicated validation tests + round-trip test for KanbanProps.

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

* Validate board display name is not empty

Add early DisplayName validation in CreateBoardChannel with a clear
error. Add tests for empty and whitespace-only display names.

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

* Exclude boards from GetMany and getChannelsMemberCount

Add board type exclusion to Store.GetMany() and use filtered channel
IDs in getChannelsMemberCount handler so board channels don't leak
into member count results. Add test covering the endpoint.

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

* Fix review issues: drop search indexing for boards, use request context

- Remove search layer indexing of board channels so they stay invisible
  to Elasticsearch/Bleve-powered search and autocomplete
- Replace context.Background() with rctx.Context() for proper
  cancellation and tracing in CreateBoardChannel

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

* Fix gofmt alignment in websocket_message.go after merge

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

* Regenerate server i18n after merge

* Restore translation for permission_policy.app_error

* Filter board channels in name lookups, autocomplete, and indexing

The store-layer board exclusion filter was missing from getByName,
getByNames, GetDeletedByName, the global Autocomplete, and
GetChannelsBatchForIndexing — leaving boards reachable via name
lookups, the no-team-filter search path, and admin reindex jobs.

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

* Reject boards in id-batch lookups, unread, and member-mutation endpoints

- GetChannelsByIds, GetChannelsWithTeamDataByIds, and GetChannelUnread
  now exclude BO/BP at the store layer so boards can't slip through if
  callers stop filtering first.
- updateChannelMemberNotifyProps, updateChannelMemberAutotranslation,
  and viewChannel now reject board IDs explicitly via the existing
  rejectBoardChannelByID helper, matching the other write endpoints.

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

* Gate boards properties setup on the IntegratedBoards feature flag

doSetupBoardsProperties registered the boards property group and
fields at every server boot regardless of the IntegratedBoards
feature flag. Skip the migration when the flag is disabled so the
property metadata only appears once boards are actually enabled.

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

* Use App accessors for boards property lookups

CreateBoardChannel reached into a.Srv().PropertyService() directly
instead of going through the App-level GetPropertyGroup and
GetPropertyFieldByName methods that already wrap the service. Switch
to the standard App accessors so the calls match the rest of the
codebase.

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

* Log full channel input on createBoard audit record

createBoard only captured team_id and type on the audit record, so
failed creations lost most of the request payload. Use
AddEventParameterAuditableToAuditRec with the full channel struct,
matching createChannel.

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

* Use allow-list of message channel types in store filters

Inverted every sq.NotEq{[BO, BP]} filter into sq.Eq{messageChannelTypes}
(or teamMessageChannelTypes for queries that also exclude direct
channels) so that any future non-message channel type — wikis, etc. —
is excluded by default rather than requiring every existing call site
to be updated. Also rewrote GetChannelUnread on top of the squirrel
builder so the same allow-list slice can be reused.

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

* go.mod: promote prometheus/common to direct after merge

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

* Use model.NewPointer for boards property permission field

Drops the local permNone variable in doSetupBoardsProperties and uses
model.NewPointer(model.PermissionLevelNone) inline, matching the
surrounding ContentFlagging/ManagedCategory code.

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

* Extract saveViewT to share Views insert between Save and SaveBoardChannel

ViewStore.Save and SaveBoardChannel both built the same INSERT INTO
Views statement, so a future column addition would need updates in two
places. Extract the insert (plus PreSave/IsValid) into a private
saveViewT method that accepts any sqlxExecutor — the regular master
handle for ViewStore.Save, and the channel transaction for
SaveBoardChannel.

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

* Add IsMessageChannel helper on model.Channel

Mirrors IsBoard for the positive case: returns true for Open, Private,
Direct, and Group channel types. Lets future filtering code be
expressed against the allow-list rather than enumerating board types,
so newly introduced non-message channel types are excluded by default.

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

* Move board input validation into Channel.IsValidBoard

CreateBoardChannel inlined four guards for type, team, and display
name. Move the type/team_id/display_name checks into a new
Channel.IsValidBoard method so the rules live with the model and
return the AppError directly. The TrimSpace on DisplayName stays at
the call site to match how CreateChannel sanitizes before validating.

Drops the now-unused app.channel.create_board_channel.{invalid_type,
no_team,no_display_name} translations and adds matching
model.channel.is_valid_board.* keys.

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

* Extract buildBoardKanbanView from CreateBoardChannel

The kanban view construction (read status options, build columns,
serialize props, assemble *model.View) only depends on the status
property field and the creator id. Pulling it into its own helper
shrinks CreateBoardChannel and makes the column-building logic
testable in isolation.

Adds board_test.go with coverage for the empty-options error path,
the standard happy path, and the option-skipping branches.

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

* Test Channel.IsValidBoard

Cover the four reject cases (wrong type, missing team_id, empty
display name) plus the open and private board accept cases.

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

* gofmt board_test.go

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

* Document POST /api/v4/boards in OpenAPI spec

* Add valid kanban props to api4 makeTestViewForAPI helper

* Assert kanban.ToProps error in makeTestViewForAPI helper

The helper used to swallow the error from kanban.ToProps. Take *testing.T
and require.NoError so a serialization failure surfaces immediately at the
call site instead of producing a malformed view.

* Run boards properties setup unconditionally

The feature-flag gate added in 6298e15e86 made the test suite impossible:
test infra applies FeatureFlags overrides only after app.NewServer returns,
but doAppMigrations runs during NewServer, so the flag was always false at
migration time. The migration short-circuited, the boards property group
was never registered, and every CreateBoardChannel test 500'd with
"boards property group not found."

Drop the gate. The migration is idempotent (keyed in System) and benign —
matches doSetupContentFlaggingProperties and doSetupManagedCategoryProperties.
IntegratedBoards still gates route registration (api4/board.go) and the
CreateBoardChannel runtime entry (app/board.go), so the property group sits
unused until the feature is enabled.

* Add Client4.CreateBoard and use it in tests

Adds boardsRoute() and CreateBoard(ctx, channel) on Client4 mirroring
CreateChannel/CreateView. Refactors api4 board tests off the raw
DoAPIPost("/boards", ...) calls and the SaveBoardChannel store
inserts that predated the API; both now exercise the public client
method, drop the makeTestBoardView helper and the manual SaveMember
follow-ups, and route through setupBoardTest so IntegratedBoards is
enabled where needed.

* make modules-tidy

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-13 16:25:08 -07:00
d8612e378f [MM-2541] Shortcut to mark all channels as read for a team (#34012)
* feat(webapp): added keyboard shortcut for Mark All As Read (MM-2541)

- Added shortcut (within sidebar) for Shift+ESC to mark _all_ messages, teams as read
    - Desktop only
- Added feature toasts for new features and localStorage support
- Added feature toast for mark-all-as-read feature
    - Should decide when/how people want this shown, I just followed designs
    - Will only show if the user has not clicked 'Got it' before, and is not on mobile
- Added confirmation modal for mark all as read shortcut
    - Contains option to not show again, saved in localStorage
- Added English translations for read shortcut
    - Will need i18n aid on other languages

This is a draft version of this feature update that still needs testing and i18n support, along with a11y validation.

* feat(webapp): feature flags and fixes for mark all as read shortcut

- Added feature flags surrounding rollout of mark-all-as-read shortcut
- Added shortcut to list of shortcuts in help section
- Extended tests for new components
- Updated snapshot for sidebar_list, keyboard_shortcuts_modal
- Fixed styling and CSS issues

Still in draft, needs documentation and e2e support.

* fix(webapp): fixed some issues with new mark-all-read feature

- Scoped persistent storage to current user ID
  so that subsequent new logins also get the notification
- Replaced LocalStorage calls with useGlobalState calls, sad
  that I missed that this updated call was being used.
- Fixed an issue that would have caused the new shortcut to
  show up in the Help menu's shortcuts without being enabled.

* Fixed a snapshot test and a missing i18n member

* Replaced useGlobalState with backend-ready usePreference. Previous version was just a mistake as we didnt know about the supported API

* fix(server): fix lint issue with gofmt

* feat(server,webapp): added cleaner and more effective method with which to mark-all-read

- Added 2 new routes to the API (need to find docs to update those):
    - `PUT /api/v4/channels/members/<userId>/direct/read` will mark a user's non-team DMs and GMs as read
    - `PUT /api/v4/users/<userId>/teams/<teamId>/read` will do a similar action as the multi-channel mark_read action, but with a teamId signifier. Because this is using a teamId, it will _not_ handle DMs or GMs.
- Updated sidebar_list.tsx to use these new routes for the new shortcut
- Added extensive testing, including feature flag assurance.

* fix from upstream changes

* fix: eslint errors in teams actions

* document new API endpoints

* fix i18n

* fix err id

* remove unused localhost methods

* use ShortcutKey and ShortcutSequence

* feature_enhancements, mark as read toast enchancements

* read all modal mount point, use openModal

* use handler

* fix style

* fix: fix refactoring typo

* Merge fix: realign branch with upstream changes

Upstream MM-67319/MM-67320 (#36037) moved ShortcutKey and
WithTooltip into the shared package and rewrote the keyboard
shortcuts test to snapshot real DOM instead of a
react-test-renderer tree. The merge resolution missed several
follow-on consequences; clean them up so the branch builds, type
checks, lints, passes i18n-extract-check and runs without
throwing at mount.

- Port the inline-content variant from the deleted channels-side
  shortcut_key.scss to the new shared shortcut_key.css.
- Refresh the keyboard_shortcuts_sequence snapshot so it matches
  Testing Library's container output (DOM only, no component
  nodes, class= not className=).
- Repoint mark_all_as_read_modal and mark_all_as_read_toast at
  components/shortcut_key for ShortcutKeys and use
  ShortcutKeys.escape; the channels-side with_tooltip is now a
  thin re-export and the field was renamed in the shared keys
  map. Without this both consumers threw "Cannot read properties
  of undefined" at mount.
- Switch mark_all_as_read_toast's UserAgent import to
  @mattermost/shared/utils/user_agent; the channels-local
  utils/user_agent path no longer resolves.
- Drop the orphan mark_all_threads_as_read_modal.cancel string
  from en.json so formatjs extraction is in sync.

* Clean up TestReadAllInTeam

Drop four lines left from debugging and replace them with a real
assertion: LastViewedAtTimes must contain the test channel with a
value at or after the most recent post.

Update three client.GetChannel calls to the (ctx, id) signature;
the prior etag argument no longer compiles after upstream removed
it.

* Use SelectBuilder for team channels query

GetTeamChannelsWithUnreadAndMentions built a squirrel query and
then manually called ToSql before handing the string+args to
GetReplica().Select. SelectBuilder accepts the builder directly
and removes the intermediate dance, matching the pattern used
elsewhere in this store.

* Mark all team-channel threads on team read

MarkTeamChannelsAndThreadsViewed used Thread().MarkAllAsReadByTeam
unconditionally, writing every thread membership in the team for
the user even when nothing was stale. Scoping the call to
channelsToView (channels with unread channel-level messages) would
have closed the perf concern but introduced a regression: in CRT
mode a thread reply does not bump the channel's TotalMsgCount, so
a channel can be read at the channel level while still having
unread thread replies, and those would have been silently skipped.

Build the channel-id list from the keys of the times map instead.
GetTeamChannelsWithUnreadAndMentions already populates that map
for every team channel the user belongs to, so no extra query is
needed. MarkAllAsReadByChannels then filters the actual UPDATE
through its LastReplyAt > LastViewed clause, keeping writes
bounded to genuinely stale rows.

Gate the channel-level work (UpdateLastViewedAt, push clearing,
the MultipleChannelsViewed event) on channelsToView being
non-empty, but always run the thread mark and broadcast
ThreadReadChanged for every team channel so CRT clients refresh
thread state in channels that had no channel-level change.

* Mark mark-read audit records as success

The handlers for mark all DM/GM and mark team read created an
audit record with status Fail and never updated it on success,
so successful calls were always logged as failures.

* Mark all DM/GM threads on full read

MarkAllDirectAndGroupMessagesViewed early-returned when no
channel had unreads, so followed threads in DMs/GMs whose
channel-level counters were already current stayed unread under
CRT. Mirror MarkTeamChannelsAndThreadsViewed and call
MarkAllAsReadByChannels for every DM/GM in times.

* Polish DM/GM channels-with-unreads query

Use model.ChannelTypeDirect/Group constants instead of bare
"D"/"G" literals, and update the error wrap to mention DM/GM
channels (it was copied from the team variant).

* Fix stale ReadAllMessages godoc

* Type last_viewed_at_times as int64 map in OpenAPI

The response field was declared as a generic object. Add
additionalProperties so generated clients see it as a
channelId -> int64 timestamp map.

* Gate MarkAllAsReadToast mount on feature flag

The toast was mounted unconditionally, so its async chunk loaded
even when EnableShiftEscapeToMarkAllRead was off. Gate the mount
with the flag so the chunk only loads when the feature is on.

* Return data from markAllInTeamAsRead thunk

Match the {data: response} shape used by adjacent thunks instead
of returning {}, so callers can read the API payload.

* Coerce undefined suffix in createStoredKey

createStoredKey('foo') returned 'fooundefined' when the suffix
arg was omitted. Coerce a missing suffix to ''.

* Refactor mark-read websocket events

* Polish DM/GM channels-with-unreads query

* Fix import order in shortcut_key consumers

* Fix CI

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Jesse Hallam <jesse@mattermost.com>
Co-authored-by: Caleb Roseland <caleb@calebroseland.com>
Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
2026-05-13 16:38:30 +00:00
Devin Binnie 8a8a4ac8b1 Add Session field to Subject (#36523) 2026-05-12 20:17:20 +00:00
5661fe1941 MM-68501 - implement GetMaskedVisualAST and wire API handler (#36413)
* MM-68501 - implement GetMaskedVisualAST and wire API handler

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

* add missing test and fix style issues

* fix styles

* implement coderabbit feedback

* MM-68501 - PR review: split masking file, model-level access mode, reject contradictory config

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

* MM-68501 - apply shared_only filter to non-option field values (binary masking)

* MM-68501 - consolidate masking flag check and log corrupt text value during masking

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-12 18:26:08 +02:00
Jesse Hallam e3fbf8711f MM-68149: Upgrade to Go 1.26.2 (#36418)
* MM-68149: upgrade to Go 1.26.2

Update go directive in go.mod and .go-version.

* MM-68149: replace pointer helpers with Go 1.26 new()

Go 1.26 extends the built-in new() to accept an initial value expression,
making typed-pointer helpers like model.NewPointer(x), bToP(x), and boolPtr(x)
redundant. Replace every call site with new(x) and remove the now-unused
helper functions and their //go:fix inline directives.

* MM-68149: apply go fix for reflect API and format-string changes

- reflect.Ptr → reflect.Pointer (renamed in Go 1.18, deprecated alias removed in 1.26)
- reflect range-over-struct: for i := 0; i < t.NumField(); i++ → for field := range t.Fields()
  and the equivalent for Methods() and interface types
- Fix format-string concatenation and variadic-arg mismatches flagged by go vet

* MM-68149: update JPEG fixtures and test infrastructure for Go 1.26 encoder

Go 1.26 ships a new image/jpeg encoder that produces slightly different output.
Regenerate all JPEG fixture files and switch the comparison helpers from
byte-equality to pixel-level comparison with a small per-channel tolerance,
so minor encoder drift across patch versions is handled automatically.

Add -update-fixtures flag to make it easy to regenerate fixtures after future
major Go upgrades. Document the update procedure in tests/README.md.

* MM-68149: CI check that go fix ./... produces no changes

* Fix real bugs flagged by CodeRabbit review

- group.go: set newGroup.MemberCount not group.MemberCount (member count
  was populated on the wrong variable and lost before publish/return)
- file_test.go: guard compareImage(GetFilePreview) on the preview slice
  length, not the thumbnail slice length (copy-paste error)
- config_test.go: remove duplicate MinimumLength assignment

* fixup! Fix real bugs flagged by CodeRabbit review
2026-05-12 15:59:12 +00:00
Jesse Hallam f3ec71f25f wrap instead of embedding sqlx.DB (#36510)
* wrap instead of embedding sqlx.DB

Expose helper methods that route to sqlx.DB instead of requiring callers
to sometimes access .DB directly for themselves. (We could rename this
to `.db` to emphasize that it's internal, but some callers legitimately
do need it, and it's all the same package so the diff just gets
  noisier.)

This change continues to improve our ability to avoid queries without
configured timeouts, but does not yet directly address the original
report.

Relates-to: https://mattermost.atlassian.net/browse/MM-68332

* fix contradictory comment on sqlxTxWrapper.Query

* rename DB→db and Tx→tx; add DB() accessor

Make the sqlx.DB and sqlx.Tx fields unexported within the package to
signal they are internal, while exposing a DB() method for the cases
where callers need the sqlx handle directly.

* pipe passthrough errors through checkErr for offline detection

Ensure network failures in Query, ExecContext, and QueryContext methods
mark the replica offline, consistent with all other wrapper methods.

* fix config test: DB is now a method, not a field
2026-05-12 15:22:37 +00:00
Doug Lauder 0530d60e4a MM-68722 - Set higher statistics target on posts.rootid and posts.channelid (#36506)
* MM-68722 - Set higher statistics target on posts.rootid and posts.channelid

  The default PostgreSQL column statistics target (100) is too coarse for
  posts.rootid: most rows have an empty rootid and the remainder is a long
  tail of distinct thread IDs, so the planner cannot accurately estimate
  selectivity for a specific non-empty rootid. As a result, queries like
  the per-thread MAX(createat) issued from updateThreadsFromPosts get
  planned to scan idx_posts_create_at backward and filter by rootid,
  scanning tens of millions of rows per call instead of using the
  idx_posts_root_id_delete_at index. This was observed during a large DM
  import where 16 workers were each stuck on the same query for 20 to 70
  seconds, dropping import throughput from a few thousand posts per minute
  to ~155 per minute.

  Raising the statistics target to 5000 on rootid (and on channelid for
  the same reason) lets ANALYZE record enough most-common-values that the
  planner correctly recognizes specific non-empty values as highly
  selective and picks the right index. Verified locally: post-fix the
  query runs in 0.058 ms with 11 buffer hits, vs 20725 ms with 25.8M
  buffer hits before.

  This is a metadata-only change. ALTER TABLE ... SET STATISTICS takes a
  SHARE UPDATE EXCLUSIVE lock and does not block concurrent DML.

* MM-68722 - Run ANALYZE in migration so new statistics target takes effect immediately

  ALTER TABLE ... SET STATISTICS only changes the target; the planner keeps
  using the existing sample in pg_statistic until ANALYZE runs. On a busy
  Posts table autoanalyze fires after ~10 percent of rows change, and on a
  quiet site it may not fire at all before the operator runs the import that
  motivates this change. Running ANALYZE in the migration ensures the new
  sample is collected at deploy time rather than at an indeterminate later
  point.

  Scoped to (rootid, channelid) since those are the only columns whose
  statistics target changed. Both are gathered in a single table scan.
  ANALYZE takes SHARE UPDATE EXCLUSIVE and does not block DML. No
  corresponding change in the down migration: resetting the target to -1
  leaves the existing samples valid for continued planner use, so no
  rollback-time ANALYZE is needed.
2026-05-12 08:45:11 -04:00
Doug Lauder 69f30c21e9 MM-68708 - Fix TestCreatePost shared DM/GM subtests when env pins feature flag (#36488)
The two shared DM/GM subtests in TestCreatePost rely on
  FeatureFlags.EnableSharedChannelsDMs being false, but the config store
  reapplies MM_FEATUREFLAGS_* env overrides on every Set, so an
  UpdateConfig pin gets clobbered. Use t.Setenv to force the flag false
  for the duration of each subtest, and drop the parent's
  mainHelper.Parallel(t) so Go's "no Setenv under a parallel ancestor"
  rule is satisfied. Sibling subtests still parallelize via their own
  mainHelper.Parallel(t) calls.
2026-05-12 08:43:57 -04:00
6083cc2282 MM-68196 Adding Global Classification configuration and banners (#36231)
* Add Classification Markings admin console page

Adds a new admin console page under Site Configuration for managing
classification markings. This allows system administrators to define
classification levels (e.g., UNCLASSIFIED, SECRET, TOP SECRET) with
associated colors and rank ordering, which will be used for system-wide
and per-channel classification banners.

The page includes:
- Enable/disable toggle backed by the property field system (field
  existence = enabled)
- Country preset dropdown (US DoD, NATO, UK GSCP, Canada, Australia
  PSPF) that auto-fills standard classification levels
- Editable classification levels table with drag-and-drop reorder,
  inline text editing, color picker, and delete
- Auto-switch to "Custom" preset when levels are manually modified
- Confirmation dialog when switching presets would overwrite custom data

Also adds:
- ClassificationMarkings feature flag (default off)
- Generic property field client methods (get/create/patch/delete) for
  the /api/v4/properties/ endpoints
- Enterprise license + feature flag gating on the admin page

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

* Fix classification markings: add validation, error handling, and system object type

- Add "system" as a valid property field object type so the
  classification markings API calls succeed
- Surface load errors instead of silently swallowing them (only
  suppress 404 for unconfigured state)
- Validate before save: require at least one level, non-empty names,
  and no duplicates
- Default to custom preset with empty levels on first open
- Add section strings to searchableStrings for admin console search

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

* Move classification field to CPA group targeting users

Store the classification markings property field in the
custom_profile_attributes group with object_type 'user' instead of the
attributes group with object_type 'system'. Clear target_id for PSAv2
system target compliance and mark the field as admin-managed.

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

* Stabilize preset option IDs and add danger warning on preset switch

Hardcode deterministic IDs for all preset classification levels so
switching away and back preserves option IDs, preventing orphaned
property values. Compare only level data (not preset label) for change
detection so cosmetic preset switches don't trigger false save states.

Show a danger modal with red confirm button when changing presets on an
existing field, warning about system-wide impact on classified resources.
The warning appears once per session then allows frictionless switching.

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

* Remove system object type from property fields

Not needed yet — will be added when system/channel banners are implemented.

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

* Fix ESLint errors in classification markings admin page

Fix import ordering and remove unused generateId import.

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

* Address CodeRabbit review feedback for classification markings

- Register property field API endpoints when ClassificationMarkings flag
  is enabled (not just IntegratedBoards) to prevent 404s
- Preserve preset option IDs when creating a new classification field
  instead of blanking them with empty strings
- Add sysconsole read/write permission constants for classification
  markings across server and webapp, and wire up resource-level
  permission checks in the admin definition

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

* Add rank attribute to classification marking options

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

* Add classification markings permissions migration and read-only support

Add a permissions migration to grant classification markings sysconsole
permissions to existing roles on upgrade. Wire up the disabled prop so
read-only users can view but not edit classification settings. Register
the permission in the Delegated Granular Administration UI.

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

* Paginate loadField to find classification field beyond first page

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

* Fix lint errors and warnings in classification markings

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

* Remove classification markings sysconsole permissions; gate on sysadmin instead

Classification markings admin page no longer uses feature-specific
read/write permissions. Visibility is gated on license + feature flag,
editing is gated on system admin role. This avoids coupling
feature-specific permissions to the generic property service.

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

* Set sysadmin-level permissions on classification markings field creation

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

* Use stable IDs instead of array indices for classification level operations

Switch updateLevel/deleteLevel to identify levels by ID rather than
index, sort levels by rank on load, and extract i18n strings.

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

* Refactor classification markings into extracted helper functions

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

* Add tests for classification markings admin console feature

Add unit and component tests covering:
- Pure function tests for detectPreset, optionsToLevels, levelsToOptions,
  processClassificationField, and fetchClassificationField pagination logic
- React component tests for rendering states, validation, and user interactions
- Client4 property field method tests for URL construction and HTTP verbs
- Server routing test verifying routes register with ClassificationMarkings flag
- Feature flag default and serialization test

Export pure functions from classification_markings.tsx to enable direct testing.

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

* Fix lint errors in classification markings tests

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

* Fix test compilation error

* Fix color input auto-filling after 3 hex characters in classification markings

Buffer ColorInput onChange in a LevelColorCell wrapper so the table
doesn't re-render mid-typing, preventing the input from losing its
focus-guarded local state.

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

* Fixing style issues with color picker z-index

* Added fix to prevent immediate dismissal when clicking inside color picker

* Adding E2E test suite for configuration

* Removing duplicates

* Fixing unrelated linter error

* Fixing test linting issues

* Updating tests to skip appropriately

* Matching configuration to UX specs

* Fixing style lint

* Added informational banner for presentational nature of markings

* Enabling the markings flag on playwright server

* Added missing feature flag to e2e test environment in ci

* Reverting changes to color_input

- Not needed as we're using a custom component

* Added and polished global banner configuration

* Refactoring webapp for readability

- Separating components
- Adding unit tests
- Isolating helper methods into utilities

* Fixing linter errors

* linter fix

* Manually fixing linter issues

* Separating global classification component

* Added persistence of classification marking configuration

* Changing LevelID with LevelName

* Making changes for PR reviews

* Changing property object of classification field to template

* syncing i18n file

* Removing inaccurate note from comments

* PR fixes for UX review

* Cleaning up unused value

* Added GlobalClassificationBanner component

- Made sure it syncs on change by using normal configuration values on it
- Works with "top" and "top_and_bottom"
- Renders on both root and admin_console

* Adding E2E test cases for global classification

* Linter fixes, i18n extract

* PR Fixes

* Linter fix

* Matching default messages

* Fixing type errors

* Fixing pipeline and runtime errors

* Fixing announcementbar rendering on top of global classifications

* Increasing banner & font sizes

* Fixing font size to 12px instead of 16px

- I read it wrong

* Replacing config values with property

* Test linter fixes

* Fixing type errors and go format error

* Making changes needed to align with specs

- Ensuring system_classification is a separate linked property that differs from the template
- Saving the global classification banner values as a propertyvalue

* Added missing arguments in e2e tests

* Added missing conditions for useEffect

- Also fixing E2E error in pipeline

* Fixing issues with V1 and V2 group mismatch

* Fixes for linter errors and coderabbit review

* Addressing more issues found by coderabbit

* Fixing issues found by coderabbit

* Migrating to use system properties

* Ran all linters and prettier

- Resolving coding style drift that happened from not running prettier on the webapp (even though CI doesn't check for this)

* Undoing the prettier changes in webapp

* Cleaning up unwanted autoformatted changes

* Reverting prettier changes to clean diff

* Fixing E2E test

* Import fixes in test

* Applying changes for PR feedback

* Fixing issues with failing e2e tests

* Changing key of selection from name to id

* Replacing field setup in E2E tests to use levelId instead of levelName

---------

Co-authored-by: David Krauser <david@krauser.org>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: David Krauser <david@kruser.org>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-11 21:35:38 +03:00
Jesse Hallam 91f8297c93 store: use noTimeoutContext for schema dump queries (#36502) 2026-05-11 18:57:35 +02:00
Maria A NunezandCursor Agent 5bdf2b6633 Fix flaky TestCreatePost upload_file subtests (#36453)
* Fix flaky TestCreatePost upload_file subtests

The tests removed upload_file from the global channel_user role, which races
with other parallel api4 tests, and (for channel-scoped schemes) could not
actually revoke upload_file because non-moderated permissions merge from the
higher-scoped team channel role.

Use an isolated team with its own team scheme, revoke upload_file on that
scheme's channel user role, re-login after subtests that clear the client
session, and align UpdatePost file-permission cases.

Tests-only change. Verified with ENABLE_FULLY_PARALLEL_TESTS=true go test
-run '^TestCreatePost$' -race -count=100 ./channels/api4/...

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

* Add no-scheme TestCreatePost/TestUpdatePost upload_file subtests

The team-scheme isolation introduced earlier in this PR fixed the
flakiness that came from mutating the shared channel_user role from
parallel api4 tests, but it changed the scope under test from the
global default scheme to a team scheme. The original test from PR
#34538 was validating the no-scheme path, where the user's effective
channel role is the built-in channel_user.

Restore that coverage by adding two new subtests that isolate at the
channel-member level instead of at the role level. The new helper:

  * creates a fresh channel inside th.BasicTeam (no team scheme, no
    channel scheme);
  * creates a unique non-scheme-managed role with channel_user's
    default permissions minus upload_file;
  * sets SchemeUser/SchemeAdmin/SchemeGuest=false and ExplicitRoles
    to the custom role on the user's membership via the store, so the
    built-in channel_user role is not merged into the effective role
    set;
  * invalidates GetAllChannelMembersForUser cache so
    SessionHasPermissionToChannel sees the new roles.

upload_file is PermissionScopeChannel and is not granted by team_user,
team_admin, system_user, etc. (only by channel_user/channel_guest), so
removing it from the channel-member role is sufficient to deny the API
request without touching any process-shared state. The new subtests
are therefore safe under ENABLE_FULLY_PARALLEL_TESTS=true.

This keeps the team-scheme deny subtests added earlier in the PR as
extra coverage for the team-scheme path.

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

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-05-11 11:46:55 -04:00
bbbfc019a9 Replace bild imaging library with boxes-ltd/imaging (#36261)
* Add benchmarks for image transformation functions

Covers GeneratePreview, GenerateThumbnail, GenerateMiniPreviewImage,
FillCenter, and MakeImageUpright — all functions that delegate to the
external imaging library. Baseline before/after the library swap.

* Replace anthonynsimon/bild with boxes-ltd/imaging

Reverts the imaging library swap from PR #29657 (disintegration/imaging →
anthonynsimon/bild) and replaces it with github.com/boxes-ltd/imaging, the
maintained fork of disintegration/imaging with the same API.

- emoji.go: restore direct use of imaging.Fit with Lanczos filter
- orientation.go: restore FlipH/FlipV/Rotate90/180/270/Transpose/Transverse
- preview.go: restore imaging.Resize calls with Lanczos filter
- utils.go: remove bild-based Resize/Fit/CropCenter helpers; restore the
  original FillCenter using imaging.Fill
- Remove tests and test images added for the bild-specific helpers
- Regenerate orientation test expected images (2,3,6,7,8) and GIF thumbnail
  to match boxes-ltd/imaging output

https://claude.ai/code/session_012f5wLSCRQrQeRraPj282sT

* gofmt imaging package files

* Add tests and fixtures for the boxes-ltd/imaging-backed functions

Cover MakeImageUpright across all 8 EXIF orientations, GeneratePreview,
GenerateMiniPreviewImage, and FillCenter against fixtures regenerated
under the new library.

* Contain boxes-ltd/imaging behind the local imaging wrapper

Add a Fit wrapper alongside FillCenter and route emoji.go through the
local package so boxes-ltd/imaging is only imported from
channels/app/imaging.

* Add TestFit to cover the local Fit wrapper

Dimensional checks for the Fit wrapper used by emoji.go, mirroring
TestGenerateThumbnail. Pixel correctness is covered by the upstream
boxes-ltd/imaging tests; this guards against wrapper-level mistakes
(transposed args, wrong filter).

* Address CodeRabbit nits in TestFillCenter and TestFit

Decode a fresh source image per TestFillCenter subtest so cases stay
isolated even if the wrapper ever mutates input. Rename the TestFit
"smaller than bounds (clone)" case to "no resize when smaller than
bounds" since the assertion only checks dimensions, not clone semantics.

* Revert per-subtest decode in TestFillCenter

imaging.Fill never mutates its input — it always returns a fresh
*image.NRGBA — so re-decoding the source for every subtest was
unnecessary work. Decode once, share across subtests.

* Compare decoded pixels instead of raw PNG/JPEG bytes in tests

image/png isn't byte-stable across Go versions, so comparing
re-encoded byte streams against checked-in fixtures is brittle to
toolchain bumps and re-encoding. Decode both sides and compare RGBA
pixels via a shared requireSameImage helper. Fixtures stay on disk
unchanged.

Covers TestFillCenter, TestGeneratePreview, TestGenerateMiniPreviewImage,
and TestMakeImageUpright. TestFillImageTransparency is left alone since
its byte-comparison pattern predates this branch.

* Report total diff rate when requireSameImage fails

Walk every pixel before failing instead of stopping at the first
mismatch. The failure message now includes how many pixels differ, the
percentage of the image that's off, and the first divergence.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-11 12:56:51 +00:00
Andre Vasconcelos 068e15f31c Bumping version of prepackaged github plugin to v2.7.1 (#36482) 2026-05-11 15:27:58 +03:00
Caleb RoselandandMattermost Build 7e1bec4d4f MM-68233: Fix sidebar icon not updating on channel privacy conversion via WS (#36006)
* fix(channels): update sidebar icon when channel converted via mmctl

The handleChannelConvertedEvent WebSocket handler hardcoded
type as PRIVATE_CHANNEL, so private-to-public conversions were
silently ignored. Now the server includes channel_type in the
channel_converted WS event and the frontend reads it.

Ref: MM-68233

* test(channels): add tests for channel_converted WS event

Add server-side tests verifying the WebSocket event payload includes
channel_type for both private→public and public→private conversions.
Add frontend tests for handleChannelConvertedEvent covering both
conversion directions, backwards compatibility fallback when
channel_type is absent, and edge cases.

Ref: MM-68233

* test(channels): add E2E test for channel privacy WS icon update

Playwright E2E tests verify that the sidebar channel icon updates
in real-time when channel privacy is changed via the API (simulating
mmctl). Tests both public→private and private→public directions.

Ref: MM-68233

* refactor: review feedback on channel_converted fix

Narrow channel_type WS field to 'O' | 'P' union type instead of
string. Drop hardcoded channel names in E2E tests to let
pw.random.channel() generate unique names and avoid collisions.

Ref: MM-68233

* fix(e2e): provide name + unique flag for channel creation

pw.random.channel() requires a name field — server rejects channels
without a valid lowercase alphanumeric name. Use unique: true to
append a random suffix for test isolation.

Ref: MM-68233

* refactor(channels): use channel type constants in channel_converted code

Address review feedback: replace inlined 'O'/'P' string literals with
predefined constants. websocket_messages.ts now types channel_type as
ChannelType (already imported); websocket_actions tests use
Constants.OPEN_CHANNEL / Constants.PRIVATE_CHANNEL.

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-11 13:37:19 +02:00
Daniel Espino García 55496c07c1 Update API docs (#36302)
* Update API docs

* Coderabbit comments

* Address feedback

* Address feedback

* Coderabbit feedback
2026-05-11 12:29:25 +02:00
Alejandro García Montoro ef8a8cf2cb Add Azurite to test infrastructure (#36485)
Wire Microsoft's local Azure Blob Storage emulator into the dev/CI
docker-compose stack so an upcoming Azure FileBackend driver and its
tests have a target to run against, without requiring an Azure
account.

* Define the azurite service in docker-compose.common.yml (used by
  both the local-dev and CI compose files).
* Add azurite to the makefile + main docker-compose.yml service maps,
  and to docker-compose-generator so callers can include it via
  ENABLED_DOCKER_SERVICES.
* Auto-include azurite in `make start-docker` (mirrors how minio is
  auto-included today), so existing local workflows keep working
  without any per-developer config change.
* Add azurite to the CI start_dependencies wait set so CI brings it up
  alongside postgres/minio.
* Set CI_AZURITE_HOST / CI_AZURITE_PORT in dotenv/test.env, mirroring
  the CI_MINIO_* pattern.

No production filestore changes — this PR is mergeable in isolation
with no user-visible behavior.

------
AI assisted commit
2026-05-11 12:06:59 +02:00
Felipe Martin 0afef7760c Include connection ID in plugin context (#36074)
* feat: include connection id in the plugin context

* refactor: group ConnectionId next to SessionId in plugin Context

Addresses review feedback to keep related identifier fields adjacent.

* fix(files): forward Connection-Id on file uploads to plugin hooks

The webapp uploadFile XHR didn't attach the Connection-Id header, so
FileWillBeUploaded plugin hooks always received an empty ConnectionId.
Read it from the websocket selector and set it on the request, matching
how drafts and channel bookmarks already do it. Adds a server-side test
asserting the connection id propagates through pluginContext.

* fix(lint): reorder file_actions imports to satisfy import/order

* Document ConnectionId on request.Context
2026-05-11 10:09:47 +02:00
yasser khan b052f3463a E2E/Playwright: balance shard timing by enabling fullyParallel in CI (#36054) 2026-05-08 21:04:32 +00:00
Doug Lauder 5504435231 MM-68705 - Order-tolerant Shared Channel plugin API's for receiving attachments (#36486)
* MM-68705 - Make ReceiveSharedChannelAttachmentSyncMsg order tolerant

  Allow plugin remotes to invoke ReceiveSharedChannelAttachmentSyncMsg
  and ReceiveSharedChannelSyncMsg in either order without losing the
  post/file binding, and make repeat deliveries of the same file id
  idempotent.

  Two localised changes inside ReceiveSharedChannelAttachmentSyncMsg:

  - Idempotency check before CreateUploadSession. If a FileInfo with
    the sender's id already exists for the same channel and creator,
    return it instead of inserting a duplicate (which would violate
    the FileInfo PK and force the caller to retry indefinitely after
    any transient ack failure). A mismatched channel or creator on the
    same id is rejected.

  - Lazy bind to post after UploadData. When the matching post is
    already present (post-then-file ordering), CreatePost will have
    stripped the unmatched file id from Post.FileIds; AttachToPost
    plus Post.Overwrite restore the binding. When the post is not
    yet present (file-then-post ordering), the FileInfo is left
    unbound so the eventual post arrival's CreatePost path binds it.
    The post is fetched first because AttachToPost is a blind UPDATE
    that does not validate post existence, so calling it before the
    post exists would orphan the FileInfo.

  No other paths change. Cluster (non-plugin) shared-channel attachments,
  ReceiveSharedChannelProfileImageSyncMsg, and UI uploads are unaffected.

  New tests in shared_channel_test.go cover both orderings, repeated
  receive success and rejection on channel/creator mismatch, and the
  empty-PostId no-op.
2026-05-08 15:35:52 -04:00
Harshil Sharma 56089922e3 Data spillage report generation (#36339)
* Added base fr report generation

* WIP

* Refactoring and cleanup

* lint fixes, added new tests

* test fix

* Several improvements

* Addressed some security enhancements

* Created zip writer entery later

* Improved a test to check for file content

* Improved error handling

* Made a geneeric function

* accepting comment in report API

* Removed an unnecessary check

* Made a geneeric function

* Made the comment body not required and updated API docs
2026-05-08 09:27:13 -04:00
7795766aa2 Update user active endpoint (#36469)
* api4: block user managers from toggling bot active status without bot permissions

The PUT /api/v4/users/{id}/active endpoint only checked for
PermissionSysconsoleWriteUserManagementUsers and a special-case for system
admins, but had no equivalent guard for bot accounts.

This meant a User Manager with edit access to users but no access to
Integrations could deactivate (or reactivate) bot accounts — causing the
bot's sessions to be revoked and its tokens invalidated — even though every
dedicated bot endpoint (/api/v4/bots/{id}/disable, /enable, PATCH, etc.)
requires the caller to pass SessionHasPermissionToManageBot.

Fix: after fetching the target user and running the system-admin guard, add
an equivalent check for IsBot that delegates to the same
SessionHasPermissionToManageBot helper used by all other bot endpoints.

Fixes MM-68686

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

* api4: assert bot is inactive after deactivation in test

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

* api4: pin bot-deactivation test assertion to 404 and clarify comment

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

* api4: use require.Zero for DeleteAt assertion in bot test

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

* api4: fix err shadow in updateUserActive bot permission check

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-08 10:00:53 +00:00
Doug Lauder 97f0ad7c3b [MM-68697] Preserve sender file ID in plugin-relayed shared channel attachments (#36468)
Set ReqFileId on the UploadSession created in
  ReceiveSharedChannelAttachmentSyncMsg so the attachment is persisted
  under the sender's file ID. Without this, the receiving server stored
  the bytes under a freshly generated ID while the synced post's FileIds
  still referenced the sender's ID, leaving the attachment invisible in
  the UI even though the file and FileInfo row existed on disk.

  Mirrors the existing cluster-to-cluster path in
  platform/services/sharedchannel/attachment.go.

  Also tightens TestPluginAPIReceiveSharedChannelAttachmentSyncMsg to
  pass a sender-side fi.Id and assert the saved FileInfo keeps it. The
  prior assertion only checked that some ID was assigned, which the
  buggy code also satisfied.
2026-05-07 21:04:10 -04:00
Ibrahim Serdar Acikgoz d1a4d74b42 MM-68589 add ever-member history lookup for ABAC sync (#36429) 2026-05-07 18:48:36 +00:00
Devin BinnieandMattermost Build 69fbaeced9 [MM-68496] Feature flag Managed Categories, expose Default Category Name to UI for channel creation and settings (#36289)
* [MM-68496] Feature flag Managed Categories, expose Default Category Name to UI for Channels

* PR feedback

* PR feedback

* Fix i18n

* Fix test

* Fix E2E

* Merge'd

* Add tests

* Re-add old tests (skipped)

* Add IncrementVersion to PropertyGroup store, increment version on managed category group

* Fix lint

* Fix mock

* Fix prettier

* Add tests

* Fixed issue when moving from existing category to existing category

* Fix e2e

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-07 09:37:53 -04:00
d9b0e27fe8 Fix flaky TestPluginAPIUpdateUserPreferences (#36458)
GetPreferencesForUser returns default preferences in non-deterministic order
under the race detector and Postgres, but the test asserted fixed slice
indices. Look up each default preference by category instead.

Tests-only change. Verified with go test -run '^TestPluginAPIUpdateUserPreferences$' -race -count=100 ./channels/app.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>
2026-05-07 09:16:36 -04:00
nang2049andNevyana Angelova b1e444274f Update jira prepackaged version (#36442)
Co-authored-by: Nevyana Angelova <nevyangelova@192.168.100.47>
2026-05-07 13:15:57 +00:00
Maria A NunezandCursor 5da794aba0 MM-68543 Invalidate active WebConn session cache on global session revocation (#36332)
* MM-68543 Invalidate active WebConn session cache on global session revocation

Mirrors the per-user revocation pattern (ClearUserSessionCache ->
ClearSessionCacheForUserSkipClusterSend -> hub fan-out) for the global
revocation path so that ClearAllUsersSessionCache invokes the same
local-side primitive on the originating node as the cluster handler
runs on remote nodes. Also covers single-node deployments where the
cluster broadcast was previously the only trigger of the WebConn
invalidation.

Adds a Hub.InvalidateAll fan-out primitive on the websocket hub and
two contract tests covering the SkipClusterSend variant and the
production RevokeSessionsFromAllUsers entry point.

Made-with: Cursor

* MM-68543 Restore error propagation on ClearAllUsersSessionCache

The previous commit moved the local-side work into
ClearSessionCacheForAllUsersSkipClusterSend, which returned no error,
so ClearAllUsersSessionCache started always returning nil even when the
underlying session-cache purge failed.

Make the helper return the cache-purge error and propagate it back
through ClearAllUsersSessionCache, restoring the historical error
contract for callers (RevokeSessionsFromAllUsers,
App.ClearSessionCacheForAllUsers, TestCache). The hub fan-out and the
cluster broadcast still run unconditionally so security invalidation
happens even on local-purge failure.

Made-with: Cursor

* MM-68543 Trim comments and fix unchecked errcheck on App wrapper

Address review feedback: trim verbose comments across the touched
files and check the error returned by ClearSessionCacheForAllUsersSkipClusterSend
in the App-level wrapper to fix the golangci-lint errcheck failure
introduced when the helper started returning an error.

Made-with: Cursor

* MM-68543 Wipe channel routing index instead of rebuilding it on global revoke

Rebuilding byChannelID per user via InvalidateCMCacheForUser issues a
GetAllChannelMembersForUser DB query for every user with a live conn
on the hub, which is wasted work when those conns have just been
invalidated. Replace it with a single clear() of byChannelID, hidden
behind a small clearChannels() helper. Index entries repopulate
naturally as conns re-handshake or fully reconnect.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-07 09:01:36 -04:00
Daniel Espino García 870c2c6b13 Add root id to webhooks (#36415)
* Add root id to webhooks

* Address feedback

* Address coderabbit comment

* Error if root post is not really a root post

* Fix test

* Address nitpick
2026-05-07 14:09:32 +02:00
Jesse Hallam 7f161bb24c Lower default test console log level from stdlog to debug (#36455)
* Lower default test console log level from stdlog to debug

Suppresses trace-level log spam (e.g. MlvlNotificationTrace) that
flooded CI output. MM_LOGSETTINGS_CONSOLELEVEL still overrides for
local debugging.

* Fix TestEnvironmentVariableHandling to expect debug default

Updates the assertion to match the new default console log level.
2026-05-06 16:35:40 -03:00
6293e354c9 Translations update from Mattermost Weblate (#36404)
* Translated using Weblate (Polish)

Currently translated at 100.0% (3191 of 3191 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/pl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (7243 of 7243 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/pl/

* Translated using Weblate (Turkish)

Currently translated at 91.5% (6634 of 7243 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/tr/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.9% (7239 of 7243 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/zh_Hans/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (3191 of 3191 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/zh_Hans/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (7243 of 7243 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/zh_Hans/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (7243 of 7243 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/zh_Hans/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (3191 of 3191 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/zh_Hans/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (7243 of 7243 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/zh_Hans/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (3191 of 3191 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/zh_Hans/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (7243 of 7243 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/zh_Hans/

* Translated using Weblate (Turkish)

Currently translated at 93.3% (6763 of 7243 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/tr/

* Translated using Weblate (Polish)

Currently translated at 100.0% (7243 of 7243 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/pl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (7243 of 7243 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/pl/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 76.9% (5617 of 7303 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/nb_NO/

---------

Co-authored-by: master7 <marcin.karkosz@rajska.info>
Co-authored-by: Kaya Zeren <kayazeren@gmail.com>
Co-authored-by: Sharuru <mave@foxmail.com>
Co-authored-by: ThrRip <coding@thrrip.space>
Co-authored-by: Frank Paul Silye <frankps@gmail.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-06 15:35:52 +00:00
Andre Vasconcelos a6ea65b44b Bumping prepackaged gitlab version to 1.12.2 (#36430) 2026-05-06 16:25:23 +03:00
Guillermo VayáandClaude Opus 4.7 ecf8a741ac Add unread badge to Recaps sidebar link (#36246)
* Add unread badge to Recaps sidebar link

Shows the count of unread finished recaps (completed or failed) on the
LHS Recaps link. Pending and processing recaps are excluded so the badge
only reflects work the user can actually read. When any unread recap has
failed, the badge is colored as an error to surface the failure.

The badge updates live through the existing recap_updated WebSocket
event, which refreshes the recap in the Redux store.

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

* Fix Recaps failed-badge color losing to active sidebar rule

The failed-badge modifier selector had the same specificity (0,4,0) as
`.channel-view .sidebar--left .active .badge` in _badge.scss, so when
the Recaps link was the active route the global mention background
color won on cascade order. Scope the rule with `#SidebarContainer` so
it wins on specificity (1 id + 4 classes) regardless of active state.

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

* Fix Recaps badge selector memoization

getUnreadFinishedRecapsBadge was keyed off getAllRecaps, which is not
memoized and returns a new array on every call. That broke reselect's
reference-equality input check, so the selector recomputed and returned
a fresh {count, hasFailed} object on every store dispatch — forcing
RecapsLink (always mounted when the feature flag is on) to re-render
on every action. Key the selector off state.entities.recaps directly
and iterate ids in the result function so memoization holds when the
recaps slice is unchanged.

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

* Address PR feedback on Recaps sidebar badge

- Pass shallowEqual to the useSelector consuming
  getUnreadFinishedRecapsBadge. The selector returns a plain
  {count, hasFailed} object, so recap updates that change a recap
  but leave the badge values the same (e.g. marking a read recap)
  would otherwise force RecapsLink to re-render.
- Scope the "no badge" negative assertion to the render container so
  it only asserts on the badge element, not any '1' or '.badge'
  elsewhere in the DOM.

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

* Address UX feedback on Recaps sidebar badge

- Add `unread` class to the sidebar item and `unread-title` to the
  link when there are unread recaps so the label goes bold and the
  icon goes full-opacity, matching how channels and the threads link
  indicate unread state.
- Keep the badge (and the new failed icon) visible on hover so it
  doesn't disappear under the cursor -- same override the threads
  link uses.
- Replace the red failed-badge modifier with an amber alert icon
  rendered in place of the count badge when any unread recap has
  failed. Red mention badges are reserved for urgent priority
  messages and caused confusion here.

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

* Keep Recaps badge in place on hover

The global sidebar hover rule shrinks padding-right from 16px to 5px
to make room for the per-channel menu button, which shifted the badge
right since it stays visible. Restore padding-right: 16px on hover for
the Recaps link, matching what the threads link already does.

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

* Align Recaps failed-icon aria-label with tooltip

The aria-label on the .RecapsFailedIcon span was a hardcoded English
string ("Recap failed") that differed from the tooltip shown to
sighted users ("One or more recaps failed"). Derive the aria-label
from the same intl message used by the tooltip so screen readers and
sighted users get the same wording and the label is localized.

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

* Stop Recaps link from overriding global unread label styling

The combined `.active .SidebarLink, .SidebarLink.unread-title` rule
pushed font-weight: 400 onto .SidebarChannelLinkLabel with specificity
(0,4,0), overriding the global `.SidebarChannel.unread` rule that sets
font-weight: 600 and --sidebar-unread-text at (0,3,0). As a result the
Recaps label rendered at normal weight when unread, inconsistent with
channels and the threads link. Split the rules: keep the active-state
overrides as they were, and limit the unread-title rule to the
icon-specific styling Recaps actually needs, letting the global unread
styling apply to the label.

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

* Add i18n entry for Recaps failed-tooltip

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

* change size of alert icon

* fix the right icon

* Add ViewedAt to recaps and POST /recaps/mark_viewed endpoint

Introduce a new ViewedAt field on Recap, separate from ReadAt, that
tracks whether the user has at least seen a finished recap on the
recaps page. ReadAt keeps its existing per-recap "Mark read" semantics.

- New Postgres migration 000172 adds the ViewedAt column (default 0)
  and an idx_recaps_user_id_viewed_at index mirroring the existing
  ReadAt index.
- New store method MarkRecapsAsViewed(userId, statuses) does a single
  UPDATE ... WHERE ViewedAt = 0 AND Status IN (...) RETURNING Id so
  the app layer can fan out one WS event per affected recap.
- New App.MarkRecapsAsViewed(rctx) marks the user's not-yet-viewed
  completed/failed recaps and broadcasts WebsocketEventRecapUpdated
  per affected id.
- New POST /recaps/mark_viewed handler. Registered before the
  {recap_id} regex routes so mark_viewed isn't captured as an id.
- RegenerateRecap now resets ViewedAt = 0 so a regenerated recap is
  surfaced again in the badge once it completes. As a related fix,
  UpdateRecap now persists ReadAt and ViewedAt -- previously it
  silently dropped the ReadAt = 0 reset that RegenerateRecap was
  setting in memory.

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

* Mark recaps as viewed when the recaps page mounts

Wire the new server endpoint into the webapp:

- Recap type now includes viewed_at: number.
- Client4.markRecapsAsViewed posts to /recaps/mark_viewed.
- New markRecapsAsViewed redux action, fired alongside getRecaps and
  getAgents in the recaps page mount effect. The server broadcasts
  recap_updated per affected recap so other tabs/devices receive the
  update through the existing handleRecapUpdated WS handler -- no new
  client-side handler needed.
- getUnreadFinishedRecapsBadge now filters on viewed_at === 0 instead
  of read_at === 0, so the sidebar badge clears on page open instead
  of requiring per-recap "Mark read" clicks. Selector tests updated to
  match.

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

* Address review feedback on Recaps viewed_at change

- Defer markRecapsAsViewed until after getRecaps resolves on the
  recaps page mount. Previously they ran in parallel, so getRecaps
  could land last and overwrite the viewed_at: <now> timestamps the
  WS-driven refresh had just written, briefly re-showing the badge.
- Switch the markRecapsAsViewed audit log to LevelContent and record
  the affected ids as result state, matching the pattern of every
  other mutating recap handler (markRecapAsRead, deleteRecap, etc).
  recap_count meta is now recorded unconditionally.
- Add an app-layer test that asserts MarkRecapsAsViewed publishes a
  recap_updated websocket event for each affected recap. The fan-out
  is the entire reason this lives in the app layer, so a regression
  removing the publish loop should fail loudly.
- Add a store-layer regression test that UpdateRecap actually
  persists ReadAt = 0 / ViewedAt = 0 resets, guarding the regenerate
  flow against a future change that drops those columns from the
  update map.

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

* Update migrations.list for 000172_add_recaps_viewed_at

Regenerated via `make migrations-extract` so the autogenerated
sequence list includes the new recaps ViewedAt migration files.

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

* Use AddMeta for Recaps mark_viewed audit ids

AddEventResultState takes a model.Auditable, not a plain map[string]any,
so the previous attempt to record the affected ids did not compile.
Record them as audit metadata instead, matching the pattern used by
getRecaps which similarly returns a slice and uses AddMeta only.

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

* Split Recaps ViewedAt index into a CONCURRENTLY migration

The lint check rejects bare CREATE/DROP INDEX in migrations because
they take an ACCESS EXCLUSIVE lock and block DML. Split the index off
into 000173 with CONCURRENTLY + the morph:nontransactional directive,
matching the pattern used by 000168/000169 (LinkedFieldID column +
its index). 000172 keeps just the ALTER TABLE ADD COLUMN, which can
stay transactional.

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

* Add viewed_at to existing Recap test fixtures

The Recap type now requires viewed_at, so the fixtures in
recap_item.test.tsx, recap_processing.test.tsx, and recaps_list.test.tsx
need it too. CI was rejecting them with TS2741.

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

* Mock markRecapsAsViewed in recaps.test.tsx

The mount effect now also dispatches markRecapsAsViewed, but the
manual jest.mock for 'mattermost-redux/actions/recaps' only exposed
getRecaps, so the runtime call resolved to undefined and crashed
with "markRecapsAsViewed is not a function". Add the missing entry
to the mock.

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

* Add /recaps/mark_viewed and Recap.viewed_at to OpenAPI spec

The recap-spec validator rejected the new POST /api/v4/recaps/mark_viewed
handler because it had no documented operation. Add the path with its
MarkRecapsAsViewed operationId, response shape, and behavior, and add
the new viewed_at timestamp field to the Recap schema in definitions.

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

* Fill in app.recap.mark_viewed.app_error translation

The new MarkRecapsAsViewed app method references this i18n key but the
en.json entry was added with an empty translation.

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

* Skip markRecapsAsViewed when getRecaps fails

Marking recaps as viewed implies the user just looked at them. If
getRecaps fails the user is staring at an error/empty state, so we
shouldn't ack them on the server. Gate the dispatch on the thunk's
result.error -- the codebase's bindClientFunc swallows errors and
returns {error}, so the conventional try/catch pattern doesn't apply
here.

Update the recaps.test.tsx dispatch mock to return a resolved promise
so the new awaited result has the expected shape.

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

* Clear and assert markRecapsAsViewed mock in recaps.test.tsx

Reset the new mock in beforeEach so it doesn't carry state across
tests, and assert that the mount effect dispatches markRecapsAsViewed
after getRecaps resolves. Awaiting via waitFor since the mark fires
inside an async fetchData chain.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 15:14:18 +02:00
Jesse Hallam 97c3b53873 MM-68532: default EnableSearchPublicChannelsWithoutMembership to true for new installations (#36399)
* MM-68532: default EnableSearchPublicChannelsWithoutMembership to true for new installations

* fix test: disable backfill in watcher tests to avoid mock store panic

* fix test: mock System store so backfill returns early in watcher tests

* MM-68532: add SetDefaults unit test for EnableSearchPublicChannelsWithoutMembership
2026-05-06 09:13:58 -04:00
unified-ci-app[bot] d5946e9477 Update latest minor version to 11.8.0 (#36437)
Automatic Merge
2026-05-06 08:23:38 +02:00
Jesse Hallam 71fb5b1e54 fix: tolerate concurrent update conflicts in content flagging migration (#36421)
When two servers race through doSetupContentFlaggingProperties
simultaneously (e.g. HA deployments or parallel CI tests sharing a DB),
both read the same UpdateAt timestamps for existing property fields and
both call UpdatePropertyFields. The store's optimistic concurrency
control causes the second writer to get ErrConflict. Since both servers
are writing identical expected values, tolerate the conflict the same
way the create path already does.

Adds a regression test that fires 5 concurrent goroutines at the
migration after fields already exist, verifying all succeed.
2026-05-06 07:36:01 +05:30
2b753c49f2 Remove unused GetChannelCounts store and app methods (#36351)
GetChannelCounts was only reachable from App and had no callers.
Remove SqlChannelStore implementation, store interface, timer/retry
layers, tests, mock, model.ChannelCounts, and the orphaned i18n key.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>
2026-05-05 10:04:36 -07:00