Skip to content
Snippets Groups Projects
Commit 6a5a6247 authored by Laura Montemayor's avatar Laura Montemayor Committed by Clement Ho
Browse files

Adds button to download metrics data as csv

This MR adds a temporary button to be able to
download the Prometheus metrics from charts
to CSV format.
parent 24aec671
No related branches found
No related tags found
No related merge requests found
<script>
import { __ } from '~/locale';
import { GlLink } from '@gitlab/ui';
import { mapState } from 'vuex';
import { GlLink, GlButton } from '@gitlab/ui';
import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
import { debounceByAnimationFrame, roundOffFloat } from '~/lib/utils/common_utils';
Loading
Loading
@@ -15,6 +16,7 @@ let debouncedResize;
export default {
components: {
GlAreaChart,
GlButton,
GlChartSeriesLabel,
GlLink,
Icon,
Loading
Loading
@@ -67,6 +69,7 @@ export default {
};
},
computed: {
...mapState('monitoringDashboard', ['exportMetricsToCsvEnabled']),
chartData() {
// Transforms & supplements query data to render appropriate labels & styles
// Input: [{ queryAttributes1 }, { queryAttributes2 }]
Loading
Loading
@@ -176,6 +179,18 @@ export default {
yAxisLabel() {
return `${this.graphData.y_label}`;
},
csvText() {
const chartData = this.chartData[0].data;
const header = `timestamp,${this.graphData.y_label}\r\n`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
return chartData.reduce((csv, data) => {
const row = data.join(',');
return `${csv}${row}\r\n`;
}, header);
},
downloadLink() {
const data = new Blob([this.csvText], { type: 'text/plain' });
return window.URL.createObjectURL(data);
},
},
watch: {
containerWidth: 'onResize',
Loading
Loading
@@ -240,10 +255,20 @@ export default {
</script>
 
<template>
<div class="col-12 col-lg-6" :class="[showBorder ? 'p-2' : 'p-0']">
<div class="prometheus-graph" :class="{ 'prometheus-graph-embed w-100 p-3': showBorder }">
<div class="prometheus-graph col-12 col-lg-6" :class="[showBorder ? 'p-2' : 'p-0']">
<div :class="{ 'prometheus-graph-embed w-100 p-3': showBorder }">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
<gl-button
v-if="exportMetricsToCsvEnabled"
:href="downloadLink"
:title="__('Download CSV')"
:aria-label="__('Download CSV')"
style="margin-left: 200px;"
download="chart_metrics.csv"
>
{{ __('Download CSV') }}
</gl-button>
<div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
</div>
<gl-area-chart
Loading
Loading
Loading
Loading
@@ -13,6 +13,7 @@ export default (props = {}) => {
prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint,
multipleDashboardsEnabled: gon.features.environmentMetricsShowMultipleDashboards,
additionalPanelTypesEnabled: gon.features.environmentMetricsAdditionalPanelTypes,
exportMetricsToCsvEnabled: gon.features.exportMetricsToCsvEnabled,
});
}
 
Loading
Loading
Loading
Loading
@@ -37,11 +37,17 @@ export const setEndpoints = ({ commit }, endpoints) => {
 
export const setFeatureFlags = (
{ commit },
{ prometheusEndpointEnabled, multipleDashboardsEnabled, additionalPanelTypesEnabled },
{
prometheusEndpointEnabled,
multipleDashboardsEnabled,
additionalPanelTypesEnabled,
exportMetricsToCsvEnabled,
},
) => {
commit(types.SET_DASHBOARD_ENABLED, prometheusEndpointEnabled);
commit(types.SET_MULTIPLE_DASHBOARDS_ENABLED, multipleDashboardsEnabled);
commit(types.SET_ADDITIONAL_PANEL_TYPES_ENABLED, additionalPanelTypesEnabled);
commit(types.SET_EXPORT_METRICS_TO_CSV_ENABLED, exportMetricsToCsvEnabled);
};
 
export const setShowErrorBanner = ({ commit }, enabled) => {
Loading
Loading
Loading
Loading
@@ -17,3 +17,4 @@ export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
export const SET_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
export const SET_EXPORT_METRICS_TO_CSV_ENABLED = 'SET_EXPORT_METRICS_TO_CSV_ENABLED';
Loading
Loading
@@ -99,4 +99,7 @@ export default {
[types.SET_SHOW_ERROR_BANNER](state, enabled) {
state.showErrorBanner = enabled;
},
[types.SET_EXPORT_METRICS_TO_CSV_ENABLED](state, enabled) {
state.exportMetricsToCsvEnabled = enabled;
},
};
Loading
Loading
@@ -10,6 +10,7 @@ export default () => ({
useDashboardEndpoint: false,
multipleDashboardsEnabled: false,
additionalPanelTypesEnabled: false,
exportMetricsToCsvEnabled: false,
emptyState: 'gettingStarted',
showEmptyState: true,
showErrorBanner: true,
Loading
Loading
Loading
Loading
@@ -15,6 +15,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
push_frontend_feature_flag(:environment_metrics_show_multiple_dashboards)
push_frontend_feature_flag(:environment_metrics_additional_panel_types)
push_frontend_feature_flag(:prometheus_computed_alerts)
push_frontend_feature_flag(:export_metrics_to_csv_enabled)
end
 
def index
Loading
Loading
---
title: Export and download CSV from metrics charts
merge_request: 30760
author:
type: added
Loading
Loading
@@ -4007,6 +4007,9 @@ msgstr ""
msgid "Download"
msgstr ""
 
msgid "Download CSV"
msgstr ""
msgid "Download artifacts"
msgstr ""
 
Loading
Loading
import { shallowMount } from '@vue/test-utils';
import { createStore } from '~/monitoring/stores';
import { GlLink } from '@gitlab/ui';
import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
import Area from '~/monitoring/components/charts/area.vue';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import { TEST_HOST } from 'spec/test_constants';
import MonitoringMock, { deploymentData } from '../mock_data';
Loading
Loading
@@ -17,13 +17,14 @@ describe('Area component', () => {
let mockGraphData;
let areaChart;
let spriteSpy;
let store;
 
beforeEach(() => {
const store = createStore();
store = createStore();
store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
 
store.dispatch('monitoringDashboard/setFeatureFlags', { exportMetricsToCsvEnabled: true });
[mockGraphData] = store.state.monitoringDashboard.groups[0].metrics;
 
areaChart = shallowMount(Area, {
Loading
Loading
@@ -36,6 +37,7 @@ describe('Area component', () => {
slots: {
default: mockWidgets,
},
store,
});
 
spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake(
Loading
Loading
@@ -107,6 +109,16 @@ describe('Area component', () => {
});
});
 
describe('when exportMetricsToCsvEnabled is disabled', () => {
beforeEach(() => {
store.dispatch('monitoringDashboard/setFeatureFlags', { exportMetricsToCsvEnabled: false });
});
it('does not render the Download CSV button', () => {
expect(areaChart.contains('glbutton-stub')).toBe(false);
});
});
describe('methods', () => {
describe('formatTooltipText', () => {
const mockDate = deploymentData[0].created_at;
Loading
Loading
@@ -252,5 +264,23 @@ describe('Area component', () => {
expect(areaChart.vm.yAxisLabel).toBe('CPU');
});
});
describe('csvText', () => {
it('converts data from json to csv', () => {
const header = `timestamp,${mockGraphData.y_label}`;
const data = mockGraphData.queries[0].result[0].values;
const firstRow = `${data[0][0]},${data[0][1]}`;
expect(areaChart.vm.csvText).toMatch(`^${header}\r\n${firstRow}`);
});
});
describe('downloadLink', () => {
it('produces a link to download metrics as csv', () => {
const link = areaChart.vm.downloadLink;
expect(link).toContain('blob:');
});
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment