mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-18 05:55:57 +08:00
feat: add suggestion to order by filter (#3162)
* feat: add suggestion to order by filter * fix: column name for order by * fix: mapper for order by * fix: render order by for different panels * fix: order by timestamp and aggrigate value --------- Co-authored-by: Palash Gupta <palashgdev@gmail.com> Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
This commit is contained in:
parent
5a2a987a9b
commit
98a2ef4080
72
frontend/src/container/ExplorerOrderBy/index.tsx
Normal file
72
frontend/src/container/ExplorerOrderBy/index.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { Select, Spin } from 'antd';
|
||||||
|
import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces';
|
||||||
|
import { useOrderByFilter } from 'container/QueryBuilder/filters/OrderByFilter/useOrderByFilter';
|
||||||
|
import { selectStyle } from 'container/QueryBuilder/filters/QueryBuilderSearch/config';
|
||||||
|
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||||
|
import { memo, useMemo } from 'react';
|
||||||
|
import { StringOperators } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
function ExplorerOrderBy({ query, onChange }: OrderByFilterProps): JSX.Element {
|
||||||
|
const {
|
||||||
|
debouncedSearchText,
|
||||||
|
selectedValue,
|
||||||
|
aggregationOptions,
|
||||||
|
generateOptions,
|
||||||
|
createOptions,
|
||||||
|
handleChange,
|
||||||
|
handleSearchKeys,
|
||||||
|
} = useOrderByFilter({ query, onChange });
|
||||||
|
|
||||||
|
const { data, isFetching } = useGetAggregateKeys(
|
||||||
|
{
|
||||||
|
aggregateAttribute: query.aggregateAttribute.key,
|
||||||
|
dataSource: query.dataSource,
|
||||||
|
aggregateOperator: query.aggregateOperator,
|
||||||
|
searchText: debouncedSearchText,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keepPreviousData: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const options = useMemo(() => {
|
||||||
|
const keysOptions = createOptions(data?.payload?.attributeKeys || []);
|
||||||
|
|
||||||
|
const customOptions = createOptions([
|
||||||
|
{ key: 'timestamp', isColumn: true, type: null, dataType: null },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const baseOptions = [
|
||||||
|
...customOptions,
|
||||||
|
...(query.aggregateOperator === StringOperators.NOOP
|
||||||
|
? []
|
||||||
|
: aggregationOptions),
|
||||||
|
...keysOptions,
|
||||||
|
];
|
||||||
|
|
||||||
|
return generateOptions(baseOptions);
|
||||||
|
}, [
|
||||||
|
aggregationOptions,
|
||||||
|
createOptions,
|
||||||
|
data?.payload?.attributeKeys,
|
||||||
|
generateOptions,
|
||||||
|
query.aggregateOperator,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
mode="tags"
|
||||||
|
style={selectStyle}
|
||||||
|
onSearch={handleSearchKeys}
|
||||||
|
showSearch
|
||||||
|
showArrow={false}
|
||||||
|
value={selectedValue}
|
||||||
|
labelInValue
|
||||||
|
options={options}
|
||||||
|
notFoundContent={isFetching ? <Spin size="small" /> : null}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(ExplorerOrderBy);
|
@ -1,13 +1,15 @@
|
|||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import ExplorerOrderBy from 'container/ExplorerOrderBy';
|
||||||
import { QueryBuilder } from 'container/QueryBuilder';
|
import { QueryBuilder } from 'container/QueryBuilder';
|
||||||
|
import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces';
|
||||||
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
||||||
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||||
import { ButtonWrapperStyled } from 'pages/LogsExplorer/styles';
|
import { ButtonWrapperStyled } from 'pages/LogsExplorer/styles';
|
||||||
import { prepareQueryWithDefaultTimestamp } from 'pages/LogsExplorer/utils';
|
import { prepareQueryWithDefaultTimestamp } from 'pages/LogsExplorer/utils';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
function LogExplorerQuerySection(): JSX.Element {
|
function LogExplorerQuerySection(): JSX.Element {
|
||||||
@ -23,6 +25,7 @@ function LogExplorerQuerySection(): JSX.Element {
|
|||||||
}, [updateAllQueriesOperators]);
|
}, [updateAllQueriesOperators]);
|
||||||
|
|
||||||
useShareBuilderUrl(defaultValue);
|
useShareBuilderUrl(defaultValue);
|
||||||
|
|
||||||
const filterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => {
|
const filterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => {
|
||||||
const isTable = panelTypes === PANEL_TYPES.TABLE;
|
const isTable = panelTypes === PANEL_TYPES.TABLE;
|
||||||
const config: QueryBuilderProps['filterConfigs'] = {
|
const config: QueryBuilderProps['filterConfigs'] = {
|
||||||
@ -32,11 +35,26 @@ function LogExplorerQuerySection(): JSX.Element {
|
|||||||
return config;
|
return config;
|
||||||
}, [panelTypes]);
|
}, [panelTypes]);
|
||||||
|
|
||||||
|
const renderOrderBy = useCallback(
|
||||||
|
({ query, onChange }: OrderByFilterProps): JSX.Element => (
|
||||||
|
<ExplorerOrderBy query={query} onChange={onChange} />
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const queryComponents = useMemo(
|
||||||
|
(): QueryBuilderProps['queryComponents'] => ({
|
||||||
|
...(panelTypes === PANEL_TYPES.LIST ? { renderOrderBy } : {}),
|
||||||
|
}),
|
||||||
|
[panelTypes, renderOrderBy],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryBuilder
|
<QueryBuilder
|
||||||
panelType={panelTypes}
|
panelType={panelTypes}
|
||||||
config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }}
|
config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }}
|
||||||
filterConfigs={filterConfigs}
|
filterConfigs={filterConfigs}
|
||||||
|
queryComponents={queryComponents}
|
||||||
actions={
|
actions={
|
||||||
<ButtonWrapperStyled>
|
<ButtonWrapperStyled>
|
||||||
<Button type="primary" onClick={handleRunQuery}>
|
<Button type="primary" onClick={handleRunQuery}>
|
||||||
|
@ -3,6 +3,7 @@ import LogDetail from 'components/LogDetail';
|
|||||||
import TabLabel from 'components/TabLabel';
|
import TabLabel from 'components/TabLabel';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import {
|
import {
|
||||||
|
initialAutocompleteData,
|
||||||
initialQueriesMap,
|
initialQueriesMap,
|
||||||
OPERATORS,
|
OPERATORS,
|
||||||
PANEL_TYPES,
|
PANEL_TYPES,
|
||||||
@ -17,6 +18,7 @@ import LogsExplorerChart from 'container/LogsExplorerChart';
|
|||||||
import LogsExplorerList from 'container/LogsExplorerList';
|
import LogsExplorerList from 'container/LogsExplorerList';
|
||||||
import LogsExplorerTable from 'container/LogsExplorerTable';
|
import LogsExplorerTable from 'container/LogsExplorerTable';
|
||||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||||
|
import { SIGNOZ_VALUE } from 'container/QueryBuilder/filters/OrderByFilter/constants';
|
||||||
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
||||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||||
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
|
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
|
||||||
@ -73,6 +75,7 @@ function LogsExplorerViews(): JSX.Element {
|
|||||||
stagedQuery,
|
stagedQuery,
|
||||||
panelType,
|
panelType,
|
||||||
updateAllQueriesOperators,
|
updateAllQueriesOperators,
|
||||||
|
updateQueriesData,
|
||||||
redirectWithQueryBuilderData,
|
redirectWithQueryBuilderData,
|
||||||
} = useQueryBuilder();
|
} = useQueryBuilder();
|
||||||
|
|
||||||
@ -175,26 +178,40 @@ function LogsExplorerViews(): JSX.Element {
|
|||||||
setActiveLog(null);
|
setActiveLog(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const getUpdateQuery = useCallback(
|
||||||
|
(newPanelType: GRAPH_TYPES): Query => {
|
||||||
|
let query = updateAllQueriesOperators(
|
||||||
|
currentQuery,
|
||||||
|
newPanelType,
|
||||||
|
DataSource.TRACES,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newPanelType === PANEL_TYPES.LIST) {
|
||||||
|
query = updateQueriesData(query, 'queryData', (item) => ({
|
||||||
|
...item,
|
||||||
|
orderBy: item.orderBy.filter((item) => item.columnName !== SIGNOZ_VALUE),
|
||||||
|
aggregateAttribute: initialAutocompleteData,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
},
|
||||||
|
[currentQuery, updateAllQueriesOperators, updateQueriesData],
|
||||||
|
);
|
||||||
|
|
||||||
const handleChangeView = useCallback(
|
const handleChangeView = useCallback(
|
||||||
(newPanelType: string) => {
|
(type: string) => {
|
||||||
|
const newPanelType = type as GRAPH_TYPES;
|
||||||
|
|
||||||
if (newPanelType === panelType) return;
|
if (newPanelType === panelType) return;
|
||||||
|
|
||||||
const query = updateAllQueriesOperators(
|
const query = getUpdateQuery(newPanelType);
|
||||||
currentQuery,
|
|
||||||
newPanelType as GRAPH_TYPES,
|
|
||||||
DataSource.LOGS,
|
|
||||||
);
|
|
||||||
|
|
||||||
redirectWithQueryBuilderData(query, {
|
redirectWithQueryBuilderData(query, {
|
||||||
[queryParamNamesMap.panelTypes]: newPanelType,
|
[queryParamNamesMap.panelTypes]: newPanelType,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[panelType, getUpdateQuery, redirectWithQueryBuilderData],
|
||||||
currentQuery,
|
|
||||||
panelType,
|
|
||||||
updateAllQueriesOperators,
|
|
||||||
redirectWithQueryBuilderData,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const getRequestData = useCallback(
|
const getRequestData = useCallback(
|
||||||
|
@ -3,12 +3,12 @@ import getFromLocalstorage from 'api/browser/localstorage/get';
|
|||||||
import setToLocalstorage from 'api/browser/localstorage/set';
|
import setToLocalstorage from 'api/browser/localstorage/set';
|
||||||
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||||
import useDebounce from 'hooks/useDebounce';
|
import useDebounce from 'hooks/useDebounce';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useQueries, useQuery } from 'react-query';
|
import { useQueries } from 'react-query';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
import {
|
import {
|
||||||
BaseAutocompleteData,
|
BaseAutocompleteData,
|
||||||
@ -116,16 +116,12 @@ const useOptionsMenu = ({
|
|||||||
const {
|
const {
|
||||||
data: searchedAttributesData,
|
data: searchedAttributesData,
|
||||||
isFetching: isSearchedAttributesFetching,
|
isFetching: isSearchedAttributesFetching,
|
||||||
} = useQuery(
|
} = useGetAggregateKeys(
|
||||||
[QueryBuilderKeys.GET_AGGREGATE_KEYS, debouncedSearchText, isFocused],
|
|
||||||
async () =>
|
|
||||||
getAggregateKeys({
|
|
||||||
...initialQueryParams,
|
|
||||||
searchText: debouncedSearchText,
|
|
||||||
}),
|
|
||||||
{
|
{
|
||||||
enabled: isFocused,
|
...initialQueryParams,
|
||||||
|
searchText: debouncedSearchText,
|
||||||
},
|
},
|
||||||
|
{ queryKey: [debouncedSearchText, isFocused], enabled: isFocused },
|
||||||
);
|
);
|
||||||
|
|
||||||
const searchedAttributeKeys = useMemo(
|
const searchedAttributeKeys = useMemo(
|
||||||
|
@ -3,6 +3,8 @@ import { ReactNode } from 'react';
|
|||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { OrderByFilterProps } from './filters/OrderByFilter/OrderByFilter.interfaces';
|
||||||
|
|
||||||
export type QueryBuilderConfig =
|
export type QueryBuilderConfig =
|
||||||
| {
|
| {
|
||||||
queryVariant: 'static';
|
queryVariant: 'static';
|
||||||
@ -17,4 +19,5 @@ export type QueryBuilderProps = {
|
|||||||
filterConfigs?: Partial<
|
filterConfigs?: Partial<
|
||||||
Record<keyof IBuilderQuery, { isHidden: boolean; isDisabled: boolean }>
|
Record<keyof IBuilderQuery, { isHidden: boolean; isDisabled: boolean }>
|
||||||
>;
|
>;
|
||||||
|
queryComponents?: { renderOrderBy?: (props: OrderByFilterProps) => ReactNode };
|
||||||
};
|
};
|
||||||
|
@ -16,6 +16,7 @@ export const QueryBuilder = memo(function QueryBuilder({
|
|||||||
panelType: newPanelType,
|
panelType: newPanelType,
|
||||||
actions,
|
actions,
|
||||||
filterConfigs = {},
|
filterConfigs = {},
|
||||||
|
queryComponents,
|
||||||
}: QueryBuilderProps): JSX.Element {
|
}: QueryBuilderProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
currentQuery,
|
currentQuery,
|
||||||
@ -74,6 +75,7 @@ export const QueryBuilder = memo(function QueryBuilder({
|
|||||||
queryVariant={config?.queryVariant || 'dropdown'}
|
queryVariant={config?.queryVariant || 'dropdown'}
|
||||||
query={query}
|
query={query}
|
||||||
filterConfigs={filterConfigs}
|
filterConfigs={filterConfigs}
|
||||||
|
queryComponents={queryComponents}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
|
@ -6,4 +6,4 @@ export type QueryProps = {
|
|||||||
isAvailableToDisable: boolean;
|
isAvailableToDisable: boolean;
|
||||||
query: IBuilderQuery;
|
query: IBuilderQuery;
|
||||||
queryVariant: 'static' | 'dropdown';
|
queryVariant: 'static' | 'dropdown';
|
||||||
} & Pick<QueryBuilderProps, 'filterConfigs'>;
|
} & Pick<QueryBuilderProps, 'filterConfigs' | 'queryComponents'>;
|
||||||
|
@ -36,6 +36,7 @@ export const Query = memo(function Query({
|
|||||||
queryVariant,
|
queryVariant,
|
||||||
query,
|
query,
|
||||||
filterConfigs,
|
filterConfigs,
|
||||||
|
queryComponents,
|
||||||
}: QueryProps): JSX.Element {
|
}: QueryProps): JSX.Element {
|
||||||
const { panelType } = useQueryBuilder();
|
const { panelType } = useQueryBuilder();
|
||||||
const {
|
const {
|
||||||
@ -110,6 +111,17 @@ export const Query = memo(function Query({
|
|||||||
[handleChangeQueryData],
|
[handleChangeQueryData],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderOrderByFilter = useCallback((): ReactNode => {
|
||||||
|
if (queryComponents?.renderOrderBy) {
|
||||||
|
return queryComponents.renderOrderBy({
|
||||||
|
query,
|
||||||
|
onChange: handleChangeOrderByKeys,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return <OrderByFilter query={query} onChange={handleChangeOrderByKeys} />;
|
||||||
|
}, [queryComponents, query, handleChangeOrderByKeys]);
|
||||||
|
|
||||||
const renderAggregateEveryFilter = useCallback(
|
const renderAggregateEveryFilter = useCallback(
|
||||||
(): JSX.Element | null =>
|
(): JSX.Element | null =>
|
||||||
!filterConfigs?.stepInterval?.isHidden ? (
|
!filterConfigs?.stepInterval?.isHidden ? (
|
||||||
@ -167,9 +179,7 @@ export const Query = memo(function Query({
|
|||||||
<Col flex="5.93rem">
|
<Col flex="5.93rem">
|
||||||
<FilterLabel label="Order by" />
|
<FilterLabel label="Order by" />
|
||||||
</Col>
|
</Col>
|
||||||
<Col flex="1 1 12.5rem">
|
<Col flex="1 1 12.5rem">{renderOrderByFilter()}</Col>
|
||||||
<OrderByFilter query={query} onChange={handleChangeOrderByKeys} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
@ -225,9 +235,7 @@ export const Query = memo(function Query({
|
|||||||
<Col flex="5.93rem">
|
<Col flex="5.93rem">
|
||||||
<FilterLabel label="Order by" />
|
<FilterLabel label="Order by" />
|
||||||
</Col>
|
</Col>
|
||||||
<Col flex="1 1 12.5rem">
|
<Col flex="1 1 12.5rem">{renderOrderByFilter()}</Col>
|
||||||
<OrderByFilter query={query} onChange={handleChangeOrderByKeys} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
@ -238,11 +246,11 @@ export const Query = memo(function Query({
|
|||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
panelType,
|
panelType,
|
||||||
query,
|
|
||||||
isMetricsDataSource,
|
isMetricsDataSource,
|
||||||
handleChangeHavingFilter,
|
query,
|
||||||
handleChangeLimit,
|
handleChangeLimit,
|
||||||
handleChangeOrderByKeys,
|
handleChangeHavingFilter,
|
||||||
|
renderOrderByFilter,
|
||||||
renderAggregateEveryFilter,
|
renderAggregateEveryFilter,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
selectValueDivider,
|
selectValueDivider,
|
||||||
} from 'constants/queryBuilder';
|
} from 'constants/queryBuilder';
|
||||||
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
|
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
|
||||||
|
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||||
import useDebounce from 'hooks/useDebounce';
|
import useDebounce from 'hooks/useDebounce';
|
||||||
import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAutocompleteFromCustomValue';
|
import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAutocompleteFromCustomValue';
|
||||||
// ** Components
|
// ** Components
|
||||||
@ -14,7 +15,7 @@ import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAut
|
|||||||
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
|
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
|
||||||
import { isEqual, uniqWith } from 'lodash-es';
|
import { isEqual, uniqWith } from 'lodash-es';
|
||||||
import { memo, useCallback, useEffect, useState } from 'react';
|
import { memo, useCallback, useEffect, useState } from 'react';
|
||||||
import { useQuery, useQueryClient } from 'react-query';
|
import { useQueryClient } from 'react-query';
|
||||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { SelectOption } from 'types/common/select';
|
import { SelectOption } from 'types/common/select';
|
||||||
|
|
||||||
@ -38,16 +39,15 @@ export const GroupByFilter = memo(function GroupByFilter({
|
|||||||
|
|
||||||
const debouncedValue = useDebounce(searchText, DEBOUNCE_DELAY);
|
const debouncedValue = useDebounce(searchText, DEBOUNCE_DELAY);
|
||||||
|
|
||||||
const { isFetching } = useQuery(
|
const { isFetching } = useGetAggregateKeys(
|
||||||
[QueryBuilderKeys.GET_AGGREGATE_KEYS, debouncedValue, isFocused],
|
|
||||||
async () =>
|
|
||||||
getAggregateKeys({
|
|
||||||
aggregateAttribute: query.aggregateAttribute.key,
|
|
||||||
dataSource: query.dataSource,
|
|
||||||
aggregateOperator: query.aggregateOperator,
|
|
||||||
searchText: debouncedValue,
|
|
||||||
}),
|
|
||||||
{
|
{
|
||||||
|
aggregateAttribute: query.aggregateAttribute.key,
|
||||||
|
dataSource: query.dataSource,
|
||||||
|
aggregateOperator: query.aggregateOperator,
|
||||||
|
searchText: debouncedValue,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryKey: [debouncedValue, isFocused],
|
||||||
enabled: !disabled && isFocused,
|
enabled: !disabled && isFocused,
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
const keys = query.groupBy.reduce<string[]>((acc, item) => {
|
const keys = query.groupBy.reduce<string[]>((acc, item) => {
|
||||||
|
@ -1,208 +1,57 @@
|
|||||||
import { Select, Spin } from 'antd';
|
import { Select, Spin } from 'antd';
|
||||||
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||||
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
import { useMemo } from 'react';
|
||||||
import { IOption } from 'hooks/useResourceAttribute/types';
|
|
||||||
import { uniqWith } from 'lodash-es';
|
|
||||||
import * as Papa from 'papaparse';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
|
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import { selectStyle } from '../QueryBuilderSearch/config';
|
import { selectStyle } from '../QueryBuilderSearch/config';
|
||||||
import { getRemoveOrderFromValue } from '../QueryBuilderSearch/utils';
|
|
||||||
import { FILTERS } from './config';
|
|
||||||
import { OrderByFilterProps } from './OrderByFilter.interfaces';
|
import { OrderByFilterProps } from './OrderByFilter.interfaces';
|
||||||
import {
|
import { useOrderByFilter } from './useOrderByFilter';
|
||||||
checkIfKeyPresent,
|
|
||||||
getLabelFromValue,
|
|
||||||
mapLabelValuePairs,
|
|
||||||
orderByValueDelimiter,
|
|
||||||
splitOrderByFromString,
|
|
||||||
transformToOrderByStringValues,
|
|
||||||
} from './utils';
|
|
||||||
|
|
||||||
export function OrderByFilter({
|
export function OrderByFilter({
|
||||||
query,
|
query,
|
||||||
onChange,
|
onChange,
|
||||||
}: OrderByFilterProps): JSX.Element {
|
}: OrderByFilterProps): JSX.Element {
|
||||||
const [searchText, setSearchText] = useState<string>('');
|
const {
|
||||||
const [selectedValue, setSelectedValue] = useState<IOption[]>(
|
debouncedSearchText,
|
||||||
transformToOrderByStringValues(query.orderBy),
|
selectedValue,
|
||||||
|
aggregationOptions,
|
||||||
|
generateOptions,
|
||||||
|
createOptions,
|
||||||
|
handleChange,
|
||||||
|
handleSearchKeys,
|
||||||
|
} = useOrderByFilter({ query, onChange });
|
||||||
|
|
||||||
|
const { data, isFetching } = useGetAggregateKeys(
|
||||||
|
{
|
||||||
|
aggregateAttribute: query.aggregateAttribute.key,
|
||||||
|
dataSource: query.dataSource,
|
||||||
|
aggregateOperator: query.aggregateOperator,
|
||||||
|
searchText: debouncedSearchText,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: !!query.aggregateAttribute.key,
|
||||||
|
keepPreviousData: true,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, isFetching } = useQuery(
|
|
||||||
[QueryBuilderKeys.GET_AGGREGATE_KEYS, searchText],
|
|
||||||
async () =>
|
|
||||||
getAggregateKeys({
|
|
||||||
aggregateAttribute: query.aggregateAttribute.key,
|
|
||||||
dataSource: query.dataSource,
|
|
||||||
aggregateOperator: query.aggregateOperator,
|
|
||||||
searchText,
|
|
||||||
}),
|
|
||||||
{ enabled: !!query.aggregateAttribute.key, keepPreviousData: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSearchKeys = useCallback(
|
|
||||||
(searchText: string): void => setSearchText(searchText),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const noAggregationOptions = useMemo(
|
|
||||||
() =>
|
|
||||||
data?.payload?.attributeKeys
|
|
||||||
? mapLabelValuePairs(data?.payload?.attributeKeys).flat()
|
|
||||||
: [],
|
|
||||||
[data?.payload?.attributeKeys],
|
|
||||||
);
|
|
||||||
|
|
||||||
const aggregationOptions = useMemo(
|
|
||||||
() =>
|
|
||||||
mapLabelValuePairs(query.groupBy)
|
|
||||||
.flat()
|
|
||||||
.concat([
|
|
||||||
{
|
|
||||||
label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${FILTERS.ASC}`,
|
|
||||||
value: `${query.aggregateOperator}(${query.aggregateAttribute.key})${orderByValueDelimiter}${FILTERS.ASC}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${FILTERS.DESC}`,
|
|
||||||
value: `${query.aggregateOperator}(${query.aggregateAttribute.key})${orderByValueDelimiter}${FILTERS.DESC}`,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
[query.aggregateAttribute.key, query.aggregateOperator, query.groupBy],
|
|
||||||
);
|
|
||||||
|
|
||||||
const customValue: IOption[] = useMemo(() => {
|
|
||||||
if (!searchText) return [];
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: `${searchText} ${FILTERS.ASC}`,
|
|
||||||
value: `${searchText}${orderByValueDelimiter}${FILTERS.ASC}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: `${searchText} ${FILTERS.DESC}`,
|
|
||||||
value: `${searchText}${orderByValueDelimiter}${FILTERS.DESC}`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [searchText]);
|
|
||||||
|
|
||||||
const optionsData = useMemo(() => {
|
const optionsData = useMemo(() => {
|
||||||
|
const keyOptions = createOptions(data?.payload?.attributeKeys || []);
|
||||||
|
const groupByOptions = createOptions(query.groupBy);
|
||||||
const options =
|
const options =
|
||||||
query.aggregateOperator === MetricAggregateOperator.NOOP
|
query.aggregateOperator === MetricAggregateOperator.NOOP
|
||||||
? noAggregationOptions
|
? keyOptions
|
||||||
: aggregationOptions;
|
: [...groupByOptions, ...aggregationOptions];
|
||||||
|
|
||||||
const resultOption = [...customValue, ...options];
|
return generateOptions(options);
|
||||||
|
|
||||||
return resultOption.filter(
|
|
||||||
(option) =>
|
|
||||||
!getLabelFromValue(selectedValue).includes(
|
|
||||||
getRemoveOrderFromValue(option.value),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}, [
|
}, [
|
||||||
aggregationOptions,
|
aggregationOptions,
|
||||||
customValue,
|
createOptions,
|
||||||
noAggregationOptions,
|
data?.payload?.attributeKeys,
|
||||||
|
generateOptions,
|
||||||
query.aggregateOperator,
|
query.aggregateOperator,
|
||||||
selectedValue,
|
query.groupBy,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const getUniqValues = useCallback((values: IOption[]): IOption[] => {
|
|
||||||
const modifiedValues = values.map((item) => {
|
|
||||||
const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter });
|
|
||||||
if (!match) return { label: item.label, value: item.value };
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars
|
|
||||||
const [_, order] = match.data.flat() as string[];
|
|
||||||
if (order)
|
|
||||||
return {
|
|
||||||
label: item.label,
|
|
||||||
value: item.value,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: `${item.value} ${FILTERS.ASC}`,
|
|
||||||
value: `${item.value}${orderByValueDelimiter}${FILTERS.ASC}`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return uniqWith(
|
|
||||||
modifiedValues,
|
|
||||||
(current, next) =>
|
|
||||||
getRemoveOrderFromValue(current.value) ===
|
|
||||||
getRemoveOrderFromValue(next.value),
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getValidResult = useCallback(
|
|
||||||
(result: IOption[]): IOption[] =>
|
|
||||||
result.reduce<IOption[]>((acc, item) => {
|
|
||||||
if (item.value === FILTERS.ASC || item.value === FILTERS.DESC) return acc;
|
|
||||||
|
|
||||||
if (item.value.includes(FILTERS.ASC) || item.value.includes(FILTERS.DESC)) {
|
|
||||||
const splittedOrderBy = splitOrderByFromString(item.value);
|
|
||||||
|
|
||||||
if (splittedOrderBy) {
|
|
||||||
acc.push({
|
|
||||||
label: `${splittedOrderBy.columnName} ${splittedOrderBy.order}`,
|
|
||||||
value: `${splittedOrderBy.columnName}${orderByValueDelimiter}${splittedOrderBy.order}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
acc.push(item);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleChange = (values: IOption[]): void => {
|
|
||||||
const validResult = getValidResult(values);
|
|
||||||
const result = getUniqValues(validResult);
|
|
||||||
|
|
||||||
const orderByValues: OrderByPayload[] = result.map((item) => {
|
|
||||||
const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter });
|
|
||||||
|
|
||||||
if (!match) {
|
|
||||||
return {
|
|
||||||
columnName: item.value,
|
|
||||||
order: 'asc',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const [columnName, order] = match.data.flat() as string[];
|
|
||||||
|
|
||||||
const columnNameValue = checkIfKeyPresent(
|
|
||||||
columnName,
|
|
||||||
query.aggregateAttribute.key,
|
|
||||||
)
|
|
||||||
? '#SIGNOZ_VALUE'
|
|
||||||
: columnName;
|
|
||||||
|
|
||||||
const orderValue = order ?? 'asc';
|
|
||||||
|
|
||||||
return {
|
|
||||||
columnName: columnNameValue,
|
|
||||||
order: orderValue,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedValue: IOption[] = orderByValues.map((item) => ({
|
|
||||||
label: `${item.columnName} ${item.order}`,
|
|
||||||
value: `${item.columnName} ${item.order}`,
|
|
||||||
}));
|
|
||||||
|
|
||||||
setSelectedValue(selectedValue);
|
|
||||||
|
|
||||||
setSearchText('');
|
|
||||||
onChange(orderByValues);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isDisabledSelect = useMemo(
|
const isDisabledSelect = useMemo(
|
||||||
() =>
|
() =>
|
||||||
!query.aggregateAttribute.key ||
|
!query.aggregateAttribute.key ||
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export const SIGNOZ_VALUE = '#SIGNOZ_VALUE';
|
@ -0,0 +1,199 @@
|
|||||||
|
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
|
||||||
|
import useDebounce from 'hooks/useDebounce';
|
||||||
|
import { IOption } from 'hooks/useResourceAttribute/types';
|
||||||
|
import { isEqual, uniqWith } from 'lodash-es';
|
||||||
|
import * as Papa from 'papaparse';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
import { getRemoveOrderFromValue } from '../QueryBuilderSearch/utils';
|
||||||
|
import { FILTERS } from './config';
|
||||||
|
import { SIGNOZ_VALUE } from './constants';
|
||||||
|
import { OrderByFilterProps } from './OrderByFilter.interfaces';
|
||||||
|
import {
|
||||||
|
getLabelFromValue,
|
||||||
|
mapLabelValuePairs,
|
||||||
|
orderByValueDelimiter,
|
||||||
|
splitOrderByFromString,
|
||||||
|
transformToOrderByStringValues,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
type UseOrderByFilterResult = {
|
||||||
|
searchText: string;
|
||||||
|
debouncedSearchText: string;
|
||||||
|
selectedValue: IOption[];
|
||||||
|
aggregationOptions: IOption[];
|
||||||
|
generateOptions: (options: IOption[]) => IOption[];
|
||||||
|
createOptions: (data: BaseAutocompleteData[]) => IOption[];
|
||||||
|
handleChange: (values: IOption[]) => void;
|
||||||
|
handleSearchKeys: (search: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useOrderByFilter = ({
|
||||||
|
query,
|
||||||
|
onChange,
|
||||||
|
}: OrderByFilterProps): UseOrderByFilterResult => {
|
||||||
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
|
|
||||||
|
const debouncedSearchText = useDebounce(searchText, DEBOUNCE_DELAY);
|
||||||
|
|
||||||
|
const handleSearchKeys = useCallback(
|
||||||
|
(searchText: string): void => setSearchText(searchText),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getUniqValues = useCallback((values: IOption[]): IOption[] => {
|
||||||
|
const modifiedValues = values.map((item) => {
|
||||||
|
const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter });
|
||||||
|
if (!match) return { label: item.label, value: item.value };
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars
|
||||||
|
const [_, order] = match.data.flat() as string[];
|
||||||
|
if (order)
|
||||||
|
return {
|
||||||
|
label: item.label,
|
||||||
|
value: item.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: `${item.value} ${FILTERS.ASC}`,
|
||||||
|
value: `${item.value}${orderByValueDelimiter}${FILTERS.ASC}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return uniqWith(
|
||||||
|
modifiedValues,
|
||||||
|
(current, next) =>
|
||||||
|
getRemoveOrderFromValue(current.value) ===
|
||||||
|
getRemoveOrderFromValue(next.value),
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const customValue: IOption[] = useMemo(() => {
|
||||||
|
if (!searchText) return [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: `${searchText} ${FILTERS.ASC}`,
|
||||||
|
value: `${searchText}${orderByValueDelimiter}${FILTERS.ASC}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `${searchText} ${FILTERS.DESC}`,
|
||||||
|
value: `${searchText}${orderByValueDelimiter}${FILTERS.DESC}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [searchText]);
|
||||||
|
|
||||||
|
const selectedValue = useMemo(() => transformToOrderByStringValues(query), [
|
||||||
|
query,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const generateOptions = useCallback(
|
||||||
|
(options: IOption[]): IOption[] => {
|
||||||
|
const currentCustomValue = options.find(
|
||||||
|
(keyOption) =>
|
||||||
|
getRemoveOrderFromValue(keyOption.value) === debouncedSearchText,
|
||||||
|
)
|
||||||
|
? []
|
||||||
|
: customValue;
|
||||||
|
|
||||||
|
const result = [...currentCustomValue, ...options];
|
||||||
|
|
||||||
|
const uniqResult = uniqWith(result, isEqual);
|
||||||
|
|
||||||
|
return uniqResult.filter(
|
||||||
|
(option) =>
|
||||||
|
!getLabelFromValue(selectedValue).includes(
|
||||||
|
getRemoveOrderFromValue(option.value),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[customValue, debouncedSearchText, selectedValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getValidResult = useCallback(
|
||||||
|
(result: IOption[]): IOption[] =>
|
||||||
|
result.reduce<IOption[]>((acc, item) => {
|
||||||
|
if (item.value === FILTERS.ASC || item.value === FILTERS.DESC) return acc;
|
||||||
|
|
||||||
|
if (item.value.includes(FILTERS.ASC) || item.value.includes(FILTERS.DESC)) {
|
||||||
|
const splittedOrderBy = splitOrderByFromString(item.value);
|
||||||
|
|
||||||
|
if (splittedOrderBy) {
|
||||||
|
acc.push({
|
||||||
|
label: `${splittedOrderBy.columnName} ${splittedOrderBy.order}`,
|
||||||
|
value: `${splittedOrderBy.columnName}${orderByValueDelimiter}${splittedOrderBy.order}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.push(item);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChange = (values: IOption[]): void => {
|
||||||
|
const validResult = getValidResult(values);
|
||||||
|
const result = getUniqValues(validResult);
|
||||||
|
|
||||||
|
const orderByValues: OrderByPayload[] = result.map((item) => {
|
||||||
|
const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter });
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return {
|
||||||
|
columnName: item.value,
|
||||||
|
order: 'asc',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const [columnName, order] = match.data.flat() as string[];
|
||||||
|
|
||||||
|
const columnNameValue =
|
||||||
|
columnName === SIGNOZ_VALUE ? SIGNOZ_VALUE : columnName;
|
||||||
|
|
||||||
|
const orderValue = order ?? 'asc';
|
||||||
|
|
||||||
|
return {
|
||||||
|
columnName: columnNameValue,
|
||||||
|
order: orderValue,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setSearchText('');
|
||||||
|
onChange(orderByValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createOptions = useCallback(
|
||||||
|
(data: BaseAutocompleteData[]): IOption[] => mapLabelValuePairs(data).flat(),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const aggregationOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${FILTERS.ASC}`,
|
||||||
|
value: `${SIGNOZ_VALUE}${orderByValueDelimiter}${FILTERS.ASC}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${FILTERS.DESC}`,
|
||||||
|
value: `${SIGNOZ_VALUE}${orderByValueDelimiter}${FILTERS.DESC}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[query],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchText,
|
||||||
|
debouncedSearchText,
|
||||||
|
selectedValue,
|
||||||
|
aggregationOptions,
|
||||||
|
createOptions,
|
||||||
|
handleChange,
|
||||||
|
handleSearchKeys,
|
||||||
|
generateOptions,
|
||||||
|
};
|
||||||
|
};
|
@ -1,31 +1,32 @@
|
|||||||
import { IOption } from 'hooks/useResourceAttribute/types';
|
import { IOption } from 'hooks/useResourceAttribute/types';
|
||||||
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
|
|
||||||
import * as Papa from 'papaparse';
|
import * as Papa from 'papaparse';
|
||||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData';
|
import {
|
||||||
|
IBuilderQuery,
|
||||||
|
OrderByPayload,
|
||||||
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
import { FILTERS } from './config';
|
import { FILTERS } from './config';
|
||||||
|
import { SIGNOZ_VALUE } from './constants';
|
||||||
|
|
||||||
export const orderByValueDelimiter = '|';
|
export const orderByValueDelimiter = '|';
|
||||||
|
|
||||||
export const transformToOrderByStringValues = (
|
export const transformToOrderByStringValues = (
|
||||||
orderBy: OrderByPayload[],
|
query: IBuilderQuery,
|
||||||
): IOption[] => {
|
): IOption[] => {
|
||||||
const prepareSelectedValue: IOption[] = orderBy.reduce<IOption[]>(
|
const prepareSelectedValue: IOption[] = query.orderBy.map((item) => {
|
||||||
(acc, item) => {
|
if (item.columnName === SIGNOZ_VALUE) {
|
||||||
if (item.columnName === '#SIGNOZ_VALUE') return acc;
|
return {
|
||||||
|
label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${item.order}`,
|
||||||
const option: IOption = {
|
|
||||||
label: `${item.columnName} ${item.order}`,
|
|
||||||
value: `${item.columnName}${orderByValueDelimiter}${item.order}`,
|
value: `${item.columnName}${orderByValueDelimiter}${item.order}`,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
acc.push(option);
|
return {
|
||||||
|
label: `${item.columnName} ${item.order}`,
|
||||||
return acc;
|
value: `${item.columnName}${orderByValueDelimiter}${item.order}`,
|
||||||
},
|
};
|
||||||
[],
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return prepareSelectedValue;
|
return prepareSelectedValue;
|
||||||
};
|
};
|
||||||
@ -34,20 +35,15 @@ export function mapLabelValuePairs(
|
|||||||
arr: BaseAutocompleteData[],
|
arr: BaseAutocompleteData[],
|
||||||
): Array<IOption>[] {
|
): Array<IOption>[] {
|
||||||
return arr.map((item) => {
|
return arr.map((item) => {
|
||||||
const label = transformStringWithPrefix({
|
|
||||||
str: item.key,
|
|
||||||
prefix: item.type || '',
|
|
||||||
condition: !item.isColumn,
|
|
||||||
});
|
|
||||||
const value = item.key;
|
const value = item.key;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: `${label} asc`,
|
label: `${value} ${FILTERS.ASC}`,
|
||||||
value: `${value}${orderByValueDelimiter}asc`,
|
value: `${value}${orderByValueDelimiter}${FILTERS.ASC}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: `${label} desc`,
|
label: `${value} ${FILTERS.DESC}`,
|
||||||
value: `${value}${orderByValueDelimiter}desc`,
|
value: `${value}${orderByValueDelimiter}${FILTERS.DESC}`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
@ -58,6 +54,7 @@ export function getLabelFromValue(arr: IOption[]): string[] {
|
|||||||
const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter });
|
const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter });
|
||||||
if (match) {
|
if (match) {
|
||||||
const [key] = match.data as string[];
|
const [key] = match.data as string[];
|
||||||
|
|
||||||
return key[0];
|
return key[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import ExplorerOrderBy from 'container/ExplorerOrderBy';
|
||||||
import { QueryBuilder } from 'container/QueryBuilder';
|
import { QueryBuilder } from 'container/QueryBuilder';
|
||||||
|
import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces';
|
||||||
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
||||||
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import { ButtonWrapper, Container } from './styles';
|
import { ButtonWrapper, Container } from './styles';
|
||||||
@ -22,6 +24,22 @@ function QuerySection(): JSX.Element {
|
|||||||
return config;
|
return config;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const renderOrderBy = useCallback(
|
||||||
|
({ query, onChange }: OrderByFilterProps) => (
|
||||||
|
<ExplorerOrderBy query={query} onChange={onChange} />
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const queryComponents = useMemo((): QueryBuilderProps['queryComponents'] => {
|
||||||
|
const shouldRenderCustomOrderBy =
|
||||||
|
panelTypes === PANEL_TYPES.LIST || panelTypes === PANEL_TYPES.TRACE;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(shouldRenderCustomOrderBy ? { renderOrderBy } : {}),
|
||||||
|
};
|
||||||
|
}, [panelTypes, renderOrderBy]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<QueryBuilder
|
<QueryBuilder
|
||||||
@ -31,6 +49,7 @@ function QuerySection(): JSX.Element {
|
|||||||
initialDataSource: DataSource.TRACES,
|
initialDataSource: DataSource.TRACES,
|
||||||
}}
|
}}
|
||||||
filterConfigs={filterConfigs}
|
filterConfigs={filterConfigs}
|
||||||
|
queryComponents={queryComponents}
|
||||||
actions={
|
actions={
|
||||||
<ButtonWrapper>
|
<ButtonWrapper>
|
||||||
<Button onClick={handleRunQuery} type="primary">
|
<Button onClick={handleRunQuery} type="primary">
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
|
||||||
import { getAttributesValues } from 'api/queryBuilder/getAttributesValues';
|
import { getAttributesValues } from 'api/queryBuilder/getAttributesValues';
|
||||||
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
|
||||||
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
|
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
|
||||||
import {
|
import {
|
||||||
getRemovePrefixFromKey,
|
getRemovePrefixFromKey,
|
||||||
@ -10,12 +8,13 @@ import {
|
|||||||
import useDebounceValue from 'hooks/useDebounce';
|
import useDebounceValue from 'hooks/useDebounce';
|
||||||
import { isEqual, uniqWith } from 'lodash-es';
|
import { isEqual, uniqWith } from 'lodash-es';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
import { useDebounce } from 'react-use';
|
import { useDebounce } from 'react-use';
|
||||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { useGetAggregateKeys } from './useGetAggregateKeys';
|
||||||
|
|
||||||
type IuseFetchKeysAndValues = {
|
type IuseFetchKeysAndValues = {
|
||||||
keys: BaseAutocompleteData[];
|
keys: BaseAutocompleteData[];
|
||||||
results: string[];
|
results: string[];
|
||||||
@ -71,19 +70,15 @@ export const useFetchKeysAndValues = (
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, isFetching, status } = useQuery(
|
const { data, isFetching, status } = useGetAggregateKeys(
|
||||||
[QueryBuilderKeys.GET_AGGREGATE_KEYS, searchParams],
|
|
||||||
async () =>
|
|
||||||
getAggregateKeys({
|
|
||||||
searchText: searchKey,
|
|
||||||
dataSource: query.dataSource,
|
|
||||||
aggregateOperator: query.aggregateOperator,
|
|
||||||
aggregateAttribute: query.aggregateAttribute.key,
|
|
||||||
tagType: query.aggregateAttribute.type ?? null,
|
|
||||||
}),
|
|
||||||
{
|
{
|
||||||
enabled: isQueryEnabled,
|
searchText: searchKey,
|
||||||
|
dataSource: query.dataSource,
|
||||||
|
aggregateOperator: query.aggregateOperator,
|
||||||
|
aggregateAttribute: query.aggregateAttribute.key,
|
||||||
|
tagType: query.aggregateAttribute.type ?? null,
|
||||||
},
|
},
|
||||||
|
{ queryKey: [searchParams], enabled: isQueryEnabled },
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
34
frontend/src/hooks/queryBuilder/useGetAggregateKeys.ts
Normal file
34
frontend/src/hooks/queryBuilder/useGetAggregateKeys.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
||||||
|
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys';
|
||||||
|
import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
type UseGetAttributeKeys = (
|
||||||
|
requestData: IGetAttributeKeysPayload,
|
||||||
|
options?: UseQueryOptions<
|
||||||
|
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
||||||
|
>,
|
||||||
|
) => UseQueryResult<
|
||||||
|
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const useGetAggregateKeys: UseGetAttributeKeys = (
|
||||||
|
requestData,
|
||||||
|
options,
|
||||||
|
) => {
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (options?.queryKey && Array.isArray(options.queryKey)) {
|
||||||
|
return [QueryBuilderKeys.GET_AGGREGATE_KEYS, ...options.queryKey];
|
||||||
|
}
|
||||||
|
return [QueryBuilderKeys.GET_AGGREGATE_KEYS, requestData];
|
||||||
|
}, [options?.queryKey, requestData]);
|
||||||
|
|
||||||
|
return useQuery<SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse>({
|
||||||
|
queryKey,
|
||||||
|
queryFn: () => getAggregateKeys(requestData),
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
@ -2,11 +2,16 @@ import { Tabs } from 'antd';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import ExplorerCard from 'components/ExplorerCard';
|
import ExplorerCard from 'components/ExplorerCard';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
import {
|
||||||
|
initialAutocompleteData,
|
||||||
|
initialQueriesMap,
|
||||||
|
PANEL_TYPES,
|
||||||
|
} from 'constants/queryBuilder';
|
||||||
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import ExportPanel from 'container/ExportPanel';
|
import ExportPanel from 'container/ExportPanel';
|
||||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||||
|
import { SIGNOZ_VALUE } from 'container/QueryBuilder/filters/OrderByFilter/constants';
|
||||||
import QuerySection from 'container/TracesExplorer/QuerySection';
|
import QuerySection from 'container/TracesExplorer/QuerySection';
|
||||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||||
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
|
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
|
||||||
@ -17,6 +22,7 @@ import history from 'lib/history';
|
|||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { generatePath } from 'react-router-dom';
|
import { generatePath } from 'react-router-dom';
|
||||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import { ActionsWrapper, Container } from './styles';
|
import { ActionsWrapper, Container } from './styles';
|
||||||
@ -29,6 +35,7 @@ function TracesExplorer(): JSX.Element {
|
|||||||
currentQuery,
|
currentQuery,
|
||||||
panelType,
|
panelType,
|
||||||
updateAllQueriesOperators,
|
updateAllQueriesOperators,
|
||||||
|
updateQueriesData,
|
||||||
redirectWithQueryBuilderData,
|
redirectWithQueryBuilderData,
|
||||||
} = useQueryBuilder();
|
} = useQueryBuilder();
|
||||||
|
|
||||||
@ -141,26 +148,42 @@ function TracesExplorer(): JSX.Element {
|
|||||||
[exportDefaultQuery, notifications, updateDashboard],
|
[exportDefaultQuery, notifications, updateDashboard],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTabChange = useCallback(
|
const getUpdateQuery = useCallback(
|
||||||
(newPanelType: string): void => {
|
(newPanelType: GRAPH_TYPES): Query => {
|
||||||
if (panelType === newPanelType) return;
|
let query = updateAllQueriesOperators(
|
||||||
|
|
||||||
const query = updateAllQueriesOperators(
|
|
||||||
currentQuery,
|
currentQuery,
|
||||||
newPanelType as GRAPH_TYPES,
|
newPanelType,
|
||||||
DataSource.TRACES,
|
DataSource.TRACES,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
newPanelType === PANEL_TYPES.LIST ||
|
||||||
|
newPanelType === PANEL_TYPES.TRACE
|
||||||
|
) {
|
||||||
|
query = updateQueriesData(query, 'queryData', (item) => ({
|
||||||
|
...item,
|
||||||
|
orderBy: item.orderBy.filter((item) => item.columnName !== SIGNOZ_VALUE),
|
||||||
|
aggregateAttribute: initialAutocompleteData,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
},
|
||||||
|
[currentQuery, updateAllQueriesOperators, updateQueriesData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTabChange = useCallback(
|
||||||
|
(type: string): void => {
|
||||||
|
const newPanelType = type as GRAPH_TYPES;
|
||||||
|
if (panelType === newPanelType) return;
|
||||||
|
|
||||||
|
const query = getUpdateQuery(newPanelType);
|
||||||
|
|
||||||
redirectWithQueryBuilderData(query, {
|
redirectWithQueryBuilderData(query, {
|
||||||
[queryParamNamesMap.panelTypes]: newPanelType,
|
[queryParamNamesMap.panelTypes]: newPanelType,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[getUpdateQuery, panelType, redirectWithQueryBuilderData],
|
||||||
currentQuery,
|
|
||||||
panelType,
|
|
||||||
redirectWithQueryBuilderData,
|
|
||||||
updateAllQueriesOperators,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useShareBuilderUrl(defaultQuery);
|
useShareBuilderUrl(defaultQuery);
|
||||||
|
@ -70,6 +70,7 @@ export const QueryBuilderContext = createContext<QueryBuilderContextType>({
|
|||||||
handleRunQuery: () => {},
|
handleRunQuery: () => {},
|
||||||
resetStagedQuery: () => {},
|
resetStagedQuery: () => {},
|
||||||
updateAllQueriesOperators: () => initialQueriesMap.metrics,
|
updateAllQueriesOperators: () => initialQueriesMap.metrics,
|
||||||
|
updateQueriesData: () => initialQueriesMap.metrics,
|
||||||
initQueryBuilderData: () => {},
|
initQueryBuilderData: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -222,6 +223,22 @@ export function QueryBuilderProvider({
|
|||||||
[getElementWithActualOperator],
|
[getElementWithActualOperator],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const updateQueriesData = useCallback(
|
||||||
|
<T extends keyof QueryBuilderData>(
|
||||||
|
query: Query,
|
||||||
|
type: T,
|
||||||
|
updateCallback: (
|
||||||
|
item: QueryBuilderData[T][number],
|
||||||
|
index: number,
|
||||||
|
) => QueryBuilderData[T][number],
|
||||||
|
): Query => {
|
||||||
|
const result = query.builder[type].map(updateCallback);
|
||||||
|
|
||||||
|
return { ...query, builder: { ...query.builder, [type]: result } };
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const removeQueryBuilderEntityByIndex = useCallback(
|
const removeQueryBuilderEntityByIndex = useCallback(
|
||||||
(type: keyof QueryBuilderData, index: number) => {
|
(type: keyof QueryBuilderData, index: number) => {
|
||||||
setCurrentQuery((prevState) => {
|
setCurrentQuery((prevState) => {
|
||||||
@ -567,6 +584,7 @@ export function QueryBuilderProvider({
|
|||||||
handleRunQuery,
|
handleRunQuery,
|
||||||
resetStagedQuery,
|
resetStagedQuery,
|
||||||
updateAllQueriesOperators,
|
updateAllQueriesOperators,
|
||||||
|
updateQueriesData,
|
||||||
initQueryBuilderData,
|
initQueryBuilderData,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
@ -588,6 +606,7 @@ export function QueryBuilderProvider({
|
|||||||
handleRunQuery,
|
handleRunQuery,
|
||||||
resetStagedQuery,
|
resetStagedQuery,
|
||||||
updateAllQueriesOperators,
|
updateAllQueriesOperators,
|
||||||
|
updateQueriesData,
|
||||||
initQueryBuilderData,
|
initQueryBuilderData,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -192,6 +192,14 @@ export type QueryBuilderContextType = {
|
|||||||
panelType: GRAPH_TYPES,
|
panelType: GRAPH_TYPES,
|
||||||
dataSource: DataSource,
|
dataSource: DataSource,
|
||||||
) => Query;
|
) => Query;
|
||||||
|
updateQueriesData: <T extends keyof QueryBuilderData>(
|
||||||
|
query: Query,
|
||||||
|
type: T,
|
||||||
|
updateCallback: (
|
||||||
|
item: QueryBuilderData[T][number],
|
||||||
|
index: number,
|
||||||
|
) => QueryBuilderData[T][number],
|
||||||
|
) => Query;
|
||||||
initQueryBuilderData: (query: Query) => void;
|
initQueryBuilderData: (query: Query) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user