* fix(review-workflows): fetch all users in assignee dropdown
The AssigneeSelect component passed no pagination params to
useAdminUsers, which defaults to pageSize 10. Users with more than
10 admin accounts could not see or select all assignees.
Pass pageSize: 100 so the dropdown lists all available users.
Fixes#25945
* fix(review-workflows): incremental loading + preserve current assignee
Replaces the fixed pageSize:100 fetch with the same incremental Combobox
pattern used by the admin-users filter (Filters.tsx): grow pageSize on
onLoadMore, debounce server-side search via _q, and reset both on close.
Also keeps the currently assigned user in the option list when they fall
outside the loaded page or active search, so the Combobox never loses
its value.
* fix(review-workflows): improve assignee dropdown pagination search
---------
Co-authored-by: Bassel Kanso <basselkanso82@gmail.com>
* chore(examples): add mariadb + sqlite + podman support to complex
Extends the complex example's DB tooling to cover all Strapi-supported
database dialects and both container runtimes, as groundwork for a
migration performance benchmark harness:
- New compose.js runtime shim auto-detects podman compose / podman-compose
/ docker compose / docker-compose and the matching container CLI; all
existing db-* scripts now go through it so podman-only environments
work without installing docker
- New db-mariadb.js mirrors db-mysql.js using mariadb-dump / mariadb CLIs
and adds a mariadb:11 service on port 3307 to docker-compose.dev.yml
- New db-sqlite.js handles file-based snapshot/restore/wipe/check via
fs.copy / better-sqlite3
- db-utils.js falls back to `<runtime> ps --filter name=` for container
lookup since podman-compose doesn't support `ps -q`
- develop-with-db.js and the v4 templates (develop-with-db.js,
seed-with-db.js) handle mariadb + sqlite (sqlite skips compose)
- setup-v4-project.js includes better-sqlite3 in v4 deps, database.js
template covers all 4 clients, and compose.js is copied into the
v4 scaffold scripts dir (dep of db-utils.js)
All four DBs smoke-tested locally against podman: start/check/snapshot/
restore/wipe cycle works for mariadb; cp-based snapshot cycle works
for sqlite.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(examples): add migration perf benchmark harness
Three new scripts enable per-migration timing and baseline-vs-candidate
comparison reports for v4→v5 migrations in the complex example:
- bench-hook.js: Node --require preload that intercepts require('umzug')
and subscribes to Umzug's native `migrating`/`migrated` events for
sub-ms timing. Captures every migration that runs (including dynamically
registered ones like discard-drafts and EE-only release migrations)
without hardcoding names. Dumps to a JSON file on process exit; self-
disables when STRAPI_BENCH_HOOK_OUTPUT is unset.
- bench.js: orchestrator with `run`, `seed`, and `suite` subcommands.
`run` restores a snapshot, spawns Strapi in migrate-then-exit mode
with the hook preload, collects row counts, and writes a result JSON
with baseline/candidate attribution, env capture (node, CPU, memory,
DB version, host type), and config (multiplier, seed/hook modes).
`seed` wipes the DB, runs the v4 seed via seed-with-db.js, then
snapshots. First iteration supports --strapi-source=local only;
experimental/pinned are stubbed with a clear error.
- bench-compare.js: takes N labels and emits both a clipboard-friendly
markdown report (stdout + results/compare-*.md) and a self-contained
HTML report (results/compare-*.html) with inline SVG bar charts,
per-DB grid, sortable tables, collapsible raw JSON, and a light/dark
adaptive theme via prefers-color-scheme. No CDN deps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(examples): bench harness smoke-test fixes
Fixes discovered during the end-to-end smoke test on the existing 6
content types at multiplier=1:
- bench-hook.js: switch from subclass-based wrapping to in-place
Umzug.prototype.up patching. The subclass approach replaced the module
export at require time, but Node's module cache hands out the original
class on subsequent requires, so listeners weren't attached on all
instances. In-place prototype patching works for every instance
regardless of how Umzug was imported.
- bench-hook.js: flush incrementally after each recorded migration.
Strapi's shutdown path can bypass process.on('exit') handlers under
some conditions (signal or explicit exit from deep inside), causing
fully-collected timing data to be lost. Writing after each recording
makes the benchmark resilient to any exit path.
- bench.js: compile TypeScript configs via @strapi/typescript-utils
before createStrapi().load(). The examples/complex project has .ts
config files; the Strapi CLI compiles them to dist/ before boot but
our direct node -e loader skipped this, producing
"db.config.connection undefined" failures.
- bench.js: propagate STRAPI_BENCH_HOOK_DEBUG to the Strapi child so
debug output is visible when tracing hook behavior.
- bench-compare.js: rework the SVG chart. Dynamic label column sized
to the longest migration name (up to 420px), 80px reserved on the
right for value labels so they never clip, inlined monospace font
(SVG text doesn't reliably inherit CSS variables from the surrounding
stylesheet), and `dominant-baseline="middle"` for proper vertical
centering.
Verified: full pipeline (setup:v4 → seed → snapshot → bench:run →
bench:compare) works against postgres at multiplier=1. Ran a baseline
vs cherry-picked PR #25988 comparison — captured all 7 v4→v5 migrations,
produced both markdown and HTML reports with correct test-setup
attribution and delta coloring.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(examples): run ANALYZE before db:check to get fresh row counts
pg_stat_user_tables.n_live_tup and information_schema.tables.table_rows
are approximate and can lag behind reality by minutes or hours depending
on autovacuum / ANALYZE cadence. For a benchmark harness that publishes
row-count numbers in its reports, stale counts are misleading.
Trigger a refresh via ANALYZE (postgres) / ANALYZE TABLE per-table
(mysql/mariadb) before each db:check invocation. Best-effort on the
mysql/mariadb side — fall through to stale stats if ANALYZE fails rather
than error the whole command.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(examples): add hc-m2m-source/target anti-pattern schemas
First anti-pattern schema pair for migration benchmark stress-testing.
A high-cardinality many-to-many relation that forces the v4→v5
discard-drafts migration's copyRelationTableRows code path to span
multiple chunks (>1000 rows) — the same scenario PR #25988's caching
fixes target.
- src/api/hc-m2m-source: collection type with DP and a manyToMany
relation to hc-m2m-target (owning side)
- src/api/hc-m2m-target: collection type with DP and the inverse
manyToMany back to source
- setup-v4-project.js: include both in the v4 scaffold CONTENT_TYPES
- seed-v4.js: seedHcM2m() method that creates sources + targets and
fans out 10 targets-per-source via the M2M relation. BASE counts at
m=1 are tiny (15 pub + 5 draft per side) but at m=100 produce ~2K
sources × ~2K targets × 10 = 20K join rows, crossing the 1000-row
chunk boundary multiple times
Intentionally NOT a realistic content-type design — this is a
stress-test fixture. See the description in schema.json.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(examples): render multiplier x db matrix in bench-compare
Rework bench-compare to index results by (label, multiplier, dbEngine)
triples pulled from each result JSON's own fields, rather than parsing
labels out of filenames. Lets the same canonical baseline/candidate
label span any number of (multiplier, db) combinations and produces:
- A speedup matrix at the top: rows = multipliers, cols = databases,
cells = "baseline -> candidate (delta%)". Missing cells render as
"-" so partial data still produces a useful report.
- A data-availability matrix listing what ran vs what's still missing.
- Per-(db, multiplier) detail sections as collapsible details in
HTML, all expanded in markdown.
Also:
- New flag syntax: --baseline <label> / --candidate <label>, with
positional args kept for backward compat.
- Legacy labels that embedded the multiplier (e.g. "baseline-m100")
are normalized to their base form ("baseline"), letting older
result files keep working.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(examples): force TCP for mysql/mariadb CLI in containers
The mysql/mariadb CLI tools default to connecting via unix socket at
/var/run/mysqld/mysqld.sock, which isn't populated in the official
mysql:8 / mariadb:11 container images. Every invocation (check,
snapshot, restore, wipe, readiness probe, version probe) needs an
explicit -h 127.0.0.1 to force TCP via the container's loopback.
Without this fix, bench:seed and bench:run error out with
"Can't connect to local MySQL server through socket" on anything
requiring the CLI inside the container (pg_stat-style row-count
queries, snapshot restore, etc.).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* enhancement(examples): parallelize entity creation in seed-v4
Replace sequential for-loops of `await entityService.create(...)` with
a `concurrentMap(count, concurrency, taskFn)` helper that runs N tasks
in flight at once. At SEED_CONCURRENCY=5 (default), a seed that was
strictly serial now fans out into 5 parallel creates.
Concurrency chosen conservatively: Strapi v4's default knex pool is
`{min: 2, max: 10}`, and entity-heavy creates (components + DZs +
localizations) can use multiple connections per call. 5 keeps us well
under the pool ceiling. Tune via `SEED_CONCURRENCY=<n>` env var if
you've also raised the pool max.
Applied to: seedBasic, seedBasicDp, updateComponentRelations,
seedBasicDpI18n, seedRelation, seedRelationDp, seedRelationDpI18n,
seedHcM2m (all entity-creation loops plus their follow-up
self-reference update loops).
Not yet done: incremental seeding (restore previous snapshot + seed
delta) — a separate optimization tracked as a follow-up.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(examples): update complex README for new bench tooling + DBs
README was documenting just the original 6-type, postgres+mysql
workflow. Updated to cover everything this branch adds:
- 8 content types (added hc-m2m-source/target anti-patterns)
- 4 supported databases (added mariadb + sqlite)
- Container runtime auto-detection (podman compose / podman-compose /
docker compose / docker-compose) with STRAPI_BENCH_RUNTIME override
- Benchmark harness workflow (bench:seed / bench:run / bench:compare /
bench:suite) for reviewing migration-performance PRs
- SEED_CONCURRENCY, STRAPI_BENCH_HOOK_OUTPUT, STRAPI_BENCH_HOOK_DEBUG,
and the existing port-override env vars
- MariaDB port default 3307 to avoid colliding with MySQL on 3306
Also collapsed the redundant per-DB command sections (postgres and
mysql both had identical copy-pasted blocks) into a single
'yarn db:<op>:<db>' table since the commands are symmetric across
all four dialects.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(examples): align better-sqlite3 version with monorepo convention
I picked `11.3.0` arbitrarily. Every other example and tests/app-template
use `12.8.0`, and the root yarn.lock already resolves that version.
Without alignment CI's `yarn install --immutable` fails with 'lockfile
would have been modified', cascading every subsequent job (build, pretty,
commitlint, aggregate_test_result) to red.
Bumping to `12.8.0` to match, regenerating yarn.lock.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: throw instead of return to fail fast
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Irvin <ben@innerdvations.com>
StageSelect and AssigneeSelect were calling unstable_useDocument without
the query params (which carry the active locale). This caused both
components to always fetch and display data from the default locale,
regardless of which locale the editor had open.
As a result, when viewing a non-default locale entry, the displayed
review stage and assignee reflected the default locale's values. After
updating the stage or assignee for a non-default locale, the cache
invalidation triggered a refetch that still omitted the locale param,
so the panel appeared unchanged even though the backend had been
updated correctly.
Passing params to unstable_useDocument aligns the document fetch with
the locale already used by the mutations (useUpdateStageMutation,
useUpdateAssigneeMutation) and the stages query (useGetStagesQuery),
ensuring the panel reads from and reflects the correct locale.
Previously the migration could leave the DB in an unbootable state after
a statement_timeout or process kill: the document_id column existed but
the migration was not recorded, and the retry crashed with PG 42701
"column already exists". Additionally, when hasColumn returned true
the migration early-exited via continue, silently skipping the backfill
and leaving rows with NULL document_id.
- Swallow duplicate-column errors (PG 42701, MySQL 1060, generic
/duplicate column/i) around ALTER TABLE ADD COLUMN to tolerate
catalog drift / operator-applied partial fixes.
- Always run the document_id backfill when the column exists; the
backfill already uses WHERE document_id IS NULL so it is a no-op on
fully-populated tables and recovery on partially-populated ones.
- Add unit tests covering fresh run, column-already-exists,
PG 42701, MySQL 1060, and non-duplicate errors.
Fixes CMS-689
* fix(document-service): preserve self-referential relations during publish/discard
* test(document-service): convert self-referential API test to TypeScript
* fix(document-service): use JoinTable type and tighten Record types in self-referential-relations
* test(document-service): remove weak load() happy-path unit test
* fix(document-service): skip mappedBy attrs, deduplicate and idempotent-insert self-refs
* test(document-service): fix missing dialect and trx mocks in self-referential-relations tests
* fix(tests): use count assertions for CM API self-referential relation tests