• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thoughtspot / rise / #363

28 May 2025 06:00AM UTC coverage: 0.0% (-85.8%) from 85.769%
#363

push

sagar1993
Add gqlVariables to gqlResolver

0 of 113 branches covered (0.0%)

Branch coverage included in aggregate %.

0 of 5 new or added lines in 2 files covered. (0.0%)

144 existing lines in 4 files now uncovered.

0 of 162 relevant lines covered (0.0%)

0.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/rest-resolver.ts
UNCOV
1
import FormData from 'form-data';
×
UNCOV
2
import fetch from 'node-fetch';
×
3
import { GraphQLFieldConfig } from 'graphql';
UNCOV
4
import nodePath from 'path';
×
UNCOV
5
import _ from 'lodash';
×
UNCOV
6
import { RiseDirectiveOptions, getReqHeaders, processResHeaders } from './common';
×
7

NEW
8
export function generateBodyFromTemplate(template, args) {
×
UNCOV
9
  const body = _.template(template)({ args });
×
UNCOV
10
  return JSON.parse(body);
×
11
}
12

13
function autogenerateBody(args) {
UNCOV
14
  return _.pick(args, Object.getOwnPropertyNames(args));
×
15
}
16

17
function formatForContentType(body, contenttype) {
UNCOV
18
  if (contenttype === 'application/json') {
×
19
    return JSON.stringify(body);
×
20
  }
21

UNCOV
22
  if (contenttype === 'application/x-www-form-urlencoded') {
×
UNCOV
23
    body = _.mapValues(body, (v) => ((typeof v === 'object') ? JSON.stringify(v) : v));
×
UNCOV
24
    return new URLSearchParams(body).toString();
×
25
  }
26

UNCOV
27
  if (contenttype === 'multipart/form-data') {
×
UNCOV
28
    const formData = new FormData();
×
UNCOV
29
    Object.keys(body).forEach((key) => {
×
UNCOV
30
      const value = typeof body[key] === 'string' ? body[key] : JSON.stringify(body[key]);
×
UNCOV
31
      formData.append(key, value);
×
32
    });
UNCOV
33
    return formData;
×
34
  }
35

36
  return body;
×
37
}
38

39
function setOrAssign(target, setter, data) {
UNCOV
40
  if (setter.field) {
×
41
    _.set(target, setter.field, _.get(data, setter.path));
×
42
  } else {
UNCOV
43
    _.assign(target, _.get(data, setter.path));
×
44
  }
45
}
46

47
function transformWithSetters(
48
  data: any,
49
  setters: { field: string; path: string }[],
50
) {
UNCOV
51
  let result = data;
×
UNCOV
52
  if (!Array.isArray(data)) {
×
UNCOV
53
    result = { ...result };
×
UNCOV
54
    setters.forEach((setter) => {
×
UNCOV
55
      setOrAssign(result, setter, data);
×
56
    });
57
  } else {
58
    result = data.map((item) => {
×
59
      const newItem = { ...item };
×
60
      setters.forEach((setter) => {
×
61
        setOrAssign(newItem, setter, data);
×
62
      });
63
      return newItem;
×
64
    });
65
  }
UNCOV
66
  return result;
×
67
}
68

69
export interface RiseDirectiveOptionsRest extends RiseDirectiveOptions {
70
  apiType: 'rest';
71
  resultroot: string | undefined;
72
  errorroot: string | undefined;
73
}
74

UNCOV
75
export function restResolver(
×
76
  riseDirective,
77
  options: RiseDirectiveOptionsRest,
78
  fieldConfig: GraphQLFieldConfig<any, any, any>,
79
) {
80
  let {
81
    path,
82
    method = 'GET',
×
83
    setters = [],
×
84
    postbody,
85
    resultroot = options.resultroot,
×
86
    errorroot = options.errorroot,
×
87
    contenttype = options.contenttype || 'application/json',
×
UNCOV
88
  } = riseDirective;
×
UNCOV
89
  const url = nodePath.join(options.baseURL, path);
×
90

UNCOV
91
  fieldConfig.resolve = (source, args, context, info) => {
×
UNCOV
92
    let urlToFetch = url;
×
UNCOV
93
    let originalContext = context;
×
94
    let body: any;
UNCOV
95
    const reqHeaders = getReqHeaders(riseDirective, options, context);
×
96

97
    // eslint-disable-next-line no-underscore-dangle
UNCOV
98
    args = { ...args, ...source?.__args };
×
99

UNCOV
100
    Object.keys(args).forEach((arg) => {
×
UNCOV
101
      const argToReplace = `$${arg}`;
×
UNCOV
102
      if (!urlToFetch.includes(argToReplace)) return;
×
103

UNCOV
104
      let argToReplaceValue = '';
×
UNCOV
105
      try {
×
106
        // We only support path params and query params to be simple types
UNCOV
107
        if (typeof args[arg] !== 'object') {
×
108
          // Do not encode the value, cause path params are received encoded
UNCOV
109
          argToReplaceValue = args[arg];
×
110
        }
111
      } catch (e) {
112
        console.debug(`[Rise] Error encoding ${arg}, Message: ${(e as any)?.message || ''}`);
×
113
      }
UNCOV
114
      urlToFetch = urlToFetch.replace(argToReplace, argToReplaceValue);
×
115
    });
116

UNCOV
117
    if (method !== 'GET' && method !== 'HEAD') {
×
UNCOV
118
      body = postbody
×
119
        ? generateBodyFromTemplate(postbody, args)
120
        : autogenerateBody(args);
UNCOV
121
      body = formatForContentType(body, contenttype);
×
122
    }
123

124
    // eslint-disable-next-line no-underscore-dangle
UNCOV
125
    if (reqHeaders['Content-Type'] === 'multipart/form-data' && body._boundary) {
×
126
      // eslint-disable-next-line no-underscore-dangle
UNCOV
127
      reqHeaders['Content-Type'] = `multipart/form-data; boundary=${body._boundary}`;
×
128
    }
129

130
    // if user sends a text request, we will forward the exact to the server along with
131
    // the content type
UNCOV
132
    const originalHeaders = context.req?.orginalHeaders || {};
×
UNCOV
133
    const contentTypeHeaderKey = Object.keys(originalHeaders).filter((header) => header.toLowerCase() === 'content-type')[0];
×
UNCOV
134
    const contentTypeHeaderValue = contentTypeHeaderKey && originalHeaders[contentTypeHeaderKey];
×
UNCOV
135
    if (contentTypeHeaderValue?.includes('text/')) {
×
UNCOV
136
      reqHeaders['Content-Type'] = contentTypeHeaderValue;
×
UNCOV
137
      body = context.req?.body;
×
138
    }
139

UNCOV
140
    console.debug('[Rise] Downstream URL', urlToFetch);
×
UNCOV
141
    return fetch(urlToFetch, {
×
142
      method,
143
      headers: reqHeaders,
144
      body,
145
      credentials: 'include',
146
    })
147
      .then(async (response) => {
UNCOV
148
        if (!response.ok) {
×
149
          let payload;
UNCOV
150
          try {
×
UNCOV
151
            const contentType = response.headers.get('Content-Type');
×
UNCOV
152
            const isTextContent = contentType && contentType.includes('text');
×
UNCOV
153
            if (isTextContent) {
×
UNCOV
154
              payload = { [errorroot]: { message: await response.text() } };
×
155
            } else {
UNCOV
156
              payload = await response.json();
×
157
            }
158
          } catch (e) {
159
            throw new options.ErrorClass(response.statusText, response.status, e);
×
160
          }
UNCOV
161
          const error = (errorroot ? _.get(payload, errorroot) : payload) || {};
×
UNCOV
162
          throw new options.ErrorClass(response.statusText, response.status, error);
×
163
        }
UNCOV
164
        processResHeaders(response, originalContext);
×
UNCOV
165
        return (fieldConfig.type.toString() === 'Void')
×
166
          ? response.text() : response.json();
167
      })
168
      .then((data: any) => {
169
        let result;
UNCOV
170
        if (!data || typeof data === 'string') {
×
UNCOV
171
          result = data;
×
172
        }
173

UNCOV
174
        if (resultroot) {
×
UNCOV
175
          if (Array.isArray(resultroot)) {
×
176
            data = resultroot
×
177
              .reduce((res, root) => Object.assign(res, _.get(data, root)), {});
×
178
          } else {
UNCOV
179
            data = _.get(data, resultroot); // TODO: support items[].field
×
180
          }
181
        }
182

UNCOV
183
        result = data;
×
UNCOV
184
        if (setters.length > 0) {
×
UNCOV
185
          result = transformWithSetters(data, setters);
×
186
        }
187

188
        // Put arguments in the result for any nested rise calls
189
        // to use.
UNCOV
190
        if (result) {
×
191
          // eslint-disable-next-line no-underscore-dangle
UNCOV
192
          result.__args = args;
×
193
        }
UNCOV
194
        return result;
×
195
      });
196
  };
197
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc