mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
perf: reset linear api key dialog state during render (#4290)
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
CLOSED_LINEAR_API_KEY_DIALOG_STATE,
|
||||
resolveLinearApiKeyDialogState,
|
||||
type LinearApiKeyDialogState
|
||||
} from './linear-api-key-dialog-state'
|
||||
|
||||
describe('resolveLinearApiKeyDialogState', () => {
|
||||
it('preserves draft and error state while the dialog is open', () => {
|
||||
const state: LinearApiKeyDialogState = {
|
||||
apiKeyDraft: 'lin_api_key',
|
||||
connectState: 'error',
|
||||
connectError: 'Nope'
|
||||
}
|
||||
|
||||
expect(resolveLinearApiKeyDialogState(state, true)).toBe(state)
|
||||
})
|
||||
|
||||
it('preserves identity for an already-reset closed dialog', () => {
|
||||
expect(resolveLinearApiKeyDialogState(CLOSED_LINEAR_API_KEY_DIALOG_STATE, false)).toBe(
|
||||
CLOSED_LINEAR_API_KEY_DIALOG_STATE
|
||||
)
|
||||
})
|
||||
|
||||
it('resets draft and connection state when the dialog is closed', () => {
|
||||
const state: LinearApiKeyDialogState = {
|
||||
apiKeyDraft: 'lin_api_key',
|
||||
connectState: 'connecting',
|
||||
connectError: 'Previous error'
|
||||
}
|
||||
|
||||
expect(resolveLinearApiKeyDialogState(state, false)).toEqual(CLOSED_LINEAR_API_KEY_DIALOG_STATE)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
export type LinearApiKeyDialogConnectState = 'idle' | 'connecting' | 'error'
|
||||
|
||||
export type LinearApiKeyDialogState = {
|
||||
apiKeyDraft: string
|
||||
connectState: LinearApiKeyDialogConnectState
|
||||
connectError: string | null
|
||||
}
|
||||
|
||||
export const CLOSED_LINEAR_API_KEY_DIALOG_STATE: LinearApiKeyDialogState = Object.freeze({
|
||||
apiKeyDraft: '',
|
||||
connectState: 'idle',
|
||||
connectError: null
|
||||
})
|
||||
|
||||
export function createLinearApiKeyDialogState(): LinearApiKeyDialogState {
|
||||
return CLOSED_LINEAR_API_KEY_DIALOG_STATE
|
||||
}
|
||||
|
||||
export function resolveLinearApiKeyDialogState(
|
||||
state: LinearApiKeyDialogState,
|
||||
open: boolean
|
||||
): LinearApiKeyDialogState {
|
||||
if (open) {
|
||||
return state
|
||||
}
|
||||
if (state.apiKeyDraft === '' && state.connectState === 'idle' && state.connectError === null) {
|
||||
return state
|
||||
}
|
||||
return CLOSED_LINEAR_API_KEY_DIALOG_STATE
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useId, useMemo, useState } from 'react'
|
||||
import { useId, useMemo, useState } from 'react'
|
||||
import { ExternalLink, LoaderCircle, Lock } from 'lucide-react'
|
||||
import type { LinearWorkspace } from '../../../shared/types'
|
||||
import {
|
||||
@@ -20,6 +20,10 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
createLinearApiKeyDialogState,
|
||||
resolveLinearApiKeyDialogState
|
||||
} from './linear-api-key-dialog-state'
|
||||
|
||||
type LinearApiKeyDialogProps = {
|
||||
open: boolean
|
||||
@@ -49,23 +53,19 @@ export function LinearApiKeyDialog({
|
||||
const mountedRef = useMountedRef()
|
||||
const apiKeyInputId = useId()
|
||||
const apiKeyErrorId = useId()
|
||||
const [apiKeyDraft, setApiKeyDraft] = useState('')
|
||||
const [connectState, setConnectState] = useState<'idle' | 'connecting' | 'error'>('idle')
|
||||
const [connectError, setConnectError] = useState<string | null>(null)
|
||||
const [dialogState, setDialogState] = useState(createLinearApiKeyDialogState)
|
||||
|
||||
const runtimeTarget = useMemo(() => getActiveRuntimeTarget(settings), [settings])
|
||||
const personalKeyUrl = buildLinearPersonalApiKeySettingsUrl(workspace?.organizationUrlKey)
|
||||
const workspaceApiUrl = buildLinearWorkspaceApiSettingsUrl(workspace?.organizationUrlKey)
|
||||
const submitLabel = connectLabel ?? (workspace ? 'Update access' : 'Connect')
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
return
|
||||
}
|
||||
setApiKeyDraft('')
|
||||
setConnectState('idle')
|
||||
setConnectError(null)
|
||||
}, [open])
|
||||
const resolvedDialogState = resolveLinearApiKeyDialogState(dialogState, open)
|
||||
if (resolvedDialogState !== dialogState) {
|
||||
// Why: parent-controlled close can race an in-flight connect request; keep
|
||||
// hidden draft/error state reset before the next open paints.
|
||||
setDialogState(resolvedDialogState)
|
||||
}
|
||||
const { apiKeyDraft, connectState, connectError } = resolvedDialogState
|
||||
|
||||
const handleOpenChange = (nextOpen: boolean): void => {
|
||||
if (connectState !== 'connecting') {
|
||||
@@ -78,26 +78,30 @@ export function LinearApiKeyDialog({
|
||||
if (!apiKey || connectState === 'connecting') {
|
||||
return
|
||||
}
|
||||
setConnectState('connecting')
|
||||
setConnectError(null)
|
||||
setDialogState((current) => ({ ...current, connectState: 'connecting', connectError: null }))
|
||||
try {
|
||||
const result = await connectLinear(apiKey)
|
||||
if (!mountedRef.current) {
|
||||
return
|
||||
}
|
||||
if (result.ok) {
|
||||
setApiKeyDraft('')
|
||||
setConnectState('idle')
|
||||
setDialogState(createLinearApiKeyDialogState())
|
||||
onOpenChange(false)
|
||||
onConnected?.()
|
||||
return
|
||||
}
|
||||
setConnectState('error')
|
||||
setConnectError(result.error)
|
||||
setDialogState((current) => ({
|
||||
...current,
|
||||
connectState: 'error',
|
||||
connectError: result.error
|
||||
}))
|
||||
} catch (error) {
|
||||
if (mountedRef.current) {
|
||||
setConnectState('error')
|
||||
setConnectError(error instanceof Error ? error.message : 'Connection failed')
|
||||
setDialogState((current) => ({
|
||||
...current,
|
||||
connectState: 'error',
|
||||
connectError: error instanceof Error ? error.message : 'Connection failed'
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,11 +147,12 @@ export function LinearApiKeyDialog({
|
||||
placeholder="lin_api_..."
|
||||
value={apiKeyDraft}
|
||||
onChange={(event) => {
|
||||
setApiKeyDraft(event.target.value)
|
||||
if (connectState === 'error') {
|
||||
setConnectState('idle')
|
||||
setConnectError(null)
|
||||
}
|
||||
const nextDraft = event.target.value
|
||||
setDialogState((current) => ({
|
||||
apiKeyDraft: nextDraft,
|
||||
connectState: current.connectState === 'error' ? 'idle' : current.connectState,
|
||||
connectError: current.connectState === 'error' ? null : current.connectError
|
||||
}))
|
||||
}}
|
||||
disabled={connectState === 'connecting'}
|
||||
aria-invalid={connectState === 'error'}
|
||||
|
||||
Reference in New Issue
Block a user