Skip to content
Closed
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
2 changes: 2 additions & 0 deletions changelogs/fragments/11392.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fix:
- Fix dataset selector not displaying correct information ([#11392](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/11392))
129 changes: 129 additions & 0 deletions src/plugins/data/common/data_views/data_views/data_views.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,4 +353,133 @@ describe('DataViews', () => {
expect(result.id).toBe('test-id');
});
});

describe('convertToDataset', () => {
test('fetches actual data source title instead of using reference name', async () => {
const mockDataView = {
id: 'test-id',
title: 'logs-*',
type: 'INDEX_PATTERN',
timeFieldName: '@timestamp',
displayName: 'Test Logs',
description: 'Test description',
dataSourceRef: {
id: 'datasource-123',
name: 'dataSource', // This is the reference name, not the actual title
type: 'data-source',
},
} as DataView;

const mockDataSource = {
id: 'datasource-123',
attributes: {
title: 'DockerTest', // This is the actual data source title
dataSourceVersion: '2.0.0',
},
};

// Mock getDataSource to return the actual data source
jest.spyOn(dataViews, 'getDataSource').mockResolvedValue(mockDataSource as any);

const dataset = await dataViews.convertToDataset(mockDataView);

// Verify that getDataSource was called with the correct ID
expect(dataViews.getDataSource).toHaveBeenCalledWith('datasource-123');

// Verify the dataset has the actual data source title, not "dataSource"
expect(dataset.dataSource?.title).toBe('DockerTest');
expect(dataset.dataSource?.id).toBe('datasource-123');
expect(dataset.dataSource?.version).toBe('2.0.0');
expect(dataset.dataSource?.type).toBe('data-source');

// Verify other dataset properties are preserved
expect(dataset.id).toBe('test-id');
expect(dataset.title).toBe('logs-*');
expect(dataset.timeFieldName).toBe('@timestamp');
expect(dataset.displayName).toBe('Test Logs');
expect(dataset.description).toBe('Test description');
});

test('falls back to data source ID when fetch fails', async () => {
const mockDataView = {
id: 'test-id',
title: 'logs-*',
type: 'INDEX_PATTERN',
dataSourceRef: {
id: 'datasource-456',
name: 'dataSource',
type: 'data-source',
},
} as DataView;

// Mock getDataSource to throw an error
jest.spyOn(dataViews, 'getDataSource').mockRejectedValue(new Error('Not found'));

const dataset = await dataViews.convertToDataset(mockDataView);

// Should fall back to using the ID as title
expect(dataset.dataSource?.title).toBe('datasource-456');
expect(dataset.dataSource?.id).toBe('datasource-456');
expect(dataset.dataSource?.version).toBe('');
});

test('falls back to data source ID when fetch returns null', async () => {
const mockDataView = {
id: 'test-id',
title: 'logs-*',
type: 'INDEX_PATTERN',
dataSourceRef: {
id: 'datasource-789',
name: 'dataSource',
type: 'data-source',
},
} as DataView;

// Mock getDataSource to return null
jest.spyOn(dataViews, 'getDataSource').mockResolvedValue(null as any);

const dataset = await dataViews.convertToDataset(mockDataView);

// Should fall back to using the ID as title
expect(dataset.dataSource?.title).toBe('datasource-789');
expect(dataset.dataSource?.id).toBe('datasource-789');
expect(dataset.dataSource?.version).toBe('');
expect(dataset.dataSource?.type).toBe('data-source');
});

test('handles data view without data source', async () => {
const mockDataView = {
id: 'test-id',
title: 'logs-*',
type: 'INDEX_PATTERN',
timeFieldName: '@timestamp',
} as DataView;

const dataset = await dataViews.convertToDataset(mockDataView);

// Should not have dataSource property
expect(dataset.dataSource).toBeUndefined();
expect(dataset.id).toBe('test-id');
expect(dataset.title).toBe('logs-*');
});

test('uses toDataset method when available on dataView', async () => {
const mockDataset = {
id: 'custom-id',
title: 'custom-title',
type: 'CUSTOM_TYPE',
};

const mockDataView = {
id: 'test-id',
title: 'logs-*',
toDataset: jest.fn().mockResolvedValue(mockDataset),
} as any;

const dataset = await dataViews.convertToDataset(mockDataView);

expect(mockDataView.toDataset).toHaveBeenCalled();
expect(dataset).toEqual(mockDataset);
});
});
});
38 changes: 32 additions & 6 deletions src/plugins/data/common/data_views/data_views/data_views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -937,19 +937,45 @@ export class DataViewsService {
return await dataView.toDataset();
}

// Fetch actual data source to get its title and version
let dataSourceInfo;
if (dataView.dataSourceRef?.id) {
// Default dataSourceInfo with common fields
const defaultDataSourceInfo = {
id: dataView.dataSourceRef.id,
title: dataView.dataSourceRef.id,
type: dataView.dataSourceRef.type || DEFAULT_DATA.SOURCE_TYPES.OPENSEARCH,
version: '',
};

try {
const dataSource = await this.getDataSource(dataView.dataSourceRef.id);
if (dataSource) {
// Override with actual data source title and version if available
dataSourceInfo = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can move the common fields into a default dataSourceInfo object to reduce duplication.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for suggestion,
Updated to use

      const defaultDataSourceInfo = {
        id: dataView.dataSourceRef.id,
        title: dataView.dataSourceRef.id,
        type: dataView.dataSourceRef.type || DEFAULT_DATA.SOURCE_TYPES.OPENSEARCH,
        version: '',
      };
      ```

...defaultDataSourceInfo,
title: dataSource.attributes?.title || defaultDataSourceInfo.title,
version: dataSource.attributes?.dataSourceVersion || '',
};
} else {
// If dataSource is null/undefined, use default
dataSourceInfo = defaultDataSourceInfo;
}
} catch (error) {
// If fetching fails, use default
dataSourceInfo = defaultDataSourceInfo;
}
}

return {
id: dataView.id || '',
title: dataView.title,
type: dataView.type || DEFAULT_DATA.SET_TYPES.INDEX_PATTERN,
timeFieldName: dataView.timeFieldName,
displayName: dataView.displayName,
description: dataView.description,
...(dataView.dataSourceRef?.id && {
dataSource: {
id: dataView.dataSourceRef.id,
title: dataView.dataSourceRef.name || dataView.dataSourceRef.id,
type: dataView.dataSourceRef.type || DEFAULT_DATA.SOURCE_TYPES.OPENSEARCH,
},
...(dataSourceInfo && {
dataSource: dataSourceInfo,
}),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ describe('DatasetSelect', () => {
id: dataView.id,
title: dataView.title,
type: DEFAULT_DATA.SET_TYPES.INDEX_PATTERN,
displayName: dataView.displayName,
description: dataView.description,
timeFieldName: dataView.timeFieldName,
});
}),
clearCache: jest.fn(),
Expand Down Expand Up @@ -254,6 +257,7 @@ describe('DatasetSelect', () => {
mockDataViews.convertToDataset = jest.fn().mockResolvedValue({
id: 'restricted-id',
title: 'Restricted Dataset',
displayName: 'Restricted Dataset',
type: 'restricted-type',
});

Expand Down Expand Up @@ -309,6 +313,7 @@ describe('DatasetSelect', () => {
mockDataViews.convertToDataset = jest.fn().mockResolvedValue({
id: 'all-apps-id',
title: 'all-apps-dataset',
displayName: 'All Apps Dataset',
type: 'all-apps-type',
});
mockQueryService.queryString.getQuery = jest.fn().mockReturnValue({
Expand Down Expand Up @@ -395,6 +400,7 @@ describe('DatasetSelect', () => {
return Promise.resolve({
id: dataView.id,
title: dataView.title,
displayName: dataView.displayName,
type: 'metrics-type',
signalType: dataView.signalType,
});
Expand Down Expand Up @@ -504,6 +510,7 @@ describe('DatasetSelect', () => {
return Promise.resolve({
id: dataView.id,
title: dataView.title,
displayName: dataView.displayName,
type: DEFAULT_DATA.SET_TYPES.INDEX_PATTERN,
signalType: dataView.signalType,
});
Expand Down Expand Up @@ -927,9 +934,9 @@ describe('DatasetSelect', () => {
return Promise.resolve({
id: dataView.id,
title: dataView.title,
displayName: dataView.displayName,
type: dataView.type,
timeFieldName: dataView.timeFieldName,
displayName: dataView.displayName,
});
});
mockQueryService.queryString.getQuery = jest.fn().mockReturnValue({
Expand Down Expand Up @@ -1013,9 +1020,9 @@ describe('DatasetSelect', () => {
return Promise.resolve({
id: dataView.id,
title: dataView.title,
displayName: dataView.displayName,
type: dataView.type,
timeFieldName: dataView.timeFieldName,
displayName: dataView.displayName,
});
});
mockQueryService.queryString.getQuery = jest.fn().mockReturnValue({
Expand Down Expand Up @@ -1058,6 +1065,7 @@ describe('DatasetSelect', () => {
mockDataViews.convertToDataset = jest.fn().mockResolvedValue({
id: 'no-time-id',
title: 'no-time-dataset',
displayName: 'Dataset Without Time Field',
type: DEFAULT_DATA.SET_TYPES.INDEX_PATTERN,
timeFieldName: undefined,
});
Expand Down Expand Up @@ -1092,6 +1100,59 @@ describe('DatasetSelect', () => {
});
});

describe('displayName from URL state', () => {
it('uses displayName from URL state for selected dataset in dropdown', async () => {
// Setup dataset without displayName in saved object
const datasetWithoutDisplayName = {
id: 'dataset-1',
title: 'logs_otel_v1_explore',
displayName: undefined,
type: DEFAULT_DATA.SET_TYPES.INDEX_PATTERN,
timeFieldName: '@timestamp',
};

mockDataViews.getIds = jest.fn().mockResolvedValue(['dataset-1']);
mockDataViews.get = jest.fn().mockResolvedValue(datasetWithoutDisplayName);
mockDataViews.convertToDataset = jest.fn().mockResolvedValue({
id: 'dataset-1',
title: 'logs_otel_v1_explore',
displayName: undefined,
type: DEFAULT_DATA.SET_TYPES.INDEX_PATTERN,
});

// URL state has displayName
const datasetWithDisplayName = {
...datasetWithoutDisplayName,
displayName: 'LogsExplore',
};

mockQueryService.queryString.getQuery = jest.fn().mockReturnValue({
dataset: datasetWithDisplayName,
});

renderWithContext();

await waitFor(() => {
expect(mockDataViews.getIds).toHaveBeenCalled();
});

// Button should show displayName from URL
expect(screen.getByText('LogsExplore')).toBeInTheDocument();

// Open dropdown
const button = screen.getByTestId('datasetSelectButton');
fireEvent.click(button);

await waitFor(() => {
expect(screen.getByPlaceholderText('Search')).toBeInTheDocument();
});

// Dropdown should also show displayName from URL, not the title
const logsExploreElements = screen.getAllByText('LogsExplore');
expect(logsExploreElements.length).toBeGreaterThan(0);
});
});

describe('TimeBasedDatasetDisclaimer', () => {
it('opens ViewDatasetsModal when "View datasets" button is clicked', async () => {
renderWithContext();
Expand Down
Loading
Loading