diff --git a/mailpoet/assets/css/src/components-automation-analytics/tabs/automation_flow.scss b/mailpoet/assets/css/src/components-automation-analytics/tabs/automation_flow.scss index 18aef429b6..1f974266dc 100644 --- a/mailpoet/assets/css/src/components-automation-analytics/tabs/automation_flow.scss +++ b/mailpoet/assets/css/src/components-automation-analytics/tabs/automation_flow.scss @@ -38,6 +38,39 @@ } } +.mailpoet-automation-analytics-step-failed { + bottom: 0; + display: none; + grid-column: 1 / -1; + justify-content: center; + left: -100px; + position: absolute; + + &:after { + border-top: 1px dashed $color-gutenberg-grey-600; + content: ''; + height: 0; + left: 100%; + position: absolute; + top: 15px; + width: 100px; + } + + span { + display: block; + } + + p { + padding: 0 8px; + } +} + +@media screen and (min-width: 600px) { + .mailpoet-automation-analytics-step-failed { + display: block; + } +} + .mailpoet-automation-analytics-step-footer { background: $color-wp-gray-0; border-top: 1px solid $color-poet-gray-dividers; @@ -47,10 +80,17 @@ margin-bottom: -12px; margin-left: -12px; width: calc(100% + 24px); + z-index: 1; p { - margin: 0; padding: 6px 0; + } +} + +.mailpoet-automation-analytics-step-footer, +.mailpoet-automation-analytics-step-failed { + p { + margin: 0; a { color: $color-gutenberg-grey-800; diff --git a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/automation_flow/step_footer.tsx b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/automation_flow/step_footer.tsx index 560333dddb..0aab9c1f36 100644 --- a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/automation_flow/step_footer.tsx +++ b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/components/tabs/automation_flow/step_footer.tsx @@ -7,6 +7,52 @@ import { locale } from '../../../config'; import { Step } from '../../../../../../editor/components/automation/types'; import { openTab } from '../../../navigation/open_tab'; +const compactFormatter = Intl.NumberFormat(locale.toString(), { + notation: 'compact', +}); +const percentFormatter = Intl.NumberFormat(locale.toString(), { + style: 'percent', +}); + +function FailedStep({ step }: { step: Step }): JSX.Element | null { + const { section } = useSelect( + (s) => + ({ + section: s(storeName).getSection('automation_flow'), + } as { + section: AutomationFlowSection; + }), + [], + ); + + const { data } = section; + + const failed = data.step_data?.failed; + const value = failed !== undefined ? failed[step.id] ?? 0 : 0; + if (!value) { + return null; + } + const percent = + data.step_data.total > 0 + ? Math.round((value / data.step_data.total) * 100) + : 0; + const formattedValue = compactFormatter.format(value); + const formattedPercent = percentFormatter.format(percent / 100); + return ( +
+

+ {formattedPercent} ({formattedValue}) + + { + // translators: "failed" as in "100 automation runs failed at this step". + __('failed', 'mailpoet') + } + +

+
+ ); +} + export function StepFooter({ step }: { step: Step }): JSX.Element | null { const { section } = useSelect( (s) => @@ -29,37 +75,36 @@ export function StepFooter({ step }: { step: Step }): JSX.Element | null { ? Math.round((value / data.step_data.total) * 100) : 0; - const formattedValue = Intl.NumberFormat(locale.toString(), { - notation: 'compact', - }).format(value); - const formattedPercent = Intl.NumberFormat(locale.toString(), { - style: 'percent', - }).format(percent / 100); + const formattedValue = compactFormatter.format(value); + const formattedPercent = percentFormatter.format(percent / 100); return ( - -
-

- { - e.preventDefault(); - openTab('subscribers', { - filters: { status: [], step: [step.id] }, - }); - }} - > - {formattedPercent} ({formattedValue}){' '} - - { - // translators: "waiting" as in "100 people are waiting for this step". - __('waiting', 'mailpoet') - } - - -

-
-
+ <> + + +
+

+ { + e.preventDefault(); + openTab('subscribers', { + filters: { status: [], step: [step.id] }, + }); + }} + > + {formattedPercent} ({formattedValue}){' '} + + { + // translators: "waiting" as in "100 people are waiting for this step". + __('waiting', 'mailpoet') + } + + +

+
+
+ ); } diff --git a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/store/types.ts b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/store/types.ts index 4cf30a3068..797eb01be4 100644 --- a/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/store/types.ts +++ b/mailpoet/assets/js/src/automation/integrations/mailpoet/analytics/store/types.ts @@ -156,6 +156,7 @@ export type SubscriberSection = Section & { export type StepFlowData = { total: number; waiting: Record | undefined; + failed: Record | undefined; flow: Record | undefined; }; export type AutomationFlowSectionData = SectionData & { diff --git a/mailpoet/lib/Automation/Integrations/MailPoet/Analytics/Controller/StepStatisticController.php b/mailpoet/lib/Automation/Integrations/MailPoet/Analytics/Controller/StepStatisticController.php index 56df9752d1..9d1a8c953e 100644 --- a/mailpoet/lib/Automation/Integrations/MailPoet/Analytics/Controller/StepStatisticController.php +++ b/mailpoet/lib/Automation/Integrations/MailPoet/Analytics/Controller/StepStatisticController.php @@ -45,6 +45,25 @@ class StepStatisticController { return $data; } + public function getFailedStatistics(Automation $automation, Query $query): array { + $rawData = $this->automationRunStorage->getAutomationStepStatisticForTimeFrame( + $automation->getId(), + AutomationRun::STATUS_FAILED, + $query->getAfter(), + $query->getBefore() + ); + + $data = []; + foreach ($automation->getSteps() as $step) { + foreach ($rawData as $rawDatum) { + if ($rawDatum['next_step_id'] === $step->getId()) { + $data[$step->getId()] = (int)$rawDatum['count']; + } + } + } + return $data; + } + public function getFlowStatistics(Automation $automation, Query $query): array { $statistics = $this->automationRunLogStorage->getAutomationRunStatisticsForAutomationInTimeFrame( $automation->getId(), diff --git a/mailpoet/lib/Automation/Integrations/MailPoet/Analytics/Endpoints/AutomationFlowEndpoint.php b/mailpoet/lib/Automation/Integrations/MailPoet/Analytics/Endpoints/AutomationFlowEndpoint.php index ad630e9dc8..57d3dc3974 100644 --- a/mailpoet/lib/Automation/Integrations/MailPoet/Analytics/Endpoints/AutomationFlowEndpoint.php +++ b/mailpoet/lib/Automation/Integrations/MailPoet/Analytics/Endpoints/AutomationFlowEndpoint.php @@ -65,6 +65,7 @@ class AutomationFlowEndpoint extends Endpoint { ); $waitingData = $this->stepStatisticController->getWaitingStatistics($automation, $query); + $failedData = $this->stepStatisticController->getFailedStatistics($automation, $query); try { $flowData = $this->stepStatisticController->getFlowStatistics($automation, $query); } catch (\Throwable $e) { @@ -76,6 +77,9 @@ class AutomationFlowEndpoint extends Endpoint { if ($waitingData) { $stepData['waiting'] = $waitingData; } + if ($failedData) { + $stepData['failed'] = $failedData; + } if ($flowData) { $stepData['flow'] = $flowData; }