From 929ec6eb1cf900c9f4706b241d1ae5c163c0f342 Mon Sep 17 00:00:00 2001 From: Allan Bowe <4420615+allanbowe@users.noreply.github.com> Date: Fri, 2 Jul 2021 12:50:03 +0300 Subject: [PATCH 1/3] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 5665baf..dfbb8d6 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,8 @@ Note - to use the web approach, the `useComputeApi` property must be `undefined` ### Using the JES API Here we are running Jobs using the Job Execution Service except this time we are making the requests directly using the REST API instead of through the JES Web App. This is helpful when we need to call web services outside of a browser (eg with the SASjs CLI or other commandline tools). To save one network request, the adapter prefetches the JOB URIs and passes them in the `__job` parameter. Depending on your network bandwidth, it may or may not be faster than the JES Web approach. +This approach (`useComputeApi: false`) also ensures that jobs are displayed in Environment Manager. + ``` { appLoc:"/Your/Path", @@ -206,6 +208,8 @@ Here we are running Jobs using the Job Execution Service except this time we are ### Using the Compute API This approach is by far the fastest, as a result of the optimisations we have built into the adapter. With this configuration, in the first sasjs request, we take a URI map of the services in the target folder, and create a session manager. This manager will spawn a additional session every time a request is made. Subsequent requests will use the existing 'hot' session, if it exists. Sessions are always deleted after every use, which actually makes this _less_ resource intensive than a typical JES web app, in which all sessions are kept alive by default for 15 minutes. +With this approach (`useComputeApi: true`), the requests/logs will _not_ appear in the list in Environment manager. + ``` { appLoc:"/Your/Path", From d4ebef4290cd12582221b6c88426e839077b03a4 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Tue, 13 Jul 2021 14:50:46 +0300 Subject: [PATCH 2/3] fix(session): provide more info if could not get session state --- src/SessionManager.ts | 32 +++++++++++++++++-------- src/request/RequestClient.ts | 12 +++++++--- src/request/Sas9RequestClient.ts | 2 +- src/types/errors/NoSessionStateError.ts | 15 ++++++++++++ src/types/errors/index.ts | 1 + 5 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 src/types/errors/NoSessionStateError.ts diff --git a/src/SessionManager.ts b/src/SessionManager.ts index 914a686..eb195a0 100644 --- a/src/SessionManager.ts +++ b/src/SessionManager.ts @@ -1,4 +1,5 @@ -import { Session, Context, CsrfToken, SessionVariable } from './types' +import { Session, Context, SessionVariable } from './types' +import { NoSessionStateError } from './types/errors' import { asyncForEach, isUrl } from './utils' import { prefixMessage } from '@sasjs/utils/error' import { RequestClient } from './request/RequestClient' @@ -173,13 +174,14 @@ export class SessionManager { this.printedSessionState.printed = true } - const state = await this.getSessionState( - `${this.serverUrl}${stateLink.href}?wait=30`, - etag!, - accessToken - ).catch((err) => { - throw prefixMessage(err, 'Error while getting session state.') - }) + const { result: state, responseStatus: responseStatus } = + await this.getSessionState( + `${this.serverUrl}${stateLink.href}?wait=30`, + etag!, + accessToken + ).catch((err) => { + throw prefixMessage(err, 'Error while getting session state.') + }) sessionState = state.trim() @@ -198,7 +200,14 @@ export class SessionManager { resolve(this.waitForSession(session, etag, accessToken)) } else { - reject('Could not get session state.') + reject( + new NoSessionStateError( + responseStatus, + this.serverUrl + stateLink.href, + session.links.find((l: any) => l.rel === 'log') + ?.href as string + ) + ) } } @@ -217,7 +226,10 @@ export class SessionManager { ) { return await this.requestClient .get(url, accessToken, 'text/plain', { 'If-None-Match': etag }) - .then((res) => res.result as string) + .then((res) => ({ + result: res.result as string, + responseStatus: res.status + })) .catch((err) => { throw err }) diff --git a/src/request/RequestClient.ts b/src/request/RequestClient.ts index eb6aac2..704401e 100644 --- a/src/request/RequestClient.ts +++ b/src/request/RequestClient.ts @@ -84,7 +84,7 @@ export class RequestClient implements HttpClient { contentType: string = 'application/json', overrideHeaders: { [key: string]: string | number } = {}, debug: boolean = false - ): Promise<{ result: T; etag: string }> { + ): Promise<{ result: T; etag: string; status: number }> { const headers = { ...this.getHeaders(accessToken, contentType), ...overrideHeaders @@ -438,9 +438,15 @@ export class RequestClient implements HttpClient { includeSAS9Log = true } - let responseToReturn: { result: T; etag: any; log?: string } = { + let responseToReturn: { + result: T + etag: any + log?: string + status: number + } = { result: parsedResponse as T, - etag + etag, + status: response.status } if (includeSAS9Log) { diff --git a/src/request/Sas9RequestClient.ts b/src/request/Sas9RequestClient.ts index eedb3ef..5fb4750 100644 --- a/src/request/Sas9RequestClient.ts +++ b/src/request/Sas9RequestClient.ts @@ -39,7 +39,7 @@ export class Sas9RequestClient extends RequestClient { contentType: string = 'application/json', overrideHeaders: { [key: string]: string | number } = {}, debug: boolean = false - ): Promise<{ result: T; etag: string }> { + ): Promise<{ result: T; etag: string; status: number }> { const headers = { ...this.getHeaders(accessToken, contentType), ...overrideHeaders diff --git a/src/types/errors/NoSessionStateError.ts b/src/types/errors/NoSessionStateError.ts new file mode 100644 index 0000000..3409b43 --- /dev/null +++ b/src/types/errors/NoSessionStateError.ts @@ -0,0 +1,15 @@ +export class NoSessionStateError extends Error { + constructor( + public serverResponseStatus: number, + public sessionStateUrl: string, + public logUrl: string + ) { + super( + `Could not get session state. Server responded with ${serverResponseStatus} whilst checking state: ${sessionStateUrl}` + ) + + this.name = 'NoSessionStatus' + + Object.setPrototypeOf(this, NoSessionStateError.prototype) + } +} diff --git a/src/types/errors/index.ts b/src/types/errors/index.ts index 3db6b72..72b63cc 100644 --- a/src/types/errors/index.ts +++ b/src/types/errors/index.ts @@ -5,3 +5,4 @@ export * from './JobExecutionError' export * from './LoginRequiredError' export * from './NotFoundError' export * from './ErrorResponse' +export * from './NoSessionStateError' From a5c9f11c7535720c4bd047477b97130ab4cf7473 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Wed, 14 Jul 2021 14:17:20 +0300 Subject: [PATCH 3/3] test(session): cover case when could not get session state --- src/test/SessionManager.spec.ts | 38 ++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/test/SessionManager.spec.ts b/src/test/SessionManager.spec.ts index 2af3855..c818d4f 100644 --- a/src/test/SessionManager.spec.ts +++ b/src/test/SessionManager.spec.ts @@ -1,7 +1,9 @@ import { SessionManager } from '../SessionManager' -import * as dotenv from 'dotenv' import { RequestClient } from '../request/RequestClient' +import { NoSessionStateError } from '../types/errors' +import * as dotenv from 'dotenv' import axios from 'axios' + jest.mock('axios') const mockedAxios = axios as jest.Mocked @@ -43,4 +45,38 @@ describe('SessionManager', () => { ).resolves.toEqual(expectedResponse) }) }) + + describe('waitForSession', () => { + it('should reject with NoSessionStateError if SAS server did not provide session state', async () => { + const responseStatus = 304 + + mockedAxios.get.mockImplementation(() => + Promise.resolve({ data: '', status: responseStatus }) + ) + + await expect( + sessionManager['waitForSession']( + { + id: 'id', + state: '', + links: [ + { rel: 'state', href: '', uri: '', type: '', method: 'GET' } + ], + attributes: { + sessionInactiveTimeout: 0 + }, + creationTimeStamp: '' + }, + null, + 'access_token' + ) + ).rejects.toEqual( + new NoSessionStateError( + responseStatus, + process.env.SERVER_URL as string, + 'logUrl' + ) + ) + }) + }) })