Skip to content

Commit

Permalink
show create indexed view banner in discover canvas; show indexed view…
Browse files Browse the repository at this point in the history
…s in dataset selector

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
  • Loading branch information
amsiglan committed Oct 24, 2024
1 parent 9a25d0d commit 1d1ec27
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 34 deletions.
26 changes: 26 additions & 0 deletions src/plugins/data/common/datasets/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ export interface Dataset extends BaseDataset {
timeFieldName?: string;
/** Optional language to default to from the language selector */
language?: string;
/** Optional name of the indexed view to search data from */
indexedView?: string;
}

export interface DatasetField {
Expand All @@ -256,6 +258,30 @@ export interface DatasetField {
// TODO: osdFieldType?
}

export type CanvasBannerProps =
| {
componentType: 'callout';
title: string;
iconType?: string;
content?: React.ReactNode;
}
| {
componentType: 'callout';
title?: string;
iconType?: string;
content: React.ReactNode;
}
| {
componentType: 'custom';
content: React.ReactNode;
};

export interface DatasetSearchOptions {
strategy?: string;
/**
* Returns props used to render a banner on the Discover canvas/results section
*/
getBannerProps?: (
searchStatus: string
) => CanvasBannerProps | Promise<CanvasBannerProps> | undefined;
}
1 change: 1 addition & 0 deletions src/plugins/data/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ export {
QueryStart,
PersistedLog,
LanguageReference,
IndexedViewsService,
} from './query';

export { AggsStart } from './search/aggs';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ export interface DataStructureFetchOptions {
paginationToken?: string;
}

export interface IndexedView {
name: string;
}

export interface IndexedViewsService {
getIndexedViews: (dataset: Dataset) => Promise<IndexedView[]>;
}

/**
* Configuration for handling dataset operations.
*/
Expand Down Expand Up @@ -72,7 +80,7 @@ export interface DatasetTypeConfig {
* Retrieves the search options to be used for running the query on the data connection associated
* with this Dataset
*/
getSearchOptions?: () => DatasetSearchOptions;
getSearchOptions?: (dataset: Dataset) => DatasetSearchOptions;
/**
* Combines a list of user selected data structures into a single one to use in discover.
* @see https://github.com/opensearch-project/OpenSearch-Dashboards/issues/8362.
Expand All @@ -82,4 +90,8 @@ export interface DatasetTypeConfig {
* Returns a list of sample queries for this dataset type
*/
getSampleQueries?: (dataset: Dataset, language: string) => any;
/**
* Service used for indexedViews related operations
*/
indexedViewsService?: IndexedViewsService;
}
1 change: 1 addition & 0 deletions src/plugins/data/public/query/query_string/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export {
DatasetService,
DatasetServiceContract,
DatasetTypeConfig,
IndexedViewsService,
} from './dataset_service';
export {
LanguageServiceContract,
Expand Down
97 changes: 81 additions & 16 deletions src/plugins/data/public/ui/dataset_selector/configurator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { BaseDataset, DEFAULT_DATA, Dataset, DatasetField } from '../../../common';
import { getIndexPatterns, getQueryService } from '../../services';
import { IDataPluginServices } from '../../types';
import { IndexedView } from '../../query/query_string/dataset_service';

export const Configurator = ({
services,
Expand All @@ -41,7 +42,7 @@ export const Configurator = ({
const languageService = queryService.queryString.getLanguageService();
const indexPatternsService = getIndexPatterns();
const type = queryString.getDatasetService().getType(baseDataset.type);
const languages = type?.supportedLanguages(baseDataset) || [];
const [languages, setLanguages] = useState(type?.supportedLanguages(baseDataset) || []);

const [dataset, setDataset] = useState<Dataset>(baseDataset);
const [timeFields, setTimeFields] = useState<DatasetField[]>([]);
Expand All @@ -52,25 +53,49 @@ export const Configurator = ({
defaultMessage: "I don't want to use the time filter",
}
);
const [language, setLanguage] = useState<string>(() => {
const [selectedLanguage, setSelectedLanguage] = useState<string>('');
const indexedViewsService = type?.indexedViewsService;
const [selectedIndexedView, setSelectedIndexedView] = useState<string | undefined>();
const [indexedViews, setIndexedViews] = useState<IndexedView[]>([]);
const [loadingIndexedViews, setLoadingIndexedViews] = useState(false);

useEffect(() => {
setLanguages(type?.supportedLanguages(dataset) || []);
}, [dataset, type]);

useEffect(() => {
const currentLanguage = queryString.getQuery().language;
if (languages.includes(currentLanguage)) {
return currentLanguage;
setSelectedLanguage(currentLanguage);
}
return languages[0];
});
setSelectedLanguage(languages[0]);
}, [languages, queryString]);

useEffect(() => {
const getIndexedViews = async () => {
if (indexedViewsService) {
setLoadingIndexedViews(true);
const fetchedIndexedViews = await indexedViewsService.getIndexedViews(baseDataset);
setLoadingIndexedViews(false);
setIndexedViews(fetchedIndexedViews || []);
}
};

getIndexedViews();
}, [indexedViewsService, baseDataset]);

const submitDisabled = useMemo(() => {
return (
timeFieldName === undefined &&
!(
languageService.getLanguage(language)?.hideDatePicker ||
dataset.type === DEFAULT_DATA.SET_TYPES.INDEX_PATTERN
) &&
timeFields &&
timeFields.length > 0
loadingIndexedViews ||
(timeFieldName === undefined &&
!(
languageService.getLanguage(selectedLanguage)?.hideDatePicker ||
dataset.type === DEFAULT_DATA.SET_TYPES.INDEX_PATTERN
) &&
timeFields &&
timeFields.length > 0)
);
}, [dataset, language, timeFieldName, timeFields, languageService]);
}, [dataset, selectedLanguage, timeFieldName, timeFields, languageService, loadingIndexedViews]);

useEffect(() => {
const fetchFields = async () => {
Expand Down Expand Up @@ -123,6 +148,46 @@ export const Configurator = ({
>
<EuiFieldText disabled value={dataset.title} />
</EuiFormRow>
{indexedViews.length > 0 && (
<EuiFormRow
label={i18n.translate(
'data.explorer.datasetSelector.advancedSelector.configurator.indexedViewLabel',
{
defaultMessage: 'Available indexed views',
}
)}
helpText={i18n.translate(
'data.explorer.datasetSelector.advancedSelector.configurator.indexedViewHelpText',
{
defaultMessage: 'Select an indexed view to speed up your query.',
}
)}
>
<EuiSelect
isLoading={loadingIndexedViews}
placeholder={i18n.translate(
'data.explorer.datasetSelector.advancedSelector.configurator.indexedViewSelector.placeholder',
{
defaultMessage: 'Select indexed view',
}
)}
options={indexedViews.map(({ name }) => ({
text: name,
value: name,
}))}
value={selectedIndexedView}
onChange={(e) => {
setSelectedIndexedView(e.target.value);
setDataset({
...dataset,
indexedView: e.target.value,
title: `${dataset.title}.${e.target.value}`,
});
}}
hasNoInitialSelection
/>
</EuiFormRow>
)}
<EuiFormRow
label={i18n.translate(
'data.explorer.datasetSelector.advancedSelector.configurator.languageLabel',
Expand All @@ -136,14 +201,14 @@ export const Configurator = ({
text: languageService.getLanguage(languageId)?.title || languageId,
value: languageId,
}))}
value={language}
value={selectedLanguage}
onChange={(e) => {
setLanguage(e.target.value);
setSelectedLanguage(e.target.value);
setDataset({ ...dataset, language: e.target.value });
}}
/>
</EuiFormRow>
{!languageService.getLanguage(language)?.hideDatePicker &&
{!languageService.getLanguage(selectedLanguage)?.hideDatePicker &&
(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 @@ -4,7 +4,7 @@
*/

import React, { useEffect, useState, useRef, useCallback } from 'react';
import { EuiPanel, EuiSpacer } from '@elastic/eui';
import { EuiCallOut, EuiPanel, EuiSpacer } from '@elastic/eui';
import { TopNav } from './top_nav';
import { ViewProps } from '../../../../../data_explorer/public';
import { DiscoverTable } from './discover_table';
Expand All @@ -28,6 +28,7 @@ import { OpenSearchSearchHit } from '../../../application/doc_views/doc_views_ty
import { buildColumns } from '../../utils/columns';
import './discover_canvas.scss';
import { HeaderVariant } from '../../../../../../core/public';
import { CanvasBannerProps } from '../../../../../data/common';

// eslint-disable-next-line import/no-default-export
export default function DiscoverCanvas({ setHeaderActionMenu, history, optionalRef }: ViewProps) {
Expand All @@ -41,6 +42,7 @@ export default function DiscoverCanvas({ setHeaderActionMenu, history, optionalR
data,
},
} = useOpenSearchDashboards<DiscoverViewServices>();
const [canvasBanner, setCanvasBanner] = useState<React.ReactNode>(null);
const { columns } = useSelector((state) => {
const stateColumns = state.discover.columns;

Expand Down Expand Up @@ -77,8 +79,6 @@ export default function DiscoverCanvas({ setHeaderActionMenu, history, optionalR

useEffect(() => {
const subscription = data$.subscribe((next) => {
if (next.status === ResultStatus.LOADING) return;

let shouldUpdateState = false;

if (next.status !== fetchState.status) shouldUpdateState = true;
Expand Down Expand Up @@ -123,6 +123,54 @@ export default function DiscoverCanvas({ setHeaderActionMenu, history, optionalR
}
};
const showSaveQuery = !!capabilities.discover?.saveQuery;
const query = data.query.queryString.getQuery();

const updateCanvasBanner = useCallback(
(bannerData: CanvasBannerProps) => {
switch (bannerData.componentType) {
case 'callout':
setCanvasBanner(
<>
<EuiSpacer size="s" />
<EuiCallOut title={bannerData.title} iconType={bannerData.iconType}>
{bannerData.content}
</EuiCallOut>
</>
);
break;
case 'custom':
setCanvasBanner(bannerData.content);
break;
default:
setCanvasBanner(null);
}
},
[setCanvasBanner]
);

useEffect(() => {
if (query.dataset) {
const typeConfig = data.query.queryString.getDatasetService().getType(query.dataset.type);
let bannerData;
if (
(bannerData = typeConfig
?.getSearchOptions?.(query.dataset)
?.getBannerProps?.(fetchState.status))
) {
if (bannerData instanceof Promise) {
bannerData
.then((newBannerData) => updateCanvasBanner(newBannerData))
.catch(() => {
// No-op
});
} else if (bannerData) {
updateCanvasBanner(bannerData);
} else {
setCanvasBanner(null);
}
}
}
}, [query.dataset, data, fetchState.status, updateCanvasBanner, setCanvasBanner]);

return (
<EuiPanel
Expand All @@ -145,15 +193,9 @@ export default function DiscoverCanvas({ setHeaderActionMenu, history, optionalR

{indexPattern ? (
<>
{fetchState.status === ResultStatus.NO_RESULTS && (
<DiscoverNoResults
queryString={data.query.queryString}
query={data.query.queryString.getQuery()}
savedQuery={data.query.savedQueries}
timeFieldName={timeField}
/>
)}
{fetchState.status === ResultStatus.ERROR && (
{isEnhancementsEnabled && canvasBanner}
{(fetchState.status === ResultStatus.NO_RESULTS ||
fetchState.status === ResultStatus.ERROR) && (
<DiscoverNoResults
queryString={data.query.queryString}
query={data.query.queryString.getQuery()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export const useSearch = (services: DiscoverViewServices) => {
let elapsedMs;
try {
// Only show loading indicator if we are fetching when the rows are empty
if (fetchStateRef.current.rows?.length === 0) {
if (!fetchStateRef.current.rows || fetchStateRef.current.rows.length === 0) {
data$.next({ status: ResultStatus.LOADING, queryStatus: { startTime } });
}

Expand Down
7 changes: 5 additions & 2 deletions src/plugins/query_enhancements/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ export class QueryEnhancementsPlugin
title: 'PPL',
search: pplSearchInterceptor,
getQueryString: (query: Query) => {
return `source = ${query.dataset?.title}`;
const dataset = query.dataset;
const source = dataset?.indexedView ?? dataset?.title;
return `source = ${source}`;
},
fields: {
filterable: false,
Expand All @@ -96,7 +98,8 @@ export class QueryEnhancementsPlugin
title: 'SQL',
search: sqlSearchInterceptor,
getQueryString: (query: Query) => {
return `SELECT * FROM ${query.dataset?.title} LIMIT 10`;
const source = query.dataset?.indexedView ?? query.dataset?.title;
return `SELECT * FROM ${source} LIMIT 10`;
},
fields: {
filterable: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class PPLSearchInterceptor extends SearchInterceptor {
const datasetTypeConfig = this.queryService.queryString
.getDatasetService()
.getType(datasetType);
strategy = datasetTypeConfig?.getSearchOptions?.().strategy ?? strategy;
strategy = datasetTypeConfig?.getSearchOptions?.(dataset).strategy ?? strategy;
}

return this.runSearch(request, options.abortSignal, strategy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class SQLSearchInterceptor extends SearchInterceptor {
const datasetTypeConfig = this.queryService.queryString
.getDatasetService()
.getType(datasetType);
strategy = datasetTypeConfig?.getSearchOptions?.().strategy ?? strategy;
strategy = datasetTypeConfig?.getSearchOptions?.(dataset).strategy ?? strategy;
}

return this.runSearch(request, options.abortSignal, strategy);
Expand Down

0 comments on commit 1d1ec27

Please sign in to comment.