From 3807d62aeb6f7001da43ece078fcec392606f444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tha=C3=AFs?= Date: Wed, 2 Aug 2023 20:48:14 +0200 Subject: [PATCH] feat: persist resized column widths (#1017) * feat: persist resized column widths Closes #981 * test: mock company and person view fields --- front/src/generated/graphql.tsx | 62 +++++++++++++- .../table/components/CompanyTable.tsx | 3 +- .../table/components/CompanyTableMockData.tsx | 23 +++++- .../people/table/components/PeopleTable.tsx | 3 +- .../ui/table/components/EntityTable.tsx | 34 +++++++- .../ui/table/components/EntityTableHeader.tsx | 45 ++++++----- .../components/GenericEntityTableData.tsx | 13 ++- .../src/modules/ui/table/hooks/useLoadView.ts | 81 +++++++++++++++++++ .../ui/table/hooks/useSetEntityTableData.ts | 14 +--- front/src/modules/views/queries/create.ts | 9 +++ front/src/modules/views/queries/select.ts | 7 +- front/src/testing/graphqlMocks.ts | 24 +++++- front/src/testing/mock-data/companies.ts | 15 +++- front/src/testing/mock-data/people.ts | 15 +++- server/src/ability/ability.factory.ts | 1 + server/src/ability/ability.module.ts | 3 + .../handlers/view-field.ability-handler.ts | 23 ++++++ .../view/resolvers/view-field.resolver.ts | 21 ++++- 18 files changed, 345 insertions(+), 51 deletions(-) create mode 100644 front/src/modules/ui/table/hooks/useLoadView.ts create mode 100644 front/src/modules/views/queries/create.ts diff --git a/front/src/generated/graphql.tsx b/front/src/generated/graphql.tsx index 0dac0e78e..15db6ea2e 100644 --- a/front/src/generated/graphql.tsx +++ b/front/src/generated/graphql.tsx @@ -885,6 +885,7 @@ export type Mutation = { allowImpersonation: WorkspaceMember; challenge: LoginToken; createEvent: Analytics; + createManyViewField: AffectedRows; createOneActivity: Activity; createOneComment: Comment; createOneCompany: Company; @@ -934,6 +935,12 @@ export type MutationCreateEventArgs = { }; +export type MutationCreateManyViewFieldArgs = { + data: Array; + skipDuplicates?: InputMaybe; +}; + + export type MutationCreateOneActivityArgs = { data: ActivityCreateInput; }; @@ -2076,6 +2083,15 @@ export type ViewField = { sizeInPx: Scalars['Int']; }; +export type ViewFieldCreateManyInput = { + fieldName: Scalars['String']; + id?: InputMaybe; + index: Scalars['Int']; + isVisible: Scalars['Boolean']; + objectName: Scalars['String']; + sizeInPx: Scalars['Int']; +}; + export type ViewFieldOrderByWithRelationInput = { fieldName?: InputMaybe; id?: InputMaybe; @@ -2640,8 +2656,16 @@ export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; } export type DeleteUserAccountMutation = { __typename?: 'Mutation', deleteUserAccount: { __typename?: 'User', id: string } }; +export type CreateViewFieldsMutationVariables = Exact<{ + data: Array | ViewFieldCreateManyInput; +}>; + + +export type CreateViewFieldsMutation = { __typename?: 'Mutation', createManyViewField: { __typename?: 'AffectedRows', count: number } }; + export type GetViewFieldsQueryVariables = Exact<{ where?: InputMaybe; + orderBy?: InputMaybe | ViewFieldOrderByWithRelationInput>; }>; @@ -5050,9 +5074,42 @@ export function useDeleteUserAccountMutation(baseOptions?: Apollo.MutationHookOp export type DeleteUserAccountMutationHookResult = ReturnType; export type DeleteUserAccountMutationResult = Apollo.MutationResult; export type DeleteUserAccountMutationOptions = Apollo.BaseMutationOptions; +export const CreateViewFieldsDocument = gql` + mutation CreateViewFields($data: [ViewFieldCreateManyInput!]!) { + createManyViewField(data: $data) { + count + } +} + `; +export type CreateViewFieldsMutationFn = Apollo.MutationFunction; + +/** + * __useCreateViewFieldsMutation__ + * + * To run a mutation, you first call `useCreateViewFieldsMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateViewFieldsMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createViewFieldsMutation, { data, loading, error }] = useCreateViewFieldsMutation({ + * variables: { + * data: // value for 'data' + * }, + * }); + */ +export function useCreateViewFieldsMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CreateViewFieldsDocument, options); + } +export type CreateViewFieldsMutationHookResult = ReturnType; +export type CreateViewFieldsMutationResult = Apollo.MutationResult; +export type CreateViewFieldsMutationOptions = Apollo.BaseMutationOptions; export const GetViewFieldsDocument = gql` - query GetViewFields($where: ViewFieldWhereInput) { - viewFields: findManyViewField(where: $where) { + query GetViewFields($where: ViewFieldWhereInput, $orderBy: [ViewFieldOrderByWithRelationInput!]) { + viewFields: findManyViewField(where: $where, orderBy: $orderBy) { id fieldName isVisible @@ -5075,6 +5132,7 @@ export const GetViewFieldsDocument = gql` * const { data, loading, error } = useGetViewFieldsQuery({ * variables: { * where: // value for 'where' + * orderBy: // value for 'orderBy' * }, * }); */ diff --git a/front/src/modules/companies/table/components/CompanyTable.tsx b/front/src/modules/companies/table/components/CompanyTable.tsx index 8a38101cf..df38b6e2f 100644 --- a/front/src/modules/companies/table/components/CompanyTable.tsx +++ b/front/src/modules/companies/table/components/CompanyTable.tsx @@ -35,11 +35,12 @@ export function CompanyTable() { return ( <> { + setViewFields(companyViewFields); + setEntityTableDimensions((prevState) => ({ + ...prevState, + numberOfColumns: companyViewFields.length, + })); + }, [setEntityTableDimensions, setViewFields]); return <>; } diff --git a/front/src/modules/people/table/components/PeopleTable.tsx b/front/src/modules/people/table/components/PeopleTable.tsx index 5b0cfca63..ab80fabd4 100644 --- a/front/src/modules/people/table/components/PeopleTable.tsx +++ b/front/src/modules/people/table/components/PeopleTable.tsx @@ -36,11 +36,12 @@ export function PeopleTable() { return ( <> ({ useUpdateEntityMutation, }: OwnProps) { const viewFields = useRecoilValue(viewFieldsFamilyState); + const setViewFields = useSetRecoilState(viewFieldsFamilyState); - const tableBodyRef = React.useRef(null); + const [updateViewFieldMutation] = useUpdateViewFieldMutation(); + + const tableBodyRef = useRef(null); useMapKeyboardToSoftFocus(); @@ -116,6 +120,25 @@ export function EntityTable({ }, }); + const handleColumnResize = useCallback( + (resizedFieldId: string, width: number) => { + setViewFields((previousViewFields) => + previousViewFields.map((viewField) => + viewField.id === resizedFieldId + ? { ...viewField, columnSize: width } + : viewField, + ), + ); + updateViewFieldMutation({ + variables: { + data: { sizeInPx: width }, + where: { id: resizedFieldId }, + }, + }); + }, + [setViewFields, updateViewFieldMutation], + ); + return ( @@ -129,7 +152,10 @@ export function EntityTable({ {viewFields.length > 0 && ( - + )} diff --git a/front/src/modules/ui/table/components/EntityTableHeader.tsx b/front/src/modules/ui/table/components/EntityTableHeader.tsx index 53ebf7dff..1f14827d5 100644 --- a/front/src/modules/ui/table/components/EntityTableHeader.tsx +++ b/front/src/modules/ui/table/components/EntityTableHeader.tsx @@ -1,7 +1,10 @@ -import { PointerEvent, useCallback, useState } from 'react'; +import { PointerEvent, useCallback, useMemo, useState } from 'react'; import styled from '@emotion/styled'; -import { ViewFieldDefinition, ViewFieldMetadata } from '../types/ViewField'; +import type { + ViewFieldDefinition, + ViewFieldMetadata, +} from '../types/ViewField'; import { ColumnHead } from './ColumnHead'; import { SelectAllCheckbox } from './SelectAllCheckbox'; @@ -40,18 +43,22 @@ const StyledResizeHandler = styled.div` `; type OwnProps = { + onColumnResize: (resizedFieldId: string, width: number) => void; viewFields: ViewFieldDefinition[]; }; -export function EntityTableHeader({ viewFields }: OwnProps) { - const initialColumnWidths = viewFields.reduce>( - (result, viewField) => ({ - ...result, - [viewField.id]: viewField.columnSize, - }), - {}, +export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) { + const columnWidths = useMemo( + () => + viewFields.reduce>( + (result, viewField) => ({ + ...result, + [viewField.id]: viewField.columnSize, + }), + {}, + ), + [viewFields], ); - const [columnWidths, setColumnWidths] = useState(initialColumnWidths); const [isResizing, setIsResizing] = useState(false); const [initialPointerPositionX, setInitialPointerPositionX] = useState< number | null @@ -82,16 +89,16 @@ export function EntityTableHeader({ viewFields }: OwnProps) { setIsResizing(false); if (!resizedFieldId) return; - const newColumnWidths = { - ...columnWidths, - [resizedFieldId]: Math.max( - columnWidths[resizedFieldId] + offset, - COLUMN_MIN_WIDTH, - ), - }; - setColumnWidths(newColumnWidths); + const nextWidth = Math.round( + Math.max(columnWidths[resizedFieldId] + offset, COLUMN_MIN_WIDTH), + ); + + if (nextWidth !== columnWidths[resizedFieldId]) { + onColumnResize(resizedFieldId, nextWidth); + } + setOffset(0); - }, [offset, setIsResizing, columnWidths, resizedFieldId]); + }, [resizedFieldId, columnWidths, offset, onColumnResize]); return ( diff --git a/front/src/modules/ui/table/components/GenericEntityTableData.tsx b/front/src/modules/ui/table/components/GenericEntityTableData.tsx index af81edf61..2c4be00ab 100644 --- a/front/src/modules/ui/table/components/GenericEntityTableData.tsx +++ b/front/src/modules/ui/table/components/GenericEntityTableData.tsx @@ -1,3 +1,4 @@ +import { defaultOrderBy } from '@/people/queries'; import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition'; import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData'; import { @@ -5,31 +6,35 @@ import { ViewFieldMetadata, } from '@/ui/table/types/ViewField'; -import { defaultOrderBy } from '../../../people/queries'; +import { useLoadView } from '../hooks/useLoadView'; export function GenericEntityTableData({ + objectName, useGetRequest, getRequestResultKey, orderBy = defaultOrderBy, whereFilters, - viewFields, + viewFieldDefinitions, filterDefinitionArray, }: { + objectName: 'company' | 'person'; useGetRequest: any; getRequestResultKey: string; orderBy?: any; whereFilters?: any; - viewFields: ViewFieldDefinition[]; + viewFieldDefinitions: ViewFieldDefinition[]; filterDefinitionArray: FilterDefinition[]; }) { const setEntityTableData = useSetEntityTableData(); + useLoadView({ objectName, viewFieldDefinitions }); + useGetRequest({ variables: { orderBy, where: whereFilters }, onCompleted: (data: any) => { const entities = data[getRequestResultKey] ?? []; - setEntityTableData(entities, viewFields, filterDefinitionArray); + setEntityTableData(entities, filterDefinitionArray); }, }); diff --git a/front/src/modules/ui/table/hooks/useLoadView.ts b/front/src/modules/ui/table/hooks/useLoadView.ts new file mode 100644 index 000000000..6d1b38e60 --- /dev/null +++ b/front/src/modules/ui/table/hooks/useLoadView.ts @@ -0,0 +1,81 @@ +import { getOperationName } from '@apollo/client/utilities'; +import { useSetRecoilState } from 'recoil'; + +import { GET_VIEW_FIELDS } from '@/views/queries/select'; +import { + SortOrder, + useCreateViewFieldsMutation, + useGetViewFieldsQuery, +} from '~/generated/graphql'; + +import { entityTableDimensionsState } from '../states/entityTableDimensionsState'; +import { viewFieldsFamilyState } from '../states/viewFieldsState'; +import { + ViewFieldDefinition, + ViewFieldMetadata, + ViewFieldTextMetadata, +} from '../types/ViewField'; + +const DEFAULT_VIEW_FIELD_METADATA: ViewFieldTextMetadata = { + type: 'text', + placeHolder: '', + fieldName: '', +}; + +export const useLoadView = ({ + objectName, + viewFieldDefinitions, +}: { + objectName: 'company' | 'person'; + viewFieldDefinitions: ViewFieldDefinition[]; +}) => { + const setEntityTableDimensions = useSetRecoilState( + entityTableDimensionsState, + ); + const setViewFields = useSetRecoilState(viewFieldsFamilyState); + + const [createViewFieldsMutation] = useCreateViewFieldsMutation(); + + useGetViewFieldsQuery({ + variables: { + orderBy: { index: SortOrder.Asc }, + where: { objectName: { equals: objectName } }, + }, + onCompleted: (data) => { + if (data.viewFields.length) { + setViewFields( + data.viewFields.map>( + (viewField) => ({ + ...(viewFieldDefinitions.find( + ({ columnLabel }) => viewField.fieldName === columnLabel, + ) || { metadata: DEFAULT_VIEW_FIELD_METADATA }), + id: viewField.id, + columnLabel: viewField.fieldName, + columnOrder: viewField.index, + columnSize: viewField.sizeInPx, + }), + ), + ); + setEntityTableDimensions((prevState) => ({ + ...prevState, + numberOfColumns: data.viewFields.length, + })); + return; + } + + // Populate if empty + createViewFieldsMutation({ + variables: { + data: viewFieldDefinitions.map((viewFieldDefinition) => ({ + fieldName: viewFieldDefinition.columnLabel, + index: viewFieldDefinition.columnOrder, + isVisible: true, + objectName, + sizeInPx: viewFieldDefinition.columnSize, + })), + }, + refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''], + }); + }, + }); +}; diff --git a/front/src/modules/ui/table/hooks/useSetEntityTableData.ts b/front/src/modules/ui/table/hooks/useSetEntityTableData.ts index 70f75806b..69765571c 100644 --- a/front/src/modules/ui/table/hooks/useSetEntityTableData.ts +++ b/front/src/modules/ui/table/hooks/useSetEntityTableData.ts @@ -8,11 +8,6 @@ import { isFetchingEntityTableDataState } from '@/ui/table/states/isFetchingEnti import { TableContext } from '@/ui/table/states/TableContext'; import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState'; import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState'; -import { viewFieldsFamilyState } from '@/ui/table/states/viewFieldsState'; -import { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/table/types/ViewField'; import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; export function useSetEntityTableData() { @@ -24,7 +19,6 @@ export function useSetEntityTableData() { ({ set, snapshot }) => ( newEntityArray: T[], - viewFields: ViewFieldDefinition[], filters: FilterDefinition[], ) => { for (const entity of newEntityArray) { @@ -49,15 +43,13 @@ export function useSetEntityTableData() { resetTableRowSelection(); - set(entityTableDimensionsState, { - numberOfColumns: viewFields.length, + set(entityTableDimensionsState, (prevState) => ({ + ...prevState, numberOfRows: entityIds.length, - }); + })); set(availableFiltersScopedState(tableContextScopeId), filters); - set(viewFieldsFamilyState, viewFields); - set(isFetchingEntityTableDataState, false); }, [], diff --git a/front/src/modules/views/queries/create.ts b/front/src/modules/views/queries/create.ts new file mode 100644 index 000000000..a91b4264d --- /dev/null +++ b/front/src/modules/views/queries/create.ts @@ -0,0 +1,9 @@ +import { gql } from '@apollo/client'; + +export const CREATE_VIEW_FIELDS = gql` + mutation CreateViewFields($data: [ViewFieldCreateManyInput!]!) { + createManyViewField(data: $data) { + count + } + } +`; diff --git a/front/src/modules/views/queries/select.ts b/front/src/modules/views/queries/select.ts index 2341c0722..735405c48 100644 --- a/front/src/modules/views/queries/select.ts +++ b/front/src/modules/views/queries/select.ts @@ -1,8 +1,11 @@ import { gql } from '@apollo/client'; export const GET_VIEW_FIELDS = gql` - query GetViewFields($where: ViewFieldWhereInput) { - viewFields: findManyViewField(where: $where) { + query GetViewFields( + $where: ViewFieldWhereInput + $orderBy: [ViewFieldOrderByWithRelationInput!] + ) { + viewFields: findManyViewField(where: $where, orderBy: $orderBy) { id fieldName isVisible diff --git a/front/src/testing/graphqlMocks.ts b/front/src/testing/graphqlMocks.ts index e21a3ce1e..39d8b9e85 100644 --- a/front/src/testing/graphqlMocks.ts +++ b/front/src/testing/graphqlMocks.ts @@ -13,6 +13,7 @@ import { SEARCH_USER_QUERY, } from '@/search/queries/search'; import { GET_CURRENT_USER } from '@/users/queries'; +import { GET_VIEW_FIELDS } from '@/views/queries/select'; import { GetCompaniesQuery, GetPeopleQuery, @@ -24,8 +25,11 @@ import { } from '~/generated/graphql'; import { mockedActivities } from './mock-data/activities'; -import { mockedCompaniesData } from './mock-data/companies'; -import { mockedPeopleData } from './mock-data/people'; +import { + mockedCompaniesData, + mockedCompanyViewFields, +} from './mock-data/companies'; +import { mockedPeopleData, mockedPersonViewFields } from './mock-data/people'; import { mockedPipelineProgressData } from './mock-data/pipeline-progress'; import { mockedPipelinesData } from './mock-data/pipelines'; import { mockedUsersData } from './mock-data/users'; @@ -206,4 +210,20 @@ export const graphqlMocks = [ }), ); }), + graphql.query(getOperationName(GET_VIEW_FIELDS) ?? '', (req, res, ctx) => { + const { + where: { + objectName: { equals: objectName }, + }, + } = req.variables; + + return res( + ctx.data({ + viewFields: + objectName === 'company' + ? mockedCompanyViewFields + : mockedPersonViewFields, + }), + ); + }), ]; diff --git a/front/src/testing/mock-data/companies.ts b/front/src/testing/mock-data/companies.ts index e881f452e..9818d07c0 100644 --- a/front/src/testing/mock-data/companies.ts +++ b/front/src/testing/mock-data/companies.ts @@ -1,4 +1,5 @@ -import { Company, User } from '../../generated/graphql'; +import { companyViewFields } from '@/companies/constants/companyViewFields'; +import { Company, User, ViewField } from '~/generated/graphql'; type MockedCompany = Pick< Company, @@ -118,3 +119,15 @@ export const mockedCompaniesData: Array = [ __typename: 'Company', }, ]; + +export const mockedCompanyViewFields = companyViewFields.map( + (viewFieldDefinition) => ({ + __typename: 'ViewField', + fieldName: viewFieldDefinition.columnLabel, + id: viewFieldDefinition.id, + index: viewFieldDefinition.columnOrder, + isVisible: true, + objectName: 'company', + sizeInPx: viewFieldDefinition.columnSize, + }), +); diff --git a/front/src/testing/mock-data/people.ts b/front/src/testing/mock-data/people.ts index 05332dbd0..3716757f8 100644 --- a/front/src/testing/mock-data/people.ts +++ b/front/src/testing/mock-data/people.ts @@ -1,4 +1,5 @@ -import { Company, Person } from '~/generated/graphql'; +import { peopleViewFields } from '@/people/constants/peopleViewFields'; +import { Company, Person, ViewField } from '~/generated/graphql'; type RequiredAndNotNull = { [P in keyof T]-?: Exclude; @@ -116,3 +117,15 @@ export const mockedPeopleData: MockedPerson[] = [ city: 'Paris', }, ]; + +export const mockedPersonViewFields = peopleViewFields.map( + (viewFieldDefinition) => ({ + __typename: 'ViewField', + fieldName: viewFieldDefinition.columnLabel, + id: viewFieldDefinition.id, + index: viewFieldDefinition.columnOrder, + isVisible: true, + objectName: 'person', + sizeInPx: viewFieldDefinition.columnSize, + }), +); diff --git a/server/src/ability/ability.factory.ts b/server/src/ability/ability.factory.ts index 32fa03f12..3d275d804 100644 --- a/server/src/ability/ability.factory.ts +++ b/server/src/ability/ability.factory.ts @@ -132,6 +132,7 @@ export class AbilityFactory { // ViewField can(AbilityAction.Read, 'ViewField', { workspaceId: workspace.id }); + can(AbilityAction.Create, 'ViewField', { workspaceId: workspace.id }); can(AbilityAction.Update, 'ViewField', { workspaceId: workspace.id }); return build(); diff --git a/server/src/ability/ability.module.ts b/server/src/ability/ability.module.ts index 7ed68238c..3c5e3a82c 100644 --- a/server/src/ability/ability.module.ts +++ b/server/src/ability/ability.module.ts @@ -95,6 +95,7 @@ import { UpdateAttachmentAbilityHandler, } from './handlers/attachment.ability-handler'; import { + CreateViewFieldAbilityHandler, ReadViewFieldAbilityHandler, UpdateViewFieldAbilityHandler, } from './handlers/view-field.ability-handler'; @@ -184,6 +185,7 @@ import { DeletePipelineProgressAbilityHandler, // ViewField ReadViewFieldAbilityHandler, + CreateViewFieldAbilityHandler, UpdateViewFieldAbilityHandler, ], exports: [ @@ -268,6 +270,7 @@ import { DeletePipelineProgressAbilityHandler, // ViewField ReadViewFieldAbilityHandler, + CreateViewFieldAbilityHandler, UpdateViewFieldAbilityHandler, ], }) diff --git a/server/src/ability/handlers/view-field.ability-handler.ts b/server/src/ability/handlers/view-field.ability-handler.ts index 4a4380cbb..219e910b3 100644 --- a/server/src/ability/handlers/view-field.ability-handler.ts +++ b/server/src/ability/handlers/view-field.ability-handler.ts @@ -28,6 +28,29 @@ export class ReadViewFieldAbilityHandler implements IAbilityHandler { } } +@Injectable() +export class CreateViewFieldAbilityHandler implements IAbilityHandler { + constructor(private readonly prismaService: PrismaService) {} + + async handle(ability: AppAbility, context: ExecutionContext) { + const gqlContext = GqlExecutionContext.create(context); + const args = gqlContext.getArgs(); + + const allowed = await relationAbilityChecker( + 'ViewField', + ability, + this.prismaService.client, + args, + ); + + if (!allowed) { + return false; + } + + return ability.can(AbilityAction.Create, 'ViewField'); + } +} + @Injectable() export class UpdateViewFieldAbilityHandler implements IAbilityHandler { constructor(private readonly prismaService: PrismaService) {} diff --git a/server/src/core/view/resolvers/view-field.resolver.ts b/server/src/core/view/resolvers/view-field.resolver.ts index 2ac1be3e7..5d0022037 100644 --- a/server/src/core/view/resolvers/view-field.resolver.ts +++ b/server/src/core/view/resolvers/view-field.resolver.ts @@ -2,17 +2,21 @@ import { UseGuards } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; import { accessibleBy } from '@casl/prisma'; -import { Prisma } from '@prisma/client'; +import { Prisma, Workspace } from '@prisma/client'; import { AppAbility } from 'src/ability/ability.factory'; import { + CreateViewFieldAbilityHandler, ReadViewFieldAbilityHandler, UpdateViewFieldAbilityHandler, } from 'src/ability/handlers/view-field.ability-handler'; +import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output'; +import { CreateManyViewFieldArgs } from 'src/core/@generated/view-field/create-many-view-field.args'; import { FindManyViewFieldArgs } from 'src/core/@generated/view-field/find-many-view-field.args'; import { UpdateOneViewFieldArgs } from 'src/core/@generated/view-field/update-one-view-field.args'; import { ViewField } from 'src/core/@generated/view-field/view-field.model'; import { ViewFieldService } from 'src/core/view/services/view-field.service'; +import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator'; import { CheckAbilities } from 'src/decorators/check-abilities.decorator'; import { PrismaSelect, @@ -27,6 +31,21 @@ import { JwtAuthGuard } from 'src/guards/jwt.auth.guard'; export class ViewFieldResolver { constructor(private readonly viewFieldService: ViewFieldService) {} + @Mutation(() => AffectedRows) + @UseGuards(AbilityGuard) + @CheckAbilities(CreateViewFieldAbilityHandler) + async createManyViewField( + @Args() args: CreateManyViewFieldArgs, + @AuthWorkspace() workspace: Workspace, + ): Promise { + return this.viewFieldService.createMany({ + data: args.data.map((dataItem) => ({ + ...dataItem, + workspaceId: workspace.id, + })), + }); + } + @Query(() => [ViewField]) @UseGuards(AbilityGuard) @CheckAbilities(ReadViewFieldAbilityHandler)