* Replace dashboard loading spinner with an instant skeleton (DSN-749)
Dashboards no longer show a loading spinner when opened. Instead they render
a skeleton of the dashboard shell and card layout instantly:
- New DashboardSkeleton renders the header chrome (title, action buttons,
tabs), a full-width border, a secondary-background body, filter-widget
placeholders, and the card grid constrained to a centered 1024px column.
- New DashboardGridSkeleton draws the card grid. When the layout is cached
in Redux from a prior visit (getLastSeenTabDashcards), it reproduces
react-grid-layout's real geometry so the cards land where the real ones
will; heading/text cards render title-style line skeletons. Otherwise a
generic placeholder layout is used.
- Grid no longer shows a spinner while refetching; the header no longer
shows a spinner while its collection query loads.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Draw the real dashboard layout before query metadata loads (DSN-749)
fetchDashboard awaited both GET /api/dashboard/:id and the slower
/query_metadata before any of the dashboard reached the store, even though
the GET alone already holds the card layout. On a fresh open it now
publishes the GET response's normalized dashboard and dashcards as soon as
it arrives (dashboardLayoutFetched), so the loading skeleton switches from
the generic layout to the dashboard's real one without waiting for
metadata. The full load transition is unchanged, and a refetch of a
dashboard already on screen publishes nothing early.
The virtual-card copy is extracted into copyVirtualCardsToCards so the
early layout and the fulfilled payload are built the same way.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Render dashboard skeletons inside the real dashboard layout (DSN-749)
Replace the separate full-page dashboard skeleton with skeletons that the
header, filter panel and card grid each render while the dashboard loads,
so the surrounding page, widths and theming are the real ones and can't
drift.
- Grid renders DashboardGridSkeleton when there's no dashboard yet. This
also covers public links, static embeds, X-rays and SDK dashboards, which
showed "This dashboard is empty" while loading. Its spinner is restored
for refetches of an already-open dashboard.
- The skeletons read the dashboard as last seen in the in-memory Redux
cache: its width (assuming the default fixed width when it isn't
cached), its tabs (a tab row only for two or more), the cards of the tab
the user is landing on, and its header filters (excluding inline and
hidden ones).
- The grid skeleton takes its geometry from the grid constants, including
a new shared GRID_MARGINS, and drops the heightPx special case.
- The skeletons are marked aria-busy, and the grid announces "Loading…".
- waitForDashboardToLoad waits for a loading dashboard in tests, since
waitForLoaderToBeRemoved no longer does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Draw no placeholder cards until the dashboard's layout is known (DSN-749)
While a dashboard that isn't in the in-memory Redux cache loads, the grid
skeleton used to draw a generic layout of placeholder cards, which rarely
matched the dashboard about to appear. It now draws no cards until the
dashboard's real layout is known, so the content area stays empty under
the header skeleton and then fills with the real layout. The grid still
marks itself busy and announces "Loading…".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Size the dashboard header skeleton from the real title (DSN-749)
The header skeleton drew a bare title bar, but the real title area is
taller: the title is an EditableText box with its own padding, border and
line height, and the last-edit label sits on a row beneath it. The header
grew when the dashboard arrived, pushing the page down.
The title and label bars now sit over invisible copies of the real
elements, so the skeleton takes their exact height without copying their
styles. The label row is only reserved when the real header would show
it; a dashboard that isn't cached is assumed to have an edit history.
Also replace a file path in a CSS comment, and note in dashboard_grid.ts
that the grid skeleton's mobile container query repeats MOBILE_BREAKPOINT.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Declare png and gif module imports for the bundler
* Hash onboarding, upsell and example images through the build
* Assert the Slack app icon link against the bundled asset
* Hash the upsell card story illustration through the build
* Take image sources as props instead of building paths from a base string
* Add a ?url image import that the SDK bundle references instead of inlining
* Hash empty state, upsell and driver logo images through the build
* Hash the map skeleton sprite and the bridge illustration through the build
* GHY-4737: Remote sync: name the cause of a diverged-export conflict
A plain export against a remote that moved past the last sync ended in a
conflict task with conflicts [], so API clients (the mb CLI, agents) saw
"conflict" with no reason: the task row stores only conflicts, and
handle-task-result! drops :message. Record a factual label instead.
Also pin that such a conflict leaves the sync base alone, so a later
export with merge still sees the remote's changes.
* GHY-4737: pin the conflict label on the task row; trim the label comment
* GHY-4737: record the diverged-export cause as a stable token
Review feedback: conflicts otherwise holds short identifiers on the
import path, and an untranslated English sentence would become a wording
contract for clients. Record "remote-changed" and let clients word it.
* GHY-4737: don't let auto-import repeat an unresolved conflict
A conflict is no longer a sync base, so auto-import's "source version
matches last import" skip no longer stops a conflicted read-only
auto-import: it retried, and recorded a new conflict task, every tick.
Skip when the newest task conflicted at the current source version;
a new remote commit or a manual import still proceeds. Review feedback.
* GHY-4737: name why a task stopped in outcome, keep conflicts for what collided
Review feedback: conflicts answers "what collided" (import categories,
merge entities). A diverged export has no collision; the divergence is
why it stopped. Record conflicts [] plus outcome {:kind "remote-changed"}
instead of a token inside conflicts, and add outcome
{:kind "history-rewritten"} to the no-merge-base paths (their conflicts
sentence stays for compatibility). handle-task-result! now writes outcome
on conflicts; the outcome schema and FE type gain both kinds.
* Let each test suite decide whether it should run
run-tests.yml computed a `skip` input for every suite it called, so the
decision for a suite lived in the caller rather than next to the jobs it
governs. A new callable workflow, test-gate.yml, carries the part that is the
same everywhere: the force-run / force-skip / defer verdict, and a paths filter
that only runs on `defer`, over a sparse `.github` checkout.
Each suite now calls the gate with the filters it cares about and reads `run`,
`matched` or `files`. app-db, drivers, semantic-search, loki, e2e-tests,
frontend, embedding-sdk, build-scripts, bundle-size and backend keep no `skip`
input; run-tests only decides whether to call them at all.
static-viz-files-changed moves into backend.yml, its only consumer, and now
runs only when `backend_all` did not already match.
The uberjar and SDK package conditions become the build_uberjar and
build_embedding_sdk_package filters, each the union of what the artifact feeds.
A force-run reports every requested filter as matched, so the project-level
backend, migration and ratchet checks now also run on master and `ci:run-all`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Drop the redundant always() from workflow conditions
`always() && !cancelled()` is `!cancelled()`: always() only adds "run even when
cancelled", which the second term takes straight back. The four conditions that
paired !cancelled() with a `needs.<job>.result == 'success'` check restated the
implicit rule instead, so they lose both terms and let a plain `if` apply it.
In e2e-tests.yml that also fixes a latent failure: the suite entered when
e2e-matrix-builder had failed, leaving `fromJson` to parse a matrix that was
never produced.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Stop gate jobs from skipping themselves
A skipped job skips everything downstream of it, which is why so many consumers
carried !cancelled() just to override the implicit success(). create-test-plan,
static-viz-files-changed and story-selection now always run and gate their steps
instead, emitting empty outputs, so their consumers keep only their real
condition. create-test-plan is best-effort: a planner failure means the suites
run in full, as a skipped one already did.
The app-db, drivers and semantic-search gates were skipped on a direct dispatch
for the same reason. They pass no filters on that path instead, which already
means "only check for a force-cancel".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Land the affected-tests stats row from the planner action
The upload job existed only to POST what create-test-plan had just computed,
so it re-checked out the repo and re-prepared bun to read one output back.
It is now the action's last step, which also drops the `stats` output that
only ever carried the payload across the job boundary.
A composite action has no `secrets` context, so the endpoint and key arrive as
inputs; an empty endpoint (a fork PR, which gets no secrets) skips the upload
rather than posting nowhere.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Reorder workflows
* Count the bookkeeping jobs in the result checks
`static-viz-files-changed` and `e2e-matrix-builder` feed every job in their
suite but were absent from the `*-result` needs, so a failure in either skipped
the whole suite and the required check reported success.
`sdk-tests` now requires a successful gate, like `e2e-tests`: a failed gate
skips the uberjar and SDK package builds for a reason the suite's own gate
cannot see, and the component tests would run against a missing artifact.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Hand out a test plan only from a clean planner run
The plan artifact is uploaded before the planner's reporting steps, so a
failure after the upload still exported an artifact id and both suites narrowed
from it. Reading the step outcome covers that; the job's `continue-on-error`
means its own `result` is always `success`, so a caller-side check would not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Run the suites when the test gate changes
test-gate.yml matched no filter that gates a suite, so a diff touching only the
workflow that decides whether every suite runs ran nothing. Putting it in
shared_ci reaches backend, frontend, loki, e2e, the SDK suites and ci-scripts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Keep file lists out of the test gate
The match step read every paths-filter output through one environment
variable, file lists included. That variable passes the 128 KiB Linux allows
a single one at around 130 changed files, so the gate failed on large PRs and
took every suite down with it.
The gate now lists no files and reads only the names of the matched filters.
The create-test-plan action, the one consumer of paths, runs its own paths
filter instead of taking file lists and infra flags as inputs, so the
run-tests gate asks only for the filters its own jobs read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* cleanup
* Address review: job-level skips, no frontend dispatch, no per-leg profiling
- story-selection, create-test-plan and static-viz-files-changed skip as a
job again instead of skipping each step. Their consumers carry
!cancelled() so a skipped one does not skip them, and the planner and
static-viz probe no longer hold a large runner just to do nothing.
- frontend.yml loses its workflow_dispatch trigger and the skip inputs it
fed.
- The profiling dispatch no longer picks a leg: the db input and every
github.event.inputs.db read are gone, so a profile runs every leg of the
suite.
- semantic-search drops ci:run-semantic-search-tests. The full suite runs on
a force-run (master, release branches, ci:run-all) or a profile; every
other run is the Postgres Latest smoke run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* cleanup
* Gate a profiling dispatch like any other run
The backend suites read the dispatch's `suite` input to run everything when
profiled. They now gate a profiling dispatch like any other run: in full on
master and release branches, by the diff anywhere else, with semantic-search
running its smoke leg.
The profiler switch keys off the dispatch event instead. backend.yml sets it
on the test step; app-db, drivers and semantic-search set it in their workflow
env, which the test-driver action inherits, so the stress-test workflows that
dispatch the action directly still never profile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* rename
* cleanup
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Ryan Laurie <iethree@gmail.com>
* Fold explorations picker search test into the manual flow test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmyW5RuqbkDJu7aUM3Po3J
* Fold explorations click-to-select test into the sidebar tabs test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmyW5RuqbkDJu7aUM3Po3J
* Merge explorations trash page tests into the trash banner test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmyW5RuqbkDJu7aUM3Po3J
* Strengthen weak explorations navigation checks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmyW5RuqbkDJu7aUM3Po3J
* Remove unused addMetricsToExploration e2e helper
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmyW5RuqbkDJu7aUM3Po3J
* Consolidate more explorations e2e tests and fix weak checks
- Merge the Summary document test into the brush explore-further test
- Merge the personal collection test into the trash banner and /trash test
- Check that the toast View action moves to a different page
- Wait for the search request with the typed query in the metrics picker
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address review feedback on explorations e2e consolidation
- Append an unfiltered page to the Summary and check both card embeds
- Check the new page renders after the cartesian toast View
- Load /trash cold before the trash row actions and flatten the trash test
- Merge the sidebar hide pages test into the tabs test
- Share one beforeEach and afterEach under a top-level describe
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Remove duplicate data-studio e2e tests
Drop tests whose steps and assertions already run in another e2e test:
a measures-queries identity test that was copied line for line, a
CountIf-segment measure test that matches another one step for step,
an empty-folder snippet test that never exercised folder expansion, a
skipped segment-cycle test whose asserted text does not exist in the
product, and an MBQL SQL-preview LIMIT check that could not fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fold subset data-studio e2e tests into their supersets
Delete tests whose steps already run in a host test with the same setup,
and add their few unique checks to that host: empty states and /new
URLs into the create flows, unsaved-changes guards, breadcrumbs,
dependency graph tabs, read-only views into the edit tests, transform
target errors, the Python library entry and editing, the inspect tabs,
and the SQL-preview LIMIT check on a transform without a limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Repair data-studio e2e assertions that could not fail
Make checks go red when the behavior breaks: wait for the run request
before reading the run label (it shows the previous result on mount),
assert values that differ before and after the action (checkpoints,
filtered rows, converted SQL, Python output, rewired dependents after a
new source row), fix selectors that never match (tab roles, a document-
wide get, wrong measure names), assert the outcome of saves that had
nothing after them, and drop checks on buttons that never render.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Strengthen weak data-studio e2e assertions
Give negative checks a positive precondition on the same element so
they cannot pass before the UI mounts, wait for the request before
checks that follow a save or a navigation, use the multi-toast helper
where a previous toast may still show, guard .each loops against empty
sets, and create the metric picker fixtures on the database the picker
shows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge data-studio e2e tests that share setup
Run tests with the same restore, user, fixtures and entry page as one
test, so each restore and page load serves several checks. Every part
keeps its original starting state: read-only parts run first, parts
that mutate data run last, each part starts from its own visit where
the state matters, and toasts are dismissed between parts. Tests that
replace a source or convert a model now create all dependents and run
the replacement once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop unused setup in data-studio e2e tests
Remove restores that a later restore overwrites, intercept aliases that
nothing waits on, duplicate snowplow resets, duplicate helpers and
unused helper parameters. Fix wrong logs, titles and comments.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix data-studio e2e merges found in the parity pass
Load the notebook fresh before the second data-picker part, check the
closed popover without the visibility guard, delete segments after a
direct visit as before, remove the failed-run test that also runs in
the runs filter test, and keep the snowplow check for the job creation
flow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix and restore data-studio e2e checks from review
Assert the Python transform output as a scalar, assert the archive
banner instead of a URL check that could not fail, and bring back the
collection checks after a direct library visit. Give the
multiple-template-tags part its own visit without a database pick, run
the non-admin source-replacement check before any sandbox exists, drop
the run-status polls before the run label, and match the join table
label by text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add positive checks before data-studio e2e negatives from review
Assert the element or container is there before each remaining
negative check (sync modal, cleared-description hovercard, row
checkboxes before read-only mode, the transform row before deletion,
the transforms header in the empty state), and wait for the dashcard
query after a filter change before checking rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge more data-studio e2e tests that share setup
Run more tests with the same restore, user and fixtures as one test:
measure queries on one measure fixture, template tags with the data
reference flow, inspect loading with the summary lenses, the transform
reproductions, transform ownership with the inline settings, tag
editing, published-table queries, metric editing and moving, table
dependencies, segment tabs and dependencies, sort orders, and table
details. Each part starts from its own visit where the state matters,
and parts that change data run last.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix data-studio e2e tests that failed in CI
Drop the test tables before the writable-database restore again: the
restore cannot drop the source table while the child table still has a
foreign key to it. Submit the transform form by its submit button,
which shows "Failed" for a few seconds after the rejected save. Close
the run error modal with Escape, since it has two close buttons, and
check a tag that is listed in the dropdown, because selected tags are
not listed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Move dashboard-filters-2 e2e specs into dashboard-filters
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Remove unused dashboard filter query-stage helpers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Remove duplicate dashboard filter target picker empty state test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fold dashboard filter reproduction subsets into their supersets
Run the target picker empty-section search in the empty-state test, add a
never-connected card to the disconnected-card reload test, and count public
dashcard requests in the public remapping test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Repair dashboard filter e2e checks that could not fail
Prove click-through navigation, give negative checks a positive precondition
on the same element, assert filtered results that differ from the unfiltered
card, wait for reverts and stored last-used values, map the first-stage
breakout column in the query-stage test, and check the first card in the
first-stage aggregation test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge dashboard filter e2e tests that share setup
Run the remapping, comma-separated value, sql model field value and public
and embedded remapping reproductions once per setup, and apply a query-stage
filter after checking the mapping options on the same dashboard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop unused dashboard filter e2e setup
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tag the writable database filter linking reproduction as external
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Check merged dashboard filter reproductions against the right state
Assert the filtered public dashcard by row count, since its Name column is
outside the card, and clear the last used remapped value before loading it
from the url.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Select the city from the autocomplete suggestions in the city filter test
The city filter searches values instead of listing them, so the selected
value shows as a pill in the search input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Drop temporal unit public and embedded dashboard tests
The multiple column breakouts dashboard test sets temporal unit
parameters on the same dashboard through a public link and a static
embed, and checks the card columns after each change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Check the auto-wired card keeps its mappings in temporal unit test
Only the manually mapped card was asserted, so overwriting the category
mapping on the auto-wired card went unnoticed. Assert both cards and
drop a negative check that ran before any data loaded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge temporal unit parameter tests that share setup
Fold the model mapping into the question mapping test, the no-breakout
auto-wiring checks into the new card test, the default clearing into the
allowed units test, and the URL value tests and the native query tests
into one test each.
The no-breakout card check now waits for the other new card's
suggestion, and the default clearing checks the unit was excluded before
asserting the default stayed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix temporal unit parameter test titles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fold temporal unit chevron alignment check into reset test
The chevron alignment test built the same no-default temporal unit
parameter as the temporal unit reset & clear test. Run its check at the
end of that test, in the parameter sidebar it already opens, and assert
both chevrons are present before comparing them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fold reset-with-open-popover check into number filter test
The required number filter test already resets a changed value with the
widget's revert icon. Open the widget popover first and assert it closes
after the reset, which covers the standalone reset-with-open-popover
test. Drop that test along with its describe and duplicate beforeEach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fold required text filter set-to-default check into reset test
The standalone test deselected all values of a required filter whose
value already equalled its default, then clicked "Set to default" and
asserted the unchanged "4 selections", so it passed even if the button
did nothing. The multi-value text reset test now changes its required
filter to a non-default value first, deselects all, clicks "Set to
default" and asserts the default is applied and the reset icon is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use a distinct other value in multi-value text reset test
The other value had two selections, as did the default, so every
"2 selections" check passed whether the filter held the default or the
other value. Pick three values so the widget text tells them apart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fold auto-wiring subset tests into their supersets
The Undo flow now runs in the "already has a parameter" test, which
undoes before mapping card 1 to Address instead of clearing it by hand.
The auto-connected toast check moves into the add-a-card Undo test, the
replace-a-card flow into the parameter mapping test (now also checking
the replaced card is mapped), and the 8s undo-toast timeout into the
12s timeout test with tighter bounds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add positive preconditions to reset & clear negative checks
checkResetAllFiltersHidden asserted "Reset all filters" was absent
without showing the menu had opened; it now sees "Enter fullscreen" in
the same menu first. The first "no status icon" check on the required
filter now asserts the widget shows the default value first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Retitle number range reset test and drop dead comment
The number "multiple values" test uses "between" parameters, so call it
a range. Remove a commented-out line in the text multi-value test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Make foreign key auto-wiring filter checks able to fail
Both tests filtered by ID 1 and asserted cells contain "1", which the
unfiltered tables also satisfy, and waited on a card query alias that
matched the initial dashboard load. They now filter by 3, assert the
exact cell text "3", and wait on an alias registered just before the
filter is applied.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add positive preconditions to auto-wiring absence checks
The card 1 "User.Name" absence check now first confirms card 1 shows
"Select…", and the no-suggestion toast check first confirms card 0 is
mapped to "User.Name", so neither passes before the mapping UI renders.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge unsaved and reset draft filter auto-apply tests
Both tests set a draft filter value on the same dashboard with auto-apply
off. Run the reload check first, asserting the widget shows its
placeholder rather than the draft value, then the reset steps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop unused intercepts from auto-wiring spec
Nothing waits on the "@dashboard" alias or reads the card query spy,
and the "@cardQuery" alias has no remaining waits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fold cancelled-edit draft filter test into dashboard edit test
Both tests set the same draft filter value before entering edit mode.
The edit test now cancels an edit first and checks the draft value and
apply button survive, then edits and saves as before. Drop the emptied
describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix stale comment in auto-wiring toast timeout test
The comment said auto-wiring toasts share an id; each toast now gets
its own id, so it states only what the test guards.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fold auto-apply enable snowplow test into disable test
After disabling auto-apply and seeing one "auto_apply_filters_disabled"
event, the disable test now enables it again and checks the event count
stays at one, covering the standalone enable test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Check the checkbox state in auto-apply filter reset test
The reset check asserted "not.be.checked" on the "Gadget" label text,
which is never checked, so it passed whether or not the reset worked.
Query the checkbox by its label instead, and assert it is checked after
selecting it so the final unchecked state is meaningful.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Check rows after disabling auto-apply in toggle test
The last step only checked the widget text after disabling auto-apply,
and its log described draft values although the change had already been
applied. Assert the card still shows the 107 Gadget and Widget rows and
describe the step as it runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fold filter type change subset tests into their supersets
The default-operator walk through Number, ID, Date and Location now
runs in the mapping reset test before it saves, and the mapping
cleared/restored checks run in the saved parameter value test, which
already switches Text to Number and back on the same dashboard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Order filter management checks after the state they test
The hidden filter widget check after saving now has a positive
precondition: the shared setup confirms the "Text" widget is shown
before editing. The operator change test checks the mapping survives
only after "No default" confirms the operator change applied, so the
check can't match the pre-change DOM.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add menu precondition to auto-apply no-permission test
The test asserted "Edit settings" was absent without showing the
dashboard menu had opened. Assert "Enter fullscreen" in the same menu
first, and title the test after what it checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix default operator log messages in filter management spec
The Number log said "Between" while asserting "Equal to", and the
Location log said "Date".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge dashboard filter default tests that share setup
Both tests built the same two-filter dashboard and visited it with the
same params. After setting Filter Two's default, the remaining test now
sets Filter One's default to "Baz" and checks the URL and widgets keep
Filter Two's value before and after saving.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fold static list clear-and-restore test into number source spec
The clear-and-restore spec built the same Orders count dashboard with a
number filter and static list source as the number source static list
test. That test now edits its dropdown filter afterwards, switches it to
a text filter (list cleared), connects it to Email, and switches it back
to number (list restored). Move the helpers and delete the spec.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Check number filter dropdown hides raw values behind labels
The number/string label test only checked the label is listed; it now
also checks the raw value "1" is not, as the string/string test does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge dashboard filter source tests that share setup
Each group now builds one dashboard with a distinctly named filter per
case and scopes every widget by name:
- structured Contains, structured Is and native Is question sources,
archiving each source and checking the plural "2 filters" warning
for the shared GUI source and the singular one for the SQL source
- string, number and id values remapped to string labels
- static list dropdown, static list search and field search box
- IP address and type/Quantity custom labels on postgres
The filter helpers take the filter name instead of clicking "Text".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop unused dataset intercept from filter source spec
Nothing waits on the "@dataset" alias.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Stop restoring twice before the UUID chained filter test
The test sat under the chained filter beforeEach, so it restored the
default snapshot and signed in before restoring postgres-writable
itself. It now has its own describe and does only its own setup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Retitle UUID chained filter test after what it checks
"should work for all field types" described none of it; the test
checks a UUID primary key column is offered for an ID filter under the
"Update a dashboard filter" click behavior.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Describe the default replacement step in filter defaults test
The log said the step removes the default value and its URL
placeholder, but it replaces Filter Two's default with "Foo".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fold nodata filter mapping checks into data permissions test
Merge the admin and nodata filter tests into one, and add the nodata
edit-mode checks there: the card actions panel shows without an
"Add a filter" button (shown to the admin in setup), and the filter
mapper shows the key icon. Remove the parameters spec tests that only
covered those nodata checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fold subset dashboard parameter tests into their supersets
Each removed test ran on the same setup as a test that already covered
its first steps, so its remaining steps now run at the end of that test:
- resetting a last used value and reloading: last used value test
- removing a heading dashcard: heading dashcard removal undo test
- removing a question dashcard: question dashcard filter edit test
- own-card connection message: other-tab connection test
- showing all inline parameters while editing one: question dashcard
filter removal test
- undoing filter moves: single tab filter move test
- dashboard_filter_moved tracking: dashboard_filter_created test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Repair dashboard filter checks that could not fail
- Data permissions: drop the search intercept, whose alias was mangled
into the URL, along with the unused 403 branch that waited on it.
Open the filter through its widget instead of matching any "Text",
drop the "Orders" and dashboard-title clicks that always matched,
click "Add filter" in the popover without force, and check the
widget value and URL param.
- Heading filters: check the grabber icon on the heading card holding
the filter, not on the question card.
- Text card variables: check the Count widget instead of Category twice.
- Public dashboards: map the Count filter to the dashcard's own card so
its widget renders, and check it does.
- Granular filter preference: wait for the dashcard query after updating
the header filter before checking the card.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Strengthen weak dashboard parameter assertions
- Filter removal: check the filtered-out rows are present before
filtering and that matching rows render before checking the others
are gone; check only one filter widget remains after removal.
- Multiple filters: check the values request count after the reopened
popover has rendered its values.
- Compare multi-slug URLs with expectSearchParams instead of exact
search strings, whose param order is not deterministic.
- Inline auto-wiring and dashcard duplication: check the own-card
message renders before checking the mapper has no Category option.
- Disabled embedded parameter: check the card shows unfiltered data.
- Filter focus: check the move toast renders before checking it has no
"Show filter" button.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge dashcard refetch-on-save tests into one
The four tests shared the same setup. Run their saves in sequence in
one test, so each later count also catches a fetch an earlier save
should not have made: no fetch after saving a disconnected filter or
an unchanged dashboard, then 1 after connecting one card, 2 after
disconnecting it, and 4 after connecting both.
Register the request spy before the setup save instead of after it, so
the disconnected-filter check can fail if that save fetches data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop duplicate dashboard PUT intercept
The dashcard duplication test registered the same updateDashboard
intercept twice; keep the one at the top of the test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Retitle merged dashboard filter tests
Name the steps each merged test now covers, including the admin and
nodata usage and nodata edit-mode checks in the data permissions test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop auto-apply toast tests for public and signed embeds
The slow dashboard toast is only shown to users who can edit the
dashboard, and the tests could not make the dashboard slow: cy.clock
does not affect performance.now and the card request was never delayed.
Neither test could fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Check the other tab message while the inline filter is connected
Also compare the public dashboard URL params regardless of slug order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Name both toast timeouts in auto-wiring timeout test title
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Describe temporal unit parameter priority without an issue reference
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix dashboard filter tests that failed in CI
The heading text is an input after the parameter sidebar closes, so
check the saved dashcards instead. The add-card sidebar stays open after
a card is added, so pick the next card from it. The auto-wired table card
is below the fold, so check its column header exists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Restore the non-required filter reset with an open popover test
Only the non-required filter reset closes the popover. The required
filter reset keeps it open, so the check cannot run on the required
number filter. The test now uses the outer describe setup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Find temporal unit auto-wired cards by name after save
Saved dashcards are ordered by their layout position, so the added
cards no longer have the indexes they had while editing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Derive the mini model only from what a connection was listed as serving
With nothing stored, llm-mini-model resolved to the provider type's mini
model straight from the registry. Connecting lists the account's own
catalog but never checked that guess against it, so a connection whose
listing did not include the cheaper tier ran every quick task — conversation
titles — on a model it could not serve, and the admin page showed the mini
model as unconfigured because the derived value was not among the options.
Every listing now records whether it included the type's mini model on the
connection, and the derivation reads that instead of the registry: the
cheaper model when the connection was seen to offer it, the Metabot model
otherwise. The per-type mini models stay in the registry as the tier a
listing looks for.
* Tighten mini-model docstrings
* Log applied sandboxes in the query execution audit log
Sandboxed (row-level security) queries used to show up in Usage
Analytics' query log as completely normal queries, making sandboxing
impossible to debug from the audit log (#71604, UXW-3767).
The logged query blob can't carry this information: it lives in the
query table keyed by a hash computed before preprocessing, so a
sandboxed and an unsandboxed run of the same question share one row.
Instead, record the restrictions per-execution:
- The sandboxing middleware records each sandbox it applies on the
query under a namespaced key, merged across preprocessing passes.
Applied sandboxes are derived from the
:query-permissions/sandboxed-table markers left on each replacement
stage -- the same source of truth is_sandboxed comes from.
- merge-sandboxing-metadata copies the entries into the results
metadata (userland queries only), next to is_sandboxed.
- process-userland-query saves them to the new
query_execution.sandbox_details column and strips them back out of
the response returned to the client.
Each entry records table_id, sandbox_id, group_id, card_id, and the
login attributes used (attribute name -> target field id). The user's
actual attribute values are only included when
analytics-pii-retention-enabled is set, matching how that setting
gates other user-specific data in Usage Analytics.
The column is exposed in v_query_log (v7) and the shipped Query log
model.
* Give the attached sandbox audit details a named schema
Review feedback:
- Rename `record-sandbox-details` to `attach-sandbox-details`. It puts data
on the query; it doesn't persist anything.
- Replace the bare `:map` and `::lib.schema/query` schemas with
`::details-entry`, `::details` and `::query-with-details`, so the key
sandboxing attaches during preprocessing is typed both where it is
produced and where `merge-sandboxing-metadata` reads it back.
- Mention sandbox attribute values in the `analytics-pii-retention-enabled`
description.
* Declare sandbox_details in the schemas master closed
Rebasing onto master brought in closed argument schemas and the checker in
`metabase.util.malli.closed-schemas`, which rejects any undeclared key and any
shape it cannot see. Every schema the new key passes through has to name it:
- `metabase.lib.schema` gains `::sandboxing.details.entry` and
`::sandboxing.details`, and declares the `::details` key on the query map
next to the existing sandboxing key. This is the one definition of the entry
shape; the enterprise middleware and the row schemas below point at it.
- The legacy MBQL query schema declares the same key, since preprocessing
round-trips the query through legacy and back.
- The result metadata threaded through the rff chain, and the QueryExecution
insert schema, declare `:sandbox_details`.
- The SDK and batch-processing tracking rows pass the value through, so they
declare it as a deliberately-open map rather than restating the shape.
Also renumber the migration from v64 to v65: v64 has branched for release, so
a v64 id on master would not roll back with the rest of v65.
* Let the query-with-details conjunct through the closed-schema check
An `:and` conjunct that constrains a single key is an open map, which
`metabase.util.malli.closed-schemas` refuses as an argument schema. Mark it
with `:malli.core/default`, the escape the checker documents for exactly this
shape: every other key is validated by `::lib.schema/query`, the first
conjunct, which is where `::details` is declared.
* Cover the new internal query key in the serialize fixture
`internal-keys-fixture-covers-the-schemas-test` asserts the fixtures carry
every internal key the schemas declare, so the serialization tests cannot
drift. Add `::details` with a schema-valid sample entry.
* Create stub databases for missing databases on serdes import
* Synthesize stub databases in the import resolver
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop Database load dependencies and fix tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Trim the admin database page for stub databases
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Test importing content on a missing database as a stub
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Exclude stub databases from serdes export
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Show database status in the admin list and clarify the stub healthcheck message
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Exempt stubs from the network policy, keep is_stub out of serdes, and wait for the save in the stub e2e test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Update the is_stub extract test now that is_stub is never exported
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Import every serialized Database as a non-stub
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix review findings: no Database load deps, empty details skip the network check, name-only config matching
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Remove dead missing-database handling now that imports stub missing databases
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Test the stub database admin page sections and list request
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Memoize every import lookup and drop the cache when an entity's load fails
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop the Dimension path helper master replaced with serdes/field-path->field-ref
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Let ee-embedding-provider name the connection semantic search embeds through
* Keep registered embedder names from being shadowed by connection keys
* Skip the connection lookup when ee-embedding-provider names an embedder
* Do not fall back to the openai connection once ee-embedding-provider names one
* Warn when removing the connection semantic search embeds through
* Let the vLLM preflight test see the losing probe start before it is cancelled
* Keep ee-embedding-provider to semantic search and to OpenAI connections
* add table-user-visibility-type to the warehouse schema overlay
* exclude tables hidden in user settings from sync
* test that the 064 user-settings migration keeps hidden tables hidden
* match field sync batches over hidden tables before skipping them
* tighten hidden-table sync tests and docstrings
* GDGT-3241 Constrain SegmentedControl items to text and icon props
Items are now typed data (label, icon, ariaLabel) instead of arbitrary
label nodes, and the component owns icon size and spacing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Update Loki Snapshots
* set line height on SegmentedControl label
* add second click to assert handler doesnt refire on multiple clicks
* interface -> type
* add test
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Metabase Automation <github-automation@metabase.com>
* GDGT-3291: drop client-sent creator in POST /api/notification/send
* GDGT-3291: clarify why /send drops the client-sent creator
* GDGT-3291: drop creator from the create/send request schema and stop sending it on Send now
* Remove duplicate permissions e2e tests
Drop the second copy of the create-queries 'query builder only' group
view test, the admin-permissions blocked-viz test that the nodata
reproduction already runs with the same graph and user, and the
transforms list denial test whose steps run in the grant-after-deny
test. The coverer for the blocked-viz test now scopes its error text to
the query builder, and the grant-after-deny test now asserts the key
image.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge permissions e2e tests that share setup
Fold tests into siblings that run the same restore, user, token and
permission graph, keeping every step and assertion in its original
starting state:
- create-queries: 'no' in group view runs after the 'query builder and
native' save, as a second save for another group
- baseline: the unauthorized-path loop runs as one test; the nodata
data-load check moves into the nodata reproduction, which also hosts
the blocked viz-settings reproduction
- reproductions: duplicate-in-view-only-collection follows the
save-modal reproduction; the two raw-mode pivot/combo checks share
one test
- application/admin: the application help panel joins the data help
test; default subscriptions and monitoring checks share one test;
the collection options check runs inside the collections edit test
- transforms: list page joins create-via-UI; create and run API denials
share one test
- view-data: the blocked-table QB question joins the native question
test
- sandboxing via API: existing and new user attributes share one test;
the three normal-user sandbox checks share one test
- impersonated: drop the outer beforeEach repeated by the inner one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Repair permissions e2e assertions that could not fail
- view-data: the impersonation-then-granular test switched the wrong
database to Granular; the database-view sandbox test clicked Save
changes without confirming, then re-read the unsaved draft
- sandboxing via UI: every custom-column case only checked '20 rows',
which the unsandboxed limit-20 query also shows; expression cases now
check Gizmo-only rows, literal cases check sandboxed responses
- sandboxing via API: a misspelled columnAssertion and an undefined
PEOPLE.USER_ID skipped the column checks; the user attribute test
now checks the saves
- admin help: not.contain with a RegExp is stringified and never fails;
the stub intercept now registers before the visit
- application settings: drop absence checks for texts that no longer
exist in that container
- downloads: assert the limited/full download level, and reload after
saving download permissions
- reproductions: the 20717 regex had a typo and the 22727 text check
passed on an empty picker; 39221 now performs a real user-local
setting write before checking for a site settings fetch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add positive preconditions and waits to permissions e2e tests
Pair negative assertions with a positive check on the same element,
wait for the requests that the following checks depend on, and assert
exclusive category lists for the native-question sandbox. The revoked
subscriptions test now sets up SMTP so the missing Subscriptions button
reflects the permission rather than the absent email channel, and the
download denials check the download button by label as well as icon.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop unused setup and fix names in permissions e2e tests
Remove intercepts that are never waited on, an unused erroring question,
a commented-out duplicate line, a reload followed by a visit, and a
data model click that only unhid a table before navigating away. Tag
the misconfiguration spec @external since it uses the writable
Postgres database. Fix the 45073 title, the 14873 describe name, a
typo, unused helper params, the OSS index constant used after the
downgrade, and the title of the no-token View data column test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Restore the reload before opening the native editor
Clicking a table on the browse page leaves the app on an ad-hoc
question URL, so the native editor helper's hash-only visit would not
reload the page without it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Test real models in native download permissions tests
The model steps only renamed the card, so they re-tested the plain
question. Convert the card to a model and visit it as one. Also assert
the visible category values before the absent ones, so the absence
checks can't pass before the values have loaded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge two more permissions e2e tests that share setup
- sandboxing via API: the 14990 subscriptions sidebar check runs in the
sandboxed subscription test, which has the same SMTP setup, sandbox
and user
- view-data: the unsaved impersonated-then-granular check runs before
the saved variant, from a fresh visit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Chain permissions e2e tests whose earlier parts save nothing
Run read-only or draft-only tests first and the saving test last, with
a full page load between parts:
- admin-permissions: the 30494 toggle check before collection editing;
the group-view revision-change check before the database-view one
- view-data: preserve-on-granular and parent inference before the
group-view granular save
- impersonated: impersonated access checks before the cache check,
signing back in as admin first
- transforms: deny, grant, then deny again in one test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix positive preconditions in permissions e2e tests
The native editor run button is scoped by test id because the hidden
floating run button also carries the Refresh label and stays enabled.
The view data popover omits the current value, so it is checked for
Blocked instead of Can view.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Close the collection permission popover by reloading
Escape does not dismiss the permission popover, so the test now visits
Second collection directly, which also discards the open popover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address permissions e2e review feedback
Merge the remaining tests that share setup: the transforms column check
and the save flow, the transform page denial and the API denials, the
adhoc native check and the unauthorized paths, the blocked card check and
the nodata reproduction, and the two database view create queries tests.
Run the steps of merged tests once instead of repeating them, and drop
the second #37811 check.
Remove an unused join and sandbox setup and a final visit with no check,
and assert the mini picker has loaded before checking an absent database.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Check a visible mini picker item before the absent database
The transforms mini picker lists no databases for this user, so the
positive check uses Our analytics.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix parseZone day arithmetic for datetimes without an offset
* Assert the sync start time with the formatter the component uses
* Parse both sides of the SmartScalar same-day comparison with parseZone
* Keep engines and the timezone list out of the bulk settings payloads
Both are large, change only when the instance is upgraded, and are read by a
handful of screens, so inlining them into every document and into
GET /api/session/properties costs every visitor. :include-in-list? false leaves
them out of those payloads while GET /api/setting/:key still serves them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Fetch engines and the timezone list by key in the screens that need them
The settings payload no longer carries either value, so the consumers read them
through RTK queries against GET /api/setting/:key instead of getSetting. Both
queries hold their entry indefinitely, since neither value changes without a
restart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Wait for model metadata before opening the Actions menu in e2e
The menu is gated on supportsImplicitActions(), which reads the source table's
metadata, so the test raced GET /api/card/:id/query_metadata. The existing
getModel alias does not cover that request, because a Cypress glob does not
cross a path separator.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Skip the redundant audit DB sync after a fresh install (GHY-4728)
The install sync in maybe-install-audit-db! sees every analytics view but
never recorded last-analytics-views-checksum, so maybe-sync-audit-db! always
found the views stale on a fresh install and synced the audit DB a second
time. Record the checksum after the install sync. On a Postgres app DB this
removes the second sync (~2s); H2/MySQL still sync again for the engine swap.
There are two significant changes here.
First, properly signal a change to `dependencies` and the remote sync machinery when the dimension (mappings) are edited. They were ignored before because they didn't publish an `:event/card-update`.
Second, for pre-curation metrics (with `:card_schema` 23 and earlier) the `:dimensions` are regenerated on each read, until the first time they are saved by an edit being made. Such metrics originally had *randomized* `:id`s, but since they're not persisted it made them permanently dirty from remote sync's POV. These are now based on the metric card's `:entity_id` as well as the mapped column, which means they are *stable* on any particular instance, *unique* within that metric (since it includes join details, not just the target field ID), but they are not *reproducible* across instances.
That's fine - the instance that created a metric pre-64 will export it with a now-stable ID; that will be persisted by downstream instances on import. Eventually the originating instance will persist them too, when it makes an edit to the metric. Since the generated IDs are now *stable*, all instances will converge on the true IDs.
Apologies for the noise, since this required adding `:entity_id` to a pile of `(t2/select [:model/Card :field :lists] ...)`, but its required by the `:card_schema` upgrades so we need to ensure that it's fetched.
Co-authored-by: Ethan McCue <ethan@mccue.dev>
Co-authored-by: John Swanson <john.swanson@metabase.com>
Co-authored-by: appleby <86076+appleby@users.noreply.github.com>