Use "automation" instead of "workflow"

[MAILPOET-4793]
This commit is contained in:
Jan Jakes
2022-11-10 10:24:31 +03:00
committed by David Remer
parent d199c3768a
commit 9d55d3f134
210 changed files with 3677 additions and 3659 deletions

View File

@ -1,4 +1,4 @@
.mailpoet-automation-workflow-add-trigger { .mailpoet-automation-automation-add-trigger {
align-items: center; align-items: center;
border: 1px dashed #c3c4c7; border: 1px dashed #c3c4c7;
border-radius: 4px; border-radius: 4px;

View File

@ -1,14 +1,14 @@
.mailpoet-automation-editor-workflow { .mailpoet-automation-editor-automation {
background: #fbfbfb; background: #fbfbfb;
flex-grow: 1; flex-grow: 1;
} }
.mailpoet-automation-editor-workflow-wrapper { .mailpoet-automation-editor-automation-wrapper {
display: grid; display: grid;
padding: 50px 20px; padding: 50px 20px;
} }
.mailpoet-automation-editor-workflow-end { .mailpoet-automation-editor-automation-end {
background: #8c8f94; background: #8c8f94;
border-radius: 999999px; border-radius: 999999px;
fill: white; fill: white;

View File

@ -1,4 +1,4 @@
.mailpoet-automation-editor-empty-workflow { .mailpoet-automation-editor-empty-automation {
align-items: center; align-items: center;
display: grid; display: grid;
height: 100%; height: 100%;

View File

@ -11,17 +11,17 @@
@import './components-automation-editor/add-step-button'; @import './components-automation-editor/add-step-button';
@import './components-automation-editor/add-trigger'; @import './components-automation-editor/add-trigger';
@import './components-automation-editor/automation';
@import './components-automation-editor/block-icon'; @import './components-automation-editor/block-icon';
@import './components-automation-editor/chip'; @import './components-automation-editor/chip';
@import './components-automation-editor/dropdown'; @import './components-automation-editor/dropdown';
@import './components-automation-editor/empty-workflow'; @import './components-automation-editor/empty-automation';
@import './components-automation-editor/errors'; @import './components-automation-editor/errors';
@import './components-automation-editor/panel'; @import './components-automation-editor/panel';
@import './components-automation-editor/separator'; @import './components-automation-editor/separator';
@import './components-automation-editor/status'; @import './components-automation-editor/status';
@import './components-automation-editor/step'; @import './components-automation-editor/step';
@import './components-automation-editor/step-card'; @import './components-automation-editor/step-card';
@import './components-automation-editor/workflow';
@import './components-automation-editor/notices'; @import './components-automation-editor/notices';
@import './components-automation-editor/deactivate-modal'; @import './components-automation-editor/deactivate-modal';

View File

@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { api } from '../config'; import { api } from '../config';
const API_URL = `${api.root}/mailpoet/v1/automation`; const API_URL = `${api.root}/mailpoet/v1`;
export const request = ( export const request = (
path: string, path: string,

View File

@ -3,7 +3,7 @@ import { api } from '../config';
export * from './hooks'; export * from './hooks';
const apiUrl = `${api.root}/mailpoet/v1/automation/`; const apiUrl = `${api.root}/mailpoet/v1/`;
export type ApiError = { export type ApiError = {
code?: string; code?: string;

View File

@ -10,11 +10,11 @@ import { createStore, storeName } from './listing/store';
import { AutomationListing, AutomationListingHeader } from './listing'; import { AutomationListing, AutomationListingHeader } from './listing';
import { registerApiErrorHandler } from './listing/api-error-handler'; import { registerApiErrorHandler } from './listing/api-error-handler';
import { Notices } from './listing/components/notices'; import { Notices } from './listing/components/notices';
import { WorkflowListingNotices } from './listing/workflow-listing-notices'; import { AutomationListingNotices } from './listing/automation-listing-notices';
import { BuildYourOwnSection, HeroSection, TemplatesSection } from './sections'; import { BuildYourOwnSection, HeroSection, TemplatesSection } from './sections';
import { import {
CreateEmptyWorkflowButton, CreateEmptyAutomationButton,
CreateWorkflowFromTemplateButton, CreateAutomationFromTemplateButton,
} from './testing'; } from './testing';
import { MailPoet } from '../mailpoet'; import { MailPoet } from '../mailpoet';
@ -24,7 +24,7 @@ const trackOpenEvent = () => {
function Content(): JSX.Element { function Content(): JSX.Element {
const [isBooting, setIsBooting] = useState(true); const [isBooting, setIsBooting] = useState(true);
const count = useSelect((select) => select(storeName).getWorkflowCount()); const count = useSelect((select) => select(storeName).getAutomationCount());
useEffect(() => { useEffect(() => {
if (!isBooting || count === 0) { if (!isBooting || count === 0) {
@ -63,7 +63,7 @@ function Content(): JSX.Element {
); );
} }
function Workflows(): JSX.Element { function Automations(): JSX.Element {
return ( return (
<> <>
<TopBarWithBeamer /> <TopBarWithBeamer />
@ -80,7 +80,7 @@ function RecreateSchemaButton(): JSX.Element {
return ( return (
<div> <div>
<WorkflowListingNotices /> <AutomationListingNotices />
<button <button
className="button button-link-delete" className="button button-link-delete"
type="button" type="button"
@ -127,20 +127,20 @@ function App(): JSX.Element {
<SlotFillProvider> <SlotFillProvider>
<BrowserRouter> <BrowserRouter>
<div> <div>
<Workflows /> <Automations />
<div style={{ marginTop: 30, display: 'grid', gridGap: 8 }}> <div style={{ marginTop: 30, display: 'grid', gridGap: 8 }}>
<CreateEmptyWorkflowButton /> <CreateEmptyAutomationButton />
<CreateWorkflowFromTemplateButton slug="simple-welcome-email"> <CreateAutomationFromTemplateButton slug="simple-welcome-email">
Create testing workflow from template (welcome email) Create testing automation from template (welcome email)
</CreateWorkflowFromTemplateButton> </CreateAutomationFromTemplateButton>
<CreateWorkflowFromTemplateButton slug="welcome-email-sequence"> <CreateAutomationFromTemplateButton slug="welcome-email-sequence">
Create testing workflow from template (welcome sequence, only Create testing automation from template (welcome sequence, only
premium) premium)
</CreateWorkflowFromTemplateButton> </CreateAutomationFromTemplateButton>
<CreateWorkflowFromTemplateButton slug="advanced-welcome-email-sequence"> <CreateAutomationFromTemplateButton slug="advanced-welcome-email-sequence">
Create testing workflow from template (advanced welcome sequence, Create testing automation from template (advanced welcome
only premium) sequence, only premium)
</CreateWorkflowFromTemplateButton> </CreateAutomationFromTemplateButton>
<RecreateSchemaButton /> <RecreateSchemaButton />
<DeleteSchemaButton /> <DeleteSchemaButton />
</div> </div>

View File

@ -4,9 +4,9 @@ declare global {
root: string; root: string;
nonce: string; nonce: string;
}; };
mailpoet_workflow_count: number; mailpoet_automation_count: number;
} }
} }
export const api = window.mailpoet_automation_api; export const api = window.mailpoet_automation_api;
export const workflowCount = window.mailpoet_workflow_count; export const automationCount = window.mailpoet_automation_count;

View File

@ -19,7 +19,7 @@ export const registerApiErrorHandler = (): void =>
const status = errorObject.data?.status; const status = errorObject.data?.status;
const code = errorObject.code; const code = errorObject.code;
if (code === 'mailpoet_automation_workflow_not_valid') { if (code === 'mailpoet_automation_automation_not_valid') {
dispatch(storeName).setErrors({ steps: errorObject.data.errors }); dispatch(storeName).setErrors({ steps: errorObject.data.errors });
return undefined; return undefined;
} }

View File

@ -9,9 +9,9 @@ import { storeName } from '../../store';
export function TrashButton(): JSX.Element { export function TrashButton(): JSX.Element {
const [showConfirmDialog, setShowConfirmDialog] = useState(false); const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const { workflow } = useSelect( const { automation } = useSelect(
(select) => ({ (select) => ({
workflow: select(storeName).getWorkflowData(), automation: select(storeName).getAutomationData(),
}), }),
[], [],
); );
@ -33,7 +33,7 @@ export function TrashButton(): JSX.Element {
> >
{sprintf( {sprintf(
__('You are about to delete the automation "%s".', 'mailpoet'), __('You are about to delete the automation "%s".', 'mailpoet'),
workflow.name, automation.name,
)} )}
<br /> <br />
{__(' This will stop it for all subscribers immediately.', 'mailpoet')} {__(' This will stop it for all subscribers immediately.', 'mailpoet')}

View File

@ -1,7 +1,7 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { __unstableCompositeItem as CompositeItem } from '@wordpress/components'; import { __unstableCompositeItem as CompositeItem } from '@wordpress/components';
import { Icon, plus } from '@wordpress/icons'; import { Icon, plus } from '@wordpress/icons';
import { WorkflowCompositeContext } from './context'; import { AutomationCompositeContext } from './context';
type Props = { type Props = {
onClick?: (element: HTMLButtonElement) => void; onClick?: (element: HTMLButtonElement) => void;
@ -9,7 +9,7 @@ type Props = {
}; };
export function AddStepButton({ onClick, previousStepId }: Props): JSX.Element { export function AddStepButton({ onClick, previousStepId }: Props): JSX.Element {
const compositeState = useContext(WorkflowCompositeContext); const compositeState = useContext(AutomationCompositeContext);
return ( return (
<CompositeItem <CompositeItem
state={compositeState} state={compositeState}

View File

@ -3,7 +3,7 @@ import { __unstableCompositeItem as CompositeItem } from '@wordpress/components'
import { Icon, plus } from '@wordpress/icons'; import { Icon, plus } from '@wordpress/icons';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { useDispatch } from '@wordpress/data'; import { useDispatch } from '@wordpress/data';
import { WorkflowCompositeContext } from './context'; import { AutomationCompositeContext } from './context';
import { Step } from './types'; import { Step } from './types';
import { storeName } from '../../store'; import { storeName } from '../../store';
@ -12,14 +12,14 @@ type Props = {
}; };
export function AddTrigger({ step }: Props): JSX.Element { export function AddTrigger({ step }: Props): JSX.Element {
const compositeState = useContext(WorkflowCompositeContext); const compositeState = useContext(AutomationCompositeContext);
const { setInserterPopover } = useDispatch(storeName); const { setInserterPopover } = useDispatch(storeName);
return ( return (
<CompositeItem <CompositeItem
state={compositeState} state={compositeState}
role="treeitem" role="treeitem"
className="mailpoet-automation-workflow-add-trigger" className="mailpoet-automation-automation-add-trigger"
data-previous-step-id={step.id} data-previous-step-id={step.id}
focusable focusable
onClick={(event) => { onClick={(event) => {

View File

@ -1,5 +1,5 @@
import { __unstableUseCompositeState as useCompositeState } from '@wordpress/components'; import { __unstableUseCompositeState as useCompositeState } from '@wordpress/components';
import { createContext } from '@wordpress/element'; import { createContext } from '@wordpress/element';
export const WorkflowCompositeContext = export const AutomationCompositeContext =
createContext<ReturnType<typeof useCompositeState>>(undefined); createContext<ReturnType<typeof useCompositeState>>(undefined);

View File

@ -0,0 +1,9 @@
import { __ } from '@wordpress/i18n';
export function EmptyAutomation(): JSX.Element {
return (
<div className="mailpoet-automation-editor-empty-automation">
{__('No automation data.', 'mailpoet')}
</div>
);
}

View File

@ -7,8 +7,8 @@ import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { Icon, check } from '@wordpress/icons'; import { Icon, check } from '@wordpress/icons';
import { Hooks } from 'wp-js-hooks'; import { Hooks } from 'wp-js-hooks';
import { WorkflowCompositeContext } from './context'; import { AutomationCompositeContext } from './context';
import { EmptyWorkflow } from './empty-workflow'; import { EmptyAutomation } from './empty-automation';
import { Separator } from './separator'; import { Separator } from './separator';
import { Step } from './step'; import { Step } from './step';
import { Step as StepData } from './types'; import { Step as StepData } from './types';
@ -21,10 +21,10 @@ import {
RenderStepType, RenderStepType,
} from '../../../types/filters'; } from '../../../types/filters';
export function Workflow(): JSX.Element { export function Automation(): JSX.Element {
const { workflowData, selectedStep } = useSelect( const { automationData, selectedStep } = useSelect(
(select) => ({ (select) => ({
workflowData: select(storeName).getWorkflowData(), automationData: select(storeName).getAutomationData(),
selectedStep: select(storeName).getSelectedStep(), selectedStep: select(storeName).getSelectedStep(),
}), }),
[], [],
@ -36,9 +36,9 @@ export function Workflow(): JSX.Element {
shift: true, shift: true,
}); });
const stepMap = workflowData?.steps ?? undefined; const stepMap = automationData?.steps ?? undefined;
// serialize steps (for now, we support only one trigger and linear workflows) // serialize steps (for now, we support only one trigger and linear automations)
const steps = useMemo(() => { const steps = useMemo(() => {
const stepArray = [stepMap.root]; const stepArray = [stepMap.root];
@ -56,7 +56,7 @@ export function Workflow(): JSX.Element {
const renderStep = useMemo( const renderStep = useMemo(
(): RenderStepType => (): RenderStepType =>
Hooks.applyFilters( Hooks.applyFilters(
'mailpoet.automation.workflow.render_step', 'mailpoet.automation.automation.render_step',
(stepData: StepData) => (stepData: StepData) =>
stepData.type === 'root' ? ( stepData.type === 'root' ? (
<AddTrigger step={stepData} /> <AddTrigger step={stepData} />
@ -73,7 +73,7 @@ export function Workflow(): JSX.Element {
const renderSeparator = useMemo( const renderSeparator = useMemo(
(): RenderStepSeparatorType => (): RenderStepSeparatorType =>
Hooks.applyFilters( Hooks.applyFilters(
'mailpoet.automation.workflow.render_step_separator', 'mailpoet.automation.automation.render_step_separator',
(previousStepData: StepData) => ( (previousStepData: StepData) => (
<Separator previousStepId={previousStepData.id} /> <Separator previousStepId={previousStepData.id} />
), ),
@ -81,20 +81,20 @@ export function Workflow(): JSX.Element {
[], [],
); );
if (!workflowData) { if (!automationData) {
return <EmptyWorkflow />; return <EmptyAutomation />;
} }
return ( return (
<WorkflowCompositeContext.Provider value={compositeState}> <AutomationCompositeContext.Provider value={compositeState}>
<Composite <Composite
state={compositeState} state={compositeState}
role="tree" role="tree"
aria-label={__('Automation', 'mailpoet')} aria-label={__('Automation', 'mailpoet')}
aria-orientation="vertical" aria-orientation="vertical"
className="mailpoet-automation-editor-workflow" className="mailpoet-automation-editor-automation"
> >
<div className="mailpoet-automation-editor-workflow-wrapper"> <div className="mailpoet-automation-editor-automation-wrapper">
<Statistics /> <Statistics />
{stepMap.root.next_steps.length === 0 ? ( {stepMap.root.next_steps.length === 0 ? (
<> <>
@ -119,13 +119,13 @@ export function Workflow(): JSX.Element {
</Fragment> </Fragment>
))} ))}
<Icon <Icon
className="mailpoet-automation-editor-workflow-end" className="mailpoet-automation-editor-automation-end"
icon={check} icon={check}
/> />
<div /> <div />
</div> </div>
<InserterPopover /> <InserterPopover />
</Composite> </Composite>
</WorkflowCompositeContext.Provider> </AutomationCompositeContext.Provider>
); );
} }

View File

@ -4,9 +4,9 @@ import { storeName } from '../../store';
import { Statistics as BaseStatistics } from '../../../components/statistics'; import { Statistics as BaseStatistics } from '../../../components/statistics';
export function Statistics(): JSX.Element { export function Statistics(): JSX.Element {
const { workflow } = useSelect( const { automation } = useSelect(
(select) => ({ (select) => ({
workflow: select(storeName).getWorkflowData(), automation: select(storeName).getAutomationData(),
}), }),
[], [],
); );
@ -19,19 +19,19 @@ export function Statistics(): JSX.Element {
key: 'entered', key: 'entered',
// translators: Total number of subscribers who entered an automation // translators: Total number of subscribers who entered an automation
label: _x('Total Entered', 'automation stats', 'mailpoet'), label: _x('Total Entered', 'automation stats', 'mailpoet'),
value: workflow.stats.totals.entered, value: automation.stats.totals.entered,
}, },
{ {
key: 'processing', key: 'processing',
// translators: Total number of subscribers who are being processed in an automation // translators: Total number of subscribers who are being processed in an automation
label: _x('Total Processing', 'automation stats', 'mailpoet'), label: _x('Total Processing', 'automation stats', 'mailpoet'),
value: workflow.stats.totals.in_progress, value: automation.stats.totals.in_progress,
}, },
{ {
key: 'exited', key: 'exited',
// translators: Total number of subscribers who exited an automation, no matter the result // translators: Total number of subscribers who exited an automation, no matter the result
label: _x('Total Exited', 'automation stats', 'mailpoet'), label: _x('Total Exited', 'automation stats', 'mailpoet'),
value: workflow.stats.totals.exited, value: automation.stats.totals.exited,
}, },
]} ]}
/> />

View File

@ -23,7 +23,7 @@ export function StepMoreMenu({ step }: Props): JSX.Element {
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const moreControls: StepMoreControlsType = Hooks.applyFilters( const moreControls: StepMoreControlsType = Hooks.applyFilters(
'mailpoet.automation.workflow.step.more-controls', 'mailpoet.automation.automation.step.more-controls',
{ {
delete: { delete: {
key: 'delete', key: 'delete',

View File

@ -4,7 +4,7 @@ import { __unstableCompositeItem as CompositeItem } from '@wordpress/components'
import { useDispatch, useRegistry, useSelect } from '@wordpress/data'; import { useDispatch, useRegistry, useSelect } from '@wordpress/data';
import { blockMeta } from '@wordpress/icons'; import { blockMeta } from '@wordpress/icons';
import { __, _x } from '@wordpress/i18n'; import { __, _x } from '@wordpress/i18n';
import { WorkflowCompositeContext } from './context'; import { AutomationCompositeContext } from './context';
import { StepMoreMenu } from './step-more-menu'; import { StepMoreMenu } from './step-more-menu';
import { Step as StepData } from './types'; import { Step as StepData } from './types';
import { Chip } from '../chip'; import { Chip } from '../chip';
@ -48,7 +48,7 @@ export function Step({ step, isSelected }: Props): JSX.Element {
[step], [step],
); );
const { openSidebar, selectStep } = useDispatch(storeName); const { openSidebar, selectStep } = useDispatch(storeName);
const compositeState = useContext(WorkflowCompositeContext); const compositeState = useContext(AutomationCompositeContext);
const { batch } = useRegistry(); const { batch } = useRegistry();
const compositeItemId = `step-${step.id}`; const compositeItemId = `step-${step.id}`;

View File

@ -1,4 +1,4 @@
import { WorkflowStatus } from '../../../listing/workflow'; import { AutomationStatus } from '../../../listing/automation';
export type NextStep = { export type NextStep = {
id: string; id: string;
@ -12,10 +12,10 @@ export type Step = {
next_steps: NextStep[]; next_steps: NextStep[];
}; };
export type Workflow = { export type Automation = {
id: number; id: number;
name: string; name: string;
status: WorkflowStatus; status: AutomationStatus;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
activated_at: string; activated_at: string;

View File

@ -10,7 +10,7 @@ import { useRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { chevronDown } from '@wordpress/icons'; import { chevronDown } from '@wordpress/icons';
import { storeName } from '../../store'; import { storeName } from '../../store';
import { WorkflowStatus } from '../../../listing/workflow'; import { AutomationStatus } from '../../../listing/automation';
// See: https://github.com/WordPress/gutenberg/blob/eff0cab2b3181c004dbd15398e570ecec28a3726/packages/edit-site/src/components/header/document-actions/index.js // See: https://github.com/WordPress/gutenberg/blob/eff0cab2b3181c004dbd15398e570ecec28a3726/packages/edit-site/src/components/header/document-actions/index.js
@ -22,10 +22,10 @@ const Dropdown: ComponentType<
> = WpDropdown; > = WpDropdown;
export function DocumentActions({ children }): JSX.Element { export function DocumentActions({ children }): JSX.Element {
const { workflowName, workflowStatus, showIconLabels } = useSelect( const { automationName, automationStatus, showIconLabels } = useSelect(
(select) => ({ (select) => ({
workflowName: select(storeName).getWorkflowData().name, automationName: select(storeName).getAutomationData().name,
workflowStatus: select(storeName).getWorkflowData().status, automationStatus: select(storeName).getAutomationData().status,
showIconLabels: select(storeName).isFeatureActive('showIconLabels'), showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
}), }),
[], [],
@ -36,9 +36,9 @@ export function DocumentActions({ children }): JSX.Element {
const titleRef = useRef(); const titleRef = useRef();
let chipClass = 'mailpoet-automation-editor-chip-gray'; let chipClass = 'mailpoet-automation-editor-chip-gray';
if (workflowStatus === WorkflowStatus.ACTIVE) { if (automationStatus === AutomationStatus.ACTIVE) {
chipClass = 'mailpoet-automation-editor-chip-success'; chipClass = 'mailpoet-automation-editor-chip-success';
} else if (workflowStatus === WorkflowStatus.DEACTIVATING) { } else if (automationStatus === AutomationStatus.DEACTIVATING) {
chipClass = 'mailpoet-automation-editor-chip-danger'; chipClass = 'mailpoet-automation-editor-chip-danger';
} }
@ -66,18 +66,18 @@ export function DocumentActions({ children }): JSX.Element {
<VisuallyHidden as="span"> <VisuallyHidden as="span">
{__('Editing automation:', 'mailpoet')} {__('Editing automation:', 'mailpoet')}
</VisuallyHidden> </VisuallyHidden>
{workflowName} {automationName}
</Text> </Text>
<Text <Text
size="body" size="body"
className={`edit-site-document-actions__secondary-item ${chipClass}`} className={`edit-site-document-actions__secondary-item ${chipClass}`}
> >
{workflowStatus === WorkflowStatus.ACTIVE && {automationStatus === AutomationStatus.ACTIVE &&
__('Active', 'mailpoet')} __('Active', 'mailpoet')}
{workflowStatus === WorkflowStatus.DEACTIVATING && {automationStatus === AutomationStatus.DEACTIVATING &&
__('Deactivating', 'mailpoet')} __('Deactivating', 'mailpoet')}
{workflowStatus === WorkflowStatus.DRAFT && {automationStatus === AutomationStatus.DRAFT &&
__('Draft', 'mailpoet')} __('Draft', 'mailpoet')}
</Text> </Text>
</a> </a>

View File

@ -35,17 +35,17 @@ type StepErrorProps = {
function StepError({ stepId }: StepErrorProps): JSX.Element { function StepError({ stepId }: StepErrorProps): JSX.Element {
const compositeState = useContext(ErrorsCompositeContext); const compositeState = useContext(ErrorsCompositeContext);
const { steps, workflowData } = useSelect( const { steps, automationData } = useSelect(
(select) => ({ (select) => ({
steps: select(storeName).getSteps(), steps: select(storeName).getSteps(),
workflowData: select(storeName).getWorkflowData(), automationData: select(storeName).getAutomationData(),
}), }),
[], [],
); );
const { openSidebar, selectStep } = useDispatch(storeName); const { openSidebar, selectStep } = useDispatch(storeName);
const stepData = workflowData.steps[stepId]; const stepData = automationData.steps[stepId];
const step = steps.find(({ key }) => key === stepData.key); const step = steps.find(({ key }) => key === stepData.key);
return ( return (
@ -78,10 +78,10 @@ export function Errors(): JSX.Element | null {
shift: true, shift: true,
}); });
const { errors, workflowData } = useSelect( const { errors, automationData } = useSelect(
(select) => ({ (select) => ({
errors: select(storeName).getErrors(), errors: select(storeName).getErrors(),
workflowData: select(storeName).getWorkflowData(), automationData: select(storeName).getAutomationData(),
}), }),
[], [],
); );
@ -93,18 +93,18 @@ export function Errors(): JSX.Element | null {
} }
const visited = new Map<string, StepErrorType | undefined>(); const visited = new Map<string, StepErrorType | undefined>();
const ids = workflowData.steps.root.next_steps.map(({ id }) => id); const ids = automationData.steps.root.next_steps.map(({ id }) => id);
while (ids.length > 0) { while (ids.length > 0) {
const id = ids.shift(); const id = ids.shift();
if (!visited.has(id)) { if (!visited.has(id)) {
visited.set(id, errors.steps[id]); visited.set(id, errors.steps[id]);
workflowData.steps[id]?.next_steps?.forEach((step) => automationData.steps[id]?.next_steps?.forEach((step) =>
ids.push(step.id), ids.push(step.id),
); );
} }
} }
return [...visited.values()].filter((error) => !!error); return [...visited.values()].filter((error) => !!error);
}, [errors, workflowData]); }, [errors, automationData]);
// automatically open the popover when errors appear // automatically open the popover when errors appear
const hasErrors = stepErrors.length > 0; const hasErrors = stepErrors.length > 0;

View File

@ -13,7 +13,7 @@ import { Errors } from './errors';
import { InserterToggle } from './inserter_toggle'; import { InserterToggle } from './inserter_toggle';
import { MoreMenu } from './more_menu'; import { MoreMenu } from './more_menu';
import { storeName } from '../../store'; import { storeName } from '../../store';
import { WorkflowStatus } from '../../../listing/workflow'; import { AutomationStatus } from '../../../listing/automation';
import { import {
DeactivateImmediatelyModal, DeactivateImmediatelyModal,
DeactivateModal, DeactivateModal,
@ -28,8 +28,8 @@ function ActivateButton({ label }): JSX.Element {
(select) => ({ (select) => ({
errors: select(storeName).getErrors(), errors: select(storeName).getErrors(),
isDeactivating: isDeactivating:
select(storeName).getWorkflowData().status === select(storeName).getAutomationData().status ===
WorkflowStatus.DEACTIVATING, AutomationStatus.DEACTIVATING,
}), }),
[], [],
); );
@ -54,7 +54,7 @@ function ActivateButton({ label }): JSX.Element {
// The following error seems to be a mismatch. It claims the 'delay' prop does not exist, but it does. // The following error seems to be a mismatch. It claims the 'delay' prop does not exist, but it does.
delay={0} delay={0}
text={__( text={__(
'Editing an active workflow is temporarily unavailable. We are working on introducing this functionality.', 'Editing an active automation is temporarily unavailable. We are working on introducing this functionality.',
'mailpoet', 'mailpoet',
)} )}
> >
@ -69,14 +69,14 @@ function ActivateButton({ label }): JSX.Element {
function UpdateButton(): JSX.Element { function UpdateButton(): JSX.Element {
const { save } = useDispatch(storeName); const { save } = useDispatch(storeName);
const { workflow } = useSelect( const { automation } = useSelect(
(select) => ({ (select) => ({
workflow: select(storeName).getWorkflowData(), automation: select(storeName).getAutomationData(),
}), }),
[], [],
); );
if (workflow.stats.totals.in_progress === 0) { if (automation.stats.totals.in_progress === 0) {
return ( return (
<Button <Button
variant="primary" variant="primary"
@ -94,7 +94,7 @@ function UpdateButton(): JSX.Element {
// The following error seems to be a mismatch. It claims the 'delay' prop does not exist, but it does. // The following error seems to be a mismatch. It claims the 'delay' prop does not exist, but it does.
delay={0} delay={0}
text={__( text={__(
'Editing an active workflow is temporarily unavailable. We are working on introducing this functionality.', 'Editing an active automation is temporarily unavailable. We are working on introducing this functionality.',
'mailpoet', 'mailpoet',
)} )}
> >
@ -126,7 +126,7 @@ function DeactivateButton(): JSX.Element {
const { hasUsersInProgress } = useSelect( const { hasUsersInProgress } = useSelect(
(select) => ({ (select) => ({
hasUsersInProgress: hasUsersInProgress:
select(storeName).getWorkflowData().stats.totals.in_progress > 0, select(storeName).getAutomationData().stats.totals.in_progress > 0,
}), }),
[], [],
); );
@ -166,7 +166,7 @@ function DeactivateNowButton(): JSX.Element {
const { hasUsersInProgress } = useSelect( const { hasUsersInProgress } = useSelect(
(select) => ({ (select) => ({
hasUsersInProgress: hasUsersInProgress:
select(storeName).getWorkflowData().stats.totals.in_progress > 0, select(storeName).getAutomationData().stats.totals.in_progress > 0,
}), }),
[], [],
); );
@ -205,11 +205,11 @@ type Props = {
}; };
export function Header({ showInserterToggle }: Props): JSX.Element { export function Header({ showInserterToggle }: Props): JSX.Element {
const { setWorkflowName } = useDispatch(storeName); const { setAutomationName } = useDispatch(storeName);
const { workflowName, workflowStatus } = useSelect( const { automationName, automationStatus } = useSelect(
(select) => ({ (select) => ({
workflowName: select(storeName).getWorkflowData().name, automationName: select(storeName).getAutomationData().name,
workflowStatus: select(storeName).getWorkflowData().status, automationStatus: select(storeName).getAutomationData().status,
}), }),
[], [],
); );
@ -234,8 +234,8 @@ export function Header({ showInserterToggle }: Props): JSX.Element {
{__('Automation name', 'mailpoet')} {__('Automation name', 'mailpoet')}
</div> </div>
<TextControl <TextControl
value={workflowName} value={automationName}
onChange={(newName) => setWorkflowName(newName)} onChange={(newName) => setAutomationName(newName)}
help={__( help={__(
`Give the automation a name that indicates its purpose. E.g. "Abandoned cart recovery"`, `Give the automation a name that indicates its purpose. E.g. "Abandoned cart recovery"`,
'mailpoet', 'mailpoet',
@ -249,19 +249,19 @@ export function Header({ showInserterToggle }: Props): JSX.Element {
<div className="edit-site-header_end"> <div className="edit-site-header_end">
<div className="edit-site-header__actions"> <div className="edit-site-header__actions">
<Errors /> <Errors />
{workflowStatus === WorkflowStatus.DRAFT && ( {automationStatus === AutomationStatus.DRAFT && (
<> <>
<SaveDraftButton /> <SaveDraftButton />
<ActivateButton label={__('Activate', 'mailpoet')} /> <ActivateButton label={__('Activate', 'mailpoet')} />
</> </>
)} )}
{workflowStatus === WorkflowStatus.ACTIVE && ( {automationStatus === AutomationStatus.ACTIVE && (
<> <>
<DeactivateButton /> <DeactivateButton />
<UpdateButton /> <UpdateButton />
</> </>
)} )}
{workflowStatus === WorkflowStatus.DEACTIVATING && ( {automationStatus === AutomationStatus.DEACTIVATING && (
<> <>
<DeactivateNowButton /> <DeactivateNowButton />
<ActivateButton label={__('Update & Activate', 'mailpoet')} /> <ActivateButton label={__('Update & Activate', 'mailpoet')} />

View File

@ -22,7 +22,7 @@ export function InserterPopover(): JSX.Element | null {
const onInsert = useCallback((item: Item) => { const onInsert = useCallback((item: Item) => {
const addStepCallback: AddStepCallbackType = Hooks.applyFilters( const addStepCallback: AddStepCallbackType = Hooks.applyFilters(
'mailpoet.automation.workflow.add_step_callback', 'mailpoet.automation.automation.add_step_callback',
() => { () => {
setShowModal(true); setShowModal(true);
}, },

View File

@ -5,7 +5,7 @@ import {
store as keyboardShortcutsStore, store as keyboardShortcutsStore,
} from '@wordpress/keyboard-shortcuts'; } from '@wordpress/keyboard-shortcuts';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store'; import { stepSidebarKey, storeName, automationSidebarKey } from '../../store';
// See: // See:
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/keyboard-shortcuts/index.js // https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/keyboard-shortcuts/index.js
@ -55,7 +55,7 @@ export function KeyboardShortcuts(): null {
} else { } else {
const sidebarToOpen = selectedStep() const sidebarToOpen = selectedStep()
? stepSidebarKey ? stepSidebarKey
: workflowSidebarKey; : automationSidebarKey;
openSidebar(sidebarToOpen); openSidebar(sidebarToOpen);
} }
}); });

View File

@ -3,7 +3,7 @@ import { Button, Modal } from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n'; import { __, sprintf } from '@wordpress/i18n';
import { dispatch, useSelect } from '@wordpress/data'; import { dispatch, useSelect } from '@wordpress/data';
import { storeName } from '../../store'; import { storeName } from '../../store';
import { WorkflowStatus } from '../../../listing/workflow'; import { AutomationStatus } from '../../../listing/automation';
type DeactivateImmediatelyModalProps = { type DeactivateImmediatelyModalProps = {
onClose: () => void; onClose: () => void;
@ -49,20 +49,20 @@ type DeactivateModalProps = {
export function DeactivateModal({ export function DeactivateModal({
onClose, onClose,
}: DeactivateModalProps): JSX.Element { }: DeactivateModalProps): JSX.Element {
const { workflowName } = useSelect( const { automationName } = useSelect(
(select) => ({ (select) => ({
workflowName: select(storeName).getWorkflowData().name, automationName: select(storeName).getAutomationData().name,
}), }),
[], [],
); );
const [selected, setSelected] = useState< const [selected, setSelected] = useState<
WorkflowStatus.DRAFT | WorkflowStatus.DEACTIVATING AutomationStatus.DRAFT | AutomationStatus.DEACTIVATING
>(WorkflowStatus.DEACTIVATING); >(AutomationStatus.DEACTIVATING);
const [isBusy, setIsBusy] = useState<boolean>(false); const [isBusy, setIsBusy] = useState<boolean>(false);
// translators: %s is the name of the automation. // translators: %s is the name of the automation.
const title = sprintf( const title = sprintf(
__('Deactivate the "%s" automation?', 'mailpoet'), __('Deactivate the "%s" automation?', 'mailpoet'),
workflowName, automationName,
); );
return ( return (
@ -79,7 +79,7 @@ export function DeactivateModal({
<li> <li>
<label <label
className={ className={
selected === WorkflowStatus.DEACTIVATING selected === AutomationStatus.DEACTIVATING
? 'mailpoet-automation-option active' ? 'mailpoet-automation-option active'
: 'mailpoet-automation-option' : 'mailpoet-automation-option'
} }
@ -89,8 +89,8 @@ export function DeactivateModal({
type="radio" type="radio"
disabled={isBusy} disabled={isBusy}
name="deactivation-method" name="deactivation-method"
checked={selected === WorkflowStatus.DEACTIVATING} checked={selected === AutomationStatus.DEACTIVATING}
onChange={() => setSelected(WorkflowStatus.DEACTIVATING)} onChange={() => setSelected(AutomationStatus.DEACTIVATING)}
/> />
</span> </span>
<span> <span>
@ -107,7 +107,7 @@ export function DeactivateModal({
<li> <li>
<label <label
className={ className={
selected === WorkflowStatus.DRAFT selected === AutomationStatus.DRAFT
? 'mailpoet-automation-option active' ? 'mailpoet-automation-option active'
: 'mailpoet-automation-option' : 'mailpoet-automation-option'
} }
@ -117,8 +117,8 @@ export function DeactivateModal({
type="radio" type="radio"
disabled={isBusy} disabled={isBusy}
name="deactivation-method" name="deactivation-method"
checked={selected === WorkflowStatus.DRAFT} checked={selected === AutomationStatus.DRAFT}
onChange={() => setSelected(WorkflowStatus.DRAFT)} onChange={() => setSelected(AutomationStatus.DRAFT)}
/> />
</span> </span>
<span> <span>
@ -140,7 +140,7 @@ export function DeactivateModal({
onClick={() => { onClick={() => {
setIsBusy(true); setIsBusy(true);
dispatch(storeName).deactivate( dispatch(storeName).deactivate(
selected !== WorkflowStatus.DEACTIVATING, selected !== AutomationStatus.DEACTIVATING,
); );
}} }}
> >

View File

@ -4,7 +4,7 @@ import { Button, Spinner } from '@wordpress/components';
import { closeSmall } from '@wordpress/icons'; import { closeSmall } from '@wordpress/icons';
import { __, sprintf } from '@wordpress/i18n'; import { __, sprintf } from '@wordpress/i18n';
import { storeName } from '../../store'; import { storeName } from '../../store';
import { WorkflowStatus } from '../../../listing/workflow'; import { AutomationStatus } from '../../../listing/automation';
import { MailPoet } from '../../../../mailpoet'; import { MailPoet } from '../../../../mailpoet';
function PreStep({ onClose }): JSX.Element { function PreStep({ onClose }): JSX.Element {
@ -58,9 +58,9 @@ function PreStep({ onClose }): JSX.Element {
} }
function PostStep({ onClose }): JSX.Element { function PostStep({ onClose }): JSX.Element {
const { workflow } = useSelect( const { automation } = useSelect(
(select) => ({ (select) => ({
workflow: select(storeName).getWorkflowData(), automation: select(storeName).getAutomationData(),
}), }),
[], [],
); );
@ -81,7 +81,7 @@ function PostStep({ onClose }): JSX.Element {
<div className="mailpoet-automation-activate-panel__body"> <div className="mailpoet-automation-activate-panel__body">
<div className="mailpoet-automation-activate-panel__section"> <div className="mailpoet-automation-activate-panel__section">
{sprintf(__('"%s" is now live.', 'mailpoet'), workflow.name)} {sprintf(__('"%s" is now live.', 'mailpoet'), automation.name)}
</div> </div>
<p> <p>
<strong>{__('Whats next?', 'mailpoet')}</strong> <strong>{__('Whats next?', 'mailpoet')}</strong>
@ -101,10 +101,10 @@ function PostStep({ onClose }): JSX.Element {
} }
export function ActivatePanel(): JSX.Element { export function ActivatePanel(): JSX.Element {
const { workflow, errors } = useSelect( const { automation, errors } = useSelect(
(select) => ({ (select) => ({
errors: select(storeName).getErrors(), errors: select(storeName).getErrors(),
workflow: select(storeName).getWorkflowData(), automation: select(storeName).getAutomationData(),
}), }),
[], [],
); );
@ -120,7 +120,7 @@ export function ActivatePanel(): JSX.Element {
if (errors) { if (errors) {
return null; return null;
} }
const isActive = workflow.status === WorkflowStatus.ACTIVE; const isActive = automation.status === AutomationStatus.ACTIVE;
return ( return (
<div className="mailpoet-automation-activate-panel"> <div className="mailpoet-automation-activate-panel">
{isActive && <PostStep onClose={closeActivationPanel} />} {isActive && <PostStep onClose={closeActivationPanel} />}

View File

@ -4,10 +4,10 @@ import { __ } from '@wordpress/i18n';
import { storeName } from '../../../store'; import { storeName } from '../../../store';
import { TrashButton } from '../../actions/trash-button'; import { TrashButton } from '../../actions/trash-button';
export function WorkflowSidebar(): JSX.Element { export function AutomationSidebar(): JSX.Element {
const { workflowData } = useSelect( const { automationData } = useSelect(
(select) => ({ (select) => ({
workflowData: select(storeName).getWorkflowData(), automationData: select(storeName).getAutomationData(),
}), }),
[], [],
); );
@ -22,30 +22,30 @@ export function WorkflowSidebar(): JSX.Element {
<PanelBody title={__('Automation details', 'mailpoet')} initialOpen> <PanelBody title={__('Automation details', 'mailpoet')} initialOpen>
<PanelRow> <PanelRow>
<strong>Date added</strong>{' '} <strong>Date added</strong>{' '}
{new Date(Date.parse(workflowData.created_at)).toLocaleDateString( {new Date(Date.parse(automationData.created_at)).toLocaleDateString(
undefined, undefined,
dateOptions, dateOptions,
)} )}
</PanelRow> </PanelRow>
<PanelRow> <PanelRow>
<strong>Activated</strong>{' '} <strong>Activated</strong>{' '}
{workflowData.status === 'active' && {automationData.status === 'active' &&
new Date(Date.parse(workflowData.updated_at)).toLocaleDateString( new Date(Date.parse(automationData.updated_at)).toLocaleDateString(
undefined, undefined,
dateOptions, dateOptions,
)} )}
{workflowData.status !== 'active' && {automationData.status !== 'active' &&
workflowData.activated_at && automationData.activated_at &&
new Date(Date.parse(workflowData.activated_at)).toLocaleDateString( new Date(Date.parse(automationData.activated_at)).toLocaleDateString(
undefined, undefined,
dateOptions, dateOptions,
)} )}
{workflowData.status !== 'active' && !workflowData.activated_at && ( {automationData.status !== 'active' && !automationData.activated_at && (
<span className="mailpoet-deactive">Not activated yet.</span> <span className="mailpoet-deactive">Not activated yet.</span>
)} )}
</PanelRow> </PanelRow>
<PanelRow> <PanelRow>
<strong>Author</strong> {workflowData.author.name} <strong>Author</strong> {automationData.author.name}
</PanelRow> </PanelRow>
<PanelRow> <PanelRow>
<TrashButton /> <TrashButton />

View File

@ -1,7 +1,7 @@
import { Button } from '@wordpress/components'; import { Button } from '@wordpress/components';
import { useDispatch } from '@wordpress/data'; import { useDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store'; import { stepSidebarKey, storeName, automationSidebarKey } from '../../store';
// See: // See:
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/sidebar/settings-header/index.js // https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/sidebar/settings-header/index.js
@ -13,11 +13,11 @@ type Props = {
export function Header({ sidebarKey }: Props): JSX.Element { export function Header({ sidebarKey }: Props): JSX.Element {
const { openSidebar } = useDispatch(storeName); const { openSidebar } = useDispatch(storeName);
const openWorkflowSettings = () => openSidebar(workflowSidebarKey); const openAutomationSettings = () => openSidebar(automationSidebarKey);
const openStepSettings = () => openSidebar(stepSidebarKey); const openStepSettings = () => openSidebar(stepSidebarKey);
const [workflowAriaLabel, workflowActiveClass] = const [automationAriaLabel, automationActiveClass] =
sidebarKey === workflowSidebarKey sidebarKey === automationSidebarKey
? [__('Automation (selected)', 'mailpoet'), 'is-active'] ? [__('Automation (selected)', 'mailpoet'), 'is-active']
: [__('Automation', 'mailpoet'), '']; : [__('Automation', 'mailpoet'), ''];
@ -30,9 +30,9 @@ export function Header({ sidebarKey }: Props): JSX.Element {
<ul> <ul>
<li> <li>
<Button <Button
onClick={openWorkflowSettings} onClick={openAutomationSettings}
className={`edit-site-sidebar__panel-tab ${workflowActiveClass}`} className={`edit-site-sidebar__panel-tab ${automationActiveClass}`}
aria-label={workflowAriaLabel} aria-label={automationAriaLabel}
data-label={__('Automation', 'mailpoet')} data-label={__('Automation', 'mailpoet')}
> >
{__('Automation', 'mailpoet')} {__('Automation', 'mailpoet')}

View File

@ -10,8 +10,8 @@ import {
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
import { Header } from './header'; import { Header } from './header';
import { StepSidebar } from './step'; import { StepSidebar } from './step';
import { WorkflowSidebar } from './workflow'; import { AutomationSidebar } from './automation';
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store'; import { stepSidebarKey, storeName, automationSidebarKey } from '../../store';
// See: // See:
// https://github.com/WordPress/gutenberg/blob/5caeae34b3fb303761e3b9432311b26f4e5ea3a6/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js // https://github.com/WordPress/gutenberg/blob/5caeae34b3fb303761e3b9432311b26f4e5ea3a6/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js
@ -26,7 +26,7 @@ const sidebarActiveByDefault = Platform.select({
type Props = ComponentProps<typeof ComplementaryArea>; type Props = ComponentProps<typeof ComplementaryArea>;
export function Sidebar(props: Props): JSX.Element { export function Sidebar(props: Props): JSX.Element {
const { keyboardShortcut, sidebarKey, showIconLabels, workflowName } = const { keyboardShortcut, sidebarKey, showIconLabels, automationName } =
useSelect( useSelect(
(select) => ({ (select) => ({
keyboardShortcut: select( keyboardShortcut: select(
@ -36,9 +36,9 @@ export function Sidebar(props: Props): JSX.Element {
), ),
sidebarKey: sidebarKey:
select(interfaceStore).getActiveComplementaryArea(storeName) ?? select(interfaceStore).getActiveComplementaryArea(storeName) ??
workflowSidebarKey, automationSidebarKey,
showIconLabels: select(storeName).isFeatureActive('showIconLabels'), showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
workflowName: select(storeName).getWorkflowData().name, automationName: select(storeName).getAutomationData().name,
}), }),
[], [],
); );
@ -53,14 +53,14 @@ export function Sidebar(props: Props): JSX.Element {
icon={cog} icon={cog}
className="edit-site-sidebar mailpoet-automation-sidebar" className="edit-site-sidebar mailpoet-automation-sidebar"
panelClassName="edit-site-sidebar" panelClassName="edit-site-sidebar"
smallScreenTitle={workflowName || __('(no title)', 'mailpoet')} smallScreenTitle={automationName || __('(no title)', 'mailpoet')}
scope={storeName} scope={storeName}
toggleShortcut={keyboardShortcut} toggleShortcut={keyboardShortcut}
isActiveByDefault={sidebarActiveByDefault} isActiveByDefault={sidebarActiveByDefault}
showIconLabels={showIconLabels} showIconLabels={showIconLabels}
{...props} {...props}
> >
{sidebarKey === workflowSidebarKey && <WorkflowSidebar />} {sidebarKey === automationSidebarKey && <AutomationSidebar />}
{sidebarKey === stepSidebarKey && <StepSidebar />} {sidebarKey === stepSidebarKey && <StepSidebar />}
</ComplementaryArea> </ComplementaryArea>
); );

View File

@ -1,9 +0,0 @@
import { __ } from '@wordpress/i18n';
export function EmptyWorkflow(): JSX.Element {
return (
<div className="mailpoet-automation-editor-empty-workflow">
{__('No automation data.', 'mailpoet')}
</div>
);
}

View File

@ -23,17 +23,17 @@ import { InserterSidebar } from './components/inserter-sidebar';
import { KeyboardShortcuts } from './components/keyboard-shortcuts'; import { KeyboardShortcuts } from './components/keyboard-shortcuts';
import { EditorNotices } from './components/notices'; import { EditorNotices } from './components/notices';
import { Sidebar } from './components/sidebar'; import { Sidebar } from './components/sidebar';
import { Workflow } from './components/workflow'; import { Automation } from './components/automation';
import { createStore, storeName } from './store'; import { createStore, storeName } from './store';
import { initializeApi } from '../api'; import { initializeApi } from '../api';
import { initialize as initializeCoreIntegration } from '../integrations/core'; import { initialize as initializeCoreIntegration } from '../integrations/core';
import { initialize as initializeMailPoetIntegration } from '../integrations/mailpoet'; import { initialize as initializeMailPoetIntegration } from '../integrations/mailpoet';
import { MailPoet } from '../../mailpoet'; import { MailPoet } from '../../mailpoet';
import { LISTING_NOTICE_PARAMETERS } from '../listing/workflow-listing-notices'; import { LISTING_NOTICE_PARAMETERS } from '../listing/automation-listing-notices';
import { registerApiErrorHandler } from './api-error-handler'; import { registerApiErrorHandler } from './api-error-handler';
import { ActivatePanel } from './components/panel/activate-panel'; import { ActivatePanel } from './components/panel/activate-panel';
import { registerTranslations } from '../i18n'; import { registerTranslations } from '../i18n';
import { WorkflowStatus } from '../listing/workflow'; import { AutomationStatus } from '../listing/automation';
// See: // See:
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/layout/index.js // https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/layout/index.js
@ -43,27 +43,27 @@ import { WorkflowStatus } from '../listing/workflow';
const showInserterSidebar = false; const showInserterSidebar = false;
/** /**
* Show temporary message that active workflows cant be updated * Show temporary message that active automations cant be updated
* *
* see MAILPOET-4744 * see MAILPOET-4744
*/ */
function updatingActiveWorkflowNotPossible() { function updatingActiveAutomationNotPossible() {
const workflow = globalSelect(storeName).getWorkflowData(); const automation = globalSelect(storeName).getAutomationData();
if ( if (
![WorkflowStatus.ACTIVE, WorkflowStatus.DEACTIVATING].includes( ![AutomationStatus.ACTIVE, AutomationStatus.DEACTIVATING].includes(
workflow.status, automation.status,
) )
) { ) {
return; return;
} }
if (workflow.stats.totals.in_progress === 0) { if (automation.stats.totals.in_progress === 0) {
return; return;
} }
const { createNotice } = dispatch(noticesStore as StoreDescriptor); const { createNotice } = dispatch(noticesStore as StoreDescriptor);
void createNotice( void createNotice(
'success', 'success',
__( __(
'Editing an active workflow is temporarily unavailable. We are working on introducing this functionality.', 'Editing an active automation is temporarily unavailable. We are working on introducing this functionality.',
'mailpoet', 'mailpoet',
), ),
{ {
@ -73,7 +73,7 @@ function updatingActiveWorkflowNotPossible() {
} }
function onUnload(event) { function onUnload(event) {
if (!globalSelect(storeName).getWorkflowSaved()) { if (!globalSelect(storeName).getAutomationSaved()) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
event.returnValue = __( event.returnValue = __(
'There are unsaved changes that will be lost. Do you want to continue?', 'There are unsaved changes that will be lost. Do you want to continue?',
@ -98,7 +98,7 @@ function Editor(): JSX.Element {
isActivationPanelOpened, isActivationPanelOpened,
isSidebarOpened, isSidebarOpened,
showIconLabels, showIconLabels,
workflow, automation,
} = useSelect( } = useSelect(
(select) => ({ (select) => ({
isFullscreenActive: select(storeName).isFeatureActive('fullscreenMode'), isFullscreenActive: select(storeName).isFeatureActive('fullscreenMode'),
@ -106,7 +106,7 @@ function Editor(): JSX.Element {
isSidebarOpened: select(storeName).isSidebarOpened(), isSidebarOpened: select(storeName).isSidebarOpened(),
isActivationPanelOpened: select(storeName).isActivationPanelOpened(), isActivationPanelOpened: select(storeName).isActivationPanelOpened(),
showIconLabels: select(storeName).isFeatureActive('showIconLabels'), showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
workflow: select(storeName).getWorkflowData(), automation: select(storeName).getAutomationData(),
}), }),
[], [],
); );
@ -118,7 +118,7 @@ function Editor(): JSX.Element {
if (!isBooting) { if (!isBooting) {
return; return;
} }
updatingActiveWorkflowNotPossible(); updatingActiveAutomationNotPossible();
setIsBooting(false); setIsBooting(false);
}, [isBooting]); }, [isBooting]);
const className = classnames('interface-interface-skeleton', { const className = classnames('interface-interface-skeleton', {
@ -126,9 +126,9 @@ function Editor(): JSX.Element {
'show-icon-labels': showIconLabels, 'show-icon-labels': showIconLabels,
}); });
if (workflow.status === 'trash') { if (automation.status === 'trash') {
window.location.href = addQueryArgs(MailPoet.urls.automationListing, { window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
[LISTING_NOTICE_PARAMETERS.workflowHadBeenDeleted]: workflow.id, [LISTING_NOTICE_PARAMETERS.automationHadBeenDeleted]: automation.id,
}); });
return null; return null;
} }
@ -157,7 +157,7 @@ function Editor(): JSX.Element {
content={ content={
<> <>
<EditorNotices /> <EditorNotices />
<Workflow /> <Automation />
</> </>
} }
sidebar={<ComplementaryArea.Slot scope={storeName} />} sidebar={<ComplementaryArea.Slot scope={storeName} />}

View File

@ -7,9 +7,9 @@ import { store as preferencesStore } from '@wordpress/preferences';
import { addQueryArgs } from '@wordpress/url'; import { addQueryArgs } from '@wordpress/url';
import { storeName } from './constants'; import { storeName } from './constants';
import { Feature, State } from './types'; import { Feature, State } from './types';
import { LISTING_NOTICE_PARAMETERS } from '../../listing/workflow-listing-notices'; import { LISTING_NOTICE_PARAMETERS } from '../../listing/automation-listing-notices';
import { MailPoet } from '../../../mailpoet'; import { MailPoet } from '../../../mailpoet';
import { WorkflowStatus } from '../../listing/workflow'; import { AutomationStatus } from '../../listing/automation';
const trackErrors = (errors) => { const trackErrors = (errors) => {
if (!errors?.steps) { if (!errors?.steps) {
@ -24,7 +24,7 @@ const trackErrors = (errors) => {
return fields; return fields;
}); });
MailPoet.trackEvent('Automations > Workflow validation error', { MailPoet.trackEvent('Automations > Automation validation error', {
errors: payload, errors: payload,
}); });
}; };
@ -74,23 +74,23 @@ export function selectStep(value) {
} as const; } as const;
} }
export function setWorkflowName(name) { export function setAutomationName(name) {
const workflow = select(storeName).getWorkflowData(); const automation = select(storeName).getAutomationData();
return { return {
type: 'UPDATE_WORKFLOW', type: 'UPDATE_AUTOMATION',
workflow: { automation: {
...workflow, ...automation,
name, name,
}, },
} as const; } as const;
} }
export function* save() { export function* save() {
const workflow = select(storeName).getWorkflowData(); const automation = select(storeName).getAutomationData();
const data = yield apiFetch({ const data = yield apiFetch({
path: `/workflows/${workflow.id}`, path: `/automations/${automation.id}`,
method: 'PUT', method: 'PUT',
data: { ...workflow }, data: { ...automation },
}); });
const { createNotice } = dispatch(noticesStore as StoreDescriptor); const { createNotice } = dispatch(noticesStore as StoreDescriptor);
@ -106,23 +106,23 @@ export function* save() {
return { return {
type: 'SAVE', type: 'SAVE',
workflow: data?.data ?? workflow, automation: data?.data ?? automation,
} as const; } as const;
} }
export function* activate() { export function* activate() {
const workflow = select(storeName).getWorkflowData(); const automation = select(storeName).getAutomationData();
const data = yield apiFetch({ const data = yield apiFetch({
path: `/workflows/${workflow.id}`, path: `/automations/${automation.id}`,
method: 'PUT', method: 'PUT',
data: { data: {
...workflow, ...automation,
status: WorkflowStatus.ACTIVE, status: AutomationStatus.ACTIVE,
}, },
}); });
const { createNotice } = dispatch(noticesStore as StoreDescriptor); const { createNotice } = dispatch(noticesStore as StoreDescriptor);
if (data?.data.status === WorkflowStatus.ACTIVE) { if (data?.data.status === AutomationStatus.ACTIVE) {
void createNotice( void createNotice(
'success', 'success',
__('Well done! Automation is now activated!', 'mailpoet'), __('Well done! Automation is now activated!', 'mailpoet'),
@ -130,30 +130,33 @@ export function* activate() {
type: 'snackbar', type: 'snackbar',
}, },
); );
MailPoet.trackEvent('Automations > Workflow activated'); MailPoet.trackEvent('Automations > Automation activated');
} }
return { return {
type: 'ACTIVATE', type: 'ACTIVATE',
workflow: data?.data ?? workflow, automation: data?.data ?? automation,
} as const; } as const;
} }
export function* deactivate(deactivateWorkflowRuns = true) { export function* deactivate(deactivateAutomationRuns = true) {
const workflow = select(storeName).getWorkflowData(); const automation = select(storeName).getAutomationData();
const data = yield apiFetch({ const data = yield apiFetch({
path: `/workflows/${workflow.id}`, path: `/automations/${automation.id}`,
method: 'PUT', method: 'PUT',
data: { data: {
...workflow, ...automation,
status: deactivateWorkflowRuns status: deactivateAutomationRuns
? WorkflowStatus.DRAFT ? AutomationStatus.DRAFT
: WorkflowStatus.DEACTIVATING, : AutomationStatus.DEACTIVATING,
}, },
}); });
const { createNotice } = dispatch(noticesStore as StoreDescriptor); const { createNotice } = dispatch(noticesStore as StoreDescriptor);
if (deactivateWorkflowRuns && data?.data.status === WorkflowStatus.DRAFT) { if (
deactivateAutomationRuns &&
data?.data.status === AutomationStatus.DRAFT
) {
void createNotice( void createNotice(
'success', 'success',
__('Automation is now deactivated!', 'mailpoet'), __('Automation is now deactivated!', 'mailpoet'),
@ -162,13 +165,13 @@ export function* deactivate(deactivateWorkflowRuns = true) {
}, },
); );
MailPoet.trackEvent('Automations > Workflow deactivated', { MailPoet.trackEvent('Automations > Automation deactivated', {
type: 'immediate', type: 'immediate',
}); });
} }
if ( if (
!deactivateWorkflowRuns && !deactivateAutomationRuns &&
data?.data.status === WorkflowStatus.DEACTIVATING data?.data.status === AutomationStatus.DEACTIVATING
) { ) {
void createNotice( void createNotice(
'success', 'success',
@ -180,39 +183,39 @@ export function* deactivate(deactivateWorkflowRuns = true) {
type: 'snackbar', type: 'snackbar',
}, },
); );
MailPoet.trackEvent('Automations > Workflow deactivated', { MailPoet.trackEvent('Automations > Automation deactivated', {
type: 'continuous', type: 'continuous',
}); });
} }
return { return {
type: 'DEACTIVATE', type: 'DEACTIVATE',
workflow: data?.data ?? workflow, automation: data?.data ?? automation,
} as const; } as const;
} }
export function* trash(onTrashed: () => void = undefined) { export function* trash(onTrashed: () => void = undefined) {
const workflow = select(storeName).getWorkflowData(); const automation = select(storeName).getAutomationData();
const data = yield apiFetch({ const data = yield apiFetch({
path: `/workflows/${workflow.id}`, path: `/automations/${automation.id}`,
method: 'PUT', method: 'PUT',
data: { data: {
...workflow, ...automation,
status: WorkflowStatus.TRASH, status: AutomationStatus.TRASH,
}, },
}); });
onTrashed?.(); onTrashed?.();
if (data?.status === WorkflowStatus.TRASH) { if (data?.status === AutomationStatus.TRASH) {
window.location.href = addQueryArgs(MailPoet.urls.automationListing, { window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
[LISTING_NOTICE_PARAMETERS.workflowDeleted]: workflow.id, [LISTING_NOTICE_PARAMETERS.automationDeleted]: automation.id,
}); });
} }
return { return {
type: 'TRASH', type: 'TRASH',
workflow: data?.data ?? workflow, automation: data?.data ?? automation,
} as const; } as const;
} }

View File

@ -1,4 +1,4 @@
export const storeName = 'mailpoet/automation-editor'; export const storeName = 'mailpoet/automation-editor';
export const workflowSidebarKey = 'mailpoet/automation-editor/workflow'; export const automationSidebarKey = 'mailpoet/automation-editor/automation';
export const stepSidebarKey = 'mailpoet/automation-editor/step'; export const stepSidebarKey = 'mailpoet/automation-editor/step';

View File

@ -5,8 +5,8 @@ declare let window: AutomationEditorWindow;
export const getInitialState = (): State => ({ export const getInitialState = (): State => ({
context: { ...window.mailpoet_automation_context }, context: { ...window.mailpoet_automation_context },
stepTypes: {}, stepTypes: {},
workflowData: { ...window.mailpoet_automation_workflow }, automationData: { ...window.mailpoet_automation_automation },
workflowSaved: true, automationSaved: true,
selectedStep: undefined, selectedStep: undefined,
inserterSidebar: { inserterSidebar: {
isOpened: false, isOpened: false,

View File

@ -29,35 +29,35 @@ export function reducer(state: State, action: Action): State {
...state, ...state,
selectedStep: action.value, selectedStep: action.value,
}; };
case 'UPDATE_WORKFLOW': case 'UPDATE_AUTOMATION':
return { return {
...state, ...state,
workflowData: action.workflow, automationData: action.automation,
workflowSaved: false, automationSaved: false,
}; };
case 'SAVE': case 'SAVE':
return { return {
...state, ...state,
workflowData: action.workflow, automationData: action.automation,
workflowSaved: true, automationSaved: true,
}; };
case 'ACTIVATE': case 'ACTIVATE':
return { return {
...state, ...state,
workflowData: action.workflow, automationData: action.automation,
workflowSaved: true, automationSaved: true,
}; };
case 'DEACTIVATE': case 'DEACTIVATE':
return { return {
...state, ...state,
workflowData: action.workflow, automationData: action.automation,
workflowSaved: true, automationSaved: true,
}; };
case 'TRASH': case 'TRASH':
return { return {
...state, ...state,
workflowData: action.workflow, automationData: action.automation,
workflowSaved: true, automationSaved: true,
}; };
case 'REGISTER_STEP_TYPE': case 'REGISTER_STEP_TYPE':
return { return {
@ -68,7 +68,7 @@ export function reducer(state: State, action: Action): State {
}, },
}; };
case 'UPDATE_STEP_ARGS': { case 'UPDATE_STEP_ARGS': {
const prevArgs = state.workflowData.steps[action.stepId].args ?? {}; const prevArgs = state.automationData.steps[action.stepId].args ?? {};
const value = const value =
typeof action.value === 'function' typeof action.value === 'function'
@ -82,7 +82,7 @@ export function reducer(state: State, action: Action): State {
) )
: { ...prevArgs, [action.name]: value }; : { ...prevArgs, [action.name]: value };
const step = { ...state.workflowData.steps[action.stepId], args }; const step = { ...state.automationData.steps[action.stepId], args };
const stepErrors = Object.values(state.errors?.steps ?? {}).filter( const stepErrors = Object.values(state.errors?.steps ?? {}).filter(
({ step_id }) => step_id !== action.stepId, ({ step_id }) => step_id !== action.stepId,
@ -90,14 +90,14 @@ export function reducer(state: State, action: Action): State {
return { return {
...state, ...state,
workflowData: { automationData: {
...state.workflowData, ...state.automationData,
steps: { steps: {
...state.workflowData.steps, ...state.automationData.steps,
[action.stepId]: step, [action.stepId]: step,
}, },
}, },
workflowSaved: false, automationSaved: false,
selectedStep: step, selectedStep: step,
errors: errors:
stepErrors.length > 0 stepErrors.length > 0

View File

@ -4,7 +4,7 @@ import { store as preferencesStore } from '@wordpress/preferences';
import { storeName } from './constants'; import { storeName } from './constants';
import { Context, Errors, Feature, State, StepErrors, StepType } from './types'; import { Context, Errors, Feature, State, StepErrors, StepType } from './types';
import { Item } from '../components/inserter/item'; import { Item } from '../components/inserter/item';
import { Step, Workflow } from '../components/workflow/types'; import { Step, Automation } from '../components/automation/types';
export const isFeatureActive = createRegistrySelector( export const isFeatureActive = createRegistrySelector(
(select) => (select) =>
@ -58,12 +58,12 @@ export function getInserterPopover(
return state.inserterPopover; return state.inserterPopover;
} }
export function getWorkflowData(state: State): Workflow { export function getAutomationData(state: State): Automation {
return state.workflowData; return state.automationData;
} }
export function getWorkflowSaved(state: State): boolean { export function getAutomationSaved(state: State): boolean {
return state.workflowSaved; return state.automationSaved;
} }
export function getSelectedStep(state: State): Step | undefined { export function getSelectedStep(state: State): Step | undefined {
@ -71,7 +71,7 @@ export function getSelectedStep(state: State): Step | undefined {
} }
export function getStepById(state: State, id: string): Step | undefined { export function getStepById(state: State, id: string): Step | undefined {
return state.workflowData.steps[id] ?? undefined; return state.automationData.steps[id] ?? undefined;
} }
export function getStepType(state: State, key: string): StepType | undefined { export function getStepType(state: State, key: string): StepType | undefined {

View File

@ -1,9 +1,9 @@
import { ComponentType } from 'react'; import { ComponentType } from 'react';
import { Step, Workflow } from '../components/workflow/types'; import { Step, Automation } from '../components/automation/types';
export interface AutomationEditorWindow extends Window { export interface AutomationEditorWindow extends Window {
mailpoet_automation_context: Context; mailpoet_automation_context: Context;
mailpoet_automation_workflow: Workflow; mailpoet_automation_automation: Automation;
} }
export type Context = { export type Context = {
@ -48,8 +48,8 @@ export type Errors = {
export type State = { export type State = {
context: Context; context: Context;
stepTypes: Record<string, StepType>; stepTypes: Record<string, StepType>;
workflowData: Workflow; automationData: Automation;
workflowSaved: boolean; automationSaved: boolean;
selectedStep: Step | undefined; selectedStep: Step | undefined;
inserterSidebar: { inserterSidebar: {
isOpened: boolean; isOpened: boolean;

View File

@ -3,7 +3,7 @@ import { chartBar } from '@wordpress/icons';
import { Hooks } from 'wp-js-hooks'; import { Hooks } from 'wp-js-hooks';
import { MoreControlType, StepMoreControlsType } from '../../../types/filters'; import { MoreControlType, StepMoreControlsType } from '../../../types/filters';
import { StepType } from '../../../editor/store'; import { StepType } from '../../../editor/store';
import { Step } from '../../../editor/components/workflow/types'; import { Step } from '../../../editor/components/automation/types';
const emailStatisticsControl = (step: Step): MoreControlType => { const emailStatisticsControl = (step: Step): MoreControlType => {
const hasEmail = step.args?.email_id > 0; const hasEmail = step.args?.email_id > 0;
@ -28,7 +28,7 @@ const emailStatisticsControl = (step: Step): MoreControlType => {
export function registerStepControls() { export function registerStepControls() {
Hooks.addFilter( Hooks.addFilter(
'mailpoet.automation.workflow.step.more-controls', 'mailpoet.automation.automation.step.more-controls',
'mailpoet', 'mailpoet',
( (
controls: StepMoreControlsType, controls: StepMoreControlsType,

View File

@ -31,11 +31,11 @@ export function EditNewsletter(): JSX.Element {
useState(false); useState(false);
const [fetchingPreviewLink, setFetchingPreviewLink] = useState(false); const [fetchingPreviewLink, setFetchingPreviewLink] = useState(false);
const { selectedStep, workflowId, workflowSaved, errors } = useSelect( const { selectedStep, automationId, automationSaved, errors } = useSelect(
(select) => ({ (select) => ({
selectedStep: select(storeName).getSelectedStep(), selectedStep: select(storeName).getSelectedStep(),
workflowId: select(storeName).getWorkflowData().id, automationId: select(storeName).getAutomationData().id,
workflowSaved: select(storeName).getWorkflowSaved(), automationSaved: select(storeName).getAutomationSaved(),
errors: select(storeName).getStepError( errors: select(storeName).getStepError(
select(storeName).getSelectedStep().id, select(storeName).getSelectedStep().id,
), ),
@ -44,7 +44,7 @@ export function EditNewsletter(): JSX.Element {
); );
const emailId = selectedStep?.args?.email_id as number | undefined; const emailId = selectedStep?.args?.email_id as number | undefined;
const workflowStepId = selectedStep.id; const automationStepId = selectedStep.id;
const errorFields = errors?.fields ?? {}; const errorFields = errors?.fields ?? {};
const emailIdError = errorFields?.email_id ?? ''; const emailIdError = errorFields?.email_id ?? '';
@ -58,28 +58,28 @@ export function EditNewsletter(): JSX.Element {
type: 'automation', type: 'automation',
subject: '', subject: '',
options: { options: {
workflowId, automationId,
workflowStepId, automationStepId,
}, },
}, },
}); });
dispatch(storeName).updateStepArgs( dispatch(storeName).updateStepArgs(
workflowStepId, automationStepId,
'email_id', 'email_id',
parseInt(response.data.id as string, 10), parseInt(response.data.id as string, 10),
); );
dispatch(storeName).save(); dispatch(storeName).save();
}, [workflowId, workflowStepId]); }, [automationId, automationStepId]);
// This component is rendered only when no email ID is set. Once we have the ID // This component is rendered only when no email ID is set. Once we have the ID
// and the workflow is saved, we can safely redirect to the email design flow. // and the automation is saved, we can safely redirect to the email design flow.
useEffect(() => { useEffect(() => {
if (redirectToTemplateSelection && emailId && workflowSaved) { if (redirectToTemplateSelection && emailId && automationSaved) {
window.location.href = `admin.php?page=mailpoet-newsletters#/template/${emailId}`; window.location.href = `admin.php?page=mailpoet-newsletters#/template/${emailId}`;
} }
}, [emailId, workflowSaved, redirectToTemplateSelection]); }, [emailId, automationSaved, redirectToTemplateSelection]);
if (!emailId || redirectToTemplateSelection) { if (!emailId || redirectToTemplateSelection) {
return ( return (

View File

@ -3,7 +3,7 @@ import { Hooks } from 'wp-js-hooks';
import { Icon } from './icon'; import { Icon } from './icon';
import { Edit } from './edit'; import { Edit } from './edit';
import { State, StepType } from '../../../../editor/store/types'; import { State, StepType } from '../../../../editor/store/types';
import { Step } from '../../../../editor/components/workflow/types'; import { Step } from '../../../../editor/components/automation/types';
export const step: StepType = { export const step: StepType = {
key: 'mailpoet:send-email', key: 'mailpoet:send-email',
@ -20,6 +20,6 @@ export const step: StepType = {
Hooks.applyFilters( Hooks.applyFilters(
'mailpoet.automation.send_email.create_step', 'mailpoet.automation.send_email.create_step',
stepData, stepData,
state.workflowData.id, state.automationData.id,
), ),
} as const; } as const;

View File

@ -2,10 +2,13 @@
* The types in this file document the expected return types of specific * The types in this file document the expected return types of specific
* filters. * filters.
*/ */
import { Step } from '../../../editor/components/workflow/types'; import { Step } from '../../../editor/components/automation/types';
// mailpoet.automation.send_email.create_step // mailpoet.automation.send_email.create_step
export type SendEmailCreateStepType = (step: Step, workflowId: number) => Step; export type SendEmailCreateStepType = (
step: Step,
automationId: number,
) => Step;
// mailpoet.automation.send_email.google_analytics_panel // mailpoet.automation.send_email.google_analytics_panel
export type GoogleAnalyticsPanelBodyType = JSX.Element; export type GoogleAnalyticsPanelBodyType = JSX.Element;

View File

@ -3,27 +3,30 @@ import { __ } from '@wordpress/i18n';
import { Notice } from '../../notices/notice'; import { Notice } from '../../notices/notice';
export const LISTING_NOTICE_PARAMETERS = { export const LISTING_NOTICE_PARAMETERS = {
workflowHadBeenDeleted: 'mailpoet-had-been-deleted', automationHadBeenDeleted: 'mailpoet-had-been-deleted',
workflowDeleted: 'mailpoet-workflow-deleted', automationDeleted: 'mailpoet-automation-deleted',
}; };
export function WorkflowListingNotices(): JSX.Element { export function AutomationListingNotices(): JSX.Element {
const workflowHadBeenDeleted = parseInt( const automationHadBeenDeleted = parseInt(
getQueryArg( getQueryArg(
window.location.href, window.location.href,
LISTING_NOTICE_PARAMETERS.workflowHadBeenDeleted, LISTING_NOTICE_PARAMETERS.automationHadBeenDeleted,
) as string, ) as string,
10, 10,
); );
const workflowDeleted = parseInt( const automationDeleted = parseInt(
getQueryArg( getQueryArg(
window.location.href, window.location.href,
LISTING_NOTICE_PARAMETERS.workflowDeleted, LISTING_NOTICE_PARAMETERS.automationDeleted,
) as string, ) as string,
10, 10,
); );
if (Number.isNaN(workflowHadBeenDeleted) && Number.isNaN(workflowDeleted)) { if (
Number.isNaN(automationHadBeenDeleted) &&
Number.isNaN(automationDeleted)
) {
return null; return null;
} }
@ -32,7 +35,7 @@ export function WorkflowListingNotices(): JSX.Element {
...Object.values(LISTING_NOTICE_PARAMETERS), ...Object.values(LISTING_NOTICE_PARAMETERS),
); );
window.history.pushState('', '', urlWithoutNotices); window.history.pushState('', '', urlWithoutNotices);
if (workflowHadBeenDeleted) { if (automationHadBeenDeleted) {
return ( return (
<Notice type="error" closable timeout={false}> <Notice type="error" closable timeout={false}>
<p> <p>
@ -44,7 +47,7 @@ export function WorkflowListingNotices(): JSX.Element {
</Notice> </Notice>
); );
} }
if (workflowDeleted) { if (automationDeleted) {
return ( return (
<Notice type="success" closable timeout={false}> <Notice type="success" closable timeout={false}>
<p>{__('1 automation moved to the Trash.', 'mailpoet')}</p> <p>{__('1 automation moved to the Trash.', 'mailpoet')}</p>

View File

@ -1,14 +1,14 @@
export enum WorkflowStatus { export enum AutomationStatus {
ACTIVE = 'active', ACTIVE = 'active',
DRAFT = 'draft', DRAFT = 'draft',
TRASH = 'trash', TRASH = 'trash',
DEACTIVATING = 'deactivating', DEACTIVATING = 'deactivating',
} }
export type Workflow = { export type Automation = {
id: number; id: number;
name: string; name: string;
status: WorkflowStatus; status: AutomationStatus;
stats: { stats: {
totals: { totals: {
entered: number; entered: number;

View File

@ -1,19 +1,19 @@
import { Button } from '@wordpress/components'; import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { addQueryArgs } from '@wordpress/url'; import { addQueryArgs } from '@wordpress/url';
import { Workflow } from '../../workflow'; import { Automation } from '../../automation';
import { MailPoet } from '../../../../mailpoet'; import { MailPoet } from '../../../../mailpoet';
type Props = { type Props = {
workflow: Workflow; automation: Automation;
label?: string; label?: string;
}; };
export function EditWorkflow({ workflow, label }: Props): JSX.Element { export function EditAutomation({ automation, label }: Props): JSX.Element {
return ( return (
<Button <Button
variant="link" variant="link"
href={addQueryArgs(MailPoet.urls.automationEditor, { id: workflow.id })} href={addQueryArgs(MailPoet.urls.automationEditor, { id: automation.id })}
> >
{label ?? __('Edit', 'mailpoet')} {label ?? __('Edit', 'mailpoet')}
</Button> </Button>

View File

@ -1,2 +1,2 @@
export * from './edit-workflow'; export * from './edit-automation';
export * from './undo-trash'; export * from './undo-trash';

View File

@ -2,23 +2,23 @@ import { Button } from '@wordpress/components';
import { useDispatch } from '@wordpress/data'; import { useDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { storeName } from '../../store/constants'; import { storeName } from '../../store/constants';
import { Workflow, WorkflowStatus } from '../../workflow'; import { Automation, AutomationStatus } from '../../automation';
type Props = { type Props = {
workflow: Workflow; automation: Automation;
previousStatus: WorkflowStatus; previousStatus: AutomationStatus;
}; };
export function UndoTrashButton({ export function UndoTrashButton({
workflow, automation,
previousStatus, previousStatus,
}: Props): JSX.Element { }: Props): JSX.Element {
const { restoreWorkflow } = useDispatch(storeName); const { restoreAutomation } = useDispatch(storeName);
return ( return (
<Button <Button
variant="link" variant="link"
onClick={() => restoreWorkflow(workflow, previousStatus)} onClick={() => restoreAutomation(automation, previousStatus)}
> >
{__('Undo', 'mailpoet')} {__('Undo', 'mailpoet')}
</Button> </Button>

View File

@ -3,25 +3,25 @@ import { __ } from '@wordpress/i18n';
import { DropdownMenu } from '@wordpress/components'; import { DropdownMenu } from '@wordpress/components';
import { moreVertical } from '@wordpress/icons'; import { moreVertical } from '@wordpress/icons';
import { useDeleteButton, useRestoreButton, useTrashButton } from '../menu'; import { useDeleteButton, useRestoreButton, useTrashButton } from '../menu';
import { Workflow } from '../../workflow'; import { Automation } from '../../automation';
import { EditWorkflow } from '../actions'; import { EditAutomation } from '../actions';
type Props = { type Props = {
workflow: Workflow; automation: Automation;
}; };
export function Actions({ workflow }: Props): JSX.Element { export function Actions({ automation }: Props): JSX.Element {
// Menu items are using custom hooks because the "DropdownMenu" component uses the "controls" // Menu items are using custom hooks because the "DropdownMenu" component uses the "controls"
// attribute rather than child components, but we need to render modal confirmation dialogs. // attribute rather than child components, but we need to render modal confirmation dialogs.
const trash = useTrashButton(workflow); const trash = useTrashButton(automation);
const restore = useRestoreButton(workflow); const restore = useRestoreButton(automation);
const del = useDeleteButton(workflow); const del = useDeleteButton(automation);
const menuItems = [trash, restore, del].filter((item) => item); const menuItems = [trash, restore, del].filter((item) => item);
return ( return (
<div className="mailpoet-automation-listing-cell-actions"> <div className="mailpoet-automation-listing-cell-actions">
<EditWorkflow workflow={workflow} /> <EditAutomation automation={automation} />
{menuItems.map(({ control, slot }) => ( {menuItems.map(({ control, slot }) => (
<Fragment key={control.title}>{slot}</Fragment> <Fragment key={control.title}>{slot}</Fragment>
))} ))}

View File

@ -1,10 +1,10 @@
import { EditWorkflow } from '../actions'; import { EditAutomation } from '../actions';
import { Workflow } from '../../workflow'; import { Automation } from '../../automation';
type Props = { type Props = {
workflow: Workflow; automation: Automation;
}; };
export function Name({ workflow }: Props): JSX.Element { export function Name({ automation }: Props): JSX.Element {
return <EditWorkflow workflow={workflow} label={workflow.name} />; return <EditAutomation automation={automation} label={automation.name} />;
} }

View File

@ -1,14 +1,14 @@
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { Workflow, WorkflowStatus } from '../../workflow'; import { Automation, AutomationStatus } from '../../automation';
type Props = { type Props = {
workflow: Workflow; automation: Automation;
}; };
export function Status({ workflow }: Props): JSX.Element { export function Status({ automation }: Props): JSX.Element {
return ( return (
<div className="mailpoet-automation-listing-cell-status"> <div className="mailpoet-automation-listing-cell-status">
{workflow.status === WorkflowStatus.ACTIVE {automation.status === AutomationStatus.ACTIVE
? __('Active', 'mailpoet') ? __('Active', 'mailpoet')
: __('Not active', 'mailpoet')} : __('Not active', 'mailpoet')}
</div> </div>

View File

@ -1,12 +1,12 @@
import { _x } from '@wordpress/i18n'; import { _x } from '@wordpress/i18n';
import { Workflow } from '../../workflow'; import { Automation } from '../../automation';
import { Statistics } from '../../../components/statistics'; import { Statistics } from '../../../components/statistics';
type Props = { type Props = {
workflow: Workflow; automation: Automation;
}; };
export function Subscribers({ workflow }: Props): JSX.Element { export function Subscribers({ automation }: Props): JSX.Element {
return ( return (
<Statistics <Statistics
labelPosition="after" labelPosition="after"
@ -15,19 +15,19 @@ export function Subscribers({ workflow }: Props): JSX.Element {
key: 'entered', key: 'entered',
// translators: Total number of subscribers who entered an automation // translators: Total number of subscribers who entered an automation
label: _x('Entered', 'automation stats', 'mailpoet'), label: _x('Entered', 'automation stats', 'mailpoet'),
value: workflow.stats.totals.entered, value: automation.stats.totals.entered,
}, },
{ {
key: 'processing', key: 'processing',
// translators: Total number of subscribers who are being processed in an automation // translators: Total number of subscribers who are being processed in an automation
label: _x('Processing', 'automation stats', 'mailpoet'), label: _x('Processing', 'automation stats', 'mailpoet'),
value: workflow.stats.totals.in_progress, value: automation.stats.totals.in_progress,
}, },
{ {
key: 'exited', key: 'exited',
// translators: Total number of subscribers who exited an automation, no matter the result // translators: Total number of subscribers who exited an automation, no matter the result
label: _x('Exited', 'automation stats', 'mailpoet'), label: _x('Exited', 'automation stats', 'mailpoet'),
value: workflow.stats.totals.exited, value: automation.stats.totals.exited,
}, },
]} ]}
/> />

View File

@ -4,13 +4,13 @@ import { useDispatch } from '@wordpress/data';
import { __, sprintf } from '@wordpress/i18n'; import { __, sprintf } from '@wordpress/i18n';
import { Item } from './item'; import { Item } from './item';
import { storeName } from '../../store'; import { storeName } from '../../store';
import { Workflow, WorkflowStatus } from '../../workflow'; import { Automation, AutomationStatus } from '../../automation';
export const useDeleteButton = (workflow: Workflow): Item | undefined => { export const useDeleteButton = (automation: Automation): Item | undefined => {
const [showDialog, setShowDialog] = useState(false); const [showDialog, setShowDialog] = useState(false);
const { deleteWorkflow } = useDispatch(storeName); const { deleteAutomation } = useDispatch(storeName);
if (workflow.status !== WorkflowStatus.TRASH) { if (automation.status !== AutomationStatus.TRASH) {
return undefined; return undefined;
} }
@ -27,7 +27,7 @@ export const useDeleteButton = (workflow: Workflow): Item | undefined => {
title={__('Permanently delete automation', 'mailpoet')} title={__('Permanently delete automation', 'mailpoet')}
confirmButtonText={__('Yes, permanently delete', 'mailpoet')} confirmButtonText={__('Yes, permanently delete', 'mailpoet')}
__experimentalHideHeader={false} __experimentalHideHeader={false}
onConfirm={() => deleteWorkflow(workflow)} onConfirm={() => deleteAutomation(automation)}
onCancel={() => setShowDialog(false)} onCancel={() => setShowDialog(false)}
> >
{sprintf( {sprintf(
@ -36,7 +36,7 @@ export const useDeleteButton = (workflow: Workflow): Item | undefined => {
'Are you sure you want to permanently delete "%s" and all associated data? This cannot be undone!', 'Are you sure you want to permanently delete "%s" and all associated data? This cannot be undone!',
'mailpoet', 'mailpoet',
), ),
workflow.name, automation.name,
)} )}
</ConfirmDialog> </ConfirmDialog>
), ),

View File

@ -2,12 +2,14 @@ import { useDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { Item } from './item'; import { Item } from './item';
import { storeName } from '../../store'; import { storeName } from '../../store';
import { Workflow, WorkflowStatus } from '../../workflow'; import { Automation, AutomationStatus } from '../../automation';
export const useDuplicateButton = (workflow: Workflow): Item | undefined => { export const useDuplicateButton = (
const { duplicateWorkflow } = useDispatch(storeName); automation: Automation,
): Item | undefined => {
const { duplicateAutomation } = useDispatch(storeName);
if (workflow.status === WorkflowStatus.TRASH) { if (automation.status === AutomationStatus.TRASH) {
return undefined; return undefined;
} }
@ -16,7 +18,7 @@ export const useDuplicateButton = (workflow: Workflow): Item | undefined => {
control: { control: {
title: __('Duplicate', 'mailpoet'), title: __('Duplicate', 'mailpoet'),
icon: null, icon: null,
onClick: () => duplicateWorkflow(workflow), onClick: () => duplicateAutomation(automation),
}, },
}; };
}; };

View File

@ -2,12 +2,12 @@ import { useDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { Item } from './item'; import { Item } from './item';
import { storeName } from '../../store'; import { storeName } from '../../store';
import { Workflow, WorkflowStatus } from '../../workflow'; import { Automation, AutomationStatus } from '../../automation';
export const useRestoreButton = (workflow: Workflow): Item | undefined => { export const useRestoreButton = (automation: Automation): Item | undefined => {
const { restoreWorkflow } = useDispatch(storeName); const { restoreAutomation } = useDispatch(storeName);
if (workflow.status !== WorkflowStatus.TRASH) { if (automation.status !== AutomationStatus.TRASH) {
return undefined; return undefined;
} }
@ -16,7 +16,7 @@ export const useRestoreButton = (workflow: Workflow): Item | undefined => {
control: { control: {
title: __('Restore', 'mailpoet'), title: __('Restore', 'mailpoet'),
icon: null, icon: null,
onClick: () => restoreWorkflow(workflow, WorkflowStatus.DRAFT), onClick: () => restoreAutomation(automation, AutomationStatus.DRAFT),
}, },
}; };
}; };

View File

@ -4,13 +4,13 @@ import { useDispatch } from '@wordpress/data';
import { __, _x, sprintf } from '@wordpress/i18n'; import { __, _x, sprintf } from '@wordpress/i18n';
import { Item } from './item'; import { Item } from './item';
import { storeName } from '../../store'; import { storeName } from '../../store';
import { Workflow, WorkflowStatus } from '../../workflow'; import { Automation, AutomationStatus } from '../../automation';
export const useTrashButton = (workflow: Workflow): Item | undefined => { export const useTrashButton = (automation: Automation): Item | undefined => {
const [showDialog, setShowDialog] = useState(false); const [showDialog, setShowDialog] = useState(false);
const { trashWorkflow } = useDispatch(storeName); const { trashAutomation } = useDispatch(storeName);
if (workflow.status === WorkflowStatus.TRASH) { if (automation.status === AutomationStatus.TRASH) {
return undefined; return undefined;
} }
@ -27,7 +27,7 @@ export const useTrashButton = (workflow: Workflow): Item | undefined => {
title={__('Trash automation', 'mailpoet')} title={__('Trash automation', 'mailpoet')}
confirmButtonText={__('Yes, move to trash', 'mailpoet')} confirmButtonText={__('Yes, move to trash', 'mailpoet')}
__experimentalHideHeader={false} __experimentalHideHeader={false}
onConfirm={() => trashWorkflow(workflow)} onConfirm={() => trashAutomation(automation)}
onCancel={() => setShowDialog(false)} onCancel={() => setShowDialog(false)}
> >
{sprintf( {sprintf(
@ -36,7 +36,7 @@ export const useTrashButton = (workflow: Workflow): Item | undefined => {
'Are you sure you want to move the automation "%s" to the Trash?', 'Are you sure you want to move the automation "%s" to the Trash?',
'mailpoet', 'mailpoet',
), ),
workflow.name, automation.name,
)} )}
</ConfirmDialog> </ConfirmDialog>
), ),

View File

@ -1,27 +1,27 @@
import { Workflow } from './workflow'; import { Automation } from './automation';
import { Actions, Name, Status, Subscribers } from './components/cells'; import { Actions, Name, Status, Subscribers } from './components/cells';
export function getRow(workflow: Workflow): object[] { export function getRow(automation: Automation): object[] {
return [ return [
{ {
id: workflow.id, id: automation.id,
value: workflow.name, value: automation.name,
display: <Name workflow={workflow} />, display: <Name automation={automation} />,
}, },
{ {
id: workflow.id, id: automation.id,
value: null, value: null,
display: <Subscribers workflow={workflow} />, display: <Subscribers automation={automation} />,
}, },
{ {
id: workflow.id, id: automation.id,
value: workflow.status, value: automation.status,
display: <Status workflow={workflow} />, display: <Status automation={automation} />,
}, },
{ {
id: workflow.id, id: automation.id,
value: null, value: null,
display: <Actions workflow={workflow} />, display: <Actions automation={automation} />,
}, },
]; ];
} }

View File

@ -7,7 +7,7 @@ import { useHistory, useLocation } from 'react-router-dom';
import { plusIcon } from 'common/button/icon/plus'; import { plusIcon } from 'common/button/icon/plus';
import { getRow } from './get-row'; import { getRow } from './get-row';
import { storeName } from './store'; import { storeName } from './store';
import { Workflow, WorkflowStatus } from './workflow'; import { Automation, AutomationStatus } from './automation';
import { MailPoet } from '../../mailpoet'; import { MailPoet } from '../../mailpoet';
const tabConfig = [ const tabConfig = [
@ -17,17 +17,17 @@ const tabConfig = [
className: 'mailpoet-tab-all', className: 'mailpoet-tab-all',
}, },
{ {
name: WorkflowStatus.ACTIVE, name: AutomationStatus.ACTIVE,
title: __('Active', 'mailpoet'), title: __('Active', 'mailpoet'),
className: 'mailpoet-tab-active', className: 'mailpoet-tab-active',
}, },
{ {
name: WorkflowStatus.DRAFT, name: AutomationStatus.DRAFT,
title: _x('Draft', 'noun', 'mailpoet'), title: _x('Draft', 'noun', 'mailpoet'),
className: 'mailpoet-tab-draft', className: 'mailpoet-tab-draft',
}, },
{ {
name: WorkflowStatus.TRASH, name: AutomationStatus.TRASH,
title: _x('Trash', 'noun', 'mailpoet'), title: _x('Trash', 'noun', 'mailpoet'),
className: 'mailpoet-tab-trash', className: 'mailpoet-tab-trash',
}, },
@ -68,14 +68,14 @@ export function AutomationListing(): JSX.Element {
[location], [location],
); );
const workflows = useSelect((select) => select(storeName).getWorkflows()); const automations = useSelect((select) => select(storeName).getAutomations());
const { loadWorkflows } = useDispatch(storeName); const { loadAutomations } = useDispatch(storeName);
const status = pageSearch.get('status'); const status = pageSearch.get('status');
useEffect(() => { useEffect(() => {
loadWorkflows(); loadAutomations();
}, [loadWorkflows]); }, [loadAutomations]);
// focus tab button on status change (needed due to the force re-mount below) // focus tab button on status change (needed due to the force re-mount below)
useLayoutEffect(() => { useLayoutEffect(() => {
@ -103,24 +103,24 @@ export function AutomationListing(): JSX.Element {
[pageSearch, history], [pageSearch, history],
); );
const groupedWorkflows = useMemo<Record<string, Workflow[]>>(() => { const groupedAutomations = useMemo<Record<string, Automation[]>>(() => {
const grouped = { all: [] }; const grouped = { all: [] };
(workflows ?? []).forEach((workflow) => { (automations ?? []).forEach((automation) => {
if (!grouped[workflow.status]) { if (!grouped[automation.status]) {
grouped[workflow.status] = []; grouped[automation.status] = [];
} }
grouped[workflow.status].push(workflow); grouped[automation.status].push(automation);
if (workflow.status !== WorkflowStatus.TRASH) { if (automation.status !== AutomationStatus.TRASH) {
grouped.all.push(workflow); grouped.all.push(automation);
} }
}); });
return grouped; return grouped;
}, [workflows]); }, [automations]);
const tabs = useMemo( const tabs = useMemo(
() => () =>
tabConfig.map((tab) => { tabConfig.map((tab) => {
const count = (groupedWorkflows[tab.name] ?? []).length; const count = (groupedAutomations[tab.name] ?? []).length;
return { return {
name: tab.name, name: tab.name,
title: ( title: (
@ -132,38 +132,39 @@ export function AutomationListing(): JSX.Element {
className: tab.className, className: tab.className,
}; };
}), }),
[groupedWorkflows], [groupedAutomations],
); );
const renderTabs = useCallback( const renderTabs = useCallback(
(tab) => { (tab) => {
const filteredWorkflows: Workflow[] = groupedWorkflows[tab.name] ?? []; const filteredAutomations: Automation[] =
groupedAutomations[tab.name] ?? [];
const rowsPerPage = parseInt(pageSearch.get('per_page') ?? '25', 10); const rowsPerPage = parseInt(pageSearch.get('per_page') ?? '25', 10);
const currentPage = parseInt(pageSearch.get('paged') ?? '1', 10); const currentPage = parseInt(pageSearch.get('paged') ?? '1', 10);
const start = (currentPage - 1) * rowsPerPage; const start = (currentPage - 1) * rowsPerPage;
const rows = filteredWorkflows const rows = filteredAutomations
.map((workflow) => getRow(workflow)) .map((automation) => getRow(automation))
.slice(start, start + rowsPerPage); .slice(start, start + rowsPerPage);
return ( return (
<TableCard <TableCard
className="mailpoet-automation-listing" className="mailpoet-automation-listing"
title="" title=""
isLoading={!workflows} isLoading={!automations}
headers={tableHeaders} headers={tableHeaders}
rows={rows} rows={rows}
rowKey={(_, i) => filteredWorkflows[i].id} rowKey={(_, i) => filteredAutomations[i].id}
rowsPerPage={rowsPerPage} rowsPerPage={rowsPerPage}
onQueryChange={(key) => (value) => { onQueryChange={(key) => (value) => {
updateUrlSearchString({ [key]: value }); updateUrlSearchString({ [key]: value });
}} }}
totalRows={filteredWorkflows.length} totalRows={filteredAutomations.length}
query={Object.fromEntries(pageSearch)} query={Object.fromEntries(pageSearch)}
showMenu={false} showMenu={false}
/> />
); );
}, },
[workflows, groupedWorkflows, pageSearch, updateUrlSearchString], [automations, groupedAutomations, pageSearch, updateUrlSearchString],
); );
return ( return (

View File

@ -2,8 +2,8 @@ import { dispatch, StoreDescriptor } from '@wordpress/data';
import { apiFetch } from '@wordpress/data-controls'; import { apiFetch } from '@wordpress/data-controls';
import { __, sprintf } from '@wordpress/i18n'; import { __, sprintf } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices'; import { store as noticesStore } from '@wordpress/notices';
import { Workflow, WorkflowStatus } from '../workflow'; import { Automation, AutomationStatus } from '../automation';
import { EditWorkflow, UndoTrashButton } from '../components/actions'; import { EditAutomation, UndoTrashButton } from '../components/actions';
const createSuccessNotice = (content: string, options?: unknown) => const createSuccessNotice = (content: string, options?: unknown) =>
dispatch(noticesStore as StoreDescriptor).createSuccessNotice( dispatch(noticesStore as StoreDescriptor).createSuccessNotice(
@ -14,78 +14,84 @@ const createSuccessNotice = (content: string, options?: unknown) =>
const removeNotice = (id: string) => const removeNotice = (id: string) =>
dispatch(noticesStore as StoreDescriptor).removeNotice(id); dispatch(noticesStore as StoreDescriptor).removeNotice(id);
export function* loadWorkflows() { export function* loadAutomations() {
const data = yield apiFetch({ const data = yield apiFetch({
path: `/workflows`, path: `/automations`,
}); });
return { return {
type: 'SET_WORKFLOWS', type: 'SET_AUTOMATIONS',
workflows: data.data, automations: data.data,
} as const; } as const;
} }
export function* duplicateWorkflow(workflow: Workflow) { export function* duplicateAutomation(automation: Automation) {
const data = yield apiFetch({ const data = yield apiFetch({
path: `/workflows/${workflow.id}/duplicate`, path: `/automations/${automation.id}/duplicate`,
method: 'POST', method: 'POST',
}); });
void createSuccessNotice( void createSuccessNotice(
// translators: %s is the automation name // translators: %s is the automation name
sprintf(__('Automation "%s" was duplicated.', 'mailpoet'), workflow.name), sprintf(__('Automation "%s" was duplicated.', 'mailpoet'), automation.name),
); );
return { return {
type: 'ADD_WORKFLOW', type: 'ADD_AUTOMATION',
workflow: data.data, automation: data.data,
} as const; } as const;
} }
export function* trashWorkflow(workflow: Workflow) { export function* trashAutomation(automation: Automation) {
const data = yield apiFetch({ const data = yield apiFetch({
path: `/workflows/${workflow.id}`, path: `/automations/${automation.id}`,
method: 'PUT', method: 'PUT',
data: { data: {
status: WorkflowStatus.TRASH, status: AutomationStatus.TRASH,
}, },
}); });
const message = __('1 automation moved to the Trash.', 'mailpoet'); const message = __('1 automation moved to the Trash.', 'mailpoet');
void createSuccessNotice(message, { void createSuccessNotice(message, {
id: `workflow-trashed-${workflow.id}`, id: `automation-trashed-${automation.id}`,
__unstableHTML: ( __unstableHTML: (
<p> <p>
{message}{' '} {message}{' '}
<UndoTrashButton workflow={workflow} previousStatus={workflow.status} /> <UndoTrashButton
automation={automation}
previousStatus={automation.status}
/>
</p> </p>
), ),
}); });
return { return {
type: 'UPDATE_WORKFLOW', type: 'UPDATE_AUTOMATION',
workflow: data.data, automation: data.data,
} as const; } as const;
} }
export function* restoreWorkflow(workflow: Workflow, status: WorkflowStatus) { export function* restoreAutomation(
automation: Automation,
status: AutomationStatus,
) {
const data = yield apiFetch({ const data = yield apiFetch({
path: `/workflows/${workflow.id}`, path: `/automations/${automation.id}`,
method: 'PUT', method: 'PUT',
data: { data: {
status, status,
}, },
}); });
void removeNotice(`workflow-trashed-${workflow.id}`); void removeNotice(`automation-trashed-${automation.id}`);
const message = __('1 automation restored from the Trash.', 'mailpoet'); const message = __('1 automation restored from the Trash.', 'mailpoet');
void createSuccessNotice(message, { void createSuccessNotice(message, {
__unstableHTML: ( __unstableHTML: (
<p> <p>
{message}{' '} {message}{' '}
<EditWorkflow <EditAutomation
workflow={workflow} automation={automation}
label={__('Edit automation', 'mailpoet')} label={__('Edit automation', 'mailpoet')}
/> />
</p> </p>
@ -93,14 +99,14 @@ export function* restoreWorkflow(workflow: Workflow, status: WorkflowStatus) {
}); });
return { return {
type: 'UPDATE_WORKFLOW', type: 'UPDATE_AUTOMATION',
workflow: data.data, automation: data.data,
} as const; } as const;
} }
export function* deleteWorkflow(workflow: Workflow) { export function* deleteAutomation(automation: Automation) {
yield apiFetch({ yield apiFetch({
path: `/workflows/${workflow.id}`, path: `/automations/${automation.id}`,
method: 'DELETE', method: 'DELETE',
}); });
@ -109,7 +115,7 @@ export function* deleteWorkflow(workflow: Workflow) {
); );
return { return {
type: 'DELETE_WORKFLOW', type: 'DELETE_AUTOMATION',
workflow, automation,
} as const; } as const;
} }

View File

@ -1,5 +1,5 @@
import { State } from './types'; import { State } from './types';
export const getInitialState = (): State => ({ export const getInitialState = (): State => ({
workflows: undefined, automations: undefined,
}); });

View File

@ -1,31 +1,33 @@
import { Action } from '@wordpress/data'; import { Action } from '@wordpress/data';
import { State } from './types'; import { State } from './types';
import { Workflow } from '../workflow'; import { Automation } from '../automation';
export function reducer(state: State, action: Action): State { export function reducer(state: State, action: Action): State {
switch (action.type) { switch (action.type) {
case 'SET_WORKFLOWS': case 'SET_AUTOMATIONS':
return { return {
...state, ...state,
workflows: action.workflows, automations: action.automations,
}; };
case 'ADD_WORKFLOW': case 'ADD_AUTOMATION':
return { return {
...state, ...state,
workflows: [action.workflow, ...state.workflows], automations: [action.automation, ...state.automations],
}; };
case 'UPDATE_WORKFLOW': case 'UPDATE_AUTOMATION':
return { return {
...state, ...state,
workflows: state.workflows.map((workflow: Workflow) => automations: state.automations.map((automation: Automation) =>
workflow.id === action.workflow.id ? action.workflow : workflow, automation.id === action.automation.id
? action.automation
: automation,
), ),
}; };
case 'DELETE_WORKFLOW': case 'DELETE_AUTOMATION':
return { return {
...state, ...state,
workflows: state.workflows.filter( automations: state.automations.filter(
(workflow: Workflow) => workflow.id !== action.workflow.id, (automation: Automation) => automation.id !== action.automation.id,
), ),
}; };
default: default:

View File

@ -1,11 +1,11 @@
import { State } from './types'; import { State } from './types';
import { Workflow } from '../workflow'; import { Automation } from '../automation';
import { workflowCount } from '../../config'; import { automationCount } from '../../config';
export function getWorkflows(state: State): Workflow[] { export function getAutomations(state: State): Automation[] {
return state.workflows; return state.automations;
} }
export function getWorkflowCount(state: State): number { export function getAutomationCount(state: State): number {
return state.workflows ? state.workflows.length : workflowCount; return state.automations ? state.automations.length : automationCount;
} }

View File

@ -1,5 +1,5 @@
import { Workflow } from '../workflow'; import { Automation } from '../automation';
export type State = { export type State = {
workflows?: Workflow[]; automations?: Automation[];
}; };

View File

@ -15,13 +15,13 @@ export function BuildYourOwnSection(): JSX.Element {
image: `${MailPoet.cdnUrl}automation/sections/start-with-a-trigger.png`, image: `${MailPoet.cdnUrl}automation/sections/start-with-a-trigger.png`,
}, },
{ {
slug: 'customize-your-workflow', slug: 'customize-your-automation',
title: __('Customize your automation', 'mailpoet'), title: __('Customize your automation', 'mailpoet'),
text: __( text: __(
'Choose steps and create a custom journey to best suit your needs.', 'Choose steps and create a custom journey to best suit your needs.',
'mailpoet', 'mailpoet',
), ),
image: `${MailPoet.cdnUrl}automation/sections/customize-your-workflow.png`, image: `${MailPoet.cdnUrl}automation/sections/customize-your-automation.png`,
}, },
{ {
slug: 'design-your-email', slug: 'design-your-email',

View File

@ -1,11 +1,11 @@
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components'; import { Button } from '@wordpress/components';
import { MailPoet } from '../../mailpoet'; import { MailPoet } from '../../mailpoet';
import { workflowTemplates } from '../templates/config'; import { automationTemplates } from '../templates/config';
import { TemplateListItem } from '../templates/components/template-list-item'; import { TemplateListItem } from '../templates/components/template-list-item';
export function TemplatesSection(): JSX.Element { export function TemplatesSection(): JSX.Element {
const templates = workflowTemplates.slice(0, 3); const templates = automationTemplates.slice(0, 3);
return ( return (
<section className="mailpoet-automation-section"> <section className="mailpoet-automation-section">

View File

@ -2,7 +2,7 @@ import { useState } from 'react';
import { Button } from '@wordpress/components'; import { Button } from '@wordpress/components';
import { addQueryArgs } from '@wordpress/url'; import { addQueryArgs } from '@wordpress/url';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { WorkflowTemplate } from '../config'; import { AutomationTemplate } from '../config';
import { useMutation } from '../../api'; import { useMutation } from '../../api';
import { MailPoet } from '../../../mailpoet'; import { MailPoet } from '../../../mailpoet';
import { Notice } from '../../../notices/notice'; import { Notice } from '../../../notices/notice';
@ -12,7 +12,7 @@ import {
} from '../../../common/premium_modal'; } from '../../../common/premium_modal';
type TemplateListItemProps = { type TemplateListItemProps = {
template: WorkflowTemplate; template: AutomationTemplate;
heading?: 'h2' | 'h3'; heading?: 'h2' | 'h3';
}; };
export function TemplateListItem({ export function TemplateListItem({
@ -20,8 +20,8 @@ export function TemplateListItem({
heading, heading,
}: TemplateListItemProps): JSX.Element { }: TemplateListItemProps): JSX.Element {
const [showPremium, setShowPremium] = useState(false); const [showPremium, setShowPremium] = useState(false);
const [createWorkflowFromTemplate, { loading, error, data }] = useMutation( const [createAutomationFromTemplate, { loading, error, data }] = useMutation(
'workflows/create-from-template', 'automations/create-from-template',
{ {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
@ -32,7 +32,7 @@ export function TemplateListItem({
if (!error && data) { if (!error && data) {
MailPoet.trackEvent('Automations > Template selected', { MailPoet.trackEvent('Automations > Template selected', {
'Workflow slug': template.slug, 'Automation slug': template.slug,
}); });
window.location.href = addQueryArgs(MailPoet.urls.automationEditor, { window.location.href = addQueryArgs(MailPoet.urls.automationEditor, {
id: data.data.id, id: data.data.id,
@ -66,7 +66,7 @@ export function TemplateListItem({
setShowPremium(true); setShowPremium(true);
return; return;
} }
void createWorkflowFromTemplate(); void createAutomationFromTemplate();
}} }}
> >
<div className="badge"> <div className="badge">

View File

@ -1,4 +1,4 @@
export type WorkflowTemplate = { export type AutomationTemplate = {
slug: string; slug: string;
name: string; name: string;
description: string; description: string;
@ -7,8 +7,8 @@ export type WorkflowTemplate = {
declare global { declare global {
interface Window { interface Window {
mailpoet_automation_templates: WorkflowTemplate[]; mailpoet_automation_templates: AutomationTemplate[];
} }
} }
export const workflowTemplates = window.mailpoet_automation_templates; export const automationTemplates = window.mailpoet_automation_templates;

View File

@ -1,7 +1,7 @@
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { Flex } from '@wordpress/components'; import { Flex } from '@wordpress/components';
import { workflowTemplates } from './config'; import { automationTemplates } from './config';
import { TemplateListItem } from './components/template-list-item'; import { TemplateListItem } from './components/template-list-item';
import { initializeApi } from '../api'; import { initializeApi } from '../api';
import { registerTranslations } from '../i18n'; import { registerTranslations } from '../i18n';
@ -23,7 +23,7 @@ function Templates(): JSX.Element {
</Flex> </Flex>
<ul className="mailpoet-automation-templates"> <ul className="mailpoet-automation-templates">
{workflowTemplates.map((template) => ( {automationTemplates.map((template) => (
<TemplateListItem key={template.slug} template={template} /> <TemplateListItem key={template.slug} template={template} />
))} ))}
<FromScratchListItem /> <FromScratchListItem />

View File

@ -1,6 +1,6 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { useMutation } from './api'; import { useMutation } from './api';
import { Step, Workflow } from './editor/components/workflow/types'; import { Step, Automation } from './editor/components/automation/types';
export const createRootStep = (): Step => ({ export const createRootStep = (): Step => ({
id: 'root', id: 'root',
@ -10,15 +10,15 @@ export const createRootStep = (): Step => ({
next_steps: [], next_steps: [],
}); });
const createWorkflow = (): Partial<Workflow> => ({ const createAutomation = (): Partial<Automation> => ({
name: 'Empty workflow', name: 'Empty automation',
steps: { steps: {
root: createRootStep(), root: createRootStep(),
}, },
}); });
export function CreateEmptyWorkflowButton(): JSX.Element { export function CreateEmptyAutomationButton(): JSX.Element {
const [createSchema, { loading, error }] = useMutation('workflows', { const [createSchema, { loading, error }] = useMutation('automations', {
method: 'POST', method: 'POST',
}); });
@ -29,13 +29,13 @@ export function CreateEmptyWorkflowButton(): JSX.Element {
type="button" type="button"
onClick={async () => { onClick={async () => {
await createSchema({ await createSchema({
body: JSON.stringify(createWorkflow()), body: JSON.stringify(createAutomation()),
}); });
window.location.reload(); window.location.reload();
}} }}
disabled={loading} disabled={loading}
> >
Create empty workflow (premium required) Create empty automation (premium required)
</button> </button>
{error && ( {error && (
<div>{error?.data?.message ?? 'An unknown error occurred'}</div> <div>{error?.data?.message ?? 'An unknown error occurred'}</div>
@ -49,12 +49,12 @@ type TemplateButtonProps = {
children?: ReactNode; children?: ReactNode;
}; };
export function CreateWorkflowFromTemplateButton({ export function CreateAutomationFromTemplateButton({
slug, slug,
children, children,
}: TemplateButtonProps): JSX.Element { }: TemplateButtonProps): JSX.Element {
const [createWorkflowFromTemplate, { loading, error }] = useMutation( const [createAutomationFromTemplate, { loading, error }] = useMutation(
'workflows/create-from-template', 'automations/create-from-template',
{ {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
@ -69,7 +69,7 @@ export function CreateWorkflowFromTemplateButton({
className="button button-primary" className="button button-primary"
type="button" type="button"
onClick={async () => { onClick={async () => {
await createWorkflowFromTemplate(); await createAutomationFromTemplate();
window.location.reload(); window.location.reload();
}} }}
disabled={loading} disabled={loading}

View File

@ -7,7 +7,7 @@ import { Dispatch } from 'react';
import { DropdownMenu } from '@wordpress/components'; import { DropdownMenu } from '@wordpress/components';
import { StoreConfig } from '@wordpress/data'; import { StoreConfig } from '@wordpress/data';
import { Item } from '../editor/components/inserter/item'; import { Item } from '../editor/components/inserter/item';
import { Step } from '../editor/components/workflow/types'; import { Step } from '../editor/components/automation/types';
import { State } from '../editor/store/types'; import { State } from '../editor/store/types';
export type MoreControlType = { export type MoreControlType = {
@ -20,17 +20,17 @@ export type MoreControlType = {
* APPLICATION HOOKS * APPLICATION HOOKS
*/ */
// mailpoet.automation.workflow.step.more-controls // mailpoet.automation.automation.step.more-controls
// mailpoet.automation.hero.actions // mailpoet.automation.hero.actions
export type StepMoreControlsType = Record<string, MoreControlType>; export type StepMoreControlsType = Record<string, MoreControlType>;
// mailpoet.automation.workflow.add_step_callback // mailpoet.automation.automation.add_step_callback
export type AddStepCallbackType = (item?: Item) => void; export type AddStepCallbackType = (item?: Item) => void;
// mailpoet.automation.workflow.render_step // mailpoet.automation.automation.render_step
export type RenderStepType = (step: Step) => JSX.Element; export type RenderStepType = (step: Step) => JSX.Element;
// mailpoet.automation.workflow.render_step_separator // mailpoet.automation.automation.render_step_separator
export type RenderStepSeparatorType = (step: Step) => JSX.Element; export type RenderStepSeparatorType = (step: Step) => JSX.Element;
// mailpoet.automation.editor.create_store // mailpoet.automation.editor.create_store

View File

@ -122,7 +122,7 @@ Module.SaveView = Marionette.View.extend({
'click .mailpoet_save_activate_wc_customizer_button': 'click .mailpoet_save_activate_wc_customizer_button':
'activateWooCommerceCustomizer', 'activateWooCommerceCustomizer',
/* Automation email */ /* Automation email */
'click .mailpoet_save_go_to_workflow': 'saveAndGoToWorkflow', 'click .mailpoet_save_go_to_automation': 'saveAndGoToAutomation',
'click .mailpoet_show_preview': 'showPreview', 'click .mailpoet_show_preview': 'showPreview',
}, },
@ -316,13 +316,13 @@ Module.SaveView = Marionette.View.extend({
}); });
} }
}, },
saveAndGoToWorkflow: function () { saveAndGoToAutomation: function () {
this.hideSaveOptions(); this.hideSaveOptions();
Module._cancelAutosave(); Module._cancelAutosave();
Module.save().done(function () { Module.save().done(function () {
const newsletter = App.getNewsletter(); const newsletter = App.getNewsletter();
const workflowId = newsletter.get('options').get('workflowId'); const automationId = newsletter.get('options').get('automationId');
const goToUrl = `admin.php?page=mailpoet-automation-editor&id=${workflowId}`; const goToUrl = `admin.php?page=mailpoet-automation-editor&id=${automationId}`;
window.location.href = goToUrl; window.location.href = goToUrl;
}); });
}, },

View File

@ -18,8 +18,8 @@ const renderHeading = (newsletterType, newsletterOptions) => {
window.location = `admin.php?page=mailpoet-newsletters`; window.location = `admin.php?page=mailpoet-newsletters`;
}; };
if (newsletterType === 'automation') { if (newsletterType === 'automation') {
const workflowId = newsletterOptions.workflowId; const automationId = newsletterOptions.automationId;
const goToUrl = `admin.php?page=mailpoet-automation-editor&id=${workflowId}`; const goToUrl = `admin.php?page=mailpoet-automation-editor&id=${automationId}`;
onLogoClick = () => { onLogoClick = () => {
window.location = goToUrl; window.location = goToUrl;
}; };
@ -27,7 +27,7 @@ const renderHeading = (newsletterType, newsletterOptions) => {
const onClickPreview = () => const onClickPreview = () =>
document.querySelector('.mailpoet_show_preview').click(); document.querySelector('.mailpoet_show_preview').click();
const onClickSave = () => const onClickSave = () =>
document.querySelector('.mailpoet_save_go_to_workflow').click(); document.querySelector('.mailpoet_save_go_to_automation').click();
buttons = ( buttons = (
<> <>
<input <input

View File

@ -203,11 +203,11 @@ class NewsletterSendComponent extends Component {
: null; : null;
const item = response.data; const item = response.data;
// Automation type emails should redirect // Automation type emails should redirect
// to an associated workflow from the send page // to an associated automation from the send page
if (item.type === 'automation') { if (item.type === 'automation') {
const workflowId = item.options?.workflowId; const automationId = item.options?.automationId;
const goToUrl = workflowId const goToUrl = automationId
? `admin.php?page=mailpoet-automation-editor&id=${workflowId}` ? `admin.php?page=mailpoet-automation-editor&id=${automationId}`
: '/new'; : '/new';
return this.setState( return this.setState(
{ {

View File

@ -317,9 +317,9 @@ class NewsletterTemplates extends Component {
let buttons = null; let buttons = null;
let onClick; let onClick;
if (this.state.emailType === 'automation') { if (this.state.emailType === 'automation') {
const workflowId = this.state.emailOptions?.workflowId; const automationId = this.state.emailOptions?.automationId;
const goToUrl = workflowId const goToUrl = automationId
? `admin.php?page=mailpoet-automation-editor&id=${workflowId}` ? `admin.php?page=mailpoet-automation-editor&id=${automationId}`
: 'admin.php?page=mailpoet-automation'; : 'admin.php?page=mailpoet-automation';
onClick = () => { onClick = () => {
window.location = goToUrl; window.location = goToUrl;

View File

@ -3,10 +3,10 @@
namespace MailPoet\AdminPages\Pages; namespace MailPoet\AdminPages\Pages;
use MailPoet\AdminPages\PageRenderer; use MailPoet\AdminPages\PageRenderer;
use MailPoet\Automation\Engine\Data\WorkflowTemplate; use MailPoet\Automation\Engine\Data\AutomationTemplate;
use MailPoet\Automation\Engine\Migrations\Migrator; use MailPoet\Automation\Engine\Migrations\Migrator;
use MailPoet\Automation\Engine\Storage\WorkflowStorage; use MailPoet\Automation\Engine\Storage\AutomationStorage;
use MailPoet\Automation\Engine\Storage\WorkflowTemplateStorage; use MailPoet\Automation\Engine\Storage\AutomationTemplateStorage;
use MailPoet\Form\AssetsController; use MailPoet\Form\AssetsController;
use MailPoet\WP\Functions as WPFunctions; use MailPoet\WP\Functions as WPFunctions;
@ -23,10 +23,10 @@ class Automation {
/** @var WPFunctions */ /** @var WPFunctions */
private $wp; private $wp;
/** @var WorkflowStorage */ /** @var AutomationStorage */
private $workflowStorage; private $automationStorage;
/** @var WorkflowTemplateStorage */ /** @var AutomationTemplateStorage */
private $templateStorage; private $templateStorage;
public function __construct( public function __construct(
@ -34,14 +34,14 @@ class Automation {
Migrator $migrator, Migrator $migrator,
PageRenderer $pageRenderer, PageRenderer $pageRenderer,
WPFunctions $wp, WPFunctions $wp,
WorkflowStorage $workflowStorage, AutomationStorage $automationStorage,
WorkflowTemplateStorage $templateStorage AutomationTemplateStorage $templateStorage
) { ) {
$this->assetsController = $assetsController; $this->assetsController = $assetsController;
$this->migrator = $migrator; $this->migrator = $migrator;
$this->pageRenderer = $pageRenderer; $this->pageRenderer = $pageRenderer;
$this->wp = $wp; $this->wp = $wp;
$this->workflowStorage = $workflowStorage; $this->automationStorage = $automationStorage;
$this->templateStorage = $templateStorage; $this->templateStorage = $templateStorage;
} }
@ -56,9 +56,9 @@ class Automation {
'root' => rtrim($this->wp->escUrlRaw($this->wp->restUrl()), '/'), 'root' => rtrim($this->wp->escUrlRaw($this->wp->restUrl()), '/'),
'nonce' => $this->wp->wpCreateNonce('wp_rest'), 'nonce' => $this->wp->wpCreateNonce('wp_rest'),
], ],
'workflowCount' => $this->workflowStorage->getWorkflowCount(), 'automationCount' => $this->automationStorage->getAutomationCount(),
'templates' => array_map( 'templates' => array_map(
function(WorkflowTemplate $template): array { function(AutomationTemplate $template): array {
return $template->toArray(); return $template->toArray();
}, },
$this->templateStorage->getTemplates() $this->templateStorage->getTemplates()

View File

@ -3,11 +3,11 @@
namespace MailPoet\AdminPages\Pages; namespace MailPoet\AdminPages\Pages;
use MailPoet\AdminPages\PageRenderer; use MailPoet\AdminPages\PageRenderer;
use MailPoet\Automation\Engine\Data\Workflow; use MailPoet\Automation\Engine\Data\Automation;
use MailPoet\Automation\Engine\Hooks; use MailPoet\Automation\Engine\Hooks;
use MailPoet\Automation\Engine\Mappers\WorkflowMapper; use MailPoet\Automation\Engine\Mappers\AutomationMapper;
use MailPoet\Automation\Engine\Registry; use MailPoet\Automation\Engine\Registry;
use MailPoet\Automation\Engine\Storage\WorkflowStorage; use MailPoet\Automation\Engine\Storage\AutomationStorage;
use MailPoet\Form\AssetsController; use MailPoet\Form\AssetsController;
use MailPoet\Segments\SegmentsRepository; use MailPoet\Segments\SegmentsRepository;
use MailPoet\WP\Functions as WPFunctions; use MailPoet\WP\Functions as WPFunctions;
@ -17,11 +17,11 @@ class AutomationEditor {
/** @var AssetsController */ /** @var AssetsController */
private $assetsController; private $assetsController;
/** @var WorkflowMapper */ /** @var AutomationMapper */
private $workflowMapper; private $automationMapper;
/** @var WorkflowStorage */ /** @var AutomationStorage */
private $workflowStorage; private $automationStorage;
/** @var PageRenderer */ /** @var PageRenderer */
private $pageRenderer; private $pageRenderer;
@ -37,16 +37,16 @@ class AutomationEditor {
public function __construct( public function __construct(
AssetsController $assetsController, AssetsController $assetsController,
WorkflowMapper $workflowMapper, AutomationMapper $automationMapper,
WorkflowStorage $workflowStorage, AutomationStorage $automationStorage,
PageRenderer $pageRenderer, PageRenderer $pageRenderer,
Registry $registry, Registry $registry,
SegmentsRepository $segmentsRepository, SegmentsRepository $segmentsRepository,
WPFunctions $wp WPFunctions $wp
) { ) {
$this->assetsController = $assetsController; $this->assetsController = $assetsController;
$this->workflowMapper = $workflowMapper; $this->automationMapper = $automationMapper;
$this->workflowStorage = $workflowStorage; $this->automationStorage = $automationStorage;
$this->pageRenderer = $pageRenderer; $this->pageRenderer = $pageRenderer;
$this->registry = $registry; $this->registry = $registry;
$this->segmentsRepository = $segmentsRepository; $this->segmentsRepository = $segmentsRepository;
@ -60,8 +60,8 @@ class AutomationEditor {
$this->wp->doAction(Hooks::EDITOR_BEFORE_LOAD, (int)$id); $this->wp->doAction(Hooks::EDITOR_BEFORE_LOAD, (int)$id);
$workflow = $id ? $this->workflowStorage->getWorkflow($id) : null; $automation = $id ? $this->automationStorage->getAutomation($id) : null;
if (!$workflow) { if (!$automation) {
$notice = new WPNotice( $notice = new WPNotice(
WPNotice::TYPE_ERROR, WPNotice::TYPE_ERROR,
__('Automation not found.', 'mailpoet') __('Automation not found.', 'mailpoet')
@ -71,7 +71,7 @@ class AutomationEditor {
return; return;
} }
if ($workflow->getStatus() === Workflow::STATUS_TRASH) { if ($automation->getStatus() === Automation::STATUS_TRASH) {
$this->wp->wpSafeRedirect($this->wp->adminUrl('admin.php?page=mailpoet-automation&status=trash')); $this->wp->wpSafeRedirect($this->wp->adminUrl('admin.php?page=mailpoet-automation&status=trash'));
exit(); exit();
} }
@ -83,7 +83,7 @@ class AutomationEditor {
$roles = new \WP_Roles(); $roles = new \WP_Roles();
$this->pageRenderer->displayPage('automation/editor.html', [ $this->pageRenderer->displayPage('automation/editor.html', [
'context' => $this->buildContext(), 'context' => $this->buildContext(),
'workflow' => $this->workflowMapper->buildWorkflow($workflow), 'automation' => $this->automationMapper->buildAutomation($automation),
'sub_menu' => 'mailpoet-automation', 'sub_menu' => 'mailpoet-automation',
'api' => [ 'api' => [
'root' => rtrim($this->wp->escUrlRaw($this->wp->restUrl()), '/'), 'root' => rtrim($this->wp->escUrlRaw($this->wp->restUrl()), '/'),

View File

@ -3,8 +3,8 @@
namespace MailPoet\AdminPages\Pages; namespace MailPoet\AdminPages\Pages;
use MailPoet\AdminPages\PageRenderer; use MailPoet\AdminPages\PageRenderer;
use MailPoet\Automation\Engine\Data\WorkflowTemplate; use MailPoet\Automation\Engine\Data\AutomationTemplate;
use MailPoet\Automation\Engine\Storage\WorkflowTemplateStorage; use MailPoet\Automation\Engine\Storage\AutomationTemplateStorage;
use MailPoet\Form\AssetsController; use MailPoet\Form\AssetsController;
use MailPoet\WP\Functions as WPFunctions; use MailPoet\WP\Functions as WPFunctions;
@ -15,7 +15,7 @@ class AutomationTemplates {
/** @var PageRenderer */ /** @var PageRenderer */
private $pageRenderer; private $pageRenderer;
/** @var WorkflowTemplateStorage */ /** @var AutomationTemplateStorage */
private $templateStorage; private $templateStorage;
/** @var WPFunctions */ /** @var WPFunctions */
@ -24,7 +24,7 @@ class AutomationTemplates {
public function __construct( public function __construct(
AssetsController $assetsController, AssetsController $assetsController,
PageRenderer $pageRenderer, PageRenderer $pageRenderer,
WorkflowTemplateStorage $templateStorage, AutomationTemplateStorage $templateStorage,
WPFunctions $wp WPFunctions $wp
) { ) {
$this->assetsController = $assetsController; $this->assetsController = $assetsController;
@ -45,7 +45,7 @@ class AutomationTemplates {
'nonce' => $this->wp->wpCreateNonce('wp_rest'), 'nonce' => $this->wp->wpCreateNonce('wp_rest'),
], ],
'templates' => array_map( 'templates' => array_map(
function(WorkflowTemplate $template): array { function(AutomationTemplate $template): array {
return $template->toArray(); return $template->toArray();
}, },
$this->templateStorage->getTemplates() $this->templateStorage->getTemplates()

View File

@ -2,9 +2,9 @@
namespace MailPoet\Analytics; namespace MailPoet\Analytics;
use MailPoet\Automation\Engine\Data\Automation;
use MailPoet\Automation\Engine\Data\Step; use MailPoet\Automation\Engine\Data\Step;
use MailPoet\Automation\Engine\Data\Workflow; use MailPoet\Automation\Engine\Storage\AutomationStorage;
use MailPoet\Automation\Engine\Storage\WorkflowStorage;
use MailPoet\Config\ServicesChecker; use MailPoet\Config\ServicesChecker;
use MailPoet\Cron\CronTrigger; use MailPoet\Cron\CronTrigger;
use MailPoet\Entities\DynamicSegmentFilterData; use MailPoet\Entities\DynamicSegmentFilterData;
@ -77,8 +77,8 @@ class Reporter {
/** @var FeaturesController */ /** @var FeaturesController */
private $featuresController; private $featuresController;
/** @var WorkflowStorage */ /** @var AutomationStorage */
private $workflowStorage; private $automationStorage;
public function __construct( public function __construct(
NewslettersRepository $newslettersRepository, NewslettersRepository $newslettersRepository,
@ -93,7 +93,7 @@ class Reporter {
TrackingConfig $trackingConfig, TrackingConfig $trackingConfig,
SubscriberListingRepository $subscriberListingRepository, SubscriberListingRepository $subscriberListingRepository,
FeaturesController $featuresController, FeaturesController $featuresController,
WorkflowStorage $workflowStorage AutomationStorage $automationStorage
) { ) {
$this->newslettersRepository = $newslettersRepository; $this->newslettersRepository = $newslettersRepository;
$this->segmentsRepository = $segmentsRepository; $this->segmentsRepository = $segmentsRepository;
@ -107,7 +107,7 @@ class Reporter {
$this->trackingConfig = $trackingConfig; $this->trackingConfig = $trackingConfig;
$this->subscriberListingRepository = $subscriberListingRepository; $this->subscriberListingRepository = $subscriberListingRepository;
$this->featuresController = $featuresController; $this->featuresController = $featuresController;
$this->workflowStorage = $workflowStorage; $this->automationStorage = $automationStorage;
} }
public function getData() { public function getData() {
@ -235,39 +235,39 @@ class Reporter {
return []; return [];
} }
$workflows = $this->workflowStorage->getWorkflows(); $automations = $this->automationStorage->getAutomations();
$activeWorkflows = array_filter( $activeAutomations = array_filter(
$workflows, $automations,
function(Workflow $workflow): bool { function(Automation $automation): bool {
return $workflow->getStatus() === Workflow::STATUS_ACTIVE; return $automation->getStatus() === Automation::STATUS_ACTIVE;
} }
); );
$activeWorkflowCount = count($activeWorkflows); $activeAutomationCount = count($activeAutomations);
$draftWorkflows = array_filter( $draftAutomations = array_filter(
$workflows, $automations,
function(Workflow $workflow): bool { function(Automation $automation): bool {
return $workflow->getStatus() === Workflow::STATUS_DRAFT; return $automation->getStatus() === Automation::STATUS_DRAFT;
} }
); );
$workflowsWithWordPressUserSubscribesTrigger = array_filter( $automationsWithWordPressUserSubscribesTrigger = array_filter(
$activeWorkflows, $activeAutomations,
function(Workflow $workflow): bool { function(Automation $automation): bool {
return $workflow->getTrigger('mailpoet:wp-user-registered') !== null; return $automation->getTrigger('mailpoet:wp-user-registered') !== null;
} }
); );
$workflowsWithSomeoneSubscribesTrigger = array_filter( $automationsWithSomeoneSubscribesTrigger = array_filter(
$activeWorkflows, $activeAutomations,
function(Workflow $workflow): bool { function(Automation $automation): bool {
return $workflow->getTrigger('mailpoet:someone-subscribes') !== null; return $automation->getTrigger('mailpoet:someone-subscribes') !== null;
} }
); );
$totalSteps = 0; $totalSteps = 0;
$minSteps = null; $minSteps = null;
$maxSteps = 0; $maxSteps = 0;
foreach ($activeWorkflows as $workflow) { foreach ($activeAutomations as $automation) {
$steps = array_filter( $steps = array_filter(
$workflow->getSteps(), $automation->getSteps(),
function(Step $step): bool { function(Step $step): bool {
return $step->getType() === Step::TYPE_ACTION; return $step->getType() === Step::TYPE_ACTION;
} }
@ -277,16 +277,16 @@ class Reporter {
$maxSteps = max($maxSteps, $stepCount); $maxSteps = max($maxSteps, $stepCount);
$totalSteps += $stepCount; $totalSteps += $stepCount;
} }
$averageSteps = $activeWorkflowCount > 0 ? $totalSteps / $activeWorkflowCount : 0; $averageSteps = $activeAutomationCount > 0 ? $totalSteps / $activeAutomationCount : 0;
return [ return [
'Automation > Number of active workflows' => $activeWorkflowCount, 'Automation > Number of active automations' => $activeAutomationCount,
'Automation > Number of draft workflows' => count($draftWorkflows), 'Automation > Number of draft automations' => count($draftAutomations),
'Automation > Number of "WordPress user registers" active workflows' => count($workflowsWithWordPressUserSubscribesTrigger), 'Automation > Number of "WordPress user registers" active automations' => count($automationsWithWordPressUserSubscribesTrigger),
'Automation > Number of "Someone subscribes" active workflows ' => count($workflowsWithSomeoneSubscribesTrigger), 'Automation > Number of "Someone subscribes" active automations ' => count($automationsWithSomeoneSubscribesTrigger),
'Automation > Number of steps in shortest active workflow' => $minSteps, 'Automation > Number of steps in shortest active automation' => $minSteps,
'Automation > Number of steps in longest active workflow' => $maxSteps, 'Automation > Number of steps in longest active automation' => $maxSteps,
'Automation > Average number of steps in active workflows' => $averageSteps, 'Automation > Average number of steps in active automations' => $averageSteps,
]; ];
} }

View File

@ -6,9 +6,7 @@ use MailPoet\API\REST\API as MailPoetApi;
use MailPoet\Automation\Engine\Hooks; use MailPoet\Automation\Engine\Hooks;
use MailPoet\Automation\Engine\WordPress; use MailPoet\Automation\Engine\WordPress;
class API { class API extends MailPoetApi {
private const PREFIX = 'automation/';
/** @var MailPoetApi */ /** @var MailPoetApi */
private $api; private $api;
@ -30,22 +28,22 @@ class API {
} }
public function registerGetRoute(string $route, string $endpoint): void { public function registerGetRoute(string $route, string $endpoint): void {
$this->api->registerGetRoute(self::PREFIX . $route, $endpoint); $this->api->registerGetRoute($route, $endpoint);
} }
public function registerPostRoute(string $route, string $endpoint): void { public function registerPostRoute(string $route, string $endpoint): void {
$this->api->registerPostRoute(self::PREFIX . $route, $endpoint); $this->api->registerPostRoute($route, $endpoint);
} }
public function registerPutRoute(string $route, string $endpoint): void { public function registerPutRoute(string $route, string $endpoint): void {
$this->api->registerPutRoute(self::PREFIX . $route, $endpoint); $this->api->registerPutRoute($route, $endpoint);
} }
public function registerPatchRoute(string $route, string $endpoint): void { public function registerPatchRoute(string $route, string $endpoint): void {
$this->api->registerPatchRoute(self::PREFIX . $route, $endpoint); $this->api->registerPatchRoute($route, $endpoint);
} }
public function registerDeleteRoute(string $route, string $endpoint): void { public function registerDeleteRoute(string $route, string $endpoint): void {
$this->api->registerDeleteRoute(self::PREFIX . $route, $endpoint); $this->api->registerDeleteRoute($route, $endpoint);
} }
} }

View File

@ -0,0 +1,47 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Builder;
use MailPoet\Automation\Engine\Data\Automation;
use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
use MailPoet\Automation\Engine\Storage\AutomationStorage;
use MailPoet\Automation\Engine\Storage\AutomationTemplateStorage;
use MailPoet\Automation\Engine\Validation\AutomationValidator;
class CreateAutomationFromTemplateController {
/** @var AutomationStorage */
private $storage;
/** @var AutomationTemplateStorage */
private $templateStorage;
/** @var AutomationValidator */
private $automationValidator;
public function __construct(
AutomationStorage $storage,
AutomationTemplateStorage $templateStorage,
AutomationValidator $automationValidator
) {
$this->storage = $storage;
$this->templateStorage = $templateStorage;
$this->automationValidator = $automationValidator;
}
public function createAutomation(string $slug): Automation {
$template = $this->templateStorage->getTemplateBySlug($slug);
if (!$template) {
throw Exceptions::automationTemplateNotFound($slug);
}
$automation = $template->getAutomation();
$this->automationValidator->validate($automation);
$automationId = $this->storage->createAutomation($automation);
$savedAutomation = $this->storage->getAutomation($automationId);
if (!$savedAutomation) {
throw new InvalidStateException('Automation not found.');
}
return $savedAutomation;
}
}

View File

@ -1,47 +0,0 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Builder;
use MailPoet\Automation\Engine\Data\Workflow;
use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
use MailPoet\Automation\Engine\Storage\WorkflowStorage;
use MailPoet\Automation\Engine\Storage\WorkflowTemplateStorage;
use MailPoet\Automation\Engine\Validation\WorkflowValidator;
class CreateWorkflowFromTemplateController {
/** @var WorkflowStorage */
private $storage;
/** @var WorkflowTemplateStorage */
private $templateStorage;
/** @var WorkflowValidator */
private $workflowValidator;
public function __construct(
WorkflowStorage $storage,
WorkflowTemplateStorage $templateStorage,
WorkflowValidator $workflowValidator
) {
$this->storage = $storage;
$this->templateStorage = $templateStorage;
$this->workflowValidator = $workflowValidator;
}
public function createWorkflow(string $slug): Workflow {
$template = $this->templateStorage->getTemplateBySlug($slug);
if (!$template) {
throw Exceptions::workflowTemplateNotFound($slug);
}
$workflow = $template->getWorkflow();
$this->workflowValidator->validate($workflow);
$workflowId = $this->storage->createWorkflow($workflow);
$savedWorkflow = $this->storage->getWorkflow($workflowId);
if (!$savedWorkflow) {
throw new InvalidStateException('Automation not found.');
}
return $savedWorkflow;
}
}

View File

@ -0,0 +1,32 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Builder;
use MailPoet\Automation\Engine\Data\Automation;
use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Storage\AutomationStorage;
class DeleteAutomationController {
/** @var AutomationStorage */
private $automationStorage;
public function __construct(
AutomationStorage $automationStorage
) {
$this->automationStorage = $automationStorage;
}
public function deleteAutomation(int $id): Automation {
$automation = $this->automationStorage->getAutomation($id);
if (!$automation) {
throw Exceptions::automationNotFound($id);
}
if ($automation->getStatus() !== Automation::STATUS_TRASH) {
throw Exceptions::automationNotTrashed($id);
}
$this->automationStorage->deleteAutomation($automation);
return $automation;
}
}

View File

@ -1,32 +0,0 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Builder;
use MailPoet\Automation\Engine\Data\Workflow;
use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Storage\WorkflowStorage;
class DeleteWorkflowController {
/** @var WorkflowStorage */
private $workflowStorage;
public function __construct(
WorkflowStorage $workflowStorage
) {
$this->workflowStorage = $workflowStorage;
}
public function deleteWorkflow(int $id): Workflow {
$workflow = $this->workflowStorage->getWorkflow($id);
if (!$workflow) {
throw Exceptions::workflowNotFound($id);
}
if ($workflow->getStatus() !== Workflow::STATUS_TRASH) {
throw Exceptions::workflowNotTrashed($id);
}
$this->workflowStorage->deleteWorkflow($workflow);
return $workflow;
}
}

View File

@ -2,55 +2,55 @@
namespace MailPoet\Automation\Engine\Builder; namespace MailPoet\Automation\Engine\Builder;
use MailPoet\Automation\Engine\Data\Automation;
use MailPoet\Automation\Engine\Data\NextStep; use MailPoet\Automation\Engine\Data\NextStep;
use MailPoet\Automation\Engine\Data\Step; use MailPoet\Automation\Engine\Data\Step;
use MailPoet\Automation\Engine\Data\Workflow;
use MailPoet\Automation\Engine\Exceptions; use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Exceptions\InvalidStateException; use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
use MailPoet\Automation\Engine\Storage\WorkflowStorage; use MailPoet\Automation\Engine\Storage\AutomationStorage;
use MailPoet\Automation\Engine\WordPress; use MailPoet\Automation\Engine\WordPress;
use MailPoet\Util\Security; use MailPoet\Util\Security;
class DuplicateWorkflowController { class DuplicateAutomationController {
/** @var WordPress */ /** @var WordPress */
private $wordPress; private $wordPress;
/** @var WorkflowStorage */ /** @var AutomationStorage */
private $workflowStorage; private $automationStorage;
public function __construct( public function __construct(
WordPress $wordPress, WordPress $wordPress,
WorkflowStorage $workflowStorage AutomationStorage $automationStorage
) { ) {
$this->wordPress = $wordPress; $this->wordPress = $wordPress;
$this->workflowStorage = $workflowStorage; $this->automationStorage = $automationStorage;
} }
public function duplicateWorkflow(int $id): Workflow { public function duplicateAutomation(int $id): Automation {
$workflow = $this->workflowStorage->getWorkflow($id); $automation = $this->automationStorage->getAutomation($id);
if (!$workflow) { if (!$automation) {
throw Exceptions::workflowNotFound($id); throw Exceptions::automationNotFound($id);
} }
$duplicate = new Workflow( $duplicate = new Automation(
$this->getName($workflow->getName()), $this->getName($automation->getName()),
$this->getSteps($workflow->getSteps()), $this->getSteps($automation->getSteps()),
$this->wordPress->wpGetCurrentUser() $this->wordPress->wpGetCurrentUser()
); );
$duplicate->setStatus(Workflow::STATUS_DRAFT); $duplicate->setStatus(Automation::STATUS_DRAFT);
$workflowId = $this->workflowStorage->createWorkflow($duplicate); $automationId = $this->automationStorage->createAutomation($duplicate);
$savedWorkflow = $this->workflowStorage->getWorkflow($workflowId); $savedAutomation = $this->automationStorage->getAutomation($automationId);
if (!$savedWorkflow) { if (!$savedAutomation) {
throw new InvalidStateException('Workflow not found.'); throw new InvalidStateException('Automation not found.');
} }
return $savedWorkflow; return $savedAutomation;
} }
private function getName(string $name): string { private function getName(string $name): string {
// translators: %s is the original automation name. // translators: %s is the original automation name.
$newName = sprintf(__('Copy of %s', 'mailpoet'), $name); $newName = sprintf(__('Copy of %s', 'mailpoet'), $name);
$maxLength = $this->workflowStorage->getNameColumnLength(); $maxLength = $this->automationStorage->getNameColumnLength();
if (strlen($newName) > $maxLength) { if (strlen($newName) > $maxLength) {
$append = '…'; $append = '…';
return substr($newName, 0, $maxLength - strlen($append)) . $append; return substr($newName, 0, $maxLength - strlen($append)) . $append;

View File

@ -0,0 +1,137 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Builder;
use MailPoet\Automation\Engine\Data\Automation;
use MailPoet\Automation\Engine\Data\Step;
use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Exceptions\UnexpectedValueException;
use MailPoet\Automation\Engine\Hooks;
use MailPoet\Automation\Engine\Storage\AutomationStatisticsStorage;
use MailPoet\Automation\Engine\Storage\AutomationStorage;
use MailPoet\Automation\Engine\Validation\AutomationValidator;
class UpdateAutomationController {
/** @var Hooks */
private $hooks;
/** @var AutomationStorage */
private $storage;
/** @var AutomationStatisticsStorage */
private $statisticsStorage;
/** @var AutomationValidator */
private $automationValidator;
/** @var UpdateStepsController */
private $updateStepsController;
public function __construct(
Hooks $hooks,
AutomationStorage $storage,
AutomationStatisticsStorage $statisticsStorage,
AutomationValidator $automationValidator,
UpdateStepsController $updateStepsController
) {
$this->hooks = $hooks;
$this->storage = $storage;
$this->statisticsStorage = $statisticsStorage;
$this->automationValidator = $automationValidator;
$this->updateStepsController = $updateStepsController;
}
public function updateAutomation(int $id, array $data): Automation {
$automation = $this->storage->getAutomation($id);
if (!$automation) {
throw Exceptions::automationNotFound($id);
}
$this->validateIfAutomationCanBeUpdated($automation, $data);
if (array_key_exists('name', $data)) {
$automation->setName($data['name']);
}
if (array_key_exists('status', $data)) {
$this->checkAutomationStatus($data['status']);
$automation->setStatus($data['status']);
}
if (array_key_exists('steps', $data)) {
$this->validateAutomationSteps($automation, $data['steps']);
$this->updateStepsController->updateSteps($automation, $data['steps']);
foreach ($automation->getSteps() as $step) {
$this->hooks->doAutomationStepBeforeSave($step);
$this->hooks->doAutomationStepByKeyBeforeSave($step);
}
}
$this->hooks->doAutomationBeforeSave($automation);
$this->automationValidator->validate($automation);
$this->storage->updateAutomation($automation);
$automation = $this->storage->getAutomation($id);
if (!$automation) {
throw Exceptions::automationNotFound($id);
}
return $automation;
}
/**
* This is a temporary validation, see MAILPOET-4744
*/
private function validateIfAutomationCanBeUpdated(Automation $automation, array $data): void {
if (
!in_array(
$automation->getStatus(),
[
Automation::STATUS_ACTIVE,
Automation::STATUS_DEACTIVATING,
],
true
)
) {
return;
}
$statistics = $this->statisticsStorage->getAutomationStats($automation->getId());
if ($statistics->getInProgress() === 0) {
return;
}
if (!isset($data['status']) || $data['status'] === $automation->getStatus()) {
throw Exceptions::automationHasActiveRuns($automation->getId());
}
}
private function checkAutomationStatus(string $status): void {
if (!in_array($status, Automation::STATUS_ALL, true)) {
// translators: %s is the status.
throw UnexpectedValueException::create()->withMessage(sprintf(__('Invalid status: %s', 'mailpoet'), $status));
}
}
protected function validateAutomationSteps(Automation $automation, array $steps): void {
$existingSteps = $automation->getSteps();
if (count($steps) !== count($existingSteps)) {
throw Exceptions::automationStructureModificationNotSupported();
}
foreach ($steps as $id => $data) {
$existingStep = $existingSteps[$id] ?? null;
if (!$existingStep || !$this->stepChanged(Step::fromArray($data), $existingStep)) {
throw Exceptions::automationStructureModificationNotSupported();
}
}
}
private function stepChanged(Step $a, Step $b): bool {
$aData = $a->toArray();
$bData = $b->toArray();
unset($aData['args']);
unset($bData['args']);
return $aData === $bData;
}
}

View File

@ -2,8 +2,8 @@
namespace MailPoet\Automation\Engine\Builder; namespace MailPoet\Automation\Engine\Builder;
use MailPoet\Automation\Engine\Data\Automation;
use MailPoet\Automation\Engine\Data\Step; use MailPoet\Automation\Engine\Data\Step;
use MailPoet\Automation\Engine\Data\Workflow;
use MailPoet\Automation\Engine\Exceptions; use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Registry; use MailPoet\Automation\Engine\Registry;
@ -17,21 +17,21 @@ class UpdateStepsController {
$this->registry = $registry; $this->registry = $registry;
} }
public function updateSteps(Workflow $workflow, array $data): Workflow { public function updateSteps(Automation $automation, array $data): Automation {
$steps = []; $steps = [];
foreach ($data as $index => $stepData) { foreach ($data as $index => $stepData) {
$step = $this->processStep($stepData, $workflow->getStep($stepData['id'])); $step = $this->processStep($stepData, $automation->getStep($stepData['id']));
$steps[$index] = $step; $steps[$index] = $step;
} }
$workflow->setSteps($steps); $automation->setSteps($steps);
return $workflow; return $automation;
} }
private function processStep(array $data, ?Step $existingStep): Step { private function processStep(array $data, ?Step $existingStep): Step {
$key = $data['key']; $key = $data['key'];
$step = $this->registry->getStep($key); $step = $this->registry->getStep($key);
if (!$step && $existingStep && $data !== $existingStep->toArray()) { if (!$step && $existingStep && $data !== $existingStep->toArray()) {
throw Exceptions::workflowStepNotFound($key); throw Exceptions::automationStepNotFound($key);
} }
return Step::fromArray($data); return Step::fromArray($data);
} }

View File

@ -1,137 +0,0 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Builder;
use MailPoet\Automation\Engine\Data\Step;
use MailPoet\Automation\Engine\Data\Workflow;
use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Exceptions\UnexpectedValueException;
use MailPoet\Automation\Engine\Hooks;
use MailPoet\Automation\Engine\Storage\WorkflowStatisticsStorage;
use MailPoet\Automation\Engine\Storage\WorkflowStorage;
use MailPoet\Automation\Engine\Validation\WorkflowValidator;
class UpdateWorkflowController {
/** @var Hooks */
private $hooks;
/** @var WorkflowStorage */
private $storage;
/** @var WorkflowStatisticsStorage */
private $statisticsStorage;
/** @var WorkflowValidator */
private $workflowValidator;
/** @var UpdateStepsController */
private $updateStepsController;
public function __construct(
Hooks $hooks,
WorkflowStorage $storage,
WorkflowStatisticsStorage $statisticsStorage,
WorkflowValidator $workflowValidator,
UpdateStepsController $updateStepsController
) {
$this->hooks = $hooks;
$this->storage = $storage;
$this->statisticsStorage = $statisticsStorage;
$this->workflowValidator = $workflowValidator;
$this->updateStepsController = $updateStepsController;
}
public function updateWorkflow(int $id, array $data): Workflow {
$workflow = $this->storage->getWorkflow($id);
if (!$workflow) {
throw Exceptions::workflowNotFound($id);
}
$this->validateIfWorkflowCanBeUpdated($workflow, $data);
if (array_key_exists('name', $data)) {
$workflow->setName($data['name']);
}
if (array_key_exists('status', $data)) {
$this->checkWorkflowStatus($data['status']);
$workflow->setStatus($data['status']);
}
if (array_key_exists('steps', $data)) {
$this->validateWorkflowSteps($workflow, $data['steps']);
$this->updateStepsController->updateSteps($workflow, $data['steps']);
foreach ($workflow->getSteps() as $step) {
$this->hooks->doWorkflowStepBeforeSave($step);
$this->hooks->doWorkflowStepByKeyBeforeSave($step);
}
}
$this->hooks->doWorkflowBeforeSave($workflow);
$this->workflowValidator->validate($workflow);
$this->storage->updateWorkflow($workflow);
$workflow = $this->storage->getWorkflow($id);
if (!$workflow) {
throw Exceptions::workflowNotFound($id);
}
return $workflow;
}
/**
* This is a temporary validation, see MAILPOET-4744
*/
private function validateIfWorkflowCanBeUpdated(Workflow $workflow, array $data): void {
if (
!in_array(
$workflow->getStatus(),
[
Workflow::STATUS_ACTIVE,
Workflow::STATUS_DEACTIVATING,
],
true
)
) {
return;
}
$statistics = $this->statisticsStorage->getWorkflowStats($workflow->getId());
if ($statistics->getInProgress() === 0) {
return;
}
if (!isset($data['status']) || $data['status'] === $workflow->getStatus()) {
throw Exceptions::workflowHasActiveRuns($workflow->getId());
}
}
private function checkWorkflowStatus(string $status): void {
if (!in_array($status, Workflow::STATUS_ALL, true)) {
// translators: %s is the status.
throw UnexpectedValueException::create()->withMessage(sprintf(__('Invalid status: %s', 'mailpoet'), $status));
}
}
protected function validateWorkflowSteps(Workflow $workflow, array $steps): void {
$existingSteps = $workflow->getSteps();
if (count($steps) !== count($existingSteps)) {
throw Exceptions::workflowStructureModificationNotSupported();
}
foreach ($steps as $id => $data) {
$existingStep = $existingSteps[$id] ?? null;
if (!$existingStep || !$this->stepChanged(Step::fromArray($data), $existingStep)) {
throw Exceptions::workflowStructureModificationNotSupported();
}
}
}
private function stepChanged(Step $a, Step $b): bool {
$aData = $a->toArray();
$bData = $b->toArray();
unset($aData['args']);
unset($bData['args']);
return $aData === $bData;
}
}

View File

@ -4,13 +4,13 @@ namespace MailPoet\Automation\Engine\Control;
use Exception; use Exception;
use MailPoet\Automation\Engine\Control\Steps\ActionStepRunner; use MailPoet\Automation\Engine\Control\Steps\ActionStepRunner;
use MailPoet\Automation\Engine\Data\Automation;
use MailPoet\Automation\Engine\Data\AutomationRun;
use MailPoet\Automation\Engine\Data\AutomationRunLog;
use MailPoet\Automation\Engine\Data\Step; use MailPoet\Automation\Engine\Data\Step;
use MailPoet\Automation\Engine\Data\StepRunArgs; use MailPoet\Automation\Engine\Data\StepRunArgs;
use MailPoet\Automation\Engine\Data\StepValidationArgs; use MailPoet\Automation\Engine\Data\StepValidationArgs;
use MailPoet\Automation\Engine\Data\SubjectEntry; use MailPoet\Automation\Engine\Data\SubjectEntry;
use MailPoet\Automation\Engine\Data\Workflow;
use MailPoet\Automation\Engine\Data\WorkflowRun;
use MailPoet\Automation\Engine\Data\WorkflowRunLog;
use MailPoet\Automation\Engine\Exceptions; use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Exceptions\InvalidStateException; use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
use MailPoet\Automation\Engine\Hooks; use MailPoet\Automation\Engine\Hooks;
@ -18,9 +18,9 @@ use MailPoet\Automation\Engine\Integration\Action;
use MailPoet\Automation\Engine\Integration\Payload; use MailPoet\Automation\Engine\Integration\Payload;
use MailPoet\Automation\Engine\Integration\Subject; use MailPoet\Automation\Engine\Integration\Subject;
use MailPoet\Automation\Engine\Registry; use MailPoet\Automation\Engine\Registry;
use MailPoet\Automation\Engine\Storage\WorkflowRunLogStorage; use MailPoet\Automation\Engine\Storage\AutomationRunLogStorage;
use MailPoet\Automation\Engine\Storage\WorkflowRunStorage; use MailPoet\Automation\Engine\Storage\AutomationRunStorage;
use MailPoet\Automation\Engine\Storage\WorkflowStorage; use MailPoet\Automation\Engine\Storage\AutomationStorage;
use MailPoet\Automation\Engine\WordPress; use MailPoet\Automation\Engine\WordPress;
use Throwable; use Throwable;
@ -37,17 +37,17 @@ class StepHandler {
/** @var WordPress */ /** @var WordPress */
private $wordPress; private $wordPress;
/** @var WorkflowRunStorage */ /** @var AutomationRunStorage */
private $workflowRunStorage; private $automationRunStorage;
/** @var WorkflowStorage */ /** @var AutomationStorage */
private $workflowStorage; private $automationStorage;
/** @var array<string, StepRunner> */ /** @var array<string, StepRunner> */
private $stepRunners = []; private $stepRunners = [];
/** @var WorkflowRunLogStorage */ /** @var AutomationRunLogStorage */
private $workflowRunLogStorage; private $automationRunLogStorage;
/** @var Hooks */ /** @var Hooks */
private $hooks; private $hooks;
@ -61,9 +61,9 @@ class StepHandler {
Hooks $hooks, Hooks $hooks,
SubjectLoader $subjectLoader, SubjectLoader $subjectLoader,
WordPress $wordPress, WordPress $wordPress,
WorkflowRunStorage $workflowRunStorage, AutomationRunStorage $automationRunStorage,
WorkflowRunLogStorage $workflowRunLogStorage, AutomationRunLogStorage $automationRunLogStorage,
WorkflowStorage $workflowStorage, AutomationStorage $automationStorage,
Registry $registry Registry $registry
) { ) {
$this->actionScheduler = $actionScheduler; $this->actionScheduler = $actionScheduler;
@ -71,14 +71,14 @@ class StepHandler {
$this->hooks = $hooks; $this->hooks = $hooks;
$this->subjectLoader = $subjectLoader; $this->subjectLoader = $subjectLoader;
$this->wordPress = $wordPress; $this->wordPress = $wordPress;
$this->workflowRunStorage = $workflowRunStorage; $this->automationRunStorage = $automationRunStorage;
$this->workflowRunLogStorage = $workflowRunLogStorage; $this->automationRunLogStorage = $automationRunLogStorage;
$this->workflowStorage = $workflowStorage; $this->automationStorage = $automationStorage;
$this->registry = $registry; $this->registry = $registry;
} }
public function initialize(): void { public function initialize(): void {
$this->wordPress->addAction(Hooks::WORKFLOW_STEP, [$this, 'handle']); $this->wordPress->addAction(Hooks::AUTOMATION_STEP, [$this, 'handle']);
$this->addStepRunner(Step::TYPE_ACTION, $this->actionStepRunner); $this->addStepRunner(Step::TYPE_ACTION, $this->actionStepRunner);
$this->wordPress->doAction(Hooks::STEP_RUNNER_INITIALIZE, [$this]); $this->wordPress->doAction(Hooks::STEP_RUNNER_INITIALIZE, [$this]);
} }
@ -110,57 +110,57 @@ class StepHandler {
try { try {
$this->handleStep($args); $this->handleStep($args);
} catch (Throwable $e) { } catch (Throwable $e) {
$status = $e instanceof InvalidStateException && $e->getErrorCode() === 'mailpoet_automation_workflow_not_active' ? WorkflowRun::STATUS_CANCELLED : WorkflowRun::STATUS_FAILED; $status = $e instanceof InvalidStateException && $e->getErrorCode() === 'mailpoet_automation_automation_not_active' ? AutomationRun::STATUS_CANCELLED : AutomationRun::STATUS_FAILED;
$this->workflowRunStorage->updateStatus((int)$args['workflow_run_id'], $status); $this->automationRunStorage->updateStatus((int)$args['automation_run_id'], $status);
$this->postProcessWorkflowRun((int)$args['workflow_run_id']); $this->postProcessAutomationRun((int)$args['automation_run_id']);
if (!$e instanceof Exception) { if (!$e instanceof Exception) {
throw new Exception($e->getMessage(), intval($e->getCode()), $e); throw new Exception($e->getMessage(), intval($e->getCode()), $e);
} }
throw $e; throw $e;
} }
$this->postProcessWorkflowRun((int)$args['workflow_run_id']); $this->postProcessAutomationRun((int)$args['automation_run_id']);
} }
private function handleStep(array $args): void { private function handleStep(array $args): void {
$workflowRunId = $args['workflow_run_id']; $automationRunId = $args['automation_run_id'];
$stepId = $args['step_id']; $stepId = $args['step_id'];
$workflowRun = $this->workflowRunStorage->getWorkflowRun($workflowRunId); $automationRun = $this->automationRunStorage->getAutomationRun($automationRunId);
if (!$workflowRun) { if (!$automationRun) {
throw Exceptions::workflowRunNotFound($workflowRunId); throw Exceptions::automationRunNotFound($automationRunId);
} }
if ($workflowRun->getStatus() !== WorkflowRun::STATUS_RUNNING) { if ($automationRun->getStatus() !== AutomationRun::STATUS_RUNNING) {
throw Exceptions::workflowRunNotRunning($workflowRunId, $workflowRun->getStatus()); throw Exceptions::automationRunNotRunning($automationRunId, $automationRun->getStatus());
} }
$workflow = $this->workflowStorage->getWorkflow($workflowRun->getWorkflowId(), $workflowRun->getVersionId()); $automation = $this->automationStorage->getAutomation($automationRun->getAutomationId(), $automationRun->getVersionId());
if (!$workflow) { if (!$automation) {
throw Exceptions::workflowVersionNotFound($workflowRun->getWorkflowId(), $workflowRun->getVersionId()); throw Exceptions::automationVersionNotFound($automationRun->getAutomationId(), $automationRun->getVersionId());
} }
if (!in_array($workflow->getStatus(), [Workflow::STATUS_ACTIVE, Workflow::STATUS_DEACTIVATING], true)) { if (!in_array($automation->getStatus(), [Automation::STATUS_ACTIVE, Automation::STATUS_DEACTIVATING], true)) {
throw Exceptions::workflowNotActive($workflowRun->getWorkflowId()); throw Exceptions::automationNotActive($automationRun->getAutomationId());
} }
// complete workflow run // complete automation run
if (!$stepId) { if (!$stepId) {
$this->workflowRunStorage->updateStatus($workflowRunId, WorkflowRun::STATUS_COMPLETE); $this->automationRunStorage->updateStatus($automationRunId, AutomationRun::STATUS_COMPLETE);
return; return;
} }
$stepData = $workflow->getStep($stepId); $stepData = $automation->getStep($stepId);
if (!$stepData) { if (!$stepData) {
throw Exceptions::workflowStepNotFound($stepId); throw Exceptions::automationStepNotFound($stepId);
} }
$step = $this->registry->getStep($stepData->getKey()); $step = $this->registry->getStep($stepData->getKey());
$stepType = $stepData->getType(); $stepType = $stepData->getType();
if (isset($this->stepRunners[$stepType])) { if (isset($this->stepRunners[$stepType])) {
$log = new WorkflowRunLog($workflowRun->getId(), $stepData->getId()); $log = new AutomationRunLog($automationRun->getId(), $stepData->getId());
try { try {
$requiredSubjects = $step instanceof Action ? $step->getSubjectKeys() : []; $requiredSubjects = $step instanceof Action ? $step->getSubjectKeys() : [];
$subjectEntries = $this->getSubjectEntries($workflowRun, $requiredSubjects); $subjectEntries = $this->getSubjectEntries($automationRun, $requiredSubjects);
$args = new StepRunArgs($workflow, $workflowRun, $stepData, $subjectEntries); $args = new StepRunArgs($automation, $automationRun, $stepData, $subjectEntries);
$validationArgs = new StepValidationArgs($workflow, $stepData, array_map(function (SubjectEntry $entry) { $validationArgs = new StepValidationArgs($automation, $stepData, array_map(function (SubjectEntry $entry) {
return $entry->getSubject(); return $entry->getSubject();
}, $subjectEntries)); }, $subjectEntries));
$this->stepRunners[$stepType]->run($args, $validationArgs); $this->stepRunners[$stepType]->run($args, $validationArgs);
@ -171,11 +171,11 @@ class StepHandler {
throw $e; throw $e;
} finally { } finally {
try { try {
$this->hooks->doWorkflowStepAfterRun($log); $this->hooks->doAutomationStepAfterRun($log);
} catch (Throwable $e) { } catch (Throwable $e) {
// Ignore integration errors // Ignore integration errors
} }
$this->workflowRunLogStorage->createWorkflowRunLog($log); $this->automationRunLogStorage->createAutomationRunLog($log);
} }
} else { } else {
throw new InvalidStateException(); throw new InvalidStateException();
@ -184,33 +184,33 @@ class StepHandler {
$nextStep = $stepData->getNextSteps()[0] ?? null; $nextStep = $stepData->getNextSteps()[0] ?? null;
$nextStepArgs = [ $nextStepArgs = [
[ [
'workflow_run_id' => $workflowRunId, 'automation_run_id' => $automationRunId,
'step_id' => $nextStep ? $nextStep->getId() : null, 'step_id' => $nextStep ? $nextStep->getId() : null,
], ],
]; ];
$this->workflowRunStorage->updateNextStep($workflowRunId, $nextStep ? $nextStep->getId() : null); $this->automationRunStorage->updateNextStep($automationRunId, $nextStep ? $nextStep->getId() : null);
// next step scheduled by action // next step scheduled by action
if ($this->actionScheduler->hasScheduledAction(Hooks::WORKFLOW_STEP, $nextStepArgs)) { if ($this->actionScheduler->hasScheduledAction(Hooks::AUTOMATION_STEP, $nextStepArgs)) {
return; return;
} }
// no need to schedule a new step if the next step is null, complete the run // no need to schedule a new step if the next step is null, complete the run
if (!$nextStep) { if (!$nextStep) {
$this->workflowRunStorage->updateStatus($workflowRunId, WorkflowRun::STATUS_COMPLETE); $this->automationRunStorage->updateStatus($automationRunId, AutomationRun::STATUS_COMPLETE);
return; return;
} }
// enqueue next step // enqueue next step
$this->actionScheduler->enqueue(Hooks::WORKFLOW_STEP, $nextStepArgs); $this->actionScheduler->enqueue(Hooks::AUTOMATION_STEP, $nextStepArgs);
// TODO: allow long-running steps (that are not done here yet) // TODO: allow long-running steps (that are not done here yet)
} }
/** @return SubjectEntry<Subject<Payload>>[] */ /** @return SubjectEntry<Subject<Payload>>[] */
private function getSubjectEntries(WorkflowRun $workflowRun, array $requiredSubjectKeys): array { private function getSubjectEntries(AutomationRun $automationRun, array $requiredSubjectKeys): array {
$subjectDataMap = []; $subjectDataMap = [];
foreach ($workflowRun->getSubjects() as $data) { foreach ($automationRun->getSubjects() as $data) {
$subjectDataMap[$data->getKey()] = array_merge($subjectDataMap[$data->getKey()] ?? [], [$data]); $subjectDataMap[$data->getKey()] = array_merge($subjectDataMap[$data->getKey()] ?? [], [$data]);
} }
@ -218,7 +218,7 @@ class StepHandler {
foreach ($requiredSubjectKeys as $key) { foreach ($requiredSubjectKeys as $key) {
$subjectData = $subjectDataMap[$key] ?? null; $subjectData = $subjectDataMap[$key] ?? null;
if (!$subjectData) { if (!$subjectData) {
throw Exceptions::subjectDataNotFound($key, $workflowRun->getId()); throw Exceptions::subjectDataNotFound($key, $automationRun->getId());
} }
foreach ($subjectData as $data) { foreach ($subjectData as $data) {
$subjectEntries[] = $this->subjectLoader->getSubjectEntry($data); $subjectEntries[] = $this->subjectLoader->getSubjectEntry($data);
@ -227,26 +227,26 @@ class StepHandler {
return $subjectEntries; return $subjectEntries;
} }
private function postProcessWorkflowRun(int $workflowRunId): void { private function postProcessAutomationRun(int $automationRunId): void {
$workflowRun = $this->workflowRunStorage->getWorkflowRun($workflowRunId); $automationRun = $this->automationRunStorage->getAutomationRun($automationRunId);
if (!$workflowRun) { if (!$automationRun) {
return; return;
} }
$workflow = $this->workflowStorage->getWorkflow($workflowRun->getWorkflowId()); $automation = $this->automationStorage->getAutomation($automationRun->getAutomationId());
if (!$workflow) { if (!$automation) {
return; return;
} }
$this->postProcessWorkflow($workflow); $this->postProcessAutomation($automation);
} }
private function postProcessWorkflow(Workflow $workflow): void { private function postProcessAutomation(Automation $automation): void {
if ($workflow->getStatus() === Workflow::STATUS_DEACTIVATING) { if ($automation->getStatus() === Automation::STATUS_DEACTIVATING) {
$activeRuns = $this->workflowRunStorage->getCountForWorkflow($workflow, WorkflowRun::STATUS_RUNNING); $activeRuns = $this->automationRunStorage->getCountForAutomation($automation, AutomationRun::STATUS_RUNNING);
// Set a deactivating Workflow to draft once all workflow runs are finished. // Set a deactivating Automation to draft once all automation runs are finished.
if ($activeRuns === 0) { if ($activeRuns === 0) {
$workflow->setStatus(Workflow::STATUS_DRAFT); $automation->setStatus(Automation::STATUS_DRAFT);
$this->workflowStorage->updateWorkflow($workflow); $this->automationStorage->updateAutomation($automation);
} }
} }
} }

View File

@ -2,14 +2,14 @@
namespace MailPoet\Automation\Engine\Control; namespace MailPoet\Automation\Engine\Control;
use MailPoet\Automation\Engine\Data\AutomationRun;
use MailPoet\Automation\Engine\Data\StepRunArgs; use MailPoet\Automation\Engine\Data\StepRunArgs;
use MailPoet\Automation\Engine\Data\Subject; use MailPoet\Automation\Engine\Data\Subject;
use MailPoet\Automation\Engine\Data\WorkflowRun;
use MailPoet\Automation\Engine\Exceptions; use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Hooks; use MailPoet\Automation\Engine\Hooks;
use MailPoet\Automation\Engine\Integration\Trigger; use MailPoet\Automation\Engine\Integration\Trigger;
use MailPoet\Automation\Engine\Storage\WorkflowRunStorage; use MailPoet\Automation\Engine\Storage\AutomationRunStorage;
use MailPoet\Automation\Engine\Storage\WorkflowStorage; use MailPoet\Automation\Engine\Storage\AutomationStorage;
use MailPoet\Automation\Engine\WordPress; use MailPoet\Automation\Engine\WordPress;
class TriggerHandler { class TriggerHandler {
@ -22,23 +22,23 @@ class TriggerHandler {
/** @var WordPress */ /** @var WordPress */
private $wordPress; private $wordPress;
/** @var WorkflowStorage */ /** @var AutomationStorage */
private $workflowStorage; private $automationStorage;
/** @var WorkflowRunStorage */ /** @var AutomationRunStorage */
private $workflowRunStorage; private $automationRunStorage;
public function __construct( public function __construct(
ActionScheduler $actionScheduler, ActionScheduler $actionScheduler,
SubjectLoader $subjectLoader, SubjectLoader $subjectLoader,
WordPress $wordPress, WordPress $wordPress,
WorkflowStorage $workflowStorage, AutomationStorage $automationStorage,
WorkflowRunStorage $workflowRunStorage AutomationRunStorage $automationRunStorage
) { ) {
$this->actionScheduler = $actionScheduler; $this->actionScheduler = $actionScheduler;
$this->wordPress = $wordPress; $this->wordPress = $wordPress;
$this->workflowStorage = $workflowStorage; $this->automationStorage = $automationStorage;
$this->workflowRunStorage = $workflowRunStorage; $this->automationRunStorage = $automationRunStorage;
$this->subjectLoader = $subjectLoader; $this->subjectLoader = $subjectLoader;
} }
@ -48,11 +48,11 @@ class TriggerHandler {
/** @param Subject[] $subjects */ /** @param Subject[] $subjects */
public function processTrigger(Trigger $trigger, array $subjects): void { public function processTrigger(Trigger $trigger, array $subjects): void {
$workflows = $this->workflowStorage->getActiveWorkflowsByTrigger($trigger); $automations = $this->automationStorage->getActiveAutomationsByTrigger($trigger);
foreach ($workflows as $workflow) { foreach ($automations as $automation) {
$step = $workflow->getTrigger($trigger->getKey()); $step = $automation->getTrigger($trigger->getKey());
if (!$step) { if (!$step) {
throw Exceptions::workflowTriggerNotFound($workflow->getId(), $trigger->getKey()); throw Exceptions::automationTriggerNotFound($automation->getId(), $trigger->getKey());
} }
// ensure subjects are registered and loadable // ensure subjects are registered and loadable
@ -61,21 +61,21 @@ class TriggerHandler {
$entry->getPayload(); $entry->getPayload();
} }
$workflowRun = new WorkflowRun($workflow->getId(), $workflow->getVersionId(), $trigger->getKey(), $subjects); $automationRun = new AutomationRun($automation->getId(), $automation->getVersionId(), $trigger->getKey(), $subjects);
if (!$trigger->isTriggeredBy(new StepRunArgs($workflow, $workflowRun, $step, $subjectEntries))) { if (!$trigger->isTriggeredBy(new StepRunArgs($automation, $automationRun, $step, $subjectEntries))) {
return; return;
} }
$workflowRunId = $this->workflowRunStorage->createWorkflowRun($workflowRun); $automationRunId = $this->automationRunStorage->createAutomationRun($automationRun);
$nextStep = $step->getNextSteps()[0] ?? null; $nextStep = $step->getNextSteps()[0] ?? null;
$this->actionScheduler->enqueue(Hooks::WORKFLOW_STEP, [ $this->actionScheduler->enqueue(Hooks::AUTOMATION_STEP, [
[ [
'workflow_run_id' => $workflowRunId, 'automation_run_id' => $automationRunId,
'step_id' => $nextStep ? $nextStep->getId() : null, 'step_id' => $nextStep ? $nextStep->getId() : null,
], ],
]); ]);
$this->workflowRunStorage->updateNextStep($workflowRunId, $nextStep ? $nextStep->getId() : null); $this->automationRunStorage->updateNextStep($automationRunId, $nextStep ? $nextStep->getId() : null);
} }
} }
} }

View File

@ -6,7 +6,7 @@ use DateTimeImmutable;
use MailPoet\Automation\Engine\Exceptions\InvalidStateException; use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
use MailPoet\Automation\Engine\Utils\Json; use MailPoet\Automation\Engine\Utils\Json;
class Workflow { class Automation {
public const STATUS_ACTIVE = 'active'; public const STATUS_ACTIVE = 'active';
public const STATUS_DEACTIVATING = 'deactivating'; public const STATUS_DEACTIVATING = 'deactivating';
public const STATUS_DRAFT = 'draft'; public const STATUS_DRAFT = 'draft';
@ -66,14 +66,14 @@ class Workflow {
public function getId(): int { public function getId(): int {
if (!$this->id) { if (!$this->id) {
throw InvalidStateException::create()->withMessage('No workflow ID was set'); throw InvalidStateException::create()->withMessage('No automation ID was set');
} }
return $this->id; return $this->id;
} }
public function getVersionId(): int { public function getVersionId(): int {
if (!$this->versionId) { if (!$this->versionId) {
throw InvalidStateException::create()->withMessage('No workflow version ID was set'); throw InvalidStateException::create()->withMessage('No automation version ID was set');
} }
return $this->versionId; return $this->versionId;
} }
@ -139,7 +139,7 @@ class Workflow {
return null; return null;
} }
public function equals(Workflow $compare): bool { public function equals(Automation $compare): bool {
$compareArray = $compare->toArray(); $compareArray = $compare->toArray();
$currentArray = $this->toArray(); $currentArray = $this->toArray();
$ignoreValues = [ $ignoreValues = [
@ -154,7 +154,7 @@ class Workflow {
} }
public function needsFullValidation(): bool { public function needsFullValidation(): bool {
return in_array($this->status, [Workflow::STATUS_ACTIVE, Workflow::STATUS_DEACTIVATING], true); return in_array($this->status, [Automation::STATUS_ACTIVE, Automation::STATUS_DEACTIVATING], true);
} }
public function toArray(): array { public function toArray(): array {
@ -180,19 +180,19 @@ class Workflow {
public static function fromArray(array $data): self { public static function fromArray(array $data): self {
// TODO: validation // TODO: validation
$workflow = new self( $automation = new self(
$data['name'], $data['name'],
array_map(function (array $stepData): Step { array_map(function (array $stepData): Step {
return Step::fromArray($stepData); return Step::fromArray($stepData);
}, Json::decode($data['steps'])), }, Json::decode($data['steps'])),
new \WP_User((int)$data['author']) new \WP_User((int)$data['author'])
); );
$workflow->id = (int)$data['id']; $automation->id = (int)$data['id'];
$workflow->versionId = (int)$data['version_id']; $automation->versionId = (int)$data['version_id'];
$workflow->status = $data['status']; $automation->status = $data['status'];
$workflow->createdAt = new DateTimeImmutable($data['created_at']); $automation->createdAt = new DateTimeImmutable($data['created_at']);
$workflow->updatedAt = new DateTimeImmutable($data['updated_at']); $automation->updatedAt = new DateTimeImmutable($data['updated_at']);
$workflow->activatedAt = $data['activated_at'] !== null ? new DateTimeImmutable($data['activated_at']) : null; $automation->activatedAt = $data['activated_at'] !== null ? new DateTimeImmutable($data['activated_at']) : null;
return $workflow; return $automation;
} }
} }

View File

@ -5,7 +5,7 @@ namespace MailPoet\Automation\Engine\Data;
use DateTimeImmutable; use DateTimeImmutable;
use MailPoet\Automation\Engine\Utils\Json; use MailPoet\Automation\Engine\Utils\Json;
class WorkflowRun { class AutomationRun {
public const STATUS_RUNNING = 'running'; public const STATUS_RUNNING = 'running';
public const STATUS_COMPLETE = 'complete'; public const STATUS_COMPLETE = 'complete';
public const STATUS_CANCELLED = 'cancelled'; public const STATUS_CANCELLED = 'cancelled';
@ -15,7 +15,7 @@ class WorkflowRun {
private $id; private $id;
/** @var int */ /** @var int */
private $workflowId; private $automationId;
/** @var int */ /** @var int */
private $versionId; private $versionId;
@ -39,13 +39,13 @@ class WorkflowRun {
* @param Subject[] $subjects * @param Subject[] $subjects
*/ */
public function __construct( public function __construct(
int $workflowId, int $automationId,
int $versionId, int $versionId,
string $triggerKey, string $triggerKey,
array $subjects, array $subjects,
int $id = null int $id = null
) { ) {
$this->workflowId = $workflowId; $this->automationId = $automationId;
$this->versionId = $versionId; $this->versionId = $versionId;
$this->triggerKey = $triggerKey; $this->triggerKey = $triggerKey;
$this->subjects = $subjects; $this->subjects = $subjects;
@ -63,8 +63,8 @@ class WorkflowRun {
return $this->id; return $this->id;
} }
public function getWorkflowId(): int { public function getAutomationId(): int {
return $this->workflowId; return $this->automationId;
} }
public function getVersionId(): int { public function getVersionId(): int {
@ -101,7 +101,7 @@ class WorkflowRun {
public function toArray(): array { public function toArray(): array {
return [ return [
'workflow_id' => $this->workflowId, 'automation_id' => $this->automationId,
'version_id' => $this->versionId, 'version_id' => $this->versionId,
'trigger_key' => $this->triggerKey, 'trigger_key' => $this->triggerKey,
'status' => $this->status, 'status' => $this->status,
@ -116,18 +116,18 @@ class WorkflowRun {
} }
public static function fromArray(array $data): self { public static function fromArray(array $data): self {
$workflowRun = new WorkflowRun( $automationRun = new AutomationRun(
(int)$data['workflow_id'], (int)$data['automation_id'],
(int)$data['version_id'], (int)$data['version_id'],
$data['trigger_key'], $data['trigger_key'],
array_map(function (array $subject) { array_map(function (array $subject) {
return Subject::fromArray($subject); return Subject::fromArray($subject);
}, Json::decode($data['subjects'])) }, Json::decode($data['subjects']))
); );
$workflowRun->id = (int)$data['id']; $automationRun->id = (int)$data['id'];
$workflowRun->status = $data['status']; $automationRun->status = $data['status'];
$workflowRun->createdAt = new DateTimeImmutable($data['created_at']); $automationRun->createdAt = new DateTimeImmutable($data['created_at']);
$workflowRun->updatedAt = new DateTimeImmutable($data['updated_at']); $automationRun->updatedAt = new DateTimeImmutable($data['updated_at']);
return $workflowRun; return $automationRun;
} }
} }

View File

@ -7,7 +7,7 @@ use InvalidArgumentException;
use MailPoet\Automation\Engine\Utils\Json; use MailPoet\Automation\Engine\Utils\Json;
use Throwable; use Throwable;
class WorkflowRunLog { class AutomationRunLog {
const STATUS_RUNNING = 'running'; const STATUS_RUNNING = 'running';
const STATUS_COMPLETED = 'completed'; const STATUS_COMPLETED = 'completed';
@ -17,7 +17,7 @@ class WorkflowRunLog {
private $id; private $id;
/** @var int */ /** @var int */
private $workflowRunId; private $automationRunId;
/** @var DateTimeImmutable */ /** @var DateTimeImmutable */
private $startedAt; private $startedAt;
@ -38,11 +38,11 @@ class WorkflowRunLog {
private $stepId; private $stepId;
public function __construct( public function __construct(
int $workflowRunId, int $automationRunId,
string $stepId, string $stepId,
int $id = null int $id = null
) { ) {
$this->workflowRunId = $workflowRunId; $this->automationRunId = $automationRunId;
$this->stepId = $stepId; $this->stepId = $stepId;
$this->status = self::STATUS_RUNNING; $this->status = self::STATUS_RUNNING;
@ -60,8 +60,8 @@ class WorkflowRunLog {
return $this->id; return $this->id;
} }
public function getWorkflowRunId(): int { public function getAutomationRunId(): int {
return $this->workflowRunId; return $this->automationRunId;
} }
public function getStepId(): string { public function getStepId(): string {
@ -102,7 +102,7 @@ class WorkflowRunLog {
public function toArray(): array { public function toArray(): array {
return [ return [
'workflow_run_id' => $this->workflowRunId, 'automation_run_id' => $this->automationRunId,
'step_id' => $this->stepId, 'step_id' => $this->stepId,
'status' => $this->status, 'status' => $this->status,
'started_at' => $this->startedAt->format(DateTimeImmutable::W3C), 'started_at' => $this->startedAt->format(DateTimeImmutable::W3C),
@ -134,18 +134,18 @@ class WorkflowRunLog {
} }
public static function fromArray(array $data): self { public static function fromArray(array $data): self {
$workflowRunLog = new WorkflowRunLog((int)$data['workflow_run_id'], $data['step_id']); $automationRunLog = new AutomationRunLog((int)$data['automation_run_id'], $data['step_id']);
$workflowRunLog->id = (int)$data['id']; $automationRunLog->id = (int)$data['id'];
$workflowRunLog->status = $data['status']; $automationRunLog->status = $data['status'];
$workflowRunLog->error = Json::decode($data['error']); $automationRunLog->error = Json::decode($data['error']);
$workflowRunLog->data = Json::decode($data['data']); $automationRunLog->data = Json::decode($data['data']);
$workflowRunLog->startedAt = new DateTimeImmutable($data['started_at']); $automationRunLog->startedAt = new DateTimeImmutable($data['started_at']);
if ($data['completed_at']) { if ($data['completed_at']) {
$workflowRunLog->completedAt = new DateTimeImmutable($data['completed_at']); $automationRunLog->completedAt = new DateTimeImmutable($data['completed_at']);
} }
return $workflowRunLog; return $automationRunLog;
} }
/** /**

View File

@ -2,27 +2,27 @@
namespace MailPoet\Automation\Engine\Data; namespace MailPoet\Automation\Engine\Data;
class WorkflowStatistics { class AutomationStatistics {
private $workflowId; private $automationId;
private $versionId; private $versionId;
private $entered; private $entered;
private $inProgress; private $inProgress;
public function __construct( public function __construct(
int $workflowId, int $automationId,
int $entered = 0, int $entered = 0,
int $inProcess = 0, int $inProcess = 0,
?int $versionId = null ?int $versionId = null
) { ) {
$this->workflowId = $workflowId; $this->automationId = $automationId;
$this->entered = $entered; $this->entered = $entered;
$this->inProgress = $inProcess; $this->inProgress = $inProcess;
$this->versionId = $versionId; $this->versionId = $versionId;
} }
public function getWorkflowId(): int { public function getAutomationId(): int {
return $this->workflowId; return $this->automationId;
} }
public function getVersionId(): ?int { public function getVersionId(): ?int {
@ -43,7 +43,7 @@ class WorkflowStatistics {
public function toArray(): array { public function toArray(): array {
return [ return [
'workflow_id' => $this->getWorkflowId(), 'automation_id' => $this->getAutomationId(),
'totals' => [ 'totals' => [
'entered' => $this->getEntered(), 'entered' => $this->getEntered(),
'in_progress' => $this->getInProgress(), 'in_progress' => $this->getInProgress(),

View File

@ -4,7 +4,7 @@ namespace MailPoet\Automation\Engine\Data;
use MailPoet\RuntimeException; use MailPoet\RuntimeException;
class WorkflowTemplate { class AutomationTemplate {
public const TYPE_DEFAULT = 'default'; public const TYPE_DEFAULT = 'default';
public const TYPE_FREE_ONLY = 'free-only'; public const TYPE_FREE_ONLY = 'free-only';
@ -34,14 +34,14 @@ class WorkflowTemplate {
/** @var string */ /** @var string */
private $description; private $description;
/** @var Workflow */ /** @var Automation */
private $workflow; private $automation;
public function __construct( public function __construct(
string $slug, string $slug,
int $category, int $category,
string $description, string $description,
Workflow $workflow, Automation $automation,
string $type = self::TYPE_DEFAULT string $type = self::TYPE_DEFAULT
) { ) {
if (!in_array($category, self::ALL_CATEGORIES)) { if (!in_array($category, self::ALL_CATEGORIES)) {
@ -50,7 +50,7 @@ class WorkflowTemplate {
$this->slug = $slug; $this->slug = $slug;
$this->category = $category; $this->category = $category;
$this->description = $description; $this->description = $description;
$this->workflow = $workflow; $this->automation = $automation;
$this->type = $type; $this->type = $type;
} }
@ -59,7 +59,7 @@ class WorkflowTemplate {
} }
public function getName(): string { public function getName(): string {
return $this->workflow->getName(); return $this->automation->getName();
} }
public function getCategory(): int { public function getCategory(): int {
@ -74,8 +74,8 @@ class WorkflowTemplate {
return $this->description; return $this->description;
} }
public function getWorkflow(): Workflow { public function getAutomation(): Automation {
return $this->workflow; return $this->automation;
} }
public function toArray(): array { public function toArray(): array {
@ -85,7 +85,7 @@ class WorkflowTemplate {
'category' => $this->getCategory(), 'category' => $this->getCategory(),
'type' => $this->getType(), 'type' => $this->getType(),
'description' => $this->getDescription(), 'description' => $this->getDescription(),
'workflow' => $this->getWorkflow()->toArray(), 'automation' => $this->getAutomation()->toArray(),
]; ];
} }
} }

View File

@ -8,11 +8,11 @@ use MailPoet\Automation\Engine\Integration\Payload;
use MailPoet\Automation\Engine\Integration\Subject; use MailPoet\Automation\Engine\Integration\Subject;
class StepRunArgs { class StepRunArgs {
/** @var Workflow */ /** @var Automation */
private $workflow; private $automation;
/** @var WorkflowRun */ /** @var AutomationRun */
private $workflowRun; private $automationRun;
/** @var Step */ /** @var Step */
private $step; private $step;
@ -25,14 +25,14 @@ class StepRunArgs {
/** @param SubjectEntry<Subject<Payload>>[] $subjectsEntries */ /** @param SubjectEntry<Subject<Payload>>[] $subjectsEntries */
public function __construct( public function __construct(
Workflow $workflow, Automation $automation,
WorkflowRun $workflowRun, AutomationRun $automationRun,
Step $step, Step $step,
array $subjectsEntries array $subjectsEntries
) { ) {
$this->workflow = $workflow; $this->automation = $automation;
$this->step = $step; $this->step = $step;
$this->workflowRun = $workflowRun; $this->automationRun = $automationRun;
foreach ($subjectsEntries as $entry) { foreach ($subjectsEntries as $entry) {
$subject = $entry->getSubject(); $subject = $entry->getSubject();
@ -42,12 +42,12 @@ class StepRunArgs {
} }
} }
public function getWorkflow(): Workflow { public function getAutomation(): Automation {
return $this->workflow; return $this->automation;
} }
public function getWorkflowRun(): WorkflowRun { public function getAutomationRun(): AutomationRun {
return $this->workflowRun; return $this->automationRun;
} }
public function getStep(): Step { public function getStep(): Step {
@ -58,10 +58,10 @@ class StepRunArgs {
public function getSingleSubjectEntry(string $key): SubjectEntry { public function getSingleSubjectEntry(string $key): SubjectEntry {
$subjects = $this->subjectEntries[$key] ?? []; $subjects = $this->subjectEntries[$key] ?? [];
if (count($subjects) === 0) { if (count($subjects) === 0) {
throw Exceptions::subjectDataNotFound($key, $this->workflowRun->getId()); throw Exceptions::subjectDataNotFound($key, $this->automationRun->getId());
} }
if (count($subjects) > 1) { if (count($subjects) > 1) {
throw Exceptions::multipleSubjectsFound($key, $this->workflowRun->getId()); throw Exceptions::multipleSubjectsFound($key, $this->automationRun->getId());
} }
return $subjects[0]; return $subjects[0];
} }
@ -92,7 +92,7 @@ class StepRunArgs {
$payloads = []; $payloads = [];
foreach ($this->subjectEntries as $entries) { foreach ($this->subjectEntries as $entries) {
if (count($entries) > 1) { if (count($entries) > 1) {
throw Exceptions::multiplePayloadsFound($class, $this->workflowRun->getId()); throw Exceptions::multiplePayloadsFound($class, $this->automationRun->getId());
} }
$entry = $entries[0]; $entry = $entries[0];
@ -103,10 +103,10 @@ class StepRunArgs {
} }
if (count($payloads) === 0) { if (count($payloads) === 0) {
throw Exceptions::payloadNotFound($class, $this->workflowRun->getId()); throw Exceptions::payloadNotFound($class, $this->automationRun->getId());
} }
if (count($payloads) > 1) { if (count($payloads) > 1) {
throw Exceptions::multiplePayloadsFound($class, $this->workflowRun->getId()); throw Exceptions::multiplePayloadsFound($class, $this->automationRun->getId());
} }
// ensure PHPStan we're indeed returning an instance of $class // ensure PHPStan we're indeed returning an instance of $class

View File

@ -7,8 +7,8 @@ use MailPoet\Automation\Engine\Integration\Payload;
use MailPoet\Automation\Engine\Integration\Subject; use MailPoet\Automation\Engine\Integration\Subject;
class StepValidationArgs { class StepValidationArgs {
/** @var Workflow */ /** @var Automation */
private $workflow; private $automation;
/** @var Step */ /** @var Step */
private $step; private $step;
@ -21,11 +21,11 @@ class StepValidationArgs {
/** @param Subject<Payload>[] $subjects */ /** @param Subject<Payload>[] $subjects */
public function __construct( public function __construct(
Workflow $workflow, Automation $automation,
Step $step, Step $step,
array $subjects array $subjects
) { ) {
$this->workflow = $workflow; $this->automation = $automation;
$this->step = $step; $this->step = $step;
foreach ($subjects as $subject) { foreach ($subjects as $subject) {
@ -35,8 +35,8 @@ class StepValidationArgs {
} }
} }
public function getWorkflow(): Workflow { public function getAutomation(): Automation {
return $this->workflow; return $this->automation;
} }
public function getStep(): Step { public function getStep(): Step {

View File

@ -1,29 +1,29 @@
<?php declare(strict_types = 1); <?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Endpoints\Workflows; namespace MailPoet\Automation\Engine\Endpoints\Automations;
use MailPoet\API\REST\Request; use MailPoet\API\REST\Request;
use MailPoet\API\REST\Response; use MailPoet\API\REST\Response;
use MailPoet\Automation\Engine\API\Endpoint; use MailPoet\Automation\Engine\API\Endpoint;
use MailPoet\Automation\Engine\Data\WorkflowTemplate; use MailPoet\Automation\Engine\Data\AutomationTemplate;
use MailPoet\Automation\Engine\Storage\WorkflowTemplateStorage; use MailPoet\Automation\Engine\Storage\AutomationTemplateStorage;
use MailPoet\Validator\Builder; use MailPoet\Validator\Builder;
class WorkflowTemplatesGetEndpoint extends Endpoint { class AutomationTemplatesGetEndpoint extends Endpoint {
private $storage; private $storage;
public function __construct( public function __construct(
WorkflowTemplateStorage $storage AutomationTemplateStorage $storage
) { ) {
$this->storage = $storage; $this->storage = $storage;
} }
public function handle(Request $request): Response { public function handle(Request $request): Response {
$templates = $this->storage->getTemplates((int)$request->getParam('category')); $templates = $this->storage->getTemplates((int)$request->getParam('category'));
return new Response(array_map(function (WorkflowTemplate $workflow) { return new Response(array_map(function (AutomationTemplate $automation) {
return $workflow->toArray(); return $automation->toArray();
}, $templates)); }, $templates));
} }

View File

@ -0,0 +1,37 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Endpoints\Automations;
use MailPoet\API\REST\Request;
use MailPoet\API\REST\Response;
use MailPoet\Automation\Engine\API\Endpoint;
use MailPoet\Automation\Engine\Builder\CreateAutomationFromTemplateController;
use MailPoet\Automation\Engine\Mappers\AutomationMapper;
use MailPoet\Validator\Builder;
class AutomationsCreateFromTemplateEndpoint extends Endpoint {
/** @var CreateAutomationFromTemplateController */
private $createAutomationFromTemplateController;
/** @var AutomationMapper */
private $automationMapper;
public function __construct(
CreateAutomationFromTemplateController $createAutomationFromTemplateController,
AutomationMapper $automationMapper
) {
$this->createAutomationFromTemplateController = $createAutomationFromTemplateController;
$this->automationMapper = $automationMapper;
}
public function handle(Request $request): Response {
$automation = $this->createAutomationFromTemplateController->createAutomation((string)$request->getParam('slug'));
return new Response($this->automationMapper->buildAutomation($automation));
}
public static function getRequestSchema(): array {
return [
'slug' => Builder::string()->required(),
];
}
}

View File

@ -1,26 +1,26 @@
<?php declare(strict_types = 1); <?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Endpoints\Workflows; namespace MailPoet\Automation\Engine\Endpoints\Automations;
use MailPoet\API\REST\Request; use MailPoet\API\REST\Request;
use MailPoet\API\REST\Response; use MailPoet\API\REST\Response;
use MailPoet\Automation\Engine\API\Endpoint; use MailPoet\Automation\Engine\API\Endpoint;
use MailPoet\Automation\Engine\Builder\DeleteWorkflowController; use MailPoet\Automation\Engine\Builder\DeleteAutomationController;
use MailPoet\Validator\Builder; use MailPoet\Validator\Builder;
class WorkflowsDeleteEndpoint extends Endpoint { class AutomationsDeleteEndpoint extends Endpoint {
/** @var DeleteWorkflowController */ /** @var DeleteAutomationController */
private $deleteController; private $deleteController;
public function __construct( public function __construct(
DeleteWorkflowController $deleteController DeleteAutomationController $deleteController
) { ) {
$this->deleteController = $deleteController; $this->deleteController = $deleteController;
} }
public function handle(Request $request): Response { public function handle(Request $request): Response {
$workflowId = intval($request->getParam('id')); $automationId = intval($request->getParam('id'));
$this->deleteController->deleteWorkflow($workflowId); $this->deleteController->deleteAutomation($automationId);
return new Response(null); return new Response(null);
} }

View File

@ -0,0 +1,38 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Endpoints\Automations;
use MailPoet\API\REST\Request;
use MailPoet\API\REST\Response;
use MailPoet\Automation\Engine\API\Endpoint;
use MailPoet\Automation\Engine\Builder\DuplicateAutomationController;
use MailPoet\Automation\Engine\Mappers\AutomationMapper;
use MailPoet\Validator\Builder;
class AutomationsDuplicateEndpoint extends Endpoint {
/** @var AutomationMapper */
private $automationMapper;
/** @var DuplicateAutomationController */
private $duplicateController;
public function __construct(
DuplicateAutomationController $duplicateController,
AutomationMapper $automationMapper
) {
$this->automationMapper = $automationMapper;
$this->duplicateController = $duplicateController;
}
public function handle(Request $request): Response {
$automationId = intval($request->getParam('id'));
$duplicate = $this->duplicateController->duplicateAutomation($automationId);
return new Response($this->automationMapper->buildAutomation($duplicate));
}
public static function getRequestSchema(): array {
return [
'id' => Builder::integer()->required(),
];
}
}

View File

@ -0,0 +1,38 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Endpoints\Automations;
use MailPoet\API\REST\Request;
use MailPoet\API\REST\Response;
use MailPoet\Automation\Engine\API\Endpoint;
use MailPoet\Automation\Engine\Mappers\AutomationMapper;
use MailPoet\Automation\Engine\Storage\AutomationStorage;
use MailPoet\Validator\Builder;
class AutomationsGetEndpoint extends Endpoint {
/** @var AutomationMapper */
private $automationMapper;
/** @var AutomationStorage */
private $automationStorage;
public function __construct(
AutomationMapper $automationMapper,
AutomationStorage $automationStorage
) {
$this->automationMapper = $automationMapper;
$this->automationStorage = $automationStorage;
}
public function handle(Request $request): Response {
$status = $request->getParam('status') ? (array)$request->getParam('status') : null;
$automations = $this->automationStorage->getAutomations($status);
return new Response($this->automationMapper->buildAutomationList($automations));
}
public static function getRequestSchema(): array {
return [
'status' => Builder::array(Builder::string()),
];
}
}

Some files were not shown because too many files have changed in this diff Show More