Commit f702423c authored by Phil Hughes's avatar Phil Hughes

Merge branch 'tr-grafana-embeds-fe' into 'master'

Embedding Grafana metrics panels in GFM - frontend

See merge request gitlab-org/gitlab!18426
parents 607d955c 7d20dde9
/* eslint-disable import/prefer-default-export */
import _ from 'underscore';
/**
* @param {Array} queryResults - Array of Result objects
* @param {Object} defaultConfig - Default chart config values (e.g. lineStyle, name)
* @returns {Array} The formatted values
*/
export const makeDataSeries = (queryResults, defaultConfig) =>
queryResults.reduce((acc, result) => {
const data = result.values.filter(([, value]) => !Number.isNaN(value));
if (!data.length) {
return acc;
}
const relevantMetric = defaultConfig.name.toLowerCase().replace(' ', '_');
const name = result.metric[relevantMetric];
const series = { data };
if (name) {
series.name = `${defaultConfig.name}: ${name}`;
}
queryResults
.map(result => {
const data = result.values.filter(([, value]) => !Number.isNaN(value));
if (!data.length) {
return null;
}
const relevantMetric = defaultConfig.name.toLowerCase().replace(' ', '_');
const name = result.metric[relevantMetric];
const series = { data };
if (name) {
series.name = `${defaultConfig.name}: ${name}`;
} else {
const template = _.template(defaultConfig.name, {
interpolate: /\{\{(.+?)\}\}/g,
});
series.name = template(result.metric);
}
return acc.concat({ ...defaultConfig, ...series });
}, []);
return { ...defaultConfig, ...series };
})
.filter(series => series !== null);
......@@ -7,7 +7,7 @@ import { s__, __ } from '../../locale';
const MAX_REQUESTS = 3;
function backOffRequest(makeRequestCallback) {
export function backOffRequest(makeRequestCallback) {
let requestCounter = 0;
return backOff((next, stop) => {
makeRequestCallback()
......@@ -111,8 +111,7 @@ export const fetchDashboard = ({ state, dispatch }, params) => {
params.dashboard = state.currentDashboard;
}
return axios
.get(state.dashboardEndpoint, { params })
return backOffRequest(() => axios.get(state.dashboardEndpoint, { params }))
.then(resp => resp.data)
.then(response => {
dispatch('receiveMetricsDashboardSuccess', { response, params });
......
......@@ -41,5 +41,76 @@ describe('monitor helper', () => {
),
).toEqual([{ ...expectedDataSeries[0], data: [[1, 1]] }]);
});
it('updates series name from templates', () => {
const config = {
...defaultConfig,
name: '{{cmd}}',
};
const [result] = monitorHelper.makeDataSeries(
[{ metric: { cmd: 'brpop' }, values: series }],
config,
);
expect(result.name).toEqual('brpop');
});
it('supports space-padded template expressions', () => {
const config = {
...defaultConfig,
name: 'backend: {{ backend }}',
};
const [result] = monitorHelper.makeDataSeries(
[{ metric: { backend: 'HA Server' }, values: series }],
config,
);
expect(result.name).toEqual('backend: HA Server');
});
it('supports repeated template variables', () => {
const config = { ...defaultConfig, name: '{{cmd}}, {{cmd}}' };
const [result] = monitorHelper.makeDataSeries(
[{ metric: { cmd: 'brpop' }, values: series }],
config,
);
expect(result.name).toEqual('brpop, brpop');
});
it('updates multiple series names from templates', () => {
const config = {
...defaultConfig,
name: '{{job}}: {{cmd}}',
};
const [result] = monitorHelper.makeDataSeries(
[{ metric: { cmd: 'brpop', job: 'redis' }, values: series }],
config,
);
expect(result.name).toEqual('redis: brpop');
});
it('updates name for each series', () => {
const config = {
...defaultConfig,
name: '{{cmd}}',
};
const [firstSeries, secondSeries] = monitorHelper.makeDataSeries(
[
{ metric: { cmd: 'brpop' }, values: series },
{ metric: { cmd: 'zrangebyscore' }, values: series },
],
config,
);
expect(firstSeries.name).toEqual('brpop');
expect(secondSeries.name).toEqual('zrangebyscore');
});
});
});
import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import { backOffRequest } from '~/monitoring/stores/actions';
import statusCodes from '~/lib/utils/http_status';
import { backOff } from '~/lib/utils/common_utils';
jest.mock('~/lib/utils/common_utils');
const MAX_REQUESTS = 3;
describe('Monitoring store helpers', () => {
let mock;
// Mock underlying `backOff` function to remove in-built delay.
backOff.mockImplementation(
callback =>
new Promise((resolve, reject) => {
const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg));
const next = () => callback(next, stop);
callback(next, stop);
}),
);
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('backOffRequest', () => {
it('returns immediately when recieving a 200 status code', () => {
mock.onGet(TEST_HOST).reply(200);
return backOffRequest(() => axios.get(TEST_HOST)).then(() => {
expect(mock.history.get.length).toBe(1);
});
});
it(`repeats the network call ${MAX_REQUESTS} times when receiving a 204 response`, done => {
mock.onGet(TEST_HOST).reply(statusCodes.NO_CONTENT, {});
backOffRequest(() => axios.get(TEST_HOST))
.then(done.fail)
.catch(() => {
expect(mock.history.get.length).toBe(MAX_REQUESTS);
done();
});
});
});
});
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