@strapi/admin 4.9.2 → 4.10.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/admin/src/content-manager/components/DynamicTable/CellContent/PublicationState/PublicationState.js +26 -0
  2. package/admin/src/content-manager/components/DynamicTable/CellContent/PublicationState/index.js +1 -0
  3. package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +2 -0
  4. package/admin/src/content-manager/components/DynamicTable/index.js +25 -49
  5. package/admin/src/content-manager/components/DynamicZone/components/DynamicComponent.js +2 -0
  6. package/admin/src/content-manager/pages/EditView/Information/index.js +77 -53
  7. package/admin/src/content-manager/pages/EditView/InformationBox/InformationBoxCE.js +13 -0
  8. package/admin/src/content-manager/pages/EditView/InformationBox/index.js +3 -0
  9. package/admin/src/content-manager/pages/EditView/index.js +3 -4
  10. package/admin/src/content-manager/pages/ListView/index.js +6 -9
  11. package/admin/src/content-manager/sharedReducers/crudReducer/actions.js +6 -0
  12. package/admin/src/content-manager/sharedReducers/crudReducer/constants.js +1 -0
  13. package/admin/src/content-manager/sharedReducers/crudReducer/reducer.js +5 -0
  14. package/admin/src/index.js +1 -0
  15. package/admin/src/translations/en.json +6 -0
  16. package/build/{Admin-authenticatedApp.217db666.chunk.js → Admin-authenticatedApp.52c88751.chunk.js} +2 -2
  17. package/build/{Admin_settingsPage.1dbfc9ce.chunk.js → Admin_settingsPage.257b3477.chunk.js} +7 -7
  18. package/build/{admin-app.558af642.chunk.js → admin-app.dfaeea5d.chunk.js} +18 -18
  19. package/build/content-manager.def692c2.chunk.js +1130 -0
  20. package/build/content-type-builder-translation-en-json.510e88ca.chunk.js +1 -0
  21. package/build/content-type-builder.5e1f4afc.chunk.js +126 -0
  22. package/build/en-json.08303b37.chunk.js +1 -0
  23. package/build/index.html +1 -1
  24. package/build/{main.ef8db4a2.js → main.120be100.js} +145 -145
  25. package/build/review-workflows-settings.9092ed72.chunk.js +106 -0
  26. package/build/{runtime~main.3a92d953.js → runtime~main.112b3101.js} +1 -1
  27. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/ReviewWorkflowsStageEE.js +15 -0
  28. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +45 -0
  29. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/index.js +3 -0
  30. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +135 -0
  31. package/ee/admin/content-manager/pages/EditView/InformationBox/index.js +3 -0
  32. package/ee/admin/hooks/useSettingsMenu/utils/customAdminLinks.js +12 -12
  33. package/ee/admin/hooks/useSettingsMenu/utils/customGlobalLinks.js +21 -13
  34. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ReviewWorkflows.js +199 -0
  35. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/actions/index.js +42 -0
  36. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/AddStage/AddStage.js +87 -0
  37. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/AddStage/index.js +1 -0
  38. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +90 -0
  39. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/index.js +1 -0
  40. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stages.js +92 -0
  41. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/index.js +1 -0
  42. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +6 -0
  43. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js +35 -0
  44. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/index.js +3 -0
  45. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +122 -0
  46. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/getWorkflowValidationSchema.js +25 -0
  47. package/ee/admin/pages/SettingsPage/utils/customRoutes.js +16 -2
  48. package/ee/admin/permissions/customPermissions.js +3 -0
  49. package/ee/server/bootstrap.js +13 -0
  50. package/ee/server/config/admin-actions.js +10 -0
  51. package/ee/server/constants/default-stages.json +14 -0
  52. package/ee/server/constants/default-workflow.json +1 -0
  53. package/ee/server/constants/workflows.js +8 -0
  54. package/ee/server/content-types/index.js +9 -0
  55. package/ee/server/content-types/workflow/index.js +31 -0
  56. package/ee/server/content-types/workflow-stage/index.js +36 -0
  57. package/ee/server/controllers/index.js +2 -0
  58. package/ee/server/controllers/workflows/index.js +36 -0
  59. package/ee/server/controllers/workflows/stages/index.js +102 -0
  60. package/ee/server/index.js +1 -0
  61. package/ee/server/middlewares/review-workflows.js +40 -0
  62. package/ee/server/register.js +8 -0
  63. package/ee/server/routes/index.js +104 -0
  64. package/ee/server/services/index.js +4 -0
  65. package/ee/server/services/review-workflows/entity-service-decorator.js +54 -0
  66. package/ee/server/services/review-workflows/review-workflows.js +111 -0
  67. package/ee/server/services/review-workflows/stages.js +249 -0
  68. package/ee/server/services/review-workflows/workflows.js +25 -0
  69. package/ee/server/utils/index.js +8 -0
  70. package/ee/server/utils/persisted-tables.js +114 -22
  71. package/ee/server/utils/review-workflows.js +34 -0
  72. package/ee/server/validation/review-workflows.js +24 -0
  73. package/package.json +9 -9
  74. package/build/content-manager.d1565bfc.chunk.js +0 -1132
  75. package/build/content-type-builder-translation-en-json.6c8e69ab.chunk.js +0 -1
  76. package/build/content-type-builder.9d780e7f.chunk.js +0 -126
  77. package/build/en-json.cf600231.chunk.js +0 -1
@@ -0,0 +1,111 @@
1
+ 'use strict';
2
+
3
+ const { set, forEach, pipe } = require('lodash/fp');
4
+ const { mapAsync } = require('@strapi/utils');
5
+ const { getService } = require('../../utils');
6
+ const { getContentTypeUIDsWithActivatedReviewWorkflows } = require('../../utils/review-workflows');
7
+
8
+ const defaultStages = require('../../constants/default-stages.json');
9
+ const defaultWorkflow = require('../../constants/default-workflow.json');
10
+ const { ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
11
+
12
+ const { getDefaultWorkflow } = require('../../utils/review-workflows');
13
+ const { persistTable, removePersistedTablesWithSuffix } = require('../../utils/persisted-tables');
14
+
15
+ async function initDefaultWorkflow({ workflowsService, stagesService, strapi }) {
16
+ const wfCount = await workflowsService.count();
17
+ const stagesCount = await stagesService.count();
18
+
19
+ // Check if there is nothing about review-workflow in DB
20
+ // If any, the feature has already been initialized with a workflow and stages
21
+ if (wfCount === 0 && stagesCount === 0) {
22
+ const stages = await stagesService.createMany(defaultStages, { fields: ['id'] });
23
+ const workflow = {
24
+ ...defaultWorkflow,
25
+ stages: {
26
+ connect: stages.map((stage) => stage.id),
27
+ },
28
+ };
29
+
30
+ await workflowsService.create(workflow);
31
+ // If there is any manually activated RW on content-types, we want to migrate the related entities
32
+ await enableReviewWorkflow({ strapi })({ contentTypes: strapi.contentTypes });
33
+ }
34
+ }
35
+
36
+ function extendReviewWorkflowContentTypes({ strapi }) {
37
+ const extendContentType = (contentTypeUID) => {
38
+ const setStageAttribute = set(`attributes.${ENTITY_STAGE_ATTRIBUTE}`, {
39
+ writable: true,
40
+ private: false,
41
+ configurable: false,
42
+ visible: false,
43
+ useJoinTable: true, // We want a join table to persist data when downgrading to CE
44
+ type: 'relation',
45
+ relation: 'oneToOne',
46
+ target: 'admin::workflow-stage',
47
+ });
48
+ strapi.container.get('content-types').extend(contentTypeUID, setStageAttribute);
49
+ };
50
+ pipe([
51
+ getContentTypeUIDsWithActivatedReviewWorkflows,
52
+ // Iterate over UIDs to extend the content-type
53
+ forEach(extendContentType),
54
+ ])(strapi.contentTypes);
55
+ }
56
+
57
+ /**
58
+ * Enables the review workflow for the given content types.
59
+ * @param {Object} strapi - Strapi instance
60
+ */
61
+ function enableReviewWorkflow({ strapi }) {
62
+ /**
63
+ * @param {Array<string>} contentTypes - Content type UIDs to enable the review workflow for.
64
+ * @returns {Promise<void>} - Promise that resolves when the review workflow is enabled.
65
+ */
66
+ return async ({ contentTypes }) => {
67
+ const defaultWorkflow = await getDefaultWorkflow({ strapi });
68
+ // This is possible if this is the first start of EE, there won't be any workflow in DB before bootstrap
69
+ if (!defaultWorkflow) {
70
+ return;
71
+ }
72
+ const firstStage = defaultWorkflow.stages[0];
73
+ const stagesService = getService('stages', { strapi });
74
+
75
+ const up = async (contentTypeUID) => {
76
+ // Persist the stage join table
77
+ const { attributes, tableName } = strapi.db.metadata.get(contentTypeUID);
78
+ const joinTableName = attributes[ENTITY_STAGE_ATTRIBUTE].joinTable.name;
79
+ await persistTable(joinTableName, [tableName]);
80
+
81
+ // Update CT entities stage
82
+ return stagesService.updateEntitiesStage(contentTypeUID, {
83
+ fromStageId: null,
84
+ toStageId: firstStage.id,
85
+ });
86
+ };
87
+
88
+ await removePersistedTablesWithSuffix('_strapi_review_workflows_stage_links');
89
+
90
+ return pipe([
91
+ getContentTypeUIDsWithActivatedReviewWorkflows,
92
+ // Iterate over UIDs to extend the content-type
93
+ (contentTypesUIDs) => mapAsync(contentTypesUIDs, up),
94
+ ])(contentTypes);
95
+ };
96
+ }
97
+
98
+ module.exports = ({ strapi }) => {
99
+ const workflowsService = getService('workflows', { strapi });
100
+ const stagesService = getService('stages', { strapi });
101
+
102
+ return {
103
+ async bootstrap() {
104
+ await initDefaultWorkflow({ workflowsService, stagesService, strapi });
105
+ },
106
+ async register() {
107
+ extendReviewWorkflowContentTypes({ strapi });
108
+ strapi.hook('strapi::content-types.afterSync').register(enableReviewWorkflow({ strapi }));
109
+ },
110
+ };
111
+ };
@@ -0,0 +1,249 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ mapAsync,
5
+ errors: { ApplicationError },
6
+ } = require('@strapi/utils');
7
+ const { map } = require('lodash/fp');
8
+
9
+ const { STAGE_MODEL_UID, ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
10
+ const { getService } = require('../../utils');
11
+ const { getContentTypeUIDsWithActivatedReviewWorkflows } = require('../../utils/review-workflows');
12
+
13
+ module.exports = ({ strapi }) => {
14
+ const workflowsService = getService('workflows', { strapi });
15
+
16
+ return {
17
+ find({ workflowId, populate }) {
18
+ const params = {
19
+ filters: { workflow: workflowId },
20
+ populate,
21
+ };
22
+ return strapi.entityService.findMany(STAGE_MODEL_UID, params);
23
+ },
24
+
25
+ findById(id, { populate } = {}) {
26
+ const params = {
27
+ populate,
28
+ };
29
+ return strapi.entityService.findOne(STAGE_MODEL_UID, id, params);
30
+ },
31
+
32
+ createMany(stagesList, { fields }) {
33
+ const params = {
34
+ select: fields,
35
+ };
36
+ return Promise.all(
37
+ stagesList.map((stage) =>
38
+ strapi.entityService.create(STAGE_MODEL_UID, { data: stage, ...params })
39
+ )
40
+ );
41
+ },
42
+
43
+ update(stageId, stageData) {
44
+ return strapi.entityService.update(STAGE_MODEL_UID, stageId, { data: stageData });
45
+ },
46
+
47
+ delete(stageId) {
48
+ return strapi.entityService.delete(STAGE_MODEL_UID, stageId);
49
+ },
50
+
51
+ count() {
52
+ return strapi.entityService.count(STAGE_MODEL_UID);
53
+ },
54
+
55
+ async replaceWorkflowStages(workflowId, stages) {
56
+ const workflow = await workflowsService.findById(workflowId, { populate: ['stages'] });
57
+
58
+ const { created, updated, deleted } = getDiffBetweenStages(workflow.stages, stages);
59
+
60
+ assertAtLeastOneStageRemain(workflow.stages, { created, deleted });
61
+
62
+ return strapi.db.transaction(async ({ trx }) => {
63
+ // Create the new stages
64
+ const createdStages = await this.createMany(created, { fields: ['id'] });
65
+ // Put all the newly created stages ids
66
+ const createdStagesIds = map('id', createdStages);
67
+ const stagesIds = stages.map((stage) => stage.id ?? createdStagesIds.shift());
68
+ const contentTypes = getContentTypeUIDsWithActivatedReviewWorkflows(strapi.contentTypes);
69
+
70
+ // Update the workflow stages
71
+ await mapAsync(updated, (stage) => this.update(stage.id, stage));
72
+
73
+ // Delete the stages that are not in the new stages list
74
+ await mapAsync(deleted, async (stage) => {
75
+ // Find the nearest stage in the workflow and newly created stages
76
+ // that is not deleted, prioritizing the previous stages
77
+ const nearestStage = findNearestMatchingStage(
78
+ [...workflow.stages, ...createdStages],
79
+ workflow.stages.findIndex((s) => s.id === stage.id),
80
+ (targetStage) => {
81
+ return !deleted.find((s) => s.id === targetStage.id);
82
+ }
83
+ );
84
+
85
+ // Assign the new stage to entities that had the deleted stage
86
+ await mapAsync(contentTypes, (contentTypeUID) => {
87
+ this.updateEntitiesStage(contentTypeUID, {
88
+ fromStageId: stage.id,
89
+ toStageId: nearestStage.id,
90
+ trx,
91
+ });
92
+ });
93
+
94
+ return this.delete(stage.id);
95
+ });
96
+
97
+ return workflowsService.update(workflowId, {
98
+ stages: stagesIds,
99
+ });
100
+ });
101
+ },
102
+
103
+ /**
104
+ * Update the stage of an entity
105
+ *
106
+ * @param {object} entityInfo
107
+ * @param {number} entityInfo.id - Entity id
108
+ * @param {string} entityInfo.modelUID - the content-type of the entity
109
+ * @param {number} stageId - The id of the stage to assign to the entity
110
+ */
111
+ async updateEntity(entityInfo, stageId) {
112
+ const stage = await this.findById(stageId);
113
+
114
+ if (!stage) {
115
+ throw new ApplicationError(`Selected stage does not exist`);
116
+ }
117
+
118
+ return strapi.entityService.update(entityInfo.modelUID, entityInfo.id, {
119
+ data: { [ENTITY_STAGE_ATTRIBUTE]: stageId },
120
+ populate: [ENTITY_STAGE_ATTRIBUTE],
121
+ });
122
+ },
123
+
124
+ /**
125
+ * Updates the stage of all entities of a content type that are in a specific stage
126
+ * @param {string} contentTypeUID
127
+ * @param {number} fromStageId
128
+ * @param {number} toStageId
129
+ * @param {KnexTransaction} trx
130
+ * @returns
131
+ */
132
+ async updateEntitiesStage(contentTypeUID, { fromStageId, toStageId, trx = null }) {
133
+ const { attributes, tableName } = strapi.db.metadata.get(contentTypeUID);
134
+ const joinTable = attributes[ENTITY_STAGE_ATTRIBUTE].joinTable;
135
+ const joinColumn = joinTable.joinColumn.name;
136
+ const invJoinColumn = joinTable.inverseJoinColumn.name;
137
+
138
+ const selectStatement = strapi.db
139
+ .getConnection()
140
+ .select({ [joinColumn]: 't1.id', [invJoinColumn]: toStageId })
141
+ .from(`${tableName} as t1`)
142
+ .leftJoin(`${joinTable.name} as t2`, `t1.id`, `t2.${joinColumn}`)
143
+ .where(`t2.${invJoinColumn}`, fromStageId)
144
+ .toSQL();
145
+
146
+ // Insert rows for all entries of the content type that do not have a
147
+ // default stage
148
+ const query = strapi.db
149
+ .getConnection(joinTable.name)
150
+ .insert(
151
+ strapi.db.connection.raw(
152
+ `(${joinColumn}, ${invJoinColumn}) ${selectStatement.sql}`,
153
+ selectStatement.bindings
154
+ )
155
+ );
156
+
157
+ if (trx) {
158
+ query.transacting(trx);
159
+ }
160
+
161
+ return query;
162
+ },
163
+ };
164
+ };
165
+
166
+ /**
167
+ * Compares two arrays of stages and returns an object indicating the differences.
168
+ *
169
+ * The function compares the `id` properties of each stage in `sourceStages` and `comparisonStages` to determine if the stage is present in both arrays.
170
+ * If a stage with the same `id` is found in both arrays but the `name` property is different, the stage is considered updated.
171
+ * If a stage with a particular `id` is only found in `comparisonStages`, it is considered created.
172
+ * If a stage with a particular `id` is only found in `sourceStages`, it is considered deleted.
173
+ *
174
+ * @typedef {{id: Number, name: String, workflow: Number}} Stage
175
+ * @typedef {{created: Stage[], updated: Stage[], deleted: Stage[]}} DiffStages
176
+ *
177
+ * The DiffStages object has three properties: `created`, `updated`, and `deleted`.
178
+ * `created` is an array of stages that are in `comparisonStages` but not in `sourceStages`.
179
+ * `updated` is an array of stages that have different names in `comparisonStages` and `sourceStages`.
180
+ * `deleted` is an array of stages that are in `sourceStages` but not in `comparisonStages`.
181
+ *
182
+ * @param {Stage[]} sourceStages
183
+ * @param {Stage[]} comparisonStages
184
+ * @returns { DiffStages }
185
+ */
186
+ function getDiffBetweenStages(sourceStages, comparisonStages) {
187
+ const result = comparisonStages.reduce(
188
+ (acc, stageToCompare) => {
189
+ const srcStage = sourceStages.find((stage) => stage.id === stageToCompare.id);
190
+
191
+ if (!srcStage) {
192
+ acc.created.push(stageToCompare);
193
+ } else if (srcStage.name !== stageToCompare.name) {
194
+ acc.updated.push(stageToCompare);
195
+ }
196
+ return acc;
197
+ },
198
+ { created: [], updated: [] }
199
+ );
200
+
201
+ result.deleted = sourceStages.filter(
202
+ (srcStage) => !comparisonStages.some((cmpStage) => cmpStage.id === srcStage.id)
203
+ );
204
+
205
+ return result;
206
+ }
207
+
208
+ /**
209
+ * Asserts that at least one stage remains in the workflow after applying deletions and additions.
210
+ *
211
+ * @param {Array} workflowStages - An array of stages in the current workflow.
212
+ * @param {Object} diffStages - An object containing the stages to be deleted and created.
213
+ * @param {Array} diffStages.deleted - An array of stages that are planned to be deleted from the workflow.
214
+ * @param {Array} diffStages.created - An array of stages that are planned to be created in the workflow.
215
+ *
216
+ * @throws {ApplicationError} If the number of remaining stages in the workflow after applying deletions and additions is less than 1.
217
+ */
218
+ function assertAtLeastOneStageRemain(workflowStages, diffStages) {
219
+ const remainingStagesCount =
220
+ workflowStages.length - diffStages.deleted.length + diffStages.created.length;
221
+ if (remainingStagesCount < 1) {
222
+ throw new ApplicationError('At least one stage must remain in the workflow.');
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Find the id of the nearest object in an array that matches a condition.
228
+ * Used for searching for the nearest stage that is not deleted.
229
+ * Starts by searching the elements before the index, then the remaining elements in the array.
230
+ *
231
+ * @param {Array} stages
232
+ * @param {Number} startIndex the index to start searching from
233
+ * @param {Function} condition must evaluate to true for the object to be considered a match
234
+ * @returns {Object} stage
235
+ */
236
+ function findNearestMatchingStage(stages, startIndex, condition) {
237
+ // Start by searching the elements before the startIndex
238
+ for (let i = startIndex; i >= 0; i -= 1) {
239
+ if (condition(stages[i])) {
240
+ return stages[i];
241
+ }
242
+ }
243
+
244
+ // If no matching element is found before the startIndex,
245
+ // search the remaining elements in the array
246
+ const remainingArray = stages.slice(startIndex + 1);
247
+ const nearestObject = remainingArray.filter(condition)[0];
248
+ return nearestObject;
249
+ }
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const { WORKFLOW_MODEL_UID } = require('../../constants/workflows');
4
+
5
+ module.exports = ({ strapi }) => ({
6
+ find(opts) {
7
+ return strapi.entityService.findMany(WORKFLOW_MODEL_UID, opts);
8
+ },
9
+
10
+ findById(id, opts) {
11
+ return strapi.entityService.findOne(WORKFLOW_MODEL_UID, id, opts);
12
+ },
13
+
14
+ create(workflowData) {
15
+ return strapi.entityService.create(WORKFLOW_MODEL_UID, { data: workflowData });
16
+ },
17
+
18
+ count() {
19
+ return strapi.entityService.count(WORKFLOW_MODEL_UID);
20
+ },
21
+
22
+ update(id, workflowData) {
23
+ return strapi.entityService.update(WORKFLOW_MODEL_UID, id, { data: workflowData });
24
+ },
25
+ });
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ const getService = (name, { strapi } = { strapi: global.strapi }) => {
4
+ return strapi.service(`admin::${name}`);
5
+ };
6
+ module.exports = {
7
+ getService,
8
+ };
@@ -1,49 +1,141 @@
1
1
  'use strict';
2
2
 
3
+ const { differenceWith, isEqual } = require('lodash/fp');
4
+
3
5
  /**
4
- * Finds all tables in the database that start with a prefix
5
- * @param {string} prefix
6
- * @returns {Array}
6
+ * Transform table name to the object format
7
+ * @param {Array<string|{ table: string; dependsOn?: Array<{ table: string;}> }>} table
8
+ * @returns Array<{ table: string; dependsOn?: Array<{ table: string;}> }>
7
9
  */
8
- const findTablesThatStartWithPrefix = async (prefix) => {
9
- const tables = await strapi.db.dialect.schemaInspector.getTables();
10
- return tables.filter((tableName) => tableName.startsWith(prefix));
10
+ const transformTableName = (table) => {
11
+ if (typeof table === 'string') {
12
+ return { name: table };
13
+ }
14
+ return table;
11
15
  };
12
16
 
13
17
  /**
14
- * Get all reserved table names from the core store
15
- * @returns {Array}
18
+ * Finds all tables in the database matching the regular expression
19
+ * @param {Object} ctx
20
+ * @param {Strapi} ctx.strapi
21
+ * @param {RegExp} regex
22
+ * @returns {Promise<string[]>}
16
23
  */
17
- const getPersistedTables = async () =>
18
- (await strapi.store.get({
24
+ async function findTables({ strapi }, regex) {
25
+ const tables = await strapi.db.dialect.schemaInspector.getTables();
26
+ return tables.filter((tableName) => regex.test(tableName));
27
+ }
28
+
29
+ /**
30
+ * Add tables name to the reserved tables in core store
31
+ * @param {Object} ctx
32
+ * @param {Strapi} ctx.strapi
33
+ * @param {Array<string|{ table: string; dependsOn?: Array<{ table: string;}> }>} tableNames
34
+ * @return {Promise<void>}
35
+ */
36
+ async function addPersistTables({ strapi }, tableNames) {
37
+ const persistedTables = await getPersistedTables({ strapi });
38
+ const tables = tableNames.map(transformTableName);
39
+
40
+ // Get new tables to be persisted, remove tables if they already were persisted
41
+ const notPersistedTableNames = differenceWith(isEqual, tables, persistedTables);
42
+ // Remove tables that are going to be changed
43
+ const tablesToPersist = differenceWith(
44
+ (t1, t2) => t1.name === t2.name,
45
+ persistedTables,
46
+ notPersistedTableNames
47
+ );
48
+
49
+ if (!notPersistedTableNames.length) {
50
+ return;
51
+ }
52
+
53
+ tablesToPersist.push(...notPersistedTableNames);
54
+ await strapi.store.set({
19
55
  type: 'core',
20
56
  key: 'persisted_tables',
21
- })) ?? [];
57
+ value: tablesToPersist,
58
+ });
59
+ }
22
60
 
23
61
  /**
24
- * Add all table names that start with a prefix to the reserved tables in
25
- * core store
26
- * @param {string} tableNamePrefix
62
+ * Remove tables name from the reserved tables in core store
63
+ * @param {Object} ctx
64
+ * @param {Strapi} ctx.strapi
65
+ * @param {Array<string>} tableNames
66
+ * @return {Promise<void>}
27
67
  */
68
+ async function removePersistedTables({ strapi }, tableNames) {
69
+ const persistedTables = await getPersistedTables({ strapi });
28
70
 
29
- const persistTablesWithPrefix = async (tableNamePrefix) => {
30
- const persistedTables = await getPersistedTables();
31
- const tableNames = await findTablesThatStartWithPrefix(tableNamePrefix);
32
- const notReservedTableNames = tableNames.filter((name) => !persistedTables.includes(name));
71
+ // Get new tables to be persisted, remove tables if they already were persisted
72
+ const newPersistedTables = differenceWith(
73
+ (t1, t2) => t1.name === t2,
74
+ persistedTables,
75
+ tableNames
76
+ );
33
77
 
34
- if (!notReservedTableNames.length) {
78
+ if (newPersistedTables.length === persistedTables.length) {
35
79
  return;
36
80
  }
37
81
 
38
- persistedTables.push(...notReservedTableNames);
39
82
  await strapi.store.set({
40
83
  type: 'core',
41
84
  key: 'persisted_tables',
42
- value: persistedTables,
85
+ value: newPersistedTables,
43
86
  });
87
+ }
88
+
89
+ /**
90
+ * Get all reserved table names from the core store
91
+ * @param {Object} ctx
92
+ * @param {Strapi} ctx.strapi
93
+ * @returns {Promise<string[]>}
94
+ */
95
+
96
+ async function getPersistedTables({ strapi }) {
97
+ const persistedTables = await strapi.store.get({
98
+ type: 'core',
99
+ key: 'persisted_tables',
100
+ });
101
+
102
+ return (persistedTables || []).map(transformTableName);
103
+ }
104
+
105
+ /**
106
+ * Add all table names that start with a prefix to the reserved tables in
107
+ * core store
108
+ * @param {string} tableNamePrefix
109
+ * @return {Promise<void>}
110
+ */
111
+
112
+ const persistTablesWithPrefix = async (tableNamePrefix) => {
113
+ const tableNameRegex = new RegExp(`^${tableNamePrefix}.*`);
114
+ const tableNames = await findTables({ strapi }, tableNameRegex);
115
+
116
+ await addPersistTables({ strapi }, tableNames);
117
+ };
118
+
119
+ /**
120
+ * Remove all table names that end with a suffix from the reserved tables in core store
121
+ * @param {string} tableNameSuffix
122
+ * @return {Promise<void>}
123
+ */
124
+ const removePersistedTablesWithSuffix = async (tableNameSuffix) => {
125
+ const tableNameRegex = new RegExp(`.*${tableNameSuffix}$`);
126
+ const tableNames = await findTables({ strapi }, tableNameRegex);
127
+ await removePersistedTables({ strapi }, tableNames);
128
+ };
129
+
130
+ const persistTable = async (tableName, dependsOn) => {
131
+ await addPersistTables({ strapi }, [
132
+ { name: tableName, dependsOn: dependsOn?.map((depTableName) => ({ name: depTableName })) },
133
+ ]);
44
134
  };
45
135
 
46
136
  module.exports = {
47
137
  persistTablesWithPrefix,
48
- findTablesThatStartWithPrefix,
138
+ removePersistedTablesWithSuffix,
139
+ persistTable,
140
+ findTables,
49
141
  };
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ const { get, keys, pickBy, pipe } = require('lodash/fp');
4
+ const { WORKFLOW_MODEL_UID } = require('../constants/workflows');
5
+
6
+ /**
7
+ * Checks if a content type has review workflows enabled.
8
+ * @param {string|Object} contentType - Either the modelUID of the content type, or the content type object.
9
+ * @returns {boolean} Whether review workflows are enabled for the specified content type.
10
+ */
11
+ function hasReviewWorkflow({ strapi }, contentType) {
12
+ if (typeof contentType === 'string') {
13
+ // If the input is a string, assume it's the modelUID of the content type and retrieve the corresponding object.
14
+ return hasReviewWorkflow({ strapi }, strapi.getModel(contentType));
15
+ }
16
+ // Otherwise, assume it's the content type object itself and return its `reviewWorkflows` option if it exists.
17
+ return contentType?.options?.reviewWorkflows || false;
18
+ }
19
+ // TODO To be refactored when multiple workflows are added
20
+ const getDefaultWorkflow = async ({ strapi }) =>
21
+ strapi.query(WORKFLOW_MODEL_UID).findOne({ populate: ['stages'] });
22
+
23
+ const getContentTypeUIDsWithActivatedReviewWorkflows = pipe([
24
+ // Pick only content-types with reviewWorkflows options set to true
25
+ pickBy(get('options.reviewWorkflows')),
26
+ // Get UIDs
27
+ keys,
28
+ ]);
29
+
30
+ module.exports = {
31
+ hasReviewWorkflow,
32
+ getDefaultWorkflow,
33
+ getContentTypeUIDsWithActivatedReviewWorkflows,
34
+ };
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const { yup, validateYupSchema } = require('@strapi/utils');
4
+
5
+ const stageObject = yup.object().shape({
6
+ id: yup.number().integer().min(1),
7
+ name: yup.string().max(255).required(),
8
+ });
9
+
10
+ const validateUpdateStagesSchema = yup.array().of(stageObject).required();
11
+ const validateUpdateStageOnEntity = yup
12
+ .object()
13
+ .shape({
14
+ id: yup.number().integer().min(1).required(),
15
+ })
16
+ .required();
17
+
18
+ module.exports = {
19
+ validateUpdateStages: validateYupSchema(validateUpdateStagesSchema, {
20
+ strict: false,
21
+ stripUnknown: true,
22
+ }),
23
+ validateUpdateStageOnEntity: validateYupSchema(validateUpdateStageOnEntity),
24
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/admin",
3
- "version": "4.9.2",
3
+ "version": "4.10.0-beta.1",
4
4
  "description": "Strapi Admin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -48,15 +48,15 @@
48
48
  "@casl/ability": "^5.4.3",
49
49
  "@fingerprintjs/fingerprintjs": "3.3.6",
50
50
  "@pmmmwh/react-refresh-webpack-plugin": "0.5.10",
51
- "@strapi/babel-plugin-switch-ee-ce": "4.9.2",
52
- "@strapi/data-transfer": "4.9.2",
51
+ "@strapi/babel-plugin-switch-ee-ce": "4.10.0-beta.1",
52
+ "@strapi/data-transfer": "4.10.0-beta.1",
53
53
  "@strapi/design-system": "1.6.6",
54
- "@strapi/helper-plugin": "4.9.2",
54
+ "@strapi/helper-plugin": "4.10.0-beta.1",
55
55
  "@strapi/icons": "1.6.6",
56
- "@strapi/permissions": "4.9.2",
57
- "@strapi/provider-audit-logs-local": "4.9.2",
58
- "@strapi/typescript-utils": "4.9.2",
59
- "@strapi/utils": "4.9.2",
56
+ "@strapi/permissions": "4.10.0-beta.1",
57
+ "@strapi/provider-audit-logs-local": "4.10.0-beta.1",
58
+ "@strapi/typescript-utils": "4.10.0-beta.1",
59
+ "@strapi/utils": "4.10.0-beta.1",
60
60
  "axios": "1.3.4",
61
61
  "babel-loader": "^9.1.2",
62
62
  "babel-plugin-styled-components": "2.0.2",
@@ -168,5 +168,5 @@
168
168
  }
169
169
  }
170
170
  },
171
- "gitHead": "91e0be2708e4d1e8ec731c75e73e54c0dfacb67d"
171
+ "gitHead": "95d581b31bee464af42e5d8db408fa578d8532c7"
172
172
  }