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

config: add Service.RequiredLabels #3531

Merged
merged 8 commits into from
Jan 15, 2024
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
11 changes: 11 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ type Config struct {
DisableCalendarSubscriptions bool `public:"true" info:"If set, disables all active calendar subscriptions as well as the ability to create new calendar subscriptions."`
}

Services struct {
RequiredLabels []string `public:"true" info:"List of label names to require new services to define."`
}

Maintenance struct {
AlertCleanupDays int `public:"true" info:"Closed alerts will be deleted after this many days (0 means disable cleanup)."`
AlertAutoCloseDays int `public:"true" info:"Unacknowledged alerts will automatically be closed after this many days of inactivity. (0 means disable auto-close)."`
Expand Down Expand Up @@ -447,6 +451,12 @@ func (cfg Config) Validate() error {
}
return validate.OAuthScope(fname, val, "openid")
}
validateLabels := func(fname string, vals []string) (err error) {
for i, v := range vals {
err = validate.Many(err, validate.LabelKey(fmt.Sprintf("%s[%d]", fname, i), v))
}
return err
}

err = validate.Many(
err,
Expand All @@ -456,6 +466,7 @@ func (cfg Config) Validate() error {
validateKey("Slack.ClientSecret", cfg.Slack.ClientSecret),
validateKey("Twilio.AccountSID", cfg.Twilio.AccountSID),
validateKey("Twilio.AuthToken", cfg.Twilio.AuthToken),
validateLabels("Services.RequiredLabels", cfg.Services.RequiredLabels),
validateKey("Twilio.AlternateAuthToken", cfg.Twilio.AlternateAuthToken),
validate.ASCII("Twilio.VoiceName", cfg.Twilio.VoiceName, 0, 50),
validate.ASCII("Twilio.VoiceLanguage", cfg.Twilio.VoiceLanguage, 0, 10),
Expand Down
4 changes: 4 additions & 0 deletions graphql2/mapconfig.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion web/src/app/services/ServiceCreateDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import { fieldErrors, nonFieldErrors } from '../util/errutil'
import FormDialog from '../dialogs/FormDialog'
import ServiceForm, { Value } from './ServiceForm'
import { Redirect } from 'wouter'
import { Label } from '../../schema'

interface InputVar {
name: string
description: string
escalationPolicyID?: string
favorite: boolean
labels: Label[]
newEscalationPolicy?: {
name: string
description: string
Expand All @@ -32,14 +34,15 @@ const createMutation = gql`
`

function inputVars(
{ name, description, escalationPolicyID }: Value,
{ name, description, escalationPolicyID, labels }: Value,
attempt = 0,
): InputVar {
const vars: InputVar = {
name,
description,
escalationPolicyID,
favorite: true,
labels,
}
if (!vars.escalationPolicyID) {
vars.newEscalationPolicy = {
Expand Down Expand Up @@ -70,6 +73,7 @@ export default function ServiceCreateDialog(props: {
name: '',
description: '',
escalationPolicyID: '',
labels: [],
})

const [createKey, createKeyStatus] = useMutation(createMutation)
Expand Down
46 changes: 39 additions & 7 deletions web/src/app/services/ServiceEditDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { fieldErrors, nonFieldErrors } from '../util/errutil'
import FormDialog from '../dialogs/FormDialog'
import ServiceForm from './ServiceForm'
import Spinner from '../loading/components/Spinner'
import { Label } from '../../schema'

interface Value {
name: string
description: string
escalationPolicyID?: string
labels: Label[]
}

const query = gql`
Expand All @@ -19,6 +21,10 @@ const query = gql`
id
name
description
labels {
key
value
}
ep: escalationPolicy {
id
name
Expand All @@ -31,6 +37,11 @@ const mutation = gql`
updateService(input: $input)
}
`
const setLabel = gql`
mutation setLabel($input: SetLabelInput!) {
setLabel(input: $input)
}
`

export default function ServiceEditDialog(props: {
serviceID: string
Expand All @@ -43,6 +54,7 @@ export default function ServiceEditDialog(props: {
})

const [saveStatus, save] = useMutation(mutation)
const [saveLabelStatus, saveLabel] = useMutation(setLabel)

if (dataFetching && !data) {
return <Spinner />
Expand All @@ -52,33 +64,53 @@ export default function ServiceEditDialog(props: {
name: data?.service?.name,
description: data?.service?.description,
escalationPolicyID: data?.service?.ep?.id,
labels: data?.service?.labels || [],
}

const fieldErrs = fieldErrors(saveStatus.error)
const fieldErrs = fieldErrors(saveStatus.error).concat(
fieldErrors(saveLabelStatus.error),
)

return (
<FormDialog
title='Edit Service'
loading={saveStatus.fetching || (!data && dataFetching)}
errors={nonFieldErrors(saveStatus.error).concat(
nonFieldErrors(dataError),
nonFieldErrors(saveLabelStatus.error),
)}
onClose={props.onClose}
onSubmit={() => {
save(
onSubmit={async () => {
const saveRes = await save(
{
input: {
...value,
id: props.serviceID,
name: value?.name || '',
description: value?.description || '',
escalationPolicyID: value?.escalationPolicyID || '',
},
},
{
additionalTypenames: ['Service'],
},
).then((res) => {
)
if (saveRes.error) return

for (const label of value?.labels || []) {
const res = await saveLabel({
input: {
target: {
type: 'service',
id: props.serviceID,
},
key: label.key,
value: label.value,
},
})
if (res.error) return
props.onClose()
})
}

props.onClose()
}}
form={
<ServiceForm
Expand Down
64 changes: 62 additions & 2 deletions web/src/app/services/ServiceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import TextField from '@mui/material/TextField'
import { EscalationPolicySelect } from '../selection/EscalationPolicySelect'
import { FormContainer, FormField } from '../forms'
import { FieldError } from '../util/errutil'
import { useConfigValue } from '../util/RequireConfig'
import { Label } from '../../schema'
import { InputAdornment } from '@mui/material'

const MaxDetailsLength = 6 * 1024 // 6KiB

export interface Value {
name: string
description: string
escalationPolicyID?: string
labels: Label[]
}

interface ServiceFormProps {
Expand All @@ -26,9 +30,27 @@ interface ServiceFormProps {
}

export default function ServiceForm(props: ServiceFormProps): JSX.Element {
const { epRequired, ...containerProps } = props
const { epRequired, errors, ...containerProps } = props

const formErrs = errors.map((e) => {
if (e.field !== 'value') {
// label value
return e
}
return {
...e,
field: 'labels',
}
})

const [reqLabels] = useConfigValue('Services.RequiredLabels') as [string[]]

return (
<FormContainer {...containerProps} optionalLabels={epRequired}>
<FormContainer
{...containerProps}
errors={formErrs}
optionalLabels={epRequired}
>
<Grid container spacing={2}>
<Grid item xs={12}>
<FormField
Expand Down Expand Up @@ -61,6 +83,44 @@ export default function ServiceForm(props: ServiceFormProps): JSX.Element {
component={EscalationPolicySelect}
/>
</Grid>
{reqLabels &&
reqLabels.map((labelName: string, idx: number) => (
<Grid item xs={12} key={labelName}>
<FormField
fullWidth
name={labelName}
required={!epRequired} // optional when editing
component={TextField}
fieldName='labels'
label={
reqLabels.length === 1
? 'Service Label'
: reqLabels.length > 1 && idx === 0
? 'Service Labels'
: ''
}
InputProps={{
startAdornment: (
<InputAdornment position='start'>
{labelName}:
</InputAdornment>
),
}}
mapOnChangeValue={(newVal: string, value: Value) => {
return [
...value.labels.filter((l) => l.key !== labelName),
{
key: labelName,
value: newVal,
},
]
}}
mapValue={(labels: Label[]) =>
labels.find((l) => l.key === labelName)?.value || ''
}
/>
</Grid>
))}
</Grid>
</FormContainer>
)
Expand Down
1 change: 1 addition & 0 deletions web/src/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1370,6 +1370,7 @@ type ConfigID =
| 'General.DisableSMSLinks'
| 'General.DisableLabelCreation'
| 'General.DisableCalendarSubscriptions'
| 'Services.RequiredLabels'
| 'Maintenance.AlertCleanupDays'
| 'Maintenance.AlertAutoCloseDays'
| 'Maintenance.APIKeyExpireDays'
Expand Down