Skip to content

Commit

Permalink
[Discover] Support custom logic to insert time filter based on datase…
Browse files Browse the repository at this point in the history
…t type (#8932)

* Pass time filter if language overrides hideDatePicker

---------

Signed-off-by: Joshua Li <[email protected]>
Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com>
  • Loading branch information
1 parent afb2605 commit 7df73dd
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 38 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/8932.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- Support custom logic to insert time filter based on dataset type ([#8932](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8932))
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const createSetupDatasetServiceMock = (): jest.Mocked<DatasetServiceContract> =>
fetchOptions: jest.fn(),
getRecentDatasets: jest.fn(),
addRecentDataset: jest.fn(),
clearCache: jest.fn(),
getLastCacheTime: jest.fn(),
removeFromRecentDatasets: jest.fn(),
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ export interface DatasetTypeConfig {
id: string;
/** Human-readable title for the dataset type */
title: string;
languageOverrides?: {
[language: string]: {
/** The override transfers the responsibility of handling the input from
* the language interceptor to the dataset type search strategy. */
hideDatePicker?: boolean;
};
};
/** Metadata for UI representation */
meta: {
/** Icon to represent the dataset type */
Expand All @@ -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;
Expand Down
72 changes: 68 additions & 4 deletions src/plugins/data/public/ui/dataset_selector/configurator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { Configurator } from './configurator';
import '@testing-library/jest-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import { setQueryService, setIndexPatterns } from '../../services';
import { IntlProvider } from 'react-intl';
import { Query } from '../../../../data/public';
import { Dataset } from 'src/plugins/data/common';
import { Query } from '../../../../data/public';
import { setIndexPatterns, setQueryService } from '../../services';
import { Configurator } from './configurator';

const getQueryMock = jest.fn().mockReturnValue({
query: '',
Expand Down Expand Up @@ -358,4 +358,68 @@ describe('Configurator Component', () => {
expect(submitButton).toBeEnabled();
});
});

it('should show the date picker if supportsTimeFilter is undefined', async () => {
const mockDataset = {
...mockBaseDataset,
timeFieldName: undefined,
type: 'index',
};
const { container } = render(
<IntlProvider locale="en" messages={messages}>
<Configurator
services={mockServices}
baseDataset={mockDataset}
onConfirm={mockOnConfirm}
onCancel={mockOnCancel}
onPrevious={mockOnPrevious}
/>
</IntlProvider>
);

expect(
container.querySelector(`[data-test-subj="advancedSelectorTimeFieldSelect"]`)
).toBeTruthy();
});

it('should hide the date picker if supportsTimeFilter is false', async () => {
const mockDataset = {
...mockBaseDataset,
timeFieldName: undefined,
type: 'index',
};
const datasetTypeConfig = mockServices
.getQueryService()
.queryString.getDatasetService()
.getType();
mockServices
.getQueryService()
.queryString.getDatasetService()
.getType.mockReturnValue({
...datasetTypeConfig,
meta: {
supportsTimeFilter: false,
},
});
const { container } = render(
<IntlProvider locale="en" messages={messages}>
<Configurator
services={mockServices}
baseDataset={mockDataset}
onConfirm={mockOnConfirm}
onCancel={mockOnCancel}
onPrevious={mockOnPrevious}
/>
</IntlProvider>
);

expect(
container.querySelector(`[data-test-subj="advancedSelectorTimeFieldSelect"]`)
).toBeFalsy();

mockServices
.getQueryService()
.queryString.getDatasetService()
.getType.mockReturnValue(datasetTypeConfig);
});
});
40 changes: 27 additions & 13 deletions src/plugins/data/public/ui/dataset_selector/configurator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const Configurator = ({
const [selectedIndexedView, setSelectedIndexedView] = useState<string | undefined>();
const [indexedViews, setIndexedViews] = useState<DatasetIndexedView[]>([]);
const [isLoadingIndexedViews, setIsLoadingIndexedViews] = useState(false);
const [timeFieldsLoading, setTimeFieldsLoading] = useState(false);

useEffect(() => {
let isMounted = true;
Expand All @@ -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 || []);
};
Expand Down Expand Up @@ -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 (
<>
<EuiModalHeader>
Expand Down Expand Up @@ -256,7 +270,7 @@ export const Configurator = ({
data-test-subj="advancedSelectorLanguageSelect"
/>
</EuiFormRow>
{!languageService.getLanguage(language)?.hideDatePicker &&
{shouldRenderDatePickerField() &&
(dataset.type === DEFAULT_DATA.SET_TYPES.INDEX_PATTERN ? (
<EuiFormRow
label={i18n.translate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Query, UI_SETTINGS } from '../../../common';
import { coreMock } from '../../../../../core/public/mocks';
import { dataPluginMock } from '../../mocks';
import React from 'react';
import { I18nProvider } from '@osd/i18n/react';
import { createEditor, DQLBody, QueryEditorTopRow, SingleLineInput } from '../';
import { OpenSearchDashboardsContextProvider } from 'src/plugins/opensearch_dashboards_react/public';
import { cleanup, render, waitFor } from '@testing-library/react';
import { LanguageConfig } from '../../query';
import React from 'react';
import { OpenSearchDashboardsContextProvider } from 'src/plugins/opensearch_dashboards_react/public';
import { createEditor, DQLBody, QueryEditorTopRow, SingleLineInput } from '../';
import { coreMock } from '../../../../../core/public/mocks';
import { Query, UI_SETTINGS } from '../../../common';
import { dataPluginMock } from '../../mocks';
import { DatasetTypeConfig, LanguageConfig } from '../../query';
import { datasetServiceMock } from '../../query/query_string/dataset_service/dataset_service.mock';
import { getQueryService } from '../../services';

const startMock = coreMock.createStart();
Expand Down Expand Up @@ -66,6 +67,7 @@ const createMockStorage = () => ({
});

const dataPlugin = dataPluginMock.createStartContract(true);
const datasetService = datasetServiceMock.createStartContract();

function wrapQueryEditorTopRowInContext(testProps: any) {
const defaultOptions = {
Expand Down Expand Up @@ -111,6 +113,7 @@ describe('QueryEditorTopRow', () => {
beforeEach(() => {
jest.clearAllMocks();
(getQueryService as jest.Mock).mockReturnValue(dataPlugin.query);
dataPlugin.query.queryString.getDatasetService = jest.fn().mockReturnValue(datasetService);
});

afterEach(() => {
Expand Down Expand Up @@ -155,4 +158,49 @@ describe('QueryEditorTopRow', () => {
await waitFor(() => expect(container.querySelector(QUERY_EDITOR)).toBeTruthy());
expect(container.querySelector(DATE_PICKER)).toBeFalsy();
});

it('Should not render date picker if dataset type does not support time field', async () => {
const query: Query = {
query: 'test query',
dataset: datasetService.getDefault(),
language: 'test-language',
};
dataPlugin.query.queryString.getQuery = jest.fn().mockReturnValue(query);
datasetService.getType.mockReturnValue({
meta: { supportsTimeFilter: false },
} as DatasetTypeConfig);

const { container } = render(
wrapQueryEditorTopRowInContext({
query,
showQueryEditor: false,
showDatePicker: true,
})
);
await waitFor(() => expect(container.querySelector(QUERY_EDITOR)).toBeTruthy());
expect(container.querySelector(DATE_PICKER)).toBeFalsy();
});

it('Should render date picker if dataset overrides hideDatePicker to false', async () => {
const query: Query = {
query: 'test query',
dataset: datasetService.getDefault(),
language: 'test-language',
};
dataPlugin.query.queryString.getQuery = jest.fn().mockReturnValue(query);
datasetService.getType.mockReturnValue(({
meta: { supportsTimeFilter: true },
languageOverrides: { 'test-language': { hideDatePicker: false } },
} as unknown) as DatasetTypeConfig);

const { container } = render(
wrapQueryEditorTopRowInContext({
query,
showQueryEditor: false,
showDatePicker: true,
})
);
await waitFor(() => expect(container.querySelector(QUERY_EDITOR)).toBeTruthy());
expect(container.querySelector(DATE_PICKER)).toBeTruthy();
});
});
55 changes: 45 additions & 10 deletions src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,18 +224,53 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) {
);
}

/**
* Determines if the date picker should be rendered based on UI settings, dataset configuration, and language settings.
*
* @returns {boolean} Whether the date picker should be rendered
*
* UI Settings permutations (isDatePickerEnabled):
* - showDatePicker=true || showAutoRefreshOnly=true => true
* - showDatePicker=false && showAutoRefreshOnly=false => false
* - both undefined => true (default)
* If isDatePickerEnabled is false, returns false immediately
*
* Dataset Type permutations (datasetType?.meta?.supportsTimeFilter):
* - supportsTimeFilter=false => false
*
* Language permutations (when dataset.meta.supportsTimeFilter is undefined or true):
* - queryLanguage=undefined => true (shows date picker)
* - queryLanguage exists:
* - languageOverrides[queryLanguage].hideDatePicker=true => false
* - languageOverrides[queryLanguage].hideDatePicker=false => true
* - hideDatePicker=true => false
* - hideDatePicker=false => true
* - hideDatePicker=undefined => true
*/
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 {
Expand Down
7 changes: 5 additions & 2 deletions src/plugins/query_enhancements/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]: {
Expand All @@ -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<T> {
Expand Down
1 change: 1 addition & 0 deletions src/plugins/query_enhancements/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Loading

0 comments on commit 7df73dd

Please sign in to comment.