200 lines
5.8 KiB
JavaScript
200 lines
5.8 KiB
JavaScript
import * as Sentry from '@sentry/browser';
|
|
import axios from '~/lib/utils/axios_utils';
|
|
|
|
function reportErrorAndThrow(e) {
|
|
Sentry.captureException(e);
|
|
throw e;
|
|
}
|
|
// Provisioning API spec: https://gitlab.com/gitlab-org/opstrace/opstrace/-/blob/main/provisioning-api/pkg/provisioningapi/routes.go#L59
|
|
async function enableTraces(provisioningUrl) {
|
|
try {
|
|
// Note: axios.put(url, undefined, {withCredentials: true}) does not send cookies properly, so need to use the API below for the correct behaviour
|
|
return await axios(provisioningUrl, {
|
|
method: 'put',
|
|
withCredentials: true,
|
|
});
|
|
} catch (e) {
|
|
return reportErrorAndThrow(e);
|
|
}
|
|
}
|
|
|
|
// Provisioning API spec: https://gitlab.com/gitlab-org/opstrace/opstrace/-/blob/main/provisioning-api/pkg/provisioningapi/routes.go#L37
|
|
async function isTracingEnabled(provisioningUrl) {
|
|
try {
|
|
const { data } = await axios.get(provisioningUrl, { withCredentials: true });
|
|
if (data && data.status) {
|
|
// we currently ignore the 'status' payload and just check if the request was successful
|
|
// We might improve this as part of https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2315
|
|
return true;
|
|
}
|
|
} catch (e) {
|
|
if (e.response.status === 404) {
|
|
return false;
|
|
}
|
|
return reportErrorAndThrow(e);
|
|
}
|
|
return reportErrorAndThrow(new Error('Failed to check provisioning')); // eslint-disable-line @gitlab/require-i18n-strings
|
|
}
|
|
|
|
async function fetchTrace(tracingUrl, traceId) {
|
|
try {
|
|
if (!traceId) {
|
|
throw new Error('traceId is required.');
|
|
}
|
|
|
|
const { data } = await axios.get(tracingUrl, {
|
|
withCredentials: true,
|
|
params: {
|
|
trace_id: traceId,
|
|
},
|
|
});
|
|
|
|
if (!Array.isArray(data.traces) || data.traces.length === 0) {
|
|
throw new Error('traces are missing/invalid in the response'); // eslint-disable-line @gitlab/require-i18n-strings
|
|
}
|
|
|
|
return data.traces[0];
|
|
} catch (e) {
|
|
return reportErrorAndThrow(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Filters (and operators) allowed by tracing query API
|
|
*/
|
|
const SUPPORTED_FILTERS = {
|
|
durationMs: ['>', '<'],
|
|
operation: ['=', '!='],
|
|
serviceName: ['=', '!='],
|
|
period: ['='],
|
|
traceId: ['=', '!='],
|
|
// free-text 'search' temporarily ignored https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2309
|
|
};
|
|
|
|
/**
|
|
* Mapping of filter name to query param
|
|
*/
|
|
const FILTER_TO_QUERY_PARAM = {
|
|
durationMs: 'duration_nano',
|
|
operation: 'operation',
|
|
serviceName: 'service_name',
|
|
period: 'period',
|
|
traceId: 'trace_id',
|
|
};
|
|
|
|
const FILTER_OPERATORS_PREFIX = {
|
|
'!=': 'not',
|
|
'>': 'gt',
|
|
'<': 'lt',
|
|
};
|
|
|
|
/**
|
|
* Builds the query param name for the given filter and operator
|
|
*
|
|
* @param {String} filterName - The filter name
|
|
* @param {String} operator - The operator
|
|
* @returns String | undefined - Query param name
|
|
*/
|
|
function getFilterParamName(filterName, operator) {
|
|
const paramKey = FILTER_TO_QUERY_PARAM[filterName];
|
|
if (!paramKey) return undefined;
|
|
|
|
if (operator === '=') {
|
|
return paramKey;
|
|
}
|
|
|
|
const prefix = FILTER_OPERATORS_PREFIX[operator];
|
|
if (prefix) {
|
|
return `${prefix}[${paramKey}]`;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Builds URLSearchParams from a filter object of type { [filterName]: undefined | null | Array<{operator: String, value: any} }
|
|
* e.g:
|
|
*
|
|
* filterObj = {
|
|
* durationMs: [{operator: '>', value: '100'}, {operator: '<', value: '1000' }],
|
|
* operation: [{operator: '=', value: 'someOp' }],
|
|
* serviceName: [{operator: '!=', value: 'foo' }]
|
|
* }
|
|
*
|
|
* It handles converting the filter to the proper supported query params
|
|
*
|
|
* @param {Object} filterObj : An Object representing filters
|
|
* @returns URLSearchParams
|
|
*/
|
|
function filterObjToQueryParams(filterObj) {
|
|
const filterParams = new URLSearchParams();
|
|
|
|
Object.keys(SUPPORTED_FILTERS).forEach((filterName) => {
|
|
const filterValues = filterObj[filterName] || [];
|
|
const supportedFilters = filterValues.filter((f) =>
|
|
SUPPORTED_FILTERS[filterName].includes(f.operator),
|
|
);
|
|
supportedFilters.forEach(({ operator, value: rawValue }) => {
|
|
const paramName = getFilterParamName(filterName, operator);
|
|
|
|
let value = rawValue;
|
|
if (filterName === 'durationMs') {
|
|
// converting durationMs to duration_nano
|
|
value *= 1000;
|
|
}
|
|
|
|
if (paramName && value) {
|
|
filterParams.append(paramName, value);
|
|
}
|
|
});
|
|
});
|
|
return filterParams;
|
|
}
|
|
|
|
/**
|
|
* Fetches traces with given tracing API URL and filters
|
|
*
|
|
* @param {String} tracingUrl : The API base URL
|
|
* @param {Object} filters : A filter object of type: { [filterName]: undefined | null | Array<{operator: String, value: String} }
|
|
* e.g:
|
|
*
|
|
* {
|
|
* durationMs: [ {operator: '>', value: '100'}, {operator: '<', value: '1000'}],
|
|
* operation: [ {operator: '=', value: 'someOp}],
|
|
* serviceName: [ {operator: '!=', value: 'foo}]
|
|
* }
|
|
*
|
|
* @returns Array<Trace> : A list of traces
|
|
*/
|
|
async function fetchTraces(tracingUrl, { filters = {}, pageToken, pageSize } = {}) {
|
|
const params = filterObjToQueryParams(filters);
|
|
if (pageToken) {
|
|
params.append('page_token', pageToken);
|
|
}
|
|
if (pageSize) {
|
|
params.append('page_size', pageSize);
|
|
}
|
|
|
|
try {
|
|
const { data } = await axios.get(tracingUrl, {
|
|
withCredentials: true,
|
|
params,
|
|
});
|
|
if (!Array.isArray(data.traces)) {
|
|
throw new Error('traces are missing/invalid in the response'); // eslint-disable-line @gitlab/require-i18n-strings
|
|
}
|
|
return data;
|
|
} catch (e) {
|
|
return reportErrorAndThrow(e);
|
|
}
|
|
}
|
|
|
|
export function buildClient({ provisioningUrl, tracingUrl }) {
|
|
return {
|
|
enableTraces: () => enableTraces(provisioningUrl),
|
|
isTracingEnabled: () => isTracingEnabled(provisioningUrl),
|
|
fetchTraces: (filters) => fetchTraces(tracingUrl, filters),
|
|
fetchTrace: (traceId) => fetchTrace(tracingUrl, traceId),
|
|
};
|
|
}
|