Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

956 workflows enabled once #1115

Merged
merged 5 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [UNRELEASED]

### Added

- Add new option to the Schedule workflow step to select the behavior when a duplicate scheduled action is found (Issue #956).

### Fixed

- Fix SQL syntax error in MariaDB lower than 11.6 when deleting orphan scheduled steps (Issue #1087).
Expand Down
120 changes: 75 additions & 45 deletions assets/js/workflowEditor.js

Large diffs are not rendered by default.

44 changes: 30 additions & 14 deletions assets/jsx/workflow-editor/components/data-fields/date-offset.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,31 @@ import { useState, useMemo } from "@wordpress/element";
import { useSelect } from "@wordpress/data";
import { store as editorStore } from "../editor-store";
import { FEATURE_ADVANCED_SETTINGS } from "../../constants";
import { filterVariableOptionsByDataType } from "../../utils";
import { DateOffsetPreview } from "../../../components/DateOffsetPreview";
import { Slot } from "@wordpress/components";
import ProFeatureButton from "../pro-feature-button";
import Recurrence from "./recurrence";
import ProFeatureField from "../pro-feature-field";
import ExpressionBuilder from "./expression-builder";

const duplicateHandlingOptions = [
{ name: __("Skip task if already exists", "post-expirator"), id: "skip" },
{ name: __("Create new task", "post-expirator"), id: "create-new" },
{ name: __("Replace existing task", "post-expirator"), id: "replace" },
];

const whenToRunOptions = [
{ name: __("As soon as possible", "post-expirator"), id: "now" },
{ name: __("On a specific date", "post-expirator"), id: "date" },
{ name: __("Relative to a specific date", "post-expirator"), id: "offset" },
];

let dateSourceOptions = [
{ name: __("Selected in the calendar", "post-expirator"), id: "calendar" },
{ name: __("When the trigger is activated", "post-expirator"), id: "event"},
{ name: __("When the step is activated", "post-expirator"), id: "step"},
{ name: __("Custom date source", "post-expirator"), id: "custom"},
];

/**
* When to execute:
* - now - As soon as possible after event
Expand Down Expand Up @@ -92,18 +109,6 @@ export function DateOffset({ name, label, defaultValue, onChange, variables = []
};
});

const whenToRunOptions = [
{ name: __("As soon as possible", "post-expirator"), id: "now" },
{ name: __("On a specific date", "post-expirator"), id: "date" },
{ name: __("Relative to a specific date", "post-expirator"), id: "offset" },
];

let dateSourceOptions = [
{ name: __("Selected in the calendar", "post-expirator"), id: "calendar" },
{ name: __("When the trigger is activated", "post-expirator"), id: "event"},
{ name: __("When the step is activated", "post-expirator"), id: "step"},
{ name: __("Custom date source", "post-expirator"), id: "custom"},
];

const validDateSources = ['calendar', 'event', 'step', 'custom'];
const isLegacyDateSource = !validDateSources.includes(defaultValue.dateSource);
Expand Down Expand Up @@ -269,6 +274,17 @@ export function DateOffset({ name, label, defaultValue, onChange, variables = []
defaultValue,
}} />

{! hidePreventDuplicateScheduling && (
<PanelRow>
<TreeSelect
label={__("Duplicate handling", "post-expirator")}
tree={duplicateHandlingOptions}
selectedId={defaultValue.duplicateHandling || 'skip'}
onChange={(value) => onChangeSetting({ settingName: "duplicateHandling", value })}
/>
</PanelRow>
)}

{isAdvancedSettingsEnabled && (
<>
{!hidePreventDuplicateScheduling && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,10 @@ export const NodeDetailsPanel = ({ node }) => {
<PanelRow>
<Text
name="label"
label={__("Step Label", "post-expirator")}
label={__("Description", "post-expirator")}
defaultValue={node.data.label || ''}
settings={{
placeholder: nodeType.label,
}}
onChange={onChangeLabel}
description={__("This is the label that will be displayed in the workflow editor.", "post-expirator")}
description={__("Add a brief description to help distinguish this step from similar ones in your workflow.", "post-expirator")}
/>
</PanelRow>
</PersistentPanelBody>
Expand Down
12 changes: 9 additions & 3 deletions assets/jsx/workflow-editor/components/node-types/generic.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ export const GenericNode = memo(({ id, data, isConnectable, selected, nodeTypeIc
};
}

const nodeLabel = data.label || nodeType.label || __('Node', 'post-expirator');
const nodeDescription = data?.label;
const nodeLabel = nodeType.label || __('Node', 'post-expirator');
const nodeClassName = nodeType?.className || 'react-flow__node-genericNode';

let targetHandles = null;
Expand Down Expand Up @@ -232,7 +233,7 @@ export const GenericNode = memo(({ id, data, isConnectable, selected, nodeTypeIc
{topText}
</div>

<div className='react-flow__node-inner-body'>
<div className={nodeDescription ? 'react-flow__node-inner-body with-description' : 'react-flow__node-inner-body'}>

{(nodeHasErrors || (nodeType.isProFeature && !isPro)) && (
<div className='react-flow__node-marker-wrapper'>
Expand All @@ -255,10 +256,15 @@ export const GenericNode = memo(({ id, data, isConnectable, selected, nodeTypeIc
</div>
)}

<div className='react-flow__node-header'>
<div className="react-flow__node-header">
<NodeIcon icon={nodeType.icon.src} iconSize={14} />
<div className="react-flow__node-label">{nodeLabel}</div>
</div>

{nodeDescription && (
<div className="react-flow__node-description">{nodeDescription}</div>
)}

{isAdvancedSettingsEnabled && nodeAttributes.length > 0 &&
<div className='react-flow__node-content'>
<table>
Expand Down
14 changes: 14 additions & 0 deletions assets/jsx/workflow-editor/css/node-types.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@
left: 2px;
}

.react-flow__node-description {
font-size: 9px;
color: #0000008f;
margin-top: 5px;
max-width: 140px;
background: #ffffff5c;
padding: 6px;
overflow-wrap: break-word;
}

.react-flow__node-inner-body.with-description {
padding-bottom: 4px;
}

.react-flow__node-slug {
font-size: 8px;
display: inline-block;
Expand Down
3 changes: 2 additions & 1 deletion legacy/classes/Reviews.class.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use PublishPress\Future\Core\Plugin;
use PublishPress\WordPressReviews\ReviewsController;

defined('ABSPATH') or die('Direct access not allowed.');
Expand All @@ -22,7 +23,7 @@ public static function init()
self::$reviewController = new ReviewsController(
'post-expirator',
'PublishPress Future',
POSTEXPIRATOR_BASEURL . 'assets/images/publishpress-future-256.png'
Plugin::getAssetUrl('images/publishpress-future-256.png')
);

self::$reviewController->init();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ class CronStep implements AsyncNodeRunnerProcessorInterface

public const UNSCHEDULE_FUTURE_ACTION_DELAY = 5;

public const DUPLICATE_HANDLING_SKIP = 'skip';
public const DUPLICATE_HANDLING_CREATE_NEW = 'create-new';
public const DUPLICATE_HANDLING_REPLACE = 'replace';

/**
* @var HooksFacade
*/
Expand Down Expand Up @@ -150,6 +154,7 @@ public function setup(array $step, callable $actionCallback): void

$recurrence = $nodeSettings['schedule']['recurrence'] ?? self::SCHEDULE_RECURRENCE_SINGLE;
$whenToRun = $nodeSettings['schedule']['whenToRun'] ?? self::WHEN_TO_RUN_NOW;
$duplicateHandling = $nodeSettings['schedule']['duplicateHandling'] ?? 'skip';

// Schedule
if (self::SCHEDULE_RECURRENCE_SINGLE === $recurrence && self::WHEN_TO_RUN_NOW === $whenToRun) {
Expand Down Expand Up @@ -213,14 +218,45 @@ public function setup(array $step, callable $actionCallback): void
return;
}

if ($scheduledActionsModel->hasRowWithActionUIDHash($actionUIDHash)) {
// It should be unique, so if the action is already scheduled, we don't need to schedule it again.
$this->addDebugLogMessage(
'Step %s is already scheduled based on its ID, skipping',
$stepSlug
);
switch ($duplicateHandling) {
case self::DUPLICATE_HANDLING_SKIP:
if ($scheduledActionsModel->hasRowWithActionUIDHash($actionUIDHash)) {
// It should be unique, so if the action is already scheduled we should not schedule it.
$this->addDebugLogMessage(
'Step %s is already scheduled based on its ID, skipping',
$stepSlug
);

return;
return;
}
break;

case self::DUPLICATE_HANDLING_CREATE_NEW:
// If the action is already scheduled, we should create a new one.
$this->addDebugLogMessage(
'Step %s is already scheduled based on its ID, creating a new one',
$stepSlug
);

// TODO: Make sure the action is not duplicated for the same step and execution ID.
// Is the execution ID the same for multiple calls to the same action hook?
// Use a session ID instead?
break;

case self::DUPLICATE_HANDLING_REPLACE:
// If the action is already scheduled, we should replace it.
$this->addDebugLogMessage(
'Step %s is already scheduled based on its ID, unscheduling to replace it',
$stepSlug
);

$actionId = $scheduledActionsModel->getActionIdByActionUIDHash($actionUIDHash);

if ($actionId) {
$scheduledActionsModel->cancelActionById($actionId);
}

break;
}

if ($isSingleAction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,19 @@ public function deleteOrphanWorkflowArgs(): void;

public function cancelWorkflowScheduledActions(int $workflowId): void;

public function cancelRecurringScheduledActions(int $workflowId, string $stepId): void;

public function deleteExpiredScheduledSteps(): void;

public function hasRowWithActionUIDHash(string $actionUIDHash): bool;

/**
* @since 4.3.1
*/
public function getActionIdByActionUIDHash(string $actionUIDHash): ?int;

/**
* @since 4.3.1
*/
public function cancelActionById(int $actionId): void;
}
37 changes: 34 additions & 3 deletions src/Modules/Workflows/Models/ScheduledActionsModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ public function deleteExpiredScheduledSteps(): void
}

public function hasRowWithActionUIDHash(string $actionUIDHash): bool
{
$actionId = $this->getActionIdByActionUIDHash($actionUIDHash);

return !is_null($actionId);
}

/**
* @since 4.3.1
*/
public function getActionIdByActionUIDHash(string $actionUIDHash): ?int
{
global $wpdb;

Expand All @@ -80,7 +90,7 @@ public function hasRowWithActionUIDHash(string $actionUIDHash): bool
$tableSchema = $container->get(ServicesAbstract::DB_TABLE_WORKFLOW_SCHEDULED_STEPS_SCHEMA);

if (! $tableSchema->isTableExistent()) {
return false;
return null;
}

// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
Expand All @@ -98,9 +108,11 @@ public function hasRowWithActionUIDHash(string $actionUIDHash): bool
)
);

$hasRow = !is_null($row);
if (is_null($row)) {
return null;
}

return $hasRow;
return $row->action_id;
}

public function cancelWorkflowScheduledActions(int $workflowId): void
Expand Down Expand Up @@ -163,4 +175,23 @@ public function cancelRecurringScheduledActions(int $workflowId, string $stepId)
);
// phpcs:enable
}

/**
* @since 4.3.1
*/
public function cancelActionById(int $actionId): void
{
global $wpdb;

// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->query(
$wpdb->prepare(
"UPDATE {$wpdb->prefix}actionscheduler_actions
SET status = 'canceled'
WHERE action_id = %d",
$actionId
)
);
// phpcs:enable
}
}
1 change: 1 addition & 0 deletions src/Modules/Workflows/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use PublishPress\Future\Modules\Workflows\Interfaces\NodeTypesModelInterface;
use PublishPress\Future\Modules\Workflows\Interfaces\RestApiManagerInterface;
use PublishPress\Future\Modules\Workflows\Interfaces\WorkflowEngineInterface;
use PublishPress\Future\Modules\Expirator\Models\CurrentUserModel;

class Module implements InitializableInterface
{
Expand Down
Loading