diff --git a/example.html b/example.html index d02c8ef..7ce2058 100644 --- a/example.html +++ b/example.html @@ -1,107 +1,114 @@ - - - - - + - - -
-
-
-

Demo Seed App for SASjs

- -
-
- -
- +
+ +
+ \ No newline at end of file diff --git a/src/SAS9ApiClient.ts b/src/SAS9ApiClient.ts index 8894b6e..ebedd80 100644 --- a/src/SAS9ApiClient.ts +++ b/src/SAS9ApiClient.ts @@ -3,49 +3,49 @@ * */ export class SAS9ApiClient { - constructor(private serverUrl: string) {} + constructor(private serverUrl: string) {} - /** - * returns on object containing the server URL - */ - public getConfig() { - return { - serverUrl: this.serverUrl, - }; - } + /** + * @returns an object containing the server URL + */ + public getConfig() { + return { + serverUrl: this.serverUrl, + } + } - /** - * Updates serverurl which is not null - * @param serverUrl - the URL of the server. - */ - public setConfig(serverUrl: string) { - if (serverUrl) this.serverUrl = serverUrl; - } + /** + * Updates serverUrl which is not null + * @param serverUrl - the URL of the server. + */ + public setConfig(serverUrl: string) { + if (serverUrl) this.serverUrl = serverUrl + } - /** - * Executes code on a SAS9 server. - * @param linesOfCode - an array of lines of code to execute - * @param serverName - the server to execute the code on - * @param repositoryName - the repository to execute the code on - */ - public async executeScript( - linesOfCode: string[], - serverName: string, - repositoryName: string - ) { - const requestPayload = linesOfCode.join("\n"); - const executeScriptRequest = { - method: "PUT", - headers: { - Accept: "application/json", - }, - body: `command=${requestPayload}`, - }; - const executeScriptResponse = await fetch( - `${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`, - executeScriptRequest - ).then((res) => res.text()); + /** + * Executes code on a SAS9 server. + * @param linesOfCode - an array of lines of code to execute + * @param serverName - the server to execute the code on + * @param repositoryName - the repository to execute the code on + */ + public async executeScript( + linesOfCode: string[], // FIXME: rename + serverName: string, + repositoryName: string + ) { + const requestPayload = linesOfCode.join('\n') + const executeScriptRequest = { + method: 'PUT', + headers: {Accept: 'application/json'}, + body: `command=${requestPayload}`, + } + // FIXME: use axios instead of fetch + const executeScriptResponse = await fetch( + `${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`, + executeScriptRequest + ).then((res) => res.text()) + // FIXME: no catch block - return executeScriptResponse; - } -} + return executeScriptResponse + } +} \ No newline at end of file diff --git a/src/SASViyaApiClient.ts b/src/SASViyaApiClient.ts index 535fb04..145472c 100644 --- a/src/SASViyaApiClient.ts +++ b/src/SASViyaApiClient.ts @@ -1,3 +1,13 @@ +/** + * TODO: needs to be split into logical blocks: + * - Folder + * - Config + * - Context + * - Session + * - Job + * - Auth + */ + import { isAuthorizeFormRequired, parseAndSubmitAuthorizeForm, @@ -32,7 +42,7 @@ export class SASViyaApiClient { if (this.rootFolderMap.size) { return this.rootFolderMap; } - + this.populateRootFolderMap(); return this.rootFolderMap; } diff --git a/src/SASjs.spec.ts b/src/SASjs.spec.ts index 70f200a..a1f0bd6 100644 --- a/src/SASjs.spec.ts +++ b/src/SASjs.spec.ts @@ -2,6 +2,7 @@ import SASjs from "./index"; const adapter = new SASjs(); +// FIXME: adapter doesn't have 'parseSAS9SourceCode' and 'parseGeneratedCode' it("should parse SAS9 source code", async done => { expect(sampleResponse).toBeTruthy(); const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse); diff --git a/src/SASjs.ts b/src/SASjs.ts index b6d953d..418f17c 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -1,3 +1,16 @@ +/** + * TODO: needs to be split into logical blocks: + * - Execute + * - Context + * - Session + * - Folder and services + * - Job + * - Auth + * - Config + * - Debug + * - Response + */ + import "isomorphic-fetch"; import * as e6p from "es6-promise"; (e6p as any).polyfill(); @@ -839,6 +852,7 @@ export default class SASjs { } } + // FIXME: this method never used private fetchLogFileContent(logLink: string) { return new Promise((resolve, reject) => { fetch(logLink, { @@ -1038,4 +1052,4 @@ export default class SASjs { ); }); } -} +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 39459c6..bc5ceae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ -import SASjs from "./SASjs"; -export * from "./types"; -export * from "./SASViyaApiClient"; -export * from "./SAS9ApiClient"; -export default SASjs; +import SASjs from './SASjs' + +export * from './types' +export * from './SASViyaApiClient' +export * from './SAS9ApiClient' + +export default SASjs \ No newline at end of file diff --git a/src/types/Context.ts b/src/types/Context.ts index c5ca093..37bbc1f 100644 --- a/src/types/Context.ts +++ b/src/types/Context.ts @@ -3,4 +3,4 @@ export interface Context { id: string; createdBy: string; version: number; -} +} \ No newline at end of file diff --git a/src/types/CsrfToken.ts b/src/types/CsrfToken.ts index 7161982..ce4b275 100644 --- a/src/types/CsrfToken.ts +++ b/src/types/CsrfToken.ts @@ -1,4 +1,4 @@ export interface CsrfToken { headerName: string; value: string; -} +} \ No newline at end of file diff --git a/src/types/Folder.ts b/src/types/Folder.ts index 849dc7c..d5716ef 100644 --- a/src/types/Folder.ts +++ b/src/types/Folder.ts @@ -4,4 +4,4 @@ export interface Folder { id: string; uri: string; links: Link[]; -} +} \ No newline at end of file diff --git a/src/types/Job.ts b/src/types/Job.ts index f6b93d9..7e81e0d 100644 --- a/src/types/Job.ts +++ b/src/types/Job.ts @@ -8,4 +8,4 @@ export interface Job { createdBy: string; links: Link[]; results: JobResult; -} +} \ No newline at end of file diff --git a/src/types/JobResult.ts b/src/types/JobResult.ts index 624ac08..074105e 100644 --- a/src/types/JobResult.ts +++ b/src/types/JobResult.ts @@ -1,3 +1,3 @@ export interface JobResult { "_webout.json": string; -} +} \ No newline at end of file diff --git a/src/types/Link.ts b/src/types/Link.ts index 7f8c5a3..658a1f7 100644 --- a/src/types/Link.ts +++ b/src/types/Link.ts @@ -4,4 +4,4 @@ export interface Link { href: string; uri: string; type: string; -} +} \ No newline at end of file diff --git a/src/types/SASjsConfig.ts b/src/types/SASjsConfig.ts index ee40745..f82d86c 100644 --- a/src/types/SASjsConfig.ts +++ b/src/types/SASjsConfig.ts @@ -10,6 +10,7 @@ export class SASjsConfig { * Can be omitted, eg if serving directly from the SAS Web Server or being * streamed. */ + // TODO: we should clarify what location we are talking about serverUrl: string = ""; pathSAS9: string = ""; pathSASViya: string = ""; diff --git a/src/types/SASjsRequest.ts b/src/types/SASjsRequest.ts index 941b5ec..55b6372 100644 --- a/src/types/SASjsRequest.ts +++ b/src/types/SASjsRequest.ts @@ -9,4 +9,4 @@ export interface SASjsRequest { generatedCode: string; logFile: string; SASWORK: any; -} +} \ No newline at end of file diff --git a/src/types/SASjsWaitingRequest.ts b/src/types/SASjsWaitingRequest.ts index f3c289d..4c8b8d6 100644 --- a/src/types/SASjsWaitingRequest.ts +++ b/src/types/SASjsWaitingRequest.ts @@ -2,6 +2,8 @@ * Represents requests that are queued, pending a signon event * */ + + // FIXME: be more specific on type declaration export interface SASjsWaitingRequest { requestPromise: { promise: any; diff --git a/src/types/ServerType.ts b/src/types/ServerType.ts index 21fbc81..df7b323 100644 --- a/src/types/ServerType.ts +++ b/src/types/ServerType.ts @@ -5,4 +5,4 @@ export enum ServerType { SASViya = "SASVIYA", SAS9 = "SAS9", -} +} \ No newline at end of file diff --git a/src/types/Session.ts b/src/types/Session.ts index 8734fdf..51a9cc0 100644 --- a/src/types/Session.ts +++ b/src/types/Session.ts @@ -1,3 +1,3 @@ export interface Session { id: string; -} +} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index ad95d88..e0b2721 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -7,4 +7,4 @@ export * from "./SASjsConfig"; export * from "./SASjsRequest"; export * from "./SASjsWaitingRequest"; export * from "./ServerType"; -export * from "./Session"; +export * from "./Session"; \ No newline at end of file diff --git a/src/utils/asyncForEach.ts b/src/utils/asyncForEach.ts index 9d71542..ee396b0 100644 --- a/src/utils/asyncForEach.ts +++ b/src/utils/asyncForEach.ts @@ -1,5 +1,6 @@ +// FIXME: use ES6 export async function asyncForEach(array: any[], callback: any) { for (let index = 0; index < array.length; index++) { await callback(array[index], index, array); } -} +} \ No newline at end of file diff --git a/src/utils/compareTimestamps.ts b/src/utils/compareTimestamps.ts index b7a5800..88fea6f 100644 --- a/src/utils/compareTimestamps.ts +++ b/src/utils/compareTimestamps.ts @@ -2,7 +2,6 @@ import { SASjsRequest } from "../types/SASjsRequest"; /** * Comparator for SASjs request timestamps - * */ export const compareTimestamps = (a: SASjsRequest, b: SASjsRequest) => { return b.timestamp.getTime() - a.timestamp.getTime(); diff --git a/src/utils/convertToCsv.ts b/src/utils/convertToCsv.ts index bfe0e01..9bcb359 100644 --- a/src/utils/convertToCsv.ts +++ b/src/utils/convertToCsv.ts @@ -3,43 +3,43 @@ * @param data - the JSON object to convert. */ export const convertToCSV = (data: any) => { - const replacer = (key: any, value: any) => (value === null ? "" : value); - const headerFields = Object.keys(data[0]); + const replacer = (key: any, value: any) => (value === null ? "" : value); // FIXME: 'key' parameter was not used, why do we compare with null (undefined, NaN)? + const headerFields = Object.keys(data[0]); // FIXME: data can be of any type, but we are working with it as with object let csvTest; let invalidString = false; const headers = headerFields.map((field) => { let firstFoundType: string | null = null; - let hasMixedTypes: boolean = false; - let rowNumError: number = -1; + let hasMixedTypes: boolean = false; // FIXME: unnecessary type declaration + let rowNumError: number = -1; // FIXME: unnecessary type declaration const longestValueForField = data - .map((row: any, index: number) => { + .map((row: any, index: number) => { // FIXME: row should be of type string | number if (row[field] || row[field] === "") { if (firstFoundType) { - let currentFieldType = - row[field] === "" || typeof row[field] === "string" + let currentFieldType = // FIXME: use const + row[field] === "" || typeof row[field] === "string" // FIXME: "" is also of type string ? "chars" : "number"; if (!hasMixedTypes) { hasMixedTypes = currentFieldType !== firstFoundType; - rowNumError = hasMixedTypes ? index + 1 : -1; + rowNumError = hasMixedTypes ? index + 1 : -1; // TODO: refactor } } else { if (row[field] === "") { firstFoundType = "chars"; } else { firstFoundType = - typeof row[field] === "string" ? "chars" : "number"; + typeof row[field] === "string" ? "chars" : "number"; // TODO: refactor } } let byteSize; if (typeof row[field] === "string") { - let doubleQuotesFound = row[field] + let doubleQuotesFound = row[field] // FIXME: use const .split("") - .filter((char: any) => char === '"'); + .filter((char: any) => char === '"'); // FIXME: why char is of type any? byteSize = getByteSize(row[field]); @@ -52,16 +52,18 @@ export const convertToCSV = (data: any) => { } }) .sort((a: number, b: number) => b - a)[0]; - if (longestValueForField && longestValueForField > 32765) { + + if (longestValueForField && longestValueForField > 32765) { // FIXME: longestValueForField is an array and it is not comparable to a number invalidString = true; } + if (hasMixedTypes) { console.error( `Row (${rowNumError}), Column (${field}) has mixed types: ERROR` ); } - return `${field}:${firstFoundType === "chars" ? "$" : ""}${ + return `${field}:${firstFoundType === "chars" ? "$" : ""}${ // TODO: format return string before return statement longestValueForField ? longestValueForField : firstFoundType === "chars" @@ -73,10 +75,11 @@ export const convertToCSV = (data: any) => { if (invalidString) { return "ERROR: LARGE STRING LENGTH"; } + csvTest = data.map((row: any) => { const fields = Object.keys(row).map((fieldName, index) => { let value; - let containsSpecialChar = false; + let containsSpecialChar = false; // FIXME: should be const const currentCell = row[fieldName]; if (JSON.stringify(currentCell).search(/(\\t|\\n|\\r)/gm) > -1) { @@ -89,7 +92,7 @@ export const convertToCSV = (data: any) => { value = value.replace(/\\\\/gm, "\\"); if (containsSpecialChar) { - if (value.includes(",") || value.includes('"')) { + if (value.includes(",") || value.includes('"')) { // FIXME: use `"` value = '"' + value + '"'; } } else { @@ -112,6 +115,7 @@ export const convertToCSV = (data: any) => { return value; }); + return fields.join(","); }); @@ -121,6 +125,7 @@ export const convertToCSV = (data: any) => { return finalCSV; }; +// TODO: refactor const getByteSize = (str: string) => { let byteSize = str.length; for (let i = str.length - 1; i >= 0; i--) { @@ -130,4 +135,4 @@ const getByteSize = (str: string) => { if (code >= 0xdc00 && code <= 0xdfff) i--; //trail surrogate } return byteSize; -}; +}; \ No newline at end of file diff --git a/src/utils/isAuthorizeFormRequired.ts b/src/utils/isAuthorizeFormRequired.ts index a1d5b38..403f593 100644 --- a/src/utils/isAuthorizeFormRequired.ts +++ b/src/utils/isAuthorizeFormRequired.ts @@ -1,3 +1,3 @@ export const isAuthorizeFormRequired = (response: string): boolean => { return //gm.test(response); -}; +}; \ No newline at end of file diff --git a/src/utils/isLoginRequired.ts b/src/utils/isLoginRequired.ts index 65448bc..e7a929b 100644 --- a/src/utils/isLoginRequired.ts +++ b/src/utils/isLoginRequired.ts @@ -1,5 +1,6 @@ export const isLogInRequired = (response: string): boolean => { - const pattern: RegExp = //gm; + const pattern: RegExp = //gm; // FIXME: unnecessary type declaration const matches = pattern.test(response); + return matches; -}; +}; \ No newline at end of file diff --git a/src/utils/isLoginSuccess.ts b/src/utils/isLoginSuccess.ts index 40d46d2..bffeaff 100644 --- a/src/utils/isLoginSuccess.ts +++ b/src/utils/isLoginSuccess.ts @@ -1,2 +1 @@ -export const isLogInSuccess = (response: string): boolean => - /You have signed in/gm.test(response); +export const isLogInSuccess = (response: string): boolean => /You have signed in/gm.test(response); // TODO: maybe we have more reliable way to verify that login was successful? \ No newline at end of file diff --git a/src/utils/makeRequest.ts b/src/utils/makeRequest.ts index 1d28c23..135b477 100644 --- a/src/utils/makeRequest.ts +++ b/src/utils/makeRequest.ts @@ -2,30 +2,33 @@ import { CsrfToken } from "../types"; export async function makeRequest( url: string, - request: RequestInit, + request: RequestInit, // Where 'RequestInit' is coming from? callback: (value: CsrfToken) => any, contentType: "text" | "json" = "json" ): Promise { - const responseTransform = - contentType === "json" - ? (res: Response) => res.json() - : (res: Response) => res.text(); - const result = await fetch(url, request).then((response) => { - if (!response.ok) { + const responseTransform = contentType === "json" ? + (res: Response) => res.json() + : + (res: Response) => res.text(); + + const result = await fetch(url, request).then((response) => { // FIXME: use axios instead of fetch + if (!response.ok) { // FIXME we can just check if status === 403 if (response.status === 403) { const tokenHeader = response.headers.get("X-CSRF-HEADER"); if (tokenHeader) { - const token = response.headers.get(tokenHeader); + const token = response.headers.get(tokenHeader) || ''; // TODO: refactor + callback({ headerName: tokenHeader, - value: token || "", + value: token, }); const retryRequest = { ...request, headers: { ...request.headers, [tokenHeader]: token }, }; + return fetch(url, retryRequest).then(responseTransform); } } @@ -33,5 +36,6 @@ export async function makeRequest( return responseTransform(response); } }); + return result; } diff --git a/src/utils/needsRetry.ts b/src/utils/needsRetry.ts index cb70c6c..01e9899 100644 --- a/src/utils/needsRetry.ts +++ b/src/utils/needsRetry.ts @@ -1,3 +1,4 @@ +// TODO: refactor export const needsRetry = (responseText: string): boolean => { return ( (responseText.includes('"errorCode":403') && @@ -8,4 +9,4 @@ export const needsRetry = (responseText: string): boolean => { (responseText.includes('"status":449') && responseText.includes("Authentication success, retry original request")) ); -}; +}; \ No newline at end of file diff --git a/src/utils/parseAndSubmitAuthorizeForm.ts b/src/utils/parseAndSubmitAuthorizeForm.ts index 7a3bf96..411581c 100644 --- a/src/utils/parseAndSubmitAuthorizeForm.ts +++ b/src/utils/parseAndSubmitAuthorizeForm.ts @@ -6,7 +6,7 @@ export const parseAndSubmitAuthorizeForm = async ( const params: any = {}; const responseBody = response.split("")[1].split("")[0]; - const bodyElement = document.createElement("div"); + const bodyElement = document.createElement("div"); // TODO: rename bodyElement.innerHTML = responseBody; const form = bodyElement.querySelector("#application_authorization"); @@ -24,7 +24,7 @@ export const parseAndSubmitAuthorizeForm = async ( const formData = new FormData(); - for (const key in params) { + for (const key in params) { // TODO: use forEach if (params.hasOwnProperty(key)) { formData.append(key, params[key]); } @@ -32,7 +32,7 @@ export const parseAndSubmitAuthorizeForm = async ( return new Promise((resolve, reject) => { if (authUrl) { - fetch(authUrl, { + fetch(authUrl, { // TODO use axios instead of fetch method: "POST", credentials: "include", body: formData, @@ -46,4 +46,4 @@ export const parseAndSubmitAuthorizeForm = async ( reject("Auth form url is null"); } }); -}; +}; \ No newline at end of file diff --git a/src/utils/parseGeneratedCode.ts b/src/utils/parseGeneratedCode.ts index 74acce8..ffb3d25 100644 --- a/src/utils/parseGeneratedCode.ts +++ b/src/utils/parseGeneratedCode.ts @@ -1,7 +1,7 @@ export const parseGeneratedCode = (log: string) => { const startsWith = "MPRINT"; - const isGeneratedCodeLine = (line: string) => - line.trim().startsWith(startsWith); + const isGeneratedCodeLine = (line: string) => line.trim().startsWith(startsWith); const logLines = log.split("\n").filter(isGeneratedCodeLine); + return logLines.join("\r\n"); }; diff --git a/src/utils/parseSasViyaLog.ts b/src/utils/parseSasViyaLog.ts index 2a29d78..62c6ec8 100644 --- a/src/utils/parseSasViyaLog.ts +++ b/src/utils/parseSasViyaLog.ts @@ -1,12 +1,15 @@ -export const parseSasViyaLog = (logResponse: { items: any[] }) => { +export const parseSasViyaLog = (logResponse: { items: any[] }) => { // TODO: be more specific on type declaration let log; + try { log = logResponse.items ? logResponse.items.map((i) => i.line).join("\n") : JSON.stringify(logResponse); - } catch (e) { + } catch (e) { // TODO: rename parameter to err or error console.error("An error has occurred while parsing the log response", e); + log = logResponse; } + return log; }; diff --git a/src/utils/parseSourceCode.ts b/src/utils/parseSourceCode.ts index d5b8ac6..1ad5f71 100644 --- a/src/utils/parseSourceCode.ts +++ b/src/utils/parseSourceCode.ts @@ -1,6 +1,6 @@ export const parseSourceCode = (log: string): string => { - const isSourceCodeLine = (line: string) => - line.trim().substring(0, 10).trimStart().match(/^\d/); + const isSourceCodeLine = (line: string) => line.trim().substring(0, 10).trimStart().match(/^\d/); const logLines = log.split("\n").filter(isSourceCodeLine); + return logLines.join("\r\n"); }; diff --git a/src/utils/serialize.ts b/src/utils/serialize.ts index 4816a5a..97c8775 100644 --- a/src/utils/serialize.ts +++ b/src/utils/serialize.ts @@ -1,6 +1,7 @@ -export const serialize = (obj: any) => { +export const serialize = (obj: any) => { // TODO: be more specific on type declaration const str: any[] = []; - for (const p in obj) { + + for (const p in obj) { // FIXME: name variables properly if (obj.hasOwnProperty(p)) { if (obj[p] instanceof Array) { for (let i = 0, n = obj[p].length; i < n; i++) { @@ -11,5 +12,6 @@ export const serialize = (obj: any) => { } } } + return str.join("&"); -}; +}; \ No newline at end of file