From 0f364cc9e72e1d03e5a5b65fd6298cb44414a16e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tha=C3=AFs?= Date: Thu, 10 Aug 2023 18:14:28 +0200 Subject: [PATCH] feat: add views and viewSorts tables (#1131) * feat: add views table Closes #1120 * feat: add viewSorts table Closes #1120 --- front/src/generated/graphql.tsx | 176 ++++++++++++++++++ server/src/ability/ability.factory.ts | 4 + .../view/resolvers/view-field.resolver.ts | 2 +- .../workspace/services/workspace.service.ts | 8 + .../migration.sql | 40 ++++ .../migration.sql | 19 ++ server/src/database/schema.prisma | 55 +++++- .../utils/prisma-select/model-select-map.ts | 2 + 8 files changed, 304 insertions(+), 2 deletions(-) create mode 100644 server/src/database/migrations/20230809143432_add_views_table/migration.sql create mode 100644 server/src/database/migrations/20230809143836_add_view_sorts_table/migration.sql diff --git a/front/src/generated/graphql.tsx b/front/src/generated/graphql.tsx index 82a519fb7b..49a69b1622 100644 --- a/front/src/generated/graphql.tsx +++ b/front/src/generated/graphql.tsx @@ -846,6 +846,20 @@ export type EnumPipelineProgressableTypeFilter = { notIn?: InputMaybe>; }; +export type EnumViewSortDirectionFilter = { + equals?: InputMaybe; + in?: InputMaybe>; + not?: InputMaybe; + notIn?: InputMaybe>; +}; + +export type EnumViewTypeFilter = { + equals?: InputMaybe; + in?: InputMaybe>; + not?: InputMaybe; + notIn?: InputMaybe>; +}; + export enum FileFolder { Attachment = 'Attachment', PersonPicture = 'PersonPicture', @@ -1178,6 +1192,20 @@ export type NestedEnumPipelineProgressableTypeFilter = { notIn?: InputMaybe>; }; +export type NestedEnumViewSortDirectionFilter = { + equals?: InputMaybe; + in?: InputMaybe>; + not?: InputMaybe; + notIn?: InputMaybe>; +}; + +export type NestedEnumViewTypeFilter = { + equals?: InputMaybe; + in?: InputMaybe>; + not?: InputMaybe; + notIn?: InputMaybe>; +}; + export type NestedIntFilter = { equals?: InputMaybe; gt?: InputMaybe; @@ -2161,6 +2189,20 @@ export type Verify = { user: User; }; +export type View = { + __typename?: 'View'; + fields?: Maybe>; + id: Scalars['ID']; + name: Scalars['String']; + objectId: Scalars['String']; + sorts?: Maybe>; + type: ViewType; +}; + +export type ViewCreateNestedOneWithoutFieldsInput = { + connect?: InputMaybe; +}; + export type ViewField = { __typename?: 'ViewField'; fieldName: Scalars['String']; @@ -2169,6 +2211,8 @@ export type ViewField = { isVisible: Scalars['Boolean']; objectName: Scalars['String']; sizeInPx: Scalars['Int']; + view?: Maybe; + viewId?: Maybe; }; export type ViewFieldCreateInput = { @@ -2178,6 +2222,7 @@ export type ViewFieldCreateInput = { isVisible: Scalars['Boolean']; objectName: Scalars['String']; sizeInPx: Scalars['Int']; + view?: InputMaybe; }; export type ViewFieldCreateManyInput = { @@ -2187,6 +2232,17 @@ export type ViewFieldCreateManyInput = { isVisible: Scalars['Boolean']; objectName: Scalars['String']; sizeInPx: Scalars['Int']; + viewId?: InputMaybe; +}; + +export type ViewFieldListRelationFilter = { + every?: InputMaybe; + none?: InputMaybe; + some?: InputMaybe; +}; + +export type ViewFieldOrderByRelationAggregateInput = { + _count?: InputMaybe; }; export type ViewFieldOrderByWithRelationInput = { @@ -2196,6 +2252,8 @@ export type ViewFieldOrderByWithRelationInput = { isVisible?: InputMaybe; objectName?: InputMaybe; sizeInPx?: InputMaybe; + view?: InputMaybe; + viewId?: InputMaybe; }; export enum ViewFieldScalarFieldEnum { @@ -2205,6 +2263,7 @@ export enum ViewFieldScalarFieldEnum { IsVisible = 'isVisible', ObjectName = 'objectName', SizeInPx = 'sizeInPx', + ViewId = 'viewId', WorkspaceId = 'workspaceId' } @@ -2215,6 +2274,7 @@ export type ViewFieldUpdateInput = { isVisible?: InputMaybe; objectName?: InputMaybe; sizeInPx?: InputMaybe; + view?: InputMaybe; }; export type ViewFieldUpdateManyWithoutWorkspaceNestedInput = { @@ -2233,10 +2293,122 @@ export type ViewFieldWhereInput = { isVisible?: InputMaybe; objectName?: InputMaybe; sizeInPx?: InputMaybe; + view?: InputMaybe; + viewId?: InputMaybe; }; export type ViewFieldWhereUniqueInput = { id?: InputMaybe; + workspaceId_viewId_objectName_fieldName?: InputMaybe; +}; + +export type ViewFieldWorkspaceIdViewIdObjectNameFieldNameCompoundUniqueInput = { + fieldName: Scalars['String']; + objectName: Scalars['String']; + viewId: Scalars['String']; +}; + +export type ViewOrderByWithRelationInput = { + fields?: InputMaybe; + id?: InputMaybe; + name?: InputMaybe; + objectId?: InputMaybe; + sorts?: InputMaybe; + type?: InputMaybe; +}; + +export type ViewRelationFilter = { + is?: InputMaybe; + isNot?: InputMaybe; +}; + +export type ViewSort = { + __typename?: 'ViewSort'; + direction: ViewSortDirection; + key: Scalars['String']; + name: Scalars['String']; + view: View; + viewId: Scalars['String']; +}; + +export enum ViewSortDirection { + Asc = 'asc', + Desc = 'desc' +} + +export type ViewSortListRelationFilter = { + every?: InputMaybe; + none?: InputMaybe; + some?: InputMaybe; +}; + +export type ViewSortOrderByRelationAggregateInput = { + _count?: InputMaybe; +}; + +export type ViewSortUpdateManyWithoutWorkspaceNestedInput = { + connect?: InputMaybe>; + disconnect?: InputMaybe>; + set?: InputMaybe>; +}; + +export type ViewSortViewIdKeyCompoundUniqueInput = { + key: Scalars['String']; + viewId: Scalars['String']; +}; + +export type ViewSortWhereInput = { + AND?: InputMaybe>; + NOT?: InputMaybe>; + OR?: InputMaybe>; + direction?: InputMaybe; + key?: InputMaybe; + name?: InputMaybe; + view?: InputMaybe; + viewId?: InputMaybe; +}; + +export type ViewSortWhereUniqueInput = { + viewId_key?: InputMaybe; +}; + +export enum ViewType { + Pipeline = 'Pipeline', + Table = 'Table' +} + +export type ViewUpdateManyWithoutWorkspaceNestedInput = { + connect?: InputMaybe>; + disconnect?: InputMaybe>; + set?: InputMaybe>; +}; + +export type ViewUpdateOneWithoutFieldsNestedInput = { + connect?: InputMaybe; + disconnect?: InputMaybe; +}; + +export type ViewWhereInput = { + AND?: InputMaybe>; + NOT?: InputMaybe>; + OR?: InputMaybe>; + fields?: InputMaybe; + id?: InputMaybe; + name?: InputMaybe; + objectId?: InputMaybe; + sorts?: InputMaybe; + type?: InputMaybe; +}; + +export type ViewWhereUniqueInput = { + id?: InputMaybe; + workspaceId_type_objectId_name?: InputMaybe; +}; + +export type ViewWorkspaceIdTypeObjectIdNameCompoundUniqueInput = { + name: Scalars['String']; + objectId: Scalars['String']; + type: ViewType; }; export type Workspace = { @@ -2258,6 +2430,8 @@ export type Workspace = { pipelines?: Maybe>; updatedAt: Scalars['DateTime']; viewFields?: Maybe>; + viewSorts?: Maybe>; + views?: Maybe>; workspaceMember?: Maybe>; }; @@ -2337,6 +2511,8 @@ export type WorkspaceUpdateInput = { pipelines?: InputMaybe; updatedAt?: InputMaybe; viewFields?: InputMaybe; + viewSorts?: InputMaybe; + views?: InputMaybe; workspaceMember?: InputMaybe; }; diff --git a/server/src/ability/ability.factory.ts b/server/src/ability/ability.factory.ts index 3d275d804e..60b307669b 100644 --- a/server/src/ability/ability.factory.ts +++ b/server/src/ability/ability.factory.ts @@ -18,6 +18,8 @@ import { PipelineProgress, UserSettings, ViewField, + View, + ViewSort, } from '@prisma/client'; import { AbilityAction } from './ability.action'; @@ -37,7 +39,9 @@ type SubjectsAbility = Subjects<{ PipelineProgress: PipelineProgress; Attachment: Attachment; UserSettings: UserSettings; + View: View; ViewField: ViewField; + ViewSort: ViewSort; }>; export type AppAbility = PureAbility< diff --git a/server/src/core/view/resolvers/view-field.resolver.ts b/server/src/core/view/resolvers/view-field.resolver.ts index ea9a94cdc6..09d9efd183 100644 --- a/server/src/core/view/resolvers/view-field.resolver.ts +++ b/server/src/core/view/resolvers/view-field.resolver.ts @@ -45,7 +45,7 @@ export class ViewFieldResolver { ): Promise> { return this.viewFieldService.create({ data: { - ...args.data, + ...(args.data as Prisma.ViewFieldCreateInput), workspace: { connect: { id: workspace.id } }, }, select: prismaSelect.value, diff --git a/server/src/core/workspace/services/workspace.service.ts b/server/src/core/workspace/services/workspace.service.ts index 19ce7105b7..a56a744f87 100644 --- a/server/src/core/workspace/services/workspace.service.ts +++ b/server/src/core/workspace/services/workspace.service.ts @@ -111,7 +111,9 @@ export class WorkspaceService { comment, activityTarget, activity, + view, viewField, + viewSort, } = this.prismaService.client; const activitys = await activity.findMany({ @@ -151,9 +153,15 @@ export class WorkspaceService { activity.deleteMany({ where, }), + view.deleteMany({ + where, + }), viewField.deleteMany({ where, }), + viewSort.deleteMany({ + where, + }), refreshToken.deleteMany({ where: { userId }, }), diff --git a/server/src/database/migrations/20230809143432_add_views_table/migration.sql b/server/src/database/migrations/20230809143432_add_views_table/migration.sql new file mode 100644 index 0000000000..17ff87f2ec --- /dev/null +++ b/server/src/database/migrations/20230809143432_add_views_table/migration.sql @@ -0,0 +1,40 @@ +/* + Warnings: + + - A unique constraint covering the columns `[workspaceId,viewId,objectName,fieldName]` on the table `viewFields` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateEnum +CREATE TYPE "ViewType" AS ENUM ('Table', 'Pipeline'); + +-- AlterTable +ALTER TABLE "viewFields" ADD COLUMN "viewId" TEXT; + +-- CreateTable +CREATE TABLE "views" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "objectId" TEXT NOT NULL, + "type" "ViewType" NOT NULL, + "workspaceId" TEXT NOT NULL, + + CONSTRAINT "views_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "views_workspaceId_type_objectId_name_key" ON "views"("workspaceId", "type", "objectId", "name"); + +-- CreateIndex +CREATE UNIQUE INDEX "viewFields_workspaceId_viewId_objectName_fieldName_key" ON "viewFields"("workspaceId", "viewId", "objectName", "fieldName"); + +-- AddForeignKey +ALTER TABLE "pipeline_progresses" ADD CONSTRAINT "pipeline_progresses_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "pipeline_progresses" ADD CONSTRAINT "pipeline_progresses_personId_fkey" FOREIGN KEY ("personId") REFERENCES "people"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "views" ADD CONSTRAINT "views_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "viewFields" ADD CONSTRAINT "viewFields_viewId_fkey" FOREIGN KEY ("viewId") REFERENCES "views"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/server/src/database/migrations/20230809143836_add_view_sorts_table/migration.sql b/server/src/database/migrations/20230809143836_add_view_sorts_table/migration.sql new file mode 100644 index 0000000000..870add1fa7 --- /dev/null +++ b/server/src/database/migrations/20230809143836_add_view_sorts_table/migration.sql @@ -0,0 +1,19 @@ +-- CreateEnum +CREATE TYPE "ViewSortDirection" AS ENUM ('asc', 'desc'); + +-- CreateTable +CREATE TABLE "viewSorts" ( + "direction" "ViewSortDirection" NOT NULL, + "key" TEXT NOT NULL, + "name" TEXT NOT NULL, + "viewId" TEXT NOT NULL, + "workspaceId" TEXT NOT NULL, + + CONSTRAINT "viewSorts_pkey" PRIMARY KEY ("viewId","key") +); + +-- AddForeignKey +ALTER TABLE "viewSorts" ADD CONSTRAINT "viewSorts_viewId_fkey" FOREIGN KEY ("viewId") REFERENCES "views"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "viewSorts" ADD CONSTRAINT "viewSorts_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/server/src/database/schema.prisma b/server/src/database/schema.prisma index 9e23288956..b02c78a93d 100644 --- a/server/src/database/schema.prisma +++ b/server/src/database/schema.prisma @@ -174,6 +174,8 @@ model Workspace { pipelineProgresses PipelineProgress[] activityTargets ActivityTarget[] viewFields ViewField[] + views View[] + viewSorts ViewSort[] /// @TypeGraphQL.omit(input: true, output: true) deletedAt DateTime? @@ -266,7 +268,7 @@ model Person { linkedinUrl String? /// @Validator.IsString() /// @Validator.IsOptional() - xUrl String? + xUrl String? /// @Validator.IsString() /// @Validator.IsOptional() jobTitle String? @@ -557,6 +559,53 @@ model Attachment { @@map("attachments") } +enum ViewType { + Table + Pipeline +} + +model View { + /// @Validator.IsString() + /// @Validator.IsOptional() + id String @id @default(uuid()) + + fields ViewField[] + name String + objectId String + sorts ViewSort[] + type ViewType + + /// @TypeGraphQL.omit(input: true, output: true) + workspace Workspace @relation(fields: [workspaceId], references: [id]) + /// @TypeGraphQL.omit(input: true, output: true) + workspaceId String + + @@unique([workspaceId, type, objectId, name]) + @@map("views") +} + +enum ViewSortDirection { + asc + desc +} + +model ViewSort { + direction ViewSortDirection + key String + name String + + view View @relation(fields: [viewId], references: [id]) + viewId String + + /// @TypeGraphQL.omit(input: true, output: true) + workspace Workspace @relation(fields: [workspaceId], references: [id]) + /// @TypeGraphQL.omit(input: true, output: true) + workspaceId String + + @@id([viewId, key]) + @@map("viewSorts") +} + model ViewField { /// @Validator.IsString() /// @Validator.IsOptional() @@ -568,10 +617,14 @@ model ViewField { objectName String sizeInPx Int + view View? @relation(fields: [viewId], references: [id]) + viewId String? + /// @TypeGraphQL.omit(input: true, output: true) workspace Workspace @relation(fields: [workspaceId], references: [id]) /// @TypeGraphQL.omit(input: true, output: true) workspaceId String + @@unique([workspaceId, viewId, objectName, fieldName]) @@map("viewFields") } diff --git a/server/src/utils/prisma-select/model-select-map.ts b/server/src/utils/prisma-select/model-select-map.ts index 653508af35..052371e71d 100644 --- a/server/src/utils/prisma-select/model-select-map.ts +++ b/server/src/utils/prisma-select/model-select-map.ts @@ -16,5 +16,7 @@ export type ModelSelectMap = { PipelineStage: Prisma.PipelineStageSelect; PipelineProgress: Prisma.PipelineProgressSelect; Attachment: Prisma.AttachmentSelect; + View: Prisma.ViewSelect; + ViewSort: Prisma.ViewSortSelect; ViewField: Prisma.ViewFieldSelect; };