import { useMachine } from '@xstate/react'; import { QueryParams } from 'constants/query'; import ROUTES from 'constants/routes'; import { useSafeNavigate } from 'hooks/useSafeNavigate'; import useUrlQuery from 'hooks/useUrlQuery'; import { encode } from 'js-base64'; import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { FeatureKeys } from '../../constants/features'; import { useAppContext } from '../../providers/App/App'; import { whilelistedKeys } from './config'; import { ResourceContext } from './context'; import { ResourceAttributesFilterMachine } from './machine'; import { IResourceAttribute, IResourceAttributeProps, OptionsData, } from './types'; import { createQuery, getResourceAttributeQueriesFromURL, getResourceDeploymentKeys, GetTagKeys, GetTagValues, mappingWithRoutesAndKeys, OperatorSchema, } from './utils'; function ResourceProvider({ children }: Props): JSX.Element { const { pathname } = useLocation(); const [loading, setLoading] = useState(true); const [selectedQuery, setSelectedQueries] = useState([]); const [staging, setStaging] = useState([]); const [queries, setQueries] = useState( getResourceAttributeQueriesFromURL(), ); const { safeNavigate } = useSafeNavigate(); const urlQuery = useUrlQuery(); const [optionsData, setOptionsData] = useState({ mode: undefined, options: [], }); // Watch for URL query changes useEffect(() => { const queriesFromUrl = getResourceAttributeQueriesFromURL(); setQueries(queriesFromUrl); }, [urlQuery]); const handleLoading = (isLoading: boolean): void => { setLoading(isLoading); if (isLoading) { setOptionsData({ mode: undefined, options: [] }); } }; const { featureFlags } = useAppContext(); const dotMetricsEnabled = featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED) ?.active || false; const dispatchQueries = useCallback( (queries: IResourceAttribute[]): void => { urlQuery.set( QueryParams.resourceAttributes, encode(JSON.stringify(queries)), ); const generatedUrl = `${pathname}?${urlQuery.toString()}`; safeNavigate(generatedUrl); setQueries(queries); }, [pathname, safeNavigate, urlQuery], ); const [state, send] = useMachine(ResourceAttributesFilterMachine, { actions: { onSelectTagKey: () => { handleLoading(true); GetTagKeys(dotMetricsEnabled) .then((tagKeys) => { const options = mappingWithRoutesAndKeys(pathname, tagKeys); setOptionsData({ options, mode: undefined, }); }) .finally(() => { handleLoading(false); }); }, onSelectOperator: () => { setOptionsData({ options: OperatorSchema, mode: undefined }); }, onSelectTagValue: () => { handleLoading(true); GetTagValues(staging[0]) .then((tagValuesOptions) => setOptionsData({ options: tagValuesOptions, mode: 'multiple' }), ) .finally(() => { handleLoading(false); }); }, onBlurPurge: () => { setSelectedQueries([]); setStaging([]); }, onValidateQuery: (): void => { if (staging.length < 2 || selectedQuery.length === 0) { return; } const generatedQuery = createQuery([...staging, selectedQuery]); if (generatedQuery) { dispatchQueries([...queries, generatedQuery]); } }, }, }); const handleFocus = useCallback((): void => { if (state.value === 'Idle') { send('NEXT'); } }, [send, state.value]); const handleBlur = useCallback((): void => { send('onBlur'); }, [send]); const handleChange = useCallback( (value: string): void => { if (!optionsData.mode) { setStaging((prevStaging) => [...prevStaging, value]); setSelectedQueries([]); send('NEXT'); return; } setSelectedQueries([...value]); }, [optionsData.mode, send], ); const handleEnvironmentChange = useCallback( (environments: string[]): void => { const staging = [getResourceDeploymentKeys(dotMetricsEnabled), 'IN']; const queriesCopy = queries.filter( (query) => query.tagKey !== getResourceDeploymentKeys(dotMetricsEnabled), ); if (environments && Array.isArray(environments) && environments.length > 0) { const generatedQuery = createQuery([...staging, environments]); if (generatedQuery) { dispatchQueries([...queriesCopy, generatedQuery]); } } else { dispatchQueries([...queriesCopy]); } send('RESET'); }, [dispatchQueries, dotMetricsEnabled, queries, send], ); const handleClose = useCallback( (id: string): void => { dispatchQueries(queries.filter((queryData) => queryData.id !== id)); }, [dispatchQueries, queries], ); const handleClearAll = useCallback(() => { send('RESET'); dispatchQueries([]); setStaging([]); setQueries([]); setOptionsData({ mode: undefined, options: [] }); }, [dispatchQueries, send]); const getVisibleQueries = useMemo(() => { if (pathname === ROUTES.SERVICE_MAP) { return queries.filter((query) => whilelistedKeys.includes(query.tagKey)); } return queries; }, [queries, pathname]); const value: IResourceAttributeProps = useMemo( () => ({ queries: getVisibleQueries, staging, handleClearAll, handleClose, handleBlur, handleFocus, loading, handleChange, handleEnvironmentChange, selectedQuery, optionsData, }), [ handleBlur, handleChange, handleEnvironmentChange, handleClearAll, handleClose, handleFocus, loading, staging, selectedQuery, optionsData, getVisibleQueries, ], ); return ( {children} ); } interface Props { children: ReactNode; } export default ResourceProvider;