From b743a5a8448206b6390e079420264317d79dfc54 Mon Sep 17 00:00:00 2001 From: SagarRajput-7 Date: Mon, 19 May 2025 03:15:22 +0530 Subject: [PATCH] feat: added apply to all and variable removal logical --- .../DashboardSettings.styles.scss | 11 ++ .../VariableItem/VariableItem.styles.scss | 16 +++ .../Variables/VariableItem/VariableItem.tsx | 2 +- .../Variables/VariableItem/WidgetSelector.tsx | 21 ++- .../Variables/addTagFiltersToDashboard.tsx | 134 +++++++++++++++--- .../DashboardSettings/Variables/index.tsx | 57 +++++++- .../DynamicVariableSelection.tsx | 6 + .../useAddDynamicVariableToPanels.tsx | 16 ++- frontend/src/hooks/dashboard/utils.ts | 75 +++++++++- 9 files changed, 304 insertions(+), 34 deletions(-) diff --git a/frontend/src/container/NewDashboard/DashboardSettings/DashboardSettings.styles.scss b/frontend/src/container/NewDashboard/DashboardSettings/DashboardSettings.styles.scss index 399d6be5331a..531155f9d02e 100644 --- a/frontend/src/container/NewDashboard/DashboardSettings/DashboardSettings.styles.scss +++ b/frontend/src/container/NewDashboard/DashboardSettings/DashboardSettings.styles.scss @@ -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); + } } } diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.styles.scss b/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.styles.scss index cce550f3930f..c7535e97360d 100644 --- a/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.styles.scss +++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.styles.scss @@ -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; diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx b/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx index d309f4e9d8ea..8bff55f69d96 100644 --- a/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx +++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx @@ -599,7 +599,7 @@ function VariableItem({ - Select Widgets to apply + Select Panels to apply this variable item.i), + ); + + // Filter and deduplicate widgets by ID, keeping only those with layout entries + const widgets = Object.values( + (selectedDashboard?.data?.widgets || []).reduce( + (acc: Record, widget) => { + if (widget.id && layoutIds.has(widget.id)) { + acc[widget.id] = widget; + } + return acc; + }, + {}, + ), + ); return ( ({ label: generateGridTitle(widget.title), value: widget.id, }))} value={selectedWidgets} - labelInValue onChange={(value): void => setSelectedWidgets(value as string[])} showLabels /> diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/addTagFiltersToDashboard.tsx b/frontend/src/container/NewDashboard/DashboardSettings/Variables/addTagFiltersToDashboard.tsx index 8fff91785409..2bd641284c4c 100644 --- a/frontend/src/container/NewDashboard/DashboardSettings/Variables/addTagFiltersToDashboard.tsx +++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/addTagFiltersToDashboard.tsx @@ -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; }, diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx index 4a4686175b4d..d2983d3b46d2 100644 --- a/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx +++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx @@ -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([]); const [variblesOrderArr, setVariablesOrderArr] = useState([]); @@ -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} + {variable.type === 'DYNAMIC' && ( + + )}