Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: updated the logic for variable update queue #6586

Merged
merged 9 commits into from
Jan 2, 2025
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { Row } from 'antd';
import { isNull } from 'lodash-es';
import { isEmpty } from 'lodash-es';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { memo, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { IDashboardVariable } from 'types/api/dashboard/getAll';

import { GlobalReducer } from 'types/reducer/globalTime';

import {
buildDependencies,
buildDependencyGraph,
buildParentDependencyGraph,
IDependencyData,
onUpdateVariableNode,
} from './util';
import VariableItem from './VariableItem';

function DashboardVariableSelection(): JSX.Element | null {
Expand All @@ -21,6 +31,14 @@ function DashboardVariableSelection(): JSX.Element | null {

const [variablesTableData, setVariablesTableData] = useState<any>([]);

const [dependencyData, setDependencyData] = useState<IDependencyData | null>(
null,
);

const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);

useEffect(() => {
if (variables) {
const tableRowData = [];
Expand All @@ -43,35 +61,37 @@ function DashboardVariableSelection(): JSX.Element | null {
}
}, [variables]);

const onVarChanged = (name: string): void => {
/**
* this function takes care of adding the dependent variables to current update queue and removing
* the updated variable name from the queue
*/
const dependentVariables = variablesTableData
?.map((variable: any) => {
if (variable.type === 'QUERY') {
const re = new RegExp(`\\{\\{\\s*?\\.${name}\\s*?\\}\\}`); // regex for `{{.var}}`
const queryValue = variable.queryValue || '';
const dependVarReMatch = queryValue.match(re);
if (dependVarReMatch !== null && dependVarReMatch.length > 0) {
return variable.name;
}
}
return null;
})
.filter((val: string | null) => !isNull(val));
setVariablesToGetUpdated((prev) => [
...prev.filter((v) => v !== name),
...dependentVariables,
]);
};
useEffect(() => {
if (variablesTableData.length > 0) {
const depGrp = buildDependencies(variablesTableData);
const { order, graph } = buildDependencyGraph(depGrp);
const parentDependencyGraph = buildParentDependencyGraph(graph);
setDependencyData({
order,
graph,
parentDependencyGraph,
});
}
}, [setVariablesToGetUpdated, variables, variablesTableData]);

// this handles the case where the dependency order changes i.e. variable list updated via creation or deletion etc. and we need to refetch the variables
// also trigger when the global time changes
useEffect(
() => {
if (!isEmpty(dependencyData?.order)) {
setVariablesToGetUpdated(dependencyData?.order || []);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[JSON.stringify(dependencyData?.order), minTime, maxTime],
);

const onValueUpdate = (
name: string,
id: string,
value: IDashboardVariable['selectedValue'],
allSelected: boolean,
// isMountedCall?: boolean,
// eslint-disable-next-line sonarjs/cognitive-complexity
): void => {
if (id) {
Expand Down Expand Up @@ -111,7 +131,20 @@ function DashboardVariableSelection(): JSX.Element | null {
});
}

onVarChanged(name);
if (dependencyData) {
const updatedVariables: string[] = [];
onUpdateVariableNode(
name,
dependencyData.graph,
dependencyData.order,
(node) => updatedVariables.push(node),
);
setVariablesToGetUpdated((prev) => [
...new Set([...prev, ...updatedVariables.filter((v) => v !== name)]),
]);
} else {
setVariablesToGetUpdated((prev) => prev.filter((v) => v !== name));
}
}
};

Expand Down Expand Up @@ -139,6 +172,7 @@ function DashboardVariableSelection(): JSX.Element | null {
onValueUpdate={onValueUpdate}
variablesToGetUpdated={variablesToGetUpdated}
setVariablesToGetUpdated={setVariablesToGetUpdated}
dependencyData={dependencyData}
/>
))}
</Row>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,10 @@ import { popupContainer } from 'utils/selectPopupContainer';

import { variablePropsToPayloadVariables } from '../utils';
import { SelectItemStyle } from './styles';
import { areArraysEqual } from './util';
import { areArraysEqual, checkAPIInvocation, IDependencyData } from './util';

const ALL_SELECT_VALUE = '__ALL__';

const variableRegexPattern = /\{\{\s*?\.([^\s}]+)\s*?\}\}/g;

enum ToggleTagValue {
Only = 'Only',
All = 'All',
Expand All @@ -57,6 +55,7 @@ interface VariableItemProps {
) => void;
variablesToGetUpdated: string[];
setVariablesToGetUpdated: React.Dispatch<React.SetStateAction<string[]>>;
dependencyData: IDependencyData | null;
}

const getSelectValue = (
Expand All @@ -79,6 +78,7 @@ function VariableItem({
onValueUpdate,
variablesToGetUpdated,
setVariablesToGetUpdated,
dependencyData,
}: VariableItemProps): JSX.Element {
const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>(
[],
Expand All @@ -88,60 +88,20 @@ function VariableItem({
(state) => state.globalTime,
);

useEffect(() => {
if (variableData.allSelected && variableData.type === 'QUERY') {
setVariablesToGetUpdated((prev) => {
const variablesQueue = [...prev.filter((v) => v !== variableData.name)];
if (variableData.name) {
variablesQueue.push(variableData.name);
}
return variablesQueue;
});
const validVariableUpdate = (): boolean => {
if (!variableData.name) {
return false;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime]);

const [errorMessage, setErrorMessage] = useState<null | string>(null);

const getDependentVariables = (queryValue: string): string[] => {
const matches = queryValue.match(variableRegexPattern);

// Extract variable names from the matches array without {{ . }}
return matches
? matches.map((match) => match.replace(variableRegexPattern, '$1'))
: [];
};

const getQueryKey = (variableData: IDashboardVariable): string[] => {
let dependentVariablesStr = '';

const dependentVariables = getDependentVariables(
variableData.queryValue || '',
// variableData.name is present as the top element or next in the queue - variablesToGetUpdated
return Boolean(
variablesToGetUpdated.length &&
variablesToGetUpdated[0] === variableData.name,
);

const variableName = variableData.name || '';

dependentVariables?.forEach((element) => {
const [, variable] =
Object.entries(existingVariables).find(
([, value]) => value.name === element,
) || [];

dependentVariablesStr += `${element}${variable?.selectedValue}`;
});

const variableKey = dependentVariablesStr.replace(/\s/g, '');

// added this time dependency for variables query as API respects the passed time range now
return [
REACT_QUERY_KEY.DASHBOARD_BY_ID,
variableName,
variableKey,
`${minTime}`,
`${maxTime}`,
];
};

const [errorMessage, setErrorMessage] = useState<null | string>(null);

// eslint-disable-next-line sonarjs/cognitive-complexity
const getOptions = (variablesRes: VariableResponseProps | null): void => {
if (variablesRes && variableData.type === 'QUERY') {
Expand Down Expand Up @@ -184,9 +144,7 @@ function VariableItem({
if (
variableData.type === 'QUERY' &&
variableData.name &&
(variablesToGetUpdated.includes(variableData.name) ||
valueNotInList ||
variableData.allSelected)
(validVariableUpdate() || valueNotInList || variableData.allSelected)
) {
let value = variableData.selectedValue;
let allSelected = false;
Expand Down Expand Up @@ -224,36 +182,64 @@ function VariableItem({
}
};

const { isLoading } = useQuery(getQueryKey(variableData), {
enabled: variableData && variableData.type === 'QUERY',
queryFn: () =>
dashboardVariablesQuery({
query: variableData.queryValue || '',
variables: variablePropsToPayloadVariables(existingVariables),
}),
refetchOnWindowFocus: false,
onSuccess: (response) => {
getOptions(response.payload);
},
onError: (error: {
details: {
error: string;
};
}) => {
const { details } = error;

if (details.error) {
let message = details.error;
if (details.error.includes('Syntax error:')) {
message =
'Please make sure query is valid and dependent variables are selected';
const { isLoading } = useQuery(
[
REACT_QUERY_KEY.DASHBOARD_BY_ID,
variableData.name || '',
`${minTime}`,
`${maxTime}`,
JSON.stringify(dependencyData?.order),
],
{
enabled:
variableData &&
variableData.type === 'QUERY' &&
checkAPIInvocation(
variablesToGetUpdated,
variableData,
dependencyData?.parentDependencyGraph,
),
queryFn: () =>
dashboardVariablesQuery({
query: variableData.queryValue || '',
variables: variablePropsToPayloadVariables(existingVariables),
}),
refetchOnWindowFocus: false,
onSuccess: (response) => {
getOptions(response.payload);
setVariablesToGetUpdated((prev) =>
prev.filter((v) => v !== variableData.name),
);
},
onError: (error: {
details: {
error: string;
};
}) => {
const { details } = error;

if (details.error) {
let message = details.error;
if (details.error.includes('Syntax error:')) {
message =
'Please make sure query is valid and dependent variables are selected';
}
setErrorMessage(message);
}
setErrorMessage(message);
}
},
},
});
);

const handleChange = (value: string | string[]): void => {
// if value is equal to selected value then return
if (
value === variableData.selectedValue ||
(Array.isArray(value) &&
Array.isArray(variableData.selectedValue) &&
areArraysEqual(value, variableData.selectedValue))
) {
return;
}
if (variableData.name) {
if (
value === ALL_SELECT_VALUE ||
Expand Down
Loading
Loading