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

Project Workflow - PMs should not be able to bypass Consultant checks for Internships #3231

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8a34441
Port progress report workflow to projects
CarsonF May 21, 2024
960a662
Refactor old ProjectStepTransition -> new ProjectWorkflowTransition m…
CarsonF May 22, 2024
9bddbc2
Migrate legacy GQL flow `updateProject.step` -> `transitionProject`
CarsonF May 23, 2024
153f86c
Rename Transition `id` -> `key` to convey that it is not an entity
CarsonF May 23, 2024
6f48d45
Allow transition to have a dynamic `to` step. aka "BackToActive" func…
CarsonF May 23, 2024
122c72b
Rename `Event.step` -> `to`
CarsonF May 24, 2024
396e9cb
Add transition conditions to disable or omit from available list
CarsonF May 24, 2024
d0de087
Add notifiers to transitions && notifications for new project workflow
CarsonF May 24, 2024
94e7c2e
Use native zlib compression for flowchart url gen
CarsonF May 28, 2024
4e8bdc6
Upgrade flowchart gen for new workflow features & to use https://merm…
CarsonF May 30, 2024
f93b4d6
Fix `MadeEnum<'a'>` to be compatible with `MadeEnum<string>`
CarsonF Jun 3, 2024
14a7317
Abstract common workflow functionality
CarsonF May 31, 2024
b9735a2
Unify workflow into a single object/type
CarsonF Jun 3, 2024
03a1d45
Port project rules to project workflow transitions
atGit2021 May 29, 2024
e73f732
Port project rules approvers to workflow policies
atGit2021 May 31, 2024
0a2b56c
consolidated transitions
atGit2021 May 31, 2024
dbd2588
Fix legacy flow bypass to
CarsonF Jun 3, 2024
986c01f
Fix injection in notifiers
CarsonF Jun 3, 2024
3bdb7e2
Fix serving stale data from legacy update flow
CarsonF Jun 3, 2024
84e78c1
revised label name
atGit2021 Jun 4, 2024
14a1d59
Render states before transitions in flowchart to improve layout
CarsonF Jun 4, 2024
e715892
Sort transitions by end state in flowchart to improve layout
CarsonF Jun 4, 2024
bfdc00e
Change label for ProjectStep Zone Director -> Field Ops
CarsonF Jun 4, 2024
e06d719
Dedupe transition label nodes in flowchart
CarsonF Jun 4, 2024
7bbc535
Add arrows into transition labels in flowchart
CarsonF Jun 4, 2024
138fdb8
Only RDs can bypass RD approval on multiplication projects
CarsonF Jun 4, 2024
2016be4
revised exception message
atGit2021 Jun 4, 2024
acb3254
Validate role for internship projects
atGit2021 Jun 6, 2024
0199362
created hasEngagement condition
atGit2021 Jun 6, 2024
06fa306
revised condition per feedback
atGit2021 Jun 7, 2024
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
73 changes: 73 additions & 0 deletions dbschema/migrations/00009-m1gm5bm.edgeql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 28 additions & 9 deletions dbschema/project.esdl
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,10 @@ module default {
);
};

required step: Project::Step {
default := Project::Step.EarlyConversations;
};
required stepChangedAt: datetime {
default := .createdAt;
rewrite update using (datetime_of_statement() if .step != __old__.step else .stepChangedAt);
}
property status := Project::statusFromStep(.step);
status := Project::statusFromStep(.step);
step := .latestWorkflowEvent.to ?? Project::Step.EarlyConversations;
latestWorkflowEvent := (select .workflowEvents order by .at desc limit 1);
workflowEvents := .<project[is Project::WorkflowEvent];

mouStart: cal::local_date;
mouEnd: cal::local_date;
Expand Down Expand Up @@ -199,7 +195,30 @@ module Project {
on target delete allow;
};
}


type WorkflowEvent {
required project: default::Project {
readonly := true;
};
required who: default::Actor {
readonly := true;
default := global default::currentActor;
};
required at: datetime {
readonly := true;
default := datetime_of_statement();
};
transitionKey: uuid {
readonly := true;
};
required to: Step {
readonly := true;
};
notes: default::RichText {
readonly := true;
};
}

scalar type Step extending enum<
EarlyConversations,
PendingConceptApproval,
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@
"nanoid": "^4.0.2",
"neo4j-driver": "^5.20.0",
"p-retry": "^5.1.2",
"pako": "^2.1.0",
"pkg-up": "^4.0.0",
"plur": "^5.1.0",
"prismjs-terminal": "^1.2.3",
Expand Down Expand Up @@ -125,7 +124,6 @@
"@types/lodash": "^4.14.200",
"@types/luxon": "^3.3.3",
"@types/node": "^20.12.5",
"@types/pako": "^2.0.2",
"@types/prismjs": "^1.26.2",
"@types/react": "^18.2.33",
"@types/stack-trace": "^0.0.32",
Expand Down
15 changes: 10 additions & 5 deletions src/common/make-enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ export type MadeEnum<
Values extends string,
ValueDeclaration = EnumValueDeclarationShape,
Extra = unknown,
> = {
readonly [Value in Values & string]: Value;
} & EnumHelpers<
> = EnumHelpers<
Values,
ValueDeclaration & ImplicitValueDeclarationShape<Values>
> &
Readonly<Extra>;
Readonly<Extra> &
// Allow direct access to the values if they're strict.
// For generic `string` we don't allow this.
// This allows strict values to be compatible with generic values.
// MadeEnum<string> = MadeEnum<X>
(string extends Values
? unknown // ignore addition
: { readonly [Value in Values & string]: Value });

interface EnumOptions<
ValueDeclaration extends EnumValueDeclarationShape,
Expand Down Expand Up @@ -217,7 +222,7 @@ type NormalizedValueDeclaration<Declaration extends EnumValueDeclarationShape> =
interface EnumHelpers<Values extends string, ValueDeclaration> {
readonly values: ReadonlySet<Values>;
readonly entries: ReadonlyArray<Readonly<ValueDeclaration>>;
readonly entry: (value: Values) => Readonly<ValueDeclaration>;
readonly entry: <V extends Values>(value: V) => Readonly<ValueDeclaration>;
readonly has: <In extends string>(
value: In & {},
) => value is In & Values & {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,9 @@ import { member, Policy, Role } from '../util';
]),
r.Unavailability.read,
r.User.read.create,
r.ProjectWorkflowEvent.read.transitions(
'Pending Consultant Endorsement -> Prep for Financial Endorsement With Consultant Endorsement',
'Pending Consultant Endorsement -> Prep for Financial Endorsement Without Consultant Endorsement',
).execute,
])
export class ConsultantPolicy {}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,16 @@ import { Policy, Role } from '../util';
// keep multiline format
r.Organization.delete,
r.Partner.delete,
r.ProjectWorkflowEvent.read.transitions(
'Pending & On Hold Finance Confirmation -> Active',
'Pending Finance Confirmation -> Pending Regional Director Approval',
'Pending Finance Confirmation -> Did Not Develop',
'Pending Finance Confirmation -> On Hold Finance Confirmation',
'Pending & On Hold Finance Confirmation -> Finalizing Proposal',
'Pending & On Hold Finance Confirmation -> Rejected',
'Pending Change To Plan Confirmation -> Discussing Change To Plan',
'Pending Change To Plan Confirmation -> Active Changed Plan',
'Pending Change To Plan Confirmation -> Back To Active',
).execute,
])
export class ControllerPolicy {}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import { Policy, Role } from '../util';
r.Product.edit.create.delete,
r.Project.edit,
r.ProjectMember.edit.create.delete,
r.ProjectWorkflowEvent.read.transitions(
'Pending Zone Director Approval -> Pending Finance Confirmation',
'Pending Zone Director Approval -> Finalizing Proposal',
'Pending Zone Director Approval -> Rejected',
).execute,
r.PeriodicReport.edit,
r.StepProgress.edit,
])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ import {
])
.children((c) => c.posts.edit),
r.ProjectMember.read.when(member).edit.create.delete,
r.ProjectWorkflowEvent.read.transitions(
'Pending Financial Endorsement -> Finalizing Proposal With Financial Endorsement',
'Pending Financial Endorsement -> Finalizing Proposal Without Financial Endorsement',
'Finalizing Completion -> Back To Active',
'Finalizing Completion -> Completed',
).execute,
r.PeriodicReport.read.when(member).edit,
r.StepProgress.read,
r.Unavailability.read,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,38 @@ const stepsUntilFinancialEndorsement = takeWhile(
'Review Reject',
'Review Approve',
).execute,
r.ProjectWorkflowEvent.read.transitions(
'Early Conversations -> Pending Regional Director Approval',
'Early Conversations -> Pending Concept Approval',
'Early Conversations -> Did Not Develop',
'Prep for Consultant Endorsement -> Pending Consultant Endorsement',
'Prep for Consultant & Financial Endorsement & Finalizing Proposal -> Pending Concept Approval',
'Prep for Consultant & Financial Endorsement & Finalizing Proposal -> Did Not Develop',
'Pending Consultant Endorsement -> Prep for Financial Endorsement With Consultant Endorsement',
'Pending Consultant Endorsement -> Prep for Financial Endorsement Without Consultant Endorsement',
'Prep for Financial Endorsement -> Pending Financial Endorsement',
'Prep for Financial Endorsement & Finalizing Proposal -> Pending Consultant Endorsement',
'Finalizing Proposal -> Pending Regional Director Approval',
'Finalizing Proposal -> Pending Financial Endorsement',
'Active -> Discussing Change To Plan',
'Active -> Discussing Termination',
'Active -> Finalizing Completion',
'Discussing Change To Plan -> Pending Change To Plan Approval',
'Discussing Change To Plan -> Discussing Suspension',
'Discussing Change To Plan -> Back To Active',
'Pending Change To Plan Approval -> Discussing Change To Plan',
'Pending Change To Plan Approval -> Pending Change To Plan Confirmation',
'Pending Change To Plan Approval -> Back To Active',
'Discussing Suspension -> Pending Suspension Approval',
'Discussing Suspension -> Back To Active',
'Suspended -> Discussing Reactivation',
'Suspended & Discussing Reactivation -> Discussing Termination',
'Discussing Reactivation -> Pending Reactivation Approval',
'Discussing Termination -> Pending Termination Approval',
'Discussing Termination -> Back To Most Recent',
'Finalizing Completion -> Back To Active',
'Finalizing Completion -> Completed',
).execute,
r.Project.read.create
.when(member)
.edit.specifically((p) => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,26 @@ import { member, Policy, Role, sensMediumOrLower } from '../util';
r.Project.when(member).edit.specifically(
(p) => p.rootDirectory.edit.when(sensMediumOrLower).read,
),
r.ProjectWorkflowEvent.read.transitions(
'Early Conversations -> Pending Finance Confirmation',
'Pending Concept Approval -> Prep for Consultant Endorsement',
'Pending Concept Approval -> Early Conversations',
'Pending Concept Approval -> Rejected',
'Pending Regional Director Approval -> Early Conversations',
'Pending Regional Director Approval -> Pending Finance Confirmation',
'Pending Regional Director Approval -> Pending Zone Director Approval',
'Pending Regional Director Approval -> Finalizing Proposal',
'Pending Regional Director Approval -> Did Not Develop',
'Pending Regional Director Approval -> Rejected',
'Pending Suspension Approval -> Discussing Suspension',
'Pending Suspension Approval -> Suspended',
'Pending Suspension Approval -> Back To Active',
'Pending Reactivation Approval -> Active Changed Plan',
'Pending Reactivation Approval -> Discussing Reactivation',
'Pending Reactivation Approval -> Discussing Termination',
'Pending Termination Approval -> Terminated',
'Pending Termination Approval -> Discussing Termination',
'Pending Termination Approval -> Back To Most Recent',
).execute,
])
export class RegionalDirectorPolicy {}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { ID, IdField } from '~/common';
import { TransitionType } from '../../../project/dto';
import { TransitionType } from '../../../workflow/dto';
import { ProgressReportStatus } from '../../dto';

export { TransitionType };

@ObjectType()
export abstract class ProgressReportWorkflowTransition {
@IdField()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import open from 'open';
import pako from 'pako';
import { deflateSync as deflate } from 'zlib';
import { ProgressReportStatus as Status } from '../dto';
import { Transitions } from './transitions';

Expand Down Expand Up @@ -77,8 +77,7 @@ export class ProgressReportWorkflowFlowchart {
}

private compressAndB64encode(str: string) {
const data = Buffer.from(str, 'utf8');
const compressed = pako.deflate(data, { level: 9 });
const compressed = deflate(str, { level: 9 });
const result = Buffer.from(compressed)
.toString('base64')
.replace(/\+/g, '-')
Expand Down
6 changes: 2 additions & 4 deletions src/components/progress-report/workflow/transitions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { mapValues } from '@seedcompany/common';
import { createHash } from 'crypto';
import { ID, Many, maybeMany, Role } from '~/common';
import { TransitionType as Type } from '../../workflow/dto';
import { ProgressReportStatus as Status } from '../dto';
import {
ProgressReportWorkflowTransition as PublicTransition,
TransitionType as Type,
} from './dto/workflow-transition.dto';
import { ProgressReportWorkflowTransition as PublicTransition } from './dto/workflow-transition.dto';

// This also controls the order shown in the UI.
// Therefore, these should generally flow down.
Expand Down
31 changes: 5 additions & 26 deletions src/components/project/dto/project-step.enum.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { ObjectType } from '@nestjs/graphql';
import { EnumType, makeEnum, SecuredEnum } from '~/common';

export type ProjectStep = EnumType<typeof ProjectStep>;
Expand All @@ -13,7 +13,10 @@ export const ProjectStep = makeEnum({
'PendingFinancialEndorsement',
'FinalizingProposal',
'PendingRegionalDirectorApproval',
'PendingZoneDirectorApproval',
{
value: 'PendingZoneDirectorApproval',
label: 'Pending Field Operations Approval',
},
'PendingFinanceConfirmation',
'OnHoldFinanceConfirmation',
'DidNotDevelop',
Expand All @@ -40,27 +43,3 @@ export const ProjectStep = makeEnum({
description: SecuredEnum.descriptionFor('a project step'),
})
export class SecuredProjectStep extends SecuredEnum(ProjectStep) {}

export type TransitionType = EnumType<typeof TransitionType>;
export const TransitionType = makeEnum({
name: 'TransitionType',
values: ['Neutral', 'Approve', 'Reject'],
});

@ObjectType()
export abstract class ProjectStepTransition {
@Field(() => ProjectStep)
to: ProjectStep;

@Field()
label: string;

@Field(() => TransitionType)
type: TransitionType;

@Field(() => Boolean, { defaultValue: false })
disabled?: boolean;

@Field(() => String, { nullable: true })
disabledReason?: string;
}
Loading
Loading