mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-29 16:14:42 +00:00
feat: added apply to all and variable removal logical
This commit is contained in:
parent
f9f463cce6
commit
b743a5a844
@ -70,6 +70,17 @@
|
||||
gap: 3px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.apply-to-all-button {
|
||||
width: min-content;
|
||||
height: 22px;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
padding: 0px 6px;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
background: var(--bg-slate-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -213,6 +213,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
.dynamic-variable-section {
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0;
|
||||
|
||||
.typography-variables {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
width: 339px;
|
||||
}
|
||||
}
|
||||
|
||||
.variable-textbox-section {
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0;
|
||||
|
||||
@ -599,7 +599,7 @@ function VariableItem({
|
||||
<VariableItemRow className="dynamic-variable-section">
|
||||
<LabelContainer>
|
||||
<Typography className="typography-variables">
|
||||
Select Widgets to apply
|
||||
Select Panels to apply this variable
|
||||
</Typography>
|
||||
</LabelContainer>
|
||||
<WidgetSelector
|
||||
|
||||
@ -11,17 +11,32 @@ export function WidgetSelector({
|
||||
}): JSX.Element {
|
||||
const { selectedDashboard } = useDashboard();
|
||||
|
||||
const widgets = selectedDashboard?.data?.widgets || [];
|
||||
// Get layout IDs for cross-referencing
|
||||
const layoutIds = new Set(
|
||||
(selectedDashboard?.data?.layout || []).map((item) => item.i),
|
||||
);
|
||||
|
||||
// Filter and deduplicate widgets by ID, keeping only those with layout entries
|
||||
const widgets = Object.values(
|
||||
(selectedDashboard?.data?.widgets || []).reduce(
|
||||
(acc: Record<string, any>, widget) => {
|
||||
if (widget.id && layoutIds.has(widget.id)) {
|
||||
acc[widget.id] = widget;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
),
|
||||
);
|
||||
|
||||
return (
|
||||
<CustomMultiSelect
|
||||
placeholder="Select Widgets"
|
||||
placeholder="Select Panels"
|
||||
options={widgets.map((widget) => ({
|
||||
label: generateGridTitle(widget.title),
|
||||
value: widget.id,
|
||||
}))}
|
||||
value={selectedWidgets}
|
||||
labelInValue
|
||||
onChange={(value): void => setSelectedWidgets(value as string[])}
|
||||
showLabels
|
||||
/>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import { cloneDeep, isArray, isEmpty } from 'lodash-es';
|
||||
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
||||
import {
|
||||
IBuilderQuery,
|
||||
@ -10,24 +11,60 @@ import {
|
||||
*/
|
||||
const updateQueryFilters = (
|
||||
queryData: IBuilderQuery,
|
||||
filters: TagFilterItem[],
|
||||
): IBuilderQuery => ({
|
||||
...queryData,
|
||||
filters: {
|
||||
...queryData.filters,
|
||||
items: [...(queryData.filters?.items || []), ...filters],
|
||||
op: queryData.filters?.op || 'AND',
|
||||
},
|
||||
});
|
||||
filter: TagFilterItem,
|
||||
): IBuilderQuery => {
|
||||
const existingFilters = queryData.filters?.items || [];
|
||||
|
||||
// addition | update
|
||||
const currentFilterKey = filter.key?.key;
|
||||
const valueToAdd = filter.value.toString();
|
||||
const newItems: TagFilterItem[] = [];
|
||||
|
||||
existingFilters.forEach((existingFilter) => {
|
||||
const newFilter = cloneDeep(existingFilter);
|
||||
if (
|
||||
newFilter.key?.key === currentFilterKey &&
|
||||
!(isArray(newFilter.value) && newFilter.value.includes(valueToAdd)) &&
|
||||
newFilter.value !== valueToAdd
|
||||
) {
|
||||
if (isEmpty(newFilter.value)) {
|
||||
newFilter.value = valueToAdd;
|
||||
newFilter.op = 'IN';
|
||||
} else {
|
||||
newFilter.value = (isArray(newFilter.value)
|
||||
? [...newFilter.value, valueToAdd]
|
||||
: [newFilter.value, valueToAdd]) as string[] | string;
|
||||
|
||||
newFilter.op = 'IN';
|
||||
}
|
||||
}
|
||||
|
||||
newItems.push(newFilter);
|
||||
});
|
||||
|
||||
// if yet the filter key doesn't get added then add it
|
||||
if (!newItems.find((item) => item.key?.key === currentFilterKey)) {
|
||||
newItems.push(filter);
|
||||
}
|
||||
|
||||
return {
|
||||
...queryData,
|
||||
filters: {
|
||||
...queryData.filters,
|
||||
items: newItems,
|
||||
op: queryData.filters?.op || 'AND',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates a single widget by adding filters to its query
|
||||
*/
|
||||
const updateSingleWidget = (
|
||||
widget: Widgets,
|
||||
filters: TagFilterItem[],
|
||||
filter: TagFilterItem,
|
||||
): Widgets => {
|
||||
if (!widget.query?.builder?.queryData) {
|
||||
if (!widget.query?.builder?.queryData || isEmpty(filter)) {
|
||||
return widget;
|
||||
}
|
||||
|
||||
@ -37,8 +74,65 @@ const updateSingleWidget = (
|
||||
...widget.query,
|
||||
builder: {
|
||||
...widget.query.builder,
|
||||
queryData: widget.query.builder.queryData.map((queryData) =>
|
||||
updateQueryFilters(queryData, filters),
|
||||
queryData: widget.query.builder.queryData.map(
|
||||
(queryData) => updateQueryFilters(queryData, filter), // todo - Sagar: check for multiple query or not
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const removeIfPresent = (
|
||||
queryData: IBuilderQuery,
|
||||
filter: TagFilterItem,
|
||||
): IBuilderQuery => {
|
||||
const existingFilters = queryData.filters?.items || [];
|
||||
|
||||
// addition | update
|
||||
const currentFilterKey = filter.key?.key;
|
||||
const valueToAdd = filter.value.toString();
|
||||
const newItems: TagFilterItem[] = [];
|
||||
|
||||
existingFilters.forEach((existingFilter) => {
|
||||
const newFilter = cloneDeep(existingFilter);
|
||||
if (newFilter.key?.key === currentFilterKey) {
|
||||
if (isArray(newFilter.value) && newFilter.value.includes(valueToAdd)) {
|
||||
newFilter.value = newFilter.value.filter((value) => value !== valueToAdd);
|
||||
} else if (newFilter.value === valueToAdd) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
newItems.push(newFilter);
|
||||
});
|
||||
|
||||
return {
|
||||
...queryData,
|
||||
filters: {
|
||||
...queryData.filters,
|
||||
items: newItems,
|
||||
op: queryData.filters?.op || 'AND',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const updateAfterRemoval = (
|
||||
widget: Widgets,
|
||||
filter: TagFilterItem,
|
||||
): Widgets => {
|
||||
if (!widget.query?.builder?.queryData || isEmpty(filter)) {
|
||||
return widget;
|
||||
}
|
||||
|
||||
// remove the filters where the current filter is available as value as this widget is not selected anymore, hence removal
|
||||
return {
|
||||
...widget,
|
||||
query: {
|
||||
...widget.query,
|
||||
builder: {
|
||||
...widget.query.builder,
|
||||
queryData: widget.query.builder.queryData.map(
|
||||
(queryData) => removeIfPresent(queryData, filter), // todo - Sagar: check for multiple query or not
|
||||
),
|
||||
},
|
||||
},
|
||||
@ -56,10 +150,11 @@ const updateSingleWidget = (
|
||||
*/
|
||||
export const addTagFiltersToDashboard = (
|
||||
dashboard: Dashboard | undefined,
|
||||
filters: TagFilterItem[],
|
||||
filter: TagFilterItem,
|
||||
widgetIds?: string[],
|
||||
applyToAll?: boolean,
|
||||
): Dashboard | undefined => {
|
||||
if (!dashboard || !filters.length) {
|
||||
if (!dashboard || isEmpty(filter)) {
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
@ -73,10 +168,11 @@ export const addTagFiltersToDashboard = (
|
||||
// Only apply to widgets with 'query' property
|
||||
if ('query' in widget) {
|
||||
// If widgetIds is provided, only update widgets with matching IDs
|
||||
if (widgetIds && !widgetIds.includes(widget.id)) {
|
||||
return widget;
|
||||
if (!applyToAll && widgetIds && !widgetIds.includes(widget.id)) {
|
||||
// removal if needed
|
||||
return updateAfterRemoval(widget as Widgets, filter);
|
||||
}
|
||||
return updateSingleWidget(widget as Widgets, filters);
|
||||
return updateSingleWidget(widget as Widgets, filter);
|
||||
}
|
||||
return widget;
|
||||
},
|
||||
|
||||
@ -16,13 +16,19 @@ import { Button, Modal, Row, Space, Table, Typography } from 'antd';
|
||||
import { RowProps } from 'antd/lib';
|
||||
import { convertVariablesToDbFormat } from 'container/NewDashboard/DashboardVariablesSelection/util';
|
||||
import { useAddDynamicVariableToPanels } from 'hooks/dashboard/useAddDynamicVariableToPanels';
|
||||
import { useGetDynamicVariables } from 'hooks/dashboard/useGetDynamicVariables';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { createDynamicVariableToWidgetsMap } from 'hooks/dashboard/utils';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { PenLine, Trash2 } from 'lucide-react';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import {
|
||||
Dashboard,
|
||||
IDashboardVariable,
|
||||
Widgets,
|
||||
} from 'types/api/dashboard/getAll';
|
||||
|
||||
import { TVariableMode } from './types';
|
||||
import VariableItem from './VariableItem/VariableItem';
|
||||
@ -89,7 +95,7 @@ function VariablesSetting({
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const { variables = {} } = selectedDashboard?.data || {};
|
||||
const { variables = {}, widgets = [] } = selectedDashboard?.data || {};
|
||||
|
||||
const [variablesTableData, setVariablesTableData] = useState<any>([]);
|
||||
const [variblesOrderArr, setVariablesOrderArr] = useState<number[]>([]);
|
||||
@ -165,9 +171,39 @@ function VariablesSetting({
|
||||
|
||||
const addDynamicVariableToPanels = useAddDynamicVariableToPanels();
|
||||
|
||||
const { dynamicVariables } = useGetDynamicVariables();
|
||||
|
||||
const dynamicVariableToWidgetsMap = useMemo(
|
||||
() =>
|
||||
createDynamicVariableToWidgetsMap(
|
||||
dynamicVariables,
|
||||
(widgets as Widgets[]) || [],
|
||||
),
|
||||
[dynamicVariables, widgets],
|
||||
);
|
||||
|
||||
// initialize and adjust dynamicVariablesWidgetIds values for all variables
|
||||
useEffect(() => {
|
||||
const newVariablesArr = Object.values(variables).map(
|
||||
(variable: IDashboardVariable) => {
|
||||
if (variable.type === 'DYNAMIC') {
|
||||
return {
|
||||
...variable,
|
||||
dynamicVariablesWidgetIds: dynamicVariableToWidgetsMap[variable.id] || [],
|
||||
};
|
||||
}
|
||||
|
||||
return variable;
|
||||
},
|
||||
);
|
||||
|
||||
setVariablesTableData(newVariablesArr);
|
||||
}, [variables, dynamicVariableToWidgetsMap]);
|
||||
|
||||
const updateVariables = (
|
||||
updatedVariablesData: Dashboard['data']['variables'],
|
||||
currentRequestedId?: string,
|
||||
applyToAll?: boolean,
|
||||
): void => {
|
||||
if (!selectedDashboard) {
|
||||
return;
|
||||
@ -178,6 +214,7 @@ function VariablesSetting({
|
||||
addDynamicVariableToPanels(
|
||||
selectedDashboard,
|
||||
updatedVariablesData[currentRequestedId || ''],
|
||||
applyToAll,
|
||||
)) ||
|
||||
selectedDashboard;
|
||||
|
||||
@ -214,6 +251,7 @@ function VariablesSetting({
|
||||
const onVariableSaveHandler = (
|
||||
mode: TVariableMode,
|
||||
variableData: IDashboardVariable,
|
||||
applyToAll?: boolean,
|
||||
): void => {
|
||||
const updatedVariableData = {
|
||||
...variableData,
|
||||
@ -237,7 +275,7 @@ function VariablesSetting({
|
||||
const variables = convertVariablesToDbFormat(newVariablesArr);
|
||||
|
||||
setVariablesTableData(newVariablesArr);
|
||||
updateVariables(variables, variableData?.id);
|
||||
updateVariables(variables, variableData?.id, applyToAll);
|
||||
onDoneVariableViewMode();
|
||||
};
|
||||
|
||||
@ -283,6 +321,17 @@ function VariablesSetting({
|
||||
{variable.description}
|
||||
</Typography.Text>
|
||||
<Space className="actions-btns">
|
||||
{variable.type === 'DYNAMIC' && (
|
||||
<Button
|
||||
type="text"
|
||||
onClick={(): void =>
|
||||
onVariableSaveHandler(variableViewMode || 'EDIT', variable, true)
|
||||
}
|
||||
className="apply-to-all-button"
|
||||
>
|
||||
<Typography.Text>Apply to all</Typography.Text>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type="text"
|
||||
onClick={(): void => onVariableViewModeEnter('EDIT', variable)}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import './DashboardVariableSelection.styles.scss';
|
||||
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { Tooltip, Typography } from 'antd';
|
||||
import { getFieldValues } from 'api/dynamicVariables/getFieldValues';
|
||||
import { CustomMultiSelect, CustomSelect } from 'components/NewSelect';
|
||||
@ -296,6 +297,11 @@ function DynamicVariableSelection({
|
||||
<div className="variable-item">
|
||||
<Typography.Text className="variable-name" ellipsis>
|
||||
${variableData.name}
|
||||
{variableData.description && (
|
||||
<Tooltip title={variableData.description}>
|
||||
<InfoCircleOutlined className="info-icon" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Typography.Text>
|
||||
<div className="variable-value">
|
||||
{variableData.multiSelect ? (
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { getFiltersFromKeyValue } from 'pages/Celery/CeleryOverview/CeleryOverviewUtils';
|
||||
import { addTagFiltersToDashboard } from 'container/NewDashboard/DashboardSettings/Variables/addTagFiltersToDashboard';
|
||||
import { useCallback } from 'react';
|
||||
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { addTagFiltersToDashboard } from '../../container/NewDashboard/DashboardSettings/Variables/addTagFiltersToDashboard';
|
||||
import { getFiltersFromKeyValue } from './utils';
|
||||
|
||||
/**
|
||||
* A hook that returns a function to add dynamic variables to dashboard panels as tag filters.
|
||||
@ -13,11 +13,13 @@ import { addTagFiltersToDashboard } from '../../container/NewDashboard/Dashboard
|
||||
export const useAddDynamicVariableToPanels = (): ((
|
||||
dashboard: Dashboard | undefined,
|
||||
variableConfig: IDashboardVariable,
|
||||
applyToAll?: boolean,
|
||||
) => Dashboard | undefined) =>
|
||||
useCallback(
|
||||
(
|
||||
dashboard: Dashboard | undefined,
|
||||
variableConfig: IDashboardVariable,
|
||||
applyToAll?: boolean,
|
||||
): Dashboard | undefined => {
|
||||
if (!variableConfig) return dashboard;
|
||||
|
||||
@ -27,14 +29,18 @@ export const useAddDynamicVariableToPanels = (): ((
|
||||
dynamicVariablesWidgetIds,
|
||||
} = variableConfig;
|
||||
|
||||
const tagFilters: TagFilterItem[] = [
|
||||
getFiltersFromKeyValue(dynamicVariablesAttribute || '', `$${name}`),
|
||||
];
|
||||
const tagFilters: TagFilterItem = getFiltersFromKeyValue(
|
||||
dynamicVariablesAttribute || '',
|
||||
`$${name}`,
|
||||
'',
|
||||
'IN',
|
||||
); // todo - Sagar: make a logic to have correct type and other details
|
||||
|
||||
return addTagFiltersToDashboard(
|
||||
dashboard,
|
||||
tagFilters,
|
||||
dynamicVariablesWidgetIds,
|
||||
applyToAll,
|
||||
);
|
||||
},
|
||||
[],
|
||||
|
||||
@ -2,8 +2,17 @@ import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { convertKeysToColumnFields } from 'container/LogsExplorerList/utils';
|
||||
import { placeWidgetAtBottom } from 'container/NewWidget/utils';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { isArray } from 'lodash-es';
|
||||
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
IBuilderQuery,
|
||||
Query,
|
||||
TagFilterItem,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { DynamicVariable } from './useGetDynamicVariables';
|
||||
|
||||
const baseLogsSelectedColumns = {
|
||||
dataType: 'string',
|
||||
@ -56,3 +65,65 @@ export const addEmptyWidgetInDashboardJSONWithQuery = (
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getFiltersFromKeyValue = (
|
||||
key: string,
|
||||
value: string | number,
|
||||
type?: string,
|
||||
op?: string,
|
||||
dataType?: DataTypes,
|
||||
): TagFilterItem => ({
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
key,
|
||||
dataType: dataType || DataTypes.String,
|
||||
type: type || '',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: `${key}--${dataType || DataTypes.String}--${type || ''}--false`,
|
||||
},
|
||||
op: op || '=',
|
||||
value: value.toString(),
|
||||
});
|
||||
|
||||
export const createDynamicVariableToWidgetsMap = (
|
||||
dynamicVariables: DynamicVariable[],
|
||||
widgets: Widgets[],
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
): Record<string, string[]> => {
|
||||
const dynamicVariableToWidgetsMap: Record<string, string[]> = {};
|
||||
|
||||
// Initialize map with empty arrays for each variable
|
||||
dynamicVariables.forEach((variable) => {
|
||||
if (variable.id) {
|
||||
dynamicVariableToWidgetsMap[variable.id] = [];
|
||||
}
|
||||
});
|
||||
|
||||
// Check each widget for usage of dynamic variables
|
||||
if (Array.isArray(widgets)) {
|
||||
widgets.forEach((widget) => {
|
||||
if (widget.query?.builder?.queryData) {
|
||||
widget.query.builder.queryData.forEach((queryData: IBuilderQuery) => {
|
||||
queryData.filters?.items?.forEach((filter: TagFilterItem) => {
|
||||
// For each filter, check if it uses any dynamic variable
|
||||
dynamicVariables.forEach((variable) => {
|
||||
if (
|
||||
variable.dynamicVariablesAttribute &&
|
||||
filter.key?.key === variable.dynamicVariablesAttribute &&
|
||||
((isArray(filter.value) &&
|
||||
filter.value.includes(`$${variable.name}`)) ||
|
||||
filter.value === `$${variable.name}`) &&
|
||||
!dynamicVariableToWidgetsMap[variable.id].includes(widget.id)
|
||||
) {
|
||||
dynamicVariableToWidgetsMap[variable.id].push(widget.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return dynamicVariableToWidgetsMap;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user