From 14aa4a8901dc587c58e2fce7ac49b10b47c8f6ad Mon Sep 17 00:00:00 2001 From: Bassel Kanso Date: Wed, 28 May 2025 13:09:00 +0300 Subject: [PATCH] feat(content-manager): contional fields --- .../api/country/content-types/country/schema.json | 15 ++++++++++++++- packages/core/admin/admin/src/index.ts | 1 + .../admin/src/utils/rulesEngine.ts} | 0 .../admin/src/utils/tests/rulesEngine.test.ts} | 2 +- packages/core/admin/package.json | 2 ++ .../history/components/VersionInputRenderer.tsx | 13 +++++++++++++ .../pages/EditView/components/DocumentActions.tsx | 1 - .../pages/EditView/components/InputRenderer.tsx | 12 ++++++++++++ .../server/src/controllers/collection-types.ts | 2 +- .../core/src/services/document-service/entries.ts | 1 - .../core/src/services/entity-validator/index.ts | 5 ++++- packages/core/types/package.json | 2 ++ packages/core/types/src/schema/attribute/base.ts | 11 +++++++++++ packages/core/utils/package.json | 2 -- packages/core/utils/src/index.ts | 1 - yarn.lock | 6 ++++-- 16 files changed, 65 insertions(+), 11 deletions(-) rename packages/core/{utils/src/rules-engine.ts => admin/admin/src/utils/rulesEngine.ts} (100%) rename packages/core/{utils/src/__tests__/rules-engine.test.ts => admin/admin/src/utils/tests/rulesEngine.test.ts} (97%) diff --git a/examples/getstarted/src/api/country/content-types/country/schema.json b/examples/getstarted/src/api/country/content-types/country/schema.json index d673d70904..d8ef87a6e8 100755 --- a/examples/getstarted/src/api/country/content-types/country/schema.json +++ b/examples/getstarted/src/api/country/content-types/country/schema.json @@ -9,7 +9,6 @@ "name": "Country" }, "options": { - "comment": "", "draftAndPublish": false }, "pluginOptions": { @@ -30,6 +29,7 @@ }, "code": { "type": "string", + "required":true, "maxLength": 3, "unique": true, "minLength": 2, @@ -37,7 +37,20 @@ "i18n": { "localized": true } + }, + "conditions": { + "visible": { + "==": [ + { + "var": "visible" + }, + true + ] + } } + }, + "visible": { + "type": "boolean" } } } diff --git a/packages/core/admin/admin/src/index.ts b/packages/core/admin/admin/src/index.ts index b2bd9be448..3fb6431884 100644 --- a/packages/core/admin/admin/src/index.ts +++ b/packages/core/admin/admin/src/index.ts @@ -76,6 +76,7 @@ export type { Widget as WidgetType } from './core/apis/Widgets'; export { translatedErrors } from './utils/translatedErrors'; export * from './utils/getFetchClient'; export * from './utils/baseQuery'; +export * from './utils/rulesEngine'; export * from './services/api'; export type { CMAdminConfiguration } from './types/adminConfiguration'; diff --git a/packages/core/utils/src/rules-engine.ts b/packages/core/admin/admin/src/utils/rulesEngine.ts similarity index 100% rename from packages/core/utils/src/rules-engine.ts rename to packages/core/admin/admin/src/utils/rulesEngine.ts diff --git a/packages/core/utils/src/__tests__/rules-engine.test.ts b/packages/core/admin/admin/src/utils/tests/rulesEngine.test.ts similarity index 97% rename from packages/core/utils/src/__tests__/rules-engine.test.ts rename to packages/core/admin/admin/src/utils/tests/rulesEngine.test.ts index 8ab5b4aa12..0dabce13b5 100644 --- a/packages/core/utils/src/__tests__/rules-engine.test.ts +++ b/packages/core/admin/admin/src/utils/tests/rulesEngine.test.ts @@ -1,4 +1,4 @@ -import { createRulesEngine, Condition } from '../rules-engine'; +import { createRulesEngine, Condition } from '../rulesEngine'; describe('RulesEngine with is & isNot operator', () => { const engine = createRulesEngine(); diff --git a/packages/core/admin/package.json b/packages/core/admin/package.json index 40636782bc..67eba6e1fa 100644 --- a/packages/core/admin/package.json +++ b/packages/core/admin/package.json @@ -110,6 +110,7 @@ "inquirer": "8.2.5", "invariant": "^2.2.4", "is-localhost-ip": "2.0.0", + "json-logic-js": "2.0.5", "jsonwebtoken": "9.0.0", "koa": "2.15.4", "koa-compose": "4.1.0", @@ -148,6 +149,7 @@ "@types/codemirror5": "npm:@types/codemirror@^5.60.15", "@types/fs-extra": "11.0.4", "@types/invariant": "2.2.36", + "@types/json-logic-js": "2.0.8", "@types/jsonwebtoken": "9.0.3", "@types/koa-passport": "6.0.1", "@types/lodash": "^4.14.191", diff --git a/packages/core/content-manager/admin/src/history/components/VersionInputRenderer.tsx b/packages/core/content-manager/admin/src/history/components/VersionInputRenderer.tsx index 29ca4758fa..6ae080501e 100644 --- a/packages/core/content-manager/admin/src/history/components/VersionInputRenderer.tsx +++ b/packages/core/content-manager/admin/src/history/components/VersionInputRenderer.tsx @@ -6,6 +6,7 @@ import { InputRenderer as FormInputRenderer, useField, Form, + createRulesEngine, } from '@strapi/admin/strapi-admin'; import { Alert, Box, Field, Flex, Link, Tooltip, Typography } from '@strapi/design-system'; import { useIntl } from 'react-intl'; @@ -325,6 +326,7 @@ const VersionInputRenderer = ({ const canReadFields = useDocumentRBAC('InputRenderer', (rbac) => rbac.canReadFields); const canUpdateFields = useDocumentRBAC('InputRenderer', (rbac) => rbac.canUpdateFields); const canUserAction = useDocumentRBAC('InputRenderer', (rbac) => rbac.canUserAction); + const fieldValues = useForm('Fields', (state) => state.values); const editableFields = id ? canUpdateFields : canCreateFields; const readableFields = id ? canReadFields : canCreateFields; @@ -345,6 +347,17 @@ const VersionInputRenderer = ({ edit: { components: componentsLayout }, } = useDocLayout(); + const rulesEngine = createRulesEngine(); + const attribute = props.attribute; + + if (attribute?.conditions && attribute?.conditions.visible) { + const isVisible = rulesEngine.evaluate(attribute?.conditions.visible, fieldValues); + + if (!isVisible) { + return null; + } + } + if (!visible) { return null; } diff --git a/packages/core/content-manager/admin/src/pages/EditView/components/DocumentActions.tsx b/packages/core/content-manager/admin/src/pages/EditView/components/DocumentActions.tsx index 009860ed6b..cee900a6e1 100644 --- a/packages/core/content-manager/admin/src/pages/EditView/components/DocumentActions.tsx +++ b/packages/core/content-manager/admin/src/pages/EditView/components/DocumentActions.tsx @@ -1011,7 +1011,6 @@ const UpdateAction: DocumentActionComponent = ({ return; } - if (isCloning) { const res = await clone( { diff --git a/packages/core/content-manager/admin/src/pages/EditView/components/InputRenderer.tsx b/packages/core/content-manager/admin/src/pages/EditView/components/InputRenderer.tsx index 2a5e455e62..95d91089da 100644 --- a/packages/core/content-manager/admin/src/pages/EditView/components/InputRenderer.tsx +++ b/packages/core/content-manager/admin/src/pages/EditView/components/InputRenderer.tsx @@ -5,6 +5,7 @@ import { useForm, InputRenderer as FormInputRenderer, useField, + createRulesEngine, } from '@strapi/admin/strapi-admin'; import { useIntl } from 'react-intl'; @@ -55,6 +56,7 @@ const InputRenderer = ({ visible, hint: providedHint, document, ...props }: Inpu const canReadFields = useDocumentRBAC('InputRenderer', (rbac) => rbac.canReadFields); const canUpdateFields = useDocumentRBAC('InputRenderer', (rbac) => rbac.canUpdateFields); const canUserAction = useDocumentRBAC('InputRenderer', (rbac) => rbac.canUserAction); + const fieldValues = useForm('Fields', (state) => state.values); let idToCheck = document.document?.documentId; if (collectionType === SINGLE_TYPES) { @@ -80,6 +82,16 @@ const InputRenderer = ({ visible, hint: providedHint, document, ...props }: Inpu // We pass field in case of Custom Fields to keep backward compatibility const field = useField(props.name); + const rulesEngine = createRulesEngine(); + const attribute = document.schema?.attributes[props.name]; + + if (attribute?.conditions && attribute?.conditions.visible) { + const isVisible = rulesEngine.evaluate(attribute?.conditions.visible, fieldValues); + + if (!isVisible) { + return null; + } + } if (!visible) { return null; diff --git a/packages/core/content-manager/server/src/controllers/collection-types.ts b/packages/core/content-manager/server/src/controllers/collection-types.ts index 6f829dc0b1..73e6455efa 100644 --- a/packages/core/content-manager/server/src/controllers/collection-types.ts +++ b/packages/core/content-manager/server/src/controllers/collection-types.ts @@ -23,7 +23,7 @@ const createDocument = async (ctx: any, opts?: Options) => { const { userAbility, user } = ctx.state; const { model } = ctx.params; const { body } = ctx.request; - + console.log('createDocument', { model, body }); const documentManager = getService('document-manager'); const permissionChecker = getService('permission-checker').create({ userAbility, model }); diff --git a/packages/core/core/src/services/document-service/entries.ts b/packages/core/core/src/services/document-service/entries.ts index dedda29209..1dcfa46f91 100644 --- a/packages/core/core/src/services/document-service/entries.ts +++ b/packages/core/core/src/services/document-service/entries.ts @@ -18,7 +18,6 @@ const createEntriesService = ( async function createEntry(params = {} as any) { const { data, ...restParams } = await transformParamsDocumentId(uid, params); - const query = transformParamsToQuery(uid, pickSelectionParams(restParams) as any); // select / populate // Validation diff --git a/packages/core/core/src/services/entity-validator/index.ts b/packages/core/core/src/services/entity-validator/index.ts index 2ed2a3edd7..8b8c625d0c 100644 --- a/packages/core/core/src/services/entity-validator/index.ts +++ b/packages/core/core/src/services/entity-validator/index.ts @@ -267,7 +267,7 @@ const createScalarAttributeValidator = const createAttributeValidator = (createOrUpdate: CreateOrUpdate) => (metas: ValidatorMetas, options: ValidatorContext) => { let validator = yup.mixed(); - + console.log('metas ==>', metas.model, metas.attr); if (isMediaAttribute(metas.attr)) { validator = yup.mixed(); } else if (isScalarAttribute(metas.attr)) { @@ -376,6 +376,9 @@ const createValidateEntity = (createOrUpdate: CreateOrUpdate) => { `Invalid payload submitted for the ${createOrUpdate} of an entity of type ${displayName}. Expected an object, but got ${typeof data}` ); } + console.log('model', model); + console.log('data', data); + console.log('entity', entity); const validator = createModelValidator(createOrUpdate)( { diff --git a/packages/core/types/package.json b/packages/core/types/package.json index c946d1111a..fc0e33661f 100644 --- a/packages/core/types/package.json +++ b/packages/core/types/package.json @@ -51,6 +51,7 @@ "@strapi/permissions": "5.13.1", "@strapi/utils": "5.13.1", "commander": "8.3.0", + "json-logic-js": "2.0.5", "koa": "2.15.4", "koa-body": "6.0.1", "node-schedule": "2.1.1", @@ -61,6 +62,7 @@ "devDependencies": { "@strapi/ts-zen": "^0.2.0", "@types/jest": "29.5.2", + "@types/json-logic-js": "2.0.8", "@types/koa": "2.13.4", "@types/koa__router": "12.0.0", "@types/node-schedule": "2.1.7", diff --git a/packages/core/types/src/schema/attribute/base.ts b/packages/core/types/src/schema/attribute/base.ts index 65b73fd50e..211e3c20c3 100644 --- a/packages/core/types/src/schema/attribute/base.ts +++ b/packages/core/types/src/schema/attribute/base.ts @@ -1,3 +1,7 @@ +import jsonLogic from 'json-logic-js'; + +type JsonLogicCondition = jsonLogic.RulesLogic; + /** * Enumerates all possible attribute types in Strapi. * @@ -65,6 +69,13 @@ export interface Attribute { */ searchable?: boolean; + /** + * Specifies conditional logic on an attribute's visibility. + */ + conditions?: { + visible: JsonLogicCondition; + }; + /** * Database validations and settings * https://docs.strapi.io/dev-docs/backend-customization/models#database-validations-and-settings diff --git a/packages/core/utils/package.json b/packages/core/utils/package.json index c719c16b33..403e9bbe5a 100644 --- a/packages/core/utils/package.json +++ b/packages/core/utils/package.json @@ -50,7 +50,6 @@ "date-fns": "2.30.0", "execa": "5.1.1", "http-errors": "2.0.0", - "json-logic-js": "2.0.5", "lodash": "4.17.21", "node-machine-id": "1.1.12", "p-map": "4.0.0", @@ -60,7 +59,6 @@ }, "devDependencies": { "@types/http-errors": "2.0.4", - "@types/json-logic-js": "2.0.8", "@types/koa": "2.13.4", "@types/node": "18.19.24", "eslint-config-custom": "5.13.1", diff --git a/packages/core/utils/src/index.ts b/packages/core/utils/src/index.ts index 7c3449819f..01280d920c 100644 --- a/packages/core/utils/src/index.ts +++ b/packages/core/utils/src/index.ts @@ -24,7 +24,6 @@ export * as errors from './errors'; export * as contentTypes from './content-types'; export * as relations from './relations'; export * as hooks from './hooks'; -export * as rulesEngine from './rules-engine'; export * from './zod'; export * from './primitives'; diff --git a/yarn.lock b/yarn.lock index 316af8384b..f25707f5c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8599,6 +8599,7 @@ __metadata: "@types/codemirror5": "npm:@types/codemirror@^5.60.15" "@types/fs-extra": "npm:11.0.4" "@types/invariant": "npm:2.2.36" + "@types/json-logic-js": "npm:2.0.8" "@types/jsonwebtoken": "npm:9.0.3" "@types/koa-passport": "npm:6.0.1" "@types/lodash": "npm:^4.14.191" @@ -8629,6 +8630,7 @@ __metadata: inquirer: "npm:8.2.5" invariant: "npm:^2.2.4" is-localhost-ip: "npm:2.0.0" + json-logic-js: "npm:2.0.5" jsonwebtoken: "npm:9.0.0" koa: "npm:2.15.4" koa-body: "npm:6.0.1" @@ -9703,11 +9705,13 @@ __metadata: "@strapi/ts-zen": "npm:^0.2.0" "@strapi/utils": "npm:5.13.1" "@types/jest": "npm:29.5.2" + "@types/json-logic-js": "npm:2.0.8" "@types/koa": "npm:2.13.4" "@types/koa__router": "npm:12.0.0" "@types/node-schedule": "npm:2.1.7" commander: "npm:8.3.0" eslint-config-custom: "npm:5.13.1" + json-logic-js: "npm:2.0.5" koa: "npm:2.15.4" koa-body: "npm:6.0.1" lodash: "npm:4.17.21" @@ -9855,14 +9859,12 @@ __metadata: dependencies: "@sindresorhus/slugify": "npm:1.1.0" "@types/http-errors": "npm:2.0.4" - "@types/json-logic-js": "npm:2.0.8" "@types/koa": "npm:2.13.4" "@types/node": "npm:18.19.24" date-fns: "npm:2.30.0" eslint-config-custom: "npm:5.13.1" execa: "npm:5.1.1" http-errors: "npm:2.0.0" - json-logic-js: "npm:2.0.5" koa: "npm:2.15.4" koa-body: "npm:6.0.1" lodash: "npm:4.17.21"