debian-mirror-gitlab/app/assets/javascripts/lib/utils/apollo_startup_js_link.js

107 lines
3 KiB
JavaScript
Raw Normal View History

2022-04-04 11:22:00 +05:30
import { ApolloLink, Observable } from '@apollo/client/core';
2021-01-29 00:20:46 +05:30
import { parse } from 'graphql';
import { isEqual, pickBy } from 'lodash';
/**
* Remove undefined values from object
* @param obj
* @returns {Dictionary<unknown>}
*/
2021-03-08 18:12:59 +05:30
const pickDefinedValues = (obj) => pickBy(obj, (x) => x !== undefined);
2021-01-29 00:20:46 +05:30
/**
* 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) {
2021-03-08 18:12:59 +05:30
calls.forEach((call) => {
2021-01-29 00:20:46 +05:30
const { query, variables, fetchCall } = call;
2021-03-08 18:12:59 +05:30
const operationName = parse(query)?.definitions?.find((x) => x.kind === 'OperationDefinition')
2021-01-29 00:20:46 +05:30
?.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);
}
2021-03-08 18:12:59 +05:30
return new Observable((observer) => {
2021-01-29 00:20:46 +05:30
fetchCall
2021-03-08 18:12:59 +05:30
.then((response) => {
2021-01-29 00:20:46 +05:30
// Handle HTTP errors
if (!response.ok) {
throw new Error('fetchCall failed');
}
operation.setContext({ response });
return response.json();
})
2021-03-08 18:12:59 +05:30
.then((result) => {
2021-01-29 00:20:46 +05:30
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({
2021-03-08 18:12:59 +05:30
next: (result) => {
2021-01-29 00:20:46 +05:30
observer.next(result);
},
2021-03-08 18:12:59 +05:30
error: (error) => {
2021-01-29 00:20:46 +05:30
observer.error(error);
},
complete: observer.complete.bind(observer),
});
});
});
}
}