@gitlab/ui 71.4.0 → 71.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/components/charts/area/area.js +1 -0
- package/dist/components/charts/column/column.js +1 -0
- package/dist/components/charts/heatmap/heatmap.js +1 -0
- package/dist/components/charts/line/line.js +1 -0
- package/dist/components/charts/tooltip/tooltip.js +37 -3
- package/dist/components/experimental/duo/chat/duo_chat.js +1 -1
- package/dist/tokens/css/tokens.css +1 -1
- package/dist/tokens/css/tokens.dark.css +1 -1
- package/dist/tokens/js/tokens.dark.js +1 -1
- package/dist/tokens/js/tokens.js +1 -1
- package/dist/tokens/scss/_tokens.dark.scss +1 -1
- package/dist/tokens/scss/_tokens.scss +1 -1
- package/dist/utils/charts/config.js +51 -1
- package/package.json +1 -1
- package/src/components/charts/area/area.vue +2 -0
- package/src/components/charts/column/column.vue +2 -0
- package/src/components/charts/heatmap/heatmap.vue +2 -0
- package/src/components/charts/line/line.vue +2 -0
- package/src/components/charts/stacked_column/stacked_column.spec.js +3 -7
- package/src/components/charts/tooltip/tooltip.spec.js +94 -1
- package/src/components/charts/tooltip/tooltip.stories.js +0 -23
- package/src/components/charts/tooltip/tooltip.vue +58 -3
- package/src/components/experimental/duo/chat/duo_chat.md +3 -3
- package/src/components/experimental/duo/chat/duo_chat.vue +2 -2
- package/src/components/experimental/experiment_badge/experiment_badge.md +1 -1
- package/src/components/experimental/experiment_badge/experiment_badge.stories.js +1 -2
- package/src/utils/charts/config.js +43 -0
- package/src/utils/charts/config.spec.js +93 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [71.5.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v71.4.0...v71.5.0) (2023-12-05)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **GlChartTooltip:** Add slot API for chart tooltip ([572b33e](https://gitlab.com/gitlab-org/gitlab-ui/commit/572b33e5151f2d0508bca07d895f3cd444f4af54))
|
|
7
|
+
|
|
1
8
|
# [71.4.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v71.3.0...v71.4.0) (2023-12-04)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -3,13 +3,16 @@ import { uid, debounceByAnimationFrame } from '../../../utils/utils';
|
|
|
3
3
|
import GlPopover from '../../base/popover/popover';
|
|
4
4
|
import { popoverPlacements } from '../../../utils/constants';
|
|
5
5
|
import { TOOLTIP_LEFT_OFFSET, TOOLTIP_TOP_OFFSET } from '../../../utils/charts/constants';
|
|
6
|
+
import { getTooltipTitle, getTooltipContent } from '../../../utils/charts/config';
|
|
7
|
+
import TooltipDefaultFormat from '../../shared_components/charts/tooltip_default_format';
|
|
6
8
|
import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
|
|
7
9
|
|
|
8
10
|
//
|
|
9
11
|
var script = {
|
|
10
12
|
name: 'GlChartTooltip',
|
|
11
13
|
components: {
|
|
12
|
-
GlPopover
|
|
14
|
+
GlPopover,
|
|
15
|
+
TooltipDefaultFormat
|
|
13
16
|
},
|
|
14
17
|
inheritAttrs: false,
|
|
15
18
|
props: {
|
|
@@ -103,13 +106,24 @@ var script = {
|
|
|
103
106
|
// popover target must have a size of at least 1
|
|
104
107
|
return value >= 1;
|
|
105
108
|
}
|
|
109
|
+
},
|
|
110
|
+
/**
|
|
111
|
+
* Set to true to use the default tooltip formatter.
|
|
112
|
+
*/
|
|
113
|
+
useDefaultTooltipFormatter: {
|
|
114
|
+
type: Boolean,
|
|
115
|
+
required: false,
|
|
116
|
+
default: false
|
|
106
117
|
}
|
|
107
118
|
},
|
|
108
119
|
data() {
|
|
109
120
|
return {
|
|
110
121
|
pointerPosition: null,
|
|
111
122
|
isPointerInChart: false,
|
|
112
|
-
debouncedMouseHandler: debounceByAnimationFrame(this.mouseHandler)
|
|
123
|
+
debouncedMouseHandler: debounceByAnimationFrame(this.mouseHandler),
|
|
124
|
+
title: null,
|
|
125
|
+
content: {},
|
|
126
|
+
params: null
|
|
113
127
|
};
|
|
114
128
|
},
|
|
115
129
|
computed: {
|
|
@@ -154,6 +168,26 @@ var script = {
|
|
|
154
168
|
created() {
|
|
155
169
|
this.chart.getZr().on('mousemove', this.debouncedMouseHandler);
|
|
156
170
|
this.chart.getZr().on('mouseout', this.debouncedMouseHandler);
|
|
171
|
+
if (this.useDefaultTooltipFormatter) {
|
|
172
|
+
this.chart.setOption({
|
|
173
|
+
xAxis: {
|
|
174
|
+
axisPointer: {
|
|
175
|
+
show: true,
|
|
176
|
+
label: {
|
|
177
|
+
formatter: params => {
|
|
178
|
+
var _options$xAxis, _options$xAxis$, _options$yAxis, _options$yAxis$;
|
|
179
|
+
const options = this.chart.getOption();
|
|
180
|
+
const titleAxisName = (_options$xAxis = options.xAxis) === null || _options$xAxis === void 0 ? void 0 : (_options$xAxis$ = _options$xAxis[0]) === null || _options$xAxis$ === void 0 ? void 0 : _options$xAxis$.name;
|
|
181
|
+
const valueAxisName = (_options$yAxis = options.yAxis) === null || _options$yAxis === void 0 ? void 0 : (_options$yAxis$ = _options$yAxis[0]) === null || _options$yAxis$ === void 0 ? void 0 : _options$yAxis$.name;
|
|
182
|
+
this.title = getTooltipTitle(params, titleAxisName);
|
|
183
|
+
this.content = getTooltipContent(params, valueAxisName);
|
|
184
|
+
this.params = params;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
157
191
|
},
|
|
158
192
|
beforeDestroy() {
|
|
159
193
|
this.chart.getZr().off('mousemove', this.debouncedMouseHandler);
|
|
@@ -182,7 +216,7 @@ var script = {
|
|
|
182
216
|
const __vue_script__ = script;
|
|
183
217
|
|
|
184
218
|
/* template */
|
|
185
|
-
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.chart)?_c('div',{staticClass:"gl-pointer-events-none"},[_c('div',{staticClass:"gl-chart-tooltip",style:(Object.assign({}, (_vm.fixedPosition || _vm.pointerPosition), _vm.targetStyle)),attrs:{"id":_vm.targetId}}),_vm._v(" "),_c('gl-popover',_vm._b({attrs:{"show":_vm.shouldShowPopover,"target":_vm.targetId,"container":_vm.targetId,"placement":_vm.placement,"triggers":""},scopedSlots:_vm._u([
|
|
219
|
+
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.chart)?_c('div',{staticClass:"gl-pointer-events-none"},[_c('div',{staticClass:"gl-chart-tooltip",style:(Object.assign({}, (_vm.fixedPosition || _vm.pointerPosition), _vm.targetStyle)),attrs:{"id":_vm.targetId}}),_vm._v(" "),_c('gl-popover',_vm._b({attrs:{"show":_vm.shouldShowPopover,"target":_vm.targetId,"container":_vm.targetId,"placement":_vm.placement,"triggers":""},scopedSlots:_vm._u([{key:"title",fn:function(){return [_vm._t("title",function(){return [_vm._v(_vm._s(_vm.title))]},null,{ title: _vm.title, params: _vm.params })]},proxy:true}],null,true)},'gl-popover',_vm.$attrs,false),[_vm._v(" "),_vm._t("default",function(){return [_c('tooltip-default-format',{attrs:{"tooltip-content":_vm.content},scopedSlots:_vm._u([(_vm.$scopedSlots['tooltip-value'])?{key:"tooltip-value",fn:function(scope){return [_vm._t("tooltip-value",null,null,scope)]}}:null],null,true)})]},null,{ content: _vm.content, params: _vm.params })],2)],1):_vm._e()};
|
|
186
220
|
var __vue_staticRenderFns__ = [];
|
|
187
221
|
|
|
188
222
|
/* style */
|
|
@@ -119,7 +119,7 @@ var script = {
|
|
|
119
119
|
default: () => i18n.CHAT_DEFAULT_PREDEFINED_PROMPTS
|
|
120
120
|
},
|
|
121
121
|
/**
|
|
122
|
-
* URL to the
|
|
122
|
+
* URL to the help page. This is passed down to the `GlExperimentBadge` component.
|
|
123
123
|
*/
|
|
124
124
|
badgeHelpPageUrl: {
|
|
125
125
|
type: String,
|
package/dist/tokens/js/tokens.js
CHANGED
|
@@ -462,6 +462,55 @@ const generateLineSeries = _ref5 => {
|
|
|
462
462
|
}
|
|
463
463
|
};
|
|
464
464
|
};
|
|
465
|
+
const getTooltipTitle = function () {
|
|
466
|
+
let params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
467
|
+
let titleAxisName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
468
|
+
if (!params) return '';
|
|
469
|
+
const title = params.seriesData.reduce((acc, _ref6) => {
|
|
470
|
+
let {
|
|
471
|
+
value
|
|
472
|
+
} = _ref6;
|
|
473
|
+
if (acc.includes(value[0])) {
|
|
474
|
+
return acc;
|
|
475
|
+
}
|
|
476
|
+
return [...acc, value[0]];
|
|
477
|
+
}, []).join(', ');
|
|
478
|
+
return titleAxisName ? `${title} (${titleAxisName})` : title;
|
|
479
|
+
};
|
|
480
|
+
const getTooltipContent = function () {
|
|
481
|
+
let params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
482
|
+
let valueAxisName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
483
|
+
if (!params) {
|
|
484
|
+
return {};
|
|
485
|
+
}
|
|
486
|
+
const {
|
|
487
|
+
seriesData
|
|
488
|
+
} = params;
|
|
489
|
+
if (seriesData.length === 1) {
|
|
490
|
+
const {
|
|
491
|
+
value: [, yValue],
|
|
492
|
+
seriesName
|
|
493
|
+
} = seriesData[0];
|
|
494
|
+
return {
|
|
495
|
+
[valueAxisName || seriesName]: {
|
|
496
|
+
value: yValue,
|
|
497
|
+
color: '' // ignore color when showing a single series
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
return seriesData.reduce((acc, _ref7) => {
|
|
502
|
+
let {
|
|
503
|
+
value: [, yValue],
|
|
504
|
+
seriesName,
|
|
505
|
+
color
|
|
506
|
+
} = _ref7;
|
|
507
|
+
acc[seriesName] = {
|
|
508
|
+
value: yValue,
|
|
509
|
+
color
|
|
510
|
+
};
|
|
511
|
+
return acc;
|
|
512
|
+
}, {});
|
|
513
|
+
};
|
|
465
514
|
|
|
466
515
|
/**
|
|
467
516
|
* The method works well if tooltip content should be against y-axis values.
|
|
@@ -472,6 +521,7 @@ const generateLineSeries = _ref5 => {
|
|
|
472
521
|
* @param {Object} params series data
|
|
473
522
|
* @param {String} yAxisTitle y-axis title
|
|
474
523
|
* @returns {Object} tooltip title and content
|
|
524
|
+
* @deprecated Use getTooltipContent and getTooltipContent to obtain the tooltip
|
|
475
525
|
*/
|
|
476
526
|
const getDefaultTooltipContent = function (params) {
|
|
477
527
|
let yAxisTitle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
@@ -503,4 +553,4 @@ const getDefaultTooltipContent = function (params) {
|
|
|
503
553
|
};
|
|
504
554
|
};
|
|
505
555
|
|
|
506
|
-
export { annotationsYAxisCoords, axes, dataZoomAdjustments, defaultAreaOpacity, defaultChartOptions, defaultFontSize, defaultHeight, defaultWidth, generateAnnotationSeries, generateBarSeries, generateLineSeries, getAnnotationsConfig, getDataZoomConfig, getDefaultTooltipContent, getThresholdConfig, grid, gridWithSecondaryYAxis, lineStyle, mergeAnnotationAxisToOptions, mergeSeriesToOptions, parseAnnotations, symbolSize, toolboxHeight, validRenderers, xAxis, yAxis };
|
|
556
|
+
export { annotationsYAxisCoords, axes, dataZoomAdjustments, defaultAreaOpacity, defaultChartOptions, defaultFontSize, defaultHeight, defaultWidth, generateAnnotationSeries, generateBarSeries, generateLineSeries, getAnnotationsConfig, getDataZoomConfig, getDefaultTooltipContent, getThresholdConfig, getTooltipContent, getTooltipTitle, grid, gridWithSecondaryYAxis, lineStyle, mergeAnnotationAxisToOptions, mergeSeriesToOptions, parseAnnotations, symbolSize, toolboxHeight, validRenderers, xAxis, yAxis };
|
package/package.json
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
mergeSeriesToOptions,
|
|
28
28
|
mergeAnnotationAxisToOptions,
|
|
29
29
|
lineStyle,
|
|
30
|
+
// eslint-disable-next-line import/no-deprecated
|
|
30
31
|
getDefaultTooltipContent,
|
|
31
32
|
} from '../../../utils/charts/config';
|
|
32
33
|
import {
|
|
@@ -265,6 +266,7 @@ export default {
|
|
|
265
266
|
},
|
|
266
267
|
methods: {
|
|
267
268
|
defaultFormatTooltipText(params) {
|
|
269
|
+
// eslint-disable-next-line import/no-deprecated
|
|
268
270
|
const { xLabels, tooltipContent } = getDefaultTooltipContent(params, this.options.yAxis.name);
|
|
269
271
|
|
|
270
272
|
this.$set(this, 'dataTooltipContent', tooltipContent);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
yAxis,
|
|
9
9
|
dataZoomAdjustments,
|
|
10
10
|
mergeSeriesToOptions,
|
|
11
|
+
// eslint-disable-next-line import/no-deprecated
|
|
11
12
|
getDefaultTooltipContent,
|
|
12
13
|
generateBarSeries,
|
|
13
14
|
generateLineSeries,
|
|
@@ -180,6 +181,7 @@ export default {
|
|
|
180
181
|
this.$emit('created', chart);
|
|
181
182
|
},
|
|
182
183
|
onLabelChange(params) {
|
|
184
|
+
// eslint-disable-next-line import/no-deprecated
|
|
183
185
|
const { xLabels, tooltipContent } = getDefaultTooltipContent(params, this.yAxisTitle);
|
|
184
186
|
|
|
185
187
|
this.$set(this, 'tooltipContent', tooltipContent);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
<script>
|
|
3
3
|
import merge from 'lodash/merge';
|
|
4
4
|
import { WHITE, GRAY_100 } from '../../../../dist/tokens/js/tokens';
|
|
5
|
+
// eslint-disable-next-line import/no-deprecated
|
|
5
6
|
import { getDefaultTooltipContent } from '../../../utils/charts/config';
|
|
6
7
|
import { HEIGHT_AUTO_CLASSES } from '../../../utils/charts/constants';
|
|
7
8
|
import { heatmapHues } from '../../../utils/charts/theme';
|
|
@@ -222,6 +223,7 @@ export default {
|
|
|
222
223
|
},
|
|
223
224
|
methods: {
|
|
224
225
|
defaultFormatTooltipText(params) {
|
|
226
|
+
// eslint-disable-next-line import/no-deprecated
|
|
225
227
|
const { xLabels, tooltipContent } = getDefaultTooltipContent(
|
|
226
228
|
params,
|
|
227
229
|
this.computedOptions.yAxis.name
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
mergeSeriesToOptions,
|
|
31
31
|
mergeAnnotationAxisToOptions,
|
|
32
32
|
lineStyle,
|
|
33
|
+
// eslint-disable-next-line import/no-deprecated
|
|
33
34
|
getDefaultTooltipContent,
|
|
34
35
|
} from '../../../utils/charts/config';
|
|
35
36
|
import {
|
|
@@ -260,6 +261,7 @@ export default {
|
|
|
260
261
|
},
|
|
261
262
|
methods: {
|
|
262
263
|
defaultFormatTooltipText(params) {
|
|
264
|
+
// eslint-disable-next-line import/no-deprecated
|
|
263
265
|
const { xLabels, tooltipContent } = getDefaultTooltipContent(params, this.options.yAxis.name);
|
|
264
266
|
|
|
265
267
|
this.$set(this, 'dataTooltipContent', tooltipContent);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { shallowMount } from '@vue/test-utils';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import { createMockChartInstance, ChartTooltipStub } from '~helpers/chart_stubs';
|
|
3
|
+
import { createMockChartInstance } from '~helpers/chart_stubs';
|
|
5
4
|
import { expectHeightAutoClasses } from '~helpers/chart_height';
|
|
6
5
|
import { LEGEND_LAYOUT_INLINE, LEGEND_LAYOUT_TABLE } from '~/utils/charts/constants';
|
|
7
6
|
import {
|
|
@@ -11,6 +10,7 @@ import {
|
|
|
11
10
|
} from '../../../utils/charts/mock_data';
|
|
12
11
|
import Chart from '../chart/chart.vue';
|
|
13
12
|
import ChartLegend from '../legend/legend.vue';
|
|
13
|
+
import ChartTooltip from '../tooltip/tooltip.vue';
|
|
14
14
|
import * as themeUtils from '../../../utils/charts/theme';
|
|
15
15
|
import StackedColumnChart from './stacked_column.vue';
|
|
16
16
|
|
|
@@ -34,17 +34,13 @@ describe('stacked column chart component', () => {
|
|
|
34
34
|
|
|
35
35
|
const findChart = () => wrapper.findComponent(Chart);
|
|
36
36
|
const findLegend = () => wrapper.findComponent(ChartLegend);
|
|
37
|
-
const findDataTooltip = () => wrapper.findComponent(
|
|
37
|
+
const findDataTooltip = () => wrapper.findComponent(ChartTooltip);
|
|
38
38
|
|
|
39
39
|
const emitChartCreated = () => findChart().vm.$emit('created', mockChartInstance);
|
|
40
40
|
|
|
41
41
|
const createShallowWrapper = ({ props = {}, slots = {} } = {}) => {
|
|
42
42
|
wrapper = shallowMount(StackedColumnChart, {
|
|
43
43
|
propsData: { ...defaultChartProps, ...props },
|
|
44
|
-
stubs: {
|
|
45
|
-
'tooltip-default-format': TooltipDefaultFormat,
|
|
46
|
-
ChartTooltip: ChartTooltipStub,
|
|
47
|
-
},
|
|
48
44
|
slots,
|
|
49
45
|
});
|
|
50
46
|
emitChartCreated();
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { nextTick } from 'vue';
|
|
1
2
|
import { shallowMount } from '@vue/test-utils';
|
|
2
3
|
import { createMockChartInstance } from '~helpers/chart_stubs';
|
|
3
4
|
import GlPopover from '../../base/popover/popover.vue';
|
|
4
5
|
import { popoverPlacements } from '../../../utils/constants';
|
|
5
6
|
import { waitForAnimationFrame } from '../../../utils/test_utils';
|
|
7
|
+
import TooltipDefaultFormat from '../../shared_components/charts/tooltip_default_format.vue';
|
|
6
8
|
|
|
7
9
|
import ChartTooltip from './tooltip.vue';
|
|
8
10
|
|
|
@@ -17,6 +19,7 @@ describe('ChartTooltip', () => {
|
|
|
17
19
|
let mockContainPixel;
|
|
18
20
|
|
|
19
21
|
const findPopover = () => wrapper.findComponent(GlPopover);
|
|
22
|
+
const findTooltipDefaultFormat = () => wrapper.findComponent(TooltipDefaultFormat);
|
|
20
23
|
const findPopoverTarget = () => wrapper.find(`#${findPopover().attributes('target')}`);
|
|
21
24
|
const getPopoverTargetStyle = (name) => findPopoverTarget().element.style.getPropertyValue(name);
|
|
22
25
|
|
|
@@ -26,11 +29,12 @@ describe('ChartTooltip', () => {
|
|
|
26
29
|
await waitForAnimationFrame();
|
|
27
30
|
};
|
|
28
31
|
|
|
29
|
-
const createWrapper = (props = {},
|
|
32
|
+
const createWrapper = (props = {}, options) => {
|
|
30
33
|
mockChartInstance = {
|
|
31
34
|
...createMockChartInstance(),
|
|
32
35
|
containPixel: mockContainPixel,
|
|
33
36
|
};
|
|
37
|
+
|
|
34
38
|
wrapper = shallowMount(ChartTooltip, {
|
|
35
39
|
propsData: {
|
|
36
40
|
chart: mockChartInstance,
|
|
@@ -186,4 +190,93 @@ describe('ChartTooltip', () => {
|
|
|
186
190
|
});
|
|
187
191
|
});
|
|
188
192
|
});
|
|
193
|
+
|
|
194
|
+
describe('is customized via slots', () => {
|
|
195
|
+
const triggerFormatter = (params) => {
|
|
196
|
+
const { formatter } = mockChartInstance.setOption.mock.calls[0][0].xAxis.axisPointer.label;
|
|
197
|
+
formatter(params);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
describe('formats tooltip', () => {
|
|
201
|
+
beforeEach(() => {
|
|
202
|
+
createWrapper(
|
|
203
|
+
{
|
|
204
|
+
useDefaultTooltipFormatter: true,
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
stubs: {
|
|
208
|
+
GlPopover: {
|
|
209
|
+
template: `<div>
|
|
210
|
+
<slot name="title"></slot>
|
|
211
|
+
<slot></slot>
|
|
212
|
+
</div>`,
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('sets tooltip formatter function', () => {
|
|
220
|
+
expect(mockChartInstance.setOption).toHaveBeenCalledWith({
|
|
221
|
+
xAxis: {
|
|
222
|
+
axisPointer: {
|
|
223
|
+
label: {
|
|
224
|
+
formatter: expect.any(Function),
|
|
225
|
+
},
|
|
226
|
+
show: true,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('formats tooltip', async () => {
|
|
233
|
+
expect(findTooltipDefaultFormat().props('tooltipContent')).toEqual({});
|
|
234
|
+
|
|
235
|
+
triggerFormatter({
|
|
236
|
+
seriesData: [
|
|
237
|
+
{
|
|
238
|
+
seriesName: 'Series 1',
|
|
239
|
+
value: ['Value', 1],
|
|
240
|
+
color: '#aaa',
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
seriesName: 'Series 2',
|
|
244
|
+
value: ['Value', 2],
|
|
245
|
+
color: '#bbb',
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
});
|
|
249
|
+
await nextTick();
|
|
250
|
+
|
|
251
|
+
expect(findPopover().text()).toBe('Value');
|
|
252
|
+
expect(findTooltipDefaultFormat().props('tooltipContent')).toEqual({
|
|
253
|
+
'Series 1': { color: '#aaa', value: 1 },
|
|
254
|
+
'Series 2': { color: '#bbb', value: 2 },
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('formats tooltip with axis names', async () => {
|
|
259
|
+
mockChartInstance.getOption.mockReturnValueOnce({
|
|
260
|
+
xAxis: [{ name: 'Time' }],
|
|
261
|
+
yAxis: [{ name: 'Amount' }],
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
triggerFormatter({
|
|
265
|
+
seriesData: [
|
|
266
|
+
{
|
|
267
|
+
seriesName: 'Series 1',
|
|
268
|
+
value: ['Value', 1],
|
|
269
|
+
color: '#aaa',
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
});
|
|
273
|
+
await nextTick();
|
|
274
|
+
|
|
275
|
+
expect(findPopover().text()).toBe('Value (Time)');
|
|
276
|
+
expect(findTooltipDefaultFormat().props('tooltipContent')).toEqual({
|
|
277
|
+
Amount: { color: '', value: 1 },
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
189
282
|
});
|
|
@@ -75,29 +75,6 @@ export const WithLongSeriesLabelWithNoSpaces = (args, { argTypes }) => ({
|
|
|
75
75
|
`),
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
export const TitleProp = (args, { argTypes }) => ({
|
|
79
|
-
props: Object.keys(argTypes),
|
|
80
|
-
...baseStoryOptions,
|
|
81
|
-
template: `
|
|
82
|
-
<div class="position-relative">
|
|
83
|
-
<gl-chart
|
|
84
|
-
:options="options"
|
|
85
|
-
:height="100"
|
|
86
|
-
@created="onCreated"
|
|
87
|
-
/>
|
|
88
|
-
<gl-chart-tooltip
|
|
89
|
-
v-if="chart"
|
|
90
|
-
:chart="chart"
|
|
91
|
-
:show="showTooltip"
|
|
92
|
-
:top="top"
|
|
93
|
-
:left="left"
|
|
94
|
-
title="Title from prop"
|
|
95
|
-
>
|
|
96
|
-
Example content
|
|
97
|
-
</gl-chart-tooltip>
|
|
98
|
-
</div>`,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
78
|
export default {
|
|
102
79
|
title: 'charts/chart-tooltip',
|
|
103
80
|
component: GlChartTooltip,
|
|
@@ -5,11 +5,14 @@ import { uid, debounceByAnimationFrame } from '../../../utils/utils';
|
|
|
5
5
|
import GlPopover from '../../base/popover/popover.vue';
|
|
6
6
|
import { popoverPlacements } from '../../../utils/constants';
|
|
7
7
|
import { TOOLTIP_LEFT_OFFSET, TOOLTIP_TOP_OFFSET } from '../../../utils/charts/constants';
|
|
8
|
+
import { getTooltipTitle, getTooltipContent } from '../../../utils/charts/config';
|
|
9
|
+
import TooltipDefaultFormat from '../../shared_components/charts/tooltip_default_format.vue';
|
|
8
10
|
|
|
9
11
|
export default {
|
|
10
12
|
name: 'GlChartTooltip',
|
|
11
13
|
components: {
|
|
12
14
|
GlPopover,
|
|
15
|
+
TooltipDefaultFormat,
|
|
13
16
|
},
|
|
14
17
|
inheritAttrs: false,
|
|
15
18
|
props: {
|
|
@@ -104,6 +107,15 @@ export default {
|
|
|
104
107
|
return value >= 1;
|
|
105
108
|
},
|
|
106
109
|
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Set to true to use the default tooltip formatter.
|
|
113
|
+
*/
|
|
114
|
+
useDefaultTooltipFormatter: {
|
|
115
|
+
type: Boolean,
|
|
116
|
+
required: false,
|
|
117
|
+
default: false,
|
|
118
|
+
},
|
|
107
119
|
},
|
|
108
120
|
data() {
|
|
109
121
|
return {
|
|
@@ -111,6 +123,10 @@ export default {
|
|
|
111
123
|
isPointerInChart: false,
|
|
112
124
|
|
|
113
125
|
debouncedMouseHandler: debounceByAnimationFrame(this.mouseHandler),
|
|
126
|
+
|
|
127
|
+
title: null,
|
|
128
|
+
content: {},
|
|
129
|
+
params: null,
|
|
114
130
|
};
|
|
115
131
|
},
|
|
116
132
|
computed: {
|
|
@@ -146,7 +162,29 @@ export default {
|
|
|
146
162
|
created() {
|
|
147
163
|
this.chart.getZr().on('mousemove', this.debouncedMouseHandler);
|
|
148
164
|
this.chart.getZr().on('mouseout', this.debouncedMouseHandler);
|
|
165
|
+
|
|
166
|
+
if (this.useDefaultTooltipFormatter) {
|
|
167
|
+
this.chart.setOption({
|
|
168
|
+
xAxis: {
|
|
169
|
+
axisPointer: {
|
|
170
|
+
show: true,
|
|
171
|
+
label: {
|
|
172
|
+
formatter: (params) => {
|
|
173
|
+
const options = this.chart.getOption();
|
|
174
|
+
const titleAxisName = options.xAxis?.[0]?.name;
|
|
175
|
+
const valueAxisName = options.yAxis?.[0]?.name;
|
|
176
|
+
|
|
177
|
+
this.title = getTooltipTitle(params, titleAxisName);
|
|
178
|
+
this.content = getTooltipContent(params, valueAxisName);
|
|
179
|
+
this.params = params;
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
149
186
|
},
|
|
187
|
+
|
|
150
188
|
beforeDestroy() {
|
|
151
189
|
this.chart.getZr().off('mousemove', this.debouncedMouseHandler);
|
|
152
190
|
this.chart.getZr().off('mouseout', this.debouncedMouseHandler);
|
|
@@ -189,10 +227,27 @@ export default {
|
|
|
189
227
|
:placement="placement"
|
|
190
228
|
triggers=""
|
|
191
229
|
>
|
|
192
|
-
<template
|
|
193
|
-
|
|
230
|
+
<template #title>
|
|
231
|
+
<!--
|
|
232
|
+
@slot Tooltip title
|
|
233
|
+
@binding {string} title - Default title
|
|
234
|
+
@binding {object} params
|
|
235
|
+
-->
|
|
236
|
+
<slot name="title" v-bind="{ title, params }">{{ title }}</slot>
|
|
194
237
|
</template>
|
|
195
|
-
|
|
238
|
+
<!--
|
|
239
|
+
@slot Tooltip content
|
|
240
|
+
@binding {object} content - Key-value pairs of series information
|
|
241
|
+
@binding {object} params - Full list of params from `onLabelChange`. Can be null before tooltip is shown
|
|
242
|
+
-->
|
|
243
|
+
<slot v-bind="{ content, params }">
|
|
244
|
+
<tooltip-default-format :tooltip-content="content">
|
|
245
|
+
<template v-if="$scopedSlots['tooltip-value']" #tooltip-value="scope">
|
|
246
|
+
<!-- @slot Tooltip value formatter -->
|
|
247
|
+
<slot name="tooltip-value" v-bind="scope"></slot>
|
|
248
|
+
</template>
|
|
249
|
+
</tooltip-default-format>
|
|
250
|
+
</slot>
|
|
196
251
|
</gl-popover>
|
|
197
252
|
</div>
|
|
198
253
|
</template>
|
|
@@ -16,7 +16,7 @@ consumer component.
|
|
|
16
16
|
:is-loading="isLoading"
|
|
17
17
|
:is-chat-available="isChatAvailable"
|
|
18
18
|
:predefined-prompts="predefinedPrompts"
|
|
19
|
-
:
|
|
19
|
+
:badge-help-page-url="badgeHelpPageUrl"
|
|
20
20
|
:tool-name="toolName"
|
|
21
21
|
@chat-hidden="onChatHidden"
|
|
22
22
|
@send-chat-prompt="onSendChatPrompt"
|
|
@@ -69,7 +69,7 @@ be necessarily reactive in the consumer component. The properties that might be
|
|
|
69
69
|
questions. Usually, this decision stays the same during the component's lifecycle.
|
|
70
70
|
- `predefinedPrompts`. The `Array` of strings that represents the possible questions to ask when
|
|
71
71
|
there are no messages in the chat.
|
|
72
|
-
- `
|
|
72
|
+
- `badgeHelpPageUrl`. The link to an external page explaining the meaning of an "experiment".
|
|
73
73
|
The prop is passed down to the [`GlExperimentBadge` component](?path=/docs/experimental-experiment-badge--docs).
|
|
74
74
|
|
|
75
75
|
### Set up communication with consumer
|
|
@@ -100,7 +100,7 @@ export default {
|
|
|
100
100
|
this.title = 'Foo Bar';
|
|
101
101
|
this.isChatAvailable = true; // this is just an example. `true` is the default value
|
|
102
102
|
this.predefinedPrompts = ['How to …?', 'Where do I …?'];
|
|
103
|
-
this.
|
|
103
|
+
this.badgeHelpPageUrl = 'https://dev.null';
|
|
104
104
|
}
|
|
105
105
|
methods: {
|
|
106
106
|
onChatHidden() {
|
|
@@ -134,7 +134,7 @@ export default {
|
|
|
134
134
|
default: () => i18n.CHAT_DEFAULT_PREDEFINED_PROMPTS,
|
|
135
135
|
},
|
|
136
136
|
/**
|
|
137
|
-
* URL to the
|
|
137
|
+
* URL to the help page. This is passed down to the `GlExperimentBadge` component.
|
|
138
138
|
*/
|
|
139
139
|
badgeHelpPageUrl: {
|
|
140
140
|
type: String,
|
|
@@ -366,7 +366,7 @@ export default {
|
|
|
366
366
|
>{{ $options.i18n.CHAT_LEGAL_GENERATED_BY_AI }}</gl-alert
|
|
367
367
|
>
|
|
368
368
|
|
|
369
|
-
<!--
|
|
369
|
+
<!--
|
|
370
370
|
@slot Subheader to be rendered right after the title. It is sticky and stays on top of the chat no matter the number of messages.
|
|
371
371
|
-->
|
|
372
372
|
<slot name="subheader"></slot>
|
|
@@ -32,8 +32,7 @@ Default.args = generateProps();
|
|
|
32
32
|
export const WithHelpPageUrl = Template.bind({});
|
|
33
33
|
WithHelpPageUrl.args = {
|
|
34
34
|
...generateProps({
|
|
35
|
-
|
|
36
|
-
'https://docs.gitlab.com/ee/policy/experiment-beta-support.html#experiment',
|
|
35
|
+
helpPageUrl: 'https://docs.gitlab.com/ee/policy/experiment-beta-support.html#experiment',
|
|
37
36
|
}),
|
|
38
37
|
};
|
|
39
38
|
|
|
@@ -433,6 +433,48 @@ export const generateLineSeries = ({ name, color, data = [], yAxisIndex = 0 }) =
|
|
|
433
433
|
itemStyle: { color },
|
|
434
434
|
});
|
|
435
435
|
|
|
436
|
+
export const getTooltipTitle = (params = null, titleAxisName = null) => {
|
|
437
|
+
if (!params) return '';
|
|
438
|
+
|
|
439
|
+
const title = params.seriesData
|
|
440
|
+
.reduce((acc, { value }) => {
|
|
441
|
+
if (acc.includes(value[0])) {
|
|
442
|
+
return acc;
|
|
443
|
+
}
|
|
444
|
+
return [...acc, value[0]];
|
|
445
|
+
}, [])
|
|
446
|
+
.join(', ');
|
|
447
|
+
|
|
448
|
+
return titleAxisName ? `${title} (${titleAxisName})` : title;
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
export const getTooltipContent = (params = null, valueAxisName = null) => {
|
|
452
|
+
if (!params) {
|
|
453
|
+
return {};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const { seriesData } = params;
|
|
457
|
+
|
|
458
|
+
if (seriesData.length === 1) {
|
|
459
|
+
const {
|
|
460
|
+
value: [, yValue],
|
|
461
|
+
seriesName,
|
|
462
|
+
} = seriesData[0];
|
|
463
|
+
|
|
464
|
+
return {
|
|
465
|
+
[valueAxisName || seriesName]: {
|
|
466
|
+
value: yValue,
|
|
467
|
+
color: '', // ignore color when showing a single series
|
|
468
|
+
},
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return seriesData.reduce((acc, { value: [, yValue], seriesName, color }) => {
|
|
473
|
+
acc[seriesName] = { value: yValue, color };
|
|
474
|
+
return acc;
|
|
475
|
+
}, {});
|
|
476
|
+
};
|
|
477
|
+
|
|
436
478
|
/**
|
|
437
479
|
* The method works well if tooltip content should be against y-axis values.
|
|
438
480
|
* However, for bar charts, the tooltip should be against x-axis values.
|
|
@@ -442,6 +484,7 @@ export const generateLineSeries = ({ name, color, data = [], yAxisIndex = 0 }) =
|
|
|
442
484
|
* @param {Object} params series data
|
|
443
485
|
* @param {String} yAxisTitle y-axis title
|
|
444
486
|
* @returns {Object} tooltip title and content
|
|
487
|
+
* @deprecated Use getTooltipContent and getTooltipContent to obtain the tooltip
|
|
445
488
|
*/
|
|
446
489
|
export const getDefaultTooltipContent = (params, yAxisTitle = null) => {
|
|
447
490
|
const seriesDataLength = params.seriesData.length;
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
parseAnnotations,
|
|
10
10
|
generateBarSeries,
|
|
11
11
|
generateLineSeries,
|
|
12
|
+
getTooltipTitle,
|
|
13
|
+
getTooltipContent,
|
|
12
14
|
} from './config';
|
|
13
15
|
import {
|
|
14
16
|
mockDefaultDataZoomConfig,
|
|
@@ -382,4 +384,95 @@ describe('chart config helpers', () => {
|
|
|
382
384
|
).toMatchObject(result);
|
|
383
385
|
});
|
|
384
386
|
});
|
|
387
|
+
|
|
388
|
+
describe('getTooltipTitle', () => {
|
|
389
|
+
it('returns an empty value', () => {
|
|
390
|
+
expect(getTooltipTitle()).toBe('');
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('returns title for single series', () => {
|
|
394
|
+
expect(
|
|
395
|
+
getTooltipTitle({
|
|
396
|
+
seriesData: [{ value: ['Value 1', 99] }],
|
|
397
|
+
})
|
|
398
|
+
).toBe('Value 1');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('returns title for multiple series', () => {
|
|
402
|
+
expect(
|
|
403
|
+
getTooltipTitle({
|
|
404
|
+
seriesData: [{ value: ['Value 1', 99] }, { value: ['Value 2', 100] }],
|
|
405
|
+
})
|
|
406
|
+
).toBe('Value 1, Value 2');
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('returns title for multiple repeated series', () => {
|
|
410
|
+
expect(
|
|
411
|
+
getTooltipTitle({
|
|
412
|
+
seriesData: [{ value: ['Value 1', 99] }, { value: ['Value 1', 99] }],
|
|
413
|
+
})
|
|
414
|
+
).toBe('Value 1');
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('returns title for multiple series with an axis name', () => {
|
|
418
|
+
expect(
|
|
419
|
+
getTooltipTitle(
|
|
420
|
+
{
|
|
421
|
+
seriesData: [{ value: ['Value 1', 99] }, { value: ['Value 2', 100] }],
|
|
422
|
+
},
|
|
423
|
+
'Time'
|
|
424
|
+
)
|
|
425
|
+
).toBe('Value 1, Value 2 (Time)');
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
describe('getTooltipContent', () => {
|
|
430
|
+
it('returns an empty value', () => {
|
|
431
|
+
expect(getTooltipContent()).toEqual({});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('returns content for single series', () => {
|
|
435
|
+
expect(
|
|
436
|
+
getTooltipContent({
|
|
437
|
+
seriesData: [{ value: ['Value 1', 99], seriesName: 'Series 1', color: '#aaa' }],
|
|
438
|
+
})
|
|
439
|
+
).toEqual({ 'Series 1': { color: '', value: 99 } });
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('returns content for single series with an axis name', () => {
|
|
443
|
+
expect(
|
|
444
|
+
getTooltipContent(
|
|
445
|
+
{
|
|
446
|
+
seriesData: [{ value: ['Value 1', 99], seriesName: 'Series 1', color: '#aaa' }],
|
|
447
|
+
},
|
|
448
|
+
'Amount'
|
|
449
|
+
)
|
|
450
|
+
).toEqual({ Amount: { color: '', value: 99 } });
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('returns content for multiple series', () => {
|
|
454
|
+
expect(
|
|
455
|
+
getTooltipContent({
|
|
456
|
+
seriesData: [
|
|
457
|
+
{ value: ['Value 1', 99], seriesName: 'Series 1', color: '#aaa' },
|
|
458
|
+
{ value: ['Value 2', 99], seriesName: 'Series 2', color: '#bbb' },
|
|
459
|
+
],
|
|
460
|
+
})
|
|
461
|
+
).toEqual({
|
|
462
|
+
'Series 1': { color: '#aaa', value: 99 },
|
|
463
|
+
'Series 2': { color: '#bbb', value: 99 },
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('returns content for multiple repeated series', () => {
|
|
468
|
+
expect(
|
|
469
|
+
getTooltipContent({
|
|
470
|
+
seriesData: [
|
|
471
|
+
{ value: ['Value 1', 99], seriesName: 'Series 1', color: '#aaa' },
|
|
472
|
+
{ value: ['Value 1', 99], seriesName: 'Series 1', color: '#aaa' },
|
|
473
|
+
],
|
|
474
|
+
})
|
|
475
|
+
).toEqual({ 'Series 1': { color: '#aaa', value: 99 } });
|
|
476
|
+
});
|
|
477
|
+
});
|
|
385
478
|
});
|