summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/monitoring
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-04-20 18:38:24 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-04-20 18:38:24 +0000
commit983a0bba5d2a042c4a3bbb22432ec192c7501d82 (patch)
treeb153cd387c14ba23bd5a07514c7c01fddf6a78a0 /app/assets/javascripts/monitoring
parenta2bddee2cdb38673df0e004d5b32d9f77797de64 (diff)
downloadgitlab-ce-983a0bba5d2a042c4a3bbb22432ec192c7501d82.tar.gz
Add latest changes from gitlab-org/gitlab@12-10-stable-ee
Diffstat (limited to 'app/assets/javascripts/monitoring')
-rw-r--r--app/assets/javascripts/monitoring/components/charts/annotations.js97
-rw-r--r--app/assets/javascripts/monitoring/components/charts/empty_chart.vue6
-rw-r--r--app/assets/javascripts/monitoring/components/charts/options.js6
-rw-r--r--app/assets/javascripts/monitoring/components/charts/time_series.vue38
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue312
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue10
-rw-r--r--app/assets/javascripts/monitoring/constants.js28
-rw-r--r--app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql29
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js29
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js35
11 files changed, 321 insertions, 270 deletions
diff --git a/app/assets/javascripts/monitoring/components/charts/annotations.js b/app/assets/javascripts/monitoring/components/charts/annotations.js
index 947750b3721..418107c4126 100644
--- a/app/assets/javascripts/monitoring/components/charts/annotations.js
+++ b/app/assets/javascripts/monitoring/components/charts/annotations.js
@@ -1,20 +1,20 @@
-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.
*/
/**
@@ -45,42 +45,49 @@ export const annotationsYAxis = {
* Fetched list of annotations are parsed into a
* format the eCharts accepts to draw markLines
*
- * If Annotation is a single line, the `starting_at` property
- * has a value and the `ending_at` is null. Because annotations
- * only supports lines the `ending_at` value does not exist yet.
- *
+ * If Annotation is a single line, the `startingAt` property
+ * has a value and the `endingAt` is null. Because annotations
+ * only supports lines the `endingAt` 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.startingAt,
+ lineStyle: {
+ color: colorValues.primaryColor,
+ },
+ });
+
+ acc.points.push({
+ name: 'annotations',
+ xAxis: annotation.startingAt,
+ yAxis: annotationsYAxisCoords.min,
+ tooltipData: {
+ title: annotation.startingAt,
+ 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,
};
};
diff --git a/app/assets/javascripts/monitoring/components/charts/empty_chart.vue b/app/assets/javascripts/monitoring/components/charts/empty_chart.vue
index 5588d9ac060..e015ef32d8c 100644
--- a/app/assets/javascripts/monitoring/components/charts/empty_chart.vue
+++ b/app/assets/javascripts/monitoring/components/charts/empty_chart.vue
@@ -3,12 +3,6 @@ import chartEmptyStateIllustration from '@gitlab/svgs/dist/illustrations/chart-e
import { chartHeight } from '../../constants';
export default {
- props: {
- graphTitle: {
- type: String,
- required: true,
- },
- },
data() {
return {
height: chartHeight,
diff --git a/app/assets/javascripts/monitoring/components/charts/options.js b/app/assets/javascripts/monitoring/components/charts/options.js
index d9f49bd81f5..09b03774580 100644
--- a/app/assets/javascripts/monitoring/components/charts/options.js
+++ b/app/assets/javascripts/monitoring/components/charts/options.js
@@ -6,9 +6,8 @@ const yAxisBoundaryGap = [0.1, 0.1];
* Max string length of formatted axis tick
*/
const maxDataAxisTickLength = 8;
-
// Defaults
-const defaultFormat = SUPPORTED_FORMATS.number;
+const defaultFormat = SUPPORTED_FORMATS.engineering;
const defaultYAxisFormat = defaultFormat;
const defaultYAxisPrecision = 2;
@@ -26,8 +25,7 @@ const chartGridLeft = 75;
* @param {Object} param - Dashboard .yml definition options
*/
const getDataAxisOptions = ({ format, precision, name }) => {
- const formatter = getFormatter(format);
-
+ const formatter = getFormatter(format); // default to engineeringNotation, same as gitlab-ui
return {
name,
nameLocation: 'center', // same as gitlab-ui's default
diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue
index 9041b01088c..bf40e8f448e 100644
--- a/app/assets/javascripts/monitoring/components/charts/time_series.vue
+++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue
@@ -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">
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 4586ce70ad6..4d60b02d0df 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -8,7 +8,6 @@ import {
GlDropdownItem,
GlDropdownHeader,
GlDropdownDivider,
- GlFormGroup,
GlModal,
GlLoadingIcon,
GlSearchBoxByType,
@@ -19,6 +18,7 @@ import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
import { s__ } from '~/locale';
import createFlash from '~/flash';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
import { mergeUrlParams, redirectTo, updateHistory } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
import Icon from '~/vue_shared/components/icon.vue';
@@ -46,8 +46,8 @@ export default {
GlDropdownHeader,
GlDropdownDivider,
GlSearchBoxByType,
- GlFormGroup,
GlModal,
+ CustomMetricsFormFields,
DateTimePicker,
GraphGroup,
@@ -206,9 +206,6 @@ export default {
};
},
computed: {
- canAddMetrics() {
- return this.customMetricsAvailable && this.customMetricsPath.length;
- },
...mapState('monitoringDashboard', [
'dashboard',
'emptyState',
@@ -229,7 +226,11 @@ export default {
return !this.showEmptyState && this.rearrangePanelsAvailable;
},
addingMetricsAvailable() {
- return IS_EE && this.canAddMetrics && !this.showEmptyState;
+ return (
+ this.customMetricsAvailable &&
+ !this.showEmptyState &&
+ this.firstDashboard === this.selectedDashboard
+ );
},
hasHeaderButtons() {
return (
@@ -378,177 +379,164 @@ export default {
<div
v-if="showHeader"
ref="prometheusGraphsHeader"
- class="prometheus-graphs-header gl-p-3 pb-0 border-bottom bg-gray-light"
+ class="prometheus-graphs-header d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 border-bottom bg-gray-light"
>
- <div class="row">
- <gl-form-group
- :label="__('Dashboard')"
- label-size="sm"
- label-for="monitor-dashboards-dropdown"
- class="col-sm-12 col-md-6 col-lg-2"
- >
- <dashboards-dropdown
- id="monitor-dashboards-dropdown"
- data-qa-selector="dashboards_filter_dropdown"
- class="mb-0 d-flex"
- toggle-class="dropdown-menu-toggle"
- :default-branch="defaultBranch"
- :selected-dashboard="selectedDashboard"
- @selectDashboard="selectDashboard($event)"
- />
- </gl-form-group>
+ <div class="mb-2 pr-2 d-flex d-sm-block">
+ <dashboards-dropdown
+ id="monitor-dashboards-dropdown"
+ data-qa-selector="dashboards_filter_dropdown"
+ class="flex-grow-1"
+ toggle-class="dropdown-menu-toggle"
+ :default-branch="defaultBranch"
+ :selected-dashboard="selectedDashboard"
+ @selectDashboard="selectDashboard($event)"
+ />
+ </div>
- <gl-form-group
- :label="s__('Metrics|Environment')"
- label-size="sm"
- label-for="monitor-environments-dropdown"
- class="col-sm-6 col-md-6 col-lg-2"
+ <div class="mb-2 pr-2 d-flex d-sm-block">
+ <gl-dropdown
+ id="monitor-environments-dropdown"
+ ref="monitorEnvironmentsDropdown"
+ class="flex-grow-1"
+ data-qa-selector="environments_dropdown"
+ toggle-class="dropdown-menu-toggle"
+ menu-class="monitor-environment-dropdown-menu"
+ :text="currentEnvironmentName"
>
- <gl-dropdown
- id="monitor-environments-dropdown"
- ref="monitorEnvironmentsDropdown"
- data-qa-selector="environments_dropdown"
- class="mb-0 d-flex"
- toggle-class="dropdown-menu-toggle"
- menu-class="monitor-environment-dropdown-menu"
- :text="currentEnvironmentName"
- >
- <div class="d-flex flex-column overflow-hidden">
- <gl-dropdown-header class="monitor-environment-dropdown-header text-center">{{
- __('Environment')
- }}</gl-dropdown-header>
- <gl-dropdown-divider />
- <gl-search-box-by-type
- ref="monitorEnvironmentsDropdownSearch"
- class="m-2"
- @input="debouncedEnvironmentsSearch"
- />
- <gl-loading-icon
- v-if="environmentsLoading"
- ref="monitorEnvironmentsDropdownLoading"
- :inline="true"
- />
- <div v-else class="flex-fill overflow-auto">
- <gl-dropdown-item
- v-for="environment in filteredEnvironments"
- :key="environment.id"
- :active="environment.name === currentEnvironmentName"
- active-class="is-active"
- :href="environment.metrics_path"
- >{{ environment.name }}</gl-dropdown-item
- >
- </div>
- <div
- v-show="shouldShowEnvironmentsDropdownNoMatchedMsg"
- ref="monitorEnvironmentsDropdownMsg"
- class="text-secondary no-matches-message"
+ <div class="d-flex flex-column overflow-hidden">
+ <gl-dropdown-header class="monitor-environment-dropdown-header text-center">
+ {{ __('Environment') }}
+ </gl-dropdown-header>
+ <gl-dropdown-divider />
+ <gl-search-box-by-type
+ ref="monitorEnvironmentsDropdownSearch"
+ class="m-2"
+ @input="debouncedEnvironmentsSearch"
+ />
+ <gl-loading-icon
+ v-if="environmentsLoading"
+ ref="monitorEnvironmentsDropdownLoading"
+ :inline="true"
+ />
+ <div v-else class="flex-fill overflow-auto">
+ <gl-dropdown-item
+ v-for="environment in filteredEnvironments"
+ :key="environment.id"
+ :active="environment.name === currentEnvironmentName"
+ active-class="is-active"
+ :href="environment.metrics_path"
+ >{{ environment.name }}</gl-dropdown-item
>
- {{ __('No matching results') }}
- </div>
</div>
- </gl-dropdown>
- </gl-form-group>
+ <div
+ v-show="shouldShowEnvironmentsDropdownNoMatchedMsg"
+ ref="monitorEnvironmentsDropdownMsg"
+ class="text-secondary no-matches-message"
+ >
+ {{ __('No matching results') }}
+ </div>
+ </div>
+ </gl-dropdown>
+ </div>
- <gl-form-group
- :label="s__('Metrics|Show last')"
- label-size="sm"
- label-for="monitor-time-window-dropdown"
- class="col-sm-auto col-md-auto col-lg-auto"
+ <div class="mb-2 pr-2 d-flex d-sm-block">
+ <date-time-picker
+ ref="dateTimePicker"
+ class="flex-grow-1 show-last-dropdown"
data-qa-selector="show_last_dropdown"
+ :value="selectedTimeRange"
+ :options="timeRanges"
+ @input="onDateTimePickerInput"
+ @invalid="onDateTimePickerInvalid"
+ />
+ </div>
+
+ <div class="mb-2 pr-2 d-flex d-sm-block">
+ <gl-deprecated-button
+ ref="refreshDashboardBtn"
+ v-gl-tooltip
+ class="flex-grow-1"
+ variant="default"
+ :title="s__('Metrics|Refresh dashboard')"
+ @click="refreshDashboard"
>
- <date-time-picker
- ref="dateTimePicker"
- :value="selectedTimeRange"
- :options="timeRanges"
- @input="onDateTimePickerInput"
- @invalid="onDateTimePickerInvalid"
- />
- </gl-form-group>
+ <icon name="retry" />
+ </gl-deprecated-button>
+ </div>
+
+ <div class="flex-grow-1"></div>
- <gl-form-group class="col-sm-2 col-md-2 col-lg-1 refresh-dashboard-button">
+ <div class="d-sm-flex">
+ <div v-if="showRearrangePanelsBtn" class="mb-2 mr-2 d-flex">
<gl-deprecated-button
- ref="refreshDashboardBtn"
- v-gl-tooltip
+ :pressed="isRearrangingPanels"
variant="default"
- :title="s__('Metrics|Refresh dashboard')"
- @click="refreshDashboard"
+ class="flex-grow-1 js-rearrange-button"
+ @click="toggleRearrangingPanels"
>
- <icon name="retry" />
+ {{ __('Arrange charts') }}
</gl-deprecated-button>
- </gl-form-group>
-
- <gl-form-group
- v-if="hasHeaderButtons"
- label-for="prometheus-graphs-dropdown-buttons"
- class="dropdown-buttons col-md d-md-flex col-lg d-lg-flex align-items-end"
- >
- <div id="prometheus-graphs-dropdown-buttons">
- <gl-deprecated-button
- v-if="showRearrangePanelsBtn"
- :pressed="isRearrangingPanels"
- variant="default"
- class="mr-2 mt-1 js-rearrange-button"
- @click="toggleRearrangingPanels"
- >{{ __('Arrange charts') }}</gl-deprecated-button
- >
- <gl-deprecated-button
- v-if="addingMetricsAvailable"
- ref="addMetricBtn"
- v-gl-modal="$options.addMetric.modalId"
- variant="outline-success"
- data-qa-selector="add_metric_button"
- class="mr-2 mt-1"
- >{{ $options.addMetric.title }}</gl-deprecated-button
- >
- <gl-modal
- v-if="addingMetricsAvailable"
- ref="addMetricModal"
- :modal-id="$options.addMetric.modalId"
- :title="$options.addMetric.title"
- >
- <form ref="customMetricsForm" :action="customMetricsPath" method="post">
- <custom-metrics-form-fields
- :validate-query-path="validateQueryPath"
- form-operation="post"
- @formValidation="setFormValidity"
- />
- </form>
- <div slot="modal-footer">
- <gl-deprecated-button @click="hideAddMetricModal">{{
- __('Cancel')
- }}</gl-deprecated-button>
- <gl-deprecated-button
- ref="submitCustomMetricsFormBtn"
- v-track-event="getAddMetricTrackingOptions()"
- :disabled="!formIsValid"
- variant="success"
- @click="submitCustomMetricsForm"
- >{{ __('Save changes') }}</gl-deprecated-button
- >
- </div>
- </gl-modal>
+ </div>
+ <div v-if="addingMetricsAvailable" class="mb-2 mr-2 d-flex d-sm-block">
+ <gl-deprecated-button
+ ref="addMetricBtn"
+ v-gl-modal="$options.addMetric.modalId"
+ variant="outline-success"
+ data-qa-selector="add_metric_button"
+ class="flex-grow-1"
+ >
+ {{ $options.addMetric.title }}
+ </gl-deprecated-button>
+ <gl-modal
+ ref="addMetricModal"
+ :modal-id="$options.addMetric.modalId"
+ :title="$options.addMetric.title"
+ >
+ <form ref="customMetricsForm" :action="customMetricsPath" method="post">
+ <custom-metrics-form-fields
+ :validate-query-path="validateQueryPath"
+ form-operation="post"
+ @formValidation="setFormValidity"
+ />
+ </form>
+ <div slot="modal-footer">
+ <gl-deprecated-button @click="hideAddMetricModal">
+ {{ __('Cancel') }}
+ </gl-deprecated-button>
+ <gl-deprecated-button
+ ref="submitCustomMetricsFormBtn"
+ v-track-event="getAddMetricTrackingOptions()"
+ :disabled="!formIsValid"
+ variant="success"
+ @click="submitCustomMetricsForm"
+ >
+ {{ __('Save changes') }}
+ </gl-deprecated-button>
+ </div>
+ </gl-modal>
+ </div>
- <gl-deprecated-button
- v-if="selectedDashboard.can_edit"
- class="mt-1 js-edit-link"
- :href="selectedDashboard.project_blob_path"
- data-qa-selector="edit_dashboard_button"
- >{{ __('Edit dashboard') }}</gl-deprecated-button
- >
+ <div v-if="selectedDashboard.can_edit" class="mb-2 mr-2 d-flex d-sm-block">
+ <gl-deprecated-button
+ class="flex-grow-1 js-edit-link"
+ :href="selectedDashboard.project_blob_path"
+ data-qa-selector="edit_dashboard_button"
+ >
+ {{ __('Edit dashboard') }}
+ </gl-deprecated-button>
+ </div>
- <gl-deprecated-button
- v-if="externalDashboardUrl.length"
- class="mt-1 js-external-dashboard-link"
- variant="primary"
- :href="externalDashboardUrl"
- target="_blank"
- rel="noopener noreferrer"
- >
- {{ __('View full dashboard') }}
- <icon name="external-link" />
- </gl-deprecated-button>
- </div>
- </gl-form-group>
+ <div v-if="externalDashboardUrl.length" class="mb-2 mr-2 d-flex d-sm-block">
+ <gl-deprecated-button
+ class="flex-grow-1 js-external-dashboard-link"
+ variant="primary"
+ :href="externalDashboardUrl"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {{ __('View full dashboard') }} <icon name="external-link" />
+ </gl-deprecated-button>
+ </div>
</div>
</div>
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index 676fc0cca64..2beae0d9540 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -4,6 +4,7 @@ import { pickBy } from 'lodash';
import invalidUrl from '~/lib/utils/invalid_url';
import {
GlResizeObserverDirective,
+ GlIcon,
GlLoadingIcon,
GlDropdown,
GlDropdownItem,
@@ -13,7 +14,6 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import { __, n__ } from '~/locale';
-import Icon from '~/vue_shared/components/icon.vue';
import MonitorTimeSeriesChart from './charts/time_series.vue';
import MonitorAnomalyChart from './charts/anomaly.vue';
import MonitorSingleStatChart from './charts/single_stat.vue';
@@ -37,7 +37,7 @@ export default {
MonitorHeatmapChart,
MonitorStackedColumnChart,
MonitorEmptyChart,
- Icon,
+ GlIcon,
GlLoadingIcon,
GlTooltip,
GlDropdown,
@@ -227,7 +227,7 @@ export default {
</div>
<div
v-if="isContextualMenuShown"
- class="js-graph-widgets"
+ ref="contextualMenu"
data-qa-selector="prometheus_graph_widgets"
>
<div class="d-flex align-items-center">
@@ -240,7 +240,7 @@ export default {
:title="__('More actions')"
>
<template slot="button-content">
- <icon name="ellipsis_v" class="text-secondary" />
+ <gl-icon name="ellipsis_v" class="text-secondary" />
</template>
<gl-dropdown-item
v-if="editCustomMetricLink"
@@ -319,6 +319,6 @@ export default {
:group-id="groupId"
@datazoom="onDatazoom"
/>
- <monitor-empty-chart v-else :graph-title="title" v-bind="$attrs" v-on="$listeners" />
+ <monitor-empty-chart v-else v-bind="$attrs" v-on="$listeners" />
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
index 8d821c27099..0b393f19789 100644
--- a/app/assets/javascripts/monitoring/constants.js
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -120,10 +120,26 @@ 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';
+
+/**
+ * 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';
diff --git a/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql b/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql
index 2fd698eadf9..27b49860b8a 100644
--- a/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql
+++ b/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql
@@ -1,12 +1,25 @@
-query getAnnotations($projectPath: ID!) {
- environment(name: $environmentName) {
- metricDashboard(id: $dashboardId) {
- annotations: nodes {
+query getAnnotations(
+ $projectPath: ID!
+ $environmentName: String
+ $dashboardPath: String!
+ $startingFrom: Time!
+) {
+ project(fullPath: $projectPath) {
+ environments(name: $environmentName) {
+ nodes {
id
- description
- starting_at
- ending_at
- panelId
+ name
+ metricsDashboard(path: $dashboardPath) {
+ annotations(from: $startingFrom) {
+ nodes {
+ id
+ description
+ startingAt
+ endingAt
+ panelId
+ }
+ }
+ }
}
}
}
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 5b2bd1f1493..f04f775761c 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -3,7 +3,12 @@ import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
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 getEnvironments from '../queries/getEnvironments.query.graphql';
import getAnnotations from '../queries/getAnnotations.query.graphql';
@@ -15,7 +20,11 @@ import {
} from '../../lib/utils/common_utils';
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) {
const { start, end } = convertToFixedRange(timeRange);
@@ -90,7 +99,7 @@ export const fetchData = ({ dispatch }) => {
* ready after the BE piece is implemented.
* https://gitlab.com/gitlab-org/gitlab/-/issues/211330
*/
- if (isFeatureFlagEnabled('metrics_dashboard_annotations')) {
+ if (isFeatureFlagEnabled('metricsDashboardAnnotations')) {
dispatch('fetchAnnotations');
}
};
@@ -283,18 +292,21 @@ export const receiveEnvironmentsDataFailure = ({ commit }) => {
};
export const fetchAnnotations = ({ state, dispatch }) => {
- dispatch('requestAnnotations');
-
+ const { start } = convertToFixedRange(state.timeRange);
+ const dashboardPath =
+ state.currentDashboard === '' ? DEFAULT_DASHBOARD_PATH : state.currentDashboard;
return gqClient
.mutate({
mutation: getAnnotations,
variables: {
projectPath: removeLeadingSlash(state.projectPath),
- dashboardId: state.currentDashboard,
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 => {
if (!annotations) {
createFlash(s__('Metrics|There was an error fetching annotations. Please try again.'));
@@ -309,9 +321,6 @@ export const fetchAnnotations = ({ state, dispatch }) => {
});
};
-// While this commit does not update the state it will
-// eventually be useful to show a loading state
-export const requestAnnotations = ({ commit }) => commit(types.REQUEST_ANNOTATIONS);
export const receiveAnnotationsSuccess = ({ commit }, data) =>
commit(types.RECEIVE_ANNOTATIONS_SUCCESS, data);
export const receiveAnnotationsFailure = ({ commit }) => commit(types.RECEIVE_ANNOTATIONS_FAILURE);
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index 2f9955da1b1..27a9a67edaa 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -4,7 +4,6 @@ export const RECEIVE_METRICS_DASHBOARD_SUCCESS = 'RECEIVE_METRICS_DASHBOARD_SUCC
export const RECEIVE_METRICS_DASHBOARD_FAILURE = 'RECEIVE_METRICS_DASHBOARD_FAILURE';
// Annotations
-export const REQUEST_ANNOTATIONS = 'REQUEST_ANNOTATIONS';
export const RECEIVE_ANNOTATIONS_SUCCESS = 'RECEIVE_ANNOTATIONS_SUCCESS';
export const RECEIVE_ANNOTATIONS_FAILURE = 'RECEIVE_ANNOTATIONS_FAILURE';
diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js
index a212e9be703..9f06d18c46f 100644
--- a/app/assets/javascripts/monitoring/stores/utils.js
+++ b/app/assets/javascripts/monitoring/stores/utils.js
@@ -58,6 +58,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
*
* This function difers from other in that is maps all
@@ -95,15 +120,19 @@ const mapXAxisToViewModel = ({ name = '' }) => ({ name });
/**
* Maps Y-axis view model
*
- * Defaults to a 2 digit precision and `number` format. It only allows
+ * Defaults to a 2 digit precision and `engineering` format. It only allows
* formats in the SUPPORTED_FORMATS array.
*
* @param {Object} axis
*/
-const mapYAxisToViewModel = ({ name = '', format = SUPPORTED_FORMATS.number, precision = 2 }) => {
+const mapYAxisToViewModel = ({
+ name = '',
+ format = SUPPORTED_FORMATS.engineering,
+ precision = 2,
+}) => {
return {
name,
- format: SUPPORTED_FORMATS[format] || SUPPORTED_FORMATS.number,
+ format: SUPPORTED_FORMATS[format] || SUPPORTED_FORMATS.engineering,
precision,
};
};