-
Notifications
You must be signed in to change notification settings - Fork 0
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
Set statuses on workflows #60
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
Hi! Looks like you've reached your API usage limit. You can increase it from your account settings page here: app.greptile.com/settings/billing/code-review
💡 (5/5) You can turn off certain types of comments like style here!
10 file(s) reviewed, no comment(s)
Edit PR Review Bot Settings | Greptile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
This PR introduces significant changes to the workflow status management system, focusing on maintaining accurate statuses for workflows and their versions. Key updates include:
- Added WorkflowStatusModule to handle workflow status updates
- Introduced new 'ARCHIVED' status for workflow versions
- Renamed endpoints to better reflect their functionality
- Implemented WorkflowStatusesUpdateJob for processing status changes
- Created WorkflowVersionStatusListener to handle version-related events
Key points to consider:
- WorkflowStatusModule is not explicitly imported in the main WorkflowModule
- New WorkflowStatusesUpdateJob handles various scenarios but may benefit from additional error handling
- WorkflowVersionStatusListener implementation is sound but could be improved with more robust error handling
- Renaming of methods in workflow-trigger.workspace-service.ts aligns with new status management approach
- Comprehensive unit tests added for WorkflowStatusesUpdateJob, ensuring proper functionality
10 file(s) reviewed, 7 comment(s)
Edit PR Review Bot Settings | Greptile
|
||
const WorkflowStatusOptions = [ | ||
{ | ||
value: WorkflowVersionStatus.DRAFT, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: WorkflowVersionStatus is used here instead of WorkflowStatus
value: WorkflowVersionStatus.DRAFT, | |
value: WorkflowStatus.DRAFT, |
describe('handle', () => { | ||
beforeEach(() => { | ||
// make twentyORMManager.getRepository return a mock object | ||
TwentyORMManager.prototype.getRepository = jest.fn(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Consider using jest.spyOn
instead of directly mocking prototype
color: 'yellow', | ||
}, | ||
{ | ||
value: WorkflowVersionStatus.ACTIVE, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: WorkflowVersionStatus is used here instead of WorkflowStatus
value: WorkflowVersionStatus.ACTIVE, | |
value: WorkflowStatus.ACTIVE, |
private async hasWorkflowVersionByStatus( | ||
workflowId: string, | ||
status: WorkflowVersionStatus, | ||
): Promise<boolean> { | ||
const workflowVersionRepository = | ||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>( | ||
'workflowVersion', | ||
); | ||
|
||
const workflowVersions = await workflowVersionRepository.find({ | ||
where: { | ||
workflowId, | ||
status, | ||
}, | ||
}); | ||
|
||
return workflowVersions.length > 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: method could be optimized to use count instead of fetching all versions
private async hasWorkflowVersionByStatus( | |
workflowId: string, | |
status: WorkflowVersionStatus, | |
): Promise<boolean> { | |
const workflowVersionRepository = | |
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>( | |
'workflowVersion', | |
); | |
const workflowVersions = await workflowVersionRepository.find({ | |
where: { | |
workflowId, | |
status, | |
}, | |
}); | |
return workflowVersions.length > 0; | |
} | |
private async hasWorkflowVersionByStatus( | |
workflowId: string, | |
status: WorkflowVersionStatus, | |
): Promise<boolean> { | |
const workflowVersionRepository = | |
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>( | |
'workflowVersion', | |
); | |
const count = await workflowVersionRepository.count({ | |
where: { | |
workflowId, | |
status, | |
}, | |
}); | |
return count > 0; | |
} |
expect(mockWorkflowRepository.update).toHaveBeenCalledTimes(0); | ||
}); | ||
|
||
test('when update that should be impossible, do not do anything', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Test name could be more specific about the impossible update
color: 'green', | ||
}, | ||
{ | ||
value: WorkflowVersionStatus.DEACTIVATED, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: WorkflowVersionStatus is used here instead of WorkflowStatus
value: WorkflowVersionStatus.DEACTIVATED, | |
value: WorkflowStatus.DEACTIVATED, |
private emitStatusUpdateEventOrThrow( | ||
workflowId: string, | ||
previousStatus: WorkflowVersionStatus, | ||
newStatus: WorkflowVersionStatus, | ||
) { | ||
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId; | ||
|
||
if (activeWorkflowVersions.length > 0) { | ||
if (!workspaceId) { | ||
throw new WorkflowTriggerException( | ||
'Cannot have more than one active workflow version', | ||
WorkflowTriggerExceptionCode.FORBIDDEN, | ||
'No workspace id found', | ||
WorkflowTriggerExceptionCode.INTERNAL_ERROR, | ||
); | ||
} | ||
|
||
await workflowVersionRepository.update( | ||
{ id: workflowVersionId }, | ||
{ status: WorkflowVersionStatus.ACTIVE }, | ||
manager, | ||
this.workspaceEventEmitter.emit( | ||
'workflowVersion.statusUpdated', | ||
[ | ||
{ | ||
workflowId, | ||
previousStatus, | ||
newStatus, | ||
} satisfies WorkflowVersionStatusUpdate, | ||
], | ||
workspaceId, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Event emission could potentially fail silently. Consider adding error handling
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
This PR continues to enhance the workflow status management system with several important changes:
- Added new
getValidWorkflowVersionOrFail
method inworkflow-common.workspace-service.ts
to improve validation logic separation - Modified
WorkflowStatus
enum inworkflow.workspace-entity.ts
to include DRAFT, ACTIVE, and DEACTIVATED states - Changed DEACTIVATED status color from gray to red in
workflow-version.workspace-entity.ts
- Introduced comprehensive test coverage in
workflow-statuses-update.job.spec.ts
for status transitions
Key points to review:
- Type mismatch in
WorkflowStatusOptions
usingWorkflowVersionStatus
instead ofWorkflowStatus
enum - Missing implementation of ARCHIVED status handling in
workflow-statuses-update.job.ts
- Potential race conditions during concurrent status updates need addressing
- No explicit error handling for missing workflows in status update logic
💡 (1/5) You can manually trigger the bot by mentioning @greptileai in a comment!
10 file(s) reviewed, 12 comment(s)
Edit PR Review Bot Settings | Greptile
import { WorkflowVersionStatusListener } from 'src/modules/workflow/workflow-status/listeners/workflow-version-status.listener'; | ||
|
||
@Module({ | ||
providers: [WorkflowStatusesUpdateJob, WorkflowVersionStatusListener], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Consider adding exports array to make these providers available to other modules that import WorkflowStatusModule
|
||
const WorkflowStatusOptions = [ | ||
{ | ||
value: WorkflowVersionStatus.DRAFT, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Using WorkflowVersionStatus.DRAFT instead of WorkflowStatus.DRAFT will cause type mismatch errors
value: WorkflowVersionStatus.DRAFT, | |
value: WorkflowStatus.DRAFT, |
const mockWorkflowVersionRepository = { | ||
find: jest.fn(), | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Repository mock is missing error cases - should test behavior when find() rejects
test('when WorkflowVersionStatus.ACTIVE to WorkflowVersionStatus.DEACTIVATED, should deactivate', async () => { | ||
// Arrange | ||
const event: WorkflowVersionBatchEvent = { | ||
workspaceId: '1', | ||
type: WorkflowVersionEventType.STATUS_UPDATE, | ||
statusUpdates: [ | ||
{ | ||
workflowId: '1', | ||
previousStatus: WorkflowVersionStatus.ACTIVE, | ||
newStatus: WorkflowVersionStatus.DEACTIVATED, | ||
}, | ||
], | ||
}; | ||
|
||
const mockWorkflow = { | ||
statuses: [WorkflowStatus.ACTIVE, WorkflowStatus.DRAFT], | ||
}; | ||
|
||
mockWorkflowRepository.findOneOrFail.mockResolvedValue(mockWorkflow); | ||
mockWorkflowVersionRepository.find.mockResolvedValue([]); | ||
|
||
// Act | ||
await job.handle(event); | ||
|
||
// Assert | ||
expect(mockWorkflowRepository.findOneOrFail).toHaveBeenCalledTimes(1); | ||
expect(mockWorkflowVersionRepository.find).toHaveBeenCalledWith({ | ||
where: { | ||
workflowId: '1', | ||
status: WorkflowVersionStatus.ACTIVE, | ||
}, | ||
}); | ||
expect(mockWorkflowRepository.update).toHaveBeenCalledWith( | ||
{ id: '1' }, | ||
{ statuses: [WorkflowStatus.DEACTIVATED, WorkflowStatus.DRAFT] }, | ||
); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Test case doesn't verify behavior when there are multiple active versions during deactivation
(event) => | ||
!event.properties.after.status || | ||
event.properties.after.status === WorkflowVersionStatus.DRAFT, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: This condition allows undefined status to be treated as DRAFT. Consider explicitly checking for undefined to make the logic more clear.
const workflow = await workflowRepository.findOneOrFail({ | ||
where: { | ||
id: workflowId, | ||
}, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: findOneOrFail could throw an exception if workflow doesn't exist. Consider adding explicit error handling to provide better error messages.
this.emitStatusUpdateEventOrThrow( | ||
workflowVersion.workflowId, | ||
workflowVersion.status, | ||
WorkflowVersionStatus.DEACTIVATED, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: The status update event is emitted with the old status (workflowVersion.status) instead of WorkflowVersionStatus.ACTIVE as the previousStatus. This will cause incorrect status tracking.
this.emitStatusUpdateEventOrThrow( | |
workflowVersion.workflowId, | |
workflowVersion.status, | |
WorkflowVersionStatus.DEACTIVATED, | |
); | |
this.emitStatusUpdateEventOrThrow( | |
workflowVersion.workflowId, | |
WorkflowVersionStatus.ACTIVE, | |
WorkflowVersionStatus.DEACTIVATED, | |
); |
if (workflow.lastPublishedVersionId) { | ||
await workflowVersionRepository.update( | ||
{ id: workflow.lastPublishedVersionId }, | ||
{ status: WorkflowVersionStatus.ARCHIVED }, | ||
manager, | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: No status update event is emitted when archiving a version. This could lead to inconsistencies in status tracking across the system.
if ( | ||
workflow.lastPublishedVersionId && | ||
workflowVersion.id !== workflow.lastPublishedVersionId | ||
) { | ||
await this.performDeactivationSteps( | ||
workflow.lastPublishedVersionId, | ||
workflowVersionRepository, | ||
manager, | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Consider checking if the lastPublishedVersionId version exists before attempting deactivation steps. Could throw if version was deleted.
const activeWorkflowVersions = await workflowVersionRepository.find( | ||
{ | ||
where: { | ||
workflowId: workflowVersion.workflowId, | ||
status: WorkflowVersionStatus.ACTIVE, | ||
}, | ||
}, | ||
manager, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: The active version check should include workflowVersion.id in the where clause to exclude the current version. Currently prevents reactivating a deactivated version.
const activeWorkflowVersions = await workflowVersionRepository.find( | |
{ | |
where: { | |
workflowId: workflowVersion.workflowId, | |
status: WorkflowVersionStatus.ACTIVE, | |
}, | |
}, | |
manager, | |
); | |
const activeWorkflowVersions = await workflowVersionRepository.find( | |
{ | |
where: { | |
workflowId: workflowVersion.workflowId, | |
status: WorkflowVersionStatus.ACTIVE, | |
id: { $ne: workflowVersion.id } | |
}, | |
}, | |
manager, | |
); |
Add listener to keep status on workflows up to date:
• version draft => statuses should contain draft
• version active => statuses should contain active
• version deactivated => if no version active, statuses should contain deactivated
Renaming also the endpoints because it was not reflecting the full behaviour.
Finally, adding a new status Archived for versions. Will be used when a version is deactivated, but is not the last published version anymore. It means this version cannot be re-activated.