Thanks to visit codestin.com
Credit goes to github.com

Skip to content

refactor: Extract ssh logic from auth service #5670

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

Merged
merged 2 commits into from
Jan 11, 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
3 changes: 2 additions & 1 deletion site/src/i18n/en/userSettingsPage.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"securityUpdateSuccessMessage": "Updated password."
"securityUpdateSuccessMessage": "Updated password.",
"sshRegenerateSuccessMessage": "SSH Key regenerated successfully."
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {
MockGitSSHKey,
renderWithAuth,
} from "../../../testHelpers/renderHelpers"
import { Language as authXServiceLanguage } from "../../../xServices/auth/authXService"
import { Language as SSHKeysPageLanguage, SSHKeysPage } from "./SSHKeysPage"
import { Language as SSHKeysPageViewLanguage } from "./SSHKeysPageView"
import { i18n } from "i18n"

const { t } = i18n

describe("SSH keys Page", () => {
it("shows the SSH key", async () => {
Expand Down Expand Up @@ -52,7 +54,10 @@ describe("SSH keys Page", () => {
fireEvent.click(confirmButton)

// Check if the success message is displayed
await screen.findByText(authXServiceLanguage.successRegenerateSSHKey)
const successMessage = t("sshRegenerateSuccessMessage", {
ns: "userSettingsPage",
})
await screen.findByText(successMessage)

// Check if the API was called correctly
expect(API.regenerateUserSSHKey).toBeCalledTimes(1)
Expand Down
32 changes: 12 additions & 20 deletions site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useActor } from "@xstate/react"
import { useContext, useEffect, PropsWithChildren, FC } from "react"
import { useMachine } from "@xstate/react"
import { PropsWithChildren, FC } from "react"
import { sshKeyMachine } from "xServices/sshKey/sshKeyXService"
import { ConfirmDialog } from "../../../components/Dialogs/ConfirmDialog/ConfirmDialog"
import { Section } from "../../../components/SettingsLayout/Section"
import { XServiceContext } from "../../../xServices/StateContext"
import { SSHKeysPageView } from "./SSHKeysPageView"

export const Language = {
Expand All @@ -15,19 +15,13 @@ export const Language = {
}

export const SSHKeysPage: FC<PropsWithChildren<unknown>> = () => {
const xServices = useContext(XServiceContext)
const [authState, authSend] = useActor(xServices.authXService)
const { sshKey, getSSHKeyError, regenerateSSHKeyError } = authState.context

useEffect(() => {
authSend({ type: "GET_SSH_KEY" })
}, [authSend])

const isLoading = authState.matches("signedIn.ssh.gettingSSHKey")
const hasLoaded = authState.matches("signedIn.ssh.loaded")
const [sshState, sshSend] = useMachine(sshKeyMachine)
const isLoading = sshState.matches("gettingSSHKey")
const hasLoaded = sshState.matches("loaded")
const { getSSHKeyError, regenerateSSHKeyError, sshKey } = sshState.context

const onRegenerateClick = () => {
authSend({ type: "REGENERATE_SSH_KEY" })
sshSend({ type: "REGENERATE_SSH_KEY" })
}

return (
Expand All @@ -46,17 +40,15 @@ export const SSHKeysPage: FC<PropsWithChildren<unknown>> = () => {
<ConfirmDialog
type="delete"
hideCancel={false}
open={authState.matches("signedIn.ssh.loaded.confirmSSHKeyRegenerate")}
confirmLoading={authState.matches(
"signedIn.ssh.loaded.regeneratingSSHKey",
)}
open={sshState.matches("confirmSSHKeyRegenerate")}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes we're making variables for sshState keys and sometimes we aren't - can we choose one and be consistent?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The matches function only return true or false. Unfortunately, xstate v4 does not have a way to bind a context type with a specific state so, sometimes, we have to check the state with match and pass the value in the context. Buuut, after these minor refactoring, I think we can remove some xstate machines and use just use fetch directly.

confirmLoading={sshState.matches("regeneratingSSHKey")}
title={Language.regenerateDialogTitle}
confirmText={Language.confirmLabel}
onConfirm={() => {
authSend({ type: "CONFIRM_REGENERATE_SSH_KEY" })
sshSend({ type: "CONFIRM_REGENERATE_SSH_KEY" })
}}
onClose={() => {
authSend({ type: "CANCEL_REGENERATE_SSH_KEY" })
sshSend({ type: "CANCEL_REGENERATE_SSH_KEY" })
}}
description={<>{Language.regenerateDialogMessage}</>}
/>
Expand Down
110 changes: 0 additions & 110 deletions site/src/xServices/auth/authXService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { displaySuccess } from "../../components/GlobalSnackbar/utils"

export const Language = {
successProfileUpdate: "Updated settings.",
successRegenerateSSHKey: "SSH Key regenerated successfully",
}

export const checks = {
Expand Down Expand Up @@ -83,20 +82,12 @@ export interface AuthContext {
methods?: TypesGen.AuthMethods
permissions?: Permissions
checkPermissionsError?: Error | unknown
// SSH
sshKey?: TypesGen.GitSSHKey
getSSHKeyError?: Error | unknown
regenerateSSHKeyError?: Error | unknown
}

export type AuthEvent =
| { type: "SIGN_OUT" }
| { type: "SIGN_IN"; email: string; password: string }
| { type: "UPDATE_PROFILE"; data: TypesGen.UpdateUserProfileRequest }
| { type: "GET_SSH_KEY" }
| { type: "REGENERATE_SSH_KEY" }
| { type: "CONFIRM_REGENERATE_SSH_KEY" }
| { type: "CANCEL_REGENERATE_SSH_KEY" }
| { type: "GET_AUTH_METHODS" }

export const authMachine =
Expand Down Expand Up @@ -128,12 +119,6 @@ export const authMachine =
checkPermissions: {
data: TypesGen.AuthorizationResponse
}
getSSHKey: {
data: TypesGen.GitSSHKey
}
regenerateSSHKey: {
data: TypesGen.GitSSHKey
}
hasFirstUser: {
data: boolean
}
Expand Down Expand Up @@ -279,79 +264,6 @@ export const authMachine =
},
},
},
ssh: {
initial: "idle",
states: {
idle: {
on: {
GET_SSH_KEY: {
target: "gettingSSHKey",
},
},
},
gettingSSHKey: {
entry: "clearGetSSHKeyError",
invoke: {
src: "getSSHKey",
onDone: [
{
actions: "assignSSHKey",
target: "loaded",
},
],
onError: [
{
actions: "assignGetSSHKeyError",
target: "idle",
},
],
},
},
loaded: {
initial: "idle",
states: {
idle: {
on: {
REGENERATE_SSH_KEY: {
target: "confirmSSHKeyRegenerate",
},
},
},
confirmSSHKeyRegenerate: {
on: {
CANCEL_REGENERATE_SSH_KEY: {
target: "idle",
},
CONFIRM_REGENERATE_SSH_KEY: {
target: "regeneratingSSHKey",
},
},
},
regeneratingSSHKey: {
entry: "clearRegenerateSSHKeyError",
invoke: {
src: "regenerateSSHKey",
onDone: [
{
actions: [
"assignSSHKey",
"notifySuccessSSHKeyRegenerated",
],
target: "idle",
},
],
onError: [
{
actions: "assignRegenerateSSHKeyError",
target: "idle",
},
],
},
},
},
},
},
},
methods: {
initial: "idle",
states: {
Expand Down Expand Up @@ -480,9 +392,6 @@ export const authMachine =
checks: permissionsToCheck,
})
},
// SSH
getSSHKey: () => API.getUserSSHKey(),
regenerateSSHKey: () => API.regenerateUserSSHKey(),
// First user
hasFirstUser: () => API.hasFirstUser(),
},
Expand Down Expand Up @@ -538,25 +447,6 @@ export const authMachine =
clearGetPermissionsError: assign({
checkPermissionsError: (_) => undefined,
}),
// SSH
assignSSHKey: assign({
sshKey: (_, event) => event.data,
}),
assignGetSSHKeyError: assign({
getSSHKeyError: (_, event) => event.data,
}),
clearGetSSHKeyError: assign({
getSSHKeyError: (_) => undefined,
}),
assignRegenerateSSHKeyError: assign({
regenerateSSHKeyError: (_, event) => event.data,
}),
clearRegenerateSSHKeyError: assign({
regenerateSSHKeyError: (_) => undefined,
}),
notifySuccessSSHKeyRegenerated: () => {
displaySuccess(Language.successRegenerateSSHKey)
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:D we love to see it

redirect: (_, { data }) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- data can be undefined
if (!data) {
Expand Down
125 changes: 125 additions & 0 deletions site/src/xServices/sshKey/sshKeyXService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { getUserSSHKey, regenerateUserSSHKey } from "api/api"
import { GitSSHKey } from "api/typesGenerated"
import { displaySuccess } from "components/GlobalSnackbar/utils"
import { createMachine, assign } from "xstate"
import { i18n } from "i18n"

const { t } = i18n

interface Context {
sshKey?: GitSSHKey
getSSHKeyError?: unknown
regenerateSSHKeyError?: unknown
}

type Events =
| { type: "REGENERATE_SSH_KEY" }
| { type: "CONFIRM_REGENERATE_SSH_KEY" }
| { type: "CANCEL_REGENERATE_SSH_KEY" }

export const sshKeyMachine = createMachine(
{
id: "sshKeyState",
predictableActionArguments: true,
schema: {
context: {} as Context,
events: {} as Events,
services: {} as {
getSSHKey: {
data: GitSSHKey
}
regenerateSSHKey: {
data: GitSSHKey
}
},
},
tsTypes: {} as import("./sshKeyXService.typegen").Typegen0,
initial: "gettingSSHKey",
states: {
gettingSSHKey: {
entry: "clearGetSSHKeyError",
invoke: {
src: "getSSHKey",
onDone: [
{
actions: "assignSSHKey",
target: "loaded",
},
],
onError: [
{
actions: "assignGetSSHKeyError",
target: "notLoaded",
},
],
},
},
notLoaded: {
type: "final",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about our conversation about simplifying XState machines by having less granularity: instead of making a separate error, getSSHKeyError, can we instead look at sshState.matches("notLoaded")?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case we store this because we want to display the error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn't see the separate loading state - makes sense!

},
loaded: {
on: {
REGENERATE_SSH_KEY: {
target: "confirmSSHKeyRegenerate",
},
},
},
confirmSSHKeyRegenerate: {
on: {
CANCEL_REGENERATE_SSH_KEY: {
target: "loaded",
},
CONFIRM_REGENERATE_SSH_KEY: {
target: "regeneratingSSHKey",
},
},
},
regeneratingSSHKey: {
entry: "clearRegenerateSSHKeyError",
invoke: {
src: "regenerateSSHKey",
onDone: [
{
actions: ["assignSSHKey", "notifySuccessSSHKeyRegenerated"],
target: "loaded",
},
],
onError: [
{
actions: "assignRegenerateSSHKeyError",
target: "loaded",
},
],
},
},
},
},
{
services: {
getSSHKey: () => getUserSSHKey(),
regenerateSSHKey: () => regenerateUserSSHKey(),
},
actions: {
assignSSHKey: assign({
sshKey: (_, { data }) => data,
}),
assignGetSSHKeyError: assign({
getSSHKeyError: (_, { data }) => data,
}),
clearGetSSHKeyError: assign({
getSSHKeyError: (_) => undefined,
}),
assignRegenerateSSHKeyError: assign({
regenerateSSHKeyError: (_, { data }) => data,
}),
clearRegenerateSSHKeyError: assign({
regenerateSSHKeyError: (_) => undefined,
}),
notifySuccessSSHKeyRegenerated: () => {
displaySuccess(
t("sshRegenerateSuccessMessage", { ns: "userSettingsPage" }),
)
},
},
},
)