Skip to content

Commit

Permalink
Make number of deploy runs fetched configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
geirsagberg committed Mar 18, 2023
1 parent 885c927 commit 9623250
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 83 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-modal-promise": "^1.0.2",
"uuid": "^9.0.0"
"recoil": "^0.7.7",
"uuid": "^9.0.0",
"zod": "^3.21.4"
},
"devDependencies": {
"@graphql-codegen/cli": "^3.2.2",
Expand Down
10 changes: 7 additions & 3 deletions src/api/fetchHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dayjs from 'dayjs'
import { getOrElse } from 'fp-ts/lib/Either'
import { pipe } from 'fp-ts/lib/function'
import { keyBy, orderBy } from 'lodash-es'
import { useRecoilValue } from 'recoil'
import {
DeployFragment,
DeploymentState,
Expand All @@ -20,10 +21,12 @@ import {
WorkflowRun,
WorkflowRunsCodec,
} from '../overmind/state'
import { appSettings } from '../state'
import graphQLApi from '../utils/graphQLApi'

export const useFetchReleases = () => {
const { selectedApplication, appSettings } = useAppState()
const { selectedApplication } = useAppState()
const refreshIntervalSecs = useRecoilValue(appSettings.refreshIntervalSecs)

const repo = selectedApplication?.repo
const prefix = selectedApplication?.releaseFilter ?? ''
Expand Down Expand Up @@ -79,7 +82,7 @@ export const useFetchReleases = () => {
return releases
},
{
refetchInterval: 1000 * appSettings.refreshIntervalSecs,
refetchInterval: 1000 * refreshIntervalSecs,
}
)

Expand Down Expand Up @@ -121,6 +124,7 @@ export const useFetchWorkflows = () => {
export const useFetchWorkflowRuns = () => {
const { token, selectedApplication } = useAppState()
const { restApi } = useEffects()
const workflowRuns = useRecoilValue(appSettings.workflowRuns)

const repo = selectedApplication?.repo
const workflowId = DeployWorkflowCodec.is(selectedApplication?.deploySettings)
Expand All @@ -137,7 +141,7 @@ export const useFetchWorkflowRuns = () => {
workflow_id: workflowId,
owner,
repo: name,
per_page: 100,
per_page: workflowRuns,
})

return pipe(
Expand Down
44 changes: 25 additions & 19 deletions src/components/SettingsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,29 @@ import {
DialogTitle,
TextField,
} from '@mui/material'
import { useRecoilState } from 'recoil'
import { useActions, useAppState } from '../overmind'
import { AppState } from '../overmind/state'
import {
appSettings,
appSettingsDescription,
defaultAppSettings,
} from '../state'
import { AppSettings } from '../state/schemas'
import { keysOf } from '../utils'

interface EditorProps<T> {
selector: (state: AppState) => T
label: string
interface EditorProps {
setting: keyof AppSettings
}

function Editor<T>({ selector, label }: EditorProps<T>) {
const state = useAppState()
const value = selector(state)
const { setState } = useActions()
function Editor<T>({ setting }: EditorProps) {
const [value, setValue] = useRecoilState(appSettings[setting])
const label = appSettingsDescription[setting]
if (typeof value === 'string') {
return (
<TextField
label={label}
value={value}
onChange={(e) => setState({ selector, value: e.target.value })}
onChange={(e) => setValue(e.target.value as any)}
fullWidth
/>
)
Expand All @@ -33,7 +38,7 @@ function Editor<T>({ selector, label }: EditorProps<T>) {
<TextField
label={label}
value={value}
onChange={(e) => setState({ selector, value: +e.target.value })}
onChange={(e) => setValue(+e.target.value)}
type="number"
fullWidth
/>
Expand All @@ -48,15 +53,16 @@ export const SettingsDialog = () => {
return (
<Dialog open={!!settingsDialog} fullWidth onClose={hideSettings}>
<DialogTitle>Settings</DialogTitle>
<DialogContent style={{ gap: '1rem', display: 'flex', padding: '1rem' }}>
<Editor
label="Deploy timeout (secs)"
selector={(state) => state.appSettings.deployTimeoutSecs}
/>
<Editor
label="Status refresh interval (secs)"
selector={(state) => state.appSettings.refreshIntervalSecs}
/>
<DialogContent
sx={{
gap: '1rem',
display: 'grid',
padding: '1rem',
gridTemplateColumns: 'repeat(auto-fit, minmax(16rem, 1fr))',
}}>
{keysOf(defaultAppSettings).map((key) => (
<Editor key={key} setting={key} />
))}
</DialogContent>
<DialogActions>
<Button onClick={hideSettings}>Close</Button>
Expand Down
21 changes: 12 additions & 9 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { Provider } from 'overmind-react'
import React from 'react'
import ReactDOM from 'react-dom'
import { RecoilRoot } from 'recoil'
import './@setup'
import App from './App'
import { overmind } from './overmind'
Expand All @@ -14,15 +15,17 @@ const queryClient = new QueryClient()

ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Provider value={overmind}>
<App />
</Provider>
</ThemeProvider>
<ReactQueryDevtools />
</QueryClientProvider>
<RecoilRoot>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Provider value={overmind}>
<App />
</Provider>
</ThemeProvider>
<ReactQueryDevtools />
</QueryClientProvider>
</RecoilRoot>
</React.StrictMode>,
document.getElementById('root')
)
Expand Down
22 changes: 5 additions & 17 deletions src/overmind/actions.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import dayjs from 'dayjs'
import { getOrElse } from 'fp-ts/lib/Either'
import { pipe } from 'fp-ts/lib/function'
import { clone, get, set, some } from 'lodash-es'
import { clone, some } from 'lodash-es'
import { Context } from '.'
import { showConfirm } from '../utils/dialog'
import {
ApplicationDialogState,
ApplicationsByIdCodec,
AppState,
createApplicationConfig,
createApplicationDialogState,
createDeployWorkflowSettings,
DeploymentDialogState,
DeployWorkflowCodec,
DeployWorkflowSettings,
DeploymentDialogState,
EnvironmentDialogState,
EnvironmentSettings,
RepoModel,
createApplicationConfig,
createApplicationDialogState,
createDeployWorkflowSettings,
} from './state'
import { getDeploymentId } from './utils'

Expand All @@ -28,17 +27,6 @@ export const showSettings = ({ state }: Context) => (state.settingsDialog = {})

export const hideSettings = ({ state }: Context) => delete state.settingsDialog

export const setState = <T>(
{ state }: Context,
{ selector, value }: { selector: (state: AppState) => T; value: T }
) => {
const path = selector.toString().replace(/^.*?\./, '')
if (get(state, path) === undefined) {
throw Error('Unkown path ' + path)
}
set(state, path, value)
}

export const showNewApplicationModal = ({ state }: Context) => {
state.newApplicationDialog = createApplicationDialogState()

Expand Down
23 changes: 3 additions & 20 deletions src/overmind/onInitialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import { Context } from '.'
import graphQLApi from '../utils/graphQLApi'
import { restApi } from './effects'
import {
ApplicationsByIdCodec,
AppSettingsCodec,
AppState,
defaultAppSettings,
ApplicationsByIdCodec,
PendingDeployment,
PendingDeploymentsCodec,
} from './state'
Expand Down Expand Up @@ -74,20 +72,6 @@ export const onInitializeOvermind = ({
{ nested: false }
)

sync(
(state) => state.appSettings,
(data) => {
state.appSettings = pipe(
AppSettingsCodec.decode(data),
getOrElse((e) => {
console.error(e)
return defaultAppSettings
})
)
},
{ nested: true }
)

sync(
(state) => state.pendingDeployments,
(data) => {
Expand All @@ -99,9 +83,8 @@ export const onInitializeOvermind = ({
}),
(data) =>
pickBy(data, (pending) =>
dayjs(pending.createdAt).isBefore(
dayjs().add(state.appSettings.deployTimeoutSecs, 'seconds')
)
// TODO: Move pending deployments to Recoil
dayjs(pending.createdAt).isBefore(dayjs().add(60, 'seconds'))
)
)
},
Expand Down
14 changes: 0 additions & 14 deletions src/overmind/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,6 @@ export const ApplicationsByIdCodec = t.record(t.string, ApplicationConfigCodec)

export const DeploySettingsByRepoCodec = t.record(t.string, DeploySettingsCodec)

export const AppSettingsCodec = t.type({
deployTimeoutSecs: t.number,
refreshIntervalSecs: t.number,
})

export type AppSettings = t.TypeOf<typeof AppSettingsCodec>

export type DeploySettings = t.TypeOf<typeof DeploySettingsCodec>

export type ApplicationDialogState = {
Expand Down Expand Up @@ -218,7 +211,6 @@ export const PendingDeploymentsCodec = t.record(

export type AppState = {
token: string
appSettings: AppSettings
applicationsById: Record<string, ApplicationConfig>
selectedApplicationId: string
selectedApplication?: ApplicationConfig
Expand All @@ -231,14 +223,8 @@ export type AppState = {
pendingDeployments: Record<string, PendingDeployment>
}

export const defaultAppSettings: AppSettings = {
deployTimeoutSecs: 60,
refreshIntervalSecs: 60,
}

const state: AppState = {
token: '',
appSettings: defaultAppSettings,
applicationsById: {},
selectedApplicationId: '',
get selectedApplication() {
Expand Down
49 changes: 49 additions & 0 deletions src/state/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { mapValues } from 'lodash-es'
import { atom, selector } from 'recoil'
import { AppSettings, appSettingsSchema } from './schemas'

export const defaultAppSettings: AppSettings = {
deployTimeoutSecs: 60,
refreshIntervalSecs: 60,
workflowRuns: 100,
}

export const appSettingsDescription: Record<keyof AppSettings, string> = {
deployTimeoutSecs: 'Deploy timeout (seconds)',
refreshIntervalSecs: 'Status refresh interval (seconds)',
workflowRuns: 'Number of deploy runs to fetch (max 100)',
}

export const appSettingsState = atom({
default: defaultAppSettings,
key: 'appSettings',
effects: [
({ onSet, setSelf }) => {
const storedJson = localStorage.getItem('appSettings')
if (storedJson) {
try {
const appSettings = appSettingsSchema.parse(JSON.parse(storedJson))
setSelf(appSettings)
} catch (error) {
console.log(error)
}
}
onSet((newValue) => {
localStorage.setItem('appSettings', JSON.stringify(newValue))
})
},
],
})

export const appSettings = mapValues(defaultAppSettings, (_, key) =>
selector({
key,
get: ({ get }) =>
get(appSettingsState)[key as keyof typeof defaultAppSettings],
set: ({ set, get }, newValue) =>
set(appSettingsState, {
...get(appSettingsState),
[key]: newValue,
}),
})
)
9 changes: 9 additions & 0 deletions src/state/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { z } from 'zod'

export const appSettingsSchema = z.object({
deployTimeoutSecs: z.number(),
refreshIntervalSecs: z.number(),
workflowRuns: z.number(),
})

export interface AppSettings extends z.infer<typeof appSettingsSchema> {}
3 changes: 3 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function keysOf<T extends {}>(x: T): (keyof T)[] {
return Object.keys(x) as (keyof T)[]
}
Loading

0 comments on commit 9623250

Please sign in to comment.