mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-03 18:50:05 +00:00
Compare commits
1 Commits
v2.12.0
...
redirected
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66061c6471 |
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -1,7 +1,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: monthly
|
||||
open-pull-requests-limit: 10
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,4 +5,4 @@ build
|
||||
|
||||
/coverage
|
||||
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
@@ -3,4 +3,3 @@ docs/
|
||||
.github/
|
||||
*.md
|
||||
*.spec.ts
|
||||
.all-contributorsrc
|
||||
|
||||
16
README.md
16
README.md
@@ -36,7 +36,7 @@ Ok ok. Deploy this [example.html](https://raw.githubusercontent.com/sasjs/adapte
|
||||
|
||||
The backend part can be deployed as follows:
|
||||
|
||||
```sas
|
||||
```
|
||||
%let appLoc=/Public/app/readme; /* Metadata or Viya Folder per SASjs config */
|
||||
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
%inc mc; /* compile macros (can also be downloaded & compiled seperately) */
|
||||
@@ -85,11 +85,11 @@ let sasJs = new SASjs.default(
|
||||
);
|
||||
```
|
||||
If you've installed it via NPM, you can import it as a default import like so:
|
||||
```js
|
||||
```
|
||||
import SASjs from '@sasjs/adapter';
|
||||
```
|
||||
You can then instantiate it with:
|
||||
```js
|
||||
```
|
||||
const sasJs = new SASjs({your config})
|
||||
```
|
||||
|
||||
@@ -119,7 +119,6 @@ sasJs.request("/path/to/my/service", dataObject)
|
||||
console.log(response.tablewith2cols1row[0].COL1.value)
|
||||
})
|
||||
```
|
||||
|
||||
We supply the path to the SAS service, and a data object. The data object can be null (for services with no input), or can contain one or more tables in the following format:
|
||||
|
||||
```javascript
|
||||
@@ -147,7 +146,6 @@ The adapter will also cache the logs (if debug enabled) and even the work tables
|
||||
The SAS side is handled by a number of macros in the [macro core](https://github.com/sasjs/core) library.
|
||||
|
||||
The following snippet shows the process of SAS tables arriving / leaving:
|
||||
|
||||
```sas
|
||||
/* fetch all input tables sent from frontend - they arrive as work tables */
|
||||
%webout(FETCH)
|
||||
@@ -163,6 +161,7 @@ run;
|
||||
%webout(OBJ,tables,fmt=N) /* unformatted (raw) data */
|
||||
%webout(OBJ,tables,label=newtable) /* rename tables on export */
|
||||
%webout(CLOSE) /* close the JSON and send some extra useful variables too */
|
||||
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -173,7 +172,6 @@ Configuration on the client side involves passing an object on startup, which ca
|
||||
* `serverType` - either `SAS9` or `SASVIYA`.
|
||||
* `serverUrl` - the location (including http protocol and port) of the SAS Server. Can be omitted, eg if serving directly from the SAS Web Server, or in streaming mode.
|
||||
* `debug` - if `true` then SAS Logs and extra debug information is returned.
|
||||
* `LoginMechanism` - either `Default` or `Redirected`. If `Redirected` then authentication occurs through the injection of an additional screen, which contains the SASLogon prompt. This allows for more complex authentication flows (such as 2FA) and avoids the need to handle passwords in the application itself. The styling of the redirect flow can also be modified. If left at "Default" then the developer must capture the username and password and use these with the `.login()` method.
|
||||
* `useComputeApi` - Only relevant when the serverType is `SASVIYA`. If `true` the [Compute API](#using-the-compute-api) is used. If `false` the [JES API](#using-the-jes-api) is used. If `null` or `undefined` the [Web](#using-jes-web-app) approach is used.
|
||||
* `contextName` - Compute context on which the requests will be called. If missing or not provided, defaults to `Job Execution Compute context`.
|
||||
|
||||
@@ -198,7 +196,7 @@ Here we are running Jobs using the Job Execution Service except this time we are
|
||||
|
||||
This approach (`useComputeApi: false`) also ensures that jobs are displayed in Environment Manager.
|
||||
|
||||
```json
|
||||
```
|
||||
{
|
||||
appLoc:"/Your/Path",
|
||||
serverType:"SASVIYA",
|
||||
@@ -212,12 +210,12 @@ This approach is by far the fastest, as a result of the optimisations we have bu
|
||||
|
||||
With this approach (`useComputeApi: true`), the requests/logs will _not_ appear in the list in Environment manager.
|
||||
|
||||
```json
|
||||
```
|
||||
{
|
||||
appLoc:"/Your/Path",
|
||||
serverType:"SASVIYA",
|
||||
useComputeApi: true,
|
||||
contextName: "yourComputeContext"
|
||||
contextName: 'yourComputeContext'
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
2144
package-lock.json
generated
2144
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -48,29 +48,29 @@
|
||||
"copyfiles": "^2.4.1",
|
||||
"cp": "^0.2.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"jest": "^27.2.0",
|
||||
"jest": "^27.1.0",
|
||||
"jest-extended": "^0.11.5",
|
||||
"node-polyfill-webpack-plugin": "^1.1.4",
|
||||
"path": "^0.12.7",
|
||||
"process": "^0.11.10",
|
||||
"rimraf": "^3.0.2",
|
||||
"semantic-release": "^17.4.7",
|
||||
"terser-webpack-plugin": "^5.2.4",
|
||||
"terser-webpack-plugin": "^5.2.0",
|
||||
"ts-jest": "^27.0.3",
|
||||
"ts-loader": "^9.2.2",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typedoc": "^0.22.3",
|
||||
"typedoc": "^0.21.9",
|
||||
"typedoc-neo-theme": "^1.1.1",
|
||||
"typedoc-plugin-external-module-name": "^4.0.6",
|
||||
"typescript": "4.3.5",
|
||||
"webpack": "^5.52.1",
|
||||
"webpack": "^5.44.0",
|
||||
"webpack-cli": "^4.7.2"
|
||||
},
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@sasjs/utils": "^2.32.0",
|
||||
"axios": "^0.21.4",
|
||||
"@sasjs/utils": "^2.30.0",
|
||||
"axios": "^0.21.1",
|
||||
"axios-cookiejar-support": "^1.0.1",
|
||||
"form-data": "^4.0.0",
|
||||
"https": "^1.0.0",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz",
|
||||
"@sasjs/test-framework": "^1.4.2",
|
||||
"@sasjs/test-framework": "^1.4.0",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/node": "^14.14.41",
|
||||
"@types/react": "^17.0.1",
|
||||
|
||||
@@ -78,8 +78,8 @@ export const basicTests = (
|
||||
'common/sendArr',
|
||||
stringData,
|
||||
undefined,
|
||||
async () => {
|
||||
await adapter.logIn(userName, password)
|
||||
() => {
|
||||
adapter.logIn(userName, password)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
98
src/FileUploader.ts
Normal file
98
src/FileUploader.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { isUrl, getValidJson, parseSasViyaDebugResponse } from './utils'
|
||||
import { UploadFile } from './types/UploadFile'
|
||||
import { ErrorResponse, LoginRequiredError } from './types/errors'
|
||||
import { RequestClient } from './request/RequestClient'
|
||||
import { ServerType } from '@sasjs/utils/types'
|
||||
import SASjs from './SASjs'
|
||||
import { Server } from 'https'
|
||||
import { SASjsConfig } from './types'
|
||||
import { config } from 'process'
|
||||
|
||||
export class FileUploader {
|
||||
constructor(
|
||||
private sasjsConfig: SASjsConfig,
|
||||
private jobsPath: string,
|
||||
private requestClient: RequestClient
|
||||
) {
|
||||
if (this.sasjsConfig.serverUrl) isUrl(this.sasjsConfig.serverUrl)
|
||||
}
|
||||
|
||||
public uploadFile(sasJob: string, files: UploadFile[], params: any) {
|
||||
if (files?.length < 1)
|
||||
return Promise.reject(
|
||||
new ErrorResponse('At least one file must be provided.')
|
||||
)
|
||||
if (!sasJob || sasJob === '')
|
||||
return Promise.reject(new ErrorResponse('sasJob must be provided.'))
|
||||
|
||||
let paramsString = ''
|
||||
|
||||
for (let param in params) {
|
||||
if (params.hasOwnProperty(param)) {
|
||||
paramsString += `&${param}=${params[param]}`
|
||||
}
|
||||
}
|
||||
|
||||
const program = this.sasjsConfig.appLoc
|
||||
? this.sasjsConfig.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
|
||||
: sasJob
|
||||
const uploadUrl = `${this.jobsPath}/?${
|
||||
'_program=' + program
|
||||
}${paramsString}`
|
||||
|
||||
const formData = new FormData()
|
||||
|
||||
for (let file of files) {
|
||||
formData.append('file', file.file, file.fileName)
|
||||
}
|
||||
|
||||
const csrfToken = this.requestClient.getCsrfToken('file')
|
||||
if (csrfToken) formData.append('_csrf', csrfToken.value)
|
||||
if (this.sasjsConfig.debug) formData.append('_debug', '131')
|
||||
if (
|
||||
this.sasjsConfig.serverType === ServerType.SasViya &&
|
||||
this.sasjsConfig.contextName
|
||||
)
|
||||
formData.append('_contextname', this.sasjsConfig.contextName)
|
||||
|
||||
const headers = {
|
||||
'cache-control': 'no-cache',
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
|
||||
// currently only web approach is supported for file upload
|
||||
// therefore log is part of response with debug enabled and must be parsed
|
||||
return this.requestClient
|
||||
.post(uploadUrl, formData, undefined, 'application/json', headers)
|
||||
.then(async (res) => {
|
||||
if (
|
||||
this.sasjsConfig.serverType === ServerType.SasViya &&
|
||||
this.sasjsConfig.debug
|
||||
) {
|
||||
const jsonResponse = await parseSasViyaDebugResponse(
|
||||
res.result as string,
|
||||
this.requestClient,
|
||||
this.sasjsConfig.serverUrl
|
||||
)
|
||||
return jsonResponse
|
||||
}
|
||||
|
||||
return typeof res.result === 'string'
|
||||
? getValidJson(res.result)
|
||||
: res.result
|
||||
|
||||
//TODO: append to SASjs requests
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
if (err instanceof LoginRequiredError) {
|
||||
return Promise.reject(
|
||||
new ErrorResponse('You must be logged in to upload a file.', err)
|
||||
)
|
||||
}
|
||||
return Promise.reject(
|
||||
new ErrorResponse('File upload request failed.', err)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -51,16 +51,6 @@ export class SASViyaApiClient {
|
||||
)
|
||||
private folderMap = new Map<string, Job[]>()
|
||||
|
||||
/**
|
||||
* A helper method used to call appendRequest method of RequestClient
|
||||
* @param response - response from sasjs request
|
||||
* @param program - name of program
|
||||
* @param debug - a boolean that indicates whether debug was enabled or not
|
||||
*/
|
||||
public appendRequest(response: any, program: string, debug: boolean) {
|
||||
this.requestClient!.appendRequest(response, program, debug)
|
||||
}
|
||||
|
||||
public get debug() {
|
||||
return this._debug
|
||||
}
|
||||
|
||||
110
src/SASjs.ts
110
src/SASjs.ts
@@ -4,14 +4,11 @@ import {
|
||||
UploadFile,
|
||||
EditContextInput,
|
||||
PollOptions,
|
||||
LoginMechanism,
|
||||
FolderMember,
|
||||
ServiceMember,
|
||||
ExecutionQuery
|
||||
LoginMechanism
|
||||
} from './types'
|
||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
||||
import { SAS9ApiClient } from './SAS9ApiClient'
|
||||
import { SASjsApiClient } from './SASjsApiClient'
|
||||
import { FileUploader } from './FileUploader'
|
||||
import { AuthManager } from './auth'
|
||||
import {
|
||||
ServerType,
|
||||
@@ -25,8 +22,7 @@ import {
|
||||
WebJobExecutor,
|
||||
ComputeJobExecutor,
|
||||
JesJobExecutor,
|
||||
Sas9JobExecutor,
|
||||
FileUploader
|
||||
Sas9JobExecutor
|
||||
} from './job-execution'
|
||||
import { ErrorResponse } from './types/errors'
|
||||
import { LoginOptions, LoginResult } from './types/Login'
|
||||
@@ -53,7 +49,6 @@ export default class SASjs {
|
||||
private jobsPath: string = ''
|
||||
private sasViyaApiClient: SASViyaApiClient | null = null
|
||||
private sas9ApiClient: SAS9ApiClient | null = null
|
||||
private SASjsApiClient: SASjsApiClient | null = null
|
||||
private fileUploader: FileUploader | null = null
|
||||
private authManager: AuthManager | null = null
|
||||
private requestClient: RequestClient | null = null
|
||||
@@ -512,7 +507,7 @@ export default class SASjs {
|
||||
...this.sasjsConfig,
|
||||
...config
|
||||
}
|
||||
this.setupConfiguration()
|
||||
await this.setupConfiguration()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -576,32 +571,24 @@ export default class SASjs {
|
||||
* Process). Is prepended at runtime with the value of `appLoc`.
|
||||
* @param files - array of files to be uploaded, including File object and file name.
|
||||
* @param params - request URL parameters.
|
||||
* @param config - provide any changes to the config here, for instance to
|
||||
* enable/disable `debug`. Any change provided will override the global config,
|
||||
* for that particular function call.
|
||||
* @param loginRequiredCallback - a function that is called if the
|
||||
* user is not logged in (eg to display a login form). The request will be
|
||||
* resubmitted after successful login.
|
||||
* @param overrideSasjsConfig - object to override existing config (optional)
|
||||
*/
|
||||
public async uploadFile(
|
||||
public uploadFile(
|
||||
sasJob: string,
|
||||
files: UploadFile[],
|
||||
params: { [key: string]: any } | null,
|
||||
config: { [key: string]: any } = {},
|
||||
loginRequiredCallback?: () => any
|
||||
params: any,
|
||||
overrideSasjsConfig?: any
|
||||
) {
|
||||
config = {
|
||||
...this.sasjsConfig,
|
||||
...config
|
||||
}
|
||||
const data = { files, params }
|
||||
const fileUploader = overrideSasjsConfig
|
||||
? new FileUploader(
|
||||
{ ...this.sasjsConfig, ...overrideSasjsConfig },
|
||||
this.jobsPath,
|
||||
this.requestClient!
|
||||
)
|
||||
: this.fileUploader ||
|
||||
new FileUploader(this.sasjsConfig, this.jobsPath, this.requestClient!)
|
||||
|
||||
return await this.fileUploader!.execute(
|
||||
sasJob,
|
||||
data,
|
||||
config,
|
||||
loginRequiredCallback
|
||||
)
|
||||
return fileUploader.uploadFile(sasJob, files, params)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -829,14 +816,6 @@ export default class SASjs {
|
||||
)
|
||||
}
|
||||
|
||||
public async deployToSASjs(members: [FolderMember, ServiceMember]) {
|
||||
return await this.SASjsApiClient?.deploy(members, this.sasjsConfig.appLoc)
|
||||
}
|
||||
|
||||
public async executeJobSASjs(query: ExecutionQuery) {
|
||||
return await this.SASjsApiClient?.executeJob(query)
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks off execution of the given job via the compute API.
|
||||
* @returns an object representing the compute session created for the given job.
|
||||
@@ -895,7 +874,6 @@ export default class SASjs {
|
||||
await this.webJobExecutor?.resendWaitingRequests()
|
||||
await this.computeJobExecutor?.resendWaitingRequests()
|
||||
await this.jesJobExecutor?.resendWaitingRequests()
|
||||
await this.fileUploader?.resendWaitingRequests()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -927,18 +905,20 @@ export default class SASjs {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* this method returns an array of SASjsRequest
|
||||
* @returns SASjsRequest[]
|
||||
*/
|
||||
public getSasRequests() {
|
||||
const requests = [...this.requestClient!.getRequests()]
|
||||
const requests = [
|
||||
...this.webJobExecutor!.getRequests(),
|
||||
...this.computeJobExecutor!.getRequests(),
|
||||
...this.jesJobExecutor!.getRequests()
|
||||
]
|
||||
const sortedRequests = requests.sort(compareTimestamps)
|
||||
return sortedRequests
|
||||
}
|
||||
|
||||
public clearSasRequests() {
|
||||
this.requestClient!.clearRequests()
|
||||
this.webJobExecutor!.clearRequests()
|
||||
this.computeJobExecutor!.clearRequests()
|
||||
this.jesJobExecutor!.clearRequests()
|
||||
}
|
||||
|
||||
private setupConfiguration() {
|
||||
@@ -961,17 +941,10 @@ export default class SASjs {
|
||||
this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1)
|
||||
}
|
||||
|
||||
if (!this.requestClient) {
|
||||
this.requestClient = new RequestClient(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.sasjsConfig.allowInsecureRequests
|
||||
)
|
||||
} else {
|
||||
this.requestClient.setConfig(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.sasjsConfig.allowInsecureRequests
|
||||
)
|
||||
}
|
||||
this.requestClient = new RequestClient(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.sasjsConfig.allowInsecureRequests
|
||||
)
|
||||
|
||||
this.jobsPath =
|
||||
this.sasjsConfig.serverType === ServerType.SasViya
|
||||
@@ -986,49 +959,34 @@ export default class SASjs {
|
||||
)
|
||||
|
||||
if (this.sasjsConfig.serverType === ServerType.SasViya) {
|
||||
if (this.sasViyaApiClient) {
|
||||
if (this.sasViyaApiClient)
|
||||
this.sasViyaApiClient!.setConfig(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.sasjsConfig.appLoc
|
||||
)
|
||||
} else {
|
||||
else
|
||||
this.sasViyaApiClient = new SASViyaApiClient(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.sasjsConfig.appLoc,
|
||||
this.sasjsConfig.contextName,
|
||||
this.requestClient
|
||||
)
|
||||
}
|
||||
|
||||
this.sasViyaApiClient.debug = this.sasjsConfig.debug
|
||||
}
|
||||
|
||||
if (this.sasjsConfig.serverType === ServerType.Sas9) {
|
||||
if (this.sas9ApiClient) {
|
||||
if (this.sas9ApiClient)
|
||||
this.sas9ApiClient!.setConfig(this.sasjsConfig.serverUrl)
|
||||
} else {
|
||||
else
|
||||
this.sas9ApiClient = new SAS9ApiClient(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.jobsPath,
|
||||
this.sasjsConfig.allowInsecureRequests
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.sasjsConfig.serverType === ServerType.Sasjs) {
|
||||
if (this.SASjsApiClient) {
|
||||
this.SASjsApiClient.setConfig(this.sasjsConfig.serverUrl)
|
||||
} else {
|
||||
this.SASjsApiClient = new SASjsApiClient(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.requestClient
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
this.fileUploader = new FileUploader(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.sasjsConfig.serverType!,
|
||||
this.sasjsConfig,
|
||||
this.jobsPath,
|
||||
this.requestClient
|
||||
)
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { FolderMember, ServiceMember, ExecutionQuery } from './types'
|
||||
import { RequestClient } from './request/RequestClient'
|
||||
|
||||
export class SASjsApiClient {
|
||||
constructor(
|
||||
private serverUrl: string,
|
||||
private requestClient: RequestClient
|
||||
) {}
|
||||
|
||||
public setConfig(serverUrl: string) {
|
||||
if (serverUrl) this.serverUrl = serverUrl
|
||||
}
|
||||
|
||||
public async deploy(members: [FolderMember, ServiceMember], appLoc: string) {
|
||||
const { result } = await this.requestClient.post<{
|
||||
status: string
|
||||
message: string
|
||||
example?: {}
|
||||
}>(
|
||||
'SASjsApi/drive/deploy',
|
||||
{ fileTree: members, appLoc: appLoc },
|
||||
undefined
|
||||
)
|
||||
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
|
||||
public async executeJob(query: ExecutionQuery) {
|
||||
const { result } = await this.requestClient.post<{
|
||||
status: string
|
||||
message: string
|
||||
log?: string
|
||||
logPath?: string
|
||||
error?: {}
|
||||
}>('SASjsApi/stp/execute', query, undefined)
|
||||
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
}
|
||||
@@ -256,8 +256,6 @@ export class AuthManager {
|
||||
.split(' ')
|
||||
.map((name: string) => name.slice(0, 3).toLowerCase())
|
||||
.join('')
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,16 +35,19 @@ export class ComputeJobExecutor extends BaseJobExecutor {
|
||||
expectWebout
|
||||
)
|
||||
.then((response) => {
|
||||
this.sasViyaApiClient.appendRequest(response, sasJob, config.debug)
|
||||
this.appendRequest(response, sasJob, config.debug)
|
||||
|
||||
resolve(response.result)
|
||||
})
|
||||
.catch(async (e: Error) => {
|
||||
if (e instanceof ComputeJobExecutionError) {
|
||||
this.sasViyaApiClient.appendRequest(e, sasJob, config.debug)
|
||||
this.appendRequest(e, sasJob, config.debug)
|
||||
|
||||
reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
|
||||
if (e instanceof LoginRequiredError) {
|
||||
await loginCallback()
|
||||
this.appendWaitingRequest(() => {
|
||||
return this.execute(
|
||||
sasJob,
|
||||
@@ -60,8 +63,6 @@ export class ComputeJobExecutor extends BaseJobExecutor {
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
await loginCallback()
|
||||
} else {
|
||||
reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
import {
|
||||
getValidJson,
|
||||
parseSasViyaDebugResponse,
|
||||
parseWeboutResponse
|
||||
} from '../utils'
|
||||
import { UploadFile } from '../types/UploadFile'
|
||||
import {
|
||||
ErrorResponse,
|
||||
JobExecutionError,
|
||||
LoginRequiredError
|
||||
} from '../types/errors'
|
||||
import { RequestClient } from '../request/RequestClient'
|
||||
import { ServerType } from '@sasjs/utils/types'
|
||||
import { BaseJobExecutor } from './JobExecutor'
|
||||
|
||||
interface dataFileUpload {
|
||||
files: UploadFile[]
|
||||
params: { [key: string]: any } | null
|
||||
}
|
||||
|
||||
export class FileUploader extends BaseJobExecutor {
|
||||
constructor(
|
||||
serverUrl: string,
|
||||
serverType: ServerType,
|
||||
private jobsPath: string,
|
||||
private requestClient: RequestClient
|
||||
) {
|
||||
super(serverUrl, serverType)
|
||||
}
|
||||
|
||||
public async execute(
|
||||
sasJob: string,
|
||||
data: any,
|
||||
config: any,
|
||||
loginRequiredCallback?: any
|
||||
) {
|
||||
const { files, params }: dataFileUpload = data
|
||||
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
||||
|
||||
if (!files?.length)
|
||||
throw new ErrorResponse('At least one file must be provided.')
|
||||
|
||||
if (!sasJob || sasJob === '')
|
||||
throw new ErrorResponse('sasJob must be provided.')
|
||||
|
||||
let paramsString = ''
|
||||
|
||||
for (let param in params)
|
||||
if (params.hasOwnProperty(param))
|
||||
paramsString += `&${param}=${params[param]}`
|
||||
|
||||
const program = config.appLoc
|
||||
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
|
||||
: sasJob
|
||||
const uploadUrl = `${this.jobsPath}/?${
|
||||
'_program=' + program
|
||||
}${paramsString}`
|
||||
|
||||
const formData = new FormData()
|
||||
|
||||
for (let file of files) {
|
||||
formData.append('file', file.file, file.fileName)
|
||||
}
|
||||
|
||||
const csrfToken = this.requestClient.getCsrfToken('file')
|
||||
if (csrfToken) formData.append('_csrf', csrfToken.value)
|
||||
if (config.debug) formData.append('_debug', '131')
|
||||
if (config.serverType === ServerType.SasViya && config.contextName)
|
||||
formData.append('_contextname', config.contextName)
|
||||
|
||||
const headers = {
|
||||
'cache-control': 'no-cache',
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
|
||||
// currently only web approach is supported for file upload
|
||||
// therefore log is part of response with debug enabled and must be parsed
|
||||
const requestPromise = new Promise((resolve, reject) => {
|
||||
this.requestClient
|
||||
.post(uploadUrl, formData, undefined, 'application/json', headers)
|
||||
.then(async (res: any) => {
|
||||
this.requestClient.appendRequest(res, sasJob, config.debug)
|
||||
|
||||
let jsonResponse = res.result
|
||||
|
||||
if (config.debug) {
|
||||
switch (this.serverType) {
|
||||
case ServerType.SasViya:
|
||||
jsonResponse = await parseSasViyaDebugResponse(
|
||||
res.result,
|
||||
this.requestClient,
|
||||
config.serverUrl
|
||||
)
|
||||
break
|
||||
case ServerType.Sas9:
|
||||
jsonResponse =
|
||||
typeof res.result === 'string'
|
||||
? parseWeboutResponse(res.result, uploadUrl)
|
||||
: res.result
|
||||
break
|
||||
}
|
||||
} else {
|
||||
jsonResponse =
|
||||
typeof res.result === 'string'
|
||||
? getValidJson(res.result)
|
||||
: res.result
|
||||
}
|
||||
|
||||
resolve(jsonResponse)
|
||||
})
|
||||
.catch(async (e: Error) => {
|
||||
if (e instanceof JobExecutionError) {
|
||||
this.requestClient!.appendRequest(e, sasJob, config.debug)
|
||||
reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
|
||||
if (e instanceof LoginRequiredError) {
|
||||
this.appendWaitingRequest(() => {
|
||||
return this.execute(
|
||||
sasJob,
|
||||
data,
|
||||
config,
|
||||
loginRequiredCallback
|
||||
).then(
|
||||
(res: any) => {
|
||||
resolve(res)
|
||||
},
|
||||
(err: any) => {
|
||||
reject(err)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
await loginCallback()
|
||||
} else {
|
||||
reject(new ErrorResponse('File upload request failed.', e))
|
||||
}
|
||||
})
|
||||
})
|
||||
return requestPromise
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ export class JesJobExecutor extends BaseJobExecutor {
|
||||
this.sasViyaApiClient
|
||||
?.executeJob(sasJob, config.contextName, config.debug, data, authConfig)
|
||||
.then((response: any) => {
|
||||
this.sasViyaApiClient.appendRequest(response, sasJob, config.debug)
|
||||
this.appendRequest(response, sasJob, config.debug)
|
||||
|
||||
const responseObject = appendExtraResponseAttributes(
|
||||
response,
|
||||
@@ -39,12 +39,14 @@ export class JesJobExecutor extends BaseJobExecutor {
|
||||
})
|
||||
.catch(async (e: Error) => {
|
||||
if (e instanceof JobExecutionError) {
|
||||
this.sasViyaApiClient.appendRequest(e, sasJob, config.debug)
|
||||
this.appendRequest(e, sasJob, config.debug)
|
||||
|
||||
reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
|
||||
if (e instanceof LoginRequiredError) {
|
||||
await loginCallback()
|
||||
|
||||
this.appendWaitingRequest(() => {
|
||||
return this.execute(
|
||||
sasJob,
|
||||
@@ -62,8 +64,6 @@ export class JesJobExecutor extends BaseJobExecutor {
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
await loginCallback()
|
||||
} else {
|
||||
reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AuthConfig, ServerType } from '@sasjs/utils/types'
|
||||
import { SASjsRequest } from '../types'
|
||||
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
||||
import { asyncForEach } from '../utils'
|
||||
import { asyncForEach, parseGeneratedCode, parseSourceCode } from '../utils'
|
||||
|
||||
export type ExecuteFunction = () => Promise<any>
|
||||
|
||||
@@ -14,12 +15,15 @@ export interface JobExecutor {
|
||||
extraResponseAttributes?: ExtraResponseAttributes[]
|
||||
) => Promise<any>
|
||||
resendWaitingRequests: () => Promise<void>
|
||||
getRequests: () => SASjsRequest[]
|
||||
clearRequests: () => void
|
||||
}
|
||||
|
||||
export abstract class BaseJobExecutor implements JobExecutor {
|
||||
constructor(protected serverUrl: string, protected serverType: ServerType) {}
|
||||
|
||||
private waitingRequests: ExecuteFunction[] = []
|
||||
private requests: SASjsRequest[] = []
|
||||
|
||||
abstract execute(
|
||||
sasJob: string,
|
||||
@@ -42,7 +46,54 @@ export abstract class BaseJobExecutor implements JobExecutor {
|
||||
return
|
||||
}
|
||||
|
||||
getRequests = () => this.requests
|
||||
|
||||
clearRequests = () => {
|
||||
this.requests = []
|
||||
}
|
||||
|
||||
protected appendWaitingRequest(request: ExecuteFunction) {
|
||||
this.waitingRequests.push(request)
|
||||
}
|
||||
|
||||
protected appendRequest(response: any, program: string, debug: boolean) {
|
||||
let sourceCode = ''
|
||||
let generatedCode = ''
|
||||
let sasWork = null
|
||||
|
||||
if (debug) {
|
||||
if (response?.log) {
|
||||
sourceCode = parseSourceCode(response.log)
|
||||
generatedCode = parseGeneratedCode(response.log)
|
||||
|
||||
if (response?.result) {
|
||||
sasWork = response.result.WORK
|
||||
} else {
|
||||
sasWork = response.log
|
||||
}
|
||||
} else if (response?.result) {
|
||||
sourceCode = parseSourceCode(response.result)
|
||||
generatedCode = parseGeneratedCode(response.result)
|
||||
sasWork = response.result.WORK
|
||||
}
|
||||
}
|
||||
|
||||
const stringifiedResult =
|
||||
typeof response?.result === 'string'
|
||||
? response?.result
|
||||
: JSON.stringify(response?.result, null, 2)
|
||||
|
||||
this.requests.push({
|
||||
logFile: response?.log || stringifiedResult || response,
|
||||
serviceLink: program,
|
||||
timestamp: new Date(),
|
||||
sourceCode,
|
||||
generatedCode,
|
||||
SASWORK: sasWork
|
||||
})
|
||||
|
||||
if (this.requests.length > 20) {
|
||||
this.requests.splice(0, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export class Sas9JobExecutor extends BaseJobExecutor {
|
||||
if (data) {
|
||||
try {
|
||||
formData = generateFileUploadForm(formData, data)
|
||||
} catch (e: any) {
|
||||
} catch (e) {
|
||||
return Promise.reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,36 +53,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
let apiUrl = `${config.serverUrl}${this.jobsPath}/?${'_program=' + program}`
|
||||
|
||||
if (config.serverType === ServerType.SasViya) {
|
||||
let jobUri
|
||||
try {
|
||||
jobUri = await this.getJobUri(sasJob)
|
||||
} catch (e: any) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (e instanceof LoginRequiredError) {
|
||||
this.appendWaitingRequest(() => {
|
||||
return this.execute(
|
||||
sasJob,
|
||||
data,
|
||||
config,
|
||||
loginRequiredCallback,
|
||||
authConfig,
|
||||
extraResponseAttributes
|
||||
).then(
|
||||
(res: any) => {
|
||||
resolve(res)
|
||||
},
|
||||
(err: any) => {
|
||||
reject(err)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
await loginCallback()
|
||||
} else {
|
||||
reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
})
|
||||
}
|
||||
const jobUri = await this.getJobUri(sasJob)
|
||||
|
||||
apiUrl += jobUri.length > 0 ? '&_job=' + jobUri : ''
|
||||
|
||||
@@ -119,7 +90,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
// file upload approach
|
||||
try {
|
||||
formData = generateFileUploadForm(formData, data)
|
||||
} catch (e: any) {
|
||||
} catch (e) {
|
||||
return Promise.reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
} else {
|
||||
@@ -129,7 +100,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
generateTableUploadForm(formData, data)
|
||||
formData = newFormData
|
||||
requestParams = { ...requestParams, ...params }
|
||||
} catch (e: any) {
|
||||
} catch (e) {
|
||||
return Promise.reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
}
|
||||
@@ -144,8 +115,6 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
const requestPromise = new Promise((resolve, reject) => {
|
||||
this.requestClient!.post(apiUrl, formData, undefined)
|
||||
.then(async (res: any) => {
|
||||
this.requestClient!.appendRequest(res, sasJob, config.debug)
|
||||
|
||||
let jsonResponse = res.result
|
||||
|
||||
if (config.debug) {
|
||||
@@ -166,6 +135,8 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
this.appendRequest(res, sasJob, config.debug)
|
||||
|
||||
const responseObject = appendExtraResponseAttributes(
|
||||
{ result: jsonResponse },
|
||||
extraResponseAttributes
|
||||
@@ -174,7 +145,8 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
})
|
||||
.catch(async (e: Error) => {
|
||||
if (e instanceof JobExecutionError) {
|
||||
this.requestClient!.appendRequest(e, sasJob, config.debug)
|
||||
this.appendRequest(e, sasJob, config.debug)
|
||||
|
||||
reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
|
||||
|
||||
@@ -3,4 +3,3 @@ export * from './JesJobExecutor'
|
||||
export * from './JobExecutor'
|
||||
export * from './Sas9JobExecutor'
|
||||
export * from './WebJobExecutor'
|
||||
export * from './FileUploader'
|
||||
|
||||
@@ -8,11 +8,10 @@ import {
|
||||
InternalServerError,
|
||||
JobExecutionError
|
||||
} from '../types/errors'
|
||||
import { SASjsRequest } from '../types'
|
||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||
import { prefixMessage } from '@sasjs/utils/error'
|
||||
import { SAS9AuthError } from '../types/errors/SAS9AuthError'
|
||||
import { parseGeneratedCode, parseSourceCode } from '../utils'
|
||||
import { getValidJson } from '../utils'
|
||||
|
||||
export interface HttpClient {
|
||||
get<T>(
|
||||
@@ -48,18 +47,27 @@ export interface HttpClient {
|
||||
}
|
||||
|
||||
export class RequestClient implements HttpClient {
|
||||
private requests: SASjsRequest[] = []
|
||||
|
||||
protected csrfToken: CsrfToken = { headerName: '', value: '' }
|
||||
protected fileUploadCsrfToken: CsrfToken | undefined
|
||||
protected httpClient!: AxiosInstance
|
||||
protected httpClient: AxiosInstance
|
||||
|
||||
constructor(protected baseUrl: string, allowInsecure = false) {
|
||||
this.createHttpClient(baseUrl, allowInsecure)
|
||||
}
|
||||
const https = require('https')
|
||||
if (allowInsecure && https.Agent) {
|
||||
this.httpClient = axios.create({
|
||||
baseURL: baseUrl,
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: !allowInsecure
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.httpClient = axios.create({
|
||||
baseURL: baseUrl
|
||||
})
|
||||
}
|
||||
|
||||
public setConfig(baseUrl: string, allowInsecure = false) {
|
||||
this.createHttpClient(baseUrl, allowInsecure)
|
||||
this.httpClient.defaults.validateStatus = (status) =>
|
||||
status >= 200 && status < 305
|
||||
}
|
||||
|
||||
public getCsrfToken(type: 'general' | 'file' = 'general') {
|
||||
@@ -75,66 +83,6 @@ export class RequestClient implements HttpClient {
|
||||
return this.httpClient.defaults.baseURL || ''
|
||||
}
|
||||
|
||||
/**
|
||||
* this method returns all requests, an array of SASjsRequest type
|
||||
* @returns SASjsRequest[]
|
||||
*/
|
||||
public getRequests = () => this.requests
|
||||
|
||||
/**
|
||||
* this method clears the requests array, i.e set to empty
|
||||
*/
|
||||
public clearRequests = () => {
|
||||
this.requests = []
|
||||
}
|
||||
|
||||
/**
|
||||
* this method appends the response from sasjs request to requests array
|
||||
* @param response - response from sasjs request
|
||||
* @param program - name of program
|
||||
* @param debug - a boolean that indicates whether debug was enabled or not
|
||||
*/
|
||||
public appendRequest(response: any, program: string, debug: boolean) {
|
||||
let sourceCode = ''
|
||||
let generatedCode = ''
|
||||
let sasWork = null
|
||||
|
||||
if (debug) {
|
||||
if (response?.log) {
|
||||
sourceCode = parseSourceCode(response.log)
|
||||
generatedCode = parseGeneratedCode(response.log)
|
||||
|
||||
if (response?.result) {
|
||||
sasWork = response.result.WORK
|
||||
} else {
|
||||
sasWork = response.log
|
||||
}
|
||||
} else if (response?.result) {
|
||||
sourceCode = parseSourceCode(response.result)
|
||||
generatedCode = parseGeneratedCode(response.result)
|
||||
sasWork = response.result.WORK
|
||||
}
|
||||
}
|
||||
|
||||
const stringifiedResult =
|
||||
typeof response?.result === 'string'
|
||||
? response?.result
|
||||
: JSON.stringify(response?.result, null, 2)
|
||||
|
||||
this.requests.push({
|
||||
logFile: response?.log || stringifiedResult || response,
|
||||
serviceLink: program,
|
||||
timestamp: new Date(),
|
||||
sourceCode,
|
||||
generatedCode,
|
||||
SASWORK: sasWork
|
||||
})
|
||||
|
||||
if (this.requests.length > 20) {
|
||||
this.requests.splice(0, 1)
|
||||
}
|
||||
}
|
||||
|
||||
public async get<T>(
|
||||
url: string,
|
||||
accessToken: string | undefined,
|
||||
@@ -182,7 +130,7 @@ export class RequestClient implements HttpClient {
|
||||
})
|
||||
}
|
||||
|
||||
public async post<T>(
|
||||
public post<T>(
|
||||
url: string,
|
||||
data: any,
|
||||
accessToken: string | undefined,
|
||||
@@ -281,16 +229,12 @@ export class RequestClient implements HttpClient {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.httpClient.post(url, content, {
|
||||
headers,
|
||||
transformRequest: (requestBody) => requestBody
|
||||
})
|
||||
|
||||
const response = await this.httpClient.post(url, content, { headers })
|
||||
return {
|
||||
result: response.data,
|
||||
etag: response.headers['etag'] as string
|
||||
}
|
||||
} catch (e: any) {
|
||||
} catch (e) {
|
||||
const response = e.response as AxiosResponse
|
||||
if (response?.status === 403 || response?.status === 449) {
|
||||
this.parseAndSetFileUploadCsrfToken(response)
|
||||
@@ -510,25 +454,6 @@ export class RequestClient implements HttpClient {
|
||||
|
||||
return responseToReturn
|
||||
}
|
||||
|
||||
private createHttpClient(baseUrl: string, allowInsecure = false) {
|
||||
const https = require('https')
|
||||
if (allowInsecure && https.Agent) {
|
||||
this.httpClient = axios.create({
|
||||
baseURL: baseUrl,
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: !allowInsecure
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.httpClient = axios.create({
|
||||
baseURL: baseUrl
|
||||
})
|
||||
}
|
||||
|
||||
this.httpClient.defaults.validateStatus = (status) =>
|
||||
status >= 200 && status < 401
|
||||
}
|
||||
}
|
||||
|
||||
export const throwIfError = (response: AxiosResponse) => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { FileUploader } from '../job-execution/FileUploader'
|
||||
import { FileUploader } from '../FileUploader'
|
||||
import { SASjsConfig, UploadFile } from '../types'
|
||||
import { RequestClient } from '../request/RequestClient'
|
||||
import axios from 'axios'
|
||||
@@ -34,71 +34,60 @@ const prepareFilesAndParams = () => {
|
||||
describe('FileUploader', () => {
|
||||
const config: SASjsConfig = {
|
||||
...new SASjsConfig(),
|
||||
appLoc: '/sample/apploc',
|
||||
debug: false
|
||||
appLoc: '/sample/apploc'
|
||||
}
|
||||
|
||||
const fileUploader = new FileUploader(
|
||||
config.serverUrl,
|
||||
config.serverType!,
|
||||
config,
|
||||
'/jobs/path',
|
||||
new RequestClient('https://sample.server.com')
|
||||
)
|
||||
|
||||
it('should upload successfully', async () => {
|
||||
const sasJob = 'test/upload'
|
||||
const data = prepareFilesAndParams()
|
||||
const { files, params } = prepareFilesAndParams()
|
||||
mockedAxios.post.mockImplementation(() =>
|
||||
Promise.resolve({ data: sampleResponse })
|
||||
)
|
||||
|
||||
const res = await fileUploader.execute(sasJob, data, config)
|
||||
const res = await fileUploader.uploadFile(sasJob, files, params)
|
||||
|
||||
expect(res).toEqual(JSON.parse(sampleResponse))
|
||||
})
|
||||
|
||||
it('should upload successfully when login is required', async () => {
|
||||
mockedAxios.post
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ data: '<form action="Logon">' })
|
||||
)
|
||||
.mockImplementationOnce(() => Promise.resolve({ data: sampleResponse }))
|
||||
|
||||
const loginCallback = jest.fn().mockImplementation(async () => {
|
||||
await fileUploader.resendWaitingRequests()
|
||||
Promise.resolve()
|
||||
})
|
||||
|
||||
const sasJob = 'test'
|
||||
const data = prepareFilesAndParams()
|
||||
|
||||
const res = await fileUploader.execute(sasJob, data, config, loginCallback)
|
||||
|
||||
expect(res).toEqual(JSON.parse(sampleResponse))
|
||||
|
||||
expect(mockedAxios.post).toHaveBeenCalledTimes(2)
|
||||
expect(loginCallback).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should an error when no files are provided', async () => {
|
||||
const sasJob = 'test/upload'
|
||||
const files: UploadFile[] = []
|
||||
const params = { table: 'libtable' }
|
||||
|
||||
const res: any = await fileUploader
|
||||
.execute(sasJob, files, params, config)
|
||||
const err = await fileUploader
|
||||
.uploadFile(sasJob, files, params)
|
||||
.catch((err: any) => err)
|
||||
expect(res.error.message).toEqual('At least one file must be provided.')
|
||||
expect(err.error.message).toEqual('At least one file must be provided.')
|
||||
})
|
||||
|
||||
it('should throw an error when no sasJob is provided', async () => {
|
||||
const sasJob = ''
|
||||
const data = prepareFilesAndParams()
|
||||
const { files, params } = prepareFilesAndParams()
|
||||
|
||||
const res: any = await fileUploader
|
||||
.execute(sasJob, data, config)
|
||||
const err = await fileUploader
|
||||
.uploadFile(sasJob, files, params)
|
||||
.catch((err: any) => err)
|
||||
expect(res.error.message).toEqual('sasJob must be provided.')
|
||||
expect(err.error.message).toEqual('sasJob must be provided.')
|
||||
})
|
||||
|
||||
it('should throw an error when login is required', async () => {
|
||||
mockedAxios.post.mockImplementation(() =>
|
||||
Promise.resolve({ data: '<form action="Logon">' })
|
||||
)
|
||||
|
||||
const sasJob = 'test'
|
||||
const { files, params } = prepareFilesAndParams()
|
||||
|
||||
const err = await fileUploader
|
||||
.uploadFile(sasJob, files, params)
|
||||
.catch((err: any) => err)
|
||||
expect(err.error.message).toEqual('You must be logged in to upload a file.')
|
||||
})
|
||||
|
||||
it('should throw an error when invalid JSON is returned by the server', async () => {
|
||||
@@ -107,13 +96,12 @@ describe('FileUploader', () => {
|
||||
)
|
||||
|
||||
const sasJob = 'test'
|
||||
const data = prepareFilesAndParams()
|
||||
const { files, params } = prepareFilesAndParams()
|
||||
|
||||
const res: any = await fileUploader
|
||||
.execute(sasJob, data, config)
|
||||
const err = await fileUploader
|
||||
.uploadFile(sasJob, files, params)
|
||||
.catch((err: any) => err)
|
||||
|
||||
expect(res.error.message).toEqual('File upload request failed.')
|
||||
expect(err.error.message).toEqual('File upload request failed.')
|
||||
})
|
||||
|
||||
it('should throw an error when the server request fails', async () => {
|
||||
@@ -122,11 +110,11 @@ describe('FileUploader', () => {
|
||||
)
|
||||
|
||||
const sasJob = 'test'
|
||||
const data = prepareFilesAndParams()
|
||||
const { files, params } = prepareFilesAndParams()
|
||||
|
||||
const res: any = await fileUploader
|
||||
.execute(sasJob, data, config)
|
||||
const err = await fileUploader
|
||||
.uploadFile(sasJob, files, params)
|
||||
.catch((err: any) => err)
|
||||
expect(res.error.message).toEqual('File upload request failed.')
|
||||
expect(err.error.message).toEqual('File upload request failed.')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -33,18 +33,4 @@ describe('jsonValidator', () => {
|
||||
}
|
||||
expect(test).toThrow(JsonParseArrayError)
|
||||
})
|
||||
|
||||
it('should throw an error when null is passed', () => {
|
||||
const test = () => {
|
||||
getValidJson(null as any)
|
||||
}
|
||||
expect(test).toThrow(InvalidJsonError)
|
||||
})
|
||||
|
||||
it('should throw an error when undefined is passed', () => {
|
||||
const test = () => {
|
||||
getValidJson(undefined as any)
|
||||
}
|
||||
expect(test).toThrow(InvalidJsonError)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export interface ExecutionQuery {
|
||||
_program: string
|
||||
_debug?: number
|
||||
_log?: boolean
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
export interface FileTree {
|
||||
members: [FolderMember, ServiceMember]
|
||||
}
|
||||
|
||||
export enum MemberType {
|
||||
folder = 'folder',
|
||||
service = 'service'
|
||||
}
|
||||
|
||||
export interface FolderMember {
|
||||
name: string
|
||||
type: MemberType.folder
|
||||
members: [FolderMember, ServiceMember]
|
||||
}
|
||||
|
||||
export interface ServiceMember {
|
||||
name: string
|
||||
type: MemberType.service
|
||||
code: string
|
||||
}
|
||||
|
||||
export const isFileTree = (arg: any): arg is FileTree =>
|
||||
arg &&
|
||||
arg.members &&
|
||||
Array.isArray(arg.members) &&
|
||||
arg.members.filter(
|
||||
(member: FolderMember | ServiceMember) =>
|
||||
!isFolderMember(member) && !isServiceMember(member)
|
||||
).length === 0
|
||||
|
||||
const isFolderMember = (arg: any): arg is FolderMember =>
|
||||
arg &&
|
||||
typeof arg.name === 'string' &&
|
||||
arg.type === MemberType.folder &&
|
||||
arg.members &&
|
||||
Array.isArray(arg.members) &&
|
||||
arg.members.filter(
|
||||
(member: FolderMember | ServiceMember) =>
|
||||
!isFolderMember(member) && !isServiceMember(member)
|
||||
).length === 0
|
||||
|
||||
const isServiceMember = (arg: any): arg is ServiceMember =>
|
||||
arg &&
|
||||
typeof arg.name === 'string' &&
|
||||
arg.type === MemberType.service &&
|
||||
arg.code &&
|
||||
typeof arg.code === 'string'
|
||||
@@ -12,5 +12,3 @@ export * from './Session'
|
||||
export * from './UploadFile'
|
||||
export * from './PollOptions'
|
||||
export * from './WriteStream'
|
||||
export * from './FileTree'
|
||||
export * from './ExecuteScript'
|
||||
|
||||
@@ -6,8 +6,6 @@ import { JsonParseArrayError, InvalidJsonError } from '../types/errors'
|
||||
*/
|
||||
export const getValidJson = (str: string | object) => {
|
||||
try {
|
||||
if (str === null || str === undefined) throw new InvalidJsonError()
|
||||
|
||||
if (Array.isArray(str)) throw new JsonParseArrayError()
|
||||
|
||||
if (typeof str === 'object') return str
|
||||
|
||||
@@ -5,11 +5,6 @@ enum domIDs {
|
||||
}
|
||||
const cssPrefix = 'sasjs-adapter'
|
||||
|
||||
const classes = {
|
||||
popUp: `${cssPrefix}popUp`,
|
||||
popUpBG: `${cssPrefix}popUpBG`
|
||||
}
|
||||
|
||||
export const openLoginPrompt = (): Promise<boolean> => {
|
||||
return new Promise(async (resolve) => {
|
||||
const style = document.createElement('style')
|
||||
@@ -18,11 +13,11 @@ export const openLoginPrompt = (): Promise<boolean> => {
|
||||
|
||||
const loginPromptBG = document.createElement('div')
|
||||
loginPromptBG.id = domIDs.overlay
|
||||
loginPromptBG.classList.add(classes.popUpBG)
|
||||
loginPromptBG.classList.add(`${cssPrefix}popUpBG`)
|
||||
|
||||
const loginPrompt = document.createElement('div')
|
||||
loginPrompt.id = domIDs.dialog
|
||||
loginPrompt.classList.add(classes.popUp)
|
||||
loginPrompt.classList.add(`${cssPrefix}popUp`)
|
||||
|
||||
const title = document.createElement('h1')
|
||||
title.innerText = 'Session Expired!'
|
||||
@@ -69,11 +64,11 @@ const closeLoginPrompt = () => {
|
||||
}
|
||||
|
||||
const cssContent = `
|
||||
.${classes.popUpBG} ,
|
||||
.${classes.popUp} {
|
||||
.${cssPrefix}popUpBG ,
|
||||
.${cssPrefix}popUp {
|
||||
z-index: 10000;
|
||||
}
|
||||
.${classes.popUp} {
|
||||
.${cssPrefix}popUp {
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
@@ -96,7 +91,7 @@ const cssContent = `
|
||||
max-height: 300px;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.${classes.popUp} > h1 {
|
||||
.${cssPrefix}popUp > h1 {
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
@@ -111,7 +106,7 @@ const cssContent = `
|
||||
border-width: 5px;
|
||||
border-color: black;
|
||||
}
|
||||
.${classes.popUp} > div {
|
||||
.${cssPrefix}popUp > div {
|
||||
width: 100%;
|
||||
height: calc(100% -108px);
|
||||
margin: 0;
|
||||
@@ -126,7 +121,7 @@ const cssContent = `
|
||||
border-style: none none solid none;
|
||||
overflow: auto;
|
||||
}
|
||||
.${classes.popUp} > div > span {
|
||||
.${cssPrefix}popUp > div > span {
|
||||
display: table-cell;
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
@@ -138,13 +133,13 @@ const cssContent = `
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
}
|
||||
.${classes.popUp} .cancel {
|
||||
.${cssPrefix}popUp .cancel {
|
||||
float: left;
|
||||
}
|
||||
.${classes.popUp} .confirm {
|
||||
.${cssPrefix}popUp .confirm {
|
||||
float: right;
|
||||
}
|
||||
.${classes.popUp} > button {
|
||||
.${cssPrefix}popUp > button {
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
@@ -158,10 +153,10 @@ const cssContent = `
|
||||
height: 50px;
|
||||
background: rgba(1, 1, 1, 0.2);
|
||||
}
|
||||
.${classes.popUp} > button:hover {
|
||||
.${cssPrefix}popUp > button:hover {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.${classes.popUpBG} {
|
||||
.${cssPrefix}popUpBG {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
@@ -25,6 +25,6 @@ export const parseSasViyaDebugResponse = async (
|
||||
}
|
||||
|
||||
return requestClient
|
||||
.get(serverUrl + jsonUrl, undefined, 'text/plain')
|
||||
.get(serverUrl + jsonUrl, undefined)
|
||||
.then((res: any) => getValidJson(res.result))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user