* Add Session Attributes system console page shell Register the gated System Console "Session Attributes" subsection backed by a component page that loads the session_attributes property fields, plus a feature discovery upsell. Removes the former config toggles from the UI. Includes display helpers and unit tests. Co-authored-by: Cursor <cursoragent@cursor.com> * Add Session Attributes listing table Render the read-only session attributes table with Display Name, Name (with server-sourced label), derived Type, platform availability icons, TTL, grace period, and status columns, plus loading and empty states. Includes status chip, platform icon, and table unit tests. Co-authored-by: Cursor <cursoragent@cursor.com> * Add Session Attributes row actions and staged save Add a per-row actions menu to tune TTL and grace period via presets and to disable/enable each attribute, with a destructive disable confirmation modal. Edits stage locally and persist on save via a per-field property patch thunk, with an unsaved-changes guard. Includes the patch thunk and component tests. Co-authored-by: Cursor <cursoragent@cursor.com> * Surface session attributes in permission policy editors only Include session attributes in the permission policy attribute picker (both advanced and table editor modes) and group them under a dedicated section, while excluding them from membership and parent access-rule editors via a shared helper. Surface the server rejection message when a membership rule references session attributes. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix lint formatting in session attributes table test Split single-line renderWithContext JSX in session_attributes_table.test.tsx onto multiple lines to satisfy react/jsx-max-props-per-line, matching the existing test formatting convention. Surfaced by the pre-commit ESLint gate. Co-authored-by: Cursor <cursoragent@cursor.com> * Add Session Attributes E2E specs Add Playwright end-to-end coverage for the Session Attributes console page: listing render, staged TTL/grace/enable edits with save persistence, disable confirmation modal, dirty navigation guard with cancel revert, and permission-versus-membership picker polarity. Includes the page object and sidebar wiring. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix playwright-lib build to resolve @/* tsconfig path alias The lib rollup build (@rollup/plugin-typescript) compiled the @/* tsconfig path alias but never rewrote it, so with preserveModules the emitted dist files still imported the unresolved @/ specifier. Node could not load these, failing Playwright config loading for the entire suite (Cannot find module @/ui/components). Add a minimal inline rollup resolveId plugin that maps @/* to the real src file so emitted dist uses correct relative imports. No new dependency required. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix session attribute group identity to use real group id The webapp conflated the property group NAME (session_attributes) with the group's real DB id. The server keys every field under a 26-char group UUID, so the listing page selected fields under the name and always rendered "No session attributes found." on a real server, and the ABAC editors compared field.group_id against the name, which never matched live data. Resolve the group properly instead: - fetchPropertyFields now also dispatches RECEIVED_PROPERTY_GROUP (mirroring the managed-channel-categories flow) so the group name -> id mapping is available, and the listing page selects fields by the resolved group id via getPropertyGroupByName. - Add isSessionAttributeField, keyed on the session object_type, and use it in the ABAC consumers (attribute picker, excludeSessionAttributes, permission policy picker, simulate-access modal). object_type is intrinsic to the autocomplete fields and avoids the name/UUID conflation entirely. - Relax UserPropertyField.group_id to string since the server returns a UUID; the group name is only ever a client-side placeholder for an unsaved field. Harden the unit tests to use realistic group UUIDs distinct from the group name and correct object_type values, so fixtures can no longer pass with a group_id the server never produces. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix SaveChangesPanel footer Cancel to revert in place The admin SaveChangesPanel rendered its onCancel handler through a BlockableButton, which intercepts clicks while navigation is blocked and defers the action via deferNavigation instead of invoking it. On the Session Attributes page this meant footer Cancel never reverted staged edits directly: it re-opened the Discard Changes guard and only reverted if a deferred navigation was later confirmed. BlockableButton is only appropriate for the cancelLink (navigation) path. For an in-place revert handler, wire onCancel to a plain button so it calls the hook cancel() directly, reverting staged edits, clearing the dirty state, releasing setNavigationBlocked, and never showing the discard modal. Repair the page-level jest test to exercise the real footer Cancel and assert direct revert plus nav-guard release without confirming a deferred navigation. Co-authored-by: Cursor <cursoragent@cursor.com> * Merge enabled session attributes into permission policy editors Inject enabled session attributes into the two permission-policy attribute pickers (CEL and table editors) while keeping membership/parent-policy surfaces restricted to long-lived user attributes via excludeSessionAttributes. Adds a useEnabledSessionAttributeFields hook plus the shared mergeSessionAttributes/celPrefixForField helpers so the editors can build CEL across the user.attributes.* and user.session.* namespaces. Addresses two review nits: - Resolve the picked attribute by its unique field id (not bare name) so a session attribute sharing a name with a CPA attribute still maps to object_type 'session' and emits user.session.<name>; the picker also uses id-based DOM ids to avoid collisions. - Make celPrefixForField the single source of CEL-prefix truth by consuming it in rowToCEL instead of inlining the namespace check. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix Session Attributes E2E spec: id-based picker selectors and per-test state isolation The attribute picker menu items are keyed by field id (#attribute-<id>) rather than name, so the permission/membership picker assertions now resolve session attribute ids from the property-fields API and the Department user-attribute id from the CPA fields API instead of targeting stale name-based ids. Add an afterEach that restores every seeded session attribute to a baseline captured at suite start, replacing the per-test finally restores. This keeps the suite order-independent so the listing test "Disabled by default" assertions no longer depend on whether another test has enabled a field first. Co-authored-by: Cursor <cursoragent@cursor.com> * UX Polish * Linting * Removed styled components * Fix session attributes unit tests Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Fix invalid Sass '&&&' selector breaking production build Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Stabilize session attributes duration-preset e2e interaction Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Fix broken session attributes doc link in feature discovery Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Fix session attributes test type error from non-union group id Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Revert unnecessary playwright-lib rollup @/* alias resolver @rollup/plugin-typescript already resolves the @/* tsconfig path alias; building with and without the added resolveSrcAlias plugin produces byte-identical dist JS, so the change was a no-op. Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Allow session-attribute row defaults and resolve table-editor lookups by namespace findFirstAvailableAttributeFromList now treats session attributes as selectable (mirroring AttributeSelectorMenu), so a session-only attribute set no longer fails addRow. The operator menu reads row.attribute_type and the value options match on name + object_type, so a user/session name collision resolves the correct namespace. Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Type session-attribute platforms on the shared property attrs Add platforms?: string[] to UserPropertyField.attrs so the attribute picker reads it without an unsafe cast, and narrow the platform filter in getSessionAttrs with a type guard. Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Session Attributes page: surface load errors, propagate read-only, and improve a11y Handle a failed property-fields fetch with an explicit error state instead of leaving the rejection uncaught and showing the empty state. Thread the page's disabled flag through to the row dot menus so read-only admins cannot stage edits. Convey each platform icon's active/inactive state in its accessible name, and document the table border override. Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * SaveChangesPanel: use shared Button for the in-place cancel control Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Cover fetchPropertyFields group name to uuid mapping Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * E2E: scope session-attribute submenu trigger to its row and document single-field save Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Comment linting * scss cleanup * linting * PR feedback * PR Feedback. Removed IP + version icons * PR feedback * PR feedback * Fix session attributes table test after IP/Version display types were removed Co-authored-by: maria.nunez <maria.nunez@mattermost.com> * Session Attributes admin console UX fixes - Reduce table row height and shrink-wrap the panel to the table - Reorder LHS sidebar so Session Attributes follows User Attributes - Cap attribute selector popover at 400px max-height with scroll Co-authored-by: Cursor <cursoragent@cursor.com> * Fix native attribute selection by assigning synthetic field IDs The ABAC editors resolve the selected attribute by field ID, but the synthetic native user attribute fields were created without an ID. Every built-in attribute therefore resolved to the first empty-ID field (Email), so no other built-in could be selected. Assign each native field a stable, unique, non-empty ID so id-based resolution works for all built-ins. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
Mattermost Web App
This folder contains the client code for the Mattermost web app. It's broken up into multiple packages each of which either contains an area of the app (such as playbooks) or shared logic used across other packages (such as the packages located in the platform directory). For anyone who's used to working in the mattermost/mattermost-webapp repo, most of that is now located in channels.
npm Workspaces
To interact with a workspace using npm, such as to add a dependency or run a script, use the --workspace (or --workspaces) flag. This can be done when using built-in npm commands such as npm add or when running scripts. Those commands should be run from this directory.
# Add a dependency to a single package
npm add react --workspace=playbooks
# Build multiple packages
npm run build --workspace=platform/client --workspace=platform/components
# Test all workspaces
npm test --workspaces
# Clean all workspaces that have a clean script defined
npm run clean --workspaces --if-present
To install dependencies for a workspace, simply run npm install from this folder as you would do normally. Most packages' dependencies will be included in the root node_modules, and all packages' dependencies will appear in the package-lock.json. A node_modules will only be created inside a package if one of its dependencies conflicts with that of another package.
Useful Links
- Developer setup, now included with the Mattermost server developer setup
- Web app developer documentation
Dependency Changes
Any PR that modifies package.json or package-lock.json needs extra scrutiny:
- No duplicate libraries. Before adding a new dependency, check whether an existing one already covers the same use case. Multiple libraries for the same purpose (e.g., two different date pickers, or Bootstrap 3 and Bootstrap 4 simultaneously) create long-term upgrade pain.
- License check. New dependencies must not use GPL or similarly restrictive licenses. Dependencies with no license at all should also be flagged.
- Justify the addition. A new dependency should solve a real problem that existing code or dependencies don't already address. Push back on adding packages for trivial functionality.
- Version conflicts. Check whether the new dependency introduces conflicting peer dependency versions. Cascading version conflicts are expensive to untangle later and have historically blocked upgrades for months.