Adds download CSV functionality to dropdown in metrics
This MR adds the functionality to download metrics data as CSV. It also removes the exportMetricsToCsvEnabled feature flag which was used before the dropdown was implemented to display a button.
This commit is contained in:
parent
0b43c1027f
commit
9e75711aea
12 changed files with 83 additions and 73 deletions
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import { __ } from '~/locale';
|
||||
import { mapState } from 'vuex';
|
||||
import { GlLink, GlButton } from '@gitlab/ui';
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
|
||||
import dateFormat from 'dateformat';
|
||||
import { debounceByAnimationFrame, roundOffFloat } from '~/lib/utils/common_utils';
|
||||
|
@ -16,7 +15,6 @@ let debouncedResize;
|
|||
export default {
|
||||
components: {
|
||||
GlAreaChart,
|
||||
GlButton,
|
||||
GlChartSeriesLabel,
|
||||
GlLink,
|
||||
Icon,
|
||||
|
@ -69,7 +67,6 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState('monitoringDashboard', ['exportMetricsToCsvEnabled']),
|
||||
chartData() {
|
||||
// Transforms & supplements query data to render appropriate labels & styles
|
||||
// Input: [{ queryAttributes1 }, { queryAttributes2 }]
|
||||
|
@ -179,18 +176,6 @@ 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',
|
||||
|
@ -259,16 +244,6 @@ export default {
|
|||
<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
|
||||
|
|
|
@ -235,6 +235,19 @@ export default {
|
|||
chart.metrics.some(metric => this.metricsWithData.includes(metric.metric_id)),
|
||||
);
|
||||
},
|
||||
csvText(graphData) {
|
||||
const chartData = graphData.queries[0].result[0].values;
|
||||
const yLabel = graphData.y_label;
|
||||
const header = `timestamp,${yLabel}\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);
|
||||
},
|
||||
downloadCsv(graphData) {
|
||||
const data = new Blob([this.csvText(graphData)], { type: 'text/plain' });
|
||||
return window.URL.createObjectURL(data);
|
||||
},
|
||||
// TODO: BEGIN, Duplicated code with panel_type until feature flag is removed
|
||||
// Issue number: https://gitlab.com/gitlab-org/gitlab-ce/issues/63845
|
||||
getGraphAlerts(queries) {
|
||||
|
@ -448,7 +461,6 @@ export default {
|
|||
@setAlerts="setAlerts"
|
||||
/>
|
||||
<gl-dropdown
|
||||
v-if="alertWidgetAvailable"
|
||||
v-gl-tooltip
|
||||
class="mx-2"
|
||||
toggle-class="btn btn-transparent border-0"
|
||||
|
@ -459,6 +471,9 @@ export default {
|
|||
<template slot="button-content">
|
||||
<icon name="ellipsis_v" class="text-secondary" />
|
||||
</template>
|
||||
<gl-dropdown-item :href="downloadCsv(graphData)" download="chart_metrics.csv">
|
||||
{{ __('Download CSV') }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item
|
||||
v-if="alertWidgetAvailable"
|
||||
v-gl-modal="`alert-modal-${index}-${graphIndex}`"
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import _ from 'underscore';
|
||||
import { GlDropdown, GlDropdownItem, GlModal, GlModalDirective } from '@gitlab/ui';
|
||||
import {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlModal,
|
||||
GlModalDirective,
|
||||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import MonitorAreaChart from './charts/area.vue';
|
||||
import MonitorSingleStatChart from './charts/single_stat.vue';
|
||||
import MonitorEmptyChart from './charts/empty_chart.vue';
|
||||
|
@ -11,12 +18,14 @@ export default {
|
|||
MonitorAreaChart,
|
||||
MonitorSingleStatChart,
|
||||
MonitorEmptyChart,
|
||||
Icon,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlModal,
|
||||
},
|
||||
directives: {
|
||||
GlModal: GlModalDirective,
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
graphData: {
|
||||
|
@ -41,6 +50,19 @@ export default {
|
|||
graphDataHasMetrics() {
|
||||
return this.graphData.queries[0].result.length > 0;
|
||||
},
|
||||
csvText() {
|
||||
const chartData = this.graphData.queries[0].result[0].values;
|
||||
const yLabel = this.graphData.y_label;
|
||||
const header = `timestamp,${yLabel}\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);
|
||||
},
|
||||
downloadCsv() {
|
||||
const data = new Blob([this.csvText], { type: 'text/plain' });
|
||||
return window.URL.createObjectURL(data);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getGraphAlerts(queries) {
|
||||
|
@ -81,7 +103,6 @@ export default {
|
|||
@setAlerts="setAlerts"
|
||||
/>
|
||||
<gl-dropdown
|
||||
v-if="alertWidgetAvailable"
|
||||
v-gl-tooltip
|
||||
class="mx-2"
|
||||
toggle-class="btn btn-transparent border-0"
|
||||
|
@ -92,6 +113,9 @@ export default {
|
|||
<template slot="button-content">
|
||||
<icon name="ellipsis_v" class="text-secondary" />
|
||||
</template>
|
||||
<gl-dropdown-item :href="downloadCsv" download="chart_metrics.csv">
|
||||
{{ __('Download CSV') }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item v-if="alertWidgetAvailable" v-gl-modal="`alert-modal-${index}`">
|
||||
{{ __('Alerts') }}
|
||||
</gl-dropdown-item>
|
||||
|
|
|
@ -13,7 +13,6 @@ export default (props = {}) => {
|
|||
prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint,
|
||||
multipleDashboardsEnabled: gon.features.environmentMetricsShowMultipleDashboards,
|
||||
additionalPanelTypesEnabled: gon.features.environmentMetricsAdditionalPanelTypes,
|
||||
exportMetricsToCsvEnabled: gon.features.exportMetricsToCsvEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -37,17 +37,11 @@ export const setEndpoints = ({ commit }, endpoints) => {
|
|||
|
||||
export const setFeatureFlags = (
|
||||
{ commit },
|
||||
{
|
||||
prometheusEndpointEnabled,
|
||||
multipleDashboardsEnabled,
|
||||
additionalPanelTypesEnabled,
|
||||
exportMetricsToCsvEnabled,
|
||||
},
|
||||
{ prometheusEndpointEnabled, multipleDashboardsEnabled, additionalPanelTypesEnabled },
|
||||
) => {
|
||||
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) => {
|
||||
|
|
|
@ -17,4 +17,3 @@ 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';
|
||||
|
|
|
@ -99,7 +99,4 @@ export default {
|
|||
[types.SET_SHOW_ERROR_BANNER](state, enabled) {
|
||||
state.showErrorBanner = enabled;
|
||||
},
|
||||
[types.SET_EXPORT_METRICS_TO_CSV_ENABLED](state, enabled) {
|
||||
state.exportMetricsToCsvEnabled = enabled;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -10,7 +10,6 @@ export default () => ({
|
|||
useDashboardEndpoint: false,
|
||||
multipleDashboardsEnabled: false,
|
||||
additionalPanelTypesEnabled: false,
|
||||
exportMetricsToCsvEnabled: false,
|
||||
emptyState: 'gettingStarted',
|
||||
showEmptyState: true,
|
||||
showErrorBanner: true,
|
||||
|
|
|
@ -15,7 +15,6 @@ 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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'feat: adds a download to csv functionality to the dropdown in prometheus metrics'
|
||||
merge_request: 31679
|
||||
author:
|
||||
type: changed
|
|
@ -24,7 +24,6 @@ describe('Area component', () => {
|
|||
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, {
|
||||
|
@ -109,16 +108,6 @@ 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;
|
||||
|
@ -264,23 +253,5 @@ 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:');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
|
|||
import * as types from '~/monitoring/stores/mutation_types';
|
||||
import { createStore } from '~/monitoring/stores';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import {
|
||||
import MonitoringMock, {
|
||||
metricsGroupsAPIResponse,
|
||||
mockApiEndpoint,
|
||||
environmentData,
|
||||
|
@ -40,6 +40,7 @@ describe('Dashboard', () => {
|
|||
let mock;
|
||||
let store;
|
||||
let component;
|
||||
let mockGraphData;
|
||||
|
||||
beforeEach(() => {
|
||||
setFixtures(`
|
||||
|
@ -482,4 +483,36 @@ describe('Dashboard', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when downloading metrics data as CSV', () => {
|
||||
beforeEach(() => {
|
||||
component = new DashboardComponent({
|
||||
propsData: {
|
||||
...propsData,
|
||||
},
|
||||
store,
|
||||
});
|
||||
store.commit(
|
||||
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
|
||||
MonitoringMock.data,
|
||||
);
|
||||
[mockGraphData] = component.$store.state.monitoringDashboard.groups[0].metrics;
|
||||
});
|
||||
|
||||
describe('csvText', () => {
|
||||
it('converts metrics 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(component.csvText(mockGraphData)).toMatch(`^${header}\r\n${firstRow}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('downloadCsv', () => {
|
||||
it('produces a link with a Blob', () => {
|
||||
expect(component.downloadCsv(mockGraphData)).toContain(`blob:`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue