Commit e0d8efbb authored by Andrei Stoicescu's avatar Andrei Stoicescu Committed by Mark Florian

Add tabular legend support to metrics dashboards

  - apply tabular layout to legends in area charts,
line charts and stacked column charts
parent 6caa2277
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
import { GlResizeObserverDirective } from '@gitlab/ui'; import { GlResizeObserverDirective } from '@gitlab/ui';
import { GlStackedColumnChart } from '@gitlab/ui/dist/charts'; import { GlStackedColumnChart } from '@gitlab/ui/dist/charts';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import { chartHeight } from '../../constants'; import { chartHeight, legendLayoutTypes } from '../../constants';
import { s__ } from '~/locale';
import { graphDataValidatorForValues } from '../../utils'; import { graphDataValidatorForValues } from '../../utils';
import { getTimeAxisOptions, axisTypes } from './options'; import { getTimeAxisOptions, axisTypes } from './options';
import { timezones } from '../../format_date'; import { timezones } from '../../format_date';
...@@ -25,6 +26,31 @@ export default { ...@@ -25,6 +26,31 @@ export default {
required: false, required: false,
default: timezones.LOCAL, default: timezones.LOCAL,
}, },
legendLayout: {
type: String,
required: false,
default: legendLayoutTypes.table,
},
legendAverageText: {
type: String,
required: false,
default: s__('Metrics|Avg'),
},
legendCurrentText: {
type: String,
required: false,
default: s__('Metrics|Current'),
},
legendMaxText: {
type: String,
required: false,
default: s__('Metrics|Max'),
},
legendMinText: {
type: String,
required: false,
default: s__('Metrics|Min'),
},
}, },
data() { data() {
return { return {
...@@ -119,6 +145,11 @@ export default { ...@@ -119,6 +145,11 @@ export default {
:width="width" :width="width"
:height="height" :height="height"
:series-names="seriesNames" :series-names="seriesNames"
:legend-layout="legendLayout"
:legend-average-text="legendAverageText"
:legend-current-text="legendCurrentText"
:legend-max-text="legendMaxText"
:legend-min-text="legendMinText"
/> />
</div> </div>
</template> </template>
...@@ -5,7 +5,7 @@ import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/ch ...@@ -5,7 +5,7 @@ import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/ch
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { panelTypes, chartHeight, lineTypes, lineWidths } from '../../constants'; import { panelTypes, chartHeight, lineTypes, lineWidths, legendLayoutTypes } from '../../constants';
import { getYAxisOptions, getTimeAxisOptions, getChartGrid, getTooltipFormatter } from './options'; import { getYAxisOptions, getTimeAxisOptions, getChartGrid, getTooltipFormatter } from './options';
import { annotationsYAxis, generateAnnotationsSeries } from './annotations'; import { annotationsYAxis, generateAnnotationsSeries } from './annotations';
import { makeDataSeries } from '~/helpers/monitor_helper'; import { makeDataSeries } from '~/helpers/monitor_helper';
...@@ -75,16 +75,31 @@ export default { ...@@ -75,16 +75,31 @@ export default {
required: false, required: false,
default: () => [], default: () => [],
}, },
legendLayout: {
type: String,
required: false,
default: legendLayoutTypes.table,
},
legendAverageText: { legendAverageText: {
type: String, type: String,
required: false, required: false,
default: s__('Metrics|Avg'), default: s__('Metrics|Avg'),
}, },
legendCurrentText: {
type: String,
required: false,
default: s__('Metrics|Current'),
},
legendMaxText: { legendMaxText: {
type: String, type: String,
required: false, required: false,
default: s__('Metrics|Max'), default: s__('Metrics|Max'),
}, },
legendMinText: {
type: String,
required: false,
default: s__('Metrics|Min'),
},
groupId: { groupId: {
type: String, type: String,
required: false, required: false,
...@@ -368,8 +383,11 @@ export default { ...@@ -368,8 +383,11 @@ export default {
:thresholds="thresholds" :thresholds="thresholds"
:width="width" :width="width"
:height="height" :height="height"
:average-text="legendAverageText" :legend-layout="legendLayout"
:max-text="legendMaxText" :legend-average-text="legendAverageText"
:legend-current-text="legendCurrentText"
:legend-max-text="legendMaxText"
:legend-min-text="legendMinText"
@created="onChartCreated" @created="onChartCreated"
@updated="onChartUpdated" @updated="onChartUpdated"
> >
......
...@@ -135,6 +135,19 @@ export const linkTypes = { ...@@ -135,6 +135,19 @@ export const linkTypes = {
GRAFANA: 'grafana', GRAFANA: 'grafana',
}; };
/**
* These are the supported values for the GitLab-UI
* chart legend layout.
*
* Currently defined in
* https://gitlab.com/gitlab-org/gitlab-ui/-/blob/master/src/utils/charts/constants.js
*
*/
export const legendLayoutTypes = {
inline: 'inline',
table: 'table',
};
/** /**
* These Vuex store properties are allowed to be * These Vuex store properties are allowed to be
* replaced dynamically after component has been created * replaced dynamically after component has been created
......
---
title: Change legends in monitor dashboards to tabular layout
merge_request: 30131
author:
type: changed
...@@ -78,7 +78,7 @@ RSpec.describe 'Cluster Health board', :js, :kubeclient, :use_clean_rails_memory ...@@ -78,7 +78,7 @@ RSpec.describe 'Cluster Health board', :js, :kubeclient, :use_clean_rails_memory
expect(page).to have_css('.prometheus-graph') expect(page).to have_css('.prometheus-graph')
expect(page).to have_css('.prometheus-graph-title') expect(page).to have_css('.prometheus-graph-title')
expect(page).to have_css('[_echarts_instance_]') expect(page).to have_css('[_echarts_instance_]')
expect(page).to have_content('Avg:') expect(page).to have_content('Avg')
end end
end end
......
...@@ -14081,6 +14081,9 @@ msgstr "" ...@@ -14081,6 +14081,9 @@ msgstr ""
msgid "Metrics|Create metric" msgid "Metrics|Create metric"
msgstr "" msgstr ""
msgid "Metrics|Current"
msgstr ""
msgid "Metrics|Delete metric" msgid "Metrics|Delete metric"
msgstr "" msgstr ""
...@@ -14131,6 +14134,9 @@ msgstr "" ...@@ -14131,6 +14134,9 @@ msgstr ""
msgid "Metrics|Max" msgid "Metrics|Max"
msgstr "" msgstr ""
msgid "Metrics|Min"
msgstr ""
msgid "Metrics|Must be a valid PromQL query." msgid "Metrics|Must be a valid PromQL query."
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount, mount } from '@vue/test-utils';
import timezoneMock from 'timezone-mock'; import timezoneMock from 'timezone-mock';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { GlStackedColumnChart } from '@gitlab/ui/dist/charts'; import { GlStackedColumnChart, GlChartLegend } from '@gitlab/ui/dist/charts';
import StackedColumnChart from '~/monitoring/components/charts/stacked_column.vue'; import StackedColumnChart from '~/monitoring/components/charts/stacked_column.vue';
import { stackedColumnMockedData } from '../../mock_data'; import { stackedColumnMockedData } from '../../mock_data';
...@@ -11,16 +11,25 @@ jest.mock('~/lib/utils/icon_utils', () => ({ ...@@ -11,16 +11,25 @@ jest.mock('~/lib/utils/icon_utils', () => ({
describe('Stacked column chart component', () => { describe('Stacked column chart component', () => {
let wrapper; let wrapper;
const findChart = () => wrapper.find(GlStackedColumnChart); const findChart = () => wrapper.find(GlStackedColumnChart);
const findLegend = () => wrapper.find(GlChartLegend);
const createWrapper = (props = {}) => { const createWrapper = (props = {}, mountingMethod = shallowMount) =>
wrapper = shallowMount(StackedColumnChart, { mountingMethod(StackedColumnChart, {
propsData: { propsData: {
graphData: stackedColumnMockedData, graphData: stackedColumnMockedData,
...props, ...props,
}, },
stubs: {
GlPopover: true,
},
attachToDocument: true,
}); });
};
beforeEach(() => {
wrapper = createWrapper({}, mount);
});
describe('when graphData is present', () => { describe('when graphData is present', () => {
beforeEach(() => { beforeEach(() => {
...@@ -130,4 +139,54 @@ describe('Stacked column chart component', () => { ...@@ -130,4 +139,54 @@ describe('Stacked column chart component', () => {
expect(findChart().exists()).toBe(true); expect(findChart().exists()).toBe(true);
}); });
}); });
describe('legend', () => {
beforeEach(() => {
wrapper = createWrapper({}, mount);
});
it('allows user to override legend label texts using props', () => {
const legendRelatedProps = {
legendMinText: 'legendMinText',
legendMaxText: 'legendMaxText',
legendAverageText: 'legendAverageText',
legendCurrentText: 'legendCurrentText',
};
wrapper.setProps({
...legendRelatedProps,
});
return wrapper.vm.$nextTick().then(() => {
expect(findChart().props()).toMatchObject(legendRelatedProps);
});
});
it('should render a tabular legend layout by default', () => {
expect(findLegend().props('layout')).toBe('table');
});
describe('when inline legend layout prop is set', () => {
beforeEach(() => {
wrapper.setProps({
legendLayout: 'inline',
});
});
it('should render an inline legend layout', () => {
expect(findLegend().props('layout')).toBe('inline');
});
});
describe('when table legend layout prop is set', () => {
beforeEach(() => {
wrapper.setProps({
legendLayout: 'table',
});
});
it('should render a tabular legend layout', () => {
expect(findLegend().props('layout')).toBe('table');
});
});
});
}); });
...@@ -55,6 +55,7 @@ describe('Time series component', () => { ...@@ -55,6 +55,7 @@ describe('Time series component', () => {
stubs: { stubs: {
GlPopover: true, GlPopover: true,
}, },
attachToDocument: true,
}); });
}; };
...@@ -87,19 +88,23 @@ describe('Time series component', () => { ...@@ -87,19 +88,23 @@ describe('Time series component', () => {
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}); });
it('allows user to override max value label text using prop', () => { afterEach(() => {
wrapper.setProps({ legendMaxText: 'legendMaxText' }); wrapper.destroy();
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.props().legendMaxText).toBe('legendMaxText');
});
}); });
it('allows user to override average value label text using prop', () => { it('allows user to override legend label texts using props', () => {
wrapper.setProps({ legendAverageText: 'averageText' }); const legendRelatedProps = {
legendMinText: 'legendMinText',
legendMaxText: 'legendMaxText',
legendAverageText: 'legendAverageText',
legendCurrentText: 'legendCurrentText',
};
wrapper.setProps({
...legendRelatedProps,
});
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(wrapper.props().legendAverageText).toBe('averageText'); expect(findChart().props()).toMatchObject(legendRelatedProps);
}); });
}); });
...@@ -744,4 +749,45 @@ describe('Time series component', () => { ...@@ -744,4 +749,45 @@ describe('Time series component', () => {
}); });
}); });
}); });
describe('legend layout', () => {
const findLegend = () => wrapper.find(GlChartLegend);
beforeEach(() => {
createWrapper(mockGraphData, mount);
return wrapper.vm.$nextTick();
});
afterEach(() => {
wrapper.destroy();
});
it('should render a tabular legend layout by default', () => {
expect(findLegend().props('layout')).toBe('table');
});
describe('when inline legend layout prop is set', () => {
beforeEach(() => {
wrapper.setProps({
legendLayout: 'inline',
});
});
it('should render an inline legend layout', () => {
expect(findLegend().props('layout')).toBe('inline');
});
});
describe('when table legend layout prop is set', () => {
beforeEach(() => {
wrapper.setProps({
legendLayout: 'table',
});
});
it('should render a tabular legend layout', () => {
expect(findLegend().props('layout')).toBe('table');
});
});
});
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment