Skip to content

Commit ea74ed6

Browse files
[Explore Vis] state timeline remaining fixes and delete pound_mass from unit (#10843)
* fix legend in statetimeline Signed-off-by: Qxisylolo <[email protected]> * Changeset file for PR #10843 created/updated * squash Signed-off-by: Qxisylolo <[email protected]> --------- Signed-off-by: Qxisylolo <[email protected]> Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com>
1 parent 298e149 commit ea74ed6

File tree

13 files changed

+160
-61
lines changed

13 files changed

+160
-61
lines changed

changelogs/fragments/10843.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fix:
2+
- Fix legend name configuration in state timeline ([#10843](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/10843))

src/plugins/explore/public/components/visualizations/bar/bucket_options.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const BucketOptionsPanel = ({ styles, onChange, bucketType }: BucketOptio
8484
compressed
8585
value={styles?.bucketSize}
8686
placeholder="auto"
87+
min={0}
8788
onChange={(value) => updateBucketOption('bucketSize', value)}
8889
/>
8990
</EuiFormRow>
@@ -96,6 +97,7 @@ export const BucketOptionsPanel = ({ styles, onChange, bucketType }: BucketOptio
9697
<DebouncedFieldNumber
9798
compressed
9899
value={styles?.bucketCount}
100+
min={0}
99101
onChange={(value) => updateBucketOption('bucketCount', value)}
100102
placeholder="30"
101103
/>

src/plugins/explore/public/components/visualizations/scatter/scatter_vis_options.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ScatterChartStyle, ScatterChartStyleOptions } from './scatter_vis_confi
1010
import { ScatterExclusiveVisOptions } from './scatter_exclusive_vis_options';
1111
import { AllAxesOptions } from '../style_panel/axes/standard_axes_options';
1212
import { StyleControlsProps } from '../utils/use_visualization_types';
13+
import { LegendOptionsPanel } from '../style_panel/legend/legend';
1314
import { LegendOptionsWrapper } from '../style_panel/legend/legend_options_wrapper';
1415
import { TooltipOptionsPanel } from '../style_panel/tooltip/tooltip';
1516
import { AxesSelectPanel } from '../style_panel/axes/axes_selector';

src/plugins/explore/public/components/visualizations/state_timeline/state_timeline_config.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ describe('createStateTimelineConfig', () => {
4848
threshold: '1h',
4949
});
5050

51+
expect(defaults.legendTitle).toBe('');
52+
5153
// Verify axes
5254
expect(defaults.standardAxes).toHaveLength(2);
5355
const xAxis = defaults.standardAxes.find((axis) => axis.axisRole === AxisRole.X);

src/plugins/explore/public/components/visualizations/state_timeline/state_timeline_config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export interface StateTimeLineChartStyleOptions {
3333
tooltipOptions?: TooltipOptions;
3434
addLegend?: boolean;
3535
legendPosition?: Positions;
36+
legendTitle?: string;
3637
// Axes configuration
3738
standardAxes?: StandardAxes[];
3839

@@ -54,6 +55,7 @@ export const defaultStateTimeLineChartStyles: StateTimeLineChartStyle = {
5455
},
5556
addLegend: true,
5657
legendPosition: Positions.RIGHT,
58+
legendTitle: '',
5759

5860
// exclusive
5961
exclusive: {

src/plugins/explore/public/components/visualizations/state_timeline/state_timeline_exclusive_vis_options.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe('StateTimeLineExclusiveVisOptions', () => {
5959
/>
6060
);
6161

62-
expect(screen.getByText('Show values')).toBeInTheDocument();
62+
expect(screen.getByText('Show display text')).toBeInTheDocument();
6363
expect(screen.getByText('Row height')).toBeInTheDocument();
6464
expect(screen.getByTestId('connectValuesGroupButton')).toBeInTheDocument();
6565
expect(screen.getByTestId('disconnectValuesGroupButton')).toBeInTheDocument();

src/plugins/explore/public/components/visualizations/state_timeline/state_timeline_exclusive_vis_options.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export const StateTimeLineExclusiveVisOptions = ({
7575
<EuiFormRow>
7676
<EuiSwitch
7777
compressed
78-
label={i18n.translate('explore.vis.scatter.useThresholdColor', {
78+
label={i18n.translate('explore.vis.statetimeline.useThresholdColor', {
7979
defaultMessage: 'Use threshold colors',
8080
})}
8181
data-test-subj="useThresholdColorButton"
@@ -88,7 +88,7 @@ export const StateTimeLineExclusiveVisOptions = ({
8888
<EuiSwitch
8989
compressed
9090
label={i18n.translate('explore.vis.stateTimeline.exclusive.showValues', {
91-
defaultMessage: 'Show values',
91+
defaultMessage: 'Show display text',
9292
})}
9393
checked={styles?.showValues ?? false}
9494
onChange={(e) => updateStyle('showValues', e.target.checked)}

src/plugins/explore/public/components/visualizations/state_timeline/state_timeline_vis_options.test.tsx

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -80,25 +80,45 @@ jest.mock('../style_panel/axes/axes_selector', () => ({
8080
)),
8181
}));
8282

83-
jest.mock('../style_panel/legend/legend', () => {
83+
jest.mock('../style_panel/legend/legend_options_wrapper', () => {
8484
const { Positions: PositionsEnum } = jest.requireActual('../types');
8585
return {
86-
LegendOptionsPanel: jest.fn(({ legendOptions, onLegendOptionsChange }) => (
87-
<div data-test-subj="mockLegendOptionsPanel">
88-
<button
89-
data-test-subj="mockLegendShow"
90-
onClick={() => onLegendOptionsChange({ show: !legendOptions.show })}
91-
>
92-
Toggle Legend
93-
</button>
94-
<button
95-
data-test-subj="mockLegendPosition"
96-
onClick={() => onLegendOptionsChange({ position: PositionsEnum.BOTTOM })}
97-
>
98-
Change Position
99-
</button>
100-
</div>
101-
)),
86+
LegendOptionsWrapper: jest.fn(
87+
({ styleOptions, updateStyleOption, hasSizeLegend, shouldShow }) => {
88+
if (!shouldShow) {
89+
return null;
90+
}
91+
92+
return (
93+
<div data-test-subj="mockLegendOptionsWrapper">
94+
<button
95+
data-test-subj="mockLegendShow"
96+
onClick={() => updateStyleOption('addLegend', !styleOptions.addLegend)}
97+
>
98+
Toggle Legend
99+
</button>
100+
<button
101+
data-test-subj="mockLegendPosition"
102+
onClick={() => updateStyleOption('legendPosition', PositionsEnum.BOTTOM)}
103+
>
104+
Change Position
105+
</button>
106+
<input
107+
data-test-subj="legend-title-input"
108+
placeholder="Legend Title"
109+
onChange={(e) => updateStyleOption('legendTitle', e.target.value)}
110+
/>
111+
{hasSizeLegend && (
112+
<input
113+
data-test-subj="legend-title-for-size-input"
114+
placeholder="Legend Title for Size"
115+
onChange={(e) => updateStyleOption('legendTitleForSize', e.target.value)}
116+
/>
117+
)}
118+
</div>
119+
);
120+
}
121+
),
102122
};
103123
});
104124

@@ -217,7 +237,7 @@ describe('StateTimeLineVisStyleControls', () => {
217237
expect(screen.getByTestId('allAxesOptions')).toBeInTheDocument();
218238
expect(screen.getByTestId('mockTooltipOptionsPanel')).toBeInTheDocument();
219239
expect(screen.getByTestId('mockStateTimelineExclusiveOptions')).toBeInTheDocument();
220-
expect(screen.queryByTestId('mockLegendOptionsPanel')).toBeInTheDocument();
240+
expect(screen.queryByTestId('mockLegendOptionsWrapper')).toBeInTheDocument();
221241
expect(screen.getByTestId('mockTitleOptionsPanel')).toBeInTheDocument();
222242
expect(screen.getByTestId('mockValueMappingOption')).toBeInTheDocument();
223243
});
@@ -329,4 +349,15 @@ describe('StateTimeLineVisStyleControls', () => {
329349
},
330350
});
331351
});
352+
353+
it('updates legend title correctly', async () => {
354+
render(<StateTimeLineVisStyleControls {...mockProps} />);
355+
356+
const legendTitleInput = screen.getByTestId('legend-title-input');
357+
await userEvent.type(legendTitleInput, 'New Legend Title');
358+
359+
await waitFor(() => {
360+
expect(mockProps.onStyleChange).toHaveBeenCalledWith({ legendTitle: 'New Legend Title' });
361+
});
362+
});
332363
});

src/plugins/explore/public/components/visualizations/state_timeline/state_timeline_vis_options.tsx

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
99
import { StateTimeLineChartStyle, StateTimeLineChartStyleOptions } from './state_timeline_config';
1010
import { AllAxesOptions } from '../style_panel/axes/standard_axes_options';
1111
import { StyleControlsProps } from '../utils/use_visualization_types';
12-
import { LegendOptionsPanel } from '../style_panel/legend/legend';
12+
import { LegendOptionsWrapper } from '../style_panel/legend/legend_options_wrapper';
1313
import { TooltipOptionsPanel } from '../style_panel/tooltip/tooltip';
1414
import { AxesSelectPanel } from '../style_panel/axes/axes_selector';
1515
import { TitleOptionsPanel } from '../style_panel/title/title';
@@ -88,22 +88,11 @@ export const StateTimeLineVisStyleControls: React.FC<StateTimeLineVisStyleContro
8888
/>
8989
</EuiFlexItem>
9090

91-
<EuiFlexItem grow={false}>
92-
<LegendOptionsPanel
93-
legendOptions={{
94-
show: styleOptions.addLegend,
95-
position: styleOptions.legendPosition,
96-
}}
97-
onLegendOptionsChange={(legendOptions) => {
98-
if (legendOptions.show !== undefined) {
99-
updateStyleOption('addLegend', legendOptions.show);
100-
}
101-
if (legendOptions.position !== undefined) {
102-
updateStyleOption('legendPosition', legendOptions.position);
103-
}
104-
}}
105-
/>
106-
</EuiFlexItem>
91+
<LegendOptionsWrapper
92+
styleOptions={styleOptions}
93+
updateStyleOption={updateStyleOption}
94+
shouldShow={true}
95+
/>
10796

10897
<EuiFlexItem grow={false}>
10998
<TitleOptionsPanel

src/plugins/explore/public/components/visualizations/state_timeline/to_expression.test.ts

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ const mockStyleOptions = defaultStateTimeLineChartStyles;
119119
describe('to_expression', () => {
120120
describe('createNumericalStateTimeline', () => {
121121
it('should create a state timeline chart with one date one cate and one metric', () => {
122+
const rangeMappings = [{ type: 'range', range: { min: 0 } }];
122123
const mockAxisColumnMappings: AxisColumnMappings = {
123124
[AxisRole.COLOR]: mockNumericalColumns[0],
124125
[AxisRole.Y]: mockCateColumns[0],
@@ -130,7 +131,7 @@ describe('to_expression', () => {
130131
mockNumericalColumns,
131132
mockCateColumns,
132133
mockTimeColumns,
133-
mockStyleOptions,
134+
{ ...mockStyleOptions, valueMappingOptions: { valueMappings: rangeMappings } },
134135
mockAxisColumnMappings
135136
);
136137

@@ -164,6 +165,52 @@ describe('to_expression', () => {
164165
expect(markLayer).toHaveProperty('encoding.color.field', 'mergedLabel');
165166
});
166167

168+
it('should fallback to categorical state timeline when no range mappings are provided', () => {
169+
const mockAxisColumnMappings: AxisColumnMappings = {
170+
[AxisRole.COLOR]: mockNumericalColumns[0],
171+
[AxisRole.Y]: mockCateColumns[0],
172+
[AxisRole.X]: mockTimeColumns[0],
173+
};
174+
175+
const result = createNumericalStateTimeline(
176+
mockData,
177+
mockNumericalColumns,
178+
mockCateColumns,
179+
mockTimeColumns,
180+
mockStyleOptions,
181+
mockAxisColumnMappings
182+
);
183+
184+
// Verify the basic structure
185+
expect(result).toHaveProperty('$schema', VEGASCHEMA);
186+
expect(result).toHaveProperty('data.values', [
187+
{
188+
timestamp: '2023-01-01',
189+
start: '2023-01-01',
190+
end: '2023-01-02',
191+
category: 'A',
192+
category2: 'true',
193+
},
194+
]);
195+
expect(result).toHaveProperty('layer');
196+
expect(Array.isArray(result.layer)).toBe(true);
197+
198+
// Verify the mark layer
199+
const markLayer = result.layer[0];
200+
expect(markLayer).toHaveProperty('mark.type', 'rect');
201+
expect(markLayer).toHaveProperty('mark.tooltip', true);
202+
203+
// Verify encoding
204+
expect(markLayer).toHaveProperty('encoding.x.field', 'timestamp');
205+
expect(markLayer).toHaveProperty('encoding.x.type', 'temporal');
206+
expect(markLayer).toHaveProperty('encoding.x.type', 'temporal');
207+
expect(markLayer).toHaveProperty('encoding.x2.field', 'end');
208+
209+
expect(markLayer).toHaveProperty('encoding.y.field', 'category');
210+
expect(markLayer).toHaveProperty('encoding.y.type', 'nominal');
211+
expect(markLayer).toHaveProperty('encoding.color.field', 'mappingValue');
212+
});
213+
167214
it('includes text layer when showValues is true', () => {
168215
const styleWithText = {
169216
...mockStyleOptions,
@@ -173,6 +220,24 @@ describe('to_expression', () => {
173220
const result = createNumericalStateTimeline(mockData, [], [], [], styleWithText);
174221
expect(result.layer).toHaveLength(2);
175222
});
223+
it('should display Ranges when customized legend title is not set', () => {
224+
const rangeMappings = [{ type: 'range', range: { min: 0 } }];
225+
const styleWithLegend = {
226+
...mockStyleOptions,
227+
valueMappingOptions: { valueMappings: rangeMappings },
228+
};
229+
const result = createNumericalStateTimeline(mockData, [], [], [], styleWithLegend);
230+
expect(result.layer[0].encoding.color.legend.title).toBe('Ranges');
231+
});
232+
233+
it('should display customized legend title', () => {
234+
const styleWithLegend = {
235+
...mockStyleOptions,
236+
legendTitle: 'default',
237+
};
238+
const result = createNumericalStateTimeline(mockData, [], [], [], styleWithLegend);
239+
expect(result.layer[0].encoding.color.legend.title).toBe('default');
240+
});
176241
});
177242

178243
describe('createCategoricalStateTimeline', () => {
@@ -229,6 +294,14 @@ describe('to_expression', () => {
229294
const result = createCategoricalStateTimeline(mockData, [], [], [], styleWithTitle);
230295
expect(result.title).toBe('Test Title');
231296
});
297+
it('should display customized legend title', () => {
298+
const styleWithLegend = {
299+
...mockStyleOptions,
300+
legendTitle: 'default',
301+
};
302+
const result = createCategoricalStateTimeline(mockData, [], [], [], styleWithLegend);
303+
expect(result.layer[0].encoding.color.legend.title).toBe('default');
304+
});
232305
});
233306

234307
describe('createSingleCategoricalStateTimeline', () => {
@@ -283,5 +356,13 @@ describe('to_expression', () => {
283356
const result = createSingleCategoricalStateTimeline(mockData, [], [], [], styleWithThreshold);
284357
expect(result).toHaveProperty('data.values');
285358
});
359+
it('should display customized legend title', () => {
360+
const styleWithLegend = {
361+
...mockStyleOptions,
362+
legendTitle: 'default',
363+
};
364+
const result = createSingleCategoricalStateTimeline(mockData, [], [], [], styleWithLegend);
365+
expect(result.layer[0].encoding.color.legend.title).toBe('default');
366+
});
286367
});
287368
});

0 commit comments

Comments
 (0)