Skip to content

Commit

Permalink
feat: updated the logic for variable update queue
Browse files Browse the repository at this point in the history
  • Loading branch information
SagarRajput-7 committed Dec 4, 2024
1 parent 6384b25 commit fa4aeae
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { Row } from 'antd';
import { isNull } from 'lodash-es';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { memo, useEffect, useState } from 'react';
import { memo, useEffect, useRef, useState } from 'react';
import { IDashboardVariable } from 'types/api/dashboard/getAll';

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

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

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

const [dependencyData, setDependencyData] = useState<{
order: string[];
graph: VariableGraph;
} | null>(null);

useEffect(() => {
if (variables) {
const tableRowData = [];
Expand All @@ -43,35 +53,26 @@ 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,
]);
};
const initializationRef = useRef(false);

useEffect(() => {
if (variablesTableData.length > 0 && !initializationRef.current) {
const depGrp = buildDependencies(variablesTableData);
const { order, graph } = buildDependencyGraph(depGrp);
setDependencyData({
order,
graph,
});
initializationRef.current = true;
}
}, [variablesTableData]);

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 +112,18 @@ function DashboardVariableSelection(): JSX.Element | null {
});
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParse
import sortValues from 'lib/dashbaordVariables/sortVariableValues';
import { debounce, isArray, isString } from 'lodash-es';
import map from 'lodash-es/map';
import { ChangeEvent, memo, useEffect, useMemo, useState } from 'react';
import { ChangeEvent, memo, useEffect, useMemo, useRef, useState } from 'react';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
Expand Down Expand Up @@ -54,6 +54,7 @@ interface VariableItemProps {
id: string,
arg1: IDashboardVariable['selectedValue'],
allSelected: boolean,
isMountedCall?: boolean,
) => void;
variablesToGetUpdated: string[];
setVariablesToGetUpdated: React.Dispatch<React.SetStateAction<string[]>>;
Expand Down Expand Up @@ -88,18 +89,26 @@ function VariableItem({
(state) => state.globalTime,
);

// logic to detect if its a rerender or a new render/mount
const isMounted = useRef(false);

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;
});
isMounted.current = true;
}, []);

const validVariableUpdate = (): boolean => {
if (!variableData.name) {
return false;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime]);
if (!isMounted.current) {
// variableData.name is present as the top element or next in the queue - variablesToGetUpdated
return Boolean(
variablesToGetUpdated.length &&
variablesToGetUpdated[0] === variableData.name,
);
}
return variablesToGetUpdated.includes(variableData.name);
};

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

Expand Down Expand Up @@ -184,9 +193,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 All @@ -200,7 +207,16 @@ function VariableItem({
}

if (variableData && variableData?.name && variableData?.id) {
onValueUpdate(variableData.name, variableData.id, value, allSelected);
onValueUpdate(
variableData.name,
variableData.id,
value,
allSelected,
isMounted.current,
);
setVariablesToGetUpdated((prev) =>
prev.filter((name) => name !== variableData.name),
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,110 @@ export const convertVariablesToDbFormat = (
result[id] = obj;
return result;
}, {});

const getDependentVariables = (queryValue: string): string[] => {
const variableRegexPattern = /\{\{\s*?\.([^\s}]+)\s*?\}\}/g;

const matches = queryValue.match(variableRegexPattern);

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

export const buildDependencies = (
variables: IDashboardVariable[],
): VariableGraph => {
const graph: VariableGraph = {};

// Initialize empty arrays for all variables first
variables.forEach((variable) => {
if (variable.name) {
graph[variable.name] = [];
}
});

// For each QUERY variable, add it as a dependent to its referenced variables
variables.forEach((variable) => {
if (variable.type === 'QUERY' && variable.name) {
const dependentVariables = getDependentVariables(variable.queryValue || '');

// For each referenced variable, add the current query as a dependent
dependentVariables.forEach((referencedVar) => {
if (graph[referencedVar]) {
graph[referencedVar].push(variable.name as string);
} else {
graph[referencedVar] = [variable.name as string];
}
});
}
});

return graph;
};

// Function to build the dependency graph
export const buildDependencyGraph = (
dependencies: VariableGraph,
): { order: string[]; graph: VariableGraph } => {
const inDegree: Record<string, number> = {};
const adjList: VariableGraph = {};

// Initialize in-degree and adjacency list
Object.keys(dependencies).forEach((node) => {
if (!inDegree[node]) inDegree[node] = 0;
if (!adjList[node]) adjList[node] = [];
dependencies[node].forEach((child) => {
if (!inDegree[child]) inDegree[child] = 0;
inDegree[child]++;
adjList[node].push(child);
});
});

// Topological sort using Kahn's Algorithm
const queue: string[] = Object.keys(inDegree).filter(
(node) => inDegree[node] === 0,
);
const topologicalOrder: string[] = [];

while (queue.length > 0) {
const current = queue.shift();
if (current === undefined) {
break;
}
topologicalOrder.push(current);

adjList[current].forEach((neighbor) => {
inDegree[neighbor]--;
if (inDegree[neighbor] === 0) queue.push(neighbor);
});
}

if (topologicalOrder.length !== Object.keys(dependencies).length) {
throw new Error('Cycle detected in the dependency graph!');
}

return { order: topologicalOrder, graph: adjList };
};

export const onUpdateVariableNode = (
nodeToUpdate: string,
graph: VariableGraph,
topologicalOrder: string[],
callback: (node: string) => void,
): void => {
const visited = new Set<string>();

// Start processing from the node to update
topologicalOrder.forEach((node) => {
if (node === nodeToUpdate || visited.has(node)) {
visited.add(node);
callback(node);
(graph[node] || []).forEach((child) => {
visited.add(child);
});
}
});
};

0 comments on commit fa4aeae

Please sign in to comment.