Skip to content

Commit 629574a

Browse files
adrians5jPavel910
andauthored
fix: add ability to immediately publish when using createEntryRevisionFrom (#3894)
Co-authored-by: Pavel Denisjuk <[email protected]>
1 parent 7d011d9 commit 629574a

File tree

4 files changed

+173
-22
lines changed

4 files changed

+173
-22
lines changed

packages/api-headless-cms/__tests__/contentAPI/contentEntriesOnByMetaFieldsOverrides.test.ts

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { useTestModelHandler } from "~tests/testHelpers/useTestModelHandler";
2-
import { SecurityIdentity } from "@webiny/api-security/types";
3-
4-
const identityA: SecurityIdentity = { id: "a", type: "admin", displayName: "A" };
5-
const identityB: SecurityIdentity = { id: "b", type: "admin", displayName: "B" };
2+
import { identityA, identityB, identityC, identityD } from "./security/utils";
63

74
describe("Content entries - Entry Meta Fields Overrides", () => {
85
const { manage: managerIdentityA } = useTestModelHandler({
@@ -13,7 +10,7 @@ describe("Content entries - Entry Meta Fields Overrides", () => {
1310
await managerIdentityA.setup();
1411
});
1512

16-
test("users should be able to create and immediately publish an entry with a custom publishing-related values", async () => {
13+
test("users should be able to create and immediately publish an entry with custom publishing-related values", async () => {
1714
// 1. Initially, all meta fields should be populated, except the "modified" ones.
1815
const testDate = new Date("2020-01-01T00:00:00.000Z").toISOString();
1916

@@ -46,4 +43,90 @@ describe("Content entries - Entry Meta Fields Overrides", () => {
4643
lastPublishedBy: identityB
4744
});
4845
});
46+
47+
test("users should be able to create a new revision from an existing revision and immediately publish it with custom publishing-related values", async () => {
48+
// 1. Initially, all meta fields should be populated, except the "modified" ones.
49+
const testDate1 = new Date("2020-01-01T00:00:00.000Z").toISOString();
50+
const testDate2 = new Date("2021-01-01T00:00:00.000Z").toISOString();
51+
const testDate3 = new Date("2022-01-01T00:00:00.000Z").toISOString();
52+
53+
const { data: rev } = await managerIdentityA.createTestEntry({
54+
data: {
55+
status: "published",
56+
revisionFirstPublishedOn: testDate1,
57+
revisionLastPublishedOn: testDate1,
58+
revisionFirstPublishedBy: identityB,
59+
revisionLastPublishedBy: identityB,
60+
firstPublishedOn: testDate1,
61+
lastPublishedOn: testDate1,
62+
firstPublishedBy: identityB,
63+
lastPublishedBy: identityB
64+
}
65+
});
66+
67+
const { data: publishedRevWithCustomLastPublishedValues } =
68+
await managerIdentityA.createTestEntryFrom({
69+
revision: rev.id,
70+
data: {
71+
status: "published",
72+
revisionLastPublishedOn: testDate2,
73+
revisionLastPublishedBy: identityC,
74+
lastPublishedOn: testDate2,
75+
lastPublishedBy: identityC
76+
}
77+
});
78+
79+
// Should not see changes in firstPublished-related fields.
80+
expect(publishedRevWithCustomLastPublishedValues).toMatchObject({
81+
createdOn: rev.createdOn,
82+
createdBy: rev.createdBy,
83+
modifiedBy: identityA,
84+
savedOn: expect.toBeDateString(),
85+
revisionFirstPublishedOn: expect.toBeDateString(),
86+
revisionLastPublishedOn: testDate2,
87+
revisionFirstPublishedBy: identityA,
88+
revisionLastPublishedBy: identityC,
89+
firstPublishedOn: testDate1,
90+
lastPublishedOn: testDate2,
91+
firstPublishedBy: identityB,
92+
lastPublishedBy: identityC
93+
});
94+
95+
expect(publishedRevWithCustomLastPublishedValues.savedOn > rev.savedOn).toBeTrue();
96+
expect(
97+
publishedRevWithCustomLastPublishedValues.revisionFirstPublishedOn >
98+
rev.revisionFirstPublishedOn
99+
).toBeTrue();
100+
101+
const { data: publishedRevWithAllCustomValues } =
102+
await managerIdentityA.createTestEntryFrom({
103+
revision: publishedRevWithCustomLastPublishedValues.id,
104+
data: {
105+
status: "published",
106+
revisionFirstPublishedOn: testDate3,
107+
revisionLastPublishedOn: testDate3,
108+
revisionFirstPublishedBy: identityD,
109+
revisionLastPublishedBy: identityD,
110+
firstPublishedOn: testDate3,
111+
lastPublishedOn: testDate3,
112+
firstPublishedBy: identityD,
113+
lastPublishedBy: identityD
114+
}
115+
});
116+
117+
expect(publishedRevWithAllCustomValues).toMatchObject({
118+
createdOn: expect.toBeDateString(),
119+
createdBy: identityA,
120+
modifiedBy: identityA,
121+
savedOn: expect.toBeDateString(),
122+
revisionFirstPublishedOn: testDate3,
123+
revisionLastPublishedOn: testDate3,
124+
revisionFirstPublishedBy: identityD,
125+
revisionLastPublishedBy: identityD,
126+
firstPublishedOn: testDate3,
127+
lastPublishedOn: testDate3,
128+
firstPublishedBy: identityD,
129+
lastPublishedBy: identityD
130+
});
131+
});
49132
});

packages/api-headless-cms/__tests__/contentAPI/security/utils.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { SecurityPermission } from "@webiny/api-security/types";
1+
import { SecurityIdentity, SecurityPermission } from "@webiny/api-security/types";
2+
3+
export const identityA: SecurityIdentity = { id: "a", type: "admin", displayName: "A" };
4+
export const identityB: SecurityIdentity = { id: "b", type: "admin", displayName: "B" };
5+
export const identityC: SecurityIdentity = { id: "c", type: "admin", displayName: "C" };
6+
export const identityD: SecurityIdentity = { id: "d", type: "admin", displayName: "D" };
27

38
export interface SetPermissionsParams {
49
groups: {

packages/api-headless-cms/src/crud/contentEntry.crud.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,8 @@ export const createContentEntryCrud = (params: CreateContentEntryCrudParams): Cm
553553
getTenant,
554554
getLocale,
555555
originalEntry,
556-
latestStorageEntry
556+
latestStorageEntry,
557+
accessControl
557558
});
558559

559560
await accessControl.ensureCanAccessEntry({ model, entry, rwd: "w" });

packages/api-headless-cms/src/crud/contentEntry/entryDataFactories/createEntryRevisionFromData.ts

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import { validateModelEntryDataOrThrow } from "../entryDataValidation";
1515
import { referenceFieldsMapping } from "../referenceFieldsMapping";
1616
import { createIdentifier, parseIdentifier } from "@webiny/utils";
1717
import WebinyError from "@webiny/error";
18-
import { STATUS_DRAFT } from "./statuses";
18+
import { STATUS_DRAFT, STATUS_PUBLISHED, STATUS_UNPUBLISHED } from "./statuses";
19+
import { AccessControl } from "~/crud/AccessControl/AccessControl";
1920

2021
type CreateEntryRevisionFromDataParams = {
2122
sourceId: string;
@@ -28,6 +29,7 @@ type CreateEntryRevisionFromDataParams = {
2829
getLocale: () => I18NLocale;
2930
originalEntry: CmsEntry;
3031
latestStorageEntry: CmsEntry;
32+
accessControl: AccessControl;
3133
};
3234

3335
export const createEntryRevisionFromData = async ({
@@ -38,7 +40,8 @@ export const createEntryRevisionFromData = async ({
3840
context,
3941
getIdentity: getSecurityIdentity,
4042
originalEntry,
41-
latestStorageEntry
43+
latestStorageEntry,
44+
accessControl
4245
}: CreateEntryRevisionFromDataParams): Promise<{
4346
entry: CmsEntry;
4447
input: Record<string, any>;
@@ -74,6 +77,74 @@ export const createEntryRevisionFromData = async ({
7477
const currentIdentity = getSecurityIdentity();
7578
const currentDateTime = new Date();
7679

80+
/**
81+
* Users can set the initial status of the entry. If so, we need to make
82+
* sure they have the required permissions and also that all the fields
83+
* are filled in correctly.
84+
*/
85+
const status = rawInput.status || STATUS_DRAFT;
86+
if (status !== STATUS_DRAFT) {
87+
if (status === STATUS_PUBLISHED) {
88+
await accessControl.ensureCanAccessEntry({ model, pw: "p" });
89+
} else if (status === STATUS_UNPUBLISHED) {
90+
// If setting the status other than draft, we have to check if the user has permissions to publish.
91+
await accessControl.ensureCanAccessEntry({ model, pw: "u" });
92+
}
93+
}
94+
95+
const locked = status !== STATUS_DRAFT;
96+
97+
let revisionLevelPublishingMetaFields: Pick<
98+
CmsEntry,
99+
| "revisionFirstPublishedOn"
100+
| "revisionLastPublishedOn"
101+
| "revisionFirstPublishedBy"
102+
| "revisionLastPublishedBy"
103+
> = {
104+
revisionFirstPublishedOn: getDate(rawInput.revisionFirstPublishedOn, null),
105+
revisionLastPublishedOn: getDate(rawInput.revisionLastPublishedOn, null),
106+
revisionFirstPublishedBy: getIdentity(rawInput.revisionFirstPublishedBy, null),
107+
revisionLastPublishedBy: getIdentity(rawInput.revisionLastPublishedBy, null)
108+
};
109+
110+
let entryLevelPublishingMetaFields: Pick<
111+
CmsEntry,
112+
"firstPublishedOn" | "lastPublishedOn" | "firstPublishedBy" | "lastPublishedBy"
113+
> = {
114+
firstPublishedOn: getDate(rawInput.firstPublishedOn, latestStorageEntry.firstPublishedOn),
115+
lastPublishedOn: getDate(rawInput.lastPublishedOn, latestStorageEntry.lastPublishedOn),
116+
firstPublishedBy: getIdentity(
117+
rawInput.firstPublishedBy,
118+
latestStorageEntry.firstPublishedBy
119+
),
120+
lastPublishedBy: getIdentity(rawInput.lastPublishedBy, latestStorageEntry.lastPublishedBy)
121+
};
122+
123+
if (status === STATUS_PUBLISHED) {
124+
revisionLevelPublishingMetaFields = {
125+
revisionFirstPublishedOn: getDate(rawInput.revisionFirstPublishedOn, currentDateTime),
126+
revisionLastPublishedOn: getDate(rawInput.revisionLastPublishedOn, currentDateTime),
127+
revisionFirstPublishedBy: getIdentity(
128+
rawInput.revisionFirstPublishedBy,
129+
currentIdentity
130+
),
131+
revisionLastPublishedBy: getIdentity(rawInput.revisionLastPublishedBy, currentIdentity)
132+
};
133+
134+
entryLevelPublishingMetaFields = {
135+
firstPublishedOn: getDate(
136+
rawInput.firstPublishedOn,
137+
latestStorageEntry.firstPublishedOn
138+
),
139+
lastPublishedOn: getDate(rawInput.lastPublishedOn, currentDateTime),
140+
firstPublishedBy: getIdentity(
141+
rawInput.firstPublishedBy,
142+
latestStorageEntry.firstPublishedBy
143+
),
144+
lastPublishedBy: getIdentity(rawInput.lastPublishedBy, currentIdentity)
145+
};
146+
}
147+
77148
const entry: CmsEntry = {
78149
...originalEntry,
79150
id,
@@ -85,33 +156,24 @@ export const createEntryRevisionFromData = async ({
85156
createdOn: getDate(rawInput.createdOn, latestStorageEntry.createdOn),
86157
savedOn: getDate(rawInput.savedOn, currentDateTime),
87158
modifiedOn: getDate(rawInput.modifiedOn, currentDateTime),
88-
firstPublishedOn: getDate(rawInput.firstPublishedOn, latestStorageEntry.firstPublishedOn),
89-
lastPublishedOn: getDate(rawInput.lastPublishedOn, latestStorageEntry.lastPublishedOn),
90159
createdBy: getIdentity(rawInput.createdBy, latestStorageEntry.createdBy),
91160
savedBy: getIdentity(rawInput.savedBy, currentIdentity),
92161
modifiedBy: getIdentity(rawInput.modifiedBy, currentIdentity),
93-
firstPublishedBy: getIdentity(
94-
rawInput.firstPublishedBy,
95-
latestStorageEntry.firstPublishedBy
96-
),
97-
lastPublishedBy: getIdentity(rawInput.lastPublishedBy, latestStorageEntry.lastPublishedBy),
162+
...entryLevelPublishingMetaFields,
98163

99164
/**
100165
* Revision-level meta fields. 👇
101166
*/
102167
revisionCreatedOn: getDate(rawInput.revisionCreatedOn, currentDateTime),
103168
revisionSavedOn: getDate(rawInput.revisionSavedOn, currentDateTime),
104169
revisionModifiedOn: getDate(rawInput.revisionModifiedOn, null),
105-
revisionFirstPublishedOn: getDate(rawInput.revisionFirstPublishedOn, null),
106-
revisionLastPublishedOn: getDate(rawInput.revisionLastPublishedOn, null),
107170
revisionCreatedBy: getIdentity(rawInput.revisionCreatedBy, currentIdentity),
108171
revisionSavedBy: getIdentity(rawInput.revisionSavedBy, currentIdentity),
109172
revisionModifiedBy: getIdentity(rawInput.revisionModifiedBy, null),
110-
revisionFirstPublishedBy: getIdentity(rawInput.revisionFirstPublishedBy, null),
111-
revisionLastPublishedBy: getIdentity(rawInput.revisionLastPublishedBy, null),
173+
...revisionLevelPublishingMetaFields,
112174

113-
locked: false,
114-
status: STATUS_DRAFT,
175+
locked,
176+
status,
115177
values
116178
};
117179

0 commit comments

Comments
 (0)