1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-04 19:20:05 +00:00

chore(merge): Merge branch 'master' into removed-url-package

This commit is contained in:
Saad Jutt
2021-07-16 04:12:20 +05:00
12 changed files with 181 additions and 14750 deletions

View File

@@ -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",

14770
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -41,6 +41,19 @@ So you can run the script like so:
SSH_ACCOUNT=me@my-sas-server.com DEPLOY_PATH=/var/www/html/my-folder/sasjs-tests npm run deploy
```
If you are on `WINDOWS`, you will first need to install one dependency:
```bash
npm i -g copyfiles
```
and then run to build:
```bash
npm run update:adapter && npm run build
```
when it finishes run to deploy:
```bash
scp -rp ./build/* me@my-sas-server.com:/var/www/html/my-folder/sasjs-tests
```
If you'd like to deploy just `sasjs-tests` without changing the adapter version, you can use the `deploy:tests` script, while also setting the same environment variables as above.
## 3. Creating the required SAS services

View File

@@ -23,7 +23,8 @@
"test": "react-scripts test",
"eject": "react-scripts eject",
"update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz",
"deploy:tests": "rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH",
"deploy:tests": "rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH || npm run deploy:tests-win",
"deploy:tests-win": "scp %DEPLOY_PATH% ./build/*",
"deploy": "npm run update:adapter && npm run build && npm run deploy:tests"
},
"eslintConfig": {

View File

@@ -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,7 +174,8 @@ export class SessionManager {
this.printedSessionState.printed = true
}
const state = await this.getSessionState(
const { result: state, responseStatus: responseStatus } =
await this.getSessionState(
`${this.serverUrl}${stateLink.href}?wait=30`,
etag!,
accessToken
@@ -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
})

View File

@@ -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
@@ -429,8 +429,8 @@ export class RequestClient implements HttpClient {
throw new Error('Valid JSON could not be extracted from response.')
}
isValidJson(weboutResponse)
parsedResponse = JSON.parse(weboutResponse)
const jsonResponse = isValidJson(weboutResponse)
parsedResponse = jsonResponse
} catch {
parsedResponse = response.data
}
@@ -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) {

View File

@@ -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

View File

@@ -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<typeof axios>
@@ -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'
)
)
})
})
})

View File

@@ -0,0 +1,31 @@
import { isValidJson } from '../../utils'
describe('jsonValidator', () => {
it('should not throw an error with an valid json', () => {
const json = {
test: 'test'
}
expect(isValidJson(json)).toBe(json)
})
it('should not throw an error with an valid json string', () => {
const json = {
test: 'test'
}
expect(isValidJson(JSON.stringify(json))).toStrictEqual(json)
})
it('should throw an error with an invalid json', () => {
const json = `{\"test\":\"test\"\"test2\":\"test\"}`
expect(() => {
try {
isValidJson(json)
} catch (err) {
throw new Error()
}
}).toThrowError
})
})

View File

@@ -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)
}
}

View File

@@ -5,3 +5,4 @@ export * from './JobExecutionError'
export * from './LoginRequiredError'
export * from './NotFoundError'
export * from './ErrorResponse'
export * from './NoSessionStateError'

View File

@@ -2,9 +2,11 @@
* Checks if string is in valid JSON format else throw error.
* @param str - string to check.
*/
export const isValidJson = (str: string) => {
export const isValidJson = (str: string | object) => {
try {
JSON.parse(str)
if (typeof str === 'object') return str
return JSON.parse(str)
} catch (e) {
throw new Error('Invalid JSON response.')
}