Skip to content

Commit

Permalink
fix: add ability to immediately publish when using `createEntryRevisi…
Browse files Browse the repository at this point in the history
…onFrom` (#3894)

Co-authored-by: Pavel Denisjuk <[email protected]>
  • Loading branch information
adrians5j and Pavel910 authored Feb 26, 2024
1 parent 7d011d9 commit 629574a
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { useTestModelHandler } from "~tests/testHelpers/useTestModelHandler";
import { SecurityIdentity } from "@webiny/api-security/types";

const identityA: SecurityIdentity = { id: "a", type: "admin", displayName: "A" };
const identityB: SecurityIdentity = { id: "b", type: "admin", displayName: "B" };
import { identityA, identityB, identityC, identityD } from "./security/utils";

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

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

Expand Down Expand Up @@ -46,4 +43,90 @@ describe("Content entries - Entry Meta Fields Overrides", () => {
lastPublishedBy: identityB
});
});

test("users should be able to create a new revision from an existing revision and immediately publish it with custom publishing-related values", async () => {
// 1. Initially, all meta fields should be populated, except the "modified" ones.
const testDate1 = new Date("2020-01-01T00:00:00.000Z").toISOString();
const testDate2 = new Date("2021-01-01T00:00:00.000Z").toISOString();
const testDate3 = new Date("2022-01-01T00:00:00.000Z").toISOString();

const { data: rev } = await managerIdentityA.createTestEntry({
data: {
status: "published",
revisionFirstPublishedOn: testDate1,
revisionLastPublishedOn: testDate1,
revisionFirstPublishedBy: identityB,
revisionLastPublishedBy: identityB,
firstPublishedOn: testDate1,
lastPublishedOn: testDate1,
firstPublishedBy: identityB,
lastPublishedBy: identityB
}
});

const { data: publishedRevWithCustomLastPublishedValues } =
await managerIdentityA.createTestEntryFrom({
revision: rev.id,
data: {
status: "published",
revisionLastPublishedOn: testDate2,
revisionLastPublishedBy: identityC,
lastPublishedOn: testDate2,
lastPublishedBy: identityC
}
});

// Should not see changes in firstPublished-related fields.
expect(publishedRevWithCustomLastPublishedValues).toMatchObject({
createdOn: rev.createdOn,
createdBy: rev.createdBy,
modifiedBy: identityA,
savedOn: expect.toBeDateString(),
revisionFirstPublishedOn: expect.toBeDateString(),
revisionLastPublishedOn: testDate2,
revisionFirstPublishedBy: identityA,
revisionLastPublishedBy: identityC,
firstPublishedOn: testDate1,
lastPublishedOn: testDate2,
firstPublishedBy: identityB,
lastPublishedBy: identityC
});

expect(publishedRevWithCustomLastPublishedValues.savedOn > rev.savedOn).toBeTrue();
expect(
publishedRevWithCustomLastPublishedValues.revisionFirstPublishedOn >
rev.revisionFirstPublishedOn
).toBeTrue();

const { data: publishedRevWithAllCustomValues } =
await managerIdentityA.createTestEntryFrom({
revision: publishedRevWithCustomLastPublishedValues.id,
data: {
status: "published",
revisionFirstPublishedOn: testDate3,
revisionLastPublishedOn: testDate3,
revisionFirstPublishedBy: identityD,
revisionLastPublishedBy: identityD,
firstPublishedOn: testDate3,
lastPublishedOn: testDate3,
firstPublishedBy: identityD,
lastPublishedBy: identityD
}
});

expect(publishedRevWithAllCustomValues).toMatchObject({
createdOn: expect.toBeDateString(),
createdBy: identityA,
modifiedBy: identityA,
savedOn: expect.toBeDateString(),
revisionFirstPublishedOn: testDate3,
revisionLastPublishedOn: testDate3,
revisionFirstPublishedBy: identityD,
revisionLastPublishedBy: identityD,
firstPublishedOn: testDate3,
lastPublishedOn: testDate3,
firstPublishedBy: identityD,
lastPublishedBy: identityD
});
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { SecurityPermission } from "@webiny/api-security/types";
import { SecurityIdentity, SecurityPermission } from "@webiny/api-security/types";

export const identityA: SecurityIdentity = { id: "a", type: "admin", displayName: "A" };
export const identityB: SecurityIdentity = { id: "b", type: "admin", displayName: "B" };
export const identityC: SecurityIdentity = { id: "c", type: "admin", displayName: "C" };
export const identityD: SecurityIdentity = { id: "d", type: "admin", displayName: "D" };

export interface SetPermissionsParams {
groups: {
Expand Down
3 changes: 2 additions & 1 deletion packages/api-headless-cms/src/crud/contentEntry.crud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,8 @@ export const createContentEntryCrud = (params: CreateContentEntryCrudParams): Cm
getTenant,
getLocale,
originalEntry,
latestStorageEntry
latestStorageEntry,
accessControl
});

await accessControl.ensureCanAccessEntry({ model, entry, rwd: "w" });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { validateModelEntryDataOrThrow } from "../entryDataValidation";
import { referenceFieldsMapping } from "../referenceFieldsMapping";
import { createIdentifier, parseIdentifier } from "@webiny/utils";
import WebinyError from "@webiny/error";
import { STATUS_DRAFT } from "./statuses";
import { STATUS_DRAFT, STATUS_PUBLISHED, STATUS_UNPUBLISHED } from "./statuses";
import { AccessControl } from "~/crud/AccessControl/AccessControl";

type CreateEntryRevisionFromDataParams = {
sourceId: string;
Expand All @@ -28,6 +29,7 @@ type CreateEntryRevisionFromDataParams = {
getLocale: () => I18NLocale;
originalEntry: CmsEntry;
latestStorageEntry: CmsEntry;
accessControl: AccessControl;
};

export const createEntryRevisionFromData = async ({
Expand All @@ -38,7 +40,8 @@ export const createEntryRevisionFromData = async ({
context,
getIdentity: getSecurityIdentity,
originalEntry,
latestStorageEntry
latestStorageEntry,
accessControl
}: CreateEntryRevisionFromDataParams): Promise<{
entry: CmsEntry;
input: Record<string, any>;
Expand Down Expand Up @@ -74,6 +77,74 @@ export const createEntryRevisionFromData = async ({
const currentIdentity = getSecurityIdentity();
const currentDateTime = new Date();

/**
* Users can set the initial status of the entry. If so, we need to make
* sure they have the required permissions and also that all the fields
* are filled in correctly.
*/
const status = rawInput.status || STATUS_DRAFT;
if (status !== STATUS_DRAFT) {
if (status === STATUS_PUBLISHED) {
await accessControl.ensureCanAccessEntry({ model, pw: "p" });
} else if (status === STATUS_UNPUBLISHED) {
// If setting the status other than draft, we have to check if the user has permissions to publish.
await accessControl.ensureCanAccessEntry({ model, pw: "u" });
}
}

const locked = status !== STATUS_DRAFT;

let revisionLevelPublishingMetaFields: Pick<
CmsEntry,
| "revisionFirstPublishedOn"
| "revisionLastPublishedOn"
| "revisionFirstPublishedBy"
| "revisionLastPublishedBy"
> = {
revisionFirstPublishedOn: getDate(rawInput.revisionFirstPublishedOn, null),
revisionLastPublishedOn: getDate(rawInput.revisionLastPublishedOn, null),
revisionFirstPublishedBy: getIdentity(rawInput.revisionFirstPublishedBy, null),
revisionLastPublishedBy: getIdentity(rawInput.revisionLastPublishedBy, null)
};

let entryLevelPublishingMetaFields: Pick<
CmsEntry,
"firstPublishedOn" | "lastPublishedOn" | "firstPublishedBy" | "lastPublishedBy"
> = {
firstPublishedOn: getDate(rawInput.firstPublishedOn, latestStorageEntry.firstPublishedOn),
lastPublishedOn: getDate(rawInput.lastPublishedOn, latestStorageEntry.lastPublishedOn),
firstPublishedBy: getIdentity(
rawInput.firstPublishedBy,
latestStorageEntry.firstPublishedBy
),
lastPublishedBy: getIdentity(rawInput.lastPublishedBy, latestStorageEntry.lastPublishedBy)
};

if (status === STATUS_PUBLISHED) {
revisionLevelPublishingMetaFields = {
revisionFirstPublishedOn: getDate(rawInput.revisionFirstPublishedOn, currentDateTime),
revisionLastPublishedOn: getDate(rawInput.revisionLastPublishedOn, currentDateTime),
revisionFirstPublishedBy: getIdentity(
rawInput.revisionFirstPublishedBy,
currentIdentity
),
revisionLastPublishedBy: getIdentity(rawInput.revisionLastPublishedBy, currentIdentity)
};

entryLevelPublishingMetaFields = {
firstPublishedOn: getDate(
rawInput.firstPublishedOn,
latestStorageEntry.firstPublishedOn
),
lastPublishedOn: getDate(rawInput.lastPublishedOn, currentDateTime),
firstPublishedBy: getIdentity(
rawInput.firstPublishedBy,
latestStorageEntry.firstPublishedBy
),
lastPublishedBy: getIdentity(rawInput.lastPublishedBy, currentIdentity)
};
}

const entry: CmsEntry = {
...originalEntry,
id,
Expand All @@ -85,33 +156,24 @@ export const createEntryRevisionFromData = async ({
createdOn: getDate(rawInput.createdOn, latestStorageEntry.createdOn),
savedOn: getDate(rawInput.savedOn, currentDateTime),
modifiedOn: getDate(rawInput.modifiedOn, currentDateTime),
firstPublishedOn: getDate(rawInput.firstPublishedOn, latestStorageEntry.firstPublishedOn),
lastPublishedOn: getDate(rawInput.lastPublishedOn, latestStorageEntry.lastPublishedOn),
createdBy: getIdentity(rawInput.createdBy, latestStorageEntry.createdBy),
savedBy: getIdentity(rawInput.savedBy, currentIdentity),
modifiedBy: getIdentity(rawInput.modifiedBy, currentIdentity),
firstPublishedBy: getIdentity(
rawInput.firstPublishedBy,
latestStorageEntry.firstPublishedBy
),
lastPublishedBy: getIdentity(rawInput.lastPublishedBy, latestStorageEntry.lastPublishedBy),
...entryLevelPublishingMetaFields,

/**
* Revision-level meta fields. 👇
*/
revisionCreatedOn: getDate(rawInput.revisionCreatedOn, currentDateTime),
revisionSavedOn: getDate(rawInput.revisionSavedOn, currentDateTime),
revisionModifiedOn: getDate(rawInput.revisionModifiedOn, null),
revisionFirstPublishedOn: getDate(rawInput.revisionFirstPublishedOn, null),
revisionLastPublishedOn: getDate(rawInput.revisionLastPublishedOn, null),
revisionCreatedBy: getIdentity(rawInput.revisionCreatedBy, currentIdentity),
revisionSavedBy: getIdentity(rawInput.revisionSavedBy, currentIdentity),
revisionModifiedBy: getIdentity(rawInput.revisionModifiedBy, null),
revisionFirstPublishedBy: getIdentity(rawInput.revisionFirstPublishedBy, null),
revisionLastPublishedBy: getIdentity(rawInput.revisionLastPublishedBy, null),
...revisionLevelPublishingMetaFields,

locked: false,
status: STATUS_DRAFT,
locked,
status,
values
};

Expand Down

0 comments on commit 629574a

Please sign in to comment.