Skip to content

Commit

Permalink
Allow submitting forms in the UI by pressing "Enter" (#4674)
Browse files Browse the repository at this point in the history
# What this PR does

- Escalation Chain and Token forms now use `react-hook-form`

## Which issue(s) this PR closes

Closes #2038
  • Loading branch information
teodosii authored Jul 15, 2024
1 parent ff7ffa9 commit 93d7708
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 126 deletions.
19 changes: 15 additions & 4 deletions src/components/ManualAlertGroup/ManualAlertGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { FC, useCallback } from 'react';

import { css } from '@emotion/css';
import { Button, Drawer, Field, HorizontalGroup, TextArea, useStyles2, VerticalGroup } from '@grafana/ui';
import { observer } from 'mobx-react';
import { Controller, FormProvider, useForm } from 'react-hook-form';
Expand Down Expand Up @@ -50,7 +51,6 @@ export const ManualAlertGroup: FC<ManualAlertGroupProps> = observer(({ onCreate,

const onSubmit = async (data: FormData) => {
const transformedData = prepareForUpdate(selectedUserResponders, selectedTeamResponder, data);

const resp = await directPagingStore.createManualAlertRule(transformedData);

if (!resp) {
Expand All @@ -65,13 +65,14 @@ export const ManualAlertGroup: FC<ManualAlertGroupProps> = observer(({ onCreate,
onHide();
};

const utils = useStyles2(getUtilStyles);
const utilStyles = useStyles2(getUtilStyles);
const styles = useStyles2(getStyles);

return (
<Drawer scrollableContent title="New escalation" onClose={onHideDrawer} closeOnMaskClick={false} width="70%">
<VerticalGroup>
<FormProvider {...formMethods}>
<form id="Manual Alert Group" onSubmit={handleSubmit(onSubmit)} className={utils.width100}>
<form id="Manual Alert Group" onSubmit={handleSubmit(onSubmit)} className={utilStyles.width100}>
<Controller
name="message"
control={control}
Expand All @@ -82,8 +83,10 @@ export const ManualAlertGroup: FC<ManualAlertGroupProps> = observer(({ onCreate,
</Field>
)}
/>

<AddResponders mode="create" />
<div className="buttons">

<div className={styles.buttons}>
<HorizontalGroup justify="flex-end">
<Button variant="secondary" onClick={onHideDrawer}>
Cancel
Expand All @@ -99,3 +102,11 @@ export const ManualAlertGroup: FC<ManualAlertGroupProps> = observer(({ onCreate,
</Drawer>
);
});

const getStyles = () => {
return {
buttons: css`
padding-top: 12px;
`,
};
};
6 changes: 4 additions & 2 deletions src/containers/ApiTokenSettings/ApiTokenForm.module.css
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
.token__inputContainer {
width: 100%;
display: flex;
margin-bottom: 24px;
}

.token__input {
flex-grow: 1;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
Expand All @@ -14,3 +12,7 @@
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}

.field {
flex-grow: 1;
}
119 changes: 75 additions & 44 deletions src/containers/ApiTokenSettings/ApiTokenForm.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { useCallback, HTMLAttributes, useState } from 'react';
import React, { HTMLAttributes, useState } from 'react';

import { Button, HorizontalGroup, Input, Label, Modal, VerticalGroup } from '@grafana/ui';
import { Button, Field, HorizontalGroup, Input, Label, Modal, VerticalGroup } from '@grafana/ui';
import cn from 'classnames/bind';
import { get } from 'lodash-es';
import { observer } from 'mobx-react';
import CopyToClipboard from 'react-copy-to-clipboard';
import { Controller, FormProvider, useForm } from 'react-hook-form';

import { RenderConditionally } from 'components/RenderConditionally/RenderConditionally';
import { SourceCode } from 'components/SourceCode/SourceCode';
import { useStore } from 'state/useStore';
import { openErrorNotification, openNotification } from 'utils/utils';
Expand All @@ -20,62 +22,81 @@ interface TokenCreationModalProps extends HTMLAttributes<HTMLElement> {
onUpdate: () => void;
}

interface FormFields {
name: string;
}

export const ApiTokenForm = observer((props: TokenCreationModalProps) => {
const { onHide = () => {}, onUpdate = () => {} } = props;
const [name, setName] = useState('');
const [token, setToken] = useState('');

const store = useStore();
const formMethods = useForm<FormFields>({
mode: 'onChange',
});

const onCreateTokenCallback = useCallback(async () => {
try {
const data = await store.apiTokenStore.create({ name });
setToken(data.token);
onUpdate();
} catch (error) {
openErrorNotification(get(error, 'response.data.detail', 'error creating token'));
}
}, [name]);
const {
control,
watch,
formState: { errors },
handleSubmit,
} = formMethods;

const handleNameChange = useCallback((event) => {
setName(event.target.value);
}, []);
const name = watch('name');

return (
<Modal isOpen closeOnEscape={false} title={token ? 'Your new API Token' : 'Create API Token'} onDismiss={onHide}>
<VerticalGroup>
<Label>Token Name</Label>
<div className={cx('token__inputContainer')}>
{renderTokenInput()}
{renderCopyToClipboard()}
</div>

{renderCurlExample()}

<HorizontalGroup justify="flex-end">
<Button variant="secondary" onClick={() => onHide()}>
{token ? 'Close' : 'Cancel'}
</Button>
{!token && (
<Button disabled={!!token || !name} variant="primary" onClick={onCreateTokenCallback}>
Create Token
</Button>
)}
</HorizontalGroup>
</VerticalGroup>
<FormProvider {...formMethods}>
<form onSubmit={handleSubmit(onCreateTokenCallback)}>
<VerticalGroup>
<Label>Token Name</Label>
<div className={cx('token__inputContainer')}>
{renderTokenInput()}
{renderCopyToClipboard()}
</div>

{renderCurlExample()}

<HorizontalGroup justify="flex-end">
<Button variant="secondary" onClick={() => onHide()}>
{token ? 'Close' : 'Cancel'}
</Button>

<RenderConditionally shouldRender={!token}>
<Button type="submit" disabled={!name} variant="primary">
Create Token
</Button>
</RenderConditionally>
</HorizontalGroup>
</VerticalGroup>
</form>
</FormProvider>
</Modal>
);

function renderTokenInput() {
return token ? (
<Input value={token} disabled={!!token} className={cx('token__input')} />
) : (
<Input
className={cx('token__input')}
maxLength={50}
onChange={handleNameChange}
placeholder="Enter token name"
autoFocus
return (
<Controller
name="name"
control={control}
rules={{ required: 'Token name is required' }}
render={({ field }) => (
<Field invalid={Boolean(errors['name'])} error={errors['name']?.message} className={cx('field')}>
<>
{token ? (
<Input {...field} disabled={!!token} className={cx('token__input')} />
) : (
<Input
{...field}
className={cx('token__input')}
maxLength={50}
placeholder="Enter token name"
autoFocus
/>
)}
</>
</Field>
)}
/>
);
}
Expand Down Expand Up @@ -104,6 +125,16 @@ export const ApiTokenForm = observer((props: TokenCreationModalProps) => {
</VerticalGroup>
);
}

async function onCreateTokenCallback() {
try {
const data = await store.apiTokenStore.create({ name });
setToken(data.token);
onUpdate();
} catch (error) {
openErrorNotification(get(error, 'response.data.detail', 'error creating token'));
}
}
});

function getCurlExample(token, onCallApiUrl) {
Expand Down
Loading

0 comments on commit 93d7708

Please sign in to comment.