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

apollo_startup_js_link.js « utils « lib « javascripts « assets « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: f240226e9910b7754cda07e2a4850c86c7069a63 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import { ApolloLink, Observable } from '@apollo/client/core';
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),
          });
        });
    });
  }
}