@sasjs/adapter 4.5.1 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,6 +36,7 @@ export declare class RequestClient implements HttpClient {
36
36
  protected baseUrl: string;
37
37
  private requests;
38
38
  private requestsLimit;
39
+ private httpInterceptor?;
39
40
  protected csrfToken: CsrfToken;
40
41
  protected fileUploadCsrfToken: CsrfToken | undefined;
41
42
  protected httpClient: AxiosInstance;
@@ -93,6 +94,29 @@ export declare class RequestClient implements HttpClient {
93
94
  }>;
94
95
  uploadFile(url: string, content: string, accessToken?: string): Promise<any>;
95
96
  authorize: (response: string) => Promise<any>;
97
+ /**
98
+ * Adds colors to the string.
99
+ * @param str - string to be prettified.
100
+ * @returns - prettified string
101
+ */
102
+ private prettifyString;
103
+ /**
104
+ * Formats HTTP request/response body.
105
+ * @param body - HTTP request/response body.
106
+ * @returns - formatted string.
107
+ */
108
+ private parseInterceptedBody;
109
+ private defaultInterceptionCallBack;
110
+ /**
111
+ * Turns on verbose mode to log every HTTP response.
112
+ * @param successCallBack - function that should be triggered on every HTTP response with the status 2**.
113
+ * @param errorCallBack - function that should be triggered on every HTTP response with the status different from 2**.
114
+ */
115
+ enableVerboseMode: (successCallBack?: (response: AxiosResponse) => AxiosResponse<any, any>, errorCallBack?: (response: AxiosResponse) => AxiosResponse<any, any>) => void;
116
+ /**
117
+ * Turns off verbose mode to log every HTTP response.
118
+ */
119
+ disableVerboseMode: () => void;
96
120
  protected getHeaders: (accessToken: string | undefined, contentType: string) => any;
97
121
  protected parseAndSetFileUploadCsrfToken: (response: AxiosResponse) => void;
98
122
  protected parseAndSetCsrfToken: (response: AxiosResponse) => void;
@@ -1,6 +1,6 @@
1
1
  import { RequestClient } from './RequestClient';
2
2
  import { AxiosResponse } from 'axios';
3
- interface SasjsParsedResponse<T> {
3
+ export interface SasjsParsedResponse<T> {
4
4
  result: T;
5
5
  log: string;
6
6
  etag: string;
@@ -15,4 +15,4 @@ export declare class SasjsRequestClient extends RequestClient {
15
15
  getHeaders: (accessToken: string | undefined, contentType: string) => any;
16
16
  protected parseResponse<T>(response: AxiosResponse<any>): SasjsParsedResponse<T>;
17
17
  }
18
- export {};
18
+ export declare const SASJS_LOGS_SEPARATOR = "SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784";
@@ -2,7 +2,6 @@ export * from './appendExtraResponseAttributes';
2
2
  export * from './asyncForEach';
3
3
  export * from './compareTimestamps';
4
4
  export * from './convertToCsv';
5
- export * from './constants';
6
5
  export * from './createAxiosInstance';
7
6
  export * from './delay';
8
7
  export * from './fetchLogByChunks';
package/package.json CHANGED
@@ -84,5 +84,5 @@
84
84
  "https": "1.0.0",
85
85
  "tough-cookie": "4.1.3"
86
86
  },
87
- "version": "4.5.1"
87
+ "version": "4.7.0"
88
88
  }
@@ -36,6 +36,7 @@ export declare class RequestClient implements HttpClient {
36
36
  protected baseUrl: string;
37
37
  private requests;
38
38
  private requestsLimit;
39
+ private httpInterceptor?;
39
40
  protected csrfToken: CsrfToken;
40
41
  protected fileUploadCsrfToken: CsrfToken | undefined;
41
42
  protected httpClient: AxiosInstance;
@@ -93,6 +94,29 @@ export declare class RequestClient implements HttpClient {
93
94
  }>;
94
95
  uploadFile(url: string, content: string, accessToken?: string): Promise<any>;
95
96
  authorize: (response: string) => Promise<any>;
97
+ /**
98
+ * Adds colors to the string.
99
+ * @param str - string to be prettified.
100
+ * @returns - prettified string
101
+ */
102
+ private prettifyString;
103
+ /**
104
+ * Formats HTTP request/response body.
105
+ * @param body - HTTP request/response body.
106
+ * @returns - formatted string.
107
+ */
108
+ private parseInterceptedBody;
109
+ private defaultInterceptionCallBack;
110
+ /**
111
+ * Turns on verbose mode to log every HTTP response.
112
+ * @param successCallBack - function that should be triggered on every HTTP response with the status 2**.
113
+ * @param errorCallBack - function that should be triggered on every HTTP response with the status different from 2**.
114
+ */
115
+ enableVerboseMode: (successCallBack?: (response: AxiosResponse) => AxiosResponse<any, any>, errorCallBack?: (response: AxiosResponse) => AxiosResponse<any, any>) => void;
116
+ /**
117
+ * Turns off verbose mode to log every HTTP response.
118
+ */
119
+ disableVerboseMode: () => void;
96
120
  protected getHeaders: (accessToken: string | undefined, contentType: string) => any;
97
121
  protected parseAndSetFileUploadCsrfToken: (response: AxiosResponse) => void;
98
122
  protected parseAndSetCsrfToken: (response: AxiosResponse) => void;
@@ -1,6 +1,6 @@
1
1
  import { RequestClient } from './RequestClient';
2
2
  import { AxiosResponse } from 'axios';
3
- interface SasjsParsedResponse<T> {
3
+ export interface SasjsParsedResponse<T> {
4
4
  result: T;
5
5
  log: string;
6
6
  etag: string;
@@ -15,4 +15,4 @@ export declare class SasjsRequestClient extends RequestClient {
15
15
  getHeaders: (accessToken: string | undefined, contentType: string) => any;
16
16
  protected parseResponse<T>(response: AxiosResponse<any>): SasjsParsedResponse<T>;
17
17
  }
18
- export {};
18
+ export declare const SASJS_LOGS_SEPARATOR = "SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784";
@@ -0,0 +1 @@
1
+ export {};
package/src/SASjs.ts CHANGED
@@ -854,6 +854,7 @@ export default class SASjs {
854
854
  * @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { maxPollCount: 24 * 60 * 60, pollInterval: 1000 }. More information available at src/api/viya/pollJobState.ts.
855
855
  * @param printPid - a boolean that indicates whether the function should print (PID) of the started job.
856
856
  * @param variables - an object that represents macro variables.
857
+ * @param verboseMode - boolean to enable verbose mode (log every HTTP response).
857
858
  */
858
859
  public async startComputeJob(
859
860
  sasJob: string,
@@ -863,7 +864,8 @@ export default class SASjs {
863
864
  waitForResult?: boolean,
864
865
  pollOptions?: PollOptions,
865
866
  printPid = false,
866
- variables?: MacroVar
867
+ variables?: MacroVar,
868
+ verboseMode?: boolean
867
869
  ) {
868
870
  config = {
869
871
  ...this.sasjsConfig,
@@ -877,6 +879,9 @@ export default class SASjs {
877
879
  )
878
880
  }
879
881
 
882
+ if (verboseMode) this.requestClient?.enableVerboseMode()
883
+ else this.requestClient?.disableVerboseMode()
884
+
880
885
  return this.sasViyaApiClient?.executeComputeJob(
881
886
  sasJob,
882
887
  config.contextName,
@@ -1,8 +1,7 @@
1
1
  import {
2
2
  getValidJson,
3
3
  parseSasViyaDebugResponse,
4
- parseWeboutResponse,
5
- SASJS_LOGS_SEPARATOR
4
+ parseWeboutResponse
6
5
  } from '../utils'
7
6
  import { UploadFile } from '../types/UploadFile'
8
7
  import {
@@ -20,6 +20,7 @@ import {
20
20
  createAxiosInstance
21
21
  } from '../utils'
22
22
  import { InvalidSASjsCsrfError } from '../types/errors/InvalidSASjsCsrfError'
23
+ import { inspect } from 'util'
23
24
 
24
25
  export interface HttpClient {
25
26
  get<T>(
@@ -59,6 +60,7 @@ export interface HttpClient {
59
60
  export class RequestClient implements HttpClient {
60
61
  private requests: SASjsRequest[] = []
61
62
  private requestsLimit: number = 10
63
+ private httpInterceptor?: number
62
64
 
63
65
  protected csrfToken: CsrfToken = { headerName: '', value: '' }
64
66
  protected fileUploadCsrfToken: CsrfToken | undefined
@@ -70,6 +72,7 @@ export class RequestClient implements HttpClient {
70
72
  requestsLimit?: number
71
73
  ) {
72
74
  this.createHttpClient(baseUrl, httpsAgentOptions)
75
+
73
76
  if (requestsLimit) this.requestsLimit = requestsLimit
74
77
  }
75
78
 
@@ -180,6 +183,7 @@ export class RequestClient implements HttpClient {
180
183
  responseType: contentType === 'text/plain' ? 'text' : 'json',
181
184
  withCredentials: true
182
185
  }
186
+
183
187
  if (contentType === 'text/plain') {
184
188
  requestConfig.transformResponse = undefined
185
189
  }
@@ -389,6 +393,105 @@ export class RequestClient implements HttpClient {
389
393
  })
390
394
  }
391
395
 
396
+ /**
397
+ * Adds colors to the string.
398
+ * @param str - string to be prettified.
399
+ * @returns - prettified string
400
+ */
401
+ private prettifyString = (str: any) => inspect(str, { colors: true })
402
+
403
+ /**
404
+ * Formats HTTP request/response body.
405
+ * @param body - HTTP request/response body.
406
+ * @returns - formatted string.
407
+ */
408
+ private parseInterceptedBody = (body: any) => {
409
+ if (!body) return ''
410
+
411
+ let parsedBody
412
+
413
+ // Tries to parse body into JSON object.
414
+ if (typeof body === 'string') {
415
+ try {
416
+ parsedBody = JSON.parse(body)
417
+ } catch (error) {
418
+ parsedBody = body
419
+ }
420
+ } else {
421
+ parsedBody = body
422
+ }
423
+
424
+ const bodyLines = this.prettifyString(parsedBody).split('\n')
425
+
426
+ // Leaves first 50 lines
427
+ if (bodyLines.length > 51) {
428
+ bodyLines.splice(50)
429
+ bodyLines.push('...')
430
+ }
431
+
432
+ return bodyLines.join('\n')
433
+ }
434
+
435
+ private defaultInterceptionCallBack = (response: AxiosResponse) => {
436
+ const { status, config, request, data: resData } = response
437
+ const { data: reqData } = config
438
+ const { _header: reqHeaders, res } = request
439
+ const { rawHeaders } = res
440
+
441
+ // Converts an array of strings into a single string with the following format:
442
+ // <headerName>: <headerValue>
443
+ const resHeaders = rawHeaders.reduce(
444
+ (acc: string, value: string, i: number) => {
445
+ if (i % 2 === 0) {
446
+ acc += `${i === 0 ? '' : '\n'}${value}`
447
+ } else {
448
+ acc += `: ${value}`
449
+ }
450
+
451
+ return acc
452
+ },
453
+ ''
454
+ )
455
+
456
+ const parsedResBody = this.parseInterceptedBody(resData)
457
+
458
+ // HTTP response summary.
459
+ process.logger?.info(`HTTP Request (first 50 lines):
460
+ ${reqHeaders}${this.parseInterceptedBody(reqData)}
461
+
462
+ HTTP Response Code: ${this.prettifyString(status)}
463
+
464
+ HTTP Response (first 50 lines):
465
+ ${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''}
466
+ `)
467
+
468
+ return response
469
+ }
470
+
471
+ /**
472
+ * Turns on verbose mode to log every HTTP response.
473
+ * @param successCallBack - function that should be triggered on every HTTP response with the status 2**.
474
+ * @param errorCallBack - function that should be triggered on every HTTP response with the status different from 2**.
475
+ */
476
+ public enableVerboseMode = (
477
+ successCallBack = this.defaultInterceptionCallBack,
478
+ errorCallBack = this.defaultInterceptionCallBack
479
+ ) => {
480
+ this.httpInterceptor = this.httpClient.interceptors.response.use(
481
+ successCallBack,
482
+ errorCallBack
483
+ )
484
+ }
485
+
486
+ /**
487
+ * Turns off verbose mode to log every HTTP response.
488
+ */
489
+ public disableVerboseMode = () => {
490
+ if (this.httpInterceptor) {
491
+ this.httpClient.interceptors.response.eject(this.httpInterceptor)
492
+ }
493
+ }
494
+
392
495
  protected getHeaders = (
393
496
  accessToken: string | undefined,
394
497
  contentType: string
@@ -1,8 +1,7 @@
1
1
  import { RequestClient } from './RequestClient'
2
2
  import { AxiosResponse } from 'axios'
3
- import { SASJS_LOGS_SEPARATOR } from '../utils'
4
3
 
5
- interface SasjsParsedResponse<T> {
4
+ export interface SasjsParsedResponse<T> {
6
5
  result: T
7
6
  log: string
8
7
  etag: string
@@ -45,13 +44,30 @@ export class SasjsRequestClient extends RequestClient {
45
44
  }
46
45
  } catch {
47
46
  if (response.data.includes(SASJS_LOGS_SEPARATOR)) {
48
- const splittedResponse = response.data.split(SASJS_LOGS_SEPARATOR)
47
+ const { data } = response
48
+ const splittedResponse: string[] = data.split(SASJS_LOGS_SEPARATOR)
49
49
 
50
- webout = splittedResponse[0]
50
+ webout = splittedResponse.splice(0, 1)[0]
51
51
  if (webout !== undefined) parsedResponse = webout
52
52
 
53
- log = splittedResponse[1]
54
- printOutput = splittedResponse[2]
53
+ // log can contain nested logs
54
+ const logs = splittedResponse.splice(0, splittedResponse.length - 1)
55
+
56
+ // tests if string ends with SASJS_LOGS_SEPARATOR
57
+ const endingWithLogSepRegExp = new RegExp(`${SASJS_LOGS_SEPARATOR}$`)
58
+
59
+ // at this point splittedResponse can contain only one item
60
+ const lastChunk = splittedResponse[0]
61
+
62
+ if (lastChunk) {
63
+ // if the last chunk doesn't end with SASJS_LOGS_SEPARATOR, then it is a printOutput
64
+ // else the last chunk is part of the log and has to be joined
65
+ if (!endingWithLogSepRegExp.test(data)) printOutput = lastChunk
66
+ else if (logs.length > 1) logs.push(lastChunk)
67
+ }
68
+
69
+ // join logs into single log with SASJS_LOGS_SEPARATOR
70
+ log = logs.join(SASJS_LOGS_SEPARATOR)
55
71
  } else {
56
72
  parsedResponse = response.data
57
73
  }
@@ -59,7 +75,7 @@ export class SasjsRequestClient extends RequestClient {
59
75
 
60
76
  const returnResult: SasjsParsedResponse<T> = {
61
77
  result: parsedResponse as T,
62
- log,
78
+ log: log || '',
63
79
  etag,
64
80
  status: response.status
65
81
  }
@@ -69,3 +85,6 @@ export class SasjsRequestClient extends RequestClient {
69
85
  return returnResult
70
86
  }
71
87
  }
88
+
89
+ export const SASJS_LOGS_SEPARATOR =
90
+ 'SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784'
@@ -2,7 +2,6 @@ export * from './appendExtraResponseAttributes'
2
2
  export * from './asyncForEach'
3
3
  export * from './compareTimestamps'
4
4
  export * from './convertToCsv'
5
- export * from './constants'
6
5
  export * from './createAxiosInstance'
7
6
  export * from './delay'
8
7
  export * from './fetchLogByChunks'
package/utils/index.d.ts CHANGED
@@ -2,7 +2,6 @@ export * from './appendExtraResponseAttributes';
2
2
  export * from './asyncForEach';
3
3
  export * from './compareTimestamps';
4
4
  export * from './convertToCsv';
5
- export * from './constants';
6
5
  export * from './createAxiosInstance';
7
6
  export * from './delay';
8
7
  export * from './fetchLogByChunks';
@@ -1 +0,0 @@
1
- export declare const SASJS_LOGS_SEPARATOR = "SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784";
@@ -1,2 +0,0 @@
1
- export const SASJS_LOGS_SEPARATOR =
2
- 'SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784'
@@ -1 +0,0 @@
1
- export declare const SASJS_LOGS_SEPARATOR = "SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784";