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

fix(admin-ui): show failure reason for batch jobs #3526

Merged
merged 6 commits into from
Mar 21, 2023
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
5 changes: 5 additions & 0 deletions .changeset/thirty-shrimps-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@medusajs/admin-ui": patch
---

fix(admin-ui): display error messages for batch jobs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Product Variant ID,SKU,Price EUR,Price NA [USD]
,MEDUSA-SWEAT-SMALL,15,13.5
variant_1234,,15,13.5
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Product Id;Product Handle;Product Title;Product Subtitle;Product Description;Product Status;Product Thumbnail;Product Weight;Product Length;Product Width;Product Height;Product HS Code;Product Origin Country;Product MID Code;Product Material;Product Collection Title;Product Collection Handle;Product Type;Product Tags;Product Discountable;Product External Id;Product Profile Name;Product Profile Type;Variant Id;Variant Title;Variant SKU;Variant Barcode;Variant Inventory Quantity;Variant Allow Backorder;Variant Manage Inventory;Variant Weight;Variant Length;Variant Width;Variant Height;Variant HS Code;Variant Origin Country;Variant MID Code;Variant Material;Price EUR;Price USD;Option 1 Name;Option 1 Value;Image 1 Url;Image 2 Url
;coffee-mug-v2;Medusa Coffee Mug;;Every programmer's best friend.;published;https://medusa-public-images.s3.eu-west-1.amazonaws.com/coffee-mug.png;400;;;;;;;;;;;;true;;;;;One Size;;;100;false;true;;;;;;;;;1000;1200;Size;One Size;https://medusa-public-images.s3.eu-west-1.amazonaws.com/coffee-mug.png;
;sweatpants-v2;Medusa Sweatpants;;Reimagine the feeling of classic sweatpants. With our cotton sweatpants, everyday essentials no longer have to be ordinary.;published;https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-front.png;400;;;;;;;;;;;;true;;;;;S;;;100;false;true;;;;;;;;;2950;3350;Size;S;https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-front.png;https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-back.png
;sweatpants-v2;Medusa Sweatpants;;Reimagine the feeling of classic sweatpants. With our cotton sweatpants, everyday essentials no longer have to be ordinary.;published;https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-front.png;400;;;;;;;;;;;;true;;;;;M;;;100;false;true;;;;;;;;;2950;3350;Size;M;https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-front.png;https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-back.png
;sweatpants-v2;Medusa Sweatpants;;Reimagine the feeling of classic sweatpants. With our cotton sweatpants, everyday essentials no longer have to be ordinary.;published;https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-front.png;400;;;;;;;;;;;;true;;;;;L;;;100;false;true;;;;;;;;;2950;3350;Size;L;https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-front.png;https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-back.png
;sweatpants-v2;Medusa Sweatpants;;Reimagine the feeling of classic sweatpants. With our cotton sweatpants, everyday essentials no longer have to be ordinary.;published;https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-front.png;400;;;;;;;;;;;;true;;;;;XL;;;100;false;true;;;;;;;;;2950;3350;Size;XL;https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-front.png;https://medusa-public-images.s3.eu-west-1.amazonaws.com/sweatpants-gray-back.png
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type TooltipProps = RadixTooltip.TooltipContentProps &
content: React.ReactNode
side?: "bottom" | "left" | "top" | "right"
onClick?: React.ButtonHTMLAttributes<HTMLButtonElement>["onClick"]
maxWidth?: number
}

const Tooltip = ({
Expand All @@ -19,6 +20,7 @@ const Tooltip = ({
defaultOpen,
onOpenChange,
delayDuration,
maxWidth = 220,
className,
side,
onClick,
Expand All @@ -43,10 +45,10 @@ const Tooltip = ({
"inter-small-semibold text-grey-50",
"bg-grey-0 shadow-dropdown rounded-rounded py-2 px-3",
"border-grey-20 border border-solid",
"max-w-[220px]",
className
)}
{...props}
style={{ ...props.style, maxWidth }}
>
{content}
</RadixTooltip.Content>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { ReactNode } from "react"
import clsx from "clsx"

import Tooltip from "../../atoms/tooltip"

type Props = {
fileName: string
fileSize?: string
errorMessage?: string
hasError?: boolean
icon?: ReactNode
onClick?: () => void
}

const BatchJobFileCard = ({ fileName, fileSize, icon, onClick }: Props) => {
const BatchJobFileCard = ({
fileName,
fileSize,
icon,
onClick,
hasError,
errorMessage,
}: Props) => {
const preparedOnClick = onClick ?? (() => void 0)

return (
Expand All @@ -27,9 +39,26 @@ const BatchJobFileCard = ({ fileName, fileSize, icon, onClick }: Props) => {
{fileName}
</div>

{!!fileSize && (
<div className="text-grey-40 inter-small-regular">{fileSize}</div>
)}
<Tooltip
side="top"
open={hasError ? undefined : false}
maxWidth={320}
content={
hasError && errorMessage ? (
<span className="font-normal text-rose-500">{errorMessage}</span>
) : null
}
>
{!!fileSize && (
<div
className={clsx("text-grey-40 inter-small-regular", {
"text-rose-500": hasError,
})}
>
{fileSize}
</div>
)}
</Tooltip>
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import MedusaIcon from "../../fundamentals/icons/medusa-icon"
import { ActivityCard } from "../../molecules/activity-card"
import BatchJobFileCard from "../../molecules/batch-job-file-card"
import { batchJobDescriptionBuilder, BatchJobOperation } from "./utils"
import CrossIcon from "../../fundamentals/icons/cross-icon"

/**
* Retrieve a batch job and refresh the data depending on the last batch job status
Expand Down Expand Up @@ -97,6 +98,8 @@ const BatchJobActivityCard = (props: { batchJob: BatchJob }) => {
batchJob.status !== "failed" &&
batchJob.status !== "canceled"

const hasError = batchJob.status === "failed"

const canDownload =
batchJob.status === "completed" && batchJob.result?.file_key

Expand Down Expand Up @@ -157,7 +160,11 @@ const BatchJobActivityCard = (props: { batchJob: BatchJob }) => {

const icon =
batchJob.status !== "completed" && batchJob.status !== "canceled" ? (
<Spinner size={"medium"} variant={"secondary"} />
batchJob.status === "failed" ? (
<CrossIcon size={18} />
) : (
<Spinner size={"medium"} variant={"secondary"} />
)
) : (
<FileIcon
className={clsx({
Expand All @@ -175,7 +182,7 @@ const BatchJobActivityCard = (props: { batchJob: BatchJob }) => {
preprocessing: `Preparing ${operation.toLowerCase()}...`,
processing: `Processing ${operation.toLowerCase()}...`,
completed: `Successful ${operation.toLowerCase()}`,
failed: `Failed batch ${operation.toLowerCase()} job`,
failed: `Job failed`,
canceled: `Canceled batch ${operation.toLowerCase()} job`,
}[batchJob.status]

Expand All @@ -185,6 +192,8 @@ const BatchJobActivityCard = (props: { batchJob: BatchJob }) => {
fileName={fileName}
icon={icon}
fileSize={fileSize}
hasError={hasError}
errorMessage={batchJob?.result?.errors?.join(" \n")}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ReactNode, useState } from "react"
import clsx from "clsx"

import Button from "../../fundamentals/button"
import CheckCircleIcon from "../../fundamentals/icons/check-circle-icon"
Expand All @@ -8,12 +9,13 @@ import FileIcon from "../../fundamentals/icons/file-icon"
import Modal from "../../molecules/modal"
import TrashIcon from "../../fundamentals/icons/trash-icon"
import WarningCircleIcon from "../../fundamentals/icons/warning-circle"
import XCircleIcon from "../../fundamentals/icons/x-circle-icon"
import clsx from "clsx"
import Tooltip from "../../atoms/tooltip"

type FileSummaryProps = {
name: string
size: number
hasError?: boolean
errorMessage?: string
action: ReactNode
progress?: number
status?: string
Expand All @@ -23,7 +25,7 @@ type FileSummaryProps = {
* Render an upload file summary (& upload progress).
*/
function FileSummary(props: FileSummaryProps) {
const { action, name, progress, size, status } = props
const { action, name, progress, size, status, hasError, errorMessage } = props

const formattedSize =
size / 1024 < 10
Expand All @@ -32,58 +34,65 @@ function FileSummary(props: FileSummaryProps) {

return (
<div className="relative">
<div
style={{ width: `${progress}%` }}
className="bg-grey-5 transition-width absolute h-full duration-150 ease-in-out"
/>
<div className="border-1 relative mt-6 flex items-center rounded-xl border">
<div className="m-4">
<FileIcon size={30} fill={progress ? "#9CA3AF" : "#2DD4BF"} />
</div>
<Tooltip
side="top"
maxWidth={320}
open={hasError ? undefined : false}
content={
hasError && errorMessage ? (
<span className="font-normal text-rose-500">{errorMessage}</span>
) : null
}
>
<div
style={{ width: `${progress}%` }}
className="bg-grey-5 transition-width absolute h-full duration-150 ease-in-out"
/>
<div className="border-1 relative mt-6 flex items-center rounded-xl border">
<div className="m-4">
<FileIcon size={30} fill={progress ? "#9CA3AF" : "#2DD4BF"} />
</div>

<div className="my-6 flex-1">
<div className="text-small text-grey-90 leading-5">{name}</div>
<div className="text-xsmall text-grey-50 leading-4">
{status || formattedSize}
<div className="my-6 flex-1">
<div className="text-small text-grey-90 leading-5">{name}</div>
<div
className={clsx("text-xsmall text-grey-50 leading-4", {
"text-rose-500": hasError,
})}
>
{status || formattedSize}
</div>
</div>
</div>

<div className="m-6">{action}</div>
</div>
<div className="m-6">{action}</div>
</div>
</Tooltip>
</div>
)
}

type UploadSummaryProps = {
creations: number
updates: number
rejections?: number
creations?: number
updates?: number
type: string
}

/**
* Render a batch update request summary.
*/
function UploadSummary(props: UploadSummaryProps) {
const { creations, updates, rejections, type } = props
const { creations, updates, type } = props
return (
<div className="flex gap-6">
<div className="text-small text-grey-90 flex items-center">
<CheckCircleIcon color="#9CA3AF" className="mr-2" />
<span className="font-semibold"> {creations}&nbsp;</span> new {type}
<span className="font-semibold"> {creations || 0}&nbsp;</span> new{" "}
{type}
</div>
<div className="text-small text-grey-90 flex items-center">
<WarningCircleIcon fill="#9CA3AF" className="mr-2" />
<span className="font-semibold">{updates || 0}&nbsp;</span> updates
</div>
{updates && (
<div className="text-small text-grey-90 flex items-center">
<WarningCircleIcon fill="#9CA3AF" className="mr-2" />
<span className="font-semibold">{updates}&nbsp;</span> updates
</div>
)}
{rejections && (
<div className="text-small text-grey-90 flex items-center">
<XCircleIcon color="#9CA3AF" className="mr-2" />
<span className="font-semibold">{rejections}&nbsp;</span> rejections
</div>
)}
</div>
)
}
Expand Down Expand Up @@ -155,6 +164,8 @@ function DropArea(props: DropAreaProps) {
type UploadModalProps = {
type: string
status?: string
hasError?: boolean
errorMessage?: string
fileTitle: string
description1Text: string
description2Title: string
Expand Down Expand Up @@ -184,8 +195,9 @@ function UploadModal(props: UploadModalProps) {
onSubmit,
onFileRemove,
templateLink,
progress,
summary,
hasError,
errorMessage,
status,
type,
} = props
Expand Down Expand Up @@ -237,6 +249,8 @@ function UploadModal(props: UploadModalProps) {
size={size!}
name={name!}
status={status}
hasError={hasError}
errorMessage={errorMessage}
// progress={progress}
// TODO: change this to actual progress once this we can track upload
progress={100}
Expand Down Expand Up @@ -284,7 +298,7 @@ function UploadModal(props: UploadModalProps) {

<Button
size="small"
disabled={!canImport}
disabled={!canImport || hasError}
variant="primary"
className="text-small"
onClick={onSubmit}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ function ImportProducts(props: ImportProductsProps) {
return undefined
}

const res = batchJob.result?.stat_descriptors[0].message.match(/\d+/g)
const res = batchJob.result?.stat_descriptors?.[0].message.match(/\d+/g)

if (!res) {
return undefined
Expand Down Expand Up @@ -182,6 +182,7 @@ function ImportProducts(props: ImportProductsProps) {
type="products"
status={status}
progress={progress}
hasError={hasError}
canImport={isPreprocessed}
onSubmit={onSubmit}
onClose={onClose}
Expand All @@ -190,6 +191,7 @@ function ImportProducts(props: ImportProductsProps) {
processUpload={processUpload}
fileTitle={"products list"}
templateLink="/temp/product-import-template.csv"
errorMessage={batchJob?.result?.errors?.join(" \n")}
description2Title="Unsure about how to arrange your list?"
description2Text="Download the template below to ensure you are following the correct format."
description1Text="Through imports you can add or update products. To update existing products/variants you must set an existing id in the Product/Variant id columns. If the value is unset a new record will be created. You will be asked for confirmation before we import products."
Expand Down
2 changes: 1 addition & 1 deletion packages/admin-ui/ui/src/providers/polling-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ export const PollingProvider = ({ children }: PropsWithChildren) => {
refetch,
} = useAdminBatchJobs(
{
limit: 100,
created_at: { gte: oneMonthAgo },
failed_at: null,
} as AdminGetBatchParams,
{
refetchOnWindowFocus: true,
Expand Down