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

Merge pull request #57 from sasjs/change-code-style

chore(*): change code style to remove semicolons
This commit is contained in:
Krishna Acondy
2020-09-01 12:07:24 +01:00
committed by GitHub
39 changed files with 1001 additions and 1003 deletions

View File

@@ -1,6 +1,6 @@
{ {
"trailingComma": "none", "trailingComma": "none",
"tabWidth": 2, "tabWidth": 2,
"semi": true, "semi": false,
"singleQuote": false "singleQuote": false
} }

View File

@@ -1,8 +1,8 @@
import { isLogInRequired, needsRetry } from "./utils"; import { isLogInRequired, needsRetry } from "./utils"
import { CsrfToken } from "./types/CsrfToken"; import { CsrfToken } from "./types/CsrfToken"
import { UploadFile } from "./types/UploadFile"; import { UploadFile } from "./types/UploadFile"
const requestRetryLimit = 5; const requestRetryLimit = 5
export class FileUploader { export class FileUploader {
constructor( constructor(
@@ -12,38 +12,38 @@ export class FileUploader {
private setCsrfTokenWeb: any, private setCsrfTokenWeb: any,
private csrfToken: CsrfToken | null = null private csrfToken: CsrfToken | null = null
) {} ) {}
private retryCount = 0; private retryCount = 0
public uploadFile(sasJob: string, files: UploadFile[], params: any) { 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) { for (let param in params) {
if (params.hasOwnProperty(param)) { if (params.hasOwnProperty(param)) {
paramsString += `&${param}=${params[param]}`; paramsString += `&${param}=${params[param]}`
} }
} }
const program = this.appLoc const program = this.appLoc
? this.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "") ? this.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "")
: sasJob; : sasJob
const uploadUrl = `${this.serverUrl}${this.jobsPath}/?${ const uploadUrl = `${this.serverUrl}${this.jobsPath}/?${
"_program=" + program "_program=" + program
}${paramsString}`; }${paramsString}`
const headers = { const headers = {
"cache-control": "no-cache" "cache-control": "no-cache"
}; }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const formData = new FormData(); const formData = new FormData()
for (let file of files) { 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, { fetch(uploadUrl, {
method: "POST", method: "POST",
@@ -54,47 +54,47 @@ export class FileUploader {
.then(async (response) => { .then(async (response) => {
if (!response.ok) { if (!response.ok) {
if (response.status === 403) { if (response.status === 403) {
const tokenHeader = response.headers.get("X-CSRF-HEADER"); const tokenHeader = response.headers.get("X-CSRF-HEADER")
if (tokenHeader) { if (tokenHeader) {
const token = response.headers.get(tokenHeader); const token = response.headers.get(tokenHeader)
this.csrfToken = { this.csrfToken = {
headerName: tokenHeader, headerName: tokenHeader,
value: token || "" value: token || ""
}; }
this.setCsrfTokenWeb(this.csrfToken); this.setCsrfTokenWeb(this.csrfToken)
} }
} }
} }
return response.text(); return response.text()
}) })
.then((responseText) => { .then((responseText) => {
if (isLogInRequired(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 (needsRetry(responseText)) {
if (this.retryCount < requestRetryLimit) { if (this.retryCount < requestRetryLimit) {
this.retryCount++; this.retryCount++
this.uploadFile(sasJob, files, params).then( this.uploadFile(sasJob, files, params).then(
(res: any) => resolve(res), (res: any) => resolve(res),
(err: any) => reject(err) (err: any) => reject(err)
); )
} else { } else {
this.retryCount = 0; this.retryCount = 0
reject(responseText); reject(responseText)
} }
} else { } else {
this.retryCount = 0; this.retryCount = 0
try { try {
resolve(JSON.parse(responseText)); resolve(JSON.parse(responseText))
} catch (e) { } catch (e) {
reject(e); reject(e)
} }
} }
}); })
}); })
} }
} }

View File

@@ -11,7 +11,7 @@ export class SAS9ApiClient {
public getConfig() { public getConfig() {
return { return {
serverUrl: this.serverUrl serverUrl: this.serverUrl
}; }
} }
/** /**
@@ -19,7 +19,7 @@ export class SAS9ApiClient {
* @param serverUrl - the URL of the server. * @param serverUrl - the URL of the server.
*/ */
public setConfig(serverUrl: string) { public setConfig(serverUrl: string) {
if (serverUrl) this.serverUrl = serverUrl; if (serverUrl) this.serverUrl = serverUrl
} }
/** /**
@@ -33,19 +33,19 @@ export class SAS9ApiClient {
serverName: string, serverName: string,
repositoryName: string repositoryName: string
) { ) {
const requestPayload = linesOfCode.join("\n"); const requestPayload = linesOfCode.join("\n")
const executeScriptRequest = { const executeScriptRequest = {
method: "PUT", method: "PUT",
headers: { headers: {
Accept: "application/json" Accept: "application/json"
}, },
body: `command=${requestPayload}` body: `command=${requestPayload}`
}; }
const executeScriptResponse = await fetch( const executeScriptResponse = await fetch(
`${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`, `${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`,
executeScriptRequest executeScriptRequest
).then((res) => res.text()); ).then((res) => res.text())
return executeScriptResponse; return executeScriptResponse
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +1,36 @@
import SASjs from "./index"; import SASjs from "./index"
const adapter = new SASjs(); const adapter = new SASjs()
it("should parse SAS9 source code", async (done) => { it("should parse SAS9 source code", async (done) => {
expect(sampleResponse).toBeTruthy(); expect(sampleResponse).toBeTruthy()
const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse); const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse)
expect(parsedSourceCode).toBeTruthy(); expect(parsedSourceCode).toBeTruthy()
const sourceCodeLines = parsedSourceCode.split("\r\n"); const sourceCodeLines = parsedSourceCode.split("\r\n")
expect(sourceCodeLines.length).toEqual(5); expect(sourceCodeLines.length).toEqual(5)
expect(sourceCodeLines[0].startsWith("6")).toBeTruthy(); expect(sourceCodeLines[0].startsWith("6")).toBeTruthy()
expect(sourceCodeLines[1].startsWith("7")).toBeTruthy(); expect(sourceCodeLines[1].startsWith("7")).toBeTruthy()
expect(sourceCodeLines[2].startsWith("8")).toBeTruthy(); expect(sourceCodeLines[2].startsWith("8")).toBeTruthy()
expect(sourceCodeLines[3].startsWith("9")).toBeTruthy(); expect(sourceCodeLines[3].startsWith("9")).toBeTruthy()
expect(sourceCodeLines[4].startsWith("10")).toBeTruthy(); expect(sourceCodeLines[4].startsWith("10")).toBeTruthy()
done(); done()
}); })
it("should parse generated code", async (done) => { it("should parse generated code", async (done) => {
expect(sampleResponse).toBeTruthy(); expect(sampleResponse).toBeTruthy()
const parsedGeneratedCode = (adapter as any).parseGeneratedCode( const parsedGeneratedCode = (adapter as any).parseGeneratedCode(
sampleResponse sampleResponse
); )
expect(parsedGeneratedCode).toBeTruthy(); expect(parsedGeneratedCode).toBeTruthy()
const generatedCodeLines = parsedGeneratedCode.split("\r\n"); const generatedCodeLines = parsedGeneratedCode.split("\r\n")
expect(generatedCodeLines.length).toEqual(5); expect(generatedCodeLines.length).toEqual(5)
expect(generatedCodeLines[0].startsWith("MPRINT(MM_WEBIN)")).toBeTruthy(); expect(generatedCodeLines[0].startsWith("MPRINT(MM_WEBIN)")).toBeTruthy()
expect(generatedCodeLines[1].startsWith("MPRINT(MM_WEBLEFT)")).toBeTruthy(); expect(generatedCodeLines[1].startsWith("MPRINT(MM_WEBLEFT)")).toBeTruthy()
expect(generatedCodeLines[2].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy(); expect(generatedCodeLines[2].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy()
expect(generatedCodeLines[3].startsWith("MPRINT(MM_WEBRIGHT)")).toBeTruthy(); expect(generatedCodeLines[3].startsWith("MPRINT(MM_WEBRIGHT)")).toBeTruthy()
expect(generatedCodeLines[4].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy(); expect(generatedCodeLines[4].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy()
done(); done()
}); })
/* tslint:disable */ /* tslint:disable */
const sampleResponse = `<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/> const sampleResponse = `<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/>
@@ -44,5 +44,5 @@ MPRINT(MM_WEBLEFT): filename _temp temp lrecl=999999;
MPRINT(MM_WEBOUT): data _null_; MPRINT(MM_WEBOUT): data _null_;
MPRINT(MM_WEBRIGHT): file _temp; MPRINT(MM_WEBRIGHT): file _temp;
MPRINT(MM_WEBOUT): if upcase(symget('_debug'))='LOG' then put '&gt;&gt;weboutBEGIN&lt;&lt;'; MPRINT(MM_WEBOUT): if upcase(symget('_debug'))='LOG' then put '&gt;&gt;weboutBEGIN&lt;&lt;';
`; `
/* tslint:enable */ /* tslint:enable */

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import { Session, Context, CsrfToken } from "./types"; import { Session, Context, CsrfToken } from "./types"
import { asyncForEach, makeRequest } from "./utils"; import { asyncForEach, makeRequest } from "./utils"
const MAX_SESSION_COUNT = 1; const MAX_SESSION_COUNT = 1
export class SessionManager { export class SessionManager {
constructor( constructor(
@@ -9,49 +9,49 @@ export class SessionManager {
private contextName: string, private contextName: string,
private setCsrfToken: (csrfToken: CsrfToken) => void private setCsrfToken: (csrfToken: CsrfToken) => void
) {} ) {}
private sessions: Session[] = []; private sessions: Session[] = []
private currentContext: Context | null = null; private currentContext: Context | null = null
private csrfToken: CsrfToken | null = null; private csrfToken: CsrfToken | null = null
async getSession(accessToken?: string) { async getSession(accessToken?: string) {
await this.createSessions(accessToken); await this.createSessions(accessToken)
this.createAndWaitForSession(accessToken); this.createAndWaitForSession(accessToken)
const session = this.sessions.pop(); const session = this.sessions.pop()
const secondsSinceSessionCreation = const secondsSinceSessionCreation =
(new Date().getTime() - new Date(session!.creationTimeStamp).getTime()) / (new Date().getTime() - new Date(session!.creationTimeStamp).getTime()) /
1000; 1000
if ( if (
secondsSinceSessionCreation >= session!.attributes.sessionInactiveTimeout secondsSinceSessionCreation >= session!.attributes.sessionInactiveTimeout
) { ) {
await this.createSessions(accessToken); await this.createSessions(accessToken)
const freshSession = this.sessions.pop(); const freshSession = this.sessions.pop()
return freshSession; return freshSession
} }
return session; return session
} }
async clearSession(id: string, accessToken?: string) { async clearSession(id: string, accessToken?: string) {
const deleteSessionRequest = { const deleteSessionRequest = {
method: "DELETE", method: "DELETE",
headers: this.getHeaders(accessToken) headers: this.getHeaders(accessToken)
}; }
return await this.request<Session>( return await this.request<Session>(
`${this.serverUrl}/compute/sessions/${id}`, `${this.serverUrl}/compute/sessions/${id}`,
deleteSessionRequest deleteSessionRequest
).then(() => { ).then(() => {
this.sessions = this.sessions.filter((s) => s.id !== id); this.sessions = this.sessions.filter((s) => s.id !== id)
}); })
} }
private async createSessions(accessToken?: string) { private async createSessions(accessToken?: string) {
if (!this.sessions.length) { if (!this.sessions.length) {
if (!this.currentContext) { if (!this.currentContext) {
await this.setCurrentContext(accessToken); await this.setCurrentContext(accessToken)
} }
await asyncForEach(new Array(MAX_SESSION_COUNT), async () => { await asyncForEach(new Array(MAX_SESSION_COUNT), async () => {
const createdSession = await this.createAndWaitForSession(accessToken); const createdSession = await this.createAndWaitForSession(accessToken)
this.sessions.push(createdSession); this.sessions.push(createdSession)
}); })
} }
} }
@@ -59,53 +59,53 @@ export class SessionManager {
const createSessionRequest = { const createSessionRequest = {
method: "POST", method: "POST",
headers: this.getHeaders(accessToken) headers: this.getHeaders(accessToken)
}; }
const { result: createdSession, etag } = await this.request<Session>( const { result: createdSession, etag } = await this.request<Session>(
`${this.serverUrl}/compute/contexts/${this.currentContext!.id}/sessions`, `${this.serverUrl}/compute/contexts/${this.currentContext!.id}/sessions`,
createSessionRequest createSessionRequest
); )
await this.waitForSession(createdSession, etag); await this.waitForSession(createdSession, etag)
this.sessions.push(createdSession); this.sessions.push(createdSession)
return createdSession; return createdSession
} }
private async setCurrentContext(accessToken?: string) { private async setCurrentContext(accessToken?: string) {
if (!this.currentContext) { if (!this.currentContext) {
const { result: contexts } = await this.request<{ const { result: contexts } = await this.request<{
items: Context[]; items: Context[]
}>(`${this.serverUrl}/compute/contexts`, { }>(`${this.serverUrl}/compute/contexts`, {
headers: this.getHeaders(accessToken) headers: this.getHeaders(accessToken)
}); })
const contextsList = const contextsList =
contexts && contexts.items && contexts.items.length contexts && contexts.items && contexts.items.length
? contexts.items ? contexts.items
: []; : []
const currentContext = contextsList.find( const currentContext = contextsList.find(
(c: any) => c.name === this.contextName (c: any) => c.name === this.contextName
); )
if (!currentContext) { if (!currentContext) {
throw new Error( throw new Error(
`The context ${this.contextName} was not found on the server ${this.serverUrl}` `The context ${this.contextName} was not found on the server ${this.serverUrl}`
); )
} }
this.currentContext = currentContext; this.currentContext = currentContext
} }
} }
private getHeaders(accessToken?: string) { private getHeaders(accessToken?: string) {
const headers: any = { const headers: any = {
"Content-Type": "application/json" "Content-Type": "application/json"
}; }
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`; headers.Authorization = `Bearer ${accessToken}`
} }
return headers; return headers
} }
private async waitForSession( private async waitForSession(
@@ -114,17 +114,17 @@ export class SessionManager {
accessToken?: string, accessToken?: string,
silent = false silent = false
) { ) {
let sessionState = session.state; let sessionState = session.state
const headers: any = { const headers: any = {
...this.getHeaders(accessToken), ...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, _) => { return new Promise(async (resolve, _) => {
if (sessionState === "pending") { if (sessionState === "pending") {
if (stateLink) { if (stateLink) {
if (!silent) { if (!silent) {
console.log("Polling session status... \n"); console.log("Polling session status... \n")
} }
const { result: state } = await this.request<string>( const { result: state } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?wait=30`, `${this.serverUrl}${stateLink.href}?wait=30`,
@@ -132,18 +132,18 @@ export class SessionManager {
headers headers
}, },
"text" "text"
); )
sessionState = state.trim(); sessionState = state.trim()
if (!silent) { if (!silent) {
console.log(`Current state: ${sessionState}\n`); console.log(`Current state: ${sessionState}\n`)
} }
resolve(sessionState); resolve(sessionState)
} }
} else { } else {
resolve(sessionState); resolve(sessionState)
} }
}); })
} }
private async request<T>( private async request<T>(
@@ -155,16 +155,16 @@ export class SessionManager {
options.headers = { options.headers = {
...options.headers, ...options.headers,
[this.csrfToken.headerName]: this.csrfToken.value [this.csrfToken.headerName]: this.csrfToken.value
}; }
} }
return await makeRequest<T>( return await makeRequest<T>(
url, url,
options, options,
(token) => { (token) => {
this.csrfToken = token; this.csrfToken = token
this.setCsrfToken(token); this.setCsrfToken(token)
}, },
contentType contentType
); )
} }
} }

View File

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

View File

@@ -1,6 +1,6 @@
export interface Context { export interface Context {
name: string; name: string
id: string; id: string
createdBy: string; createdBy: string
version: number; version: number
} }

View File

@@ -1,4 +1,4 @@
export interface CsrfToken { export interface CsrfToken {
headerName: string; headerName: string
value: string; value: string
} }

View File

@@ -1,7 +1,7 @@
import { Link } from "./Link"; import { Link } from "./Link"
export interface Folder { export interface Folder {
id: string; id: string
uri: string; uri: string
links: Link[]; links: Link[]
} }

View File

@@ -1,13 +1,13 @@
import { Link } from "./Link"; import { Link } from "./Link"
import { JobResult } from "./JobResult"; import { JobResult } from "./JobResult"
export interface Job { export interface Job {
id: string; id: string
name: string; name: string
uri: string; uri: string
createdBy: string; createdBy: string
code?: string; code?: string
links: Link[]; links: Link[]
results: JobResult; results: JobResult
error?: any; error?: any
} }

View File

@@ -1,3 +1,3 @@
export interface JobDefinition { export interface JobDefinition {
code: string; code: string
} }

View File

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

View File

@@ -1,7 +1,7 @@
export interface Link { export interface Link {
method: string; method: string
rel: string; rel: string
href: string; href: string
uri: string; uri: string
type: string; type: string
} }

View File

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

View File

@@ -3,10 +3,10 @@
* *
*/ */
export interface SASjsRequest { export interface SASjsRequest {
serviceLink: string; serviceLink: string
timestamp: Date; timestamp: Date
sourceCode: string; sourceCode: string
generatedCode: string; generatedCode: string
logFile: string; logFile: string
SASWORK: any; SASWORK: any
} }

View File

@@ -4,11 +4,11 @@
*/ */
export interface SASjsWaitingRequest { export interface SASjsWaitingRequest {
requestPromise: { requestPromise: {
promise: any; promise: any
resolve: any; resolve: any
reject: any; reject: any
}; }
SASjob: string; SASjob: string
data: any; data: any
config?: any; config?: any
} }

View File

@@ -1,11 +1,11 @@
import { Link } from "./Link"; import { Link } from "./Link"
export interface Session { export interface Session {
id: string; id: string
state: string; state: string
links: Link[]; links: Link[]
attributes: { attributes: {
sessionInactiveTimeout: number; sessionInactiveTimeout: number
}; }
creationTimeStamp: string; creationTimeStamp: string
} }

View File

@@ -3,6 +3,6 @@
* *
*/ */
export interface UploadFile { export interface UploadFile {
file: File; file: File
fileName: string; fileName: string
} }

View File

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

View File

@@ -1,5 +1,5 @@
export async function asyncForEach(array: any[], callback: any) { export async function asyncForEach(array: any[], callback: any) {
for (let index = 0; index < array.length; index++) { for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array); await callback(array[index], index, array)
} }
} }

View File

@@ -1,9 +1,9 @@
import { SASjsRequest } from "../types/SASjsRequest"; import { SASjsRequest } from "../types/SASjsRequest"
/** /**
* Comparator for SASjs request timestamps * Comparator for SASjs request timestamps
* *
*/ */
export const compareTimestamps = (a: SASjsRequest, b: SASjsRequest) => { export const compareTimestamps = (a: SASjsRequest, b: SASjsRequest) => {
return b.timestamp.getTime() - a.timestamp.getTime(); return b.timestamp.getTime() - a.timestamp.getTime()
}; }

View File

@@ -3,14 +3,14 @@
* @param data - the JSON object to convert. * @param data - the JSON object to convert.
*/ */
export const convertToCSV = (data: any) => { 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]); const headerFields = Object.keys(data[0])
let csvTest; let csvTest
let invalidString = false; let invalidString = false
const headers = headerFields.map((field) => { const headers = headerFields.map((field) => {
let firstFoundType: string | null = null; let firstFoundType: string | null = null
let hasMixedTypes: boolean = false; let hasMixedTypes: boolean = false
let rowNumError: number = -1; let rowNumError: number = -1
const longestValueForField = data const longestValueForField = data
.map((row: any, index: number) => { .map((row: any, index: number) => {
@@ -19,46 +19,46 @@ export const convertToCSV = (data: any) => {
let currentFieldType = let currentFieldType =
row[field] === "" || typeof row[field] === "string" row[field] === "" || typeof row[field] === "string"
? "chars" ? "chars"
: "number"; : "number"
if (!hasMixedTypes) { if (!hasMixedTypes) {
hasMixedTypes = currentFieldType !== firstFoundType; hasMixedTypes = currentFieldType !== firstFoundType
rowNumError = hasMixedTypes ? index + 1 : -1; rowNumError = hasMixedTypes ? index + 1 : -1
} }
} else { } else {
if (row[field] === "") { if (row[field] === "") {
firstFoundType = "chars"; firstFoundType = "chars"
} else { } else {
firstFoundType = firstFoundType =
typeof row[field] === "string" ? "chars" : "number"; typeof row[field] === "string" ? "chars" : "number"
} }
} }
let byteSize; let byteSize
if (typeof row[field] === "string") { if (typeof row[field] === "string") {
let doubleQuotesFound = row[field] let doubleQuotesFound = row[field]
.split("") .split("")
.filter((char: any) => char === '"'); .filter((char: any) => char === '"')
byteSize = getByteSize(row[field]); byteSize = getByteSize(row[field])
if (doubleQuotesFound.length > 0) { if (doubleQuotesFound.length > 0) {
byteSize += doubleQuotesFound.length; byteSize += doubleQuotesFound.length
} }
} }
return byteSize; return byteSize
} }
}) })
.sort((a: number, b: number) => b - a)[0]; .sort((a: number, b: number) => b - a)[0]
if (longestValueForField && longestValueForField > 32765) { if (longestValueForField && longestValueForField > 32765) {
invalidString = true; invalidString = true
} }
if (hasMixedTypes) { if (hasMixedTypes) {
console.error( console.error(
`Row (${rowNumError}), Column (${field}) has mixed types: ERROR` `Row (${rowNumError}), Column (${field}) has mixed types: ERROR`
); )
} }
return `${field}:${firstFoundType === "chars" ? "$" : ""}${ return `${field}:${firstFoundType === "chars" ? "$" : ""}${
@@ -67,30 +67,30 @@ export const convertToCSV = (data: any) => {
: firstFoundType === "chars" : firstFoundType === "chars"
? "1" ? "1"
: "best" : "best"
}.`; }.`
}); })
if (invalidString) { if (invalidString) {
return "ERROR: LARGE STRING LENGTH"; return "ERROR: LARGE STRING LENGTH"
} }
csvTest = data.map((row: any) => { csvTest = data.map((row: any) => {
const fields = Object.keys(row).map((fieldName, index) => { const fields = Object.keys(row).map((fieldName, index) => {
let value; let value
let containsSpecialChar = false; let containsSpecialChar = false
const currentCell = row[fieldName]; const currentCell = row[fieldName]
if (JSON.stringify(currentCell).search(/(\\t|\\n|\\r)/gm) > -1) { if (JSON.stringify(currentCell).search(/(\\t|\\n|\\r)/gm) > -1) {
value = currentCell.toString(); value = currentCell.toString()
containsSpecialChar = true; containsSpecialChar = true
} else { } else {
value = JSON.stringify(currentCell, replacer); value = JSON.stringify(currentCell, replacer)
} }
value = value.replace(/\\\\/gm, "\\"); value = value.replace(/\\\\/gm, "\\")
if (containsSpecialChar) { if (containsSpecialChar) {
if (value.includes(",") || value.includes('"')) { if (value.includes(",") || value.includes('"')) {
value = '"' + value + '"'; value = '"' + value + '"'
} }
} else { } else {
if ( if (
@@ -98,36 +98,36 @@ export const convertToCSV = (data: any) => {
value.includes('"') && value.includes('"') &&
!value.includes('\\"') !value.includes('\\"')
) { ) {
value = value.substring(1, value.length - 1); value = value.substring(1, value.length - 1)
} }
value = value.replace(/\\"/gm, '""'); value = value.replace(/\\"/gm, '""')
} }
value = value.replace(/\r\n/gm, "\n"); value = value.replace(/\r\n/gm, "\n")
if (value === "" && headers[index].includes("best")) { if (value === "" && headers[index].includes("best")) {
value = "."; value = "."
} }
return value; return value
}); })
return fields.join(","); return fields.join(",")
}); })
let finalCSV = let finalCSV =
headers.join(",").replace(/,/g, " ") + "\r\n" + csvTest.join("\r\n"); headers.join(",").replace(/,/g, " ") + "\r\n" + csvTest.join("\r\n")
return finalCSV; return finalCSV
}; }
const getByteSize = (str: string) => { const getByteSize = (str: string) => {
let byteSize = str.length; let byteSize = str.length
for (let i = str.length - 1; i >= 0; i--) { for (let i = str.length - 1; i >= 0; i--) {
const code = str.charCodeAt(i); const code = str.charCodeAt(i)
if (code > 0x7f && code <= 0x7ff) byteSize++; if (code > 0x7f && code <= 0x7ff) byteSize++
else if (code > 0x7ff && code <= 0xffff) byteSize += 2; else if (code > 0x7ff && code <= 0xffff) byteSize += 2
if (code >= 0xdc00 && code <= 0xdfff) i--; //trail surrogate if (code >= 0xdc00 && code <= 0xdfff) i-- //trail surrogate
} }
return byteSize; return byteSize
}; }

View File

@@ -1,33 +1,33 @@
import { convertToCSV } from "./convertToCsv"; import { convertToCSV } from "./convertToCsv"
import { splitChunks } from "./splitChunks"; import { splitChunks } from "./splitChunks"
export const formatDataForRequest = (data: any) => { export const formatDataForRequest = (data: any) => {
const sasjsTables = []; const sasjsTables = []
let tableCounter = 0; let tableCounter = 0
const result: any = {}; const result: any = {}
for (const tableName in data) { for (const tableName in data) {
tableCounter++; tableCounter++
sasjsTables.push(tableName); sasjsTables.push(tableName)
const csv = convertToCSV(data[tableName]); const csv = convertToCSV(data[tableName])
if (csv === "ERROR: LARGE STRING LENGTH") { if (csv === "ERROR: LARGE STRING LENGTH") {
throw new Error( 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 // if csv has length more then 16k, send in chunks
if (csv.length > 16000) { if (csv.length > 16000) {
const csvChunks = splitChunks(csv); const csvChunks = splitChunks(csv)
// append chunks to form data with same key // append chunks to form data with same key
result[`sasjs${tableCounter}data0`] = csvChunks.length; result[`sasjs${tableCounter}data0`] = csvChunks.length
csvChunks.forEach((chunk, index) => { csvChunks.forEach((chunk, index) => {
result[`sasjs${tableCounter}data${index + 1}`] = chunk; result[`sasjs${tableCounter}data${index + 1}`] = chunk
}); })
} else { } else {
result[`sasjs${tableCounter}data`] = csv; result[`sasjs${tableCounter}data`] = csv
} }
} }
result["sasjs_tables"] = sasjsTables.join(" "); result["sasjs_tables"] = sasjsTables.join(" ")
return result; return result
}; }

View File

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

View File

@@ -1,3 +1,3 @@
export const isAuthorizeFormRequired = (response: string): boolean => { export const isAuthorizeFormRequired = (response: string): boolean => {
return /<form.+action="(.*Logon\/oauth\/authorize[^"]*).*>/gm.test(response); return /<form.+action="(.*Logon\/oauth\/authorize[^"]*).*>/gm.test(response)
}; }

View File

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

View File

@@ -1,5 +1,5 @@
export const isLogInRequired = (response: string): boolean => { export const isLogInRequired = (response: string): boolean => {
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/gm; const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/gm
const matches = pattern.test(response); const matches = pattern.test(response)
return matches; return matches
}; }

View File

@@ -1,2 +1,2 @@
export const isLogInSuccess = (response: string): boolean => export const isLogInSuccess = (response: string): boolean =>
/You have signed in/gm.test(response); /You have signed in/gm.test(response)

View File

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

View File

@@ -10,5 +10,5 @@ export const needsRetry = (responseText: string): boolean => {
responseText.includes( responseText.includes(
"Authentication success, retry original request" "Authentication success, retry original request"
))) )))
); )
}; }

View File

@@ -2,31 +2,31 @@ export const parseAndSubmitAuthorizeForm = async (
response: string, response: string,
serverUrl: string serverUrl: string
) => { ) => {
let authUrl: string | null = null; let authUrl: string | null = null
const params: any = {}; const params: any = {}
const responseBody = response.split("<body>")[1].split("</body>")[0]; const responseBody = response.split("<body>")[1].split("</body>")[0]
const bodyElement = document.createElement("div"); const bodyElement = document.createElement("div")
bodyElement.innerHTML = responseBody; bodyElement.innerHTML = responseBody
const form = bodyElement.querySelector("#application_authorization"); const form = bodyElement.querySelector("#application_authorization")
authUrl = form ? serverUrl + form.getAttribute("action") : null; authUrl = form ? serverUrl + form.getAttribute("action") : null
const inputs: any = form?.querySelectorAll("input"); const inputs: any = form?.querySelectorAll("input")
for (const input of inputs) { for (const input of inputs) {
if (input.name === "user_oauth_approval") { if (input.name === "user_oauth_approval") {
input.value = "true"; input.value = "true"
} }
params[input.name] = input.value; params[input.name] = input.value
} }
const formData = new FormData(); const formData = new FormData()
for (const key in params) { for (const key in params) {
if (params.hasOwnProperty(key)) { if (params.hasOwnProperty(key)) {
formData.append(key, params[key]); formData.append(key, params[key])
} }
} }
@@ -40,10 +40,10 @@ export const parseAndSubmitAuthorizeForm = async (
}) })
.then((res) => res.text()) .then((res) => res.text())
.then((res) => { .then((res) => {
resolve(res); resolve(res)
}); })
} else { } else {
reject("Auth form url is null"); reject("Auth form url is null")
} }
}); })
}; }

View File

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

View File

@@ -1,12 +1,12 @@
export const parseSasViyaLog = (logResponse: { items: any[] }) => { export const parseSasViyaLog = (logResponse: { items: any[] }) => {
let log; let log
try { try {
log = logResponse.items log = logResponse.items
? logResponse.items.map((i) => i.line).join("\n") ? logResponse.items.map((i) => i.line).join("\n")
: JSON.stringify(logResponse); : JSON.stringify(logResponse)
} catch (e) { } 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; log = logResponse
} }
return log; return log
}; }

View File

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

View File

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

View File

@@ -1,15 +1,15 @@
export const serialize = (obj: any) => { export const serialize = (obj: any) => {
const str: any[] = []; const str: any[] = []
for (const p in obj) { for (const p in obj) {
if (obj.hasOwnProperty(p)) { if (obj.hasOwnProperty(p)) {
if (obj[p] instanceof Array) { if (obj[p] instanceof Array) {
for (let i = 0, n = obj[p].length; i < n; i++) { 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 { } else {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]))
} }
} }
} }
return str.join("&"); return str.join("&")
}; }

View File

@@ -1,12 +1,12 @@
export const splitChunks = (content: string) => { export const splitChunks = (content: string) => {
const size = 16000; const size = 16000
const numChunks = Math.ceil(content.length / size); const numChunks = Math.ceil(content.length / size)
const chunks = new Array(numChunks); const chunks = new Array(numChunks)
for (let i = 0, o = 0; i < numChunks; ++i, o += size) { for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
chunks[i] = content.substr(o, size); chunks[i] = content.substr(o, size)
} }
return chunks; return chunks
}; }