Skip to content

Commit 9a7b64a

Browse files
authored
Merge pull request #425 from contentstack/VE-5851-no-update-permission-disable-fields
feat(VE-5851): disable fields of update restricted entries
2 parents be0e784 + ce30b02 commit 9a7b64a

23 files changed

Lines changed: 945 additions & 82 deletions

src/visualBuilder/components/FieldRevert/FieldRevertComponent.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ export const VariantRevertDropdown = (props: any) => {
195195
invertTooltipPosition,
196196
toggleVariantDropdown,
197197
variantStatus = BASE_VARIANT_STATUS,
198+
disabled,
198199
} = props;
199200
const dropdownRef = useRef<HTMLDivElement>(null);
200201
useHandleOutsideClick(dropdownRef, closeDropdown);
@@ -221,6 +222,7 @@ export const VariantRevertDropdown = (props: any) => {
221222
style={{ padding: "6px" }}
222223
data-tooltip={"Variant"}
223224
data-testid={`visual-builder-canvas-variant-icon`}
225+
disabled={disabled}
224226
>
225227
<VariantIcon />
226228
</button>
@@ -236,6 +238,7 @@ export const VariantRevertDropdown = (props: any) => {
236238
data-tooltip={"Variant Revert"}
237239
data-testid={`visual-builder-canvas-variant-revert`}
238240
onClick={toggleVariantDropdown}
241+
disabled={disabled}
239242
>
240243
<VariantIcon />
241244
<CaretIcon open={props.isOpen} />

src/visualBuilder/components/FieldToolbar.tsx

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ import { getDOMEditStack } from "../utils/getCsDataOfElement";
3434
import { VariantIcon } from "./icons/variant";
3535
import {
3636
BASE_VARIANT_STATUS,
37-
FieldRevertComponent,
3837
getFieldVariantStatus,
3938
IVariantStatus,
4039
VariantRevertDropdown,
4140
} from "./FieldRevert/FieldRevertComponent";
4241
import { LoadingIcon } from "./icons/loading";
42+
import { EntryPermissions } from "../utils/getEntryPermissions";
4343

4444
export type FieldDetails = Pick<
4545
VisualBuilderCslpEventDetails,
@@ -52,6 +52,7 @@ interface MultipleFieldToolbarProps {
5252
eventDetails: VisualBuilderCslpEventDetails;
5353
hideOverlay: () => void;
5454
isVariant?: boolean;
55+
entryPermissions?: EntryPermissions;
5556
}
5657

5758
function handleReplaceAsset(fieldMetadata: CslpData) {
@@ -107,7 +108,11 @@ function handleFormFieldFocus(eventDetails: VisualBuilderCslpEventDetails) {
107108
function FieldToolbarComponent(
108109
props: MultipleFieldToolbarProps
109110
): JSX.Element | null {
110-
const { eventDetails, isVariant: isVariantOrParentOfVariant } = props;
111+
const {
112+
eventDetails,
113+
isVariant: isVariantOrParentOfVariant,
114+
entryPermissions,
115+
} = props;
111116
const { fieldMetadata, editableElement: targetElement } = eventDetails;
112117
const [isFormLoading, setIsFormLoading] = useState(false);
113118

@@ -131,16 +136,17 @@ function FieldToolbarComponent(
131136
let fieldType = null;
132137
let isWholeMultipleField = false;
133138

139+
let disableFieldActions = false;
134140
if (fieldSchema) {
135-
const { isDisabled } = isFieldDisabled(fieldSchema, {
136-
editableElement: targetElement,
137-
fieldMetadata,
138-
});
139-
140-
// field is disabled, no actions needed
141-
if (isDisabled) {
142-
return null;
143-
}
141+
const { isDisabled } = isFieldDisabled(
142+
fieldSchema,
143+
{
144+
editableElement: targetElement,
145+
fieldMetadata,
146+
},
147+
entryPermissions
148+
);
149+
disableFieldActions = isDisabled;
144150

145151
fieldType = getFieldType(fieldSchema);
146152
isModalEditable = ALLOWED_MODAL_EDITABLE_FIELD.includes(fieldType);
@@ -165,7 +171,8 @@ function FieldToolbarComponent(
165171
fieldMetadata.instance.fieldPathWithIndex ||
166172
fieldMetadata.multipleFieldMetadata?.index === -1);
167173

168-
isReplaceAllowed = ALLOWED_REPLACE_FIELDS.includes(fieldType) && !isWholeMultipleField;
174+
isReplaceAllowed =
175+
ALLOWED_REPLACE_FIELDS.includes(fieldType) && !isWholeMultipleField;
169176
// if (
170177
// DEFAULT_MULTIPLE_FIELDS.includes(fieldType) &&
171178
// isWholeMultipleField &&
@@ -201,6 +208,7 @@ function FieldToolbarComponent(
201208
e.stopPropagation();
202209
handleEdit(fieldMetadata);
203210
}}
211+
disabled={disableFieldActions}
204212
>
205213
<Icon />
206214
</button>
@@ -232,6 +240,7 @@ function FieldToolbarComponent(
232240
return;
233241
}
234242
}}
243+
disabled={disableFieldActions}
235244
>
236245
<ReplaceAssetIcon />
237246
</button>
@@ -250,8 +259,10 @@ function FieldToolbarComponent(
250259
invertTooltipPosition,
251260
},
252261
{
253-
[visualBuilderStyles()["visual-builder__button--comment-loader"]]: isFormLoading,
254-
"visual-builder__button--comment-loader": isFormLoading
262+
[visualBuilderStyles()[
263+
"visual-builder__button--comment-loader"
264+
]]: isFormLoading,
265+
"visual-builder__button--comment-loader": isFormLoading,
255266
}
256267
)}
257268
data-tooltip={"Form"}
@@ -303,6 +314,7 @@ function FieldToolbarComponent(
303314
</button>
304315
);
305316

317+
// TODO sibling count is incorrect for this purpose
306318
const totalElementCount = targetElement?.parentNode?.childElementCount ?? 1;
307319
const indexOfElement = fieldMetadata?.multipleFieldMetadata?.index;
308320

@@ -387,6 +399,7 @@ function FieldToolbarComponent(
387399
closeDropdown={closeVariantDropdown}
388400
invertTooltipPosition={invertTooltipPosition}
389401
toggleVariantDropdown={toggleVariantDropdown}
402+
disabled={disableFieldActions}
390403
/>
391404
) : null}
392405
{isMultiple && !isWholeMultipleField ? (
@@ -409,7 +422,9 @@ function FieldToolbarComponent(
409422
"previous"
410423
);
411424
}}
412-
disabled={disableMoveLeft}
425+
disabled={
426+
disableFieldActions || disableMoveLeft
427+
}
413428
>
414429
<MoveLeftIcon
415430
className={classNames({
@@ -419,7 +434,10 @@ function FieldToolbarComponent(
419434
"visual-builder__rotate--90"
420435
]]: direction === "vertical",
421436
})}
422-
disabled={disableMoveLeft}
437+
disabled={
438+
disableFieldActions ||
439+
disableMoveLeft
440+
}
423441
/>
424442
</button>
425443

@@ -441,7 +459,9 @@ function FieldToolbarComponent(
441459
"next"
442460
);
443461
}}
444-
disabled={disableMoveRight}
462+
disabled={
463+
disableFieldActions || disableMoveRight
464+
}
445465
>
446466
<MoveRightIcon
447467
className={classNames({
@@ -451,7 +471,10 @@ function FieldToolbarComponent(
451471
"visual-builder__rotate--90"
452472
]]: direction === "vertical",
453473
})}
454-
disabled={disableMoveRight}
474+
disabled={
475+
disableFieldActions ||
476+
disableMoveRight
477+
}
455478
/>
456479
</button>
457480

@@ -470,6 +493,7 @@ function FieldToolbarComponent(
470493
e.stopPropagation();
471494
handleDeleteInstance(fieldMetadata);
472495
}}
496+
disabled={disableFieldActions}
473497
>
474498
<DeleteIcon />
475499
</button>

src/visualBuilder/components/__test__/fieldLabelWrapper.test.tsx

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import { render, cleanup, waitFor,screen } from "@testing-library/preact";
1+
import { waitFor } from "@testing-library/preact";
22
import FieldLabelWrapperComponent from "../fieldLabelWrapper";
33
import { CslpData } from "../../../cslp/types/cslp.types";
44
import { VisualBuilderCslpEventDetails } from "../../types/visualBuilder.types";
55
import { VisualBuilderPostMessageEvents } from "../../utils/types/postMessage.types";
66
import { singleLineFieldSchema } from "../../../__test__/data/fields";
77
import { asyncRender } from "../../../__test__/utils";
8+
import { isFieldDisabled } from "../../utils/isFieldDisabled";
9+
import { FieldSchemaMap } from "../../utils/fieldSchemaMap";
10+
import { getEntryPermissionsCached } from "../../utils/getEntryPermissionsCached";
11+
import React from "preact/compat";
812

913
const DISPLAY_NAMES = {
1014
mockFieldCslp: "Field 0",
@@ -73,14 +77,7 @@ vi.mock("../../utils/visualBuilderPostMessage", async () => {
7377
});
7478

7579
vi.mock("../../utils/isFieldDisabled", () => ({
76-
isFieldDisabled: vi
77-
.fn()
78-
.mockReturnValueOnce({ isDisabled: false })
79-
.mockReturnValueOnce({ isDisabled: false })
80-
.mockReturnValueOnce({
81-
isDisabled: true,
82-
reason: "You have only read access to this field",
83-
}),
80+
isFieldDisabled: vi.fn().mockReturnValue({ isDisabled: false }),
8481
}));
8582

8683
vi.mock("../../../cslp", () => ({
@@ -90,7 +87,17 @@ vi.mock("../../../cslp", () => ({
9087
}));
9188

9289
describe("FieldLabelWrapperComponent", () => {
93-
afterEach(cleanup);
90+
beforeEach(() => {
91+
vi.mocked(isFieldDisabled).mockReturnValue({
92+
isDisabled: false,
93+
// @ts-expect-error - reason is an unexported literal
94+
reason: "",
95+
});
96+
});
97+
98+
afterEach(() => {
99+
vi.clearAllMocks();
100+
});
94101

95102
const mockFieldMetadata: CslpData = {
96103
entry_uid: "",
@@ -156,6 +163,11 @@ describe("FieldLabelWrapperComponent", () => {
156163
});
157164

158165
test("renders with correct class when field is disabled", async () => {
166+
vi.mocked(isFieldDisabled).mockReturnValue({
167+
isDisabled: true,
168+
// @ts-expect-error - reason is an unexported literal
169+
reason: "You have only read access to this field",
170+
});
159171
const { findByTestId } = await asyncRender(
160172
<FieldLabelWrapperComponent
161173
fieldMetadata={mockFieldMetadata}
@@ -175,4 +187,46 @@ describe("FieldLabelWrapperComponent", () => {
175187
);
176188
});
177189
});
190+
191+
test("calls isFieldDisabled with correct arguments", async () => {
192+
const mockFieldSchema = { ...singleLineFieldSchema };
193+
const mockEntryPermissions = {
194+
create: true,
195+
read: true,
196+
update: false,
197+
delete: true,
198+
publish: true,
199+
};
200+
201+
vi.mocked(FieldSchemaMap.getFieldSchema).mockResolvedValue(
202+
mockFieldSchema
203+
);
204+
vi.mocked(getEntryPermissionsCached).mockResolvedValue(
205+
mockEntryPermissions
206+
);
207+
208+
await asyncRender(
209+
<FieldLabelWrapperComponent
210+
fieldMetadata={mockFieldMetadata}
211+
eventDetails={mockEventDetails}
212+
parentPaths={[]}
213+
getParentEditableElement={mockGetParentEditable}
214+
/>
215+
);
216+
217+
// wait for component to mount
218+
await waitFor(() => {
219+
expect(
220+
document.querySelector(
221+
".visual-builder__focused-toolbar__field-label-container"
222+
)
223+
).toBeInTheDocument();
224+
});
225+
226+
expect(isFieldDisabled).toHaveBeenCalledWith(
227+
mockFieldSchema,
228+
mockEventDetails,
229+
mockEntryPermissions
230+
);
231+
});
178232
});

0 commit comments

Comments
 (0)