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

feat(poll-strategy): added subsequentStrategies to PollStrategy

This commit is contained in:
Yury Shkoda
2023-05-16 17:48:04 +03:00
parent ea68c3dff3
commit 5c74186bab
4 changed files with 111 additions and 113 deletions

View File

@@ -24,20 +24,19 @@ export enum JobState {
* @param authConfig - an access token, refresh token, client and secret for an authorized user. * @param authConfig - an access token, refresh token, client and secret for an authorized user.
* @param pollStrategy - an object containing maxPollCount, pollInterval, streamLog and logFolderPath. It will override the first default poll strategy if provided. * @param pollStrategy - an object containing maxPollCount, pollInterval, streamLog and logFolderPath. It will override the first default poll strategy if provided.
* Example: * Example:
* { maxPollCount: 200, pollInterval: 300, streamLog: false } * {
* @param pollStrategies - an array of poll strategies. It will override default poll strategies if provided. * maxPollCount: 200,
* Example: * pollInterval: 300,
* streamLog: true, // optional, equals to false by default.
* subsequentStrategies?: // optional array of the strategies that should be applied after 'maxPollCount' of the provided poll strategy is reached. If not provided the default (see example below) subsequent poll strategies will be used.
* }
* @param pollStrategies - optional array of poll strategies. It will override default poll strategies if provided.
* Example (default poll strategies):
* [ * [
* { maxPollCount: 200, pollInterval: 300, streamLog: false }, * { maxPollCount: 200, pollInterval: 300 }, // approximately ~2 mins (including time to get response (~300ms))
* { maxPollCount: 300, pollInterval: 3000, streamLog: false }, * { maxPollCount: 300, pollInterval: 3000 }, // approximately ~5.5 mins (including time to get response (~300ms))
* { maxPollCount: 500, pollInterval: 30000, streamLog: false } * { maxPollCount: 500, pollInterval: 30000 }, // approximately ~50.5 mins (including time to get response (~300ms))
* ] * { maxPollCount: 3400, pollInterval: 60000 } // approximately ~3015 mins (~125 hours) (including time to get response (~300ms))
* Default poll strategies:
* [
* { maxPollCount: 200, pollInterval: 300, streamLog: false }, // INFO: approximately ~2 mins (including time to get response (~300ms))
* { maxPollCount: 300, pollInterval: 3000, streamLog: false }, // INFO: approximately ~5.5 mins (including time to get response (~300ms))
* { maxPollCount: 400, pollInterval: 30000, streamLog: false }, // INFO: approximately ~50.5 mins (including time to get response (~300ms))
* { maxPollCount: 3400, pollInterval: 60000, streamLog: false } // INFO: approximately ~3015 mins (~125 hours) (including time to get response (~300ms))
* ] * ]
* @returns - a promise which resolves with a job state * @returns - a promise which resolves with a job state
*/ */
@@ -47,20 +46,42 @@ export async function pollJobState(
debug: boolean, debug: boolean,
authConfig?: AuthConfig, authConfig?: AuthConfig,
pollStrategy?: PollStrategy, pollStrategy?: PollStrategy,
pollStrategies?: PollStrategies streamLog?: boolean
): Promise<JobState> { ): Promise<JobState> {
const logger = process.logger || console const logger = process.logger || console
const defaultStreamLog = pollStrategy ? pollStrategy.streamLog : false streamLog = streamLog || false
const defaultPollStrategies: PollStrategies = [ const defaultPollStrategies: PollStrategies = [
{ maxPollCount: 200, pollInterval: 300, streamLog: defaultStreamLog }, { maxPollCount: 200, pollInterval: 300 },
{ maxPollCount: 300, pollInterval: 3000, streamLog: defaultStreamLog }, { maxPollCount: 400, pollInterval: 30000 },
{ maxPollCount: 400, pollInterval: 30000, streamLog: defaultStreamLog }, { maxPollCount: 300, pollInterval: 3000 },
{ maxPollCount: 3400, pollInterval: 60000, streamLog: defaultStreamLog } { maxPollCount: 3400, pollInterval: 60000 }
] ]
if (pollStrategies === undefined) pollStrategies = defaultPollStrategies let pollStrategies: PollStrategies
else validatePollStrategies(pollStrategies)
if (pollStrategy !== undefined) {
pollStrategies = [pollStrategy]
let { subsequentStrategies } = pollStrategy
if (subsequentStrategies !== undefined) {
validatePollStrategies(subsequentStrategies)
// INFO: sort by 'maxPollCount'
subsequentStrategies = subsequentStrategies.sort(
(strategyA: PollStrategy, strategyB: PollStrategy) =>
strategyA.maxPollCount - strategyB.maxPollCount
)
pollStrategies = [...pollStrategies, ...subsequentStrategies]
} else {
pollStrategies = [...pollStrategies, ...defaultPollStrategies]
}
} else {
pollStrategies = defaultPollStrategies
}
let defaultPollStrategy: PollStrategy = pollStrategies.splice(0, 1)[0] let defaultPollStrategy: PollStrategy = pollStrategies.splice(0, 1)[0]
@@ -93,8 +114,9 @@ export async function pollJobState(
} }
let logFileStream let logFileStream
if (pollStrategy.streamLog && isNode()) { if (streamLog && isNode()) {
const { getFileStream } = require('./getFileStream') const { getFileStream } = require('./getFileStream')
logFileStream = await getFileStream(postedJob, pollStrategy.logFolderPath) logFileStream = await getFileStream(postedJob, pollStrategy.logFolderPath)
} }
@@ -106,6 +128,7 @@ export async function pollJobState(
pollCount, pollCount,
pollStrategy, pollStrategy,
authConfig, authConfig,
streamLog,
logFileStream logFileStream
) )
@@ -125,7 +148,6 @@ export async function pollJobState(
defaultPollStrategy = pollStrategies.splice(0, 1)[0] defaultPollStrategy = pollStrategies.splice(0, 1)[0]
if (pollStrategy) { if (pollStrategy) {
defaultPollStrategy.streamLog = pollStrategy.streamLog
defaultPollStrategy.logFolderPath = pollStrategy.logFolderPath defaultPollStrategy.logFolderPath = pollStrategy.logFolderPath
} }
@@ -137,6 +159,7 @@ export async function pollJobState(
pollCount, pollCount,
defaultPollStrategy, defaultPollStrategy,
authConfig, authConfig,
streamLog,
logFileStream logFileStream
) )
@@ -195,6 +218,7 @@ const doPoll = async (
pollCount: number, pollCount: number,
pollStrategy: PollStrategy, pollStrategy: PollStrategy,
authConfig?: AuthConfig, authConfig?: AuthConfig,
streamLog?: boolean,
logStream?: WriteStream logStream?: WriteStream
): Promise<{ state: JobState; pollCount: number }> => { ): Promise<{ state: JobState; pollCount: number }> => {
const { maxPollCount, pollInterval } = pollStrategy const { maxPollCount, pollInterval } = pollStrategy
@@ -232,7 +256,7 @@ const doPoll = async (
const jobHref = postedJob.links.find((l: Link) => l.rel === 'self')!.href const jobHref = postedJob.links.find((l: Link) => l.rel === 'self')!.href
if (pollStrategy?.streamLog) { if (streamLog) {
const { result: job } = await requestClient.get<Job>( const { result: job } = await requestClient.get<Job>(
jobHref, jobHref,
authConfig?.access_token authConfig?.access_token
@@ -285,8 +309,6 @@ const validatePollStrategies = (strategies: PollStrategies) => {
) )
} }
if (!strategies.length) throwError('No strategies provided.')
strategies.forEach((strategy: PollStrategy, i: number) => { strategies.forEach((strategy: PollStrategy, i: number) => {
const { maxPollCount, pollInterval } = strategy const { maxPollCount, pollInterval } = strategy

View File

@@ -15,8 +15,7 @@ const sessionManager = new (<jest.Mock<SessionManager>>SessionManager)()
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)() const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
const defaultPollStrategy: PollStrategy = { const defaultPollStrategy: PollStrategy = {
maxPollCount: 100, maxPollCount: 100,
pollInterval: 500, pollInterval: 500
streamLog: false
} }
describe('executeScript', () => { describe('executeScript', () => {

View File

@@ -14,10 +14,10 @@ const baseUrl = 'http://localhost'
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)() const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
requestClient['httpClient'].defaults.baseURL = baseUrl requestClient['httpClient'].defaults.baseURL = baseUrl
const defaultStreamLog = false
const defaultPollStrategy: PollStrategy = { const defaultPollStrategy: PollStrategy = {
maxPollCount: 100, maxPollCount: 100,
pollInterval: 500, pollInterval: 500
streamLog: false
} }
describe('pollJobState', () => { describe('pollJobState', () => {
@@ -32,7 +32,8 @@ describe('pollJobState', () => {
mockJob, mockJob,
false, false,
mockAuthConfig, mockAuthConfig,
defaultPollStrategy defaultPollStrategy,
defaultStreamLog
) )
expect(getTokensModule.getTokens).toHaveBeenCalledWith( expect(getTokensModule.getTokens).toHaveBeenCalledWith(
@@ -83,10 +84,14 @@ describe('pollJobState', () => {
mockSimplePoll() mockSimplePoll()
const { saveLog } = require('../saveLog') const { saveLog } = require('../saveLog')
await pollJobState(requestClient, mockJob, false, mockAuthConfig, { await pollJobState(
...defaultPollStrategy, requestClient,
streamLog: true mockJob,
}) false,
mockAuthConfig,
defaultPollStrategy,
true
)
expect(saveLog).toHaveBeenCalledTimes(2) expect(saveLog).toHaveBeenCalledTimes(2)
}) })
@@ -96,10 +101,14 @@ describe('pollJobState', () => {
const { getFileStream } = require('../getFileStream') const { getFileStream } = require('../getFileStream')
const { saveLog } = require('../saveLog') const { saveLog } = require('../saveLog')
await pollJobState(requestClient, mockJob, false, mockAuthConfig, { await pollJobState(
...defaultPollStrategy, requestClient,
streamLog: true mockJob,
}) false,
mockAuthConfig,
defaultPollStrategy,
true
)
expect(getFileStream).toHaveBeenCalled() expect(getFileStream).toHaveBeenCalled()
expect(saveLog).toHaveBeenCalledTimes(2) expect(saveLog).toHaveBeenCalledTimes(2)
@@ -111,10 +120,14 @@ describe('pollJobState', () => {
const { saveLog } = require('../saveLog') const { saveLog } = require('../saveLog')
const { getFileStream } = require('../getFileStream') const { getFileStream } = require('../getFileStream')
await pollJobState(requestClient, mockJob, false, mockAuthConfig, { await pollJobState(
...defaultPollStrategy, requestClient,
streamLog: true mockJob,
}) false,
mockAuthConfig,
defaultPollStrategy,
true
)
expect(getFileStream).not.toHaveBeenCalled() expect(getFileStream).not.toHaveBeenCalled()
expect(saveLog).not.toHaveBeenCalled() expect(saveLog).not.toHaveBeenCalled()
@@ -137,19 +150,18 @@ describe('pollJobState', () => {
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 () => {
mockRunningPoll() mockRunningPoll()
const pollStrategy = { const pollStrategy: PollStrategy = {
...defaultPollStrategy, ...defaultPollStrategy,
maxPollCount: 1 maxPollCount: 1,
subsequentStrategies: []
} }
const pollStrategies = [pollStrategy]
const state = await pollJobState( const state = await pollJobState(
requestClient, requestClient,
mockJob, mockJob,
false, false,
mockAuthConfig, mockAuthConfig,
pollStrategy, pollStrategy
pollStrategies
) )
expect(state).toEqual('running') expect(state).toEqual('running')
@@ -264,53 +276,32 @@ describe('pollJobState', () => {
const pollIntervals = [3, 4, 5, 6] const pollIntervals = [3, 4, 5, 6]
const pollStrategies = [ const pollStrategies = [
{ maxPollCount: 1, pollInterval: pollIntervals[0], streamLog: false }, { maxPollCount: 2, pollInterval: pollIntervals[1] },
{ maxPollCount: 2, pollInterval: pollIntervals[1], streamLog: false }, { maxPollCount: 3, pollInterval: pollIntervals[2] },
{ maxPollCount: 3, pollInterval: pollIntervals[2], streamLog: false }, { maxPollCount: 4, pollInterval: pollIntervals[3] }
{ maxPollCount: 4, pollInterval: pollIntervals[3], streamLog: false }
] ]
await pollJobState( const pollStrategy: PollStrategy = {
requestClient, maxPollCount: 1,
mockJob, pollInterval: pollIntervals[0],
false, subsequentStrategies: pollStrategies
undefined, }
undefined,
pollStrategies await pollJobState(requestClient, mockJob, false, undefined, pollStrategy)
)
expect(delays).toEqual([pollIntervals[0], ...pollIntervals]) expect(delays).toEqual([pollIntervals[0], ...pollIntervals])
}) })
it('should throw an error if not valid poll strategies provided', async () => { it('should throw an error if not valid poll strategies provided', async () => {
// INFO: No strategies provided.
let expectedError = new Error(
'Poll strategies are not valid. No strategies provided.'
)
let pollStrategies: PollStrategies = []
await expect(
pollJobState(
requestClient,
mockJob,
false,
undefined,
undefined,
pollStrategies
)
).rejects.toThrow(expectedError)
// INFO: 'maxPollCount' has to be > 0 // INFO: 'maxPollCount' has to be > 0
let invalidPollStrategy = { let invalidPollStrategy = {
maxPollCount: 0, maxPollCount: 0,
pollInterval: 3, pollInterval: 3
streamLog: false
} }
pollStrategies.push(invalidPollStrategy) let pollStrategies: PollStrategies = [invalidPollStrategy]
expectedError = new Error( let expectedError = new Error(
`Poll strategies are not valid. 'maxPollCount' has to be greater than 0. Invalid poll strategy: \n${JSON.stringify( `Poll strategies are not valid. 'maxPollCount' has to be greater than 0. Invalid poll strategy: \n${JSON.stringify(
invalidPollStrategy, invalidPollStrategy,
null, null,
@@ -319,27 +310,21 @@ describe('pollJobState', () => {
) )
await expect( await expect(
pollJobState( pollJobState(requestClient, mockJob, false, undefined, {
requestClient, ...defaultPollStrategy,
mockJob, subsequentStrategies: pollStrategies
false, })
undefined,
undefined,
pollStrategies
)
).rejects.toThrow(expectedError) ).rejects.toThrow(expectedError)
// INFO: 'maxPollCount' has to be > than 'maxPollCount' of the previous strategy // INFO: 'maxPollCount' has to be > than 'maxPollCount' of the previous strategy
const validPollStrategy = { const validPollStrategy = {
maxPollCount: 5, maxPollCount: 5,
pollInterval: 2, pollInterval: 2
streamLog: false
} }
invalidPollStrategy = { invalidPollStrategy = {
maxPollCount: validPollStrategy.maxPollCount, maxPollCount: validPollStrategy.maxPollCount,
pollInterval: 3, pollInterval: 3
streamLog: false
} }
pollStrategies = [validPollStrategy, invalidPollStrategy] pollStrategies = [validPollStrategy, invalidPollStrategy]
@@ -353,21 +338,16 @@ describe('pollJobState', () => {
) )
await expect( await expect(
pollJobState( pollJobState(requestClient, mockJob, false, undefined, {
requestClient, ...defaultPollStrategy,
mockJob, subsequentStrategies: pollStrategies
false, })
undefined,
undefined,
pollStrategies
)
).rejects.toThrow(expectedError) ).rejects.toThrow(expectedError)
// INFO: invalid 'pollInterval' // INFO: invalid 'pollInterval'
invalidPollStrategy = { invalidPollStrategy = {
maxPollCount: 1, maxPollCount: 1,
pollInterval: 0, pollInterval: 0
streamLog: false
} }
pollStrategies = [invalidPollStrategy] pollStrategies = [invalidPollStrategy]
@@ -381,14 +361,10 @@ describe('pollJobState', () => {
) )
await expect( await expect(
pollJobState( pollJobState(requestClient, mockJob, false, undefined, {
requestClient, ...defaultPollStrategy,
mockJob, subsequentStrategies: pollStrategies
false, })
undefined,
undefined,
pollStrategies
)
).rejects.toThrow(expectedError) ).rejects.toThrow(expectedError)
}) })
}) })

View File

@@ -1,7 +1,8 @@
export interface PollStrategy { export interface PollStrategy {
maxPollCount: number maxPollCount: number
pollInterval: number // milliseconds pollInterval: number // milliseconds
streamLog: boolean subsequentStrategies?: PollStrategy[]
streamLog?: boolean
logFolderPath?: string logFolderPath?: string
} }