Skip to content

Commit

Permalink
[MD] Concatenate data source name with index pattern name and change …
Browse files Browse the repository at this point in the history
…delimiter to double colon (opensearch-project#5907)

* concatenate data source name with index pattern name

Signed-off-by: Lu Yu <nluyu@amazon.com>

* add changelog

Signed-off-by: Lu Yu <nluyu@amazon.com>

* add tests

Signed-off-by: Lu Yu <nluyu@amazon.com>

---------

Signed-off-by: Lu Yu <nluyu@amazon.com>
  • Loading branch information
BionIT committed Feb 23, 2024
1 parent 4081154 commit 4ab0ca8
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [Multiple Datasource] Add interfaces to register add-on authentication method from plug-in module ([#5851](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5851))
- [Multiple Datasource] Able to Hide "Local Cluster" option from datasource DropDown ([#5827](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5827))
- [Multiple Datasource] Add api registry and allow it to be added into client config in data source plugin ([#5895](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5895))
- [Multiple Datasource] Concatenate data source name with index pattern name and change delimiter to double colon ([#5907](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5907))

### 🐛 Bug Fixes

Expand Down
54 changes: 54 additions & 0 deletions src/plugins/data/common/index_patterns/lib/get_title.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { SavedObjectsClientContract } from '../../../../../core/public';
import { getTitle } from './get_title';

describe('test getTitle', () => {
let client: SavedObjectsClientContract;

it('with dataSourceId match', async () => {
const dataSourceIdToTitle = new Map();
dataSourceIdToTitle.set('dataSourceId', 'dataSourceTitle');
client = {
get: jest.fn().mockResolvedValue({
attributes: { title: 'indexTitle' },
references: [{ type: 'data-source', id: 'dataSourceId' }],
}),
} as any;
const title = await getTitle(client, 'indexPatternId', dataSourceIdToTitle);
expect(title).toEqual('dataSourceTitle::indexTitle');
});

it('with no dataSourceId match and error to get data source', async () => {
const dataSourceIdToTitle = new Map();
client = {
get: jest
.fn()
.mockResolvedValueOnce({
attributes: { title: 'indexTitle' },
references: [{ type: 'data-source', id: 'dataSourceId' }],
})
.mockRejectedValue(new Error('error')),
} as any;
const title = await getTitle(client, 'indexPatternId', dataSourceIdToTitle);
expect(title).toEqual('dataSourceId::indexTitle');
});

it('with no dataSourceId match and success to get data source', async () => {
const dataSourceIdToTitle = new Map();
client = {
get: jest
.fn()
.mockResolvedValueOnce({
attributes: { title: 'indexTitle' },
references: [{ type: 'data-source', id: 'dataSourceId' }],
})
.mockResolvedValue({ attributes: { title: 'acquiredDataSourceTitle' } }),
} as any;
const title = await getTitle(client, 'indexPatternId', dataSourceIdToTitle);
expect(title).toEqual('acquiredDataSourceTitle::indexTitle');
});
});
28 changes: 25 additions & 3 deletions src/plugins/data/common/index_patterns/lib/get_title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,39 @@
* under the License.
*/

import { DataSourceAttributes } from 'src/plugins/data_source/common/data_sources';
import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public';
import {
concatDataSourceWithIndexPattern,
getIndexPatternTitle,
getDataSourceReference,
} from '../utils';

export async function getTitle(
client: SavedObjectsClientContract,
indexPatternId: string
): Promise<SimpleSavedObject<any>> {
indexPatternId: string,
dataSourceIdToTitle: Map<string, string>
): Promise<string> {
const savedObject = (await client.get('index-pattern', indexPatternId)) as SimpleSavedObject<any>;

if (savedObject.error) {
throw new Error(`Unable to get index-pattern title: ${savedObject.error.message}`);
}

return savedObject.attributes.title;
const dataSourceReference = getDataSourceReference(savedObject.references);

if (dataSourceReference) {
const dataSourceId = dataSourceReference.id;
if (dataSourceIdToTitle.has(dataSourceId)) {
return concatDataSourceWithIndexPattern(
dataSourceIdToTitle.get(dataSourceId)!,
savedObject.attributes.title
);
}
}

const getDataSource = async (id: string) =>
await client.get<DataSourceAttributes>('data-source', id);

return getIndexPatternTitle(savedObject.attributes.title, savedObject.references, getDataSource);
}
4 changes: 2 additions & 2 deletions src/plugins/data/common/index_patterns/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('test getIndexPatternTitle', () => {
referencesMock,
getDataSourceMock
);
expect(res).toEqual('dataSourceMockTitle.indexPatternMockTitle');
expect(res).toEqual('dataSourceMockTitle::indexPatternMockTitle');
});

test('getIndexPatternTitle should return index pattern title, when index-pattern is not referenced to any datasource', async () => {
Expand All @@ -87,6 +87,6 @@ describe('test getIndexPatternTitle', () => {
referencesMock,
getDataSourceMock
);
expect(res).toEqual('dataSourceId.indexPatternMockTitle');
expect(res).toEqual('dataSourceId::indexPatternMockTitle');
});
});
19 changes: 15 additions & 4 deletions src/plugins/data/common/index_patterns/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@ export const getIndexPatternTitle = async (
references: SavedObjectReference[],
getDataSource: (id: string) => Promise<SavedObject<DataSourceAttributes>>
): Promise<string> => {
const DATA_SOURCE_INDEX_PATTERN_DELIMITER = '.';
let dataSourceTitle;
const dataSourceReference = references.find((ref) => ref.type === 'data-source');
const dataSourceReference = getDataSourceReference(references);

// If an index-pattern references datasource, prepend data source name with index pattern name for display purpose
if (dataSourceReference) {
Expand All @@ -99,10 +98,22 @@ export const getIndexPatternTitle = async (
// use datasource id as title when failing to fetch datasource
dataSourceTitle = dataSourceId;
}

return dataSourceTitle.concat(DATA_SOURCE_INDEX_PATTERN_DELIMITER).concat(indexPatternTitle);
return concatDataSourceWithIndexPattern(dataSourceTitle, indexPatternTitle);
} else {
// if index pattern doesn't reference datasource, return as it is.
return indexPatternTitle;
}
};

export const concatDataSourceWithIndexPattern = (
dataSourceTitle: string,
indexPatternTitle: string
) => {
const DATA_SOURCE_INDEX_PATTERN_DELIMITER = '::';

return dataSourceTitle.concat(DATA_SOURCE_INDEX_PATTERN_DELIMITER).concat(indexPatternTitle);
};

export const getDataSourceReference = (references: SavedObjectReference[]) => {
return references.find((ref) => ref.type === 'data-source');
};
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ import { EuiComboBox, EuiComboBoxProps } from '@elastic/eui';

import { SavedObjectsClientContract, SimpleSavedObject } from 'src/core/public';
import { getTitle } from '../../../common/index_patterns/lib';
import {
getDataSourceReference,
concatDataSourceWithIndexPattern,
} from '../../../common/index_patterns/utils';

export type IndexPatternSelectProps = Required<
Omit<EuiComboBoxProps<any>, 'isLoading' | 'onSearchChange' | 'options' | 'selectedOptions'>,
Expand All @@ -52,6 +56,7 @@ interface IndexPatternSelectState {
options: [];
selectedIndexPattern: { value: string; label: string } | undefined;
searchValue: string | undefined;
dataSourceIdToTitle: Map<string, string>;
}

const getIndexPatterns = async (
Expand Down Expand Up @@ -80,6 +85,7 @@ export default class IndexPatternSelect extends Component<IndexPatternSelectProp

this.state = {
isLoading: false,
dataSourceIdToTitle: new Map(),
options: [],
selectedIndexPattern: undefined,
searchValue: undefined,
Expand Down Expand Up @@ -113,7 +119,11 @@ export default class IndexPatternSelect extends Component<IndexPatternSelectProp

let indexPatternTitle;
try {
indexPatternTitle = await getTitle(this.props.savedObjectsClient, indexPatternId);
indexPatternTitle = await getTitle(
this.props.savedObjectsClient,
indexPatternId,
this.state.dataSourceIdToTitle
);
} catch (err) {
// index pattern no longer exists
return;
Expand Down Expand Up @@ -161,16 +171,66 @@ export default class IndexPatternSelect extends Component<IndexPatternSelectProp
// We need this check to handle the case where search results come back in a different
// order than they were sent out. Only load results for the most recent search.
if (searchValue === this.state.searchValue) {
const dataSourcesToFetch: Array<{ type: string; id: string }> = [];
savedObjects.map((indexPatternSavedObject: SimpleSavedObject<any>) => {
const dataSourceReference = getDataSourceReference(indexPatternSavedObject.references);
if (dataSourceReference && !this.state.dataSourceIdToTitle.has(dataSourceReference.id)) {
dataSourcesToFetch.push({ type: 'data-source', id: dataSourceReference.id });
}
});

const dataSourceIdToTitleToUpdate = new Map();

if (dataSourcesToFetch.length > 0) {
const resp = await savedObjectsClient.bulkGet(dataSourcesToFetch);
resp.savedObjects.map((dataSourceSavedObject: SimpleSavedObject<any>) => {
dataSourceIdToTitleToUpdate.set(
dataSourceSavedObject.id,
dataSourceSavedObject.attributes.title
);
});
}

const options = savedObjects.map((indexPatternSavedObject: SimpleSavedObject<any>) => {
const dataSourceReference = getDataSourceReference(indexPatternSavedObject.references);
if (dataSourceReference) {
const dataSourceTitle =
this.state.dataSourceIdToTitle.get(dataSourceReference.id) ||
dataSourceIdToTitleToUpdate.get(dataSourceReference.id) ||
dataSourceReference.id;
return {
label: `${concatDataSourceWithIndexPattern(
dataSourceTitle,
indexPatternSavedObject.attributes.title
)}`,
value: indexPatternSavedObject.id,
};
}
return {
label: indexPatternSavedObject.attributes.title,
value: indexPatternSavedObject.id,
};
});
this.setState({
isLoading: false,
options,
});

if (dataSourceIdToTitleToUpdate.size > 0) {
const mergedDataSourceIdToTitle = new Map();
this.state.dataSourceIdToTitle.forEach((k, v) => {
mergedDataSourceIdToTitle.set(k, v);
});
dataSourceIdToTitleToUpdate.forEach((k, v) => {
mergedDataSourceIdToTitle.set(k, v);
});
this.setState({
dataSourceIdToTitle: mergedDataSourceIdToTitle,
isLoading: false,
options,
});
} else {
this.setState({
isLoading: false,
options,
});
}

if (onNoIndexPatterns && searchValue === '' && options.length === 0) {
onNoIndexPatterns();
Expand Down

0 comments on commit 4ab0ca8

Please sign in to comment.