1
0
mirror of https://github.com/sasjs/adapter.git synced 2025-12-10 08:54:35 +00:00

chore(*): change code style to use single quote

This commit is contained in:
Yury Shkoda
2020-09-01 14:28:15 +03:00
parent 82b14fad14
commit c626c57662
28 changed files with 404 additions and 404 deletions

View File

@@ -2,5 +2,5 @@
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": false
"singleQuote": true
}

View File

@@ -1,6 +1,6 @@
import { isLogInRequired, needsRetry } from "./utils"
import { CsrfToken } from "./types/CsrfToken"
import { UploadFile } from "./types/UploadFile"
import { isLogInRequired, needsRetry } from './utils'
import { CsrfToken } from './types/CsrfToken'
import { UploadFile } from './types/UploadFile'
const requestRetryLimit = 5
@@ -15,9 +15,9 @@ export class FileUploader {
private retryCount = 0
public uploadFile(sasJob: string, files: UploadFile[], params: any) {
if (files?.length < 1) throw new Error("Atleast one file must be provided")
if (files?.length < 1) throw new Error('Atleast one file must be provided')
let paramsString = ""
let paramsString = ''
for (let param in params) {
if (params.hasOwnProperty(param)) {
@@ -26,41 +26,41 @@ export class FileUploader {
}
const program = this.appLoc
? this.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "")
? this.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
: sasJob
const uploadUrl = `${this.serverUrl}${this.jobsPath}/?${
"_program=" + program
'_program=' + program
}${paramsString}`
const headers = {
"cache-control": "no-cache"
'cache-control': 'no-cache'
}
return new Promise((resolve, reject) => {
const formData = new FormData()
for (let file of files) {
formData.append("file", file.file, file.fileName)
formData.append('file', file.file, file.fileName)
}
if (this.csrfToken) formData.append("_csrf", this.csrfToken.value)
if (this.csrfToken) formData.append('_csrf', this.csrfToken.value)
fetch(uploadUrl, {
method: "POST",
method: 'POST',
body: formData,
referrerPolicy: "same-origin",
referrerPolicy: 'same-origin',
headers
})
.then(async (response) => {
if (!response.ok) {
if (response.status === 403) {
const tokenHeader = response.headers.get("X-CSRF-HEADER")
const tokenHeader = response.headers.get('X-CSRF-HEADER')
if (tokenHeader) {
const token = response.headers.get(tokenHeader)
this.csrfToken = {
headerName: tokenHeader,
value: token || ""
value: token || ''
}
this.setCsrfTokenWeb(this.csrfToken)
@@ -72,7 +72,7 @@ export class FileUploader {
})
.then((responseText) => {
if (isLogInRequired(responseText))
reject("You must be logged in to upload a fle")
reject('You must be logged in to upload a fle')
if (needsRetry(responseText)) {
if (this.retryCount < requestRetryLimit) {

View File

@@ -33,11 +33,11 @@ export class SAS9ApiClient {
serverName: string,
repositoryName: string
) {
const requestPayload = linesOfCode.join("\n")
const requestPayload = linesOfCode.join('\n')
const executeScriptRequest = {
method: "PUT",
method: 'PUT',
headers: {
Accept: "application/json"
Accept: 'application/json'
},
body: `command=${requestPayload}`
}

View File

@@ -3,13 +3,13 @@ import {
parseAndSubmitAuthorizeForm,
convertToCSV,
makeRequest
} from "./utils"
import * as NodeFormData from "form-data"
import * as path from "path"
import { Job, Session, Context, Folder, CsrfToken } from "./types"
import { JobDefinition } from "./types/JobDefinition"
import { formatDataForRequest } from "./utils/formatDataForRequest"
import { SessionManager } from "./SessionManager"
} from './utils'
import * as NodeFormData from 'form-data'
import * as path from 'path'
import { Job, Session, Context, Folder, CsrfToken } from './types'
import { JobDefinition } from './types/JobDefinition'
import { formatDataForRequest } from './utils/formatDataForRequest'
import { SessionManager } from './SessionManager'
/**
* A client for interfacing with the SAS Viya REST API
@@ -24,7 +24,7 @@ export class SASViyaApiClient {
private rootFolderMap = new Map<string, Job[]>()
) {
if (!rootFolderName) {
throw new Error("Root folder must be provided.")
throw new Error('Root folder must be provided.')
}
}
private csrfToken: CsrfToken | null = null
@@ -73,7 +73,7 @@ export class SASViyaApiClient {
*/
public async getAllContexts(accessToken?: string) {
const headers: any = {
"Content-Type": "application/json"
'Content-Type': 'application/json'
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
@@ -98,7 +98,7 @@ export class SASViyaApiClient {
*/
public async getExecutableContexts(accessToken?: string) {
const headers: any = {
"Content-Type": "application/json"
'Content-Type': 'application/json'
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
@@ -112,7 +112,7 @@ export class SASViyaApiClient {
const executableContexts: any[] = []
const promises = contextsList.map((context: any) => {
const linesOfCode = ["%put &=sysuserid;"]
const linesOfCode = ['%put &=sysuserid;']
return this.executeScript(
`test-${context.name}`,
linesOfCode,
@@ -122,14 +122,14 @@ export class SASViyaApiClient {
})
const results = await Promise.all(promises)
results.forEach((result: any, index: number) => {
if (result && result.jobStatus === "completed") {
let sysUserId = ""
if (result && result.jobStatus === 'completed') {
let sysUserId = ''
if (result && result.log && result.log.items) {
const sysUserIdLog = result.log.items.find((i: any) =>
i.line.startsWith("SYSUSERID=")
i.line.startsWith('SYSUSERID=')
)
if (sysUserIdLog) {
sysUserId = sysUserIdLog.line.replace("SYSUSERID=", "")
sysUserId = sysUserIdLog.line.replace('SYSUSERID=', '')
}
}
@@ -155,7 +155,7 @@ export class SASViyaApiClient {
*/
public async createSession(contextName: string, accessToken?: string) {
const headers: any = {
"Content-Type": "application/json"
'Content-Type': 'application/json'
}
if (accessToken) {
@@ -175,10 +175,10 @@ export class SASViyaApiClient {
}
const createSessionRequest = {
method: "POST",
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
'Content-Type': 'application/json'
}
}
const { result: createdSession } = await this.request<Session>(
@@ -210,7 +210,7 @@ export class SASViyaApiClient {
silent = !debug
try {
const headers: any = {
"Content-Type": "application/json"
'Content-Type': 'application/json'
}
if (accessToken) {
@@ -231,26 +231,26 @@ export class SASViyaApiClient {
}
if (debug) {
jobArguments["_OMITTEXTLOG"] = false
jobArguments["_OMITSESSIONRESULTS"] = false
jobArguments["_DEBUG"] = 131
jobArguments['_OMITTEXTLOG'] = false
jobArguments['_OMITSESSIONRESULTS'] = false
jobArguments['_DEBUG'] = 131
}
const fileName = `exec-${
jobName.includes("/") ? jobName.split("/")[1] : jobName
jobName.includes('/') ? jobName.split('/')[1] : jobName
}`
let jobVariables: any = {
SYS_JES_JOB_URI: "",
_program: this.rootFolderName + "/" + jobName
SYS_JES_JOB_URI: '',
_program: this.rootFolderName + '/' + jobName
}
let files: any[] = []
if (data) {
if (JSON.stringify(data).includes(";")) {
if (JSON.stringify(data).includes(';')) {
files = await this.uploadTables(data, accessToken)
jobVariables["_webin_file_count"] = files.length
jobVariables['_webin_file_count'] = files.length
files.forEach((fileInfo, index) => {
jobVariables[
`_webin_fileuri${index + 1}`
@@ -264,11 +264,11 @@ export class SASViyaApiClient {
// Execute job in session
const postJobRequest = {
method: "POST",
method: 'POST',
headers,
body: JSON.stringify({
name: fileName,
description: "Powered by SASjs",
description: 'Powered by SASjs',
code: linesOfCode,
variables: jobVariables,
arguments: jobArguments
@@ -284,7 +284,7 @@ export class SASViyaApiClient {
console.log(`Job has been submitted for ${fileName}`)
console.log(
`You can monitor the job progress at ${this.serverUrl}${
postedJob.links.find((l: any) => l.rel === "state")!.href
postedJob.links.find((l: any) => l.rel === 'state')!.href
}`
)
}
@@ -303,7 +303,7 @@ export class SASViyaApiClient {
let jobResult, log
const logLink = currentJob.links.find((l) => l.rel === "log")
const logLink = currentJob.links.find((l) => l.rel === 'log')
if (true && logLink) {
log = await this.request<any>(
@@ -312,11 +312,11 @@ export class SASViyaApiClient {
headers
}
).then((res: any) =>
res.result.items.map((i: any) => i.line).join("\n")
res.result.items.map((i: any) => i.line).join('\n')
)
}
if (jobStatus === "failed" || jobStatus === "error") {
if (jobStatus === 'failed' || jobStatus === 'error') {
return Promise.reject({ error: currentJob.error, log: log })
}
const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`
@@ -325,7 +325,7 @@ export class SASViyaApiClient {
jobResult = await this.request<any>(
`${this.serverUrl}${resultLink}`,
{ headers },
"text"
'text'
).catch((e) => ({
result: JSON.stringify(e)
}))
@@ -367,7 +367,7 @@ export class SASViyaApiClient {
accessToken?: string
): Promise<Folder> {
if (!parentFolderPath && !parentFolderUri) {
throw new Error("Parent folder path or uri is required")
throw new Error('Parent folder path or uri is required')
}
if (!parentFolderUri && parentFolderPath) {
@@ -377,11 +377,11 @@ export class SASViyaApiClient {
const newParentFolderPath = parentFolderPath.substring(
0,
parentFolderPath.lastIndexOf("/")
parentFolderPath.lastIndexOf('/')
)
const newFolderName = `${parentFolderPath.split("/").pop()}`
if (newParentFolderPath === "") {
throw new Error("Root Folder should have been present on server")
const newFolderName = `${parentFolderPath.split('/').pop()}`
if (newParentFolderPath === '') {
throw new Error('Root Folder should have been present on server')
}
console.log(
`Creating Parent Folder:\n${newFolderName} in ${newParentFolderPath}`
@@ -398,14 +398,14 @@ export class SASViyaApiClient {
}
const createFolderRequest: RequestInit = {
method: "POST",
method: 'POST',
body: JSON.stringify({
name: folderName,
type: "folder"
type: 'folder'
})
}
createFolderRequest.headers = { "Content-Type": "application/json" }
createFolderRequest.headers = { 'Content-Type': 'application/json' }
if (accessToken) {
createFolderRequest.headers.Authorization = `Bearer ${accessToken}`
}
@@ -437,7 +437,7 @@ export class SASViyaApiClient {
) {
if (!parentFolderPath && !parentFolderUri) {
throw new Error(
"Either parentFolderPath or parentFolderUri must be provided"
'Either parentFolderPath or parentFolderUri must be provided'
)
}
@@ -446,21 +446,21 @@ export class SASViyaApiClient {
}
const createJobDefinitionRequest: RequestInit = {
method: "POST",
method: 'POST',
headers: {
"Content-Type": "application/vnd.sas.job.definition+json",
Accept: "application/vnd.sas.job.definition+json"
'Content-Type': 'application/vnd.sas.job.definition+json',
Accept: 'application/vnd.sas.job.definition+json'
},
body: JSON.stringify({
name: jobName,
parameters: [
{
name: "_addjesbeginendmacros",
type: "CHARACTER",
defaultValue: "false"
name: '_addjesbeginendmacros',
type: 'CHARACTER',
defaultValue: 'false'
}
],
type: "Compute",
type: 'Compute',
code
})
}
@@ -486,12 +486,12 @@ export class SASViyaApiClient {
const authUrl = `${this.serverUrl}/SASLogon/oauth/authorize?client_id=${clientId}&response_type=code`
const authCode = await fetch(authUrl, {
referrerPolicy: "same-origin",
credentials: "include"
referrerPolicy: 'same-origin',
credentials: 'include'
})
.then((response) => response.text())
.then(async (response) => {
let code = ""
let code = ''
if (isAuthorizeFormRequired(response)) {
const formResponse: any = await parseAndSubmitAuthorizeForm(
response,
@@ -499,21 +499,21 @@ export class SASViyaApiClient {
)
const responseBody = formResponse
.split("<body>")[1]
.split("</body>")[0]
const bodyElement: any = document.createElement("div")
.split('<body>')[1]
.split('</body>')[0]
const bodyElement: any = document.createElement('div')
bodyElement.innerHTML = responseBody
code = bodyElement.querySelector(".infobox h4").innerText
code = bodyElement.querySelector('.infobox h4').innerText
return code
} else {
const responseBody = response.split("<body>")[1].split("</body>")[0]
const bodyElement: any = document.createElement("div")
const responseBody = response.split('<body>')[1].split('</body>')[0]
const bodyElement: any = document.createElement('div')
bodyElement.innerHTML = responseBody
if (bodyElement) {
code = bodyElement.querySelector(".infobox h4").innerText
code = bodyElement.querySelector('.infobox h4').innerText
}
return code
@@ -535,34 +535,34 @@ export class SASViyaApiClient {
clientSecret: string,
authCode: string
) {
const url = this.serverUrl + "/SASLogon/oauth/token"
const url = this.serverUrl + '/SASLogon/oauth/token'
let token
if (typeof Buffer === "undefined") {
token = btoa(clientId + ":" + clientSecret)
if (typeof Buffer === 'undefined') {
token = btoa(clientId + ':' + clientSecret)
} else {
token = Buffer.from(clientId + ":" + clientSecret).toString("base64")
token = Buffer.from(clientId + ':' + clientSecret).toString('base64')
}
const headers = {
Authorization: "Basic " + token
Authorization: 'Basic ' + token
}
let formData
if (typeof FormData === "undefined") {
if (typeof FormData === 'undefined') {
formData = new NodeFormData()
formData.append("grant_type", "authorization_code")
formData.append("code", authCode)
formData.append('grant_type', 'authorization_code')
formData.append('code', authCode)
} else {
formData = new FormData()
formData.append("grant_type", "authorization_code")
formData.append("code", authCode)
formData.append('grant_type', 'authorization_code')
formData.append('code', authCode)
}
const authResponse = await fetch(url, {
method: "POST",
credentials: "include",
method: 'POST',
credentials: 'include',
headers,
body: formData as any,
referrerPolicy: "same-origin"
referrerPolicy: 'same-origin'
}).then((res) => res.json())
return authResponse
@@ -579,34 +579,34 @@ export class SASViyaApiClient {
clientSecret: string,
refreshToken: string
) {
const url = this.serverUrl + "/SASLogon/oauth/token"
const url = this.serverUrl + '/SASLogon/oauth/token'
let token
if (typeof Buffer === "undefined") {
token = btoa(clientId + ":" + clientSecret)
if (typeof Buffer === 'undefined') {
token = btoa(clientId + ':' + clientSecret)
} else {
token = Buffer.from(clientId + ":" + clientSecret).toString("base64")
token = Buffer.from(clientId + ':' + clientSecret).toString('base64')
}
const headers = {
Authorization: "Basic " + token
Authorization: 'Basic ' + token
}
let formData
if (typeof FormData === "undefined") {
if (typeof FormData === 'undefined') {
formData = new NodeFormData()
formData.append("grant_type", "refresh_token")
formData.append("refresh_token", refreshToken)
formData.append('grant_type', 'refresh_token')
formData.append('refresh_token', refreshToken)
} else {
formData = new FormData()
formData.append("grant_type", "refresh_token")
formData.append("refresh_token", refreshToken)
formData.append('grant_type', 'refresh_token')
formData.append('refresh_token', refreshToken)
}
const authResponse = await fetch(url, {
method: "POST",
credentials: "include",
method: 'POST',
credentials: 'include',
headers,
body: formData as any,
referrerPolicy: "same-origin"
referrerPolicy: 'same-origin'
}).then((res) => res.json())
return authResponse
@@ -624,8 +624,8 @@ export class SASViyaApiClient {
headers.Authorization = `Bearer ${accessToken}`
}
const deleteResponse = await this.request(url, {
method: "DELETE",
credentials: "include",
method: 'DELETE',
credentials: 'include',
headers
})
@@ -651,8 +651,8 @@ export class SASViyaApiClient {
await this.populateRootFolder(accessToken)
}
if (!this.rootFolder) {
console.error("Root folder was not found")
throw new Error("Root folder was not found")
console.error('Root folder was not found')
throw new Error('Root folder was not found')
}
if (!this.rootFolderMap.size) {
await this.populateRootFolderMap(accessToken)
@@ -664,27 +664,27 @@ export class SASViyaApiClient {
)
}
const headers: any = { "Content-Type": "application/json" }
const headers: any = { 'Content-Type': 'application/json' }
if (!!accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
const folderName = sasJob.split("/")[0]
const jobName = sasJob.split("/")[1]
const folderName = sasJob.split('/')[0]
const jobName = sasJob.split('/')[1]
const jobFolder = this.rootFolderMap.get(folderName)
const jobToExecute = jobFolder?.find((item) => item.name === jobName)
if (!jobToExecute) {
throw new Error("Job was not found.")
throw new Error('Job was not found.')
}
let code = jobToExecute?.code
if (!code) {
const jobDefinitionLink = jobToExecute?.links.find(
(l) => l.rel === "getResource"
(l) => l.rel === 'getResource'
)
if (!jobDefinitionLink) {
console.error("Job definition URI was not found.")
throw new Error("Job definition URI was not found.")
console.error('Job definition URI was not found.')
throw new Error('Job definition URI was not found.')
}
const { result: jobDefinition } = await this.request<JobDefinition>(
`${this.serverUrl}${jobDefinitionLink.href}`,
@@ -696,7 +696,7 @@ export class SASViyaApiClient {
// Add code to existing job definition
jobToExecute.code = code
}
const linesToExecute = code.replace(/\r\n/g, "\n").split("\n")
const linesToExecute = code.replace(/\r\n/g, '\n').split('\n')
return await this.executeScript(
sasJob,
linesToExecute,
@@ -728,7 +728,7 @@ export class SASViyaApiClient {
}
if (!this.rootFolder) {
throw new Error("Root folder was not found")
throw new Error('Root folder was not found')
}
if (!this.rootFolderMap.size) {
await this.populateRootFolderMap(accessToken)
@@ -745,17 +745,17 @@ export class SASViyaApiClient {
}
const jobName = path.basename(sasJob)
const jobFolder = sasJob.replace(`/${jobName}`, "")
const allJobsInFolder = this.rootFolderMap.get(jobFolder.replace("/", ""))
const jobFolder = sasJob.replace(`/${jobName}`, '')
const allJobsInFolder = this.rootFolderMap.get(jobFolder.replace('/', ''))
if (allJobsInFolder) {
const jobSpec = allJobsInFolder.find((j: Job) => j.name === jobName)
const jobDefinitionLink = jobSpec?.links.find(
(l) => l.rel === "getResource"
(l) => l.rel === 'getResource'
)?.href
const requestInfo: any = {
method: "GET"
method: 'GET'
}
const headers: any = { "Content-Type": "application/json" }
const headers: any = { 'Content-Type': 'application/json' }
if (!!accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
@@ -777,9 +777,9 @@ export class SASViyaApiClient {
}
if (debug) {
jobArguments["_OMITTEXTLOG"] = "false"
jobArguments["_OMITSESSIONRESULTS"] = "false"
jobArguments["_DEBUG"] = 131
jobArguments['_OMITTEXTLOG'] = 'false'
jobArguments['_OMITSESSIONRESULTS'] = 'false'
jobArguments['_DEBUG'] = 131
}
files.forEach((fileInfo, index) => {
@@ -790,11 +790,11 @@ export class SASViyaApiClient {
})
const postJobRequest = {
method: "POST",
method: 'POST',
headers,
body: JSON.stringify({
name: `exec-${jobName}`,
description: "Powered by SASjs",
description: 'Powered by SASjs',
jobDefinition,
arguments: jobArguments
})
@@ -815,16 +815,16 @@ export class SASViyaApiClient {
)
let jobResult, log
if (jobStatus === "failed") {
if (jobStatus === 'failed') {
return Promise.reject(currentJob.error)
}
const resultLink = currentJob.results["_webout.json"]
const logLink = currentJob.links.find((l) => l.rel === "log")
const resultLink = currentJob.results['_webout.json']
const logLink = currentJob.links.find((l) => l.rel === 'log')
if (resultLink) {
jobResult = await this.request<any>(
`${this.serverUrl}${resultLink}/content`,
{ headers },
"text"
'text'
)
}
if (debug && logLink) {
@@ -834,7 +834,7 @@ export class SASViyaApiClient {
headers
}
).then((res: any) =>
res.result.items.map((i: any) => i.line).join("\n")
res.result.items.map((i: any) => i.line).join('\n')
)
}
return { result: jobResult?.result, log }
@@ -847,9 +847,9 @@ export class SASViyaApiClient {
private async populateRootFolderMap(accessToken?: string) {
const allItems = new Map<string, Job[]>()
const url = "/folders/folders/@item?path=" + this.rootFolderName
const url = '/folders/folders/@item?path=' + this.rootFolderName
const requestInfo: any = {
method: "GET"
method: 'GET'
}
if (accessToken) {
requestInfo.headers = { Authorization: `Bearer ${accessToken}` }
@@ -859,7 +859,7 @@ export class SASViyaApiClient {
requestInfo
)
if (!folder) {
throw new Error("Cannot populate RootFolderMap unless rootFolder exists")
throw new Error('Cannot populate RootFolderMap unless rootFolder exists')
}
const { result: members } = await this.request<{ items: any[] }>(
`${this.serverUrl}/folders/folders/${folder.id}/members`,
@@ -867,14 +867,14 @@ export class SASViyaApiClient {
)
const itemsAtRoot = members.items
allItems.set("", itemsAtRoot)
allItems.set('', itemsAtRoot)
const subfolderRequests = members.items
.filter((i: any) => i.contentType === "folder")
.filter((i: any) => i.contentType === 'folder')
.map(async (member: any) => {
const subFolderUrl =
"/folders/folders/@item?path=" +
'/folders/folders/@item?path=' +
this.rootFolderName +
"/" +
'/' +
member.name
const { result: memberDetail } = await this.request<Folder>(
`${this.serverUrl}${subFolderUrl}`,
@@ -882,7 +882,7 @@ export class SASViyaApiClient {
)
const membersLink = memberDetail.links.find(
(l: any) => l.rel === "members"
(l: any) => l.rel === 'members'
)
const { result: memberContents } = await this.request<{ items: any[] }>(
@@ -899,9 +899,9 @@ export class SASViyaApiClient {
}
private async populateRootFolder(accessToken?: string) {
const url = "/folders/folders/@item?path=" + this.rootFolderName
const url = '/folders/folders/@item?path=' + this.rootFolderName
const requestInfo: RequestInit = {
method: "GET"
method: 'GET'
}
if (accessToken) {
requestInfo.headers = { Authorization: `Bearer ${accessToken}` }
@@ -926,18 +926,18 @@ export class SASViyaApiClient {
) {
const MAX_POLL_COUNT = 1000
const POLL_INTERVAL = 100
let postedJobState = ""
let postedJobState = ''
let pollCount = 0
const headers: any = {
"Content-Type": "application/json",
"If-None-Match": etag
'Content-Type': 'application/json',
'If-None-Match': etag
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
const stateLink = postedJob.links.find((l: any) => l.rel === "state")
const stateLink = postedJob.links.find((l: any) => l.rel === 'state')
if (!stateLink) {
Promise.reject("Job state link was not found.")
Promise.reject('Job state link was not found.')
}
const { result: state } = await this.request<string>(
@@ -945,31 +945,31 @@ export class SASViyaApiClient {
{
headers
},
"text"
'text'
)
const currentState = state.trim()
if (currentState === "completed") {
if (currentState === 'completed') {
return Promise.resolve(currentState)
}
return new Promise(async (resolve, _) => {
const interval = setInterval(async () => {
if (
postedJobState === "running" ||
postedJobState === "" ||
postedJobState === "pending"
postedJobState === 'running' ||
postedJobState === '' ||
postedJobState === 'pending'
) {
if (stateLink) {
if (!silent) {
console.log("Polling job status... \n")
console.log('Polling job status... \n')
}
const { result: jobState } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?_action=wait&wait=30`,
{
headers
},
"text"
'text'
)
postedJobState = jobState.trim()
@@ -998,25 +998,25 @@ export class SASViyaApiClient {
let sessionState = session.state
let pollCount = 0
const headers: any = {
"Content-Type": "application/json",
"If-None-Match": etag
'Content-Type': 'application/json',
'If-None-Match': etag
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
const stateLink = session.links.find((l: any) => l.rel === "state")
const stateLink = session.links.find((l: any) => l.rel === 'state')
return new Promise(async (resolve, _) => {
if (sessionState === "pending") {
if (sessionState === 'pending') {
if (stateLink) {
if (!silent) {
console.log("Polling session status... \n")
console.log('Polling session status... \n')
}
const { result: state } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?wait=30`,
{
headers
},
"text"
'text'
)
sessionState = state.trim()
@@ -1035,7 +1035,7 @@ export class SASViyaApiClient {
private async uploadTables(data: any, accessToken?: string) {
const uploadedFiles = []
const headers: any = {
"Content-Type": "application/json"
'Content-Type': 'application/json'
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
@@ -1043,14 +1043,14 @@ export class SASViyaApiClient {
for (const tableName in data) {
const csv = convertToCSV(data[tableName])
if (csv === "ERROR: LARGE STRING LENGTH") {
if (csv === 'ERROR: LARGE STRING LENGTH') {
throw new Error(
"The max length of a string value in SASjs is 32765 characters."
'The max length of a string value in SASjs is 32765 characters.'
)
}
const createFileRequest = {
method: "POST",
method: 'POST',
body: csv,
headers
}
@@ -1066,9 +1066,9 @@ export class SASViyaApiClient {
}
private async getFolderUri(folderPath: string, accessToken?: string) {
const url = "/folders/folders/@item?path=" + folderPath
const url = '/folders/folders/@item?path=' + folderPath
const requestInfo: any = {
method: "GET"
method: 'GET'
}
if (accessToken) {
requestInfo.headers = { Authorization: `Bearer ${accessToken}` }
@@ -1092,7 +1092,7 @@ export class SASViyaApiClient {
private async request<T>(
url: string,
options: RequestInit,
contentType: "text" | "json" = "json"
contentType: 'text' | 'json' = 'json'
) {
if (this.csrfToken) {
options.headers = {

View File

@@ -1,34 +1,34 @@
import SASjs from "./index"
import SASjs from './index'
const adapter = new SASjs()
it("should parse SAS9 source code", async (done) => {
it('should parse SAS9 source code', async (done) => {
expect(sampleResponse).toBeTruthy()
const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse)
expect(parsedSourceCode).toBeTruthy()
const sourceCodeLines = parsedSourceCode.split("\r\n")
const sourceCodeLines = parsedSourceCode.split('\r\n')
expect(sourceCodeLines.length).toEqual(5)
expect(sourceCodeLines[0].startsWith("6")).toBeTruthy()
expect(sourceCodeLines[1].startsWith("7")).toBeTruthy()
expect(sourceCodeLines[2].startsWith("8")).toBeTruthy()
expect(sourceCodeLines[3].startsWith("9")).toBeTruthy()
expect(sourceCodeLines[4].startsWith("10")).toBeTruthy()
expect(sourceCodeLines[0].startsWith('6')).toBeTruthy()
expect(sourceCodeLines[1].startsWith('7')).toBeTruthy()
expect(sourceCodeLines[2].startsWith('8')).toBeTruthy()
expect(sourceCodeLines[3].startsWith('9')).toBeTruthy()
expect(sourceCodeLines[4].startsWith('10')).toBeTruthy()
done()
})
it("should parse generated code", async (done) => {
it('should parse generated code', async (done) => {
expect(sampleResponse).toBeTruthy()
const parsedGeneratedCode = (adapter as any).parseGeneratedCode(
sampleResponse
)
expect(parsedGeneratedCode).toBeTruthy()
const generatedCodeLines = parsedGeneratedCode.split("\r\n")
const generatedCodeLines = parsedGeneratedCode.split('\r\n')
expect(generatedCodeLines.length).toEqual(5)
expect(generatedCodeLines[0].startsWith("MPRINT(MM_WEBIN)")).toBeTruthy()
expect(generatedCodeLines[1].startsWith("MPRINT(MM_WEBLEFT)")).toBeTruthy()
expect(generatedCodeLines[2].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy()
expect(generatedCodeLines[3].startsWith("MPRINT(MM_WEBRIGHT)")).toBeTruthy()
expect(generatedCodeLines[4].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy()
expect(generatedCodeLines[0].startsWith('MPRINT(MM_WEBIN)')).toBeTruthy()
expect(generatedCodeLines[1].startsWith('MPRINT(MM_WEBLEFT)')).toBeTruthy()
expect(generatedCodeLines[2].startsWith('MPRINT(MM_WEBOUT)')).toBeTruthy()
expect(generatedCodeLines[3].startsWith('MPRINT(MM_WEBRIGHT)')).toBeTruthy()
expect(generatedCodeLines[4].startsWith('MPRINT(MM_WEBOUT)')).toBeTruthy()
done()
})

View File

@@ -1,5 +1,5 @@
import { isIEorEdgeOrOldFirefox } from "./utils/isIeOrEdge"
import * as e6p from "es6-promise"
import { isIEorEdgeOrOldFirefox } from './utils/isIeOrEdge'
import * as e6p from 'es6-promise'
;(e6p as any).polyfill()
if (isIEorEdgeOrOldFirefox()) {
if (window) {
@@ -7,7 +7,7 @@ if (isIEorEdgeOrOldFirefox()) {
}
}
// tslint:disable-next-line
require("isomorphic-fetch")
require('isomorphic-fetch')
import {
convertToCSV,
compareTimestamps,
@@ -22,7 +22,7 @@ import {
parseWeboutResponse,
needsRetry,
asyncForEach
} from "./utils"
} from './utils'
import {
SASjsConfig,
SASjsRequest,
@@ -30,19 +30,19 @@ import {
ServerType,
CsrfToken,
UploadFile
} from "./types"
import { SASViyaApiClient } from "./SASViyaApiClient"
import { SAS9ApiClient } from "./SAS9ApiClient"
import { FileUploader } from "./FileUploader"
} from './types'
import { SASViyaApiClient } from './SASViyaApiClient'
import { SAS9ApiClient } from './SAS9ApiClient'
import { FileUploader } from './FileUploader'
const defaultConfig: SASjsConfig = {
serverUrl: "",
pathSAS9: "/SASStoredProcess/do",
pathSASViya: "/SASJobExecution",
appLoc: "/Public/seedapp",
serverUrl: '',
pathSAS9: '/SASStoredProcess/do',
pathSASViya: '/SASJobExecution',
appLoc: '/Public/seedapp',
serverType: ServerType.SASViya,
debug: true,
contextName: "SAS Job Execution compute context",
contextName: 'SAS Job Execution compute context',
useComputeApi: false
}
@@ -54,9 +54,9 @@ const requestRetryLimit = 5
*/
export default class SASjs {
private sasjsConfig: SASjsConfig = new SASjsConfig()
private jobsPath: string = ""
private logoutUrl: string = ""
private loginUrl: string = ""
private jobsPath: string = ''
private logoutUrl: string = ''
private loginUrl: string = ''
private csrfTokenApi: CsrfToken | null = null
private csrfTokenWeb: CsrfToken | null = null
private retryCountWeb: number = 0
@@ -64,7 +64,7 @@ export default class SASjs {
private retryCountJeseApi: number = 0
private sasjsRequests: SASjsRequest[] = []
private sasjsWaitingRequests: SASjsWaitingRequest[] = []
private userName: string = ""
private userName: string = ''
private sasViyaApiClient: SASViyaApiClient | null = null
private sas9ApiClient: SAS9ApiClient | null = null
private fileUploader: FileUploader | null = null
@@ -84,7 +84,7 @@ export default class SASjs {
repositoryName: string
) {
if (this.sasjsConfig.serverType !== ServerType.SAS9) {
throw new Error("This operation is only supported on SAS9 servers.")
throw new Error('This operation is only supported on SAS9 servers.')
}
return await this.sas9ApiClient?.executeScript(
linesOfCode,
@@ -95,21 +95,21 @@ export default class SASjs {
public async getAllContexts(accessToken: string) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
throw new Error("This operation is only supported on SAS Viya servers.")
throw new Error('This operation is only supported on SAS Viya servers.')
}
return await this.sasViyaApiClient!.getAllContexts(accessToken)
}
public async getExecutableContexts(accessToken: string) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
throw new Error("This operation is only supported on SAS Viya servers.")
throw new Error('This operation is only supported on SAS Viya servers.')
}
return await this.sasViyaApiClient!.getExecutableContexts(accessToken)
}
public async createSession(contextName: string, accessToken: string) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
throw new Error("This operation is only supported on SAS Viya servers.")
throw new Error('This operation is only supported on SAS Viya servers.')
}
return await this.sasViyaApiClient!.createSession(contextName, accessToken)
}
@@ -119,11 +119,11 @@ export default class SASjs {
linesOfCode: string[],
contextName: string,
accessToken?: string,
sessionId = "",
sessionId = '',
silent = false
) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
throw new Error("This operation is only supported on SAS Viya servers.")
throw new Error('This operation is only supported on SAS Viya servers.')
}
return await this.sasViyaApiClient!.executeScript(
fileName,
@@ -142,7 +142,7 @@ export default class SASjs {
sasApiClient?: SASViyaApiClient
) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
throw new Error("This operation is only supported on SAS Viya servers.")
throw new Error('This operation is only supported on SAS Viya servers.')
}
if (sasApiClient)
return await sasApiClient.createFolder(
@@ -168,7 +168,7 @@ export default class SASjs {
sasApiClient?: SASViyaApiClient
) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
throw new Error("This operation is only supported on SAS Viya servers.")
throw new Error('This operation is only supported on SAS Viya servers.')
}
if (sasApiClient)
return await sasApiClient!.createJobDefinition(
@@ -189,7 +189,7 @@ export default class SASjs {
public async getAuthCode(clientId: string) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
throw new Error("This operation is only supported on SAS Viya servers.")
throw new Error('This operation is only supported on SAS Viya servers.')
}
return await this.sasViyaApiClient!.getAuthCode(clientId)
}
@@ -200,7 +200,7 @@ export default class SASjs {
authCode: string
) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
throw new Error("This operation is only supported on SAS Viya servers.")
throw new Error('This operation is only supported on SAS Viya servers.')
}
return await this.sasViyaApiClient!.getAccessToken(
clientId,
@@ -215,7 +215,7 @@ export default class SASjs {
refreshToken: string
) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
throw new Error("This operation is only supported on SAS Viya servers.")
throw new Error('This operation is only supported on SAS Viya servers.')
}
return await this.sasViyaApiClient!.refreshTokens(
clientId,
@@ -226,7 +226,7 @@ export default class SASjs {
public async deleteClient(clientId: string, accessToken: string) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
throw new Error("This operation is only supported on SAS Viya servers.")
throw new Error('This operation is only supported on SAS Viya servers.')
}
return await this.sasViyaApiClient!.deleteClient(clientId, accessToken)
}
@@ -289,7 +289,7 @@ export default class SASjs {
* @returns a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`
*/
public async checkSession() {
const loginResponse = await fetch(this.loginUrl.replace(".do", ""))
const loginResponse = await fetch(this.loginUrl.replace('.do', ''))
const responseText = await loginResponse.text()
const isLoggedIn = /<button.+onClick.+logout/gm.test(responseText)
@@ -306,7 +306,7 @@ export default class SASjs {
*/
public async logIn(username: string, password: string) {
const loginParams: any = {
_service: "default",
_service: 'default',
username,
password
}
@@ -331,12 +331,12 @@ export default class SASjs {
const loginParamsStr = serialize(loginParams)
return fetch(this.loginUrl, {
method: "post",
credentials: "include",
referrerPolicy: "same-origin",
method: 'post',
credentials: 'include',
referrerPolicy: 'same-origin',
body: loginParamsStr,
headers: new Headers({
"Content-Type": "application/x-www-form-urlencoded"
'Content-Type': 'application/x-www-form-urlencoded'
})
})
.then((response) => response.text())
@@ -438,7 +438,7 @@ export default class SASjs {
...config
}
sasJob = sasJob.startsWith("/") ? sasJob.replace("/", "") : sasJob
sasJob = sasJob.startsWith('/') ? sasJob.replace('/', '') : sasJob
if (config.serverType === ServerType.SASViya && config.contextName) {
if (config.useComputeApi) {
@@ -492,7 +492,7 @@ export default class SASjs {
accessToken?: string
) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) {
throw new Error("This operation is only supported on SAS Viya servers.")
throw new Error('This operation is only supported on SAS Viya servers.')
}
let sasApiClient: any = null
@@ -527,12 +527,12 @@ export default class SASjs {
// members of type 'folder' should be processed first
if (serviceJson.members[0].members) {
serviceJson.members[0].members.sort((member: { type: string }) =>
member.type === "folder" ? -1 : 1
member.type === 'folder' ? -1 : 1
)
}
const members =
serviceJson.members[0].name === "services"
serviceJson.members[0].name === 'services'
? serviceJson.members[0].members
: serviceJson.members
@@ -606,7 +606,7 @@ export default class SASjs {
resolve(retryResponse)
} else {
this.retryCountComputeApi = 0
reject({ MESSAGE: "Compute API retry requests limit reached" })
reject({ MESSAGE: 'Compute API retry requests limit reached' })
}
}
@@ -617,7 +617,7 @@ export default class SASjs {
sasjsWaitingRequest.config = config
this.sasjsWaitingRequests.push(sasjsWaitingRequest)
} else {
reject({ MESSAGE: error || "Job execution failed" })
reject({ MESSAGE: error || 'Job execution failed' })
}
this.appendSasjsRequest(response.log, sasJob, null)
@@ -699,11 +699,11 @@ export default class SASjs {
resolve(retryResponse)
} else {
this.retryCountJeseApi = 0
reject({ MESSAGE: "Jes API retry requests limit reached" })
reject({ MESSAGE: 'Jes API retry requests limit reached' })
}
}
reject({ MESSAGE: (e && e.message) || "Job execution failed" })
reject({ MESSAGE: (e && e.message) || 'Job execution failed' })
})
)
}
@@ -728,14 +728,14 @@ export default class SASjs {
data
}
const program = config.appLoc
? config.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "")
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
: sasJob
const jobUri =
config.serverType === "SASVIYA" ? await this.getJobUri(sasJob) : ""
config.serverType === 'SASVIYA' ? await this.getJobUri(sasJob) : ''
const apiUrl = `${config.serverUrl}${this.jobsPath}/?${
jobUri.length > 0
? "__program=" + program + "&_job=" + jobUri
: "_program=" + program
? '__program=' + program + '&_job=' + jobUri
: '_program=' + program
}`
const requestParams = {
@@ -745,14 +745,14 @@ export default class SASjs {
const formData = new FormData()
let isError = false
let errorMsg = ""
let errorMsg = ''
if (data) {
const stringifiedData = JSON.stringify(data)
if (
config.serverType === ServerType.SAS9 ||
stringifiedData.length > 500000 ||
stringifiedData.includes(";")
stringifiedData.includes(';')
) {
// file upload approach
for (const tableName in data) {
@@ -761,14 +761,14 @@ export default class SASjs {
}
const name = tableName
const csv = convertToCSV(data[tableName])
if (csv === "ERROR: LARGE STRING LENGTH") {
if (csv === 'ERROR: LARGE STRING LENGTH') {
isError = true
errorMsg =
"The max length of a string value in SASjs is 32765 characters."
'The max length of a string value in SASjs is 32765 characters.'
}
const file = new Blob([csv], {
type: "application/csv"
type: 'application/csv'
})
formData.append(name, file, `${name}.csv`)
@@ -784,10 +784,10 @@ export default class SASjs {
tableCounter++
sasjsTables.push(tableName)
const csv = convertToCSV(data[tableName])
if (csv === "ERROR: LARGE STRING LENGTH") {
if (csv === 'ERROR: LARGE STRING LENGTH') {
isError = true
errorMsg =
"The max length of a string value in SASjs is 32765 characters."
'The max length of a string value in SASjs is 32765 characters.'
}
// if csv has length more then 16k, send in chunks
if (csv.length > 16000) {
@@ -800,7 +800,7 @@ export default class SASjs {
requestParams[`sasjs${tableCounter}data`] = csv
}
}
requestParams["sasjs_tables"] = sasjsTables.join(" ")
requestParams['sasjs_tables'] = sasjsTables.join(' ')
}
}
@@ -822,21 +822,21 @@ export default class SASjs {
headers[this.csrfTokenWeb.headerName] = this.csrfTokenWeb.value
}
fetch(apiUrl, {
method: "POST",
method: 'POST',
body: formData,
referrerPolicy: "same-origin",
referrerPolicy: 'same-origin',
headers
})
.then(async (response) => {
if (!response.ok) {
if (response.status === 403) {
const tokenHeader = response.headers.get("X-CSRF-HEADER")
const tokenHeader = response.headers.get('X-CSRF-HEADER')
if (tokenHeader) {
const token = response.headers.get(tokenHeader)
this.csrfTokenWeb = {
headerName: tokenHeader,
value: token || ""
value: token || ''
}
}
}
@@ -878,7 +878,7 @@ export default class SASjs {
this.updateUsername(responseText)
const jsonResponseText = parseWeboutResponse(responseText)
if (jsonResponseText !== "") {
if (jsonResponseText !== '') {
resolve(JSON.parse(jsonResponseText))
} else {
reject({
@@ -954,14 +954,14 @@ export default class SASjs {
const requestParams: any = {}
if (this.csrfTokenWeb) {
requestParams["_csrf"] = this.csrfTokenWeb.value
requestParams['_csrf'] = this.csrfTokenWeb.value
}
if (config.debug) {
requestParams["_omittextlog"] = "false"
requestParams["_omitsessionresults"] = "false"
requestParams['_omittextlog'] = 'false'
requestParams['_omitsessionresults'] = 'false'
requestParams["_debug"] = 131
requestParams['_debug'] = 131
}
return requestParams
@@ -971,12 +971,12 @@ export default class SASjs {
try {
const responseJson = JSON.parse(response)
if (this.sasjsConfig.serverType === ServerType.SAS9) {
this.userName = responseJson["_METAUSER"]
this.userName = responseJson['_METAUSER']
} else {
this.userName = responseJson["SYSUSERID"]
this.userName = responseJson['SYSUSERID']
}
} catch (e) {
this.userName = ""
this.userName = ''
}
}
@@ -994,24 +994,24 @@ export default class SASjs {
resolve(resText)
})
} else {
reject("No debug info in response")
reject('No debug info in response')
}
})
}
private async getJobUri(sasJob: string) {
if (!this.sasViyaApiClient) return ""
if (!this.sasViyaApiClient) return ''
const jobMap: any = await this.sasViyaApiClient.getAppLocMap()
let uri = ""
let uri = ''
if (jobMap.size) {
const jobKey = sasJob.split("/")[0]
const jobName = sasJob.split("/")[1]
const jobKey = sasJob.split('/')[0]
const jobName = sasJob.split('/')[1]
const locJobs = jobMap.get(jobKey)
if (locJobs) {
const job = locJobs.find(
(el: any) => el.name === jobName && el.contentType === "jobDefinition"
(el: any) => el.name === jobName && el.contentType === 'jobDefinition'
)
if (job) {
uri = job.uri
@@ -1022,14 +1022,14 @@ export default class SASjs {
}
private parseSAS9ErrorResponse(response: string) {
const logLines = response.split("\n")
const logLines = response.split('\n')
const parsedLines: string[] = []
let firstErrorLineIndex: number = -1
logLines.map((line: string, index: number) => {
if (
line.toLowerCase().includes("error") &&
!line.toLowerCase().includes("this request completed with errors.") &&
line.toLowerCase().includes('error') &&
!line.toLowerCase().includes('this request completed with errors.') &&
firstErrorLineIndex === -1
) {
firstErrorLineIndex = index
@@ -1040,7 +1040,7 @@ export default class SASjs {
parsedLines.push(logLines[i])
}
return parsedLines.join(", ")
return parsedLines.join(', ')
}
private parseLogFromResponse(response: any, program: string) {
@@ -1058,7 +1058,7 @@ export default class SASjs {
private fetchLogFileContent(logLink: string) {
return new Promise((resolve, reject) => {
fetch(logLink, {
method: "GET"
method: 'GET'
})
.then((response: any) => response.text())
.then((response: any) => resolve(response))
@@ -1071,8 +1071,8 @@ export default class SASjs {
program: string,
pgmData: any
) {
let sourceCode = ""
let generatedCode = ""
let sourceCode = ''
let generatedCode = ''
let sasWork = null
if (response && response.result && response.log) {
@@ -1154,7 +1154,7 @@ export default class SASjs {
private setupConfiguration() {
if (
this.sasjsConfig.serverUrl === undefined ||
this.sasjsConfig.serverUrl === ""
this.sasjsConfig.serverUrl === ''
) {
let url = `${location.protocol}//${location.hostname}`
if (location.port) {
@@ -1163,7 +1163,7 @@ export default class SASjs {
this.sasjsConfig.serverUrl = url
}
if (this.sasjsConfig.serverUrl.slice(-1) === "/") {
if (this.sasjsConfig.serverUrl.slice(-1) === '/') {
this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1)
}
@@ -1174,8 +1174,8 @@ export default class SASjs {
this.loginUrl = `${this.sasjsConfig.serverUrl}/SASLogon/login`
this.logoutUrl =
this.sasjsConfig.serverType === ServerType.SAS9
? "/SASLogon/logout?"
: "/SASLogon/logout.do?"
? '/SASLogon/logout?'
: '/SASLogon/logout.do?'
if (this.sasjsConfig.serverType === ServerType.SASViya) {
if (this.sasViyaApiClient)
@@ -1206,8 +1206,8 @@ export default class SASjs {
}
private setLoginUrl = (matches: RegExpExecArray) => {
let parsedURL = matches[1].replace(/\?.*/, "")
if (parsedURL[0] === "/") {
let parsedURL = matches[1].replace(/\?.*/, '')
if (parsedURL[0] === '/') {
parsedURL = parsedURL.substr(1)
const tempLoginLink = this.sasjsConfig.serverUrl
@@ -1219,7 +1219,7 @@ export default class SASjs {
this.loginUrl =
this.sasjsConfig.serverType === ServerType.SASViya
? tempLoginLink
: loginUrl.replace(".do", "")
: loginUrl.replace('.do', '')
}
}
@@ -1251,7 +1251,7 @@ export default class SASjs {
) {
await asyncForEach(membersJson, async (member: any) => {
switch (member.type) {
case "folder":
case 'folder':
await this.createFolder(
member.name,
parentFolder,
@@ -1260,7 +1260,7 @@ export default class SASjs {
sasApiClient
)
break
case "service":
case 'service':
await this.createJobDefinition(
member.name,
member.code,
@@ -1273,7 +1273,7 @@ export default class SASjs {
default:
throw new Error(`Unidenitied member present in Json: ${member.name}`)
}
if (member.type === "folder" && member.members && member.members.length)
if (member.type === 'folder' && member.members && member.members.length)
await this.createFoldersAndServices(
`${parentFolder}/${member.name}`,
member.members,

View File

@@ -1,5 +1,5 @@
import { Session, Context, CsrfToken } from "./types"
import { asyncForEach, makeRequest } from "./utils"
import { Session, Context, CsrfToken } from './types'
import { asyncForEach, makeRequest } from './utils'
const MAX_SESSION_COUNT = 1
@@ -32,7 +32,7 @@ export class SessionManager {
async clearSession(id: string, accessToken?: string) {
const deleteSessionRequest = {
method: "DELETE",
method: 'DELETE',
headers: this.getHeaders(accessToken)
}
return await this.request<Session>(
@@ -57,7 +57,7 @@ export class SessionManager {
private async createAndWaitForSession(accessToken?: string) {
const createSessionRequest = {
method: "POST",
method: 'POST',
headers: this.getHeaders(accessToken)
}
const { result: createdSession, etag } = await this.request<Session>(
@@ -99,7 +99,7 @@ export class SessionManager {
private getHeaders(accessToken?: string) {
const headers: any = {
"Content-Type": "application/json"
'Content-Type': 'application/json'
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
@@ -117,21 +117,21 @@ export class SessionManager {
let sessionState = session.state
const headers: any = {
...this.getHeaders(accessToken),
"If-None-Match": etag
'If-None-Match': etag
}
const stateLink = session.links.find((l: any) => l.rel === "state")
const stateLink = session.links.find((l: any) => l.rel === 'state')
return new Promise(async (resolve, _) => {
if (sessionState === "pending") {
if (sessionState === 'pending') {
if (stateLink) {
if (!silent) {
console.log("Polling session status... \n")
console.log('Polling session status... \n')
}
const { result: state } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?wait=30`,
{
headers
},
"text"
'text'
)
sessionState = state.trim()
@@ -149,7 +149,7 @@ export class SessionManager {
private async request<T>(
url: string,
options: RequestInit,
contentType: "text" | "json" = "json"
contentType: 'text' | 'json' = 'json'
) {
if (this.csrfToken) {
options.headers = {

View File

@@ -1,5 +1,5 @@
import SASjs from "./SASjs"
export * from "./types"
export * from "./SASViyaApiClient"
export * from "./SAS9ApiClient"
import SASjs from './SASjs'
export * from './types'
export * from './SASViyaApiClient'
export * from './SAS9ApiClient'
export default SASjs

View File

@@ -1,4 +1,4 @@
import { Link } from "./Link"
import { Link } from './Link'
export interface Folder {
id: string

View File

@@ -1,5 +1,5 @@
import { Link } from "./Link"
import { JobResult } from "./JobResult"
import { Link } from './Link'
import { JobResult } from './JobResult'
export interface Job {
id: string

View File

@@ -1,3 +1,3 @@
export interface JobResult {
"_webout.json": string
'_webout.json': string
}

View File

@@ -1,4 +1,4 @@
import { ServerType } from "./ServerType"
import { ServerType } from './ServerType'
/**
* Specifies the configuration for the SASjs instance.
@@ -10,14 +10,14 @@ export class SASjsConfig {
* Can be omitted, eg if serving directly from the SAS Web Server or being
* streamed.
*/
serverUrl: string = ""
pathSAS9: string = ""
pathSASViya: string = ""
serverUrl: string = ''
pathSAS9: string = ''
pathSASViya: string = ''
/**
* The appLoc is the parent folder under which the SAS services (STPs or Job
* Execution Services) are stored.
*/
appLoc: string = ""
appLoc: string = ''
/**
* Can be SAS9 or SASVIYA
*/
@@ -26,6 +26,6 @@ export class SASjsConfig {
* Set to `true` to enable additional debugging.
*/
debug: boolean = true
contextName: string = ""
contextName: string = ''
useComputeApi = false
}

View File

@@ -3,6 +3,6 @@
*
*/
export enum ServerType {
SASViya = "SASVIYA",
SAS9 = "SAS9"
SASViya = 'SASVIYA',
SAS9 = 'SAS9'
}

View File

@@ -1,4 +1,4 @@
import { Link } from "./Link"
import { Link } from './Link'
export interface Session {
id: string

View File

@@ -1,11 +1,11 @@
export * from "./Context"
export * from "./CsrfToken"
export * from "./Folder"
export * from "./Job"
export * from "./Link"
export * from "./SASjsConfig"
export * from "./SASjsRequest"
export * from "./SASjsWaitingRequest"
export * from "./ServerType"
export * from "./Session"
export * from "./UploadFile"
export * from './Context'
export * from './CsrfToken'
export * from './Folder'
export * from './Job'
export * from './Link'
export * from './SASjsConfig'
export * from './SASjsRequest'
export * from './SASjsWaitingRequest'
export * from './ServerType'
export * from './Session'
export * from './UploadFile'

View File

@@ -1,4 +1,4 @@
import { SASjsRequest } from "../types/SASjsRequest"
import { SASjsRequest } from '../types/SASjsRequest'
/**
* Comparator for SASjs request timestamps

View File

@@ -3,7 +3,7 @@
* @param data - the JSON object to convert.
*/
export const convertToCSV = (data: any) => {
const replacer = (key: any, value: any) => (value === null ? "" : value)
const replacer = (key: any, value: any) => (value === null ? '' : value)
const headerFields = Object.keys(data[0])
let csvTest
let invalidString = false
@@ -14,31 +14,31 @@ export const convertToCSV = (data: any) => {
const longestValueForField = data
.map((row: any, index: number) => {
if (row[field] || row[field] === "") {
if (row[field] || row[field] === '') {
if (firstFoundType) {
let currentFieldType =
row[field] === "" || typeof row[field] === "string"
? "chars"
: "number"
row[field] === '' || typeof row[field] === 'string'
? 'chars'
: 'number'
if (!hasMixedTypes) {
hasMixedTypes = currentFieldType !== firstFoundType
rowNumError = hasMixedTypes ? index + 1 : -1
}
} else {
if (row[field] === "") {
firstFoundType = "chars"
if (row[field] === '') {
firstFoundType = 'chars'
} else {
firstFoundType =
typeof row[field] === "string" ? "chars" : "number"
typeof row[field] === 'string' ? 'chars' : 'number'
}
}
let byteSize
if (typeof row[field] === "string") {
if (typeof row[field] === 'string') {
let doubleQuotesFound = row[field]
.split("")
.split('')
.filter((char: any) => char === '"')
byteSize = getByteSize(row[field])
@@ -61,17 +61,17 @@ export const convertToCSV = (data: any) => {
)
}
return `${field}:${firstFoundType === "chars" ? "$" : ""}${
return `${field}:${firstFoundType === 'chars' ? '$' : ''}${
longestValueForField
? longestValueForField
: firstFoundType === "chars"
? "1"
: "best"
: firstFoundType === 'chars'
? '1'
: 'best'
}.`
})
if (invalidString) {
return "ERROR: LARGE STRING LENGTH"
return 'ERROR: LARGE STRING LENGTH'
}
csvTest = data.map((row: any) => {
const fields = Object.keys(row).map((fieldName, index) => {
@@ -86,15 +86,15 @@ export const convertToCSV = (data: any) => {
value = JSON.stringify(currentCell, replacer)
}
value = value.replace(/\\\\/gm, "\\")
value = value.replace(/\\\\/gm, '\\')
if (containsSpecialChar) {
if (value.includes(",") || value.includes('"')) {
if (value.includes(',') || value.includes('"')) {
value = '"' + value + '"'
}
} else {
if (
!value.includes(",") &&
!value.includes(',') &&
value.includes('"') &&
!value.includes('\\"')
) {
@@ -104,19 +104,19 @@ export const convertToCSV = (data: any) => {
value = value.replace(/\\"/gm, '""')
}
value = value.replace(/\r\n/gm, "\n")
value = value.replace(/\r\n/gm, '\n')
if (value === "" && headers[index].includes("best")) {
value = "."
if (value === '' && headers[index].includes('best')) {
value = '.'
}
return value
})
return fields.join(",")
return fields.join(',')
})
let finalCSV =
headers.join(",").replace(/,/g, " ") + "\r\n" + csvTest.join("\r\n")
headers.join(',').replace(/,/g, ' ') + '\r\n' + csvTest.join('\r\n')
return finalCSV
}

View File

@@ -1,5 +1,5 @@
import { convertToCSV } from "./convertToCsv"
import { splitChunks } from "./splitChunks"
import { convertToCSV } from './convertToCsv'
import { splitChunks } from './splitChunks'
export const formatDataForRequest = (data: any) => {
const sasjsTables = []
@@ -10,9 +10,9 @@ export const formatDataForRequest = (data: any) => {
tableCounter++
sasjsTables.push(tableName)
const csv = convertToCSV(data[tableName])
if (csv === "ERROR: LARGE STRING LENGTH") {
if (csv === 'ERROR: LARGE STRING LENGTH') {
throw new Error(
"The max length of a string value in SASjs is 32765 characters."
'The max length of a string value in SASjs is 32765 characters.'
)
}
// if csv has length more then 16k, send in chunks
@@ -27,7 +27,7 @@ export const formatDataForRequest = (data: any) => {
result[`sasjs${tableCounter}data`] = csv
}
}
result["sasjs_tables"] = sasjsTables.join(" ")
result['sasjs_tables'] = sasjsTables.join(' ')
return result
}

View File

@@ -1,15 +1,15 @@
export * from "./asyncForEach"
export * from "./compareTimestamps"
export * from "./convertToCsv"
export * from "./isAuthorizeFormRequired"
export * from "./isLoginRequired"
export * from "./isLoginSuccess"
export * from "./makeRequest"
export * from "./needsRetry"
export * from "./parseAndSubmitAuthorizeForm"
export * from "./parseGeneratedCode"
export * from "./parseSourceCode"
export * from "./parseSasViyaLog"
export * from "./serialize"
export * from "./splitChunks"
export * from "./parseWeboutResponse"
export * from './asyncForEach'
export * from './compareTimestamps'
export * from './convertToCsv'
export * from './isAuthorizeFormRequired'
export * from './isLoginRequired'
export * from './isLoginSuccess'
export * from './makeRequest'
export * from './needsRetry'
export * from './parseAndSubmitAuthorizeForm'
export * from './parseGeneratedCode'
export * from './parseSourceCode'
export * from './parseSasViyaLog'
export * from './serialize'
export * from './splitChunks'
export * from './parseWeboutResponse'

View File

@@ -1,29 +1,29 @@
export function isIEorEdgeOrOldFirefox() {
if (typeof window === "undefined") {
if (typeof window === 'undefined') {
return false
}
const ua = window.navigator.userAgent
if (ua.indexOf("Firefox") > 0) {
if (ua.indexOf('Firefox') > 0) {
const version = parseInt(
ua.substring(ua.lastIndexOf("Firefox/") + 8, ua.length),
ua.substring(ua.lastIndexOf('Firefox/') + 8, ua.length),
10
)
return version <= 60
}
const msie = ua.indexOf("MSIE ")
const msie = ua.indexOf('MSIE ')
if (msie > 0) {
// IE 10 or older => return version number
return true
}
const trident = ua.indexOf("Trident/")
const trident = ua.indexOf('Trident/')
if (trident > 0) {
return true
}
const edge = ua.indexOf("Edge/")
const edge = ua.indexOf('Edge/')
if (edge > 0) {
// Edge (IE 12+) => return version number
return true

View File

@@ -1,5 +1,5 @@
import { CsrfToken } from "../types"
import { needsRetry } from "./needsRetry"
import { CsrfToken } from '../types'
import { needsRetry } from './needsRetry'
let retryCount: number = 0
let retryLimit: number = 5
@@ -8,28 +8,28 @@ export async function makeRequest<T>(
url: string,
request: RequestInit,
callback: (value: CsrfToken) => any,
contentType: "text" | "json" = "json"
contentType: 'text' | 'json' = 'json'
): Promise<{ result: T; etag: string | null }> {
let retryRequest: any = null
const responseTransform =
contentType === "json"
contentType === 'json'
? (res: Response) => res.json()
: (res: Response) => res.text()
let etag = null
const result = await fetch(url, request).then(async (response) => {
if (response.redirected && response.url.includes("SASLogon/login")) {
if (response.redirected && response.url.includes('SASLogon/login')) {
return Promise.reject({ status: 401 })
}
if (!response.ok) {
if (response.status === 403) {
const tokenHeader = response.headers.get("X-CSRF-HEADER")
const tokenHeader = response.headers.get('X-CSRF-HEADER')
if (tokenHeader) {
const token = response.headers.get(tokenHeader)
callback({
headerName: tokenHeader,
value: token || ""
value: token || ''
})
retryRequest = {
@@ -37,7 +37,7 @@ export async function makeRequest<T>(
headers: { ...request.headers, [tokenHeader]: token }
}
return fetch(url, retryRequest).then((res) => {
etag = res.headers.get("ETag")
etag = res.headers.get('ETag')
return responseTransform(res)
})
}
@@ -60,7 +60,7 @@ export async function makeRequest<T>(
} else {
retryCount = 0
throw new Error("Request retry limit exceeded")
throw new Error('Request retry limit exceeded')
}
}
@@ -71,9 +71,9 @@ export async function makeRequest<T>(
return Promise.resolve()
}
const responseTransformed = await responseTransform(response)
let responseText = ""
let responseText = ''
if (typeof responseTransformed === "string") {
if (typeof responseTransformed === 'string') {
responseText = responseTransformed
} else {
responseText = JSON.stringify(responseTransformed)
@@ -95,11 +95,11 @@ export async function makeRequest<T>(
} else {
retryCount = 0
throw new Error("Request retry limit exceeded")
throw new Error('Request retry limit exceeded')
}
}
etag = response.headers.get("ETag")
etag = response.headers.get('ETag')
return responseTransformed
}
})

View File

@@ -2,13 +2,13 @@ export const needsRetry = (responseText: string): boolean => {
return (
!!responseText &&
((responseText.includes('"errorCode":403') &&
responseText.includes("_csrf") &&
responseText.includes("X-CSRF-TOKEN")) ||
responseText.includes('_csrf') &&
responseText.includes('X-CSRF-TOKEN')) ||
(responseText.includes('"status":403') &&
responseText.includes('"error":"Forbidden"')) ||
(responseText.includes('"status":449') &&
responseText.includes(
"Authentication success, retry original request"
'Authentication success, retry original request'
)))
)
}

View File

@@ -5,18 +5,18 @@ export const parseAndSubmitAuthorizeForm = async (
let authUrl: string | null = null
const params: any = {}
const responseBody = response.split("<body>")[1].split("</body>")[0]
const bodyElement = document.createElement("div")
const responseBody = response.split('<body>')[1].split('</body>')[0]
const bodyElement = document.createElement('div')
bodyElement.innerHTML = responseBody
const form = bodyElement.querySelector("#application_authorization")
authUrl = form ? serverUrl + form.getAttribute("action") : null
const form = bodyElement.querySelector('#application_authorization')
authUrl = form ? serverUrl + form.getAttribute('action') : null
const inputs: any = form?.querySelectorAll("input")
const inputs: any = form?.querySelectorAll('input')
for (const input of inputs) {
if (input.name === "user_oauth_approval") {
input.value = "true"
if (input.name === 'user_oauth_approval') {
input.value = 'true'
}
params[input.name] = input.value
@@ -33,17 +33,17 @@ export const parseAndSubmitAuthorizeForm = async (
return new Promise((resolve, reject) => {
if (authUrl) {
fetch(authUrl, {
method: "POST",
credentials: "include",
method: 'POST',
credentials: 'include',
body: formData,
referrerPolicy: "same-origin"
referrerPolicy: 'same-origin'
})
.then((res) => res.text())
.then((res) => {
resolve(res)
})
} else {
reject("Auth form url is null")
reject('Auth form url is null')
}
})
}

View File

@@ -1,7 +1,7 @@
export const parseGeneratedCode = (log: string) => {
const startsWith = "MPRINT"
const startsWith = 'MPRINT'
const isGeneratedCodeLine = (line: string) =>
line.trim().startsWith(startsWith)
const logLines = log.split("\n").filter(isGeneratedCodeLine)
return logLines.join("\r\n")
const logLines = log.split('\n').filter(isGeneratedCodeLine)
return logLines.join('\r\n')
}

View File

@@ -2,10 +2,10 @@ export const parseSasViyaLog = (logResponse: { items: any[] }) => {
let log
try {
log = logResponse.items
? logResponse.items.map((i) => i.line).join("\n")
? logResponse.items.map((i) => i.line).join('\n')
: JSON.stringify(logResponse)
} catch (e) {
console.error("An error has occurred while parsing the log response", e)
console.error('An error has occurred while parsing the log response', e)
log = logResponse
}
return log

View File

@@ -1,6 +1,6 @@
export const parseSourceCode = (log: string): string => {
const isSourceCodeLine = (line: string) =>
line.trim().substring(0, 10).trimStart().match(/^\d/)
const logLines = log.split("\n").filter(isSourceCodeLine)
return logLines.join("\r\n")
const logLines = log.split('\n').filter(isSourceCodeLine)
return logLines.join('\r\n')
}

View File

@@ -1,13 +1,13 @@
export const parseWeboutResponse = (response: string) => {
let sasResponse = ""
let sasResponse = ''
if (response.includes(">>weboutBEGIN<<")) {
if (response.includes('>>weboutBEGIN<<')) {
try {
sasResponse = response
.split(">>weboutBEGIN<<")[1]
.split(">>weboutEND<<")[0]
.split('>>weboutBEGIN<<')[1]
.split('>>weboutEND<<')[0]
} catch (e) {
sasResponse = ""
sasResponse = ''
console.error(e)
}
}

View File

@@ -4,12 +4,12 @@ export const serialize = (obj: any) => {
if (obj.hasOwnProperty(p)) {
if (obj[p] instanceof Array) {
for (let i = 0, n = obj[p].length; i < n; i++) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p][i]))
str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p][i]))
}
} else {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]))
str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]))
}
}
}
return str.join("&")
return str.join('&')
}