mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-08 14:19:01 +08:00
Rearrange variables (#4187)
* feat: variable re-arrange * feat: update variable update from dashboard description * feat: update variable update from dashboard description * feat: update custom variable dropdown values on change * feat: handle dependent value updates to dashboard description * feat: handle dependent 0th order variable update * feat: update variable item test * feat: transform variables data to support rearraging * feat: update modal import * feat: remove console logs * feat: ts-ignore * feat: show variable name in delete modal
This commit is contained in:
parent
418ab67d50
commit
1d014ab4f7
@ -29,6 +29,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/colors": "6.0.0",
|
"@ant-design/colors": "6.0.0",
|
||||||
"@ant-design/icons": "4.8.0",
|
"@ant-design/icons": "4.8.0",
|
||||||
|
"@dnd-kit/core": "6.1.0",
|
||||||
|
"@dnd-kit/modifiers": "7.0.0",
|
||||||
|
"@dnd-kit/sortable": "8.0.0",
|
||||||
"@grafana/data": "^9.5.2",
|
"@grafana/data": "^9.5.2",
|
||||||
"@mdx-js/loader": "2.3.0",
|
"@mdx-js/loader": "2.3.0",
|
||||||
"@mdx-js/react": "2.3.0",
|
"@mdx-js/react": "2.3.0",
|
||||||
|
@ -29,7 +29,7 @@ function SettingsDrawer({ drawerTitle }: { drawerTitle: string }): JSX.Element {
|
|||||||
<DrawerContainer
|
<DrawerContainer
|
||||||
title={drawerTitle}
|
title={drawerTitle}
|
||||||
placement="right"
|
placement="right"
|
||||||
width="50%"
|
width="60%"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
open={visible}
|
open={visible}
|
||||||
>
|
>
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
.delete-variable-name {
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgb(207, 19, 34);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
@ -18,10 +18,10 @@ import {
|
|||||||
VariableQueryTypeArr,
|
VariableQueryTypeArr,
|
||||||
VariableSortTypeArr,
|
VariableSortTypeArr,
|
||||||
} from 'types/api/dashboard/getAll';
|
} from 'types/api/dashboard/getAll';
|
||||||
import { v4 } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
|
||||||
import { variablePropsToPayloadVariables } from '../../../utils';
|
import { variablePropsToPayloadVariables } from '../../../utils';
|
||||||
import { TVariableViewMode } from '../types';
|
import { TVariableMode } from '../types';
|
||||||
import { LabelContainer, VariableItemRow } from './styles';
|
import { LabelContainer, VariableItemRow } from './styles';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
@ -30,9 +30,9 @@ interface VariableItemProps {
|
|||||||
variableData: IDashboardVariable;
|
variableData: IDashboardVariable;
|
||||||
existingVariables: Record<string, IDashboardVariable>;
|
existingVariables: Record<string, IDashboardVariable>;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onSave: (name: string, arg0: IDashboardVariable, arg1: string) => void;
|
onSave: (mode: TVariableMode, variableData: IDashboardVariable) => void;
|
||||||
validateName: (arg0: string) => boolean;
|
validateName: (arg0: string) => boolean;
|
||||||
variableViewMode: TVariableViewMode;
|
mode: TVariableMode;
|
||||||
}
|
}
|
||||||
function VariableItem({
|
function VariableItem({
|
||||||
variableData,
|
variableData,
|
||||||
@ -40,7 +40,7 @@ function VariableItem({
|
|||||||
onCancel,
|
onCancel,
|
||||||
onSave,
|
onSave,
|
||||||
validateName,
|
validateName,
|
||||||
variableViewMode,
|
mode,
|
||||||
}: VariableItemProps): JSX.Element {
|
}: VariableItemProps): JSX.Element {
|
||||||
const [variableName, setVariableName] = useState<string>(
|
const [variableName, setVariableName] = useState<string>(
|
||||||
variableData.name || '',
|
variableData.name || '',
|
||||||
@ -97,7 +97,7 @@ function VariableItem({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const handleSave = (): void => {
|
const handleSave = (): void => {
|
||||||
const newVariableData: IDashboardVariable = {
|
const variable: IDashboardVariable = {
|
||||||
name: variableName,
|
name: variableName,
|
||||||
description: variableDescription,
|
description: variableDescription,
|
||||||
type: queryType,
|
type: queryType,
|
||||||
@ -111,16 +111,12 @@ function VariableItem({
|
|||||||
selectedValue: (variableData.selectedValue ||
|
selectedValue: (variableData.selectedValue ||
|
||||||
variableTextboxValue) as never,
|
variableTextboxValue) as never,
|
||||||
}),
|
}),
|
||||||
modificationUUID: v4(),
|
modificationUUID: generateUUID(),
|
||||||
|
id: variableData.id || generateUUID(),
|
||||||
|
order: variableData.order,
|
||||||
};
|
};
|
||||||
onSave(
|
|
||||||
variableName,
|
onSave(mode, variable);
|
||||||
newVariableData,
|
|
||||||
(variableViewMode === 'EDIT' && variableName !== variableData.name
|
|
||||||
? variableData.name
|
|
||||||
: '') as string,
|
|
||||||
);
|
|
||||||
onCancel();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetches the preview values for the SQL variable query
|
// Fetches the preview values for the SQL variable query
|
||||||
@ -175,7 +171,6 @@ function VariableItem({
|
|||||||
return (
|
return (
|
||||||
<div className="variable-item-container">
|
<div className="variable-item-container">
|
||||||
<div className="variable-item-content">
|
<div className="variable-item-content">
|
||||||
{/* <Typography.Title level={3}>Add Variable</Typography.Title> */}
|
|
||||||
<VariableItemRow>
|
<VariableItemRow>
|
||||||
<LabelContainer>
|
<LabelContainer>
|
||||||
<Typography>Name</Typography>
|
<Typography>Name</Typography>
|
||||||
|
@ -1,20 +1,78 @@
|
|||||||
|
import '../DashboardSettings.styles.scss';
|
||||||
|
|
||||||
import { blue, red } from '@ant-design/colors';
|
import { blue, red } from '@ant-design/colors';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { MenuOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import { Button, Modal, Row, Space, Tag } from 'antd';
|
import type { DragEndEvent, UniqueIdentifier } from '@dnd-kit/core';
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import {
|
||||||
|
DndContext,
|
||||||
|
PointerSensor,
|
||||||
|
useSensor,
|
||||||
|
useSensors,
|
||||||
|
} from '@dnd-kit/core';
|
||||||
|
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
|
||||||
|
import { arrayMove, SortableContext, useSortable } from '@dnd-kit/sortable';
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
|
import { Button, Modal, Row, Space, Table, Typography } from 'antd';
|
||||||
|
import { RowProps } from 'antd/lib';
|
||||||
|
import { convertVariablesToDbFormat } from 'container/NewDashboard/DashboardVariablesSelection/util';
|
||||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { PencilIcon, TrashIcon } from 'lucide-react';
|
import { PencilIcon, TrashIcon } from 'lucide-react';
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
import { useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
import { TVariableViewMode } from './types';
|
import { TVariableMode } from './types';
|
||||||
import VariableItem from './VariableItem/VariableItem';
|
import VariableItem from './VariableItem/VariableItem';
|
||||||
|
|
||||||
|
function TableRow({ children, ...props }: RowProps): JSX.Element {
|
||||||
|
const {
|
||||||
|
attributes,
|
||||||
|
listeners,
|
||||||
|
setNodeRef,
|
||||||
|
setActivatorNodeRef,
|
||||||
|
transform,
|
||||||
|
transition,
|
||||||
|
isDragging,
|
||||||
|
} = useSortable({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
id: props['data-row-key'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const style: React.CSSProperties = {
|
||||||
|
...props.style,
|
||||||
|
transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }),
|
||||||
|
transition,
|
||||||
|
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
<tr {...props} ref={setNodeRef} style={style} {...attributes}>
|
||||||
|
{React.Children.map(children, (child) => {
|
||||||
|
if ((child as React.ReactElement).key === 'sort') {
|
||||||
|
return React.cloneElement(child as React.ReactElement, {
|
||||||
|
children: (
|
||||||
|
<MenuOutlined
|
||||||
|
ref={setActivatorNodeRef}
|
||||||
|
style={{ touchAction: 'none', cursor: 'move' }}
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...listeners}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function VariablesSetting(): JSX.Element {
|
function VariablesSetting(): JSX.Element {
|
||||||
const variableToDelete = useRef<string | null>(null);
|
const variableToDelete = useRef<IDashboardVariable | null>(null);
|
||||||
const [deleteVariableModal, setDeleteVariableModal] = useState(false);
|
const [deleteVariableModal, setDeleteVariableModal] = useState(false);
|
||||||
|
|
||||||
const { t } = useTranslation(['dashboard']);
|
const { t } = useTranslation(['dashboard']);
|
||||||
@ -25,16 +83,15 @@ function VariablesSetting(): JSX.Element {
|
|||||||
|
|
||||||
const { variables = {} } = selectedDashboard?.data || {};
|
const { variables = {} } = selectedDashboard?.data || {};
|
||||||
|
|
||||||
const variablesTableData = Object.keys(variables).map((variableName) => ({
|
const [variablesTableData, setVariablesTableData] = useState<any>([]);
|
||||||
key: variableName,
|
const [variblesOrderArr, setVariablesOrderArr] = useState<number[]>([]);
|
||||||
name: variableName,
|
const [existingVariableNamesMap, setExistingVariableNamesMap] = useState<
|
||||||
...variables[variableName],
|
Record<string, string>
|
||||||
}));
|
>({});
|
||||||
|
|
||||||
const [
|
const [variableViewMode, setVariableViewMode] = useState<null | TVariableMode>(
|
||||||
variableViewMode,
|
null,
|
||||||
setVariableViewMode,
|
);
|
||||||
] = useState<null | TVariableViewMode>(null);
|
|
||||||
|
|
||||||
const [
|
const [
|
||||||
variableEditData,
|
variableEditData,
|
||||||
@ -47,7 +104,7 @@ function VariablesSetting(): JSX.Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onVariableViewModeEnter = (
|
const onVariableViewModeEnter = (
|
||||||
viewType: TVariableViewMode,
|
viewType: TVariableMode,
|
||||||
varData: IDashboardVariable,
|
varData: IDashboardVariable,
|
||||||
): void => {
|
): void => {
|
||||||
setVariableEditData(varData);
|
setVariableEditData(varData);
|
||||||
@ -56,6 +113,41 @@ function VariablesSetting(): JSX.Element {
|
|||||||
|
|
||||||
const updateMutation = useUpdateDashboard();
|
const updateMutation = useUpdateDashboard();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const tableRowData = [];
|
||||||
|
const variableOrderArr = [];
|
||||||
|
const variableNamesMap = {};
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const [key, value] of Object.entries(variables)) {
|
||||||
|
const { order, id, name } = value;
|
||||||
|
|
||||||
|
tableRowData.push({
|
||||||
|
key,
|
||||||
|
name: key,
|
||||||
|
...variables[key],
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
variableNamesMap[name] = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order) {
|
||||||
|
variableOrderArr.push(order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tableRowData.sort((a, b) => a.order - b.order);
|
||||||
|
variableOrderArr.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
setVariablesTableData(tableRowData);
|
||||||
|
setVariablesOrderArr(variableOrderArr);
|
||||||
|
setExistingVariableNamesMap(variableNamesMap);
|
||||||
|
}, [variables]);
|
||||||
|
|
||||||
const updateVariables = (
|
const updateVariables = (
|
||||||
updatedVariablesData: Dashboard['data']['variables'],
|
updatedVariablesData: Dashboard['data']['variables'],
|
||||||
): void => {
|
): void => {
|
||||||
@ -89,34 +181,58 @@ function VariablesSetting(): JSX.Element {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getVariableOrder = (): number => {
|
||||||
|
if (variblesOrderArr && variblesOrderArr.length > 0) {
|
||||||
|
return variblesOrderArr[variblesOrderArr.length - 1] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
const onVariableSaveHandler = (
|
const onVariableSaveHandler = (
|
||||||
name: string,
|
mode: TVariableMode,
|
||||||
variableData: IDashboardVariable,
|
variableData: IDashboardVariable,
|
||||||
oldName: string,
|
|
||||||
): void => {
|
): void => {
|
||||||
if (!variableData.name) {
|
const updatedVariableData = {
|
||||||
return;
|
...variableData,
|
||||||
|
order: variableData?.order >= 0 ? variableData.order : getVariableOrder(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const newVariablesArr = variablesTableData.map(
|
||||||
|
(variable: IDashboardVariable) => {
|
||||||
|
if (variable.id === updatedVariableData.id) {
|
||||||
|
return updatedVariableData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return variable;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mode === 'ADD') {
|
||||||
|
newVariablesArr.push(updatedVariableData);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newVariables = { ...variables };
|
const variables = convertVariablesToDbFormat(newVariablesArr);
|
||||||
newVariables[name] = variableData;
|
|
||||||
|
|
||||||
if (oldName) {
|
setVariablesTableData(newVariablesArr);
|
||||||
delete newVariables[oldName];
|
updateVariables(variables);
|
||||||
}
|
|
||||||
updateVariables(newVariables);
|
|
||||||
onDoneVariableViewMode();
|
onDoneVariableViewMode();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onVariableDeleteHandler = (variableName: string): void => {
|
const onVariableDeleteHandler = (variable: IDashboardVariable): void => {
|
||||||
variableToDelete.current = variableName;
|
variableToDelete.current = variable;
|
||||||
setDeleteVariableModal(true);
|
setDeleteVariableModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteConfirm = (): void => {
|
const handleDeleteConfirm = (): void => {
|
||||||
const newVariables = { ...variables };
|
const newVariablesArr = variablesTableData.filter(
|
||||||
if (variableToDelete?.current) delete newVariables[variableToDelete?.current];
|
(variable: IDashboardVariable) =>
|
||||||
updateVariables(newVariables);
|
variable.id !== variableToDelete?.current?.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedVariables = convertVariablesToDbFormat(newVariablesArr);
|
||||||
|
|
||||||
|
updateVariables(updatedVariables);
|
||||||
variableToDelete.current = null;
|
variableToDelete.current = null;
|
||||||
setDeleteVariableModal(false);
|
setDeleteVariableModal(false);
|
||||||
};
|
};
|
||||||
@ -125,31 +241,36 @@ function VariablesSetting(): JSX.Element {
|
|||||||
setDeleteVariableModal(false);
|
setDeleteVariableModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateVariableName = (name: string): boolean => !variables[name];
|
const validateVariableName = (name: string): boolean =>
|
||||||
|
!existingVariableNamesMap[name];
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
|
{
|
||||||
|
key: 'sort',
|
||||||
|
width: '10%',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Variable',
|
title: 'Variable',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
width: 100,
|
width: '40%',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Description',
|
title: 'Description',
|
||||||
dataIndex: 'description',
|
dataIndex: 'description',
|
||||||
width: 100,
|
width: '35%',
|
||||||
key: 'description',
|
key: 'description',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
width: 50,
|
width: '15%',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
render: (_: IDashboardVariable): JSX.Element => (
|
render: (variable: IDashboardVariable): JSX.Element => (
|
||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
style={{ padding: 8, cursor: 'pointer', color: blue[5] }}
|
style={{ padding: 8, cursor: 'pointer', color: blue[5] }}
|
||||||
onClick={(): void => onVariableViewModeEnter('EDIT', _)}
|
onClick={(): void => onVariableViewModeEnter('EDIT', variable)}
|
||||||
>
|
>
|
||||||
<PencilIcon size={14} />
|
<PencilIcon size={14} />
|
||||||
</Button>
|
</Button>
|
||||||
@ -157,7 +278,9 @@ function VariablesSetting(): JSX.Element {
|
|||||||
type="text"
|
type="text"
|
||||||
style={{ padding: 8, color: red[6], cursor: 'pointer' }}
|
style={{ padding: 8, color: red[6], cursor: 'pointer' }}
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
if (_.name) onVariableDeleteHandler(_.name);
|
if (variable) {
|
||||||
|
onVariableDeleteHandler(variable);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TrashIcon size={14} />
|
<TrashIcon size={14} />
|
||||||
@ -167,6 +290,51 @@ function VariablesSetting(): JSX.Element {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const sensors = useSensors(
|
||||||
|
useSensor(PointerSensor, {
|
||||||
|
activationConstraint: {
|
||||||
|
// https://docs.dndkit.com/api-documentation/sensors/pointer#activation-constraints
|
||||||
|
distance: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDragEnd = ({ active, over }: DragEndEvent): void => {
|
||||||
|
if (active.id !== over?.id) {
|
||||||
|
const activeIndex = variablesTableData.findIndex(
|
||||||
|
(i: { key: UniqueIdentifier }) => i.key === active.id,
|
||||||
|
);
|
||||||
|
const overIndex = variablesTableData.findIndex(
|
||||||
|
(i: { key: UniqueIdentifier | undefined }) => i.key === over?.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedVariables: IDashboardVariable[] = arrayMove(
|
||||||
|
variablesTableData,
|
||||||
|
activeIndex,
|
||||||
|
overIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
const reArrangedVariables = {};
|
||||||
|
|
||||||
|
for (let index = 0; index < updatedVariables.length; index += 1) {
|
||||||
|
const variableName = updatedVariables[index].name;
|
||||||
|
|
||||||
|
if (variableName) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
reArrangedVariables[variableName] = {
|
||||||
|
...updatedVariables[index],
|
||||||
|
order: index,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateVariables(reArrangedVariables);
|
||||||
|
|
||||||
|
setVariablesTableData(updatedVariables);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{variableViewMode ? (
|
{variableViewMode ? (
|
||||||
@ -176,11 +344,17 @@ function VariablesSetting(): JSX.Element {
|
|||||||
onSave={onVariableSaveHandler}
|
onSave={onVariableSaveHandler}
|
||||||
onCancel={onDoneVariableViewMode}
|
onCancel={onDoneVariableViewMode}
|
||||||
validateName={validateVariableName}
|
validateName={validateVariableName}
|
||||||
variableViewMode={variableViewMode}
|
mode={variableViewMode}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Row style={{ flexDirection: 'row-reverse', padding: '0.5rem 0' }}>
|
<Row
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
padding: '0.5rem 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
data-testid="add-new-variable"
|
data-testid="add-new-variable"
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -192,7 +366,28 @@ function VariablesSetting(): JSX.Element {
|
|||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<ResizeTable columns={columns} dataSource={variablesTableData} />
|
<DndContext
|
||||||
|
sensors={sensors}
|
||||||
|
modifiers={[restrictToVerticalAxis]}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
>
|
||||||
|
<SortableContext
|
||||||
|
// rowKey array
|
||||||
|
items={variablesTableData.map((variable: { key: any }) => variable.key)}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
components={{
|
||||||
|
body: {
|
||||||
|
row: TableRow,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
rowKey="key"
|
||||||
|
columns={columns}
|
||||||
|
pagination={false}
|
||||||
|
dataSource={variablesTableData}
|
||||||
|
/>
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Modal
|
<Modal
|
||||||
@ -202,8 +397,13 @@ function VariablesSetting(): JSX.Element {
|
|||||||
onOk={handleDeleteConfirm}
|
onOk={handleDeleteConfirm}
|
||||||
onCancel={handleDeleteCancel}
|
onCancel={handleDeleteCancel}
|
||||||
>
|
>
|
||||||
Are you sure you want to delete variable{' '}
|
<Typography.Text>
|
||||||
<Tag>{variableToDelete.current}</Tag>?
|
Are you sure you want to delete variable{' '}
|
||||||
|
<span className="delete-variable-name">
|
||||||
|
{variableToDelete?.current?.name}
|
||||||
|
</span>
|
||||||
|
?
|
||||||
|
</Typography.Text>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1 +1,7 @@
|
|||||||
export type TVariableViewMode = 'EDIT' | 'ADD';
|
export type TVariableMode = 'VIEW' | 'EDIT' | 'ADD';
|
||||||
|
|
||||||
|
export const VariableModes = {
|
||||||
|
VIEW: 'VIEW',
|
||||||
|
EDIT: 'EDIT',
|
||||||
|
ADD: 'ADD',
|
||||||
|
};
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { Row } from 'antd';
|
import { Row } from 'antd';
|
||||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { map, sortBy } from 'lodash-es';
|
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
import { memo, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
|
|
||||||
|
import { convertVariablesToDbFormat } from './util';
|
||||||
import VariableItem from './VariableItem';
|
import VariableItem from './VariableItem';
|
||||||
|
|
||||||
function DashboardVariableSelection(): JSX.Element | null {
|
function DashboardVariableSelection(): JSX.Element | null {
|
||||||
@ -21,8 +21,32 @@ function DashboardVariableSelection(): JSX.Element | null {
|
|||||||
const [update, setUpdate] = useState<boolean>(false);
|
const [update, setUpdate] = useState<boolean>(false);
|
||||||
const [lastUpdatedVar, setLastUpdatedVar] = useState<string>('');
|
const [lastUpdatedVar, setLastUpdatedVar] = useState<string>('');
|
||||||
|
|
||||||
|
const [variablesTableData, setVariablesTableData] = useState<any>([]);
|
||||||
|
|
||||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (variables) {
|
||||||
|
const tableRowData = [];
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const [key, value] of Object.entries(variables)) {
|
||||||
|
const { id } = value;
|
||||||
|
|
||||||
|
tableRowData.push({
|
||||||
|
key,
|
||||||
|
name: key,
|
||||||
|
...variables[key],
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tableRowData.sort((a, b) => a.order - b.order);
|
||||||
|
|
||||||
|
setVariablesTableData(tableRowData);
|
||||||
|
}
|
||||||
|
}, [variables]);
|
||||||
|
|
||||||
const onVarChanged = (name: string): void => {
|
const onVarChanged = (name: string): void => {
|
||||||
setLastUpdatedVar(name);
|
setLastUpdatedVar(name);
|
||||||
setUpdate(!update);
|
setUpdate(!update);
|
||||||
@ -64,40 +88,56 @@ function DashboardVariableSelection(): JSX.Element | null {
|
|||||||
|
|
||||||
const onValueUpdate = (
|
const onValueUpdate = (
|
||||||
name: string,
|
name: string,
|
||||||
|
id: string,
|
||||||
value: IDashboardVariable['selectedValue'],
|
value: IDashboardVariable['selectedValue'],
|
||||||
allSelected: boolean,
|
allSelected: boolean,
|
||||||
): void => {
|
): void => {
|
||||||
const updatedVariablesData = { ...variables };
|
if (id) {
|
||||||
updatedVariablesData[name].selectedValue = value;
|
const newVariablesArr = variablesTableData.map(
|
||||||
updatedVariablesData[name].allSelected = allSelected;
|
(variable: IDashboardVariable) => {
|
||||||
|
const variableCopy = { ...variable };
|
||||||
|
|
||||||
console.log('onValue Update', name);
|
if (variableCopy.id === id) {
|
||||||
|
variableCopy.selectedValue = value;
|
||||||
|
variableCopy.allSelected = allSelected;
|
||||||
|
}
|
||||||
|
|
||||||
if (role !== 'VIEWER' && selectedDashboard) {
|
return variableCopy;
|
||||||
updateVariables(name, updatedVariablesData);
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const variables = convertVariablesToDbFormat(newVariablesArr);
|
||||||
|
|
||||||
|
if (role !== 'VIEWER' && selectedDashboard) {
|
||||||
|
updateVariables(name, variables);
|
||||||
|
}
|
||||||
|
onVarChanged(name);
|
||||||
|
|
||||||
|
setUpdate(!update);
|
||||||
}
|
}
|
||||||
onVarChanged(name);
|
|
||||||
|
|
||||||
setUpdate(!update);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!variables) {
|
if (!variables) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const variablesKeys = sortBy(Object.keys(variables));
|
const orderBasedSortedVariables = variablesTableData.sort(
|
||||||
|
(a: { order: number }, b: { order: number }) => a.order - b.order,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
{variablesKeys &&
|
{orderBasedSortedVariables &&
|
||||||
map(variablesKeys, (variableName) => (
|
Array.isArray(orderBasedSortedVariables) &&
|
||||||
|
orderBasedSortedVariables.length > 0 &&
|
||||||
|
orderBasedSortedVariables.map((variable) => (
|
||||||
<VariableItem
|
<VariableItem
|
||||||
key={`${variableName}${variables[variableName].modificationUUID}`}
|
key={`${variable.name}${variable.id}}${variable.order}`}
|
||||||
existingVariables={variables}
|
existingVariables={variables}
|
||||||
lastUpdatedVar={lastUpdatedVar}
|
lastUpdatedVar={lastUpdatedVar}
|
||||||
variableData={{
|
variableData={{
|
||||||
name: variableName,
|
name: variable.name,
|
||||||
...variables[variableName],
|
...variable,
|
||||||
change: update,
|
change: update,
|
||||||
}}
|
}}
|
||||||
onValueUpdate={onValueUpdate}
|
onValueUpdate={onValueUpdate}
|
||||||
|
@ -14,6 +14,7 @@ import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
|||||||
import VariableItem from './VariableItem';
|
import VariableItem from './VariableItem';
|
||||||
|
|
||||||
const mockVariableData: IDashboardVariable = {
|
const mockVariableData: IDashboardVariable = {
|
||||||
|
id: 'test_variable',
|
||||||
description: 'Test Variable',
|
description: 'Test Variable',
|
||||||
type: 'TEXTBOX',
|
type: 'TEXTBOX',
|
||||||
textboxValue: 'defaultValue',
|
textboxValue: 'defaultValue',
|
||||||
@ -95,6 +96,7 @@ describe('VariableItem', () => {
|
|||||||
// expect(mockOnValueUpdate).toHaveBeenCalledTimes(1);
|
// expect(mockOnValueUpdate).toHaveBeenCalledTimes(1);
|
||||||
expect(mockOnValueUpdate).toHaveBeenCalledWith(
|
expect(mockOnValueUpdate).toHaveBeenCalledWith(
|
||||||
'testVariable',
|
'testVariable',
|
||||||
|
'test_variable',
|
||||||
'newValue',
|
'newValue',
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
@ -2,13 +2,14 @@ import './DashboardVariableSelection.styles.scss';
|
|||||||
|
|
||||||
import { orange } from '@ant-design/colors';
|
import { orange } from '@ant-design/colors';
|
||||||
import { WarningOutlined } from '@ant-design/icons';
|
import { WarningOutlined } from '@ant-design/icons';
|
||||||
import { Input, Popover, Select, Typography } from 'antd';
|
import { Input, Popover, Select, Tooltip, Typography } from 'antd';
|
||||||
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
|
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import useDebounce from 'hooks/useDebounce';
|
import useDebounce from 'hooks/useDebounce';
|
||||||
import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser';
|
import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser';
|
||||||
import sortValues from 'lib/dashbaordVariables/sortVariableValues';
|
import sortValues from 'lib/dashbaordVariables/sortVariableValues';
|
||||||
import map from 'lodash-es/map';
|
import map from 'lodash-es/map';
|
||||||
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
import { memo, useEffect, useMemo, useState } from 'react';
|
import { memo, useEffect, useMemo, useState } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||||
@ -27,6 +28,7 @@ interface VariableItemProps {
|
|||||||
existingVariables: Record<string, IDashboardVariable>;
|
existingVariables: Record<string, IDashboardVariable>;
|
||||||
onValueUpdate: (
|
onValueUpdate: (
|
||||||
name: string,
|
name: string,
|
||||||
|
id: string,
|
||||||
arg1: IDashboardVariable['selectedValue'],
|
arg1: IDashboardVariable['selectedValue'],
|
||||||
allSelected: boolean,
|
allSelected: boolean,
|
||||||
) => void;
|
) => void;
|
||||||
@ -48,6 +50,7 @@ function VariableItem({
|
|||||||
onValueUpdate,
|
onValueUpdate,
|
||||||
lastUpdatedVar,
|
lastUpdatedVar,
|
||||||
}: VariableItemProps): JSX.Element {
|
}: VariableItemProps): JSX.Element {
|
||||||
|
const { isDashboardLocked } = useDashboard();
|
||||||
const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>(
|
const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>(
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -137,8 +140,9 @@ function VariableItem({
|
|||||||
} else {
|
} else {
|
||||||
[value] = newOptionsData;
|
[value] = newOptionsData;
|
||||||
}
|
}
|
||||||
if (variableData.name) {
|
|
||||||
onValueUpdate(variableData.name, value, allSelected);
|
if (variableData && variableData?.name && variableData?.id) {
|
||||||
|
onValueUpdate(variableData.name, variableData.id, value, allSelected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,14 +153,13 @@ function VariableItem({
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
} else if (variableData.type === 'CUSTOM') {
|
} else if (variableData.type === 'CUSTOM') {
|
||||||
setOptionsData(
|
const optionsData = sortValues(
|
||||||
sortValues(
|
commaValuesParser(variableData.customValue || ''),
|
||||||
commaValuesParser(variableData.customValue || ''),
|
variableData.sort,
|
||||||
variableData.sort,
|
) as never;
|
||||||
) as never,
|
|
||||||
);
|
setOptionsData(optionsData);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const { isLoading } = useQuery(getQueryKey(variableData), {
|
const { isLoading } = useQuery(getQueryKey(variableData), {
|
||||||
@ -195,9 +198,9 @@ function VariableItem({
|
|||||||
(Array.isArray(value) && value.includes(ALL_SELECT_VALUE)) ||
|
(Array.isArray(value) && value.includes(ALL_SELECT_VALUE)) ||
|
||||||
(Array.isArray(value) && value.length === 0)
|
(Array.isArray(value) && value.length === 0)
|
||||||
) {
|
) {
|
||||||
onValueUpdate(variableData.name, optionsData, true);
|
onValueUpdate(variableData.name, variableData.id, optionsData, true);
|
||||||
} else {
|
} else {
|
||||||
onValueUpdate(variableData.name, value, false);
|
onValueUpdate(variableData.name, variableData.id, value, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -230,72 +233,79 @@ function VariableItem({
|
|||||||
getOptions(null);
|
getOptions(null);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, [variableData.type, variableData.customValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VariableContainer>
|
<Tooltip
|
||||||
<Typography.Text className="variable-name" ellipsis>
|
placement="top"
|
||||||
${variableData.name}
|
title={isDashboardLocked ? 'Dashboard is locked' : ''}
|
||||||
</Typography.Text>
|
>
|
||||||
<VariableValue>
|
<VariableContainer>
|
||||||
{variableData.type === 'TEXTBOX' ? (
|
<Typography.Text className="variable-name" ellipsis>
|
||||||
<Input
|
${variableData.name}
|
||||||
placeholder="Enter value"
|
</Typography.Text>
|
||||||
bordered={false}
|
<VariableValue>
|
||||||
value={variableValue}
|
{variableData.type === 'TEXTBOX' ? (
|
||||||
onChange={(e): void => {
|
<Input
|
||||||
setVaribleValue(e.target.value || '');
|
placeholder="Enter value"
|
||||||
}}
|
disabled={isDashboardLocked}
|
||||||
style={{
|
|
||||||
width:
|
|
||||||
50 + ((variableData.selectedValue?.toString()?.length || 0) * 7 || 50),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
!errorMessage &&
|
|
||||||
optionsData && (
|
|
||||||
<Select
|
|
||||||
value={selectValue}
|
|
||||||
onChange={handleChange}
|
|
||||||
bordered={false}
|
bordered={false}
|
||||||
placeholder="Select value"
|
value={variableValue}
|
||||||
mode={mode}
|
onChange={(e): void => {
|
||||||
dropdownMatchSelectWidth={false}
|
setVaribleValue(e.target.value || '');
|
||||||
style={SelectItemStyle}
|
}}
|
||||||
loading={isLoading}
|
style={{
|
||||||
showArrow
|
width:
|
||||||
showSearch
|
50 + ((variableData.selectedValue?.toString()?.length || 0) * 7 || 50),
|
||||||
data-testid="variable-select"
|
}}
|
||||||
>
|
/>
|
||||||
{enableSelectAll && (
|
) : (
|
||||||
<Select.Option data-testid="option-ALL" value={ALL_SELECT_VALUE}>
|
!errorMessage &&
|
||||||
ALL
|
optionsData && (
|
||||||
</Select.Option>
|
<Select
|
||||||
)}
|
value={selectValue}
|
||||||
{map(optionsData, (option) => (
|
onChange={handleChange}
|
||||||
<Select.Option
|
bordered={false}
|
||||||
data-testid={`option-${option}`}
|
placeholder="Select value"
|
||||||
key={option.toString()}
|
mode={mode}
|
||||||
value={option}
|
dropdownMatchSelectWidth={false}
|
||||||
>
|
style={SelectItemStyle}
|
||||||
{option.toString()}
|
loading={isLoading}
|
||||||
</Select.Option>
|
showArrow
|
||||||
))}
|
showSearch
|
||||||
</Select>
|
data-testid="variable-select"
|
||||||
)
|
disabled={isDashboardLocked}
|
||||||
)}
|
>
|
||||||
{errorMessage && (
|
{enableSelectAll && (
|
||||||
<span style={{ margin: '0 0.5rem' }}>
|
<Select.Option data-testid="option-ALL" value={ALL_SELECT_VALUE}>
|
||||||
<Popover
|
ALL
|
||||||
placement="top"
|
</Select.Option>
|
||||||
content={<Typography>{errorMessage}</Typography>}
|
)}
|
||||||
>
|
{map(optionsData, (option) => (
|
||||||
<WarningOutlined style={{ color: orange[5] }} />
|
<Select.Option
|
||||||
</Popover>
|
data-testid={`option-${option}`}
|
||||||
</span>
|
key={option.toString()}
|
||||||
)}
|
value={option}
|
||||||
</VariableValue>
|
>
|
||||||
</VariableContainer>
|
{option.toString()}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
{variableData.type !== 'TEXTBOX' && errorMessage && (
|
||||||
|
<span style={{ margin: '0 0.5rem' }}>
|
||||||
|
<Popover
|
||||||
|
placement="top"
|
||||||
|
content={<Typography>{errorMessage}</Typography>}
|
||||||
|
>
|
||||||
|
<WarningOutlined style={{ color: orange[5] }} />
|
||||||
|
</Popover>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</VariableValue>
|
||||||
|
</VariableContainer>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
export function areArraysEqual(
|
export function areArraysEqual(
|
||||||
a: (string | number | boolean)[],
|
a: (string | number | boolean)[],
|
||||||
b: (string | number | boolean)[],
|
b: (string | number | boolean)[],
|
||||||
@ -14,3 +16,16 @@ export function areArraysEqual(
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const convertVariablesToDbFormat = (
|
||||||
|
variblesArr: IDashboardVariable[],
|
||||||
|
): Dashboard['data']['variables'] =>
|
||||||
|
variblesArr.reduce((result, obj: IDashboardVariable) => {
|
||||||
|
const { id } = obj;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
result[id] = obj;
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
@ -6,8 +6,10 @@ export function variablePropsToPayloadVariables(
|
|||||||
): PayloadVariables {
|
): PayloadVariables {
|
||||||
const payloadVariables: PayloadVariables = {};
|
const payloadVariables: PayloadVariables = {};
|
||||||
|
|
||||||
Object.entries(variables).forEach(([key, value]) => {
|
Object.entries(variables).forEach(([, value]) => {
|
||||||
payloadVariables[key] = value?.selectedValue;
|
if (value?.name) {
|
||||||
|
payloadVariables[value.name] = value?.selectedValue;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return payloadVariables;
|
return payloadVariables;
|
||||||
|
@ -71,8 +71,8 @@ export default function ModuleStepsContainer({
|
|||||||
} = useOnboardingContext();
|
} = useOnboardingContext();
|
||||||
|
|
||||||
const [current, setCurrent] = useState(0);
|
const [current, setCurrent] = useState(0);
|
||||||
const [metaData, setMetaData] = useState<MetaDataProps[]>(defaultMetaData);
|
|
||||||
const { trackEvent } = useAnalytics();
|
const { trackEvent } = useAnalytics();
|
||||||
|
const [metaData, setMetaData] = useState<MetaDataProps[]>(defaultMetaData);
|
||||||
const lastStepIndex = selectedModuleSteps.length - 1;
|
const lastStepIndex = selectedModuleSteps.length - 1;
|
||||||
|
|
||||||
const isValidForm = (): boolean => {
|
const isValidForm = (): boolean => {
|
||||||
|
@ -20,9 +20,13 @@ export const getDashboardVariables = (
|
|||||||
SIGNOZ_START_TIME: parseInt(start, 10) * 1e3,
|
SIGNOZ_START_TIME: parseInt(start, 10) * 1e3,
|
||||||
SIGNOZ_END_TIME: parseInt(end, 10) * 1e3,
|
SIGNOZ_END_TIME: parseInt(end, 10) * 1e3,
|
||||||
};
|
};
|
||||||
Object.keys(variables).forEach((key) => {
|
|
||||||
variablesTuple[key] = variables[key].selectedValue;
|
Object.entries(variables).forEach(([, value]) => {
|
||||||
|
if (value?.name) {
|
||||||
|
variablesTuple[value.name] = value?.selectedValue;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return variablesTuple;
|
return variablesTuple;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Modal from 'antd/es/modal';
|
import { Modal } from 'antd';
|
||||||
import getDashboard from 'api/dashboard/get';
|
import getDashboard from 'api/dashboard/get';
|
||||||
import lockDashboardApi from 'api/dashboard/lockDashboard';
|
import lockDashboardApi from 'api/dashboard/lockDashboard';
|
||||||
import unlockDashboardApi from 'api/dashboard/unlockDashboard';
|
import unlockDashboardApi from 'api/dashboard/unlockDashboard';
|
||||||
@ -30,9 +30,10 @@ import { Dispatch } from 'redux';
|
|||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime';
|
import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime';
|
||||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
|
||||||
import { IDashboardContext } from './types';
|
import { IDashboardContext } from './types';
|
||||||
|
|
||||||
@ -102,6 +103,44 @@ export function DashboardProvider({
|
|||||||
const { t } = useTranslation(['dashboard']);
|
const { t } = useTranslation(['dashboard']);
|
||||||
const dashboardRef = useRef<Dashboard>();
|
const dashboardRef = useRef<Dashboard>();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
const transformDashboardVariables = (data: Dashboard): Dashboard => {
|
||||||
|
if (data && data.data && data.data.variables) {
|
||||||
|
const clonedDashboardData = JSON.parse(JSON.stringify(data));
|
||||||
|
const { variables } = clonedDashboardData.data;
|
||||||
|
const existingOrders: Set<number> = new Set();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const key in variables) {
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (variables.hasOwnProperty(key)) {
|
||||||
|
const variable: IDashboardVariable = variables[key];
|
||||||
|
|
||||||
|
// Check if 'order' property doesn't exist or is undefined
|
||||||
|
if (variable.order === undefined) {
|
||||||
|
// Find a unique order starting from 0
|
||||||
|
let order = 0;
|
||||||
|
while (existingOrders.has(order)) {
|
||||||
|
order += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
variable.order = order;
|
||||||
|
existingOrders.add(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variable.id === undefined) {
|
||||||
|
variable.id = generateUUID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clonedDashboardData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
const dashboardResponse = useQuery(
|
const dashboardResponse = useQuery(
|
||||||
[REACT_QUERY_KEY.DASHBOARD_BY_ID, isDashboardPage?.params],
|
[REACT_QUERY_KEY.DASHBOARD_BY_ID, isDashboardPage?.params],
|
||||||
{
|
{
|
||||||
@ -112,26 +151,27 @@ export function DashboardProvider({
|
|||||||
}),
|
}),
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
const updatedDate = dayjs(data.updated_at);
|
const updatedDashboardData = transformDashboardVariables(data);
|
||||||
|
const updatedDate = dayjs(updatedDashboardData.updated_at);
|
||||||
|
|
||||||
setIsDashboardLocked(data?.isLocked || false);
|
setIsDashboardLocked(updatedDashboardData?.isLocked || false);
|
||||||
|
|
||||||
// on first render
|
// on first render
|
||||||
if (updatedTimeRef.current === null) {
|
if (updatedTimeRef.current === null) {
|
||||||
setSelectedDashboard(data);
|
setSelectedDashboard(updatedDashboardData);
|
||||||
|
|
||||||
updatedTimeRef.current = updatedDate;
|
updatedTimeRef.current = updatedDate;
|
||||||
|
|
||||||
dashboardRef.current = data;
|
dashboardRef.current = updatedDashboardData;
|
||||||
|
|
||||||
setLayouts(getUpdatedLayout(data.data.layout));
|
setLayouts(getUpdatedLayout(updatedDashboardData.data.layout));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
updatedTimeRef.current !== null &&
|
updatedTimeRef.current !== null &&
|
||||||
updatedDate.isAfter(updatedTimeRef.current) &&
|
updatedDate.isAfter(updatedTimeRef.current) &&
|
||||||
isVisible &&
|
isVisible &&
|
||||||
dashboardRef.current?.id === data.id
|
dashboardRef.current?.id === updatedDashboardData.id
|
||||||
) {
|
) {
|
||||||
// show modal when state is out of sync
|
// show modal when state is out of sync
|
||||||
const modal = onModal.confirm({
|
const modal = onModal.confirm({
|
||||||
@ -139,7 +179,7 @@ export function DashboardProvider({
|
|||||||
title: t('dashboard_has_been_updated'),
|
title: t('dashboard_has_been_updated'),
|
||||||
content: t('do_you_want_to_refresh_the_dashboard'),
|
content: t('do_you_want_to_refresh_the_dashboard'),
|
||||||
onOk() {
|
onOk() {
|
||||||
setSelectedDashboard(data);
|
setSelectedDashboard(updatedDashboardData);
|
||||||
|
|
||||||
const { maxTime, minTime } = getMinMax(
|
const { maxTime, minTime } = getMinMax(
|
||||||
globalTime.selectedTime,
|
globalTime.selectedTime,
|
||||||
@ -156,32 +196,32 @@ export function DashboardProvider({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
dashboardRef.current = data;
|
dashboardRef.current = updatedDashboardData;
|
||||||
|
|
||||||
updatedTimeRef.current = dayjs(data.updated_at);
|
updatedTimeRef.current = dayjs(updatedDashboardData.updated_at);
|
||||||
|
|
||||||
setLayouts(getUpdatedLayout(data.data.layout));
|
setLayouts(getUpdatedLayout(updatedDashboardData.data.layout));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
modalRef.current = modal;
|
modalRef.current = modal;
|
||||||
} else {
|
} else {
|
||||||
// normal flow
|
// normal flow
|
||||||
updatedTimeRef.current = dayjs(data.updated_at);
|
updatedTimeRef.current = dayjs(updatedDashboardData.updated_at);
|
||||||
|
|
||||||
dashboardRef.current = data;
|
dashboardRef.current = updatedDashboardData;
|
||||||
|
|
||||||
if (!isEqual(selectedDashboard, data)) {
|
if (!isEqual(selectedDashboard, updatedDashboardData)) {
|
||||||
setSelectedDashboard(data);
|
setSelectedDashboard(updatedDashboardData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isEqual(
|
!isEqual(
|
||||||
[omitBy(layouts, (value): boolean => isUndefined(value))[0]],
|
[omitBy(layouts, (value): boolean => isUndefined(value))[0]],
|
||||||
data.data.layout,
|
updatedDashboardData.data.layout,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
setLayouts(getUpdatedLayout(data.data.layout));
|
setLayouts(getUpdatedLayout(updatedDashboardData.data.layout));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14,6 +14,8 @@ export const VariableSortTypeArr = ['DISABLED', 'ASC', 'DESC'] as const;
|
|||||||
export type TSortVariableValuesType = typeof VariableSortTypeArr[number];
|
export type TSortVariableValuesType = typeof VariableSortTypeArr[number];
|
||||||
|
|
||||||
export interface IDashboardVariable {
|
export interface IDashboardVariable {
|
||||||
|
id: string;
|
||||||
|
order?: any;
|
||||||
name?: string; // key will be the source of truth
|
name?: string; // key will be the source of truth
|
||||||
description: string;
|
description: string;
|
||||||
type: TVariableQueryType;
|
type: TVariableQueryType;
|
||||||
|
@ -2346,6 +2346,45 @@
|
|||||||
resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz"
|
resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz"
|
||||||
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
|
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
|
||||||
|
|
||||||
|
"@dnd-kit/accessibility@^3.1.0":
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.1.0.tgz#1054e19be276b5f1154ced7947fc0cb5d99192e0"
|
||||||
|
integrity sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.0.0"
|
||||||
|
|
||||||
|
"@dnd-kit/core@6.1.0":
|
||||||
|
version "6.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.1.0.tgz#e81a3d10d9eca5d3b01cbf054171273a3fe01def"
|
||||||
|
integrity sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==
|
||||||
|
dependencies:
|
||||||
|
"@dnd-kit/accessibility" "^3.1.0"
|
||||||
|
"@dnd-kit/utilities" "^3.2.2"
|
||||||
|
tslib "^2.0.0"
|
||||||
|
|
||||||
|
"@dnd-kit/modifiers@7.0.0":
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@dnd-kit/modifiers/-/modifiers-7.0.0.tgz#229666dd4e8b9487f348035117f993af755b3db9"
|
||||||
|
integrity sha512-BG/ETy3eBjFap7+zIti53f0PCLGDzNXyTmn6fSdrudORf+OH04MxrW4p5+mPu4mgMk9kM41iYONjc3DOUWTcfg==
|
||||||
|
dependencies:
|
||||||
|
"@dnd-kit/utilities" "^3.2.2"
|
||||||
|
tslib "^2.0.0"
|
||||||
|
|
||||||
|
"@dnd-kit/sortable@8.0.0":
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@dnd-kit/sortable/-/sortable-8.0.0.tgz#086b7ac6723d4618a4ccb6f0227406d8a8862a96"
|
||||||
|
integrity sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==
|
||||||
|
dependencies:
|
||||||
|
"@dnd-kit/utilities" "^3.2.2"
|
||||||
|
tslib "^2.0.0"
|
||||||
|
|
||||||
|
"@dnd-kit/utilities@^3.2.2":
|
||||||
|
version "3.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz#5a32b6af356dc5f74d61b37d6f7129a4040ced7b"
|
||||||
|
integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.0.0"
|
||||||
|
|
||||||
"@emotion/hash@^0.8.0":
|
"@emotion/hash@^0.8.0":
|
||||||
version "0.8.0"
|
version "0.8.0"
|
||||||
resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz"
|
resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz"
|
||||||
@ -14821,6 +14860,11 @@ tslib@^1.8.1, tslib@^1.9.0:
|
|||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
|
tslib@^2.0.0:
|
||||||
|
version "2.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
||||||
|
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||||
|
|
||||||
tsutils@^3.21.0:
|
tsutils@^3.21.0:
|
||||||
version "3.21.0"
|
version "3.21.0"
|
||||||
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"
|
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user