mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-15 17:55:56 +08:00
fix: issues on WHERE search filter (#2629)
* fix: search filter validation on data source * fix: value search not working for in/nin * fix: unwanted key api while searching value & disabled tag * fix: unnecessary , at end of in/nin value * fix: added space after operator to get value * fix: custom value not being selected * fix: space after tag and value * fix: api call debounce duration * fix: suggested changes * fix: updated query params * fix: search filter data for logs and traces * fix: search filter value data type issue * fix: search filter value tag type * fix: chip & iscolumn issue * fix: null handled * fix: label in list of search filter component * fix: label in list of search filter component * fix: code level changes * fix: incorrect filter operators * fix: key selection dancing * fix: missing suggestion * fix: keys are not getting updated * fix: strange behaviour - removed duplicate options * fix: driver id exclusion not working * fix: loader when 0 options * fix: exists/not-exists tag value issue * fix: some weird behaviour about exists * fix: added duplicate option remove logic at hook level * fix: removed empty options from list * fix: closable chip handler on edit * fix: search filter validation on data source * fix: value search not working for in/nin * fix: unwanted key api while searching value & disabled tag * fix: unnecessary , at end of in/nin value * fix: added space after operator to get value * fix: custom value not being selected * fix: space after tag and value * fix: api call debounce duration * fix: suggested changes * fix: updated query params * fix: search filter data for logs and traces * fix: search filter value data type issue * fix: search filter value tag type * fix: chip & iscolumn issue * fix: null handled * fix: label in list of search filter component * fix: label in list of search filter component * fix: code level changes * fix: incorrect filter operators * fix: key selection dancing * fix: missing suggestion * fix: keys are not getting updated * fix: strange behaviour - removed duplicate options * fix: driver id exclusion not working * fix: loader when 0 options * fix: exists/not-exists tag value issue * fix: some weird behaviour about exists * fix: added duplicate option remove logic at hook level * fix: removed empty options from list * fix: closable chip handler on edit * fix: search filter validation on data source * fix: lint issues is fixed * fix: chip & iscolumn issue * fix: lint changes are updated * fix: undefined case handled * fix: undefined case handled * fix: removed settimeout * fix: delete chip getting value undefined * fix: payload correctness * fix: incorrect value selection * fix: key text typing doesn't change anything * fix: search value issue * fix: payload updated * fix: auto populate value issue * fix: payload updated & populate values * fix: split value for in/nin * fix: split value getting undefined * fix: new version of search filter using papaparse library * fix: removed unwanted space before operator * fix: added exact find method & removed includes logic * fix: issue when user create chip for exists not exists operator * fix: white space logic removed * fix: allow custom key in from list * fix: issue when user create chip for exists not exists operator * fix: removed unwanted includes * fix: removed unwanted utils function * fix: replaced join with papa unparse * fix: removed get count of space utils * fix: resolved build issue * fix: code level fixes * fix: space after key * fix: quote a value if comma present * fix: handle custom key object onchange * chore: coverted into string * Merge branch 'develop' into fix/issue-search-filter * chore: eslint rule disabling is removed * fix: serviceName contains sql * chore: less restrictive expression * fix: custom key selection issue * chore: papa parse version is made exact --------- Co-authored-by: Palash Gupta <palashgdev@gmail.com> Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
This commit is contained in:
parent
10e47b5bff
commit
76331001b7
@ -69,6 +69,7 @@
|
|||||||
"less-loader": "^10.2.0",
|
"less-loader": "^10.2.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mini-css-extract-plugin": "2.4.5",
|
"mini-css-extract-plugin": "2.4.5",
|
||||||
|
"papaparse": "5.4.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-force-graph": "^1.41.0",
|
"react-force-graph": "^1.41.0",
|
||||||
@ -136,6 +137,7 @@
|
|||||||
"@types/lodash-es": "^4.17.4",
|
"@types/lodash-es": "^4.17.4",
|
||||||
"@types/mini-css-extract-plugin": "^2.5.1",
|
"@types/mini-css-extract-plugin": "^2.5.1",
|
||||||
"@types/node": "^16.10.3",
|
"@types/node": "^16.10.3",
|
||||||
|
"@types/papaparse": "5.3.7",
|
||||||
"@types/react": "18.0.26",
|
"@types/react": "18.0.26",
|
||||||
"@types/react-dom": "18.0.10",
|
"@types/react-dom": "18.0.10",
|
||||||
"@types/react-grid-layout": "^1.1.2",
|
"@types/react-grid-layout": "^1.1.2",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ApiV3Instance } from 'api';
|
import { ApiV3Instance } from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
import { AxiosError, AxiosResponse } from 'axios';
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
|
import createQueryParams from 'lib/createQueryParams';
|
||||||
// ** Helpers
|
// ** Helpers
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
// ** Types
|
// ** Types
|
||||||
@ -18,7 +19,11 @@ export const getAggregateAttribute = async ({
|
|||||||
const response: AxiosResponse<{
|
const response: AxiosResponse<{
|
||||||
data: IQueryAutocompleteResponse;
|
data: IQueryAutocompleteResponse;
|
||||||
}> = await ApiV3Instance.get(
|
}> = await ApiV3Instance.get(
|
||||||
`autocomplete/aggregate_attributes?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&searchText=${searchText}`,
|
`autocomplete/aggregate_attributes?${createQueryParams({
|
||||||
|
aggregateOperator,
|
||||||
|
searchText,
|
||||||
|
dataSource,
|
||||||
|
})}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ApiV3Instance } from 'api';
|
import { ApiV3Instance } from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
import { AxiosError, AxiosResponse } from 'axios';
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
|
import createQueryParams from 'lib/createQueryParams';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
// ** Types
|
// ** Types
|
||||||
import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys';
|
import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys';
|
||||||
@ -11,6 +12,7 @@ export const getAggregateKeys = async ({
|
|||||||
searchText,
|
searchText,
|
||||||
dataSource,
|
dataSource,
|
||||||
aggregateAttribute,
|
aggregateAttribute,
|
||||||
|
tagType,
|
||||||
}: IGetAttributeKeysPayload): Promise<
|
}: IGetAttributeKeysPayload): Promise<
|
||||||
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
||||||
> => {
|
> => {
|
||||||
@ -18,7 +20,12 @@ export const getAggregateKeys = async ({
|
|||||||
const response: AxiosResponse<{
|
const response: AxiosResponse<{
|
||||||
data: IQueryAutocompleteResponse;
|
data: IQueryAutocompleteResponse;
|
||||||
}> = await ApiV3Instance.get(
|
}> = await ApiV3Instance.get(
|
||||||
`autocomplete/attribute_keys?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&aggregateAttribute=${aggregateAttribute}&searchText=${searchText}`,
|
`autocomplete/attribute_keys?${createQueryParams({
|
||||||
|
aggregateOperator,
|
||||||
|
searchText,
|
||||||
|
dataSource,
|
||||||
|
aggregateAttribute,
|
||||||
|
})}&tagType=${tagType}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
import { ApiV3Instance } from 'api';
|
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
|
|
||||||
export type TagKeyValueProps = {
|
|
||||||
dataSource: string;
|
|
||||||
aggregateOperator?: string;
|
|
||||||
aggregateAttribute?: string;
|
|
||||||
searchText?: string;
|
|
||||||
attributeKey?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface AttributeKeyOptions {
|
|
||||||
key: string;
|
|
||||||
type: string;
|
|
||||||
dataType: 'string' | 'boolean' | 'number';
|
|
||||||
isColumn: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getAttributesKeys = async (
|
|
||||||
props: TagKeyValueProps,
|
|
||||||
): Promise<SuccessResponse<AttributeKeyOptions[]> | ErrorResponse> => {
|
|
||||||
try {
|
|
||||||
const response = await ApiV3Instance.get(
|
|
||||||
`/autocomplete/attribute_keys?aggregateOperator=${props.aggregateOperator}&dataSource=${props.dataSource}&aggregateAttribute=${props.aggregateAttribute}&searchText=${props.searchText}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data.data.attributeKeys,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorResponseHandler(error as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface TagValuePayloadProps {
|
|
||||||
boolAttributeValues: null | string[];
|
|
||||||
numberAttributeValues: null | string[];
|
|
||||||
stringAttributeValues: null | string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getAttributesValues = async (
|
|
||||||
props: TagKeyValueProps,
|
|
||||||
): Promise<SuccessResponse<TagValuePayloadProps> | ErrorResponse> => {
|
|
||||||
try {
|
|
||||||
const response = await ApiV3Instance.get(
|
|
||||||
`/autocomplete/attribute_values?aggregateOperator=${props.aggregateOperator}&dataSource=${props.dataSource}&aggregateAttribute=${props.aggregateAttribute}&searchText=${props.searchText}&attributeKey=${props.attributeKey}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data.data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorResponseHandler(error as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
|
42
frontend/src/api/queryBuilder/getAttributesValues.ts
Normal file
42
frontend/src/api/queryBuilder/getAttributesValues.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { ApiV3Instance } from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import createQueryParams from 'lib/createQueryParams';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import {
|
||||||
|
IAttributeValuesResponse,
|
||||||
|
IGetAttributeValuesPayload,
|
||||||
|
} from 'types/api/queryBuilder/getAttributesValues';
|
||||||
|
|
||||||
|
export const getAttributesValues = async ({
|
||||||
|
aggregateOperator,
|
||||||
|
dataSource,
|
||||||
|
aggregateAttribute,
|
||||||
|
attributeKey,
|
||||||
|
filterAttributeKeyDataType,
|
||||||
|
tagType,
|
||||||
|
searchText,
|
||||||
|
}: IGetAttributeValuesPayload): Promise<
|
||||||
|
SuccessResponse<IAttributeValuesResponse> | ErrorResponse
|
||||||
|
> => {
|
||||||
|
try {
|
||||||
|
const response = await ApiV3Instance.get(
|
||||||
|
`/autocomplete/attribute_values?${createQueryParams({
|
||||||
|
aggregateOperator,
|
||||||
|
dataSource,
|
||||||
|
aggregateAttribute,
|
||||||
|
attributeKey,
|
||||||
|
searchText,
|
||||||
|
})}&filterAttributeKeyDataType=${filterAttributeKeyDataType}&tagType=${tagType}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
@ -34,6 +34,7 @@ export const alphabet: string[] = alpha.map((str) => String.fromCharCode(str));
|
|||||||
export enum QueryBuilderKeys {
|
export enum QueryBuilderKeys {
|
||||||
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
||||||
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
|
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
|
||||||
|
GET_ATTRIBUTE_KEY = 'GET_ATTRIBUTE_KEY',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapOfOperators: Record<DataSource, string[]> = {
|
export const mapOfOperators: Record<DataSource, string[]> = {
|
||||||
@ -88,7 +89,7 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
|
|||||||
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
|
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
|
||||||
aggregateOperator: MetricAggregateOperator.NOOP,
|
aggregateOperator: MetricAggregateOperator.NOOP,
|
||||||
aggregateAttribute: initialAggregateAttribute,
|
aggregateAttribute: initialAggregateAttribute,
|
||||||
tagFilters: { items: [], op: 'AND' },
|
filters: { items: [], op: 'AND' },
|
||||||
expression: createNewBuilderItemName({
|
expression: createNewBuilderItemName({
|
||||||
existNames: [],
|
existNames: [],
|
||||||
sourceNames: alphabet,
|
sourceNames: alphabet,
|
||||||
@ -166,7 +167,7 @@ export const QUERY_BUILDER_OPERATORS_BY_TYPES = {
|
|||||||
OPERATORS.EXISTS,
|
OPERATORS.EXISTS,
|
||||||
OPERATORS.NOT_EXISTS,
|
OPERATORS.NOT_EXISTS,
|
||||||
],
|
],
|
||||||
number: [
|
int64: [
|
||||||
OPERATORS['='],
|
OPERATORS['='],
|
||||||
OPERATORS['!='],
|
OPERATORS['!='],
|
||||||
OPERATORS.IN,
|
OPERATORS.IN,
|
||||||
@ -178,7 +179,19 @@ export const QUERY_BUILDER_OPERATORS_BY_TYPES = {
|
|||||||
OPERATORS['<='],
|
OPERATORS['<='],
|
||||||
OPERATORS['<'],
|
OPERATORS['<'],
|
||||||
],
|
],
|
||||||
boolean: [
|
float64: [
|
||||||
|
OPERATORS['='],
|
||||||
|
OPERATORS['!='],
|
||||||
|
OPERATORS.IN,
|
||||||
|
OPERATORS.NIN,
|
||||||
|
OPERATORS.EXISTS,
|
||||||
|
OPERATORS.NOT_EXISTS,
|
||||||
|
OPERATORS['>='],
|
||||||
|
OPERATORS['>'],
|
||||||
|
OPERATORS['<='],
|
||||||
|
OPERATORS['<'],
|
||||||
|
],
|
||||||
|
bool: [
|
||||||
OPERATORS['='],
|
OPERATORS['='],
|
||||||
OPERATORS['!='],
|
OPERATORS['!='],
|
||||||
OPERATORS.EXISTS,
|
OPERATORS.EXISTS,
|
||||||
|
@ -25,7 +25,7 @@ export const getQueryBuilderQueries = ({
|
|||||||
aggregateAttribute: metricName,
|
aggregateAttribute: metricName,
|
||||||
legend,
|
legend,
|
||||||
reduceTo: 'sum',
|
reduceTo: 'sum',
|
||||||
tagFilters: {
|
filters: {
|
||||||
items: itemsA,
|
items: itemsA,
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
},
|
},
|
||||||
@ -60,7 +60,7 @@ export const getQueryBuilderQuerieswithFormula = ({
|
|||||||
legend,
|
legend,
|
||||||
aggregateAttribute: metricNameA,
|
aggregateAttribute: metricNameA,
|
||||||
reduceTo: 'sum',
|
reduceTo: 'sum',
|
||||||
tagFilters: {
|
filters: {
|
||||||
items: additionalItemsA,
|
items: additionalItemsA,
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
},
|
},
|
||||||
@ -75,7 +75,7 @@ export const getQueryBuilderQuerieswithFormula = ({
|
|||||||
queryName: 'B',
|
queryName: 'B',
|
||||||
expression: 'B',
|
expression: 'B',
|
||||||
reduceTo: 'sum',
|
reduceTo: 'sum',
|
||||||
tagFilters: {
|
filters: {
|
||||||
items: additionalItemsB,
|
items: additionalItemsB,
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
},
|
},
|
||||||
|
@ -81,8 +81,8 @@ export const Query = memo(function Query({
|
|||||||
}, [handleChangeQueryData, query]);
|
}, [handleChangeQueryData, query]);
|
||||||
|
|
||||||
const handleChangeTagFilters = useCallback(
|
const handleChangeTagFilters = useCallback(
|
||||||
(value: IBuilderQuery['tagFilters']) => {
|
(value: IBuilderQuery['filters']) => {
|
||||||
handleChangeQueryData('tagFilters', value);
|
handleChangeQueryData('filters', value);
|
||||||
},
|
},
|
||||||
[handleChangeQueryData],
|
[handleChangeQueryData],
|
||||||
);
|
);
|
||||||
|
@ -34,7 +34,6 @@ export function HavingFilter({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { isMulti } = useTagValidation(
|
const { isMulti } = useTagValidation(
|
||||||
searchText,
|
|
||||||
currentFormValue.op,
|
currentFormValue.op,
|
||||||
currentFormValue.value,
|
currentFormValue.value,
|
||||||
);
|
);
|
||||||
|
@ -1,15 +1,23 @@
|
|||||||
import { Select, Spin, Tag, Tooltip } from 'antd';
|
import { Select, Spin, Tag, Tooltip } from 'antd';
|
||||||
import { useAutoComplete } from 'hooks/queryBuilder/useAutoComplete';
|
import { useAutoComplete } from 'hooks/queryBuilder/useAutoComplete';
|
||||||
|
import { useFetchKeysAndValues } from 'hooks/queryBuilder/useFetchKeysAndValues';
|
||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
IBuilderQuery,
|
IBuilderQuery,
|
||||||
TagFilter,
|
TagFilter,
|
||||||
} from 'types/api/queryBuilder/queryBuilderData';
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import { selectStyle } from './config';
|
import { selectStyle } from './config';
|
||||||
import { StyledCheckOutlined, TypographyText } from './style';
|
import { StyledCheckOutlined, TypographyText } from './style';
|
||||||
import { isInNotInOperator } from './utils';
|
import {
|
||||||
|
getOperatorValue,
|
||||||
|
getRemovePrefixFromKey,
|
||||||
|
getTagToken,
|
||||||
|
isExistsNotExistsOperator,
|
||||||
|
isInNInOperator,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
function QueryBuilderSearch({
|
function QueryBuilderSearch({
|
||||||
query,
|
query,
|
||||||
@ -26,18 +34,27 @@ function QueryBuilderSearch({
|
|||||||
searchValue,
|
searchValue,
|
||||||
isMulti,
|
isMulti,
|
||||||
isFetching,
|
isFetching,
|
||||||
|
setSearchKey,
|
||||||
|
searchKey,
|
||||||
} = useAutoComplete(query);
|
} = useAutoComplete(query);
|
||||||
|
|
||||||
|
const { keys } = useFetchKeysAndValues(searchValue, query, searchKey);
|
||||||
|
|
||||||
const onTagRender = ({
|
const onTagRender = ({
|
||||||
value,
|
value,
|
||||||
closable,
|
closable,
|
||||||
onClose,
|
onClose,
|
||||||
}: CustomTagProps): React.ReactElement => {
|
}: CustomTagProps): React.ReactElement => {
|
||||||
const isInNin = isInNotInOperator(value);
|
const { tagOperator } = getTagToken(value);
|
||||||
|
const isInNin = isInNInOperator(tagOperator);
|
||||||
|
const chipValue = isInNin
|
||||||
|
? value?.trim()?.replace(/,\s*$/, '')
|
||||||
|
: value?.trim();
|
||||||
|
|
||||||
const onCloseHandler = (): void => {
|
const onCloseHandler = (): void => {
|
||||||
onClose();
|
onClose();
|
||||||
handleSearch('');
|
handleSearch('');
|
||||||
|
setSearchKey('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const tagEditHandler = (value: string): void => {
|
const tagEditHandler = (value: string): void => {
|
||||||
@ -46,14 +63,16 @@ function QueryBuilderSearch({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tag closable={closable} onClose={onCloseHandler}>
|
<Tag closable={!searchValue && closable} onClose={onCloseHandler}>
|
||||||
<Tooltip title={value}>
|
<Tooltip title={chipValue}>
|
||||||
<TypographyText
|
<TypographyText
|
||||||
ellipsis
|
ellipsis
|
||||||
$isInNin={isInNin}
|
$isInNin={isInNin}
|
||||||
|
disabled={!!searchValue}
|
||||||
|
$isEnabled={!!searchValue}
|
||||||
onClick={(): void => tagEditHandler(value)}
|
onClick={(): void => tagEditHandler(value)}
|
||||||
>
|
>
|
||||||
{value}
|
{chipValue}
|
||||||
</TypographyText>
|
</TypographyText>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Tag>
|
</Tag>
|
||||||
@ -66,43 +85,57 @@ function QueryBuilderSearch({
|
|||||||
|
|
||||||
const onInputKeyDownHandler = (event: React.KeyboardEvent<Element>): void => {
|
const onInputKeyDownHandler = (event: React.KeyboardEvent<Element>): void => {
|
||||||
if (isMulti || event.key === 'Backspace') handleKeyDown(event);
|
if (isMulti || event.key === 'Backspace') handleKeyDown(event);
|
||||||
|
if (isExistsNotExistsOperator(searchValue)) handleKeyDown(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isMatricsDataSource = useMemo(
|
||||||
|
() => query.dataSource === DataSource.METRICS,
|
||||||
|
[query.dataSource],
|
||||||
|
);
|
||||||
|
|
||||||
const queryTags = useMemo(() => {
|
const queryTags = useMemo(() => {
|
||||||
if (!query.aggregateAttribute.key) return [];
|
if (!query.aggregateAttribute.key && isMatricsDataSource) return [];
|
||||||
return tags;
|
return tags;
|
||||||
}, [query.aggregateAttribute.key, tags]);
|
}, [isMatricsDataSource, query.aggregateAttribute.key, tags]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initialTagFilters: TagFilter = { items: [], op: 'AND' };
|
const initialTagFilters: TagFilter = { items: [], op: 'AND' };
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
initialTagFilters.items = tags.map((tag) => {
|
initialTagFilters.items = tags.map((tag) => {
|
||||||
const [tagKey, tagOperator, ...tagValue] = tag.split(' ');
|
const { tagKey, tagOperator, tagValue } = getTagToken(tag);
|
||||||
|
const filterAttribute = (keys || []).find(
|
||||||
|
(key) => key.key === getRemovePrefixFromKey(tagKey),
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
id: uuid().slice(0, 8),
|
id: uuid().slice(0, 8),
|
||||||
// TODO: key should be fixed by Chintan Sudani
|
key: filterAttribute ?? {
|
||||||
key: tagKey,
|
key: tagKey,
|
||||||
op: tagOperator,
|
dataType: null,
|
||||||
value: tagValue.map((i) => i.replace(',', '')),
|
type: null,
|
||||||
|
isColumn: null,
|
||||||
|
},
|
||||||
|
op: getOperatorValue(tagOperator),
|
||||||
|
value:
|
||||||
|
tagValue[tagValue.length - 1] === ''
|
||||||
|
? tagValue?.slice(0, -1)
|
||||||
|
: tagValue ?? '',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
onChange(initialTagFilters);
|
onChange(initialTagFilters);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [tags]);
|
}, [keys, tags]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
virtual
|
virtual
|
||||||
showSearch
|
showSearch
|
||||||
tagRender={onTagRender}
|
tagRender={onTagRender}
|
||||||
filterOption={!isMulti}
|
filterOption={false}
|
||||||
autoClearSearchValue={false}
|
autoClearSearchValue={false}
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="Search Filter"
|
placeholder="Search Filter"
|
||||||
value={queryTags}
|
value={queryTags}
|
||||||
searchValue={searchValue}
|
searchValue={searchValue}
|
||||||
disabled={!query.aggregateAttribute.key}
|
disabled={isMatricsDataSource && !query.aggregateAttribute.key}
|
||||||
style={selectStyle}
|
style={selectStyle}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
onChange={onChangeHandler}
|
onChange={onChangeHandler}
|
||||||
@ -111,9 +144,9 @@ function QueryBuilderSearch({
|
|||||||
onInputKeyDown={onInputKeyDownHandler}
|
onInputKeyDown={onInputKeyDownHandler}
|
||||||
notFoundContent={isFetching ? <Spin size="small" /> : null}
|
notFoundContent={isFetching ? <Spin size="small" /> : null}
|
||||||
>
|
>
|
||||||
{options?.map((option) => (
|
{options.map((option) => (
|
||||||
<Select.Option key={option.value} value={option.value}>
|
<Select.Option key={option.label} value={option.label}>
|
||||||
{option.value}
|
{option.label}
|
||||||
{option.selected && <StyledCheckOutlined />}
|
{option.selected && <StyledCheckOutlined />}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
))}
|
))}
|
||||||
|
@ -2,9 +2,14 @@ import { CheckOutlined } from '@ant-design/icons';
|
|||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const TypographyText = styled(Typography.Text)<{ $isInNin: boolean }>`
|
export const TypographyText = styled(Typography.Text)<{
|
||||||
|
$isInNin: boolean;
|
||||||
|
$isEnabled: boolean;
|
||||||
|
}>`
|
||||||
width: ${({ $isInNin }): string => ($isInNin ? '10rem' : 'auto')};
|
width: ${({ $isInNin }): string => ($isInNin ? '10rem' : 'auto')};
|
||||||
cursor: pointer;
|
cursor: ${({ $isEnabled }): string =>
|
||||||
|
$isEnabled ? 'not-allowed' : 'pointer'};
|
||||||
|
pointer-events: ${({ $isEnabled }): string => ($isEnabled ? 'none' : 'auto')};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const StyledCheckOutlined = styled(CheckOutlined)`
|
export const StyledCheckOutlined = styled(CheckOutlined)`
|
||||||
|
@ -1,9 +1,108 @@
|
|||||||
import { OPERATORS } from 'constants/queryBuilder';
|
import { OPERATORS } from 'constants/queryBuilder';
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
import * as Papa from 'papaparse';
|
||||||
|
|
||||||
export function isInNotInOperator(value: string): boolean {
|
export const tagRegexp = /([a-zA-Z0-9_.:@$()\-/\\]+)\s*(!=|=|<=|<|>=|>|IN|NOT_IN|LIKE|NOT_LIKE|EXISTS|NOT_EXISTS|CONTAINS|NOT_CONTAINS)\s*([\s\S]*)/g;
|
||||||
return value?.includes(OPERATORS.IN || OPERATORS.NIN);
|
|
||||||
|
export function isInNInOperator(value: string): boolean {
|
||||||
|
return value === OPERATORS.IN || value === OPERATORS.NIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ITagToken {
|
||||||
|
tagKey: string;
|
||||||
|
tagOperator: string;
|
||||||
|
tagValue: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTagToken(tag: string): ITagToken {
|
||||||
|
const matches = tag?.matchAll(tagRegexp);
|
||||||
|
const [match] = matches ? Array.from(matches) : [];
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const [, matchTagKey, matchTagOperator, matchTagValue] = match;
|
||||||
|
return {
|
||||||
|
tagKey: matchTagKey,
|
||||||
|
tagOperator: matchTagOperator,
|
||||||
|
tagValue: isInNInOperator(matchTagOperator)
|
||||||
|
? Papa.parse(matchTagValue).data.flat()
|
||||||
|
: matchTagValue,
|
||||||
|
} as ITagToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tagKey: tag,
|
||||||
|
tagOperator: '',
|
||||||
|
tagValue: [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isExistsNotExistsOperator(value: string): boolean {
|
export function isExistsNotExistsOperator(value: string): boolean {
|
||||||
return value?.includes(OPERATORS.EXISTS || OPERATORS.NOT_EXISTS);
|
const { tagOperator } = getTagToken(value);
|
||||||
|
return (
|
||||||
|
tagOperator === OPERATORS.NOT_EXISTS || tagOperator === OPERATORS.EXISTS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRemovePrefixFromKey(tag: string): string {
|
||||||
|
return tag?.replace(/^(tag_|resource_)/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOperatorValue(op: string): string {
|
||||||
|
switch (op) {
|
||||||
|
case 'IN':
|
||||||
|
return 'in';
|
||||||
|
case 'NOT_IN':
|
||||||
|
return 'nin';
|
||||||
|
case 'LIKE':
|
||||||
|
return 'like';
|
||||||
|
case 'NOT_LIKE':
|
||||||
|
return 'nlike';
|
||||||
|
case 'EXISTS':
|
||||||
|
return 'exists';
|
||||||
|
case 'NOT_EXISTS':
|
||||||
|
return 'nexists';
|
||||||
|
case 'CONTAINS':
|
||||||
|
return 'contains';
|
||||||
|
case 'NOT_CONTAINS':
|
||||||
|
return 'ncontains';
|
||||||
|
default:
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOperatorFromValue(op: string): string {
|
||||||
|
switch (op) {
|
||||||
|
case 'in':
|
||||||
|
return 'IN';
|
||||||
|
case 'nin':
|
||||||
|
return 'NOT_IN';
|
||||||
|
case 'like':
|
||||||
|
return 'LIKE';
|
||||||
|
case 'nlike':
|
||||||
|
return 'NOT_LIKE';
|
||||||
|
case 'exists':
|
||||||
|
return 'EXISTS';
|
||||||
|
case 'nexists':
|
||||||
|
return 'NOT_EXISTS';
|
||||||
|
case 'contains':
|
||||||
|
return 'CONTAINS';
|
||||||
|
case 'ncontains':
|
||||||
|
return 'NOT_CONTAINS';
|
||||||
|
default:
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function replaceStringWithMaxLength(
|
||||||
|
mainString: string,
|
||||||
|
array: string[],
|
||||||
|
replacementString: string,
|
||||||
|
): string {
|
||||||
|
const lastSearchValue = array.pop() ?? ''; // Remove the last search value from the array
|
||||||
|
if (lastSearchValue === '') return `${mainString}${replacementString},`; // if user select direclty from options
|
||||||
|
return mainString.replace(lastSearchValue, `${replacementString},`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkCommaInValue(str: string): string {
|
||||||
|
return str.includes(',') ? `"${str}"` : str;
|
||||||
}
|
}
|
||||||
|
@ -12,5 +12,6 @@ export interface ContextValueI {
|
|||||||
|
|
||||||
export type Option = {
|
export type Option = {
|
||||||
value: string;
|
value: string;
|
||||||
|
label: string;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
};
|
};
|
||||||
|
@ -13,6 +13,7 @@ function CustomDateTimeModal({
|
|||||||
}: CustomDateTimeModalProps): JSX.Element {
|
}: CustomDateTimeModalProps): JSX.Element {
|
||||||
const [selectedDate, setDateTime] = useState<DateTimeRangeType>();
|
const [selectedDate, setDateTime] = useState<DateTimeRangeType>();
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const onModalOkHandler = (date_time: any): void => {
|
const onModalOkHandler = (date_time: any): void => {
|
||||||
onCreate(date_time);
|
onCreate(date_time);
|
||||||
setDateTime(date_time);
|
setDateTime(date_time);
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
import { isExistsNotExistsOperator } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
import {
|
||||||
|
getRemovePrefixFromKey,
|
||||||
|
getTagToken,
|
||||||
|
isExistsNotExistsOperator,
|
||||||
|
replaceStringWithMaxLength,
|
||||||
|
tagRegexp,
|
||||||
|
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||||
import { Option } from 'container/QueryBuilder/type';
|
import { Option } from 'container/QueryBuilder/type';
|
||||||
|
import * as Papa from 'papaparse';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { checkStringEndsWithSpace } from 'utils/checkStringEndsWithSpace';
|
|
||||||
|
|
||||||
import { useFetchKeysAndValues } from './useFetchKeysAndValues';
|
import { useFetchKeysAndValues } from './useFetchKeysAndValues';
|
||||||
import { useOptions } from './useOptions';
|
import { useOptions } from './useOptions';
|
||||||
@ -21,52 +27,56 @@ interface IAutoComplete {
|
|||||||
searchValue: string;
|
searchValue: string;
|
||||||
isMulti: boolean;
|
isMulti: boolean;
|
||||||
isFetching: boolean;
|
isFetching: boolean;
|
||||||
|
setSearchKey: (value: string) => void;
|
||||||
|
searchKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => {
|
export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => {
|
||||||
const [searchValue, setSearchValue] = useState<string>('');
|
const [searchValue, setSearchValue] = useState<string>('');
|
||||||
|
const [searchKey, setSearchKey] = useState<string>('');
|
||||||
const handleSearch = (value: string): void => setSearchValue(value);
|
|
||||||
|
|
||||||
const { keys, results, isFetching } = useFetchKeysAndValues(
|
const { keys, results, isFetching } = useFetchKeysAndValues(
|
||||||
searchValue,
|
searchValue,
|
||||||
query,
|
query,
|
||||||
|
searchKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue, keys);
|
const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue, keys);
|
||||||
|
|
||||||
const {
|
const handleSearch = (value: string): void => {
|
||||||
isValidTag,
|
const prefixFreeValue = getRemovePrefixFromKey(getTagToken(value).tagKey);
|
||||||
isExist,
|
setSearchValue(value);
|
||||||
isValidOperator,
|
setSearchKey(prefixFreeValue);
|
||||||
isMulti,
|
};
|
||||||
isFreeText,
|
|
||||||
} = useTagValidation(searchValue, operator, result);
|
const { isValidTag, isExist, isValidOperator, isMulti } = useTagValidation(
|
||||||
|
operator,
|
||||||
|
result,
|
||||||
|
);
|
||||||
|
|
||||||
const { handleAddTag, handleClearTag, tags, updateTag } = useTag(
|
const { handleAddTag, handleClearTag, tags, updateTag } = useTag(
|
||||||
key,
|
key,
|
||||||
isValidTag,
|
isValidTag,
|
||||||
isFreeText,
|
|
||||||
handleSearch,
|
handleSearch,
|
||||||
query,
|
query,
|
||||||
|
setSearchKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelect = useCallback(
|
const handleSelect = useCallback(
|
||||||
(value: string): void => {
|
(value: string): void => {
|
||||||
if (isMulti) {
|
if (isMulti) {
|
||||||
setSearchValue((prev: string) => {
|
setSearchValue((prev: string) => {
|
||||||
if (prev.includes(value)) {
|
const matches = prev?.matchAll(tagRegexp);
|
||||||
return prev.replace(` ${value}`, '');
|
const [match] = matches ? Array.from(matches) : [];
|
||||||
}
|
const [, , , matchTagValue] = match;
|
||||||
return checkStringEndsWithSpace(prev)
|
const data = Papa.parse(matchTagValue).data.flat();
|
||||||
? `${prev} ${value}`
|
return replaceStringWithMaxLength(prev, data as string[], value);
|
||||||
: `${prev} ${value},`;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!isMulti && isValidTag && !isExistsNotExistsOperator(value)) {
|
if (!isMulti && isValidTag && !isExistsNotExistsOperator(value)) {
|
||||||
handleAddTag(value);
|
handleAddTag(value);
|
||||||
}
|
}
|
||||||
if (!isMulti && isExistsNotExistsOperator(value)) {
|
if (!isMulti && isValidTag && isExistsNotExistsOperator(value)) {
|
||||||
handleAddTag(value);
|
handleAddTag(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -83,7 +93,7 @@ export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === 'Enter' && searchValue && isValidTag) {
|
if (event.key === 'Enter' && searchValue && isValidTag) {
|
||||||
if (isMulti || isFreeText) {
|
if (isMulti) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -96,15 +106,7 @@ export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => {
|
|||||||
handleClearTag(last);
|
handleClearTag(last);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[handleAddTag, handleClearTag, isMulti, isValidTag, searchValue, tags],
|
||||||
handleAddTag,
|
|
||||||
handleClearTag,
|
|
||||||
isFreeText,
|
|
||||||
isMulti,
|
|
||||||
isValidTag,
|
|
||||||
searchValue,
|
|
||||||
tags,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const options = useOptions(
|
const options = useOptions(
|
||||||
@ -130,5 +132,7 @@ export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => {
|
|||||||
searchValue,
|
searchValue,
|
||||||
isMulti,
|
isMulti,
|
||||||
isFetching,
|
isFetching,
|
||||||
|
setSearchKey,
|
||||||
|
searchKey,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
|
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
||||||
|
import { getAttributesValues } from 'api/queryBuilder/getAttributesValues';
|
||||||
|
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
||||||
import {
|
import {
|
||||||
AttributeKeyOptions,
|
getRemovePrefixFromKey,
|
||||||
getAttributesKeys,
|
getTagToken,
|
||||||
getAttributesValues,
|
isInNInOperator,
|
||||||
} from 'api/queryBuilder/getAttributesKeysValues';
|
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { useDebounce } from 'react-use';
|
import { useDebounce } from 'react-use';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { separateSearchValue } from 'utils/separateSearchValue';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
type UseFetchKeysAndValuesReturnValues = {
|
type IuseFetchKeysAndValues = {
|
||||||
keys: AttributeKeyOptions[];
|
keys: BaseAutocompleteData[];
|
||||||
results: string[];
|
results: string[];
|
||||||
isFetching: boolean;
|
isFetching: boolean;
|
||||||
};
|
};
|
||||||
@ -25,25 +29,44 @@ type UseFetchKeysAndValuesReturnValues = {
|
|||||||
export const useFetchKeysAndValues = (
|
export const useFetchKeysAndValues = (
|
||||||
searchValue: string,
|
searchValue: string,
|
||||||
query: IBuilderQuery,
|
query: IBuilderQuery,
|
||||||
): UseFetchKeysAndValuesReturnValues => {
|
searchKey: string,
|
||||||
const [keys, setKeys] = useState<AttributeKeyOptions[]>([]);
|
): IuseFetchKeysAndValues => {
|
||||||
|
const [keys, setKeys] = useState<BaseAutocompleteData[]>([]);
|
||||||
const [results, setResults] = useState<string[]>([]);
|
const [results, setResults] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const isQueryEnabled = useMemo(
|
||||||
|
() =>
|
||||||
|
query.dataSource === DataSource.METRICS
|
||||||
|
? !!query.aggregateOperator &&
|
||||||
|
!!query.dataSource &&
|
||||||
|
!!query.aggregateAttribute.dataType
|
||||||
|
: true,
|
||||||
|
[
|
||||||
|
query.aggregateAttribute.dataType,
|
||||||
|
query.aggregateOperator,
|
||||||
|
query.dataSource,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const { data, isFetching, status } = useQuery(
|
const { data, isFetching, status } = useQuery(
|
||||||
[
|
[
|
||||||
'GET_ATTRIBUTE_KEY',
|
QueryBuilderKeys.GET_ATTRIBUTE_KEY,
|
||||||
searchValue,
|
searchKey,
|
||||||
query.dataSource,
|
query.dataSource,
|
||||||
query.aggregateOperator,
|
query.aggregateOperator,
|
||||||
query.aggregateAttribute.key,
|
query.aggregateAttribute.key,
|
||||||
],
|
],
|
||||||
async () =>
|
async () =>
|
||||||
getAttributesKeys({
|
getAggregateKeys({
|
||||||
searchText: searchValue,
|
searchText: searchKey,
|
||||||
dataSource: query.dataSource,
|
dataSource: query.dataSource,
|
||||||
aggregateOperator: query.aggregateOperator,
|
aggregateOperator: query.aggregateOperator,
|
||||||
aggregateAttribute: query.aggregateAttribute.key,
|
aggregateAttribute: query.aggregateAttribute.key,
|
||||||
|
tagType: query.aggregateAttribute.type ?? null,
|
||||||
}),
|
}),
|
||||||
{ enabled: !!query.aggregateOperator && !!query.dataSource },
|
{
|
||||||
|
enabled: isQueryEnabled,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,28 +77,36 @@ export const useFetchKeysAndValues = (
|
|||||||
const handleFetchOption = async (
|
const handleFetchOption = async (
|
||||||
value: string,
|
value: string,
|
||||||
query: IBuilderQuery,
|
query: IBuilderQuery,
|
||||||
|
keys: BaseAutocompleteData[],
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
if (value) {
|
if (!value) {
|
||||||
// separate the search value into the attribute key and the operator
|
return;
|
||||||
const [tKey, operator] = separateSearchValue(value);
|
}
|
||||||
|
const { tagKey, tagOperator, tagValue } = getTagToken(value);
|
||||||
|
const filterAttributeKey = keys.find(
|
||||||
|
(item) => item.key === getRemovePrefixFromKey(tagKey),
|
||||||
|
);
|
||||||
setResults([]);
|
setResults([]);
|
||||||
if (tKey && operator) {
|
|
||||||
|
if (!tagKey || !tagOperator) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { payload } = await getAttributesValues({
|
const { payload } = await getAttributesValues({
|
||||||
searchText: searchValue,
|
|
||||||
dataSource: query.dataSource,
|
|
||||||
aggregateOperator: query.aggregateOperator,
|
aggregateOperator: query.aggregateOperator,
|
||||||
|
dataSource: query.dataSource,
|
||||||
aggregateAttribute: query.aggregateAttribute.key,
|
aggregateAttribute: query.aggregateAttribute.key,
|
||||||
attributeKey: tKey,
|
attributeKey: filterAttributeKey?.key ?? tagKey,
|
||||||
|
filterAttributeKeyDataType: filterAttributeKey?.dataType ?? null,
|
||||||
|
tagType: filterAttributeKey?.type ?? null,
|
||||||
|
searchText: isInNInOperator(tagOperator)
|
||||||
|
? tagValue[tagValue.length - 1]?.toString() ?? '' // last element of tagvalue will be always user search value
|
||||||
|
: tagValue?.toString() ?? '',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (payload) {
|
if (payload) {
|
||||||
const values = Object.values(payload).find((el) => !!el);
|
const values = Object.values(payload).find((el) => !!el) || [];
|
||||||
if (values) {
|
|
||||||
setResults(values);
|
setResults(values);
|
||||||
} else {
|
|
||||||
setResults([]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -83,20 +114,21 @@ export const useFetchKeysAndValues = (
|
|||||||
const clearFetcher = useRef(handleFetchOption).current;
|
const clearFetcher = useRef(handleFetchOption).current;
|
||||||
|
|
||||||
// debounces the fetch function to avoid excessive API calls
|
// debounces the fetch function to avoid excessive API calls
|
||||||
useDebounce(() => clearFetcher(searchValue, query), 500, [
|
useDebounce(() => clearFetcher(searchValue, query, keys), 750, [
|
||||||
clearFetcher,
|
clearFetcher,
|
||||||
searchValue,
|
searchValue,
|
||||||
query,
|
query,
|
||||||
|
keys,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// update the fetched keys when the fetch status changes
|
// update the fetched keys when the fetch status changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === 'success' && data?.payload) {
|
if (status === 'success' && data?.payload?.attributeKeys) {
|
||||||
setKeys(data?.payload);
|
setKeys(data?.payload.attributeKeys);
|
||||||
} else {
|
} else {
|
||||||
setKeys([]);
|
setKeys([]);
|
||||||
}
|
}
|
||||||
}, [data?.payload, status]);
|
}, [data?.payload?.attributeKeys, status]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
keys,
|
keys,
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
import { AttributeKeyOptions } from 'api/queryBuilder/getAttributesKeysValues';
|
|
||||||
import { QUERY_BUILDER_OPERATORS_BY_TYPES } from 'constants/queryBuilder';
|
import { QUERY_BUILDER_OPERATORS_BY_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { getRemovePrefixFromKey } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
type IOperators =
|
type IOperators =
|
||||||
| typeof QUERY_BUILDER_OPERATORS_BY_TYPES.universal
|
| typeof QUERY_BUILDER_OPERATORS_BY_TYPES.universal
|
||||||
| typeof QUERY_BUILDER_OPERATORS_BY_TYPES.string
|
| typeof QUERY_BUILDER_OPERATORS_BY_TYPES.string
|
||||||
| typeof QUERY_BUILDER_OPERATORS_BY_TYPES.boolean
|
| typeof QUERY_BUILDER_OPERATORS_BY_TYPES.bool
|
||||||
| typeof QUERY_BUILDER_OPERATORS_BY_TYPES.number;
|
| typeof QUERY_BUILDER_OPERATORS_BY_TYPES.int64
|
||||||
|
| typeof QUERY_BUILDER_OPERATORS_BY_TYPES.float64;
|
||||||
|
|
||||||
export const useOperators = (
|
export const useOperators = (
|
||||||
key: string,
|
key: string,
|
||||||
keys: AttributeKeyOptions[],
|
keys: BaseAutocompleteData[],
|
||||||
): IOperators =>
|
): IOperators =>
|
||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
const currentKey = keys?.find((el) => el.key === key);
|
const currentKey = keys?.find((el) => el.key === getRemovePrefixFromKey(key));
|
||||||
return currentKey
|
return currentKey?.dataType
|
||||||
? QUERY_BUILDER_OPERATORS_BY_TYPES[currentKey.dataType]
|
? QUERY_BUILDER_OPERATORS_BY_TYPES[currentKey.dataType]
|
||||||
: QUERY_BUILDER_OPERATORS_BY_TYPES.universal;
|
: QUERY_BUILDER_OPERATORS_BY_TYPES.universal;
|
||||||
}, [keys, key]);
|
}, [keys, key]);
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
import { AttributeKeyOptions } from 'api/queryBuilder/getAttributesKeysValues';
|
import {
|
||||||
|
checkCommaInValue,
|
||||||
|
getTagToken,
|
||||||
|
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||||
import { Option } from 'container/QueryBuilder/type';
|
import { Option } from 'container/QueryBuilder/type';
|
||||||
|
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
import { useOperators } from './useOperators';
|
import { useOperators } from './useOperators';
|
||||||
|
|
||||||
export const useOptions = (
|
export const useOptions = (
|
||||||
key: string,
|
key: string,
|
||||||
keys: AttributeKeyOptions[],
|
keys: BaseAutocompleteData[],
|
||||||
operator: string,
|
operator: string,
|
||||||
searchValue: string,
|
searchValue: string,
|
||||||
isMulti: boolean,
|
isMulti: boolean,
|
||||||
@ -18,37 +23,73 @@ export const useOptions = (
|
|||||||
const [options, setOptions] = useState<Option[]>([]);
|
const [options, setOptions] = useState<Option[]>([]);
|
||||||
const operators = useOperators(key, keys);
|
const operators = useOperators(key, keys);
|
||||||
|
|
||||||
const updateOptions = useCallback(() => {
|
const getLabel = useCallback(
|
||||||
|
(data: BaseAutocompleteData): Option['label'] =>
|
||||||
|
transformStringWithPrefix({
|
||||||
|
str: data?.key,
|
||||||
|
prefix: data?.type || '',
|
||||||
|
condition: !data?.isColumn,
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getOptionsFromKeys = useCallback(
|
||||||
|
(items: BaseAutocompleteData[]): Option[] =>
|
||||||
|
items?.map((item) => ({
|
||||||
|
label: `${getLabel(item)}`,
|
||||||
|
value: item.key,
|
||||||
|
})),
|
||||||
|
[getLabel],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getKeyOpValue = useCallback(
|
||||||
|
(items: string[]): Option[] =>
|
||||||
|
items?.map((item) => ({
|
||||||
|
label: `${key} ${operator} ${item}`,
|
||||||
|
value: `${key} ${operator} ${item}`,
|
||||||
|
})),
|
||||||
|
[key, operator],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (!key) {
|
if (!key) {
|
||||||
setOptions(
|
setOptions(
|
||||||
searchValue
|
searchValue
|
||||||
? [{ value: searchValue }, ...keys.map((k) => ({ value: k.key }))]
|
? [
|
||||||
: keys?.map((k) => ({ value: k.key })),
|
{ label: `${searchValue} `, value: `${searchValue} ` },
|
||||||
|
...getOptionsFromKeys(keys),
|
||||||
|
]
|
||||||
|
: getOptionsFromKeys(keys),
|
||||||
);
|
);
|
||||||
} else if (key && !operator) {
|
} else if (key && !operator) {
|
||||||
setOptions(
|
setOptions(
|
||||||
operators?.map((o) => ({
|
operators?.map((operator) => ({
|
||||||
value: `${key} ${o}`,
|
value: `${key} ${operator} `,
|
||||||
label: `${key} ${o.replace('_', ' ')}`,
|
label: `${key} ${operator} `,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
} else if (key && operator) {
|
} else if (key && operator) {
|
||||||
if (isMulti) {
|
if (isMulti) {
|
||||||
setOptions(results.map((r) => ({ value: `${r}` })));
|
setOptions(
|
||||||
|
results.map((item) => ({
|
||||||
|
label: checkCommaInValue(String(item)),
|
||||||
|
value: String(item),
|
||||||
|
})),
|
||||||
|
);
|
||||||
} else if (isExist) {
|
} else if (isExist) {
|
||||||
setOptions([]);
|
setOptions([]);
|
||||||
} else if (isValidOperator) {
|
} else if (isValidOperator) {
|
||||||
const hasAllResults = result.every((val) => results.includes(val));
|
const hasAllResults = results.every((value) => result.includes(value));
|
||||||
const values = results.map((r) => ({
|
const values = getKeyOpValue(results);
|
||||||
value: `${key} ${operator} ${r}`,
|
|
||||||
}));
|
|
||||||
const options = hasAllResults
|
const options = hasAllResults
|
||||||
? values
|
? [{ label: searchValue, value: searchValue }]
|
||||||
: [{ value: searchValue }, ...values];
|
: [{ label: searchValue, value: searchValue }, ...values];
|
||||||
setOptions(options);
|
setOptions(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
getKeyOpValue,
|
||||||
|
getOptionsFromKeys,
|
||||||
isExist,
|
isExist,
|
||||||
isMulti,
|
isMulti,
|
||||||
isValidOperator,
|
isValidOperator,
|
||||||
@ -61,15 +102,25 @@ export const useOptions = (
|
|||||||
searchValue,
|
searchValue,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateOptions();
|
|
||||||
}, [updateOptions]);
|
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() =>
|
() =>
|
||||||
options?.map((option) => {
|
(
|
||||||
|
options.filter(
|
||||||
|
(option, index, self) =>
|
||||||
|
index ===
|
||||||
|
self.findIndex(
|
||||||
|
(o) => o.label === option.label && o.value === option.value, // to remove duplicate & empty options from list
|
||||||
|
) && option.value !== '',
|
||||||
|
) || []
|
||||||
|
).map((option) => {
|
||||||
|
const { tagValue } = getTagToken(searchValue);
|
||||||
if (isMulti) {
|
if (isMulti) {
|
||||||
return { ...option, selected: searchValue.includes(option.value) };
|
return {
|
||||||
|
...option,
|
||||||
|
selected: tagValue
|
||||||
|
.filter((i) => i.trim().replace(/^\s+/, '') === option.value)
|
||||||
|
.includes(option.value),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return option;
|
return option;
|
||||||
}),
|
}),
|
||||||
|
@ -44,7 +44,7 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
|
|||||||
having: [],
|
having: [],
|
||||||
orderBy: [],
|
orderBy: [],
|
||||||
limit: null,
|
limit: null,
|
||||||
tagFilters: { items: [], op: 'AND' },
|
filters: { items: [], op: 'AND' },
|
||||||
...(shouldResetAggregateAttribute
|
...(shouldResetAggregateAttribute
|
||||||
? { aggregateAttribute: initialAggregateAttribute }
|
? { aggregateAttribute: initialAggregateAttribute }
|
||||||
: {}),
|
: {}),
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { AttributeKeyOptions } from 'api/queryBuilder/getAttributesKeysValues';
|
import {
|
||||||
|
getRemovePrefixFromKey,
|
||||||
|
getTagToken,
|
||||||
|
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { getCountOfSpace } from 'utils/getCountOfSpace';
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { separateSearchValue } from 'utils/separateSearchValue';
|
|
||||||
|
|
||||||
type ICurrentKeyAndOperator = [string, string, string[]];
|
type ICurrentKeyAndOperator = [string, string, string[]];
|
||||||
|
|
||||||
export const useSetCurrentKeyAndOperator = (
|
export const useSetCurrentKeyAndOperator = (
|
||||||
value: string,
|
value: string,
|
||||||
keys: AttributeKeyOptions[],
|
keys: BaseAutocompleteData[],
|
||||||
): ICurrentKeyAndOperator => {
|
): ICurrentKeyAndOperator => {
|
||||||
const [key, operator, result] = useMemo(() => {
|
const [key, operator, result] = useMemo(() => {
|
||||||
let key = '';
|
let key = '';
|
||||||
@ -15,13 +17,14 @@ export const useSetCurrentKeyAndOperator = (
|
|||||||
let result: string[] = [];
|
let result: string[] = [];
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
const [tKey, tOperator, tResult] = separateSearchValue(value);
|
const { tagKey, tagOperator, tagValue } = getTagToken(value);
|
||||||
const isSuggestKey = keys?.some((el) => el.key === tKey);
|
const isSuggestKey = keys?.some(
|
||||||
|
(el) => el?.key === getRemovePrefixFromKey(tagKey),
|
||||||
if (getCountOfSpace(value) >= 1 || isSuggestKey) {
|
);
|
||||||
key = tKey || '';
|
if (isSuggestKey || keys.length === 0) {
|
||||||
operator = tOperator || '';
|
key = tagKey || '';
|
||||||
result = tResult.filter((el) => el);
|
operator = tagOperator || '';
|
||||||
|
result = tagValue || [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
|
getOperatorFromValue,
|
||||||
isExistsNotExistsOperator,
|
isExistsNotExistsOperator,
|
||||||
isInNotInOperator,
|
isInNInOperator,
|
||||||
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
import * as Papa from 'papaparse';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
@ -16,16 +19,16 @@ type IUseTag = {
|
|||||||
* A custom React hook for handling tags.
|
* A custom React hook for handling tags.
|
||||||
* @param {string} key - A string value to identify tags.
|
* @param {string} key - A string value to identify tags.
|
||||||
* @param {boolean} isValidTag - A boolean value to indicate whether the tag is valid.
|
* @param {boolean} isValidTag - A boolean value to indicate whether the tag is valid.
|
||||||
* @param {boolean} isFreeText - A boolean value to indicate whether free text is allowed.
|
|
||||||
* @param {function} handleSearch - A callback function to handle search.
|
* @param {function} handleSearch - A callback function to handle search.
|
||||||
* @returns {IUseTag} The return object containing handlers and tags.
|
* @returns {IUseTag} The return object containing handlers and tags.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const useTag = (
|
export const useTag = (
|
||||||
key: string,
|
key: string,
|
||||||
isValidTag: boolean,
|
isValidTag: boolean,
|
||||||
isFreeText: boolean,
|
|
||||||
handleSearch: (value: string) => void,
|
handleSearch: (value: string) => void,
|
||||||
query: IBuilderQuery,
|
query: IBuilderQuery,
|
||||||
|
setSearchKey: (value: string) => void,
|
||||||
): IUseTag => {
|
): IUseTag => {
|
||||||
const [tags, setTags] = useState<string[]>([]);
|
const [tags, setTags] = useState<string[]>([]);
|
||||||
|
|
||||||
@ -40,16 +43,13 @@ export const useTag = (
|
|||||||
*/
|
*/
|
||||||
const handleAddTag = useCallback(
|
const handleAddTag = useCallback(
|
||||||
(value: string): void => {
|
(value: string): void => {
|
||||||
if (
|
if ((value && key && isValidTag) || isExistsNotExistsOperator(value)) {
|
||||||
(value && key && isValidTag) ||
|
|
||||||
isFreeText ||
|
|
||||||
isExistsNotExistsOperator(value)
|
|
||||||
) {
|
|
||||||
setTags((prevTags) => [...prevTags, value]);
|
setTags((prevTags) => [...prevTags, value]);
|
||||||
handleSearch('');
|
handleSearch('');
|
||||||
|
setSearchKey('');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[key, isValidTag, isFreeText, handleSearch],
|
[key, isValidTag, handleSearch, setSearchKey],
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,13 +61,14 @@ export const useTag = (
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTags(
|
const initialTags = (query?.filters?.items || []).map((ele) => {
|
||||||
(query?.tagFilters?.items || []).map((obj) =>
|
if (isInNInOperator(getOperatorFromValue(ele.op))) {
|
||||||
isInNotInOperator(obj.op)
|
const csvString = Papa.unparse([ele.value]);
|
||||||
? `${obj.key} ${obj.op} ${obj.value.join(',')}`
|
return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${csvString}`;
|
||||||
: `${obj.key} ${obj.op} ${obj.value.join(' ')}`,
|
}
|
||||||
),
|
return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${ele.value}`;
|
||||||
);
|
});
|
||||||
|
setTags(initialTags);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { QUERY_BUILDER_SEARCH_VALUES } from 'constants/queryBuilder';
|
import { QUERY_BUILDER_SEARCH_VALUES } from 'constants/queryBuilder';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { checkStringEndsWithSpace } from 'utils/checkStringEndsWithSpace';
|
|
||||||
|
|
||||||
import { useIsValidTag } from './useIsValidTag';
|
import { useIsValidTag } from './useIsValidTag';
|
||||||
import { useOperatorType } from './useOperatorType';
|
import { useOperatorType } from './useOperatorType';
|
||||||
@ -10,26 +9,25 @@ type ITagValidation = {
|
|||||||
isExist: boolean;
|
isExist: boolean;
|
||||||
isValidOperator: boolean;
|
isValidOperator: boolean;
|
||||||
isMulti: boolean;
|
isMulti: boolean;
|
||||||
isFreeText: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTagValidation = (
|
export const useTagValidation = (
|
||||||
value: string,
|
|
||||||
operator: string,
|
operator: string,
|
||||||
result: string[],
|
result: string[],
|
||||||
): ITagValidation => {
|
): ITagValidation => {
|
||||||
const operatorType = useOperatorType(operator);
|
const operatorType = useOperatorType(operator);
|
||||||
const isValidTag = useIsValidTag(operatorType, result.length);
|
const resultLength =
|
||||||
|
operatorType === 'SINGLE_VALUE' ? [result]?.length : result?.length;
|
||||||
|
const isValidTag = useIsValidTag(operatorType, resultLength);
|
||||||
|
|
||||||
const { isExist, isValidOperator, isMulti, isFreeText } = useMemo(() => {
|
const { isExist, isValidOperator, isMulti } = useMemo(() => {
|
||||||
const isExist = operatorType === QUERY_BUILDER_SEARCH_VALUES.NON;
|
const isExist = operatorType === QUERY_BUILDER_SEARCH_VALUES.NON;
|
||||||
const isValidOperator =
|
const isValidOperator =
|
||||||
operatorType !== QUERY_BUILDER_SEARCH_VALUES.NOT_VALID;
|
operatorType !== QUERY_BUILDER_SEARCH_VALUES.NOT_VALID;
|
||||||
const isMulti = operatorType === QUERY_BUILDER_SEARCH_VALUES.MULTIPLY;
|
const isMulti = operatorType === QUERY_BUILDER_SEARCH_VALUES.MULTIPLY;
|
||||||
const isFreeText = operator === '' && !checkStringEndsWithSpace(value);
|
|
||||||
|
|
||||||
return { isExist, isValidOperator, isMulti, isFreeText };
|
return { isExist, isValidOperator, isMulti };
|
||||||
}, [operator, operatorType, value]);
|
}, [operatorType]);
|
||||||
|
|
||||||
return { isValidTag, isExist, isValidOperator, isMulti, isFreeText };
|
return { isValidTag, isExist, isValidOperator, isMulti };
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { BaseAutocompleteData } from './queryAutocompleteResponse';
|
||||||
|
|
||||||
export interface IGetAttributeKeysPayload {
|
export interface IGetAttributeKeysPayload {
|
||||||
aggregateOperator: string;
|
aggregateOperator: string;
|
||||||
dataSource: DataSource;
|
dataSource: DataSource;
|
||||||
searchText: string;
|
searchText: string;
|
||||||
aggregateAttribute: string;
|
aggregateAttribute: string;
|
||||||
|
tagType?: BaseAutocompleteData['type'];
|
||||||
}
|
}
|
||||||
|
19
frontend/src/types/api/queryBuilder/getAttributesValues.ts
Normal file
19
frontend/src/types/api/queryBuilder/getAttributesValues.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { BaseAutocompleteData } from './queryAutocompleteResponse';
|
||||||
|
|
||||||
|
export interface IGetAttributeValuesPayload {
|
||||||
|
dataSource: DataSource;
|
||||||
|
aggregateOperator: string;
|
||||||
|
aggregateAttribute: string;
|
||||||
|
searchText: string;
|
||||||
|
attributeKey: string;
|
||||||
|
filterAttributeKeyDataType: BaseAutocompleteData['dataType'];
|
||||||
|
tagType: BaseAutocompleteData['type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAttributeValuesResponse {
|
||||||
|
boolAttributeValues: null | string[];
|
||||||
|
numberAttributeValues: null | string[];
|
||||||
|
stringAttributeValues: null | string[];
|
||||||
|
}
|
@ -15,12 +15,11 @@ export interface TagFilterItem {
|
|||||||
id: string;
|
id: string;
|
||||||
key?: BaseAutocompleteData;
|
key?: BaseAutocompleteData;
|
||||||
op: string;
|
op: string;
|
||||||
value: string[];
|
value: string[] | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TagFilter {
|
export interface TagFilter {
|
||||||
items: TagFilterItem[];
|
items: TagFilterItem[];
|
||||||
// TODO: type it in the future
|
|
||||||
op: string;
|
op: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +39,7 @@ export type IBuilderQuery = {
|
|||||||
dataSource: DataSource;
|
dataSource: DataSource;
|
||||||
aggregateOperator: string;
|
aggregateOperator: string;
|
||||||
aggregateAttribute: BaseAutocompleteData;
|
aggregateAttribute: BaseAutocompleteData;
|
||||||
tagFilters: TagFilter;
|
filters: TagFilter;
|
||||||
groupBy: BaseAutocompleteData[];
|
groupBy: BaseAutocompleteData[];
|
||||||
expression: string;
|
expression: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
export const checkStringEndsWithSpace = (str: string): boolean => {
|
|
||||||
const endSpace = / $/;
|
|
||||||
return endSpace.test(str);
|
|
||||||
};
|
|
@ -1 +0,0 @@
|
|||||||
export const getCountOfSpace = (s: string): number => s.split(' ').length - 1;
|
|
@ -1,9 +0,0 @@
|
|||||||
export const getSearchParams = (newParams: {
|
|
||||||
[key: string]: string;
|
|
||||||
}): URLSearchParams => {
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
Object.entries(newParams).forEach(([key, value]) => {
|
|
||||||
params.set(key, value);
|
|
||||||
});
|
|
||||||
return params;
|
|
||||||
};
|
|
@ -1,12 +0,0 @@
|
|||||||
import { OPERATORS } from 'constants/queryBuilder';
|
|
||||||
|
|
||||||
export const separateSearchValue = (
|
|
||||||
value: string,
|
|
||||||
): [string, string, string[]] => {
|
|
||||||
const separatedString = value.split(' ');
|
|
||||||
const [key, operator, ...result] = separatedString;
|
|
||||||
if (operator === OPERATORS.IN || operator === OPERATORS.NIN) {
|
|
||||||
return [key, operator, result];
|
|
||||||
}
|
|
||||||
return [key, operator, Array(result.join(' '))];
|
|
||||||
};
|
|
Loading…
x
Reference in New Issue
Block a user