Skip to content

Commit

Permalink
[MDS] TSVB Support (#6298)
Browse files Browse the repository at this point in the history
* Add MDS support to TSVB

Signed-off-by: Huy Nguyen <[email protected]>

* Refactor datasource picker component

Signed-off-by: Huy Nguyen <[email protected]>

* Allow picker to persist state

Signed-off-by: Huy Nguyen <[email protected]>

* Refactored picker component params

Signed-off-by: Huy Nguyen <[email protected]>

* Add unit tests

Signed-off-by: Huy Nguyen <[email protected]>

* Add to CHANGELOG

Signed-off-by: Huy Nguyen <[email protected]>

* Refactor components to use hideLocalCluster

Signed-off-by: Huy Nguyen <[email protected]>

* Remove Picker wrapper

Signed-off-by: Huy Nguyen <[email protected]>

* Update selector component and rename field to index name

Signed-off-by: Huy Nguyen <[email protected]>

* Address comments

Signed-off-by: Huy Nguyen <[email protected]>

* Refactor to use different decideClient

Signed-off-by: Huy Nguyen <[email protected]>

* Add optional arg

Signed-off-by: Huy Nguyen <[email protected]>

* Remove hidelocalcluster as a setting

Signed-off-by: Huy Nguyen <[email protected]>

* Fixed case where local cluster is disabled but the datasource id could be local cluster

Signed-off-by: Huy Nguyen <[email protected]>

* Add test for create data source picker handler

Signed-off-by: Huy Nguyen <[email protected]>

---------

Signed-off-by: Huy Nguyen <[email protected]>
  • Loading branch information
huyaboo authored Apr 16, 2024
1 parent b9c703b commit be0f9d5
Show file tree
Hide file tree
Showing 26 changed files with 251 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [Workspace] Add permission control logic ([#6052](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6052))
- [Multiple Datasource] Add default icon for selectable component and make sure the default datasource shows automatically ([#6327](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6327))
- [Multiple Datasource] Pass selected data sources to plugin consumers when the multi-select component initially loads ([#6333](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6333))
- [Mulitple Datasource] Add multi data source support to TSVB ([#6298](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6298))
- [Multiple Datasource] Add installedPlugins list to data source saved object ([#6348](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6348))
- [Multiple Datasource] Add default icon in multi-selectable picker ([#6357](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6357))
- [Workspace] Add APIs to support plugin state in request ([#6303](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6303))
Expand Down
1 change: 1 addition & 0 deletions src/plugins/data/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export {
FieldDescriptor as IndexPatternFieldDescriptor,
shouldReadFieldFromDocValues, // used only in logstash_fields fixture
FieldDescriptor,
decideClient,
} from './index_patterns';

export {
Expand Down
1 change: 1 addition & 0 deletions src/plugins/data/server/index_patterns/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@
export * from './utils';
export { IndexPatternsFetcher, FieldDescriptor, shouldReadFieldFromDocValues } from './fetcher';
export { IndexPatternsService, IndexPatternsServiceStart } from './index_patterns_service';
export { decideClient } from './routes';
2 changes: 1 addition & 1 deletion src/plugins/data/server/index_patterns/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export function registerRoutes(http: HttpServiceSetup) {
);
}

const decideClient = async (
export const decideClient = async (
context: RequestHandlerContext,
request: any
): Promise<LegacyAPICaller> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface DataSourceSelectorProps {
dataSourceFilter?: (dataSource: SavedObject<DataSourceAttributes>) => boolean;
compressed?: boolean;
uiSettings?: IUiSettingsClient;
isClearable?: boolean;
}

interface DataSourceSelectorState {
Expand Down Expand Up @@ -202,6 +203,7 @@ export class DataSourceSelector extends React.Component<

return (
<EuiComboBox
isClearable={this.props.isClearable}
aria-label={
placeholderText
? i18n.translate('dataSourceSelectorComboBoxAriaLabel', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
* SPDX-License-Identifier: Apache-2.0
*/

export { DataSourceSelector } from './data_source_selector';
export { DataSourceSelector, DataSourceOption } from './data_source_selector';
2 changes: 1 addition & 1 deletion src/plugins/data_source_management/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function plugin() {
}

export { DataSourceManagementPluginStart } from './types';
export { DataSourceSelector } from './components/data_source_selector';
export { DataSourceSelector, DataSourceOption } from './components/data_source_selector';
export { DataSourceMenu } from './components/data_source_menu';
export { DataSourceManagementPlugin, DataSourceManagementPluginSetup } from './plugin';
export {
Expand Down
1 change: 1 addition & 0 deletions src/plugins/vis_type_timeseries/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@

export const MAX_BUCKETS_SETTING = 'metrics:max_buckets';
export const INDEXES_SEPARATOR = ',';
export const DATA_SOURCE_ID_KEY = 'data_source_id';
1 change: 1 addition & 0 deletions src/plugins/vis_type_timeseries/common/vis_schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ export const panel = schema.object({
ignore_global_filters: numberOptional,
ignore_global_filter: numberOptional,
index_pattern: stringRequired,
data_source_id: stringOptionalNullable,
interval: stringRequired,
isModelInvalid: schema.maybe(schema.boolean()),
legend_position: stringOptionalNullable,
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/vis_type_timeseries/opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"server": true,
"ui": true,
"requiredPlugins": ["charts", "data", "expressions", "visualizations"],
"optionalPlugins": ["usageCollection"],
"optionalPlugins": ["usageCollection", "dataSourceManagement", "dataSource"],
"requiredBundles": ["opensearchDashboardsUtils", "opensearchDashboardsReact"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
EuiText,
} from '@elastic/eui';
import { FormattedMessage } from '@osd/i18n/react';
import { i18n } from '@osd/i18n';

function newAnnotation() {
return {
Expand Down Expand Up @@ -131,9 +132,13 @@ export class AnnotationsEditor extends Component {
label={
<FormattedMessage
id="visTypeTimeseries.annotationsEditor.indexPatternLabel"
defaultMessage="Index pattern (required)"
defaultMessage="Index name (required)"
/>
}
helpText={i18n.translate('visTypeTimeseries.indexPattern.searchByIndex', {
defaultMessage:
'Use an asterisk (*) to match multiple indices. Spaces and the characters , /, ?, ", <, >, | are not allowed.',
})}
fullWidth
>
<EuiFieldText
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ import {
EuiText,
} from '@elastic/eui';
import { FieldSelect } from './aggs/field_select';
import { createDataSourcePickerHandler } from './lib/create_data_source_change_handler';
import {
getSavedObjectsClient,
getNotifications,
getDataSourceManagementSetup,
} from '../../services';
import { createSelectHandler } from './lib/create_select_handler';
import { createTextHandler } from './lib/create_text_handler';
import { YesNo } from './yes_no';
Expand Down Expand Up @@ -112,6 +118,14 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model
};

const model = { ...defaults, ..._model };

const dataSourceManagementEnabled =
!!getDataSourceManagementSetup().dataSourceManagement || false;
const handleDataSourceSelectChange = createDataSourcePickerHandler(onChange);
const DataSourceSelector = dataSourceManagementEnabled
? getDataSourceManagementSetup().dataSourceManagement.ui.DataSourceSelector
: undefined;

const isDefaultIndexPatternUsed = model.default_index_pattern && !model[indexPatternName];
const intervalValidation = validateIntervalValue(model[intervalName]);
const selectedTimeRangeOption = timeRangeOptions.find(
Expand Down Expand Up @@ -157,18 +171,47 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model
</EuiFlexItem>
</EuiFlexGroup>
)}
{!!dataSourceManagementEnabled && (
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
id={htmlId('dataSource')}
label={i18n.translate('visTypeTimeseries.indexPattern.dataSourceLabel', {
defaultMessage: 'Data source',
})}
>
<DataSourceSelector
savedObjectsClient={getSavedObjectsClient().client}
notifications={getNotifications().toasts}
onSelectedDataSource={handleDataSourceSelectChange}
defaultOption={
model.data_source_id !== undefined ? [{ id: model.data_source_id }] : undefined
}
disabled={false}
fullWidth={false}
removePrepend={true}
isClearable={false}
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
)}
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
id={htmlId('indexPattern')}
label={i18n.translate('visTypeTimeseries.indexPatternLabel', {
defaultMessage: 'Index pattern',
defaultMessage: 'Index name',
})}
helpText={
isDefaultIndexPatternUsed &&
i18n.translate('visTypeTimeseries.indexPattern.searchByDefaultIndex', {
defaultMessage: 'Default index pattern is used. To query all indexes use *',
})
isDefaultIndexPatternUsed
? i18n.translate('visTypeTimeseries.indexPattern.searchByDefaultIndex', {
defaultMessage: 'Default index pattern is used. To query all indexes use *',
})
: i18n.translate('visTypeTimeseries.indexPattern.searchByIndex', {
defaultMessage:
'Use an asterisk (*) to match multiple indices. Spaces and the characters , /, ?, ", <, >, | are not allowed.',
})
}
>
<EuiFieldText
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { createDataSourcePickerHandler } from './create_data_source_change_handler';

describe('createDataSourcePickerHandler()', () => {
let handleChange: jest.Mock<any, any>;
let changeHandler: (selectedOptions: []) => void;

beforeEach(() => {
handleChange = jest.fn();
changeHandler = createDataSourcePickerHandler(handleChange);
});

test.each([
{
id: undefined,
},
{},
])(
'calls handleChange() and sets data_source_id to undefined if id cannot be found or is undefined',
({ id }) => {
// @ts-ignore
changeHandler([{ id }]);
expect(handleChange.mock.calls.length).toEqual(1);
expect(handleChange.mock.calls[0][0]).toEqual({
data_source_id: undefined,
});
}
);

test.each([
{
id: '',
},
{
id: 'foo',
},
])('calls handleChange() function with partial and updates the data_source_id', ({ id }) => {
// @ts-ignore
changeHandler([{ id }]);
expect(handleChange.mock.calls.length).toEqual(1);
expect(handleChange.mock.calls[0][0]).toEqual({
data_source_id: id,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import _ from 'lodash';

import { PanelSchema } from 'src/plugins/vis_type_timeseries/common/types';
import { DATA_SOURCE_ID_KEY } from '../../../../common/constants';

export const createDataSourcePickerHandler = (handleChange: (e: PanelSchema) => void) => {
return (selectedOptions: []): void => {
return handleChange?.({
[DATA_SOURCE_ID_KEY]: _.get(selectedOptions, '[0].id', undefined),
} as PanelSchema);
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { createBrushHandler } from '../lib/create_brush_handler';
import { fetchFields } from '../lib/fetch_fields';
import { extractIndexPatterns } from '../../../../../plugins/vis_type_timeseries/common/extract_index_patterns';
import { getSavedObjectsClient, getUISettings, getDataStart, getCoreStart } from '../../services';
import { DATA_SOURCE_ID_KEY } from '../../../common/constants';

import { CoreStartContextProvider } from '../contexts/query_input_bar_context';
import { OpenSearchDashboardsContextProvider } from '../../../../../plugins/opensearch_dashboards_react/public';
Expand Down Expand Up @@ -113,9 +114,13 @@ export class VisEditor extends Component {
}

if (this.props.isEditorMode) {
const dataSourceId = nextModel[DATA_SOURCE_ID_KEY] || undefined;
const extractedIndexPatterns = extractIndexPatterns(nextModel);
if (!isEqual(this.state.extractedIndexPatterns, extractedIndexPatterns)) {
fetchFields(extractedIndexPatterns).then((visFields) =>
if (
!isEqual(this.state.extractedIndexPatterns, extractedIndexPatterns) ||
!isEqual(this.state.model[DATA_SOURCE_ID_KEY], dataSourceId)
) {
fetchFields(extractedIndexPatterns, dataSourceId).then((visFields) =>
this.setState({
visFields,
extractedIndexPatterns,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,18 @@

import { i18n } from '@osd/i18n';
import { extractIndexPatterns } from '../../../common/extract_index_patterns';
import { DATA_SOURCE_ID_KEY } from '../../../common/constants';
import { getCoreStart } from '../../services';

export async function fetchFields(indexPatterns = ['*']) {
export async function fetchFields(indexPatterns = ['*'], dataSourceId = undefined) {
const patterns = Array.isArray(indexPatterns) ? indexPatterns : [indexPatterns];
try {
const indexFields = await Promise.all(
patterns.map((pattern) => {
return getCoreStart().http.get('/api/metrics/fields', {
query: {
index: pattern,
data_source: dataSourceId,
},
});
})
Expand All @@ -62,7 +64,8 @@ export async function fetchFields(indexPatterns = ['*']) {
}

export async function fetchIndexPatternFields({ params, fields = {} }) {
const dataSourceId = params[DATA_SOURCE_ID_KEY] || undefined;
const indexPatterns = extractIndexPatterns(params, fields);

return await fetchFields(indexPatterns);
return await fetchFields(indexPatterns, dataSourceId);
}
16 changes: 15 additions & 1 deletion src/plugins/vis_type_timeseries/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import {
CoreStart,
Plugin,
} from 'opensearch-dashboards/public';
import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_management/public';
import { DataSourcePluginSetup } from 'src/plugins/data_source/public';
import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public';
import { VisualizationsSetup } from '../../visualizations/public';

Expand All @@ -49,6 +51,8 @@ import {
setCoreStart,
setDataStart,
setChartsSetup,
setDataSourceManagementSetup,
setNotifications,
} from './services';
import { DataPublicPluginStart } from '../../data/public';
import { ChartsPluginSetup } from '../../charts/public';
Expand All @@ -58,6 +62,8 @@ export interface MetricsPluginSetupDependencies {
expressions: ReturnType<ExpressionsPublicPlugin['setup']>;
visualizations: VisualizationsSetup;
charts: ChartsPluginSetup;
dataSourceManagement?: DataSourceManagementPluginSetup;
dataSource?: DataSourcePluginSetup;
}

/** @internal */
Expand All @@ -75,12 +81,19 @@ export class MetricsPlugin implements Plugin<Promise<void>, void> {

public async setup(
core: CoreSetup,
{ expressions, visualizations, charts }: MetricsPluginSetupDependencies
{
expressions,
visualizations,
charts,
dataSourceManagement,
dataSource,
}: MetricsPluginSetupDependencies
) {
expressions.registerFunction(createMetricsFn);
setUISettings(core.uiSettings);
setChartsSetup(charts);
visualizations.createReactVisualization(metricsVisDefinition);
setDataSourceManagementSetup({ dataSourceManagement });
}

public start(core: CoreStart, { data }: MetricsPluginStartDependencies) {
Expand All @@ -89,5 +102,6 @@ export class MetricsPlugin implements Plugin<Promise<void>, void> {
setFieldFormats(data.fieldFormats);
setDataStart(data);
setCoreStart(core);
setNotifications(core.notifications);
}
}
17 changes: 16 additions & 1 deletion src/plugins/vis_type_timeseries/public/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@
* under the License.
*/

import { I18nStart, SavedObjectsStart, IUiSettingsClient, CoreStart } from 'src/core/public';
import {
I18nStart,
SavedObjectsStart,
IUiSettingsClient,
CoreStart,
NotificationsStart,
} from 'src/core/public';
import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_management/public';
import { createGetterSetter } from '../../opensearch_dashboards_utils/public';
import { ChartsPluginSetup } from '../../charts/public';
import { DataPublicPluginStart } from '../../data/public';
Expand All @@ -52,3 +59,11 @@ export const [getI18n, setI18n] = createGetterSetter<I18nStart>('I18n');
export const [getChartsSetup, setChartsSetup] = createGetterSetter<ChartsPluginSetup>(
'ChartsPluginSetup'
);

export const [getDataSourceManagementSetup, setDataSourceManagementSetup] = createGetterSetter<{
dataSourceManagement: DataSourceManagementPluginSetup | undefined;
}>('DataSourceManagementSetup');

export const [getNotifications, setNotifications] = createGetterSetter<NotificationsStart>(
'Notifications'
);
Loading

0 comments on commit be0f9d5

Please sign in to comment.