mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
fix: move blocks logic
This commit is contained in:
parent
a864816c34
commit
e0a7a783f9
@ -255,6 +255,28 @@ export class BlockController extends BaseController<
|
|||||||
return await this.blockService.create(block);
|
return await this.blockService.create(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates multiple blocks by their IDs.
|
||||||
|
* @param ids - IDs of blocks to be updated.
|
||||||
|
* @param payload - The data to update blocks with.
|
||||||
|
* @returns A Promise that resolves to the updates if successful.
|
||||||
|
*/
|
||||||
|
@CsrfCheck(true)
|
||||||
|
@Patch('bulk')
|
||||||
|
async updateMany(@Body() body: { ids: string[]; payload: BlockUpdateDto }) {
|
||||||
|
if (!body.ids || body.ids.length === 0) {
|
||||||
|
throw new BadRequestException('No IDs provided for ...');
|
||||||
|
}
|
||||||
|
const updates = await this.blockService.updateMany(
|
||||||
|
{
|
||||||
|
_id: { $in: body.ids },
|
||||||
|
},
|
||||||
|
body.payload,
|
||||||
|
);
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a specific block by ID.
|
* Updates a specific block by ID.
|
||||||
*
|
*
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import { Injectable, Optional } from '@nestjs/common';
|
import { Injectable, Optional } from '@nestjs/common';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { InjectModel } from '@nestjs/mongoose';
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
import {
|
import mongoose, {
|
||||||
Document,
|
Document,
|
||||||
Model,
|
Model,
|
||||||
Query,
|
Query,
|
||||||
@ -113,6 +113,88 @@ export class BlockRepository extends BaseRepository<
|
|||||||
this.checkDeprecatedAttachmentUrl(updates);
|
this.checkDeprecatedAttachmentUrl(updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-processing logic for updating blocks.
|
||||||
|
*
|
||||||
|
* @param query - The query to update blocks.
|
||||||
|
* @param criteria - The filter criteria for the update query.
|
||||||
|
* @param updates - The update data.
|
||||||
|
*/
|
||||||
|
async preUpdateMany(
|
||||||
|
_query: Query<
|
||||||
|
Document<Block, any, any>,
|
||||||
|
Document<Block, any, any>,
|
||||||
|
unknown,
|
||||||
|
Block,
|
||||||
|
'updateMany',
|
||||||
|
Record<string, never>
|
||||||
|
>,
|
||||||
|
_criteria: TFilterQuery<Block>,
|
||||||
|
_updates: UpdateQuery<Document<Block, any, any>>,
|
||||||
|
): Promise<void> {
|
||||||
|
const ids: string[] = _criteria._id?.$in || [];
|
||||||
|
const objIds = ids.map((b) => {
|
||||||
|
return new mongoose.Types.ObjectId(b);
|
||||||
|
});
|
||||||
|
const category: string = _updates.$set.category;
|
||||||
|
const objCategory = new mongoose.Types.ObjectId(category);
|
||||||
|
const otherBlocks = await this.model.find({
|
||||||
|
_id: { $nin: objIds },
|
||||||
|
category: { $ne: objCategory },
|
||||||
|
$or: [
|
||||||
|
{ attachedBlock: { $in: objIds } },
|
||||||
|
{ nextBlocks: { $in: objIds } },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
const oldState = await this.model.findOne({
|
||||||
|
_id: new mongoose.Types.ObjectId(id),
|
||||||
|
});
|
||||||
|
if (oldState.category.toString() !== category) {
|
||||||
|
const updatedNextBlocks = oldState.nextBlocks.filter((nextBlock) =>
|
||||||
|
ids.includes(nextBlock.toString()),
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedAttachedBlock = ids.includes(
|
||||||
|
oldState.attachedBlock?.toString() || '',
|
||||||
|
)
|
||||||
|
? oldState.attachedBlock
|
||||||
|
: null;
|
||||||
|
|
||||||
|
await this.model.updateOne(
|
||||||
|
{ _id: new mongoose.Types.ObjectId(id) },
|
||||||
|
{
|
||||||
|
nextBlocks: updatedNextBlocks,
|
||||||
|
attachedBlock: updatedAttachedBlock,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const block of otherBlocks) {
|
||||||
|
if (ids.includes(block.attachedBlock?.toString())) {
|
||||||
|
await this.model.updateOne(
|
||||||
|
{ _id: block.id },
|
||||||
|
{
|
||||||
|
attachedBlock: null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (block.nextBlocks.some((item) => ids.includes(item.toString()))) {
|
||||||
|
const updatedNextBlocks = block.nextBlocks.filter(
|
||||||
|
(nextBlock) => !ids.includes(nextBlock.toString()),
|
||||||
|
);
|
||||||
|
await this.model.updateOne(
|
||||||
|
{ _id: block.id },
|
||||||
|
{
|
||||||
|
nextBlocks: updatedNextBlocks,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post-processing logic after deleting a block.
|
* Post-processing logic after deleting a block.
|
||||||
*
|
*
|
||||||
|
@ -38,10 +38,12 @@ export type DeleteResult = {
|
|||||||
export enum EHook {
|
export enum EHook {
|
||||||
preCreate = 'preCreate',
|
preCreate = 'preCreate',
|
||||||
preUpdate = 'preUpdate',
|
preUpdate = 'preUpdate',
|
||||||
|
preUpdateMany = 'preUpdateMany',
|
||||||
preDelete = 'preDelete',
|
preDelete = 'preDelete',
|
||||||
preValidate = 'preValidate',
|
preValidate = 'preValidate',
|
||||||
postCreate = 'postCreate',
|
postCreate = 'postCreate',
|
||||||
postUpdate = 'postUpdate',
|
postUpdate = 'postUpdate',
|
||||||
|
postUpdateMany = 'postUpdateMany',
|
||||||
postDelete = 'postDelete',
|
postDelete = 'postDelete',
|
||||||
postValidate = 'postValidate',
|
postValidate = 'postValidate',
|
||||||
}
|
}
|
||||||
@ -157,6 +159,19 @@ export abstract class BaseRepository<
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
hooks?.updateMany.pre.execute(async function () {
|
||||||
|
const query = this as Query<D, D, unknown, T, 'updateMany'>;
|
||||||
|
const criteria = query.getFilter();
|
||||||
|
const updates = query.getUpdate();
|
||||||
|
|
||||||
|
await repository.preUpdateMany(query, criteria, updates);
|
||||||
|
repository.emitter.emit(
|
||||||
|
repository.getEventName(EHook.preUpdateMany),
|
||||||
|
criteria,
|
||||||
|
updates?.['$set'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
hooks?.findOneAndUpdate.post.execute(async function (
|
hooks?.findOneAndUpdate.post.execute(async function (
|
||||||
updated: HydratedDocument<T>,
|
updated: HydratedDocument<T>,
|
||||||
) {
|
) {
|
||||||
@ -375,6 +390,14 @@ export abstract class BaseRepository<
|
|||||||
// Nothing ...
|
// Nothing ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async preUpdateMany(
|
||||||
|
_query: Query<D, D, unknown, T, 'updateMany'>,
|
||||||
|
_criteria: TFilterQuery<T>,
|
||||||
|
_updates: UpdateWithAggregationPipeline | UpdateQuery<D>,
|
||||||
|
) {
|
||||||
|
// Nothing ...
|
||||||
|
}
|
||||||
|
|
||||||
async postUpdate(
|
async postUpdate(
|
||||||
_query: Query<D, D, unknown, T, 'findOneAndUpdate'>,
|
_query: Query<D, D, unknown, T, 'findOneAndUpdate'>,
|
||||||
_updated: T,
|
_updated: T,
|
||||||
|
@ -17,7 +17,7 @@ enum LifecycleOperation {
|
|||||||
// InsertMany = 'insertMany',
|
// InsertMany = 'insertMany',
|
||||||
// Update = 'update',
|
// Update = 'update',
|
||||||
// UpdateOne = 'updateOne',
|
// UpdateOne = 'updateOne',
|
||||||
// UpdateMany = 'updateMany',
|
UpdateMany = 'updateMany',
|
||||||
}
|
}
|
||||||
|
|
||||||
type PreHook = (...args: any[]) => void;
|
type PreHook = (...args: any[]) => void;
|
||||||
@ -69,7 +69,7 @@ export class LifecycleHookManager {
|
|||||||
// insertMany: ['pre'],
|
// insertMany: ['pre'],
|
||||||
// update: ['pre', 'post'],
|
// update: ['pre', 'post'],
|
||||||
// updateOne: ['pre', 'post'],
|
// updateOne: ['pre', 'post'],
|
||||||
// updateMany: ['pre', 'post'],
|
updateMany: ['pre', 'post'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const lifecycleHooks: LifecycleHooks = {} as LifecycleHooks;
|
const lifecycleHooks: LifecycleHooks = {} as LifecycleHooks;
|
||||||
|
@ -47,6 +47,7 @@ import { useDelete, useDeleteFromCache } from "@/hooks/crud/useDelete";
|
|||||||
import { useFind } from "@/hooks/crud/useFind";
|
import { useFind } from "@/hooks/crud/useFind";
|
||||||
import { useGetFromCache } from "@/hooks/crud/useGet";
|
import { useGetFromCache } from "@/hooks/crud/useGet";
|
||||||
import { useUpdate, useUpdateCache } from "@/hooks/crud/useUpdate";
|
import { useUpdate, useUpdateCache } from "@/hooks/crud/useUpdate";
|
||||||
|
import { useUpdateMany } from "@/hooks/crud/useUpdateMany";
|
||||||
import useDebouncedUpdate from "@/hooks/useDebouncedUpdate";
|
import useDebouncedUpdate from "@/hooks/useDebouncedUpdate";
|
||||||
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
|
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
|
||||||
import { useSearch } from "@/hooks/useSearch";
|
import { useSearch } from "@/hooks/useSearch";
|
||||||
@ -73,6 +74,7 @@ const Diagrams = () => {
|
|||||||
const deleteDialogCtl = useDialog<string>(false);
|
const deleteDialogCtl = useDialog<string>(false);
|
||||||
const moveDialogCtl = useDialog<string[] | string>(false);
|
const moveDialogCtl = useDialog<string[] | string>(false);
|
||||||
const addCategoryDialogCtl = useDialog<ICategory>(false);
|
const addCategoryDialogCtl = useDialog<ICategory>(false);
|
||||||
|
const { mutateAsync: updateBlocks } = useUpdateMany(EntityType.BLOCK);
|
||||||
const {
|
const {
|
||||||
buildDiagram,
|
buildDiagram,
|
||||||
setViewerZoom,
|
setViewerZoom,
|
||||||
@ -451,38 +453,16 @@ const Diagrams = () => {
|
|||||||
|
|
||||||
const ids = moveDialogCtl?.data;
|
const ids = moveDialogCtl?.data;
|
||||||
|
|
||||||
if (ids) {
|
if (ids?.length && Array.isArray(ids)) {
|
||||||
for (const blockId of ids) {
|
await updateBlocks({ ids, payload: { category: newCategoryId } });
|
||||||
const block = getBlockFromCache(blockId);
|
|
||||||
const updatedNextBlocks = block?.nextBlocks?.filter((nextBlockId) =>
|
|
||||||
ids.includes(nextBlockId),
|
|
||||||
);
|
|
||||||
const updatedAttachedBlock = ids.includes(
|
|
||||||
block?.attachedBlock as string,
|
|
||||||
)
|
|
||||||
? block?.attachedBlock
|
|
||||||
: null;
|
|
||||||
|
|
||||||
await updateBlock({
|
queryClient.invalidateQueries({
|
||||||
id: blockId,
|
|
||||||
params: {
|
|
||||||
category: newCategoryId,
|
|
||||||
nextBlocks: updatedNextBlocks,
|
|
||||||
attachedBlock: updatedAttachedBlock,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
queryClient.removeQueries({
|
|
||||||
predicate: ({ queryKey }) => {
|
predicate: ({ queryKey }) => {
|
||||||
const [qType, qEntity, qId] = queryKey;
|
const [qType, qEntity] = queryKey;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(qType === QueryType.collection &&
|
qType === QueryType.collection &&
|
||||||
isSameEntity(qEntity, EntityType.BLOCK) &&
|
isSameEntity(qEntity, EntityType.BLOCK)
|
||||||
qId === selectedCategoryId) ||
|
|
||||||
(isSameEntity(qEntity, EntityType.CATEGORY) &&
|
|
||||||
qId === selectedCategoryId)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
83
frontend/src/hooks/crud/useUpdateMany.tsx
Normal file
83
frontend/src/hooks/crud/useUpdateMany.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useMutation, useQueryClient } from "react-query";
|
||||||
|
|
||||||
|
import { QueryType, TMutationOptions } from "@/services/types";
|
||||||
|
import { IBaseSchema, IDynamicProps, TType } from "@/types/base.types";
|
||||||
|
|
||||||
|
import { useEntityApiClient } from "../useApiClient";
|
||||||
|
|
||||||
|
import { isSameEntity } from "./helpers";
|
||||||
|
|
||||||
|
export const useUpdateMany = <
|
||||||
|
TEntity extends IDynamicProps["entity"],
|
||||||
|
TAttr = TType<TEntity>["attributes"],
|
||||||
|
TBasic extends IBaseSchema = TType<TEntity>["basic"],
|
||||||
|
TFull extends IBaseSchema = TType<TEntity>["full"],
|
||||||
|
>(
|
||||||
|
entity: TEntity,
|
||||||
|
options?: Omit<
|
||||||
|
TMutationOptions<
|
||||||
|
string,
|
||||||
|
Error,
|
||||||
|
{
|
||||||
|
ids: string[];
|
||||||
|
payload: Partial<TAttr>;
|
||||||
|
},
|
||||||
|
TBasic
|
||||||
|
>,
|
||||||
|
"mutationFn" | "mutationKey"
|
||||||
|
> & {
|
||||||
|
invalidate?: boolean;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const api = useEntityApiClient<TAttr, TBasic, TFull>(entity);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { invalidate = true, ...otherOptions } = options || {};
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
ids,
|
||||||
|
payload,
|
||||||
|
}: {
|
||||||
|
ids: string[];
|
||||||
|
payload: Partial<TAttr>;
|
||||||
|
}) => {
|
||||||
|
const result = await api.UpdateMany(ids, payload);
|
||||||
|
|
||||||
|
queryClient.removeQueries({
|
||||||
|
predicate: ({ queryKey }) => {
|
||||||
|
const [qType, qEntity, qId] = queryKey;
|
||||||
|
|
||||||
|
return (
|
||||||
|
qType === QueryType.item &&
|
||||||
|
isSameEntity(qEntity, entity) &&
|
||||||
|
ids.includes(qId as string)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (invalidate) {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
predicate: ({ queryKey }) => {
|
||||||
|
const [qType, qEntity] = queryKey;
|
||||||
|
|
||||||
|
return (
|
||||||
|
(qType === QueryType.count || qType === QueryType.collection) &&
|
||||||
|
isSameEntity(qEntity, entity)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
...otherOptions,
|
||||||
|
});
|
||||||
|
};
|
@ -342,6 +342,23 @@ export class EntityApiClient<TAttr, TBasic, TFull> extends ApiClient {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bulk Update entries.
|
||||||
|
*/
|
||||||
|
async UpdateMany(ids: string[], payload: Partial<TAttr>) {
|
||||||
|
const { _csrf } = await this.getCsrf();
|
||||||
|
const { data } = await this.request.patch<string>(
|
||||||
|
`${ROUTES[this.type]}/bulk`,
|
||||||
|
{
|
||||||
|
_csrf,
|
||||||
|
ids,
|
||||||
|
payload,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete an entry.
|
* Delete an entry.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user