diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1cb4e2a0..f21e5709 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -247,7 +247,19 @@ jobs: cache: 'pnpm' - name: Run security audit - run: pnpm audit --audit-level=high --prod + # The npm audit endpoint has intermittent outages (ERR_SOCKET_TIMEOUT). + # Treat registry connectivity failures as a warning instead of failing + # the build; real vulnerability findings still fail. + run: | + set +e + output=$(pnpm audit --audit-level=high --prod 2>&1) + exit_code=$? + echo "$output" + if [ $exit_code -ne 0 ] && echo "$output" | grep -qE 'ERR_SOCKET_TIMEOUT|ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|FetchError'; then + echo "::warning::npm audit registry endpoint unreachable — skipping audit (network error, not a vulnerability finding)" + exit 0 + fi + exit $exit_code - name: Run dependency license check run: pnpm dlx license-checker --onlyAllow 'MIT;Apache-2.0;ISC;BSD;BSD-2-Clause;BSD-3-Clause;Apache;Unlicense' \ No newline at end of file diff --git a/src/components/DataVisNITRO/DataVisNITRO.stories.tsx b/src/components/DataVisNITRO/DataVisNITRO.stories.tsx index 71072d06..0f292ffe 100644 --- a/src/components/DataVisNITRO/DataVisNITRO.stories.tsx +++ b/src/components/DataVisNITRO/DataVisNITRO.stories.tsx @@ -1,12 +1,25 @@ -import { useContext, useState } from 'react'; +import { useContext, useEffect, useMemo, useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react-vite'; -import { GridAssistant, type GridAssistantColumn } from '@mieweb/datavis'; +import { + GridAssistant, + type GridAssistantColumn, + type PrefsInstance, +} from '@mieweb/datavis'; +import { Prefs } from 'datavis-ace'; import { DataVisNitroContext, DataVisNitroGrid, DataVisNitroSource, } from './DataVisNITRO'; +// datavis-ace is untyped JS (`Prefs` is built with a runtime `makeSubclass` +// helper), so give it an explicit constructor signature for use below. +const PrefsConstructor = Prefs as unknown as new ( + name: string, + moduleBindings: unknown, + opts: Record +) => PrefsInstance; + const meta: Meta = { title: 'Components/Text & Data Display/DataVis NITRO', component: DataVisNitroGrid, @@ -302,3 +315,78 @@ export const OzwellAssistant: Story = { ), }; + +const PREFS_STORAGE_KEY = 'mieweb-ui-storybook:datavis-prefs'; + +/** + * Reads the shared view from DataVisNitroContext, binds a localStorage-backed + * Prefs module to it, and passes the module to the grid so the perspective + * toolbar ("Main Perspective" dropdown, save/reset/history buttons) renders. + */ +const PerspectivesGrid = () => { + const view = useContext(DataVisNitroContext); + + const prefs = useMemo(() => { + if (!view) return null; + + return new PrefsConstructor('mieweb-ui-storybook:employees', null, { + autoSave: true, + backend: { + type: 'localStorage', + localStorage: { + key: PREFS_STORAGE_KEY, + }, + }, + }); + }, [view]); + + useEffect(() => { + if (!view || !prefs) return; + view.setPrefs(prefs); + prefs.prime?.(); + }, [view, prefs]); + + if (!prefs) return null; + + return ( + + ); +}; + +export const WithPerspectives: Story = { + loaders: [ + () => { + // Clear saved perspectives in automated runs (test runner, visual + // regression) so the story renders deterministically, while keeping + // localStorage persistence for normal browsing. localStorage access + // can throw in restricted contexts, so fall back to defaults quietly. + try { + if (typeof navigator !== 'undefined' && navigator.webdriver) { + window.localStorage.removeItem(PREFS_STORAGE_KEY); + } + } catch { + // Storage unavailable — the story just renders its defaults. + } + return {}; + }, + ], + parameters: { + docs: { + description: { + story: + 'Passing a `prefs` module (a `PrefsInstance` from `datavis-ace`) enables the perspective toolbar: the "Main Perspective" dropdown, save / save-as / reset buttons, and undo/redo history. Perspectives capture the grid configuration (sort, filter, group, pivot, aggregate, column layout) and here persist to `localStorage`. Create the `Prefs` instance, bind it to the shared view with `view.setPrefs(prefs)`, and pass it to ``. In minimal mode the same toolbar appears inside the hamburger menu.', + }, + }, + }, + render: () => ( + + + + ), +};