diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 200d4ce3ff..c46838a7ae 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -374,7 +374,7 @@ jobs: name: Start Playwright frontend env: BACKGROUND_SERVICE_NAME: Playwright frontend - BACKGROUND_RUN_COMMAND: CAPTCHA_KEY='' bun run serve:worktree + BACKGROUND_RUN_COMMAND: bun run supabase:with-env -- bun scripts/playwright-frontend-preview.ts BACKGROUND_WAIT_ON: | http-get://localhost:5173 BACKGROUND_LOG_PATH: ${{ runner.temp }}/playwright-frontend.log diff --git a/scripts/playwright-frontend-preview.ts b/scripts/playwright-frontend-preview.ts new file mode 100644 index 0000000000..c4d2bb1a59 --- /dev/null +++ b/scripts/playwright-frontend-preview.ts @@ -0,0 +1,30 @@ +import { spawnSync } from 'node:child_process' + +const supabaseUrl = process.env.SUPABASE_URL +if (!supabaseUrl) { + console.error('SUPABASE_URL is required for Playwright frontend preview') + process.exit(1) +} + +const normalizedSupabaseHost = supabaseUrl.replace(/^https?:\/\//, '').replace(/\/+$/, '') +const apiDomain = `${normalizedSupabaseHost}/functions/v1` + +const env = { + ...process.env, + API_DOMAIN: apiDomain, + CAPTCHA_KEY: '', + ENV: 'local', + SUPA_ANON: process.env.SUPABASE_ANON_KEY || '', + SUPA_URL: supabaseUrl, +} + +const build = spawnSync('bun', ['run', 'build'], { env, stdio: 'inherit' }) +if ((build.status ?? 1) !== 0) + process.exit(build.status ?? 1) + +const preview = spawnSync('bunx', ['vite', 'preview', '--host', '127.0.0.1', '--port', '5173'], { + env, + stdio: 'inherit', +}) + +process.exit(preview.status ?? 1) diff --git a/src/components/dashboard/UpdateStatsChart.vue b/src/components/dashboard/UpdateStatsChart.vue index b05a975dbd..a6c499c252 100644 --- a/src/components/dashboard/UpdateStatsChart.vue +++ b/src/components/dashboard/UpdateStatsChart.vue @@ -59,11 +59,27 @@ const cycleEnd = computed(() => { const DAY_IN_MS = 1000 * 60 * 60 * 24 -// Map action display names back to action filter keys for navigation -const actionToFilterKey: Record = { - requested: 'get', - install: 'set', - fail: 'set_fail', +const UPDATE_FAILURE_ACTIONS = [ + 'set_fail', + 'update_fail', + 'download_fail', + 'windows_path_fail', + 'canonical_path_fail', + 'directory_path_fail', + 'unzip_fail', + 'low_mem_fail', + 'download_manifest_file_fail', + 'download_manifest_checksum_fail', + 'download_manifest_brotli_fail', + 'decrypt_fail', + 'checksum_fail', +] as const + +// Map chart buckets back to the log actions they aggregate. +const actionToLogActions: Record = { + requested: ['get'], + install: ['set'], + fail: UPDATE_FAILURE_ACTIONS, } // Click handler for tooltip items - navigates to logs page filtered by action type and date @@ -81,11 +97,11 @@ const tooltipClickHandler = computed(() => { onAppClick: (actionKey: string, clickContext?: { date: Date, dataIndex: number }) => { // Navigate to logs page with action filter and date range // The actionKey here is the internal key like 'requested', 'install', 'fail' - const filterAction = actionToFilterKey[actionKey] || actionKey + const filterActions = actionToLogActions[actionKey] ?? [actionKey] const params = new URLSearchParams() - params.set('action', filterAction) + filterActions.forEach(action => params.append('action', action)) - // Add date range if provided (start of day to end of day) + // Add date range if provided (selected day) if (clickContext?.date) { const startOfDay = dayjs(clickContext.date).startOf('day') const endOfDay = dayjs(clickContext.date).endOf('day') diff --git a/src/components/tables/LogTable.vue b/src/components/tables/LogTable.vue index 362ab3ab53..3edcce89aa 100644 --- a/src/components/tables/LogTable.vue +++ b/src/components/tables/LogTable.vue @@ -145,14 +145,16 @@ function formatAction(elem: Element): string { // Initialize action filters from URL query parameter function initializeActionFilters(): void { - const actionParam = route.query.action - if (actionParam && typeof actionParam === 'string') { - // Find the filter key for this action - const filterKey = actionToFilter[actionParam] + const actionParams = [route.query.action] + .flat() + .filter((action): action is string => typeof action === 'string') + + actionParams.forEach((action) => { + const filterKey = actionToFilter[action] if (filterKey && actionFilters.value[filterKey] !== undefined) { actionFilters.value[filterKey] = true } - } + }) } // Compute active actions based on filters