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

Failed event deliveries list #5163

Merged
merged 14 commits into from
Sep 26, 2024
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,65 @@
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}
marginX={2}
borderRadius={4}
borderStyle="solid"
borderWidth={1}
borderColor={{
default: "default1",
hover: "default1Hovered",
}}
backgroundColor={{
hover: "default1Hovered",
}}
paddingTop={1}
marginBottom={2}
>
<Box display="flex" gap={4} key={id}>
<Text display="block">
<Text color="default2">Status</Text>:{" "}
<Text color={status === "FAILED" ? "critical1" : "default1"} fontWeight="bold">
{status}
</Text>
</Text>
<Text display="block" size={3}>
<Text color="default2">HTTP</Text>{" "}
<Text color="critical1" fontWeight="bold">
{responseStatusCode}
</Text>
</Text>
<Text display="block" size={3} marginLeft="auto" fontWeight="bold">
{new Date(createdAt).toLocaleString()}
Cloud11PL marked this conversation as resolved.
Show resolved Hide resolved
</Text>
</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
Loading