mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-18 00:52:21 +03:00
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:
parent
552fb2378b
commit
3807d62aeb
@ -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<ViewFieldCreateManyInput>;
|
||||
skipDuplicates?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateOneActivityArgs = {
|
||||
data: ActivityCreateInput;
|
||||
};
|
||||
@ -2076,6 +2083,15 @@ export type ViewField = {
|
||||
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 = {
|
||||
fieldName?: 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 CreateViewFieldsMutationVariables = Exact<{
|
||||
data: Array<ViewFieldCreateManyInput> | ViewFieldCreateManyInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateViewFieldsMutation = { __typename?: 'Mutation', createManyViewField: { __typename?: 'AffectedRows', count: number } };
|
||||
|
||||
export type GetViewFieldsQueryVariables = Exact<{
|
||||
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 DeleteUserAccountMutationResult = Apollo.MutationResult<DeleteUserAccountMutation>;
|
||||
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`
|
||||
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'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
@ -35,11 +35,12 @@ export function CompanyTable() {
|
||||
return (
|
||||
<>
|
||||
<GenericEntityTableData
|
||||
objectName="company"
|
||||
getRequestResultKey="companies"
|
||||
useGetRequest={useGetCompaniesQuery}
|
||||
orderBy={orderBy}
|
||||
whereFilters={whereFilters}
|
||||
viewFields={companyViewFields}
|
||||
viewFieldDefinitions={companyViewFields}
|
||||
filterDefinitionArray={companiesFilters}
|
||||
/>
|
||||
<EntityTable
|
||||
|
@ -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 { entityTableDimensionsState } from '@/ui/table/states/entityTableDimensionsState';
|
||||
import { viewFieldsFamilyState } from '@/ui/table/states/viewFieldsState';
|
||||
|
||||
import { companyViewFields } from '../../constants/companyViewFields';
|
||||
|
||||
import { mockedCompaniesData } from './companies-mock-data';
|
||||
|
||||
export function CompanyTableMockData() {
|
||||
const setEntityTableDimensions = useSetRecoilState(
|
||||
entityTableDimensionsState,
|
||||
);
|
||||
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
|
||||
const setEntityTableData = useSetEntityTableData();
|
||||
|
||||
setEntityTableData(mockedCompaniesData, companyViewFields, []);
|
||||
setEntityTableData(mockedCompaniesData, []);
|
||||
|
||||
useEffect(() => {
|
||||
setViewFields(companyViewFields);
|
||||
setEntityTableDimensions((prevState) => ({
|
||||
...prevState,
|
||||
numberOfColumns: companyViewFields.length,
|
||||
}));
|
||||
}, [setEntityTableDimensions, setViewFields]);
|
||||
|
||||
return <></>;
|
||||
}
|
||||
|
@ -36,11 +36,12 @@ export function PeopleTable() {
|
||||
return (
|
||||
<>
|
||||
<GenericEntityTableData
|
||||
objectName="person"
|
||||
getRequestResultKey="people"
|
||||
useGetRequest={useGetPeopleQuery}
|
||||
orderBy={orderBy}
|
||||
whereFilters={whereFilters}
|
||||
viewFields={peopleViewFields}
|
||||
viewFieldDefinitions={peopleViewFields}
|
||||
filterDefinitionArray={peopleFilters}
|
||||
/>
|
||||
<EntityTable
|
||||
|
@ -1,9 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
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 { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { useUpdateViewFieldMutation } from '~/generated/graphql';
|
||||
|
||||
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
|
||||
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
|
||||
@ -102,8 +103,11 @@ export function EntityTable<SortField>({
|
||||
useUpdateEntityMutation,
|
||||
}: OwnProps<SortField>) {
|
||||
const viewFields = useRecoilValue(viewFieldsFamilyState);
|
||||
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
|
||||
|
||||
const tableBodyRef = React.useRef<HTMLDivElement>(null);
|
||||
const [updateViewFieldMutation] = useUpdateViewFieldMutation();
|
||||
|
||||
const tableBodyRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
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 (
|
||||
<EntityUpdateMutationHookContext.Provider value={useUpdateEntityMutation}>
|
||||
<StyledTableWithHeader>
|
||||
@ -129,7 +152,10 @@ export function EntityTable<SortField>({
|
||||
<StyledTableWrapper>
|
||||
{viewFields.length > 0 && (
|
||||
<StyledTable>
|
||||
<EntityTableHeader viewFields={viewFields} />
|
||||
<EntityTableHeader
|
||||
onColumnResize={handleColumnResize}
|
||||
viewFields={viewFields}
|
||||
/>
|
||||
<EntityTableBody />
|
||||
</StyledTable>
|
||||
)}
|
||||
|
@ -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<ViewFieldMetadata>[];
|
||||
};
|
||||
|
||||
export function EntityTableHeader({ viewFields }: OwnProps) {
|
||||
const initialColumnWidths = viewFields.reduce<Record<string, number>>(
|
||||
(result, viewField) => ({
|
||||
...result,
|
||||
[viewField.id]: viewField.columnSize,
|
||||
}),
|
||||
{},
|
||||
export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) {
|
||||
const columnWidths = useMemo(
|
||||
() =>
|
||||
viewFields.reduce<Record<string, number>>(
|
||||
(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 (
|
||||
<thead>
|
||||
|
@ -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<ViewFieldMetadata>[];
|
||||
viewFieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
|
81
front/src/modules/ui/table/hooks/useLoadView.ts
Normal file
81
front/src/modules/ui/table/hooks/useLoadView.ts
Normal 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) ?? ''],
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
@ -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 }) =>
|
||||
<T extends { id: string }>(
|
||||
newEntityArray: T[],
|
||||
viewFields: ViewFieldDefinition<ViewFieldMetadata>[],
|
||||
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);
|
||||
},
|
||||
[],
|
||||
|
9
front/src/modules/views/queries/create.ts
Normal file
9
front/src/modules/views/queries/create.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const CREATE_VIEW_FIELDS = gql`
|
||||
mutation CreateViewFields($data: [ViewFieldCreateManyInput!]!) {
|
||||
createManyViewField(data: $data) {
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
@ -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
|
||||
|
@ -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,
|
||||
}),
|
||||
);
|
||||
}),
|
||||
];
|
||||
|
@ -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<MockedCompany> = [
|
||||
__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,
|
||||
}),
|
||||
);
|
||||
|
@ -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> = {
|
||||
[P in keyof T]-?: Exclude<T[P], null | undefined>;
|
||||
@ -116,3 +117,15 @@ export const mockedPeopleData: MockedPerson[] = [
|
||||
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,
|
||||
}),
|
||||
);
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
],
|
||||
})
|
||||
|
@ -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) {}
|
||||
|
@ -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<Prisma.BatchPayload> {
|
||||
return this.viewFieldService.createMany({
|
||||
data: args.data.map((dataItem) => ({
|
||||
...dataItem,
|
||||
workspaceId: workspace.id,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => [ViewField])
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadViewFieldAbilityHandler)
|
||||
|
Loading…
Reference in New Issue
Block a user