diff --git a/src/plugins/data/public/query/query_string/dataset_service/types.ts b/src/plugins/data/public/query/query_string/dataset_service/types.ts index 65c322acec6f..4b1cbb553ad7 100644 --- a/src/plugins/data/public/query/query_string/dataset_service/types.ts +++ b/src/plugins/data/public/query/query_string/dataset_service/types.ts @@ -43,6 +43,13 @@ export interface DatasetTypeConfig { id: string; /** Human-readable title for the dataset type */ title: string; + languageOverrides?: { + [language: string]: { + /** The overrides transfer the responsibility of handling the input from + * the language interceptor to the dataset types strategy. */ + hideDatePicker?: boolean; + }; + }; /** Metadata for UI representation */ meta: { /** Icon to represent the dataset type */ @@ -51,7 +58,7 @@ export interface DatasetTypeConfig { tooltip?: string; /** Optional preference for search on page load else defaulted to true */ searchOnLoad?: boolean; - /** Optional supportsTimeFilter determines if a time filter is needed */ + /** Optional supportsTimeFilter determines if a time field is supported */ supportsTimeFilter?: boolean; /** Optional isFieldLoadAsync determines if field loads are async */ isFieldLoadAsync?: boolean; diff --git a/src/plugins/data/public/query/query_string/language_service/types.ts b/src/plugins/data/public/query/query_string/language_service/types.ts index c80858d67102..6404aa98ed65 100644 --- a/src/plugins/data/public/query/query_string/language_service/types.ts +++ b/src/plugins/data/public/query/query_string/language_service/types.ts @@ -61,6 +61,11 @@ export interface LanguageConfig { }; editorSupportedAppNames?: string[]; supportedAppNames?: string[]; + /** + * @deprecated + * + * Use `datasetTypeConfig.supportsTimeFilter` instead + */ hideDatePicker?: boolean; sampleQueries?: SampleQuery[]; } diff --git a/src/plugins/data/public/ui/dataset_selector/configurator.tsx b/src/plugins/data/public/ui/dataset_selector/configurator.tsx index 0dba9107934c..4906bec2ef84 100644 --- a/src/plugins/data/public/ui/dataset_selector/configurator.tsx +++ b/src/plugins/data/public/ui/dataset_selector/configurator.tsx @@ -69,6 +69,7 @@ export const Configurator = ({ const [selectedIndexedView, setSelectedIndexedView] = useState(); const [indexedViews, setIndexedViews] = useState([]); const [isLoadingIndexedViews, setIsLoadingIndexedViews] = useState(false); + const [timeFieldsLoading, setTimeFieldsLoading] = useState(false); useEffect(() => { let isMounted = true; @@ -91,23 +92,26 @@ export const Configurator = ({ const submitDisabled = useMemo(() => { return ( - timeFieldName === undefined && - !( - languageService.getLanguage(language)?.hideDatePicker || - dataset.type === DEFAULT_DATA.SET_TYPES.INDEX_PATTERN - ) && - timeFields && - timeFields.length > 0 + timeFieldsLoading || + (timeFieldName === undefined && + !(dataset.type === DEFAULT_DATA.SET_TYPES.INDEX_PATTERN) && + timeFields && + timeFields.length > 0) ); - }, [dataset, language, timeFieldName, timeFields, languageService]); + }, [dataset, timeFieldName, timeFields, timeFieldsLoading]); useEffect(() => { const fetchFields = async () => { - const datasetFields = await queryString - .getDatasetService() - .getType(baseDataset.type) - ?.fetchFields(baseDataset); + const datasetType = queryString.getDatasetService().getType(baseDataset.type); + if (!datasetType) { + setTimeFields([]); + return; + } + setTimeFieldsLoading(true); + const datasetFields = await datasetType + .fetchFields(baseDataset) + .finally(() => setTimeFieldsLoading(false)); const dateFields = datasetFields?.filter((field) => field.type === 'date'); setTimeFields(dateFields || []); }; @@ -152,6 +156,16 @@ export const Configurator = ({ }; }, [indexedViewsService, selectedIndexedView, dataset]); + const shouldRenderDatePickerField = useCallback(() => { + const datasetType = queryString.getDatasetService().getType(dataset.type); + + const supportsTimeField = datasetType?.meta?.supportsTimeFilter; + if (supportsTimeField !== undefined) { + return Boolean(supportsTimeField); + } + return true; + }, [dataset.type, queryString]); + return ( <> @@ -256,7 +270,7 @@ export const Configurator = ({ data-test-subj="advancedSelectorLanguageSelect" /> - {!languageService.getLanguage(language)?.hideDatePicker && + {shouldRenderDatePickerField() && (dataset.type === DEFAULT_DATA.SET_TYPES.INDEX_PATTERN ? ( true + * - showDatePicker=false && showAutoRefreshOnly=false => false + * - both undefined => true (default) + * If isDatePickerEnabled is false, returns false immediately + * + * Dataset Type permutations (datasetType?.meta?.supportsTimeFilter): + * - supportsTimeFilter=true => true + * - supportsTimeFilter=false => false + * - supportsTimeFilter=undefined && dataset exists => falls through to language check + * - no dataset => falls through to language check + * + * Language permutations (when dataset.meta.supportsTimeFilter is undefined): + * - queryLanguage=undefined => true (shows date picker) + * - queryLanguage exists: + * - hideDatePicker=true => false + * - hideDatePicker=false => true + * - hideDatePicker=undefined => true + * + * Example scenarios: + * 1. {showDatePicker: false} => false + * 2. {dataset: {type: 'x', meta: {supportsTimeFilter: true}}} => true + * 3. {dataset: {type: 'x', meta: {supportsTimeFilter: false}}} => false + * 4. {dataset: {type: 'x'}, queryLanguage: 'sql', hideDatePicker: true} => false + * 5. {dataset: {type: 'x'}, queryLanguage: 'sql', hideDatePicker: false} => true + * 6. {dataset: {type: 'x'}, queryLanguage: undefined} => true (no language restrictions) + * 7. No configuration => true (default behavior shows date picker) + */ function shouldRenderDatePicker(): boolean { - return ( - Boolean((props.showDatePicker || props.showAutoRefreshOnly) ?? true) && - !( - queryLanguage && - data.query.queryString.getLanguageService().getLanguage(queryLanguage)?.hideDatePicker - ) && - (props.query?.dataset - ? data.query.queryString.getDatasetService().getType(props.query.dataset.type)?.meta - ?.supportsTimeFilter !== false - : true) + const { queryString } = data.query; + const datasetService = queryString.getDatasetService(); + const languageService = queryString.getLanguageService(); + const isDatePickerEnabled = Boolean( + (props.showDatePicker || props.showAutoRefreshOnly) ?? true ); + if (!isDatePickerEnabled) return false; + + // Get dataset type configuration + const datasetType = props.query?.dataset + ? datasetService.getType(props.query?.dataset.type) + : undefined; + // Check if dataset type explicitly configures the `supportsTimeFilter` option + if (datasetType?.meta?.supportsTimeFilter === false) return false; + + if ( + queryLanguage && + datasetType?.languageOverrides?.[queryLanguage]?.hideDatePicker !== undefined + ) { + return Boolean(!datasetType.languageOverrides[queryLanguage].hideDatePicker); + } + + return Boolean(!(queryLanguage && languageService.getLanguage(queryLanguage)?.hideDatePicker)); } function shouldRenderQueryEditor(): boolean { diff --git a/src/plugins/query_enhancements/common/types.ts b/src/plugins/query_enhancements/common/types.ts index 1bb977527d4a..2f73ca52d496 100644 --- a/src/plugins/query_enhancements/common/types.ts +++ b/src/plugins/query_enhancements/common/types.ts @@ -4,7 +4,7 @@ */ import { CoreSetup } from 'opensearch-dashboards/public'; -import { PollQueryResultsParams } from '../../data/common'; +import { PollQueryResultsParams, TimeRange } from '../../data/common'; export interface QueryAggConfig { [key: string]: { @@ -26,7 +26,10 @@ export interface EnhancedFetchContext { http: CoreSetup['http']; path: string; signal?: AbortSignal; - body?: { pollQueryResultsParams: PollQueryResultsParams }; + body?: { + pollQueryResultsParams?: PollQueryResultsParams; + timeRange?: TimeRange; + }; } export interface QueryStatusOptions { diff --git a/src/plugins/query_enhancements/common/utils.ts b/src/plugins/query_enhancements/common/utils.ts index 9b2bb9e3aacf..cdb8fb05c08b 100644 --- a/src/plugins/query_enhancements/common/utils.ts +++ b/src/plugins/query_enhancements/common/utils.ts @@ -55,6 +55,7 @@ export const fetch = (context: EnhancedFetchContext, query: Query, aggConfig?: Q query: { ...query, format: 'jdbc' }, aggConfig, pollQueryResultsParams: context.body?.pollQueryResultsParams, + timeRange: context.body?.timeRange, }); return from( http.fetch({ diff --git a/src/plugins/query_enhancements/public/search/ppl_search_interceptor.ts b/src/plugins/query_enhancements/public/search/ppl_search_interceptor.ts index 57152dbe98ea..ecfe32ff8a75 100644 --- a/src/plugins/query_enhancements/public/search/ppl_search_interceptor.ts +++ b/src/plugins/query_enhancements/public/search/ppl_search_interceptor.ts @@ -50,6 +50,7 @@ export class PPLSearchInterceptor extends SearchInterceptor { signal, body: { pollQueryResultsParams: request.params?.pollQueryResultsParams, + timeRange: request.params?.body?.timeRange, }, }; @@ -68,15 +69,33 @@ export class PPLSearchInterceptor extends SearchInterceptor { .getDatasetService() .getType(datasetType); strategy = datasetTypeConfig?.getSearchOptions?.().strategy ?? strategy; + + if ( + dataset?.timeFieldName && + datasetTypeConfig?.languageOverrides?.PPL?.hideDatePicker === false + ) { + request.params = { + ...request.params, + body: { + ...request.params.body, + timeRange: this.queryService.timefilter.timefilter.getTime(), + }, + }; + } } return this.runSearch(request, options.abortSignal, strategy); } private buildQuery() { - const query: Query = this.queryService.queryString.getQuery(); + const { queryString } = this.queryService; + const query: Query = queryString.getQuery(); const dataset = query.dataset; if (!dataset || !dataset.timeFieldName) return query; + const datasetService = queryString.getDatasetService(); + if (datasetService.getType(dataset.type)?.languageOverrides?.PPL?.hideDatePicker === false) + return query; + const [baseQuery, ...afterPipeParts] = query.query.split('|'); const afterPipe = afterPipeParts.length > 0 ? ` | ${afterPipeParts.join('|').trim()}` : ''; const timeFilter = this.getTimeFilter(dataset.timeFieldName); diff --git a/src/plugins/query_enhancements/public/search/sql_search_interceptor.ts b/src/plugins/query_enhancements/public/search/sql_search_interceptor.ts index 9fe17fc79322..9f93dd067cb3 100644 --- a/src/plugins/query_enhancements/public/search/sql_search_interceptor.ts +++ b/src/plugins/query_enhancements/public/search/sql_search_interceptor.ts @@ -42,6 +42,7 @@ export class SQLSearchInterceptor extends SearchInterceptor { signal, body: { pollQueryResultsParams: request.params?.pollQueryResultsParams, + timeRange: request.params?.body?.timeRange, }, }; @@ -62,6 +63,16 @@ export class SQLSearchInterceptor extends SearchInterceptor { .getDatasetService() .getType(datasetType); strategy = datasetTypeConfig?.getSearchOptions?.().strategy ?? strategy; + + if (datasetTypeConfig?.languageOverrides?.SQL?.hideDatePicker === false) { + request.params = { + ...request.params, + body: { + ...request.params.body, + timeRange: this.queryService.timefilter.timefilter.getTime(), + }, + }; + } } return this.runSearch(request, options.abortSignal, strategy); diff --git a/src/plugins/query_enhancements/server/routes/index.ts b/src/plugins/query_enhancements/server/routes/index.ts index 79b93a279272..20928c5529f4 100644 --- a/src/plugins/query_enhancements/server/routes/index.ts +++ b/src/plugins/query_enhancements/server/routes/index.ts @@ -77,6 +77,7 @@ export function defineSearchStrategyRouteProvider(logger: Logger, router: IRoute sessionId: schema.maybe(schema.string()), }) ), + timeRange: schema.maybe(schema.object({}, { unknowns: 'allow' })), }), }, },