Skip to content

Commit

Permalink
Rewrite ReassignUserTasks to ModifyUserTasks
Browse files Browse the repository at this point in the history
Handles multiple potential use cases:
- Reassign tasks from one list of users to another.
- Delete all tasks for a product/project, optionally limit by user or user role.
- Create new tasks, optionally by user or role.
- Update tasks, optionally by user or role. (This is the main one used by workflow)

In all cases, can be applied to either a single product, or all products within a project. Will also refresh the PreExecuteEntries (i.e. blank-ish ProductTransitions that show the ideal path beyond the current workflow state).

Updated `databaseWrites/Projects.ts` and `workflow/index.ts` to use the new code.
  • Loading branch information
FyreByrd committed Oct 9, 2024
1 parent dbe423d commit 14cbe6a
Show file tree
Hide file tree
Showing 11 changed files with 434 additions and 258 deletions.
47 changes: 42 additions & 5 deletions source/SIL.AppBuilder.Portal/common/BullJobTypes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Channels } from './build-engine-api/types.js';
import { RoleId } from './public/prisma.js';

export enum ScriptoriaJobType {
Test = 'Test',
ReassignUserTasks = 'ReassignUserTasks',
ModifyUserTasks = 'ModifyUserTasks',
CreateProduct = 'CreateProduct',
BuildProduct = 'BuildProduct',
EmailReviewers = 'EmailReviewers',
Expand All @@ -19,11 +20,47 @@ export interface TestJob {
time: number;
}

export interface SyncUserTasksJob {
type: ScriptoriaJobType.ReassignUserTasks;
projectId: number;
export enum UserTaskOp {
Delete = 'Delete',
Update = 'Update',
Create = 'Create',
Reassign = 'Reassign'
}

type UserTaskOpConfig = (
| ({
type: UserTaskOp.Delete | UserTaskOp.Create | UserTaskOp.Update;
} & (
| { by: 'All' }
| { by: 'Role'; roles: RoleId[] }
| {
by: 'UserId';
users: number[];
}
))
| {
type: UserTaskOp.Reassign;
by?: 'UserIdMapping' // <- This is literally just so TS doesn't complain
userMapping: { from: number; to: number }[];
}
);

// Using type here instead of interface for easier composition
export type ModifyUserTasksJob = (
| {
scope: 'Project';
projectId: number;
}
| {
scope: 'Product';
productId: string;
}
) & {
type: ScriptoriaJobType.ModifyUserTasks;
comment?: string; // just ignore comment for Delete and Reassign
operation: UserTaskOpConfig;
};

export interface CreateProductJob {
type: ScriptoriaJobType.CreateProduct;
productId: string;
Expand Down Expand Up @@ -86,7 +123,7 @@ export type ScriptoriaJob = JobTypeMap[keyof JobTypeMap];

export type JobTypeMap = {
[ScriptoriaJobType.Test]: TestJob;
[ScriptoriaJobType.ReassignUserTasks]: SyncUserTasksJob;
[ScriptoriaJobType.ModifyUserTasks]: ModifyUserTasksJob;
[ScriptoriaJobType.CreateProduct]: CreateProductJob;
[ScriptoriaJobType.BuildProduct]: BuildProductJob;
[ScriptoriaJobType.EmailReviewers]: EmailReviewersJob;
Expand Down
26 changes: 21 additions & 5 deletions source/SIL.AppBuilder.Portal/common/databaseProxy/Projects.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { Prisma } from '@prisma/client';
import { ScriptoriaJobType } from '../BullJobTypes.js';
import {
ScriptoriaJobType,
UserTaskOp
} from '../BullJobTypes.js';
import { scriptoriaQueue } from '../bullmq.js';
import prisma from '../prisma.js';
import type { RequirePrimitive } from './utility.js';
Expand All @@ -19,7 +22,13 @@ import type { RequirePrimitive } from './utility.js';
export async function create(
projectData: RequirePrimitive<Prisma.ProjectsUncheckedCreateInput>
): Promise<boolean | number> {
if (!(await validateProjectBase(projectData.OrganizationId, projectData.GroupId, projectData.OwnerId)))
if (
!(await validateProjectBase(
projectData.OrganizationId,
projectData.GroupId,
projectData.OwnerId
))
)
return false;

// No additional verification steps
Expand Down Expand Up @@ -63,9 +72,16 @@ export async function update(
// If the owner has changed, we need to reassign all the user tasks related to this project
// TODO: But we don't need to change *every* user task, just the tasks associated with the owner.
if (ownerId && ownerId !== existing?.OwnerId) {
scriptoriaQueue.add(ScriptoriaJobType.ReassignUserTasks, {
type: ScriptoriaJobType.ReassignUserTasks,
projectId: id
scriptoriaQueue.add(`Reassign tasks for Project #${id} (New Owner)`, {
type: ScriptoriaJobType.ModifyUserTasks,
scope: 'Project',
projectId: id,
operation: {
type: UserTaskOp.Reassign,
userMapping: [
{ from: existing.OwnerId, to: ownerId }
]
}
});
}
} catch (e) {
Expand Down
42 changes: 42 additions & 0 deletions source/SIL.AppBuilder.Portal/common/databaseProxy/utility.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import prisma from '../prisma.js';
import { RoleId } from '../public/prisma.js';

export type RequirePrimitive<T> = {
[K in keyof T]: Extract<T[K], string | number | boolean | Date>;
Expand Down Expand Up @@ -36,3 +37,44 @@ export async function getOrCreateUser(profile: {
}
});
}

export async function allUsersByRole(projectId: number) {
const project = await prisma.projects.findUnique({
where: {
Id: projectId
},
select: {
Organization: {
select: {
UserRoles: {
where: {
RoleId: RoleId.OrgAdmin
},
select: {
UserId: true
}
}
}
},
OwnerId: true,
Authors: {
select: {
UserId: true
}
}
}
});

const map = new Map<RoleId, number[]>();

map.set(
RoleId.OrgAdmin,
project.Organization.UserRoles.map((u) => u.UserId)
);
map.set(RoleId.AppBuilder, [project.OwnerId]);
map.set(
RoleId.Author,
project.Authors.map((a) => a.UserId)
);
return map;
}
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ export const DefaultWorkflow = setup({
entry: [
assign({ instructions: 'waiting' }),
({ context }) => {
scriptoriaQueue.add(`Create Product (${context.productId})`, {
scriptoriaQueue.add(`Create Product #${context.productId}`, {
type: ScriptoriaJobType.CreateProduct,
productId: context.productId
},
Expand Down Expand Up @@ -471,7 +471,7 @@ export const DefaultWorkflow = setup({
instructions: 'waiting'
}),
({ context }) => {
scriptoriaQueue.add(`Build Product (${context.productId})`, {
scriptoriaQueue.add(`Build Product #${context.productId}`, {
type: ScriptoriaJobType.BuildProduct,
productId: context.productId,
// TODO: assign targets
Expand Down Expand Up @@ -700,7 +700,7 @@ export const DefaultWorkflow = setup({
entry: [
assign({ instructions: 'waiting' }),
({ context }) => {
scriptoriaQueue.add(`Publish Product (${context.productId})`, {
scriptoriaQueue.add(`Publish Product #${context.productId}`, {
type: ScriptoriaJobType.PublishProduct,
productId: context.productId,
// TODO: How should these values be determined?
Expand Down
Loading

0 comments on commit 14cbe6a

Please sign in to comment.