@gitlab/ui 71.4.0 → 71.6.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/charts/area/area.js +1 -0
  3. package/dist/components/charts/column/column.js +1 -0
  4. package/dist/components/charts/heatmap/heatmap.js +1 -0
  5. package/dist/components/charts/line/line.js +1 -0
  6. package/dist/components/charts/tooltip/tooltip.js +37 -3
  7. package/dist/components/experimental/duo/chat/duo_chat.js +2 -2
  8. package/dist/tokens/css/tokens.css +1 -1
  9. package/dist/tokens/css/tokens.dark.css +1 -1
  10. package/dist/tokens/js/tokens.dark.js +1 -1
  11. package/dist/tokens/js/tokens.js +1 -1
  12. package/dist/tokens/scss/_tokens.dark.scss +1 -1
  13. package/dist/tokens/scss/_tokens.scss +1 -1
  14. package/dist/utils/charts/config.js +51 -1
  15. package/package.json +2 -2
  16. package/src/components/charts/area/area.vue +2 -0
  17. package/src/components/charts/column/column.vue +2 -0
  18. package/src/components/charts/heatmap/heatmap.vue +2 -0
  19. package/src/components/charts/line/line.vue +2 -0
  20. package/src/components/charts/stacked_column/stacked_column.spec.js +3 -7
  21. package/src/components/charts/tooltip/tooltip.spec.js +94 -1
  22. package/src/components/charts/tooltip/tooltip.stories.js +0 -23
  23. package/src/components/charts/tooltip/tooltip.vue +58 -3
  24. package/src/components/experimental/duo/chat/duo_chat.md +3 -3
  25. package/src/components/experimental/duo/chat/duo_chat.spec.js +3 -2
  26. package/src/components/experimental/duo/chat/duo_chat.vue +3 -3
  27. package/src/components/experimental/experiment_badge/experiment_badge.md +1 -1
  28. package/src/components/experimental/experiment_badge/experiment_badge.stories.js +1 -2
  29. package/src/utils/charts/config.js +43 -0
  30. package/src/utils/charts/config.spec.js +93 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [71.6.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v71.5.0...v71.6.0) (2023-12-05)
2
+
3
+
4
+ ### Features
5
+
6
+ * **GlDuoChat:** switched /test to /tests command ([5045d5d](https://gitlab.com/gitlab-org/gitlab-ui/commit/5045d5daaaeaa10094ad75e4cf6b11da3a73bcd4))
7
+
8
+ # [71.5.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v71.4.0...v71.5.0) (2023-12-05)
9
+
10
+
11
+ ### Features
12
+
13
+ * **GlChartTooltip:** Add slot API for chart tooltip ([572b33e](https://gitlab.com/gitlab-org/gitlab-ui/commit/572b33e5151f2d0508bca07d895f3cd444f4af54))
14
+
1
15
  # [71.4.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v71.3.0...v71.4.0) (2023-12-04)
2
16
 
3
17
 
@@ -216,6 +216,7 @@ var script = {
216
216
  },
217
217
  methods: {
218
218
  defaultFormatTooltipText(params) {
219
+ // eslint-disable-next-line import/no-deprecated
219
220
  const {
220
221
  xLabels,
221
222
  tooltipContent
@@ -190,6 +190,7 @@ var script = {
190
190
  this.$emit('created', chart);
191
191
  },
192
192
  onLabelChange(params) {
193
+ // eslint-disable-next-line import/no-deprecated
193
194
  const {
194
195
  xLabels,
195
196
  tooltipContent
@@ -223,6 +223,7 @@ var script = {
223
223
  },
224
224
  methods: {
225
225
  defaultFormatTooltipText(params) {
226
+ // eslint-disable-next-line import/no-deprecated
226
227
  const {
227
228
  xLabels,
228
229
  tooltipContent
@@ -212,6 +212,7 @@ var script = {
212
212
  },
213
213
  methods: {
214
214
  defaultFormatTooltipText(params) {
215
+ // eslint-disable-next-line import/no-deprecated
215
216
  const {
216
217
  xLabels,
217
218
  tooltipContent
@@ -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([(_vm.$scopedSlots.title)?{key:"title",fn:function(){return [_vm._t("title")]},proxy:true}:null],null,true)},'gl-popover',_vm.$attrs,false),[_vm._v(" "),_vm._t("default")],2)],1):_vm._e()};
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 */
@@ -34,7 +34,7 @@ const slashCommands = [{
34
34
  shouldSubmit: true,
35
35
  description: 'Reset conversation, ignore the previous messages.'
36
36
  }, {
37
- name: '/test',
37
+ name: '/tests',
38
38
  shouldSubmit: false,
39
39
  description: 'Write tests for the code snippet.'
40
40
  }, {
@@ -119,7 +119,7 @@ var script = {
119
119
  default: () => i18n.CHAT_DEFAULT_PREDEFINED_PROMPTS
120
120
  },
121
121
  /**
122
- * URL to the experiment/beta help page. This is passed down to the `GlExperimentBadge` component. Refer that component for more information.
122
+ * URL to the help page. This is passed down to the `GlExperimentBadge` component.
123
123
  */
124
124
  badgeHelpPageUrl: {
125
125
  type: String,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Mon, 04 Dec 2023 16:16:35 GMT
3
+ * Generated on Tue, 05 Dec 2023 15:34:24 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Mon, 04 Dec 2023 16:16:35 GMT
3
+ * Generated on Tue, 05 Dec 2023 15:34:24 GMT
4
4
  */
5
5
 
6
6
  :root.gl-dark {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Mon, 04 Dec 2023 16:16:35 GMT
3
+ * Generated on Tue, 05 Dec 2023 15:34:24 GMT
4
4
  */
5
5
 
6
6
  export const DATA_VIZ_GREEN_50 = "#133a03";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Mon, 04 Dec 2023 16:16:35 GMT
3
+ * Generated on Tue, 05 Dec 2023 15:34:24 GMT
4
4
  */
5
5
 
6
6
  export const DATA_VIZ_GREEN_50 = "#ddfab7";
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Mon, 04 Dec 2023 16:16:35 GMT
3
+ // Generated on Tue, 05 Dec 2023 15:34:24 GMT
4
4
 
5
5
  $red-950: #fff4f3;
6
6
  $red-900: #fcf1ef;
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Mon, 04 Dec 2023 16:16:35 GMT
3
+ // Generated on Tue, 05 Dec 2023 15:34:24 GMT
4
4
 
5
5
  $gl-line-height-52: 3.25rem;
6
6
  $gl-line-height-44: 2.75rem;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "71.4.0",
3
+ "version": "71.6.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -127,7 +127,7 @@
127
127
  "cypress-real-events": "^1.11.0",
128
128
  "dompurify": "^3.0.0",
129
129
  "emoji-regex": "^10.0.0",
130
- "eslint": "8.54.0",
130
+ "eslint": "8.55.0",
131
131
  "eslint-import-resolver-jest": "3.0.2",
132
132
  "eslint-plugin-cypress": "2.15.1",
133
133
  "eslint-plugin-storybook": "0.6.15",
@@ -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 TooltipDefaultFormat from '~/components/shared_components/charts/tooltip_default_format.vue';
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(ChartTooltipStub);
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 = {}, ...options) => {
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 v-if="$scopedSlots.title" #title>
193
- <slot name="title"></slot>
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
- <slot></slot>
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
- :experiment-help-page-url="experimentHelpPageUrl"
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
- - `experimentHelpPageUrl`. The link to an external page explaining the meaning of an "experiment".
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.experimentHelpPageUrl = 'https://dev.null';
103
+ this.badgeHelpPageUrl = 'https://dev.null';
104
104
  }
105
105
  methods: {
106
106
  onChatHidden() {
@@ -542,8 +542,9 @@ describe('GlDuoChat', () => {
542
542
  it.each`
543
543
  prompt | expectedCommands
544
544
  ${'/'} | ${slashCommandsNames}
545
- ${'/t'} | ${slashCommandsOnly(['/test'])}
546
- ${'/tes'} | ${slashCommandsOnly(['/test'])}
545
+ ${'/t'} | ${slashCommandsOnly(['/tests'])}
546
+ ${'/tes'} | ${slashCommandsOnly(['/tests'])}
547
+ ${'/test'} | ${slashCommandsOnly(['/tests'])}
547
548
  ${'/e'} | ${slashCommandsOnly(['/explain'])}
548
549
  ${'/explai'} | ${slashCommandsOnly(['/explain'])}
549
550
  ${'/r'} | ${slashCommandsOnly(['/reset', '/refactor'])}
@@ -43,7 +43,7 @@ export const slashCommands = [
43
43
  description: 'Reset conversation, ignore the previous messages.',
44
44
  },
45
45
  {
46
- name: '/test',
46
+ name: '/tests',
47
47
  shouldSubmit: false,
48
48
  description: 'Write tests for the code snippet.',
49
49
  },
@@ -134,7 +134,7 @@ export default {
134
134
  default: () => i18n.CHAT_DEFAULT_PREDEFINED_PROMPTS,
135
135
  },
136
136
  /**
137
- * URL to the experiment/beta help page. This is passed down to the `GlExperimentBadge` component. Refer that component for more information.
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>
@@ -5,5 +5,5 @@ what experiment means.
5
5
  ## Usage
6
6
 
7
7
  ```html
8
- <gl-experiment-badge experiment-help-page-url="https://gitlab.com" popover-placement="bottom" />
8
+ <gl-experiment-badge help-page-url="https://gitlab.com" popover-placement="bottom" />
9
9
  ```
@@ -32,8 +32,7 @@ Default.args = generateProps();
32
32
  export const WithHelpPageUrl = Template.bind({});
33
33
  WithHelpPageUrl.args = {
34
34
  ...generateProps({
35
- experimentHelpPageUrl:
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
  });