diff --git a/dashboards/src/components/Datasources/EditDatasourcesButton.tsx b/dashboards/src/components/Datasources/EditDatasourcesButton.tsx index 2a986c9..364270b 100644 --- a/dashboards/src/components/Datasources/EditDatasourcesButton.tsx +++ b/dashboards/src/components/Datasources/EditDatasourcesButton.tsx @@ -15,7 +15,6 @@ import { ReactElement, useState } from 'react'; import { Button } from '@mui/material'; import PencilIcon from 'mdi-material-ui/PencilOutline'; import { Drawer, InfoTooltip } from '@perses-dev/components'; -import { DashboardResource, EphemeralDashboardResource } from '@perses-dev/core'; // TODO weird that ephemeral dashboard is required here import { DatasourceSpec } from '@perses-dev/spec'; import { useDatasourceStore } from '@perses-dev/plugin-system'; import { TOOLTIP_TEXT, editButtonStyle } from '../../constants'; @@ -60,23 +59,13 @@ export function EditDatasourcesButton(): ReactElement { {} as Record ); - setDashboard( - dashboard.kind === 'Dashboard' - ? ({ - ...dashboard, - spec: { - ...dashboard.spec, - datasources: datasources, - }, - } as DashboardResource) - : ({ - ...dashboard, - spec: { - ...dashboard.spec, - datasources: datasources, - }, - } as EphemeralDashboardResource) - ); + setDashboard({ + ...dashboard, + spec: { + ...dashboard.spec, + datasources: datasources, + }, + }); setSavedDatasources(newSavedDatasources); setLocalDatasources(datasources); setIsDatasourceEditorOpen(false); diff --git a/dashboards/src/components/DownloadButton/serializeDashboard.ts b/dashboards/src/components/DownloadButton/serializeDashboard.ts index 393f46e..7523738 100644 --- a/dashboards/src/components/DownloadButton/serializeDashboard.ts +++ b/dashboards/src/components/DownloadButton/serializeDashboard.ts @@ -11,8 +11,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { DashboardResource, EphemeralDashboardResource } from '@perses-dev/core'; // TODO metadata should not be used, same for ephemeral dashboard import { stringify } from 'yaml'; +import { DashboardMinimalResource } from '../../context'; + +//TODO: Although the previous comment suggests the metadata not should not be used, I keep them +// Check git history to find prev comment type SerializedDashboard = { contentType: string; @@ -20,13 +23,13 @@ type SerializedDashboard = { }; function serializeYaml( - dashboard: DashboardResource | EphemeralDashboardResource, + dashboard: DashboardMinimalResource, shape?: 'cr-v1alpha1' | 'cr-v1alpha2' ): SerializedDashboard { let content: string; if (shape === 'cr-v1alpha1') { - const name = dashboard.metadata.name.toLowerCase().replace(/[^a-z0-9-]/g, '-'); + const name = (dashboard.metadata.name as string)?.toLowerCase().replace(/[^a-z0-9-]/g, '-'); content = stringify( { apiVersion: 'perses.dev/v1alpha1', @@ -45,7 +48,7 @@ function serializeYaml( { schema: 'yaml-1.1' } ); } else if (shape === 'cr-v1alpha2') { - const name = dashboard.metadata.name.toLowerCase().replace(/[^a-z0-9-]/g, '-'); + const name = (dashboard.metadata.name as string).toLowerCase().replace(/[^a-z0-9-]/g, '-'); content = stringify( { apiVersion: 'perses.dev/v1alpha2', @@ -73,7 +76,7 @@ function serializeYaml( } export function serializeDashboard( - dashboard: DashboardResource | EphemeralDashboardResource, + dashboard: DashboardMinimalResource, format: 'json' | 'yaml', shape?: 'cr-v1alpha1' | 'cr-v1alpha2' ): SerializedDashboard { diff --git a/dashboards/src/components/LeaveDialog/LeaveDialog.tsx b/dashboards/src/components/LeaveDialog/LeaveDialog.tsx index 832e8a1..add0375 100644 --- a/dashboards/src/components/LeaveDialog/LeaveDialog.tsx +++ b/dashboards/src/components/LeaveDialog/LeaveDialog.tsx @@ -11,11 +11,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { DashboardResource, EphemeralDashboardResource } from '@perses-dev/core'; // TODO only dashboard spec should be used import { ReactElement, ReactNode, useEffect } from 'react'; import { useBlocker } from 'react-router-dom'; import { DiscardChangesConfirmationDialog } from '@perses-dev/components'; import type { BlockerFunction } from '@remix-run/router'; +import { DashboardMinimalResource } from '../../context'; const handleRouteChange = (event: BeforeUnloadEvent): string => { event.preventDefault(); @@ -69,8 +69,8 @@ export function LeaveDialog({ original, current, }: { - original: DashboardResource | EphemeralDashboardResource | undefined; - current: DashboardResource | EphemeralDashboardResource; + original: DashboardMinimalResource | undefined; + current: DashboardMinimalResource; }): ReactNode { const handleIsBlocked: BlockerFunction = (ctx) => { if (JSON.stringify(original) !== JSON.stringify(current)) { diff --git a/dashboards/src/context/DashboardProvider/DashboardProvider.tsx b/dashboards/src/context/DashboardProvider/DashboardProvider.tsx index 2e8cb27..9ef85a4 100644 --- a/dashboards/src/context/DashboardProvider/DashboardProvider.tsx +++ b/dashboards/src/context/DashboardProvider/DashboardProvider.tsx @@ -18,13 +18,8 @@ import { devtools } from 'zustand/middleware'; import { immer } from 'zustand/middleware/immer'; import { shallow } from 'zustand/shallow'; import { createContext, ReactElement, ReactNode, useCallback, useContext, useEffect, useState } from 'react'; -import { - ProjectMetadata, - DashboardResource, - DEFAULT_REFRESH_INTERVAL, - EphemeralDashboardResource, -} from '@perses-dev/core'; -import { Display, DurationString, DatasourceSpec } from '@perses-dev/spec'; +import { DEFAULT_REFRESH_INTERVAL } from '@perses-dev/core'; +import { Display, DurationString, DatasourceSpec, DashboardSpec } from '@perses-dev/spec'; import { usePlugin, usePluginRegistry } from '@perses-dev/plugin-system'; import { createPanelGroupEditorSlice, PanelGroupEditorSlice } from './panel-group-editor-slice'; import { convertLayoutsToPanelGroups, createPanelGroupSlice, PanelGroupSlice } from './panel-group-slice'; @@ -40,6 +35,16 @@ import { createPanelDefinition } from './common'; import { createViewPanelSlice, ViewPanelSlice, VirtualPanelRef } from './view-panel-slice'; import { createLinksSlice, LinksSlice } from './links-slice'; +export type DashboardKind = 'Dashboard' | 'EphemeralDashboard'; +export type DashboardGenericMetaData = Record; + +export interface DashboardMinimalResource { + kind: DashboardKind; + name: string; + spec: DashboardSpec; + metadata: DashboardGenericMetaData; +} + export interface DashboardStoreState extends PanelGroupSlice, PanelSlice, @@ -55,9 +60,10 @@ export interface DashboardStoreState LinksSlice { isEditMode: boolean; setEditMode: (isEditMode: boolean) => void; - setDashboard: (dashboard: DashboardResource | EphemeralDashboardResource) => void; - kind: DashboardResource['kind'] | EphemeralDashboardResource['kind']; - metadata: ProjectMetadata; + setDashboard: (dashboard: DashboardMinimalResource) => void; + dashboardName: string; + kind: DashboardKind; + metadata: DashboardGenericMetaData; duration: DurationString; refreshInterval: DurationString; display?: Display; @@ -76,7 +82,7 @@ export function useDashboardStore(selector: (state: DashboardStoreState) => T } export interface DashboardStoreProps { - dashboardResource: DashboardResource | EphemeralDashboardResource; + dashboardResource: DashboardMinimalResource; isEditMode?: boolean; viewPanelRef?: VirtualPanelRef; setViewPanelRef?: (viewPanelRef: VirtualPanelRef | undefined) => void; @@ -124,11 +130,12 @@ function initStore(props: DashboardProviderProps): StoreApi kind, metadata, spec: { display, duration, refreshInterval = DEFAULT_REFRESH_INTERVAL, datasources, layouts = [], panels = {} }, + name, } = dashboardResource; const links = dashboardResource.spec.links ?? []; - const ttl = 'ttl' in dashboardResource.spec ? dashboardResource.spec.ttl : undefined; + const ttl = 'ttl' in dashboardResource.spec ? (dashboardResource.spec.ttl as DurationString) : undefined; const store = createStore()( immer( @@ -151,6 +158,7 @@ function initStore(props: DashboardProviderProps): StoreApi ...createDiscardChangesDialogSlice(...args), ...createEditJsonDialogSlice(...args), ...createSaveChangesDialogSlice(...args), + dashboardName: name, kind, metadata, display, @@ -163,12 +171,14 @@ function initStore(props: DashboardProviderProps): StoreApi set({ isEditMode }); }, setDashboard: ({ + name, kind, metadata, spec: { display, panels = {}, layouts = [], duration, refreshInterval, datasources = {}, links = [] }, }): void => { set((state) => { state.kind = kind; + state.dashboardName = name; state.metadata = metadata; state.display = display; state.panels = panels; diff --git a/dashboards/src/context/DashboardProvider/common.ts b/dashboards/src/context/DashboardProvider/common.ts index f69c770..9d0532a 100644 --- a/dashboards/src/context/DashboardProvider/common.ts +++ b/dashboards/src/context/DashboardProvider/common.ts @@ -11,10 +11,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { DashboardResource, EphemeralDashboardResource } from '@perses-dev/core'; // TODO import { PanelDefinition, UnknownSpec } from '@perses-dev/spec'; +import { DashboardMinimalResource } from './DashboardProvider'; -export type OnSaveDashboard = (dashboard: DashboardResource | EphemeralDashboardResource) => Promise; +export type OnSaveDashboard = (dashboard: DashboardMinimalResource) => Promise; /** * The middleware applied to the DashboardStore (can be used as generic argument in StateCreator). diff --git a/dashboards/src/context/DashboardProvider/dashboard-provider-api.ts b/dashboards/src/context/DashboardProvider/dashboard-provider-api.ts index cf7baf3..0ca9dd9 100644 --- a/dashboards/src/context/DashboardProvider/dashboard-provider-api.ts +++ b/dashboards/src/context/DashboardProvider/dashboard-provider-api.ts @@ -12,15 +12,9 @@ // limitations under the License. import { useCallback, useMemo } from 'react'; -import { - DashboardResource, - EphemeralDashboardResource, - PanelGroupItemLayout, - PanelGroupDefinition, - PanelGroupItemId, -} from '@perses-dev/core'; // TODO +import { PanelGroupItemLayout, PanelGroupDefinition, PanelGroupItemId } from '@perses-dev/core'; // TODO import { DurationString, Link, PanelDefinition, PanelGroupId } from '@perses-dev/spec'; -import { DashboardStoreState, useDashboardStore } from './DashboardProvider'; +import { DashboardMinimalResource, DashboardStoreState, useDashboardStore } from './DashboardProvider'; import { DeletePanelGroupDialogState } from './delete-panel-group-slice'; import { PanelGroupEditor } from './panel-group-editor-slice'; import { PanelEditorState } from './panel-editor-slice'; @@ -41,7 +35,7 @@ export function useEditMode(): { setEditMode: (isEditMode: boolean) => void; isE const selectDashboardActions: ({ setDashboard, openAddPanelGroup, openAddPanel }: DashboardStoreState) => { openAddPanelGroup: () => void; openAddPanel: (panelGroupId?: PanelGroupId) => void; - setDashboard: (dashboard: DashboardResource | EphemeralDashboardResource) => void; + setDashboard: (dashboard: DashboardMinimalResource) => void; } = ({ setDashboard, openAddPanelGroup, openAddPanel }: DashboardStoreState) => ({ setDashboard, openAddPanelGroup, @@ -53,7 +47,7 @@ const selectDashboardActions: ({ setDashboard, openAddPanelGroup, openAddPanel } export function useDashboardActions(): { openAddPanelGroup: () => void; openAddPanel: () => void; - setDashboard: (dashboard: DashboardResource | EphemeralDashboardResource) => void; + setDashboard: (dashboard: DashboardMinimalResource) => void; } { const { setDashboard, openAddPanelGroup, openAddPanel } = useDashboardStore(selectDashboardActions); return { diff --git a/dashboards/src/context/DatasourceStoreProvider.tsx b/dashboards/src/context/DatasourceStoreProvider.tsx index 38309da..2ca2ae7 100644 --- a/dashboards/src/context/DatasourceStoreProvider.tsx +++ b/dashboards/src/context/DatasourceStoreProvider.tsx @@ -12,13 +12,7 @@ // limitations under the License. import { ReactElement, ReactNode, useCallback, useMemo, useState } from 'react'; -import { - DashboardResource, - EphemeralDashboardResource, - DatasourceDefinition, - BuildDatasourceProxyUrlParams, - DatasourceApi, -} from '@perses-dev/core'; // TODO +import { DatasourceDefinition, BuildDatasourceProxyUrlParams, DatasourceApi } from '@perses-dev/core'; // TODO import { DashboardSpec, DatasourceSelector, DatasourceSpec } from '@perses-dev/spec'; import { DatasourceStoreContext, @@ -29,9 +23,10 @@ import { DatasourceClient, DatasourceSelectItem, } from '@perses-dev/plugin-system'; +import { DashboardMinimalResource } from './DashboardProvider'; export interface DatasourceStoreProviderProps { - dashboardResource?: DashboardResource | EphemeralDashboardResource; + dashboardResource?: DashboardMinimalResource; projectName?: string; datasourceApi: DatasourceApi; children?: ReactNode; @@ -61,8 +56,8 @@ export function DatasourceStoreProvider(props: DatasourceStoreProviderProps): Re return { spec: dashboardDatasource.spec, proxyUrl: buildDatasourceProxyUrl(datasourceApi, { - project: dashboardResource.metadata.project, - dashboard: dashboardResource.metadata.name, + project: dashboardResource.metadata.project as string, + dashboard: dashboardResource.metadata.name as string, name: dashboardDatasource.name, }), }; @@ -71,7 +66,7 @@ export function DatasourceStoreProvider(props: DatasourceStoreProviderProps): Re if (project) { // Try to find it at the project level as a Datasource resource - const datasource = await datasourceApi.getDatasource(project, selector); + const datasource = await datasourceApi.getDatasource(String(project), selector); if (datasource !== undefined) { return { spec: datasource.spec, @@ -126,7 +121,7 @@ export function DatasourceStoreProvider(props: DatasourceStoreProviderProps): Re async (datasourcePluginName: string): Promise => { const [pluginMetadata, datasources, globalDatasources] = await Promise.all([ listPluginMetadata(['Datasource']), - project ? datasourceApi.listDatasources(project, datasourcePluginName) : [], + project ? datasourceApi.listDatasources(String(project), datasourcePluginName) : [], datasourceApi.listGlobalDatasources(datasourcePluginName), ]); @@ -182,23 +177,13 @@ export function DatasourceStoreProvider(props: DatasourceStoreProviderProps): Re const setLocalDatasources = useCallback( (datasources: Record) => { if (dashboardResource) { - setDashboardResource( - dashboardResource.kind === 'Dashboard' - ? ({ - ...dashboardResource, - spec: { - ...dashboardResource.spec, - datasources: datasources, - }, - } as DashboardResource) - : ({ - ...dashboardResource, - spec: { - ...dashboardResource.spec, - datasources: datasources, - }, - } as EphemeralDashboardResource) - ); + setDashboardResource({ + ...dashboardResource, + spec: { + ...dashboardResource.spec, + datasources: datasources, + }, + }); } }, [dashboardResource] diff --git a/dashboards/src/context/useDashboard.tsx b/dashboards/src/context/useDashboard.tsx index 00235df..ce4044a 100644 --- a/dashboards/src/context/useDashboard.tsx +++ b/dashboards/src/context/useDashboard.tsx @@ -11,14 +11,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { DashboardResource, EphemeralDashboardResource, PanelGroupDefinition } from '@perses-dev/core'; // TODO -import { createPanelRef, GridDefinition, PanelGroupId } from '@perses-dev/spec'; -import { useDashboardStore } from './DashboardProvider'; +import { DurationString, PanelGroupDefinition } from '@perses-dev/core'; +import { createPanelRef, DashboardSpec, GridDefinition, PanelGroupId } from '@perses-dev/spec'; +import { DashboardMinimalResource, useDashboardStore } from './DashboardProvider'; import { useVariableDefinitionActions, useVariableDefinitions } from './VariableProvider'; +type DashboardType = Omit & { spec: DashboardSpec & { ttl?: DurationString } }; export function useDashboard(): { - dashboard: DashboardResource | EphemeralDashboardResource; - setDashboard: (dashboardResource: DashboardResource | EphemeralDashboardResource) => void; + dashboard: DashboardType; + setDashboard: (dashboardResource: DashboardMinimalResource) => void; } { const { panels, @@ -33,8 +34,10 @@ export function useDashboard(): { datasources, links, ttl, + dashboardName, } = useDashboardStore( ({ + dashboardName, panels, panelGroups, panelGroupOrder, @@ -60,15 +63,17 @@ export function useDashboard(): { datasources, links, ttl, + dashboardName, }) ); const { setVariableDefinitions } = useVariableDefinitionActions(); const variables = useVariableDefinitions(); const layouts = convertPanelGroupsToLayouts(panelGroups, panelGroupOrder); - const dashboard = + const dashboard: DashboardType = kind === 'Dashboard' - ? ({ + ? { + name: dashboardName, kind, metadata, spec: { @@ -81,8 +86,9 @@ export function useDashboard(): { datasources, links, }, - } as DashboardResource) - : ({ + } + : { + name: dashboardName, kind, metadata, spec: { @@ -96,9 +102,9 @@ export function useDashboard(): { links, ttl, }, - } as EphemeralDashboardResource); + }; - const setDashboard = (dashboardResource: DashboardResource | EphemeralDashboardResource): void => { + const setDashboard = (dashboardResource: DashboardMinimalResource): void => { setVariableDefinitions(dashboardResource.spec.variables); setDashboardResource(dashboardResource); }; diff --git a/dashboards/src/test/dashboard-provider.tsx b/dashboards/src/test/dashboard-provider.tsx index fa28d2c..3c3f161 100644 --- a/dashboards/src/test/dashboard-provider.tsx +++ b/dashboards/src/test/dashboard-provider.tsx @@ -11,16 +11,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { DashboardResource } from '@perses-dev/core'; // TODO import { useContext } from 'react'; import { StoreApi } from 'zustand'; -import { DashboardContext, DashboardStoreState } from '../context'; +import { DashboardContext, DashboardMinimalResource, DashboardStoreState } from '../context'; import testDashboard from './testDashboard'; /** * Helper to get a test dashboard resource. */ -export function getTestDashboard(): DashboardResource { +export function getTestDashboard(): DashboardMinimalResource { // TODO: Should we be cloning this to create a new object each time? return testDashboard; } diff --git a/dashboards/src/test/testDashboard.ts b/dashboards/src/test/testDashboard.ts index 83e630f..cd289ae 100644 --- a/dashboards/src/test/testDashboard.ts +++ b/dashboards/src/test/testDashboard.ts @@ -11,9 +11,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { DashboardResource } from '@perses-dev/core'; // TODO +import { DashboardMinimalResource } from '../context'; -const testDashboard: DashboardResource = { +const testDashboard: DashboardMinimalResource = { + name: 'Node Stats', kind: 'Dashboard', metadata: { name: 'Node Stats', diff --git a/dashboards/src/views/ViewDashboard/DashboardApp.tsx b/dashboards/src/views/ViewDashboard/DashboardApp.tsx index 5609ea5..26e8ec1 100644 --- a/dashboards/src/views/ViewDashboard/DashboardApp.tsx +++ b/dashboards/src/views/ViewDashboard/DashboardApp.tsx @@ -14,8 +14,8 @@ import { ReactElement, ReactNode, useState } from 'react'; import { Box } from '@mui/material'; import { ChartsProvider, ErrorAlert, ErrorBoundary, useChartsTheme } from '@perses-dev/components'; -import { DashboardResource, EphemeralDashboardResource } from '@perses-dev/core'; // TODO only spec should be used import { useDatasourceStore } from '@perses-dev/plugin-system'; +import { DashboardSpec } from '@perses-dev/spec'; import { PanelDrawer, Dashboard, @@ -29,10 +29,16 @@ import { SaveChangesConfirmationDialog, LeaveDialog, } from '../../components'; -import { OnSaveDashboard, useDashboard, useDiscardChangesConfirmationDialog, useEditMode } from '../../context'; +import { + DashboardMinimalResource, + OnSaveDashboard, + useDashboard, + useDiscardChangesConfirmationDialog, + useEditMode, +} from '../../context'; export interface DashboardAppProps { - dashboardResource: DashboardResource | EphemeralDashboardResource; + dashboardResource: DashboardMinimalResource; emptyDashboardProps?: Partial; isReadonly: boolean; isVariableEnabled: boolean; @@ -43,7 +49,7 @@ export interface DashboardAppProps { isLeavingConfirmDialogEnabled?: boolean; dashboardTitleComponent?: ReactNode; onSave?: OnSaveDashboard; - onDiscard?: (entity: DashboardResource) => void; + onDiscard?: (name: string, spec: DashboardSpec) => void; } export const DashboardApp = (props: DashboardAppProps): ReactElement => { @@ -64,10 +70,10 @@ export const DashboardApp = (props: DashboardAppProps): ReactElement => { const chartsTheme = useChartsTheme(); const { isEditMode, setEditMode } = useEditMode(); + const { dashboard, setDashboard } = useDashboard(); - const [originalDashboard, setOriginalDashboard] = useState< - DashboardResource | EphemeralDashboardResource | undefined - >(undefined); + const [originalDashboard, setOriginalDashboard] = useState(undefined); + const { setSavedDatasources } = useDatasourceStore(); const { openDiscardChangesConfirmationDialog, closeDiscardChangesConfirmationDialog } = @@ -81,7 +87,7 @@ export const DashboardApp = (props: DashboardAppProps): ReactElement => { setEditMode(false); closeDiscardChangesConfirmationDialog(); if (onDiscard) { - onDiscard(dashboard as unknown as DashboardResource); + onDiscard(dashboard.name, dashboard.spec); } }; @@ -118,7 +124,7 @@ export const DashboardApp = (props: DashboardAppProps): ReactElement => { }} >