mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-23 13:46:45 +02:00
update impact metrics state
This commit is contained in:
parent
69905185c5
commit
a9a7bc372d
@ -9,6 +9,8 @@ import { ChartItem } from './ChartItem.tsx';
|
||||
import { GridLayoutWrapper, type GridItem } from './GridLayoutWrapper.tsx';
|
||||
import { useImpactMetricsState } from './hooks/useImpactMetricsState.ts';
|
||||
import type { ChartConfig, LayoutItem } from './types.ts';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
|
||||
const StyledEmptyState = styled(Paper)(({ theme }) => ({
|
||||
textAlign: 'center',
|
||||
@ -21,9 +23,18 @@ const StyledEmptyState = styled(Paper)(({ theme }) => ({
|
||||
export const ImpactMetrics: FC = () => {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editingChart, setEditingChart] = useState<ChartConfig | undefined>();
|
||||
const { setToastApiError } = useToast();
|
||||
|
||||
const { charts, layout, addChart, updateChart, deleteChart, updateLayout } =
|
||||
useImpactMetricsState();
|
||||
const {
|
||||
charts,
|
||||
layout,
|
||||
loading: settingsLoading,
|
||||
error: settingsError,
|
||||
addChart,
|
||||
updateChart,
|
||||
deleteChart,
|
||||
updateLayout,
|
||||
} = useImpactMetricsState();
|
||||
|
||||
const {
|
||||
metadata,
|
||||
@ -51,20 +62,39 @@ export const ImpactMetrics: FC = () => {
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const handleSaveChart = (config: Omit<ChartConfig, 'id'>) => {
|
||||
if (editingChart) {
|
||||
updateChart(editingChart.id, config);
|
||||
} else {
|
||||
addChart(config);
|
||||
const handleSaveChart = async (config: Omit<ChartConfig, 'id'>) => {
|
||||
try {
|
||||
if (editingChart) {
|
||||
await updateChart(editingChart.id, config);
|
||||
} else {
|
||||
await addChart(config);
|
||||
}
|
||||
setModalOpen(false);
|
||||
} catch (error) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
setModalOpen(false);
|
||||
};
|
||||
|
||||
const handleLayoutChange = useCallback(
|
||||
(layout: any[]) => {
|
||||
updateLayout(layout as LayoutItem[]);
|
||||
async (layout: any[]) => {
|
||||
try {
|
||||
await updateLayout(layout as LayoutItem[]);
|
||||
} catch (error) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
},
|
||||
[updateLayout],
|
||||
[updateLayout, setToastApiError],
|
||||
);
|
||||
|
||||
const handleDeleteChart = useCallback(
|
||||
async (id: string) => {
|
||||
try {
|
||||
await deleteChart(id);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete chart:', error);
|
||||
}
|
||||
},
|
||||
[deleteChart],
|
||||
);
|
||||
|
||||
const gridItems: GridItem[] = useMemo(
|
||||
@ -79,7 +109,7 @@ export const ImpactMetrics: FC = () => {
|
||||
<ChartItem
|
||||
config={config}
|
||||
onEdit={handleEditChart}
|
||||
onDelete={deleteChart}
|
||||
onDelete={handleDeleteChart}
|
||||
/>
|
||||
),
|
||||
w: existingLayout?.w ?? 6,
|
||||
@ -92,10 +122,11 @@ export const ImpactMetrics: FC = () => {
|
||||
maxH: 8,
|
||||
};
|
||||
}),
|
||||
[charts, layout, handleEditChart, deleteChart],
|
||||
[charts, layout, handleEditChart, handleDeleteChart],
|
||||
);
|
||||
|
||||
const hasError = metadataError;
|
||||
const hasError = metadataError || settingsError;
|
||||
const isLoading = metadataLoading || settingsLoading;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -111,14 +142,14 @@ export const ImpactMetrics: FC = () => {
|
||||
variant='contained'
|
||||
startIcon={<Add />}
|
||||
onClick={handleAddChart}
|
||||
disabled={metadataLoading || !!hasError}
|
||||
disabled={isLoading || !!hasError}
|
||||
>
|
||||
Add Chart
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
{charts.length === 0 && !metadataLoading && !hasError ? (
|
||||
{charts.length === 0 && !isLoading && !hasError ? (
|
||||
<StyledEmptyState>
|
||||
<Typography variant='h6' gutterBottom>
|
||||
No charts configured
|
||||
@ -135,7 +166,7 @@ export const ImpactMetrics: FC = () => {
|
||||
variant='contained'
|
||||
startIcon={<Add />}
|
||||
onClick={handleAddChart}
|
||||
disabled={metadataLoading || !!hasError}
|
||||
disabled={isLoading || !!hasError}
|
||||
>
|
||||
Add Chart
|
||||
</Button>
|
||||
@ -153,7 +184,7 @@ export const ImpactMetrics: FC = () => {
|
||||
onSave={handleSaveChart}
|
||||
initialConfig={editingChart}
|
||||
metricSeries={metricSeries}
|
||||
loading={metadataLoading}
|
||||
loading={metadataLoading || settingsLoading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -184,7 +184,11 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
||||
background: theme.palette.background.elevation1,
|
||||
})}
|
||||
>
|
||||
<Typography variant='caption' color='text.secondary'>
|
||||
<Typography
|
||||
variant='caption'
|
||||
color='text.secondary'
|
||||
sx={{ textWrap: 'break-all' }}
|
||||
>
|
||||
<code>{debug.query}</code>
|
||||
</Typography>
|
||||
</Box>
|
||||
|
@ -1,72 +1,40 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { withDefault } from 'use-query-params';
|
||||
import { usePersistentTableState } from 'hooks/usePersistentTableState';
|
||||
import { useCallback } from 'react';
|
||||
import { useImpactMetricsSettings } from 'hooks/api/getters/useImpactMetricsSettings/useImpactMetricsSettings.js';
|
||||
import {useImpactMetricsSettingsApi} from 'hooks/api/actions/useImpactMetricsSettingsApi/useImpactMetricsSettingsApi.js';
|
||||
import type { ChartConfig, ImpactMetricsState, LayoutItem } from '../types.ts';
|
||||
|
||||
const createArrayParam = <T>() => ({
|
||||
encode: (items: T[]): string =>
|
||||
items.length > 0 ? btoa(JSON.stringify(items)) : '',
|
||||
|
||||
decode: (value: string | (string | null)[] | null | undefined): T[] => {
|
||||
if (typeof value !== 'string' || !value) return [];
|
||||
try {
|
||||
return JSON.parse(atob(value));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const ChartsParam = createArrayParam<ChartConfig>();
|
||||
const LayoutParam = createArrayParam<LayoutItem>();
|
||||
|
||||
export const useImpactMetricsState = () => {
|
||||
const stateConfig = {
|
||||
charts: withDefault(ChartsParam, []),
|
||||
layout: withDefault(LayoutParam, []),
|
||||
};
|
||||
const {
|
||||
settings,
|
||||
loading: settingsLoading,
|
||||
error: settingsError,
|
||||
refetch,
|
||||
} = useImpactMetricsSettings();
|
||||
|
||||
const [tableState, setTableState] = usePersistentTableState(
|
||||
'impact-metrics-state',
|
||||
stateConfig,
|
||||
);
|
||||
|
||||
const currentState: ImpactMetricsState = useMemo(
|
||||
() => ({
|
||||
charts: tableState.charts || [],
|
||||
layout: tableState.layout || [],
|
||||
}),
|
||||
[tableState.charts, tableState.layout],
|
||||
);
|
||||
|
||||
const updateState = useCallback(
|
||||
(newState: ImpactMetricsState) => {
|
||||
setTableState({
|
||||
charts: newState.charts,
|
||||
layout: newState.layout,
|
||||
});
|
||||
},
|
||||
[setTableState],
|
||||
);
|
||||
const {
|
||||
updateSettings,
|
||||
loading: actionLoading,
|
||||
errors: actionErrors,
|
||||
} = useImpactMetricsSettingsApi();
|
||||
|
||||
const addChart = useCallback(
|
||||
(config: Omit<ChartConfig, 'id'>) => {
|
||||
async (config: Omit<ChartConfig, 'id'>) => {
|
||||
const newChart: ChartConfig = {
|
||||
...config,
|
||||
id: `chart-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
};
|
||||
|
||||
const maxY =
|
||||
currentState.layout.length > 0
|
||||
settings.layout.length > 0
|
||||
? Math.max(
|
||||
...currentState.layout.map((item) => item.y + item.h),
|
||||
...settings.layout.map((item) => item.y + item.h),
|
||||
)
|
||||
: 0;
|
||||
|
||||
updateState({
|
||||
charts: [...currentState.charts, newChart],
|
||||
const newState: ImpactMetricsState = {
|
||||
charts: [...settings.charts, newChart],
|
||||
layout: [
|
||||
...currentState.layout,
|
||||
...settings.layout,
|
||||
{
|
||||
i: newChart.id,
|
||||
x: 0,
|
||||
@ -79,46 +47,58 @@ export const useImpactMetricsState = () => {
|
||||
maxH: 8,
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
await updateSettings(newState);
|
||||
refetch();
|
||||
},
|
||||
[currentState.charts, currentState.layout, updateState],
|
||||
[settings, updateSettings, refetch],
|
||||
);
|
||||
|
||||
const updateChart = useCallback(
|
||||
(id: string, updates: Partial<ChartConfig>) => {
|
||||
updateState({
|
||||
charts: currentState.charts.map((chart) =>
|
||||
chart.id === id ? { ...chart, ...updates } : chart,
|
||||
),
|
||||
layout: currentState.layout,
|
||||
});
|
||||
async (id: string, updates: Partial<ChartConfig>) => {
|
||||
const updatedCharts = settings.charts.map((chart) =>
|
||||
chart.id === id ? { ...chart, ...updates } : chart,
|
||||
);
|
||||
const newState: ImpactMetricsState = {
|
||||
charts: updatedCharts,
|
||||
layout: settings.layout,
|
||||
};
|
||||
await updateSettings(newState);
|
||||
refetch();
|
||||
},
|
||||
[currentState.charts, currentState.layout, updateState],
|
||||
[settings, updateSettings, refetch],
|
||||
);
|
||||
|
||||
const deleteChart = useCallback(
|
||||
(id: string) => {
|
||||
updateState({
|
||||
charts: currentState.charts.filter((chart) => chart.id !== id),
|
||||
layout: currentState.layout.filter((item) => item.i !== id),
|
||||
});
|
||||
async (id: string) => {
|
||||
const newState: ImpactMetricsState = {
|
||||
charts: settings.charts.filter((chart) => chart.id !== id),
|
||||
layout: settings.layout.filter((item) => item.i !== id),
|
||||
};
|
||||
await updateSettings(newState);
|
||||
refetch();
|
||||
},
|
||||
[currentState.charts, currentState.layout, updateState],
|
||||
[settings, updateSettings, refetch],
|
||||
);
|
||||
|
||||
const updateLayout = useCallback(
|
||||
(newLayout: LayoutItem[]) => {
|
||||
updateState({
|
||||
charts: currentState.charts,
|
||||
async (newLayout: LayoutItem[]) => {
|
||||
const newState: ImpactMetricsState = {
|
||||
charts: settings.charts,
|
||||
layout: newLayout,
|
||||
});
|
||||
};
|
||||
await updateSettings(newState);
|
||||
refetch();
|
||||
},
|
||||
[currentState.charts, updateState],
|
||||
[settings, updateSettings, refetch],
|
||||
);
|
||||
|
||||
return {
|
||||
charts: currentState.charts || [],
|
||||
layout: currentState.layout || [],
|
||||
charts: settings.charts || [],
|
||||
layout: settings.layout || [],
|
||||
loading: settingsLoading || actionLoading,
|
||||
error: settingsError || Object.keys(actionErrors).length > 0 ? actionErrors : undefined,
|
||||
addChart,
|
||||
updateChart,
|
||||
deleteChart,
|
||||
|
@ -0,0 +1,32 @@
|
||||
import { useCallback } from 'react';
|
||||
import useAPI from '../useApi/useApi.js';
|
||||
import type { ImpactMetricsState } from 'component/impact-metrics/types.ts';
|
||||
|
||||
export const useImpactMetricsSettingsApi = () => {
|
||||
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||
propagateErrors: true,
|
||||
});
|
||||
|
||||
const updateSettings = useCallback(
|
||||
async (settings: ImpactMetricsState) => {
|
||||
const path = `api/admin/impact-metrics/settings`;
|
||||
const req = createRequest(
|
||||
path,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(settings)
|
||||
},
|
||||
'updateImpactMetricsSettings',
|
||||
);
|
||||
|
||||
return makeRequest(req.caller, req.id);
|
||||
},
|
||||
[makeRequest, createRequest],
|
||||
);
|
||||
|
||||
return {
|
||||
updateSettings,
|
||||
errors,
|
||||
loading,
|
||||
};
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
import { fetcher, useApiGetter } from '../useApiGetter/useApiGetter.js';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import type { ImpactMetricsState } from 'component/impact-metrics/types.ts';
|
||||
|
||||
export const useImpactMetricsSettings = () => {
|
||||
const PATH = `api/admin/impact-metrics/settings`;
|
||||
const { data, refetch, loading, error } =
|
||||
useApiGetter<ImpactMetricsState>(formatApiPath(PATH), () =>
|
||||
fetcher(formatApiPath(PATH), 'Impact metrics settings'),
|
||||
);
|
||||
|
||||
return {
|
||||
settings: data || { charts: [], layout: [] },
|
||||
refetch,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user