/* eslint-disable sonarjs/cognitive-complexity */ /* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable react/jsx-props-no-spreading */ /* eslint-disable no-nested-ternary */ import './DashboardVariableSelection.styles.scss'; import { orange } from '@ant-design/colors'; import { WarningOutlined } from '@ant-design/icons'; import { Checkbox, Input, Popover, Select, Tag, Tooltip, Typography, } from 'antd'; import { CheckboxChangeEvent } from 'antd/es/checkbox'; import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser'; 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 { useQuery } from 'react-query'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { IDashboardVariable } from 'types/api/dashboard/getAll'; import { VariableResponseProps } from 'types/api/dashboard/variables/query'; import { GlobalReducer } from 'types/reducer/globalTime'; import { popupContainer } from 'utils/selectPopupContainer'; import { variablePropsToPayloadVariables } from '../utils'; import { SelectItemStyle } from './styles'; import { areArraysEqual } from './util'; const ALL_SELECT_VALUE = '__ALL__'; const variableRegexPattern = /\{\{\s*?\.([^\s}]+)\s*?\}\}/g; enum ToggleTagValue { Only = 'Only', All = 'All', } interface VariableItemProps { variableData: IDashboardVariable; existingVariables: Record; onValueUpdate: ( name: string, id: string, arg1: IDashboardVariable['selectedValue'], allSelected: boolean, ) => void; variablesToGetUpdated: string[]; setVariablesToGetUpdated: React.Dispatch>; } const getSelectValue = ( selectedValue: IDashboardVariable['selectedValue'], variableData: IDashboardVariable, ): string | string[] | undefined => { if (Array.isArray(selectedValue)) { if (!variableData.multiSelect && selectedValue.length === 1) { return selectedValue[0]?.toString(); } return selectedValue.map((item) => item.toString()); } return selectedValue?.toString(); }; // eslint-disable-next-line sonarjs/cognitive-complexity function VariableItem({ variableData, existingVariables, onValueUpdate, variablesToGetUpdated, setVariablesToGetUpdated, }: VariableItemProps): JSX.Element { const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>( [], ); const { maxTime, minTime } = useSelector( (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; }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [minTime, maxTime]); const [errorMessage, setErrorMessage] = useState(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 || '', ); 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}`, ]; }; // eslint-disable-next-line sonarjs/cognitive-complexity const getOptions = (variablesRes: VariableResponseProps | null): void => { if (variablesRes && variableData.type === 'QUERY') { try { setErrorMessage(null); if ( variablesRes?.variableValues && Array.isArray(variablesRes?.variableValues) ) { const newOptionsData = sortValues( variablesRes?.variableValues, variableData.sort, ); const oldOptionsData = sortValues(optionsData, variableData.sort) as never; if (!areArraysEqual(newOptionsData, oldOptionsData)) { /* eslint-disable no-useless-escape */ let valueNotInList = false; if (isArray(variableData.selectedValue)) { variableData.selectedValue.forEach((val) => { const isUsed = newOptionsData.includes(val); if (!isUsed) { valueNotInList = true; } }); } else if (isString(variableData.selectedValue)) { const isUsed = newOptionsData.includes(variableData.selectedValue); if (!isUsed) { valueNotInList = true; } } // variablesData.allSelected is added for the case where on change of options we need to update the // local storage if ( variableData.type === 'QUERY' && variableData.name && (variablesToGetUpdated.includes(variableData.name) || valueNotInList || variableData.allSelected) ) { let value = variableData.selectedValue; let allSelected = false; // The default value for multi-select is ALL and first value for // single select if (variableData.multiSelect) { value = newOptionsData; allSelected = true; } else { [value] = newOptionsData; } if (variableData && variableData?.name && variableData?.id) { onValueUpdate(variableData.name, variableData.id, value, allSelected); } } setOptionsData(newOptionsData); } else { setVariablesToGetUpdated((prev) => prev.filter((name) => name !== variableData.name), ); } } } catch (e) { console.error(e); } } else if (variableData.type === 'CUSTOM') { const optionsData = sortValues( commaValuesParser(variableData.customValue || ''), variableData.sort, ) as never; setOptionsData(optionsData); } }; 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'; } setErrorMessage(message); } }, }); const handleChange = (value: string | string[]): void => { if (variableData.name) { if ( value === ALL_SELECT_VALUE || (Array.isArray(value) && value.includes(ALL_SELECT_VALUE)) ) { onValueUpdate(variableData.name, variableData.id, optionsData, true); } else { onValueUpdate(variableData.name, variableData.id, value, false); } } }; // do not debounce the above function as we do not need debounce in select variables const debouncedHandleChange = debounce(handleChange, 500); const { selectedValue } = variableData; const selectedValueStringified = useMemo( () => getSelectValue(selectedValue, variableData), [selectedValue, variableData], ); const enableSelectAll = variableData.multiSelect && variableData.showALLOption; const selectValue = variableData.allSelected && enableSelectAll ? 'ALL' : selectedValueStringified; const mode: 'multiple' | undefined = variableData.multiSelect && !variableData.allSelected ? 'multiple' : undefined; useEffect(() => { // Fetch options for CUSTOM Type if (variableData.type === 'CUSTOM') { getOptions(null); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [variableData.type, variableData.customValue]); const checkAll = (e: MouseEvent): void => { e.stopPropagation(); e.preventDefault(); const isChecked = variableData.allSelected || selectValue?.includes(ALL_SELECT_VALUE); if (isChecked) { handleChange([]); } else { handleChange(ALL_SELECT_VALUE); } }; const handleOptionSelect = ( e: CheckboxChangeEvent, option: string | number | boolean, ): void => { const newSelectedValue = Array.isArray(selectedValue) ? ((selectedValue.filter( (val) => val.toString() !== option.toString(), ) as unknown) as string[]) : []; if ( !e.target.checked && Array.isArray(selectedValueStringified) && selectedValueStringified.includes(option.toString()) ) { if (newSelectedValue.length === 1) { handleChange(newSelectedValue[0].toString()); return; } handleChange(newSelectedValue); } else if (!e.target.checked && selectedValue === option.toString()) { handleChange(ALL_SELECT_VALUE); } else if (newSelectedValue.length === optionsData.length - 1) { handleChange(ALL_SELECT_VALUE); } }; const [optionState, setOptionState] = useState({ tag: '', visible: false, }); function currentToggleTagValue({ option, }: { option: string; }): ToggleTagValue { if ( option.toString() === selectValue || (Array.isArray(selectValue) && selectValue?.includes(option.toString()) && selectValue.length === 1) ) { return ToggleTagValue.All; } return ToggleTagValue.Only; } function handleToggle(e: ChangeEvent, option: string): void { e.stopPropagation(); const mode = currentToggleTagValue({ option: option as string }); const isChecked = variableData.allSelected || option.toString() === selectValue || (Array.isArray(selectValue) && selectValue?.includes(option.toString())); if (isChecked) { if (mode === ToggleTagValue.Only && variableData.multiSelect) { handleChange([option.toString()]); } else if (!variableData.multiSelect) { handleChange(option.toString()); } else { handleChange(ALL_SELECT_VALUE); } } else { handleChange(option.toString()); } } function retProps( option: string, ): { onMouseOver: () => void; onMouseOut: () => void; } { return { onMouseOver: (): void => setOptionState({ tag: option.toString(), visible: true, }), onMouseOut: (): void => setOptionState({ tag: option.toString(), visible: false, }), }; } const ensureValidOption = (option: string): boolean => !( currentToggleTagValue({ option }) === ToggleTagValue.All && !enableSelectAll ); return (
${variableData.name}
{variableData.type === 'TEXTBOX' ? ( { debouncedHandleChange(e.target.value || ''); }} style={{ width: 50 + ((variableData.selectedValue?.toString()?.length || 0) * 7 || 50), }} /> ) : ( !errorMessage && optionsData && ( ) )} {variableData.type !== 'TEXTBOX' && errorMessage && ( {errorMessage}} > )}
); } export default memo(VariableItem);