Commit 91c0cc75 authored by Andrew Newdigate's avatar Andrew Newdigate Committed by Natalia Tepluhina

GraphQL client sends feature category header

parent 3944da82
import { ApolloLink } from 'apollo-link';
import { memoize } from 'lodash';
export const FEATURE_CATEGORY_HEADER = 'x-gitlab-feature-category';
/**
* Returns the ApolloLink (or null) used to add instrumentation metadata to the GraphQL request.
*
* - The result will be null if the `feature_category` cannot be found.
* - The result is memoized since the `feature_category` is the same for the entire page.
*/
export const getInstrumentationLink = memoize(() => {
const { feature_category: featureCategory } = gon;
if (!featureCategory) {
return null;
}
return new ApolloLink((operation, forward) => {
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
[FEATURE_CATEGORY_HEADER]: featureCategory,
},
}));
return forward(operation);
});
});
...@@ -10,6 +10,7 @@ import { StartupJSLink } from '~/lib/utils/apollo_startup_js_link'; ...@@ -10,6 +10,7 @@ import { StartupJSLink } from '~/lib/utils/apollo_startup_js_link';
import csrf from '~/lib/utils/csrf'; import csrf from '~/lib/utils/csrf';
import { objectToQuery, queryToObject } from '~/lib/utils/url_utility'; import { objectToQuery, queryToObject } from '~/lib/utils/url_utility';
import PerformanceBarService from '~/performance_bar/services/performance_bar_service'; import PerformanceBarService from '~/performance_bar/services/performance_bar_service';
import { getInstrumentationLink } from './apollo/instrumentation_link';
export const fetchPolicies = { export const fetchPolicies = {
CACHE_FIRST: 'cache-first', CACHE_FIRST: 'cache-first',
...@@ -140,14 +141,17 @@ export default (resolvers = {}, config = {}) => { ...@@ -140,14 +141,17 @@ export default (resolvers = {}, config = {}) => {
const appLink = ApolloLink.split( const appLink = ApolloLink.split(
hasSubscriptionOperation, hasSubscriptionOperation,
new ActionCableLink(), new ActionCableLink(),
ApolloLink.from([ ApolloLink.from(
[
getInstrumentationLink(),
requestCounterLink, requestCounterLink,
performanceBarLink, performanceBarLink,
new StartupJSLink(), new StartupJSLink(),
apolloCaptchaLink, apolloCaptchaLink,
uploadsLink, uploadsLink,
requestLink, requestLink,
]), ].filter(Boolean),
),
); );
return new ApolloClient({ return new ApolloClient({
......
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloClient } from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import gql from 'graphql-tag';
const FOO_QUERY = gql`
query {
foo
}
`;
/**
* This function returns a promise that resolves to the final operation after
* running an ApolloClient query with the given ApolloLink
*
* @typedef {Object} TestApolloLinkOptions
* @property {Object} context the default context object sent along the ApolloLink chain
*
* @param {ApolloLink} subjectLink the ApolloLink which is under test
* @param {TestApolloLinkOptions} options contains options to send a long with the query
*
* @returns Promise resolving to the resulting operation after running the subjectLink
*/
export const testApolloLink = (subjectLink, options = {}) =>
new Promise((resolve) => {
const { context = {} } = options;
// Use the terminating link to capture the final operation and resolve with this.
const terminatingLink = new ApolloLink((operation) => {
resolve(operation);
return null;
});
const client = new ApolloClient({
link: ApolloLink.from([subjectLink, terminatingLink]),
// cache is a required option
cache: new InMemoryCache(),
});
// Trigger a query so the ApolloLink chain will be executed.
client.query({
context,
query: FOO_QUERY,
});
});
import { testApolloLink } from 'helpers/test_apollo_link';
import { getInstrumentationLink, FEATURE_CATEGORY_HEADER } from '~/lib/apollo/instrumentation_link';
const TEST_FEATURE_CATEGORY = 'foo_feature';
describe('~/lib/apollo/instrumentation_link', () => {
const setFeatureCategory = (val) => {
window.gon.feature_category = val;
};
afterEach(() => {
getInstrumentationLink.cache.clear();
});
describe('getInstrumentationLink', () => {
describe('with no gon.feature_category', () => {
beforeEach(() => {
setFeatureCategory(null);
});
it('returns null', () => {
expect(getInstrumentationLink()).toBe(null);
});
});
describe('with gon.feature_category', () => {
beforeEach(() => {
setFeatureCategory(TEST_FEATURE_CATEGORY);
});
it('returns memoized apollo link', () => {
const result = getInstrumentationLink();
// expect.any(ApolloLink) doesn't work for some reason...
expect(result).toHaveProp('request');
expect(result).toBe(getInstrumentationLink());
});
it('adds a feature category header from the returned apollo link', async () => {
const defaultHeaders = { Authorization: 'foo' };
const operation = await testApolloLink(getInstrumentationLink(), {
context: { headers: defaultHeaders },
});
const { headers } = operation.getContext();
expect(headers).toEqual({
...defaultHeaders,
[FEATURE_CATEGORY_HEADER]: TEST_FEATURE_CATEGORY,
});
});
});
});
});
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