From dfe7780b9044e1982e69285302cf8be29e62b0ea Mon Sep 17 00:00:00 2001 From: soupette Date: Mon, 15 Feb 2021 09:22:39 +0100 Subject: [PATCH] Add documentation Signed-off-by: soupette --- .../Roles/CreatePage/Permissions/README.md | 377 ++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 packages/strapi-admin/ee/admin/containers/Roles/CreatePage/Permissions/README.md diff --git a/packages/strapi-admin/ee/admin/containers/Roles/CreatePage/Permissions/README.md b/packages/strapi-admin/ee/admin/containers/Roles/CreatePage/Permissions/README.md new file mode 100644 index 0000000000..ba9132c00c --- /dev/null +++ b/packages/strapi-admin/ee/admin/containers/Roles/CreatePage/Permissions/README.md @@ -0,0 +1,377 @@ +# Technical documentation + +Here we will cover how the `` component works. +This component is in charge of managing the permissions of the admin panel, it has 4 children one child for each tab. +The collection types & single types tabs uses the same component: `` similarly, the settings & plugins tab uses the `` component. + +The UI uses the layout received from the back-end in order to build the UI. + +## Layout shape + +```js +const layout = { + conditions: [{ id: 'string', displayName: 'string', category: 'string'}], // Array of conditions that can be applied on a permission + sections: { + plugins: [ + { displayName: 'string', action: 'string', subCategory: 'string', plugin: 'string'} + ], + settings: [], // Same shape as the plugins + collectionTypes: { + subjects: [ // Array of subjects + { + uid: 'string', // Collection type uid, + label: 'string', // Collection type label, + properties: [ + { + label: 'string', // Property label ex: Fields, + value: 'string', // Value of the property ex: fields, + children: [ // This corresponds to the fields that we will display + { + label: 'string', + value: 'string', + required: 'boolean', // This key is optional, + children: [], // This key is optional, if it exists it means that a field has nested fields + } + ] + }, + ] + } + ], + actions: [ // Array of actions + { + label: 'string', // Label of the action ex: Create, + actionId: 'string', // id of the action ex: content-manager.explorer.create + subjects: [], // Array of subjects (collection type uid) on which the action can be applied + applyToProperties: [] // Array of properties (ex: fields or locales) on which an action can be applied + } + ] + } + } +} +``` + +--- + +## Concepts + +- Checkbox: has only 1 `checked=true` or `checked=false`. + +- Parent checkbox: A **parent checkbox** is a checkbox which the state value depends on the state of its children ones. It means that we cannot directly access its value from the `modifiedData` object. + Such checkbox can have multiple states: + - someChecked: `true` or `false`, + - checked: `true` of `false` + > Both states are coupled: if `someChecked=true` then `checked=false`. + > In terms of user's interaction when cliking a parent checkbox is will toggle the value of its children. + +Ex: given the following data: + +```js +const modifiedData = { + address: { + create: { + fields: { f1: true, f2: true,} + locales: { en: false, fr: false} + }, + update: { + enabled: true, + } + } +} +``` + +From the `modifiedData` object we can identify 4 parent checkboxes: + +1. `address` which value depends on the values of `address.create` & `address.create.update` here the state will be: `someChecked=true` +2. `create` which value depends on the values of `address.create.fields` & `address.create.locles` here the state will be: `someChecked=true, checked=true` +3. `fields` which value depends on the values of `address.create.fields.f1` & `address.create.fields.f2` here the state will be: `checked=true` +4. `locales` which value depends on the values of `address.create.locales.en` & `address.create.locales.fr` here the state will be `checked=false, someChecked=falses` + +> `address.update` is not a parent checkbox since we can access its value directly `address.update.enabled` + +--- + +## Components architecture + +### `` archictecture + +```js + + + // Used with the `layout.sections.collectionTypes` data + // Used with the `layout.sections.singleTypes` data + // Used with the `layout.sections.settings` data + // Used with the `layout.sections.plugins` data + + +``` + +Below, is the architecture of the `` component: + +```js + + // Component in charge of displaying the global action checkboxes (parent + checkboxes), they are used to toggle all the checkboxes of the associated column + + {' '} + // Wrapper or the collapse and the matrix + + // Main row of a content type => Displays the main actions of a content type + (parent checkboxes) + + {' '} + // Matrix of the actions subject property +
// Row that displays the actions labels inside a property (ex: create, read, + update) + + {' '} + // Displays a subject's property values + // Recursive component if a property has a children key, the component + will return itself + + + + + +``` + +### Building the matrix layout + +In order to build the layout, the components uses the `sections.collectionTypes.subjects` value received from the API. + +#### Retrieving the actions to display in the `` + +Pratical example: + +```js +// layout.sections.collectionTypes.actions +const actions = [ + { + label: 'Create', + actionId: 'content-manager.explorer.A1', + subjects: ['address', 'restaurant'], + applyToProperties: ['fields', 'locales',], + }, + { + label: 'Read', + actionId: 'content-manager.explorer.read', + subjects: ['address'], + applyToProperties: ['fields'], + }, + { + label: 'Delete', + actionId: 'content-manager.explorer.delete', + subjects: ['restaurant'], + } + { + label: 'Publish', + actionId: 'content-manager.explorer.publish', + subjects: [], + } +] +``` + +Here we will display only 3 actions: `create`, `read` and `delete` since they are the only actions that can be applied to a `subject`. + +#### Building the content type's matrix + +We will use the actions defined above and the following data: + +```js +// layout.sections.collectionTypes.subjects +const subjects = [ + { + uid: 'address', + label: 'Address' + properties: { + { + label: 'Fields', + value: 'fields', + children: [ + {value: 'f1', label: 'F1'}, + ] + } + } + }, + { + uid: 'restaurant', + label: 'Restaurant', + properties: [ + { + label: 'Fields', + value: 'fields', + children: [ + { + label: 'F1', + value: 'f1', + children: [ + { + label: 'F11', + value: 'f11', + children: [ + { label: 'F111', value: 'f111' } + ] + } + ] + }, + { label: 'F2', value: 'f2' } + ] + }, + { + label: 'Locales', + value: 'locales', + children: [{ label: 'en', value: 'en'}, { label: 'fr', value: 'fr' }] + } + ] + } +] +``` + +With this layout the ui will look like the following: (`[]` represents a checkbox ) + +| | [] Create | [] Read | [] Delete | `` | Parent Wrapper: `` | +| ------------- | ---------- | -------- | --------- | ------------------- | -------------------------------------------- | +| [] Address | [] | [] | | `` | Parent Wrapper: `` | +| **Fields** | **Create** | **Read** | | `
` | Parent Wrapper: `` | +| [] F1 | [] | [] | | `` | Parent Wrapper: `` | +| | | | | | | +| [] Restaurant | [] Create | [] Read | [] Delete | `` | Parent Wrapper: `` | +| **Fields** | **Create** | | | `
` | Parent Wrapper: `` | +| [] F1 | [] | | | `` | Parent Wrapper: `` | +| F1.F11 | [] | | | `` | Parent Wrapper: `` | +| F1.F11.F111 | [] | | | `` | Parent Wrapper: `` | +| [ ] F2 | [] | | | `` | Parent Wrapper: `` | +| **Locales** | **Create** | **Read** | | `
` | Parent Wrapper: `` | +| [ ] EN | [] | [] | | `` | Parent Wrapper: `` | +| [ ] FR | [] | [] | | `` | Parent Wrapper: `` | + +#### Shape of the `modifiedData.collectionTypes` object: + +In order to easily know the state of a checkbox, we build the `modifiedData` using the `layout.sections.collectionTypes` to generate the following shape: + +```js +const conditions = [ + { + id: 'admin::is-creator', + displayName: 'Is creator', + category: 'default', + }, + { + id: 'admin::has-same-role-as-creator', + displayName: 'Has same role as creator', + category: 'default', + }, +]; +const collectionTypesDefaultForm = createDefaultCTFormFromLayout(layout.sections.collectionTypes, action, conditions) +// createDefaultCTFormFromLayout returns an object with all the values set to false. +// Using the data from above it will return + +console.log(collectionTypesDefaultForm) + +{ + address: { + 'content-manager.explorer.create': { + fields: { + f1: false, + }, + conditions: { + 'admin::is-creator': false, + 'admin::has-same-role-as-creator': false + } + }, + 'content-manager.explorer.read': { + fields: { + f1: false, + }, + conditions: { + 'admin::is-creator': false, + 'admin::has-same-role-as-creator': false + } + }, + }, + restaurant: { + 'content-manager.explorer.create': { + fields: { + f1: { + f11: { + f111: false + }, + }, + f2: false + }, + locales: { en: false, fr: false}, + conditions: { + 'admin::is-creator': false, + 'admin::has-same-role-as-creator': false + } + }, + 'content-manager.explorer.delete: { + enabled: false, + conditions: { + 'admin::is-creator': false, + 'admin::has-same-role-as-creator': false + } + }, + }, +}; +``` + +#### Getting the state of a checkbox using the `modifiedData` object + +Examples: + +- The `create` checkbox located in the `` component is a **parent checkbox** therefore, it's value depends on its children ones. Since, this checkbox is related the `content-manager.explorer.create` action we need to know the values of: + - `address['content-manager.explorer.create'].fields.f1` + - `restaurant['content-manager.explorer.create'].fields.f1.f11.f111` + - `restaurant['content-manager.explorer.create'].fields.f2` + - `restaurant['content-manager.explorer.create'].locales.en` + - `restaurant['content-manager.explorer.create'].locales.fr` + +> The `conditions` key is not a property of an action so the value of `create` does not depend on it + +A way to dynamically retrieve the state of the `create` checkbox, is to create the following object: + +```js +const objectToRetrieveTheStateOfTheCreateCheckbox = { + address: { + fields: { f1: false }, + }, + restaurant: { + fields: { + f1: { f11: { f111: false } }, + f2: false, + }, + locales: { en: false, fr: false }, + }, +}; +``` + +Know we need to know, if all the properties are `false` or if some of them `true`, since we are only dealing with `Boolean` values we can create the following array using the object: + +```js +const arrayOfBooleanValues = [ + false, // address.fields.f1 + false, // restaurant.field.f1.f11.f111, + false, // restaurant.fields.f2, + false, // restaurant.locales.en + false, // restaurant.locales.fr +]; + +const checkboxCreateState = { someChecked: false, allChecked: false }; +``` + +- The state of the checkbox located on the left of F1 field (`address.fields.f1`) depends on the values of `address.create.fields.f1` & `address.read.fields.f2` + +```js +const objectToRetrieveTheStateOfTheCheckbox = { + create: { + f1: false, + }, + read: { + f1: false, + }, +}; + +const arrayOfBooleanVales = [false, false]; + +const checkboxState = { someChecked: false, allChecked: false }; +```