Commit 1b83c2cb authored by Phil Hughes's avatar Phil Hughes

Merge branch 'graphql-performance-bar' into 'master'

Automatically add GraphQL requests to performance bar dropdown

See merge request gitlab-org/gitlab!39956
parents ea3df992 e2cf2f6c
......@@ -4,6 +4,7 @@ import { createUploadLink } from 'apollo-upload-client';
import { ApolloLink } from 'apollo-link';
import { BatchHttpLink } from 'apollo-link-batch-http';
import csrf from '~/lib/utils/csrf';
import PerformanceBarService from '~/performance_bar/services/performance_bar_service';
export const fetchPolicies = {
CACHE_FIRST: 'cache-first',
......@@ -32,13 +33,35 @@ export default (resolvers = {}, config = {}) => {
credentials: 'same-origin',
};
const uploadsLink = ApolloLink.split(
operation => operation.getContext().hasUpload || operation.getContext().isSingleRequest,
createUploadLink(httpOptions),
new BatchHttpLink(httpOptions),
);
const performanceBarLink = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
const httpResponse = operation.getContext().response;
if (PerformanceBarService.interceptor) {
PerformanceBarService.interceptor({
config: {
url: httpResponse.url,
},
headers: {
'x-request-id': httpResponse.headers.get('x-request-id'),
'x-gitlab-from-cache': httpResponse.headers.get('x-gitlab-from-cache'),
},
});
}
return response;
});
});
return new ApolloClient({
typeDefs: config.typeDefs,
link: ApolloLink.split(
operation => operation.getContext().hasUpload || operation.getContext().isSingleRequest,
createUploadLink(httpOptions),
new BatchHttpLink(httpOptions),
),
link: ApolloLink.from([performanceBarLink, uploadsLink]),
cache: new InMemoryCache({
...config.cacheConfig,
freezeResults: config.assumeImmutableResults,
......
......@@ -24,15 +24,12 @@ export default ({ container }) =>
};
},
mounted() {
this.interceptor = PerformanceBarService.registerInterceptor(
this.peekUrl,
this.loadRequestDetails,
);
PerformanceBarService.registerInterceptor(this.peekUrl, this.loadRequestDetails);
this.loadRequestDetails(this.requestId, window.location.href);
},
beforeDestroy() {
PerformanceBarService.removeInterceptor(this.interceptor);
PerformanceBarService.removeInterceptor();
},
methods: {
addRequestManually(urlOrRequestId) {
......
......@@ -2,12 +2,14 @@ import axios from '../../lib/utils/axios_utils';
import { parseBoolean } from '~/lib/utils/common_utils';
export default class PerformanceBarService {
static interceptor = null;
static fetchRequestDetails(peekUrl, requestId) {
return axios.get(peekUrl, { params: { request_id: requestId } });
}
static registerInterceptor(peekUrl, callback) {
const interceptor = response => {
PerformanceBarService.interceptor = response => {
const [fireCallback, requestId, requestUrl] = PerformanceBarService.callbackParams(
response,
peekUrl,
......@@ -20,18 +22,17 @@ export default class PerformanceBarService {
return response;
};
return axios.interceptors.response.use(interceptor);
return axios.interceptors.response.use(PerformanceBarService.interceptor);
}
static removeInterceptor(interceptor) {
axios.interceptors.response.eject(interceptor);
static removeInterceptor() {
axios.interceptors.response.eject(PerformanceBarService.interceptor);
PerformanceBarService.interceptor = null;
}
static callbackParams(response, peekUrl) {
const requestId = response.headers && response.headers['x-request-id'];
// Get the request URL from response.config for Axios, and response for
// Vue Resource.
const requestUrl = (response.config || response).url;
const requestUrl = response.config?.url;
const cachedResponse =
response.headers && parseBoolean(response.headers['x-gitlab-from-cache']);
const fireCallback = requestUrl !== peekUrl && Boolean(requestId) && !cachedResponse;
......
......@@ -47,7 +47,10 @@ export default class PerformanceBarStore {
}
canTrackRequest(requestUrl) {
return this.requests.filter(request => request.url === requestUrl).length < 2;
return (
requestUrl.endsWith('/api/graphql') ||
this.requests.filter(request => request.url === requestUrl).length < 2
);
}
static truncateUrl(requestUrl) {
......
......@@ -8,13 +8,13 @@ describe('PerformanceBarService', () => {
}
it('returns false when the request URL is the peek URL', () => {
expect(fireCallback({ headers: { 'x-request-id': '123' }, url: '/peek' }, '/peek')).toBe(
false,
);
expect(
fireCallback({ headers: { 'x-request-id': '123' }, config: { url: '/peek' } }, '/peek'),
).toBe(false);
});
it('returns false when there is no request ID', () => {
expect(fireCallback({ headers: {}, url: '/request' }, '/peek')).toBe(false);
expect(fireCallback({ headers: {}, config: { url: '/request' } }, '/peek')).toBe(false);
});
it('returns false when the response is from the cache', () => {
......@@ -27,15 +27,18 @@ describe('PerformanceBarService', () => {
});
it('returns true when the request is an API request', () => {
expect(fireCallback({ headers: { 'x-request-id': '123' }, url: '/api/' }, '/peek')).toBe(
true,
);
expect(
fireCallback({ headers: { 'x-request-id': '123' }, config: { url: '/api/' } }, '/peek'),
).toBe(true);
});
it('returns true for all other requests', () => {
expect(fireCallback({ headers: { 'x-request-id': '123' }, url: '/request' }, '/peek')).toBe(
true,
);
expect(
fireCallback(
{ headers: { 'x-request-id': '123' }, config: { url: '/request' } },
'/peek',
),
).toBe(true);
});
});
......@@ -45,7 +48,7 @@ describe('PerformanceBarService', () => {
}
it('gets the request ID from the headers', () => {
expect(requestId({ headers: { 'x-request-id': '123' } }, '/peek')).toEqual('123');
expect(requestId({ headers: { 'x-request-id': '123' } }, '/peek')).toBe('123');
});
});
......@@ -54,14 +57,10 @@ describe('PerformanceBarService', () => {
return PerformanceBarService.callbackParams(response, peekUrl)[2];
}
it('gets the request URL from the response object', () => {
expect(requestUrl({ headers: {}, url: '/request' }, '/peek')).toEqual('/request');
});
it('gets the request URL from response.config if present', () => {
expect(
requestUrl({ headers: {}, config: { url: '/config-url' }, url: '/request' }, '/peek'),
).toEqual('/config-url');
it('gets the request URL from response.config', () => {
expect(requestUrl({ headers: {}, config: { url: '/config-url' } }, '/peek')).toBe(
'/config-url',
);
});
});
});
......
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