mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 12:15:57 +08:00
feat: support dashboard local state (#4475)
This commit is contained in:
parent
6837c41090
commit
26bc94fc46
@ -1,25 +1,31 @@
|
|||||||
.DynamicColumnTable {
|
.DynamicColumnTable {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.dynamicColumnTable-button {
|
.dynamicColumnTable-button {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
|
||||||
|
&.filter-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dynamicColumnsTable-items {
|
.dynamicColumnsTable-items {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 10.625rem;
|
width: 10.625rem;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.dynamicColumnsTable-items {
|
.dynamicColumnsTable-items {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: auto;
|
width: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
/* eslint-disable react/jsx-props-no-spreading */
|
/* eslint-disable react/jsx-props-no-spreading */
|
||||||
import './DynamicColumnTable.syles.scss';
|
import './DynamicColumnTable.syles.scss';
|
||||||
|
|
||||||
import { SettingOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Dropdown, MenuProps, Switch } from 'antd';
|
import { Button, Dropdown, MenuProps, Switch } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
|
import { SlidersHorizontal } from 'lucide-react';
|
||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
import { popupContainer } from 'utils/selectPopupContainer';
|
import { popupContainer } from 'utils/selectPopupContainer';
|
||||||
|
|
||||||
@ -90,9 +90,9 @@ function DynamicColumnTable({
|
|||||||
trigger={['click']}
|
trigger={['click']}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="dynamicColumnTable-button"
|
className="dynamicColumnTable-button filter-btn"
|
||||||
size="middle"
|
size="middle"
|
||||||
icon={<SettingOutlined />}
|
icon={<SlidersHorizontal size={14} />}
|
||||||
/>
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
|
@ -15,4 +15,5 @@ export enum LOCALSTORAGE {
|
|||||||
LOGGED_IN_USER_EMAIL = 'LOGGED_IN_USER_EMAIL',
|
LOGGED_IN_USER_EMAIL = 'LOGGED_IN_USER_EMAIL',
|
||||||
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
||||||
IS_IDENTIFIED_USER = 'IS_IDENTIFIED_USER',
|
IS_IDENTIFIED_USER = 'IS_IDENTIFIED_USER',
|
||||||
|
DASHBOARD_VARIABLES = 'DASHBOARD_VARIABLES',
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
import { Row } from 'antd';
|
import { Row } from 'antd';
|
||||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
import { useDashboardVariablesFromLocalStorage } from 'hooks/dashboard/useDashboardFromLocalStorage';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
|
||||||
import AppReducer from 'types/reducer/app';
|
|
||||||
|
|
||||||
import { convertVariablesToDbFormat } from './util';
|
import { convertVariablesToDbFormat } from './util';
|
||||||
import VariableItem from './VariableItem';
|
import VariableItem from './VariableItem';
|
||||||
|
|
||||||
function DashboardVariableSelection(): JSX.Element | null {
|
function DashboardVariableSelection(): JSX.Element | null {
|
||||||
const { selectedDashboard, setSelectedDashboard } = useDashboard();
|
const {
|
||||||
|
selectedDashboard,
|
||||||
|
setSelectedDashboard,
|
||||||
|
dashboardId,
|
||||||
|
} = useDashboard();
|
||||||
|
|
||||||
|
const {
|
||||||
|
updateLocalStorageDashboardVariables,
|
||||||
|
} = useDashboardVariablesFromLocalStorage(dashboardId);
|
||||||
|
|
||||||
const { data } = selectedDashboard || {};
|
const { data } = selectedDashboard || {};
|
||||||
|
|
||||||
@ -23,8 +27,6 @@ function DashboardVariableSelection(): JSX.Element | null {
|
|||||||
|
|
||||||
const [variablesTableData, setVariablesTableData] = useState<any>([]);
|
const [variablesTableData, setVariablesTableData] = useState<any>([]);
|
||||||
|
|
||||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (variables) {
|
if (variables) {
|
||||||
const tableRowData = [];
|
const tableRowData = [];
|
||||||
@ -52,40 +54,6 @@ function DashboardVariableSelection(): JSX.Element | null {
|
|||||||
setUpdate(!update);
|
setUpdate(!update);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateMutation = useUpdateDashboard();
|
|
||||||
const { notifications } = useNotifications();
|
|
||||||
|
|
||||||
const updateVariables = (
|
|
||||||
name: string,
|
|
||||||
updatedVariablesData: Dashboard['data']['variables'],
|
|
||||||
): void => {
|
|
||||||
if (!selectedDashboard) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMutation.mutateAsync(
|
|
||||||
{
|
|
||||||
...selectedDashboard,
|
|
||||||
data: {
|
|
||||||
...selectedDashboard.data,
|
|
||||||
variables: updatedVariablesData,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onSuccess: (updatedDashboard) => {
|
|
||||||
if (updatedDashboard.payload) {
|
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
notifications.error({
|
|
||||||
message: `Error updating ${name} variable`,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onValueUpdate = (
|
const onValueUpdate = (
|
||||||
name: string,
|
name: string,
|
||||||
id: string,
|
id: string,
|
||||||
@ -105,12 +73,22 @@ function DashboardVariableSelection(): JSX.Element | null {
|
|||||||
return variableCopy;
|
return variableCopy;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
updateLocalStorageDashboardVariables(id, value, allSelected);
|
||||||
|
|
||||||
const variables = convertVariablesToDbFormat(newVariablesArr);
|
const variables = convertVariablesToDbFormat(newVariablesArr);
|
||||||
|
|
||||||
if (role !== 'VIEWER' && selectedDashboard) {
|
if (selectedDashboard) {
|
||||||
updateVariables(name, variables);
|
setSelectedDashboard({
|
||||||
|
...selectedDashboard,
|
||||||
|
data: {
|
||||||
|
...selectedDashboard?.data,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onVarChanged(name);
|
onVarChanged(name);
|
||||||
|
|
||||||
setUpdate(!update);
|
setUpdate(!update);
|
||||||
|
100
frontend/src/hooks/dashboard/useDashboardFromLocalStorage.tsx
Normal file
100
frontend/src/hooks/dashboard/useDashboardFromLocalStorage.tsx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||||
|
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
|
import { defaultTo } from 'lodash-es';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
|
interface LocalStoreDashboardVariables {
|
||||||
|
[id: string]: {
|
||||||
|
selectedValue: IDashboardVariable['selectedValue'];
|
||||||
|
allSelected: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
interface DashboardLocalStorageVariables {
|
||||||
|
[id: string]: LocalStoreDashboardVariables;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseDashboardVariablesFromLocalStorageReturn {
|
||||||
|
currentDashboard: LocalStoreDashboardVariables;
|
||||||
|
updateLocalStorageDashboardVariables: (
|
||||||
|
id: string,
|
||||||
|
selectedValue: IDashboardVariable['selectedValue'],
|
||||||
|
allSelected: boolean,
|
||||||
|
) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDashboardVariablesFromLocalStorage = (
|
||||||
|
dashboardId: string,
|
||||||
|
): UseDashboardVariablesFromLocalStorageReturn => {
|
||||||
|
const [
|
||||||
|
allDashboards,
|
||||||
|
setAllDashboards,
|
||||||
|
] = useState<DashboardLocalStorageVariables>({});
|
||||||
|
|
||||||
|
const [
|
||||||
|
currentDashboard,
|
||||||
|
setCurrentDashboard,
|
||||||
|
] = useState<LocalStoreDashboardVariables>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const localStoreDashboardVariablesString = getLocalStorageKey(
|
||||||
|
LOCALSTORAGE.DASHBOARD_VARIABLES,
|
||||||
|
);
|
||||||
|
let localStoreDashboardVariables: DashboardLocalStorageVariables = {};
|
||||||
|
if (localStoreDashboardVariablesString === null) {
|
||||||
|
try {
|
||||||
|
const serialzedData = JSON.stringify({
|
||||||
|
[dashboardId]: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
setLocalStorageKey(LOCALSTORAGE.DASHBOARD_VARIABLES, serialzedData);
|
||||||
|
} catch {
|
||||||
|
console.error('Failed to seralise the data');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
localStoreDashboardVariables = JSON.parse(
|
||||||
|
localStoreDashboardVariablesString,
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
console.error('Failed to parse dashboards from local storage');
|
||||||
|
localStoreDashboardVariables = {};
|
||||||
|
} finally {
|
||||||
|
setAllDashboards(localStoreDashboardVariables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCurrentDashboard(defaultTo(localStoreDashboardVariables[dashboardId], {}));
|
||||||
|
}, [dashboardId]);
|
||||||
|
|
||||||
|
const updateLocalStorageDashboardVariables = (
|
||||||
|
id: string,
|
||||||
|
selectedValue: IDashboardVariable['selectedValue'],
|
||||||
|
allSelected: boolean,
|
||||||
|
): void => {
|
||||||
|
const newCurrentDashboard = {
|
||||||
|
...currentDashboard,
|
||||||
|
[id]: { selectedValue, allSelected },
|
||||||
|
};
|
||||||
|
|
||||||
|
const newAllDashboards = {
|
||||||
|
...allDashboards,
|
||||||
|
[dashboardId]: newCurrentDashboard,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const serializedData = JSON.stringify(newAllDashboards);
|
||||||
|
setLocalStorageKey(LOCALSTORAGE.DASHBOARD_VARIABLES, serializedData);
|
||||||
|
} catch {
|
||||||
|
console.error('Failed to set dashboards in local storage');
|
||||||
|
}
|
||||||
|
|
||||||
|
setAllDashboards(newAllDashboards);
|
||||||
|
setCurrentDashboard(newCurrentDashboard);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentDashboard,
|
||||||
|
updateLocalStorageDashboardVariables,
|
||||||
|
};
|
||||||
|
};
|
@ -6,6 +6,7 @@ import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
|||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import { useDashboardVariablesFromLocalStorage } from 'hooks/dashboard/useDashboardFromLocalStorage';
|
||||||
import useAxiosError from 'hooks/useAxiosError';
|
import useAxiosError from 'hooks/useAxiosError';
|
||||||
import useTabVisibility from 'hooks/useTabFocus';
|
import useTabVisibility from 'hooks/useTabFocus';
|
||||||
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
|
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
|
||||||
@ -95,6 +96,10 @@ export function DashboardProvider({
|
|||||||
|
|
||||||
const [selectedDashboard, setSelectedDashboard] = useState<Dashboard>();
|
const [selectedDashboard, setSelectedDashboard] = useState<Dashboard>();
|
||||||
|
|
||||||
|
const { currentDashboard } = useDashboardVariablesFromLocalStorage(
|
||||||
|
dashboardId,
|
||||||
|
);
|
||||||
|
|
||||||
const updatedTimeRef = useRef<Dayjs | null>(null); // Using ref to store the updated time
|
const updatedTimeRef = useRef<Dayjs | null>(null); // Using ref to store the updated time
|
||||||
const modalRef = useRef<any>(null);
|
const modalRef = useRef<any>(null);
|
||||||
|
|
||||||
@ -103,11 +108,33 @@ export function DashboardProvider({
|
|||||||
const { t } = useTranslation(['dashboard']);
|
const { t } = useTranslation(['dashboard']);
|
||||||
const dashboardRef = useRef<Dashboard>();
|
const dashboardRef = useRef<Dashboard>();
|
||||||
|
|
||||||
|
const mergeDBWithLocalStorage = (
|
||||||
|
data: Dashboard,
|
||||||
|
localStorageVariables: any,
|
||||||
|
): Dashboard => {
|
||||||
|
const updatedData = data;
|
||||||
|
if (data && localStorageVariables) {
|
||||||
|
const updatedVariables = data.data.variables;
|
||||||
|
Object.keys(data.data.variables).forEach((variable) => {
|
||||||
|
const updatedVariable = {
|
||||||
|
...data.data.variables[variable],
|
||||||
|
...localStorageVariables[variable as any],
|
||||||
|
};
|
||||||
|
|
||||||
|
updatedVariables[variable] = updatedVariable;
|
||||||
|
});
|
||||||
|
updatedData.data.variables = updatedVariables;
|
||||||
|
}
|
||||||
|
return updatedData;
|
||||||
|
};
|
||||||
// As we do not have order and ID's in the variables object, we have to process variables to add order and ID if they do not exist in the variables object
|
// As we do not have order and ID's in the variables object, we have to process variables to add order and ID if they do not exist in the variables object
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const transformDashboardVariables = (data: Dashboard): Dashboard => {
|
const transformDashboardVariables = (data: Dashboard): Dashboard => {
|
||||||
if (data && data.data && data.data.variables) {
|
if (data && data.data && data.data.variables) {
|
||||||
const clonedDashboardData = JSON.parse(JSON.stringify(data));
|
const clonedDashboardData = mergeDBWithLocalStorage(
|
||||||
|
JSON.parse(JSON.stringify(data)),
|
||||||
|
currentDashboard,
|
||||||
|
);
|
||||||
const { variables } = clonedDashboardData.data;
|
const { variables } = clonedDashboardData.data;
|
||||||
const existingOrders: Set<number> = new Set();
|
const existingOrders: Set<number> = new Set();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user