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

React Hook Form: Update to v 7.49.2 #79493

Merged
merged 21 commits into from Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -378,7 +378,7 @@
"react-dropzone": "^14.2.3",
"react-grid-layout": "1.4.2",
"react-highlight-words": "0.20.0",
"react-hook-form": "7.5.3",
"react-hook-form": "^7.49.2",
"react-i18next": "^12.0.0",
"react-inlinesvg": "3.0.2",
"react-loading-skeleton": "3.3.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/grafana-ui/package.json
Expand Up @@ -92,7 +92,7 @@
"react-custom-scrollbars-2": "4.5.0",
"react-dropzone": "14.2.3",
"react-highlight-words": "0.20.0",
"react-hook-form": "7.5.3",
"react-hook-form": "^7.49.2",
"react-i18next": "^12.0.0",
"react-inlinesvg": "3.0.2",
"react-loading-skeleton": "3.3.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/grafana-ui/src/components/Forms/Form.tsx
@@ -1,14 +1,14 @@
import { css } from '@emotion/css';
import React, { HTMLProps, useEffect } from 'react';
import { useForm, Mode, DeepPartial, UnpackNestedValue, SubmitHandler, FieldValues } from 'react-hook-form';
import { useForm, Mode, DefaultValues, SubmitHandler, FieldValues } from 'react-hook-form';

import { FormAPI } from '../../types';

interface FormProps<T extends FieldValues> extends Omit<HTMLProps<HTMLFormElement>, 'onSubmit' | 'children'> {
validateOn?: Mode;
validateOnMount?: boolean;
validateFieldsOnMount?: string | string[];
defaultValues?: UnpackNestedValue<DeepPartial<T>>;
defaultValues?: DefaultValues<T>;
onSubmit: SubmitHandler<T>;
children: (api: FormAPI<T>) => React.ReactNode;
/** Sets max-width for container. Use it instead of setting individual widths on inputs.*/
Expand Down
Expand Up @@ -58,7 +58,7 @@ export const NotificationChannelOptions = ({
label={option.label}
description={option.description}
invalid={errors.settings && !!errors.settings[option.propertyName]}
error={errors.settings && errors.settings[option.propertyName]?.message}
error={errors.settings && String(errors.settings[option.propertyName]?.message || '')}
>
{secureFields && secureFields[option.propertyName] ? (
<Input
Expand Down
Expand Up @@ -14,12 +14,12 @@ import { TimezoneSelect } from './timezones';

export const MuteTimingTimeInterval = () => {
const styles = useStyles2(getStyles);
const { formState, register, setValue } = useFormContext();
const { formState, register, setValue } = useFormContext<MuteTimingFields>();
const {
fields: timeIntervals,
append: addTimeInterval,
remove: removeTimeInterval,
} = useFieldArray<MuteTimingFields>({
} = useFieldArray({
name: 'time_intervals',
});

Expand All @@ -43,7 +43,11 @@ export const MuteTimingTimeInterval = () => {
return (
<div key={timeInterval.id} className={styles.timeIntervalSection}>
<MuteTimingTimeRange intervalIndex={timeIntervalIndex} />
<Field label="Location" invalid={Boolean(errors.location)} error={errors.location?.message}>
<Field
label="Location"
invalid={Boolean(errors.time_intervals?.[timeIntervalIndex]?.location)}
error={errors.time_intervals?.[timeIntervalIndex]?.location?.message}
>
<TimezoneSelect
prefix={<Icon name="map-marker" />}
width={50}
Expand Down
Expand Up @@ -28,7 +28,7 @@ export const MuteTimingTimeRange = ({ intervalIndex }: Props) => {
});

const formErrors = formState.errors.time_intervals?.[intervalIndex];
const timeRangeInvalid = formErrors?.times?.some((value) => value?.start_time || value?.end_time) ?? false;
const timeRangeInvalid = formErrors?.times?.some?.((value) => value?.start_time || value?.end_time) ?? false;
Clarity-89 marked this conversation as resolved.
Show resolved Hide resolved

return (
<div>
Expand Down
Expand Up @@ -258,7 +258,7 @@ export const AmRoutesExpandedForm = ({
>
<PromDurationInput
{...register('repeatIntervalValue', {
validate: (value: string) => {
validate: (value = '') => {
const groupInterval = getValues('groupIntervalValue');
return repeatIntervalValidator(value, groupInterval);
},
Expand Down
Expand Up @@ -147,7 +147,7 @@ export const TemplateForm = ({ existing, alertManagerSourceName, config, provena
watch,
} = formApi;

const validateNameIsUnique: Validate<string> = (name: string) => {
const validateNameIsUnique: Validate<string, TemplateFormValues> = (name: string) => {
return !config.template_files[name] || existing?.name === name
? true
: 'Another template with this name already exists.';
Expand Down
Expand Up @@ -69,7 +69,8 @@ export function ChannelSubForm<R extends ChannelValues>({
// Prevent forgetting about initial values when switching the integration type and the oncall integration type
useEffect(() => {
// Restore values when switching back from a changed integration to the default one
const subscription = watch((_, { name, type, value }) => {
const subscription = watch((v, { name, type }) => {
const value = name ? v[name] : '';
if (initialValues && name === fieldName('type') && value === initialValues.type && type === 'change') {
setValue(fieldName('settings'), initialValues.settings);
}
Expand Down
Expand Up @@ -83,7 +83,7 @@ export function ReceiverForm<R extends ChannelValues>({

const { fields, append, remove } = useControlledFieldArray<R>({ name: 'items', formAPI, softDelete: true });

const validateNameIsAvailable: Validate<string> = useCallback(
const validateNameIsAvailable: Validate<string, ReceiverFormValues<R>> = useCallback(
(name: string) =>
takenReceiverNames.map((name) => name.trim().toLowerCase()).includes(name.trim().toLowerCase())
? 'Another receiver with this name already exists.'
Expand Down
@@ -1,9 +1,19 @@
import { css, cx } from '@emotion/css';
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { FieldArrayMethodProps, useFieldArray, useFormContext } from 'react-hook-form';
import { useFieldArray, UseFieldArrayAppend, useFormContext } from 'react-hook-form';

import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { Button, Field, InlineLabel, Input, LoadingPlaceholder, Stack, Text, useStyles2 } from '@grafana/ui';
import {
Button,
Field,
InlineLabel,
Input,
InputControl,
LoadingPlaceholder,
Stack,
Text,
useStyles2,
} from '@grafana/ui';
import { useDispatch } from 'app/types';

import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
Expand Down Expand Up @@ -85,10 +95,7 @@ const RemoveButton: FC<{
);

const AddButton: FC<{
append: (
value: Partial<{ key: string; value: string }> | Array<Partial<{ key: string; value: string }>>,
options?: FieldArrayMethodProps | undefined
) => void;
append: UseFieldArrayAppend<RuleFormValues, 'labels'>;
className: string;
}> = ({ append, className }) => (
<Button
Expand All @@ -107,11 +114,9 @@ const AddButton: FC<{
const LabelsWithSuggestions: FC<{ dataSourceName: string }> = ({ dataSourceName }) => {
const styles = useStyles2(getStyles);
const {
register,
control,
watch,
formState: { errors },
setValue,
} = useFormContext<RuleFormValues>();

const labels = watch('labels');
Expand Down Expand Up @@ -151,17 +156,24 @@ const LabelsWithSuggestions: FC<{ dataSourceName: string }> = ({ dataSourceName
error={errors.labels?.[index]?.key?.message}
data-testid={`label-key-${index}`}
>
<AlertLabelDropdown
{...register(`labels.${index}.key`, {
required: { value: Boolean(labels[index]?.value), message: 'Required.' },
})}
defaultValue={field.key ? { label: field.key, value: field.key } : undefined}
options={keys}
onChange={(newValue: SelectableValue) => {
setValue(`labels.${index}.key`, newValue.value);
setSelectedKey(newValue.value);
<InputControl
name={`labels.${index}.key`}
control={control}
rules={{ required: Boolean(labels[index]?.value) ? 'Required.' : false }}
render={({ field: { onChange, ref, ...rest } }) => {
return (
<AlertLabelDropdown
{...rest}
defaultValue={field.key ? { label: field.key, value: field.key } : undefined}
options={keys}
onChange={(newValue: SelectableValue) => {
onChange(newValue.value);
setSelectedKey(newValue.value);
}}
type="key"
/>
);
}}
type="key"
/>
</Field>
<InlineLabel className={styles.equalSign}>=</InlineLabel>
Expand All @@ -171,19 +183,26 @@ const LabelsWithSuggestions: FC<{ dataSourceName: string }> = ({ dataSourceName
error={errors.labels?.[index]?.value?.message}
data-testid={`label-value-${index}`}
>
<AlertLabelDropdown
{...register(`labels.${index}.value`, {
required: { value: Boolean(labels[index]?.key), message: 'Required.' },
})}
defaultValue={field.value ? { label: field.value, value: field.value } : undefined}
options={values}
onChange={(newValue: SelectableValue) => {
setValue(`labels.${index}.value`, newValue.value);
}}
onOpenMenu={() => {
setSelectedKey(labels[index].key);
<InputControl
control={control}
name={`labels.${index}.value`}
rules={{ required: Boolean(labels[index]?.value) ? 'Required.' : false }}
render={({ field: { onChange, ref, ...rest } }) => {
return (
<AlertLabelDropdown
{...rest}
defaultValue={field.value ? { label: field.value, value: field.value } : undefined}
options={values}
onChange={(newValue: SelectableValue) => {
onChange(newValue.value);
}}
onOpenMenu={() => {
setSelectedKey(labels[index].key);
}}
type="value"
/>
);
}}
type="value"
/>
</Field>

Expand Down Expand Up @@ -268,7 +287,7 @@ const LabelsField: FC<Props> = ({ dataSourceName }) => {
Add labels to your rule to annotate your rules, ease searching, or route to a notification policy.
</Text>
<NeedHelpInfo
contentText="The dropdown only displays labels that you have previously used for alerts.
contentText="The dropdown only displays labels that you have previously used for alerts.
Select a label from the options below or type in a new one."
title="Labels"
/>
Expand Down
@@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import React, { useEffect, useMemo, useState } from 'react';
import { DeepMap, FieldError, FormProvider, useForm, UseFormWatch } from 'react-hook-form';
import { FormProvider, SubmitErrorHandler, useForm, UseFormWatch } from 'react-hook-form';
import { Link, useParams } from 'react-router-dom';

import { GrafanaTheme2 } from '@grafana/data';
Expand Down Expand Up @@ -144,7 +144,7 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
}
};

const onInvalid = (errors: DeepMap<RuleFormValues, FieldError>): void => {
const onInvalid: SubmitErrorHandler<RuleFormValues> = (errors): void => {
if (!existing) {
trackNewAlerRuleFormError({
grafana_version: config.buildInfo.version,
Expand Down
Expand Up @@ -6,7 +6,7 @@ import { CloudNotifierType, NotifierType } from 'app/types';
import { ControlledField } from '../hooks/useControlledFieldArray';

export interface ChannelValues {
__id: string; // used to correllate form values to original DTOs
__id: string; // used to correlate form values to original DTOs
type: string;
settings: Record<string, any>;
secureSettings: Record<string, any>;
Expand Down
6 changes: 3 additions & 3 deletions public/app/features/alerting/unified/utils/amroutes.ts
Expand Up @@ -221,8 +221,8 @@ export const mapMultiSelectValueToStrings = (
return selectableValuesToStrings(selectableValues);
};

export function promDurationValidator(duration: string) {
if (duration.length === 0) {
export function promDurationValidator(duration?: string) {
if (!duration || duration.length === 0) {
return true;
}

Expand All @@ -237,7 +237,7 @@ export const objectMatchersToString = (matchers: ObjectMatcher[]): string[] => {
});
};

export const repeatIntervalValidator = (repeatInterval: string, groupInterval: string) => {
export const repeatIntervalValidator = (repeatInterval: string, groupInterval = '') => {
if (repeatInterval.length === 0) {
return true;
}
Expand Down
5 changes: 3 additions & 2 deletions public/app/features/correlations/CorrelationsPage.test.tsx
Expand Up @@ -538,7 +538,7 @@ describe('CorrelationsPage', () => {

// select Regex, be sure expression field is not disabled and contains the former expression
openMenu(typeFilterSelect[0]);
await userEvent.click(screen.getByText('Regular expression', { selector: 'span' }));
await userEvent.click(screen.getByText('Regular expression'));
expressionInput = screen.queryByLabelText(/expression/i);
expect(expressionInput).toBeInTheDocument();
expect(expressionInput).toBeEnabled();
Expand All @@ -554,7 +554,8 @@ describe('CorrelationsPage', () => {
await userEvent.click(screen.getByRole('button', { name: /add transformation/i }));
typeFilterSelect = screen.getAllByLabelText('Type');
openMenu(typeFilterSelect[0]);
await userEvent.click(screen.getByText('Regular expression'));
const menu = await screen.findByLabelText('Select options menu');
await userEvent.click(within(menu).getByText('Regular expression'));
expressionInput = screen.queryByLabelText(/expression/i);
expect(expressionInput).toBeInTheDocument();
expect(expressionInput).toBeEnabled();
Expand Down
Expand Up @@ -12,6 +12,7 @@ import { getVariableUsageInfo } from '../../explore/utils/links';

import { TransformationsEditor } from './TransformationsEditor';
import { useCorrelationsFormContext } from './correlationsFormContext';
import { FormDTO } from './types';
import { getInputId } from './utils';

const getStyles = (theme: GrafanaTheme2) => ({
Expand All @@ -25,7 +26,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
});

export const ConfigureCorrelationSourceForm = () => {
const { control, formState, register, getValues } = useFormContext();
const { control, formState, register, getValues } = useFormContext<FormDTO>();
const styles = useStyles2(getStyles);
const withDsUID = (fn: Function) => (ds: DataSourceInstanceSettings) => fn(ds.uid);

Expand Down
Expand Up @@ -8,9 +8,10 @@ import { DataSourcePicker } from 'app/features/datasources/components/picker/Dat

import { QueryEditorField } from './QueryEditorField';
import { useCorrelationsFormContext } from './correlationsFormContext';
import { FormDTO } from './types';

export const ConfigureCorrelationTargetForm = () => {
const { control, formState } = useFormContext();
const { control, formState } = useFormContext<FormDTO>();
const withDsUID = (fn: Function) => (ds: DataSourceInstanceSettings) => fn(ds.uid);
const { correlation } = useCorrelationsFormContext();
const targetUID: string | undefined = useWatch({ name: 'targetUID' }) || correlation?.targetUID;
Expand Down