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