Show failed runs per step

[MAILPOET-5460]
This commit is contained in:
David Remer
2023-07-17 14:56:19 +03:00
committed by Aschepikov
parent 0e3b7b20e7
commit 66e05a0063
5 changed files with 141 additions and 32 deletions

View File

@ -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 { .mailpoet-automation-analytics-step-footer {
background: $color-wp-gray-0; background: $color-wp-gray-0;
border-top: 1px solid $color-poet-gray-dividers; border-top: 1px solid $color-poet-gray-dividers;
@ -47,10 +80,17 @@
margin-bottom: -12px; margin-bottom: -12px;
margin-left: -12px; margin-left: -12px;
width: calc(100% + 24px); width: calc(100% + 24px);
z-index: 1;
p { p {
margin: 0;
padding: 6px 0; padding: 6px 0;
}
}
.mailpoet-automation-analytics-step-footer,
.mailpoet-automation-analytics-step-failed {
p {
margin: 0;
a { a {
color: $color-gutenberg-grey-800; color: $color-gutenberg-grey-800;

View File

@ -7,6 +7,52 @@ import { locale } from '../../../config';
import { Step } from '../../../../../../editor/components/automation/types'; import { Step } from '../../../../../../editor/components/automation/types';
import { openTab } from '../../../navigation/open_tab'; 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 (
<div className="mailpoet-automation-analytics-step-failed">
<p>
{formattedPercent} ({formattedValue})
<span>
{
// translators: "failed" as in "100 automation runs failed at this step".
__('failed', 'mailpoet')
}
</span>
</p>
</div>
);
}
export function StepFooter({ step }: { step: Step }): JSX.Element | null { export function StepFooter({ step }: { step: Step }): JSX.Element | null {
const { section } = useSelect( const { section } = useSelect(
(s) => (s) =>
@ -29,37 +75,36 @@ export function StepFooter({ step }: { step: Step }): JSX.Element | null {
? Math.round((value / data.step_data.total) * 100) ? Math.round((value / data.step_data.total) * 100)
: 0; : 0;
const formattedValue = Intl.NumberFormat(locale.toString(), { const formattedValue = compactFormatter.format(value);
notation: 'compact', const formattedPercent = percentFormatter.format(percent / 100);
}).format(value);
const formattedPercent = Intl.NumberFormat(locale.toString(), {
style: 'percent',
}).format(percent / 100);
return ( return (
<Tooltip text={__('View subscribers', 'mailpoet')}> <>
<div className="mailpoet-automation-analytics-step-footer"> <FailedStep step={step} />
<p> <Tooltip text={__('View subscribers', 'mailpoet')}>
<a <div className="mailpoet-automation-analytics-step-footer">
href={addQueryArgs(window.location.href, { <p>
tab: 'automation-subscribers', <a
})} href={addQueryArgs(window.location.href, {
onClick={(e) => { tab: 'automation-subscribers',
e.preventDefault(); })}
openTab('subscribers', { onClick={(e) => {
filters: { status: [], step: [step.id] }, e.preventDefault();
}); openTab('subscribers', {
}} filters: { status: [], step: [step.id] },
> });
{formattedPercent} ({formattedValue}){' '} }}
<span> >
{ {formattedPercent} ({formattedValue}){' '}
// translators: "waiting" as in "100 people are waiting for this step". <span>
__('waiting', 'mailpoet') {
} // translators: "waiting" as in "100 people are waiting for this step".
</span> __('waiting', 'mailpoet')
</a> }
</p> </span>
</div> </a>
</Tooltip> </p>
</div>
</Tooltip>
</>
); );
} }

View File

@ -156,6 +156,7 @@ export type SubscriberSection = Section & {
export type StepFlowData = { export type StepFlowData = {
total: number; total: number;
waiting: Record<string, number> | undefined; waiting: Record<string, number> | undefined;
failed: Record<string, number> | undefined;
flow: Record<string, number> | undefined; flow: Record<string, number> | undefined;
}; };
export type AutomationFlowSectionData = SectionData & { export type AutomationFlowSectionData = SectionData & {

View File

@ -45,6 +45,25 @@ class StepStatisticController {
return $data; 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 { public function getFlowStatistics(Automation $automation, Query $query): array {
$statistics = $this->automationRunLogStorage->getAutomationRunStatisticsForAutomationInTimeFrame( $statistics = $this->automationRunLogStorage->getAutomationRunStatisticsForAutomationInTimeFrame(
$automation->getId(), $automation->getId(),

View File

@ -65,6 +65,7 @@ class AutomationFlowEndpoint extends Endpoint {
); );
$waitingData = $this->stepStatisticController->getWaitingStatistics($automation, $query); $waitingData = $this->stepStatisticController->getWaitingStatistics($automation, $query);
$failedData = $this->stepStatisticController->getFailedStatistics($automation, $query);
try { try {
$flowData = $this->stepStatisticController->getFlowStatistics($automation, $query); $flowData = $this->stepStatisticController->getFlowStatistics($automation, $query);
} catch (\Throwable $e) { } catch (\Throwable $e) {
@ -76,6 +77,9 @@ class AutomationFlowEndpoint extends Endpoint {
if ($waitingData) { if ($waitingData) {
$stepData['waiting'] = $waitingData; $stepData['waiting'] = $waitingData;
} }
if ($failedData) {
$stepData['failed'] = $failedData;
}
if ($flowData) { if ($flowData) {
$stepData['flow'] = $flowData; $stepData['flow'] = $flowData;
} }