From 8fa09a83e1a2ab9755a1d36dce0350c72bb6692d Mon Sep 17 00:00:00 2001 From: Praveen K B <30530587+praveen5959@users.noreply.github.com> Date: Thu, 7 Nov 2024 16:32:44 +0530 Subject: [PATCH] Added filter_query to filter builder (#355) This PR ensures that console adds the SQL query equivalent to the filter user has added. This is required for other clients like `pb` to work properly. --- src/@types/parseable/api/savedFilters.ts | 3 +- src/hooks/useSavedFilters.tsx | 8 ++++- .../components/Querier/SaveFilterModal.tsx | 12 ++------ .../components/Querier/SavedFiltersModal.tsx | 30 ++++++++++++------- src/pages/Stream/hooks/useParamsController.ts | 27 +++++++++++++---- src/pages/Stream/providers/LogsProvider.tsx | 8 ++--- src/pages/Stream/utils.ts | 25 ++++++++++++++++ 7 files changed, 81 insertions(+), 32 deletions(-) diff --git a/src/@types/parseable/api/savedFilters.ts b/src/@types/parseable/api/savedFilters.ts index 3737b180..de9a639c 100644 --- a/src/@types/parseable/api/savedFilters.ts +++ b/src/@types/parseable/api/savedFilters.ts @@ -21,8 +21,7 @@ export type CreateSavedFilterType = { filter_name: string; query: { filter_type: 'sql' | 'builder'; - filter_query?: string; - filter_builder?: QueryType; + filter_query: string; }; time_filter: null | { from: string; diff --git a/src/hooks/useSavedFilters.tsx b/src/hooks/useSavedFilters.tsx index 0789ed93..45623870 100644 --- a/src/hooks/useSavedFilters.tsx +++ b/src/hooks/useSavedFilters.tsx @@ -26,7 +26,13 @@ const useSavedFiltersQuery = () => { ); const { mutate: updateSavedFilters, isLoading: isUpdating } = useMutation( - (data: { filter: SavedFilterType; onSuccess?: () => void }) => putSavedFilters(data.filter.filter_id, data.filter), + (data: { filter: SavedFilterType; onSuccess?: () => void }) => { + // filter_builder will be deleted only for new filters. + if (_.has(data.filter.query, 'filter_builder')) { + data.filter.query = _.omit(data.filter.query, 'filter_builder'); + } + return putSavedFilters(data.filter.filter_id, data.filter); + }, { onSuccess: (_data, variables) => { variables.onSuccess && variables.onSuccess(); diff --git a/src/pages/Stream/components/Querier/SaveFilterModal.tsx b/src/pages/Stream/components/Querier/SaveFilterModal.tsx index 5fbce403..cac31a69 100644 --- a/src/pages/Stream/components/Querier/SaveFilterModal.tsx +++ b/src/pages/Stream/components/Querier/SaveFilterModal.tsx @@ -22,14 +22,7 @@ interface FormObjectType extends Omit } const sanitizeFilterItem = (formObject: FormObjectType): SavedFilterType => { - const { - stream_name, - filter_name, - filter_id = '', - query, - selectedTimeRangeOption, - version = '', - } = formObject; + const { stream_name, filter_name, filter_id = '', query, selectedTimeRangeOption, version = '' } = formObject; return { filter_id, version, @@ -42,7 +35,6 @@ const sanitizeFilterItem = (formObject: FormObjectType): SavedFilterType => { const SaveFilterModal = () => { const [isSaveFiltersModalOpen, setFilterStore] = useFilterStore((store) => store.isSaveFiltersModalOpen); - const [appliedQuery] = useFilterStore((store) => store.appliedQuery); const [activeSavedFilters] = useAppStore((store) => store.activeSavedFilters); const [formObject, setFormObject] = useState(null); const [currentStream] = useAppStore((store) => store.currentStream); @@ -76,7 +68,7 @@ const SaveFilterModal = () => { filter_name: '', query: { filter_type: isSqlMode ? 'sql' : 'builder', - ...(isSqlMode ? { filter_query: custSearchQuery } : { filter_builder: appliedQuery }), + filter_query: custSearchQuery, }, time_filter: { from: timeRange.startTime.toISOString(), diff --git a/src/pages/Stream/components/Querier/SavedFiltersModal.tsx b/src/pages/Stream/components/Querier/SavedFiltersModal.tsx index 6c93dd8b..c06cabb7 100644 --- a/src/pages/Stream/components/Querier/SavedFiltersModal.tsx +++ b/src/pages/Stream/components/Querier/SavedFiltersModal.tsx @@ -12,6 +12,7 @@ import classes from './styles/SavedFiltersModalStyles.module.css'; import { EmptySimple } from '@/components/Empty'; import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; import useSavedFiltersQuery from '@/hooks/useSavedFilters'; +import { generateQueryBuilderASTFromSQL } from '../../utils'; const { toggleSavedFiltersModal, resetFilters, parseQuery, applySavedFilters } = filterStoreReducers; const { applyCustomQuery, updateSavedFilterId, getCleanStoreForRefetch, setTimeRange } = logsStoreReducers; @@ -68,21 +69,30 @@ const SavedFilterItem = (props: { deleteSavedFilterMutation({ filter_id }); }, [showDeletePropmt]); + const handleTimeFilter = useCallback(() => { + if (time_filter === null || (time_filter && isStoredAndCurrentTimeRangeAreSame(time_filter.from, time_filter.to))) { + hardRefresh(); + } else { + changeTimerange(time_filter.from, time_filter.to); + } + }, [time_filter, isStoredAndCurrentTimeRangeAreSame, hardRefresh, changeTimerange]); + const onApplyFilters = useCallback(() => { - if (_.isString(query.filter_query)) { - props.onSqlSearchApply(query.filter_query, filter_id, time_filter); + if (query.filter_query) { + if (query.filter_type === 'sql') { + props.onSqlSearchApply(query.filter_query, filter_id, time_filter); + } else { + if (filter_id !== savedFilterId) { + props.onFilterBuilderQueryApply(generateQueryBuilderASTFromSQL(query.filter_query), filter_id); + } else { + handleTimeFilter(); + } + } } else if (query.filter_builder) { if (filter_id !== savedFilterId) { props.onFilterBuilderQueryApply(query.filter_builder, filter_id); } else { - if ( - time_filter === null || - (time_filter && isStoredAndCurrentTimeRangeAreSame(time_filter.from, time_filter.to)) - ) { - hardRefresh(); - } else { - changeTimerange(time_filter.from, time_filter.to); - } + handleTimeFilter(); } } }, [savedFilterId, isStoredAndCurrentTimeRangeAreSame, hardRefresh, changeTimerange]); diff --git a/src/pages/Stream/hooks/useParamsController.ts b/src/pages/Stream/hooks/useParamsController.ts index c64811e6..c90d96c7 100644 --- a/src/pages/Stream/hooks/useParamsController.ts +++ b/src/pages/Stream/hooks/useParamsController.ts @@ -6,11 +6,14 @@ import { FIXED_DURATIONS } from '@/constants/timeConstants'; import dayjs from 'dayjs'; import timeRangeUtils from '@/utils/timeRangeUtils'; import moment from 'moment-timezone'; +import { filterStoreReducers, QueryType, useFilterStore } from '../providers/FilterProvider'; +import { generateQueryBuilderASTFromSQL } from '../utils'; const { getRelativeStartAndEndDate, formatDateWithTimezone, getLocalTimezone } = timeRangeUtils; const { setTimeRange, onToggleView, setPerPage, setCustQuerySearchState } = logsStoreReducers; +const { applySavedFilters } = filterStoreReducers; const timeRangeFormat = 'DD-MMM-YYYY_HH-mmz'; -const keys = ['view', 'rows', 'interval', 'from', 'to', 'query']; +const keys = ['view', 'rows', 'interval', 'from', 'to', 'query', 'filterType']; const FIXED_ROWS = ['50', '100', '150', '200']; const dateToParamString = (date: Date) => { @@ -54,8 +57,9 @@ const storeToParamsObj = (opts: { page: string; rows: string; query: string; + filterType: string; }): Record => { - const { timeRange, offset, page, view, rows, query } = opts; + const { timeRange, offset, page, view, rows, query, filterType } = opts; const params: Record = { ...deriveTimeRangeParams(timeRange), view, @@ -63,6 +67,7 @@ const storeToParamsObj = (opts: { rows, page, query, + filterType: query ? filterType : '', }; return _.pickBy(params, (val, key) => !_.isEmpty(val) && _.includes(keys, key)); }; @@ -84,6 +89,7 @@ const useParamsController = () => { const [viewMode] = useLogsStore((store) => store.viewMode); const [custQuerySearchState] = useLogsStore((store) => store.custQuerySearchState); const [timeRange, setLogsStore] = useLogsStore((store) => store.timeRange); + const [, setFilterStore] = useFilterStore((store) => store); const { currentOffset, currentPage, perPage } = tableOpts; @@ -97,6 +103,7 @@ const useParamsController = () => { view: viewMode, rows: `${perPage}`, query: custQuerySearchState.custSearchQuery, + filterType: custQuerySearchState.viewMode, }); const presentParams = paramsStringToParamsObj(searchParams); if (['table', 'json'].includes(presentParams.view) && presentParams.view !== storeAsParams.view) { @@ -107,7 +114,11 @@ const useParamsController = () => { } if (storeAsParams.query !== presentParams.query) { - setLogsStore((store) => setCustQuerySearchState(store, presentParams.query)); + setLogsStore((store) => setCustQuerySearchState(store, presentParams.query, presentParams.filterType)); + if (presentParams.filterType === 'filters') + setFilterStore((store) => + applySavedFilters(store, generateQueryBuilderASTFromSQL(presentParams.query) as QueryType), + ); } syncTimeRangeToStore(storeAsParams, presentParams); setStoreSynced(true); @@ -122,6 +133,7 @@ const useParamsController = () => { view: viewMode, rows: `${perPage}`, query: custQuerySearchState.custSearchQuery, + filterType: custQuerySearchState.viewMode, }); const presentParams = paramsStringToParamsObj(searchParams); if (_.isEqual(storeAsParams, presentParams)) return; @@ -139,6 +151,7 @@ const useParamsController = () => { view: viewMode, rows: `${perPage}`, query: custQuerySearchState.custSearchQuery, + filterType: custQuerySearchState.viewMode, }); const presentParams = paramsStringToParamsObj(searchParams); @@ -152,8 +165,12 @@ const useParamsController = () => { setLogsStore((store) => setPerPage(store, _.toNumber(presentParams.rows))); } - if (storeAsParams.query !== presentParams.query) { - setLogsStore((store) => setCustQuerySearchState(store, presentParams.query)); + if (storeAsParams.query !== presentParams.query && !_.isEmpty(presentParams.query)) { + if (presentParams.filterType === 'filters') + setFilterStore((store) => + applySavedFilters(store, generateQueryBuilderASTFromSQL(presentParams.query) as QueryType), + ); + setLogsStore((store) => setCustQuerySearchState(store, presentParams.query, presentParams.filterType)); } syncTimeRangeToStore(storeAsParams, presentParams); }, [searchParams]); diff --git a/src/pages/Stream/providers/LogsProvider.tsx b/src/pages/Stream/providers/LogsProvider.tsx index b9a0ead8..684e8d42 100644 --- a/src/pages/Stream/providers/LogsProvider.tsx +++ b/src/pages/Stream/providers/LogsProvider.tsx @@ -248,7 +248,7 @@ type LogsStoreReducers = { resetQuickFilters: (store: LogsStore) => ReducerOutput; streamChangeCleanup: (store: LogsStore) => ReducerOutput; toggleQueryBuilder: (store: LogsStore, val?: boolean) => ReducerOutput; - setCustQuerySearchState: (store: LogsStore, query: string) => ReducerOutput; + setCustQuerySearchState: (store: LogsStore, query: string, viewMode: string) => ReducerOutput; resetCustQuerySearchState: (store: LogsStore) => ReducerOutput; toggleCustQuerySearchViewMode: (store: LogsStore, targetMode: 'sql' | 'filters') => ReducerOutput; toggleDeleteModal: (store: LogsStore, val?: boolean) => ReducerOutput; @@ -455,7 +455,7 @@ const toggleQueryBuilder = (store: LogsStore, val?: boolean) => { }; }; -const setCustQuerySearchState = (store: LogsStore, query: string) => { +const setCustQuerySearchState = (store: LogsStore, query: string, viewMode: string) => { const { timeRange } = store; return { custQuerySearchState: { @@ -463,8 +463,8 @@ const setCustQuerySearchState = (store: LogsStore, query: string) => { savedFilterId: null, isQuerySearchActive: true, custSearchQuery: query, - viewMode: 'sql', - activeMode: 'sql' as 'sql', + viewMode, + activeMode: viewMode === 'filters' ? ('filters' as 'filters') : ('sql' as 'sql'), }, ...getCleanStoreForRefetch(store), timeRange, diff --git a/src/pages/Stream/utils.ts b/src/pages/Stream/utils.ts index a709934d..8a89ab5f 100644 --- a/src/pages/Stream/utils.ts +++ b/src/pages/Stream/utils.ts @@ -1,6 +1,8 @@ import { Log } from '@/@types/parseable/api/query'; import { LogStreamSchemaData } from '@/@types/parseable/api/stream'; import { columnsToSkip } from './providers/LogsProvider'; +import { parseSQL } from 'react-querybuilder'; +import { QueryType, RuleGroupTypeOverride, RuleTypeOverride } from './providers/FilterProvider'; export const getPageSlice = (page = 1, perPage: number, data: Log[]) => { const firstPageIndex = (page - 1) * perPage; @@ -8,6 +10,29 @@ export const getPageSlice = (page = 1, perPage: number, data: Log[]) => { return data ? data.slice(firstPageIndex, lastPageIndex) : []; }; +export const generateQueryBuilderASTFromSQL = (sqlString: string) => { + const parsedQuery = parseSQL(sqlString) as QueryType; + + function isRuleGroup(rule: RuleTypeOverride | RuleGroupTypeOverride): rule is RuleGroupTypeOverride { + return 'combinator' in rule && 'rules' in rule; + } + + function addIds(query: QueryType | RuleGroupTypeOverride) { + if (Array.isArray(query.rules)) { + query.rules.forEach((rule) => { + rule.id = `rule-${Math.random()}`; + + if (isRuleGroup(rule)) { + addIds(rule); + } + }); + } + } + + addIds(parsedQuery); + return parsedQuery; +}; + export const makeHeadersFromSchema = (schema: LogStreamSchemaData | null): string[] => { if (schema) { const { fields } = schema;