+
{
prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint,
multipleDashboardsEnabled: gon.features.environmentMetricsShowMultipleDashboards,
additionalPanelTypesEnabled: gon.features.environmentMetricsAdditionalPanelTypes,
+ exportMetricsToCsvEnabled: gon.features.exportMetricsToCsvEnabled,
});
}
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 0cbad179f179dd29fd3e29c20bfc079482d010e5..a9c491c7c6ce6275f7be0f4e18579e34e225c4a6 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -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) => {
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index 4b1aadbcf05310779bd4d56ddce99a163f80645b..9ec8214b16776db320bd44fc86850509c2a7d933 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -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';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index b19520d663835a3b67d89f9500649520793da267..a2dceb21fc0fef52da4bf1baa9a45fa5564110c0 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -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;
+ },
};
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index 440bdc951e013146940500254e0273db420b0718..a14a25e3a2095216b9cf5a27e8e30e4a02637d9f 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -10,6 +10,7 @@ export default () => ({
useDashboardEndpoint: false,
multipleDashboardsEnabled: false,
additionalPanelTypesEnabled: false,
+ exportMetricsToCsvEnabled: false,
emptyState: 'gettingStarted',
showEmptyState: true,
showErrorBanner: true,
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index b709ac85e399089174d907352b27eee123b311bc..4ae79186143c79a75fee8d355d220ee2a61df1f7 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -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
diff --git a/changelogs/unreleased/lm-download-csv-of-charts-from-metrics-dashboard.yml b/changelogs/unreleased/lm-download-csv-of-charts-from-metrics-dashboard.yml
new file mode 100644
index 0000000000000000000000000000000000000000..59f12fca1f1e11fc8ea0082e3844b0cd8fa075f4
--- /dev/null
+++ b/changelogs/unreleased/lm-download-csv-of-charts-from-metrics-dashboard.yml
@@ -0,0 +1,5 @@
+---
+title: Export and download CSV from metrics charts
+merge_request: 30760
+author:
+type: added
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9b41f32042a62e07e74cc790b4efd9cf43b4f408..50103c226d4da87a39ebe22d52f60c559dbea399 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4007,6 +4007,9 @@ msgstr ""
msgid "Download"
msgstr ""
+msgid "Download CSV"
+msgstr ""
+
msgid "Download artifacts"
msgstr ""
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
index d3a76f33679fa770546dc494ac97a61d1bd301c7..4541119dd2e72d9ddaf1c519f50456004252ff80 100644
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -1,9 +1,9 @@
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';
@@ -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, {
@@ -36,6 +37,7 @@ describe('Area component', () => {
slots: {
default: mockWidgets,
},
+ store,
});
spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake(
@@ -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;
@@ -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:');
+ });
+ });
});
});