Skip to content

Commit

Permalink
Failed event deliveries list (#5163)
Browse files Browse the repository at this point in the history
* improve app install loading state

* tiny ui fix

* failed event delivery list in app details

* changeset

* fix types

* add tests

* fix type

* cr fixes

* ui fixes

* remove unused import
  • Loading branch information
Cloud11PL authored Sep 26, 2024
1 parent 5da872a commit e2a0632
Show file tree
Hide file tree
Showing 12 changed files with 467 additions and 125 deletions.
5 changes: 5 additions & 0 deletions .changeset/silver-items-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"saleor-dashboard": patch
---

You can now see app's webhooks' event delivery attempts in app settings. These include last 6 failed or pending deliveries with their details: payload, status and date.
55 changes: 35 additions & 20 deletions src/apps/components/AppInstallPage/AppInstallPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { AppFetchMutation, AppInstallMutation } from "@dashboard/graphql";
import { SubmitPromise } from "@dashboard/hooks/useForm";
import { buttonMessages } from "@dashboard/intl";
import { useTheme } from "@dashboard/theme";
import { Box, Button, Skeleton, Spinner, Text } from "@saleor/macaw-ui-next";
import { Box, Button, Skeleton, Text } from "@saleor/macaw-ui-next";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";

Expand Down Expand Up @@ -52,25 +52,31 @@ export const AppInstallPage: React.FC<AppInstallPageProps> = ({
<DashboardCard>
<DashboardCard.Header>
<DashboardCard.Title data-test-id="app-installation-page-header">
{loading ? <Skeleton /> : intl.formatMessage(messages.title, { name })}
{loading ? (
<Skeleton __width="20ch" height={6} />
) : (
intl.formatMessage(messages.title, { name })
)}
</DashboardCard.Title>
</DashboardCard.Header>
<DashboardCard.Content className={classes.installCard}>
{loading ? (
<Spinner />
) : (
<div className={classes.installAppContainer}>
<Box
width={12}
height={12}
display="flex"
placeItems="center"
borderRadius={2}
overflow="hidden"
>
<img src={getSaleorLogoUrl()} alt="Saleor" />
<div className={classes.installAppContainer}>
<Box
width={12}
height={12}
display="flex"
placeItems="center"
borderRadius={2}
overflow="hidden"
>
<img src={getSaleorLogoUrl()} alt="Saleor" />
</Box>
<img src={plusIcon} alt="" />
{loading ? (
<Box width={12} height={12} backgroundColor="default1">
<Skeleton width={12} height={12} />
</Box>
<img src={plusIcon} alt="" />
) : (
<AppAvatar
size={12}
logo={
Expand All @@ -81,8 +87,8 @@ export const AppInstallPage: React.FC<AppInstallPageProps> = ({
: undefined
}
/>
</div>
)}
)}
</div>
</DashboardCard.Content>
</DashboardCard>

Expand All @@ -91,12 +97,21 @@ export const AppInstallPage: React.FC<AppInstallPageProps> = ({
<DashboardCard>
<DashboardCard.Header>
<DashboardCard.Title>
{loading ? <Skeleton /> : intl.formatMessage(messages.permissionsTitle)}
{intl.formatMessage(messages.permissionsTitle)}
</DashboardCard.Title>
</DashboardCard.Header>
<DashboardCard.Content>
{loading ? (
<Skeleton />
<>
<Skeleton __width="30%" height={6} marginBottom={2} />

<Skeleton height={4} __width="150px" marginBottom={2} />
<Skeleton height={4} __width="120px" />

<Hr className={classes.installSpacer} />

<Skeleton height={3} __width="50%" />
</>
) : (
<>
<Text className={classes.installPermissionTitle}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { DateTime } from "@dashboard/components/Date";
import { EventDeliveryAttemptFragment } from "@dashboard/graphql";
import { Box, Text } from "@saleor/macaw-ui-next";
import React from "react";

interface AppWebhooksAttemptDetailsProps {
attempt: EventDeliveryAttemptFragment;
}

export const AppWebhooksAttemptDetails: React.FC<AppWebhooksAttemptDetailsProps> = ({
attempt,
}) => {
const { createdAt, status, responseStatusCode, response, id } = attempt;

return (
<Box
display="flex"
flexDirection="column"
paddingX={2}
borderRadius={4}
backgroundColor={{
hover: "default1Hovered",
}}
paddingTop={1}
borderWidth={1}
>
<Box display="flex" gap={4} key={id}>
<Text display="block" size={3} fontWeight="bold">
<DateTime plain date={createdAt} />
</Text>

<Box display="block" marginLeft="auto">
<Text color="default2">HTTP</Text>{" "}
<Text color="critical1" fontWeight="bold" marginRight={4}>
{responseStatusCode}
</Text>
<Text color="default2">Status</Text>:{" "}
<Text color={status === "FAILED" ? "critical1" : "default1"} fontWeight="bold">
{status}
</Text>
</Box>
</Box>

<Box paddingTop={4} paddingBottom={2}>
<Text
// @ts-expect-error - "pre" is missing in Text props
as="pre"
wordBreak="break-all"
maxWidth="100%"
size={3}
style={{ whiteSpace: "pre-wrap" }}
>
{response}
</Text>
</Box>
</Box>
);
};
107 changes: 8 additions & 99 deletions src/apps/components/AppWebhooksDisplay/AppWebhooksDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import { DateTime } from "@dashboard/components/Date";
import { EventDeliveryStatusEnum, useAppWebhookDeliveriesQuery } from "@dashboard/graphql";
import { useAppWebhookDeliveriesQuery } from "@dashboard/graphql";
import { useHasManagedAppsPermission } from "@dashboard/hooks/useHasManagedAppsPermission";
import {
Accordion,
Box,
BoxProps,
Chip,
Skeleton,
Text,
ThemeTokensValues,
} from "@saleor/macaw-ui-next";
import { Accordion, Box, BoxProps, Chip, Skeleton, Text } from "@saleor/macaw-ui-next";
import React from "react";
import { useIntl } from "react-intl";

import { EventDeliveriesList } from "./EventDeliveriesList";

interface AppWebhooksDisplayProps extends BoxProps {
appId: string;
}
Expand All @@ -39,53 +32,7 @@ const Wrapper = (boxProps: BoxProps) => {
</Box>
);
};
const mapDeliveryStatusToTextColor = (
status: EventDeliveryStatusEnum,
): keyof ThemeTokensValues["colors"]["text"] => {
switch (status) {
case EventDeliveryStatusEnum.FAILED:
return "critical1";
case EventDeliveryStatusEnum.PENDING:
return "accent1";
case EventDeliveryStatusEnum.SUCCESS:
return "success1";
}
};
const mapDeliveryStatusToBackgroundColor = (
status: EventDeliveryStatusEnum,
): keyof ThemeTokensValues["colors"]["background"] => {
switch (status) {
case EventDeliveryStatusEnum.FAILED:
return "default2";
case EventDeliveryStatusEnum.PENDING:
return "default1";
case EventDeliveryStatusEnum.SUCCESS:
return "accent1";
}
};
const DeliveryStatusDisplay = ({ status }: { status: EventDeliveryStatusEnum }) => {
const { formatMessage } = useIntl();

switch (status) {
case EventDeliveryStatusEnum.FAILED:
return <>{formatMessage({ defaultMessage: "Failed", id: "vXCeIi" })}</>;
case EventDeliveryStatusEnum.PENDING:
return <>{formatMessage({ defaultMessage: "Pending", id: "eKEL/g" })}</>;
case EventDeliveryStatusEnum.SUCCESS:
return <>{formatMessage({ defaultMessage: "Success", id: "xrKHS6" })} </>;
default:
throw new Error("Invalid EventDeliveryStatusEnum value");
}
};
const StatusChip = ({ status }: { status: EventDeliveryStatusEnum }) => {
return (
<Chip backgroundColor={mapDeliveryStatusToBackgroundColor(status)}>
<Text color={mapDeliveryStatusToTextColor(status)}>
<DeliveryStatusDisplay status={status} />
</Text>
</Chip>
);
};
const DisabledWebhookChip = () => {
const { formatMessage } = useIntl();

Expand Down Expand Up @@ -127,7 +74,7 @@ export const AppWebhooksDisplay = ({ appId, ...boxProps }: AppWebhooksDisplayPro
if (webhooksData?.app?.webhooks) {
return (
<Wrapper {...boxProps}>
<Accordion>
<Accordion __marginLeft="-24px" __width="calc(100% + 48px)">
{webhooksData.app.webhooks.map((wh, index) => {
const isLastWebhook = index === (webhooksData?.app?.webhooks ?? []).length - 1;
const events = [...wh.asyncEvents, ...wh.syncEvents].flatMap(e => e.name).join(", ");
Expand All @@ -137,6 +84,7 @@ export const AppWebhooksDisplay = ({ appId, ...boxProps }: AppWebhooksDisplayPro
<Box
key={wh.id}
padding={4}
paddingLeft={6}
borderBottomWidth={isLastWebhook ? 0 : 1}
borderColor="default1"
borderBottomStyle="solid"
Expand All @@ -156,10 +104,9 @@ export const AppWebhooksDisplay = ({ appId, ...boxProps }: AppWebhooksDisplayPro
borderWidth={1}
borderStyle="solid"
paddingY={2}
paddingX={4}
borderRadius={4}
>
<Accordion.Trigger alignItems="center">
<Accordion.Trigger alignItems="center" paddingX={4}>
<Text size={4} fontWeight="bold" as="h2">
{formatMessage({
defaultMessage: "Pending & failed deliveries (last 10)",
Expand All @@ -169,45 +116,7 @@ export const AppWebhooksDisplay = ({ appId, ...boxProps }: AppWebhooksDisplayPro
<Accordion.TriggerButton />
</Accordion.Trigger>
<Accordion.Content marginTop={6}>
{eventDeliveries.map(ed => {
const { createdAt } = ed.node;
const attempts = ed.node.attempts?.edges ?? [];
const attemptsCount = attempts.length;
const lastAttemptDate = attempts[attemptsCount - 1]?.node.createdAt;

return (
<Box key={createdAt} marginBottom={4}>
<Box paddingLeft={0} display="grid" __gridTemplateColumns={"1fr 1fr"}>
<Text as="p" size={4} fontWeight="bold">
<DateTime plain date={createdAt} />
</Text>
<Box marginLeft="auto">
<StatusChip status={ed.node.status} />
</Box>
</Box>
{attempts.length > 0 && (
<Box>
<Text>
{formatMessage({
defaultMessage: "Attempts:",
id: "OFTsI1",
})}{" "}
<Text size={4} fontWeight="bold">
{attemptsCount} / 6
</Text>
</Text>
<Text as="p">
{formatMessage({
defaultMessage: "Last delivery attempt:",
id: "EY/jqC",
})}{" "}
<DateTime plain date={lastAttemptDate} />
</Text>
</Box>
)}
</Box>
);
})}
<EventDeliveriesList eventDeliveries={eventDeliveries} />
</Accordion.Content>
</Accordion.Item>
)}
Expand Down
Loading

0 comments on commit e2a0632

Please sign in to comment.