/* eslint-disable sonarjs/cognitive-complexity */ import './VariableItem.styles.scss'; import { orange } from '@ant-design/colors'; import { Button, Collapse, Input, Select, Switch, Tag, Typography } from 'antd'; import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery'; import cx from 'classnames'; import Editor from 'components/Editor'; import { CustomSelect } from 'components/NewSelect'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { useGetFieldValues } from 'hooks/dynamicVariables/useGetFieldValues'; import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser'; import sortValues from 'lib/dashbaordVariables/sortVariableValues'; import { map } from 'lodash-es'; import { ArrowLeft, Check, ClipboardType, DatabaseZap, LayoutList, Pyramid, X, } from 'lucide-react'; import { useCallback, useEffect, useState } from 'react'; import { useQuery } from 'react-query'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { IDashboardVariable, TSortVariableValuesType, TVariableQueryType, VariableSortTypeArr, } from 'types/api/dashboard/getAll'; import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as generateUUID } from 'uuid'; import { buildDependencies, buildDependencyGraph, } from '../../../DashboardVariablesSelection/util'; import { variablePropsToPayloadVariables } from '../../../utils'; import { TVariableMode } from '../types'; import DynamicVariable from './DynamicVariable/DynamicVariable'; import { LabelContainer, VariableItemRow } from './styles'; import { WidgetSelector } from './WidgetSelector'; const { Option } = Select; interface VariableItemProps { variableData: IDashboardVariable; existingVariables: Record; onCancel: () => void; onSave: (mode: TVariableMode, variableData: IDashboardVariable) => void; validateName: (arg0: string) => boolean; mode: TVariableMode; } function VariableItem({ variableData, existingVariables, onCancel, onSave, validateName, mode, }: VariableItemProps): JSX.Element { const [variableName, setVariableName] = useState( variableData.name || '', ); const [variableDescription, setVariableDescription] = useState( variableData.description || '', ); const [queryType, setQueryType] = useState( variableData.type || 'DYNAMIC', ); const [variableQueryValue, setVariableQueryValue] = useState( variableData.queryValue || '', ); const [variableCustomValue, setVariableCustomValue] = useState( variableData.customValue || '', ); const [variableTextboxValue, setVariableTextboxValue] = useState( variableData.textboxValue || '', ); const [ variableSortType, setVariableSortType, ] = useState( variableData.sort || VariableSortTypeArr[0], ); const [variableMultiSelect, setVariableMultiSelect] = useState( variableData.multiSelect || false, ); const [variableShowALLOption, setVariableShowALLOption] = useState( variableData.showALLOption || false, ); const [previewValues, setPreviewValues] = useState([]); const [variableDefaultValue, setVariableDefaultValue] = useState( (variableData.defaultValue as string) || '', ); const [ dynamicVariablesSelectedValue, setDynamicVariablesSelectedValue, ] = useState<{ name: string; value: string }>(); useEffect(() => { if ( variableData.dynamicVariablesAttribute && variableData.dynamicVariablesSource ) { setDynamicVariablesSelectedValue({ name: variableData.dynamicVariablesAttribute, value: variableData.dynamicVariablesSource, }); } }, [ variableData.dynamicVariablesAttribute, variableData.dynamicVariablesSource, ]); // Error messages const [errorName, setErrorName] = useState(false); const [errorPreview, setErrorPreview] = useState(null); const { maxTime, minTime } = useSelector( (state) => state.globalTime, ); const { data: fieldValues } = useGetFieldValues({ signal: dynamicVariablesSelectedValue?.value === 'All Sources' ? undefined : (dynamicVariablesSelectedValue?.value?.toLowerCase() as | 'traces' | 'logs' | 'metrics'), name: dynamicVariablesSelectedValue?.name || '', enabled: !!dynamicVariablesSelectedValue?.name && !!dynamicVariablesSelectedValue?.value, startUnixMilli: minTime, endUnixMilli: maxTime, }); const [selectedWidgets, setSelectedWidgets] = useState([]); useEffect(() => { if (queryType === 'DYNAMIC') { setSelectedWidgets(variableData?.dynamicVariablesWidgetIds || []); } }, [queryType, variableData?.dynamicVariablesWidgetIds]); useEffect(() => { if (queryType === 'CUSTOM') { setPreviewValues( sortValues( commaValuesParser(variableCustomValue), variableSortType, ) as never, ); } if (queryType === 'QUERY') { setPreviewValues((prev) => sortValues(prev, variableSortType) as never); } }, [ queryType, variableCustomValue, variableData.customValue, variableData.type, variableSortType, ]); useEffect(() => { if ( queryType === 'DYNAMIC' && fieldValues && dynamicVariablesSelectedValue?.name && dynamicVariablesSelectedValue?.value ) { setPreviewValues( sortValues( fieldValues.payload?.normalizedValues || [], variableSortType, ) as never, ); } }, [ fieldValues, variableSortType, queryType, dynamicVariablesSelectedValue?.name, dynamicVariablesSelectedValue?.value, dynamicVariablesSelectedValue, ]); const handleSave = (): void => { // Check for cyclic dependencies const newVariable = { name: variableName, description: variableDescription, type: queryType, queryValue: variableQueryValue, customValue: variableCustomValue, textboxValue: variableTextboxValue, multiSelect: variableMultiSelect, showALLOption: variableShowALLOption, sort: variableSortType, ...(queryType === 'TEXTBOX' && { selectedValue: (variableData.selectedValue || variableTextboxValue) as never, }), ...(queryType !== 'TEXTBOX' && { defaultValue: variableDefaultValue as never, }), modificationUUID: generateUUID(), id: variableData.id || generateUUID(), order: variableData.order, ...(queryType === 'DYNAMIC' && { dynamicVariablesAttribute: dynamicVariablesSelectedValue?.name, dynamicVariablesSource: dynamicVariablesSelectedValue?.value, }), ...(queryType === 'DYNAMIC' && { dynamicVariablesWidgetIds: selectedWidgets?.length > 0 ? selectedWidgets : [], }), }; const allVariables = [...Object.values(existingVariables), newVariable]; const dependencies = buildDependencies(allVariables); const { hasCycle, cycleNodes } = buildDependencyGraph(dependencies); if (hasCycle) { setErrorPreview( `Cannot save: Circular dependency detected between variables: ${cycleNodes?.join( ' → ', )}`, ); return; } onSave(mode, newVariable); }; // Fetches the preview values for the SQL variable query const handleQueryResult = (response: any): void => { if (response?.payload?.variableValues) { setPreviewValues( sortValues( response.payload?.variableValues || [], variableSortType, ) as never, ); } else { setPreviewValues([]); } }; const { isFetching: previewLoading, refetch: runQuery } = useQuery( [REACT_QUERY_KEY.DASHBOARD_BY_ID, variableData.name, variableName], { enabled: false, queryFn: () => dashboardVariablesQuery({ query: variableQueryValue || '', variables: variablePropsToPayloadVariables(existingVariables), }), refetchOnWindowFocus: false, onSuccess: (response) => { setErrorPreview(null); handleQueryResult(response); }, onError: (error: { details: { error: string; }; }) => { const { details } = error; if (details.error) { let message = details.error; if ((details.error ?? '').toString().includes('Syntax error:')) { message = 'Please make sure query is valid and dependent variables are selected'; } setErrorPreview(message); } }, }, ); const handleTestRunQuery = useCallback(() => { runQuery(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( <>
Name
{ setVariableName(e.target.value); setErrorName( !validateName(e.target.value) && e.target.value !== variableData.name, ); }} />
{errorName ? 'Variable name already exists' : ''}
Description setVariableDescription(e.target.value)} /> Variable Type
{queryType === 'DYNAMIC' && (
)} {queryType === 'QUERY' && (
Query
setVariableQueryValue(e)} height="240px" options={{ fontSize: 13, wordWrap: 'on', lineNumbers: 'off', glyphMargin: false, folding: false, lineDecorationsWidth: 0, lineNumbersMinChars: 0, minimap: { enabled: false, }, }} />
)} {queryType === 'CUSTOM' && ( { setVariableCustomValue(e.target.value); setPreviewValues( sortValues( commaValuesParser(e.target.value), variableSortType, ) as never, ); }} /> ), }, ]} /> )} {queryType === 'TEXTBOX' && ( Default Value { setVariableTextboxValue(e.target.value); }} placeholder="Enter a default value (if any)..." style={{ width: 400 }} /> )} {(queryType === 'QUERY' || queryType === 'CUSTOM' || queryType === 'DYNAMIC') && ( <> Preview of Values
{errorPreview ? ( {errorPreview} ) : ( map(previewValues, (value, idx) => ( {value.toString()} )) )}
Sort Values Sort the query output values Enable multiple values to be checked { setVariableMultiSelect(e); if (!e) { setVariableShowALLOption(false); } }} /> {variableMultiSelect && ( Include an option for ALL values setVariableShowALLOption(e)} /> )} Default Value {queryType === 'QUERY' ? 'Click Test Run Query to see the values or add custom value' : 'Select a value from the preview values or add custom value'} setVariableDefaultValue(value)} options={previewValues.map((value) => ({ label: value, value, }))} /> )} {queryType === 'DYNAMIC' && ( Select Widgets to apply )}
); } export default VariableItem;