From 9dcc0d53f58465f9627a071036232dd08b33c0b9 Mon Sep 17 00:00:00 2001 From: Nishant Bangarwa Date: Thu, 23 Jul 2026 10:21:59 +0530 Subject: [PATCH 1/2] ci: restore web unit coverage --- .github/workflows/web-test-unit-tests.yml | 83 +++++++- package-lock.json | 183 ++++++++++++++---- web-admin/package.json | 4 +- ...xplore-bookmark-legacy-data-transformer.ts | 28 +++ .../src/features/bookmarks/utils.spec.ts | 17 +- web-admin/src/features/bookmarks/utils.ts | 24 ++- .../status/tables/model-actions.spec.ts | 5 +- .../projects/status/tables/utils.spec.ts | 5 +- web-admin/vite.config.ts | 16 ++ web-common/package.json | 1 + web-common/vite.config.ts | 17 +- 11 files changed, 318 insertions(+), 65 deletions(-) diff --git a/.github/workflows/web-test-unit-tests.yml b/.github/workflows/web-test-unit-tests.yml index 68c539bee3c7..182b6383265b 100644 --- a/.github/workflows/web-test-unit-tests.yml +++ b/.github/workflows/web-test-unit-tests.yml @@ -1,37 +1,68 @@ -name: Unit tests for web code +name: Unit tests and coverage for web code + on: pull_request: paths: - - ".github/workflows/web-test.yml" + - ".github/workflows/web-test-unit-tests.yml" + - "codecov.yml" + - "package.json" + - "package-lock.json" + - "web-admin/**" + - "web-common/**" + - "web-local/**" + push: + branches: [main] + paths: + - ".github/workflows/web-test-unit-tests.yml" + - "codecov.yml" + - "package.json" + - "package-lock.json" - "web-admin/**" - "web-common/**" - "web-local/**" + workflow_dispatch: + +permissions: + contents: read + pull-requests: read + +concurrency: + group: web-unit-${{ github.ref }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-22.04 steps: - - name: Checkout code + - name: Check out code uses: actions/checkout@v4 - - name: Filter modified codepaths + - name: Filter modified code paths uses: dorny/paths-filter@v3 id: filter with: filters: | admin: - - ".github/workflows/web-test.yml" + - ".github/workflows/web-test-unit-tests.yml" + - "codecov.yml" + - "package.json" + - "package-lock.json" - "web-admin/**" + - "web-common/**" common: - - ".github/workflows/web-test.yml" + - ".github/workflows/web-test-unit-tests.yml" + - "codecov.yml" + - "package.json" + - "package-lock.json" - "web-common/**" - - name: Set up NodeJS + - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version-file: '.nvmrc' - cache: 'npm' + node-version-file: ".nvmrc" + cache: npm - - name: NPM Install + - name: Install dependencies run: npm ci - name: Generate i18n files @@ -40,3 +71,35 @@ jobs: - name: Run web-common unit tests if: steps.filter.outputs.common == 'true' run: npm run test -w web-common + + - name: Collect web-common coverage + if: steps.filter.outputs.common == 'true' + run: npm run coverage -w web-common + + - name: Run web-admin unit tests + if: steps.filter.outputs.admin == 'true' + run: npm run test:unit -w web-admin + + - name: Collect web-admin coverage + if: steps.filter.outputs.admin == 'true' + run: npm run coverage -w web-admin + + - name: Upload web-common coverage + if: success() && steps.filter.outputs.common == 'true' + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: web-common/coverage/lcov.info + disable_search: true + flags: web-common + fail_ci_if_error: true + + - name: Upload web-admin coverage + if: success() && steps.filter.outputs.admin == 'true' + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: web-admin/coverage/lcov.info + disable_search: true + flags: web-admin + fail_ci_if_error: true diff --git a/package-lock.json b/package-lock.json index 6177083b77e2..448306ae44c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -877,9 +877,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", "dev": true, "license": "MIT", "dependencies": { @@ -2318,6 +2318,16 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@braintree/sanitize-url": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", @@ -5224,22 +5234,22 @@ } }, "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.1", + "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, @@ -5249,9 +5259,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dev": true, "license": "MIT", "optional": true, @@ -7301,18 +7311,6 @@ "node": ">= 8" } }, - "node_modules/@opentelemetry/api": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", - "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@orval/angular": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@orval/angular/-/angular-7.21.0.tgz", @@ -11815,6 +11813,37 @@ "node": ">=18.16.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.2.tgz", + "integrity": "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.2", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.2", + "vitest": "4.1.2" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/eslint-plugin": { "version": "1.6.14", "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.6.14.tgz", @@ -12737,6 +12766,25 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", + "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/astring": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", @@ -21053,6 +21101,45 @@ "node": ">=0.10.0" } }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -22397,6 +22484,34 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mark.js": { "version": "8.11.1", "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", @@ -25055,14 +25170,6 @@ "json-pointer": "0.6.2" } }, - "node_modules/openapi-types": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", - "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/openapi3-ts": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-4.5.0.tgz", @@ -30269,14 +30376,6 @@ "dev": true, "license": "MIT" }, - "node_modules/search-insights": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", - "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -36163,6 +36262,7 @@ "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.55.0", "@tanstack/svelte-query": "~5.76.0", + "@vitest/coverage-v8": "^4.1.2", "autoprefixer": "^10.4.20", "axios": "1.13.6", "cronstrue": "^2.41.0", @@ -36226,6 +36326,7 @@ "@types/luxon": "^3.4.2", "@types/memoizee": "^0.4.11", "@types/picomatch": "^4.0.3", + "@vitest/coverage-v8": "^4.1.2", "@xyflow/svelte": "^0.1.39", "autoprefixer": "^10.4.20", "bits-ui": "~2.14.4", diff --git a/web-admin/package.json b/web-admin/package.json index 136ac21d5414..7ee5f7f5ee47 100644 --- a/web-admin/package.json +++ b/web-admin/package.json @@ -8,6 +8,7 @@ "preview": "vite preview", "test": "playwright test", "test:unit": "vitest run", + "coverage": "vitest run --coverage", "generate:sveltekit": "svelte-kit sync", "test:setup": "E2E_NO_TEARDOWN=true playwright test --project=setup", "test:dev": "E2E_NO_SETUP_OR_TEARDOWN=true playwright test --project=e2e", @@ -24,7 +25,7 @@ "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.55.0", "@tanstack/svelte-query": "~5.76.0", - "tanstack-table-8-svelte-5": "^0.1.2", + "@vitest/coverage-v8": "^4.1.2", "autoprefixer": "^10.4.20", "axios": "1.13.6", "cronstrue": "^2.41.0", @@ -35,6 +36,7 @@ "svelte-check": "^4.0.4", "svelte-preprocess": "^6.0.3", "tailwindcss": "^3.4.13", + "tanstack-table-8-svelte-5": "^0.1.2", "vitest": "^4.1.1", "web-common": "*" }, diff --git a/web-admin/src/features/bookmarks/explore-bookmark-legacy-data-transformer.ts b/web-admin/src/features/bookmarks/explore-bookmark-legacy-data-transformer.ts index 3ed06291a64c..6fb7b8943778 100644 --- a/web-admin/src/features/bookmarks/explore-bookmark-legacy-data-transformer.ts +++ b/web-admin/src/features/bookmarks/explore-bookmark-legacy-data-transformer.ts @@ -2,6 +2,8 @@ import { getDashboardStateFromUrl } from "@rilldata/web-common/features/dashboar import { getMetricsViewTimeRangeFromExploreQueryOptions } from "@rilldata/web-common/features/dashboards/selectors.ts"; import { getTimeControlState } from "@rilldata/web-common/features/dashboards/time-controls/time-control-store.ts"; import { convertPartialExploreStateToUrlParams } from "@rilldata/web-common/features/dashboards/url-state/convert-partial-explore-state-to-url-params.ts"; +import { getRillDefaultExploreUrlParams } from "@rilldata/web-common/features/dashboards/url-state/get-rill-default-explore-url-params.ts"; +import { ExploreStateURLParams } from "@rilldata/web-common/features/dashboards/url-state/url-params.ts"; import { getExploreValidSpecQueryOptions } from "@rilldata/web-common/features/explores/selectors.ts"; import type { RuntimeClient } from "@rilldata/web-common/runtime-client/v2"; import type { @@ -72,5 +74,31 @@ export function exploreBookmarkDataTransformer({ ), ); + // Legacy protobuf bookmarks predate these two Explore settings. Complete + // bookmarks inherit them, while filter-only bookmarks must remain partial. + const filterOnlyKeys = new Set([ + ExploreStateURLParams.Filters, + ExploreStateURLParams.TimeRange, + ExploreStateURLParams.TimeGrain, + ]); + const isFilterOnly = [...searchParams.keys()].every((key) => + filterOnlyKeys.has(key as ExploreStateURLParams), + ); + if (!isFilterOnly) { + const defaultParams = getRillDefaultExploreUrlParams( + metricsViewSpec, + exploreSpec, + timeRangeSummary, + ); + for (const key of [ + ExploreStateURLParams.DynamicYAxisScale, + ExploreStateURLParams.LeaderboardShowContextForAllMeasures, + ]) { + const value = defaultParams.get(key); + if (value !== null && !searchParams.has(key)) + searchParams.set(key, value); + } + } + return "?" + searchParams.toString(); } diff --git a/web-admin/src/features/bookmarks/utils.spec.ts b/web-admin/src/features/bookmarks/utils.spec.ts index 88ce88a9c78d..e4c572d8c628 100644 --- a/web-admin/src/features/bookmarks/utils.spec.ts +++ b/web-admin/src/features/bookmarks/utils.spec.ts @@ -84,7 +84,7 @@ describe("getBookmarkData and parseBookmarks", () => { subTitle: "Empty url", curUrlSearch: "", expectedFullUrlSearch: - "view=explore&tr=P7D&tz=UTC&grain=hour&compare_tr=rill-PP&f=publisher+IN+('Facebook','Yahoo')&compare_dim=&measures=bid_price&dims=*&expand_dim=domain&sort_by=bid_price&sort_dir=DESC&sort_type=value&leaderboard_measures=bid_price", + "view=explore&tr=P7D&tz=UTC&grain=hour&compare_tr=rill-PP&f=publisher+IN+('Facebook','Yahoo')&compare_dim=&measures=bid_price&dims=*&expand_dim=domain&sort_by=bid_price&sort_dir=DESC&sort_type=value&leaderboard_measures=bid_price&chart_type=adaptive&dyn_y=false&lb_ctx=false", isActive: false, }, { @@ -92,15 +92,15 @@ describe("getBookmarkData and parseBookmarks", () => { curUrlSearch: "view=tdd&tr=P7D&compare_tr=rill-PW&f=publisher+IN+('Facebook','Yahoo')&measure=impressions&chart_type=stacked_bar", expectedFullUrlSearch: - "view=explore&tr=P7D&tz=UTC&grain=hour&compare_tr=rill-PP&f=publisher+IN+('Facebook','Yahoo')&compare_dim=&measures=bid_price&dims=*&expand_dim=domain&sort_by=bid_price&sort_dir=DESC&sort_type=value&leaderboard_measures=bid_price", + "view=explore&tr=P7D&tz=UTC&grain=hour&compare_tr=rill-PP&f=publisher+IN+('Facebook','Yahoo')&compare_dim=&measures=bid_price&dims=*&expand_dim=domain&sort_by=bid_price&sort_dir=DESC&sort_type=value&leaderboard_measures=bid_price&chart_type=adaptive&dyn_y=false&lb_ctx=false", isActive: false, }, { subTitle: "Same url", curUrlSearch: - "tr=P7D&compare_tr=rill-PP&f=publisher+IN+('Facebook','Yahoo')&measures=bid_price&expand_dim=domain&sort_by=bid_price&leaderboard_measures=bid_price", + "tr=P7D&grain=hour&compare_tr=rill-PP&f=publisher+IN+('Facebook','Yahoo')&measures=bid_price&expand_dim=domain&sort_by=bid_price&leaderboard_measures=bid_price", expectedFullUrlSearch: - "view=explore&tr=P7D&tz=UTC&grain=hour&compare_tr=rill-PP&f=publisher+IN+('Facebook','Yahoo')&compare_dim=&measures=bid_price&dims=*&expand_dim=domain&sort_by=bid_price&sort_dir=DESC&sort_type=value&leaderboard_measures=bid_price", + "view=explore&tr=P7D&tz=UTC&grain=hour&compare_tr=rill-PP&f=publisher+IN+('Facebook','Yahoo')&compare_dim=&measures=bid_price&dims=*&expand_dim=domain&sort_by=bid_price&sort_dir=DESC&sort_type=value&leaderboard_measures=bid_price&chart_type=adaptive&dyn_y=false&lb_ctx=false", isActive: true, }, ], @@ -139,7 +139,7 @@ describe("getBookmarkData and parseBookmarks", () => { { subTitle: "Filter only equal", curUrlSearch: - "view=tdd&tr=P7D&compare_tr=rill-PW&f=publisher+IN+('Facebook','Yahoo')&measure=impressions&chart_type=stacked_bar", + "view=tdd&tr=P7D&grain=hour&compare_tr=rill-PW&f=publisher+IN+('Facebook','Yahoo')&measure=impressions&chart_type=stacked_bar", expectedFullUrlSearch: "view=tdd&tr=P7D&grain=hour&compare_tr=rill-PW&f=publisher+IN+('Facebook','Yahoo')&measure=impressions&chart_type=stacked_bar", isActive: true, @@ -147,7 +147,7 @@ describe("getBookmarkData and parseBookmarks", () => { { subTitle: "Same url", curUrlSearch: - "tr=P7D&compare_tr=rill-PP&f=publisher+IN+('Facebook','Yahoo')&measures=bid_price&expand_dim=domain&sort_by=bid_price&leaderboard_measures=bid_price", + "tr=P7D&grain=hour&compare_tr=rill-PP&f=publisher+IN+('Facebook','Yahoo')&measures=bid_price&expand_dim=domain&sort_by=bid_price&leaderboard_measures=bid_price", expectedFullUrlSearch: "tr=P7D&grain=hour&compare_tr=rill-PP&f=publisher+IN+('Facebook','Yahoo')&measures=bid_price&expand_dim=domain&sort_by=bid_price&leaderboard_measures=bid_price", isActive: true, @@ -200,6 +200,8 @@ describe("getBookmarkData and parseBookmarks", () => { isActive, } of urls) { it(subTitle, () => { + // The current URL and the generated bookmark are compared semantically; + // defaults and filter-only parameters must remain explicit in fullUrl. // Parse both new and old formats together const [parsedBookmark, parsedProtoBookmark] = parseBookmarks( [ @@ -223,6 +225,9 @@ describe("getBookmarkData and parseBookmarks", () => { parsedBookmark.fullUrl.slice(1), expectedFullUrlSearch, ); + if (isActive && !filtersOnly) { + assertUnorderedUrlSearch(parsedBookmark.url, curUrlSearch); + } expect(parsedBookmark.isActive).toEqual(isActive); assertUnorderedUrlSearch(parsedProtoBookmark.url, parsedBookmark.url); diff --git a/web-admin/src/features/bookmarks/utils.ts b/web-admin/src/features/bookmarks/utils.ts index 5f5d0cf0d084..1776cc22e1fe 100644 --- a/web-admin/src/features/bookmarks/utils.ts +++ b/web-admin/src/features/bookmarks/utils.ts @@ -211,7 +211,7 @@ function isBookmarkActive( filtersOnly: boolean, ) { if (!filtersOnly) - return bookmarkUrlParams.toString() === curUrlParams.toString(); + return areUrlSearchParamsEqual(bookmarkUrlParams, curUrlParams); return [...bookmarkUrlParams.entries()].every(([key, value]) => { const curValue = curUrlParams.get(key); @@ -219,6 +219,28 @@ function isBookmarkActive( }); } +function areUrlSearchParamsEqual( + left: URLSearchParams, + right: URLSearchParams, +): boolean { + const normalize = (params: URLSearchParams) => + [...params.entries()].sort( + ([leftKey, leftValue], [rightKey, rightValue]) => + leftKey === rightKey + ? leftValue.localeCompare(rightValue) + : leftKey.localeCompare(rightKey), + ); + const leftEntries = normalize(left); + const rightEntries = normalize(right); + return ( + leftEntries.length === rightEntries.length && + leftEntries.every( + ([key, value], index) => + rightEntries[index][0] === key && rightEntries[index][1] === value, + ) + ); +} + function isAbsoluteTimeRangeBookmark(bookmarkUrlParams: URLSearchParams) { const timeRange = bookmarkUrlParams.get(ExploreStateURLParams.TimeRange); if (!timeRange) return false; diff --git a/web-admin/src/features/projects/status/tables/model-actions.spec.ts b/web-admin/src/features/projects/status/tables/model-actions.spec.ts index 7db9268c26df..de2c176f0d6c 100644 --- a/web-admin/src/features/projects/status/tables/model-actions.spec.ts +++ b/web-admin/src/features/projects/status/tables/model-actions.spec.ts @@ -70,9 +70,12 @@ describe("getAvailableModelActions", () => { expect(actions).not.toContain("refreshErrored"); }); - it("includes refreshErrored for partitioned model with errored partitions", () => { + it("includes refreshErrored for an incremental partitioned model with errors", () => { + // The backend only refreshes failed partitions for incremental models, so + // the fixture must represent that complete capability contract. const resource = makeModel({ partitionsResolver: "sql", + incremental: true, partitionsModelId: "abc-123", partitionsHaveErrors: true, }); diff --git a/web-admin/src/features/projects/status/tables/utils.spec.ts b/web-admin/src/features/projects/status/tables/utils.spec.ts index b4be99bde0f3..3caa89baa99d 100644 --- a/web-admin/src/features/projects/status/tables/utils.spec.ts +++ b/web-admin/src/features/projects/status/tables/utils.spec.ts @@ -235,9 +235,12 @@ describe("tables utils", () => { expect(hasModelErroredPartitions(resource)).toBe(false); }); - it("returns true when both conditions are met", () => { + it("returns true when every incremental partition condition is met", () => { + // Errored-partition refresh is meaningful only for an incremental model; + // all three backend signals are required for a true result. const resource: V1Resource = { model: { + spec: { incremental: true }, state: { partitionsModelId: "123", partitionsHaveErrors: true }, }, }; diff --git a/web-admin/vite.config.ts b/web-admin/vite.config.ts index c010847ee0b9..3a8324a4db18 100644 --- a/web-admin/vite.config.ts +++ b/web-admin/vite.config.ts @@ -8,6 +8,22 @@ import { readPublicEmailDomains } from "./src/features/projects/user-management/ dns.setDefaultResultOrder("verbatim"); export default defineConfig({ + test: { + include: ["src/**/*.{test,spec}.{js,ts}"], + coverage: { + provider: "v8", + reportsDirectory: "coverage", + reporter: ["text", "lcov"], + include: ["src/**/*.{js,ts,svelte}"], + exclude: [ + "src/**/*.d.ts", + "src/proto/gen/**", + "src/client/gen/**", + "src/runtime-client/**/gen/**", + "src/lib/i18n/gen/**", + ], + }, + }, resolve: { alias: { "@rilldata/web-admin": "/src", diff --git a/web-common/package.json b/web-common/package.json index bb6c1aafe2e7..54265381c17a 100644 --- a/web-common/package.json +++ b/web-common/package.json @@ -53,6 +53,7 @@ "@types/luxon": "^3.4.2", "@types/memoizee": "^0.4.11", "@types/picomatch": "^4.0.3", + "@vitest/coverage-v8": "^4.1.2", "@xyflow/svelte": "^0.1.39", "autoprefixer": "^10.4.20", "bits-ui": "~2.14.4", diff --git a/web-common/vite.config.ts b/web-common/vite.config.ts index 4ffc724c71af..b9ed9a9f94cc 100644 --- a/web-common/vite.config.ts +++ b/web-common/vite.config.ts @@ -34,6 +34,19 @@ export default defineConfig(({ mode }) => { }, plugins: [sveltekit()], test: { + coverage: { + provider: "v8", + reportsDirectory: "coverage", + reporter: ["text", "lcov"], + include: ["src/**/*.{js,ts,svelte}"], + exclude: [ + "src/**/*.d.ts", + "src/proto/gen/**", + "src/client/gen/**", + "src/runtime-client/**/gen/**", + "src/lib/i18n/gen/**", + ], + }, projects: [ { extends: "./vite.config.ts", @@ -44,10 +57,6 @@ export default defineConfig(({ mode }) => { clearMocks: true, setupFiles: ["./vitest-setup.ts"], globals: true, - coverage: { - provider: "v8", - include: ["src/**"], - }, }, }, ], From c42c50d72228795c8a99474647689264822c9196 Mon Sep 17 00:00:00 2001 From: Nishant Bangarwa Date: Thu, 23 Jul 2026 10:28:34 +0530 Subject: [PATCH 2/2] ci: generate SvelteKit types before unit tests --- .github/workflows/web-test-unit-tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/web-test-unit-tests.yml b/.github/workflows/web-test-unit-tests.yml index 182b6383265b..accd6cf34ae4 100644 --- a/.github/workflows/web-test-unit-tests.yml +++ b/.github/workflows/web-test-unit-tests.yml @@ -68,6 +68,11 @@ jobs: - name: Generate i18n files run: npm run build:i18n + - name: Generate SvelteKit types + run: |- + npm run generate:sveltekit -w web-common + npm run generate:sveltekit -w web-admin + - name: Run web-common unit tests if: steps.filter.outputs.common == 'true' run: npm run test -w web-common