diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 744ed15..03f5c8a 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -12,9 +12,9 @@ What code changes have been made to achieve the intent. ## Checks -No PR (that involves a non-trivial code change) should be merged, unless all four of the items below are confirmed! If an urgent fix is needed - use a tar file. +No PR (that involves a non-trivial code change) should be merged, unless all items below are confirmed! If an urgent fix is needed - use a tar file. + -- [ ] Code is formatted correctly (`npm run lint:fix`). -- [ ] All unit tests are passing (`npm test`). - [ ] All `sasjs-cli` unit tests are passing (`npm test`). - [ ] All `sasjs-tests` are passing (instructions available [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md)). +- [ ] [Data Controller](https://datacontroller.io) builds and is functional on both SAS 9 and Viya diff --git a/src/FileUploader.ts b/src/FileUploader.ts index 148f534..7001bcb 100644 --- a/src/FileUploader.ts +++ b/src/FileUploader.ts @@ -1,4 +1,4 @@ -import { isUrl } from './utils' +import { isUrl, getValidJson, parseSasViyaDebugResponse } from './utils' import { UploadFile } from './types/UploadFile' import { ErrorResponse, LoginRequiredError } from './types/errors' import { RequestClient } from './request/RequestClient' @@ -63,13 +63,28 @@ export class FileUploader { return this.requestClient .post(uploadUrl, formData, undefined, 'application/json', headers) - .then((res) => { - let result + .then(async (res) => { + // for web approach on Viya + if ( + this.sasjsConfig.debug && + (this.sasjsConfig.useComputeApi === null || + this.sasjsConfig.useComputeApi === undefined) && + this.sasjsConfig.serverType === ServerType.SasViya + ) { + const jsonResponse = await parseSasViyaDebugResponse( + res.result as string, + this.requestClient, + this.sasjsConfig.serverUrl + ) + return typeof jsonResponse === 'string' + ? getValidJson(jsonResponse) + : jsonResponse + } - result = - typeof res.result === 'string' ? JSON.parse(res.result) : res.result + return typeof res.result === 'string' + ? getValidJson(res.result) + : res.result - return result //TODO: append to SASjs requests }) .catch((err: Error) => { diff --git a/src/SASjs.ts b/src/SASjs.ts index b91e034..705386d 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -544,11 +544,22 @@ export default class SASjs { * Process). Is prepended at runtime with the value of `appLoc`. * @param files - array of files to be uploaded, including File object and file name. * @param params - request URL parameters. + * @param overrideSasjsConfig - object to override existing config (optional) */ - public uploadFile(sasJob: string, files: UploadFile[], params: any) { - const fileUploader = - this.fileUploader || - new FileUploader(this.sasjsConfig, this.jobsPath, this.requestClient!) + public uploadFile( + sasJob: string, + files: UploadFile[], + params: any, + overrideSasjsConfig?: any + ) { + const fileUploader = overrideSasjsConfig + ? new FileUploader( + { ...this.sasjsConfig, ...overrideSasjsConfig }, + this.jobsPath, + this.requestClient! + ) + : this.fileUploader || + new FileUploader(this.sasjsConfig, this.jobsPath, this.requestClient!) return fileUploader.uploadFile(sasJob, files, params) } diff --git a/src/api/viya/getFileStream.ts b/src/api/viya/getFileStream.ts new file mode 100644 index 0000000..c647f3e --- /dev/null +++ b/src/api/viya/getFileStream.ts @@ -0,0 +1,17 @@ +import { isFolder } from '@sasjs/utils/file' +import { generateTimestamp } from '@sasjs/utils/time' +import { Job } from '../../types' + +export const getFileStream = async (job: Job, filePath?: string) => { + const { createWriteStream } = require('@sasjs/utils/file') + const logPath = filePath || process.cwd() + const isFolderPath = await isFolder(logPath) + if (isFolderPath) { + const logFileName = `${job.name || 'job'}-${generateTimestamp()}.log` + const path = require('path') + const logFilePath = path.join(filePath || process.cwd(), logFileName) + return await createWriteStream(logFilePath) + } else { + return await createWriteStream(logPath) + } +} diff --git a/src/api/viya/pollJobState.ts b/src/api/viya/pollJobState.ts index 3f033a5..c4b05d0 100644 --- a/src/api/viya/pollJobState.ts +++ b/src/api/viya/pollJobState.ts @@ -3,11 +3,8 @@ import { Job, PollOptions } from '../..' import { getTokens } from '../../auth/getTokens' import { RequestClient } from '../../request/RequestClient' import { JobStatePollError } from '../../types/errors' -import { generateTimestamp } from '@sasjs/utils/time' -import { saveLog } from './saveLog' -import { createWriteStream, isFolder } from '@sasjs/utils/file' -import { WriteStream } from 'fs' -import { Link } from '../../types' +import { Link, WriteStream } from '../../types' +import { isNode } from '../../utils' export async function pollJobState( requestClient: RequestClient, @@ -21,11 +18,14 @@ export async function pollJobState( let pollInterval = 300 let maxPollCount = 1000 - if (pollOptions) { - pollInterval = pollOptions.pollInterval || pollInterval - maxPollCount = pollOptions.maxPollCount || maxPollCount + const defaultPollOptions: PollOptions = { + maxPollCount, + pollInterval, + streamLog: false } + pollOptions = { ...defaultPollOptions, ...(pollOptions || {}) } + const stateLink = postedJob.links.find((l: any) => l.rel === 'state') if (!stateLink) { throw new Error(`Job state link was not found.`) @@ -52,23 +52,12 @@ export async function pollJobState( } let logFileStream - if (pollOptions?.streamLog) { - const logPath = pollOptions?.logFolderPath || process.cwd() - const isFolderPath = await isFolder(logPath) - if (isFolderPath) { - const logFileName = `${ - postedJob.name || 'job' - }-${generateTimestamp()}.log` - const logFilePath = `${ - pollOptions?.logFolderPath || process.cwd() - }/${logFileName}` - - logFileStream = await createWriteStream(logFilePath) - } else { - logFileStream = await createWriteStream(logPath) - } + if (pollOptions.streamLog && isNode()) { + const { getFileStream } = require('./getFileStream') + logFileStream = await getFileStream(postedJob, pollOptions.logFolderPath) } + // Poll up to the first 100 times with the specified poll interval let result = await doPoll( requestClient, postedJob, @@ -76,14 +65,18 @@ export async function pollJobState( debug, pollCount, authConfig, - pollOptions, + { + ...pollOptions, + maxPollCount: + pollOptions.maxPollCount <= 100 ? pollOptions.maxPollCount : 100 + }, logFileStream ) currentState = result.state pollCount = result.pollCount - if (!needsRetry(currentState) || pollCount >= maxPollCount) { + if (!needsRetry(currentState) || pollCount >= pollOptions.maxPollCount) { return currentState } @@ -192,7 +185,7 @@ const doPoll = async ( throw new Error(`Job state link was not found.`) } - while (needsRetry(state) && pollCount <= 100 && pollCount <= maxPollCount) { + while (needsRetry(state) && pollCount <= maxPollCount) { state = await getJobState( requestClient, postedJob, @@ -222,14 +215,17 @@ const doPoll = async ( const endLogLine = job.logStatistics?.lineCount ?? 1000000 - await saveLog( - postedJob, - requestClient, - startLogLine, - endLogLine, - logStream, - authConfig?.access_token - ) + const { saveLog } = isNode() ? require('./saveLog') : { saveLog: null } + if (saveLog) { + await saveLog( + postedJob, + requestClient, + startLogLine, + endLogLine, + logStream, + authConfig?.access_token + ) + } startLogLine += endLogLine } diff --git a/src/api/viya/saveLog.ts b/src/api/viya/saveLog.ts index 3bbd498..2b5ec08 100644 --- a/src/api/viya/saveLog.ts +++ b/src/api/viya/saveLog.ts @@ -1,7 +1,7 @@ import { Job } from '../..' import { RequestClient } from '../../request/RequestClient' import { fetchLog } from '../../utils' -import { WriteStream } from 'fs' +import { WriteStream } from '../../types' import { writeStream } from './writeStream' /** diff --git a/src/api/viya/spec/getFileStream.spec.ts b/src/api/viya/spec/getFileStream.spec.ts new file mode 100644 index 0000000..9ab766b --- /dev/null +++ b/src/api/viya/spec/getFileStream.spec.ts @@ -0,0 +1,41 @@ +import { Logger, LogLevel } from '@sasjs/utils/logger' +import * as path from 'path' +import * as fileModule from '@sasjs/utils/file' +import { getFileStream } from '../getFileStream' +import { mockJob } from './mockResponses' +import { WriteStream } from '../../../types' + +describe('getFileStream', () => { + beforeEach(() => { + ;(process as any).logger = new Logger(LogLevel.Off) + setupMocks() + }) + it('should use the given log path if it points to a file', async () => { + const { createWriteStream } = require('@sasjs/utils/file') + + await getFileStream(mockJob, path.join(__dirname, 'test.log')) + + expect(createWriteStream).toHaveBeenCalledWith( + path.join(__dirname, 'test.log') + ) + }) + + it('should generate a log file path with a timestamp if it points to a folder', async () => { + const { createWriteStream } = require('@sasjs/utils/file') + + await getFileStream(mockJob, __dirname) + + expect(createWriteStream).not.toHaveBeenCalledWith(__dirname) + expect(createWriteStream).toHaveBeenCalledWith( + expect.stringContaining(path.join(__dirname, 'test job-20')) + ) + }) +}) + +const setupMocks = () => { + jest.restoreAllMocks() + jest.mock('@sasjs/utils/file/file') + jest + .spyOn(fileModule, 'createWriteStream') + .mockImplementation(() => Promise.resolve({} as unknown as WriteStream)) +} diff --git a/src/api/viya/spec/pollJobState.spec.ts b/src/api/viya/spec/pollJobState.spec.ts index 7855f57..74f39e1 100644 --- a/src/api/viya/spec/pollJobState.spec.ts +++ b/src/api/viya/spec/pollJobState.spec.ts @@ -1,11 +1,11 @@ import { Logger, LogLevel } from '@sasjs/utils' -import * as path from 'path' -import * as fileModule from '@sasjs/utils/file' import { RequestClient } from '../../../request/RequestClient' import { mockAuthConfig, mockJob } from './mockResponses' import { pollJobState } from '../pollJobState' import * as getTokensModule from '../../../auth/getTokens' import * as saveLogModule from '../saveLog' +import * as getFileStreamModule from '../getFileStream' +import * as isNodeModule from '../../../utils/isNode' import { PollOptions } from '../../../types' import { WriteStream } from 'fs' @@ -77,42 +77,43 @@ describe('pollJobState', () => { it('should attempt to fetch and save the log after each poll when streamLog is true', async () => { mockSimplePoll() + const { saveLog } = require('../saveLog') await pollJobState(requestClient, mockJob, false, mockAuthConfig, { ...defaultPollOptions, streamLog: true }) - expect(saveLogModule.saveLog).toHaveBeenCalledTimes(2) + expect(saveLog).toHaveBeenCalledTimes(2) }) - it('should use the given log path if it points to a file', async () => { + it('should create a write stream in Node.js environment when streamLog is true', async () => { mockSimplePoll() + const { getFileStream } = require('../getFileStream') + const { saveLog } = require('../saveLog') await pollJobState(requestClient, mockJob, false, mockAuthConfig, { ...defaultPollOptions, - streamLog: true, - logFolderPath: path.join(__dirname, 'test.log') + streamLog: true }) - expect(fileModule.createWriteStream).toHaveBeenCalledWith( - path.join(__dirname, 'test.log') - ) + expect(getFileStream).toHaveBeenCalled() + expect(saveLog).toHaveBeenCalledTimes(2) }) - it('should generate a log file path with a timestamp if it points to a folder', async () => { + it('should not create a write stream in a non-Node.js environment', async () => { mockSimplePoll() + jest.spyOn(isNodeModule, 'isNode').mockImplementation(() => false) + const { saveLog } = require('../saveLog') + const { getFileStream } = require('../getFileStream') await pollJobState(requestClient, mockJob, false, mockAuthConfig, { ...defaultPollOptions, - streamLog: true, - logFolderPath: path.join(__dirname) + streamLog: true }) - expect(fileModule.createWriteStream).not.toHaveBeenCalledWith(__dirname) - expect(fileModule.createWriteStream).toHaveBeenCalledWith( - expect.stringContaining(__dirname + '/test job-20') - ) + expect(getFileStream).not.toHaveBeenCalled() + expect(saveLog).not.toHaveBeenCalled() }) it('should not attempt to fetch and save the log after each poll when streamLog is false', async () => { @@ -247,7 +248,8 @@ const setupMocks = () => { jest.mock('../../../request/RequestClient') jest.mock('../../../auth/getTokens') jest.mock('../saveLog') - jest.mock('@sasjs/utils/file') + jest.mock('../getFileStream') + jest.mock('../../../utils/isNode') jest .spyOn(requestClient, 'get') @@ -261,8 +263,9 @@ const setupMocks = () => { .spyOn(saveLogModule, 'saveLog') .mockImplementation(() => Promise.resolve()) jest - .spyOn(fileModule, 'createWriteStream') + .spyOn(getFileStreamModule, 'getFileStream') .mockImplementation(() => Promise.resolve({} as unknown as WriteStream)) + jest.spyOn(isNodeModule, 'isNode').mockImplementation(() => true) } const mockSimplePoll = (runningCount = 2) => { @@ -308,7 +311,7 @@ const mockLongPoll = () => { return Promise.resolve({ result: mockJob, etag: '', status: 200 }) } return Promise.resolve({ - result: count <= 101 ? 'running' : 'completed', + result: count <= 102 ? 'running' : 'completed', etag: '', status: 200 }) diff --git a/src/api/viya/spec/saveLog.spec.ts b/src/api/viya/spec/saveLog.spec.ts index a6c662b..261438e 100644 --- a/src/api/viya/spec/saveLog.spec.ts +++ b/src/api/viya/spec/saveLog.spec.ts @@ -4,7 +4,7 @@ import * as fetchLogsModule from '../../../utils/fetchLogByChunks' import * as writeStreamModule from '../writeStream' import { saveLog } from '../saveLog' import { mockJob } from './mockResponses' -import { WriteStream } from 'fs' +import { WriteStream } from '../../../types' const requestClient = new (>RequestClient)() const stream = {} as unknown as WriteStream diff --git a/src/api/viya/spec/writeStream.spec.ts b/src/api/viya/spec/writeStream.spec.ts new file mode 100644 index 0000000..358c82a --- /dev/null +++ b/src/api/viya/spec/writeStream.spec.ts @@ -0,0 +1,25 @@ +import { WriteStream } from '../../../types' +import { writeStream } from '../writeStream' +import 'jest-extended' + +describe('writeStream', () => { + const stream: WriteStream = { + write: jest.fn(), + path: 'test' + } + + it('should resolve when the stream is written successfully', async () => { + expect(writeStream(stream, 'test')).toResolve() + + expect(stream.write).toHaveBeenCalledWith('test\n', expect.anything()) + }) + + it('should reject when the write errors out', async () => { + jest + .spyOn(stream, 'write') + .mockImplementation((_, callback) => callback(new Error('Test Error'))) + const error = await writeStream(stream, 'test').catch((e) => e) + + expect(error.message).toEqual('Test Error') + }) +}) diff --git a/src/api/viya/writeStream.ts b/src/api/viya/writeStream.ts index dc09885..0baaaa0 100644 --- a/src/api/viya/writeStream.ts +++ b/src/api/viya/writeStream.ts @@ -1,11 +1,11 @@ -import { WriteStream } from 'fs' +import { WriteStream } from '../../types' export const writeStream = async ( stream: WriteStream, content: string ): Promise => { return new Promise((resolve, reject) => { - stream.write(content + '\n\nnext chunk\n\n', (e) => { + stream.write(content + '\n', (e) => { if (e) { return reject(e) } diff --git a/src/auth/getTokens.ts b/src/auth/getTokens.ts index 031c6a3..fe7779d 100644 --- a/src/auth/getTokens.ts +++ b/src/auth/getTokens.ts @@ -1,9 +1,9 @@ import { - AuthConfig, isAccessTokenExpiring, isRefreshTokenExpiring, hasTokenExpired -} from '@sasjs/utils' +} from '@sasjs/utils/auth' +import { AuthConfig } from '@sasjs/utils/types' import { RequestClient } from '../request/RequestClient' import { refreshTokens } from './refreshTokens' diff --git a/src/job-execution/WebJobExecutor.ts b/src/job-execution/WebJobExecutor.ts index 30a6e2f..98063fa 100644 --- a/src/job-execution/WebJobExecutor.ts +++ b/src/job-execution/WebJobExecutor.ts @@ -8,7 +8,11 @@ import { generateFileUploadForm } from '../file/generateFileUploadForm' import { generateTableUploadForm } from '../file/generateTableUploadForm' import { RequestClient } from '../request/RequestClient' import { SASViyaApiClient } from '../SASViyaApiClient' -import { isRelativePath, isValidJson } from '../utils' +import { + isRelativePath, + getValidJson, + parseSasViyaDebugResponse +} from '../utils' import { BaseJobExecutor } from './JobExecutor' import { parseWeboutResponse } from '../utils/parseWeboutResponse' @@ -95,8 +99,10 @@ export class WebJobExecutor extends BaseJobExecutor { this.requestClient!.post(apiUrl, formData, undefined) .then(async (res) => { if (this.serverType === ServerType.SasViya && config.debug) { - const jsonResponse = await this.parseSasViyaDebugResponse( - res.result as string + const jsonResponse = await parseSasViyaDebugResponse( + res.result as string, + this.requestClient, + this.serverUrl ) this.appendRequest(res, sasJob, config.debug) resolve(jsonResponse) @@ -109,11 +115,11 @@ export class WebJobExecutor extends BaseJobExecutor { ) } - isValidJson(jsonResponse) + getValidJson(jsonResponse) this.appendRequest(res, sasJob, config.debug) resolve(res.result) } - isValidJson(res.result as string) + getValidJson(res.result as string) this.appendRequest(res, sasJob, config.debug) resolve(res.result) }) @@ -151,20 +157,6 @@ export class WebJobExecutor extends BaseJobExecutor { return requestPromise } - private parseSasViyaDebugResponse = async (response: string) => { - const iframeStart = response.split( - '')[0] : null - if (!jsonUrl) { - throw new Error('Unable to find webout file URL.') - } - - return this.requestClient - .get(this.serverUrl + jsonUrl, undefined) - .then((res) => res.result) - } - private async getJobUri(sasJob: string) { if (!this.sasViyaApiClient) return '' let uri = '' diff --git a/src/request/RequestClient.ts b/src/request/RequestClient.ts index 05ca0db..e44a8c4 100644 --- a/src/request/RequestClient.ts +++ b/src/request/RequestClient.ts @@ -11,7 +11,7 @@ import { import { parseWeboutResponse } from '../utils/parseWeboutResponse' import { prefixMessage } from '@sasjs/utils/error' import { SAS9AuthError } from '../types/errors/SAS9AuthError' -import { isValidJson } from '../utils' +import { getValidJson } from '../utils' export interface HttpClient { get( @@ -434,7 +434,7 @@ export class RequestClient implements HttpClient { throw new Error('Valid JSON could not be extracted from response.') } - const jsonResponse = isValidJson(weboutResponse) + const jsonResponse = getValidJson(weboutResponse) parsedResponse = jsonResponse } catch { parsedResponse = response.data diff --git a/src/test/utils/getValidJson.spec.ts b/src/test/utils/getValidJson.spec.ts new file mode 100644 index 0000000..e7fbf66 --- /dev/null +++ b/src/test/utils/getValidJson.spec.ts @@ -0,0 +1,41 @@ +import { getValidJson } from '../../utils' + +describe('jsonValidator', () => { + it('should not throw an error with a valid json', () => { + const json = { + test: 'test' + } + + expect(getValidJson(json)).toBe(json) + }) + + it('should not throw an error with a valid json string', () => { + const json = { + test: 'test' + } + + expect(getValidJson(JSON.stringify(json))).toStrictEqual(json) + }) + + it('should throw an error with an invalid json', () => { + const json = `{\"test\":\"test\"\"test2\":\"test\"}` + let errorThrown = false + try { + getValidJson(json) + } catch (error) { + errorThrown = true + } + expect(errorThrown).toBe(true) + }) + + it('should throw an error when an array is passed', () => { + const array = ['hello', 'world'] + let errorThrown = false + try { + getValidJson(array) + } catch (error) { + errorThrown = true + } + expect(errorThrown).toBe(true) + }) +}) diff --git a/src/test/utils/isValidJson.spec.ts b/src/test/utils/isValidJson.spec.ts deleted file mode 100644 index b7af712..0000000 --- a/src/test/utils/isValidJson.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { isValidJson } from '../../utils' - -describe('jsonValidator', () => { - it('should not throw an error with an valid json', () => { - const json = { - test: 'test' - } - - expect(isValidJson(json)).toBe(json) - }) - - it('should not throw an error with an valid json string', () => { - const json = { - test: 'test' - } - - expect(isValidJson(JSON.stringify(json))).toStrictEqual(json) - }) - - it('should throw an error with an invalid json', () => { - const json = `{\"test\":\"test\"\"test2\":\"test\"}` - - expect(() => { - try { - isValidJson(json) - } catch (err) { - throw new Error() - } - }).toThrowError - }) -}) diff --git a/src/types/WriteStream.ts b/src/types/WriteStream.ts new file mode 100644 index 0000000..83a1d13 --- /dev/null +++ b/src/types/WriteStream.ts @@ -0,0 +1,4 @@ +export interface WriteStream { + write: (content: string, callback: (err?: Error) => any) => void + path: string +} diff --git a/src/types/index.ts b/src/types/index.ts index 313aef2..2303619 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -11,3 +11,4 @@ export * from './SASjsRequest' export * from './Session' export * from './UploadFile' export * from './PollOptions' +export * from './WriteStream' diff --git a/src/utils/getValidJson.ts b/src/utils/getValidJson.ts new file mode 100644 index 0000000..0313157 --- /dev/null +++ b/src/utils/getValidJson.ts @@ -0,0 +1,16 @@ +/** + * if string passed then parse the string to json else if throw error for all other types unless it is not a valid json object. + * @param str - string to check. + */ +export const getValidJson = (str: string | object) => { + try { + if (Array.isArray(str)) { + throw new Error('Can not parse array object to json.') + } + if (typeof str === 'object') return str + + return JSON.parse(str) + } catch (e) { + throw new Error('Invalid JSON response.') + } +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 3f6ec1d..2a05d63 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,7 @@ export * from './asyncForEach' export * from './compareTimestamps' export * from './convertToCsv' +export * from './isNode' export * from './isRelativePath' export * from './isUri' export * from './isUrl' @@ -12,4 +13,5 @@ export * from './serialize' export * from './splitChunks' export * from './parseWeboutResponse' export * from './fetchLogByChunks' -export * from './isValidJson' +export * from './getValidJson' +export * from './parseViyaDebugResponse' diff --git a/src/utils/isNode.ts b/src/utils/isNode.ts new file mode 100644 index 0000000..4a47e8f --- /dev/null +++ b/src/utils/isNode.ts @@ -0,0 +1,4 @@ +export const isNode = () => + typeof process !== 'undefined' && + process.versions != null && + process.versions.node != null diff --git a/src/utils/isValidJson.ts b/src/utils/isValidJson.ts deleted file mode 100644 index 253440f..0000000 --- a/src/utils/isValidJson.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Checks if string is in valid JSON format else throw error. - * @param str - string to check. - */ -export const isValidJson = (str: string | object) => { - try { - if (typeof str === 'object') return str - - return JSON.parse(str) - } catch (e) { - throw new Error('Invalid JSON response.') - } -} diff --git a/src/utils/parseViyaDebugResponse.ts b/src/utils/parseViyaDebugResponse.ts new file mode 100644 index 0000000..3137995 --- /dev/null +++ b/src/utils/parseViyaDebugResponse.ts @@ -0,0 +1,29 @@ +import { RequestClient } from '../request/RequestClient' + +/** + * When querying a Viya job using the Web approach (as opposed to using the APIs) with _DEBUG enabled, + * the first response contains the log with the content in an iframe. Therefore when debug is enabled, + * and the serverType is VIYA, and useComputeApi is null (WEB), we call this function to extract the + * (_webout) content from the iframe. + * @param response - first response from viya job + * @param requestClient + * @param serverUrl + * @returns + */ +export const parseSasViyaDebugResponse = async ( + response: string, + requestClient: RequestClient, + serverUrl: string +) => { + const iframeStart = response.split( + '')[0] : null + if (!jsonUrl) { + throw new Error('Unable to find webout file URL.') + } + + return requestClient + .get(serverUrl + jsonUrl, undefined) + .then((res) => res.result) +}