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

Set statuses on workflows #60

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

vkameswaran
Copy link

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.

Copy link

@greptile-apps greptile-apps bot left a 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

Copy link

@greptile-apps-local greptile-apps-local bot left a 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,

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

Suggested change
value: WorkflowVersionStatus.DRAFT,
value: WorkflowStatus.DRAFT,

describe('handle', () => {
beforeEach(() => {
// make twentyORMManager.getRepository return a mock object
TwentyORMManager.prototype.getRepository = jest.fn();

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,

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

Suggested change
value: WorkflowVersionStatus.ACTIVE,
value: WorkflowStatus.ACTIVE,

Comment on lines +264 to +281
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;
}

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

Suggested change
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 () => {

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,

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

Suggested change
value: WorkflowVersionStatus.DEACTIVATED,
value: WorkflowStatus.DEACTIVATED,

Comment on lines +346 to 370
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,
);

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

Copy link

@greptile-apps-staging greptile-apps-staging bot left a 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 in workflow-common.workspace-service.ts to improve validation logic separation
  • Modified WorkflowStatus enum in workflow.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 using WorkflowVersionStatus instead of WorkflowStatus 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],

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,

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

Suggested change
value: WorkflowVersionStatus.DRAFT,
value: WorkflowStatus.DRAFT,

Comment on lines +15 to +17
const mockWorkflowVersionRepository = {
find: jest.fn(),
};

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

Comment on lines +194 to +230
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] },
);
});

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

Comment on lines +36 to +38
(event) =>
!event.properties.after.status ||
event.properties.after.status === WorkflowVersionStatus.DRAFT,

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.

Comment on lines +95 to +99
const workflow = await workflowRepository.findOneOrFail({
where: {
id: workflowId,
},
});

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.

Comment on lines +276 to +280
this.emitStatusUpdateEventOrThrow(
workflowVersion.workflowId,
workflowVersion.status,
WorkflowVersionStatus.DEACTIVATED,
);

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.

Suggested change
this.emitStatusUpdateEventOrThrow(
workflowVersion.workflowId,
workflowVersion.status,
WorkflowVersionStatus.DEACTIVATED,
);
this.emitStatusUpdateEventOrThrow(
workflowVersion.workflowId,
WorkflowVersionStatus.ACTIVE,
WorkflowVersionStatus.DEACTIVATED,
);

Comment on lines +294 to +300
if (workflow.lastPublishedVersionId) {
await workflowVersionRepository.update(
{ id: workflow.lastPublishedVersionId },
{ status: WorkflowVersionStatus.ARCHIVED },
manager,
);
}

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.

Comment on lines +168 to +177
if (
workflow.lastPublishedVersionId &&
workflowVersion.id !== workflow.lastPublishedVersionId
) {
await this.performDeactivationSteps(
workflow.lastPublishedVersionId,
workflowVersionRepository,
manager,
);
}

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.

Comment on lines +226 to +234
const activeWorkflowVersions = await workflowVersionRepository.find(
{
where: {
workflowId: workflowVersion.workflowId,
status: WorkflowVersionStatus.ACTIVE,
},
},
manager,
);

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.

Suggested change
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,
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants