Commit 1f05f317 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'update-annotations-parse-config' into 'master'

Update annotations configs to add arrows

See merge request gitlab-org/gitlab!29392
parents 59634d7c 0570f4b2
import { graphTypes, symbolSizes, colorValues } from '../../constants';
import { graphTypes, symbolSizes, colorValues, annotationsSymbolIcon } from '../../constants';
/**
* Annotations and deployments are decoration layers on
* top of the actual chart data. We use a scatter plot to
* display this information. Each chart has its coordinate
* system based on data and irresptive of the data, these
* system based on data and irrespective of the data, these
* decorations have to be placed in specific locations.
* For this reason, annotations have their own coordinate system,
*
* As of %12.9, only deployment icons, a type of annotations, need
* to be displayed on the chart.
*
* After https://gitlab.com/gitlab-org/gitlab/-/issues/211418,
* annotations and deployments will co-exist in the same
* series as they logically belong together. Annotations will be
* passed as markLine objects.
* Annotations and deployments co-exist in the same series as
* they logically belong together. Annotations are passed as
* markLines and markPoints while deployments are passed as
* data points with custom icons.
*/
/**
......@@ -49,38 +49,45 @@ export const annotationsYAxis = {
* has a value and the `ending_at` is null. Because annotations
* only supports lines the `ending_at` value does not exist yet.
*
*
* @param {Object} annotation object
* @returns {Object} markLine object
*/
export const parseAnnotations = ({ starting_at = '', color = colorValues.primaryColor }) => ({
xAxis: starting_at,
lineStyle: {
color,
},
});
export const parseAnnotations = annotations =>
annotations.reduce(
(acc, annotation) => {
acc.lines.push({
xAxis: annotation.starting_at,
lineStyle: {
color: colorValues.primaryColor,
},
});
acc.points.push({
name: 'annotations',
xAxis: annotation.starting_at,
yAxis: annotationsYAxisCoords.min,
tooltipData: {
title: annotation.starting_at,
content: annotation.description,
},
});
return acc;
},
{ lines: [], points: [] },
);
/**
* This method currently generates deployments and annotations
* but are not used in the chart. The method calling
* generateAnnotationsSeries will not pass annotations until
* https://gitlab.com/gitlab-org/gitlab/-/issues/211330 is
* implemented.
*
* This method is extracted out of the charts so that
* annotation lines can be easily supported in
* the future.
*
* In order to make hover work, hidden annotation data points
* are created along with the markLines. These data points have
* the necessart metadata that is used to display in the tooltip.
* This method generates a decorative series that has
* deployments as data points with custom icons and
* annotations as markLines and markPoints
*
* @param {Array} deployments deployments data
* @returns {Object} annotation series object
*/
export const generateAnnotationsSeries = ({ deployments = [], annotations = [] } = {}) => {
// deployment data points
const deploymentsData = deployments.map(deployment => {
const data = deployments.map(deployment => {
return {
name: 'deployments',
value: [deployment.createdAt, annotationsYAxisCoords.pos],
......@@ -98,31 +105,29 @@ export const generateAnnotationsSeries = ({ deployments = [], annotations = [] }
};
});
// annotation data points
const annotationsData = annotations.map(annotation => {
return {
name: 'annotations',
value: [annotation.starting_at, annotationsYAxisCoords.pos],
// style options
symbol: 'none',
// metadata that are accessible in `formatTooltipText` method
tooltipData: {
description: annotation.description,
},
};
});
const parsedAnnotations = parseAnnotations(annotations);
// annotation markLine option
// markLine option draws the annotations dotted line
const markLine = {
symbol: 'none',
silent: true,
data: annotations.map(parseAnnotations),
data: parsedAnnotations.lines,
};
// markPoints are the arrows under the annotations lines
const markPoint = {
symbol: annotationsSymbolIcon,
symbolSize: '8',
symbolOffset: [0, ' 60%'],
data: parsedAnnotations.points,
};
return {
name: 'annotations',
type: graphTypes.annotationsData,
yAxisIndex: 1, // annotationsYAxis index
data: [...deploymentsData, ...annotationsData],
data,
markLine,
markPoint,
};
};
......@@ -6,7 +6,7 @@ import dateFormat from 'dateformat';
import { s__, __ } from '~/locale';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import Icon from '~/vue_shared/components/icon.vue';
import { chartHeight, lineTypes, lineWidths, dateFormats, tooltipTypes } from '../../constants';
import { chartHeight, lineTypes, lineWidths, dateFormats } from '../../constants';
import { getYAxisOptions, getChartGrid, getTooltipFormatter } from './options';
import { annotationsYAxis, generateAnnotationsSeries } from './annotations';
import { makeDataSeries } from '~/helpers/monitor_helper';
......@@ -20,7 +20,6 @@ const events = {
};
export default {
tooltipTypes,
components: {
GlAreaChart,
GlLineChart,
......@@ -262,6 +261,21 @@ export default {
isTooltipOfType(tooltipType, defaultType) {
return tooltipType === defaultType;
},
/**
* This method is triggered when hovered over a single markPoint.
*
* The annotations title timestamp should match the data tooltip
* title.
*
* @params {Object} params markPoint object
* @returns {Object}
*/
formatAnnotationsTooltipText(params) {
return {
title: dateFormat(params.data?.tooltipData?.title, dateFormats.default),
content: params.data?.tooltipData?.content,
};
},
formatTooltipText(params) {
this.tooltip.title = dateFormat(params.value, dateFormats.default);
this.tooltip.content = [];
......@@ -270,15 +284,10 @@ export default {
if (dataPoint.value) {
const [, yVal] = dataPoint.value;
this.tooltip.type = dataPoint.name;
if (this.isTooltipOfType(this.tooltip.type, this.$options.tooltipTypes.deployments)) {
if (this.tooltip.type === 'deployments') {
const { data = {} } = dataPoint;
this.tooltip.sha = data?.tooltipData?.sha;
this.tooltip.commitUrl = data?.tooltipData?.commitUrl;
} else if (
this.isTooltipOfType(this.tooltip.type, this.$options.tooltipTypes.annotations)
) {
const { data } = dataPoint;
this.tooltip.content.push(data?.tooltipData?.description);
} else {
const { seriesName, color, dataIndex } = dataPoint;
......@@ -356,6 +365,7 @@ export default {
:data="chartData"
:option="chartOptions"
:format-tooltip-text="formatTooltipText"
:format-annotations-tooltip-text="formatAnnotationsTooltipText"
:thresholds="thresholds"
:width="width"
:height="height"
......@@ -364,7 +374,7 @@ export default {
@created="onChartCreated"
@updated="onChartUpdated"
>
<template v-if="isTooltipOfType(tooltip.type, this.$options.tooltipTypes.deployments)">
<template v-if="tooltip.type === 'deployments'">
<template slot="tooltipTitle">
{{ __('Deployed') }}
</template>
......@@ -373,16 +383,6 @@ export default {
<gl-link :href="tooltip.commitUrl">{{ tooltip.sha }}</gl-link>
</div>
</template>
<template v-else-if="isTooltipOfType(tooltip.type, this.$options.tooltipTypes.annotations)">
<template slot="tooltipTitle">
<div class="text-nowrap">
{{ tooltip.title }}
</div>
</template>
<div slot="tooltipContent" class="d-flex align-items-center">
{{ tooltip.content.join('\n') }}
</div>
</template>
<template v-else>
<template slot="tooltipTitle">
<div class="text-nowrap">
......
......@@ -120,10 +120,14 @@ export const NOT_IN_DB_PREFIX = 'NO_DB';
export const ENVIRONMENT_AVAILABLE_STATE = 'available';
/**
* Time series charts have different types of
* tooltip based on the hovered data point.
* As of %12.10, the svg icon library does not have an annotation
* arrow icon yet. In order to deliver annotations feature, the icon
* is hard coded until the icon is added. The below issue is
* to track the icon.
*
* https://gitlab.com/gitlab-org/gitlab-svgs/-/issues/118
*
* Once the icon is merged this can be removed.
* https://gitlab.com/gitlab-org/gitlab/-/issues/214540
*/
export const tooltipTypes = {
deployments: 'deployments',
annotations: 'annotations',
};
export const annotationsSymbolIcon = 'path://m5 229 5 8h-10z';
......@@ -54,6 +54,7 @@ describe('annotations spec', () => {
yAxisIndex: 1,
data: expect.any(Array),
markLine: expect.any(Object),
markPoint: expect.any(Object),
}),
);
......@@ -61,11 +62,12 @@ describe('annotations spec', () => {
expect(annotation).toEqual(expect.any(Object));
});
expect(annotations.data).toHaveLength(annotationsData.length);
expect(annotations.data).toHaveLength(0);
expect(annotations.markLine.data).toHaveLength(annotationsData.length);
expect(annotations.markPoint.data).toHaveLength(annotationsData.length);
});
it('when deploments and annotations data is passed', () => {
it('when deployments and annotations data is passed', () => {
const annotations = generateAnnotationsSeries({
deployments: deploymentData,
annotations: annotationsData,
......@@ -77,6 +79,7 @@ describe('annotations spec', () => {
yAxisIndex: 1,
data: expect.any(Array),
markLine: expect.any(Object),
markPoint: expect.any(Object),
}),
);
......@@ -84,7 +87,9 @@ describe('annotations spec', () => {
expect(annotation).toEqual(expect.any(Object));
});
expect(annotations.data).toHaveLength(deploymentData.length + annotationsData.length);
expect(annotations.data).toHaveLength(deploymentData.length);
expect(annotations.markLine.data).toHaveLength(annotationsData.length);
expect(annotations.markPoint.data).toHaveLength(annotationsData.length);
});
});
});
......@@ -13,7 +13,7 @@ import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper';
import { createStore } from '~/monitoring/stores';
import TimeSeries from '~/monitoring/components/charts/time_series.vue';
import * as types from '~/monitoring/stores/mutation_types';
import { deploymentData, mockProjectDir } from '../../mock_data';
import { deploymentData, mockProjectDir, annotationsData } from '../../mock_data';
import {
metricsDashboardPayload,
metricsDashboardViewModel,
......@@ -278,6 +278,33 @@ describe('Time series component', () => {
});
});
describe('formatAnnotationsTooltipText', () => {
const annotationsMetadata = {
name: 'annotations',
xAxis: annotationsData[0].from,
yAxis: 0,
tooltipData: {
title: '2020/02/19 10:01:41',
content: annotationsData[0].description,
},
};
const mockMarkPoint = {
componentType: 'markPoint',
name: 'annotations',
value: undefined,
data: annotationsMetadata,
};
it('formats tooltip title and sets tooltip content', () => {
const formattedTooltipData = timeSeriesChart.vm.formatAnnotationsTooltipText(
mockMarkPoint,
);
expect(formattedTooltipData.title).toBe('19 Feb 2020, 10:01AM');
expect(formattedTooltipData.content).toBe(annotationsMetadata.tooltipData.content);
});
});
describe('setSvg', () => {
const mockSvgName = 'mockSvgName';
......@@ -380,6 +407,8 @@ describe('Time series component', () => {
series: [
{
name: mockSeriesName,
type: 'line',
data: [],
},
],
},
......
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