Commit 4ca4b22e authored by Nathan Friend's avatar Nathan Friend

Merge branch 'update-annotations-graphql-query' into 'master'

Update annotations graphql query

See merge request gitlab-org/gitlab!29676
parents f62f3979 4672e98f
...@@ -45,9 +45,9 @@ export const annotationsYAxis = { ...@@ -45,9 +45,9 @@ export const annotationsYAxis = {
* Fetched list of annotations are parsed into a * Fetched list of annotations are parsed into a
* format the eCharts accepts to draw markLines * format the eCharts accepts to draw markLines
* *
* If Annotation is a single line, the `starting_at` property * If Annotation is a single line, the `startingAt` property
* has a value and the `ending_at` is null. Because annotations * has a value and the `endingAt` is null. Because annotations
* only supports lines the `ending_at` value does not exist yet. * only supports lines the `endingAt` value does not exist yet.
* *
* @param {Object} annotation object * @param {Object} annotation object
* @returns {Object} markLine object * @returns {Object} markLine object
...@@ -56,7 +56,7 @@ export const parseAnnotations = annotations => ...@@ -56,7 +56,7 @@ export const parseAnnotations = annotations =>
annotations.reduce( annotations.reduce(
(acc, annotation) => { (acc, annotation) => {
acc.lines.push({ acc.lines.push({
xAxis: annotation.starting_at, xAxis: annotation.startingAt,
lineStyle: { lineStyle: {
color: colorValues.primaryColor, color: colorValues.primaryColor,
}, },
...@@ -64,10 +64,10 @@ export const parseAnnotations = annotations => ...@@ -64,10 +64,10 @@ export const parseAnnotations = annotations =>
acc.points.push({ acc.points.push({
name: 'annotations', name: 'annotations',
xAxis: annotation.starting_at, xAxis: annotation.startingAt,
yAxis: annotationsYAxisCoords.min, yAxis: annotationsYAxisCoords.min,
tooltipData: { tooltipData: {
title: annotation.starting_at, title: annotation.startingAt,
content: annotation.description, content: annotation.description,
}, },
}); });
......
...@@ -131,3 +131,15 @@ export const ENVIRONMENT_AVAILABLE_STATE = 'available'; ...@@ -131,3 +131,15 @@ export const ENVIRONMENT_AVAILABLE_STATE = 'available';
* https://gitlab.com/gitlab-org/gitlab/-/issues/214540 * https://gitlab.com/gitlab-org/gitlab/-/issues/214540
*/ */
export const annotationsSymbolIcon = 'path://m5 229 5 8h-10z'; export const annotationsSymbolIcon = 'path://m5 229 5 8h-10z';
/**
* As of %12.10, dashboard path is required to create annotation.
* The FE gets the dashboard name from the URL params. It is not
* ideal to store the path this way but there is no other way to
* get this path unless annotations fetch is delayed. This could
* potentially be removed and have the backend send this to the FE.
*
* This technical debt is being tracked here
* https://gitlab.com/gitlab-org/gitlab/-/issues/214671
*/
export const DEFAULT_DASHBOARD_PATH = 'config/prometheus/common_metrics.yml';
query getAnnotations($projectPath: ID!) { query getAnnotations(
environment(name: $environmentName) { $projectPath: ID!
metricDashboard(id: $dashboardId) { $environmentName: String
annotations: nodes { $dashboardPath: String!
$startingFrom: Time!
) {
project(fullPath: $projectPath) {
environments(name: $environmentName) {
nodes {
id
name
metricsDashboard(path: $dashboardPath) {
annotations(from: $startingFrom) {
nodes {
id id
description description
starting_at startingAt
ending_at endingAt
panelId panelId
} }
} }
} }
}
}
}
} }
...@@ -3,7 +3,12 @@ import * as types from './mutation_types'; ...@@ -3,7 +3,12 @@ import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { convertToFixedRange } from '~/lib/utils/datetime_range'; import { convertToFixedRange } from '~/lib/utils/datetime_range';
import { gqClient, parseEnvironmentsResponse, removeLeadingSlash } from './utils'; import {
gqClient,
parseEnvironmentsResponse,
parseAnnotationsResponse,
removeLeadingSlash,
} from './utils';
import trackDashboardLoad from '../monitoring_tracking_helper'; import trackDashboardLoad from '../monitoring_tracking_helper';
import getEnvironments from '../queries/getEnvironments.query.graphql'; import getEnvironments from '../queries/getEnvironments.query.graphql';
import getAnnotations from '../queries/getAnnotations.query.graphql'; import getAnnotations from '../queries/getAnnotations.query.graphql';
...@@ -15,7 +20,11 @@ import { ...@@ -15,7 +20,11 @@ import {
} from '../../lib/utils/common_utils'; } from '../../lib/utils/common_utils';
import { s__, sprintf } from '../../locale'; import { s__, sprintf } from '../../locale';
import { PROMETHEUS_TIMEOUT, ENVIRONMENT_AVAILABLE_STATE } from '../constants'; import {
PROMETHEUS_TIMEOUT,
ENVIRONMENT_AVAILABLE_STATE,
DEFAULT_DASHBOARD_PATH,
} from '../constants';
function prometheusMetricQueryParams(timeRange) { function prometheusMetricQueryParams(timeRange) {
const { start, end } = convertToFixedRange(timeRange); const { start, end } = convertToFixedRange(timeRange);
...@@ -283,16 +292,21 @@ export const receiveEnvironmentsDataFailure = ({ commit }) => { ...@@ -283,16 +292,21 @@ export const receiveEnvironmentsDataFailure = ({ commit }) => {
}; };
export const fetchAnnotations = ({ state, dispatch }) => { export const fetchAnnotations = ({ state, dispatch }) => {
const { start } = convertToFixedRange(state.timeRange);
const dashboardPath =
state.currentDashboard === '' ? DEFAULT_DASHBOARD_PATH : state.currentDashboard;
return gqClient return gqClient
.mutate({ .mutate({
mutation: getAnnotations, mutation: getAnnotations,
variables: { variables: {
projectPath: removeLeadingSlash(state.projectPath), projectPath: removeLeadingSlash(state.projectPath),
dashboardId: state.currentDashboard,
environmentName: state.currentEnvironmentName, environmentName: state.currentEnvironmentName,
dashboardPath,
startingFrom: start,
}, },
}) })
.then(resp => resp.data?.project?.environment?.metricDashboard?.annotations) .then(resp => resp.data?.project?.environments?.nodes?.[0].metricsDashboard?.annotations.nodes)
.then(parseAnnotationsResponse)
.then(annotations => { .then(annotations => {
if (!annotations) { if (!annotations) {
createFlash(s__('Metrics|There was an error fetching annotations. Please try again.')); createFlash(s__('Metrics|There was an error fetching annotations. Please try again.'));
......
...@@ -57,6 +57,31 @@ export const parseEnvironmentsResponse = (response = [], projectPath) => ...@@ -57,6 +57,31 @@ export const parseEnvironmentsResponse = (response = [], projectPath) =>
}; };
}); });
/**
* Annotation API returns time in UTC. This method
* converts time to local time.
*
* startingAt always exists but endingAt does not.
* If endingAt does not exist, a threshold line is
* drawn.
*
* If endingAt exists, a threshold range is drawn.
* But this is not supported as of %12.10
*
* @param {Array} response annotations response
* @returns {Array} parsed responses
*/
export const parseAnnotationsResponse = response => {
if (!response) {
return [];
}
return response.map(annotation => ({
...annotation,
startingAt: new Date(annotation.startingAt),
endingAt: annotation.endingAt ? new Date(annotation.endingAt) : null,
}));
};
/** /**
* Maps metrics to its view model * Maps metrics to its view model
* *
......
...@@ -247,23 +247,23 @@ export const deploymentData = [ ...@@ -247,23 +247,23 @@ export const deploymentData = [
export const annotationsData = [ export const annotationsData = [
{ {
id: 'gid://gitlab/Metrics::Dashboard::Annotation/1', id: 'gid://gitlab/Metrics::Dashboard::Annotation/1',
starting_at: '2020-04-01T12:51:58.373Z', startingAt: '2020-04-12 12:51:53 UTC',
ending_at: null, endingAt: null,
panelId: null, panelId: null,
description: 'This is a test annotation', description: 'This is a test annotation',
}, },
{ {
id: 'gid://gitlab/Metrics::Dashboard::Annotation/2', id: 'gid://gitlab/Metrics::Dashboard::Annotation/2',
description: 'test annotation 2', description: 'test annotation 2',
starting_at: '2020-04-02T12:51:58.373Z', startingAt: '2020-04-13 12:51:53 UTC',
ending_at: null, endingAt: null,
panelId: null, panelId: null,
}, },
{ {
id: 'gid://gitlab/Metrics::Dashboard::Annotation/3', id: 'gid://gitlab/Metrics::Dashboard::Annotation/3',
description: 'test annotation 3', description: 'test annotation 3',
starting_at: '2020-04-04T12:51:58.373Z', startingAt: '2020-04-16 12:51:53 UTC',
ending_at: null, endingAt: null,
panelId: null, panelId: null,
}, },
]; ];
......
...@@ -23,7 +23,11 @@ import { ...@@ -23,7 +23,11 @@ import {
setGettingStartedEmptyState, setGettingStartedEmptyState,
duplicateSystemDashboard, duplicateSystemDashboard,
} from '~/monitoring/stores/actions'; } from '~/monitoring/stores/actions';
import { gqClient, parseEnvironmentsResponse } from '~/monitoring/stores/utils'; import {
gqClient,
parseEnvironmentsResponse,
parseAnnotationsResponse,
} from '~/monitoring/stores/utils';
import getEnvironments from '~/monitoring/queries/getEnvironments.query.graphql'; import getEnvironments from '~/monitoring/queries/getEnvironments.query.graphql';
import getAnnotations from '~/monitoring/queries/getAnnotations.query.graphql'; import getAnnotations from '~/monitoring/queries/getAnnotations.query.graphql';
import storeState from '~/monitoring/stores/state'; import storeState from '~/monitoring/stores/state';
...@@ -224,6 +228,10 @@ describe('Monitoring store actions', () => { ...@@ -224,6 +228,10 @@ describe('Monitoring store actions', () => {
describe('fetchAnnotations', () => { describe('fetchAnnotations', () => {
const { state } = store; const { state } = store;
state.timeRange = {
start: '2020-04-15T12:54:32.137Z',
end: '2020-08-15T12:54:32.137Z',
};
state.projectPath = 'gitlab-org/gitlab-test'; state.projectPath = 'gitlab-org/gitlab-test';
state.currentEnvironmentName = 'production'; state.currentEnvironmentName = 'production';
state.currentDashboard = '.gitlab/dashboards/custom_dashboard.yml'; state.currentDashboard = '.gitlab/dashboards/custom_dashboard.yml';
...@@ -239,19 +247,27 @@ describe('Monitoring store actions', () => { ...@@ -239,19 +247,27 @@ describe('Monitoring store actions', () => {
variables: { variables: {
projectPath: state.projectPath, projectPath: state.projectPath,
environmentName: state.currentEnvironmentName, environmentName: state.currentEnvironmentName,
dashboardId: state.currentDashboard, dashboardPath: state.currentDashboard,
startingFrom: state.timeRange.start,
}, },
}; };
const parsedResponse = parseAnnotationsResponse(annotationsData);
mockMutate.mockResolvedValue({ mockMutate.mockResolvedValue({
data: { data: {
project: { project: {
environment: { environments: {
metricDashboard: { nodes: [
annotations: annotationsData, {
metricsDashboard: {
annotations: {
nodes: parsedResponse,
}, },
}, },
}, },
],
},
},
}, },
}); });
...@@ -260,7 +276,7 @@ describe('Monitoring store actions', () => { ...@@ -260,7 +276,7 @@ describe('Monitoring store actions', () => {
null, null,
state, state,
[], [],
[{ type: 'receiveAnnotationsSuccess', payload: annotationsData }], [{ type: 'receiveAnnotationsSuccess', payload: parsedResponse }],
() => { () => {
expect(mockMutate).toHaveBeenCalledWith(mutationVariables); expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
}, },
...@@ -274,7 +290,8 @@ describe('Monitoring store actions', () => { ...@@ -274,7 +290,8 @@ describe('Monitoring store actions', () => {
variables: { variables: {
projectPath: state.projectPath, projectPath: state.projectPath,
environmentName: state.currentEnvironmentName, environmentName: state.currentEnvironmentName,
dashboardId: state.currentDashboard, dashboardPath: state.currentDashboard,
startingFrom: state.timeRange.start,
}, },
}; };
......
...@@ -2,9 +2,11 @@ import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format'; ...@@ -2,9 +2,11 @@ import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format';
import { import {
uniqMetricsId, uniqMetricsId,
parseEnvironmentsResponse, parseEnvironmentsResponse,
parseAnnotationsResponse,
removeLeadingSlash, removeLeadingSlash,
mapToDashboardViewModel, mapToDashboardViewModel,
} from '~/monitoring/stores/utils'; } from '~/monitoring/stores/utils';
import { annotationsData } from '../mock_data';
import { NOT_IN_DB_PREFIX } from '~/monitoring/constants'; import { NOT_IN_DB_PREFIX } from '~/monitoring/constants';
const projectPath = 'gitlab-org/gitlab-test'; const projectPath = 'gitlab-org/gitlab-test';
...@@ -376,6 +378,27 @@ describe('parseEnvironmentsResponse', () => { ...@@ -376,6 +378,27 @@ describe('parseEnvironmentsResponse', () => {
}); });
}); });
describe('parseAnnotationsResponse', () => {
const parsedAnnotationResponse = [
{
description: 'This is a test annotation',
endingAt: null,
id: 'gid://gitlab/Metrics::Dashboard::Annotation/1',
panelId: null,
startingAt: new Date('2020-04-12T12:51:53.000Z'),
},
];
it.each`
case | input | expected
${'Returns empty array for null input'} | ${null} | ${[]}
${'Returns empty array for undefined input'} | ${undefined} | ${[]}
${'Returns empty array for empty input'} | ${[]} | ${[]}
${'Returns parsed responses for annotations data'} | ${[annotationsData[0]]} | ${parsedAnnotationResponse}
`('$case', ({ input, expected }) => {
expect(parseAnnotationsResponse(input)).toEqual(expected);
});
});
describe('removeLeadingSlash', () => { describe('removeLeadingSlash', () => {
[ [
{ input: null, output: '' }, { input: null, output: '' },
......
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