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

Commit74c8766

Browse files
authored
fix: handle more auth API errors (#3241)
1 parent6b82fdd commit74c8766

File tree

9 files changed

+203
-70
lines changed

9 files changed

+203
-70
lines changed

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

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import{Story}from"@storybook/react"
2-
import{SignInForm,SignInFormProps}from"./SignInForm"
2+
import{makeMockApiError}from"testHelpers/entities"
3+
import{LoginErrors,SignInForm,SignInFormProps}from"./SignInForm"
34

45
exportdefault{
56
title:"components/SignInForm",
@@ -15,7 +16,7 @@ const Template: Story<SignInFormProps> = (args: SignInFormProps) => <SignInForm
1516
exportconstSignedOut=Template.bind({})
1617
SignedOut.args={
1718
isLoading:false,
18-
authError:undefined,
19+
loginErrors:{},
1920
onSubmit:()=>{
2021
returnPromise.resolve()
2122
},
@@ -34,29 +35,39 @@ Loading.args = {
3435
exportconstWithLoginError=Template.bind({})
3536
WithLoginError.args={
3637
...SignedOut.args,
37-
authError:{
38-
response:{
39-
data:{
40-
message:"Email or password was invalid",
41-
validations:[
42-
{
43-
field:"password",
44-
detail:"Password is invalid.",
45-
},
46-
],
47-
},
48-
},
49-
isAxiosError:true,
38+
loginErrors:{
39+
[LoginErrors.AUTH_ERROR]:makeMockApiError({
40+
message:"Email or password was invalid",
41+
validations:[
42+
{
43+
field:"password",
44+
detail:"Password is invalid.",
45+
},
46+
],
47+
}),
5048
},
5149
initialTouched:{
5250
password:true,
5351
},
5452
}
5553

54+
exportconstWithCheckPermissionsError=Template.bind({})
55+
WithCheckPermissionsError.args={
56+
...SignedOut.args,
57+
loginErrors:{
58+
[LoginErrors.CHECK_PERMISSIONS_ERROR]:makeMockApiError({
59+
message:"Unable to fetch user permissions",
60+
detail:"Resource not found or you do not have access to this resource.",
61+
}),
62+
},
63+
}
64+
5665
exportconstWithAuthMethodsError=Template.bind({})
5766
WithAuthMethodsError.args={
5867
...SignedOut.args,
59-
methodsError:newError("Failed to fetch auth methods"),
68+
loginErrors:{
69+
[LoginErrors.GET_METHODS_ERROR]:newError("Failed to fetch auth methods"),
70+
},
6071
}
6172

6273
exportconstWithGithub=Template.bind({})

‎site/src/components/SignInForm/SignInForm.tsx

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,22 @@ interface BuiltInAuthFormValues {
2323
password:string
2424
}
2525

26+
exportenumLoginErrors{
27+
AUTH_ERROR="authError",
28+
CHECK_PERMISSIONS_ERROR="checkPermissionsError",
29+
GET_METHODS_ERROR="getMethodsError",
30+
}
31+
2632
exportconstLanguage={
2733
emailLabel:"Email",
2834
passwordLabel:"Password",
2935
emailInvalid:"Please enter a valid email address.",
3036
emailRequired:"Please enter an email address.",
31-
authErrorMessage:"Incorrect email or password.",
32-
methodsErrorMessage:"Unable to fetch auth methods.",
37+
errorMessages:{
38+
[LoginErrors.AUTH_ERROR]:"Incorrect email or password.",
39+
[LoginErrors.CHECK_PERMISSIONS_ERROR]:"Unable to fetch user permissions.",
40+
[LoginErrors.GET_METHODS_ERROR]:"Unable to fetch auth methods.",
41+
},
3342
passwordSignIn:"Sign In",
3443
githubSignIn:"GitHub",
3544
}
@@ -68,8 +77,7 @@ const useStyles = makeStyles((theme) => ({
6877
exportinterfaceSignInFormProps{
6978
isLoading:boolean
7079
redirectTo:string
71-
authError?:Error|unknown
72-
methodsError?:Error|unknown
80+
loginErrors:Partial<Record<LoginErrors,Error|unknown>>
7381
authMethods?:AuthMethods
7482
onSubmit:({ email, password}:{email:string;password:string})=>Promise<void>
7583
// initialTouched is only used for testing the error state of the form.
@@ -80,8 +88,7 @@ export const SignInForm: FC<SignInFormProps> = ({
8088
authMethods,
8189
redirectTo,
8290
isLoading,
83-
authError,
84-
methodsError,
91+
loginErrors,
8592
onSubmit,
8693
initialTouched,
8794
})=>{
@@ -101,18 +108,24 @@ export const SignInForm: FC<SignInFormProps> = ({
101108
onSubmit,
102109
initialTouched,
103110
})
104-
constgetFieldHelpers=getFormHelpersWithError<BuiltInAuthFormValues>(form,authError)
111+
constgetFieldHelpers=getFormHelpersWithError<BuiltInAuthFormValues>(
112+
form,
113+
loginErrors.authError,
114+
)
105115

106116
return(
107117
<>
108118
<Welcome/>
109119
<formonSubmit={form.handleSubmit}>
110120
<Stack>
111-
{authError&&(
112-
<ErrorSummaryerror={authError}defaultMessage={Language.authErrorMessage}/>
113-
)}
114-
{methodsError&&(
115-
<ErrorSummaryerror={methodsError}defaultMessage={Language.methodsErrorMessage}/>
121+
{Object.keys(loginErrors).map((errorKey:string)=>
122+
loginErrors[errorKeyasLoginErrors] ?(
123+
<ErrorSummary
124+
key={errorKey}
125+
error={loginErrors[errorKeyasLoginErrors]}
126+
defaultMessage={Language.errorMessages[errorKeyasLoginErrors]}
127+
/>
128+
) :null,
116129
)}
117130
<TextField
118131
{...getFieldHelpers("email")}

‎site/src/pages/LoginPage/LoginPage.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ describe("LoginPage", () => {
3030
server.use(
3131
// Make login fail
3232
rest.post("/api/v2/users/login",async(req,res,ctx)=>{
33-
returnres(ctx.status(500),ctx.json({message:Language.authErrorMessage}))
33+
returnres(ctx.status(500),ctx.json({message:Language.errorMessages.authError}))
3434
}),
3535
)
3636

@@ -45,7 +45,7 @@ describe("LoginPage", () => {
4545
act(()=>signInButton.click())
4646

4747
// Then
48-
consterrorMessage=awaitscreen.findByText(Language.authErrorMessage)
48+
consterrorMessage=awaitscreen.findByText(Language.errorMessages.authError)
4949
expect(errorMessage).toBeDefined()
5050
expect(history.location.pathname).toEqual("/login")
5151
})

‎site/src/pages/LoginPage/LoginPage.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export const LoginPage: React.FC = () => {
4040
authSend({type:"SIGN_IN", email, password})
4141
}
4242

43+
const{ authError, checkPermissionsError, getMethodsError}=authState.context
44+
4345
if(authState.matches("signedIn")){
4446
return<Navigateto={redirectTo}replace/>
4547
}else{
@@ -54,8 +56,11 @@ export const LoginPage: React.FC = () => {
5456
authMethods={authState.context.methods}
5557
redirectTo={redirectTo}
5658
isLoading={isLoading}
57-
authError={authState.context.authError}
58-
methodsError={authState.context.getMethodsErrorasError}
59+
loginErrors={{
60+
authError,
61+
checkPermissionsError,
62+
getMethodsError,
63+
}}
5964
onSubmit={onSubmit}
6065
/>
6166
</div>

‎site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.test.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { GlobalSnackbar } from "../../../components/GlobalSnackbar/GlobalSnackba
44
import{MockGitSSHKey,renderWithAuth}from"../../../testHelpers/renderHelpers"
55
import{LanguageasauthXServiceLanguage}from"../../../xServices/auth/authXService"
66
import{LanguageasSSHKeysPageLanguage,SSHKeysPage}from"./SSHKeysPage"
7+
import{LanguageasSSHKeysPageViewLanguage}from"./SSHKeysPageView"
78

89
describe("SSH keys Page",()=>{
910
it("shows the SSH key",async()=>{
@@ -26,7 +27,7 @@ describe("SSH keys Page", () => {
2627

2728
// Click on the "Regenerate" button to display the confirm dialog
2829
constregenerateButton=screen.getByRole("button",{
29-
name:SSHKeysPageLanguage.regenerateLabel,
30+
name:SSHKeysPageViewLanguage.regenerateLabel,
3031
})
3132
fireEvent.click(regenerateButton)
3233
constconfirmDialog=screen.getByRole("dialog")
@@ -72,7 +73,7 @@ describe("SSH keys Page", () => {
7273

7374
// Click on the "Regenerate" button to display the confirm dialog
7475
constregenerateButton=screen.getByRole("button",{
75-
name:SSHKeysPageLanguage.regenerateLabel,
76+
name:SSHKeysPageViewLanguage.regenerateLabel,
7677
})
7778
fireEvent.click(regenerateButton)
7879
constconfirmDialog=screen.getByRole("dialog")
@@ -85,7 +86,7 @@ describe("SSH keys Page", () => {
8586
fireEvent.click(confirmButton)
8687

8788
// Check if the error message is displayed
88-
awaitscreen.findByText(authXServiceLanguage.errorRegenerateSSHKey)
89+
awaitscreen.findByText(SSHKeysPageViewLanguage.errorRegenerateSSHKey)
8990

9091
// Check if the API was called correctly
9192
expect(API.regenerateUserSSHKey).toBeCalledTimes(1)

‎site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.tsx

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
1-
importBoxfrom"@material-ui/core/Box"
2-
importButtonfrom"@material-ui/core/Button"
3-
importCircularProgressfrom"@material-ui/core/CircularProgress"
41
import{useActor}from"@xstate/react"
52
importReact,{useContext,useEffect}from"react"
6-
import{CodeExample}from"../../../components/CodeExample/CodeExample"
73
import{ConfirmDialog}from"../../../components/ConfirmDialog/ConfirmDialog"
84
import{Section}from"../../../components/Section/Section"
9-
import{Stack}from"../../../components/Stack/Stack"
105
import{XServiceContext}from"../../../xServices/StateContext"
6+
import{SSHKeysPageView}from"./SSHKeysPageView"
117

128
exportconstLanguage={
139
title:"SSH keys",
1410
description:
1511
"Coder automatically inserts a private key into every workspace; you can add the corresponding public key to any services (such as Git) that you need access to from your workspace.",
16-
regenerateLabel:"Regenerate",
1712
regenerateDialogTitle:"Regenerate SSH key?",
1813
regenerateDialogMessage:
1914
"You will need to replace the public SSH key on services you use it with, and you'll need to rebuild existing workspaces.",
@@ -24,36 +19,30 @@ export const Language = {
2419
exportconstSSHKeysPage:React.FC=()=>{
2520
constxServices=useContext(XServiceContext)
2621
const[authState,authSend]=useActor(xServices.authXService)
27-
const{ sshKey}=authState.context
22+
const{ sshKey, getSSHKeyError, regenerateSSHKeyError}=authState.context
2823

2924
useEffect(()=>{
3025
authSend({type:"GET_SSH_KEY"})
3126
},[authSend])
3227

28+
constisLoading=authState.matches("signedIn.ssh.gettingSSHKey")
29+
consthasLoaded=authState.matches("signedIn.ssh.loaded")
30+
31+
constonRegenerateClick=()=>{
32+
authSend({type:"REGENERATE_SSH_KEY"})
33+
}
34+
3335
return(
3436
<>
3537
<Sectiontitle={Language.title}description={Language.description}>
36-
{!sshKey&&(
37-
<Boxp={4}>
38-
<CircularProgresssize={26}/>
39-
</Box>
40-
)}
41-
42-
{sshKey&&(
43-
<Stack>
44-
<CodeExamplecode={sshKey.public_key.trim()}/>
45-
<div>
46-
<Button
47-
variant="outlined"
48-
onClick={()=>{
49-
authSend({type:"REGENERATE_SSH_KEY"})
50-
}}
51-
>
52-
{Language.regenerateLabel}
53-
</Button>
54-
</div>
55-
</Stack>
56-
)}
38+
<SSHKeysPageView
39+
isLoading={isLoading}
40+
hasLoaded={hasLoaded}
41+
getSSHKeyError={getSSHKeyError}
42+
regenerateSSHKeyError={regenerateSSHKeyError}
43+
sshKey={sshKey}
44+
onRegenerateClick={onRegenerateClick}
45+
/>
5746
</Section>
5847

5948
<ConfirmDialog
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import{Story}from"@storybook/react"
2+
import{makeMockApiError}from"testHelpers/entities"
3+
import{SSHKeysPageView,SSHKeysPageViewProps}from"./SSHKeysPageView"
4+
5+
exportdefault{
6+
title:"components/SSHKeysPageView",
7+
component:SSHKeysPageView,
8+
argTypes:{
9+
onRegenerateClick:{action:"Submit"},
10+
},
11+
}
12+
13+
constTemplate:Story<SSHKeysPageViewProps>=(args:SSHKeysPageViewProps)=>(
14+
<SSHKeysPageView{...args}/>
15+
)
16+
17+
exportconstExample=Template.bind({})
18+
Example.args={
19+
isLoading:false,
20+
hasLoaded:true,
21+
sshKey:{
22+
user_id:"test-user-id",
23+
created_at:"2022-07-28T07:45:50.795918897Z",
24+
updated_at:"2022-07-28T07:45:50.795919142Z",
25+
public_key:"SSH-Key",
26+
},
27+
onRegenerateClick:()=>{
28+
returnPromise.resolve()
29+
},
30+
}
31+
32+
exportconstLoading=Template.bind({})
33+
Loading.args={
34+
...Example.args,
35+
isLoading:true,
36+
}
37+
38+
exportconstWithGetSSHKeyError=Template.bind({})
39+
WithGetSSHKeyError.args={
40+
...Example.args,
41+
hasLoaded:false,
42+
getSSHKeyError:makeMockApiError({
43+
message:"Failed to get SSH key",
44+
}),
45+
}
46+
47+
exportconstWithRegenerateSSHKeyError=Template.bind({})
48+
WithRegenerateSSHKeyError.args={
49+
...Example.args,
50+
regenerateSSHKeyError:makeMockApiError({
51+
message:"Failed to regenerate SSH key",
52+
}),
53+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp