Skip to content

Commit 92ae684

Browse files
Add Data scope and Schema mapping info on dataset editing page (#10737)
* Add Data scope and Schema mapping info on dataset editing page Signed-off-by: Joey Liu <[email protected]> * Changeset file for PR #10737 created/updated --------- Signed-off-by: Joey Liu <[email protected]> Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com>
1 parent 18c92ee commit 92ae684

File tree

12 files changed

+581
-14
lines changed

12 files changed

+581
-14
lines changed

changelogs/fragments/10737.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
feat:
2+
- Add Data scope and Schema mapping info on dataset editing page ([#10737](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/10737))

src/plugins/data/public/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export {
7272
DatasetSelectorAppearance,
7373
DetailedDataset,
7474
AdvancedSelector,
75+
ConfiguratorV2,
7576
} from './ui';
7677

7778
import {
@@ -630,3 +631,8 @@ export {
630631

631632
export { SavedQueryManagementComponent } from './ui/saved_query_management';
632633
export { SaveQueryForm, SavedQueryMeta } from './ui/saved_query_form';
634+
export {
635+
getSchemaConfigs,
636+
SchemaConfig,
637+
SchemaAttributeConfig,
638+
} from './ui/dataset_selector/configurator/schema_config';

src/plugins/data/public/ui/dataset_selector/advanced_selector.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,26 @@
44
*/
55

66
import React, { useState } from 'react';
7+
8+
/**
9+
* AdvancedSelector component for dataset selection and configuration.
10+
*
11+
* This component provides a two-step interface:
12+
* 1. DatasetExplorer - Browse and select datasets
13+
* 2. ConfiguratorV2 - Configure the selected dataset (language, time field, schema mappings, etc.)
14+
*
15+
* Usage with modals:
16+
* When rendering this component inside a modal, apply the className 'datasetSelector__advancedModal'
17+
* to the modal container for consistent sizing (1200px × 800px) and layout.
18+
*
19+
* Example:
20+
* ```tsx
21+
* overlays.openModal(
22+
* toMountPoint(<AdvancedSelector {...props} />),
23+
* { maxWidth: false, className: 'datasetSelector__advancedModal' }
24+
* );
25+
* ```
26+
*/
727
import {
828
BaseDataset,
929
DATA_STRUCTURE_META_TYPES,

src/plugins/data/public/ui/dataset_selector/configurator/schema_mappings.test.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,18 @@ describe('SchemaMappings Component', () => {
6969
expect(accordion).toBeInTheDocument();
7070
});
7171

72-
it('should expand accordion when clicked', () => {
72+
it('should toggle accordion when clicked', () => {
7373
renderWithIntl(<SchemaMappings {...defaultProps} />);
74+
7475
const accordionButton = screen.getByText('Schema Mappings');
76+
77+
// Click to open
7578
fireEvent.click(accordionButton);
7679
expect(screen.getByText('OTel logs')).toBeInTheDocument();
80+
81+
// Click to close
82+
fireEvent.click(accordionButton);
83+
// Content may still be in DOM but accordion should toggle
7784
});
7885

7986
it('should render schema attributes after expanding accordion', () => {
@@ -185,4 +192,21 @@ describe('SchemaMappings Component', () => {
185192
},
186193
});
187194
});
195+
196+
it('should auto-open accordion when there are existing schema mappings', () => {
197+
const propsWithMapping: SchemaMappingsProps = {
198+
...defaultProps,
199+
schemaMappings: {
200+
otelLogs: {
201+
traceId: 'field1',
202+
},
203+
},
204+
};
205+
206+
renderWithIntl(<SchemaMappings {...propsWithMapping} />);
207+
208+
// Accordion should be auto-opened, so schema details should be visible without clicking
209+
expect(screen.getByText('OTel logs')).toBeInTheDocument();
210+
expect(screen.getByText('Trace ID')).toBeInTheDocument();
211+
});
188212
});

src/plugins/data/public/ui/dataset_selector/configurator/schema_mappings.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ export const SchemaMappings: React.FC<SchemaMappingsProps> = ({
3131
onChange,
3232
schemas,
3333
}) => {
34-
const [isOpen, setIsOpen] = useState(false);
34+
// Auto-open accordion if there are existing schema mappings
35+
const hasExistingMappings = Object.keys(schemaMappings).length > 0;
36+
const [isOpen, setIsOpen] = useState(hasExistingMappings);
3537

3638
const handleFieldChange = (schemaKey: string, attributeKey: string, fieldName: string) => {
3739
const newMappings = { ...schemaMappings };

src/plugins/data/public/ui/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,4 @@ export {
5656
export { SuggestionsComponent } from './typeahead';
5757
export { DatasetSelector, DatasetSelectorAppearance } from './dataset_selector';
5858
export { AdvancedSelector } from './dataset_selector/advanced_selector';
59+
export { ConfiguratorV2 } from './dataset_selector/configurator/configurator_v2';

src/plugins/dataset_management/public/components/dataset_table/dataset_table_v2.scss

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
.datasetSelectModal {
2-
width: 1200px;
3-
height: 800px;
4-
5-
// euiOverlayMask pushes the modal up due to having padding-bottom: 10vh
6-
max-height: calc(90vh - $euiSizeL);
7-
}
8-
91
// AdvancedSelector styles from data plugin
102
.datasetExplorer {
113
display: grid;

src/plugins/dataset_management/public/components/dataset_table/dataset_table_v2.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export const DatasetTableV2 = ({ canSave, history }: Props) => {
212212
),
213213
{
214214
maxWidth: false,
215-
className: 'datasetSelectModal',
215+
className: 'datasetSelector__advancedModal',
216216
}
217217
);
218218
},
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
.datasetInfoPanel {
7+
&__schemaGroup {
8+
margin-bottom: $euiSizeM;
9+
10+
&:last-child {
11+
margin-bottom: 0;
12+
}
13+
}
14+
15+
&__titleWithButton {
16+
display: flex;
17+
align-items: center;
18+
gap: $euiSizeXS;
19+
}
20+
21+
&__schemaLabel {
22+
font-weight: $euiFontWeightSemiBold;
23+
margin-bottom: $euiSizeS;
24+
}
25+
26+
&__mappingRow {
27+
display: flex;
28+
align-items: center;
29+
gap: $euiSizeS;
30+
margin-bottom: $euiSizeXS;
31+
32+
&:last-child {
33+
margin-bottom: 0;
34+
}
35+
}
36+
37+
&__attributeTag {
38+
display: inline-block;
39+
padding: $euiSizeXS $euiSizeS;
40+
background-color: $euiColorLightestShade;
41+
border: $euiBorderThin;
42+
border-radius: $euiBorderRadius;
43+
font-size: $euiFontSizeXS;
44+
font-weight: $euiFontWeightMedium;
45+
color: $euiColorDarkestShade;
46+
white-space: nowrap;
47+
}
48+
49+
&__fieldName {
50+
font-size: $euiFontSizeS;
51+
flex: 1;
52+
}
53+
54+
&__moreText {
55+
font-size: $euiFontSizeS;
56+
color: $euiColorDarkShade;
57+
margin-top: $euiSizeXS;
58+
}
59+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import React from 'react';
7+
import { render, screen } from '@testing-library/react';
8+
import { DatasetInfoPanel } from './dataset_info_panel';
9+
import { IntlProvider } from 'react-intl';
10+
11+
// Mock getSchemaConfigs
12+
jest.mock('../../../../data/public', () => ({
13+
getSchemaConfigs: jest.fn(() => ({
14+
otelLogs: {
15+
displayName: 'OTel logs',
16+
description: 'OTel schema mappings for your logs dataset',
17+
signalType: 'logs',
18+
attributes: {
19+
traceId: {
20+
displayName: 'Trace ID',
21+
description: 'Unique identifier for the trace',
22+
},
23+
spanId: {
24+
displayName: 'Span ID',
25+
description: 'Unique identifier for the span',
26+
},
27+
},
28+
},
29+
})),
30+
}));
31+
32+
const createMockDataset = (overrides = {}) => ({
33+
id: 'test-dataset',
34+
title: 'test-index',
35+
signalType: 'logs',
36+
timeFieldName: '@timestamp',
37+
dataSourceRef: undefined,
38+
schemaMappings: undefined,
39+
savedObjectsClient: {
40+
get: jest.fn(),
41+
},
42+
...overrides,
43+
});
44+
45+
describe('DatasetInfoPanel', () => {
46+
const renderComponent = (dataset: any, editConfiguration?: () => void) => {
47+
return render(
48+
<IntlProvider locale="en">
49+
<DatasetInfoPanel dataset={dataset} editConfiguration={editConfiguration} />
50+
</IntlProvider>
51+
);
52+
};
53+
54+
it('renders basic dataset information', () => {
55+
const dataset = createMockDataset();
56+
renderComponent(dataset);
57+
58+
expect(screen.getByText('Type')).toBeInTheDocument();
59+
expect(screen.getByText('logs')).toBeInTheDocument();
60+
expect(screen.getByText('Data scope')).toBeInTheDocument();
61+
expect(screen.getByText('test-index')).toBeInTheDocument();
62+
});
63+
64+
it('displays N/A when no schema mappings exist', () => {
65+
const dataset = createMockDataset();
66+
renderComponent(dataset);
67+
68+
expect(screen.getByText('Schema mapping')).toBeInTheDocument();
69+
const naElements = screen.getAllByText('N/A');
70+
expect(naElements.length).toBeGreaterThan(0);
71+
});
72+
73+
it('displays schema mappings with display names', () => {
74+
const dataset = createMockDataset({
75+
schemaMappings: {
76+
otelLogs: {
77+
traceId: 'trace.id',
78+
spanId: 'span.id',
79+
},
80+
},
81+
});
82+
renderComponent(dataset);
83+
84+
expect(screen.getByText('OTel logs')).toBeInTheDocument();
85+
expect(screen.getByText('Trace ID')).toBeInTheDocument();
86+
expect(screen.getByText('trace.id')).toBeInTheDocument();
87+
expect(screen.getByText('Span ID')).toBeInTheDocument();
88+
expect(screen.getByText('span.id')).toBeInTheDocument();
89+
});
90+
91+
it('limits display to 4 mappings and shows "X more" message', () => {
92+
const dataset = createMockDataset({
93+
schemaMappings: {
94+
otelLogs: {
95+
traceId: 'trace.id',
96+
spanId: 'span.id',
97+
},
98+
otherSchema: {
99+
field1: 'field_1',
100+
field2: 'field_2',
101+
field3: 'field_3',
102+
field4: 'field_4',
103+
},
104+
},
105+
});
106+
renderComponent(dataset);
107+
108+
// Should show "2 more" since we have 6 total mappings but limit to 4
109+
expect(screen.getByText('2 more')).toBeInTheDocument();
110+
});
111+
112+
it('renders edit button when editConfiguration is provided', () => {
113+
const dataset = createMockDataset({
114+
schemaMappings: {
115+
otelLogs: {
116+
traceId: 'trace.id',
117+
},
118+
},
119+
});
120+
const editConfiguration = jest.fn();
121+
renderComponent(dataset, editConfiguration);
122+
123+
const editButton = screen.getByTestId('editSchemaConfigurationButton');
124+
expect(editButton).toBeInTheDocument();
125+
});
126+
127+
it('does not render edit button when editConfiguration is not provided', () => {
128+
const dataset = createMockDataset({
129+
schemaMappings: {
130+
otelLogs: {
131+
traceId: 'trace.id',
132+
},
133+
},
134+
});
135+
renderComponent(dataset);
136+
137+
const editButton = screen.queryByTestId('editSchemaConfigurationButton');
138+
expect(editButton).toBeNull();
139+
});
140+
141+
it('displays data source as N/A when no dataSourceRef exists', () => {
142+
const dataset = createMockDataset();
143+
renderComponent(dataset);
144+
145+
expect(screen.getByText('Data scope')).toBeInTheDocument();
146+
const naElements = screen.getAllByText('N/A');
147+
// Should have N/A for data source and schema mapping
148+
expect(naElements.length).toBeGreaterThanOrEqual(1);
149+
});
150+
});

0 commit comments

Comments
 (0)