Skip to content

Commit 1a04a21

Browse files
feat: disable editing when workflow stage rules restrict the same
Fetch workflow stage details for the entry from parent and disable editing based on the received data
1 parent 71913fb commit 1a04a21

12 files changed

Lines changed: 398 additions & 184 deletions

src/visualBuilder/components/fieldLabelWrapper.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import { visualBuilderStyles } from "../visualBuilder.style";
1414
import { CslpError } from "./CslpError";
1515
import { hasPostMessageError } from "../utils/errorHandling";
1616
import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types";
17-
import { getEntryPermissionsCached } from "../utils/getEntryPermissionsCached";
1817
import { ContentTypeIcon } from "./icons";
1918
import { ToolbarTooltip } from "./Tooltip";
19+
import { fetchEntryPermissionsAndStageDetails } from "../utils/fetchEntryPermissionsAndStageDetails";
2020

2121
interface ReferenceParentMap {
2222
[entryUid: string]: {
@@ -55,7 +55,6 @@ async function getReferenceParentMap() {
5555
console.warn("[getFieldLabelWrapper] Error getting reference parent map", e);
5656
return {};
5757
}
58-
5958
}
6059

6160
interface FieldLabelWrapperProps {
@@ -157,15 +156,18 @@ function FieldLabelWrapperComponent(
157156
return;
158157
}
159158

160-
const entryPermissions = await getEntryPermissionsCached({
161-
entryUid: props.fieldMetadata.entry_uid,
162-
contentTypeUid: props.fieldMetadata.content_type_uid,
163-
locale: props.fieldMetadata.locale,
164-
});
159+
const { acl: entryAcl, workflowStage: entryWorkflowStageDetails } =
160+
await fetchEntryPermissionsAndStageDetails({
161+
entryUid: props.fieldMetadata.entry_uid,
162+
contentTypeUid: props.fieldMetadata.content_type_uid,
163+
locale: props.fieldMetadata.locale,
164+
variantUid: props.fieldMetadata.variant,
165+
});
165166
const { isDisabled: fieldDisabled, reason } = isFieldDisabled(
166167
fieldSchema,
167168
eventDetails,
168-
entryPermissions
169+
entryAcl,
170+
entryWorkflowStageDetails
169171
);
170172

171173
const currentFieldDisplayName =

src/visualBuilder/listeners/mouseClick.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ import { isCollabThread } from "../generators/generateThread";
3030
import { toggleCollabPopup } from "../generators/generateThread";
3131
import { fixSvgXPath } from "../utils/collabUtils";
3232
import { v4 as uuidV4 } from "uuid";
33-
import { getEntryPermissionsCached } from "../utils/getEntryPermissionsCached";
33+
import { CslpData } from "../../cslp/types/cslp.types";
34+
import { fetchEntryPermissionsAndStageDetails } from "../utils/fetchEntryPermissionsAndStageDetails";
3435

3536
export type HandleBuilderInteractionParams = Omit<
3637
EventListenerHandlerParams,
@@ -45,7 +46,11 @@ type AddFocusOverlayParams = Pick<
4546
type AddFocusedToolbarParams = Pick<
4647
EventListenerHandlerParams,
4748
"eventDetails" | "focusedToolbar"
48-
> & { hideOverlay: () => void; isVariant: boolean, options?: { isHover?: boolean } };
49+
> & {
50+
hideOverlay: () => void;
51+
isVariant: boolean;
52+
options?: { isHover?: boolean };
53+
};
4954

5055
function addOverlay(params: AddFocusOverlayParams) {
5156
if (!params.overlayWrapper || !params.editableElement) return;
@@ -295,35 +300,35 @@ function addOverlayAndToolbar(
295300
async function handleFieldSchemaAndIndividualFields(
296301
params: HandleBuilderInteractionParams,
297302
eventDetails: any,
298-
fieldMetadata: any,
303+
fieldMetadata: CslpData,
299304
editableElement: Element,
300305
previousSelectedElement: Element | null
301306
) {
302-
const { content_type_uid, entry_uid, fieldPath, locale } = fieldMetadata;
307+
const {
308+
content_type_uid,
309+
entry_uid,
310+
fieldPath,
311+
locale,
312+
variant: variantUid,
313+
} = fieldMetadata;
303314
const fieldSchema = await FieldSchemaMap.getFieldSchema(
304315
content_type_uid,
305316
fieldPath
306317
);
307-
let entryAcl;
308-
try {
309-
entryAcl = await getEntryPermissionsCached({
318+
const { acl: entryAcl, workflowStage: entryWorkflowStageDetails } =
319+
await fetchEntryPermissionsAndStageDetails({
310320
entryUid: entry_uid,
311321
contentTypeUid: content_type_uid,
312322
locale,
323+
variantUid,
313324
});
314-
} catch (error) {
315-
console.error(
316-
"[Visual Builder] Error retrieving entry permissions:",
317-
error
318-
);
319-
return;
320-
}
321325

322326
if (fieldSchema) {
323327
const { isDisabled } = isFieldDisabled(
324328
fieldSchema,
325329
eventDetails,
326-
entryAcl
330+
entryAcl,
331+
entryWorkflowStageDetails
327332
);
328333
if (isDisabled) {
329334
addOverlay({

src/visualBuilder/listeners/mouseHover.ts

Lines changed: 78 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,11 @@ import { visualBuilderStyles } from "../visualBuilder.style";
1313
import { VB_EmptyBlockParentClass } from "../..";
1414
import Config from "../../configManager/configManager";
1515
import { isCollabThread } from "../generators/generateThread";
16-
import { getEntryPermissionsCached } from "../utils/getEntryPermissionsCached";
17-
import { EntryPermissions } from "../utils/getEntryPermissions";
18-
import visualBuilderPostMessage from "../utils/visualBuilderPostMessage";
19-
import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types";
2016
import { HandleBuilderInteractionParams } from "./mouseClick";
21-
import { appendFieldPathDropdown, removeFieldToolbar } from "../generators/generateToolbar";
17+
import { appendFieldPathDropdown } from "../generators/generateToolbar";
2218
import { VisualBuilderCslpEventDetails } from "../types/visualBuilder.types";
2319
import { CslpData } from "../../cslp/types/cslp.types";
20+
import { fetchEntryPermissionsAndStageDetails } from "../utils/fetchEntryPermissionsAndStageDetails";
2421

2522
const config = Config.get();
2623
export interface HandleMouseHoverParams
@@ -71,46 +68,43 @@ function handleCursorPosition(
7168
}
7269
}
7370

74-
function addOutline(params?: AddOutlineParams): void {
75-
if(!params) {
71+
async function addOutline(params?: AddOutlineParams): Promise<void> {
72+
if (!params) {
7673
return;
7774
}
78-
const { editableElement, eventDetails, content_type_uid, fieldPath, fieldMetadata, fieldDisabled } = params;
75+
const {
76+
editableElement,
77+
eventDetails,
78+
content_type_uid,
79+
fieldPath,
80+
fieldMetadata,
81+
fieldDisabled,
82+
} = params;
7983
if (!editableElement) return;
8084
addHoverOutline(editableElement as HTMLElement, fieldDisabled);
81-
FieldSchemaMap.getFieldSchema(content_type_uid, fieldPath).then(
82-
(fieldSchema) => {
83-
let entryAcl: EntryPermissions | undefined;
84-
if (!fieldSchema) return;
85-
getEntryPermissionsCached({
86-
entryUid: fieldMetadata.entry_uid,
87-
contentTypeUid: fieldMetadata.content_type_uid,
88-
locale: fieldMetadata.locale,
89-
})
90-
.then((data) => {
91-
entryAcl = data;
92-
})
93-
.catch((error) => {
94-
console.error(
95-
"[Visual Builder] Error retrieving entry permissions:",
96-
error
97-
);
98-
})
99-
.finally(() => {
100-
const { isDisabled: fieldDisabled } =
101-
isFieldDisabled(
102-
fieldSchema,
103-
eventDetails,
104-
entryAcl
105-
);
106-
addHoverOutline(editableElement, fieldDisabled);
107-
});
108-
}
85+
const fieldSchema = await FieldSchemaMap.getFieldSchema(
86+
content_type_uid,
87+
fieldPath
10988
);
89+
if (!fieldSchema) return;
90+
const { acl: entryAcl, workflowStage: entryWorkflowStageDetails } =
91+
await fetchEntryPermissionsAndStageDetails({
92+
entryUid: fieldMetadata.entry_uid,
93+
contentTypeUid: fieldMetadata.content_type_uid,
94+
locale: fieldMetadata.locale,
95+
variantUid: fieldMetadata.variant,
96+
});
97+
const { isDisabled } = isFieldDisabled(
98+
fieldSchema,
99+
eventDetails,
100+
entryAcl,
101+
entryWorkflowStageDetails
102+
);
103+
addHoverOutline(editableElement, fieldDisabled || isDisabled);
110104
}
111105

112106
const debouncedAddOutline = debounce(addOutline, 50, { trailing: true });
113-
const showOutline = (params?: AddOutlineParams): void => debouncedAddOutline(params);
107+
const showOutline = (params?: AddOutlineParams): Promise<void> | undefined => debouncedAddOutline(params);
114108

115109
function hideDefaultCursor(): void {
116110
if (
@@ -219,7 +213,7 @@ const throttledMouseHover = throttle(async (params: HandleMouseHoverParams) => {
219213
hideCustomCursor(params.customCursor);
220214
return;
221215
}
222-
if(
216+
if (
223217
eventTarget &&
224218
isFieldPathDropdown(eventTarget)
225219
) {
@@ -287,10 +281,7 @@ const throttledMouseHover = throttle(async (params: HandleMouseHoverParams) => {
287281
handleCursorPosition(params.event, params.customCursor);
288282
showCustomCursor(params.customCursor);
289283
return;
290-
} else if (
291-
config?.collab.enable &&
292-
!config?.collab.isFeedbackMode
293-
) {
284+
} else if (config?.collab.enable && !config?.collab.isFeedbackMode) {
294285
hideCustomCursor(params.customCursor);
295286
return;
296287
}
@@ -314,48 +305,11 @@ const throttledMouseHover = throttle(async (params: HandleMouseHoverParams) => {
314305
});
315306
}
316307

317-
/**
318-
* We called it seperately inside the code block to ensure that
319-
* the code will not wait for the promise to resolve.
320-
* If we get a cache miss, we will send a message to the iframe
321-
* without blocking the code.
322-
*/
323-
FieldSchemaMap.getFieldSchema(content_type_uid, fieldPath).then(
324-
(fieldSchema) => {
325-
if (!fieldSchema) return;
326-
327-
let entryAcl: EntryPermissions | undefined;
328-
getEntryPermissionsCached({
329-
entryUid: fieldMetadata.entry_uid,
330-
contentTypeUid: fieldMetadata.content_type_uid,
331-
locale: fieldMetadata.locale,
332-
})
333-
.then((data) => {
334-
entryAcl = data;
335-
})
336-
.catch((error) => {
337-
console.error(
338-
"[Visual Builder] Error retrieving entry permissions:",
339-
error
340-
);
341-
})
342-
.finally(() => {
343-
if (!params.customCursor) return;
344-
const { isDisabled: fieldDisabled } =
345-
isFieldDisabled(
346-
fieldSchema,
347-
eventDetails,
348-
entryAcl
349-
);
350-
const fieldType = getFieldType(fieldSchema);
351-
generateCustomCursor({
352-
fieldType,
353-
customCursor: params.customCursor,
354-
fieldDisabled,
355-
});
356-
});
357-
}
358-
);
308+
// we can generate the cursor asynchronously
309+
generateCursor({
310+
eventDetails,
311+
customCursor: params.customCursor,
312+
});
359313

360314
handleCursorPosition(params.event, params.customCursor);
361315
showCustomCursor(params.customCursor);
@@ -398,6 +352,45 @@ const throttledMouseHover = throttle(async (params: HandleMouseHoverParams) => {
398352
editableElement;
399353
}, 10);
400354

401-
const handleMouseHover = async (params: HandleMouseHoverParams): Promise<void> => await throttledMouseHover(params);
355+
async function generateCursor({
356+
eventDetails,
357+
customCursor,
358+
}: {
359+
eventDetails: VisualBuilderCslpEventDetails;
360+
customCursor: HTMLDivElement | null;
361+
}) {
362+
if (!customCursor) return;
363+
const { fieldMetadata } = eventDetails;
364+
const fieldSchema = await FieldSchemaMap.getFieldSchema(
365+
fieldMetadata.content_type_uid,
366+
fieldMetadata.fieldPath
367+
);
368+
if (!fieldSchema) {
369+
return;
370+
}
371+
const { acl: entryAcl, workflowStage: entryWorkflowStageDetails } =
372+
await fetchEntryPermissionsAndStageDetails({
373+
entryUid: fieldMetadata.entry_uid,
374+
contentTypeUid: fieldMetadata.content_type_uid,
375+
locale: fieldMetadata.locale,
376+
variantUid: fieldMetadata.variant,
377+
});
378+
const { isDisabled: fieldDisabled } = isFieldDisabled(
379+
fieldSchema,
380+
eventDetails,
381+
entryAcl,
382+
entryWorkflowStageDetails
383+
);
384+
const fieldType = getFieldType(fieldSchema);
385+
generateCustomCursor({
386+
fieldType,
387+
customCursor,
388+
fieldDisabled,
389+
});
390+
}
391+
392+
const handleMouseHover = async (
393+
params: HandleMouseHoverParams
394+
): Promise<void> => await throttledMouseHover(params);
402395

403396
export default handleMouseHover;

src/visualBuilder/utils/__test__/handleIndividualFields.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { handleAddButtonsForMultiple, removeAddInstanceButtons } from "../multip
99
import { VisualBuilderPostMessageEvents } from "../types/postMessage.types";
1010
import visualBuilderPostMessage from "../visualBuilderPostMessage";
1111
import { VisualBuilder } from "../..";
12-
import { act, screen } from "@testing-library/preact";
12+
import { act } from "@testing-library/preact";
1313
import { VISUAL_BUILDER_FIELD_TYPE_ATTRIBUTE_KEY } from "../constants";
1414
import { FieldDataType } from "../types/index.types";
1515

@@ -31,6 +31,7 @@ describe("handleIndividualFields", () => {
3131

3232
beforeEach(() => {
3333
eventDetails = {
34+
// @ts-expect-error mocking only required properties
3435
fieldMetadata: {
3536
content_type_uid: "contentTypeUid",
3637
entry_uid: "entryUid",
@@ -79,6 +80,16 @@ describe("handleIndividualFields", () => {
7980
update: true,
8081
delete: true,
8182
publish: true,
83+
},
84+
{
85+
permissions: {
86+
entry: {
87+
update: true,
88+
},
89+
},
90+
stage: {
91+
name: "Unknown"
92+
}
8293
}
8394
);
8495
expect(eventDetails.editableElement.getAttribute(VISUAL_BUILDER_FIELD_TYPE_ATTRIBUTE_KEY)).toBe(fieldType);

0 commit comments

Comments
 (0)