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

thoughtspot / rise / #304

10 Oct 2024 11:31PM UTC coverage: 35.349% (-3.3%) from 38.605%
#304

push

jbhanu-thoughtspot
Merge branch 'main' into SCAL-210178

19 of 79 branches covered (24.05%)

Branch coverage included in aggregate %.

3 of 75 new or added lines in 1 file covered. (4.0%)

4 existing lines in 2 files now uncovered.

57 of 136 relevant lines covered (41.91%)

3.21 hits per line

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

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

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

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

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

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

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

NEW
36
  return body;
×
37
}
38

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

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

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

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

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

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

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

NEW
104
      let argToReplaceValue = '';
×
NEW
105
      try {
×
NEW
106
        if (typeof args[arg] !== 'object' || Array.isArray(args[arg])) {
×
NEW
107
          argToReplaceValue = encodeURI(args[arg]);
×
108
        }
109
      } catch (e) {
NEW
110
        console.debug(`[Rise] Error encoding ${arg}, Message: ${(e as any)?.message || ''}`);
×
111
      }
NEW
112
      urlToFetch = urlToFetch.replace(argToReplace, argToReplaceValue);
×
113
    });
114

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

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

NEW
128
    console.debug('[Rise] Downstream URL', urlToFetch);
×
NEW
129
    return fetch(urlToFetch, {
×
130
      method,
131
      headers: reqHeaders,
132
      body,
133
      credentials: 'include',
134
    })
135
      .then(async (response) => {
NEW
136
        if (!response.ok) {
×
137
          let payload;
NEW
138
          try {
×
NEW
139
            const contentType = response.headers.get('Content-Type');
×
NEW
140
            const isTextContent = contentType && contentType.includes('text');
×
NEW
141
            if (isTextContent) {
×
NEW
142
              payload = { [errorroot]: { message: await response.text() } };
×
143
            } else {
NEW
144
              payload = await response.json();
×
145
            }
146
          } catch (e) {
NEW
147
            throw new options.ErrorClass(response.statusText, response.status, e);
×
148
          }
NEW
149
          const error = (errorroot ? _.get(payload, errorroot) : payload) || {};
×
NEW
150
          throw new options.ErrorClass(response.statusText, response.status, error);
×
151
        }
NEW
152
        processResHeaders(response, originalContext);
×
NEW
153
        return (fieldConfig.type.toString() === 'Void')
×
154
          ? response.text() : response.json();
155
      })
156
      .then((data: any) => {
157
        let result;
NEW
158
        if (!data || typeof data === 'string') {
×
159
          result = data;
×
160
        }
161

NEW
162
        if (resultroot) {
×
NEW
163
          if (Array.isArray(resultroot)) {
×
NEW
164
            data = resultroot
×
NEW
165
              .reduce((res, root) => Object.assign(res, _.get(data, root)), {});
×
166
          } else {
NEW
167
            data = _.get(data, resultroot); // TODO: support items[].field
×
168
          }
169
        }
170

NEW
171
        result = data;
×
NEW
172
        if (setters.length > 0) {
×
NEW
173
          result = transformWithSetters(data, setters);
×
174
        }
175

176
        // Put arguments in the result for any nested rise calls
177
        // to use.
NEW
178
        if (result) {
×
179
          // eslint-disable-next-line no-underscore-dangle
NEW
180
          result.__args = args;
×
181
        }
NEW
182
        return result;
×
183
      });
184
  };
185
}
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

© 2025 Coveralls, Inc