Skip to content
This repository was archived by the owner on Nov 21, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4c50a7a
add stage copy & move mutation, methods
Buyantogtokh Dec 24, 2019
83c909a
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
Buyantogtokh Dec 24, 2019
963d47a
improve stage copy & move methods with tests
Buyantogtokh Dec 24, 2019
d3ee8f6
update copyStage mutation
Buyantogtokh Dec 28, 2019
4dcdd49
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
Buyantogtokh Dec 28, 2019
a6423ff
merge with develop
Buyantogtokh Jan 6, 2020
48fbfd2
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
Buyantogtokh Jan 7, 2020
2de2a66
update board item clone method
Buyantogtokh Jan 7, 2020
3812992
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
Buyantogtokh Jan 7, 2020
c3a8a0e
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
Buyantogtokh Jan 9, 2020
7486905
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
Buyantogtokh Jan 17, 2020
5d0b37c
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
munkhjin0223 Jan 28, 2020
ffb2dd8
merge with develop
Buyantogtokh Mar 3, 2020
907b212
Merge branch 'enhancement/copy-move-stages' of github.com:erxes/erxes…
Buyantogtokh Mar 3, 2020
72549b0
Merge branch 'develop' of https://github.com/erxes/erxes-api into enh…
batchimeg-a Mar 9, 2020
099eec6
Merge branch 'develop' of https://github.com/erxes/erxes-api into enh…
batchimeg-a Mar 12, 2020
12856cd
remove stage move log writing
Buyantogtokh Mar 13, 2020
3287eb5
copy card & stage status field
Buyantogtokh Mar 13, 2020
b74d23a
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
Buyantogtokh Mar 13, 2020
1f3a182
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
Buyantogtokh Mar 16, 2020
c5e9709
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
Buyantogtokh Mar 26, 2020
310ef6b
Merge branch 'develop' of https://github.com/erxes/erxes-api into enh…
Mar 26, 2020
5e5edbd
Merge branch 'enhancement/copy-move-stages' of github.com:erxes/erxes…
Buyantogtokh Mar 26, 2020
e3cf897
check permission in stage copy, move action
Buyantogtokh Mar 26, 2020
d374bdd
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
Buyantogtokh Mar 27, 2020
cdf5085
add visibility filter to pipelines query
Buyantogtokh Mar 27, 2020
7e67bc8
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
Buyantogtokh Mar 27, 2020
39cb29c
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
Buyantogtokh Mar 28, 2020
2d44633
verify stage type when copying or moving
Buyantogtokh Mar 31, 2020
2c2076d
Merge branch 'develop' of github.com:erxes/erxes-api into enhancement…
Buyantogtokh Mar 31, 2020
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
57 changes: 56 additions & 1 deletion src/__tests__/boardDb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
stageFactory,
userFactory,
} from '../db/factories';
import { Boards, Forms, Pipelines, Stages } from '../db/models';
import { Boards, Deals, Forms, Pipelines, Stages } from '../db/models';
import { IBoardDocument, IPipelineDocument, IStageDocument } from '../db/models/definitions/boards';
import { IUserDocument } from '../db/models/definitions/users';

Expand All @@ -34,6 +34,7 @@ describe('Test board model', () => {
await Pipelines.deleteMany({});
await Stages.deleteMany({});
await Pipelines.deleteMany({});
await Deals.deleteMany({});
});

test('Get board', async () => {
Expand Down Expand Up @@ -375,4 +376,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);
});
});
65 changes: 65 additions & 0 deletions src/__tests__/boardMutations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ describe('Test boards mutations', () => {
memberIds: $memberIds
`;

const stageCopyMoveParamDefs = `$_id: String!, $pipelineId: String!, $includeCards: Boolean`;
const stageCopyMoveParams = `_id: $_id, pipelineId: $pipelineId, includeCards: $includeCards`;

beforeEach(async () => {
// Creating test data
board = await boardFactory();
Expand Down Expand Up @@ -319,6 +322,68 @@ describe('Test boards mutations', () => {
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`);
});

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) {
Expand Down
12 changes: 12 additions & 0 deletions src/data/resolvers/boardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
};
37 changes: 36 additions & 1 deletion src/data/resolvers/mutations/boards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,6 +16,12 @@ interface IPipelinesEdit extends IPipelinesAdd {
_id: string;
}

interface IStagesCopyMove {
_id: string;
pipelineId: string;
includeCards: boolean;
}

interface IStageEdit extends IStage {
_id: string;
}
Expand Down Expand Up @@ -161,6 +167,35 @@ const boardMutations = {
return Stages.updateOrder(orders);
},

async stagesMove(_root, { _id, includeCards, pipelineId }: IStagesCopyMove, { user }: IContext) {
const stage = await Stages.getStage(_id);

await checkPermission(stage.type, user, 'stagesEdit');

await verifyPipelineType(pipelineId, stage.type);

return Stages.moveStage({
includeCards,
stageId: _id,
pipelineId,
userId: user._id,
});
},

async stagesCopy(_root, { _id, includeCards, pipelineId }: IStagesCopyMove, { user }: IContext) {
const stage = await Stages.getStage(_id);

await checkPermission(stage.type, user, 'stagesEdit');

await verifyPipelineType(pipelineId, stage.type);

return Stages.copyStage({
stageId: _id,
pipelineId,
includeCards,
userId: user._id,
});
},
/**
* Edit stage
*/
Expand Down
9 changes: 8 additions & 1 deletion src/data/resolvers/queries/boards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/data/schema/board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ const pipelineParams = `
excludeCheckUserIds: [String],
`;

const copyMoveParams = `_id: String!, pipelineId: String!, includeCards: Boolean`;

export const mutations = `
boardsAdd(${commonParams}): Board
boardsEdit(_id: String!, ${commonParams}): Board
Expand All @@ -133,5 +135,7 @@ export const mutations = `

stagesUpdateOrder(orders: [OrderItem]): [Stage]
stagesRemove(_id: String!): JSON
stagesCopy(${copyMoveParams}): Stage
stagesMove(${copyMoveParams}): Stage
stagesEdit(_id: String!, type: String, name: String, status: String): Stage
`;
83 changes: 83 additions & 0 deletions src/db/models/Boards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ import {
boardSchema,
IBoard,
IBoardDocument,
ICopyMoveParams,
IPipeline,
IPipelineDocument,
IStage,
IStageDocument,
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';
import { ITicketDocument } from './definitions/tickets';
import { getDuplicatedStages } from './PipelineTemplates';

export interface IOrderInput {
Expand Down Expand Up @@ -264,12 +270,18 @@ export const loadPipelineClass = () => {
return pipelineSchema;
};

type Cards = IDealDocument[] | ITaskDocument[] | ITicketDocument[] | IGrowthHackDocument[];

export interface IStageModel extends Model<IStageDocument> {
getStage(_id: string): Promise<IStageDocument>;
createStage(doc: IStage): Promise<IStageDocument>;
removeStage(_id: string): object;
updateStage(_id: string, doc: IStage): Promise<IStageDocument>;
updateOrder(orders: IOrderInput[]): Promise<IStageDocument[]>;
getCards(_id: string): Promise<Cards>;
cloneCards(_id: string, destStageId: string, userId: string): Promise<Cards>;
copyStage(params: ICopyMoveParams): Promise<IStageDocument>;
moveStage(params: ICopyMoveParams): Promise<IStageDocument>;
}

export const loadStageClass = () => {
Expand Down Expand Up @@ -310,6 +322,77 @@ export const loadStageClass = () => {
return updateOrder(Stages, orders);
}

public static async getCards(_id: string) {
const stage: IStageDocument = await Stages.getStage(_id);

const collection = getCollection(stage.type);

return collection.find({ stageId: stage._id }).lean();
}

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(),
assignedUserIds: card.assignedUserIds,
watchedUserIds: card.watchedUserIds,
labelIds: card.labelIds,
priority: card.priority,
userId,
description: card.description,
status: card.status,
};

await collection.create(itemDoc);
}

return collection.find({ stageId: destStageId });
}

public static async copyStage(params: ICopyMoveParams) {
const { stageId, pipelineId, includeCards, userId } = params;

const destinationPipeline = await Pipelines.getPipeline(pipelineId);
const stage = await Stages.getStage(stageId);

const copiedStage = await Stages.createStage({
pipelineId: destinationPipeline._id,
createdAt: new Date(),
name: `${stage.name}-copied`,
userId,
type: stage.type,
formId: stage.formId,
probability: stage.probability || PROBABILITY.TEN,
status: stage.status,
});

if (includeCards === true) {
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 });
}

public static async removeStage(_id: string) {
const stage = await Stages.getStage(_id);
const pipeline = await Pipelines.getPipeline(stage.pipelineId);
Expand Down
7 changes: 7 additions & 0 deletions src/db/models/definitions/boards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ export interface IOrderInput {
order: number;
}

export interface ICopyMoveParams {
stageId: string;
pipelineId: string;
includeCards: boolean;
userId: string;
}

export const attachmentSchema = new Schema(
{
name: field({ type: String, label: 'Name' }),
Expand Down