-
Notifications
You must be signed in to change notification settings - Fork 4
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 v2 #3236
Project Workflow v2 #3236
Conversation
99f68d0
to
111673a
Compare
🗞 GraphQL SummaryView schema changes@@ -1608,8 +1608,28 @@
"""
transition: ID
}
+input ExecuteProjectTransitionInput {
+ """
+ Bypass the workflow, and go straight to this state.
+ `transition` is not required and ignored when using this.
+ """
+ bypassTo: ProjectStep
+
+ """Any additional user notes related to this transition"""
+ notes: RichText
+
+ """The project ID to transition"""
+ project: ID!
+
+ """
+ The transition `key` to execute.
+ This is required unless specifying bypassing the workflow with a `step` input.
+ """
+ transition: ID
+}
+
"""
First scripture that has been created but managed _outside_ of CORD. `hasFirst` will always be true.
"""
type ExternalFirstScripture implements FirstScripture {
@@ -2233,8 +2253,9 @@
"""The project members"""
team(input: ProjectMemberListInput = {count: 25, filter: {}, order: ASC, page: 1, sort: "createdAt"}): SecuredProjectMemberList!
type: ProjectType!
+ workflowEvents: [ProjectWorkflowEvent!]!
}
type InternshipProjectListOutput implements PaginatedList {
"""Whether the next page exists"""
@@ -2731,8 +2752,9 @@
"""The project members"""
team(input: ProjectMemberListInput = {count: 25, filter: {}, order: ASC, page: 1, sort: "createdAt"}): SecuredProjectMemberList!
type: ProjectType!
+ workflowEvents: [ProjectWorkflowEvent!]!
}
input MoveFileInput {
"""The file or directory's ID"""
@@ -2847,8 +2869,9 @@
"""The project members"""
team(input: ProjectMemberListInput = {count: 25, filter: {}, order: ASC, page: 1, sort: "createdAt"}): SecuredProjectMemberList!
type: ProjectType!
+ workflowEvents: [ProjectWorkflowEvent!]!
}
type Mutation {
"""Add a location to a language"""
@@ -3101,8 +3124,9 @@
"""
pinned: Boolean
): Boolean!
transitionProgressReport(input: ExecuteProgressReportTransitionInput!): ProgressReport!
+ transitionProject(input: ExecuteProjectTransitionInput!): Project!
"""Update a budget"""
updateBudget(input: UpdateBudgetInput!): UpdateBudgetOutput!
@@ -4423,8 +4447,9 @@
"""The project members"""
team(input: ProjectMemberListInput = {count: 25, filter: {}, order: ASC, page: 1, sort: "createdAt"}): SecuredProjectMemberList!
type: ProjectType!
+ workflowEvents: [ProjectWorkflowEvent!]!
}
type ProjectChangeRequest implements Changeset & Resource {
"""Whether the changes have been applied to live data"""
@@ -4627,19 +4652,35 @@
PendingReactivationApproval
PendingRegionalDirectorApproval
PendingSuspensionApproval
PendingTerminationApproval
+
+ """@label Pending Field Operations Approval"""
PendingZoneDirectorApproval
PrepForConsultantEndorsement
PrepForFinancialEndorsement
Rejected
Suspended
Terminated
}
+"""
+A transition for the project workflow.
+
+This is not a normalized entity.
+A transition represented by its `key` can have different field values
+based on the workflow's state.
+"""
type ProjectStepTransition {
disabled: Boolean!
disabledReason: String
+
+ """
+ An local identifier for this transition.
+ It cannot be used to globally identify a transition.
+ It is passed to the transition mutation.
+ """
+ key: ID!
label: String!
to: ProjectStep!
type: TransitionType!
}
@@ -4672,8 +4713,19 @@
projectTypes: [ProjectType!]!
user: ID!
}
+type ProjectWorkflowEvent {
+ at: DateTime!
+ id: ID!
+ notes: SecuredRichTextNullable!
+ to: ProjectStep!
+
+ """The transition taken, null if workflow was bypassed"""
+ transition: ProjectStepTransition
+ who: SecuredActor!
+}
+
type Prompt {
"""Whether the requesting user can delete this resource"""
canDelete: Boolean!
createdAt: DateTime!
@@ -4878,8 +4930,9 @@
"""Look up project members"""
projectMembers(input: ProjectMemberListInput = {count: 25, filter: {}, order: ASC, page: 1, sort: "createdAt"}): ProjectMemberListOutput! @deprecated(reason: "Query via project instead")
projectTypeFinancialApprovers(projectTypes: [ProjectType!]): [ProjectTypeFinancialApprover!]!
+ projectWorkflow: Workflow!
"""Look up projects"""
projects(input: ProjectListInput = {count: 25, filter: {}, order: ASC, page: 1, sort: "name"}): ProjectListOutput!
@@ -5194,8 +5247,19 @@
canRead: Boolean!
}
"""
+An object with a actor `value` and additional authorization information.
+The value is only given if `canRead` is `true` otherwise it is `null`.
+These `can*` authorization properties are specific to the user making the request.
+"""
+type SecuredActor implements Secured {
+ canEdit: Boolean!
+ canRead: Boolean!
+ value: Actor
+}
+
+"""
An object with a boolean `value` and additional authorization information.
The value is only given if `canRead` is `true` otherwise it is `null`.
These `can*` authorization properties are specific to the user making the request.
"""
@@ -6167,15 +6231,15 @@
"""
type SecuredProjectStep implements Secured {
"""
Is the current user allowed to bypass transitions entirely
- and change the step to any other step?
+ and change to any other state?
"""
canBypassTransitions: Boolean!
canEdit: Boolean!
canRead: Boolean!
- """The available steps a project can be transitioned to."""
+ """The transitions currently available to execute for this project"""
transitions: [ProjectStepTransition!]!
value: ProjectStep
}
@@ -6657,8 +6721,9 @@
"""The project members"""
team(input: ProjectMemberListInput = {count: 25, filter: {}, order: ASC, page: 1, sort: "createdAt"}): SecuredProjectMemberList!
type: ProjectType!
+ workflowEvents: [ProjectWorkflowEvent!]!
}
type TranslationProjectListOutput implements PaginatedList {
"""Whether the next page exists"""
@@ -7558,4 +7623,69 @@
"""
download: Boolean! = false
): URL!
}
+
+type Workflow {
+ id: ID!
+ states: [WorkflowState!]!
+ transitions: [WorkflowTransition!]!
+}
+
+type WorkflowCondition {
+ label: String!
+}
+
+type WorkflowNotifier {
+ label: String!
+}
+
+type WorkflowState {
+ label: String!
+ value: String!
+}
+
+type WorkflowTransition {
+ conditions: [WorkflowCondition!]!
+ devName: String!
+ from: [WorkflowState!]!
+ key: ID!
+ label: String!
+ notifiers: [WorkflowNotifier!]!
+ permissions: [WorkflowTransitionPermission!]!
+ to: WorkflowTransitionTo!
+ type: TransitionType!
+}
+
+type WorkflowTransitionDynamicTo {
+ id: ID!
+ label: String!
+ relatedStates: [WorkflowState!]!
+}
+
+"""
+A permission for a transition.
+
+This will either have `readEvent` or `execute` as a boolean,
+specifying the action this permission defines.
+If this is true, there could still be a condition that must be met,
+described by the `condition` field.
+"""
+type WorkflowTransitionPermission {
+ """
+ The action for this permission is conditional, described by this field.
+ """
+ condition: String
+
+ """Can this role execute this transition?"""
+ execute: Boolean
+
+ """Can this role read historical events for this transition?"""
+ readEvent: Boolean
+ role: Role!
+}
+
+type WorkflowTransitionStaticTo {
+ state: WorkflowState!
+}
+
+union WorkflowTransitionTo = WorkflowTransitionDynamicTo | WorkflowTransitionStaticTo
|
111673a
to
8e2bd41
Compare
This means that a single transition can have multiple states based on other things (like project state).
This opens the door for `from` field & generic abstraction
This throws a better error message than "not available." It is not that it is not available, it doesn't exist, it will never work.
This is mainly so that the project step can be determined correctly, even if the current user can't read workflow events. This should be slightly more performant for reads too. The current value is still based on events which trigger state changes, and enforced.
84af753
to
b2e86fa
Compare
This allows transitions to depend on membership, sensitivity, etc.
This will allow complex condition expressions to be simplified based on a resolution of certain conditions within the expression. This allows us to emit condition string for each transition by resolving only that one condition. i.e. `(Member AND Transition { Approve }) OR Sens Low` becomes `Member OR Sens Low` `(Member OR Transition { Approve }) AND Sens Low` becomes `Sens Low`
8f275ff
to
ed89905
Compare
@CarsonF Where did you handle the condition for internships and the requirement that project managers should not be able to approve the consultant endorsement step? I didn't see it, so wasn't sure if you added it to this PR or you were planning to add it to a different PR. |
I may have missed it. Going to follow up in another PR with business logic changes. Much easier to discuss now that the workflow can be visualized. |
https://seed-company-squad.monday.com/boards/5989610236/pulses/6639688582