feat: default pipeline provisioning at workspace creation (#728)
* feat: default pipeline and pipeline stage on workspace creation * Create seed data files, typo * Naming --------- Co-authored-by: Emilien <emilien.chauvet.enpc@gmail.com>
This commit is contained in:
33
server/src/core/pipeline/seed-data/pipeline-stages.json
Normal file
33
server/src/core/pipeline/seed-data/pipeline-stages.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "New",
|
||||||
|
"color": "#B76796",
|
||||||
|
"index": 0,
|
||||||
|
"type": "open"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Screening",
|
||||||
|
"color": "#CB912F",
|
||||||
|
"index": 1,
|
||||||
|
"type": "ongoing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Meeting",
|
||||||
|
"color": "#9065B0",
|
||||||
|
"index": 2,
|
||||||
|
"type": "ongoing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Proposal",
|
||||||
|
"color": "#337EA9",
|
||||||
|
"index": 3,
|
||||||
|
"type": "ongoing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Customer",
|
||||||
|
"color": "#079039",
|
||||||
|
"index": 4,
|
||||||
|
"type": "won"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
6
server/src/core/pipeline/seed-data/sales-pipeline.json
Normal file
6
server/src/core/pipeline/seed-data/sales-pipeline.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "Sales pipeline",
|
||||||
|
"icon": "💰",
|
||||||
|
"pipelineProgressableType": "Company"
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PrismaService } from 'src/database/prisma.service';
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
|
import seedPipelineStages from '../seed-data/pipeline-stages.json';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PipelineStageService {
|
export class PipelineStageService {
|
||||||
@ -35,4 +36,22 @@ export class PipelineStageService {
|
|||||||
|
|
||||||
// GroupBy
|
// GroupBy
|
||||||
groupBy = this.prismaService.pipelineStage.groupBy;
|
groupBy = this.prismaService.pipelineStage.groupBy;
|
||||||
|
|
||||||
|
// Customs
|
||||||
|
async createDefaultPipelineStages({
|
||||||
|
workspaceId,
|
||||||
|
pipelineId,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
pipelineId: string;
|
||||||
|
}) {
|
||||||
|
const pipelineStages = seedPipelineStages.map((pipelineStage) => ({
|
||||||
|
...pipelineStage,
|
||||||
|
workspaceId,
|
||||||
|
pipelineId,
|
||||||
|
}));
|
||||||
|
return this.createMany({
|
||||||
|
data: pipelineStages,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PrismaService } from 'src/database/prisma.service';
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
|
import seedSalesPipeline from '../seed-data/sales-pipeline.json';
|
||||||
|
import { PipelineProgressableType } from '@prisma/client';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PipelineService {
|
export class PipelineService {
|
||||||
@ -35,4 +37,18 @@ export class PipelineService {
|
|||||||
|
|
||||||
// GroupBy
|
// GroupBy
|
||||||
groupBy = this.prismaService.pipeline.groupBy;
|
groupBy = this.prismaService.pipeline.groupBy;
|
||||||
|
|
||||||
|
// Customs
|
||||||
|
async createDefaultPipeline({ workspaceId }: { workspaceId: string }) {
|
||||||
|
const pipeline = {
|
||||||
|
...seedSalesPipeline,
|
||||||
|
pipelineProgressableType:
|
||||||
|
seedSalesPipeline.pipelineProgressableType as PipelineProgressableType,
|
||||||
|
workspaceId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.create({
|
||||||
|
data: pipeline,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
|
|||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
import { UserResolver } from './user.resolver';
|
import { UserResolver } from './user.resolver';
|
||||||
import { FileModule } from '../file/file.module';
|
import { FileModule } from '../file/file.module';
|
||||||
|
import { WorkspaceModule } from '../workspace/workspace.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [FileModule],
|
imports: [FileModule, WorkspaceModule],
|
||||||
providers: [UserService, UserResolver],
|
providers: [UserService, UserResolver],
|
||||||
exports: [UserService],
|
exports: [UserService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
import { PrismaService } from 'src/database/prisma.service';
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||||
|
import { WorkspaceService } from '../workspace/services/workspace.service';
|
||||||
|
|
||||||
describe('UserService', () => {
|
describe('UserService', () => {
|
||||||
let service: UserService;
|
let service: UserService;
|
||||||
@ -14,6 +15,10 @@ describe('UserService', () => {
|
|||||||
provide: PrismaService,
|
provide: PrismaService,
|
||||||
useValue: prismaMock,
|
useValue: prismaMock,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: WorkspaceService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { BadRequestException, Injectable } from '@nestjs/common';
|
|||||||
import { PrismaService } from 'src/database/prisma.service';
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import { assert } from 'src/utils/assert';
|
import { assert } from 'src/utils/assert';
|
||||||
|
import { WorkspaceService } from '../workspace/services/workspace.service';
|
||||||
|
|
||||||
export type UserPayload = {
|
export type UserPayload = {
|
||||||
displayName: string | undefined | null;
|
displayName: string | undefined | null;
|
||||||
@ -10,7 +11,10 @@ export type UserPayload = {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
constructor(private readonly prismaService: PrismaService) {}
|
constructor(
|
||||||
|
private readonly prismaService: PrismaService,
|
||||||
|
private readonly workspaceService: WorkspaceService,
|
||||||
|
) {}
|
||||||
|
|
||||||
// Find
|
// Find
|
||||||
findFirst = this.prismaService.user.findFirst;
|
findFirst = this.prismaService.user.findFirst;
|
||||||
@ -50,6 +54,18 @@ export class UserService {
|
|||||||
): Promise<Prisma.UserGetPayload<T>> {
|
): Promise<Prisma.UserGetPayload<T>> {
|
||||||
assert(args.data.email, 'email is missing', BadRequestException);
|
assert(args.data.email, 'email is missing', BadRequestException);
|
||||||
|
|
||||||
|
// Create workspace if not exists
|
||||||
|
const workspace = workspaceId
|
||||||
|
? await this.workspaceService.findUnique({
|
||||||
|
where: {
|
||||||
|
id: workspaceId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: await this.workspaceService.createDefaultWorkspace();
|
||||||
|
|
||||||
|
assert(workspace, 'workspace is missing', BadRequestException);
|
||||||
|
|
||||||
|
// Create user
|
||||||
const user = await this.prismaService.user.upsert({
|
const user = await this.prismaService.user.upsert({
|
||||||
where: {
|
where: {
|
||||||
email: args.data.email,
|
email: args.data.email,
|
||||||
@ -57,22 +73,13 @@ export class UserService {
|
|||||||
create: {
|
create: {
|
||||||
...(args.data as Prisma.UserCreateInput),
|
...(args.data as Prisma.UserCreateInput),
|
||||||
|
|
||||||
workspaceMember: workspaceId
|
workspaceMember: {
|
||||||
? {
|
create: {
|
||||||
create: {
|
workspace: {
|
||||||
workspace: {
|
connect: { id: workspace.id },
|
||||||
connect: { id: workspaceId },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: // Assign the user to a new workspace by default
|
|
||||||
{
|
|
||||||
create: {
|
|
||||||
workspace: {
|
|
||||||
create: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
},
|
},
|
||||||
update: {},
|
update: {},
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { WorkspaceService } from './workspace.service';
|
import { WorkspaceService } from './workspace.service';
|
||||||
import { PrismaService } from 'src/database/prisma.service';
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||||
|
import { PipelineService } from 'src/core/pipeline/services/pipeline.service';
|
||||||
|
import { PipelineStageService } from 'src/core/pipeline/services/pipeline-stage.service';
|
||||||
|
|
||||||
describe('WorkspaceService', () => {
|
describe('WorkspaceService', () => {
|
||||||
let service: WorkspaceService;
|
let service: WorkspaceService;
|
||||||
@ -14,6 +16,14 @@ describe('WorkspaceService', () => {
|
|||||||
provide: PrismaService,
|
provide: PrismaService,
|
||||||
useValue: prismaMock,
|
useValue: prismaMock,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: PipelineService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PipelineStageService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,15 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { PipelineStageService } from 'src/core/pipeline/services/pipeline-stage.service';
|
||||||
|
import { PipelineService } from 'src/core/pipeline/services/pipeline.service';
|
||||||
import { PrismaService } from 'src/database/prisma.service';
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceService {
|
export class WorkspaceService {
|
||||||
constructor(private readonly prismaService: PrismaService) {}
|
constructor(
|
||||||
|
private readonly prismaService: PrismaService,
|
||||||
|
private readonly pipelineService: PipelineService,
|
||||||
|
private readonly pipelineStageService: PipelineStageService,
|
||||||
|
) {}
|
||||||
|
|
||||||
// Find
|
// Find
|
||||||
findFirst = this.prismaService.workspace.findFirst;
|
findFirst = this.prismaService.workspace.findFirst;
|
||||||
@ -35,4 +41,22 @@ export class WorkspaceService {
|
|||||||
|
|
||||||
// GroupBy
|
// GroupBy
|
||||||
groupBy = this.prismaService.workspace.groupBy;
|
groupBy = this.prismaService.workspace.groupBy;
|
||||||
|
|
||||||
|
// Customs
|
||||||
|
async createDefaultWorkspace() {
|
||||||
|
const workspace = await this.create({ data: {} });
|
||||||
|
|
||||||
|
// Create default pipeline
|
||||||
|
const pipeline = await this.pipelineService.createDefaultPipeline({
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create default stages
|
||||||
|
await this.pipelineStageService.createDefaultPipelineStages({
|
||||||
|
pipelineId: pipeline.id,
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return workspace;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,10 @@ import { WorkspaceMemberService } from './services/workspace-member.service';
|
|||||||
import { WorkspaceMemberResolver } from './resolvers/workspace-member.resolver';
|
import { WorkspaceMemberResolver } from './resolvers/workspace-member.resolver';
|
||||||
import { WorkspaceResolver } from './resolvers/workspace.resolver';
|
import { WorkspaceResolver } from './resolvers/workspace.resolver';
|
||||||
import { FileUploadService } from '../file/services/file-upload.service';
|
import { FileUploadService } from '../file/services/file-upload.service';
|
||||||
|
import { PipelineModule } from '../pipeline/pipeline.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [PipelineModule],
|
||||||
providers: [
|
providers: [
|
||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
FileUploadService,
|
FileUploadService,
|
||||||
|
|||||||
Reference in New Issue
Block a user