Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/api-headless-cms-workflows/src/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Context } from "~/types.js";
import { getModelIdFromAppName } from "~/utils/appName.js";
import { getStateValues } from "~/utils/state.js";
import type { IWorkflowState } from "@webiny/api-workflows";
import type { ICmsEntryState } from "@webiny/api-headless-cms/types/index.js";
import type { IEntryState } from "@webiny/api-headless-cms/types/index.js";

interface IParams {
context: Context;
Expand All @@ -11,7 +11,7 @@ interface IParams {
export const attachStateLifecycleEvents = ({ context }: IParams) => {
const updateEntry = async (
state: IWorkflowState,
values: ICmsEntryState | undefined
values: IEntryState | undefined
): Promise<void> => {
const modelId = getModelIdFromAppName(state.app);
if (!modelId) {
Expand Down
4 changes: 2 additions & 2 deletions packages/api-headless-cms-workflows/src/utils/state.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ICmsEntryState } from "@webiny/api-headless-cms/types/index.js";
import type { IEntryState } from "@webiny/api-headless-cms/types/index.js";
import type { IWorkflowStateModel } from "@webiny/api-workflows/context/abstractions/WorkflowState.js";

export const getStateValues = (state: IWorkflowStateModel): ICmsEntryState | undefined => {
export const getStateValues = (state: IWorkflowStateModel): IEntryState | undefined => {
const activeStep = state.getActiveStep();
if (!activeStep) {
return undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { CmsEntry, ICmsEntryState } from "~/types/index.js";
import type { CmsEntry, IEntryState } from "~/types/index.js";

interface IInputWithPossibleState {
state: Partial<ICmsEntryState> | null;
state: Partial<IEntryState> | null;
}
interface IParams {
input: Partial<IInputWithPossibleState>;
original?: CmsEntry | null;
}

export const getState = ({ input, original }: IParams): ICmsEntryState | undefined => {
export const getState = ({ input, original }: IParams): IEntryState | undefined => {
if (
!input?.state?.stepId ||
!input.state.state ||
Expand Down
3 changes: 3 additions & 0 deletions packages/api-headless-cms/src/graphql/schema/baseSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,17 @@ const createSchema = (plugins: PluginsContainer): IGraphQLSchemaPlugin<CmsContex
}

type CmsEntryState {
workflowId: String
stepId: ID
stepName: String
state: CmsEntryStateType
}

input ListWhereInputCmsEntryState {
workflowId: String
stepId: ID
state: CmsEntryStateType
stepName: String
}
`,
resolvers: {}
Expand Down
10 changes: 5 additions & 5 deletions packages/api-headless-cms/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ export interface ICmsEntryLocation {
folderId?: string | null;
}

export interface ICmsEntryState {
export interface IEntryState {
state: string;
workflowId: string;
stepId: string;
Expand Down Expand Up @@ -686,7 +686,7 @@ export interface CmsEntry<T = CmsEntryValues> {
*/
binOriginalFolderId?: string | null;

state?: ICmsEntryState;
state?: IEntryState;
}

export interface CmsStorageEntry extends CmsEntry {
Expand Down Expand Up @@ -1471,7 +1471,7 @@ export type CreateCmsEntryInput<TValues = CmsEntryValues> = TValues & {
folderId?: string | null;
};

state?: Partial<ICmsEntryState>;
state?: Partial<IEntryState>;
};

export interface CreateCmsEntryOptionsInput {
Expand Down Expand Up @@ -1511,7 +1511,7 @@ export interface CreateFromCmsEntryInput {
firstPublishedBy?: CmsIdentity;
lastPublishedBy?: CmsIdentity;

state?: Partial<ICmsEntryState>;
state?: Partial<IEntryState>;

[key: string]: any;
}
Expand Down Expand Up @@ -1565,7 +1565,7 @@ export type UpdateCmsEntryInput<TValues = CmsEntryValues> = TValues & {
folderId?: string | null;
};

state?: Partial<ICmsEntryState>;
state?: Partial<IEntryState>;
};

export interface UpdateCmsEntryOptionsInput {
Expand Down
3 changes: 3 additions & 0 deletions packages/api-website-builder-workflows/.babelrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = require("@webiny/build-tools").createBabelConfigForNode({
path: __dirname
});
21 changes: 21 additions & 0 deletions packages/api-website-builder-workflows/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) Webiny

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
35 changes: 35 additions & 0 deletions packages/api-website-builder-workflows/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@webiny/api-website-builder-workflows",
"version": "0.0.0",
"type": "module",
"main": "index.js",
"description": "Headless CMS Workflows",
"keywords": [
"api-website-builder-workflows:base"
],
"repository": {
"type": "git",
"url": "https://github.com/webiny/webiny-js.git",
"directory": "packages/api-website-builder-workflows"
},
"license": "MIT",
"dependencies": {
"@webiny/api-website-builder": "0.0.0",
"@webiny/api-workflows": "0.0.0",
"@webiny/error": "0.0.0",
"@webiny/handler": "0.0.0",
"@webiny/handler-graphql": "0.0.0"
},
"devDependencies": {
"@webiny/build-tools": "0.0.0",
"@webiny/plugins": "0.0.0",
"@webiny/project-utils": "0.0.0",
"@webiny/testing": "0.0.0",
"typescript": "5.9.3",
"vitest": "^3.2.4"
},
"publishConfig": {
"access": "public",
"directory": "dist"
}
}
1 change: 1 addition & 0 deletions packages/api-website-builder-workflows/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const WB_PAGE_APP = "wb.page";
49 changes: 49 additions & 0 deletions packages/api-website-builder-workflows/src/graphql/page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { GraphQLSchemaPlugin } from "@webiny/handler-graphql";
import type { Context } from "~/types.js";

export const createWebsiteBuilderPageGraphQLExtension = () => {
return new GraphQLSchemaPlugin<Context>({
isApplicable: context => {
if (!context.wcp.canUseWorkflows()) {
return false;
} else if (!context.workflows) {
return false;
} else if (!context.websiteBuilder) {
return false;
}
return true;
},
typeDefs: /* GraphQL */ `
# CmsEntryStateType
enum WbPageStateType {
pending
inReview
rejected
approved
}

# CmsEntryState
type WbPageState {
workflowId: String
stepId: ID
stepName: String
state: WbPageStateType
}

extend type WbPage {
state: WbPageState
}

input ListWhereInputWbPageState {
workflowId: String
stepId: ID
state: CmsEntryStateType
stepName: String
}

extend input WbPagesListWhereInput {
state: ListWhereInputWbPageState
}
`
});
};
26 changes: 26 additions & 0 deletions packages/api-website-builder-workflows/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ContextPlugin } from "@webiny/handler/Context.js";
import { attachLifecycleEvents } from "~/websiteBuilder/index.js";
import { Context } from "./types.js";
import { attachStateLifecycleEvents } from "~/state/index.js";
import { createWebsiteBuilderPageGraphQLExtension } from "~/graphql/page.js";

export const createWebsiteBuilderWorkflows = () => {
const plugin = new ContextPlugin<Context>(async context => {
if (!context.wcp.canUseWorkflows()) {
return;
} else if (!context.workflows) {
return;
}

attachLifecycleEvents({
context
});
attachStateLifecycleEvents({
context
});
});

plugin.name = "website-builder-workflows.context";

return [plugin, createWebsiteBuilderPageGraphQLExtension()];
};
35 changes: 35 additions & 0 deletions packages/api-website-builder-workflows/src/state/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Context, IWbPageState } from "~/types.js";
import type { IWorkflowState } from "@webiny/api-workflows";
import { getStateValues } from "~/utils/state.js";

interface IParams {
context: Context;
}

export const attachStateLifecycleEvents = ({ context }: IParams) => {
const updatePage = async (
state: IWorkflowState,
values: IWbPageState | undefined
): Promise<void> => {
try {
await context.websiteBuilder.pages.update(state.targetRevisionId, {
state: values
});
} catch (ex) {
// no need to do anything, just log the error
console.log(ex);
}
};

context.workflowState.onStateAfterCreate.subscribe(async ({ state }) => {
return updatePage(state, getStateValues(state));
});

context.workflowState.onStateAfterUpdate.subscribe(async ({ state }) => {
return updatePage(state, getStateValues(state));
});

context.workflowState.onStateAfterDelete.subscribe(async ({ state }) => {
return updatePage(state, undefined);
});
};
13 changes: 13 additions & 0 deletions packages/api-website-builder-workflows/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { WebsiteBuilderContext } from "@webiny/api-website-builder";
import type { Context as WorkflowsContext } from "@webiny/api-workflows/types.js";

export interface Context extends WebsiteBuilderContext, WorkflowsContext {
//
}

export interface IWbPageState {
workflowId: string;
stepId: string;
stepName: string;
state: string;
}
16 changes: 16 additions & 0 deletions packages/api-website-builder-workflows/src/utils/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { IWorkflowStateModel } from "@webiny/api-workflows";
import type { IWbPageState } from "~/types.js";

export const getStateValues = (state: IWorkflowStateModel): IWbPageState | undefined => {
const activeStep = state.getActiveStep();
if (!activeStep) {
return undefined;
}

return {
workflowId: state.workflowId,
stepId: activeStep.id,
stepName: activeStep.title,
state: activeStep.state
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Context } from "~/types.js";
import { WB_PAGE_APP } from "~/constants.js";

interface IParams {
context: Pick<Context, "workflowState" | "websiteBuilder">;
}

export const attachDeletePageLifecycleEvents = (params: IParams) => {
const { context } = params;
context.websiteBuilder.pages.onPageBeforeDelete.subscribe(async ({ page }) => {
try {
await context.workflowState.deleteTargetState(WB_PAGE_APP, page.id);
} catch {
// does not matter
}
});
};
14 changes: 14 additions & 0 deletions packages/api-website-builder-workflows/src/websiteBuilder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Context } from "~/types.js";
import { attachUpdatePageLifecycleEvents } from "./updatePage.js";
import { attachDeletePageLifecycleEvents } from "./deletePage.js";
import { attachPublishPageLifecycleEvents } from "./publishPage.js";

interface IParams {
context: Pick<Context, "workflowState" | "websiteBuilder">;
}

export const attachLifecycleEvents = (params: IParams) => {
attachUpdatePageLifecycleEvents(params);
attachDeletePageLifecycleEvents(params);
attachPublishPageLifecycleEvents(params);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { WebinyError } from "@webiny/error";
import type { Context } from "~/types.js";
import type { IWorkflowState } from "@webiny/api-workflows";
import { WorkflowStateNotFoundError } from "@webiny/api-workflows";
import { WB_PAGE_APP } from "~/constants.js";

interface IParams {
context: Pick<Context, "workflowState" | "websiteBuilder">;
}

export const attachPublishPageLifecycleEvents = (params: IParams) => {
const { context } = params;

context.websiteBuilder.pages.onPageBeforePublish.subscribe(async ({ page }) => {
let state: IWorkflowState | undefined = undefined;
try {
state = await context.workflowState.getTargetState(WB_PAGE_APP, page.id);
if (state?.done) {
return;
}
} catch (ex) {
// Swallow error if workflow state is not found.
if (ex instanceof WorkflowStateNotFoundError) {
return;
}
throw ex;
}
throw new WebinyError(
"Cannot publish page because its workflow state is not completed.",
"WORKFLOW_STATE_NOT_COMPLETED",
{
pageId: page.id,
state: {
...state
}
}
);
});
};
Loading
Loading