From 3fc2f3c5fdb3ecc4159a25f2628b9c2a64e0a38e Mon Sep 17 00:00:00 2001 From: Nicolas Van Labeke Date: Tue, 19 Dec 2023 09:22:26 +0000 Subject: [PATCH] refactor(18161): Change the persistence of the metrics to a local store (#243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(18161): add a store for the metrics * refactor(18161): replace the deprecated localStorage with the new met… * test(18161): add tests * chore(18077): little bit of cleaning --- .../frontend/src/modules/Metrics/Metrics.tsx | 47 ++++--- .../Metrics/hooks/useMetricsStore.spec.ts | 128 ++++++++++++++++++ .../modules/Metrics/hooks/useMetricsStore.ts | 48 +++++++ .../Workspace/components/nodes/NodeGroup.tsx | 1 - 4 files changed, 204 insertions(+), 20 deletions(-) create mode 100644 hivemq-edge/src/frontend/src/modules/Metrics/hooks/useMetricsStore.spec.ts create mode 100644 hivemq-edge/src/frontend/src/modules/Metrics/hooks/useMetricsStore.ts diff --git a/hivemq-edge/src/frontend/src/modules/Metrics/Metrics.tsx b/hivemq-edge/src/frontend/src/modules/Metrics/Metrics.tsx index 25a363f985..9d6b5e83a8 100644 --- a/hivemq-edge/src/frontend/src/modules/Metrics/Metrics.tsx +++ b/hivemq-edge/src/frontend/src/modules/Metrics/Metrics.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react' +import { FC, useEffect } from 'react' import { Accordion, AccordionButton, @@ -12,13 +12,13 @@ import { useDisclosure, } from '@chakra-ui/react' import { useTranslation } from 'react-i18next' -import { useLocalStorage } from '@uidotdev/usehooks' import { NodeTypes } from '@/modules/Workspace/types.ts' import config from '@/config' import { ChartType, MetricDefinition } from './types.ts' +import useMetricsStore from './hooks/useMetricsStore.ts' import MetricEditor from './components/editor/MetricEditor.tsx' import ChartContainer from './components/container/ChartContainer.tsx' import Sample from './components/container/Sample.tsx' @@ -37,23 +37,32 @@ export interface MetricSpecStorage { } const Metrics: FC = ({ nodeId, adapterIDs, initMetrics, defaultChartType }) => { - const [metrics, setMetrics] = useLocalStorage( - `edge.reports-${nodeId}`, - initMetrics ? initMetrics.map((e) => ({ selectedTopic: e })) : [] - ) - const showEditor = config.features.METRICS_SHOW_EDITOR - const { isOpen, onOpen, onClose } = useDisclosure() const { t } = useTranslation() + const { isOpen, onOpen, onClose } = useDisclosure() + const { addMetrics, getMetricsFor, removeMetrics } = useMetricsStore() + const showEditor = config.features.METRICS_SHOW_EDITOR const handleCreateMetrics = (value: MetricDefinition) => { const { selectedTopic, selectedChart } = value - setMetrics((old) => [...old, { selectedTopic: selectedTopic?.value, selectedChart: selectedChart?.value }]) + addMetrics(nodeId, selectedTopic.value, selectedChart?.value) } const handleRemoveMetrics = (selectedTopic: string) => { - setMetrics((old) => old.filter((x) => x.selectedTopic !== selectedTopic)) + removeMetrics(nodeId, selectedTopic) } + const metrics = getMetricsFor(nodeId) + + useEffect(() => { + const gg = getMetricsFor(nodeId) + if (gg.length === 0 && initMetrics) { + initMetrics.map((e) => { + addMetrics(nodeId, e, defaultChartType) + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + return ( {showEditor && ( @@ -75,7 +84,7 @@ const Metrics: FC = ({ nodeId, adapterIDs, initMetrics, defaultCha e.selectedTopic)} + selectedMetrics={metrics.map((e) => e.metrics)} selectedChart={defaultChartType} onSubmit={handleCreateMetrics} /> @@ -87,22 +96,22 @@ const Metrics: FC = ({ nodeId, adapterIDs, initMetrics, defaultCha {metrics.map((e) => { - if (!e.selectedChart || e.selectedChart === ChartType.SAMPLE) + if (!e.chart || e.chart === ChartType.SAMPLE) return ( handleRemoveMetrics(e.selectedTopic)} + key={e.metrics} + metricName={e.metrics} + onClose={() => handleRemoveMetrics(e.metrics)} canEdit={isOpen} /> ) else return ( handleRemoveMetrics(e.selectedTopic)} + key={e.metrics} + chartType={e.chart} + metricName={e.metrics} + onClose={() => handleRemoveMetrics(e.metrics)} canEdit={isOpen} /> ) diff --git a/hivemq-edge/src/frontend/src/modules/Metrics/hooks/useMetricsStore.spec.ts b/hivemq-edge/src/frontend/src/modules/Metrics/hooks/useMetricsStore.spec.ts new file mode 100644 index 0000000000..0ffc8e0da6 --- /dev/null +++ b/hivemq-edge/src/frontend/src/modules/Metrics/hooks/useMetricsStore.spec.ts @@ -0,0 +1,128 @@ +import { expect } from 'vitest' +import { act, renderHook } from '@testing-library/react' + +import useMetricsStore, { + MetricDefinitionSpec, + MetricDefinitionStore, +} from '@/modules/Metrics/hooks/useMetricsStore.ts' +import { ChartType } from '@/modules/Metrics/types.ts' + +describe('useMetricsStore', () => { + beforeEach(() => { + const { result } = renderHook(useMetricsStore) + act(() => { + result.current.reset() + }) + }) + + it('should start with an empty store', async () => { + const { result } = renderHook(useMetricsStore) + const { metrics } = result.current + + expect(metrics).toHaveLength(0) + }) + + it('should add a metric to the store', async () => { + const { result } = renderHook(useMetricsStore) + const { metrics } = result.current + + expect(metrics).toHaveLength(0) + + act(() => { + const { addMetrics } = result.current + addMetrics('nodeId', 'metricNAme', ChartType.SAMPLE) + }) + + expect(result.current.metrics).toHaveLength(1) + }) + + it('should remove a metric to the store', async () => { + const { result } = renderHook(useMetricsStore) + const { metrics } = result.current + + expect(metrics).toHaveLength(0) + + act(() => { + const { addMetrics } = result.current + addMetrics('nodeId', 'metricName1', ChartType.SAMPLE) + addMetrics('nodeId', 'metricName2', ChartType.SAMPLE) + addMetrics('anotherNodeId', 'metricName1', ChartType.SAMPLE) + }) + + expect(result.current.metrics).toHaveLength(3) + + act(() => { + const { removeMetrics } = result.current + removeMetrics('nodeId', 'metricName1') + }) + + expect(result.current.metrics).toStrictEqual([ + { + chart: ChartType.SAMPLE, + metrics: 'metricName2', + source: 'nodeId', + }, + { + chart: ChartType.SAMPLE, + metrics: 'metricName1', + source: 'anotherNodeId', + }, + ]) + }) + + it('should return all the metrics for a node', async () => { + const { result } = renderHook(useMetricsStore) + const { metrics } = result.current + + expect(metrics).toHaveLength(0) + + act(() => { + const { addMetrics } = result.current + addMetrics('nodeId', 'metricName1', ChartType.SAMPLE) + addMetrics('nodeId', 'metricName2', ChartType.SAMPLE) + addMetrics('anotherNodeId', 'metricName1', ChartType.SAMPLE) + }) + + expect(result.current.metrics).toHaveLength(3) + + act(() => { + const { getMetricsFor } = result.current + const res = getMetricsFor('nodeId') + expect(res).toStrictEqual([ + { + chart: ChartType.SAMPLE, + metrics: 'metricName1', + source: 'nodeId', + }, + { + chart: ChartType.SAMPLE, + metrics: 'metricName2', + source: 'nodeId', + }, + ]) + }) + }) + + it('should reset the store', async () => { + const { result } = renderHook(useMetricsStore) + const { metrics } = result.current + + expect(metrics).toHaveLength(0) + + act(() => { + const { addMetrics } = result.current + addMetrics('nodeId', 'metricName1', ChartType.SAMPLE) + addMetrics('nodeId', 'metricName2', ChartType.SAMPLE) + addMetrics('anotherNodeId', 'metricName1', ChartType.SAMPLE) + }) + + expect(result.current.metrics).toHaveLength(3) + + act(() => { + const { reset } = result.current + reset() + }) + + expect(result.current.metrics).toHaveLength(0) + }) +}) diff --git a/hivemq-edge/src/frontend/src/modules/Metrics/hooks/useMetricsStore.ts b/hivemq-edge/src/frontend/src/modules/Metrics/hooks/useMetricsStore.ts new file mode 100644 index 0000000000..273bb6a2f7 --- /dev/null +++ b/hivemq-edge/src/frontend/src/modules/Metrics/hooks/useMetricsStore.ts @@ -0,0 +1,48 @@ +import { create } from 'zustand' +import { persist, createJSONStorage } from 'zustand/middleware' + +import { ChartType } from '../types.ts' + +export interface MetricDefinitionSpec { + source: string + metrics: string + chart?: ChartType +} + +export interface MetricDefinitionStore { + metrics: MetricDefinitionSpec[] + reset: () => void + addMetrics: (source: string, metrics: string, chart?: ChartType) => void + removeMetrics: (source: string, metrics: string) => void + getMetricsFor: (source: string) => MetricDefinitionSpec[] +} + +const useMetricsStore = create()( + persist( + (set, get) => ({ + metrics: [], + reset: () => { + set({ metrics: [] }) + }, + addMetrics: (source: string, metrics, chart) => { + set({ + metrics: [...get().metrics, { source: source, metrics: metrics, chart: chart }], + }) + }, + removeMetrics: (source: string, metrics: string) => { + set({ + metrics: get().metrics.filter((e) => e.source !== source || e.metrics !== metrics), + }) + }, + getMetricsFor: (source: string) => { + return get().metrics.filter((m) => m.source === source) + }, + }), + { + name: 'edge.observability', + storage: createJSONStorage(() => localStorage), + } + ) +) + +export default useMetricsStore diff --git a/hivemq-edge/src/frontend/src/modules/Workspace/components/nodes/NodeGroup.tsx b/hivemq-edge/src/frontend/src/modules/Workspace/components/nodes/NodeGroup.tsx index 164640724b..b6bd1bcb3b 100644 --- a/hivemq-edge/src/frontend/src/modules/Workspace/components/nodes/NodeGroup.tsx +++ b/hivemq-edge/src/frontend/src/modules/Workspace/components/nodes/NodeGroup.tsx @@ -56,7 +56,6 @@ const NodeGroup: FC> = ({ id, data, selected, ...props }) => { onEdgesChange(edges.filter((e) => e.source === id).map((e) => ({ id: e.id, type: 'remove' } as EdgeRemoveChange))) } - console.log('XXXXXXX data', data) return ( <>