diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-26 15:08:44 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-26 15:08:44 +0300 |
commit | 6e320396b26439a0c3fa1df1ce9f4c2395518227 (patch) | |
tree | 46e646052ba87e38f6e6866692d92cdb01878189 /app/assets/javascripts/lib | |
parent | c60d68bbaca234673f2f689e1f7444ce8edbcf86 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/lib')
-rw-r--r-- | app/assets/javascripts/lib/graphql.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/apollo_startup_js_link.js | 106 |
2 files changed, 108 insertions, 1 deletions
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js index 0e07f7d8e44..e0d9a903e0a 100644 --- a/app/assets/javascripts/lib/graphql.js +++ b/app/assets/javascripts/lib/graphql.js @@ -5,6 +5,7 @@ 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'; +import { StartupJSLink } from '~/lib/utils/apollo_startup_js_link'; export const fetchPolicies = { CACHE_FIRST: 'cache-first', @@ -62,7 +63,7 @@ export default (resolvers = {}, config = {}) => { return new ApolloClient({ typeDefs: config.typeDefs, - link: ApolloLink.from([performanceBarLink, uploadsLink]), + link: ApolloLink.from([performanceBarLink, new StartupJSLink(), uploadsLink]), cache: new InMemoryCache({ ...config.cacheConfig, freezeResults: config.assumeImmutableResults, diff --git a/app/assets/javascripts/lib/utils/apollo_startup_js_link.js b/app/assets/javascripts/lib/utils/apollo_startup_js_link.js new file mode 100644 index 00000000000..5c120dd532f --- /dev/null +++ b/app/assets/javascripts/lib/utils/apollo_startup_js_link.js @@ -0,0 +1,106 @@ +import { ApolloLink, Observable } from 'apollo-link'; +import { parse } from 'graphql'; +import { isEqual, pickBy } from 'lodash'; + +/** + * Remove undefined values from object + * @param obj + * @returns {Dictionary<unknown>} + */ +const pickDefinedValues = obj => pickBy(obj, x => x !== undefined); + +/** + * Compares two set of variables, order independent + * + * Ignores undefined values (in the top level) and supports arrays etc. + */ +const variablesMatch = (var1 = {}, var2 = {}) => { + return isEqual(pickDefinedValues(var1), pickDefinedValues(var2)); +}; + +export class StartupJSLink extends ApolloLink { + constructor() { + super(); + this.startupCalls = new Map(); + this.parseStartupCalls(window.gl?.startup_graphql_calls || []); + } + + // Extract operationNames from the queries and ensure that we can + // match operationName => element from result array + parseStartupCalls(calls) { + calls.forEach(call => { + const { query, variables, fetchCall } = call; + const operationName = parse(query)?.definitions?.find(x => x.kind === 'OperationDefinition') + ?.name?.value; + + if (operationName) { + this.startupCalls.set(operationName, { + variables, + fetchCall, + }); + } + }); + } + + static noopRequest = (operation, forward) => forward(operation); + + disable() { + this.request = StartupJSLink.noopRequest; + this.startupCalls = null; + } + + request(operation, forward) { + // Disable StartupJSLink in case all calls are done or none are set up + if (this.startupCalls && this.startupCalls.size === 0) { + this.disable(); + return forward(operation); + } + + const { operationName } = operation; + + // Skip startup call if the operationName doesn't match + if (!this.startupCalls.has(operationName)) { + return forward(operation); + } + + const { variables: startupVariables, fetchCall } = this.startupCalls.get(operationName); + this.startupCalls.delete(operationName); + + // Skip startup call if the variables values do not match + if (!variablesMatch(startupVariables, operation.variables)) { + return forward(operation); + } + + return new Observable(observer => { + fetchCall + .then(response => { + // Handle HTTP errors + if (!response.ok) { + throw new Error('fetchCall failed'); + } + operation.setContext({ response }); + return response.json(); + }) + .then(result => { + if (result && (result.errors || !result.data)) { + throw new Error('Received GraphQL error'); + } + + // we have data and can send it to back up the link chain + observer.next(result); + observer.complete(); + }) + .catch(() => { + forward(operation).subscribe({ + next: result => { + observer.next(result); + }, + error: error => { + observer.error(error); + }, + complete: observer.complete.bind(observer), + }); + }); + }); + } +} |