From 4c50a7ae2d7fae162df4c05ad7faea8ebeaa4cea Mon Sep 17 00:00:00 2001 From: Buyantogtokh Date: Tue, 24 Dec 2019 11:30:16 +0800 Subject: [PATCH 1/9] add stage copy & move mutation, methods --- src/data/resolvers/mutations/boards.ts | 40 +++++++++++++++++ src/data/schema/board.ts | 5 +++ src/db/models/Boards.ts | 59 ++++++++++++++++++++++++++ src/db/models/definitions/boards.ts | 7 +++ 4 files changed, 111 insertions(+) diff --git a/src/data/resolvers/mutations/boards.ts b/src/data/resolvers/mutations/boards.ts index 6731a798e..851361a33 100644 --- a/src/data/resolvers/mutations/boards.ts +++ b/src/data/resolvers/mutations/boards.ts @@ -16,6 +16,12 @@ interface IPipelinesEdit extends IPipelinesAdd { _id: string; } +interface IStagesEdit { + _id: string; + pipelineId: string; + includeCards: boolean; +} + const boardMutations = { /** * Create new board @@ -147,6 +153,40 @@ const boardMutations = { stagesUpdateOrder(_root, { orders }: { orders: IOrderInput[] }) { return Stages.updateOrder(orders); }, + + async stagesMove(_root, { _id, includeCards, pipelineId }: IStagesEdit, { user }: IContext) { + const stage: IStageDocument | null = await Stages.findOne({ _id }); + + const movedStage = await Stages.copyOrMove({ + includeCards, + stageId: _id, + pipelineId, + move: true, + }); + + if (stage && movedStage) { + await putUpdateLog( + { + type: `${stage.type}Stage`, + newData: JSON.stringify({ pipelineId }), + description: `${movedStage.name} has been moved`, + object: stage, + }, + user, + ); + } + + return stage; + }, + + stagesCopy(_root, { _id, includeCards, pipelineId }: IStagesEdit) { + return Stages.copyOrMove({ + stageId: _id, + pipelineId, + move: false, + includeCards, + }); + }, }; export default boardMutations; diff --git a/src/data/schema/board.ts b/src/data/schema/board.ts index a0f3f11de..d1c655605 100644 --- a/src/data/schema/board.ts +++ b/src/data/schema/board.ts @@ -107,6 +107,8 @@ const pipelineParams = ` excludeCheckUserIds: [String], `; +const copyMoveParams = `_id: String!, pipelineId: String!, includeCards: Boolean`; + export const mutations = ` boardsAdd(${commonParams}): Board boardsEdit(_id: String!, ${commonParams}): Board @@ -120,4 +122,7 @@ export const mutations = ` stagesUpdateOrder(orders: [OrderItem]): [Stage] stagesRemove(_id: String!): JSON + + stagesCopy(${copyMoveParams}): Stage + stagesMove(${copyMoveParams}): Stage `; diff --git a/src/db/models/Boards.ts b/src/db/models/Boards.ts index a3f134878..caa95ed11 100644 --- a/src/db/models/Boards.ts +++ b/src/db/models/Boards.ts @@ -4,6 +4,7 @@ import { boardSchema, IBoard, IBoardDocument, + ICopyMoveParams, IPipeline, IPipelineDocument, IStage, @@ -11,6 +12,7 @@ import { pipelineSchema, stageSchema, } from './definitions/boards'; +import { Deals, Tasks, Tickets } from './index'; import { getDuplicatedStages } from './PipelineTemplates'; export interface IOrderInput { @@ -256,6 +258,7 @@ export interface IStageModel extends Model { createStage(doc: IStage): Promise; updateStage(_id: string, doc: IStage): Promise; updateOrder(orders: IOrderInput[]): Promise; + copyOrMove(params: ICopyMoveParams): Promise; } export const loadStageClass = () => { @@ -295,6 +298,62 @@ export const loadStageClass = () => { public static async updateOrder(orders: IOrderInput[]) { return updateOrder(Stages, orders); } + + public static async copyOrMove(params: ICopyMoveParams) { + const { stageId, pipelineId, move, includeCards } = params; + + const itemTypes = ['deal', 'task', 'ticket']; + let collection; + + const stage = await Stages.findOne({ _id: stageId }); + + if (!stage) { + throw new Error('Stage not found'); + } + + if (!itemTypes.includes(stage.type)) { + throw new Error('Wrong board item type'); + } + + const pipeline = await Pipelines.findOne({ _id: pipelineId }); + + if (!pipeline) { + throw new Error('Pipeline not found'); + } + + switch (stage.type) { + case 'deal': + collection = Deals; + break; + case 'task': + collection = Tasks; + break; + case 'ticket': + collection = Tickets; + break; + default: + break; + } + + if (move === true) { + await Stages.updateOne({ _id: stageId }, { $set: { pipelineId } }); + } + + const stageDoc = { + ...stage, + pipelineId, + createdAt: new Date(), + name: `${stage.name}-copied`, + }; + + const copiedStage = await Stages.createStage(stageDoc); + + if (includeCards === true) { + await collection.updateMany({ stageId }, { $set: { stageId: copiedStage._id } }); + } + + return copiedStage; + } } stageSchema.loadClass(Stage); diff --git a/src/db/models/definitions/boards.ts b/src/db/models/definitions/boards.ts index 5a1ccea5d..aaa32178c 100644 --- a/src/db/models/definitions/boards.ts +++ b/src/db/models/definitions/boards.ts @@ -85,6 +85,13 @@ export interface IOrderInput { order: number; } +export interface ICopyMoveParams { + stageId: string; + pipelineId: string; + move: boolean; + includeCards: boolean; +} + const attachmentSchema = new Schema( { name: field({ type: String, label: 'Name' }), From 963d47a4397b838d674e24a6d60687a52382e83f Mon Sep 17 00:00:00 2001 From: Buyantogtokh Date: Tue, 24 Dec 2019 16:01:37 +0800 Subject: [PATCH 2/9] improve stage copy & move methods with tests --- src/__tests__/boardDb.test.ts | 57 +++++++++++++- src/__tests__/boardMutations.test.ts | 43 ++++++++++ src/data/resolvers/mutations/boards.ts | 12 +-- src/db/models/Boards.ts | 104 +++++++++++++++---------- src/db/models/definitions/boards.ts | 2 +- 5 files changed, 169 insertions(+), 49 deletions(-) diff --git a/src/__tests__/boardDb.test.ts b/src/__tests__/boardDb.test.ts index 8cafdfe7d..9d21dfa46 100644 --- a/src/__tests__/boardDb.test.ts +++ b/src/__tests__/boardDb.test.ts @@ -7,7 +7,7 @@ import { stageFactory, userFactory, } from '../db/factories'; -import { Boards, Pipelines, Stages } from '../db/models'; +import { Boards, Deals, Pipelines, Stages } from '../db/models'; import { IBoardDocument, IPipelineDocument, IStageDocument } from '../db/models/definitions/boards'; import { IUserDocument } from '../db/models/definitions/users'; @@ -34,6 +34,7 @@ describe('Test board model', () => { await Pipelines.deleteMany({}); await Stages.deleteMany({}); await Pipelines.deleteMany({}); + await Deals.deleteMany({}); }); test('Get board', async () => { @@ -356,4 +357,58 @@ describe('Test board model', () => { expect(updatedStage.order).toBe(5); expect(updatedStageToOrder.order).toBe(9); }); + + test('Test copyStage()', async () => { + const secondPipeline = await pipelineFactory(); + + const params = { + stageId: stage._id, + pipelineId: secondPipeline._id, + includeCards: false, + userId: user._id, + }; + + const copiedStage = await Stages.copyStage(params); + + const { name, pipelineId } = copiedStage; + + expect(name).toBe(`${stage.name}-copied`); + expect(pipelineId).toBe(secondPipeline._id); + }); + + test('Test copyStage() with cards', async () => { + const secondPipeline = await pipelineFactory(); + + await dealFactory({ stageId: stage._id }); + + const params = { + stageId: stage._id, + pipelineId: secondPipeline._id, + includeCards: true, + userId: user._id, + }; + + const copiedStage = await Stages.copyStage(params); + const items = await Stages.getCards(copiedStage._id); + + // above 1 & copied 1 + expect(items.length).toBe(2); + }); + + test('Test moveStage()', async () => { + const secondPipeline = await pipelineFactory(); + + const params = { + stageId: stage._id, + pipelineId: secondPipeline._id, + includeCards: false, + userId: user._id, + }; + + const movedStage = await Stages.moveStage(params); + + const { pipelineId } = movedStage; + + expect(pipelineId).toBe(secondPipeline._id); + }); }); diff --git a/src/__tests__/boardMutations.test.ts b/src/__tests__/boardMutations.test.ts index 8970b55b4..4967dcdfc 100644 --- a/src/__tests__/boardMutations.test.ts +++ b/src/__tests__/boardMutations.test.ts @@ -29,6 +29,9 @@ describe('Test boards mutations', () => { bgColor: $bgColor `; + const stageCopyMoveParamDefs = `$_id: String!, $pipelineId: String!, $includeCards: Boolean`; + const stageCopyMoveParams = `_id: $_id, pipelineId: $pipelineId, includeCards: $includeCards`; + beforeEach(async () => { // Creating test data board = await boardFactory(); @@ -294,4 +297,44 @@ describe('Test boards mutations', () => { expect(updatedStage.order).toBe(3); expect(updatedStageToOrder.order).toBe(9); }); + + test('Test stagesMove()', async () => { + const secondPipeline = await pipelineFactory(); + + const params = { + _id: stage._id, + pipelineId: secondPipeline._id, + includeCards: false, + }; + + const mutation = ` + mutation stagesMove(${stageCopyMoveParamDefs}) { + stagesMove(${stageCopyMoveParams}) { pipelineId } + } + `; + + const result = await graphqlRequest(mutation, 'stagesMove', params, context); + + expect(result.pipelineId).toBe(params.pipelineId); + }); + + test('Test stagesCopy()', async () => { + const secondPipeline = await pipelineFactory(); + + const params = { + _id: stage._id, + pipelineId: secondPipeline._id, + includeCards: false, + }; + + const mutation = ` + mutation stagesCopy(${stageCopyMoveParamDefs}) { + stagesCopy(${stageCopyMoveParams}) { name } + } + `; + + const result = await graphqlRequest(mutation, 'stagesCopy', params, context); + + expect(result.name).toBe(`${stage.name}-copied`); + }); }); diff --git a/src/data/resolvers/mutations/boards.ts b/src/data/resolvers/mutations/boards.ts index 851361a33..d0ff4f0b1 100644 --- a/src/data/resolvers/mutations/boards.ts +++ b/src/data/resolvers/mutations/boards.ts @@ -157,11 +157,11 @@ const boardMutations = { async stagesMove(_root, { _id, includeCards, pipelineId }: IStagesEdit, { user }: IContext) { const stage: IStageDocument | null = await Stages.findOne({ _id }); - const movedStage = await Stages.copyOrMove({ + const movedStage = await Stages.moveStage({ includeCards, stageId: _id, pipelineId, - move: true, + userId: user._id, }); if (stage && movedStage) { @@ -176,15 +176,15 @@ const boardMutations = { ); } - return stage; + return movedStage; }, - stagesCopy(_root, { _id, includeCards, pipelineId }: IStagesEdit) { - return Stages.copyOrMove({ + stagesCopy(_root, { _id, includeCards, pipelineId }: IStagesEdit, { user }: IContext) { + return Stages.copyStage({ stageId: _id, pipelineId, - move: false, includeCards, + userId: user._id, }); }, }; diff --git a/src/db/models/Boards.ts b/src/db/models/Boards.ts index caa95ed11..10314e92e 100644 --- a/src/db/models/Boards.ts +++ b/src/db/models/Boards.ts @@ -12,7 +12,10 @@ import { pipelineSchema, stageSchema, } from './definitions/boards'; -import { Deals, Tasks, Tickets } from './index'; +import { IDealDocument } from './definitions/deals'; +import { IGrowthHackDocument } from './definitions/growthHacks'; +import { ITaskDocument } from './definitions/tasks'; +import { ITicketDocument } from './definitions/tickets'; import { getDuplicatedStages } from './PipelineTemplates'; export interface IOrderInput { @@ -253,12 +256,17 @@ export const loadPipelineClass = () => { return pipelineSchema; }; +type Cards = IDealDocument[] | ITaskDocument[] | ITicketDocument[] | IGrowthHackDocument[]; + export interface IStageModel extends Model { getStage(_id: string): Promise; createStage(doc: IStage): Promise; updateStage(_id: string, doc: IStage): Promise; updateOrder(orders: IOrderInput[]): Promise; - copyOrMove(params: ICopyMoveParams): Promise; + getCards(_id: string): Promise; + cloneCards(_id: string, destStageId: string, userId: string): Promise; + copyStage(params: ICopyMoveParams): Promise; + moveStage(params: ICopyMoveParams): Promise; } export const loadStageClass = () => { @@ -299,61 +307,75 @@ export const loadStageClass = () => { return updateOrder(Stages, orders); } - public static async copyOrMove(params: ICopyMoveParams) { - const { stageId, pipelineId, move, includeCards } = params; - - const itemTypes = ['deal', 'task', 'ticket']; - let collection; + public static async getCards(_id: string) { + const stage: IStageDocument = await Stages.getStage(_id); - const stage = await Stages.findOne({ _id: stageId }); + const collection = getCollection(stage.type); - if (!stage) { - throw new Error('Stage not found'); - } + return collection.find({ stageId: stage._id }).lean(); + } - if (!itemTypes.includes(stage.type)) { - throw new Error('Wrong board item type'); + public static async cloneCards(_id: string, destStageId: string, userId: string) { + const stage = await Stages.getStage(_id); + const cards = await Stages.getCards(stage._id); + const collection = getCollection(stage.type); + + for (const card of cards) { + const itemDoc = { + name: `${card.name}-copied`, + stageId: destStageId, + initialStageId: destStageId, + createdAt: new Date(), + companyIds: card.companyIds, + customerIds: card.customerIds, + assignedUserIds: card.assignedUserIds, + watchedUserIds: card.watchedUserIds, + labelIds: card.labelIds, + priority: card.priority, + userId, + }; + + await collection.create(itemDoc); } - const pipeline = await Pipelines.findOne({ _id: pipelineId }); - - if (!pipeline) { - throw new Error('Pipeline not found'); - } + return collection.find({ stageId: destStageId }); + } - switch (stage.type) { - case 'deal': - collection = Deals; - break; - case 'task': - collection = Tasks; - break; - case 'ticket': - collection = Tickets; - break; - default: - break; - } + public static async copyStage(params: ICopyMoveParams) { + const { stageId, pipelineId, includeCards, userId } = params; - if (move === true) { - await Stages.updateOne({ _id: stageId }, { $set: { pipelineId } }); - } + const destinationPipeline = await Pipelines.getPipeline(pipelineId); + const stage = await Stages.getStage(stageId); - const stageDoc = { - ...stage, - pipelineId, + const copiedStage = await Stages.createStage({ + pipelineId: destinationPipeline._id, createdAt: new Date(), name: `${stage.name}-copied`, - }; - - const copiedStage = await Stages.createStage(stageDoc); + userId, + type: stage.type, + formId: stage.formId, + probability: stage.probability, + }); if (includeCards === true) { - await collection.updateMany({ stageId }, { $set: { stageId: copiedStage._id } }); + await Stages.cloneCards(stage._id, copiedStage._id, userId); } return copiedStage; } + + /** + * Moves a stage to given pipeline + */ + public static async moveStage(params: ICopyMoveParams) { + const { stageId, pipelineId } = params; + + const pipeline = await Pipelines.getPipeline(pipelineId); + + await Stages.updateOne({ _id: stageId }, { $set: { pipelineId: pipeline._id } }); + + return Stages.findOne({ _id: stageId }); + } } stageSchema.loadClass(Stage); diff --git a/src/db/models/definitions/boards.ts b/src/db/models/definitions/boards.ts index aaa32178c..b6d74f5ce 100644 --- a/src/db/models/definitions/boards.ts +++ b/src/db/models/definitions/boards.ts @@ -88,8 +88,8 @@ export interface IOrderInput { export interface ICopyMoveParams { stageId: string; pipelineId: string; - move: boolean; includeCards: boolean; + userId: string; } const attachmentSchema = new Schema( From d3ee8f6bade614ea9f4b7f25f259839eb5d23ce2 Mon Sep 17 00:00:00 2001 From: Buyantogtokh Date: Sat, 28 Dec 2019 12:39:24 +0800 Subject: [PATCH 3/9] update copyStage mutation --- src/db/models/Boards.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/db/models/Boards.ts b/src/db/models/Boards.ts index 10314e92e..be29d2def 100644 --- a/src/db/models/Boards.ts +++ b/src/db/models/Boards.ts @@ -12,6 +12,7 @@ import { pipelineSchema, stageSchema, } from './definitions/boards'; +import { PROBABILITY } from './definitions/constants'; import { IDealDocument } from './definitions/deals'; import { IGrowthHackDocument } from './definitions/growthHacks'; import { ITaskDocument } from './definitions/tasks'; @@ -354,7 +355,7 @@ export const loadStageClass = () => { userId, type: stage.type, formId: stage.formId, - probability: stage.probability, + probability: stage.probability || PROBABILITY.TEN, }); if (includeCards === true) { From 2de2a66e6a80c414202e29a2a607644188af8852 Mon Sep 17 00:00:00 2001 From: Buyantogtokh Date: Tue, 7 Jan 2020 15:05:08 +0800 Subject: [PATCH 4/9] update board item clone method --- src/db/models/Boards.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/db/models/Boards.ts b/src/db/models/Boards.ts index 28bc6a392..b7582c36c 100644 --- a/src/db/models/Boards.ts +++ b/src/db/models/Boards.ts @@ -341,13 +341,12 @@ export const loadStageClass = () => { stageId: destStageId, initialStageId: destStageId, createdAt: new Date(), - companyIds: card.companyIds, - customerIds: card.customerIds, assignedUserIds: card.assignedUserIds, watchedUserIds: card.watchedUserIds, labelIds: card.labelIds, priority: card.priority, userId, + description: card.description, }; await collection.create(itemDoc); From 12856cdef21055235811acec85a3c8379902efa3 Mon Sep 17 00:00:00 2001 From: Buyantogtokh Date: Fri, 13 Mar 2020 16:08:57 +0800 Subject: [PATCH 5/9] remove stage move log writing --- src/data/resolvers/mutations/boards.ts | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/data/resolvers/mutations/boards.ts b/src/data/resolvers/mutations/boards.ts index ce5ae32a3..5a7f30fca 100644 --- a/src/data/resolvers/mutations/boards.ts +++ b/src/data/resolvers/mutations/boards.ts @@ -167,29 +167,13 @@ const boardMutations = { return Stages.updateOrder(orders); }, - async stagesMove(_root, { _id, includeCards, pipelineId }: IStagesCopyMove, { user }: IContext) { - const stage: IStageDocument | null = await Stages.findOne({ _id }); - - const movedStage = await Stages.moveStage({ + stagesMove(_root, { _id, includeCards, pipelineId }: IStagesCopyMove, { user }: IContext) { + return Stages.moveStage({ includeCards, stageId: _id, pipelineId, userId: user._id, }); - - if (stage && movedStage) { - await putUpdateLog( - { - type: `${stage.type}Stage`, - newData: { pipelineId }, - description: `${movedStage.name} has been moved`, - object: stage, - }, - user, - ); - } - - return movedStage; }, stagesCopy(_root, { _id, includeCards, pipelineId }: IStagesCopyMove, { user }: IContext) { From 3287eb57223a2bd2157c1d3f036014de15897732 Mon Sep 17 00:00:00 2001 From: Buyantogtokh Date: Fri, 13 Mar 2020 17:36:07 +0800 Subject: [PATCH 6/9] copy card & stage status field --- src/db/models/Boards.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/db/models/Boards.ts b/src/db/models/Boards.ts index 66fb90c6e..6d58c0f99 100644 --- a/src/db/models/Boards.ts +++ b/src/db/models/Boards.ts @@ -347,6 +347,7 @@ export const loadStageClass = () => { priority: card.priority, userId, description: card.description, + status: card.status, }; await collection.create(itemDoc); @@ -369,6 +370,7 @@ export const loadStageClass = () => { type: stage.type, formId: stage.formId, probability: stage.probability || PROBABILITY.TEN, + status: stage.status, }); if (includeCards === true) { From e3cf897827da44517b0f5c261bb39923db2bf459 Mon Sep 17 00:00:00 2001 From: Buyantogtokh Date: Thu, 26 Mar 2020 18:56:17 +0800 Subject: [PATCH 7/9] check permission in stage copy, move action --- src/data/resolvers/mutations/boards.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/data/resolvers/mutations/boards.ts b/src/data/resolvers/mutations/boards.ts index 5a7f30fca..1097036e8 100644 --- a/src/data/resolvers/mutations/boards.ts +++ b/src/data/resolvers/mutations/boards.ts @@ -167,7 +167,11 @@ const boardMutations = { return Stages.updateOrder(orders); }, - stagesMove(_root, { _id, includeCards, pipelineId }: IStagesCopyMove, { user }: IContext) { + async stagesMove(_root, { _id, includeCards, pipelineId }: IStagesCopyMove, { user }: IContext) { + const stage = await Stages.getStage(_id); + + await checkPermission(stage.type, user, 'stagesEdit'); + return Stages.moveStage({ includeCards, stageId: _id, @@ -176,7 +180,11 @@ const boardMutations = { }); }, - stagesCopy(_root, { _id, includeCards, pipelineId }: IStagesCopyMove, { user }: IContext) { + async stagesCopy(_root, { _id, includeCards, pipelineId }: IStagesCopyMove, { user }: IContext) { + const stage = await Stages.getStage(_id); + + await checkPermission(stage.type, user, 'stagesEdit'); + return Stages.copyStage({ stageId: _id, pipelineId, From cdf50852ffea64d94466fbae2e96915199ffd523 Mon Sep 17 00:00:00 2001 From: Buyantogtokh Date: Fri, 27 Mar 2020 12:00:09 +0800 Subject: [PATCH 8/9] add visibility filter to pipelines query --- src/data/resolvers/queries/boards.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/data/resolvers/queries/boards.ts b/src/data/resolvers/queries/boards.ts index f6b3191ee..c6d9dfe27 100644 --- a/src/data/resolvers/queries/boards.ts +++ b/src/data/resolvers/queries/boards.ts @@ -80,9 +80,16 @@ const boardQueries = { pipelines( _root, { boardId, type, ...queryParams }: { boardId: string; type: string; page: number; perPage: number }, + { user }: IContext, ) { - const query: any = {}; const { page, perPage } = queryParams; + const query: any = { + $or: [{ visibility: 'public' }, { visibility: 'private', $or: [{ memberIds: user._id }, { userId: user._id }] }], + }; + + if (user.isOwner) { + delete query.$or; + } if (boardId) { query.boardId = boardId; From 2d44633fecf933036997a298b628a2bd8ecff504 Mon Sep 17 00:00:00 2001 From: Buyantogtokh Date: Tue, 31 Mar 2020 14:53:45 +0800 Subject: [PATCH 9/9] verify stage type when copying or moving --- src/__tests__/boardMutations.test.ts | 22 ++++++++++++++++++++++ src/data/resolvers/boardUtils.ts | 12 ++++++++++++ src/data/resolvers/mutations/boards.ts | 6 +++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/__tests__/boardMutations.test.ts b/src/__tests__/boardMutations.test.ts index c9779cd6d..8ec943b10 100644 --- a/src/__tests__/boardMutations.test.ts +++ b/src/__tests__/boardMutations.test.ts @@ -362,6 +362,28 @@ describe('Test boards mutations', () => { expect(result.name).toBe(`${stage.name}-copied`); }); + test('Test stagesCopy() with wrong type', async () => { + const secondPipeline = await pipelineFactory({ type: BOARD_TYPES.TASK }); + + const params = { + _id: stage._id, + pipelineId: secondPipeline._id, + includeCards: false, + }; + + const mutation = ` + mutation stagesCopy(${stageCopyMoveParamDefs}) { + stagesCopy(${stageCopyMoveParams}) { name } + } + `; + + try { + await graphqlRequest(mutation, 'stagesCopy', params, context); + } catch (e) { + expect(e[0].message).toBe('Pipeline and stage type does not match'); + } + }); + test('Edit stage', async () => { const mutation = ` mutation stagesEdit($_id: String!, $type: String, $name: String) { diff --git a/src/data/resolvers/boardUtils.ts b/src/data/resolvers/boardUtils.ts index ac1f3a720..9650f8efa 100644 --- a/src/data/resolvers/boardUtils.ts +++ b/src/data/resolvers/boardUtils.ts @@ -372,3 +372,15 @@ export const prepareBoardItemDoc = async (_id: string, type: string, userId: str return doc; }; + +/** + * Used in stage move, copy mutations. + * Target pipeline type must be the same as stage type. + */ +export const verifyPipelineType = async (pipelineId: string, stageType: string) => { + const pipeline = await Pipelines.getPipeline(pipelineId); + + if (pipeline.type !== stageType) { + throw new Error('Pipeline and stage type does not match'); + } +}; diff --git a/src/data/resolvers/mutations/boards.ts b/src/data/resolvers/mutations/boards.ts index 1097036e8..0c04f7916 100644 --- a/src/data/resolvers/mutations/boards.ts +++ b/src/data/resolvers/mutations/boards.ts @@ -2,7 +2,7 @@ import { Boards, Pipelines, Stages } from '../../../db/models'; import { IBoard, IOrderInput, IPipeline, IStage, IStageDocument } from '../../../db/models/definitions/boards'; import { putCreateLog, putDeleteLog, putUpdateLog } from '../../logUtils'; import { IContext } from '../../types'; -import { checkPermission } from '../boardUtils'; +import { checkPermission, verifyPipelineType } from '../boardUtils'; interface IBoardsEdit extends IBoard { _id: string; @@ -172,6 +172,8 @@ const boardMutations = { await checkPermission(stage.type, user, 'stagesEdit'); + await verifyPipelineType(pipelineId, stage.type); + return Stages.moveStage({ includeCards, stageId: _id, @@ -185,6 +187,8 @@ const boardMutations = { await checkPermission(stage.type, user, 'stagesEdit'); + await verifyPipelineType(pipelineId, stage.type); + return Stages.copyStage({ stageId: _id, pipelineId,