mirror of
https://github.com/mattermost/mattermost.git
synced 2026-10-13 09:27:38 +08:00
59cd9d628259cfaab3ef55aad25c421aca493de4
23264
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
59cd9d6282 |
Clone missing enterprise checkout in Cloud Agent install (#38117)
* Clone enterprise in Cloud Agent install when the multi-repo layout is missing. Git-triggered automations only add mattermost/enterprise to the GitHub token scope; they do not check it out, so the install hook clones it when needed. Co-authored-by: Cursor <cursoragent@cursor.com> * Reject bare Git repos when locating the enterprise checkout. git rev-parse --is-inside-work-tree exits 0 for bare repositories while printing false, so require a real work tree. Document the $HOME/enterprise fallback in AGENTS.md. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Honor config.override.mk during Enterprise detection and skip unusable sibling clone dests. make evaluated BUILD_TAGS before reading the override, so a $HOME/enterprise fallback still shipped Team Edition; a bare ../enterprise also aborted install instead of falling back. Co-authored-by: Cursor <cursoragent@cursor.com> * Match Enterprise branches in Cloud Agent clones Prefer the corresponding Enterprise branch for PR-triggered environments while keeping clone publication safe and Cloud-only guidance scoped under .cursor. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify Enterprise clone publication Use an atomic no-clobber move instead of maintaining lock state in the ephemeral Cloud Agent environment. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: mattermost-code <matty-code@mattermost.com> Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
786d22bce3 |
Land the React 19 fixes that stand on their own, ahead of the upgrade (#38390)
* fix(webapp): stop Card forwarding expand props to non-Card children Card cloned every valid element child with expanded/disableExpandAnimation, so a plain <div> child (e.g. the delete-policy warning notices in policy_details) received them as unknown DOM attributes and React logged 'Received `true` for a non-boolean attribute `expanded`'. Only Header and Body consume these props, so only forward to those. Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com> * fix(webapp): key react-intl message chunks the way react-intl 8 does React 19 marks elements cloned by React.Children.map as still needing a key, which defeats the cloneElement-based workaround in react-intl 7 and makes every rich-text message log a missing-key error. Backport the fix from react-intl 8, which wraps each element chunk in a keyed Fragment instead. Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com> * fix(webapp): stop rendering images with an empty src React 19 reports an empty src as an error because the browser re-requests the current page. Give the Jest image mock a non-empty value, fall back to the bot icon when an Avatar has no url, and render the brand image preview from state once the FileReader resolves instead of mutating an empty img. Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com> * test(webapp): assert notification icons against the mocked image URL Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com> * test(webapp): stop opening nested act scopes around user interactions React 19 rejects an act(...) scope entered while it is already flushing, and it also errors when userEvent flips IS_REACT_ACT_ENVIRONMENT off inside an outer act. Let userEvent manage its own act scope, and dispatch events fired from inside layout effects directly. Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com> * test(webapp): wait on the settled state instead of an already-present element Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com> * test(webapp): await rendered rows instead of the fetch spy in list tests Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com> * test(webapp): snapshot loaded lists instead of their loading placeholders Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com> * test(webapp): await rendered output instead of action spies Under React 19 the commit that follows a resolved promise lands one flush later than these assertions assumed, so each test read the DOM (or a renderHook result published from an effect) from the previous commit: - policy_details: the Delete policy card renders before fetchPolicy resolves, so the has-resources subtitle must be awaited. - table_editor: becoming disabled wraps the button in WithTooltip, which remounts it, so a reference captured before the flip stays enabled forever; re-query inside waitFor. - channel_invite_modal: searchUsers resolving is two renders ahead of the option appearing, since only the effect mirroring computed options into groupAndUserOptions feeds MultiSelect. - use_rewrite: isProcessing starts false, so waiting on it is satisfied by the initial render; wait on originalMessage instead. - team_details: onSave counts affected members over several awaits before opening the confirmation modal. Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com> * chore(webapp): satisfy lint in the touched tests Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com> * fix(webapp): clear the brand image preview when a new file is selected The preview effect only cleared brandImagePreview when brandImage was absent, so replacing one selection with another left the previous data URL on screen while the new FileReader was still pending -- and left it there for good if that read never completed. Clear the preview at the top of the effect instead, so it resets on every brandImage change rather than only on the transition to empty. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com> |
||
|
|
1c738a29fc |
Hide the System Console Recaps page unless EnableAIRecaps is on (#38331)
* Hide the System Console Recaps page unless EnableAIRecaps is on The page was only gated on sysconsole_read_ai_recaps. After the permissions migration that grants that to system_admin, Recaps appeared even when the feature flag was off. Co-authored-by: Nick Misasi <nick13misasi@gmail.com> * Update admin sidebar snapshots after hiding Recaps by default With EnableAIRecaps off, the Recaps System Console item no longer renders. Refresh the existing sidebar snapshots to match. Co-authored-by: Nick Misasi <nick13misasi@gmail.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
f45e25d54b |
MM-69997: Fix bulk import account handling (#37718)
* MM-69997: Fix bulk import account handling See MM-69997 for more details. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * MM-69997: Adjust ordering in bulk import account handling See MM-69997 for more details. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
a40861c414 |
MM-70511: Add (CreateAt, Id) indices on FileInfo, Channels (#38398)
* Add (CreateAt, Id) indices on FileInfo, Channels * make generated * Update expected indices in the Channels table |
||
|
|
ea98f361ca |
MM-70543: Cap single image previews at 700px wide (#38406)
Co-authored-by: Nevyana Angelova <nevyangelova@Nevy-Macbook-16-2025.local> Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
bca77a7764 |
Select the French language label the code actually renders (#38407)
site_statistics_spec.js picks French from the display language dropdown by its label, and asked for 'Français (Beta)'. i18n.ts has said 'Français (Alpha)' for some time, so cy.findByText never matched the option it meant to click. The other spec that touches this dropdown selects 'English (US)' and 'English (Australia)', neither of which carries a quality suffix, so this is the only stale selector of its kind. |
||
|
|
e75e823ff2 |
E2E/Playwright: Migrate active Cypress tests (batch 1 of 4) (#37454)
* E2E/Playwright: Migrate active Cypress tests batch 1 * E2E/Cypress: Retire migrated prod tests batch 1 * E2E/Playwright: Address Cypress batch 1 review * E2E/Playwright: Fix inline image spec lint * E2E/Playwright: Consolidate batch 1 page objects * E2E/Playwright: Verify selected file preview names * E2E/Playwright: Reuse merged POMs in Cypress batch 1 --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: saturnino <saturnino@mattermost.com> |
||
|
|
050c5c8040 |
chore: update dependencies in cypress (#38016)
Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
fe9a36e80e |
E2E/Playwright: rolling upgrade tests (#38367)
* E2E/Playwright: keep server state across a container swap `/mattermost/config` and `/mattermost/data` are Dockerfile VOLUMEs, so replacing the container factory-reset the config and dropped uploads. Store the config in Postgres and bind-mount the data directory to `.mattermost_data/` instead. The server writes there as UID 2000 into 0750 dirs, so on Linux the host user cannot delete them: resetting on a fresh boot threw EACCES and failed global setup on every second boot, and so on every retest. Clear from a container when the host cannot. Adds the library pieces the upgrade projects need — image swapping, phase log capture with boot env redacted — and fixes the specs this exposed. * E2E/Playwright: add the upgrade-path harness The suite only ever ran against a server booted fresh on the new image, so an upgrade-only regression would surface first in a customer's upgrade. Three projects perform an in-place upgrade: `upgrade-from` seeds an older image and records a baseline, `upgrade-swap-to` replaces only the server container, `upgrade-to` re-verifies the baseline. API-first, since older images ship older web apps. From-versions come from the published support policy at run time — the last three minors plus any active ESR, strictly older than the version under test. * E2E/Playwright: run rolling upgrades in CI, one status per from-version An entry workflow resolves the from-version matrix and calls a template once per version, in parallel. Each run posts its own commit status, so a failure names the version it came from; an empty matrix posts `upgrade-from-none`. Every worker upgrades its own server, then draws from the full suite against it with no re-preparation in between — an upgrade needing the suite's setup re-run is not a passing upgrade. Runs on release cut and on demand. Not on merge: it costs `matrix x workers` runners. On PRs, only when the diff touches the harness or these workflows. * E2E/Playwright: address review findings - Keep the to-phase loader read-only. It patched EmailSettings, FileSettings, SiteURL, AccessControlSettings and PluginSettings on the upgraded server before any assertion, so config the upgrade dropped was repaired instead of reported — the opposite of what this harness is for. - Return the raw post ID for a combined post. `<postId>:<timestamp>` has the same shape as a pending_post_id, so `getId` retried and then threw on posts that were already permanent. - Point `npm run clean` at `.mattermost_data`; it still removed the old `local_storage` name. - Trigger rolling upgrades on `package-lock.json` too, and document the full path set. - Quote the license placeholder in the local run instructions: bare `<key>` is a shell redirection, not a value. - Quote `$GITHUB_OUTPUT` in the two rolling-upgrade workflows. * E2E/Playwright: retarget the rolling-upgrade auto-trigger Every file under `lib/` matched, so any page-object edit fired the pipeline — `matrix size x workers` runners, each performing its own upgrade. Narrow the harness side to what actually defines an upgrade run: `upgrade-specs/`, the matrix resolver, and the rolling-upgrade workflows. Add the server surfaces an upgrade has to survive, which had no trigger at all: schema and config migrations, the app-layer data migrations, and the version list the from-version matrix is derived from. A new numbered migration is exactly what `upgrade-to`'s "schema migrations match baseline" check exists to catch. * E2E/Playwright: use repo-root paths in the trigger table `model/version.go` and `app/migrations.go` were ambiguous; spell them from the repo root so a contributor can match them against a diff. * E2E/Playwright: align rolling-upgrade trigger docs with the implemented matcher The trigger prose and table in e2e-tests-workflows.md still listed e2e-tests/playwright/lib/ and playwright.config.ts as auto-trigger paths, and marked merge-to-master/release-* as an auto-trigger. Neither matches e2e-tests-ci.yml's check-changes step: those two paths are unmatched and require the manual opt-in checkbox, and check-changes is never even called with run_rolling_upgrades=true on a merge (e2e-tests-on-merge.yml never passes it). Align both descriptions with the implemented matcher and with README.md's already-correct table. Co-authored-by: sabril <saturninoabril@users.noreply.github.com> * E2E/Playwright: fix write-after-end crash in collectLogs() teardown container.logs() follows a running container, so a stack left running (PW_TESTCONTAINERS_REUSE=true) never emits 'end' on its own. The 10s stall timeout then called outFile.end() while the source was still piping into it, so the next chunk landed on the closed destination and crashed the process with an unhandled ERR_STREAM_WRITE_AFTER_END. This intermittently killed rolling-upgrade worker jobs (seen failing the upgrade-from-release-11.10 lane on this PR). Unpipe and destroy the source before ending outFile, and add an error handler on outFile, so no write can land after it closes. Co-authored-by: sabril <saturninoabril@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: sabril <saturninoabril@users.noreply.github.com> |
||
|
|
3b88476e31 | Add a test pinning supportedLocales to the shipped catalogs (#38408) | ||
|
|
5e6f509f36 |
Add deterministic i18n checkers for the server and webapp locale catalogs (#38231)
* Add deterministic i18n checkers for both surfaces
Nothing in CI reads the locale catalogs. webapp/channels has an
i18n-verify-translations script -- `formatjs verify --structural-equality` --
but no workflow calls it, and it has no extra-key check. The server has nothing
at all. So a translation that throws at format time, invents a variable the
source does not have, or carries an id en.json retired is invisible until a
user in that language hits it.
Webapp: scripts/check_icu.mjs, wired up as the existing i18n-verify-translations
script. It imports @formatjs/icu-messageformat-parser, declared as a dependency
of channels and pinned to 2.11.4 -- the copy react-intl 7.1.14 resolves -- so
"parses here" really does imply "parses there". The pin is load-bearing: three
versions coexist in the tree, and a bare import resolved to the 2.4.0 that
babel-plugin-formatjs hoists to the root, the build-time transformer's grammar,
seven minors behind the parser that actually renders the catalogs, and a dedupe
could have moved it again without notice. The header records that the pin has
to move with react-intl.
The checks are in two layers. Layer 1 is the runtime guarantee and always
fails: valid JSON, string values, key parity with en.json, every value parses,
and no variable or tag the source does not have (an unknown variable throws
MISSING_VALUE at format time; an unknown tag renders as raw markup). Layer 2 is
fidelity: a dropped variable or tag, a variable demoted to a different type --
{count, plural, ...} becoming a bare {count} still parses and renders, it just
silently stops pluralizing -- and an ASCII apostrophe before < or {, which in
ICU opens a quoted literal that swallows the tag and everything after it. That
last one still parses, so this check is the only thing that catches it.
Type parity compares the set of types each variable is used as rather than a
single type per variable. A name is routinely used in two roles at once --
"{count, number} new {count, plural, one {message} other {messages}}" -- and a
type-per-name map mis-flags every one of those.
The comparison is asymmetric, because only one direction is a defect. Demotion
loses the plural. Promotion is how a translator makes a sentence grammatical:
English needs a plural far less often than the languages it is translated into
-- "{count} items were deleted" is one sentence in English and four in Russian
-- and react-intl reads the value out of the same values object either way. A
PROMOTIONS table names the roles that are safe to widen into and everything
else stays fatal. The formatting types stay out of it: {x, number} over a
string renders NaN and {x, date} over a non-date renders "Invalid Date", so
reaching for one the source did not use is a defect rather than a grammar fix.
Server: `mmgotool i18n verify` and `make i18n-verify`. The strongest check
available there is the loader itself: go-i18n's LoadTranslationFile is what the
server runs at startup, so a file it rejects is a file that breaks the server.
Each locale loads into its own bundle rather than the package-global one, so
one file cannot mask a defect in another depending on the order they happen to
be walked in, and the checks can be run in isolation. On top of the loader, id
parity, {{.Field}} parity in both directions, and exactly the CLDR plural
categories the locale uses -- stricter than the webapp, because go-i18n rejects
a category the locale does not have.
Plural shape is compared against en.json in the collapsing direction too, since
flattening a pluralized entry into a single string still loads and still
renders and just silently stops pluralizing. The opposite direction is left
alone: whether a count reaches go-i18n is a property of the call site, not of
en.json -- the batched email title is a plain string in English and pluralized
in six locales, and email_batching.go passes len(notifications)-1 -- so a
language that has to inflect where English does not is translating correctly.
Empty plural forms are flagged as well. countEmptyItems and
removeEmptyTranslations both test the raw JSON for `""`, so every plural map
slips past them, and go-i18n builds no template for an empty form, finds none
at format time, and returns the translation id. Empty single-string
translations render that same raw id and are deliberately left alone: several
hundred already exist across es, fr and it, and `i18n clean-empty` already
removes exactly that shape.
There is no allowlist. A translation that deviates from its source is either
wrong, or the source is encoding English grammar the other languages cannot
follow -- and both of those are worth fixing rather than recording. Every key
that wanted an exemption while this was being written turned out to be the
second kind, and the four source strings responsible landed upstream in #38228,
so the corpus passes with nothing exempted. If a case ever turns up that
genuinely is a property of a target language, the mechanism is ten lines and
should be scoped to key and locale, which a real example would show how to do.
Both take a flag (--warn-missing-keys / --warn-missing-ids) that downgrades
missing entries to warnings, since key parity is a separate problem from
whether the entries that exist are correct.
Pulling in go-i18n resolves the minimum versions its go.mod requires --
pelletier/go-toml v1.2.0 and yaml.v2 v2.2.1, both with known advisories. Pinned
to the versions the server module already resolves, v1.9.5 and v2.4.0, so the
tool module and the server agree rather than each carrying its own copy.
The server tests cover each check the command makes: the runtime loader gate,
id parity in both directions and under --warn-missing-ids, template token
parity, plural shape against en.json in both directions, CLDR categories per
locale, and empty plural forms. They also pin the two behaviours the command
walk depends on, that en.json, non-JSON files and subdirectories are skipped.
Writing them turned up that the "no CLDR plural spec" branch is unreachable:
the loader derives the language with language.Parse, which only returns one
when GetPluralSpec is non-nil for the same tag, so a file that loaded always
has a spec. It is left in place as an invariant guard and labelled as one, with
the test asserting what an unrecognized filename actually does.
The webapp tests drive the real script in a subprocess, since its contract is
its exit code: 0 clean, 1 defects found, 2 misuse. They cover well-formedness,
key parity in both directions and under --warn-missing-keys, variable and tag
parity including names nested inside plural branches, the argument role rules
in both directions, and the apostrophe check with its doubled and typographic
escapes. The first test asserts that the resolved ICU parser is the version
react-intl depends on, which is the property the rest of the suite rests on and
the one that silently regressed before. It fails against a node_modules tree
installed before that pin landed; npm ci fixes it.
Both are wired into the i18n workflow, which until now only ever looked at
which files changed. The existing check-files job stays as it is -- reporting a
direct edit to a non-English catalog is still worth doing, and it answers a
different question from whether the edit is correct.
Both run with missing entries downgraded to warnings (--warn-missing-keys /
--warn-missing-ids). Key parity is a real gap, 55,164 entries on the webapp and
21,748 on the server, but it is a separate problem from whether the entries
that do exist are valid, and a hard parity gate is only fair once a contributor
adding a string has a documented way to produce the other 21 locales.
The workflow triggers on server/i18n/** and webapp/channels/src/i18n/**, and on
the checkers themselves -- check_icu.mjs, mmgotool, and the workflow files. It
is the only workflow that invokes either checker, so without those paths a PR
touching one of them never ran it and a broken checker could land green. That
matches how webapp-ci.yml already lists its own workflow and composite action.
The catalogs do not pass yet; the three commits that follow repair them.
* Fix the locale catalog defects the checkers found
Running the new checkers over the shipped catalogs turns up 2,474 webapp errors
and 1,495 server errors. They are not stylistic: 98 webapp strings fail to parse
with the parser react-intl runs, so they throw the moment a user in that
language opens the screen. vi has 36 of them, ro 18, fa 15. Another 395 name a
variable or tag the source does not have, which throws MISSING_VALUE or renders
raw markup, and on the server 57 interpolate a {{.Token}} that is not there and
render "<no value>".
Two kinds of change here, and no new translation of anything.
Repaired mechanically, where the intended text is unambiguous:
webapp 74 variable or tag the source renamed -- {fullUsers} -> {fullSeats},
{displayname} -> {displayName}, {linkApi} -> {linkAPI}. Paired
only when each unknown name has one clear counterpart.
17 an apostrophe opening an ICU quoted literal. After a letter it is
an elision, so it becomes the typographic apostrophe -- French
l'<link>; otherwise it is a quote mark and every lone apostrophe
in the string is doubled, which is the ICU escape and keeps an
opening and closing pair matched.
15 a translated variable name -- {потребителско име} for {username}.
These are MALFORMED_ARGUMENT, so the string never parsed at all.
3 a pseudo-tag the source quotes deliberately, '<blank>', copied
without the quoting and so an UNCLOSED_TAG.
server 22 a plural branch for a category the locale does not have. go-i18n
rejects those outright; pt-BR, es, fr and it carry a "many" that
its CLDR spec does not define.
14 a {{.Token}} the source renamed.
Removed, because the entry translates an English string that no longer exists:
webapp 1285 keys en.json no longer has
702 translations whose structure no longer matches the source
server 809 ids en.json no longer has
497 translations whose tokens or plural categories no longer match
Removing is the conservative option, not the destructive one. react-intl falls
back to the English defaultMessage for a key a catalog does not carry, and the
server falls back to the id; what these entries render today is a stray
'<your-mattermost-url>' literal, unrendered markdown, a missing hyperlink, or an
exception. The catalogs are already 55,164 webapp and 21,748 server entries
short of parity, so this does not change what a translator has to do next -- it
changes what a user sees in the meantime. Every removed entry is recoverable
from history if someone wants to repair rather than retranslate it.
Nothing here is exempted, because there is nothing to exempt with. Six keys
wanted an allowlist entry over the course of this work, and every one turned out
to be the same thing: an English sentence assembled from parts, forcing
translators into a shape the source did not have. The four source strings
responsible are fixed in the commit this branch is stacked on, which is why the
checker ships without an allowlist at all.
Both checkers now pass with --warn-missing-keys / --warn-missing-ids. Key parity
is a separate problem and is left as warnings.
* Fix the French batched email title plural forms
The entry used the CLDR category "many", which the go-i18n plural spec for
French does not define, and left "other" empty. French resolves any count of
two or more to "other", so every French batched email digest covering more
than one message shipped with an empty subject line.
Move the plural text into "other" and drop the unused category.
* Remove the half-translated plural entries from six locale catalogs
Eighteen entries across bg, es, fa, fr, it and ro filled "one" and left the
remaining categories empty. An empty form is worse than an absent translation:
go-i18n builds no template for it, finds none at format time, and returns the
translation id, so a Spanish user inviting two people who are already members
saw "api.command_invite.user_already_in_channel.app_error".
Deleting the entry lets the en fallback serve real English instead. No
translation is invented here -- the singular text goes away with the entry,
because a catalog that answers only for count == 1 cannot be served safely.
* Use singular agreement in the Italian and Romanian out-of-channel mention strings
|
||
|
|
d2cec875b8 |
[MM-68252] Close the schedule message menu after picking a time on mobile web (#38288)
* [MM-68252] Close the schedule message menu after picking a time on mobile web In mobile view the menu renders as a modal that only dismisses once the click reaches the list wrapping the menu items, so the stopPropagation call in handleOnSelect left the menu open after scheduling and let the same draft be scheduled again. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Cover the mobile menu modal's dismiss-on-select behavior Pins the invariant the fix relies on: a plain menu item closes the mobile modal, while an item that opts out of closing on select does not. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Add E2E coverage for the mobile schedule message menu Co-authored-by: mattermost-code <matty-code@mattermost.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: mattermost-code <matty-code@mattermost.com> |
||
|
|
7eebdb8f94 |
Migrate from react-beautiful-dnd to @hello-pangea/dnd (#38320)
* refactor(webapp): import JSX from react instead of the global namespace
React 19 removes the global JSX namespace, so JSX.Element and friends have to
come from react itself. The scoped JSX namespace already exists in
@types/react 18 as an alias of the global one, so this compiles identically
today and stays a no-op until the React 19 upgrade lands.
Generated with:
npx types-react-codemod@latest scoped-jsx ./channels/src ./platform \
--ignore-pattern "**/{node_modules,dist,build}/**"
Then brought in line with lint: the codemod spaces its braces, and where a
file already had a separate react type import it left two react imports.
Note that eslint --fix cannot resolve the latter on its own; its
import/no-duplicates autofix merges a default import with a type import and
produces invalid syntax, so the JSX specifier was moved by hand instead.
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
* refactor(webapp): spell out ReactElement's any props
React 19 changes ReactElement's default props type from any to unknown.
Stating any explicitly keeps these call sites behaving as they do today once
that lands. @types/react 18 already defaults P to any, so this is a no-op
refactor for now.
Generated with:
npx types-react-codemod@latest react-element-default-any-props \
./channels/src ./platform --ignore-pattern "**/{node_modules,dist,build}/**"
Followed by eslint --fix for the codemod's brace spacing.
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
* refactor(webapp): replace the deprecated ReactNodeArray type
ReactNodeArray is removed in React 19. It is declared as
ReadonlyArray<ReactNode> in @types/react 18, so the replacement is equivalent
today.
Generated with:
npx types-react-codemod@latest deprecated-react-node-array \
./channels/src ./platform --ignore-pattern "**/{node_modules,dist,build}/**"
Followed by eslint --fix, which rewrites the codemod's ReadonlyArray<T> into
the readonly T[] form this repo's array-type rule requires.
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
* refactor(webapp): migrate from react-beautiful-dnd to @hello-pangea/dnd
react-beautiful-dnd has been unmaintained since 2022 and does not support
React 19, which makes it a blocker for that upgrade. @hello-pangea/dnd is the
maintained fork, with an unchanged API surface, and declares
react: ^18.0.0 || ^19.0.0, so this lands and is verified on React 18 today.
Two workarounds go away with it:
- patches/react-beautiful-dnd+13.1.1.patch, 109 lines of vendored edits that
stripped connect()'s `pure: true` option, removed in react-redux 8.
- the "react-beautiful-dnd": {"react-redux": "9.2.0"} npm override that forced
the library onto a react-redux it did not declare support for.
The fork also drops a duplicate nested copy of redux, plus use-memo-one and
memoize-one, from the tree.
The library renames its generated DOM attributes from data-rbd-* to data-rfd-*,
which is the one behavioural change to watch. SidebarList reads the categories
droppable back out of the DOM by that attribute to freeze the scroll box during
a drag, so a stale selector there makes querySelectorAll return nothing and
throws "Cannot read properties of undefined (reading 'style')" on drag start,
breaking sidebar drag and drop entirely.
The existing coverage could not catch that: it mocks document.querySelectorAll
to return a stub element, so the selector string is never exercised. Added a
test that drives onBeforeCapture and onDragStart against the real rendered DOM
instead, and confirmed it fails with exactly that TypeError when the data-rbd-*
selector is restored.
Snapshots are regenerated for the attribute rename. Module federation shares
'@hello-pangea/dnd' in place of 'react-beautiful-dnd' so product modules keep
resolving to the host's single instance.
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
|
||
|
|
80fc4302ae |
Apply React 19 type codemods ahead of the upgrade (#38313)
* refactor(webapp): import JSX from react instead of the global namespace
React 19 removes the global JSX namespace, so JSX.Element and friends have to
come from react itself. The scoped JSX namespace already exists in
@types/react 18 as an alias of the global one, so this compiles identically
today and stays a no-op until the React 19 upgrade lands.
Generated with:
npx types-react-codemod@latest scoped-jsx ./channels/src ./platform \
--ignore-pattern "**/{node_modules,dist,build}/**"
Then brought in line with lint: the codemod spaces its braces, and where a
file already had a separate react type import it left two react imports.
Note that eslint --fix cannot resolve the latter on its own; its
import/no-duplicates autofix merges a default import with a type import and
produces invalid syntax, so the JSX specifier was moved by hand instead.
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
* refactor(webapp): spell out ReactElement's any props
React 19 changes ReactElement's default props type from any to unknown.
Stating any explicitly keeps these call sites behaving as they do today once
that lands. @types/react 18 already defaults P to any, so this is a no-op
refactor for now.
Generated with:
npx types-react-codemod@latest react-element-default-any-props \
./channels/src ./platform --ignore-pattern "**/{node_modules,dist,build}/**"
Followed by eslint --fix for the codemod's brace spacing.
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
* refactor(webapp): replace the deprecated ReactNodeArray type
ReactNodeArray is removed in React 19. It is declared as
ReadonlyArray<ReactNode> in @types/react 18, so the replacement is equivalent
today.
Generated with:
npx types-react-codemod@latest deprecated-react-node-array \
./channels/src ./platform --ignore-pattern "**/{node_modules,dist,build}/**"
Followed by eslint --fix, which rewrites the codemod's ReadonlyArray<T> into
the readonly T[] form this repo's array-type rule requires.
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
|
||
|
|
a66e9c034b |
MM-70511: Optimize FileInfo and Channels bulk-indexing queries (#38206)
* Add (CreateAt, Id) indices on FileInfo, Channels * Optimize FileInfo and Channels indexing queries * make generated * Update expected indices in the Channels table * Remove indices --------- Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
0d79d72074 | Bumping prepackaged version of the zoom plugin to v1.13.1 (#38339) | ||
|
|
9c8d4164ab | MM-70074: Fix output formatting issue in mmctl (#37794) | ||
|
|
d611ca8f4a |
chore: always render with createRoot and drop the EnableConcurrentReact flag (#38391)
The flag has defaulted to true since it was enabled server-wide, so concurrent React is already what every user gets. Calling createRoot unconditionally makes that the only path, which also removes the ReactDOM.render call that React 19 deletes, and leaves the flag with nothing left to switch: it goes from the web app, the server's root.html script injection and its CSP hash, and the e2e config. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com> |
||
|
|
904ee27f26 |
docs: emit redirect stubs at both <from>/index.html and <from>.html (#38389)
active-redirects.json entries whose `from` lives outside the docs CloudFront function's frozen legacy-Sphinx prefix set currently 404 in production. The stub file exists in S3 at <from>/index.html but the CFF (mattermost-iac-docs-prod -> 05-cdn -> clean-url-rewrite.js) only serves that key for the frozen legacy prefixes; for any other first path segment it falls back to <from>.html, which doesn't exist for plugin-client-redirects stubs. Add a postbuild step that duplicates every stub at the sibling flat key. The legacy branch keeps hitting <from>/index.html unchanged; the fallback branch now finds <from>.html. Real content pages are never overwritten (the script skips any path where <from>.html already exists), .html-suffixed from-values are skipped (CFF Phase A canonicalizes them before Phase B routes them), and .. traversal in a malformed from is rejected before any filesystem call. Smoke-tested against a local build: 741 stubs duplicated, 0 real content pages touched, byte-identical meta-refresh content. |
||
|
|
03a30e9183 |
[MM-70592] Fix read-only settings for section-based plugins without a footer (#38384)
The enable setting is hidden, so its config value only reaches the form state when the setting is part of the schema. In the sections branch it was added only when the schema had a footer, so a section-based plugin without one never seeded PluginSettings.PluginStates.<id>.Enable, and stateIsFalse read undefined and disabled every setting even while the plugin was running. Always unshift the hidden enable setting in the sections branch, and assert editability across all three schema shapes (flat, sections with a footer, sections without one). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
597d9c4296 |
docs/site: add legacy Sphinx URL redirects for 281 URLs, fix 3 broken targets (#38387)
* docs/site: add legacy Sphinx URL redirects for 281 URLs, fix 3 broken targets
Adds 281 new entries to active-redirects.json for legacy Sphinx URLs that
currently return 404 on docs.mattermost.com, and corrects 3 existing entries
whose 'to' pointed at pages that were moved after the initial migration.
Sources verified from:
- source .rst/.md files in mattermost/docs Sphinx repo
- redirects_map in source/redirects.py (sphinx_reredirects extension)
- old-Sphinx CDN at d3r1ild9aa68qq.cloudfront.net (still serving legacy URLs)
Every target was live-probed against docs.mattermost.com (200, no soft-404).
Release Note:
```release-note
NONE
```
* docs/site: fix two malformed redirect targets
- /guides/install-deploy-upgrade-scale: strip extra leading slash
(//deployment-guide/... -> /deployment-guide/deployment-architecture).
- /overview/index.html: strip stray space ("/ " -> "/") so the
target is the docs root instead of a whitespace-encoded path.
Both hand-edited values in the source CSV; the initial verify pass
missed them because HTTP silently collapses '//' and encodes ' ' -> '%20'
which still lands on the root. Added a small validator that flags
non-'/'-prefixed, leading-'//', or whitespace-in-path 'to' values, plus
duplicate 'from' values.
Release Note:
```release-note
NONE
```
* docs/site: drop trailing slash on /developer/before-you-get-started target
Docusaurus is configured with trailingSlash: false; all other 'to' values
in active-redirects.json omit the trailing slash. Normalize this one entry
for consistency.
Release Note:
```release-note
NONE
```
|
||
|
|
f301b5310e |
Update latest version to 12.0.0 (#38378)
Automatic Merge |
||
|
|
53aaf6a4a8 |
Move changelog generation automation to server repo (#38113)
Automatic Merge |
||
|
|
3ed4cb03b3 |
chore: Update NOTICE.txt file with updated dependencies (#38376)
Automatic Merge |
||
|
|
2266df0c33 |
[MM-68319] Stop capping OutgoingIntegrationRequestsTimeout at 30 seconds (#38103)
* [MM-68319] Stop capping OutgoingIntegrationRequestsTimeout at 30s Outgoing integration requests apply ServiceSettings.OutgoingIntegrationRequestsTimeout as a per-request context deadline, but the clients making them also carried the default 30s http.Client.Timeout, so any configured value above 30 seconds was silently capped. Add HTTPService.MakeClientWithTimeout so the shared outgoing webhook client and the post action clients can be built without an end-to-end timeout of their own, leaving the configured deadline in charge. Other clients keep the default RequestTimeout. Co-authored-by: mattermost-code <matty-code@mattermost.com> * [MM-68319] Cover outgoing integration client timeouts with tests Co-authored-by: mattermost-code <matty-code@mattermost.com> * [MM-68319] Fall back to the configured timeout without a request deadline Post action clients take their deadline from the request context. Keep a request that arrives without one bounded by OutgoingIntegrationRequestsTimeout instead of letting it run unbounded, and assert the effective deadline behaviourally rather than only reading the client timeout. Co-authored-by: mattermost-code <matty-code@mattermost.com> * [MM-68319] Tighten outgoing integration timeout test assertions Co-authored-by: mattermost-code <matty-code@mattermost.com> * Rename MakeClientWithTimeout to MakeClientWithoutTimeout and drop post-action fallback Address review feedback on getPostActionClient: every caller of DoActionRequest sets a context deadline derived from OutgoingIntegrationRequestsTimeout, so the no-deadline fallback that re-applied a client-level timeout was over-protective. Removing it means all callers construct the client without a timeout, so the HTTPService method now takes no timeout argument and is named for that intent. * Address PR feedback: 0 answered, 1 resolved, 0 declined Restore the configured-timeout fallback in getPostActionClient: when the request context carries no deadline, bound the client at ServiceSettings.OutgoingIntegrationRequestsTimeout instead of running unbounded. Production callers always set a deadline, but the exported DoActionRequest can be reached by deadline-less callers (tests, out-of-tree), which previously regressed from the 30s MakeClient bound to no bound at all. * Address PR feedback: cancel outgoing integration requests on shutdown The shared outgoingWebhookClient no longer imposes a client timeout, and outgoing webhook requests run inside Srv().Go goroutines that Shutdown waits on. Their contexts descended from context.Background(), so with a large configured OutgoingIntegrationRequestsTimeout graceful shutdown could block for the whole timeout while a hung endpoint kept the request open. Add a server-lifetime context to PlatformService (GoContext) that is cancelled during shutdown before waiting on tracked goroutines, and derive the outgoing integration request contexts (post actions, dialogs, outgoing webhooks, slash commands) from it so shutdown cancels them promptly instead of blocking. * Address PR feedback: 2 answered, 1 resolved, 0 declined * Address PR feedback: keep MakeClientWithoutTimeout off exported HTTPService Revert the added HTTPService.MakeClientWithoutTimeout interface method to avoid a breaking change to the public server/public module. The two call sites now use MakeClient and clear the client's Timeout after construction instead. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: mattermost-code <matty-code@mattermost.com> Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
b4b70b9218 |
MM-70570 - Fix flaky ESC blur test by enabling fake timers (#38316)
Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
15abf3275f |
[MM-67612] Show the default channel URL as read-only instead of failing the save (#38110)
* Lock the default channel URL in the channel name form field The server rejects any change to the default channel's `name` (`api.channel.update_channel.tried.app_error`, HTTP 400), but the webapp still offered a URL Edit button on town-square in both the Channel Settings Info tab and the legacy Rename Channel modal. Saving failed with the generic "There are errors in the form above", which does not tell an admin why the update was refused. Present the URL read-only with an explanation for the default channel. The display name stays editable, since the server does allow that. `URLInput` gains generic `readOnly` and `helpText` props; when read-only it renders neither the Edit/Done button nor the editable input, including when an error would otherwise force it open. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Add E2E coverage for the default channel URL lock Cover both directions against a real server: the default channel offers no URL edit and still saves its display name, and an ordinary channel can still have its URL changed. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Use shared Button for the URL input edit control Replace the bespoke Edit/Done <button> with Button from @mattermost/shared so the control follows the webapp style guide. Keep quaternary + extra-small sizing, and override .btn chrome so the compact link appearance is unchanged. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Lock the channel URL when ChannelNameFormField is read-only When the user cannot manage channel properties, the display name is already disabled. Pass that same readOnly flag through to URLInput so ordinary channels do not still offer Edit. Co-authored-by: mattermost-code <matty-code@mattermost.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: mattermost-code <matty-code@mattermost.com> |
||
|
|
cd9bcb005e |
chore(ci): warm Go module and build cache daily, restore read-only in CI (#38285)
* chore: extract install-<tool> Makefile targets
Pull each `go install` of a build tool into its own `install-<tool>`
target so the installs are declared once and reused as prerequisites,
and add `install-ci-tools` plus a `go-cache` target that pre-warms the
Go module and build caches.
Also collapse the duplicate `vet-api` target declarations into one.
* chore(ci): add restore-go-cache composite action
* chore(ci): mount warmed Go cache into run-in-buildenv
* chore(ci): add daily Go module/build cache warm workflow
* chore(ci): restore warmed Go cache read-only across server CI jobs
* Simplify Go cache key to glob-match all go.mod and go.sum files
* make GOBIN explicit
* Pass Go cache paths as step outputs, not GITHUB_ENV
restore-go-cache wrote GOMODCACHE/GOCACHE/GOBIN to GITHUB_ENV so later
steps would pick them up. zizmor flags any GITHUB_ENV write as a possible
code-execution vector, since an attacker-controlled value there can set
LD_PRELOAD or NODE_OPTIONS for every subsequent step.
Nothing untrusted reaches these particular values, but the write is also
unnecessary: expose them as action outputs and map them into the one step
that actually needs them, matching what
|
||
|
|
94d6a20395 |
MM-70228: Adjust LDAP test connection validation (#37913)
* MM-70181: Adjust LDAP test connection validation * MM-70181: Generalize AD/LDAP bind password re-entry message Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * MM-70181: Narrow LDAP test connection validation fields Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
8b5045c27b | Ensure we only warm the node_modules cache from the master branch (#38342) | ||
|
|
d7880054f7 |
[MM-70278] Add enable button for disabled plugin settings (#37588)
* Add enable button for disabled plugin settings Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Simplify disabled plugin enable action Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Refresh plugin settings after enabling Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Use action button for plugin enable state Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Add plugin uninstall action to settings Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Move plugin actions under metadata Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Update plugin action search index tests Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Make disable plugin action neutral Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Guard plugin actions with unsaved settings Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Add plugin status under metadata Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Use colored plugin status panel Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Polish plugin status and action errors Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Place plugin status on separate line Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Assert plugin settings status in e2e Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> * Fix icon spacing in plugin status panel PluginMetadataPanel__status previously relied on the ambient .form-horizontal .alert .fa margin-right rule from _admin-console.scss, which only applies when the panel is nested inside a .form-horizontal form. Since the panel renders outside that context, the status icon touched the message text. * Sync live plugin status on plugin settings page The plugin settings page read pluginStatuses straight from the store with nothing to keep it fresh, so it only ever showed whatever status was last fetched elsewhere (e.g. a previous Plugin Management visit) and never reflected a plugin transitioning through starting/stopping/ failed states after an enable/disable action taken from this page. Wrap the container with usePluginStatusesSync(), mirroring how plugin_management/index.tsx subscribes to PluginStatusesChanged. * Fix stylelint property order in plugin metadata panel order/properties-order requires width before align-items. * Give plugin run state and action buttons their own box Wrap the status alert and enable/disable/uninstall buttons in a bordered panel, similar to the footer treatment on the system user detail card, so the runtime status and actions read as a distinct unit from the plugin metadata line above and the settings form below. * fix lint * Move plugin metadata into action panel Co-authored-by: ben.schumacher <ben.schumacher@mattermost.com> * Put plugin status in the header and restyle settings actions. Keep enable/disable as a labeled toggle with a kebab for uninstall, and move start failures into a section notice instead of the status pill. Co-authored-by: Cursor <cursoragent@cursor.com> * Add plugin homepage action and tighten status notice spacing. Expose More about this plugin from the actions menu when a homepage URL exists, and remove title-to-body gap on the plugin failure SectionNotice only. Co-authored-by: Cursor <cursoragent@cursor.com> * Merge master into cursor/plugin-enable-button-88bc Resolves conflicts in the admin console plugin settings refactor: - i18n/en.json: keep both new upload-related keys - admin_console_plugin_index.test.ts: keep both new test cases - custom_plugin_settings/index.tsx (renamed from index.ts): keep this branch's leftover-section design, but stop attaching a duplicate footer to it now that schema_admin_settings renders the schema footer once for the whole sections list; also fix hasAllCustomSectionsDisabled vacuously returning true for an empty sections array - schema_admin_settings.tsx: apply master's buildSettingsList extraction while keeping this branch's header suppression for plugin schemas - custom_plugin_settings/index.test.tsx: keep both branches' new tests and reconcile wording of the disabled-plugin warning banners to match master's "...and click Save." copy update, applied consistently * Remove orphaned admin.plugin.upload i18n key Its usage in plugin_management.tsx was removed by master's plugin upload dropzone UX change (#37569); the merge kept the code removal but left the now-unused string behind in en.json, failing i18n-extract:check. * Fix flaky menu-item click assertion in enable_plugin_button test The homepage-open assertion could run before the menu item's click handler resolved, causing an intermittent 0-calls failure in CI. * Address review feedback: update stale copy and add plugin actions tooltip The plugin-disabled banners still told admins to enable the plugin and click Save, but enable/disable now applies immediately via the new toggle. Also add a tooltip to the icon-only plugin actions menu button so sighted users get a visual hint of what it does. * Rename PluginMetadataPanel showName prop to hideName Defaulting an optional boolean to true means an omitted prop silently changes behavior; defaulting to false is the safer, more idiomatic choice. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * [MM-70278] Add e2e coverage for plugin settings page actions Extends Cypress coverage to exercise the new immediate enable/disable toggle, uninstall confirmation + redirect, and unsaved-changes guard on the plugin settings page, which the existing spec did not touch. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com> Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Matthew Birtch <2040554+matthewbirtch@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
8608859279 |
fix(e2e): repoint markdown image tests off the deleted docs.mattermost.com asset (#38330)
docs.mattermost.com migrated from Sphinx to Docusaurus, which removed the /_images/ static path. https://docs.mattermost.com/_images/icon-76x76.png now returns 404, so the image proxy fails and the webapp renders the broken-image placeholder instead. That breaks four specs on every run, independent of the change under test: - markdown/markdown_image_spec.js "with in-line images 1" - messaging/inline_markdown_image_link_open_link_spec.js MM-T188 - files_and_attachments/image_link_preview_spec.js MM-T2389 - fixtures/markdown/markdown_inline_images_1.md The icon is gone from the docs site entirely (no /img/, /static/img/, /img/brand/ or /assets/images/ equivalent), so point these at an image we actually own: the in-repo fixture mattermost-icon_128x128.png, served over raw.githubusercontent.com. That is the convention several sibling specs already use, and it removes the dependency on the docs site's asset layout. Size assertions move from 76px to 128px to match the new fixture. 128px stays under the 350px max-height cap on .markdown-inline-img, so the image still renders at its natural size. The height tolerance tightens from closeTo(76, 76) - which accepted anything from 0 to 152 and would have passed on a zero-height broken image - to closeTo(128, 2). |
||
|
|
0019ecc531 |
(1) - Feature post delivery audit setting (#37882)
* Added server and client side frameowkr for configuring settings * Type fixes * Added test mocks * Review fixes * Restored gotool go modules * CI * (2) - Post tracking audit event implementation (#37919) * Initial implementation for generating delivery tracking * Initial implementation for generating delivery tracking * Some performance improvements * Some performance improvements * Record list.Posts instead of list.Order * Fixed email batch delivery recording * Fixed a couple of pending delivery mechanisms * Caching fixes * another few fixes * Stub DeliveryTracking in store mocks used by cache purge InvalidateAllCachesSkipSend now also clears the delivery tracking store's caches. The partial store mocks in these tests mirror the store list that function touches, so the added call panicked with an unexpected DeliveryTracking() during test cleanup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Recorded post delivery event after JSON encoding succeeds * Updated decisionmkaing point in email delivery tracking * Plugin hook handling * Added the Learn More button on delivery tracking section * Added in inline code comment for explaining a decision * Restored go modules * Updated indirect status of a go dependency * Removed learn more button * fixed a test --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
502cf7e3e5 | Bump @floating-ui/react, MUI (within v5) and react-day-picker for React 19 support (#38337) | ||
|
|
b3946ef5e2 |
[MM-69862][MM-69867] Lock Definition and Applies-to for plugin-owned attributes (#38268)
* [MM-69862][MM-69867] Lock Definition and Applies-to for plugin-owned attributes
Plugin-owned attributes currently redirect away from the edit page entirely,
with no way to inspect them. This renders the page instead, with every
Definition control (Display Name, Unique Name, Type, Options) shown in its
existing disabled state and a "Managed by {plugin}" panel replacing the
LDAP/SAML picker; Applies-to's Add-resource triggers are hidden and existing
resource rows stay visible but read-only. Also enables the listing's Edit
action for plugin-owned rows, since the page it now leads to can render them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Make lock tooltips keyboard-accessible
WithTooltip's useFocus only fires on its cloned child, so a disabled control
wrapped for a lock tooltip needs the wrapper span itself to be focusable, or
keyboard/screen-reader users can never reach the tooltip explaining the lock
(mouse hover only). Fixes this for the 3 new Applies-to row toggles plus the
3 pre-existing lock wraps in the same file (Type, Unique Name, external
source) that had the same gap.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Label the listing's action "View attribute" for plugin-owned rows
The edit page is now reachable but fully locked for these rows, so "Edit
attribute" sets the wrong expectation before the admin even clicks. Swaps
to "View attribute" with an eye icon, matching Edit's own convention of
pairing a distinct label with what the destination page actually offers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix orphaned-plugin lock copy and a plugin-status-fetch race
Type/Unique-Name/Applies-to lock tooltips still said "managed by a plugin"
(present tense) for an orphaned attribute, contradicting the "(no longer
installed)" copy the Managed-by panel already showed one card up -- none of
the three lock-reason computations read isOrphaned, only isPluginOwned. Adds
a pluginOrphaned reason/copy variant to all three, and a shared
firstMatchingReason() helper so the "which lock reason wins" priority chain
exists once instead of being copy-pasted per lockable field.
Also: the "Plugin settings" link could briefly render in its optimistic
(non-orphaned) form before the plugin-inventory fetch settles, since
isOrphaned reads false during that window regardless of the real state.
Threads pluginInventoryLoaded into AttributePluginSource and gates the link
on it too, so it stays hidden until the orphan check is actually known.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
|
||
|
|
1b6a579759 |
Sync Cloud Agent Go toolchain with server/.go-version (1.26.7) (#38298)
* Sync Cloud Agent Go toolchain with server/.go-version (1.26.7) The checked-in Dockerfile was still pinned at Go 1.25.9 after several .go-version bumps. Agents that boot from a default/personal image can also land on an older distro Go (this environment had 1.22.2), which cannot build server/. Pin the image to 1.26.7 and make the install hook install whatever server/.go-version requires when the running toolchain does not match. Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Align Cloud Agent Node toolchain with .nvmrc Mirror the Go self-heal: read the required version from .nvmrc (or CLOUD_AGENT_NODE_VERSION), install it via nvm when the running node does not match, symlink node/npm/npx onto /usr/local/bin, and persist nvm on PATH. A major.minor pin such as 24.11 matches 24.11.x. Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Revert Cloud Agent toolchain notes from AGENTS.md Keep version-pin documentation in .cursor/README.md so the root AGENTS.md stays unchanged. Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Persist the active NVM_DIR in cloud-agent-install.sh Write the resolved NVM_DIR into .bashrc so a custom nvm location survives later shells instead of always resetting to $HOME/.nvm. Co-authored-by: maria.nunez <maria.nunez@mattermost.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: maria.nunez <maria.nunez@mattermost.com> |
||
|
|
791fc09b5d |
Test and lint mmgotool in CI (#38338)
* Fix the pre-existing lint failures in mmgotool Nothing lints this module yet, so these accumulated: an unchecked Close on the en.json write path, two unused walk parameters, a nil initialiser and three redundant breaks. The Close is the only behaviour change -- en.json is regenerated wholesale, so a failed flush would otherwise truncate it silently. * Test and lint mmgotool in CI tools-ci.yml only ever covered mattermost-govet, and each tool under tools/ is its own Go module that the server and mmctl suites cannot reach -- so mmgotool was tested and linted nowhere. Matrix both jobs over the two modules and give mmgotool the Makefile and golangci config the matrix expects. |
||
|
|
1807e4b681 |
[MM-70273] Validate and limit mention_keys notify prop (#38121)
* [MM-70273] Validate and limit mention_keys notify prop Add MentionKeysExceedLimits validation to User.IsValid and bulk import, and pass MaxCount/MaxLength params so error messages render the actual limits. * Fix golangci-lint modernize: use strings.SplitSeq in parseMentionKeys * Restore i18n strings lost during merge with origin/master MM-70291 added global_relay custom header strings after this branch was cut. The merge conflict resolution dropped them; restore them now. * Don't cap mention keys in PreUpdate so IsValid can reject them * fixup! Don't cap mention keys in PreUpdate so IsValid can reject them * fixup! Restore i18n strings lost during merge with origin/master * use joinMentionKeys for clarity * Cap mention keys while parsing and bound the raw value * Test mention key normalization and limit boundaries --------- Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
9905a827e2 |
Skip flaky TestLicenseFromBytesEnvironmentMismatch (MM-70560) (#38297)
Automatic Merge |
||
|
|
43b2ae87e0 |
Stop encoding English grammar in four i18n source strings (#38228)
* Allow direct modifications to non-English translation files
i18n-ci-template.yml existed to fail any PR that touched a non-English
translation file unless it came from the weblate account, back when
translations arrived exclusively through Weblate.
Translations can now be authored in-repo and land in the same PR as the
English string they translate, so that gate rejects exactly the change it
should be letting through. The changed-file detection stays -- a direct edit to
a locale catalog is still worth surfacing on the PR -- but it emits a warning
annotation instead of failing, and the weblate-account bypass is gone with
nothing left to bypass.
* Remove EnableExperimentalLocales and the experimental locales
The 42 webapp and 33 server locale catalogs outside the supported 22 were
never selectable except behind EnableExperimentalLocales, and they had
rotted: hundreds of thousands of missing keys between them, several files
100% missing, and strings that fail to parse with the runtime ICU parser and
would throw if a user ever selected that language. We are not shipping those
languages, so the catalogs go.
With the locale set trimmed to the supported 22 the setting was left inert.
It fed getAllLanguages(true), which built entries from langIDs -- the list of
locale files on disk -- and then spread the curated languages map over the
top. With langIDs now a subset of that map, every experimental entry was
shadowed and the flag changed nothing: getAllLanguages(true) deep-equalled
getAllLanguages().
Rather than leave an admin console toggle that does nothing, this removes the
setting end to end: the model field and its default, the client prop, the
TypeScript config types, the admin console control and its two strings, the
playwright default config, and the configuration docs.
Downstream of that, getAllLanguages no longer takes a parameter, and
getLanguageInfo reads the supported map directly instead of going through the
experimental path -- so it now resolves the supported 22 rather than whatever
JSON happens to sit in the locale directory.
That in turn orphaned the langIDs and langLabels exports from
gen_lang_imports.mjs, and langmap.mjs existed only to populate langLabels, so
all three go too. imports.ts is regenerated and now imports exactly the 21
supported non-English catalogs.
The two orphaned admin strings are dropped from en.json and from the 13
translated catalogs that carried them.
Existing config.json files keep working: config is decoded without
DisallowUnknownFields, so a stale EnableExperimentalLocales key is ignored.
The v10 changelog entry for the setting stays: it is a historical record of a
release that did ship it.
* Stop encoding English grammar in four i18n source strings
Four messages made themselves impossible to translate faithfully, by building an
English sentence out of parts and asking every other language to reassemble it
the same way. Each one forces translators into a structure the source does not
have, which is a defect in the source, not in the translations.
This is {article} {minimumPlanRequiredForFeature} feature, ...
`article` was 'an' or 'a', chosen in TypeScript from the plan name. Most target
languages have no indefinite article at all; those that do inflect it for gender
and case, which cannot be decided before the noun. Every locale that kept the
placeholder renders a stray English "a" mid-sentence -- German ships "Dies ist a
Professional Funktion" today. Rephrased so no determiner is needed:
This feature requires {minimumPlanRequiredForFeature}, ...
and the `article` value, and the LicenseSkus import it needed, are gone.
{count} messages exported.
{count} warning(s) encountered, see warning.txt for details
A bare count welded to a fixed noun, plus the "(s)" hack. Ukrainian, Polish and
Russian need one/few/many/other and had nowhere to put the agreement, so
translators wrapped the variable in a plural the source did not declare. Now
each is a plural in the source, which costs English nothing and gives every
locale the structure it needs.
... {countReturned, number} {countReturned, plural, one {result} other {results}} found out of {totalCount} ...
The number was split from its noun into a separate argument, inconsistently --
{countReturned, number} beside a bare {totalCount}. ICU's # does both jobs at
once and applies the locale's number formatting, which is what zh-CN was
reaching for when it added {totalCount, number} of its own accord.
Save and remove {amount, plural, one {user} other {users}}
A plural selector on a count the button never shows, so de, ja, vi and zh-TW put
the number back. This one is the least clear-cut of the four -- the English is
defensible as written -- and it changes the button to "Save and remove 3 users".
It is a one-line revert if that is not wanted.
Changing a message orphans its translations, so the 21 catalogs are realigned
here rather than left to fail. No new translation of anything:
16 wrapped in {n, plural, other {...}} for a locale with a single plural
category, where that is a no-op on the rendered string
6 folded a standalone {n} or {n, number} into the # of the plural beside it,
which renders identically
6 en-AU set to the regenerated English, which is what it already held
69 dropped, because the entry translates a sentence that no longer exists
The dropped entries fall back to English and rejoin the translation backlog.
Some, like the German LDAP string that splits a noun across a plural to supply
only its suffix, cannot be refolded onto # without rewriting the translation.
Checked with the deterministic ICU lint: these six keys now produce zero errors
with the exception list disabled entirely, and the tree's error count is two
lower than before rather than higher. i18n-extract:check passes, and the message
export settings tests, including the getJobDetails cases that exercise two of
these strings, still pass with their snapshots.
* Update MM-T1168 for the pluralized export count
admin.complianceExport.messagesExportedCount became an ICU plural, so a
single-message export now renders "1 message exported." rather than the
ungrammatical "1 messages exported." the assertion still expected.
The other assertions on this string all pass counts of 2 or more, which
select the same "other" branch as before and keep passing unchanged.
|
||
|
|
e9379690d5 |
Remove EnableExperimentalLocales and the experimental locales (#38219)
* Allow direct modifications to non-English translation files i18n-ci-template.yml existed to fail any PR that touched a non-English translation file unless it came from the weblate account, back when translations arrived exclusively through Weblate. Translations can now be authored in-repo and land in the same PR as the English string they translate, so that gate rejects exactly the change it should be letting through. The changed-file detection stays -- a direct edit to a locale catalog is still worth surfacing on the PR -- but it emits a warning annotation instead of failing, and the weblate-account bypass is gone with nothing left to bypass. * Remove EnableExperimentalLocales and the experimental locales The 42 webapp and 33 server locale catalogs outside the supported 22 were never selectable except behind EnableExperimentalLocales, and they had rotted: hundreds of thousands of missing keys between them, several files 100% missing, and strings that fail to parse with the runtime ICU parser and would throw if a user ever selected that language. We are not shipping those languages, so the catalogs go. With the locale set trimmed to the supported 22 the setting was left inert. It fed getAllLanguages(true), which built entries from langIDs -- the list of locale files on disk -- and then spread the curated languages map over the top. With langIDs now a subset of that map, every experimental entry was shadowed and the flag changed nothing: getAllLanguages(true) deep-equalled getAllLanguages(). Rather than leave an admin console toggle that does nothing, this removes the setting end to end: the model field and its default, the client prop, the TypeScript config types, the admin console control and its two strings, the playwright default config, and the configuration docs. Downstream of that, getAllLanguages no longer takes a parameter, and getLanguageInfo reads the supported map directly instead of going through the experimental path -- so it now resolves the supported 22 rather than whatever JSON happens to sit in the locale directory. That in turn orphaned the langIDs and langLabels exports from gen_lang_imports.mjs, and langmap.mjs existed only to populate langLabels, so all three go too. imports.ts is regenerated and now imports exactly the 21 supported non-English catalogs. The two orphaned admin strings are dropped from en.json and from the 13 translated catalogs that carried them. Existing config.json files keep working: config is decoded without DisallowUnknownFields, so a stale EnableExperimentalLocales key is ignored. The v10 changelog entry for the setting stays: it is a historical record of a release that did ship it. |
||
|
|
bbd208cffd |
chore(ci): bump pr-test-analysis toolkit ref to pick up claude-code-action v1.0.210 (#38335)
Points at mattermost/mattermost-test-automation-toolkit b51a481e3fe6332053be09800d59da1a8c3e3e2e, which upgrades anthropics/claude-code-action from v1.0.70 → v1.0.210. v1.0.210 includes anthropics/claude-code-action#1580 (shipped in v1.0.188), which passes no-cache: true to oven-sh/setup-bun. The upstream setup-bun key is not ref-aware, so every second-and-subsequent run against the same PR ref hit a 409 on save, and @actions/cache treated the HTML error body as transient and burned ~20-30s on 5 retries plus emitted a noisy warning per PR push. Disabling the cache is a net wallclock win — the 35 MB Bun binary downloads in 2-3s. Refs: - https://github.com/anthropics/claude-code-action/pull/1580 - https://github.com/anthropics/claude-code-action/issues/1252 - https://github.com/anthropics/claude-code-action/releases/tag/v1.0.210 |
||
|
|
d78e09b9a5 |
fix(check-external-links): follow 301s, route GET-fallback through the loop, skip Zendesk (#38319)
* docs: add 10 more missing legacy redirect entries to active-redirects.json Fixes 404s for legacy Sphinx URLs that mattermost.com/pl/* shortlinks still redirect to (10 shortlinks → 9 unique target paths under legacy IA prefixes, plus /developer/bot-accounts for the docs-subdomain merge). Bumps _meta.active 747 → 757. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(check-external-links): follow 301s and skip Zendesk-hosted URLs Two related fixes to the CI link checker: 1. Remove the 301 short-circuit in processResponse so 301 responses are followed to their final destination just like 302s. The previous behavior masked real 404s at the end of redirect chains — in particular, mattermost.com/pl/* shortlinks whose docs.mattermost.com destinations no longer exist post-migration were reported as OK. 2. Add support.mattermost.com to a new UNCRAWLABLE_HOSTS skip set. Zendesk fronts these URLs with Cloudflare bot management that inspects TLS fingerprints (JA3/JA4) and blocks non-browser clients regardless of User-Agent spoofing. The URLs are all valid in a browser; broken help-center articles surface via support tickets. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(check-external-links): route GET-fallback responses through the redirect loop When HEAD returns 405/403, we retry with GET. Previously the fallback branch returned processResponse's result directly, which bypassed the outer loop's Location handling. Combined with the earlier removal of the 301 short-circuit, a GET that returned a 3xx would come back to the caller as {redirect: true} and be treated as broken with no error text. Reassign 'response' in the fallback branch so the same code path below processes and follows the response like any other HEAD result. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(check-external-links): release response bodies before continuing chain We only read status and headers from each response but never consume the body. Node's fetch (undici) keeps the underlying socket reserved until the body is consumed or cancelled, so under concurrency this pinned sockets in the pool longer than necessary. Wrap the per-iteration logic in try/finally so the body is always released, and cancel the HEAD response body before replacing it with the GET fallback. HEAD responses have a null body (safe no-op) but the GET fallback returns real bodies we never read. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
53e6ffa876 |
Block link previews after redirects to restricted domains (#36665)
* Block link previews after redirects to restricted domains Co-authored-by: mattermost-code <matty-code@mattermost.com> * Allow localhost in link metadata redirect test Co-authored-by: mattermost-code <matty-code@mattermost.com> * Correct redirect metadata test setup Co-authored-by: mattermost-code <matty-code@mattermost.com> * Strengthen link metadata redirect tests Co-authored-by: mattermost-code <matty-code@mattermost.com> * Avoid draining rejected redirect responses Co-authored-by: mattermost-code <matty-code@mattermost.com> * Preserve default redirect limit in link metadata client Co-authored-by: mattermost-code <matty-code@mattermost.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: mattermost-code <matty-code@mattermost.com> Co-authored-by: Mattermost Build <build@mattermost.com> |
||
|
|
3bd85d0a93 |
[MM-69882] Decouple pin/unpin from ServiceSettings.PostEditTimeLimit (#38296)
* Decouple pin/unpin from ServiceSettings.PostEditTimeLimit The post edit time limit was extended to cover pin state, so posts older than ServiceSettings.PostEditTimeLimit could no longer be pinned or unpinned. Scope the limit back to post content (message, props, files) so pin state can always be changed, and drop the pinning no-op shortcut that only existed to soften that check. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Broaden pin/unpin time limit coverage Derive the content check from PostPatch.IsEmpty so a future patch field is gated by the edit time limit by default, and cover the helper directly, an unpin through updatePost, a non-author pin/unpin, and EditAt staying put. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Guard PostPatch.IsEmpty against silently exempting new fields The edit time limit now derives its content check from IsEmpty, so cover IsEmpty directly next to the struct it mirrors and assert the field count so a new field cannot slip past unnoticed. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Tighten test comments and use reflect.TypeFor Co-authored-by: mattermost-code <matty-code@mattermost.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: mattermost-code <matty-code@mattermost.com> |
||
|
|
97813196cd |
docs: unescape HTML entities inside markdown code spans (#38329)
* docs: unescape HTML entities inside markdown code spans RST-to-MDX migration left </> placeholders inside backticks, which rendered literally. Restore <placeholder> syntax in code spans only. * correct the url schema |
||
|
|
240b9bed8b |
[MM-70274] Add permissions migration so upgraded installs can access the System Console Recaps page (#38058)
* [MM-70274] Add permissions migration for AI Recaps sysconsole permissions The sysconsole_read_ai_recaps and sysconsole_write_ai_recaps permissions were only added to the SysconsoleReadPermissions/SysconsoleWritePermissions slices. Those slices are applied to system_admin by the one-time add_system_console_permissions migration, which has already run on every existing install, so upgraded servers never received the new permissions and the System Console Recaps page stayed hidden for system admins. Add a dedicated add_ai_recaps_permissions migration that grants both permissions to the system_admin role, matching fresh-install defaults. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Add tests for the AI Recaps permissions migration Cover the transformation itself (system_admin gains both permissions, other roles untouched, idempotent on re-run) and guard that the migration is actually registered in the migration list, which is the mistake that left existing installs without the permissions. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Tighten the AI Recaps migration registration test Assert the exact permission count so a migration key that collides with an already-completed key, which would never run on a real upgrade, fails the test. Drop the unreachable store expectations. Co-authored-by: mattermost-code <matty-code@mattermost.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: mattermost-code <matty-code@mattermost.com> |
||
|
|
01fd4cfe26 |
Stabilize flaky Cypress and Playwright E2E specs (#38300)
* Stabilize flaky Cypress and Playwright E2E specs Fix Reset password menu targeting, mention-badge isolation, invite enablement, local image aspect-ratio fixtures, and Playwright waits for members RHS, BoR posts, file attachments, and ABAC menus. Co-authored-by: saturnino <saturnino@mattermost.com> * Fix BoR concealed post locator after reveal Co-authored-by: saturnino <saturnino@mattermost.com> * Harden Cypress locators and image fixtures Use existing aspect-ratio fixtures, invite chips, and emoji search so the flake specs wait on the right UI instead of avatars or long timeouts. Co-authored-by: saturnino <saturnino@mattermost.com> * Read Cypress smtp and db URLs from the environment Co-authored-by: saturnino <saturnino@mattermost.com> * Replace Playwright E2E magic timeouts with readiness checks Drop half-minute and 30s waits in favor of visible/attached/enabled assertions and file-preview counts so specs do not rely on brittle timeouts. Co-authored-by: saturnino <saturnino@mattermost.com> * Fix compact-view edited indicator flake in MM-T103 Co-authored-by: saturnino <saturnino@mattermost.com> * Wait for the image post before asserting aspect ratio Co-authored-by: saturnino <saturnino@mattermost.com> * Shorten image post readiness comment Co-authored-by: saturnino <saturnino@mattermost.com> * Save edited posts via the Save control; expand image embeds Co-authored-by: saturnino <saturnino@mattermost.com> * Expand collapsed image embeds before ratio asserts Co-authored-by: saturnino <saturnino@mattermost.com> * Fix post-edit Save locator and collapsed image embed click Co-authored-by: saturnino <saturnino@mattermost.com> * Wait for file attach before expanding image embeds Co-authored-by: saturnino <saturnino@mattermost.com> * Keep the mention notification stub after channel switch Co-authored-by: saturnino <saturnino@mattermost.com> * Shorten mention-spy readiness comment Co-authored-by: saturnino <saturnino@mattermost.com> * Pin MM-T103 to the posted text instead of last post Co-authored-by: saturnino <saturnino@mattermost.com> * Force-click the remounted post edit menu item Co-authored-by: saturnino <saturnino@mattermost.com> * Requery the post box after re-render; wait for domain config Co-authored-by: saturnino <saturnino@mattermost.com> * Assert restricted-domain config before signup and invite Co-authored-by: saturnino <saturnino@mattermost.com> * Send MM-T103 posts via the Send button Co-authored-by: saturnino <saturnino@mattermost.com> * Harden Add People empty-state and file-chooser waits Assert the real no-matches copy instead of a stale class, wait for Add to be enabled, and attach files via the filechooser event so uploads do not depend on a long preview timeout. Co-authored-by: saturnino <saturnino@mattermost.com> * Dismiss a leftover emoji picker before opening another Co-authored-by: saturnino <saturnino@mattermost.com> * Wait for upload and post-create APIs before UI asserts Co-authored-by: saturnino <saturnino@mattermost.com> * Click the Add-email option and invite from a new team Co-authored-by: saturnino <saturnino@mattermost.com> * Reload login after client config shows creation disabled Co-authored-by: saturnino <saturnino@mattermost.com> * Create the invite team before restricting email domains Co-authored-by: saturnino <saturnino@mattermost.com> * Clear leftover domain restriction before creating the invite team Co-authored-by: saturnino <saturnino@mattermost.com> * Pass the uploaded post ID when opening the image RHS Co-authored-by: saturnino <saturnino@mattermost.com> * Use # and * E2E comments on the stabilized specs Co-authored-by: saturnino <saturnino@mattermost.com> * Attach uploads via the hidden file input Co-authored-by: saturnino <saturnino@mattermost.com> * Wait for the deletion report file before opening details Co-authored-by: saturnino <saturnino@mattermost.com> * Poll site-statistics guest counts after creating guests Co-authored-by: saturnino <saturnino@mattermost.com> * Free leftover CPA fields before hitting the 20-field cap Co-authored-by: saturnino <saturnino@mattermost.com> * Keep the polled guest-count baseline for the increment assert Co-authored-by: saturnino <saturnino@mattermost.com> * Delete only this suite's leftover UAAE CPA fields Co-authored-by: saturnino <saturnino@mattermost.com> * Wait for the upload API before asserting the file preview Co-authored-by: saturnino <saturnino@mattermost.com> * Format the guest-count poll for Prettier Co-authored-by: saturnino <saturnino@mattermost.com> * Type at-mentions so profile-popover autocomplete can search Co-authored-by: saturnino <saturnino@mattermost.com> * Open the content-review thread to assert the deletion report Co-authored-by: saturnino <saturnino@mattermost.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: saturnino <saturnino@mattermost.com> |
||
|
|
4086261f00 |
docs: fix 10 legacy redirect gaps and two bugs in the external link checker (#38318)
* docs: add 10 more missing legacy redirect entries to active-redirects.json Fixes 404s for legacy Sphinx URLs that mattermost.com/pl/* shortlinks still redirect to (10 shortlinks → 9 unique target paths under legacy IA prefixes, plus /developer/bot-accounts for the docs-subdomain merge). Bumps _meta.active 747 → 757. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(check-external-links): follow 301s and skip Zendesk-hosted URLs Two related fixes to the CI link checker: 1. Remove the 301 short-circuit in processResponse so 301 responses are followed to their final destination just like 302s. The previous behavior masked real 404s at the end of redirect chains — in particular, mattermost.com/pl/* shortlinks whose docs.mattermost.com destinations no longer exist post-migration were reported as OK. 2. Add support.mattermost.com to a new UNCRAWLABLE_HOSTS skip set. Zendesk fronts these URLs with Cloudflare bot management that inspects TLS fingerprints (JA3/JA4) and blocks non-browser clients regardless of User-Agent spoofing. The URLs are all valid in a browser; broken help-center articles surface via support tickets. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(check-external-links): route GET-fallback responses through the redirect loop When HEAD returns 405/403, we retry with GET. Previously the fallback branch returned processResponse's result directly, which bypassed the outer loop's Location handling. Combined with the earlier removal of the 301 short-circuit, a GET that returned a 3xx would come back to the caller as {redirect: true} and be treated as broken with no error text. Reassign 'response' in the fallback branch so the same code path below processes and follows the response like any other HEAD result. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |