diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f18e7c6ad..e3003c722 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest services: postgres: - image: postgres:latest + image: postgres@sha256:596e4c843a9db32269a3757624d8a6a6f633e01895acb83fe0842497fd897eb7 env: POSTGRES_PASSWORD: ctfnote POSTGRES_USER: ctfnote diff --git a/api/migrations/56-assign-users-to-task.sql b/api/migrations/56-assign-users-to-task.sql new file mode 100644 index 000000000..57ff1bccb --- /dev/null +++ b/api/migrations/56-assign-users-to-task.sql @@ -0,0 +1,35 @@ +-- Migration: Assign users to tasks by managers (GraphQL integration) +-- This migration introduces GraphQL functions to allow managers to assign/unassign any team member to a task. + +-- GraphQL mutation for assign_user_to_task +CREATE OR REPLACE FUNCTION ctfnote.assign_user_to_task(profile_id int, task_id int) + RETURNS ctfnote.work_on_task + AS $$ + WITH inserted AS ( + INSERT INTO ctfnote.work_on_task (task_id, profile_id, active) + VALUES (assign_user_to_task.task_id, assign_user_to_task.profile_id, TRUE) + ON CONFLICT (task_id, profile_id) DO UPDATE + SET active = TRUE + RETURNING * + ) + SELECT * FROM inserted; +$$ +LANGUAGE SQL; + +GRANT EXECUTE ON FUNCTION ctfnote.assign_user_to_task(profile_id int, task_id int) TO user_manager; + +-- GraphQL mutation for unassign_user_from_task +CREATE OR REPLACE FUNCTION ctfnote.unassign_user_from_task(profile_id int, task_id int) + RETURNS ctfnote.work_on_task + AS $$ + WITH updated AS ( + UPDATE ctfnote.work_on_task + SET active = FALSE + WHERE task_id = unassign_user_from_task.task_id + AND profile_id = unassign_user_from_task.profile_id + RETURNING * + ) + SELECT * FROM updated; +$$ LANGUAGE SQL; + +GRANT EXECUTE ON FUNCTION ctfnote.unassign_user_from_task(profile_id int, task_id int) TO user_manager; diff --git a/api/src/discord/agile/hooks.ts b/api/src/discord/agile/hooks.ts index 52ef5773f..b46e203b1 100644 --- a/api/src/discord/agile/hooks.ts +++ b/api/src/discord/agile/hooks.ts @@ -100,15 +100,6 @@ async function handleUpdateTask( } } -async function handleStartWorkingOn( - guild: Guild, - taskId: bigint, - userId: bigint -) { - await moveChannel(guild, taskId, null, ChannelMovingEvent.START); - await sendStartWorkingOnMessage(guild, userId, taskId); -} - async function handleUpdateUserRole( guild: Guild, userId: bigint, @@ -177,7 +168,9 @@ const discordMutationHook = (_build: Build) => (fieldContext: Context) => { fieldContext.scope.fieldName !== "deleteCtf" && fieldContext.scope.fieldName !== "updateUserRole" && fieldContext.scope.fieldName !== "setDiscordEventLink" && - fieldContext.scope.fieldName !== "registerWithToken" + fieldContext.scope.fieldName !== "registerWithToken" && + fieldContext.scope.fieldName !== "assignUserToTask" && + fieldContext.scope.fieldName !== "unassignUserFromTask" ) { return null; } @@ -225,7 +218,7 @@ const discordMutationHook = (_build: Build) => (fieldContext: Context) => { }); break; case "startWorkingOn": - handleStartWorkingOn( + sendStartWorkingOnMessage( guild, args.input.taskId, context.jwtClaims.user_id @@ -285,6 +278,27 @@ const discordMutationHook = (_build: Build) => (fieldContext: Context) => { console.error("Failed to register with token.", err); }); break; + case "assignUserToTask": + sendStartWorkingOnMessage( + guild, + args.input.profileId, + args.input.taskId, + context.jwtClaims.user_id + ).catch((err) => { + console.error("Failed to start working on task.", err); + }); + break; + case "unassignUserFromTask": + sendStopWorkingOnMessage( + guild, + args.input.profileId, + args.input.taskId, + false, + context.jwtClaims.user_id + ).catch((err) => { + console.error("Failed to stop working on task.", err); + }); + break; default: break; } @@ -348,31 +362,37 @@ const discordMutationHook = (_build: Build) => (fieldContext: Context) => { export async function sendStartWorkingOnMessage( guild: Guild, userId: bigint, - task: Task | bigint + task: Task | bigint, + assignedBy: bigint | null = null ) { await moveChannel(guild, task, null, ChannelMovingEvent.START); - return sendMessageToTask( - guild, - task, - `${await convertToUsernameFormat(userId)} is working on this task!` - ); + + let msg = `${await convertToUsernameFormat(userId)} is working on this task!`; + if (assignedBy != null) { + msg += ` (assigned by: ${await convertToUsernameFormat(assignedBy)})`; + } + + return sendMessageToTask(guild, task, msg); } export async function sendStopWorkingOnMessage( guild: Guild, userId: bigint, task: Task | bigint, - cancel = false + cancel = false, + assignedBy: bigint | null = null ) { let text = "stopped"; if (cancel) { text = "cancelled"; } - return sendMessageToTask( - guild, - task, - `${await convertToUsernameFormat(userId)} ${text} working on this task!` - ); + + let msg = `${await convertToUsernameFormat(userId)} ${text} working on this task!`; + if (assignedBy != null) { + msg += ` (assigned by: ${await convertToUsernameFormat(assignedBy)})`; + } + + return sendMessageToTask(guild, task, msg); } export async function handleDeleteCtf(ctfId: string | bigint, guild: Guild) { diff --git a/front/graphql.schema.json b/front/graphql.schema.json index f3b4fbaf9..2845f01c5 100644 --- a/front/graphql.schema.json +++ b/front/graphql.schema.json @@ -98,6 +98,159 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "AssignUserToTaskInput", + "description": "All input for the `assignUserToTask` mutation.", + "isOneOf": false, + "fields": null, + "inputFields": [ + { + "name": "clientMutationId", + "description": "An arbitrary string value with no semantic meaning. Will be included in the\npayload verbatim. May be used to track mutations by the client.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "profileId", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "taskId", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "AssignUserToTaskPayload", + "description": "The output of our `assignUserToTask` mutation.", + "isOneOf": null, + "fields": [ + { + "name": "clientMutationId", + "description": "The exact same `clientMutationId` that was provided in the mutation input,\nunchanged and unused. May be used by a client to track mutations.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "profile", + "description": "Reads a single `Profile` that is related to this `WorkOnTask`.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Profile", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "query", + "description": "Our root query field type. Allows us to run any query from our mutation payload.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Query", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "task", + "description": "Reads a single `Task` that is related to this `WorkOnTask`.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Task", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "workOnTask", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "WorkOnTask", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "workOnTaskEdge", + "description": "An edge for our `WorkOnTask`. May be used by Relay 1.", + "args": [ + { + "name": "orderBy", + "description": "The method to use when ordering `WorkOnTask`.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "WorkOnTasksOrderBy", + "ofType": null + } + } + }, + "defaultValue": "[PRIMARY_KEY_ASC]", + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "WorkOnTasksEdge", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "AssignedTag", @@ -6044,6 +6197,35 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "assignUserToTask", + "description": null, + "args": [ + { + "name": "input", + "description": "The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "AssignUserToTaskInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "AssignUserToTaskPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "cancelWorkingOn", "description": null, @@ -6964,6 +7146,35 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "unassignUserFromTask", + "description": null, + "args": [ + { + "name": "input", + "description": "The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "UnassignUserFromTaskInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "UnassignUserFromTaskPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "updateCtf", "description": "Updates a single `Ctf` using a unique key and a patch.", @@ -15630,6 +15841,159 @@ ], "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "UnassignUserFromTaskInput", + "description": "All input for the `unassignUserFromTask` mutation.", + "isOneOf": false, + "fields": null, + "inputFields": [ + { + "name": "clientMutationId", + "description": "An arbitrary string value with no semantic meaning. Will be included in the\npayload verbatim. May be used to track mutations by the client.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "profileId", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "taskId", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "UnassignUserFromTaskPayload", + "description": "The output of our `unassignUserFromTask` mutation.", + "isOneOf": null, + "fields": [ + { + "name": "clientMutationId", + "description": "The exact same `clientMutationId` that was provided in the mutation input,\nunchanged and unused. May be used by a client to track mutations.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "profile", + "description": "Reads a single `Profile` that is related to this `WorkOnTask`.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Profile", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "query", + "description": "Our root query field type. Allows us to run any query from our mutation payload.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Query", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "task", + "description": "Reads a single `Task` that is related to this `WorkOnTask`.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Task", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "workOnTask", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "WorkOnTask", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "workOnTaskEdge", + "description": "An edge for our `WorkOnTask`. May be used by Relay 1.", + "args": [ + { + "name": "orderBy", + "description": "The method to use when ordering `WorkOnTask`.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "WorkOnTasksOrderBy", + "ofType": null + } + } + }, + "defaultValue": "[PRIMARY_KEY_ASC]", + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "WorkOnTasksEdge", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "UpdateCtfByNodeIdInput", diff --git a/front/src/components/Task/TaskAssignDialog.vue b/front/src/components/Task/TaskAssignDialog.vue new file mode 100644 index 000000000..acbc0f59d --- /dev/null +++ b/front/src/components/Task/TaskAssignDialog.vue @@ -0,0 +1,158 @@ + + + diff --git a/front/src/components/Task/TaskMenu.vue b/front/src/components/Task/TaskMenu.vue index 53fbfdf63..0aa9f2a6e 100644 --- a/front/src/components/Task/TaskMenu.vue +++ b/front/src/components/Task/TaskMenu.vue @@ -20,6 +20,19 @@ Solved + + + + + + Assign + + @@ -43,7 +56,7 @@ diff --git a/front/src/ctfnote/profiles.ts b/front/src/ctfnote/profiles.ts index e2a1cec8f..0893d883c 100644 --- a/front/src/ctfnote/profiles.ts +++ b/front/src/ctfnote/profiles.ts @@ -63,7 +63,7 @@ export function buildProfile(p: ProfileFragment): Profile { const TeamSymbol: InjectionKey> = Symbol('team'); -export function provideTeam() { +export function provideTeam(): Ref { const { result: team } = getTeam(); provide(TeamSymbol, team); return team; diff --git a/front/src/ctfnote/tasks.ts b/front/src/ctfnote/tasks.ts index 8c945105a..42f8e6b73 100644 --- a/front/src/ctfnote/tasks.ts +++ b/front/src/ctfnote/tasks.ts @@ -8,6 +8,8 @@ import { useStartWorkingOnMutation, useStopWorkingOnMutation, useUpdateTaskMutation, + useAssignUserToTaskMutation, + useUnassignUserFromTaskMutation, } from 'src/generated/graphql'; import { Ctf, Id, Task, WorkingOn, makeId } from './models'; @@ -15,6 +17,7 @@ import { Dialog } from 'quasar'; import TaskEditDialogVue from '../components/Dialogs/TaskEditDialog.vue'; import TaskSolveDialogVue from '../components/Dialogs/TaskSolveDialog.vue'; import { ref, computed } from 'vue'; +import TaskAssignDialog from 'src/components/Task/TaskAssignDialog.vue'; export function buildWorkingOn(w: WorkingOnFragment): WorkingOn { return { @@ -57,6 +60,19 @@ export function useCancelWorkingOn() { return (task: Task) => doCancelWorking({ taskId: task.id }); } +export function useAssignUserToTask() { + const { mutate: assignUserToTaskMutation } = useAssignUserToTaskMutation({}); + return (profileId: number, taskId: number) => + assignUserToTaskMutation({ profileId, taskId }); +} + +export function useUnassignUserFromTask() { + const { mutate: unassignUserFromTaskMutation } = + useUnassignUserFromTaskMutation({}); + return (profileId: number, taskId: number) => + unassignUserFromTaskMutation({ profileId, taskId }); +} + export function useSolveTaskPopup() { // Used to force opening at most one dialog at a time const openedSolveTaskPopup = ref(false); @@ -116,3 +132,14 @@ export function useEditTaskPopup() { }); }; } + +export function useAssignTaskPopup() { + return (task: Task) => { + Dialog.create({ + component: TaskAssignDialog, + componentProps: { + task, + }, + }); + }; +} diff --git a/front/src/generated/graphql.ts b/front/src/generated/graphql.ts index ab4eda385..ecd1b0406 100644 --- a/front/src/generated/graphql.ts +++ b/front/src/generated/graphql.ts @@ -46,6 +46,42 @@ export type AddTagsForTaskPayload = { query?: Maybe; }; +/** All input for the `assignUserToTask` mutation. */ +export type AssignUserToTaskInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: InputMaybe; + profileId?: InputMaybe; + taskId?: InputMaybe; +}; + +/** The output of our `assignUserToTask` mutation. */ +export type AssignUserToTaskPayload = { + __typename?: 'AssignUserToTaskPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + /** Reads a single `Profile` that is related to this `WorkOnTask`. */ + profile?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; + /** Reads a single `Task` that is related to this `WorkOnTask`. */ + task?: Maybe; + workOnTask?: Maybe; + /** An edge for our `WorkOnTask`. May be used by Relay 1. */ + workOnTaskEdge?: Maybe; +}; + + +/** The output of our `assignUserToTask` mutation. */ +export type AssignUserToTaskPayloadWorkOnTaskEdgeArgs = { + orderBy?: InputMaybe>; +}; + export type AssignedTag = Node & { __typename?: 'AssignedTag'; /** A globally unique identifier. Can be used in various places throughout the system to identify this single value. */ @@ -1156,6 +1192,7 @@ export type LoginPayload = { export type Mutation = { __typename?: 'Mutation'; addTagsForTask?: Maybe; + assignUserToTask?: Maybe; cancelWorkingOn?: Maybe; changePassword?: Maybe; /** Creates a single `AssignedTag`. */ @@ -1203,6 +1240,7 @@ export type Mutation = { setDiscordEventLink?: Maybe; startWorkingOn?: Maybe; stopWorkingOn?: Maybe; + unassignUserFromTask?: Maybe; /** Updates a single `Ctf` using a unique key and a patch. */ updateCtf?: Maybe; /** Updates a single `Ctf` using its globally unique id and a patch. */ @@ -1241,6 +1279,12 @@ export type MutationAddTagsForTaskArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationAssignUserToTaskArgs = { + input: AssignUserToTaskInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationCancelWorkingOnArgs = { input: CancelWorkingOnInput; @@ -1433,6 +1477,12 @@ export type MutationStopWorkingOnArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationUnassignUserFromTaskArgs = { + input: UnassignUserFromTaskInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationUpdateCtfArgs = { input: UpdateCtfInput; @@ -2872,6 +2922,42 @@ export enum TasksOrderBy { TitleDesc = 'TITLE_DESC' } +/** All input for the `unassignUserFromTask` mutation. */ +export type UnassignUserFromTaskInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: InputMaybe; + profileId?: InputMaybe; + taskId?: InputMaybe; +}; + +/** The output of our `unassignUserFromTask` mutation. */ +export type UnassignUserFromTaskPayload = { + __typename?: 'UnassignUserFromTaskPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + /** Reads a single `Profile` that is related to this `WorkOnTask`. */ + profile?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; + /** Reads a single `Task` that is related to this `WorkOnTask`. */ + task?: Maybe; + workOnTask?: Maybe; + /** An edge for our `WorkOnTask`. May be used by Relay 1. */ + workOnTaskEdge?: Maybe; +}; + + +/** The output of our `unassignUserFromTask` mutation. */ +export type UnassignUserFromTaskPayloadWorkOnTaskEdgeArgs = { + orderBy?: InputMaybe>; +}; + /** All input for the `updateCtfByNodeId` mutation. */ export type UpdateCtfByNodeIdInput = { /** @@ -3384,6 +3470,22 @@ export type UpdateRoleForUserIdMutationVariables = Exact<{ export type UpdateRoleForUserIdMutation = { __typename?: 'Mutation', updateUserRole?: { __typename?: 'UpdateUserRolePayload', role?: Role | null } | null }; +export type AssignUserToTaskMutationVariables = Exact<{ + profileId: Scalars['Int']['input']; + taskId: Scalars['Int']['input']; +}>; + + +export type AssignUserToTaskMutation = { __typename?: 'Mutation', assignUserToTask?: { __typename?: 'AssignUserToTaskPayload', workOnTask?: { __typename?: 'WorkOnTask', nodeId: string, profileId: number, active: boolean, taskId: number } | null } | null }; + +export type UnassignUserFromTaskMutationVariables = Exact<{ + profileId: Scalars['Int']['input']; + taskId: Scalars['Int']['input']; +}>; + + +export type UnassignUserFromTaskMutation = { __typename?: 'Mutation', unassignUserFromTask?: { __typename?: 'UnassignUserFromTaskPayload', workOnTask?: { __typename?: 'WorkOnTask', nodeId: string, profileId: number, active: boolean, taskId: number } | null } | null }; + export type MeQueryVariables = Exact<{ [key: string]: never; }>; @@ -4136,6 +4238,70 @@ export function useUpdateRoleForUserIdMutation(options: VueApolloComposable.UseM return VueApolloComposable.useMutation(UpdateRoleForUserIdDocument, options); } export type UpdateRoleForUserIdMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn; +export const AssignUserToTaskDocument = gql` + mutation assignUserToTask($profileId: Int!, $taskId: Int!) { + assignUserToTask(input: {profileId: $profileId, taskId: $taskId}) { + workOnTask { + ...WorkingOnFragment + } + } +} + ${WorkingOnFragmentDoc}`; + +/** + * __useAssignUserToTaskMutation__ + * + * To run a mutation, you first call `useAssignUserToTaskMutation` within a Vue component and pass it any options that fit your needs. + * When your component renders, `useAssignUserToTaskMutation` returns an object that includes: + * - A mutate function that you can call at any time to execute the mutation + * - Several other properties: https://v4.apollo.vuejs.org/api/use-mutation.html#return + * + * @param options that will be passed into the mutation, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/mutation.html#options; + * + * @example + * const { mutate, loading, error, onDone } = useAssignUserToTaskMutation({ + * variables: { + * profileId: // value for 'profileId' + * taskId: // value for 'taskId' + * }, + * }); + */ +export function useAssignUserToTaskMutation(options: VueApolloComposable.UseMutationOptions | ReactiveFunction> = {}) { + return VueApolloComposable.useMutation(AssignUserToTaskDocument, options); +} +export type AssignUserToTaskMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn; +export const UnassignUserFromTaskDocument = gql` + mutation unassignUserFromTask($profileId: Int!, $taskId: Int!) { + unassignUserFromTask(input: {profileId: $profileId, taskId: $taskId}) { + workOnTask { + ...WorkingOnFragment + } + } +} + ${WorkingOnFragmentDoc}`; + +/** + * __useUnassignUserFromTaskMutation__ + * + * To run a mutation, you first call `useUnassignUserFromTaskMutation` within a Vue component and pass it any options that fit your needs. + * When your component renders, `useUnassignUserFromTaskMutation` returns an object that includes: + * - A mutate function that you can call at any time to execute the mutation + * - Several other properties: https://v4.apollo.vuejs.org/api/use-mutation.html#return + * + * @param options that will be passed into the mutation, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/mutation.html#options; + * + * @example + * const { mutate, loading, error, onDone } = useUnassignUserFromTaskMutation({ + * variables: { + * profileId: // value for 'profileId' + * taskId: // value for 'taskId' + * }, + * }); + */ +export function useUnassignUserFromTaskMutation(options: VueApolloComposable.UseMutationOptions | ReactiveFunction> = {}) { + return VueApolloComposable.useMutation(UnassignUserFromTaskDocument, options); +} +export type UnassignUserFromTaskMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn; export const MeDocument = gql` query Me { me { @@ -6200,6 +6366,24 @@ export const UpdateRoleForUserId = gql` } } `; +export const AssignUserToTask = gql` + mutation assignUserToTask($profileId: Int!, $taskId: Int!) { + assignUserToTask(input: {profileId: $profileId, taskId: $taskId}) { + workOnTask { + ...WorkingOnFragment + } + } +} + ${WorkingOnFragment}`; +export const UnassignUserFromTask = gql` + mutation unassignUserFromTask($profileId: Int!, $taskId: Int!) { + unassignUserFromTask(input: {profileId: $profileId, taskId: $taskId}) { + workOnTask { + ...WorkingOnFragment + } + } +} + ${WorkingOnFragment}`; export const Me = gql` query Me { me { diff --git a/front/src/graphql/Assign.graphql b/front/src/graphql/Assign.graphql new file mode 100644 index 000000000..9ee871fcf --- /dev/null +++ b/front/src/graphql/Assign.graphql @@ -0,0 +1,17 @@ +# GraphQL mutations for assigning/unassigning users to tasks + +mutation assignUserToTask($profileId: Int!, $taskId: Int!) { + assignUserToTask(input: { profileId: $profileId, taskId: $taskId }) { + workOnTask { + ...WorkingOnFragment + } + } +} + +mutation unassignUserFromTask($profileId: Int!, $taskId: Int!) { + unassignUserFromTask(input: { profileId: $profileId, taskId: $taskId }) { + workOnTask { + ...WorkingOnFragment + } + } +}