feat: optimistic metadata store updates for navigation menu items (#18710)
## Summary - **Optimistic metadata store updates**: Replace `refetchQueries` with direct `addToDraft`/`applyChanges` calls in create, update, and delete navigation menu item mutation hooks for instant UI feedback. Client-side UUID generation enables optimistic creates before the server responds. - **SSE event enrichment with `targetRecordIdentifier`**: Introduce `NavigationMenuItemRecordIdentifierService` to resolve record display info (label, image) and enrich SSE metadata events at emission time, so the sidebar shows record names immediately without a page refresh. - **Centralized role permission resolution**: Add `resolveRolePermissionConfigFromAuthContext` to `PermissionsService`, removing duplicated role resolution logic from individual services. - **Mutation fragments include `targetRecordIdentifier`**: Switch create/update/delete mutations from `NavigationMenuItemFields` to `NavigationMenuItemQueryFields` so the mutation response includes `targetRecordIdentifier`, preventing a brief gap where RECORD favorites are invisible in the sidebar. - **Folder UI fixes**: Remove transparent border on `StyledFolderContainer` that caused a 1px size inconsistency between folder and non-folder items in Favorites. Make the folder kebab menu hover-only instead of always visible.
This commit is contained in:
File diff suppressed because one or more lines are too long
+3
-3
@@ -1,11 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { NAVIGATION_MENU_ITEM_FRAGMENT } from '@/navigation-menu-item/common/graphql/fragments/navigationMenuItemFragment';
|
||||
import { NAVIGATION_MENU_ITEM_QUERY_FRAGMENT } from '@/navigation-menu-item/common/graphql/fragments/navigationMenuItemQueryFragment';
|
||||
|
||||
export const CREATE_NAVIGATION_MENU_ITEM = gql`
|
||||
${NAVIGATION_MENU_ITEM_FRAGMENT}
|
||||
${NAVIGATION_MENU_ITEM_QUERY_FRAGMENT}
|
||||
mutation CreateNavigationMenuItem($input: CreateNavigationMenuItemInput!) {
|
||||
createNavigationMenuItem(input: $input) {
|
||||
...NavigationMenuItemFields
|
||||
...NavigationMenuItemQueryFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { NAVIGATION_MENU_ITEM_FRAGMENT } from '@/navigation-menu-item/common/graphql/fragments/navigationMenuItemFragment';
|
||||
import { NAVIGATION_MENU_ITEM_QUERY_FRAGMENT } from '@/navigation-menu-item/common/graphql/fragments/navigationMenuItemQueryFragment';
|
||||
|
||||
export const DELETE_NAVIGATION_MENU_ITEM = gql`
|
||||
${NAVIGATION_MENU_ITEM_FRAGMENT}
|
||||
${NAVIGATION_MENU_ITEM_QUERY_FRAGMENT}
|
||||
mutation DeleteNavigationMenuItem($id: UUID!) {
|
||||
deleteNavigationMenuItem(id: $id) {
|
||||
...NavigationMenuItemFields
|
||||
...NavigationMenuItemQueryFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { NAVIGATION_MENU_ITEM_FRAGMENT } from '@/navigation-menu-item/common/graphql/fragments/navigationMenuItemFragment';
|
||||
import { NAVIGATION_MENU_ITEM_QUERY_FRAGMENT } from '@/navigation-menu-item/common/graphql/fragments/navigationMenuItemQueryFragment';
|
||||
|
||||
export const UPDATE_NAVIGATION_MENU_ITEM = gql`
|
||||
${NAVIGATION_MENU_ITEM_FRAGMENT}
|
||||
${NAVIGATION_MENU_ITEM_QUERY_FRAGMENT}
|
||||
mutation UpdateNavigationMenuItem($input: UpdateOneNavigationMenuItemInput!) {
|
||||
updateNavigationMenuItem(input: $input) {
|
||||
...NavigationMenuItemFields
|
||||
...NavigationMenuItemQueryFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
+90
-47
@@ -1,23 +1,47 @@
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { CreateNavigationMenuItemDocument } from '~/generated-metadata/graphql';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import {
|
||||
type CreateNavigationMenuItemInput,
|
||||
CreateNavigationMenuItemDocument,
|
||||
type NavigationMenuItem,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
|
||||
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
|
||||
const buildOptimisticNavigationMenuItem = (
|
||||
input: CreateNavigationMenuItemInput & { id: string },
|
||||
): NavigationMenuItem => ({
|
||||
id: input.id,
|
||||
type: input.type,
|
||||
position: input.position ?? 0,
|
||||
userWorkspaceId: input.userWorkspaceId ?? null,
|
||||
targetRecordId: input.targetRecordId ?? null,
|
||||
targetObjectMetadataId: input.targetObjectMetadataId ?? null,
|
||||
viewId: input.viewId ?? null,
|
||||
folderId: input.folderId ?? null,
|
||||
name: input.name ?? null,
|
||||
link: input.link ?? null,
|
||||
icon: input.icon ?? null,
|
||||
color: input.color ?? null,
|
||||
applicationId: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
export const useCreateNavigationMenuItem = () => {
|
||||
const { navigationMenuItems, currentWorkspaceMemberId } =
|
||||
useNavigationMenuItemsData();
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
|
||||
const { addToDraft, applyChanges } = useMetadataStore();
|
||||
|
||||
const [createNavigationMenuItemMutation] = useMutation(
|
||||
CreateNavigationMenuItemDocument,
|
||||
{
|
||||
refetchQueries: ['FindManyNavigationMenuItems'],
|
||||
},
|
||||
);
|
||||
|
||||
const createNavigationMenuItem = async (
|
||||
@@ -26,31 +50,48 @@ export const useCreateNavigationMenuItem = () => {
|
||||
folderId?: string,
|
||||
) => {
|
||||
const isView = targetObjectNameSingular === 'view';
|
||||
const id = uuidv4();
|
||||
|
||||
const relevantItems = folderId
|
||||
? navigationMenuItems.filter((item) => item.folderId === folderId)
|
||||
: navigationMenuItems.filter(
|
||||
(item) =>
|
||||
!isDefined(item.folderId) && isDefined(item.userWorkspaceId),
|
||||
);
|
||||
|
||||
const maxPosition = Math.max(
|
||||
...relevantItems.map((item) => item.position),
|
||||
0,
|
||||
);
|
||||
|
||||
const position = maxPosition + 1;
|
||||
|
||||
if (isView) {
|
||||
const relevantItems = folderId
|
||||
? navigationMenuItems.filter((item) => item.folderId === folderId)
|
||||
: navigationMenuItems.filter(
|
||||
(item) =>
|
||||
!isDefined(item.folderId) && isDefined(item.userWorkspaceId),
|
||||
);
|
||||
const input: CreateNavigationMenuItemInput = {
|
||||
id,
|
||||
type: NavigationMenuItemType.VIEW,
|
||||
viewId: targetRecord.id,
|
||||
userWorkspaceId: currentWorkspaceMemberId,
|
||||
folderId,
|
||||
position,
|
||||
};
|
||||
|
||||
const maxPosition = Math.max(
|
||||
...relevantItems.map((item) => item.position),
|
||||
0,
|
||||
);
|
||||
|
||||
await createNavigationMenuItemMutation({
|
||||
variables: {
|
||||
input: {
|
||||
type: NavigationMenuItemType.VIEW,
|
||||
viewId: targetRecord.id,
|
||||
userWorkspaceId: currentWorkspaceMemberId,
|
||||
folderId,
|
||||
position: maxPosition + 1,
|
||||
},
|
||||
},
|
||||
addToDraft({
|
||||
key: 'navigationMenuItems',
|
||||
items: [buildOptimisticNavigationMenuItem({ ...input, id })],
|
||||
});
|
||||
applyChanges();
|
||||
|
||||
const result = await createNavigationMenuItemMutation({
|
||||
variables: { input },
|
||||
});
|
||||
|
||||
const created = result.data?.createNavigationMenuItem;
|
||||
|
||||
if (isDefined(created)) {
|
||||
addToDraft({ key: 'navigationMenuItems', items: [created] });
|
||||
applyChanges();
|
||||
}
|
||||
} else {
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === targetObjectNameSingular,
|
||||
@@ -62,30 +103,32 @@ export const useCreateNavigationMenuItem = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const relevantItems = folderId
|
||||
? navigationMenuItems.filter((item) => item.folderId === folderId)
|
||||
: navigationMenuItems.filter(
|
||||
(item) =>
|
||||
!isDefined(item.folderId) && isDefined(item.userWorkspaceId),
|
||||
);
|
||||
const input: CreateNavigationMenuItemInput = {
|
||||
id,
|
||||
type: NavigationMenuItemType.RECORD,
|
||||
targetRecordId: targetRecord.id,
|
||||
targetObjectMetadataId: objectMetadataItem.id,
|
||||
userWorkspaceId: currentWorkspaceMemberId,
|
||||
folderId,
|
||||
position,
|
||||
};
|
||||
|
||||
const maxPosition = Math.max(
|
||||
...relevantItems.map((item) => item.position),
|
||||
0,
|
||||
);
|
||||
|
||||
await createNavigationMenuItemMutation({
|
||||
variables: {
|
||||
input: {
|
||||
type: NavigationMenuItemType.RECORD,
|
||||
targetRecordId: targetRecord.id,
|
||||
targetObjectMetadataId: objectMetadataItem.id,
|
||||
userWorkspaceId: currentWorkspaceMemberId,
|
||||
folderId,
|
||||
position: maxPosition + 1,
|
||||
},
|
||||
},
|
||||
addToDraft({
|
||||
key: 'navigationMenuItems',
|
||||
items: [buildOptimisticNavigationMenuItem({ ...input, id })],
|
||||
});
|
||||
applyChanges();
|
||||
|
||||
const result = await createNavigationMenuItemMutation({
|
||||
variables: { input },
|
||||
});
|
||||
|
||||
const created = result.data?.createNavigationMenuItem;
|
||||
|
||||
if (isDefined(created)) {
|
||||
addToDraft({ key: 'navigationMenuItems', items: [created] });
|
||||
applyChanges();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+7
-3
@@ -1,15 +1,19 @@
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { DeleteNavigationMenuItemDocument } from '~/generated-metadata/graphql';
|
||||
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
|
||||
export const useDeleteNavigationMenuItem = () => {
|
||||
const { removeFromDraft, applyChanges } = useMetadataStore();
|
||||
|
||||
const [deleteNavigationMenuItemMutation] = useMutation(
|
||||
DeleteNavigationMenuItemDocument,
|
||||
{
|
||||
refetchQueries: ['FindManyNavigationMenuItems'],
|
||||
},
|
||||
);
|
||||
|
||||
const deleteNavigationMenuItem = async (id: string) => {
|
||||
removeFromDraft({ key: 'navigationMenuItems', itemIds: [id] });
|
||||
applyChanges();
|
||||
|
||||
await deleteNavigationMenuItemMutation({
|
||||
variables: { id },
|
||||
});
|
||||
|
||||
+21
-5
@@ -1,31 +1,47 @@
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type NavigationMenuItem,
|
||||
type UpdateNavigationMenuItemInput,
|
||||
type UpdateOneNavigationMenuItemInput,
|
||||
UpdateNavigationMenuItemDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
|
||||
export const useUpdateNavigationMenuItem = () => {
|
||||
const { addToDraft, applyChanges } = useMetadataStore();
|
||||
|
||||
const [updateNavigationMenuItemMutation] = useMutation(
|
||||
UpdateNavigationMenuItemDocument,
|
||||
{
|
||||
refetchQueries: ['FindManyNavigationMenuItems'],
|
||||
awaitRefetchQueries: false,
|
||||
},
|
||||
);
|
||||
|
||||
const updateNavigationMenuItem = async (
|
||||
input: UpdateNavigationMenuItemInput & { id: string },
|
||||
) => {
|
||||
const { id, ...update } = input;
|
||||
|
||||
addToDraft({
|
||||
key: 'navigationMenuItems',
|
||||
items: [{ id, ...update } as NavigationMenuItem],
|
||||
});
|
||||
applyChanges();
|
||||
|
||||
const updateOneInput: UpdateOneNavigationMenuItemInput = {
|
||||
id,
|
||||
update,
|
||||
};
|
||||
|
||||
await updateNavigationMenuItemMutation({
|
||||
const result = await updateNavigationMenuItemMutation({
|
||||
variables: { input: updateOneInput },
|
||||
});
|
||||
|
||||
const updated = result.data?.updateNavigationMenuItem;
|
||||
|
||||
if (isDefined(updated)) {
|
||||
addToDraft({ key: 'navigationMenuItems', items: [updated] });
|
||||
applyChanges();
|
||||
}
|
||||
};
|
||||
|
||||
return { updateNavigationMenuItem };
|
||||
|
||||
+2
-1
@@ -8,7 +8,8 @@ export const navigationMenuItemsSelector = createAtomSelector<
|
||||
key: 'navigationMenuItemsSelector',
|
||||
get: ({ get }) => {
|
||||
const entry = get(metadataStoreState, 'navigationMenuItems');
|
||||
const items = entry.current as unknown as NavigationMenuItem[];
|
||||
|
||||
return entry.current as unknown as NavigationMenuItem[];
|
||||
return items;
|
||||
},
|
||||
});
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { getPositionBetween } from '@/navigation-menu-item/common/utils/getPositionBetween';
|
||||
|
||||
export const computeDndReorderPosition = ({
|
||||
sortedList,
|
||||
draggableId,
|
||||
destinationIndex,
|
||||
}: {
|
||||
sortedList: Array<{ id: string; position: number }>;
|
||||
draggableId: string;
|
||||
destinationIndex: number;
|
||||
}): number => {
|
||||
const sourceIndexInList = sortedList.findIndex(
|
||||
(item) => item.id === draggableId,
|
||||
);
|
||||
const isSameList = sourceIndexInList >= 0;
|
||||
|
||||
if (isSameList) {
|
||||
const listWithoutDragged = sortedList.filter(
|
||||
(item) => item.id !== draggableId,
|
||||
);
|
||||
const adjustedIndex =
|
||||
sourceIndexInList < destinationIndex &&
|
||||
destinationIndex <= listWithoutDragged.length
|
||||
? destinationIndex - 1
|
||||
: destinationIndex;
|
||||
const prevItem = listWithoutDragged[adjustedIndex - 1];
|
||||
const nextItem = listWithoutDragged[adjustedIndex];
|
||||
|
||||
return getPositionBetween(prevItem?.position, nextItem?.position);
|
||||
}
|
||||
|
||||
const prevItem = sortedList[destinationIndex - 1];
|
||||
const nextItem = sortedList[destinationIndex];
|
||||
|
||||
return getPositionBetween(prevItem?.position, nextItem?.position);
|
||||
};
|
||||
+158
-97
@@ -1,45 +1,127 @@
|
||||
import { type OnDragEndResponder } from '@hello-pangea/dnd';
|
||||
import { useStore } from 'jotai';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/common/constants/NavigationMenuItemDroppableIds';
|
||||
import { canNavigationMenuItemBeDroppedIn } from '@/navigation-menu-item/common/utils/canNavigationMenuItemBeDroppedIn';
|
||||
import { useSortedNavigationMenuItems } from '@/navigation-menu-item/display/hooks/useSortedNavigationMenuItems';
|
||||
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
|
||||
import { useUpdateNavigationMenuItem } from '@/navigation-menu-item/common/hooks/useUpdateNavigationMenuItem';
|
||||
import { NAVIGATION_MENU_ITEM_SECTION_DROPPABLE_CONFIG } from '@/navigation-menu-item/common/constants/NavigationMenuItemSectionDroppableConfig';
|
||||
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/common/states/navigationMenuItemsDraftState';
|
||||
import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/common/states/openNavigationMenuItemFolderIdsState';
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
import { calculateNewPosition } from '@/ui/layout/draggable-list/utils/calculateNewPosition';
|
||||
import type { NavigationMenuItemSection } from '@/navigation-menu-item/common/types/NavigationMenuItemSection';
|
||||
import { canNavigationMenuItemBeDroppedIn } from '@/navigation-menu-item/common/utils/canNavigationMenuItemBeDroppedIn';
|
||||
import { computeDndReorderPosition } from '@/navigation-menu-item/common/utils/computeDndReorderPosition';
|
||||
import { extractFolderIdFromDroppableId } from '@/navigation-menu-item/common/utils/extractFolderIdFromDroppableId';
|
||||
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/common/utils/isNavigationMenuItemFolder';
|
||||
import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
|
||||
import { useSortedNavigationMenuItems } from '@/navigation-menu-item/display/hooks/useSortedNavigationMenuItems';
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
|
||||
export const useHandleNavigationMenuItemDragAndDrop = () => {
|
||||
const { navigationMenuItems } = useNavigationMenuItemsData();
|
||||
const matchesFolderId = (
|
||||
item: { folderId?: string | null },
|
||||
folderId: string | null,
|
||||
): boolean => (item.folderId ?? null) === folderId;
|
||||
|
||||
export const useHandleNavigationMenuItemDragAndDrop = (
|
||||
section: NavigationMenuItemSection,
|
||||
) => {
|
||||
const store = useStore();
|
||||
const { navigationMenuItems, workspaceNavigationMenuItems } =
|
||||
useNavigationMenuItemsData();
|
||||
const { navigationMenuItemsSorted } = useSortedNavigationMenuItems();
|
||||
const { updateNavigationMenuItem } = useUpdateNavigationMenuItem();
|
||||
const setNavigationMenuItemsDraft = useSetAtomState(
|
||||
navigationMenuItemsDraftState,
|
||||
);
|
||||
const setOpenNavigationMenuItemFolderIds = useSetAtomState(
|
||||
openNavigationMenuItemFolderIdsState,
|
||||
);
|
||||
|
||||
const openDestinationFolder = (folderId: string | null) => {
|
||||
if (!folderId) {
|
||||
return;
|
||||
const isDraftMode = section === 'workspace';
|
||||
const config = NAVIGATION_MENU_ITEM_SECTION_DROPPABLE_CONFIG[section];
|
||||
const allItems = isDraftMode
|
||||
? workspaceNavigationMenuItems
|
||||
: navigationMenuItems;
|
||||
|
||||
const getSortedItems = (): Array<{
|
||||
id: string;
|
||||
position: number;
|
||||
folderId?: string | null;
|
||||
}> =>
|
||||
isDraftMode
|
||||
? (store.get(navigationMenuItemsDraftState.atom) ?? []).sort(
|
||||
(a, b) => a.position - b.position,
|
||||
)
|
||||
: navigationMenuItemsSorted;
|
||||
|
||||
const applyReorder = async (
|
||||
draggableId: string,
|
||||
newPosition: number,
|
||||
newFolderId?: string | null,
|
||||
) => {
|
||||
const folderUpdate =
|
||||
newFolderId !== undefined ? { folderId: newFolderId } : {};
|
||||
|
||||
if (isDraftMode) {
|
||||
const draft = store.get(navigationMenuItemsDraftState.atom);
|
||||
if (!draft) return;
|
||||
|
||||
setNavigationMenuItemsDraft(
|
||||
draft.map((item): NavigationMenuItem => {
|
||||
if (item.id !== draggableId) return item;
|
||||
return { ...item, position: newPosition, ...folderUpdate };
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
await updateNavigationMenuItem({
|
||||
id: draggableId,
|
||||
position: newPosition,
|
||||
...folderUpdate,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const computeAndApplyReorder = async (
|
||||
draggableId: string,
|
||||
list: Array<{ id: string; position: number }>,
|
||||
destinationIndex: number,
|
||||
newFolderId?: string | null,
|
||||
) => {
|
||||
const newPosition = computeDndReorderPosition({
|
||||
sortedList: list,
|
||||
draggableId,
|
||||
destinationIndex,
|
||||
});
|
||||
await applyReorder(draggableId, newPosition, newFolderId);
|
||||
};
|
||||
|
||||
const isDropAllowed = (
|
||||
sourceDroppableId: string,
|
||||
destinationDroppableId: string,
|
||||
): boolean => {
|
||||
if (isDraftMode) {
|
||||
return (
|
||||
sourceDroppableId.startsWith('workspace-') &&
|
||||
destinationDroppableId.startsWith('workspace-') &&
|
||||
store.get(isLayoutCustomizationModeEnabledState.atom) &&
|
||||
isDefined(store.get(navigationMenuItemsDraftState.atom))
|
||||
);
|
||||
}
|
||||
|
||||
setOpenNavigationMenuItemFolderIds((current) => {
|
||||
if (!current.includes(folderId)) {
|
||||
return [...current, folderId];
|
||||
}
|
||||
return current;
|
||||
return !canNavigationMenuItemBeDroppedIn({
|
||||
navigationMenuItemSection: 'workspace',
|
||||
droppableId: destinationDroppableId,
|
||||
});
|
||||
};
|
||||
|
||||
const handleNavigationMenuItemDragAndDrop: OnDragEndResponder = async (
|
||||
result,
|
||||
result: Parameters<OnDragEndResponder>[0] & {
|
||||
insertBeforeItemId?: string | null;
|
||||
},
|
||||
) => {
|
||||
const { destination, source, draggableId } = result;
|
||||
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
if (!destination) return;
|
||||
|
||||
if (
|
||||
destination.droppableId === source.droppableId &&
|
||||
@@ -48,104 +130,83 @@ export const useHandleNavigationMenuItemDragAndDrop = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
canNavigationMenuItemBeDroppedIn({
|
||||
navigationMenuItemSection: 'workspace',
|
||||
droppableId: destination.droppableId,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!isDropAllowed(source.droppableId, destination.droppableId)) return;
|
||||
|
||||
const draggedNavigationMenuItem = navigationMenuItems.find(
|
||||
(item) => item.id === draggableId,
|
||||
);
|
||||
if (!draggedNavigationMenuItem) {
|
||||
return;
|
||||
}
|
||||
const draggedItem = allItems.find((item) => item.id === draggableId);
|
||||
if (!draggedItem) return;
|
||||
|
||||
const destinationFolderId = extractFolderIdFromDroppableId(
|
||||
destination.droppableId,
|
||||
'favorite',
|
||||
section,
|
||||
);
|
||||
const sourceFolderId = extractFolderIdFromDroppableId(
|
||||
source.droppableId,
|
||||
'favorite',
|
||||
section,
|
||||
);
|
||||
|
||||
if (
|
||||
destination.droppableId.startsWith(
|
||||
NavigationMenuItemDroppableIds.FAVORITE_FOLDER_HEADER_PREFIX,
|
||||
)
|
||||
isDraftMode &&
|
||||
isNavigationMenuItemFolder(draggedItem) &&
|
||||
isDefined(destinationFolderId)
|
||||
) {
|
||||
if (destinationFolderId === null)
|
||||
throw new Error('Invalid folder header ID');
|
||||
|
||||
const folderNavigationMenuItems = navigationMenuItemsSorted.filter(
|
||||
(item) => item.folderId === destinationFolderId,
|
||||
);
|
||||
|
||||
const newPosition =
|
||||
folderNavigationMenuItems.length === 0
|
||||
? 1
|
||||
: folderNavigationMenuItems[folderNavigationMenuItems.length - 1]
|
||||
.position + 1;
|
||||
|
||||
await updateNavigationMenuItem({
|
||||
id: draggableId,
|
||||
folderId: destinationFolderId,
|
||||
position: newPosition,
|
||||
});
|
||||
|
||||
openDestinationFolder(destinationFolderId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (destination.droppableId !== source.droppableId) {
|
||||
const destinationNavigationMenuItems = navigationMenuItemsSorted.filter(
|
||||
(item) => item.folderId === destinationFolderId,
|
||||
);
|
||||
|
||||
let newPosition;
|
||||
if (destinationNavigationMenuItems.length === 0) {
|
||||
newPosition = 1;
|
||||
} else if (destination.index === 0) {
|
||||
newPosition = destinationNavigationMenuItems[0].position - 1;
|
||||
} else if (destination.index >= destinationNavigationMenuItems.length) {
|
||||
newPosition =
|
||||
destinationNavigationMenuItems[
|
||||
destinationNavigationMenuItems.length - 1
|
||||
].position + 1;
|
||||
} else {
|
||||
newPosition = calculateNewPosition({
|
||||
destinationIndex: destination.index,
|
||||
sourceIndex: -1,
|
||||
items: destinationNavigationMenuItems,
|
||||
});
|
||||
if (destination.droppableId.startsWith(config.folderHeaderPrefix)) {
|
||||
if (destinationFolderId === null) {
|
||||
throw new Error('Invalid folder header ID');
|
||||
}
|
||||
|
||||
await updateNavigationMenuItem({
|
||||
id: draggableId,
|
||||
folderId: destinationFolderId ?? null,
|
||||
position: newPosition,
|
||||
});
|
||||
const folderList = getSortedItems().filter((item) =>
|
||||
matchesFolderId(item, destinationFolderId),
|
||||
);
|
||||
|
||||
await computeAndApplyReorder(
|
||||
draggableId,
|
||||
folderList,
|
||||
folderList.length,
|
||||
destinationFolderId,
|
||||
);
|
||||
setOpenNavigationMenuItemFolderIds((current) =>
|
||||
current.includes(destinationFolderId)
|
||||
? current
|
||||
: [...current, destinationFolderId],
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const navigationMenuItemsInSameList = navigationMenuItemsSorted
|
||||
.filter((item) => item.folderId === sourceFolderId)
|
||||
.filter((item) => item.id !== draggableId);
|
||||
if (sourceFolderId === destinationFolderId) {
|
||||
const sourceList = getSortedItems().filter((item) =>
|
||||
matchesFolderId(item, sourceFolderId),
|
||||
);
|
||||
|
||||
const newPosition = calculateNewPosition({
|
||||
destinationIndex: destination.index,
|
||||
sourceIndex: source.index,
|
||||
items: navigationMenuItemsInSameList,
|
||||
});
|
||||
if (!sourceList.some((item) => item.id === draggableId)) return;
|
||||
|
||||
await updateNavigationMenuItem({
|
||||
id: draggableId,
|
||||
position: newPosition,
|
||||
});
|
||||
const insertBeforeIndex =
|
||||
result.insertBeforeItemId != null
|
||||
? sourceList.findIndex(
|
||||
(item) => item.id === result.insertBeforeItemId,
|
||||
)
|
||||
: -1;
|
||||
|
||||
await computeAndApplyReorder(
|
||||
draggableId,
|
||||
sourceList,
|
||||
insertBeforeIndex >= 0 ? insertBeforeIndex : destination.index,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const destinationList = getSortedItems().filter((item) =>
|
||||
matchesFolderId(item, destinationFolderId),
|
||||
);
|
||||
|
||||
await computeAndApplyReorder(
|
||||
draggableId,
|
||||
destinationList,
|
||||
destination.index,
|
||||
destinationFolderId ?? null,
|
||||
);
|
||||
};
|
||||
|
||||
return { handleNavigationMenuItemDragAndDrop };
|
||||
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
import { type OnDragEndResponder } from '@hello-pangea/dnd';
|
||||
import { useStore } from 'jotai';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
|
||||
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/common/constants/NavigationMenuItemDroppableIds';
|
||||
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/common/states/navigationMenuItemsDraftState';
|
||||
import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/common/states/openNavigationMenuItemFolderIdsState';
|
||||
import { getPositionBetween } from '@/navigation-menu-item/common/utils/getPositionBetween';
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/common/utils/isNavigationMenuItemFolder';
|
||||
import {
|
||||
matchesWorkspaceFolderId,
|
||||
validateAndExtractWorkspaceFolderId,
|
||||
} from '@/navigation-menu-item/common/utils/validateAndExtractWorkspaceFolderId';
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
|
||||
|
||||
export const useHandleWorkspaceNavigationMenuItemDragAndDrop = () => {
|
||||
const store = useStore();
|
||||
const { workspaceNavigationMenuItems } = useNavigationMenuItemsData();
|
||||
const setNavigationMenuItemsDraft = useSetAtomState(
|
||||
navigationMenuItemsDraftState,
|
||||
);
|
||||
const setOpenNavigationMenuItemFolderIds = useSetAtomState(
|
||||
openNavigationMenuItemFolderIdsState,
|
||||
);
|
||||
|
||||
const openDestinationFolder = (folderId: string | null) => {
|
||||
if (!folderId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setOpenNavigationMenuItemFolderIds((current) => {
|
||||
if (!current.includes(folderId)) {
|
||||
return [...current, folderId];
|
||||
}
|
||||
return current;
|
||||
});
|
||||
};
|
||||
|
||||
const handleWorkspaceNavigationMenuItemDragAndDrop: OnDragEndResponder = (
|
||||
result: Parameters<OnDragEndResponder>[0] & {
|
||||
insertBeforeItemId?: string | null;
|
||||
},
|
||||
) => {
|
||||
const { destination, source, draggableId } = result;
|
||||
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
destination.droppableId === source.droppableId &&
|
||||
destination.index === source.index
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isWorkspaceDrop =
|
||||
source.droppableId.startsWith('workspace-') &&
|
||||
destination.droppableId.startsWith('workspace-');
|
||||
|
||||
if (!isWorkspaceDrop) {
|
||||
return;
|
||||
}
|
||||
|
||||
const navigationMenuItemsDraft = store.get(
|
||||
navigationMenuItemsDraftState.atom,
|
||||
);
|
||||
const isLayoutCustomizationModeEnabled = store.get(
|
||||
isLayoutCustomizationModeEnabledState.atom,
|
||||
);
|
||||
if (!isLayoutCustomizationModeEnabled || !navigationMenuItemsDraft) {
|
||||
return;
|
||||
}
|
||||
|
||||
const draggedItem = workspaceNavigationMenuItems.find(
|
||||
(item) => item.id === draggableId,
|
||||
);
|
||||
|
||||
if (!draggedItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const destinationFolderId = validateAndExtractWorkspaceFolderId(
|
||||
destination.droppableId,
|
||||
);
|
||||
|
||||
if (
|
||||
isNavigationMenuItemFolder(draggedItem) &&
|
||||
isDefined(destinationFolderId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const sourceFolderId = validateAndExtractWorkspaceFolderId(
|
||||
source.droppableId,
|
||||
);
|
||||
|
||||
const isDropOnFolderHeader = destination.droppableId.startsWith(
|
||||
NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_HEADER_PREFIX,
|
||||
);
|
||||
|
||||
if (isDropOnFolderHeader && isDefined(destinationFolderId)) {
|
||||
openDestinationFolder(destinationFolderId);
|
||||
}
|
||||
|
||||
const sourceList = (navigationMenuItemsDraft ?? [])
|
||||
.filter((item) => matchesWorkspaceFolderId(item, sourceFolderId))
|
||||
.sort((a, b) => a.position - b.position);
|
||||
|
||||
const destinationList = (navigationMenuItemsDraft ?? [])
|
||||
.filter((item) => matchesWorkspaceFolderId(item, destinationFolderId))
|
||||
.sort((a, b) => a.position - b.position);
|
||||
|
||||
if (!sourceList.some((item) => item.id === draggableId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isSameList = sourceFolderId === destinationFolderId;
|
||||
|
||||
if (isSameList) {
|
||||
const listWithoutDragged = sourceList.filter(
|
||||
(item) => item.id !== draggableId,
|
||||
);
|
||||
const sourceIndexInList = sourceList.findIndex(
|
||||
(item) => item.id === draggableId,
|
||||
);
|
||||
const insertBeforeIndex =
|
||||
result.insertBeforeItemId != null
|
||||
? sourceList.findIndex(
|
||||
(item) => item.id === result.insertBeforeItemId,
|
||||
)
|
||||
: -1;
|
||||
const destinationIndexInFullList =
|
||||
insertBeforeIndex >= 0 ? insertBeforeIndex : destination.index;
|
||||
const destIndexInListWithoutDragged =
|
||||
sourceIndexInList < destinationIndexInFullList &&
|
||||
destinationIndexInFullList <= listWithoutDragged.length
|
||||
? destinationIndexInFullList - 1
|
||||
: destinationIndexInFullList;
|
||||
const prevItem = listWithoutDragged[destIndexInListWithoutDragged - 1];
|
||||
const nextItem = listWithoutDragged[destIndexInListWithoutDragged];
|
||||
const newPosition = getPositionBetween(
|
||||
prevItem?.position,
|
||||
nextItem?.position,
|
||||
);
|
||||
const updatedDraft = navigationMenuItemsDraft.map(
|
||||
(item): NavigationMenuItem =>
|
||||
item.id === draggableId ? { ...item, position: newPosition } : item,
|
||||
);
|
||||
setNavigationMenuItemsDraft(updatedDraft);
|
||||
return;
|
||||
}
|
||||
|
||||
const prevItem = destinationList[destination.index - 1];
|
||||
const nextItem = destinationList[destination.index];
|
||||
const newPosition = getPositionBetween(
|
||||
prevItem?.position,
|
||||
nextItem?.position,
|
||||
);
|
||||
const updatedDraft = navigationMenuItemsDraft.map(
|
||||
(item): NavigationMenuItem => {
|
||||
if (item.id !== draggableId) return item;
|
||||
return {
|
||||
...item,
|
||||
position: newPosition,
|
||||
folderId: destinationFolderId,
|
||||
};
|
||||
},
|
||||
);
|
||||
setNavigationMenuItemsDraft(updatedDraft);
|
||||
};
|
||||
|
||||
return { handleWorkspaceNavigationMenuItemDragAndDrop };
|
||||
};
|
||||
+6
-21
@@ -18,7 +18,6 @@ import { isNavigationMenuItemFolder } from '@/navigation-menu-item/common/utils/
|
||||
import { DROP_RESULT_OPTIONS } from '@/navigation-menu-item/display/dnd/constants/navigationMenuItemDndKitDropResultOptions';
|
||||
import { useHandleAddToNavigationDrop } from '@/navigation-menu-item/display/dnd/hooks/useHandleAddToNavigationDrop';
|
||||
import { useHandleNavigationMenuItemDragAndDrop } from '@/navigation-menu-item/display/dnd/hooks/useHandleNavigationMenuItemDragAndDrop';
|
||||
import { useHandleWorkspaceNavigationMenuItemDragAndDrop } from '@/navigation-menu-item/display/dnd/hooks/useHandleWorkspaceNavigationMenuItemDragAndDrop';
|
||||
import { resolveDropTarget } from '@/navigation-menu-item/display/dnd/utils/navigationMenuItemDndKitResolveDropTarget';
|
||||
import { toDropResult } from '@/navigation-menu-item/display/dnd/utils/navigationMenuItemDndKitToDropResult';
|
||||
import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
|
||||
@@ -92,9 +91,7 @@ export const useNavigationMenuItemDndKit = (
|
||||
const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState();
|
||||
const { handleAddToNavigationDrop } = useHandleAddToNavigationDrop();
|
||||
const { handleNavigationMenuItemDragAndDrop } =
|
||||
useHandleNavigationMenuItemDragAndDrop();
|
||||
const { handleWorkspaceNavigationMenuItemDragAndDrop } =
|
||||
useHandleWorkspaceNavigationMenuItemDragAndDrop();
|
||||
useHandleNavigationMenuItemDragAndDrop(sectionType);
|
||||
|
||||
const items = isWorkspaceSection
|
||||
? workspaceNavigationMenuItems
|
||||
@@ -169,25 +166,13 @@ export const useNavigationMenuItemDndKit = (
|
||||
[sectionType, getAddToNavPayload, isSourceFolderDrag],
|
||||
);
|
||||
|
||||
const applyWorkspaceReorderIfAllowed = useCallback(
|
||||
const applyWorkspaceReorder = useCallback(
|
||||
(
|
||||
id: string,
|
||||
source: DropDestination,
|
||||
destination: DropDestination,
|
||||
insertBeforeItemId?: string | null,
|
||||
) => {
|
||||
const draggedItem = getNavItemById(id);
|
||||
const destFolderId = extractFolderIdFromDroppableId(
|
||||
destination.droppableId,
|
||||
'workspace',
|
||||
);
|
||||
if (
|
||||
isDefined(destFolderId) &&
|
||||
isDefined(draggedItem) &&
|
||||
isNavigationMenuItemFolder(draggedItem)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const result = toDropResult(
|
||||
id,
|
||||
{
|
||||
@@ -197,7 +182,7 @@ export const useNavigationMenuItemDndKit = (
|
||||
destination,
|
||||
);
|
||||
const provided: ResponderProvided = { announce: () => {} };
|
||||
handleWorkspaceNavigationMenuItemDragAndDrop(
|
||||
handleNavigationMenuItemDragAndDrop(
|
||||
{
|
||||
...result,
|
||||
...DROP_RESULT_OPTIONS,
|
||||
@@ -206,7 +191,7 @@ export const useNavigationMenuItemDndKit = (
|
||||
provided,
|
||||
);
|
||||
},
|
||||
[getNavItemById, handleWorkspaceNavigationMenuItemDragAndDrop],
|
||||
[handleNavigationMenuItemDragAndDrop],
|
||||
);
|
||||
|
||||
const handleDragStart = (event: DragStartPayload) => {
|
||||
@@ -344,7 +329,7 @@ export const useNavigationMenuItemDndKit = (
|
||||
const insertBeforeItemId = resolved.isTargetFolder
|
||||
? null
|
||||
: String(target?.id ?? '');
|
||||
applyWorkspaceReorderIfAllowed(
|
||||
applyWorkspaceReorder(
|
||||
draggableId,
|
||||
{ droppableId: initialGroup, index: initialIndex },
|
||||
resolved.destination,
|
||||
@@ -388,7 +373,7 @@ export const useNavigationMenuItemDndKit = (
|
||||
droppableId: destination.droppableId,
|
||||
})
|
||||
) {
|
||||
applyWorkspaceReorderIfAllowed(
|
||||
applyWorkspaceReorder(
|
||||
draggableId,
|
||||
{
|
||||
droppableId: data?.sourceDroppableId ?? '',
|
||||
|
||||
+1
-2
@@ -58,7 +58,7 @@ const StyledFolderContainer = styled.div<{
|
||||
border: ${({ $isSelectedInEditMode }) =>
|
||||
$isSelectedInEditMode
|
||||
? `1px solid ${themeCssVariables.color.blue}`
|
||||
: '1px solid transparent'};
|
||||
: 'none'};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
transition: background-color 150ms ease-in-out;
|
||||
|
||||
@@ -240,7 +240,6 @@ export const NavigationMenuItemFolderDnd = ({
|
||||
triggerEvent="CLICK"
|
||||
preventCollapseOnMobile={isMobile}
|
||||
isDragging={isDragging}
|
||||
alwaysShowRightOptions
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
+7
-14
@@ -11,9 +11,6 @@ export const useCreateNavigationMenuItemFolder = () => {
|
||||
|
||||
const [createNavigationMenuItemMutation] = useMutation(
|
||||
CreateNavigationMenuItemDocument,
|
||||
{
|
||||
refetchQueries: ['FindManyNavigationMenuItems'],
|
||||
},
|
||||
);
|
||||
|
||||
const createNewNavigationMenuItemFolder = async (
|
||||
@@ -23,20 +20,16 @@ export const useCreateNavigationMenuItemFolder = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const folderNavigationMenuItems = navigationMenuItems.filter(
|
||||
const topLevelItems = navigationMenuItems.filter(
|
||||
(item) =>
|
||||
isDefined(item.name) &&
|
||||
!item.folderId &&
|
||||
!item.targetRecordId &&
|
||||
!item.targetObjectMetadataId &&
|
||||
!item.viewId &&
|
||||
!isDefined(item.folderId) &&
|
||||
item.userWorkspaceId === currentWorkspaceMemberId,
|
||||
);
|
||||
|
||||
const maxPosition = Math.max(
|
||||
...folderNavigationMenuItems.map((item) => item.position),
|
||||
0,
|
||||
);
|
||||
const minPosition =
|
||||
topLevelItems.length > 0
|
||||
? Math.min(...topLevelItems.map((item) => item.position))
|
||||
: 1;
|
||||
|
||||
await createNavigationMenuItemMutation({
|
||||
variables: {
|
||||
@@ -47,7 +40,7 @@ export const useCreateNavigationMenuItemFolder = () => {
|
||||
targetObjectMetadataId: null,
|
||||
userWorkspaceId: currentWorkspaceMemberId,
|
||||
folderId: null,
|
||||
position: maxPosition + 1,
|
||||
position: minPosition - 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
-3
@@ -23,9 +23,6 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
const { deleteNavigationMenuItem } = useDeleteNavigationMenuItem();
|
||||
const [createNavigationMenuItemMutation] = useMutation(
|
||||
CreateNavigationMenuItemDocument,
|
||||
{
|
||||
refetchQueries: ['FindManyNavigationMenuItems'],
|
||||
},
|
||||
);
|
||||
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
|
||||
|
||||
|
||||
@@ -1236,6 +1236,47 @@ enum PageLayoutType {
|
||||
DASHBOARD
|
||||
}
|
||||
|
||||
type FileWithSignedUrl {
|
||||
id: UUID!
|
||||
path: String!
|
||||
size: Float!
|
||||
createdAt: DateTime!
|
||||
url: String!
|
||||
}
|
||||
|
||||
type RecordIdentifier {
|
||||
id: UUID!
|
||||
labelIdentifier: String!
|
||||
imageIdentifier: String
|
||||
}
|
||||
|
||||
type NavigationMenuItem {
|
||||
id: UUID!
|
||||
userWorkspaceId: UUID
|
||||
targetRecordId: UUID
|
||||
targetObjectMetadataId: UUID
|
||||
viewId: UUID
|
||||
type: NavigationMenuItemType!
|
||||
name: String
|
||||
link: String
|
||||
icon: String
|
||||
color: String
|
||||
folderId: UUID
|
||||
position: Float!
|
||||
applicationId: UUID
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
targetRecordIdentifier: RecordIdentifier
|
||||
}
|
||||
|
||||
enum NavigationMenuItemType {
|
||||
VIEW
|
||||
FOLDER
|
||||
LINK
|
||||
OBJECT
|
||||
RECORD
|
||||
}
|
||||
|
||||
type ObjectRecordEventProperties {
|
||||
updatedFields: [String!]
|
||||
before: JSON
|
||||
@@ -1482,14 +1523,6 @@ type ApprovedAccessDomain {
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
type FileWithSignedUrl {
|
||||
id: UUID!
|
||||
path: String!
|
||||
size: Float!
|
||||
createdAt: DateTime!
|
||||
url: String!
|
||||
}
|
||||
|
||||
type WorkspaceInvitation {
|
||||
id: UUID!
|
||||
email: String!
|
||||
@@ -1924,39 +1957,6 @@ type WorkspaceInviteHashValid {
|
||||
isValid: Boolean!
|
||||
}
|
||||
|
||||
type RecordIdentifier {
|
||||
id: UUID!
|
||||
labelIdentifier: String!
|
||||
imageIdentifier: String
|
||||
}
|
||||
|
||||
type NavigationMenuItem {
|
||||
id: UUID!
|
||||
userWorkspaceId: UUID
|
||||
targetRecordId: UUID
|
||||
targetObjectMetadataId: UUID
|
||||
viewId: UUID
|
||||
type: NavigationMenuItemType!
|
||||
name: String
|
||||
link: String
|
||||
icon: String
|
||||
color: String
|
||||
folderId: UUID
|
||||
position: Float!
|
||||
applicationId: UUID
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
targetRecordIdentifier: RecordIdentifier
|
||||
}
|
||||
|
||||
enum NavigationMenuItemType {
|
||||
VIEW
|
||||
FOLDER
|
||||
LINK
|
||||
OBJECT
|
||||
RECORD
|
||||
}
|
||||
|
||||
type LogicFunctionExecutionResult {
|
||||
"""Execution result in JSON format"""
|
||||
data: JSON
|
||||
@@ -2925,6 +2925,8 @@ type EventLogQueryResult {
|
||||
}
|
||||
|
||||
type Query {
|
||||
navigationMenuItems: [NavigationMenuItem!]!
|
||||
navigationMenuItem(id: UUID!): NavigationMenuItem
|
||||
getPageLayoutWidgets(pageLayoutTabId: String!): [PageLayoutWidget!]!
|
||||
getPageLayoutWidget(id: String!): PageLayoutWidget!
|
||||
getPageLayoutTabs(pageLayoutId: String!): [PageLayoutTab!]!
|
||||
@@ -2978,13 +2980,11 @@ type Query {
|
||||
enterprisePortalSession(returnUrlPath: String): String
|
||||
enterpriseCheckoutSession(billingInterval: String): String
|
||||
enterpriseSubscriptionStatus: EnterpriseSubscriptionStatusDTO
|
||||
navigationMenuItems: [NavigationMenuItem!]!
|
||||
navigationMenuItem(id: UUID!): NavigationMenuItem
|
||||
apiKeys: [ApiKey!]!
|
||||
apiKey(input: GetApiKeyInput!): ApiKey
|
||||
getRoles: [Role!]!
|
||||
findWorkspaceInvitations: [WorkspaceInvitation!]!
|
||||
getApprovedAccessDomains: [ApprovedAccessDomain!]!
|
||||
apiKeys: [ApiKey!]!
|
||||
apiKey(input: GetApiKeyInput!): ApiKey
|
||||
getToolIndex: [ToolIndexEntry!]!
|
||||
getToolInputSchema(toolName: String!): JSON
|
||||
field(
|
||||
@@ -3171,6 +3171,15 @@ input BarChartDataInput {
|
||||
type Mutation {
|
||||
addQueryToEventStream(input: AddQuerySubscriptionInput!): Boolean!
|
||||
removeQueryFromEventStream(input: RemoveQueryFromEventStreamInput!): Boolean!
|
||||
createNavigationMenuItem(input: CreateNavigationMenuItemInput!): NavigationMenuItem!
|
||||
updateNavigationMenuItem(input: UpdateOneNavigationMenuItemInput!): NavigationMenuItem!
|
||||
deleteNavigationMenuItem(id: UUID!): NavigationMenuItem!
|
||||
uploadAIChatFile(file: Upload!): FileWithSignedUrl!
|
||||
uploadWorkflowFile(file: Upload!): FileWithSignedUrl!
|
||||
uploadWorkspaceLogo(file: Upload!): FileWithSignedUrl!
|
||||
uploadWorkspaceMemberProfilePicture(file: Upload!): FileWithSignedUrl!
|
||||
uploadFilesFieldFile(file: Upload!, fieldMetadataId: String!): FileWithSignedUrl!
|
||||
uploadFilesFieldFileByUniversalIdentifier(file: Upload!, fieldMetadataUniversalIdentifier: String!): FileWithSignedUrl!
|
||||
createObjectEvent(event: String!, recordId: UUID!, objectMetadataId: UUID!, properties: JSON): Analytics!
|
||||
trackAnalytics(type: AnalyticsType!, name: String, event: String, properties: JSON): Analytics!
|
||||
createPageLayoutWidget(input: CreatePageLayoutWidgetInput!): PageLayoutWidget!
|
||||
@@ -3218,12 +3227,6 @@ type Mutation {
|
||||
createOneAgent(input: CreateAgentInput!): Agent!
|
||||
updateOneAgent(input: UpdateAgentInput!): Agent!
|
||||
deleteOneAgent(input: AgentIdInput!): Agent!
|
||||
uploadAIChatFile(file: Upload!): FileWithSignedUrl!
|
||||
uploadWorkflowFile(file: Upload!): FileWithSignedUrl!
|
||||
uploadWorkspaceLogo(file: Upload!): FileWithSignedUrl!
|
||||
uploadWorkspaceMemberProfilePicture(file: Upload!): FileWithSignedUrl!
|
||||
uploadFilesFieldFile(file: Upload!, fieldMetadataId: String!): FileWithSignedUrl!
|
||||
uploadFilesFieldFileByUniversalIdentifier(file: Upload!, fieldMetadataUniversalIdentifier: String!): FileWithSignedUrl!
|
||||
checkoutSession(recurringInterval: SubscriptionInterval!, plan: BillingPlanKey! = PRO, requirePaymentMethod: Boolean! = true, successUrlPath: String): BillingSession!
|
||||
switchSubscriptionInterval: BillingUpdate!
|
||||
switchBillingPlan: BillingUpdate!
|
||||
@@ -3234,13 +3237,6 @@ type Mutation {
|
||||
cancelSwitchMeteredPrice: BillingUpdate!
|
||||
refreshEnterpriseValidityToken: Boolean!
|
||||
setEnterpriseKey(enterpriseKey: String!): EnterpriseLicenseInfoDTO!
|
||||
createNavigationMenuItem(input: CreateNavigationMenuItemInput!): NavigationMenuItem!
|
||||
updateNavigationMenuItem(input: UpdateOneNavigationMenuItemInput!): NavigationMenuItem!
|
||||
deleteNavigationMenuItem(id: UUID!): NavigationMenuItem!
|
||||
createApiKey(input: CreateApiKeyInput!): ApiKey!
|
||||
updateApiKey(input: UpdateApiKeyInput!): ApiKey
|
||||
revokeApiKey(input: RevokeApiKeyInput!): ApiKey
|
||||
assignRoleToApiKey(apiKeyId: UUID!, roleId: UUID!): Boolean!
|
||||
updateWorkspaceMemberRole(workspaceMemberId: UUID!, roleId: UUID!): WorkspaceMember!
|
||||
createOneRole(createRoleInput: CreateRoleInput!): Role!
|
||||
updateOneRole(updateRoleInput: UpdateRoleInput!): Role!
|
||||
@@ -3259,6 +3255,10 @@ type Mutation {
|
||||
createApprovedAccessDomain(input: CreateApprovedAccessDomainInput!): ApprovedAccessDomain!
|
||||
deleteApprovedAccessDomain(input: DeleteApprovedAccessDomainInput!): Boolean!
|
||||
validateApprovedAccessDomain(input: ValidateApprovedAccessDomainInput!): ApprovedAccessDomain!
|
||||
createApiKey(input: CreateApiKeyInput!): ApiKey!
|
||||
updateApiKey(input: UpdateApiKeyInput!): ApiKey
|
||||
revokeApiKey(input: RevokeApiKeyInput!): ApiKey
|
||||
assignRoleToApiKey(apiKeyId: UUID!, roleId: UUID!): Boolean!
|
||||
createOneField(input: CreateOneFieldMetadataInput!): Field!
|
||||
updateOneField(input: UpdateOneFieldMetadataInput!): Field!
|
||||
deleteOneField(input: DeleteOneFieldInput!): Field!
|
||||
@@ -3373,6 +3373,41 @@ input RemoveQueryFromEventStreamInput {
|
||||
queryId: String!
|
||||
}
|
||||
|
||||
input CreateNavigationMenuItemInput {
|
||||
id: UUID
|
||||
userWorkspaceId: UUID
|
||||
targetRecordId: UUID
|
||||
targetObjectMetadataId: UUID
|
||||
viewId: UUID
|
||||
type: NavigationMenuItemType!
|
||||
name: String
|
||||
link: String
|
||||
icon: String
|
||||
color: String
|
||||
folderId: UUID
|
||||
position: Float
|
||||
}
|
||||
|
||||
input UpdateOneNavigationMenuItemInput {
|
||||
"""The id of the record to update"""
|
||||
id: UUID!
|
||||
|
||||
"""The record to update"""
|
||||
update: UpdateNavigationMenuItemInput!
|
||||
}
|
||||
|
||||
input UpdateNavigationMenuItemInput {
|
||||
folderId: UUID
|
||||
position: Float
|
||||
name: String
|
||||
link: String
|
||||
icon: String
|
||||
color: String
|
||||
}
|
||||
|
||||
"""The `Upload` scalar type represents a file upload."""
|
||||
scalar Upload
|
||||
|
||||
enum AnalyticsType {
|
||||
PAGEVIEW
|
||||
TRACK
|
||||
@@ -3793,58 +3828,6 @@ input UpdateAgentInput {
|
||||
evaluationInputs: [String!]
|
||||
}
|
||||
|
||||
"""The `Upload` scalar type represents a file upload."""
|
||||
scalar Upload
|
||||
|
||||
input CreateNavigationMenuItemInput {
|
||||
userWorkspaceId: UUID
|
||||
targetRecordId: UUID
|
||||
targetObjectMetadataId: UUID
|
||||
viewId: UUID
|
||||
type: NavigationMenuItemType!
|
||||
name: String
|
||||
link: String
|
||||
icon: String
|
||||
color: String
|
||||
folderId: UUID
|
||||
position: Float
|
||||
}
|
||||
|
||||
input UpdateOneNavigationMenuItemInput {
|
||||
"""The id of the record to update"""
|
||||
id: UUID!
|
||||
|
||||
"""The record to update"""
|
||||
update: UpdateNavigationMenuItemInput!
|
||||
}
|
||||
|
||||
input UpdateNavigationMenuItemInput {
|
||||
folderId: UUID
|
||||
position: Float
|
||||
name: String
|
||||
link: String
|
||||
icon: String
|
||||
color: String
|
||||
}
|
||||
|
||||
input CreateApiKeyInput {
|
||||
name: String!
|
||||
expiresAt: String!
|
||||
revokedAt: String
|
||||
roleId: UUID!
|
||||
}
|
||||
|
||||
input UpdateApiKeyInput {
|
||||
id: UUID!
|
||||
name: String
|
||||
expiresAt: String
|
||||
revokedAt: String
|
||||
}
|
||||
|
||||
input RevokeApiKeyInput {
|
||||
id: UUID!
|
||||
}
|
||||
|
||||
input CreateRoleInput {
|
||||
id: String
|
||||
label: String!
|
||||
@@ -3954,6 +3937,24 @@ input ValidateApprovedAccessDomainInput {
|
||||
approvedAccessDomainId: UUID!
|
||||
}
|
||||
|
||||
input CreateApiKeyInput {
|
||||
name: String!
|
||||
expiresAt: String!
|
||||
revokedAt: String
|
||||
roleId: UUID!
|
||||
}
|
||||
|
||||
input UpdateApiKeyInput {
|
||||
id: UUID!
|
||||
name: String
|
||||
expiresAt: String
|
||||
revokedAt: String
|
||||
}
|
||||
|
||||
input RevokeApiKeyInput {
|
||||
id: UUID!
|
||||
}
|
||||
|
||||
input CreateOneFieldMetadataInput {
|
||||
"""The record to create"""
|
||||
field: CreateFieldInput!
|
||||
|
||||
@@ -963,6 +963,44 @@ export interface PageLayout {
|
||||
|
||||
export type PageLayoutType = 'RECORD_INDEX' | 'RECORD_PAGE' | 'DASHBOARD'
|
||||
|
||||
export interface FileWithSignedUrl {
|
||||
id: Scalars['UUID']
|
||||
path: Scalars['String']
|
||||
size: Scalars['Float']
|
||||
createdAt: Scalars['DateTime']
|
||||
url: Scalars['String']
|
||||
__typename: 'FileWithSignedUrl'
|
||||
}
|
||||
|
||||
export interface RecordIdentifier {
|
||||
id: Scalars['UUID']
|
||||
labelIdentifier: Scalars['String']
|
||||
imageIdentifier?: Scalars['String']
|
||||
__typename: 'RecordIdentifier'
|
||||
}
|
||||
|
||||
export interface NavigationMenuItem {
|
||||
id: Scalars['UUID']
|
||||
userWorkspaceId?: Scalars['UUID']
|
||||
targetRecordId?: Scalars['UUID']
|
||||
targetObjectMetadataId?: Scalars['UUID']
|
||||
viewId?: Scalars['UUID']
|
||||
type: NavigationMenuItemType
|
||||
name?: Scalars['String']
|
||||
link?: Scalars['String']
|
||||
icon?: Scalars['String']
|
||||
color?: Scalars['String']
|
||||
folderId?: Scalars['UUID']
|
||||
position: Scalars['Float']
|
||||
applicationId?: Scalars['UUID']
|
||||
createdAt: Scalars['DateTime']
|
||||
updatedAt: Scalars['DateTime']
|
||||
targetRecordIdentifier?: RecordIdentifier
|
||||
__typename: 'NavigationMenuItem'
|
||||
}
|
||||
|
||||
export type NavigationMenuItemType = 'VIEW' | 'FOLDER' | 'LINK' | 'OBJECT' | 'RECORD'
|
||||
|
||||
export interface ObjectRecordEventProperties {
|
||||
updatedFields?: Scalars['String'][]
|
||||
before?: Scalars['JSON']
|
||||
@@ -1203,15 +1241,6 @@ export interface ApprovedAccessDomain {
|
||||
__typename: 'ApprovedAccessDomain'
|
||||
}
|
||||
|
||||
export interface FileWithSignedUrl {
|
||||
id: Scalars['UUID']
|
||||
path: Scalars['String']
|
||||
size: Scalars['Float']
|
||||
createdAt: Scalars['DateTime']
|
||||
url: Scalars['String']
|
||||
__typename: 'FileWithSignedUrl'
|
||||
}
|
||||
|
||||
export interface WorkspaceInvitation {
|
||||
id: Scalars['UUID']
|
||||
email: Scalars['String']
|
||||
@@ -1658,35 +1687,6 @@ export interface WorkspaceInviteHashValid {
|
||||
__typename: 'WorkspaceInviteHashValid'
|
||||
}
|
||||
|
||||
export interface RecordIdentifier {
|
||||
id: Scalars['UUID']
|
||||
labelIdentifier: Scalars['String']
|
||||
imageIdentifier?: Scalars['String']
|
||||
__typename: 'RecordIdentifier'
|
||||
}
|
||||
|
||||
export interface NavigationMenuItem {
|
||||
id: Scalars['UUID']
|
||||
userWorkspaceId?: Scalars['UUID']
|
||||
targetRecordId?: Scalars['UUID']
|
||||
targetObjectMetadataId?: Scalars['UUID']
|
||||
viewId?: Scalars['UUID']
|
||||
type: NavigationMenuItemType
|
||||
name?: Scalars['String']
|
||||
link?: Scalars['String']
|
||||
icon?: Scalars['String']
|
||||
color?: Scalars['String']
|
||||
folderId?: Scalars['UUID']
|
||||
position: Scalars['Float']
|
||||
applicationId?: Scalars['UUID']
|
||||
createdAt: Scalars['DateTime']
|
||||
updatedAt: Scalars['DateTime']
|
||||
targetRecordIdentifier?: RecordIdentifier
|
||||
__typename: 'NavigationMenuItem'
|
||||
}
|
||||
|
||||
export type NavigationMenuItemType = 'VIEW' | 'FOLDER' | 'LINK' | 'OBJECT' | 'RECORD'
|
||||
|
||||
export interface LogicFunctionExecutionResult {
|
||||
/** Execution result in JSON format */
|
||||
data?: Scalars['JSON']
|
||||
@@ -2565,6 +2565,8 @@ export interface EventLogQueryResult {
|
||||
}
|
||||
|
||||
export interface Query {
|
||||
navigationMenuItems: NavigationMenuItem[]
|
||||
navigationMenuItem?: NavigationMenuItem
|
||||
getPageLayoutWidgets: PageLayoutWidget[]
|
||||
getPageLayoutWidget: PageLayoutWidget
|
||||
getPageLayoutTabs: PageLayoutTab[]
|
||||
@@ -2600,13 +2602,11 @@ export interface Query {
|
||||
enterprisePortalSession?: Scalars['String']
|
||||
enterpriseCheckoutSession?: Scalars['String']
|
||||
enterpriseSubscriptionStatus?: EnterpriseSubscriptionStatusDTO
|
||||
navigationMenuItems: NavigationMenuItem[]
|
||||
navigationMenuItem?: NavigationMenuItem
|
||||
apiKeys: ApiKey[]
|
||||
apiKey?: ApiKey
|
||||
getRoles: Role[]
|
||||
findWorkspaceInvitations: WorkspaceInvitation[]
|
||||
getApprovedAccessDomains: ApprovedAccessDomain[]
|
||||
apiKeys: ApiKey[]
|
||||
apiKey?: ApiKey
|
||||
getToolIndex: ToolIndexEntry[]
|
||||
getToolInputSchema?: Scalars['JSON']
|
||||
field: Field
|
||||
@@ -2683,6 +2683,15 @@ export type EventLogTable = 'WORKSPACE_EVENT' | 'PAGEVIEW' | 'OBJECT_EVENT'
|
||||
export interface Mutation {
|
||||
addQueryToEventStream: Scalars['Boolean']
|
||||
removeQueryFromEventStream: Scalars['Boolean']
|
||||
createNavigationMenuItem: NavigationMenuItem
|
||||
updateNavigationMenuItem: NavigationMenuItem
|
||||
deleteNavigationMenuItem: NavigationMenuItem
|
||||
uploadAIChatFile: FileWithSignedUrl
|
||||
uploadWorkflowFile: FileWithSignedUrl
|
||||
uploadWorkspaceLogo: FileWithSignedUrl
|
||||
uploadWorkspaceMemberProfilePicture: FileWithSignedUrl
|
||||
uploadFilesFieldFile: FileWithSignedUrl
|
||||
uploadFilesFieldFileByUniversalIdentifier: FileWithSignedUrl
|
||||
createObjectEvent: Analytics
|
||||
trackAnalytics: Analytics
|
||||
createPageLayoutWidget: PageLayoutWidget
|
||||
@@ -2730,12 +2739,6 @@ export interface Mutation {
|
||||
createOneAgent: Agent
|
||||
updateOneAgent: Agent
|
||||
deleteOneAgent: Agent
|
||||
uploadAIChatFile: FileWithSignedUrl
|
||||
uploadWorkflowFile: FileWithSignedUrl
|
||||
uploadWorkspaceLogo: FileWithSignedUrl
|
||||
uploadWorkspaceMemberProfilePicture: FileWithSignedUrl
|
||||
uploadFilesFieldFile: FileWithSignedUrl
|
||||
uploadFilesFieldFileByUniversalIdentifier: FileWithSignedUrl
|
||||
checkoutSession: BillingSession
|
||||
switchSubscriptionInterval: BillingUpdate
|
||||
switchBillingPlan: BillingUpdate
|
||||
@@ -2746,13 +2749,6 @@ export interface Mutation {
|
||||
cancelSwitchMeteredPrice: BillingUpdate
|
||||
refreshEnterpriseValidityToken: Scalars['Boolean']
|
||||
setEnterpriseKey: EnterpriseLicenseInfoDTO
|
||||
createNavigationMenuItem: NavigationMenuItem
|
||||
updateNavigationMenuItem: NavigationMenuItem
|
||||
deleteNavigationMenuItem: NavigationMenuItem
|
||||
createApiKey: ApiKey
|
||||
updateApiKey?: ApiKey
|
||||
revokeApiKey?: ApiKey
|
||||
assignRoleToApiKey: Scalars['Boolean']
|
||||
updateWorkspaceMemberRole: WorkspaceMember
|
||||
createOneRole: Role
|
||||
updateOneRole: Role
|
||||
@@ -2771,6 +2767,10 @@ export interface Mutation {
|
||||
createApprovedAccessDomain: ApprovedAccessDomain
|
||||
deleteApprovedAccessDomain: Scalars['Boolean']
|
||||
validateApprovedAccessDomain: ApprovedAccessDomain
|
||||
createApiKey: ApiKey
|
||||
updateApiKey?: ApiKey
|
||||
revokeApiKey?: ApiKey
|
||||
assignRoleToApiKey: Scalars['Boolean']
|
||||
createOneField: Field
|
||||
updateOneField: Field
|
||||
deleteOneField: Field
|
||||
@@ -3898,6 +3898,45 @@ export interface PageLayoutGenqlSelection{
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface FileWithSignedUrlGenqlSelection{
|
||||
id?: boolean | number
|
||||
path?: boolean | number
|
||||
size?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
url?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface RecordIdentifierGenqlSelection{
|
||||
id?: boolean | number
|
||||
labelIdentifier?: boolean | number
|
||||
imageIdentifier?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface NavigationMenuItemGenqlSelection{
|
||||
id?: boolean | number
|
||||
userWorkspaceId?: boolean | number
|
||||
targetRecordId?: boolean | number
|
||||
targetObjectMetadataId?: boolean | number
|
||||
viewId?: boolean | number
|
||||
type?: boolean | number
|
||||
name?: boolean | number
|
||||
link?: boolean | number
|
||||
icon?: boolean | number
|
||||
color?: boolean | number
|
||||
folderId?: boolean | number
|
||||
position?: boolean | number
|
||||
applicationId?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
updatedAt?: boolean | number
|
||||
targetRecordIdentifier?: RecordIdentifierGenqlSelection
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface ObjectRecordEventPropertiesGenqlSelection{
|
||||
updatedFields?: boolean | number
|
||||
before?: boolean | number
|
||||
@@ -4143,16 +4182,6 @@ export interface ApprovedAccessDomainGenqlSelection{
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface FileWithSignedUrlGenqlSelection{
|
||||
id?: boolean | number
|
||||
path?: boolean | number
|
||||
size?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
url?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface WorkspaceInvitationGenqlSelection{
|
||||
id?: boolean | number
|
||||
email?: boolean | number
|
||||
@@ -4648,35 +4677,6 @@ export interface WorkspaceInviteHashValidGenqlSelection{
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface RecordIdentifierGenqlSelection{
|
||||
id?: boolean | number
|
||||
labelIdentifier?: boolean | number
|
||||
imageIdentifier?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface NavigationMenuItemGenqlSelection{
|
||||
id?: boolean | number
|
||||
userWorkspaceId?: boolean | number
|
||||
targetRecordId?: boolean | number
|
||||
targetObjectMetadataId?: boolean | number
|
||||
viewId?: boolean | number
|
||||
type?: boolean | number
|
||||
name?: boolean | number
|
||||
link?: boolean | number
|
||||
icon?: boolean | number
|
||||
color?: boolean | number
|
||||
folderId?: boolean | number
|
||||
position?: boolean | number
|
||||
applicationId?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
updatedAt?: boolean | number
|
||||
targetRecordIdentifier?: RecordIdentifierGenqlSelection
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface LogicFunctionExecutionResultGenqlSelection{
|
||||
/** Execution result in JSON format */
|
||||
data?: boolean | number
|
||||
@@ -5607,6 +5607,8 @@ export interface EventLogQueryResultGenqlSelection{
|
||||
}
|
||||
|
||||
export interface QueryGenqlSelection{
|
||||
navigationMenuItems?: NavigationMenuItemGenqlSelection
|
||||
navigationMenuItem?: (NavigationMenuItemGenqlSelection & { __args: {id: Scalars['UUID']} })
|
||||
getPageLayoutWidgets?: (PageLayoutWidgetGenqlSelection & { __args: {pageLayoutTabId: Scalars['String']} })
|
||||
getPageLayoutWidget?: (PageLayoutWidgetGenqlSelection & { __args: {id: Scalars['String']} })
|
||||
getPageLayoutTabs?: (PageLayoutTabGenqlSelection & { __args: {pageLayoutId: Scalars['String']} })
|
||||
@@ -5654,13 +5656,11 @@ export interface QueryGenqlSelection{
|
||||
enterprisePortalSession?: { __args: {returnUrlPath?: (Scalars['String'] | null)} } | boolean | number
|
||||
enterpriseCheckoutSession?: { __args: {billingInterval?: (Scalars['String'] | null)} } | boolean | number
|
||||
enterpriseSubscriptionStatus?: EnterpriseSubscriptionStatusDTOGenqlSelection
|
||||
navigationMenuItems?: NavigationMenuItemGenqlSelection
|
||||
navigationMenuItem?: (NavigationMenuItemGenqlSelection & { __args: {id: Scalars['UUID']} })
|
||||
apiKeys?: ApiKeyGenqlSelection
|
||||
apiKey?: (ApiKeyGenqlSelection & { __args: {input: GetApiKeyInput} })
|
||||
getRoles?: RoleGenqlSelection
|
||||
findWorkspaceInvitations?: WorkspaceInvitationGenqlSelection
|
||||
getApprovedAccessDomains?: ApprovedAccessDomainGenqlSelection
|
||||
apiKeys?: ApiKeyGenqlSelection
|
||||
apiKey?: (ApiKeyGenqlSelection & { __args: {input: GetApiKeyInput} })
|
||||
getToolIndex?: ToolIndexEntryGenqlSelection
|
||||
getToolInputSchema?: { __args: {toolName: Scalars['String']} }
|
||||
field?: (FieldGenqlSelection & { __args: {
|
||||
@@ -5768,6 +5768,15 @@ export interface BarChartDataInput {objectMetadataId: Scalars['UUID'],configurat
|
||||
export interface MutationGenqlSelection{
|
||||
addQueryToEventStream?: { __args: {input: AddQuerySubscriptionInput} }
|
||||
removeQueryFromEventStream?: { __args: {input: RemoveQueryFromEventStreamInput} }
|
||||
createNavigationMenuItem?: (NavigationMenuItemGenqlSelection & { __args: {input: CreateNavigationMenuItemInput} })
|
||||
updateNavigationMenuItem?: (NavigationMenuItemGenqlSelection & { __args: {input: UpdateOneNavigationMenuItemInput} })
|
||||
deleteNavigationMenuItem?: (NavigationMenuItemGenqlSelection & { __args: {id: Scalars['UUID']} })
|
||||
uploadAIChatFile?: (FileWithSignedUrlGenqlSelection & { __args: {file: Scalars['Upload']} })
|
||||
uploadWorkflowFile?: (FileWithSignedUrlGenqlSelection & { __args: {file: Scalars['Upload']} })
|
||||
uploadWorkspaceLogo?: (FileWithSignedUrlGenqlSelection & { __args: {file: Scalars['Upload']} })
|
||||
uploadWorkspaceMemberProfilePicture?: (FileWithSignedUrlGenqlSelection & { __args: {file: Scalars['Upload']} })
|
||||
uploadFilesFieldFile?: (FileWithSignedUrlGenqlSelection & { __args: {file: Scalars['Upload'], fieldMetadataId: Scalars['String']} })
|
||||
uploadFilesFieldFileByUniversalIdentifier?: (FileWithSignedUrlGenqlSelection & { __args: {file: Scalars['Upload'], fieldMetadataUniversalIdentifier: Scalars['String']} })
|
||||
createObjectEvent?: (AnalyticsGenqlSelection & { __args: {event: Scalars['String'], recordId: Scalars['UUID'], objectMetadataId: Scalars['UUID'], properties?: (Scalars['JSON'] | null)} })
|
||||
trackAnalytics?: (AnalyticsGenqlSelection & { __args: {type: AnalyticsType, name?: (Scalars['String'] | null), event?: (Scalars['String'] | null), properties?: (Scalars['JSON'] | null)} })
|
||||
createPageLayoutWidget?: (PageLayoutWidgetGenqlSelection & { __args: {input: CreatePageLayoutWidgetInput} })
|
||||
@@ -5815,12 +5824,6 @@ export interface MutationGenqlSelection{
|
||||
createOneAgent?: (AgentGenqlSelection & { __args: {input: CreateAgentInput} })
|
||||
updateOneAgent?: (AgentGenqlSelection & { __args: {input: UpdateAgentInput} })
|
||||
deleteOneAgent?: (AgentGenqlSelection & { __args: {input: AgentIdInput} })
|
||||
uploadAIChatFile?: (FileWithSignedUrlGenqlSelection & { __args: {file: Scalars['Upload']} })
|
||||
uploadWorkflowFile?: (FileWithSignedUrlGenqlSelection & { __args: {file: Scalars['Upload']} })
|
||||
uploadWorkspaceLogo?: (FileWithSignedUrlGenqlSelection & { __args: {file: Scalars['Upload']} })
|
||||
uploadWorkspaceMemberProfilePicture?: (FileWithSignedUrlGenqlSelection & { __args: {file: Scalars['Upload']} })
|
||||
uploadFilesFieldFile?: (FileWithSignedUrlGenqlSelection & { __args: {file: Scalars['Upload'], fieldMetadataId: Scalars['String']} })
|
||||
uploadFilesFieldFileByUniversalIdentifier?: (FileWithSignedUrlGenqlSelection & { __args: {file: Scalars['Upload'], fieldMetadataUniversalIdentifier: Scalars['String']} })
|
||||
checkoutSession?: (BillingSessionGenqlSelection & { __args: {recurringInterval: SubscriptionInterval, plan: BillingPlanKey, requirePaymentMethod: Scalars['Boolean'], successUrlPath?: (Scalars['String'] | null)} })
|
||||
switchSubscriptionInterval?: BillingUpdateGenqlSelection
|
||||
switchBillingPlan?: BillingUpdateGenqlSelection
|
||||
@@ -5831,13 +5834,6 @@ export interface MutationGenqlSelection{
|
||||
cancelSwitchMeteredPrice?: BillingUpdateGenqlSelection
|
||||
refreshEnterpriseValidityToken?: boolean | number
|
||||
setEnterpriseKey?: (EnterpriseLicenseInfoDTOGenqlSelection & { __args: {enterpriseKey: Scalars['String']} })
|
||||
createNavigationMenuItem?: (NavigationMenuItemGenqlSelection & { __args: {input: CreateNavigationMenuItemInput} })
|
||||
updateNavigationMenuItem?: (NavigationMenuItemGenqlSelection & { __args: {input: UpdateOneNavigationMenuItemInput} })
|
||||
deleteNavigationMenuItem?: (NavigationMenuItemGenqlSelection & { __args: {id: Scalars['UUID']} })
|
||||
createApiKey?: (ApiKeyGenqlSelection & { __args: {input: CreateApiKeyInput} })
|
||||
updateApiKey?: (ApiKeyGenqlSelection & { __args: {input: UpdateApiKeyInput} })
|
||||
revokeApiKey?: (ApiKeyGenqlSelection & { __args: {input: RevokeApiKeyInput} })
|
||||
assignRoleToApiKey?: { __args: {apiKeyId: Scalars['UUID'], roleId: Scalars['UUID']} }
|
||||
updateWorkspaceMemberRole?: (WorkspaceMemberGenqlSelection & { __args: {workspaceMemberId: Scalars['UUID'], roleId: Scalars['UUID']} })
|
||||
createOneRole?: (RoleGenqlSelection & { __args: {createRoleInput: CreateRoleInput} })
|
||||
updateOneRole?: (RoleGenqlSelection & { __args: {updateRoleInput: UpdateRoleInput} })
|
||||
@@ -5856,6 +5852,10 @@ export interface MutationGenqlSelection{
|
||||
createApprovedAccessDomain?: (ApprovedAccessDomainGenqlSelection & { __args: {input: CreateApprovedAccessDomainInput} })
|
||||
deleteApprovedAccessDomain?: { __args: {input: DeleteApprovedAccessDomainInput} }
|
||||
validateApprovedAccessDomain?: (ApprovedAccessDomainGenqlSelection & { __args: {input: ValidateApprovedAccessDomainInput} })
|
||||
createApiKey?: (ApiKeyGenqlSelection & { __args: {input: CreateApiKeyInput} })
|
||||
updateApiKey?: (ApiKeyGenqlSelection & { __args: {input: UpdateApiKeyInput} })
|
||||
revokeApiKey?: (ApiKeyGenqlSelection & { __args: {input: RevokeApiKeyInput} })
|
||||
assignRoleToApiKey?: { __args: {apiKeyId: Scalars['UUID'], roleId: Scalars['UUID']} }
|
||||
createOneField?: (FieldGenqlSelection & { __args: {input: CreateOneFieldMetadataInput} })
|
||||
updateOneField?: (FieldGenqlSelection & { __args: {input: UpdateOneFieldMetadataInput} })
|
||||
deleteOneField?: (FieldGenqlSelection & { __args: {input: DeleteOneFieldInput} })
|
||||
@@ -5965,6 +5965,16 @@ export interface AddQuerySubscriptionInput {eventStreamId: Scalars['String'],que
|
||||
|
||||
export interface RemoveQueryFromEventStreamInput {eventStreamId: Scalars['String'],queryId: Scalars['String']}
|
||||
|
||||
export interface CreateNavigationMenuItemInput {id?: (Scalars['UUID'] | null),userWorkspaceId?: (Scalars['UUID'] | null),targetRecordId?: (Scalars['UUID'] | null),targetObjectMetadataId?: (Scalars['UUID'] | null),viewId?: (Scalars['UUID'] | null),type: NavigationMenuItemType,name?: (Scalars['String'] | null),link?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),color?: (Scalars['String'] | null),folderId?: (Scalars['UUID'] | null),position?: (Scalars['Float'] | null)}
|
||||
|
||||
export interface UpdateOneNavigationMenuItemInput {
|
||||
/** The id of the record to update */
|
||||
id: Scalars['UUID'],
|
||||
/** The record to update */
|
||||
update: UpdateNavigationMenuItemInput}
|
||||
|
||||
export interface UpdateNavigationMenuItemInput {folderId?: (Scalars['UUID'] | null),position?: (Scalars['Float'] | null),name?: (Scalars['String'] | null),link?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),color?: (Scalars['String'] | null)}
|
||||
|
||||
export interface CreatePageLayoutWidgetInput {pageLayoutTabId: Scalars['UUID'],title: Scalars['String'],type: WidgetType,objectMetadataId?: (Scalars['UUID'] | null),gridPosition: GridPositionInput,position?: (Scalars['JSON'] | null),configuration: Scalars['JSON']}
|
||||
|
||||
export interface GridPositionInput {row: Scalars['Float'],column: Scalars['Float'],rowSpan: Scalars['Float'],columnSpan: Scalars['Float']}
|
||||
@@ -6107,22 +6117,6 @@ export interface CreateAgentInput {name?: (Scalars['String'] | null),label: Scal
|
||||
|
||||
export interface UpdateAgentInput {id: Scalars['UUID'],name?: (Scalars['String'] | null),label?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),description?: (Scalars['String'] | null),prompt?: (Scalars['String'] | null),modelId?: (Scalars['String'] | null),roleId?: (Scalars['UUID'] | null),responseFormat?: (Scalars['JSON'] | null),modelConfiguration?: (Scalars['JSON'] | null),evaluationInputs?: (Scalars['String'][] | null)}
|
||||
|
||||
export interface CreateNavigationMenuItemInput {userWorkspaceId?: (Scalars['UUID'] | null),targetRecordId?: (Scalars['UUID'] | null),targetObjectMetadataId?: (Scalars['UUID'] | null),viewId?: (Scalars['UUID'] | null),type: NavigationMenuItemType,name?: (Scalars['String'] | null),link?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),color?: (Scalars['String'] | null),folderId?: (Scalars['UUID'] | null),position?: (Scalars['Float'] | null)}
|
||||
|
||||
export interface UpdateOneNavigationMenuItemInput {
|
||||
/** The id of the record to update */
|
||||
id: Scalars['UUID'],
|
||||
/** The record to update */
|
||||
update: UpdateNavigationMenuItemInput}
|
||||
|
||||
export interface UpdateNavigationMenuItemInput {folderId?: (Scalars['UUID'] | null),position?: (Scalars['Float'] | null),name?: (Scalars['String'] | null),link?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),color?: (Scalars['String'] | null)}
|
||||
|
||||
export interface CreateApiKeyInput {name: Scalars['String'],expiresAt: Scalars['String'],revokedAt?: (Scalars['String'] | null),roleId: Scalars['UUID']}
|
||||
|
||||
export interface UpdateApiKeyInput {id: Scalars['UUID'],name?: (Scalars['String'] | null),expiresAt?: (Scalars['String'] | null),revokedAt?: (Scalars['String'] | null)}
|
||||
|
||||
export interface RevokeApiKeyInput {id: Scalars['UUID']}
|
||||
|
||||
export interface CreateRoleInput {id?: (Scalars['String'] | null),label: Scalars['String'],description?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),canUpdateAllSettings?: (Scalars['Boolean'] | null),canAccessAllTools?: (Scalars['Boolean'] | null),canReadAllObjectRecords?: (Scalars['Boolean'] | null),canUpdateAllObjectRecords?: (Scalars['Boolean'] | null),canSoftDeleteAllObjectRecords?: (Scalars['Boolean'] | null),canDestroyAllObjectRecords?: (Scalars['Boolean'] | null),canBeAssignedToUsers?: (Scalars['Boolean'] | null),canBeAssignedToAgents?: (Scalars['Boolean'] | null),canBeAssignedToApiKeys?: (Scalars['Boolean'] | null)}
|
||||
|
||||
export interface UpdateRoleInput {update: UpdateRolePayload,
|
||||
@@ -6153,6 +6147,12 @@ export interface DeleteApprovedAccessDomainInput {id: Scalars['UUID']}
|
||||
|
||||
export interface ValidateApprovedAccessDomainInput {validationToken: Scalars['String'],approvedAccessDomainId: Scalars['UUID']}
|
||||
|
||||
export interface CreateApiKeyInput {name: Scalars['String'],expiresAt: Scalars['String'],revokedAt?: (Scalars['String'] | null),roleId: Scalars['UUID']}
|
||||
|
||||
export interface UpdateApiKeyInput {id: Scalars['UUID'],name?: (Scalars['String'] | null),expiresAt?: (Scalars['String'] | null),revokedAt?: (Scalars['String'] | null)}
|
||||
|
||||
export interface RevokeApiKeyInput {id: Scalars['UUID']}
|
||||
|
||||
export interface CreateOneFieldMetadataInput {
|
||||
/** The record to create */
|
||||
field: CreateFieldInput}
|
||||
@@ -6824,6 +6824,30 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
|
||||
|
||||
|
||||
|
||||
const FileWithSignedUrl_possibleTypes: string[] = ['FileWithSignedUrl']
|
||||
export const isFileWithSignedUrl = (obj?: { __typename?: any } | null): obj is FileWithSignedUrl => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isFileWithSignedUrl"')
|
||||
return FileWithSignedUrl_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const RecordIdentifier_possibleTypes: string[] = ['RecordIdentifier']
|
||||
export const isRecordIdentifier = (obj?: { __typename?: any } | null): obj is RecordIdentifier => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isRecordIdentifier"')
|
||||
return RecordIdentifier_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const NavigationMenuItem_possibleTypes: string[] = ['NavigationMenuItem']
|
||||
export const isNavigationMenuItem = (obj?: { __typename?: any } | null): obj is NavigationMenuItem => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isNavigationMenuItem"')
|
||||
return NavigationMenuItem_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const ObjectRecordEventProperties_possibleTypes: string[] = ['ObjectRecordEventProperties']
|
||||
export const isObjectRecordEventProperties = (obj?: { __typename?: any } | null): obj is ObjectRecordEventProperties => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isObjectRecordEventProperties"')
|
||||
@@ -7040,14 +7064,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
|
||||
|
||||
|
||||
|
||||
const FileWithSignedUrl_possibleTypes: string[] = ['FileWithSignedUrl']
|
||||
export const isFileWithSignedUrl = (obj?: { __typename?: any } | null): obj is FileWithSignedUrl => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isFileWithSignedUrl"')
|
||||
return FileWithSignedUrl_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const WorkspaceInvitation_possibleTypes: string[] = ['WorkspaceInvitation']
|
||||
export const isWorkspaceInvitation = (obj?: { __typename?: any } | null): obj is WorkspaceInvitation => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isWorkspaceInvitation"')
|
||||
@@ -7536,22 +7552,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
|
||||
|
||||
|
||||
|
||||
const RecordIdentifier_possibleTypes: string[] = ['RecordIdentifier']
|
||||
export const isRecordIdentifier = (obj?: { __typename?: any } | null): obj is RecordIdentifier => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isRecordIdentifier"')
|
||||
return RecordIdentifier_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const NavigationMenuItem_possibleTypes: string[] = ['NavigationMenuItem']
|
||||
export const isNavigationMenuItem = (obj?: { __typename?: any } | null): obj is NavigationMenuItem => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isNavigationMenuItem"')
|
||||
return NavigationMenuItem_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const LogicFunctionExecutionResult_possibleTypes: string[] = ['LogicFunctionExecutionResult']
|
||||
export const isLogicFunctionExecutionResult = (obj?: { __typename?: any } | null): obj is LogicFunctionExecutionResult => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isLogicFunctionExecutionResult"')
|
||||
@@ -8593,6 +8593,14 @@ export const enumPageLayoutType = {
|
||||
DASHBOARD: 'DASHBOARD' as const
|
||||
}
|
||||
|
||||
export const enumNavigationMenuItemType = {
|
||||
VIEW: 'VIEW' as const,
|
||||
FOLDER: 'FOLDER' as const,
|
||||
LINK: 'LINK' as const,
|
||||
OBJECT: 'OBJECT' as const,
|
||||
RECORD: 'RECORD' as const
|
||||
}
|
||||
|
||||
export const enumMetadataEventAction = {
|
||||
CREATED: 'CREATED' as const,
|
||||
UPDATED: 'UPDATED' as const,
|
||||
@@ -8685,14 +8693,6 @@ export const enumRelationType = {
|
||||
MANY_TO_ONE: 'MANY_TO_ONE' as const
|
||||
}
|
||||
|
||||
export const enumNavigationMenuItemType = {
|
||||
VIEW: 'VIEW' as const,
|
||||
FOLDER: 'FOLDER' as const,
|
||||
LINK: 'LINK' as const,
|
||||
OBJECT: 'OBJECT' as const,
|
||||
RECORD: 'RECORD' as const
|
||||
}
|
||||
|
||||
export const enumLogicFunctionExecutionStatus = {
|
||||
IDLE: 'IDLE' as const,
|
||||
SUCCESS: 'SUCCESS' as const,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -25,7 +25,7 @@ export const fromCreateNavigationMenuItemInputToFlatNavigationMenuItemToCreate =
|
||||
AllFlatEntityMaps,
|
||||
'flatObjectMetadataMaps' | 'flatViewMaps'
|
||||
>): FlatNavigationMenuItem => {
|
||||
const id = uuidv4();
|
||||
const id = createNavigationMenuItemInput.id ?? uuidv4();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
let position = createNavigationMenuItemInput.position;
|
||||
|
||||
+5
@@ -13,6 +13,11 @@ import { NavigationMenuItemType } from 'src/engine/metadata-modules/navigation-m
|
||||
|
||||
@InputType()
|
||||
export class CreateNavigationMenuItemInput {
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
id?: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
|
||||
+6
-5
@@ -1,6 +1,5 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
@@ -12,8 +11,8 @@ import { NavigationMenuItemResolver } from 'src/engine/metadata-modules/navigati
|
||||
import { NavigationMenuItemService } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service';
|
||||
import { NavigationMenuItemAccessService } from 'src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-access.service';
|
||||
import { NavigationMenuItemDeletionService } from 'src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-deletion.service';
|
||||
import { NavigationMenuItemRecordIdentifierService } from 'src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-record-identifier.service';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
||||
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
||||
|
||||
@@ -25,8 +24,6 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
FlatNavigationMenuItemModule,
|
||||
PermissionsModule,
|
||||
FileModule,
|
||||
UserRoleModule,
|
||||
ApiKeyModule,
|
||||
],
|
||||
providers: [
|
||||
NavigationMenuItemService,
|
||||
@@ -35,9 +32,13 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
NavigationMenuItemDeletionListener,
|
||||
NavigationMenuItemDeletionJob,
|
||||
NavigationMenuItemResolver,
|
||||
NavigationMenuItemRecordIdentifierService,
|
||||
NavigationMenuItemGraphqlApiExceptionInterceptor,
|
||||
WorkspaceMigrationGraphqlApiExceptionInterceptor,
|
||||
],
|
||||
exports: [NavigationMenuItemService],
|
||||
exports: [
|
||||
NavigationMenuItemService,
|
||||
NavigationMenuItemRecordIdentifierService,
|
||||
],
|
||||
})
|
||||
export class NavigationMenuItemModule {}
|
||||
|
||||
+7
-145
@@ -2,15 +2,8 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { isApiKeyAuthContext } from 'src/engine/core-modules/auth/guards/is-api-key-auth-context.guard';
|
||||
import { isApplicationAuthContext } from 'src/engine/core-modules/auth/guards/is-application-auth-context.guard';
|
||||
import { isUserAuthContext } from 'src/engine/core-modules/auth/guards/is-user-auth-context.guard';
|
||||
import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { getRecordDisplayName } from 'src/engine/core-modules/record-crud/utils/get-record-display-name.util';
|
||||
import { getRecordImageIdentifier } from 'src/engine/core-modules/record-crud/utils/get-record-image-identifier.util';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
@@ -27,12 +20,8 @@ import {
|
||||
NavigationMenuItemExceptionCode,
|
||||
} from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.exception';
|
||||
import { NavigationMenuItemAccessService } from 'src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-access.service';
|
||||
import { getMinimalSelectForRecordIdentifier } from 'src/engine/metadata-modules/navigation-menu-item/utils/get-minimal-select-for-record-identifier.util';
|
||||
import { NavigationMenuItemRecordIdentifierService } from 'src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-record-identifier.service';
|
||||
import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@@ -43,10 +32,7 @@ export class NavigationMenuItemService {
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly navigationMenuItemAccessService: NavigationMenuItemAccessService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly fileService: FileService,
|
||||
private readonly userRoleService: UserRoleService,
|
||||
private readonly apiKeyRoleService: ApiKeyRoleService,
|
||||
private readonly navigationMenuItemRecordIdentifierService: NavigationMenuItemRecordIdentifierService,
|
||||
) {}
|
||||
|
||||
async findAll({
|
||||
@@ -379,41 +365,6 @@ export class NavigationMenuItemService {
|
||||
);
|
||||
}
|
||||
|
||||
private async getRoleId(
|
||||
authContext: WorkspaceAuthContext,
|
||||
workspaceId: string,
|
||||
): Promise<string | undefined> {
|
||||
if (isApiKeyAuthContext(authContext)) {
|
||||
return this.apiKeyRoleService.getRoleIdForApiKeyId(
|
||||
authContext.apiKey.id,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
isApplicationAuthContext(authContext) &&
|
||||
isDefined(authContext.application.defaultRoleId)
|
||||
) {
|
||||
return authContext.application.defaultRoleId;
|
||||
}
|
||||
|
||||
if (isUserAuthContext(authContext)) {
|
||||
try {
|
||||
return await this.userRoleService.getRoleIdForUserWorkspace({
|
||||
userWorkspaceId: authContext.userWorkspaceId,
|
||||
workspaceId,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof PermissionsException) {
|
||||
return undefined;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async findTargetRecord({
|
||||
targetRecordId,
|
||||
targetObjectMetadataId,
|
||||
@@ -425,104 +376,15 @@ export class NavigationMenuItemService {
|
||||
workspaceId: string;
|
||||
authContext: WorkspaceAuthContext;
|
||||
}): Promise<RecordIdentifierDTO | null> {
|
||||
const { flatObjectMetadataMaps, flatFieldMetadataMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
try {
|
||||
return await this.navigationMenuItemRecordIdentifierService.resolveRecordIdentifier(
|
||||
{
|
||||
targetRecordId,
|
||||
targetObjectMetadataId,
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps', 'flatFieldMetadataMaps'],
|
||||
authContext,
|
||||
},
|
||||
);
|
||||
|
||||
const objectMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: targetObjectMetadataId,
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(objectMetadata)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const roleId = await this.getRoleId(authContext, workspaceId);
|
||||
|
||||
if (!isDefined(roleId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rolePermissionConfig: RolePermissionConfig = {
|
||||
unionOf: [roleId],
|
||||
};
|
||||
|
||||
const minimalSelectColumns = getMinimalSelectForRecordIdentifier({
|
||||
flatObjectMetadata: objectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const record =
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const repository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
objectMetadata.nameSingular,
|
||||
rolePermissionConfig,
|
||||
);
|
||||
|
||||
const alias = objectMetadata.nameSingular;
|
||||
const queryBuilder = repository.createQueryBuilder(alias);
|
||||
|
||||
queryBuilder.select([]);
|
||||
|
||||
for (const column of minimalSelectColumns) {
|
||||
queryBuilder.addSelect(`"${alias}"."${column}"`, column);
|
||||
}
|
||||
|
||||
const rawResult = await queryBuilder
|
||||
.where(`${alias}.id = :id`, { id: targetRecordId })
|
||||
.getRawOne();
|
||||
|
||||
if (!isDefined(rawResult)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const formattedRecord = formatResult<Record<string, unknown>>(
|
||||
rawResult,
|
||||
objectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
return formattedRecord;
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
|
||||
if (!isDefined(record)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const labelIdentifier = getRecordDisplayName(
|
||||
record,
|
||||
objectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const imageIdentifier = getRecordImageIdentifier({
|
||||
record,
|
||||
flatObjectMetadata: objectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
signUrl: (url: string) =>
|
||||
this.fileService.signFileUrl({
|
||||
url,
|
||||
workspaceId,
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
id: record.id as string,
|
||||
labelIdentifier,
|
||||
imageIdentifier,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof PermissionsException) {
|
||||
return null;
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { getRecordDisplayName } from 'src/engine/core-modules/record-crud/utils/get-record-display-name.util';
|
||||
import { getRecordImageIdentifier } from 'src/engine/core-modules/record-crud/utils/get-record-image-identifier.util';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { RecordIdentifierDTO } from 'src/engine/metadata-modules/navigation-menu-item/dtos/record-identifier.dto';
|
||||
import { getMinimalSelectForRecordIdentifier } from 'src/engine/metadata-modules/navigation-menu-item/utils/get-minimal-select-for-record-identifier.util';
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class NavigationMenuItemRecordIdentifierService {
|
||||
constructor(
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly fileService: FileService,
|
||||
private readonly permissionsService: PermissionsService,
|
||||
) {}
|
||||
|
||||
async resolveRecordIdentifier({
|
||||
targetRecordId,
|
||||
targetObjectMetadataId,
|
||||
workspaceId,
|
||||
authContext,
|
||||
}: {
|
||||
targetRecordId: string;
|
||||
targetObjectMetadataId: string;
|
||||
workspaceId: string;
|
||||
authContext?: WorkspaceAuthContext;
|
||||
}): Promise<RecordIdentifierDTO | null> {
|
||||
const { flatObjectMetadataMaps, flatFieldMetadataMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps', 'flatFieldMetadataMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const objectMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: targetObjectMetadataId,
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(objectMetadata)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const minimalSelectColumns = getMinimalSelectForRecordIdentifier({
|
||||
flatObjectMetadata: objectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const resolvedAuthContext: WorkspaceAuthContext =
|
||||
authContext ??
|
||||
({
|
||||
type: 'system',
|
||||
workspace: { id: workspaceId },
|
||||
} as WorkspaceAuthContext);
|
||||
|
||||
const rolePermissionConfig =
|
||||
await this.permissionsService.resolveRolePermissionConfigFromAuthContext(
|
||||
resolvedAuthContext,
|
||||
);
|
||||
|
||||
const record =
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const repository = await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
objectMetadata.nameSingular,
|
||||
rolePermissionConfig ?? { shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const alias = objectMetadata.nameSingular;
|
||||
const queryBuilder = repository.createQueryBuilder(alias);
|
||||
|
||||
queryBuilder.select([]);
|
||||
|
||||
for (const column of minimalSelectColumns) {
|
||||
queryBuilder.addSelect(`"${alias}"."${column}"`, column);
|
||||
}
|
||||
|
||||
const rawResult = await queryBuilder
|
||||
.where(`${alias}.id = :id`, { id: targetRecordId })
|
||||
.getRawOne();
|
||||
|
||||
if (!isDefined(rawResult)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatResult<Record<string, unknown>>(
|
||||
rawResult,
|
||||
objectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
},
|
||||
resolvedAuthContext,
|
||||
);
|
||||
|
||||
if (!isDefined(record)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const labelIdentifier = getRecordDisplayName(
|
||||
record,
|
||||
objectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const imageIdentifier = getRecordImageIdentifier({
|
||||
record,
|
||||
flatObjectMetadata: objectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
signUrl: (url: string) =>
|
||||
this.fileService.signFileUrl({
|
||||
url,
|
||||
workspaceId,
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
id: record.id as string,
|
||||
labelIdentifier,
|
||||
imageIdentifier,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,11 @@ import {
|
||||
ApplicationException,
|
||||
ApplicationExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { isApiKeyAuthContext } from 'src/engine/core-modules/auth/guards/is-api-key-auth-context.guard';
|
||||
import { isApplicationAuthContext } from 'src/engine/core-modules/auth/guards/is-application-auth-context.guard';
|
||||
import { isSystemAuthContext } from 'src/engine/core-modules/auth/guards/is-system-auth-context.guard';
|
||||
import { isUserAuthContext } from 'src/engine/core-modules/auth/guards/is-user-auth-context.guard';
|
||||
import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type';
|
||||
import { TOOL_PERMISSION_FLAGS } from 'src/engine/metadata-modules/permissions/constants/tool-permission-flags';
|
||||
import {
|
||||
PermissionsException,
|
||||
@@ -129,6 +134,48 @@ export class PermissionsService {
|
||||
objectsPermissions: {},
|
||||
}) as const satisfies UserWorkspacePermissions;
|
||||
|
||||
// TODO: this could likely be handled in the ORM layer
|
||||
public async resolveRolePermissionConfigFromAuthContext(
|
||||
authContext: WorkspaceAuthContext,
|
||||
): Promise<RolePermissionConfig | null> {
|
||||
const workspaceId = authContext.workspace.id;
|
||||
|
||||
if (isSystemAuthContext(authContext)) {
|
||||
return { shouldBypassPermissionChecks: true };
|
||||
}
|
||||
|
||||
if (isApiKeyAuthContext(authContext)) {
|
||||
const roleId = await this.apiKeyRoleService.getRoleIdForApiKeyId(
|
||||
authContext.apiKey.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return { intersectionOf: [roleId] };
|
||||
}
|
||||
|
||||
if (
|
||||
isApplicationAuthContext(authContext) &&
|
||||
isDefined(authContext.application.defaultRoleId)
|
||||
) {
|
||||
return { intersectionOf: [authContext.application.defaultRoleId] };
|
||||
}
|
||||
|
||||
if (isUserAuthContext(authContext)) {
|
||||
const roleId = await this.userRoleService.getRoleIdForUserWorkspace({
|
||||
userWorkspaceId: authContext.userWorkspaceId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!isDefined(roleId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { intersectionOf: [roleId] };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
|
||||
+9
@@ -22,6 +22,7 @@ import { type EventStreamData } from 'src/engine/subscriptions/types/event-strea
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { type WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type';
|
||||
import { NavigationMenuItemRecordIdentifierService } from 'src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-record-identifier.service';
|
||||
import { WorkspaceEventEmitterService } from 'src/engine/workspace-event-emitter/workspace-event-emitter.service';
|
||||
|
||||
jest.mock(
|
||||
@@ -301,6 +302,14 @@ describe('WorkspaceEventEmitterService', () => {
|
||||
provide: CommonSelectFieldsHelper,
|
||||
useValue: new CommonSelectFieldsHelper(),
|
||||
},
|
||||
{
|
||||
provide: NavigationMenuItemRecordIdentifierService,
|
||||
useValue: {
|
||||
enrichNavigationMenuItemEventsWithRecordIdentifiers: jest
|
||||
.fn()
|
||||
.mockResolvedValue(undefined),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
+2
@@ -4,6 +4,7 @@ import { ProcessNestedRelationsV2Helper } from 'src/engine/api/common/common-nes
|
||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/common/common-nested-relations-processor/process-nested-relations.helper';
|
||||
import { CommonSelectFieldsHelper } from 'src/engine/api/common/common-select-fields/common-select-fields-helper';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { NavigationMenuItemModule } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.module';
|
||||
import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
@@ -16,6 +17,7 @@ import { WorkspaceEventEmitterService } from 'src/engine/workspace-event-emitter
|
||||
SubscriptionsModule,
|
||||
WorkspaceCacheModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
NavigationMenuItemModule,
|
||||
],
|
||||
providers: [
|
||||
WorkspaceEventEmitter,
|
||||
|
||||
+57
@@ -31,6 +31,7 @@ import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/typ
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { NavigationMenuItemRecordIdentifierService } from 'src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-record-identifier.service';
|
||||
import { enrichFieldMetadataEventWithRelations } from 'src/engine/workspace-event-emitter/utils/enrich-field-metadata-event-with-relations.util';
|
||||
import { UserWorkspaceRoleMap } from 'src/engine/metadata-modules/role-target/types/user-workspace-role-map';
|
||||
import { type FlatRowLevelPermissionPredicateGroupMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group-maps.type';
|
||||
@@ -62,6 +63,7 @@ export class WorkspaceEventEmitterService {
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly commonSelectFieldsHelper: CommonSelectFieldsHelper,
|
||||
private readonly navigationMenuItemRecordIdentifierService: NavigationMenuItemRecordIdentifierService,
|
||||
) {}
|
||||
|
||||
async publish(
|
||||
@@ -134,6 +136,8 @@ export class WorkspaceEventEmitterService {
|
||||
const enrichedMetadataEventBatch = isMetadata
|
||||
? await this.enrichFieldMetadataEventsWithRelations(
|
||||
eventBatch as MetadataEventBatch,
|
||||
).then((batch) =>
|
||||
this.enrichNavigationMenuItemEventsWithTargetRecordIdentifier(batch),
|
||||
)
|
||||
: undefined;
|
||||
|
||||
@@ -251,6 +255,59 @@ export class WorkspaceEventEmitterService {
|
||||
return { ...metadataEventBatch, events: enrichedEvents };
|
||||
}
|
||||
|
||||
private async enrichNavigationMenuItemEventsWithTargetRecordIdentifier(
|
||||
metadataEventBatch: MetadataEventBatch,
|
||||
): Promise<MetadataEventBatch> {
|
||||
if (metadataEventBatch.metadataName !== 'navigationMenuItem') {
|
||||
return metadataEventBatch;
|
||||
}
|
||||
|
||||
const enrichedEvents = await Promise.all(
|
||||
metadataEventBatch.events.map(async (event) => {
|
||||
if (
|
||||
!('after' in event.properties) ||
|
||||
!isDefined(event.properties.after)
|
||||
) {
|
||||
return event;
|
||||
}
|
||||
|
||||
const after = event.properties.after as Record<string, unknown>;
|
||||
const targetRecordId = after.targetRecordId as string | undefined;
|
||||
const targetObjectMetadataId = after.targetObjectMetadataId as
|
||||
| string
|
||||
| undefined;
|
||||
|
||||
if (!isDefined(targetRecordId) || !isDefined(targetObjectMetadataId)) {
|
||||
return event;
|
||||
}
|
||||
|
||||
const targetRecordIdentifier =
|
||||
await this.navigationMenuItemRecordIdentifierService.resolveRecordIdentifier(
|
||||
{
|
||||
targetRecordId,
|
||||
targetObjectMetadataId,
|
||||
workspaceId: metadataEventBatch.workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const enrichedAfter: Record<string, unknown> = {
|
||||
...after,
|
||||
targetRecordIdentifier,
|
||||
};
|
||||
|
||||
return {
|
||||
...event,
|
||||
properties: {
|
||||
...event.properties,
|
||||
after: enrichedAfter,
|
||||
},
|
||||
} as typeof event;
|
||||
}),
|
||||
);
|
||||
|
||||
return { ...metadataEventBatch, events: enrichedEvents };
|
||||
}
|
||||
|
||||
private async processObjectRecordStreamEvents(
|
||||
streamChannelId: string,
|
||||
streamData: EventStreamData,
|
||||
|
||||
Reference in New Issue
Block a user