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>
* 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>
* 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.
* 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>
* 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
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.
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>
* add WithRPCErr hooks (server-facing/internal only)
* zero _returns on RPC failure in WithRPCErr companions
Aligns the WithRPCErr template with the HooksRPCErr godoc contract: when
g.client.Call returns a transport error, gob may have partially decoded the
reply. Reassign _returns to a zero value before destructuring so callers always
receive zeroed outputs alongside a non-nil transport error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* rename HooksRPCErr to HooksWithRPCErr for naming consistency
Every related symbol uses the WithRPCErr suffix (MessageHasBeenPostedWithRPCErr,
RunMultiPluginHookWithRPCErr, RunMultiHookWithRPCErr, etc.). Aligning the
interface name removes the only outlier and makes the convention uniform.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* rename rpcErrImpl to hooksWithRPCErrImpl
Mirrors the existing hooksImpl/Hooks naming pattern on hooksTimerLayer.
* add supervisor.HooksWithRPCErr() and drop runtime type assertion
The old path did rp.supervisor.Hooks().(HooksWithRPCErr) and handled the
"doesn't implement" branch — but that branch was structurally unreachable
(the compile-time `_ HooksWithRPCErr = (*hooksTimerLayer)(nil)` assertion
guards it).
Change supervisor.hooks from `Hooks` to the concrete `*hooksTimerLayer`
(which implements both interfaces, enforced at field assignment), add a
parallel HooksWithRPCErr() accessor, and call it directly. Hooks() keeps
its public Hooks-interface signature via implicit conversion at return.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* drop "implemented by" clause from HooksWithRPCErr godoc
Both hooksRPCClient and hooksTimerLayer satisfy the interface, and naming
implementations in interface godocs adds rot — the contract is what readers
need, not the list of wrappers.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* MM-68547: Tighten authorization on group syncable link and patch endpoints
Adds an additional permission check on the group syncable link and patch
endpoints. Callers must hold the role-management permission for the
target team or channel (or the sysconsole groups-management permission).
Made-with: Cursor
* Linting
* MM-68547: Extend group syncable scheme_admin authorization checks
Gate any explicit scheme_admin value (in either direction) on link and
patch. Populate SchemeAdmin in the singular getGroupSyncable so that
patches that do not touch scheme_admin no longer overwrite the persisted
value. Restrict PermittedSyncableAdmins to active syncables. Start the
link upsert from the existing active row to preserve fields the caller
did not, or could not, set.
Made-with: Cursor
* MM-68547: Add store-layer regression coverage for SchemeAdmin handling
Extend testGetGroupSyncable to round-trip SchemeAdmin: true through
UpdateGroupSyncable and re-fetch, locking in that getGroupSyncable
populates the field from the persisted row.
Strengthen groupTestPermittedSyncableAdmins{Team,Channel} to assert
that DeleteGroupSyncable preserves SchemeAdmin in the persisted row
and that PermittedSyncableAdmins still excludes the row, making the
coupling between the two store changes explicit.
Made-with: Cursor
* MM-68547: Fix group details role-change dedup on remove
The roleChangeKey helper was reading team_id/channel_id from the items
in itemsToRemove, but onRemoveTeamOrChannel pushes those items with a
generic id field. The deletion of the staged role change in
handleRemovedTeamsAndChannels therefore never matched the key produced
by onChangeRoles, and a stale patchGroupSyncable was dispatched after
the unlink.
Accept either id or team_id/channel_id when computing the key. Also
extend the e2e assertion to verify the channel removal took effect
(delete_at != 0) alongside the existing scheme_admin check.
Made-with: Cursor
* MM-68547: Mirror delete_at assertion on the removed-team e2e test
The team variant of "does not update the role of a removed X" was left
asserting only on scheme_admin. Add the matching delete_at != 0 check
already present in the channel variant so both tests verify the same
user-visible contract.
Made-with: Cursor
* Skip SyncSyncableRoles if no scheme_admin
* Implemented edit file permission
* lint fixes
* Updated snapshot
* Updated tests
* Updated test
* CI
* Permission reordering and tooltip text update
* Made a geneeric function
Bump the default prepackaged Agents plugin from 2.0.2 to 2.0.3 (non-FIPS).
FIPS prepackaged plugin package list is unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
* MM-68622: start inter-cluster services before plugin activation
Move startInterClusterServices from the end of Server.Start() to the
beginning, before Channels().Start() initializes plugins. This lets
plugins call shared channels APIs (ShareChannel, InviteRemoteToChannel,
UninviteRemoteFromChannel, UnshareChannel, UpdateSharedChannel,
CheckCanInviteToSharedChannel) during OnActivate instead of failing with
"Shared Channels Service is disabled".
Side-effect analysis:
* Plugin API gating: getSharedChannelsService in
channels/app/shared_channel.go:32 only requires the service to be
non-nil. The plugin-facing wrappers all pass ensureIsActive=false, so
Active() is bypassed. Once SetSharedChannelService runs, calls succeed
on both leader and follower nodes. This is the fix path.
* Multi-node leader timing: the enterprise cluster impl in
enterprise/cluster/cluster.go:70 initializes currentLeader="", so
IsLeader() returns false before StartInterNodeCommunication runs. The
immediate onClusterLeaderChange in scs.Start at
platform/services/sharedchannel/service.go:151 therefore takes the
pause path, which is a no-op since the service was never active. When
memberlist.Create fires NotifyJoin for the local node,
addPotentialLeader runs and InvokeClusterLeaderChangedListeners drives
the registered listener to resume() the sync loop on the elected
leader. End state matches the prior ordering.
* Single-node: IsLeader() returns true unconditionally per
channels/app/platform/cluster.go:33, so SharedChannelSyncHandler is
active during plugin OnActivate. Events emitted by plugins during
activation (posts to shared channels, DM creation) now flow through
sync where they were previously dropped. This is intended correctness,
not a regression.
* Transport and handlers: api4 remote-cluster routes are registered
before Server.Start, so HTTP handlers exist when rcs.Start runs early.
rcs and scs do not send cluster-broadcast messages during Start; they
only register topic listeners on the rcs transport, which is
independent of cluster gossip. registerClusterHandlers ordering is
unaffected.
* Config: scs reads only ConnectedWorkspacesSettings and the License at
construction, both stable from the initial config load. ReloadConfig
at server.go:912 has no bearing on inter-cluster service init.
Errors from startInterClusterServices remain logged and non-fatal,
matching prior behavior.
* Report POC
* Including more error logs
* Added localisationj for each reviewer
* Optimisations
* Minor tweaks
* restored go module files
* lint fixes
* Added back transslations
* Added translations
* linter and test fixes
* restored go module files
* e2e lint fix
* lint fixes
* AI fixes
* fixed typo
* fixed nil pointer error
* Added more tests
* Publish report even if deletion fails
* Fixed the e2e test
* Distinguished between no data and deleted data
* lint fixes
* fixed tests
* e2e test fix
* Updated test to also upload actual file
* Removed file name tracking
* Text updates
* fixed e2e test
* lint fix
* [MM-67867] Update Playbooks plugin to v2.8.1
Updates the prepackaged Playbooks plugin to v2.8.1 (regular and FIPS builds).
Prepackage FIPS version for Playbooks.
* Removing FIPS version
* Move min_date, max_date, time_interval into DialogElement.datetime_config
Consolidate date/datetime configuration into the datetime_config sub-object
on both DialogElement (Go/TS) and AppField (TS), deprecating the top-level
fields while keeping them for backward compatibility. DateTimeConfig values
take precedence over legacy fields via EffectiveDateTimeConfig() (Go) and
nullish coalescing fallback chains (TS).
Also fixes: timezone indicator now uses FormattedMessage for i18n, CSS class
with theme variable instead of inline styles, and proper DateTimeConfig type
instead of Record<string, unknown> cast.
* MM-68382 - align team creation invite permissions
Keep invite-related team settings consistent during team creation so authorization matches existing update and patch behavior.
Made-with: Cursor
* MM-68382 - move team create helper closer to usage
Keep the create-team authorization helper next to createTeam so the file reads in usage order.
Made-with: Cursor
* commit before ff
* MM-68382 - reject invite fields on create without permission
Reject createTeam with 403 (matching updateTeam/patchTeam) when the creator
tries to set AllowOpenInvite or AllowedDomains without PermissionInviteUser,
instead of silently stripping those fields. Add scheme-branch coverage and
log scheme-fetch failures from the permission check.
Made-with: Cursor
* api4: add symmetric happy path test for scheme InviteUser without invite fields
Cover team creation when the scheme grants InviteUser but AllowOpenInvite and
AllowedDomains are unset, asserting InviteId is returned in the response.
Made-with: Cursor
* [MM-68393] Tighten protected role patch authorization
Harden role patch authorization for protected system roles and cover the restricted paths with focused API tests.
Made-with: Cursor
* [MM-68393] Fix role patch test shadowing
Rename shadowing response variables in the protected role patch tests so govet passes in core and enterprise check-style jobs.
Made-with: Cursor
* [MM-68393] Block privileged role permissions
Made-with: Cursor
Every test binary that uses TestPool builds 16 stores in parallel, each
running the full migration set. Without DisableMorphLogging() the morph
debug stream from each store flows through to the test logger (which is
configured at LvlTrace), producing tens of thousands of "migrating (up)"
lines per shard — amplified further on shards that re-run flaky tests,
since every re-run spawns a fresh TestMain and a fresh pool.
Migration failures are still surfaced: engine.ApplyAll returns the
error, sqlstore.New wraps it as "failed to apply database migrations",
and both NewTestPool callers panic on a non-nil result.
Co-authored-by: Mattermost Build <build@mattermost.com>
* Avoid setting an empty value on slash command IconURL
When `PostEnablePostIconOverride` is enabled and no icon URL is
provided, the override icon URL was being set to empty and triggering
a warning. This change updates the behavior not to set the icon at
all, avoiding the triggering of the warn message while keeping the
behavior.
* Adds an additional check to the test
---------
Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
* MM-68499 - auto run sync jobs on team admin abac policy creation
* Use child-policy flow for access-control sync ownership test
---------
Co-authored-by: Mattermost Build <build@mattermost.com>
* fix: detect ADFS when IdpDescriptorURL has no trailing slash
The ADFS detection in detectSAMLProviderType was checking for "/adfs/"
(with trailing slash) but standard ADFS IdpDescriptorURL values often
end with just "/adfs" (e.g. https://adfs.company.com/adfs), causing the
provider type to show as "unknown" in support packets.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: lowercase FederationMetadata pattern for case-insensitive matching
The normalizedURL is already lowercased, so comparing against the mixed-case
literal "/FederationMetadata/" made that branch unreachable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The new TestRegisterPluginForSharedChannels tests added in #36126 broke
master CI because RegisterPluginForSharedChannels assigned opts.Displayname
directly to RemoteCluster.Name, which IsValid validates against the slug
regex ^[a-zA-Z0-9.\-_]+$. Display names with spaces (e.g. "legacy plugin")
fail validation. The tests didn't run in the PR's final CI shard and the
issue surfaced post-merge.
Add CleanRemoteName to the public model, mirroring CleanTeamName and
CleanUsername: lowercase, replace spaces and other disallowed characters
with hyphens, trim, truncate to RemoteNameMaxLength, fall back to NewId
when the result is empty. Use it in RegisterPluginForSharedChannels so
Name is always slug-valid while DisplayName keeps the human-readable label.
This also lets real plugins register with display names containing spaces.
* MM-67979 MM-67980: Add SMTP and push proxy connectivity to support packet
Adds a `notifications` section to `diagnostics.yaml` in the support
packet with SMTP email and push proxy connectivity probe results.
- `notifications.email.status`: ok/fail/disabled based on whether
SendEmailNotifications is enabled and an SMTP connection can be
established using mail.TestConnection()
- `notifications.push.status`: ok/fail/disabled based on whether
SendPushNotifications is enabled and an HTTP GET to the configured
PushNotificationServer URL succeeds
- Error messages are included in the `error` field on failure
- No email or push notification is sent during the probe
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix: handle errcheck lint violations in support_packet_test.go
Suppress unhandled error return values from rw.WriteString calls in
the mock SMTP server used in tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: use 127.0.0.1 directly in SMTP reachability test
Replace localhost:0 with 127.0.0.1:0 for the mock SMTP listener so
that it always binds to the loopback interface. In CI Docker containers
localhost may resolve to the container IP rather than 127.0.0.1, causing
the SMTP dial to fail with connection refused. Also switch from string
manipulation to net.TCPAddr type assertion for reliable host/port
extraction.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: override MM_EMAILSETTINGS_SMTPSERVER env var in SMTP reachability test
The CI environment sets MM_EMAILSETTINGS_SMTPSERVER=inbucket via
test.env. Mattermost's config Store.Set() calls GetEnvironment()
(os.Environ()) on every UpdateConfig, so env vars silently override
any programmatic config change. Use t.Setenv before UpdateConfig so
the env var points to 127.0.0.1 for the duration of the subtest.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add model.StatusDisabled constant and use it in support_packet.go
Replace "disabled" string literals with model.StatusDisabled for
consistency with model.StatusOk and model.StatusFail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: use utils.GetHostnameFromSiteURL, extract testPushProxyConnection helper, set LDAP StatusDisabled
- Replace manual url.Parse with utils.GetHostnameFromSiteURL (consistent with app/config.go)
- Extract push proxy HTTP check into testPushProxyConnection with TODO to move to its own package
- Set d.LDAP.Status = model.StatusDisabled when LDAP is not configured
- Replace "disabled" string literals in tests with model.StatusDisabled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add status field to ElasticSearch diagnostics with ok/fail/disabled
When indexing is enabled, reports ok or fail based on TestConfig result.
When indexing is disabled or the engine is unavailable, reports disabled.
Backend/ServerVersion/ServerPlugins are still collected when the engine
exists regardless of indexing status.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: update Happy path test for LDAP and ES StatusDisabled assertions
Both are disabled in the test environment so they now report StatusDisabled.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: use GET /version endpoint for push proxy connectivity check
Use url.JoinPath to construct the /version path safely, replacing
raw root URL access. Also validate the HTTP status code so non-2xx/3xx
responses are treated as failures.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>