mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
React Hook Form: Update to v 7.49.2 (#79493)
* Update RHF to latest * Update Form types * Fix alerting types * Fix correlations types * Update tests * Fix tests * Update LabelsField.tsx to use InputControl * Update RuleEditorGrafanaRules.test.tsx * Update RuleEditorCloudRules.test.tsx * Only require one label * Update RuleEditorRecordingRule.test.tsx * Fix labels rules * Revert * Remove RHF from ignore rules * Revert * update form validation for overriding group timings * Fix changes to correlations * Fix auth type errors --------- Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com> Co-authored-by: Kristina Durivage <kristina.durivage@grafana.com>
This commit is contained in:
parent
3537c5440f
commit
99f7110e39
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -1,6 +1,6 @@
|
||||
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';
|
||||
|
||||
@ -8,7 +8,7 @@ interface FormProps<T extends FieldValues> extends Omit<HTMLProps<HTMLFormElemen
|
||||
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.*/
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
});
|
||||
|
||||
@ -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}
|
||||
|
@ -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;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -258,7 +258,7 @@ export const AmRoutesExpandedForm = ({
|
||||
>
|
||||
<PromDurationInput
|
||||
{...register('repeatIntervalValue', {
|
||||
validate: (value: string) => {
|
||||
validate: (value = '') => {
|
||||
const groupInterval = getValues('groupIntervalValue');
|
||||
return repeatIntervalValidator(value, groupInterval);
|
||||
},
|
||||
|
@ -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.';
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.'
|
||||
|
@ -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';
|
||||
@ -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
|
||||
@ -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');
|
||||
@ -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>
|
||||
@ -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);
|
||||
<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"
|
||||
/>
|
||||
);
|
||||
}}
|
||||
onOpenMenu={() => {
|
||||
setSelectedKey(labels[index].key);
|
||||
}}
|
||||
type="value"
|
||||
/>
|
||||
</Field>
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
@ -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';
|
||||
@ -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,
|
||||
|
@ -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>;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { FieldData, SSOProvider } from './types';
|
||||
import { isSelectableValue } from './utils/guards';
|
||||
|
||||
/** Map providers to their settings */
|
||||
export const fields: Record<SSOProvider['provider'], Array<keyof SSOProvider['settings']>> = {
|
||||
@ -46,7 +47,10 @@ export const fieldMap: Record<string, FieldData> = {
|
||||
if (typeof value === 'string') {
|
||||
return isNumeric(value);
|
||||
}
|
||||
return value.every((v) => v?.value && isNumeric(v.value));
|
||||
if (isSelectableValue(value)) {
|
||||
return value.every((v) => v?.value && isNumeric(v.value));
|
||||
}
|
||||
return true;
|
||||
},
|
||||
message: 'Team ID must be a number.',
|
||||
},
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Validate } from 'react-hook-form';
|
||||
|
||||
import { IconName, SelectableValue } from '@grafana/data';
|
||||
import { Settings } from 'app/types';
|
||||
|
||||
@ -26,7 +28,7 @@ export type SSOProviderSettingsBase = {
|
||||
emailAttributePath?: string;
|
||||
emptyScopes?: boolean;
|
||||
enabled: boolean;
|
||||
extra?: Record<string, unknown>;
|
||||
extra?: Record<string, string>;
|
||||
groupsAttributePath?: string;
|
||||
hostedDomain?: string;
|
||||
icon?: IconName;
|
||||
@ -97,7 +99,7 @@ export type FieldData = {
|
||||
validation?: {
|
||||
required?: boolean;
|
||||
message?: string;
|
||||
validate?: (value: string | Array<SelectableValue<string>>) => boolean | string | Promise<boolean | string>;
|
||||
validate?: Validate<SSOProviderDTO[keyof SSOProviderDTO], SSOProviderDTO>;
|
||||
};
|
||||
multi?: boolean;
|
||||
allowCustomValue?: boolean;
|
||||
|
@ -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();
|
||||
@ -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();
|
||||
|
@ -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) => ({
|
||||
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -5,7 +5,7 @@ import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { Field, Icon, IconButton, Input, Label, Select, Stack, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import { Trans, t } from 'app/core/internationalization';
|
||||
|
||||
import { getSupportedTransTypeDetails, getTransformOptions } from './types';
|
||||
import { FormDTO, getSupportedTransTypeDetails, getTransformOptions } from './types';
|
||||
type Props = {
|
||||
index: number;
|
||||
value: Record<string, string>;
|
||||
@ -23,7 +23,7 @@ const getStyles = () => ({
|
||||
|
||||
const TransformationEditorRow = (props: Props) => {
|
||||
const { index, value: defaultValue, readOnly, remove } = props;
|
||||
const { control, formState, register, setValue, watch, getValues } = useFormContext();
|
||||
const { control, formState, register, setValue, watch, getValues } = useFormContext<FormDTO>();
|
||||
|
||||
const [keptVals, setKeptVals] = useState<{ expression?: string; mapValue?: string }>({});
|
||||
|
||||
@ -63,34 +63,37 @@ const TransformationEditorRow = (props: Props) => {
|
||||
</Stack>
|
||||
}
|
||||
invalid={!!formState.errors?.config?.transformations?.[index]?.type}
|
||||
error={formState.errors?.config?.transformations?.[index]?.type?.message}
|
||||
error={formState.errors?.config?.transformations?.[index]?.message}
|
||||
validationMessageHorizontalOverflow={true}
|
||||
>
|
||||
<Select
|
||||
value={typeValue}
|
||||
onChange={(value) => {
|
||||
if (!readOnly) {
|
||||
const currentValues = getValues().config.transformations[index];
|
||||
setKeptVals({
|
||||
expression: currentValues.expression,
|
||||
mapValue: currentValues.mapValue,
|
||||
});
|
||||
|
||||
const newValueDetails = getSupportedTransTypeDetails(value.value);
|
||||
|
||||
if (newValueDetails.expressionDetails.show) {
|
||||
setValue(`config.transformations.${index}.expression`, keptVals?.expression || '');
|
||||
} else {
|
||||
setValue(`config.transformations.${index}.expression`, '');
|
||||
const currentValues = getValues()?.config?.transformations?.[index];
|
||||
if (currentValues) {
|
||||
setKeptVals({
|
||||
expression: currentValues.expression,
|
||||
mapValue: currentValues.mapValue,
|
||||
});
|
||||
}
|
||||
if (value.value) {
|
||||
const newValueDetails = getSupportedTransTypeDetails(value.value);
|
||||
|
||||
if (newValueDetails.mapValueDetails.show) {
|
||||
setValue(`config.transformations.${index}.mapValue`, keptVals?.mapValue || '');
|
||||
} else {
|
||||
setValue(`config.transformations.${index}.mapValue`, '');
|
||||
if (newValueDetails.expressionDetails.show) {
|
||||
setValue(`config.transformations.${index}.expression`, keptVals?.expression || '');
|
||||
} else {
|
||||
setValue(`config.transformations.${index}.expression`, '');
|
||||
}
|
||||
|
||||
if (newValueDetails.mapValueDetails.show) {
|
||||
setValue(`config.transformations.${index}.mapValue`, keptVals?.mapValue || '');
|
||||
} else {
|
||||
setValue(`config.transformations.${index}.mapValue`, '');
|
||||
}
|
||||
|
||||
setValue(`config.transformations.${index}.type`, value.value);
|
||||
}
|
||||
|
||||
setValue(`config.transformations.${index}.type`, value.value);
|
||||
}
|
||||
}}
|
||||
options={transformOptions}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { DeepPartial, UnpackNestedValue } from 'react-hook-form';
|
||||
import { DefaultValues } from 'react-hook-form';
|
||||
|
||||
export type WizardProps<T> = {
|
||||
/**
|
||||
* Initial values for the form
|
||||
*/
|
||||
defaultValues?: UnpackNestedValue<DeepPartial<T>>;
|
||||
defaultValues?: DefaultValues<T>;
|
||||
|
||||
/**
|
||||
* List of steps/pages in the wizard.
|
||||
|
@ -30,7 +30,7 @@ type CorrelationConfigType = 'query';
|
||||
|
||||
export interface CorrelationConfig {
|
||||
field: string;
|
||||
target: object;
|
||||
target: object; // this contains anything that would go in the query editor, so any extension off DataQuery a datasource would have, and needs to be generic
|
||||
type: CorrelationConfigType;
|
||||
transformations?: DataLinkTransformationConfig[];
|
||||
}
|
||||
|
@ -84,10 +84,10 @@ export const CorrelationHelper = ({ exploreId, correlations }: Props) => {
|
||||
useEffect(() => {
|
||||
const subscription = watch((value) => {
|
||||
let dirty = correlationDetails?.correlationDirty || false;
|
||||
|
||||
if (!dirty && (value.label !== defaultLabel || value.description !== '')) {
|
||||
let description = value.description || '';
|
||||
if (!dirty && (value.label !== defaultLabel || description !== '')) {
|
||||
dirty = true;
|
||||
} else if (dirty && value.label === defaultLabel && value.description.trim() === '') {
|
||||
} else if (dirty && value.label === defaultLabel && description.trim() === '') {
|
||||
dirty = false;
|
||||
}
|
||||
dispatch(
|
||||
|
@ -101,18 +101,21 @@ export const CorrelationTransformationAddModal = ({
|
||||
isExpressionValid = !formFieldsVis.expressionDetails.show;
|
||||
}
|
||||
setIsExpValid(isExpressionValid);
|
||||
const transformationVars = getTransformationVars(
|
||||
{
|
||||
type: formValues.type,
|
||||
expression: isExpressionValid ? expression : '',
|
||||
mapValue: formValues.mapValue,
|
||||
},
|
||||
fieldList[formValues.field!] || '',
|
||||
formValues.field!
|
||||
);
|
||||
let transKeys = [];
|
||||
if (formValues.type) {
|
||||
const transformationVars = getTransformationVars(
|
||||
{
|
||||
type: formValues.type,
|
||||
expression: isExpressionValid ? expression : '',
|
||||
mapValue: formValues.mapValue,
|
||||
},
|
||||
fieldList[formValues.field!] || '',
|
||||
formValues.field!
|
||||
);
|
||||
|
||||
const transKeys = Object.keys(transformationVars);
|
||||
setTransformationVars(transKeys.length > 0 ? { ...transformationVars } : {});
|
||||
transKeys = Object.keys(transformationVars);
|
||||
setTransformationVars(transKeys.length > 0 ? { ...transformationVars } : {});
|
||||
}
|
||||
|
||||
if (transKeys.length === 0 || !isExpressionValid) {
|
||||
setValidToSave(false);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { partial } from 'lodash';
|
||||
import React, { type ReactElement, useEffect, useState } from 'react';
|
||||
import { DeepMap, FieldError, useForm } from 'react-hook-form';
|
||||
import { DeepMap, FieldError, FieldErrors, useForm } from 'react-hook-form';
|
||||
|
||||
import { locationUtil, SelectableValue } from '@grafana/data';
|
||||
import { config, locationService, reportInteraction } from '@grafana/runtime';
|
||||
@ -34,7 +34,7 @@ interface SaveToExistingDashboard extends SaveTargetDTO {
|
||||
type FormDTO = SaveToNewDashboardDTO | SaveToExistingDashboard;
|
||||
|
||||
function assertIsSaveToExistingDashboardError(
|
||||
errors: DeepMap<FormDTO, FieldError>
|
||||
errors: FieldErrors<FormDTO>
|
||||
): asserts errors is DeepMap<SaveToExistingDashboard, FieldError> {
|
||||
// the shape of the errors object is always compatible with the type above, but we need to
|
||||
// explicitly assert its type so that TS can narrow down FormDTO to SaveToExistingDashboard
|
||||
|
@ -299,8 +299,8 @@ const builtInVariables = [
|
||||
* @param query
|
||||
* @param scopedVars
|
||||
*/
|
||||
export function getVariableUsageInfo<T extends DataLink>(
|
||||
query: T,
|
||||
export function getVariableUsageInfo(
|
||||
query: object,
|
||||
scopedVars: ScopedVars
|
||||
): { variables: VariableInterpolation[]; allVariablesDefined: boolean } {
|
||||
let variables: VariableInterpolation[] = [];
|
||||
|
@ -39,6 +39,7 @@ jest.mock('app/core/core', () => ({
|
||||
hasPermission: () => true,
|
||||
hasPermissionInMetadata: () => true,
|
||||
user: { orgId: 1 },
|
||||
fetchUserPermissions: () => Promise.resolve(),
|
||||
},
|
||||
}));
|
||||
|
||||
|
@ -8,7 +8,7 @@ type FormModel = { folderName: string };
|
||||
interface Props {
|
||||
onSubmit: SubmitHandler<FormModel>;
|
||||
onDismiss: () => void;
|
||||
validate: Validate<string>;
|
||||
validate: Validate<string, FormModel>;
|
||||
}
|
||||
|
||||
const initialFormModel = { folderName: '' };
|
||||
|
14
yarn.lock
14
yarn.lock
@ -3551,7 +3551,7 @@ __metadata:
|
||||
react-dom: "npm:18.2.0"
|
||||
react-dropzone: "npm:14.2.3"
|
||||
react-highlight-words: "npm:0.20.0"
|
||||
react-hook-form: "npm:7.5.3"
|
||||
react-hook-form: "npm:^7.49.2"
|
||||
react-i18next: "npm:^12.0.0"
|
||||
react-inlinesvg: "npm:3.0.2"
|
||||
react-loading-skeleton: "npm:3.3.1"
|
||||
@ -17664,7 +17664,7 @@ __metadata:
|
||||
react-dropzone: "npm:^14.2.3"
|
||||
react-grid-layout: "npm:1.4.2"
|
||||
react-highlight-words: "npm:0.20.0"
|
||||
react-hook-form: "npm:7.5.3"
|
||||
react-hook-form: "npm:^7.49.2"
|
||||
react-i18next: "npm:^12.0.0"
|
||||
react-inlinesvg: "npm:3.0.2"
|
||||
react-loading-skeleton: "npm:3.3.1"
|
||||
@ -25683,12 +25683,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-hook-form@npm:7.5.3":
|
||||
version: 7.5.3
|
||||
resolution: "react-hook-form@npm:7.5.3"
|
||||
"react-hook-form@npm:^7.49.2":
|
||||
version: 7.49.2
|
||||
resolution: "react-hook-form@npm:7.49.2"
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17
|
||||
checksum: ee359714c538ee8f328e147732aba629269849e159a88f96662c2e2d8ca175a6015e5364304bb8bada0249de0c335bafa64835abe09b5db895b702bce9159912
|
||||
react: ^16.8.0 || ^17 || ^18
|
||||
checksum: 7895d65b8458c42d46eb338803bb0fd1aab42fc69ecf80b47846eace9493a10cac5b05c9b744a5f9f1f7969a3e2703fc2118cdab97e49a7798a72d09f106383f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user