Skip to content
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

ui/services: convert IntegrationKeyCreateDialog & IntegrationKeyList to ts #2568

Merged
merged 13 commits into from
Oct 13, 2022
2 changes: 1 addition & 1 deletion web/src/app/lists/FlatList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export interface FlatListProps extends ListProps {
items: FlatListListItem[]

// header elements will be displayed at the top of the list.
headerNote?: string // left-aligned
headerNote?: JSX.Element | string // left-aligned
headerAction?: JSX.Element // right-aligned

// emptyMessage will be displayed if there are no items in the list.
Expand Down
98 changes: 0 additions & 98 deletions web/src/app/services/IntegrationKeyCreateDialog.js

This file was deleted.

58 changes: 58 additions & 0 deletions web/src/app/services/IntegrationKeyCreateDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useState } from 'react'
import { useMutation, gql } from 'urql'

import { fieldErrors, nonFieldErrors } from '../util/errutil'

import FormDialog from '../dialogs/FormDialog'
import IntegrationKeyForm, { Value } from './IntegrationKeyForm'

const mutation = gql`
mutation ($input: CreateIntegrationKeyInput!) {
createIntegrationKey(input: $input) {
id
name
type
href
}
}
`

export default function IntegrationKeyCreateDialog(props: {
serviceID: string
onClose: () => void
}): JSX.Element {
const [value, setValue] = useState<Value | null>(null)
const { serviceID, onClose } = props

const [createIntegrationKeyStatus, createIntegrationKey] =
useMutation(mutation)

return (
<FormDialog
maxWidth='sm'
title='Create New Integration Key'
loading={createIntegrationKeyStatus.fetching}
errors={nonFieldErrors(createIntegrationKeyStatus.error)}
onClose={onClose}
onSubmit={(): void => {
createIntegrationKey(
{ input: { serviceID, ...value } },
{ additionalTypenames: ['IntegrationKey'] },
).then(onClose)
}}
form={
<IntegrationKeyForm
errors={fieldErrors(createIntegrationKeyStatus.error)}
disabled={createIntegrationKeyStatus.fetching}
value={
value || {
name: '',
type: 'generic',
}
}
onChange={(value): void => setValue(value)}
/>
}
/>
)
}
15 changes: 8 additions & 7 deletions web/src/app/services/IntegrationKeyForm.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import React from 'react'
import React, { ReactElement } from 'react'
import Grid from '@mui/material/Grid'
import TextField from '@mui/material/TextField'
import MenuItem from '@mui/material/MenuItem'
import { FormContainer, FormField } from '../forms'
import { Config } from '../util/RequireConfig'
import { IntegrationKeyType } from '../../schema'
import { FieldError } from '../util/errutil'

interface Value {
export interface Value {
name: string
type: IntegrationKeyType
}

interface IntegrationKeyFormProps {
value: Value

errors: {
field: 'name' | 'type'
message: string
}[]
errors: FieldError[]

onChange: (val: Value) => void

// can be deleted when FormContainer.js is converted to ts
disabled: boolean
}

export default function IntegrationKeyForm(
Expand All @@ -40,7 +41,7 @@ export default function IntegrationKeyForm(
</Grid>
<Grid item xs={12}>
<Config>
{(cfg: { [x: string]: unknown }) => (
{(cfg: { [x: string]: unknown }): ReactElement => (
<FormField
fullWidth
component={TextField}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useState } from 'react'
import { gql, useQuery } from '@apollo/client'
import p from 'prop-types'
import React, { ReactNode, useState, ReactElement } from 'react'
import { gql, useQuery } from 'urql'
import Grid from '@mui/material/Grid'
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
Expand All @@ -17,6 +16,13 @@ import AppLink from '../util/AppLink'
import makeStyles from '@mui/styles/makeStyles'
import Spinner from '../loading/components/Spinner'
import { GenericError } from '../error-pages'
import { IntegrationKey } from '../../schema'

interface Item {
title: string
subText: ReactElement
secondaryAction: ReactElement
}

const query = gql`
query ($serviceID: ID!) {
Expand Down Expand Up @@ -47,16 +53,20 @@ const useStyles = makeStyles({
},
})

const sortItems = (a, b) => {
const sortItems = (a: IntegrationKey, b: IntegrationKey): number => {
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
if (a.name < b.name) return -1
if (a.name > b.name) return 1
return 0
}

export function IntegrationKeyDetails(props) {
let copyText = (
export function IntegrationKeyDetails(props: {
href: string
label: string
type: string
}): JSX.Element {
let copyText: ReactNode = (
<CopyText title={'Copy ' + props.label} value={props.href} asURL />
)

Expand All @@ -71,27 +81,28 @@ export function IntegrationKeyDetails(props) {
{props.type === 'email' && (
<RequireConfig
configID='Mailgun.Enable'
else='Email integration keys are currently disabled.'
else={
<React.Fragment>
Email integration keys are currently disabled.
</React.Fragment>
}
/>
)}
</React.Fragment>
)
}

IntegrationKeyDetails.propTypes = {
href: p.string.isRequired,
label: p.string.isRequired,
type: p.string.isRequired,
}

export default function IntegrationKeyList({ serviceID }) {
export default function IntegrationKeyList(props: {
serviceID: string
}): JSX.Element {
const classes = useStyles()

const [create, setCreate] = useState(false)
const [deleteDialog, setDeleteDialog] = useState(null)
const [create, setCreate] = useState<boolean>(false)
const [deleteDialog, setDeleteDialog] = useState<string | null>(null)

const { loading, error, data } = useQuery(query, {
variables: { serviceID },
const [{ fetching, error, data }] = useQuery({
query,
variables: { serviceID: props.serviceID },
})

const typeLabels = {
Expand All @@ -101,28 +112,33 @@ export default function IntegrationKeyList({ serviceID }) {
email: 'Email Address',
prometheusAlertmanager: 'Alertmanager Webhook URL',
}
if (loading && !data) return <Spinner />
if (fetching && !data) return <Spinner />
if (error) return <GenericError error={error.message} />

const items = (data.service.integrationKeys || [])
.slice()
.sort(sortItems)
.map((key) => ({
title: key.name,
subText: (
<IntegrationKeyDetails
key={key.id}
href={key.href}
label={typeLabels[key.type]}
type={key.type}
/>
),
secondaryAction: (
<IconButton onClick={() => setDeleteDialog(key.id)} size='large'>
<Trash />
</IconButton>
),
}))
.map(
(key: IntegrationKey): Item => ({
title: key.name,
subText: (
<IntegrationKeyDetails
key={key.id}
href={key.href}
label={typeLabels[key.type]}
type={key.type}
/>
),
secondaryAction: (
<IconButton
onClick={(): void => setDeleteDialog(key.id)}
size='large'
>
<Trash />
</IconButton>
),
}),
)

return (
<React.Fragment>
Expand All @@ -144,19 +160,19 @@ export default function IntegrationKeyList({ serviceID }) {
</Card>
</Grid>
<CreateFAB
onClick={() => setCreate(true)}
onClick={(): void => setCreate(true)}
title='Create Integration Key'
/>
{create && (
<IntegrationKeyCreateDialog
serviceID={serviceID}
onClose={() => setCreate(false)}
serviceID={props.serviceID}
onClose={(): void => setCreate(false)}
/>
)}
{deleteDialog && (
<IntegrationKeyDeleteDialog
integrationKeyID={deleteDialog}
onClose={() => setDeleteDialog(null)}
onClose={(): void => setDeleteDialog(null)}
/>
)}
</React.Fragment>
Expand Down
2 changes: 1 addition & 1 deletion web/src/app/util/RequireConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export type RequireConfigProps = {
// react element to render if checks failed
else?: JSX.Element
isAdmin?: boolean
children: ReactChild
children?: ReactChild
}

export default function RequireConfig(
Expand Down