Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 2.x] [Explorer] Data selector enhancement and refactoring adoptions #1771

Merged
merged 1 commit into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions common/constants/data_sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,7 @@ export const ACCELERATION_AGGREGRATION_FUNCTIONS = [
];

export const SPARK_PARTITION_INFO = `# Partition Information`;
export const OBS_DEFAULT_CLUSTER = 'observability-default'; // prefix key for generating data source id for default cluster in data selector
export const OBS_S3_DATA_SOURCE = 'observability-s3'; // prefix key for generating data source id for s3 data sources in data selector
export const S3_DATA_SOURCE_GROUP_DISPLAY_NAME = 'Amazon S3'; // display group name for Amazon-managed-s3 data sources in data selector
export const S3_DATA_SOURCE_GROUP_SPARK_DISPLAY_NAME = 'Spark'; // display group name for OpenSearch-spark-s3 data sources in data selector
2 changes: 1 addition & 1 deletion common/constants/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ export const VISUALIZATION_ERROR = {
NO_METRIC: 'Invalid Metric MetaData',
};

export const S3_DATASOURCE_TYPE = 'S3_DATASOURCE';
export const S3_DATA_SOURCE_TYPE = 's3glue';

export const ASYNC_QUERY_SESSION_ID = 'async-query-session-id';
export const ASYNC_QUERY_DATASOURCE_CACHE = 'async-query-catalog-cache';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { batch, useDispatch, useSelector } from 'react-redux';
import { htmlIdGenerator } from '@elastic/eui';
import { LogExplorerRouterContext } from '../..';
import {
DataSource,
DataSourceGroup,
DataSourceSelectable,
DataSourceType,
Expand All @@ -20,6 +22,7 @@
DEFAULT_DATA_SOURCE_TYPE,
DEFAULT_DATA_SOURCE_TYPE_NAME,
INDEX_URL_PARAM_KEY,
OBS_DEFAULT_CLUSTER,
OLLY_QUESTION_URL_PARAM_KEY,
QUERY_LANGUAGE,
} from '../../../../../common/constants/data_sources';
Expand Down Expand Up @@ -90,6 +93,21 @@
}
};

const getMatchedOption = (
dataSourceList: DataSourceGroup[],
dataSourceName: string,
dataSourceType: string
) => {
if (!dataSourceName || !dataSourceType) return [];
for (const dsGroup of dataSourceList) {
const matchedOption = dsGroup.options.find(
(item) => item.type === dataSourceType && item.name === dataSourceName
);
if (matchedOption !== undefined) return [matchedOption];
}
return [];
};

export const DataSourceSelection = ({ tabId }: { tabId: string }) => {
const { dataSources, http } = coreRefs;
const sqlService = new SQLService(http!);
Expand All @@ -99,7 +117,11 @@
const [activeDataSources, setActiveDataSources] = useState<DataSourceType[]>([]);
const [dataSourceOptionList, setDataSourceOptionList] = useState<DataSourceGroup[]>([]);
const [selectedSources, setSelectedSources] = useState<SelectedDataSource[]>(
getDataSourceState(explorerSearchMetadata.datasources)
getMatchedOption(
dataSourceOptionList,
explorerSearchMetadata.datasources?.[0]?.name || '',
explorerSearchMetadata.datasources?.[0]?.type || ''
)
);

/**
Expand Down Expand Up @@ -149,8 +171,14 @@
};

useEffect(() => {
setSelectedSources(getDataSourceState(explorerSearchMetadata.datasources));
}, [explorerSearchMetadata.datasources]);
setSelectedSources(
getMatchedOption(
memorizedDataSourceOptionList,
explorerSearchMetadata.datasources?.[0]?.name || '',
explorerSearchMetadata.datasources?.[0]?.type || ''
)
);
}, [explorerSearchMetadata.datasources, dataSourceOptionList]);

Check warning on line 181 in public/components/event_analytics/explorer/datasources/datasources_selection.tsx

View workflow job for this annotation

GitHub Actions / Lint

React Hook useEffect has a missing dependency: 'memorizedDataSourceOptionList'. Either include it or remove the dependency array

const handleDataSetFetchError = useCallback(() => {
return (error: Error) => {
Expand All @@ -162,25 +190,36 @@
* Subscribe to data source updates and manage the active data sources state.
*/
useEffect(() => {
const subscription = dataSources.dataSourceService.dataSources$.subscribe(
(currentDataSources) => {
const subscription = dataSources.dataSourceService
.getDataSources$()
.subscribe((currentDataSources: DataSource[]) => {
// temporary solution for 2.11 to render OpenSearch / default cluster for observability
// local indices and index patterns, while keep listing all index patterns for data explorer
// it filters the registered index pattern data sources in data plugin, and attach default cluster
// for all indices
setActiveDataSources([
new ObservabilityDefaultDataSource({
id: htmlIdGenerator(OBS_DEFAULT_CLUSTER)(DEFAULT_DATA_SOURCE_TYPE),
name: DEFAULT_DATA_SOURCE_NAME,
type: DEFAULT_DATA_SOURCE_TYPE,
metadata: null,
metadata: {
ui: {
label: DEFAULT_DATA_SOURCE_OBSERVABILITY_DISPLAY_NAME,
groupType: DEFAULT_DATA_SOURCE_OBSERVABILITY_DISPLAY_NAME,
selector: {
displayDatasetsAsSource: false, // when true, selector UI will render data sets with source by calling getDataSets()
},
},
},
}),
...Object.values(currentDataSources).filter((ds) => ds.type !== DEFAULT_DATA_SOURCE_TYPE),
...Object.values(currentDataSources).filter(
(ds) => ds.getType() !== DEFAULT_DATA_SOURCE_TYPE
),
]);
}
);
});

return () => subscription.unsubscribe();
}, []);

Check warning on line 222 in public/components/event_analytics/explorer/datasources/datasources_selection.tsx

View workflow job for this annotation

GitHub Actions / Lint

React Hook useEffect has a missing dependency: 'dataSources.dataSourceService'. Either include it or remove the dependency array

/**
* Check for URL parameters to update the data source if redirected from discover.
Expand Down Expand Up @@ -228,7 +267,7 @@
);
}
}
}, []);

Check warning on line 270 in public/components/event_analytics/explorer/datasources/datasources_selection.tsx

View workflow job for this annotation

GitHub Actions / Lint

React Hook useEffect has missing dependencies: 'dispatch', 'resetStateOnDataSourceChange', 'routerContext?.searchParams', and 'tabId'. Either include them or remove the dependency array

useEffect(() => {
// Execute a dummy query to initialize the cluster and obtain a sessionId for subsequent queries.
Expand All @@ -241,7 +280,7 @@
) {
runDummyQuery(dsName);
}
}, [explorerSearchMetadata.datasources]);

Check warning on line 283 in public/components/event_analytics/explorer/datasources/datasources_selection.tsx

View workflow job for this annotation

GitHub Actions / Lint

React Hook useEffect has a missing dependency: 'runDummyQuery'. Either include it or remove the dependency array

/**
* Process the data source options to display different than discover's group names.
Expand All @@ -259,6 +298,10 @@
});
}, [dataSourceOptionList]);

const onRefresh = useCallback(() => {
dataSources.dataSourceService.reload();
}, [dataSources.dataSourceService]);

return (
<DataSourceSelectable
className="dsc-selector"
Expand All @@ -267,9 +310,10 @@
setDataSourceOptionList={setDataSourceOptionList}
selectedSources={selectedSources}
onDataSourceSelect={handleSourceChange}
onFetchDataSetError={handleDataSetFetchError}
singleSelection={{ asPlainText: true }}
dataSourceSelectorConfigs={DATA_SOURCE_SELECTOR_CONFIGS}
onGetDataSetError={handleDataSetFetchError}
onRefresh={onRefresh}
/>
);
};
25 changes: 2 additions & 23 deletions public/components/event_analytics/explorer/log_explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { isEmpty } from 'lodash';
import React, { useContext, useEffect, useRef, useState } from 'react';
import { batch, useSelector, useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { LogExplorerRouterContext } from '..';
import {
Expand All @@ -26,14 +26,6 @@ import {
} from '../../../../common/constants/shared';
import { coreRefs } from '../../../../public/framework/core_refs';

import { init as initFields } from '../../event_analytics/redux/slices/field_slice';
import { init as initPatterns } from '../../event_analytics/redux/slices/patterns_slice';
import { init as initQueryResult } from '../../event_analytics/redux/slices/query_result_slice';
import { init as initQuery } from '../../event_analytics/redux/slices/query_slice';
import { init as initVisualizationConfig } from '../../event_analytics/redux/slices/viualization_config_slice';
import { resetSummary as initQueryAssistSummary } from '../../event_analytics/redux/slices/query_assistant_summarization_slice';
import { init as initSearchMetaData } from '../../event_analytics/redux/slices/search_meta_data_slice';

const searchBarConfigs = {
[TAB_EVENT_ID]: {
showSaveButton: true,
Expand All @@ -60,7 +52,6 @@ export const LogExplorer = ({
dataSourcePluggables,
}: ILogExplorerProps) => {
const history = useHistory();
const dispatch = useDispatch();
const routerContext = useContext(LogExplorerRouterContext);
const tabIds = useSelector(selectQueryTabs).queryTabIds.filter(
(tabid: string) => !tabid.match(APP_ANALYTICS_TAB_ID_REGEX)
Expand Down Expand Up @@ -97,20 +88,8 @@ export const LogExplorer = ({
useEffect(() => {
if (!isEmpty(savedObjectId)) {
dispatchSavedObjectId();
} else {
// below piece of code was added to simulate creating a new tab if saved obj isn't being loaded,
// since tabs being visually removed means 'new tab' cannot be created any other way
const tabId = tabIds[0];
batch(() => {
dispatch(initQuery({ tabId }));
dispatch(initQueryResult({ tabId }));
dispatch(initFields({ tabId }));
dispatch(initVisualizationConfig({ tabId }));
dispatch(initPatterns({ tabId }));
dispatch(initQueryAssistSummary({ tabId }));
dispatch(initSearchMetaData({ tabId }));
});
}

if (routerContext && routerContext.searchParams.has(CREATE_TAB_PARAM_KEY)) {
// need to wait for current redux event loop to finish
setImmediate(() => {
Expand Down
9 changes: 5 additions & 4 deletions public/framework/datasources/obs_opensearch_datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,26 @@
import { DataSource } from '../../../../../src/plugins/data/public';

interface DataSourceConfig {
id: string;
name: string;
type: string;
metadata: any;

Check warning on line 12 in public/framework/datasources/obs_opensearch_datasource.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
}

export class ObservabilityDefaultDataSource extends DataSource<any, any, any, any, any> {

Check warning on line 15 in public/framework/datasources/obs_opensearch_datasource.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type

Check warning on line 15 in public/framework/datasources/obs_opensearch_datasource.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type

Check warning on line 15 in public/framework/datasources/obs_opensearch_datasource.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type

Check warning on line 15 in public/framework/datasources/obs_opensearch_datasource.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type

Check warning on line 15 in public/framework/datasources/obs_opensearch_datasource.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
constructor({ name, type, metadata }: DataSourceConfig) {
super(name, type, metadata);
constructor({ id, name, type, metadata }: DataSourceConfig) {
super({ id, name, type, metadata });
}

async getDataSet(dataSetParams?: any) {
async getDataSet() {
return ['Default data source'];
}

async testConnection(): Promise<boolean> {
return true;
}

async runQuery(queryParams: any) {
async runQuery() {
return null;
}
}
19 changes: 10 additions & 9 deletions public/framework/datasources/s3_datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,23 @@ interface DataSourceConfig {
name: string;
type: string;
metadata: any;
id: string;
}

export class S3DataSource extends DataSource<any, any, any, any, any> {
constructor({ name, type, metadata }: DataSourceConfig) {
super(name, type, metadata);
export class S3DataSource extends DataSource {
constructor({ id, name, type, metadata }: DataSourceConfig) {
super({ id, name, type, metadata });
}

async getDataSet(dataSetParams?: any) {
return [this.getName()];
async getDataSet() {
return { dataSets: [this.getName()] };
}

async testConnection(): Promise<void> {
throw new Error('This operation is not supported for this class.');
async testConnection(): Promise<boolean> {
return true;
}

async runQuery(queryParams: any) {
return null;
async runQuery() {
return { data: {} };
}
}
66 changes: 49 additions & 17 deletions public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { i18n } from '@osd/i18n';
import React from 'react';
import { i18n } from '@osd/i18n';
import { htmlIdGenerator } from '@elastic/eui';
import {
AppCategory,
AppMountParameters,
Expand Down Expand Up @@ -46,7 +47,7 @@ import {
observabilityTracesID,
observabilityTracesPluginOrder,
observabilityTracesTitle,
S3_DATASOURCE_TYPE,
S3_DATA_SOURCE_TYPE,
} from '../common/constants/shared';
import { QueryManager } from '../common/query_manager';
import { AssociatedObject, CachedAcceleration } from '../common/types/data_connections';
Expand Down Expand Up @@ -95,6 +96,12 @@ import {
ObservabilityStart,
SetupDependencies,
} from './types';
import {
DATA_SOURCE_TYPES,
OBS_S3_DATA_SOURCE,
S3_DATA_SOURCE_GROUP_DISPLAY_NAME,
S3_DATA_SOURCE_GROUP_SPARK_DISPLAY_NAME,
} from '../common/constants/data_sources';

interface PublicConfig {
query_assist: {
Expand Down Expand Up @@ -387,38 +394,63 @@ export class ObservabilityPlugin
coreRefs.overlays = core.overlays;

const { dataSourceService, dataSourceFactory } = startDeps.data.dataSources;
dataSourceFactory.registerDataSourceType(S3_DATA_SOURCE_TYPE, S3DataSource);

const getDataSourceTypeLabel = (type: string) => {
if (type === DATA_SOURCE_TYPES.S3Glue) return S3_DATA_SOURCE_GROUP_DISPLAY_NAME;
if (type === DATA_SOURCE_TYPES.SPARK) return S3_DATA_SOURCE_GROUP_SPARK_DISPLAY_NAME;
return `${type.charAt(0).toUpperCase()}${type.slice(1)}`;
};

// register all s3 datasources
const registerS3Datasource = () => {
dataSourceFactory.registerDataSourceType(S3_DATASOURCE_TYPE, S3DataSource);
core.http.get(`${DATACONNECTIONS_BASE}`).then((s3DataSources) => {
s3DataSources.map((s3ds) => {
dataSourceService.registerDataSource(
dataSourceFactory.getDataSourceInstance(S3_DATASOURCE_TYPE, {
name: s3ds.name,
type: s3ds.connector.toLowerCase(),
metadata: s3ds,
})
);
const registerDataSources = () => {
try {
core.http.get(`${DATACONNECTIONS_BASE}`).then((s3DataSources) => {
s3DataSources.map((s3ds) => {
dataSourceService.registerDataSource(
dataSourceFactory.getDataSourceInstance(S3_DATA_SOURCE_TYPE, {
id: htmlIdGenerator(OBS_S3_DATA_SOURCE)(),
name: s3ds.name,
type: s3ds.connector.toLowerCase(),
metadata: {
...s3ds.properties,
ui: {
label: s3ds.name,
typeLabel: getDataSourceTypeLabel(s3ds.connector.toLowerCase()),
groupType: s3ds.connector.toLowerCase(),
selector: {
displayDatasetsAsSource: false,
},
},
},
})
);
});
});
});
} catch (error) {
console.error('Error registering S3 datasources', error);
}
};

dataSourceService.registerDataSourceFetchers([
{ type: S3_DATA_SOURCE_TYPE, registerDataSources },
]);

if (startDeps.securityDashboards) {
core.http
.get(SECURITY_PLUGIN_ACCOUNT_API)
.then(() => {
registerS3Datasource();
registerDataSources();
})
.catch((e) => {
if (e?.response?.status !== 401) {
// accounts api should not return any error status other than 401 if security installed,
// this datasource register is included just in case
registerS3Datasource();
registerDataSources();
}
});
} else {
registerS3Datasource();
registerDataSources();
}

core.http.intercept({
Expand Down
Loading