Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit1b19a09

Browse files
authored
feat: New static error summary component (#3107)
1 parentfd4954b commit1b19a09

File tree

4 files changed

+213
-21
lines changed

4 files changed

+213
-21
lines changed

‎site/src/api/errors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,6 @@ export const getValidationErrorMessage = (error: Error | ApiError | unknown): st
8383
isApiError(error)&&error.response.data.validations ?error.response.data.validations :[]
8484
returnvalidationErrors.map((error)=>error.detail).join("\n")
8585
}
86+
87+
exportconstgetErrorDetail=(error:Error|ApiError|unknown):string|undefined|null=>
88+
isApiError(error) ?error.response.data.detail :errorinstanceofError ?error.stack :null

‎site/src/components/ErrorSummary/ErrorSummary.stories.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,39 @@ WithRetry.args = {
2323
}
2424

2525
exportconstWithUndefined=Template.bind({})
26+
27+
exportconstWithDefaultMessage=Template.bind({})
28+
WithDefaultMessage.args={
29+
// Unknown error type
30+
error:{
31+
message:"Failed to fetch something!",
32+
},
33+
defaultMessage:"This is a default error message",
34+
}
35+
36+
exportconstWithDismissible=Template.bind({})
37+
WithDismissible.args={
38+
error:{
39+
response:{
40+
data:{
41+
message:"Failed to fetch something!",
42+
},
43+
},
44+
isAxiosError:true,
45+
},
46+
dismissible:true,
47+
}
48+
49+
exportconstWithDetails=Template.bind({})
50+
WithDetails.args={
51+
error:{
52+
response:{
53+
data:{
54+
message:"Failed to fetch something!",
55+
detail:"The resource you requested does not exist in the database.",
56+
},
57+
},
58+
isAxiosError:true,
59+
},
60+
dismissible:true,
61+
}
Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import{render,screen}from"@testing-library/react"
1+
import{fireEvent,render,screen}from"@testing-library/react"
22
import{ErrorSummary}from"./ErrorSummary"
33

44
describe("ErrorSummary",()=>{
@@ -8,7 +8,67 @@ describe("ErrorSummary", () => {
88
render(<ErrorSummaryerror={error}/>)
99

1010
// Then
11-
constelement=awaitscreen.findByText("test error message",{exact:false})
11+
constelement=awaitscreen.findByText("test error message")
1212
expect(element).toBeDefined()
1313
})
14+
15+
it("shows details on More click",async()=>{
16+
// When
17+
consterror={
18+
response:{
19+
data:{
20+
message:"Failed to fetch something!",
21+
detail:"The resource you requested does not exist in the database.",
22+
},
23+
},
24+
isAxiosError:true,
25+
}
26+
render(<ErrorSummaryerror={error}/>)
27+
28+
// Then
29+
fireEvent.click(screen.getByText("More"))
30+
constelement=awaitscreen.findByText(
31+
"The resource you requested does not exist in the database.",
32+
{exact:false},
33+
)
34+
expect(element.closest(".MuiCollapse-entered")).toBeDefined()
35+
})
36+
37+
it("hides details on Less click",async()=>{
38+
// When
39+
consterror={
40+
response:{
41+
data:{
42+
message:"Failed to fetch something!",
43+
detail:"The resource you requested does not exist in the database.",
44+
},
45+
},
46+
isAxiosError:true,
47+
}
48+
render(<ErrorSummaryerror={error}/>)
49+
50+
// Then
51+
fireEvent.click(screen.getByText("More"))
52+
fireEvent.click(screen.getByText("Less"))
53+
constelement=awaitscreen.findByText(
54+
"The resource you requested does not exist in the database.",
55+
{exact:false},
56+
)
57+
expect(element.closest(".MuiCollapse-hidden")).toBeDefined()
58+
})
59+
60+
it("renders nothing on closing",async()=>{
61+
// When
62+
consterror=newError("test error message")
63+
render(<ErrorSummaryerror={error}dismissible/>)
64+
65+
// Then
66+
constelement=awaitscreen.findByText("test error message")
67+
expect(element).toBeDefined()
68+
69+
constcloseIcon=screen.getAllByRole("button")[0]
70+
fireEvent.click(closeIcon)
71+
constnullElement=screen.queryByText("test error message")
72+
expect(nullElement).toBeNull()
73+
})
1474
})
Lines changed: 112 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,125 @@
11
importButtonfrom"@material-ui/core/Button"
2+
importCollapsefrom"@material-ui/core/Collapse"
3+
importIconButtonfrom"@material-ui/core/IconButton"
4+
importLinkfrom"@material-ui/core/Link"
5+
import{darken,makeStyles,Theme}from"@material-ui/core/styles"
6+
importCloseIconfrom"@material-ui/icons/Close"
27
importRefreshIconfrom"@material-ui/icons/Refresh"
8+
import{ApiError,getErrorDetail,getErrorMessage}from"api/errors"
39
import{Stack}from"components/Stack/Stack"
4-
import{FC}from"react"
10+
import{FC,useState}from"react"
511

612
constLanguage={
713
retryMessage:"Retry",
814
unknownErrorMessage:"An unknown error has occurred",
15+
moreDetails:"More",
16+
lessDetails:"Less",
917
}
1018

1119
exportinterfaceErrorSummaryProps{
12-
error:Error|unknown
20+
error:ApiError|Error|unknown
1321
retry?:()=>void
22+
dismissible?:boolean
23+
defaultMessage?:string
1424
}
1525

16-
exportconstErrorSummary:FC<ErrorSummaryProps>=({ error, retry})=>(
17-
<Stack>
18-
{!(errorinstanceofError) ?(
19-
<div>{Language.unknownErrorMessage}</div>
20-
) :(
21-
<div>{error.toString()}</div>
22-
)}
23-
24-
{retry&&(
25-
<div>
26-
<ButtononClick={retry}startIcon={<RefreshIcon/>}variant="outlined">
27-
{Language.retryMessage}
28-
</Button>
29-
</div>
30-
)}
31-
</Stack>
32-
)
26+
exportconstErrorSummary:FC<ErrorSummaryProps>=({
27+
error,
28+
retry,
29+
dismissible,
30+
defaultMessage,
31+
})=>{
32+
constmessage=getErrorMessage(error,defaultMessage||Language.unknownErrorMessage)
33+
constdetail=getErrorDetail(error)
34+
const[showDetails,setShowDetails]=useState(false)
35+
const[isOpen,setOpen]=useState(true)
36+
37+
conststyles=useStyles({ showDetails})
38+
39+
consttoggleShowDetails=()=>{
40+
setShowDetails(!showDetails)
41+
}
42+
43+
constcloseError=()=>{
44+
setOpen(false)
45+
}
46+
47+
if(!isOpen){
48+
returnnull
49+
}
50+
51+
return(
52+
<StackclassName={styles.root}>
53+
<Stackdirection="row"alignItems="center"className={styles.messageBox}>
54+
<div>
55+
<spanclassName={styles.errorMessage}>{message}</span>
56+
{!!detail&&(
57+
<Link
58+
aria-expanded={showDetails}
59+
onClick={toggleShowDetails}
60+
className={styles.detailsLink}
61+
tabIndex={0}
62+
>
63+
{showDetails ?Language.lessDetails :Language.moreDetails}
64+
</Link>
65+
)}
66+
</div>
67+
{dismissible&&(
68+
<IconButtononClick={closeError}className={styles.iconButton}>
69+
<CloseIconclassName={styles.closeIcon}/>
70+
</IconButton>
71+
)}
72+
</Stack>
73+
<Collapsein={showDetails}>
74+
<divclassName={styles.details}>{detail}</div>
75+
</Collapse>
76+
{retry&&(
77+
<divclassName={styles.retry}>
78+
<Buttonsize="small"onClick={retry}startIcon={<RefreshIcon/>}variant="outlined">
79+
{Language.retryMessage}
80+
</Button>
81+
</div>
82+
)}
83+
</Stack>
84+
)
85+
}
86+
87+
interfaceStyleProps{
88+
showDetails?:boolean
89+
}
90+
91+
constuseStyles=makeStyles<Theme,StyleProps>((theme)=>({
92+
root:{
93+
background:darken(theme.palette.error.main,0.6),
94+
margin:`${theme.spacing(2)}px`,
95+
padding:`${theme.spacing(2)}px`,
96+
borderRadius:theme.shape.borderRadius,
97+
gap:0,
98+
},
99+
messageBox:{
100+
justifyContent:"space-between",
101+
},
102+
errorMessage:{
103+
marginRight:`${theme.spacing(1)}px`,
104+
},
105+
detailsLink:{
106+
cursor:"pointer",
107+
},
108+
details:{
109+
marginTop:`${theme.spacing(2)}px`,
110+
padding:`${theme.spacing(2)}px`,
111+
background:darken(theme.palette.error.main,0.7),
112+
borderRadius:theme.shape.borderRadius,
113+
},
114+
iconButton:{
115+
padding:0,
116+
},
117+
closeIcon:{
118+
width:25,
119+
height:25,
120+
color:theme.palette.primary.contrastText,
121+
},
122+
retry:{
123+
marginTop:`${theme.spacing(2)}px`,
124+
},
125+
}))

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp