@strapi/admin 4.10.0-beta.0 → 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.
- package/.eslintignore +4 -0
- package/.eslintrc.js +14 -0
- package/admin/src/components/LanguageProvider/index.js +1 -1
- package/admin/src/components/LocalesProvider/__mocks__/useLocalesProvider.js +7 -0
- package/admin/src/components/LocalesProvider/index.js +2 -3
- package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +3 -6
- package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +2 -0
- package/admin/src/content-manager/components/DynamicTable/index.js +11 -29
- package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +12 -6
- package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/findAllAndReplace.js +10 -3
- package/admin/src/content-manager/components/InputUID/endActionStyle.js +4 -13
- package/admin/src/content-manager/components/InputUID/index.js +95 -72
- package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +4 -8
- package/admin/src/content-manager/pages/App/LeftMenu/index.js +56 -35
- package/admin/src/content-manager/pages/App/actions.js +19 -7
- package/admin/src/content-manager/pages/App/constants.js +3 -3
- package/admin/src/content-manager/pages/App/index.js +5 -4
- package/admin/src/content-manager/pages/App/reducer.js +7 -6
- package/admin/src/content-manager/pages/App/selectors.js +3 -0
- package/admin/src/content-manager/pages/App/{useModels.js → useContentManagerInitData.js} +29 -28
- package/admin/src/content-manager/pages/App/utils/generateModelsLinks.js +2 -2
- package/admin/src/content-manager/pages/App/utils/getContentTypeLinks.js +17 -15
- package/admin/src/content-manager/pages/EditSettingsView/components/ModalForm.js +4 -5
- package/admin/src/content-manager/pages/EditSettingsView/index.js +4 -0
- package/admin/src/content-manager/pages/EditSettingsView/reducer.js +6 -7
- package/admin/src/content-manager/pages/EditSettingsView/utils/layout.js +1 -30
- package/admin/src/content-manager/pages/EditView/DeleteLink/index.js +5 -11
- package/admin/src/content-manager/pages/EditView/index.js +2 -7
- package/admin/src/content-manager/pages/ListSettingsView/index.js +1 -0
- package/admin/src/content-manager/sharedReducers/crudReducer/actions.js +6 -0
- package/admin/src/content-manager/sharedReducers/crudReducer/constants.js +1 -0
- package/admin/src/content-manager/sharedReducers/crudReducer/reducer.js +5 -0
- package/admin/src/hooks/useConfigurations/__mocks__/index.js +7 -0
- package/admin/src/hooks/useFetchMarketplacePlugins/utils/api.js +7 -8
- package/admin/src/hooks/useFetchMarketplaceProviders/utils/api.js +5 -0
- package/admin/src/hooks/useRegenerate/index.js +12 -7
- package/admin/src/pages/AuthPage/components/Register/index.js +46 -38
- package/admin/src/pages/HomePage/SocialLinks.js +1 -1
- package/admin/src/pages/MarketplacePage/components/EmptyNpmPackageSearch/index.js +3 -3
- package/admin/src/pages/MarketplacePage/components/NpmPackagesGrid/index.js +42 -9
- package/admin/src/pages/MarketplacePage/components/NpmPackagesPagination/index.js +26 -0
- package/admin/src/pages/MarketplacePage/components/OfflineLayout/index.js +45 -0
- package/admin/src/pages/MarketplacePage/index.js +80 -175
- package/admin/src/pages/MarketplacePage/utils/useMarketplaceData.js +85 -0
- package/admin/src/pages/SettingsPage/components/Tokens/FormHead/index.js +4 -0
- package/admin/src/pages/SettingsPage/components/Tokens/Regenerate/index.js +5 -3
- package/admin/src/pages/SettingsPage/components/Tokens/TokenTypeSelect/index.js +7 -5
- package/admin/src/pages/SettingsPage/pages/Roles/ListPage/components/RoleRow/index.js +12 -4
- package/admin/src/pages/SettingsPage/pages/Roles/ListPage/index.js +21 -2
- package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/components/FormTransferTokenContainer/index.js +41 -0
- package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/index.js +53 -9
- package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/utils/schema.js +1 -0
- package/admin/src/pages/SettingsPage/pages/TransferTokens/ListView/index.js +27 -5
- package/admin/src/translations/en.json +51 -49
- package/admin/src/translations/ru.json +51 -19
- package/build/1387.84b454d3.chunk.js +1 -0
- package/build/1657.45231968.chunk.js +168 -0
- package/build/3081.bcf9a12f.chunk.js +108 -0
- package/build/462.8fff7f3b.chunk.js +71 -0
- package/build/4628.20631dd1.chunk.js +1 -0
- package/build/5542.b8240e3f.chunk.js +70 -0
- package/build/5563.905daa13.chunk.js +79 -0
- package/build/6404.68405699.chunk.js +100 -0
- package/build/7259.b7d00cea.chunk.js +1 -0
- package/build/8694.6522968d.chunk.js +247 -0
- package/build/Admin-authenticatedApp.52c88751.chunk.js +79 -0
- package/build/Admin_InternalErrorPage.15c6bf07.chunk.js +1 -0
- package/build/Admin_homePage.f9309c6d.chunk.js +73 -0
- package/build/Admin_marketplace.56bc1008.chunk.js +31 -0
- package/build/Admin_pluginsPage.f6b52ee9.chunk.js +6 -0
- package/build/Admin_profilePage.9112cffc.chunk.js +15 -0
- package/build/Admin_settingsPage.257b3477.chunk.js +79 -0
- package/build/Upload_ConfigureTheView.eaaec495.chunk.js +1 -0
- package/build/admin-app.dfaeea5d.chunk.js +110 -0
- package/build/admin-edit-roles-page.4f1858e9.chunk.js +280 -0
- package/build/admin-edit-users.7e14d85f.chunk.js +10 -0
- package/build/admin-roles-list.329c1f63.chunk.js +31 -0
- package/build/admin-users.d02de059.chunk.js +34 -0
- package/build/api-tokens-create-page.97595e12.chunk.js +1 -0
- package/build/api-tokens-edit-page.cd36e30e.chunk.js +1 -0
- package/build/api-tokens-list-page.6757c7b9.chunk.js +16 -0
- package/build/audit-logs-settings-page.19d90bda.chunk.js +76 -0
- package/build/content-manager.def692c2.chunk.js +1130 -0
- package/build/content-type-builder-list-view.9c2c020c.chunk.js +214 -0
- package/build/content-type-builder-translation-en-json.510e88ca.chunk.js +1 -0
- package/build/content-type-builder.5e1f4afc.chunk.js +126 -0
- package/build/email-settings-page.1095e1ab.chunk.js +10 -0
- package/build/en-json.08303b37.chunk.js +1 -0
- package/build/{highlight.js.26ef649f.chunk.js → highlight.js.28a1547e.chunk.js} +2 -2
- package/build/i18n-settings-page.7d80aae0.chunk.js +60 -0
- package/build/index.html +1 -1
- package/build/main.120be100.js +2280 -0
- package/build/review-workflows-settings.9092ed72.chunk.js +106 -0
- package/build/ru-json.e0662702.chunk.js +1 -0
- package/build/{runtime~main.5a95bee6.js → runtime~main.112b3101.js} +2 -2
- package/build/sso-settings-page.1dd4886e.chunk.js +1 -0
- package/build/transfer-tokens-create-page.ec2ca215.chunk.js +1 -0
- package/build/transfer-tokens-edit-page.22bf28e5.chunk.js +1 -0
- package/build/transfer-tokens-list-page.cf8c77f2.chunk.js +16 -0
- package/build/upload-settings.945fdcfa.chunk.js +13 -0
- package/build/{upload-translation-th-json.3847dae0.chunk.js → upload-translation-th-json.98d35574.chunk.js} +1 -1
- package/build/upload.a86b1054.chunk.js +33 -0
- package/build/users-advanced-settings-page.5b5a9baa.chunk.js +8 -0
- package/build/users-email-settings-page.e5506eb4.chunk.js +23 -0
- package/build/users-providers-settings-page.e32089c2.chunk.js +28 -0
- package/build/users-roles-settings-page.2f85dcec.chunk.js +30 -0
- package/build/webhook-edit-page.213f0075.chunk.js +75 -0
- package/build/webhook-list-page.5beb2a5c.chunk.js +71 -0
- package/{admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/ReviewWorkflowsStage.js → ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/ReviewWorkflowsStageEE.js} +2 -2
- package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +45 -0
- package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/index.js +3 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +61 -18
- package/ee/admin/hooks/useLicenseLimitNotification/index.js +1 -1
- package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/hooks/useAuditLogsData.js +6 -3
- package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/index.js +15 -5
- package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getDisplayedFilters.js +52 -45
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ReviewWorkflows.js +8 -4
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +2 -2
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js +2 -2
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +2 -1
- package/ee/server/content-types/workflow/index.js +0 -3
- package/ee/server/content-types/workflow-stage/index.js +0 -5
- package/ee/server/controllers/workflows/stages/index.js +8 -1
- package/ee/server/register.js +3 -1
- package/ee/server/services/audit-logs.js +75 -16
- package/ee/server/services/review-workflows/entity-service-decorator.js +17 -5
- package/ee/server/services/review-workflows/review-workflows.js +27 -91
- package/ee/server/services/review-workflows/stages.js +108 -7
- package/ee/server/utils/persisted-tables.js +114 -22
- package/ee/server/utils/review-workflows.js +9 -0
- package/jest.config.front.js +1 -6
- package/package.json +24 -22
- package/server/controllers/transfer/runner.js +4 -2
- package/server/middlewares/data-transfer.js +4 -1
- package/server/routes/transfer.js +13 -4
- package/server/services/constants.js +4 -0
- package/server/services/transfer/permission.js +1 -1
- package/server/services/transfer/token.js +33 -31
- package/server/validation/transfer/token.js +10 -2
- package/webpack.config.js +6 -2
- package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/index.js +0 -1
- package/admin/src/content-manager/pages/App/LeftMenu/utils/index.js +0 -1
- package/admin/src/content-manager/pages/App/LeftMenu/utils/matchByTitle.js +0 -24
- package/build/2263.4c5916f9.chunk.js +0 -98
- package/build/4049.64715f20.chunk.js +0 -1
- package/build/4649.213b8a3b.chunk.js +0 -30
- package/build/6985.66cca29c.chunk.js +0 -1
- package/build/7259.aefb51e8.chunk.js +0 -1
- package/build/8469.853c822b.chunk.js +0 -1
- package/build/9505.dbe702ab.chunk.js +0 -14
- package/build/9816.01ee964f.chunk.js +0 -2
- package/build/Admin-authenticatedApp.f50ad423.chunk.js +0 -79
- package/build/Admin_InternalErrorPage.4ad8b0df.chunk.js +0 -1
- package/build/Admin_homePage.1411fb7c.chunk.js +0 -68
- package/build/Admin_marketplace.02608d56.chunk.js +0 -22
- package/build/Admin_pluginsPage.15e3b0fd.chunk.js +0 -1
- package/build/Admin_profilePage.76afeca0.chunk.js +0 -15
- package/build/Admin_settingsPage.147755cd.chunk.js +0 -9
- package/build/Upload_ConfigureTheView.34dde278.chunk.js +0 -1
- package/build/admin-app.55dd7921.chunk.js +0 -112
- package/build/admin-edit-roles-page.cf543488.chunk.js +0 -216
- package/build/admin-edit-users.31c20712.chunk.js +0 -10
- package/build/admin-roles-list.489c501f.chunk.js +0 -2
- package/build/admin-users.3e111a7d.chunk.js +0 -11
- package/build/api-tokens-create-page.4328b852.chunk.js +0 -1
- package/build/api-tokens-edit-page.bce5050f.chunk.js +0 -1
- package/build/api-tokens-list-page.93f24348.chunk.js +0 -16
- package/build/audit-logs-settings-page.7be97e82.chunk.js +0 -1
- package/build/content-manager.4480ae88.chunk.js +0 -1137
- package/build/content-type-builder-list-view.cf38fe2f.chunk.js +0 -191
- package/build/content-type-builder-translation-en-json.7961593e.chunk.js +0 -1
- package/build/content-type-builder.af9abf1e.chunk.js +0 -126
- package/build/email-settings-page.4bdbef9a.chunk.js +0 -3
- package/build/en-json.697b4bcf.chunk.js +0 -1
- package/build/i18n-settings-page.2bb5be96.chunk.js +0 -1
- package/build/main.af8c0f31.js +0 -3790
- package/build/review-workflows-settings.7a7dc773.chunk.js +0 -57
- package/build/ru-json.6a01cea6.chunk.js +0 -1
- package/build/sso-settings-page.272b87c8.chunk.js +0 -1
- package/build/transfer-tokens-create-page.a1f14bb1.chunk.js +0 -1
- package/build/transfer-tokens-edit-page.00ee1c74.chunk.js +0 -1
- package/build/transfer-tokens-list-page.ce37354b.chunk.js +0 -16
- package/build/upload-settings.0875e973.chunk.js +0 -1
- package/build/upload.c7da1611.chunk.js +0 -13
- package/build/users-advanced-settings-page.1d3c14c7.chunk.js +0 -1
- package/build/users-email-settings-page.e8db68c4.chunk.js +0 -1
- package/build/users-providers-settings-page.14cac425.chunk.js +0 -1
- package/build/users-roles-settings-page.2ea4de84.chunk.js +0 -30
- package/build/webhook-edit-page.329141a5.chunk.js +0 -23
- package/build/webhook-list-page.029957a4.chunk.js +0 -1
- package/ee/server/migrations/review-workflows.js +0 -39
- package/ee/server/utils/test.js +0 -11
- /package/admin/src/{content-manager/components/InputUID/useDebounce.js → hooks/useDebounce/index.js} +0 -0
|
@@ -45,7 +45,7 @@ function Stage({ id, name, index, canDelete, isOpen: isOpenDefault = false }) {
|
|
|
45
45
|
action={
|
|
46
46
|
canDelete ? (
|
|
47
47
|
<IconButton
|
|
48
|
-
|
|
48
|
+
background="transparent"
|
|
49
49
|
noBorder
|
|
50
50
|
onClick={() => dispatch(deleteStage(id))}
|
|
51
51
|
label={formatMessage({
|
|
@@ -57,7 +57,7 @@ function Stage({ id, name, index, canDelete, isOpen: isOpenDefault = false }) {
|
|
|
57
57
|
) : null
|
|
58
58
|
}
|
|
59
59
|
/>
|
|
60
|
-
<AccordionContent padding={6} background="neutral0">
|
|
60
|
+
<AccordionContent padding={6} background="neutral0" hasRadius>
|
|
61
61
|
<Grid gap={4}>
|
|
62
62
|
<GridItem col={6}>
|
|
63
63
|
<TextInput
|
|
@@ -22,8 +22,8 @@ export function useReviewWorkflows(workflowId) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
function refetchWorkflow() {
|
|
26
|
-
client.refetchQueries(workflowQueryKey);
|
|
25
|
+
async function refetchWorkflow() {
|
|
26
|
+
await client.refetchQueries(workflowQueryKey);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const workflows = useQuery(workflowQueryKey, fetchWorkflows);
|
|
@@ -35,6 +35,7 @@ export function reducer(state = initialState, action) {
|
|
|
35
35
|
draft.serverState.workflows = workflows;
|
|
36
36
|
draft.serverState.currentWorkflow = defaultWorkflow;
|
|
37
37
|
draft.clientState.currentWorkflow.data = defaultWorkflow;
|
|
38
|
+
draft.clientState.currentWorkflow.hasDeletedServerStages = false;
|
|
38
39
|
}
|
|
39
40
|
break;
|
|
40
41
|
}
|
|
@@ -97,7 +98,7 @@ export function reducer(state = initialState, action) {
|
|
|
97
98
|
if (state.clientState.currentWorkflow.data) {
|
|
98
99
|
draft.clientState.currentWorkflow.isDirty = !isEqual(
|
|
99
100
|
current(draft.clientState.currentWorkflow).data,
|
|
100
|
-
|
|
101
|
+
draft.serverState.currentWorkflow
|
|
101
102
|
);
|
|
102
103
|
}
|
|
103
104
|
});
|
|
@@ -46,6 +46,11 @@ module.exports = {
|
|
|
46
46
|
};
|
|
47
47
|
},
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Replace all stages in a workflow
|
|
51
|
+
* @param {import('koa').BaseContext} ctx - koa context
|
|
52
|
+
*
|
|
53
|
+
*/
|
|
49
54
|
async replace(ctx) {
|
|
50
55
|
const { workflow_id: workflowId } = ctx.params;
|
|
51
56
|
const stagesService = getService('stages');
|
|
@@ -90,6 +95,8 @@ module.exports = {
|
|
|
90
95
|
// TODO When multiple workflows are possible, check if the stage is part of the right one
|
|
91
96
|
// Didn't need this today as their can only be one workflow
|
|
92
97
|
|
|
93
|
-
|
|
98
|
+
const data = await stagesService.updateEntity({ id: entityId, modelUID }, stageId);
|
|
99
|
+
|
|
100
|
+
ctx.body = { data };
|
|
94
101
|
},
|
|
95
102
|
};
|
package/ee/server/register.js
CHANGED
|
@@ -8,7 +8,9 @@ const reviewWorkflowsMiddlewares = require('./middlewares/review-workflows');
|
|
|
8
8
|
const { getService } = require('./utils');
|
|
9
9
|
|
|
10
10
|
module.exports = async ({ strapi }) => {
|
|
11
|
-
|
|
11
|
+
const auditLogsIsEnabled = strapi.config.get('admin.auditLogs.enabled', true);
|
|
12
|
+
|
|
13
|
+
if (auditLogsIsEnabled) {
|
|
12
14
|
strapi.hook('strapi::content-types.beforeSync').register(migrateAuditLogsTable);
|
|
13
15
|
const auditLogsService = createAuditLogsService(strapi);
|
|
14
16
|
strapi.container.register('audit-logs', auditLogsService);
|
|
@@ -63,16 +63,37 @@ const getEventMap = (defaultEvents) => {
|
|
|
63
63
|
}, {});
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
const getRetentionDays = (strapi) => {
|
|
67
|
+
const licenseRetentionDays = features.get('audit-logs')?.options.retentionDays;
|
|
68
|
+
const userRetentionDays = strapi.config.get('admin.auditLogs.retentionDays');
|
|
69
|
+
|
|
70
|
+
// For enterprise plans, use 90 days by default, but allow users to override it
|
|
71
|
+
if (licenseRetentionDays == null) {
|
|
72
|
+
return userRetentionDays ?? DEFAULT_RETENTION_DAYS;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Allow users to override the license retention days, but not to increase it
|
|
76
|
+
if (userRetentionDays && userRetentionDays < licenseRetentionDays) {
|
|
77
|
+
return userRetentionDays;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// User didn't provide a retention days value, use the license one
|
|
81
|
+
return licenseRetentionDays;
|
|
82
|
+
};
|
|
83
|
+
|
|
66
84
|
const createAuditLogsService = (strapi) => {
|
|
85
|
+
// Manage internal service state privately
|
|
86
|
+
const state = {};
|
|
87
|
+
|
|
67
88
|
// NOTE: providers should be able to replace getEventMap to add or remove events
|
|
68
89
|
const eventMap = getEventMap(defaultEvents);
|
|
69
90
|
|
|
70
91
|
const processEvent = (name, ...args) => {
|
|
71
|
-
const
|
|
92
|
+
const requestState = strapi.requestContext.get()?.state;
|
|
72
93
|
|
|
73
94
|
// Ignore events with auth strategies different from admin
|
|
74
|
-
const isUsingAdminAuth =
|
|
75
|
-
const user =
|
|
95
|
+
const isUsingAdminAuth = requestState?.auth?.strategy.name === 'admin';
|
|
96
|
+
const user = requestState?.user;
|
|
76
97
|
|
|
77
98
|
if (!isUsingAdminAuth || !user) {
|
|
78
99
|
return null;
|
|
@@ -103,26 +124,60 @@ const createAuditLogsService = (strapi) => {
|
|
|
103
124
|
const processedEvent = processEvent(name, ...args);
|
|
104
125
|
|
|
105
126
|
if (processedEvent) {
|
|
106
|
-
await
|
|
127
|
+
await state.provider.saveEvent(processedEvent);
|
|
107
128
|
}
|
|
108
129
|
}
|
|
109
130
|
|
|
110
131
|
return {
|
|
111
132
|
async register() {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
133
|
+
// Handle license being enabled
|
|
134
|
+
if (!state.eeEnableUnsubscribe) {
|
|
135
|
+
state.eeEnableUnsubscribe = strapi.eventHub.on('ee.enable', () => {
|
|
136
|
+
// Recreate the service to use the new license info
|
|
137
|
+
this.destroy();
|
|
138
|
+
this.register();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Handle license being updated
|
|
143
|
+
if (!state.eeUpdateUnsubscribe) {
|
|
144
|
+
state.eeUpdateUnsubscribe = strapi.eventHub.on('ee.update', () => {
|
|
145
|
+
// Recreate the service to use the new license info
|
|
146
|
+
this.destroy();
|
|
147
|
+
this.register();
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Handle license being disabled
|
|
152
|
+
state.eeDisableUnsubscribe = strapi.eventHub.on('ee.disable', () => {
|
|
153
|
+
// Turn off service when the license gets disabled
|
|
154
|
+
// Only ee.enable and ee.update listeners remain active to recreate the service
|
|
155
|
+
this.destroy();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Register the provider now because collections can't be added later at runtime
|
|
159
|
+
state.provider = await localProvider.register({ strapi });
|
|
160
|
+
|
|
161
|
+
// Check current state of license
|
|
162
|
+
if (!features.isEnabled('audit-logs')) {
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Start saving events
|
|
167
|
+
state.eventHubUnsubscribe = strapi.eventHub.subscribe(handleEvent.bind(this));
|
|
168
|
+
|
|
169
|
+
// Manage audit logs auto deletion
|
|
170
|
+
const retentionDays = getRetentionDays(strapi);
|
|
171
|
+
state.deleteExpiredJob = scheduleJob('0 0 * * *', () => {
|
|
117
172
|
const expirationDate = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);
|
|
118
|
-
|
|
173
|
+
state.provider.deleteExpiredEvents(expirationDate);
|
|
119
174
|
});
|
|
120
175
|
|
|
121
176
|
return this;
|
|
122
177
|
},
|
|
123
178
|
|
|
124
179
|
async findMany(query) {
|
|
125
|
-
const { results, pagination } = await
|
|
180
|
+
const { results, pagination } = await state.provider.findMany(query);
|
|
126
181
|
|
|
127
182
|
const sanitizedResults = results.map((result) => {
|
|
128
183
|
const { user, ...rest } = result;
|
|
@@ -139,7 +194,7 @@ const createAuditLogsService = (strapi) => {
|
|
|
139
194
|
},
|
|
140
195
|
|
|
141
196
|
async findOne(id) {
|
|
142
|
-
const result = await
|
|
197
|
+
const result = await state.provider.findOne(id);
|
|
143
198
|
|
|
144
199
|
if (!result) {
|
|
145
200
|
return null;
|
|
@@ -153,12 +208,16 @@ const createAuditLogsService = (strapi) => {
|
|
|
153
208
|
},
|
|
154
209
|
|
|
155
210
|
unsubscribe() {
|
|
156
|
-
if (
|
|
157
|
-
|
|
211
|
+
if (state.eeDisableUnsubscribe) {
|
|
212
|
+
state.eeDisableUnsubscribe();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (state.eventHubUnsubscribe) {
|
|
216
|
+
state.eventHubUnsubscribe();
|
|
158
217
|
}
|
|
159
218
|
|
|
160
|
-
if (
|
|
161
|
-
|
|
219
|
+
if (state.deleteExpiredJob) {
|
|
220
|
+
state.deleteExpiredJob.cancel();
|
|
162
221
|
}
|
|
163
222
|
|
|
164
223
|
return this;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { isNil } = require('lodash/fp');
|
|
3
|
+
const { isNil, isNull } = require('lodash/fp');
|
|
4
4
|
const { ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
|
|
5
5
|
const { hasReviewWorkflow, getDefaultWorkflow } = require('../../utils/review-workflows');
|
|
6
6
|
|
|
@@ -30,10 +30,22 @@ const decorator = (service) => ({
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const data = await getDataWithStage(opts.data);
|
|
33
|
-
return service.create.call(this, uid, {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
});
|
|
33
|
+
return service.create.call(this, uid, { ...opts, data });
|
|
34
|
+
},
|
|
35
|
+
async update(uid, entityId, opts = {}) {
|
|
36
|
+
const hasRW = hasReviewWorkflow({ strapi }, uid);
|
|
37
|
+
|
|
38
|
+
if (!hasRW) {
|
|
39
|
+
return service.update.call(this, uid, entityId, opts);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Prevents the stage from being set to null
|
|
43
|
+
const data = { ...opts.data };
|
|
44
|
+
if (isNull(data[ENTITY_STAGE_ATTRIBUTE])) {
|
|
45
|
+
delete data[ENTITY_STAGE_ATTRIBUTE];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return service.update.call(this, uid, entityId, { ...opts, data });
|
|
37
49
|
},
|
|
38
50
|
});
|
|
39
51
|
|
|
@@ -1,46 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { set,
|
|
3
|
+
const { set, forEach, pipe } = require('lodash/fp');
|
|
4
4
|
const { mapAsync } = require('@strapi/utils');
|
|
5
5
|
const { getService } = require('../../utils');
|
|
6
|
+
const { getContentTypeUIDsWithActivatedReviewWorkflows } = require('../../utils/review-workflows');
|
|
6
7
|
|
|
7
8
|
const defaultStages = require('../../constants/default-stages.json');
|
|
8
9
|
const defaultWorkflow = require('../../constants/default-workflow.json');
|
|
9
10
|
const { ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
|
|
10
11
|
|
|
11
|
-
const {
|
|
12
|
-
disableOnContentTypes: disableReviewWorkflows,
|
|
13
|
-
} = require('../../migrations/review-workflows');
|
|
14
12
|
const { getDefaultWorkflow } = require('../../utils/review-workflows');
|
|
15
|
-
|
|
16
|
-
const getContentTypeUIDsWithActivatedReviewWorkflows = pipe([
|
|
17
|
-
// Pick only content-types with reviewWorkflows options set to true
|
|
18
|
-
pickBy(get('options.reviewWorkflows')),
|
|
19
|
-
// Get UIDs
|
|
20
|
-
keys,
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Map every stage in the array to be ordered in the relation
|
|
25
|
-
* @param {Object[]} stages
|
|
26
|
-
* @param {number} stages.id
|
|
27
|
-
* @return {Object[]}
|
|
28
|
-
*/
|
|
29
|
-
function buildStagesConnectArray(stages) {
|
|
30
|
-
return stages.map((stage, index) => {
|
|
31
|
-
const connect = {
|
|
32
|
-
id: stage.id,
|
|
33
|
-
position: {},
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
if (index === 0) {
|
|
37
|
-
connect.position.start = true;
|
|
38
|
-
} else {
|
|
39
|
-
connect.position.after = stages[index - 1].id;
|
|
40
|
-
}
|
|
41
|
-
return connect;
|
|
42
|
-
});
|
|
43
|
-
}
|
|
13
|
+
const { persistTable, removePersistedTablesWithSuffix } = require('../../utils/persisted-tables');
|
|
44
14
|
|
|
45
15
|
async function initDefaultWorkflow({ workflowsService, stagesService, strapi }) {
|
|
46
16
|
const wfCount = await workflowsService.count();
|
|
@@ -53,7 +23,7 @@ async function initDefaultWorkflow({ workflowsService, stagesService, strapi })
|
|
|
53
23
|
const workflow = {
|
|
54
24
|
...defaultWorkflow,
|
|
55
25
|
stages: {
|
|
56
|
-
connect:
|
|
26
|
+
connect: stages.map((stage) => stage.id),
|
|
57
27
|
},
|
|
58
28
|
};
|
|
59
29
|
|
|
@@ -63,19 +33,18 @@ async function initDefaultWorkflow({ workflowsService, stagesService, strapi })
|
|
|
63
33
|
}
|
|
64
34
|
}
|
|
65
35
|
|
|
66
|
-
const setStageAttribute = set(`attributes.${ENTITY_STAGE_ATTRIBUTE}`, {
|
|
67
|
-
writable: true,
|
|
68
|
-
private: false,
|
|
69
|
-
configurable: false,
|
|
70
|
-
visible: false,
|
|
71
|
-
type: 'relation',
|
|
72
|
-
relation: 'morphOne',
|
|
73
|
-
target: 'admin::workflow-stage',
|
|
74
|
-
morphBy: 'related',
|
|
75
|
-
});
|
|
76
|
-
|
|
77
36
|
function extendReviewWorkflowContentTypes({ strapi }) {
|
|
78
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
|
+
});
|
|
79
48
|
strapi.container.get('content-types').extend(contentTypeUID, setStageAttribute);
|
|
80
49
|
};
|
|
81
50
|
pipe([
|
|
@@ -101,55 +70,23 @@ function enableReviewWorkflow({ strapi }) {
|
|
|
101
70
|
return;
|
|
102
71
|
}
|
|
103
72
|
const firstStage = defaultWorkflow.stages[0];
|
|
73
|
+
const stagesService = getService('stages', { strapi });
|
|
104
74
|
|
|
105
75
|
const up = async (contentTypeUID) => {
|
|
106
|
-
|
|
107
|
-
const {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
.
|
|
115
|
-
|
|
116
|
-
[idColumn.name]: 'entity.id',
|
|
117
|
-
field: strapi.db.connection.raw('?', [ENTITY_STAGE_ATTRIBUTE]),
|
|
118
|
-
order: 1,
|
|
119
|
-
[joinTable.joinColumn.name]: firstStage.id,
|
|
120
|
-
[typeColumn.name]: strapi.db.connection.raw('?', [contentTypeUID]),
|
|
121
|
-
})
|
|
122
|
-
.leftJoin(`${joinTable.name} AS jointable`, function joinFunc() {
|
|
123
|
-
this.on('entity.id', '=', `jointable.${idColumn.name}`).andOn(
|
|
124
|
-
`jointable.${typeColumn.name}`,
|
|
125
|
-
'=',
|
|
126
|
-
strapi.db.connection.raw('?', [contentTypeUID])
|
|
127
|
-
);
|
|
128
|
-
})
|
|
129
|
-
.where(`jointable.${idColumn.name}`, null)
|
|
130
|
-
.from(`${contentTypeMetadata.tableName} AS entity`)
|
|
131
|
-
.toSQL();
|
|
132
|
-
|
|
133
|
-
const columnsToInsert = [
|
|
134
|
-
idColumn.name,
|
|
135
|
-
'field',
|
|
136
|
-
strapi.db.connection.raw('??', ['order']),
|
|
137
|
-
joinTable.joinColumn.name,
|
|
138
|
-
typeColumn.name,
|
|
139
|
-
];
|
|
140
|
-
|
|
141
|
-
// Insert rows for all entries of the content type that do not have a
|
|
142
|
-
// default stage
|
|
143
|
-
await strapi.db
|
|
144
|
-
.getConnection(joinTable.name)
|
|
145
|
-
.insert(
|
|
146
|
-
strapi.db.connection.raw(
|
|
147
|
-
`(${columnsToInsert.join(',')}) ${selectStatement.sql}`,
|
|
148
|
-
selectStatement.bindings
|
|
149
|
-
)
|
|
150
|
-
);
|
|
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
|
+
});
|
|
151
86
|
};
|
|
152
87
|
|
|
88
|
+
await removePersistedTablesWithSuffix('_strapi_review_workflows_stage_links');
|
|
89
|
+
|
|
153
90
|
return pipe([
|
|
154
91
|
getContentTypeUIDsWithActivatedReviewWorkflows,
|
|
155
92
|
// Iterate over UIDs to extend the content-type
|
|
@@ -169,7 +106,6 @@ module.exports = ({ strapi }) => {
|
|
|
169
106
|
async register() {
|
|
170
107
|
extendReviewWorkflowContentTypes({ strapi });
|
|
171
108
|
strapi.hook('strapi::content-types.afterSync').register(enableReviewWorkflow({ strapi }));
|
|
172
|
-
strapi.hook('strapi::content-types.afterSync').register(disableReviewWorkflows);
|
|
173
109
|
},
|
|
174
110
|
};
|
|
175
111
|
};
|
|
@@ -4,9 +4,11 @@ const {
|
|
|
4
4
|
mapAsync,
|
|
5
5
|
errors: { ApplicationError },
|
|
6
6
|
} = require('@strapi/utils');
|
|
7
|
+
const { map } = require('lodash/fp');
|
|
7
8
|
|
|
8
9
|
const { STAGE_MODEL_UID, ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
|
|
9
10
|
const { getService } = require('../../utils');
|
|
11
|
+
const { getContentTypeUIDsWithActivatedReviewWorkflows } = require('../../utils/review-workflows');
|
|
10
12
|
|
|
11
13
|
module.exports = ({ strapi }) => {
|
|
12
14
|
const workflowsService = getService('workflows', { strapi });
|
|
@@ -20,9 +22,8 @@ module.exports = ({ strapi }) => {
|
|
|
20
22
|
return strapi.entityService.findMany(STAGE_MODEL_UID, params);
|
|
21
23
|
},
|
|
22
24
|
|
|
23
|
-
findById(id, {
|
|
25
|
+
findById(id, { populate } = {}) {
|
|
24
26
|
const params = {
|
|
25
|
-
filters: { workflow: workflowId },
|
|
26
27
|
populate,
|
|
27
28
|
};
|
|
28
29
|
return strapi.entityService.findOne(STAGE_MODEL_UID, id, params);
|
|
@@ -58,12 +59,41 @@ module.exports = ({ strapi }) => {
|
|
|
58
59
|
|
|
59
60
|
assertAtLeastOneStageRemain(workflow.stages, { created, deleted });
|
|
60
61
|
|
|
61
|
-
return strapi.db.transaction(async () => {
|
|
62
|
-
|
|
63
|
-
const
|
|
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);
|
|
64
69
|
|
|
70
|
+
// Update the workflow stages
|
|
65
71
|
await mapAsync(updated, (stage) => this.update(stage.id, stage));
|
|
66
|
-
|
|
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
|
+
|
|
67
97
|
return workflowsService.update(workflowId, {
|
|
68
98
|
stages: stagesIds,
|
|
69
99
|
});
|
|
@@ -78,12 +108,58 @@ module.exports = ({ strapi }) => {
|
|
|
78
108
|
* @param {string} entityInfo.modelUID - the content-type of the entity
|
|
79
109
|
* @param {number} stageId - The id of the stage to assign to the entity
|
|
80
110
|
*/
|
|
81
|
-
updateEntity(entityInfo, stageId) {
|
|
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
|
+
|
|
82
118
|
return strapi.entityService.update(entityInfo.modelUID, entityInfo.id, {
|
|
83
119
|
data: { [ENTITY_STAGE_ATTRIBUTE]: stageId },
|
|
84
120
|
populate: [ENTITY_STAGE_ATTRIBUTE],
|
|
85
121
|
});
|
|
86
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
|
+
},
|
|
87
163
|
};
|
|
88
164
|
};
|
|
89
165
|
|
|
@@ -146,3 +222,28 @@ function assertAtLeastOneStageRemain(workflowStages, diffStages) {
|
|
|
146
222
|
throw new ApplicationError('At least one stage must remain in the workflow.');
|
|
147
223
|
}
|
|
148
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
|
+
}
|