From 93a2eec88fadf7dacbc47aec557404c44cee287c Mon Sep 17 00:00:00 2001 From: Ishan Uniyal Date: Mon, 2 Mar 2026 07:42:28 +0530 Subject: [PATCH] feat: merge conflicts --- .../api/querySuggestions/getKeySuggestions.ts | 20 +- .../querySuggestions/getValueSuggestion.ts | 32 +- .../QueryV2/QuerySearch/QuerySearch.tsx | 40 +- .../Checkbox/Checkbox.styles.scss | 71 ++++ .../FilterRenderers/Checkbox/Checkbox.tsx | 370 ++++++++++++++---- .../useGetQueryKeyValueSuggestions.ts | 25 +- .../src/types/api/querySuggestions/types.ts | 1 + 7 files changed, 444 insertions(+), 115 deletions(-) diff --git a/frontend/src/api/querySuggestions/getKeySuggestions.ts b/frontend/src/api/querySuggestions/getKeySuggestions.ts index 50626dee8b..fd9f5a4bcf 100644 --- a/frontend/src/api/querySuggestions/getKeySuggestions.ts +++ b/frontend/src/api/querySuggestions/getKeySuggestions.ts @@ -1,5 +1,6 @@ import axios from 'api'; import { AxiosResponse } from 'axios'; +import store from 'store'; import { QueryKeyRequestProps, QueryKeySuggestionsResponseProps, @@ -17,6 +18,12 @@ export const getKeySuggestions = ( signalSource = '', } = props; + const { globalTime } = store.getState(); + const resolvedTimeRange = { + startUnixMilli: Math.floor(globalTime.minTime / 1000000), + endUnixMilli: Math.floor(globalTime.maxTime / 1000000), + }; + const encodedSignal = encodeURIComponent(signal); const encodedSearchText = encodeURIComponent(searchText); const encodedMetricName = encodeURIComponent(metricName); @@ -24,7 +31,14 @@ export const getKeySuggestions = ( const encodedFieldDataType = encodeURIComponent(fieldDataType); const encodedSource = encodeURIComponent(signalSource); - return axios.get( - `/fields/keys?signal=${encodedSignal}&searchText=${encodedSearchText}&metricName=${encodedMetricName}&fieldContext=${encodedFieldContext}&fieldDataType=${encodedFieldDataType}&source=${encodedSource}`, - ); + let url = `/fields/keys?signal=${encodedSignal}&searchText=${encodedSearchText}&metricName=${encodedMetricName}&fieldContext=${encodedFieldContext}&fieldDataType=${encodedFieldDataType}&source=${encodedSource}`; + + if (resolvedTimeRange.startUnixMilli !== undefined) { + url += `&startUnixMilli=${resolvedTimeRange.startUnixMilli}`; + } + if (resolvedTimeRange.endUnixMilli !== undefined) { + url += `&endUnixMilli=${resolvedTimeRange.endUnixMilli}`; + } + + return axios.get(url); }; diff --git a/frontend/src/api/querySuggestions/getValueSuggestion.ts b/frontend/src/api/querySuggestions/getValueSuggestion.ts index f91a40139b..1fff5fcb3a 100644 --- a/frontend/src/api/querySuggestions/getValueSuggestion.ts +++ b/frontend/src/api/querySuggestions/getValueSuggestion.ts @@ -1,5 +1,6 @@ import axios from 'api'; import { AxiosResponse } from 'axios'; +import store from 'store'; import { QueryKeyValueRequestProps, QueryKeyValueSuggestionsResponseProps, @@ -8,7 +9,20 @@ import { export const getValueSuggestions = ( props: QueryKeyValueRequestProps, ): Promise> => { - const { signal, key, searchText, signalSource, metricName } = props; + const { + signal, + key, + searchText, + signalSource, + metricName, + existingQuery, + } = props; + + const { globalTime } = store.getState(); + const resolvedTimeRange = { + startUnixMilli: Math.floor(globalTime.minTime / 1000000), + endUnixMilli: Math.floor(globalTime.maxTime / 1000000), + }; const encodedSignal = encodeURIComponent(signal); const encodedKey = encodeURIComponent(key); @@ -16,7 +30,17 @@ export const getValueSuggestions = ( const encodedSearchText = encodeURIComponent(searchText); const encodedSource = encodeURIComponent(signalSource || ''); - return axios.get( - `/fields/values?signal=${encodedSignal}&name=${encodedKey}&searchText=${encodedSearchText}&metricName=${encodedMetricName}&source=${encodedSource}`, - ); + let url = `/fields/values?signal=${encodedSignal}&name=${encodedKey}&searchText=${encodedSearchText}&metricName=${encodedMetricName}&source=${encodedSource}`; + + if (resolvedTimeRange.startUnixMilli !== undefined) { + url += `&startUnixMilli=${resolvedTimeRange.startUnixMilli}`; + } + if (resolvedTimeRange.endUnixMilli !== undefined) { + url += `&endUnixMilli=${resolvedTimeRange.endUnixMilli}`; + } + if (existingQuery) { + url += `&existingQuery=${encodeURIComponent(existingQuery)}`; + } + + return axios.get(url); }; diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx index 2d73ce0d7e..b7dbeaabe8 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx @@ -272,7 +272,6 @@ function QuerySearch({ metricName: debouncedMetricName ?? undefined, signalSource: signalSource as 'meter' | '', }); - if (response.data.data) { const { keys } = response.data.data; const options = generateOptions(keys); @@ -432,6 +431,7 @@ function QuerySearch({ } const sanitizedSearchText = searchText ? searchText?.trim() : ''; + const existingQuery = queryData.filter?.expression || ''; try { const response = await getValueSuggestions({ @@ -440,9 +440,9 @@ function QuerySearch({ signal: dataSource, signalSource: signalSource as 'meter' | '', metricName: debouncedMetricName ?? undefined, - }); + existingQuery, + }); // Skip updates if component unmounted or key changed - // Skip updates if component unmounted or key changed if ( !isMountedRef.current || lastKeyRef.current !== key || @@ -454,7 +454,9 @@ function QuerySearch({ // Process the response data const responseData = response.data as any; const values = responseData.data?.values || {}; - const stringValues = values.stringValues || []; + const relatedValues = values.relatedValues || []; + const stringValues = + relatedValues.length > 0 ? relatedValues : values.stringValues || []; const numberValues = values.numberValues || []; // Generate options from string values - explicitly handle empty strings @@ -529,11 +531,12 @@ function QuerySearch({ }, [ activeKey, - dataSource, isLoadingSuggestions, - debouncedMetricName, - signalSource, + queryData.filter?.expression, toggleSuggestions, + dataSource, + signalSource, + debouncedMetricName, ], ); @@ -1240,19 +1243,17 @@ function QuerySearch({ if (!queryContext) { return; } - // Trigger suggestions based on context - if (editorRef.current) { + // Only trigger suggestions and fetch if editor is focused (i.e., user is interacting) + if (isFocused && editorRef.current) { toggleSuggestions(10); - } - - // Handle value suggestions for value context - if (queryContext.isInValue) { - const { keyToken, currentToken } = queryContext; - const key = keyToken || currentToken; - - // Only fetch if needed and if we have a valid key - if (key && key !== activeKey && !isLoadingSuggestions) { - fetchValueSuggestions({ key }); + // Handle value suggestions for value context + if (queryContext.isInValue) { + const { keyToken, currentToken } = queryContext; + const key = keyToken || currentToken; + // Only fetch if needed and if we have a valid key + if (key && key !== activeKey && !isLoadingSuggestions) { + fetchValueSuggestions({ key }); + } } } }, [ @@ -1261,6 +1262,7 @@ function QuerySearch({ isLoadingSuggestions, activeKey, fetchValueSuggestions, + isFocused, ]); const getTooltipContent = (): JSX.Element => ( diff --git a/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.styles.scss b/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.styles.scss index 6d614ded70..2f10e10123 100644 --- a/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.styles.scss +++ b/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.styles.scss @@ -138,6 +138,77 @@ cursor: pointer; } } + + .search-prompt { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 10px 12px; + margin-top: 4px; + border: 1px dashed #d79a3f; + border-radius: 10px; + color: #ffdca0; + background: linear-gradient(90deg, #2d2413 0%, #3a2d15 100%); + cursor: pointer; + transition: all 0.16s ease, transform 0.12s ease; + text-align: left; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35); + + &:hover { + background: linear-gradient(90deg, #3b2f18 0%, #47371c 100%); + box-shadow: 0 4px 18px rgba(0, 0, 0, 0.45); + } + + &:active { + transform: translateY(1px); + } + + &__icon { + color: #f3b45a; + flex-shrink: 0; + } + + &__text { + display: flex; + flex-direction: column; + gap: 2px; + } + + &__title { + color: #ffdca0; + } + + &__subtitle { + color: #f0c578; + font-size: 12px; + } + } + .lightMode & { + .search-prompt { + border: 1px dashed #f3b45a; + color: #b86b00; + background: linear-gradient(90deg, #fff6e9 0%, #fff0d9 100%); + box-shadow: 0 2px 12px rgba(184, 107, 0, 0.08); + + &:hover { + background: linear-gradient(90deg, #ffecc8 0%, #ffe3b0 100%); + box-shadow: 0 4px 16px rgba(184, 107, 0, 0.15); + } + + &__icon { + color: #d17c00; + } + + &__title { + color: #b86b00; + } + + &__subtitle { + color: #8a5a0d; + } + } + } .go-to-docs { display: flex; flex-direction: column; diff --git a/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.tsx b/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.tsx index 72a82707ad..c8d1629da5 100644 --- a/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.tsx +++ b/frontend/src/components/QuickFilters/FilterRenderers/Checkbox/Checkbox.tsx @@ -1,6 +1,15 @@ /* eslint-disable sonarjs/no-identical-functions */ -import { Fragment, useMemo, useState } from 'react'; -import { Button, Checkbox, Input, Skeleton, Typography } from 'antd'; +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import { + Fragment, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { Button, Checkbox, Input, InputRef, Skeleton, Typography } from 'antd'; import cx from 'classnames'; import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils'; import { @@ -8,19 +17,14 @@ import { QuickFiltersSource, } from 'components/QuickFilters/types'; import { OPERATORS } from 'constants/antlrQueryConstants'; -import { - DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY, - PANEL_TYPES, -} from 'constants/queryBuilder'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig'; import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; -import { useGetAggregateValues } from 'hooks/queryBuilder/useGetAggregateValues'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useGetQueryKeyValueSuggestions } from 'hooks/querySuggestions/useGetQueryKeyValueSuggestions'; import useDebouncedFn from 'hooks/useDebouncedFunction'; import { cloneDeep, isArray, isEqual, isFunction } from 'lodash-es'; -import { ChevronDown, ChevronRight } from 'lucide-react'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { AlertTriangle, ChevronDown, ChevronRight } from 'lucide-react'; import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; import { v4 as uuid } from 'uuid'; @@ -57,6 +61,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element { // null = no user action, true = user opened, false = user closed const [userToggleState, setUserToggleState] = useState(null); const [visibleItemsCount, setVisibleItemsCount] = useState(10); + const [visibleUncheckedCount, setVisibleUncheckedCount] = useState(5); const { lastUsedQuery, @@ -78,6 +83,12 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element { return lastUsedQuery || 0; }, [isListView, source, lastUsedQuery]); + // Extract current filter expression for the active query + const currentFilterExpression = useMemo(() => { + const queryData = currentQuery.builder.queryData?.[activeQueryIndex]; + return queryData?.filter?.expression || ''; + }, [currentQuery.builder.queryData, activeQueryIndex]); + // Check if this filter has active filters in the query const isSomeFilterPresentForCurrentAttribute = useMemo( () => @@ -109,54 +120,125 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element { filter.defaultOpen, ]); - const { data, isLoading } = useGetAggregateValues( - { - aggregateOperator: filter.aggregateOperator || 'noop', - dataSource: filter.dataSource || DataSource.LOGS, - aggregateAttribute: filter.aggregateAttribute || '', - attributeKey: filter.attributeKey.key, - filterAttributeKeyDataType: filter.attributeKey.dataType || DataTypes.EMPTY, - tagType: filter.attributeKey.type || '', - searchText: searchText ?? '', - }, - { - enabled: isOpen && source !== QuickFiltersSource.METER_EXPLORER, - keepPreviousData: true, - }, - ); - const { data: keyValueSuggestions, isLoading: isLoadingKeyValueSuggestions, + refetch: refetchKeyValueSuggestions, } = useGetQueryKeyValueSuggestions({ key: filter.attributeKey.key, signal: filter.dataSource || DataSource.LOGS, signalSource: 'meter', + searchText: searchText || '', + existingQuery: currentFilterExpression, options: { - enabled: isOpen && source === QuickFiltersSource.METER_EXPLORER, + enabled: isOpen, keepPreviousData: true, }, }); - const attributeValues: string[] = useMemo(() => { - const dataType = filter.attributeKey.dataType || DataTypes.String; + const searchInputRef = useRef(null); + const searchContainerRef = useRef(null); + const previousFiltersItemsRef = useRef( + currentQuery.builder.queryData?.[activeQueryIndex]?.filters?.items, + ); - if (source === QuickFiltersSource.METER_EXPLORER && keyValueSuggestions) { - // Process the response data + // Refetch when other filters change (not this filter) + // Watch for when filters.items is different from previous value, indicating other filters changed + useEffect(() => { + const currentFiltersItems = + currentQuery.builder.queryData?.[activeQueryIndex]?.filters?.items; + + const previousFiltersItems = previousFiltersItemsRef.current; + + // Check if filters items have changed (not the same) + const filtersChanged = !isEqual(previousFiltersItems, currentFiltersItems); + + if (isOpen && filtersChanged) { + // Check if OTHER filters (not this filter) have changed + const currentOtherFilters = currentFiltersItems?.filter( + (item) => !isEqual(item.key?.key, filter.attributeKey.key), + ); + const previousOtherFilters = previousFiltersItems?.filter( + (item) => !isEqual(item.key?.key, filter.attributeKey.key), + ); + + // Refetch if other filters changed (not just this filter's values) + const otherFiltersChanged = !isEqual( + currentOtherFilters, + previousOtherFilters, + ); + + // Only update ref if we have valid API data or if filters actually changed + // Don't update if search returned 0 results to preserve unchecked values + const hasValidData = keyValueSuggestions && !isLoadingKeyValueSuggestions; + if (otherFiltersChanged || hasValidData) { + previousFiltersItemsRef.current = currentFiltersItems; + } + + if (otherFiltersChanged) { + refetchKeyValueSuggestions(); + } + } else { + previousFiltersItemsRef.current = currentFiltersItems; + } + }, [ + activeQueryIndex, + isOpen, + refetchKeyValueSuggestions, + filter.attributeKey.key, + currentQuery.builder.queryData, + keyValueSuggestions, + isLoadingKeyValueSuggestions, + ]); + + const handleSearchPromptClick = useCallback((): void => { + if (searchContainerRef.current) { + searchContainerRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }); + } + if (searchInputRef.current) { + setTimeout(() => searchInputRef.current?.focus({ cursor: 'end' }), 120); + } + }, []); + + const isDataComplete = useMemo(() => { + if (keyValueSuggestions) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const responseData = keyValueSuggestions?.data as any; + return responseData.data?.complete || false; + } + return false; + }, [keyValueSuggestions]); + + const previousUncheckedValuesRef = useRef([]); + + const { attributeValues, relatedValuesSet } = useMemo(() => { + if (keyValueSuggestions) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const responseData = keyValueSuggestions?.data as any; const values = responseData.data?.values || {}; - const stringValues = values.stringValues || []; - const numberValues = values.numberValues || []; + const relatedValues: string[] = values.relatedValues || []; + const stringValues: string[] = values.stringValues || []; + const numberValues: number[] = values.numberValues || []; - // Generate options from string values - explicitly handle empty strings - const stringOptions = stringValues - // Strict filtering for empty string - we'll handle it as a special case if needed - .filter( - (value: string | null | undefined): value is string => - value !== null && value !== undefined && value !== '', - ); + const valuesToUse = [ + ...relatedValues, + ...stringValues.filter( + (value: string | null | undefined) => + value !== null && + value !== undefined && + value !== '' && + !relatedValues.includes(value), + ), + ]; + + const stringOptions = valuesToUse.filter( + (value: string | null | undefined): value is string => + value !== null && value !== undefined && value !== '', + ); - // Generate options from number values const numberOptions = numberValues .filter( (value: number | null | undefined): value is number => @@ -164,15 +246,27 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element { ) .map((value: number) => value.toString()); - // Combine all options and make sure we don't have duplicate labels - return [...stringOptions, ...numberOptions]; - } + const filteredRelated = new Set( + relatedValues.filter( + (v): v is string => v !== null && v !== undefined && v !== '', + ), + ); - const key = DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY[dataType]; - return (data?.payload?.[key] || []).filter( - (val) => val !== undefined && val !== null, - ); - }, [data?.payload, filter.attributeKey.dataType, keyValueSuggestions, source]); + const baseValues = [...stringOptions, ...numberOptions]; + const previousUnchecked = previousUncheckedValuesRef.current || []; + const preservedUnchecked = previousUnchecked.filter( + (value) => !baseValues.includes(value), + ); + return { + attributeValues: [...baseValues, ...preservedUnchecked], + relatedValuesSet: filteredRelated, + }; + } + return { + attributeValues: [] as string[], + relatedValuesSet: new Set(), + }; + }, [keyValueSuggestions]); const setSearchTextDebounced = useDebouncedFn((...args) => { setSearchText(args[0] as string); @@ -246,22 +340,51 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element { const isMultipleValuesTrueForTheKey = Object.values(currentFilterState).filter((val) => val).length > 1; - // Sort checked items to the top, then unchecked items - const currentAttributeKeys = useMemo(() => { + // Sort checked items to the top; always show unchecked items beneath, regardless of pagination + const { + visibleCheckedValues, + uncheckedValues, + visibleUncheckedValues, + visibleCheckedCount, + hasMoreChecked, + hasMoreUnchecked, + checkedSeparatorIndex, + } = useMemo(() => { const checkedValues = attributeValues.filter( (val) => currentFilterState[val], ); - const uncheckedValues = attributeValues.filter( - (val) => !currentFilterState[val], - ); - return [...checkedValues, ...uncheckedValues].slice(0, visibleItemsCount); - }, [attributeValues, currentFilterState, visibleItemsCount]); + const unchecked = attributeValues.filter((val) => !currentFilterState[val]); + const visibleChecked = checkedValues.slice(0, visibleItemsCount); + const visibleUnchecked = unchecked.slice(0, visibleUncheckedCount); - // Count of checked values in the currently visible items - const checkedValuesCount = useMemo( - () => currentAttributeKeys.filter((val) => currentFilterState[val]).length, - [currentAttributeKeys, currentFilterState], - ); + const findSeparatorIndex = (list: string[]): number => { + if (relatedValuesSet.size === 0) { + return -1; + } + const firstNonRelated = list.findIndex((v) => !relatedValuesSet.has(v)); + return firstNonRelated > 0 ? firstNonRelated : -1; + }; + + return { + visibleCheckedValues: visibleChecked, + uncheckedValues: unchecked, + visibleUncheckedValues: visibleUnchecked, + visibleCheckedCount: visibleChecked.length, + hasMoreChecked: checkedValues.length > visibleChecked.length, + hasMoreUnchecked: unchecked.length > visibleUnchecked.length, + checkedSeparatorIndex: findSeparatorIndex(visibleChecked), + }; + }, [ + attributeValues, + currentFilterState, + visibleItemsCount, + visibleUncheckedCount, + relatedValuesSet, + ]); + + useEffect(() => { + previousUncheckedValuesRef.current = uncheckedValues; + }, [uncheckedValues]); const handleClearFilterAttribute = (): void => { const preparedQuery: Query = { @@ -302,6 +425,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element { isOnlyOrAllClicked: boolean, // eslint-disable-next-line sonarjs/cognitive-complexity ): void => { + setVisibleUncheckedCount(5); const query = cloneDeep(currentQuery.builder.queryData?.[activeQueryIndex]); // if only or all are clicked we do not need to worry about anything just override whatever we have @@ -562,6 +686,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element { if (isOpen) { setUserToggleState(false); setVisibleItemsCount(10); + setVisibleUncheckedCount(5); } else { setUserToggleState(true); } @@ -590,35 +715,93 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element { )} - {isOpen && - (isLoading || isLoadingKeyValueSuggestions) && - !attributeValues.length && ( -
- -
- )} - {isOpen && !isLoading && !isLoadingKeyValueSuggestions && ( + {isOpen && isLoadingKeyValueSuggestions && !attributeValues.length && ( +
+ +
+ )} + {isOpen && !isLoadingKeyValueSuggestions && ( <> {!isEmptyStateWithDocsEnabled && ( -
+
setSearchTextDebounced(e.target.value)} disabled={isFilterDisabled} + ref={searchInputRef} />
)} {attributeValues.length > 0 ? (
- {currentAttributeKeys.map((value: string, index: number) => ( + {visibleCheckedValues.map((value: string, index: number) => ( - {index === checkedValuesCount && checkedValuesCount > 0 && ( -
+ {index === checkedSeparatorIndex && ( +
)} +
+ onChange(value, e.target.checked, false)} + checked={currentFilterState[value]} + disabled={isFilterDisabled} + rootClassName="check-box" + /> + +
{ + if (isFilterDisabled) { + return; + } + onChange(value, currentFilterState[value], true); + }} + > +
+ {filter.customRendererForValue ? ( + filter.customRendererForValue(value) + ) : ( + + {String(value)} + + )} + + +
+
+ + ))} + + {hasMoreChecked && ( +
+ setVisibleItemsCount((prev) => prev + 10)} + > + Show More... + +
+ )} + + {visibleCheckedCount > 0 && uncheckedValues.length > 0 && ( +
+ )} + + {visibleUncheckedValues.map((value: string) => ( +
onChange(value, e.target.checked, false)} @@ -670,6 +853,17 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
))} + + {hasMoreUnchecked && ( +
+ setVisibleUncheckedCount((prev) => prev + 5)} + > + Show More... + +
+ )}
) : isEmptyStateWithDocsEnabled ? ( @@ -678,16 +872,18 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element { No values found{' '}
)} - {visibleItemsCount < attributeValues?.length && ( -
- setVisibleItemsCount((prev) => prev + 10)} - > - Show More... - -
- )} + {visibleItemsCount >= attributeValues?.length && + attributeValues?.length > 0 && + !isDataComplete && ( +
+ + + + Tap to search and load more suggestions. + + +
+ )} )} diff --git a/frontend/src/hooks/querySuggestions/useGetQueryKeyValueSuggestions.ts b/frontend/src/hooks/querySuggestions/useGetQueryKeyValueSuggestions.ts index 224f80205f..6299bdec5c 100644 --- a/frontend/src/hooks/querySuggestions/useGetQueryKeyValueSuggestions.ts +++ b/frontend/src/hooks/querySuggestions/useGetQueryKeyValueSuggestions.ts @@ -1,7 +1,11 @@ +/* eslint-disable no-restricted-imports */ import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; +import { useSelector } from 'react-redux'; import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion'; import { AxiosError, AxiosResponse } from 'axios'; +import { AppState } from 'store/reducers'; import { QueryKeyValueSuggestionsResponseProps } from 'types/api/querySuggestions/types'; +import { GlobalReducer } from 'types/reducer/globalTime'; export const useGetQueryKeyValueSuggestions = ({ key, @@ -9,6 +13,7 @@ export const useGetQueryKeyValueSuggestions = ({ searchText, signalSource, metricName, + existingQuery, options, }: { key: string; @@ -20,11 +25,24 @@ export const useGetQueryKeyValueSuggestions = ({ AxiosError >; metricName?: string; + existingQuery?: string; }): UseQueryResult< AxiosResponse, AxiosError -> => - useQuery, AxiosError>({ +> => { + const { minTime, maxTime } = useSelector( + (state) => state.globalTime, + ); + + const timeRangeKey = + minTime != null && maxTime != null + ? `${Math.floor(minTime / 1e9)}-${Math.floor(maxTime / 1e9)}` + : null; + + return useQuery< + AxiosResponse, + AxiosError + >({ queryKey: [ 'queryKeyValueSuggestions', key, @@ -32,6 +50,7 @@ export const useGetQueryKeyValueSuggestions = ({ searchText, signalSource, metricName, + timeRangeKey, ], queryFn: () => getValueSuggestions({ @@ -40,6 +59,8 @@ export const useGetQueryKeyValueSuggestions = ({ searchText: searchText || '', signalSource: signalSource as 'meter' | '', metricName: metricName || '', + existingQuery, }), ...options, }); +}; diff --git a/frontend/src/types/api/querySuggestions/types.ts b/frontend/src/types/api/querySuggestions/types.ts index 4d6d7609c6..c5871c3527 100644 --- a/frontend/src/types/api/querySuggestions/types.ts +++ b/frontend/src/types/api/querySuggestions/types.ts @@ -47,6 +47,7 @@ export interface QueryKeyValueRequestProps { searchText: string; signalSource?: 'meter' | ''; metricName?: string; + existingQuery?: string; } export type SignalType = 'traces' | 'logs' | 'metrics';