Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-10-26 15:08:44 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-26 15:08:44 +0300
commit6e320396b26439a0c3fa1df1ce9f4c2395518227 (patch)
tree46e646052ba87e38f6e6866692d92cdb01878189 /app/assets/javascripts/lib
parentc60d68bbaca234673f2f689e1f7444ce8edbcf86 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/lib')
-rw-r--r--app/assets/javascripts/lib/graphql.js3
-rw-r--r--app/assets/javascripts/lib/utils/apollo_startup_js_link.js106
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),
+ });
+ });
+ });
+ }
+}