1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-18 09:30:06 +00:00

chore(refactor): only fetch job if streaming logs, fix tests, add JSDoc comments

This commit is contained in:
Krishna Acondy
2021-07-21 08:12:34 +01:00
parent df9c1c643f
commit cfa0c8b9af
5 changed files with 60 additions and 48 deletions

View File

@@ -8,7 +8,6 @@ import { saveLog } from './saveLog'
import { createWriteStream } from '@sasjs/utils/file' import { createWriteStream } from '@sasjs/utils/file'
import { WriteStream } from 'fs' import { WriteStream } from 'fs'
import { Link } from '../../types' import { Link } from '../../types'
import { prefixMessage } from '@sasjs/utils/error'
export async function pollJobState( export async function pollJobState(
requestClient: RequestClient, requestClient: RequestClient,
@@ -206,25 +205,26 @@ const doPoll = async (
pollCount++ pollCount++
const jobUrl = postedJob.links.find((l: Link) => l.rel === 'self') if (pollOptions?.streamLog) {
const { result: job } = await requestClient.get<Job>( const jobUrl = postedJob.links.find((l: Link) => l.rel === 'self')
jobUrl!.href, const { result: job } = await requestClient.get<Job>(
authConfig?.access_token jobUrl!.href,
) authConfig?.access_token
)
const endLogLine = job.logStatistics?.lineCount ?? 1000000 const endLogLine = job.logStatistics?.lineCount ?? 1000000
await saveLog( await saveLog(
postedJob, postedJob,
requestClient, requestClient,
pollOptions?.streamLog || false, startLogLine,
startLogLine, endLogLine,
endLogLine, logStream,
logStream, authConfig?.access_token
authConfig?.access_token )
)
startLogLine += job.logStatistics.lineCount startLogLine += endLogLine
}
if (debug && printedState !== state) { if (debug && printedState !== state) {
logger.info('Polling job status...') logger.info('Polling job status...')

View File

@@ -4,20 +4,25 @@ import { fetchLog } from '../../utils'
import { WriteStream } from 'fs' import { WriteStream } from 'fs'
import { writeStream } from './writeStream' import { writeStream } from './writeStream'
/**
* Appends logs to a supplied write stream.
* This is useful for getting quick feedback on longer running jobs.
* @param job - the job to fetch logs for
* @param requestClient - the pre-configured HTTP request client
* @param startLine - the line at which to start fetching the log
* @param endLine - the line at which to stop fetching the log
* @param logFileStream - the write stream to which the log is appended
* @accessToken - an optional access token for authentication/authorization
* The access token is not required when fetching logs from the browser.
*/
export async function saveLog( export async function saveLog(
job: Job, job: Job,
requestClient: RequestClient, requestClient: RequestClient,
shouldSaveLog: boolean,
startLine: number, startLine: number,
endLine: number, endLine: number,
logFileStream?: WriteStream, logFileStream?: WriteStream,
accessToken?: string accessToken?: string
) { ) {
console.log('startLine: ', startLine, ' endLine: ', endLine)
if (!shouldSaveLog) {
return
}
if (!accessToken) { if (!accessToken) {
throw new Error( throw new Error(
`Logs for job ${job.id} cannot be fetched without a valid access token.` `Logs for job ${job.id} cannot be fetched without a valid access token.`

View File

@@ -1,11 +1,12 @@
import * as fs from 'fs'
import { Logger, LogLevel } from '@sasjs/utils' import { Logger, LogLevel } from '@sasjs/utils'
import * as fileModule from '@sasjs/utils/file'
import { RequestClient } from '../../../request/RequestClient' import { RequestClient } from '../../../request/RequestClient'
import { mockAuthConfig, mockJob } from './mockResponses' import { mockAuthConfig, mockJob } from './mockResponses'
import { pollJobState } from '../pollJobState' import { pollJobState } from '../pollJobState'
import * as getTokensModule from '../../../auth/getTokens' import * as getTokensModule from '../../../auth/getTokens'
import * as saveLogModule from '../saveLog' import * as saveLogModule from '../saveLog'
import { PollOptions } from '../../../types' import { PollOptions } from '../../../types'
import { WriteStream } from 'fs'
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)() const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
const defaultPollOptions: PollOptions = { const defaultPollOptions: PollOptions = {
@@ -73,7 +74,18 @@ describe('pollJobState', () => {
expect(getTokensModule.getTokens).toHaveBeenCalledTimes(3) expect(getTokensModule.getTokens).toHaveBeenCalledTimes(3)
}) })
it('should attempt to fetch and save the log after each poll', async () => { it('should attempt to fetch and save the log after each poll when streamLog is true', async () => {
mockSimplePoll()
await pollJobState(requestClient, mockJob, false, mockAuthConfig, {
...defaultPollOptions,
streamLog: true
})
expect(saveLogModule.saveLog).toHaveBeenCalledTimes(2)
})
it('should not attempt to fetch and save the log after each poll when streamLog is false', async () => {
mockSimplePoll() mockSimplePoll()
await pollJobState( await pollJobState(
@@ -84,7 +96,7 @@ describe('pollJobState', () => {
defaultPollOptions defaultPollOptions
) )
expect(saveLogModule.saveLog).toHaveBeenCalledTimes(2) expect(saveLogModule.saveLog).not.toHaveBeenCalled()
}) })
it('should return the current status when the max poll count is reached', async () => { it('should return the current status when the max poll count is reached', async () => {
@@ -133,7 +145,7 @@ describe('pollJobState', () => {
defaultPollOptions defaultPollOptions
) )
expect(requestClient.get).toHaveBeenCalledTimes(3) expect(requestClient.get).toHaveBeenCalledTimes(2)
expect(state).toEqual('completed') expect(state).toEqual('completed')
}) })
@@ -179,7 +191,7 @@ describe('pollJobState', () => {
defaultPollOptions defaultPollOptions
) )
expect(requestClient.get).toHaveBeenCalledTimes(3) expect(requestClient.get).toHaveBeenCalledTimes(2)
expect(state).toEqual('completed') expect(state).toEqual('completed')
}) })
@@ -205,7 +217,7 @@ const setupMocks = () => {
jest.mock('../../../request/RequestClient') jest.mock('../../../request/RequestClient')
jest.mock('../../../auth/getTokens') jest.mock('../../../auth/getTokens')
jest.mock('../saveLog') jest.mock('../saveLog')
jest.mock('fs') jest.mock('@sasjs/utils/file')
jest jest
.spyOn(requestClient, 'get') .spyOn(requestClient, 'get')
@@ -219,8 +231,8 @@ const setupMocks = () => {
.spyOn(saveLogModule, 'saveLog') .spyOn(saveLogModule, 'saveLog')
.mockImplementation(() => Promise.resolve()) .mockImplementation(() => Promise.resolve())
jest jest
.spyOn(fs, 'createWriteStream') .spyOn(fileModule, 'createWriteStream')
.mockImplementation(() => ({} as unknown as fs.WriteStream)) .mockImplementation(() => Promise.resolve({} as unknown as WriteStream))
} }
const mockSimplePoll = (runningCount = 2) => { const mockSimplePoll = (runningCount = 2) => {

View File

@@ -15,22 +15,10 @@ describe('saveLog', () => {
setupMocks() setupMocks()
}) })
it('should return immediately if shouldSaveLog is false', async () => {
await saveLog(mockJob, requestClient, false, 0, 100, stream, 't0k3n')
expect(fetchLogsModule.fetchLog).not.toHaveBeenCalled()
expect(writeStreamModule.writeStream).not.toHaveBeenCalled()
})
it('should throw an error when a valid access token is not provided', async () => { it('should throw an error when a valid access token is not provided', async () => {
const error = await saveLog( const error = await saveLog(mockJob, requestClient, 0, 100, stream).catch(
mockJob, (e) => e
requestClient, )
true,
0,
100,
stream
).catch((e) => e)
expect(error.message).toContain( expect(error.message).toContain(
`Logs for job ${mockJob.id} cannot be fetched without a valid access token.` `Logs for job ${mockJob.id} cannot be fetched without a valid access token.`
@@ -41,7 +29,6 @@ describe('saveLog', () => {
const error = await saveLog( const error = await saveLog(
{ ...mockJob, links: mockJob.links.filter((l) => l.rel !== 'log') }, { ...mockJob, links: mockJob.links.filter((l) => l.rel !== 'log') },
requestClient, requestClient,
true,
0, 0,
100, 100,
stream, stream,
@@ -54,7 +41,7 @@ describe('saveLog', () => {
}) })
it('should fetch and save logs to the given path', async () => { it('should fetch and save logs to the given path', async () => {
await saveLog(mockJob, requestClient, true, 0, 100, stream, 't0k3n') await saveLog(mockJob, requestClient, 0, 100, stream, 't0k3n')
expect(fetchLogsModule.fetchLog).toHaveBeenCalledWith( expect(fetchLogsModule.fetchLog).toHaveBeenCalledWith(
requestClient, requestClient,

View File

@@ -2,6 +2,14 @@ import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from '../../request/RequestClient' import { RequestClient } from '../../request/RequestClient'
import { convertToCSV } from '../../utils/convertToCsv' import { convertToCSV } from '../../utils/convertToCsv'
/**
* Uploads tables to SAS as specially formatted CSVs.
* This is more compact than JSON, and easier to read within SAS.
* @param requestClient - the pre-configured HTTP request client
* @param data - the JSON representation of the data to be uploaded
* @param accessToken - an optional access token for authentication/authorization
* The access token is not required when uploading tables from the browser.
*/
export async function uploadTables( export async function uploadTables(
requestClient: RequestClient, requestClient: RequestClient,
data: any, data: any,