feat: persist resized column widths (#1017)

* feat: persist resized column widths

Closes #981

* test: mock company and person view fields
This commit is contained in:
Thaïs 2023-08-02 20:48:14 +02:00 committed by GitHub
parent 552fb2378b
commit 3807d62aeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 345 additions and 51 deletions

View File

@ -885,6 +885,7 @@ export type Mutation = {
allowImpersonation: WorkspaceMember; allowImpersonation: WorkspaceMember;
challenge: LoginToken; challenge: LoginToken;
createEvent: Analytics; createEvent: Analytics;
createManyViewField: AffectedRows;
createOneActivity: Activity; createOneActivity: Activity;
createOneComment: Comment; createOneComment: Comment;
createOneCompany: Company; createOneCompany: Company;
@ -934,6 +935,12 @@ export type MutationCreateEventArgs = {
}; };
export type MutationCreateManyViewFieldArgs = {
data: Array<ViewFieldCreateManyInput>;
skipDuplicates?: InputMaybe<Scalars['Boolean']>;
};
export type MutationCreateOneActivityArgs = { export type MutationCreateOneActivityArgs = {
data: ActivityCreateInput; data: ActivityCreateInput;
}; };
@ -2076,6 +2083,15 @@ export type ViewField = {
sizeInPx: Scalars['Int']; sizeInPx: Scalars['Int'];
}; };
export type ViewFieldCreateManyInput = {
fieldName: Scalars['String'];
id?: InputMaybe<Scalars['String']>;
index: Scalars['Int'];
isVisible: Scalars['Boolean'];
objectName: Scalars['String'];
sizeInPx: Scalars['Int'];
};
export type ViewFieldOrderByWithRelationInput = { export type ViewFieldOrderByWithRelationInput = {
fieldName?: InputMaybe<SortOrder>; fieldName?: InputMaybe<SortOrder>;
id?: InputMaybe<SortOrder>; id?: InputMaybe<SortOrder>;
@ -2640,8 +2656,16 @@ export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }
export type DeleteUserAccountMutation = { __typename?: 'Mutation', deleteUserAccount: { __typename?: 'User', id: string } }; export type DeleteUserAccountMutation = { __typename?: 'Mutation', deleteUserAccount: { __typename?: 'User', id: string } };
export type CreateViewFieldsMutationVariables = Exact<{
data: Array<ViewFieldCreateManyInput> | ViewFieldCreateManyInput;
}>;
export type CreateViewFieldsMutation = { __typename?: 'Mutation', createManyViewField: { __typename?: 'AffectedRows', count: number } };
export type GetViewFieldsQueryVariables = Exact<{ export type GetViewFieldsQueryVariables = Exact<{
where?: InputMaybe<ViewFieldWhereInput>; where?: InputMaybe<ViewFieldWhereInput>;
orderBy?: InputMaybe<Array<ViewFieldOrderByWithRelationInput> | ViewFieldOrderByWithRelationInput>;
}>; }>;
@ -5050,9 +5074,42 @@ export function useDeleteUserAccountMutation(baseOptions?: Apollo.MutationHookOp
export type DeleteUserAccountMutationHookResult = ReturnType<typeof useDeleteUserAccountMutation>; export type DeleteUserAccountMutationHookResult = ReturnType<typeof useDeleteUserAccountMutation>;
export type DeleteUserAccountMutationResult = Apollo.MutationResult<DeleteUserAccountMutation>; export type DeleteUserAccountMutationResult = Apollo.MutationResult<DeleteUserAccountMutation>;
export type DeleteUserAccountMutationOptions = Apollo.BaseMutationOptions<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>; export type DeleteUserAccountMutationOptions = Apollo.BaseMutationOptions<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>;
export const CreateViewFieldsDocument = gql`
mutation CreateViewFields($data: [ViewFieldCreateManyInput!]!) {
createManyViewField(data: $data) {
count
}
}
`;
export type CreateViewFieldsMutationFn = Apollo.MutationFunction<CreateViewFieldsMutation, CreateViewFieldsMutationVariables>;
/**
* __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<CreateViewFieldsMutation, CreateViewFieldsMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateViewFieldsMutation, CreateViewFieldsMutationVariables>(CreateViewFieldsDocument, options);
}
export type CreateViewFieldsMutationHookResult = ReturnType<typeof useCreateViewFieldsMutation>;
export type CreateViewFieldsMutationResult = Apollo.MutationResult<CreateViewFieldsMutation>;
export type CreateViewFieldsMutationOptions = Apollo.BaseMutationOptions<CreateViewFieldsMutation, CreateViewFieldsMutationVariables>;
export const GetViewFieldsDocument = gql` export const GetViewFieldsDocument = gql`
query GetViewFields($where: ViewFieldWhereInput) { query GetViewFields($where: ViewFieldWhereInput, $orderBy: [ViewFieldOrderByWithRelationInput!]) {
viewFields: findManyViewField(where: $where) { viewFields: findManyViewField(where: $where, orderBy: $orderBy) {
id id
fieldName fieldName
isVisible isVisible
@ -5075,6 +5132,7 @@ export const GetViewFieldsDocument = gql`
* const { data, loading, error } = useGetViewFieldsQuery({ * const { data, loading, error } = useGetViewFieldsQuery({
* variables: { * variables: {
* where: // value for 'where' * where: // value for 'where'
* orderBy: // value for 'orderBy'
* }, * },
* }); * });
*/ */

View File

@ -35,11 +35,12 @@ export function CompanyTable() {
return ( return (
<> <>
<GenericEntityTableData <GenericEntityTableData
objectName="company"
getRequestResultKey="companies" getRequestResultKey="companies"
useGetRequest={useGetCompaniesQuery} useGetRequest={useGetCompaniesQuery}
orderBy={orderBy} orderBy={orderBy}
whereFilters={whereFilters} whereFilters={whereFilters}
viewFields={companyViewFields} viewFieldDefinitions={companyViewFields}
filterDefinitionArray={companiesFilters} filterDefinitionArray={companiesFilters}
/> />
<EntityTable <EntityTable

View File

@ -1,11 +1,30 @@
import { companyViewFields } from '@/companies/constants/companyViewFields'; import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData'; import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
import { entityTableDimensionsState } from '@/ui/table/states/entityTableDimensionsState';
import { viewFieldsFamilyState } from '@/ui/table/states/viewFieldsState';
import { companyViewFields } from '../../constants/companyViewFields';
import { mockedCompaniesData } from './companies-mock-data'; import { mockedCompaniesData } from './companies-mock-data';
export function CompanyTableMockData() { export function CompanyTableMockData() {
const setEntityTableDimensions = useSetRecoilState(
entityTableDimensionsState,
);
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
const setEntityTableData = useSetEntityTableData(); const setEntityTableData = useSetEntityTableData();
setEntityTableData(mockedCompaniesData, companyViewFields, []); setEntityTableData(mockedCompaniesData, []);
useEffect(() => {
setViewFields(companyViewFields);
setEntityTableDimensions((prevState) => ({
...prevState,
numberOfColumns: companyViewFields.length,
}));
}, [setEntityTableDimensions, setViewFields]);
return <></>; return <></>;
} }

View File

@ -36,11 +36,12 @@ export function PeopleTable() {
return ( return (
<> <>
<GenericEntityTableData <GenericEntityTableData
objectName="person"
getRequestResultKey="people" getRequestResultKey="people"
useGetRequest={useGetPeopleQuery} useGetRequest={useGetPeopleQuery}
orderBy={orderBy} orderBy={orderBy}
whereFilters={whereFilters} whereFilters={whereFilters}
viewFields={peopleViewFields} viewFieldDefinitions={peopleViewFields}
filterDefinitionArray={peopleFilters} filterDefinitionArray={peopleFilters}
/> />
<EntityTable <EntityTable

View File

@ -1,9 +1,10 @@
import * as React from 'react'; import { useCallback, useRef } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil'; import { useRecoilValue, useSetRecoilState } from 'recoil';
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface'; import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
import { useUpdateViewFieldMutation } from '~/generated/graphql';
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus'; import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus'; import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
@ -102,8 +103,11 @@ export function EntityTable<SortField>({
useUpdateEntityMutation, useUpdateEntityMutation,
}: OwnProps<SortField>) { }: OwnProps<SortField>) {
const viewFields = useRecoilValue(viewFieldsFamilyState); const viewFields = useRecoilValue(viewFieldsFamilyState);
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
const tableBodyRef = React.useRef<HTMLDivElement>(null); const [updateViewFieldMutation] = useUpdateViewFieldMutation();
const tableBodyRef = useRef<HTMLDivElement>(null);
useMapKeyboardToSoftFocus(); useMapKeyboardToSoftFocus();
@ -116,6 +120,25 @@ export function EntityTable<SortField>({
}, },
}); });
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 ( return (
<EntityUpdateMutationHookContext.Provider value={useUpdateEntityMutation}> <EntityUpdateMutationHookContext.Provider value={useUpdateEntityMutation}>
<StyledTableWithHeader> <StyledTableWithHeader>
@ -129,7 +152,10 @@ export function EntityTable<SortField>({
<StyledTableWrapper> <StyledTableWrapper>
{viewFields.length > 0 && ( {viewFields.length > 0 && (
<StyledTable> <StyledTable>
<EntityTableHeader viewFields={viewFields} /> <EntityTableHeader
onColumnResize={handleColumnResize}
viewFields={viewFields}
/>
<EntityTableBody /> <EntityTableBody />
</StyledTable> </StyledTable>
)} )}

View File

@ -1,7 +1,10 @@
import { PointerEvent, useCallback, useState } from 'react'; import { PointerEvent, useCallback, useMemo, useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ViewFieldDefinition, ViewFieldMetadata } from '../types/ViewField'; import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '../types/ViewField';
import { ColumnHead } from './ColumnHead'; import { ColumnHead } from './ColumnHead';
import { SelectAllCheckbox } from './SelectAllCheckbox'; import { SelectAllCheckbox } from './SelectAllCheckbox';
@ -40,18 +43,22 @@ const StyledResizeHandler = styled.div`
`; `;
type OwnProps = { type OwnProps = {
onColumnResize: (resizedFieldId: string, width: number) => void;
viewFields: ViewFieldDefinition<ViewFieldMetadata>[]; viewFields: ViewFieldDefinition<ViewFieldMetadata>[];
}; };
export function EntityTableHeader({ viewFields }: OwnProps) { export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) {
const initialColumnWidths = viewFields.reduce<Record<string, number>>( const columnWidths = useMemo(
() =>
viewFields.reduce<Record<string, number>>(
(result, viewField) => ({ (result, viewField) => ({
...result, ...result,
[viewField.id]: viewField.columnSize, [viewField.id]: viewField.columnSize,
}), }),
{}, {},
),
[viewFields],
); );
const [columnWidths, setColumnWidths] = useState(initialColumnWidths);
const [isResizing, setIsResizing] = useState(false); const [isResizing, setIsResizing] = useState(false);
const [initialPointerPositionX, setInitialPointerPositionX] = useState< const [initialPointerPositionX, setInitialPointerPositionX] = useState<
number | null number | null
@ -82,16 +89,16 @@ export function EntityTableHeader({ viewFields }: OwnProps) {
setIsResizing(false); setIsResizing(false);
if (!resizedFieldId) return; if (!resizedFieldId) return;
const newColumnWidths = { const nextWidth = Math.round(
...columnWidths, Math.max(columnWidths[resizedFieldId] + offset, COLUMN_MIN_WIDTH),
[resizedFieldId]: Math.max( );
columnWidths[resizedFieldId] + offset,
COLUMN_MIN_WIDTH, if (nextWidth !== columnWidths[resizedFieldId]) {
), onColumnResize(resizedFieldId, nextWidth);
}; }
setColumnWidths(newColumnWidths);
setOffset(0); setOffset(0);
}, [offset, setIsResizing, columnWidths, resizedFieldId]); }, [resizedFieldId, columnWidths, offset, onColumnResize]);
return ( return (
<thead> <thead>

View File

@ -1,3 +1,4 @@
import { defaultOrderBy } from '@/people/queries';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition'; import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData'; import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
import { import {
@ -5,31 +6,35 @@ import {
ViewFieldMetadata, ViewFieldMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/table/types/ViewField';
import { defaultOrderBy } from '../../../people/queries'; import { useLoadView } from '../hooks/useLoadView';
export function GenericEntityTableData({ export function GenericEntityTableData({
objectName,
useGetRequest, useGetRequest,
getRequestResultKey, getRequestResultKey,
orderBy = defaultOrderBy, orderBy = defaultOrderBy,
whereFilters, whereFilters,
viewFields, viewFieldDefinitions,
filterDefinitionArray, filterDefinitionArray,
}: { }: {
objectName: 'company' | 'person';
useGetRequest: any; useGetRequest: any;
getRequestResultKey: string; getRequestResultKey: string;
orderBy?: any; orderBy?: any;
whereFilters?: any; whereFilters?: any;
viewFields: ViewFieldDefinition<ViewFieldMetadata>[]; viewFieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
filterDefinitionArray: FilterDefinition[]; filterDefinitionArray: FilterDefinition[];
}) { }) {
const setEntityTableData = useSetEntityTableData(); const setEntityTableData = useSetEntityTableData();
useLoadView({ objectName, viewFieldDefinitions });
useGetRequest({ useGetRequest({
variables: { orderBy, where: whereFilters }, variables: { orderBy, where: whereFilters },
onCompleted: (data: any) => { onCompleted: (data: any) => {
const entities = data[getRequestResultKey] ?? []; const entities = data[getRequestResultKey] ?? [];
setEntityTableData(entities, viewFields, filterDefinitionArray); setEntityTableData(entities, filterDefinitionArray);
}, },
}); });

View File

@ -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<ViewFieldMetadata>[];
}) => {
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<ViewFieldDefinition<ViewFieldMetadata>>(
(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) ?? ''],
});
},
});
};

View File

@ -8,11 +8,6 @@ import { isFetchingEntityTableDataState } from '@/ui/table/states/isFetchingEnti
import { TableContext } from '@/ui/table/states/TableContext'; import { TableContext } from '@/ui/table/states/TableContext';
import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState'; import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState';
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState'; 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'; import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
export function useSetEntityTableData() { export function useSetEntityTableData() {
@ -24,7 +19,6 @@ export function useSetEntityTableData() {
({ set, snapshot }) => ({ set, snapshot }) =>
<T extends { id: string }>( <T extends { id: string }>(
newEntityArray: T[], newEntityArray: T[],
viewFields: ViewFieldDefinition<ViewFieldMetadata>[],
filters: FilterDefinition[], filters: FilterDefinition[],
) => { ) => {
for (const entity of newEntityArray) { for (const entity of newEntityArray) {
@ -49,15 +43,13 @@ export function useSetEntityTableData() {
resetTableRowSelection(); resetTableRowSelection();
set(entityTableDimensionsState, { set(entityTableDimensionsState, (prevState) => ({
numberOfColumns: viewFields.length, ...prevState,
numberOfRows: entityIds.length, numberOfRows: entityIds.length,
}); }));
set(availableFiltersScopedState(tableContextScopeId), filters); set(availableFiltersScopedState(tableContextScopeId), filters);
set(viewFieldsFamilyState, viewFields);
set(isFetchingEntityTableDataState, false); set(isFetchingEntityTableDataState, false);
}, },
[], [],

View File

@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const CREATE_VIEW_FIELDS = gql`
mutation CreateViewFields($data: [ViewFieldCreateManyInput!]!) {
createManyViewField(data: $data) {
count
}
}
`;

View File

@ -1,8 +1,11 @@
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
export const GET_VIEW_FIELDS = gql` export const GET_VIEW_FIELDS = gql`
query GetViewFields($where: ViewFieldWhereInput) { query GetViewFields(
viewFields: findManyViewField(where: $where) { $where: ViewFieldWhereInput
$orderBy: [ViewFieldOrderByWithRelationInput!]
) {
viewFields: findManyViewField(where: $where, orderBy: $orderBy) {
id id
fieldName fieldName
isVisible isVisible

View File

@ -13,6 +13,7 @@ import {
SEARCH_USER_QUERY, SEARCH_USER_QUERY,
} from '@/search/queries/search'; } from '@/search/queries/search';
import { GET_CURRENT_USER } from '@/users/queries'; import { GET_CURRENT_USER } from '@/users/queries';
import { GET_VIEW_FIELDS } from '@/views/queries/select';
import { import {
GetCompaniesQuery, GetCompaniesQuery,
GetPeopleQuery, GetPeopleQuery,
@ -24,8 +25,11 @@ import {
} from '~/generated/graphql'; } from '~/generated/graphql';
import { mockedActivities } from './mock-data/activities'; import { mockedActivities } from './mock-data/activities';
import { mockedCompaniesData } from './mock-data/companies'; import {
import { mockedPeopleData } from './mock-data/people'; mockedCompaniesData,
mockedCompanyViewFields,
} from './mock-data/companies';
import { mockedPeopleData, mockedPersonViewFields } from './mock-data/people';
import { mockedPipelineProgressData } from './mock-data/pipeline-progress'; import { mockedPipelineProgressData } from './mock-data/pipeline-progress';
import { mockedPipelinesData } from './mock-data/pipelines'; import { mockedPipelinesData } from './mock-data/pipelines';
import { mockedUsersData } from './mock-data/users'; 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,
}),
);
}),
]; ];

View File

@ -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< type MockedCompany = Pick<
Company, Company,
@ -118,3 +119,15 @@ export const mockedCompaniesData: Array<MockedCompany> = [
__typename: 'Company', __typename: 'Company',
}, },
]; ];
export const mockedCompanyViewFields = companyViewFields.map<ViewField>(
(viewFieldDefinition) => ({
__typename: 'ViewField',
fieldName: viewFieldDefinition.columnLabel,
id: viewFieldDefinition.id,
index: viewFieldDefinition.columnOrder,
isVisible: true,
objectName: 'company',
sizeInPx: viewFieldDefinition.columnSize,
}),
);

View File

@ -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<T> = { type RequiredAndNotNull<T> = {
[P in keyof T]-?: Exclude<T[P], null | undefined>; [P in keyof T]-?: Exclude<T[P], null | undefined>;
@ -116,3 +117,15 @@ export const mockedPeopleData: MockedPerson[] = [
city: 'Paris', city: 'Paris',
}, },
]; ];
export const mockedPersonViewFields = peopleViewFields.map<ViewField>(
(viewFieldDefinition) => ({
__typename: 'ViewField',
fieldName: viewFieldDefinition.columnLabel,
id: viewFieldDefinition.id,
index: viewFieldDefinition.columnOrder,
isVisible: true,
objectName: 'person',
sizeInPx: viewFieldDefinition.columnSize,
}),
);

View File

@ -132,6 +132,7 @@ export class AbilityFactory {
// ViewField // ViewField
can(AbilityAction.Read, 'ViewField', { workspaceId: workspace.id }); can(AbilityAction.Read, 'ViewField', { workspaceId: workspace.id });
can(AbilityAction.Create, 'ViewField', { workspaceId: workspace.id });
can(AbilityAction.Update, 'ViewField', { workspaceId: workspace.id }); can(AbilityAction.Update, 'ViewField', { workspaceId: workspace.id });
return build(); return build();

View File

@ -95,6 +95,7 @@ import {
UpdateAttachmentAbilityHandler, UpdateAttachmentAbilityHandler,
} from './handlers/attachment.ability-handler'; } from './handlers/attachment.ability-handler';
import { import {
CreateViewFieldAbilityHandler,
ReadViewFieldAbilityHandler, ReadViewFieldAbilityHandler,
UpdateViewFieldAbilityHandler, UpdateViewFieldAbilityHandler,
} from './handlers/view-field.ability-handler'; } from './handlers/view-field.ability-handler';
@ -184,6 +185,7 @@ import {
DeletePipelineProgressAbilityHandler, DeletePipelineProgressAbilityHandler,
// ViewField // ViewField
ReadViewFieldAbilityHandler, ReadViewFieldAbilityHandler,
CreateViewFieldAbilityHandler,
UpdateViewFieldAbilityHandler, UpdateViewFieldAbilityHandler,
], ],
exports: [ exports: [
@ -268,6 +270,7 @@ import {
DeletePipelineProgressAbilityHandler, DeletePipelineProgressAbilityHandler,
// ViewField // ViewField
ReadViewFieldAbilityHandler, ReadViewFieldAbilityHandler,
CreateViewFieldAbilityHandler,
UpdateViewFieldAbilityHandler, UpdateViewFieldAbilityHandler,
], ],
}) })

View File

@ -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() @Injectable()
export class UpdateViewFieldAbilityHandler implements IAbilityHandler { export class UpdateViewFieldAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {} constructor(private readonly prismaService: PrismaService) {}

View File

@ -2,17 +2,21 @@ import { UseGuards } from '@nestjs/common';
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { accessibleBy } from '@casl/prisma'; import { accessibleBy } from '@casl/prisma';
import { Prisma } from '@prisma/client'; import { Prisma, Workspace } from '@prisma/client';
import { AppAbility } from 'src/ability/ability.factory'; import { AppAbility } from 'src/ability/ability.factory';
import { import {
CreateViewFieldAbilityHandler,
ReadViewFieldAbilityHandler, ReadViewFieldAbilityHandler,
UpdateViewFieldAbilityHandler, UpdateViewFieldAbilityHandler,
} from 'src/ability/handlers/view-field.ability-handler'; } 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 { 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 { UpdateOneViewFieldArgs } from 'src/core/@generated/view-field/update-one-view-field.args';
import { ViewField } from 'src/core/@generated/view-field/view-field.model'; import { ViewField } from 'src/core/@generated/view-field/view-field.model';
import { ViewFieldService } from 'src/core/view/services/view-field.service'; 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 { CheckAbilities } from 'src/decorators/check-abilities.decorator';
import { import {
PrismaSelect, PrismaSelect,
@ -27,6 +31,21 @@ import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
export class ViewFieldResolver { export class ViewFieldResolver {
constructor(private readonly viewFieldService: ViewFieldService) {} constructor(private readonly viewFieldService: ViewFieldService) {}
@Mutation(() => AffectedRows)
@UseGuards(AbilityGuard)
@CheckAbilities(CreateViewFieldAbilityHandler)
async createManyViewField(
@Args() args: CreateManyViewFieldArgs,
@AuthWorkspace() workspace: Workspace,
): Promise<Prisma.BatchPayload> {
return this.viewFieldService.createMany({
data: args.data.map((dataItem) => ({
...dataItem,
workspaceId: workspace.id,
})),
});
}
@Query(() => [ViewField]) @Query(() => [ViewField])
@UseGuards(AbilityGuard) @UseGuards(AbilityGuard)
@CheckAbilities(ReadViewFieldAbilityHandler) @CheckAbilities(ReadViewFieldAbilityHandler)