mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-06 20:10:05 +00:00
251 lines
5.8 KiB
TypeScript
251 lines
5.8 KiB
TypeScript
import { AuthConfig } from '@sasjs/utils/types'
|
|
import { Job, PollOptions } from '../..'
|
|
import { getTokens } from '../../auth/getTokens'
|
|
import { RequestClient } from '../../request/RequestClient'
|
|
import { JobStatePollError } from '../../types/errors'
|
|
import { Link, WriteStream } from '../../types'
|
|
import { isNode } from '../../utils'
|
|
|
|
export async function pollJobState(
|
|
requestClient: RequestClient,
|
|
postedJob: Job,
|
|
debug: boolean,
|
|
authConfig?: AuthConfig,
|
|
pollOptions?: PollOptions
|
|
) {
|
|
const logger = process.logger || console
|
|
|
|
let pollInterval = 300
|
|
let maxPollCount = 1000
|
|
|
|
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.`)
|
|
}
|
|
|
|
let currentState = await getJobState(
|
|
requestClient,
|
|
postedJob,
|
|
'',
|
|
debug,
|
|
authConfig
|
|
).catch((err) => {
|
|
logger.error(
|
|
`Error fetching job state from ${stateLink.href}. Starting poll, assuming job to be running.`,
|
|
err
|
|
)
|
|
return 'unavailable'
|
|
})
|
|
|
|
let pollCount = 0
|
|
|
|
if (currentState === 'completed') {
|
|
return Promise.resolve(currentState)
|
|
}
|
|
|
|
let logFileStream
|
|
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,
|
|
currentState,
|
|
debug,
|
|
pollCount,
|
|
authConfig,
|
|
{
|
|
...pollOptions,
|
|
maxPollCount:
|
|
pollOptions.maxPollCount <= 100 ? pollOptions.maxPollCount : 100
|
|
},
|
|
logFileStream
|
|
)
|
|
|
|
currentState = result.state
|
|
pollCount = result.pollCount
|
|
|
|
if (!needsRetry(currentState) || pollCount >= pollOptions.maxPollCount) {
|
|
return currentState
|
|
}
|
|
|
|
// If we get to this point, this is a long-running job that needs longer polling.
|
|
// We will resume polling with a bigger interval of 1 minute
|
|
let longJobPollOptions: PollOptions = {
|
|
maxPollCount: 24 * 60,
|
|
pollInterval: 60000,
|
|
streamLog: false
|
|
}
|
|
if (pollOptions) {
|
|
longJobPollOptions.streamLog = pollOptions.streamLog
|
|
longJobPollOptions.logFolderPath = pollOptions.logFolderPath
|
|
}
|
|
|
|
result = await doPoll(
|
|
requestClient,
|
|
postedJob,
|
|
currentState,
|
|
debug,
|
|
pollCount,
|
|
authConfig,
|
|
longJobPollOptions,
|
|
logFileStream
|
|
)
|
|
|
|
currentState = result.state
|
|
pollCount = result.pollCount
|
|
|
|
if (logFileStream) {
|
|
logFileStream.end()
|
|
}
|
|
|
|
return currentState
|
|
}
|
|
|
|
const getJobState = async (
|
|
requestClient: RequestClient,
|
|
job: Job,
|
|
currentState: string,
|
|
debug: boolean,
|
|
authConfig?: AuthConfig
|
|
) => {
|
|
const stateLink = job.links.find((l: any) => l.rel === 'state')
|
|
if (!stateLink) {
|
|
throw new Error(`Job state link was not found.`)
|
|
}
|
|
|
|
if (needsRetry(currentState)) {
|
|
let tokens
|
|
if (authConfig) {
|
|
tokens = await getTokens(requestClient, authConfig)
|
|
}
|
|
|
|
const { result: jobState } = await requestClient
|
|
.get<string>(
|
|
`${stateLink.href}?_action=wait&wait=300`,
|
|
tokens?.access_token,
|
|
'text/plain',
|
|
{},
|
|
debug
|
|
)
|
|
.catch((err) => {
|
|
throw new JobStatePollError(job.id, err)
|
|
})
|
|
|
|
return jobState.trim()
|
|
} else {
|
|
return currentState
|
|
}
|
|
}
|
|
|
|
const needsRetry = (state: string) =>
|
|
state === 'running' ||
|
|
state === '' ||
|
|
state === 'pending' ||
|
|
state === 'unavailable'
|
|
|
|
const doPoll = async (
|
|
requestClient: RequestClient,
|
|
postedJob: Job,
|
|
currentState: string,
|
|
debug: boolean,
|
|
pollCount: number,
|
|
authConfig?: AuthConfig,
|
|
pollOptions?: PollOptions,
|
|
logStream?: WriteStream
|
|
): Promise<{ state: string; pollCount: number }> => {
|
|
let pollInterval = 300
|
|
let maxPollCount = 1000
|
|
let maxErrorCount = 5
|
|
let errorCount = 0
|
|
let state = currentState
|
|
let printedState = ''
|
|
let startLogLine = 0
|
|
|
|
const logger = process.logger || console
|
|
|
|
if (pollOptions) {
|
|
pollInterval = pollOptions.pollInterval || pollInterval
|
|
maxPollCount = pollOptions.maxPollCount || maxPollCount
|
|
}
|
|
|
|
const stateLink = postedJob.links.find((l: Link) => l.rel === 'state')
|
|
if (!stateLink) {
|
|
throw new Error(`Job state link was not found.`)
|
|
}
|
|
|
|
while (needsRetry(state) && pollCount <= maxPollCount) {
|
|
state = await getJobState(
|
|
requestClient,
|
|
postedJob,
|
|
state,
|
|
debug,
|
|
authConfig
|
|
).catch((err) => {
|
|
errorCount++
|
|
if (pollCount >= maxPollCount || errorCount >= maxErrorCount) {
|
|
throw err
|
|
}
|
|
logger.error(
|
|
`Error fetching job state from ${stateLink.href}. Resuming poll, assuming job to be running.`,
|
|
err
|
|
)
|
|
return 'unavailable'
|
|
})
|
|
|
|
pollCount++
|
|
|
|
if (pollOptions?.streamLog) {
|
|
const jobUrl = postedJob.links.find((l: Link) => l.rel === 'self')
|
|
const { result: job } = await requestClient.get<Job>(
|
|
jobUrl!.href,
|
|
authConfig?.access_token
|
|
)
|
|
|
|
const endLogLine = job.logStatistics?.lineCount ?? 1000000
|
|
|
|
const { saveLog } = isNode() ? require('./saveLog') : { saveLog: null }
|
|
if (saveLog) {
|
|
await saveLog(
|
|
postedJob,
|
|
requestClient,
|
|
startLogLine,
|
|
endLogLine,
|
|
logStream,
|
|
authConfig?.access_token
|
|
)
|
|
}
|
|
|
|
startLogLine += endLogLine
|
|
}
|
|
|
|
if (debug && printedState !== state) {
|
|
logger.info('Polling job status...')
|
|
logger.info(`Current job state: ${state}`)
|
|
|
|
printedState = state
|
|
}
|
|
|
|
if (state != 'unavailable' && errorCount > 0) {
|
|
errorCount = 0
|
|
}
|
|
|
|
await delay(pollInterval)
|
|
}
|
|
|
|
return { state, pollCount }
|
|
}
|
|
|
|
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|