diff --git a/examples/hello-world/.gitignore b/examples/hello-world/.gitignore index 3c0f0280..c764e5aa 100644 --- a/examples/hello-world/.gitignore +++ b/examples/hello-world/.gitignore @@ -1 +1,2 @@ +baselines sde-prep diff --git a/examples/hello-world/package.json b/examples/hello-world/package.json index 161fbb65..8e643f27 100644 --- a/examples/hello-world/package.json +++ b/examples/hello-world/package.json @@ -6,6 +6,7 @@ "scripts": { "build": "sde bundle", "dev": "sde dev", + "save-baseline": "sde-check baseline --save", "serve": "sirv ./sde-prep/check-report" }, "dependencies": { diff --git a/examples/sample-check-app/src/index.ts b/examples/sample-check-app/src/index.ts index ccb7a97c..3db945dc 100644 --- a/examples/sample-check-app/src/index.ts +++ b/examples/sample-check-app/src/index.ts @@ -25,6 +25,8 @@ if (suiteSummaryJson) { const checkOptions = getConfigOptions(baselineBundle() as Bundle, currentBundle() as Bundle) // Initialize the root Svelte component -const appShell = initAppShell(checkOptions, suiteSummary) +const appShell = initAppShell(checkOptions, { + suiteSummary +}) export default appShell diff --git a/examples/template-default/.gitignore b/examples/template-default/.gitignore index f5c47214..5a2a8e83 100644 --- a/examples/template-default/.gitignore +++ b/examples/template-default/.gitignore @@ -1,3 +1,4 @@ +baselines sde-prep *.vdf *.vdfx diff --git a/examples/template-default/package.json b/examples/template-default/package.json index f5723216..214dc073 100644 --- a/examples/template-default/package.json +++ b/examples/template-default/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "sde bundle", "dev": "sde dev", - "start": "sde dev" + "save-baseline": "sde-check baseline --save" }, "workspaces": [ "packages/core", diff --git a/examples/template-minimal/.gitignore b/examples/template-minimal/.gitignore index 3c0f0280..c764e5aa 100644 --- a/examples/template-minimal/.gitignore +++ b/examples/template-minimal/.gitignore @@ -1 +1,2 @@ +baselines sde-prep diff --git a/examples/template-minimal/package.json b/examples/template-minimal/package.json index 7f0ec704..e0cde93f 100644 --- a/examples/template-minimal/package.json +++ b/examples/template-minimal/package.json @@ -5,7 +5,8 @@ "type": "module", "scripts": { "build": "sde bundle", - "dev": "sde dev" + "dev": "sde dev", + "save-baseline": "sde-check baseline --save" }, "dependencies": { "@sdeverywhere/build": "^0.2.0", diff --git a/packages/check-ui-shell/src/_shared/stores.ts b/packages/check-ui-shell/src/_shared/stores.ts new file mode 100644 index 00000000..0c2deb89 --- /dev/null +++ b/packages/check-ui-shell/src/_shared/stores.ts @@ -0,0 +1,36 @@ +// Copyright (c) 2022 Climate Interactive / New Venture Fund + +import type { Writable } from 'svelte/store' +import { writable } from 'svelte/store' + +/** + * Return a Svelte writable store that is backed by local storage. + */ +export function localStorageWritableBoolean(key: string, defaultValue: boolean): Writable { + const initialStringValue = localStorage.getItem(key) + let initialValue: boolean + if (initialStringValue !== undefined) { + initialValue = initialStringValue === '1' + } else { + initialValue = defaultValue + } + + let currentValue = initialValue + const { subscribe, set } = writable(initialValue) + + const _set = (newValue: boolean) => { + currentValue = newValue + localStorage.setItem(key, newValue ? '1' : '0') + set(newValue) + } + + const _update = (updater: (value: boolean) => boolean) => { + _set(updater(currentValue)) + } + + return { + subscribe, + set: _set, + update: _update + } +} diff --git a/packages/check-ui-shell/src/app-init.ts b/packages/check-ui-shell/src/app-init.ts index 812c67ee..e67d1c9e 100644 --- a/packages/check-ui-shell/src/app-init.ts +++ b/packages/check-ui-shell/src/app-init.ts @@ -7,12 +7,15 @@ import { initAppModel } from './model/app-model' import { default as AppShell } from './app-shell.svelte' import { AppViewModel } from './app-vm' -export function initAppShell( - configOptions: ConfigOptions, - suiteSummary?: SuiteSummary, - containerId = 'app-shell-container' -): AppShell { +export interface AppShellOptions { + suiteSummary?: SuiteSummary + containerId?: string + bundleNames?: string[] +} + +export function initAppShell(configOptions: ConfigOptions, appShellOptions?: AppShellOptions): AppShell { // Initialize the root Svelte component + const containerId = appShellOptions?.containerId || 'app-shell-container' const appShell = new AppShell({ target: document.getElementById(containerId), props: { @@ -23,8 +26,17 @@ export function initAppShell( // Initialize the app model asynchronously initAppModel(configOptions) .then(appModel => { - // Create the app view model and update the AppShell component - const appViewModel = new AppViewModel(appModel, suiteSummary) + // Create the app view model + const appViewModel = new AppViewModel(appModel, appShellOptions?.suiteSummary) + + if (appShellOptions?.bundleNames) { + // Set the list of available bundle names + // TODO: Pass these to AppViewModel constructor instead of setting them here + appViewModel.headerViewModel.bundleNamesL.set(appShellOptions.bundleNames) + appViewModel.headerViewModel.bundleNamesR.set(appShellOptions.bundleNames) + } + + // Update the AppShell component appShell.$set({ appViewModel }) diff --git a/packages/check-ui-shell/src/components/check/summary/check-summary-row-vm.ts b/packages/check-ui-shell/src/components/check/summary/check-summary-row-vm.ts index 0da21102..958aec61 100644 --- a/packages/check-ui-shell/src/components/check/summary/check-summary-row-vm.ts +++ b/packages/check-ui-shell/src/components/check/summary/check-summary-row-vm.ts @@ -31,7 +31,8 @@ export function row( rowClass: string, status: CheckStatus, content: string, - graphBoxViewModel?: CheckSummaryGraphBoxViewModel + graphBoxViewModel?: CheckSummaryGraphBoxViewModel, + graphVisible = false ): CheckSummaryRowViewModel { const whitespace = ' '.repeat(2 + indent * 4) const statusChar = charForStatus(status) @@ -42,6 +43,6 @@ export function row( status, span, graphBoxViewModel, - graphVisible: writable(false) + graphVisible: writable(graphVisible) } } diff --git a/packages/check-ui-shell/src/components/check/summary/check-summary-test-vm.ts b/packages/check-ui-shell/src/components/check/summary/check-summary-test-vm.ts index e2c0f444..a871357c 100644 --- a/packages/check-ui-shell/src/components/check/summary/check-summary-test-vm.ts +++ b/packages/check-ui-shell/src/components/check/summary/check-summary-test-vm.ts @@ -24,6 +24,8 @@ export function createCheckSummaryTestViewModel( dataCoordinator: CheckDataCoordinator, test: CheckTestReport ): CheckSummaryTestViewModel { + let expandedFirstGraph = false + const rows: CheckSummaryRowViewModel[] = [] const testRow = row(0, 'test', test.status, test.name) for (const scenario of test.scenarios) { @@ -32,6 +34,7 @@ export function createCheckSummaryTestViewModel( rows.push(row(2, 'dataset', dataset.status, datasetMessage(dataset, bold))) for (const predicate of dataset.predicates) { let graphBoxViewModel: CheckSummaryGraphBoxViewModel + let graphVisible = false if (scenario.checkScenario.scenario && dataset.checkDataset.datasetKey) { graphBoxViewModel = new CheckSummaryGraphBoxViewModel( dataCoordinator, @@ -39,8 +42,22 @@ export function createCheckSummaryTestViewModel( dataset.checkDataset.datasetKey, predicate ) + if (!expandedFirstGraph && predicate.result.status === 'failed') { + // Expand the graph for the first failing check + expandedFirstGraph = true + graphVisible = true + } } - rows.push(row(3, 'predicate', predicate.result.status, predicateMessage(predicate, bold), graphBoxViewModel)) + rows.push( + row( + 3, + 'predicate', + predicate.result.status, + predicateMessage(predicate, bold), + graphBoxViewModel, + graphVisible + ) + ) } } } diff --git a/packages/check-ui-shell/src/components/header/header-vm.ts b/packages/check-ui-shell/src/components/header/header-vm.ts index f586f234..e841893f 100644 --- a/packages/check-ui-shell/src/components/header/header-vm.ts +++ b/packages/check-ui-shell/src/components/header/header-vm.ts @@ -3,10 +3,13 @@ import type { Writable } from 'svelte/store' import { writable } from 'svelte/store' import type { CompareConfig } from '@sdeverywhere/check-core' +import { localStorageWritableBoolean } from '../../_shared/stores' export interface HeaderViewModel { nameL?: string nameR?: string + bundleNamesL: Writable + bundleNamesR: Writable thresholds?: string[] simplifyScenarios?: Writable } @@ -15,7 +18,12 @@ export function createHeaderViewModel( compareConfig: CompareConfig | undefined, includeSimplifyScenarios: boolean ): HeaderViewModel { - const simplifyScenarios: Writable = includeSimplifyScenarios ? writable(true) : undefined + let simplifyScenarios: Writable + if (includeSimplifyScenarios) { + simplifyScenarios = localStorageWritableBoolean('sde-check-simplify-scenarios', true) + } else { + simplifyScenarios = undefined + } // Only include the comparison-related header elements if the compare // config is defined @@ -31,11 +39,15 @@ export function createHeaderViewModel( return { nameL: compareConfig.bundleL.name, nameR: compareConfig.bundleR.name, + bundleNamesL: writable([compareConfig.bundleL.name]), + bundleNamesR: writable([compareConfig.bundleR.name]), thresholds: thresholdStrings, simplifyScenarios } } else { return { + bundleNamesL: writable([]), + bundleNamesR: writable([]), simplifyScenarios } } diff --git a/packages/check-ui-shell/src/components/header/header.pug b/packages/check-ui-shell/src/components/header/header.pug new file mode 100644 index 00000000..474388de --- /dev/null +++ b/packages/check-ui-shell/src/components/header/header.pug @@ -0,0 +1,13 @@ +//- Copyright (c) 2021-2022 Climate Interactive / New Venture Fund + +//- Note: These mixins are kept in an external pug file to work around a +//- Svelte issue related to `each` and TypeScript, see: +//- https://github.com/sveltejs/svelte-preprocess/issues/207 + +mixin optionsL + +each(`$bundleNamesL as name`) + option(selected!='{name === viewModel.nameL}') { name } + +mixin optionsR + +each(`$bundleNamesR as name`) + option(selected!='{name === viewModel.nameR}') { name } diff --git a/packages/check-ui-shell/src/components/header/header.svelte b/packages/check-ui-shell/src/components/header/header.svelte index 78830fa8..09a99e75 100644 --- a/packages/check-ui-shell/src/components/header/header.svelte +++ b/packages/check-ui-shell/src/components/header/header.svelte @@ -11,6 +11,8 @@ import type { HeaderViewModel } from './header-vm' export let viewModel: HeaderViewModel const simplifyScenarios = viewModel.simplifyScenarios const thresholds = viewModel.thresholds +const bundleNamesL = viewModel.bundleNamesL +const bundleNamesR = viewModel.bundleNamesR const dispatch = createEventDispatcher() @@ -18,6 +20,24 @@ function onHome() { dispatch('command', { cmd: 'show-summary' }) } +function onSelectBundle(kind: string, name: string): void { + const changeEvent = new CustomEvent('sde-check-bundle', { + detail: { + kind, + name + } + }) + document.dispatchEvent(changeEvent) +} + +function onSelectBundleL(e: Event) { + onSelectBundle('left', (e.target as HTMLSelectElement).value) +} + +function onSelectBundleR(e: Event) { + onSelectBundle('right', (e.target as HTMLSelectElement).value) +} + @@ -26,6 +46,8 @@ function onHome() {