Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Avoid trigger last execution "unknown" while the connector is still running #1515

Merged
merged 3 commits into from
Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions packages/cozy-harvest-lib/src/models/ConnectionFlow.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ export class ConnectionFlow {
this.twoFAWaiters = this.twoFAWaiters || []

this.realtime = new Realtime({ client })

this.watchCurrentJobIfTriggerIsAlreadyRunning()
}

getTwoFACodeProvider() {
Expand Down Expand Up @@ -344,7 +346,8 @@ export class ConnectionFlow {
}

handleLoginSuccess() {
this.jobWatcher.handleSuccess()
this.jobWatcher.handleLoginSuccess()
this.triggerEvent(LOGIN_SUCCESS_EVENT)
}

handleLoginSuccessHandled() {
Expand Down Expand Up @@ -576,16 +579,29 @@ export class ConnectionFlow {
logger.info('Found no client connector launcher')
}
}
this.watchJob({ autoSuccessTimer: computedAutoSuccessTimer })
}

/**
* If the trigger we display is already running, subscribe to the associated job
*/
watchCurrentJobIfTriggerIsAlreadyRunning() {
if (get(this, 'trigger.current_state.status') === 'running') {
this.job = {
_id: get(this, 'trigger.current_state.last_executed_job_id')
}
this.watchJob()
}
}

watchJob(options = {autoSuccessTimer: false}) {
this.realtime.subscribe(
'updated',
JOBS_DOCTYPE,
this.job._id,
this.handleJobUpdated.bind(this)
)
this.jobWatcher = watchKonnectorJob(this.client, this.job, {
autoSuccessTimer: computedAutoSuccessTimer
})
this.jobWatcher = watchKonnectorJob(this.client, this.job, options)
logger.info(`ConnectionFlow: Subscribed to ${JOBS_DOCTYPE}:${this.job._id}`)

for (const ev of JOB_EVENTS) {
Expand Down Expand Up @@ -621,7 +637,9 @@ export class ConnectionFlow {
const { status, accountError } = this.state
const triggerError = triggersModel.getKonnectorJobError(trigger)
return {
running: ![ERRORED, IDLE, SUCCESS].includes(status),
running:
get(trigger, 'current_state.status') === 'running' ||
![ERRORED, IDLE, SUCCESS].includes(status),
twoFARunning: status === RUNNING_TWOFA,
twoFARetry: status == TWO_FA_MISMATCH,
triggerError: triggerError,
Expand Down
37 changes: 36 additions & 1 deletion packages/cozy-harvest-lib/src/models/ConnectionFlow.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import {
launchTrigger
} from '../connections/triggers'
import CozyRealtime from 'cozy-realtime'
import KonnectorJobWatcher from './konnector/KonnectorJobWatcher'
import KonnectorJobWatcher, { watchKonnectorJob } from './konnector/KonnectorJobWatcher'
import { konnectorPolicy as biKonnectorPolicy } from '../services/budget-insight'
import fixtures from '../../test/fixtures'
import sentryHub from '../sentry'
import { Q } from 'cozy-client'

jest.mock('./konnector/KonnectorJobWatcher')
jest.mock('../sentry', () => {
const mockScope = {
setTag: jest.fn()
Expand Down Expand Up @@ -116,6 +117,17 @@ describe('ConnectionFlow', () => {
const isSubmitting = flow => {
return flow.getState().running === true
}
beforeAll(() => {
watchKonnectorJob.mockReturnValue({on: () => ({})})
})

afterEach(() => {
jest.clearAllMocks()
})

afterAll(() => {
jest.restoreAllMocks()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the point to use a clearAllMocks before restoreAllMocks:

https://jestjs.io/docs/mock-function-api#mockfnmockrestore

I read here that mockRestore does everything that mockReset does
And mockReset does everything that mockClear deos

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: i recommend to remove clearAllMocks

})

it('should render as submitting when there is no account', async () => {
const { flow } = setup()
Expand Down Expand Up @@ -213,6 +225,10 @@ describe('ConnectionFlow', () => {
userCredentials: {
login: 'foo',
password: 'bar'
},
account: {
login: 'old',
password: 'old'
}
})

Expand Down Expand Up @@ -314,6 +330,7 @@ describe('ConnectionFlow', () => {
describe('ensureTriggerAndLaunch', () => {
beforeAll(() => {
jest.spyOn(cronHelpers, 'fromFrequency').mockReturnValue('0 0 0 * * 0')
watchKonnectorJob.mockReturnValue({on: () => ({})})
})

afterEach(() => {
Expand Down Expand Up @@ -481,6 +498,24 @@ describe('ConnectionFlow', () => {
delete window.cozy
delete window.ReactNativeWebView
})

describe('contructor', () => {
doubleface marked this conversation as resolved.
Show resolved Hide resolved
beforeAll(() => {
watchKonnectorJob.mockReturnValue({on: () => ({})})
})

afterEach(() => {
jest.clearAllMocks()
})

afterAll(() => {
jest.restoreAllMocks()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the point to use a clearAllMocks before restoreAllMocks:

https://jestjs.io/docs/mock-function-api#mockfnmockrestore

I read here that mockRestore does everything that mockReset does
And mockReset does everything that mockClear deos

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: i recommend to remove clearAllMocks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to remove restoreAllMocks in this case. ClearAllMocks will erase the history of calls between test which is what I want in the first place.

And removing clearAllMocks makes the tests fail

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, then.

ps: I need to understand one day why mocks are not reset by default. 🤯

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

})
it('should watch a running trigger', () => {
setup({trigger: fixtures.runningTrigger})
expect(watchKonnectorJob).toHaveBeenCalledWith(expect.any(Object), {_id: 'runningjobid'}, {autoSuccessTimer: false})
})
})
})

// it should have running false on trigger updates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ export class KonnectorJobWatcher {
this.emit('error', new KonnectorJobError(error))
}

handleLoginSuccess() {
Copy link
Contributor

@trollepierre trollepierre Apr 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: don't we test that part?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would need to be an integration test. I think there is no point to add a unit test here.

logger.info(`KonnectorJobWatcher: login success`)
this.disableSuccessTimer()
}

handleSuccess() {
logger.info(`KonnectorJobWatcher: Job has succeeded`)
this.disableSuccessTimer()
Expand Down
17 changes: 17 additions & 0 deletions packages/cozy-harvest-lib/test/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,23 @@ const fixtures = {
}
}
},
runningTrigger: {
id: 'running-trigger-id',
_type: 'io.cozy.triggers',
current_state: {
status: 'running',
last_executed_job_id: 'runningjobid'
},
attributes: {
arguments: '0 0 0 * * 0',
type: '@cron',
worker: 'konnector',
message: {
account: 'updated-account-id',
konnector: 'konnectest'
}
}
},
createdTriggerWithFolder: {
id: 'created-trigger-id',
_type: 'io.cozy.triggers',
Expand Down