refactor: create/update/delete one view instead of many (#1384)

Closes #1359
This commit is contained in:
Thaïs
2023-08-30 15:35:18 +02:00
committed by GitHub
parent fa33506b96
commit 6eadd1d132
8 changed files with 223 additions and 137 deletions

View File

@ -1004,6 +1004,7 @@ export type Mutation = {
createOneCompany: Company;
createOnePerson: Person;
createOnePipelineProgress: PipelineProgress;
createOneView: View;
createOneViewField: ViewField;
deleteCurrentWorkspace: Workspace;
deleteFavorite: Favorite;
@ -1014,6 +1015,7 @@ export type Mutation = {
deleteManyView: AffectedRows;
deleteManyViewFilter: AffectedRows;
deleteManyViewSort: AffectedRows;
deleteOneView: View;
deleteUserAccount: User;
deleteWorkspaceMember: WorkspaceMember;
impersonate: Verify;
@ -1128,6 +1130,11 @@ export type MutationCreateOnePipelineProgressArgs = {
};
export type MutationCreateOneViewArgs = {
data: ViewCreateInput;
};
export type MutationCreateOneViewFieldArgs = {
data: ViewFieldCreateInput;
};
@ -1173,6 +1180,11 @@ export type MutationDeleteManyViewSortArgs = {
};
export type MutationDeleteOneViewArgs = {
where: ViewWhereUniqueInput;
};
export type MutationDeleteWorkspaceMemberArgs = {
where: WorkspaceMemberWhereUniqueInput;
};
@ -2423,6 +2435,16 @@ export type View = {
type: ViewType;
};
export type ViewCreateInput = {
fields?: InputMaybe<ViewFieldCreateNestedManyWithoutViewInput>;
filters?: InputMaybe<ViewFilterCreateNestedManyWithoutViewInput>;
id?: InputMaybe<Scalars['String']>;
name: Scalars['String'];
objectId: Scalars['String'];
sorts?: InputMaybe<ViewSortCreateNestedManyWithoutViewInput>;
type: ViewType;
};
export type ViewCreateManyInput = {
id?: InputMaybe<Scalars['String']>;
name: Scalars['String'];
@ -2466,6 +2488,10 @@ export type ViewFieldCreateManyInput = {
viewId?: InputMaybe<Scalars['String']>;
};
export type ViewFieldCreateNestedManyWithoutViewInput = {
connect?: InputMaybe<Array<ViewFieldWhereUniqueInput>>;
};
export type ViewFieldListRelationFilter = {
every?: InputMaybe<ViewFieldWhereInput>;
none?: InputMaybe<ViewFieldWhereInput>;
@ -2565,6 +2591,10 @@ export type ViewFilterCreateManyInput = {
viewId: Scalars['String'];
};
export type ViewFilterCreateNestedManyWithoutViewInput = {
connect?: InputMaybe<Array<ViewFilterWhereUniqueInput>>;
};
export type ViewFilterListRelationFilter = {
every?: InputMaybe<ViewFilterWhereInput>;
none?: InputMaybe<ViewFilterWhereInput>;
@ -2686,6 +2716,10 @@ export type ViewSortCreateManyInput = {
viewId: Scalars['String'];
};
export type ViewSortCreateNestedManyWithoutViewInput = {
connect?: InputMaybe<Array<ViewSortWhereUniqueInput>>;
};
export enum ViewSortDirection {
Asc = 'asc',
Desc = 'desc'
@ -3380,6 +3414,13 @@ export type GetUsersQueryVariables = Exact<{ [key: string]: never; }>;
export type GetUsersQuery = { __typename?: 'Query', findManyUser: Array<{ __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null }> };
export type CreateViewMutationVariables = Exact<{
data: ViewCreateInput;
}>;
export type CreateViewMutation = { __typename?: 'Mutation', view: { __typename?: 'View', id: string, name: string } };
export type CreateViewFieldsMutationVariables = Exact<{
data: Array<ViewFieldCreateManyInput> | ViewFieldCreateManyInput;
}>;
@ -3401,12 +3442,12 @@ export type CreateViewSortsMutationVariables = Exact<{
export type CreateViewSortsMutation = { __typename?: 'Mutation', createManyViewSort: { __typename?: 'AffectedRows', count: number } };
export type CreateViewsMutationVariables = Exact<{
data: Array<ViewCreateManyInput> | ViewCreateManyInput;
export type DeleteViewMutationVariables = Exact<{
where: ViewWhereUniqueInput;
}>;
export type CreateViewsMutation = { __typename?: 'Mutation', createManyView: { __typename?: 'AffectedRows', count: number } };
export type DeleteViewMutation = { __typename?: 'Mutation', view: { __typename?: 'View', id: string, name: string } };
export type DeleteViewFiltersMutationVariables = Exact<{
where: ViewFilterWhereInput;
@ -3422,20 +3463,13 @@ export type DeleteViewSortsMutationVariables = Exact<{
export type DeleteViewSortsMutation = { __typename?: 'Mutation', deleteManyViewSort: { __typename?: 'AffectedRows', count: number } };
export type DeleteViewsMutationVariables = Exact<{
where: ViewWhereInput;
}>;
export type DeleteViewsMutation = { __typename?: 'Mutation', deleteManyView: { __typename?: 'AffectedRows', count: number } };
export type UpdateViewMutationVariables = Exact<{
data: ViewUpdateInput;
where: ViewWhereUniqueInput;
}>;
export type UpdateViewMutation = { __typename?: 'Mutation', updateOneView: { __typename?: 'View', id: string, name: string } };
export type UpdateViewMutation = { __typename?: 'Mutation', view: { __typename?: 'View', id: string, name: string } };
export type UpdateViewFieldMutationVariables = Exact<{
data: ViewFieldUpdateInput;
@ -6108,6 +6142,40 @@ export function useGetUsersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<G
export type GetUsersQueryHookResult = ReturnType<typeof useGetUsersQuery>;
export type GetUsersLazyQueryHookResult = ReturnType<typeof useGetUsersLazyQuery>;
export type GetUsersQueryResult = Apollo.QueryResult<GetUsersQuery, GetUsersQueryVariables>;
export const CreateViewDocument = gql`
mutation CreateView($data: ViewCreateInput!) {
view: createOneView(data: $data) {
id
name
}
}
`;
export type CreateViewMutationFn = Apollo.MutationFunction<CreateViewMutation, CreateViewMutationVariables>;
/**
* __useCreateViewMutation__
*
* To run a mutation, you first call `useCreateViewMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateViewMutation` 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 [createViewMutation, { data, loading, error }] = useCreateViewMutation({
* variables: {
* data: // value for 'data'
* },
* });
*/
export function useCreateViewMutation(baseOptions?: Apollo.MutationHookOptions<CreateViewMutation, CreateViewMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateViewMutation, CreateViewMutationVariables>(CreateViewDocument, options);
}
export type CreateViewMutationHookResult = ReturnType<typeof useCreateViewMutation>;
export type CreateViewMutationResult = Apollo.MutationResult<CreateViewMutation>;
export type CreateViewMutationOptions = Apollo.BaseMutationOptions<CreateViewMutation, CreateViewMutationVariables>;
export const CreateViewFieldsDocument = gql`
mutation CreateViewFields($data: [ViewFieldCreateManyInput!]!) {
createManyViewField(data: $data) {
@ -6207,39 +6275,40 @@ export function useCreateViewSortsMutation(baseOptions?: Apollo.MutationHookOpti
export type CreateViewSortsMutationHookResult = ReturnType<typeof useCreateViewSortsMutation>;
export type CreateViewSortsMutationResult = Apollo.MutationResult<CreateViewSortsMutation>;
export type CreateViewSortsMutationOptions = Apollo.BaseMutationOptions<CreateViewSortsMutation, CreateViewSortsMutationVariables>;
export const CreateViewsDocument = gql`
mutation CreateViews($data: [ViewCreateManyInput!]!) {
createManyView(data: $data) {
count
export const DeleteViewDocument = gql`
mutation DeleteView($where: ViewWhereUniqueInput!) {
view: deleteOneView(where: $where) {
id
name
}
}
`;
export type CreateViewsMutationFn = Apollo.MutationFunction<CreateViewsMutation, CreateViewsMutationVariables>;
export type DeleteViewMutationFn = Apollo.MutationFunction<DeleteViewMutation, DeleteViewMutationVariables>;
/**
* __useCreateViewsMutation__
* __useDeleteViewMutation__
*
* To run a mutation, you first call `useCreateViewsMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateViewsMutation` returns a tuple that includes:
* To run a mutation, you first call `useDeleteViewMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteViewMutation` 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 [createViewsMutation, { data, loading, error }] = useCreateViewsMutation({
* const [deleteViewMutation, { data, loading, error }] = useDeleteViewMutation({
* variables: {
* data: // value for 'data'
* where: // value for 'where'
* },
* });
*/
export function useCreateViewsMutation(baseOptions?: Apollo.MutationHookOptions<CreateViewsMutation, CreateViewsMutationVariables>) {
export function useDeleteViewMutation(baseOptions?: Apollo.MutationHookOptions<DeleteViewMutation, DeleteViewMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateViewsMutation, CreateViewsMutationVariables>(CreateViewsDocument, options);
return Apollo.useMutation<DeleteViewMutation, DeleteViewMutationVariables>(DeleteViewDocument, options);
}
export type CreateViewsMutationHookResult = ReturnType<typeof useCreateViewsMutation>;
export type CreateViewsMutationResult = Apollo.MutationResult<CreateViewsMutation>;
export type CreateViewsMutationOptions = Apollo.BaseMutationOptions<CreateViewsMutation, CreateViewsMutationVariables>;
export type DeleteViewMutationHookResult = ReturnType<typeof useDeleteViewMutation>;
export type DeleteViewMutationResult = Apollo.MutationResult<DeleteViewMutation>;
export type DeleteViewMutationOptions = Apollo.BaseMutationOptions<DeleteViewMutation, DeleteViewMutationVariables>;
export const DeleteViewFiltersDocument = gql`
mutation DeleteViewFilters($where: ViewFilterWhereInput!) {
deleteManyViewFilter(where: $where) {
@ -6306,42 +6375,9 @@ export function useDeleteViewSortsMutation(baseOptions?: Apollo.MutationHookOpti
export type DeleteViewSortsMutationHookResult = ReturnType<typeof useDeleteViewSortsMutation>;
export type DeleteViewSortsMutationResult = Apollo.MutationResult<DeleteViewSortsMutation>;
export type DeleteViewSortsMutationOptions = Apollo.BaseMutationOptions<DeleteViewSortsMutation, DeleteViewSortsMutationVariables>;
export const DeleteViewsDocument = gql`
mutation DeleteViews($where: ViewWhereInput!) {
deleteManyView(where: $where) {
count
}
}
`;
export type DeleteViewsMutationFn = Apollo.MutationFunction<DeleteViewsMutation, DeleteViewsMutationVariables>;
/**
* __useDeleteViewsMutation__
*
* To run a mutation, you first call `useDeleteViewsMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteViewsMutation` 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 [deleteViewsMutation, { data, loading, error }] = useDeleteViewsMutation({
* variables: {
* where: // value for 'where'
* },
* });
*/
export function useDeleteViewsMutation(baseOptions?: Apollo.MutationHookOptions<DeleteViewsMutation, DeleteViewsMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<DeleteViewsMutation, DeleteViewsMutationVariables>(DeleteViewsDocument, options);
}
export type DeleteViewsMutationHookResult = ReturnType<typeof useDeleteViewsMutation>;
export type DeleteViewsMutationResult = Apollo.MutationResult<DeleteViewsMutation>;
export type DeleteViewsMutationOptions = Apollo.BaseMutationOptions<DeleteViewsMutation, DeleteViewsMutationVariables>;
export const UpdateViewDocument = gql`
mutation UpdateView($data: ViewUpdateInput!, $where: ViewWhereUniqueInput!) {
updateOneView(data: $data, where: $where) {
view: updateOneView(data: $data, where: $where) {
id
name
}

View File

@ -0,0 +1,10 @@
import { gql } from '@apollo/client';
export const CREATE_VIEW = gql`
mutation CreateView($data: ViewCreateInput!) {
view: createOneView(data: $data) {
id
name
}
}
`;

View File

@ -1,9 +0,0 @@
import { gql } from '@apollo/client';
export const CREATE_VIEWS = gql`
mutation CreateViews($data: [ViewCreateManyInput!]!) {
createManyView(data: $data) {
count
}
}
`;

View File

@ -0,0 +1,10 @@
import { gql } from '@apollo/client';
export const DELETE_VIEW = gql`
mutation DeleteView($where: ViewWhereUniqueInput!) {
view: deleteOneView(where: $where) {
id
name
}
}
`;

View File

@ -1,9 +0,0 @@
import { gql } from '@apollo/client';
export const DELETE_VIEWS = gql`
mutation DeleteViews($where: ViewWhereInput!) {
deleteManyView(where: $where) {
count
}
}
`;

View File

@ -2,7 +2,7 @@ import { gql } from '@apollo/client';
export const UPDATE_VIEW = gql`
mutation UpdateView($data: ViewUpdateInput!, $where: ViewWhereUniqueInput!) {
updateOneView(data: $data, where: $where) {
view: updateOneView(data: $data, where: $where) {
id
name
}

View File

@ -10,8 +10,8 @@ import {
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import {
useCreateViewsMutation,
useDeleteViewsMutation,
useCreateViewMutation,
useDeleteViewMutation,
useGetViewsQuery,
useUpdateViewMutation,
ViewType,
@ -38,60 +38,42 @@ export const useViews = ({
TableRecoilScopeContext,
);
const [createViewsMutation] = useCreateViewsMutation();
const [createViewMutation] = useCreateViewMutation();
const [updateViewMutation] = useUpdateViewMutation();
const [deleteViewsMutation] = useDeleteViewsMutation();
const [deleteViewMutation] = useDeleteViewMutation();
const createViews = useCallback(
async (views: TableView[]) => {
if (!views.length) return;
await createViewsMutation({
const createView = useCallback(
async (view: TableView) => {
const { data } = await createViewMutation({
variables: {
data: views.map((view) => ({
data: {
...view,
objectId,
type: ViewType.Table,
})),
},
});
await Promise.all(views.map((view) => onViewCreate(view.id)));
},
[createViewsMutation, objectId, onViewCreate],
);
const updateViews = useCallback(
(views: TableView[]) => {
if (!views.length) return;
return Promise.all(
views.map((view) =>
updateViewMutation({
variables: {
data: { name: view.name },
where: { id: view.id },
},
}),
),
);
},
[updateViewMutation],
);
const deleteViews = useCallback(
(viewIds: string[]) => {
if (!viewIds.length) return;
return deleteViewsMutation({
variables: {
where: {
id: { in: viewIds },
},
},
});
if (data?.view) await onViewCreate(data.view.id);
},
[deleteViewsMutation],
[createViewMutation, objectId, onViewCreate],
);
const updateView = useCallback(
(view: TableView) =>
updateViewMutation({
variables: {
data: { name: view.name },
where: { id: view.id },
},
}),
[updateViewMutation],
);
const deleteView = useCallback(
(viewId: string) =>
deleteViewMutation({ variables: { where: { id: viewId } } }),
[deleteViewMutation],
);
const { refetch } = useGetViewsQuery({
@ -114,27 +96,33 @@ export const useViews = ({
const handleViewsChange = useCallback(
async (nextViews: TableView[]) => {
const viewsToCreate = nextViews.filter(
const viewToCreate = nextViews.find(
(nextView) => !viewsById[nextView.id],
);
await createViews(viewsToCreate);
if (viewToCreate) {
await createView(viewToCreate);
return refetch();
}
const viewsToUpdate = nextViews.filter(
const viewToUpdate = nextViews.find(
(nextView) =>
viewsById[nextView.id] &&
viewsById[nextView.id].name !== nextView.name,
);
await updateViews(viewsToUpdate);
if (viewToUpdate) {
await updateView(viewToUpdate);
return refetch();
}
const nextViewIds = nextViews.map((nextView) => nextView.id);
const viewIdsToDelete = Object.keys(viewsById).filter(
const viewIdToDelete = Object.keys(viewsById).find(
(previousViewId) => !nextViewIds.includes(previousViewId),
);
await deleteViews(viewIdsToDelete);
if (viewIdToDelete) await deleteView(viewIdToDelete);
return refetch();
},
[createViews, deleteViews, refetch, updateViews, viewsById],
[createView, deleteView, refetch, updateView, viewsById],
);
return { handleViewsChange };

View File

@ -1,6 +1,7 @@
import {
BadRequestException,
ForbiddenException,
NotFoundException,
UseGuards,
} from '@nestjs/common';
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
@ -15,9 +16,16 @@ import {
ReadViewAbilityHandler,
UpdateViewAbilityHandler,
} from 'src/ability/handlers/view.ability-handler';
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
import { CreateManyViewArgs } from 'src/core/@generated/view/create-many-view.args';
import { CreateOneViewArgs } from 'src/core/@generated/view/create-one-view.args';
import { DeleteManyViewArgs } from 'src/core/@generated/view/delete-many-view.args';
import { DeleteOneViewArgs } from 'src/core/@generated/view/delete-one-view.args';
import { FindManyViewArgs } from 'src/core/@generated/view/find-many-view.args';
import { UpdateOneViewArgs } from 'src/core/@generated/view/update-one-view.args';
import { View } from 'src/core/@generated/view/view.model';
import { ViewService } from 'src/core/view/services/view.service';
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
import {
PrismaSelect,
@ -26,17 +34,32 @@ import {
import { UserAbility } from 'src/decorators/user-ability.decorator';
import { AbilityGuard } from 'src/guards/ability.guard';
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
import { UpdateOneViewArgs } from 'src/core/@generated/view/update-one-view.args';
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
import { CreateManyViewArgs } from 'src/core/@generated/view/create-many-view.args';
import { DeleteManyViewArgs } from 'src/core/@generated/view/delete-many-view.args';
@UseGuards(JwtAuthGuard)
@Resolver(() => View)
export class ViewResolver {
constructor(private readonly viewService: ViewService) {}
@Mutation(() => View, {
nullable: false,
})
@UseGuards(AbilityGuard)
@CheckAbilities(CreateViewAbilityHandler)
async createOneView(
@Args() args: CreateOneViewArgs,
@AuthWorkspace() workspace: Workspace,
@PrismaSelector({ modelName: 'View' })
prismaSelect: PrismaSelect<'View'>,
): Promise<Partial<View>> {
return this.viewService.create({
data: {
...args.data,
workspace: { connect: { id: workspace.id } },
},
select: prismaSelect.value,
} as Prisma.ViewCreateArgs);
}
@Mutation(() => AffectedRows)
@UseGuards(AbilityGuard)
@CheckAbilities(CreateViewAbilityHandler)
@ -91,6 +114,43 @@ export class ViewResolver {
} as Prisma.ViewUpdateArgs);
}
@Mutation(() => View, {
nullable: false,
})
@UseGuards(AbilityGuard)
@CheckAbilities(DeleteViewAbilityHandler)
async deleteOneView(
@Args() args: DeleteOneViewArgs,
@AuthWorkspace() workspace: Workspace,
): Promise<View> {
const viewToDelete = await this.viewService.findUnique({
where: args.where,
});
if (!viewToDelete) {
throw new NotFoundException();
}
const { objectId } = viewToDelete;
const viewsNb = await this.viewService.count({
where: {
objectId: { equals: objectId },
workspaceId: { equals: workspace.id },
},
});
if (viewsNb <= 1) {
throw new ForbiddenException(
`Deleting last '${objectId}' view is not allowed`,
);
}
return this.viewService.delete({
where: args.where,
});
}
@Mutation(() => AffectedRows, {
nullable: false,
})