Commit 0570f4b2 authored by Dhiraj Bodicherla's avatar Dhiraj Bodicherla

Update annotations configs

This MR adds the logic to add markLines
and markPoints for annotations within
GitLab and stop using the annotations
prop that was recently introduced in
GitLab UI. This makes it easy to interact
with annotation arrows and tooltips
parent a0d178ab
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,
export const parseAnnotations = annotations =>
annotations.reduce(
(acc, annotation) => {
acc.lines.push({
xAxis: annotation.starting_at,
lineStyle: {
color,
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