mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Correlations: Fix incorrect state transitions in transformations editor (#77434)
* Extract transformations editor row to a seprate component
* Use css object instead of string literals
* Update .betterer.results
* Post merge fixes
* Bring back validation rules
* Switch to css/object
* Post-merge fixes
Ensuring Stack from grafana-ui is used after conflicts with 25779bb6e5
This commit is contained in:
parent
662bc286c2
commit
b7f854a06c
@ -2869,10 +2869,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"]
|
||||
],
|
||||
"public/app/features/correlations/Forms/TransformationsEditor.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx:5381": [
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
|
||||
],
|
||||
|
@ -0,0 +1,202 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useState } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
|
||||
import { Field, Icon, IconButton, Input, Label, Select, Stack, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { getSupportedTransTypeDetails, getTransformOptions } from './types';
|
||||
|
||||
type Props = {
|
||||
index: number;
|
||||
value: Record<string, string>;
|
||||
readOnly: boolean;
|
||||
remove: (index?: number | number[]) => void;
|
||||
};
|
||||
|
||||
const getStyles = () => ({
|
||||
// set fixed position from the top instead of centring as the container
|
||||
// may get bigger when the for is invalid
|
||||
removeButton: css({
|
||||
marginTop: '25px',
|
||||
}),
|
||||
});
|
||||
|
||||
const TransformationEditorRow = (props: Props) => {
|
||||
const { index, value: defaultValue, readOnly, remove } = props;
|
||||
const { control, formState, register, setValue, watch, getValues } = useFormContext();
|
||||
|
||||
const [keptVals, setKeptVals] = useState<{ expression?: string; mapValue?: string }>({});
|
||||
|
||||
register(`config.transformations.${index}.type`, {
|
||||
required: { value: true, message: 'Please select a transformation type' },
|
||||
});
|
||||
const typeValue = useWatch({ name: `config.transformations.${index}.type`, control });
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const transformOptions = getTransformOptions();
|
||||
|
||||
return (
|
||||
<Stack direction="row" key={defaultValue.id} alignItems="flex-start">
|
||||
<Field
|
||||
label={
|
||||
<Stack gap={0.5}>
|
||||
<Label htmlFor={`config.transformations.${defaultValue.id}-${index}.type`}>Type</Label>
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
<p>The type of transformation that will be applied to the source data.</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon name="info-circle" size="sm" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
}
|
||||
invalid={!!formState.errors?.config?.transformations?.[index]?.type}
|
||||
error={formState.errors?.config?.transformations?.[index]?.type?.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`, '');
|
||||
}
|
||||
|
||||
if (newValueDetails.mapValueDetails.show) {
|
||||
setValue(`config.transformations.${index}.mapValue`, keptVals?.mapValue || '');
|
||||
} else {
|
||||
setValue(`config.transformations.${index}.mapValue`, '');
|
||||
}
|
||||
|
||||
setValue(`config.transformations.${index}.type`, value.value);
|
||||
}
|
||||
}}
|
||||
options={transformOptions}
|
||||
width={25}
|
||||
inputId={`config.transformations.${defaultValue.id}-${index}.type`}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label={
|
||||
<Stack gap={0.5}>
|
||||
<Label htmlFor={`config.transformations.${defaultValue.id}.field`}>Field</Label>
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
<p>
|
||||
Optional. The field to transform. If not specified, the transformation will be applied to the
|
||||
results field.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon name="info-circle" size="sm" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
{...register(`config.transformations.${index}.field`)}
|
||||
readOnly={readOnly}
|
||||
defaultValue={defaultValue.field}
|
||||
label="field"
|
||||
id={`config.transformations.${defaultValue.id}.field`}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label={
|
||||
<Stack gap={0.5}>
|
||||
<Label htmlFor={`config.transformations.${defaultValue.id}.expression`}>
|
||||
Expression
|
||||
{getSupportedTransTypeDetails(watch(`config.transformations.${index}.type`)).expressionDetails.required
|
||||
? ' *'
|
||||
: ''}
|
||||
</Label>
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
<p>
|
||||
Required for regular expression. The expression the transformation will use. Logfmt does not use
|
||||
further specifications.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon name="info-circle" size="sm" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
}
|
||||
invalid={!!formState.errors?.config?.transformations?.[index]?.expression}
|
||||
error={formState.errors?.config?.transformations?.[index]?.expression?.message}
|
||||
>
|
||||
<Input
|
||||
{...register(`config.transformations.${index}.expression`, {
|
||||
required: getSupportedTransTypeDetails(watch(`config.transformations.${index}.type`)).expressionDetails
|
||||
.required
|
||||
? 'Please define an expression'
|
||||
: undefined,
|
||||
})}
|
||||
defaultValue={defaultValue.expression}
|
||||
readOnly={readOnly}
|
||||
disabled={!getSupportedTransTypeDetails(watch(`config.transformations.${index}.type`)).expressionDetails.show}
|
||||
id={`config.transformations.${defaultValue.id}.expression`}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label={
|
||||
<Stack gap={0.5}>
|
||||
<Label htmlFor={`config.transformations.${defaultValue.id}.mapValue`}>Map value</Label>
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
<p>
|
||||
Optional. Defines the name of the variable. This is currently only valid for regular expressions
|
||||
with a single, unnamed capture group.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon name="info-circle" size="sm" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
{...register(`config.transformations.${index}.mapValue`)}
|
||||
defaultValue={defaultValue.mapValue}
|
||||
readOnly={readOnly}
|
||||
disabled={!getSupportedTransTypeDetails(watch(`config.transformations.${index}.type`)).mapValueDetails.show}
|
||||
id={`config.transformations.${defaultValue.id}.mapValue`}
|
||||
/>
|
||||
</Field>
|
||||
{!readOnly && (
|
||||
<div className={styles.removeButton}>
|
||||
<IconButton
|
||||
tooltip="Remove transformation"
|
||||
name="trash-alt"
|
||||
onClick={() => {
|
||||
remove(index);
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransformationEditorRow;
|
@ -1,48 +1,27 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { compact, fill } from 'lodash';
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import {
|
||||
Button,
|
||||
Field,
|
||||
FieldArray,
|
||||
Icon,
|
||||
IconButton,
|
||||
Input,
|
||||
InputControl,
|
||||
Label,
|
||||
Select,
|
||||
Tooltip,
|
||||
useStyles2,
|
||||
Stack,
|
||||
} from '@grafana/ui';
|
||||
import { Button, FieldArray, Stack, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { getSupportedTransTypeDetails, getTransformOptions } from './types';
|
||||
import TransformationsEditorRow from './TransformationEditorRow';
|
||||
|
||||
type Props = { readOnly: boolean };
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
heading: css`
|
||||
font-size: ${theme.typography.h5.fontSize};
|
||||
font-weight: ${theme.typography.fontWeightRegular};
|
||||
`,
|
||||
// set fixed position from the top instead of centring as the container
|
||||
// may get bigger when the for is invalid
|
||||
removeButton: css`
|
||||
margin-top: 25px;
|
||||
`,
|
||||
heading: css({
|
||||
fontSize: theme.typography.h5.fontSize,
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
}),
|
||||
});
|
||||
|
||||
export const TransformationsEditor = (props: Props) => {
|
||||
const { control, formState, register, setValue, watch, getValues } = useFormContext();
|
||||
const { control, register } = useFormContext();
|
||||
const { readOnly } = props;
|
||||
const [keptVals, setKeptVals] = useState<Array<{ expression?: string; mapValue?: string }>>([]);
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const transformOptions = getTransformOptions();
|
||||
return (
|
||||
<>
|
||||
<input type="hidden" {...register('id')} />
|
||||
@ -56,198 +35,13 @@ export const TransformationsEditor = (props: Props) => {
|
||||
<div>
|
||||
{fields.map((fieldVal, index) => {
|
||||
return (
|
||||
<Stack direction="row" key={fieldVal.id} alignItems="flex-start">
|
||||
<Field
|
||||
label={
|
||||
<Stack gap={0.5}>
|
||||
<Label htmlFor={`config.transformations.${fieldVal.id}-${index}.type`}>Type</Label>
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
<p>The type of transformation that will be applied to the source data.</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon name="info-circle" size="sm" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
}
|
||||
invalid={!!formState.errors?.config?.transformations?.[index]?.type}
|
||||
error={formState.errors?.config?.transformations?.[index]?.type?.message}
|
||||
validationMessageHorizontalOverflow={true}
|
||||
>
|
||||
<InputControl
|
||||
render={({ field: { onChange, ref, ...field } }) => {
|
||||
// input control field is not manipulated with remove, use value from control
|
||||
return (
|
||||
<Select
|
||||
{...field}
|
||||
value={fieldVal.type}
|
||||
onChange={(value) => {
|
||||
if (!readOnly) {
|
||||
const currentValues = getValues().config.transformations[index];
|
||||
let keptValsCopy = fill(Array(index + 1), {});
|
||||
keptVals.forEach((keptVal, i) => (keptValsCopy[i] = keptVal));
|
||||
keptValsCopy[index] = {
|
||||
expression: currentValues.expression,
|
||||
mapValue: currentValues.mapValue,
|
||||
};
|
||||
|
||||
setKeptVals(keptValsCopy);
|
||||
|
||||
const newValueDetails = getSupportedTransTypeDetails(value.value);
|
||||
|
||||
if (newValueDetails.expressionDetails.show) {
|
||||
setValue(
|
||||
`config.transformations.${index}.expression`,
|
||||
keptVals[index]?.expression || ''
|
||||
);
|
||||
} else {
|
||||
setValue(`config.transformations.${index}.expression`, '');
|
||||
}
|
||||
|
||||
if (newValueDetails.mapValueDetails.show) {
|
||||
setValue(
|
||||
`config.transformations.${index}.mapValue`,
|
||||
keptVals[index]?.mapValue || ''
|
||||
);
|
||||
} else {
|
||||
setValue(`config.transformations.${index}.mapValue`, '');
|
||||
}
|
||||
|
||||
onChange(value.value);
|
||||
}
|
||||
}}
|
||||
options={transformOptions}
|
||||
width={25}
|
||||
inputId={`config.transformations.${fieldVal.id}-${index}.type`}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
control={control}
|
||||
name={`config.transformations.${index}.type`}
|
||||
rules={{ required: { value: true, message: 'Please select a transformation type' } }}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label={
|
||||
<Stack gap={0.5}>
|
||||
<Label htmlFor={`config.transformations.${fieldVal.id}.field`}>Field</Label>
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
<p>
|
||||
Optional. The field to transform. If not specified, the transformation will be
|
||||
applied to the results field.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon name="info-circle" size="sm" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
{...register(`config.transformations.${index}.field`)}
|
||||
readOnly={readOnly}
|
||||
defaultValue={fieldVal.field}
|
||||
label="field"
|
||||
id={`config.transformations.${fieldVal.id}.field`}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label={
|
||||
<Stack gap={0.5}>
|
||||
<Label htmlFor={`config.transformations.${fieldVal.id}.expression`}>
|
||||
Expression
|
||||
{getSupportedTransTypeDetails(watch(`config.transformations.${index}.type`))
|
||||
.expressionDetails.required
|
||||
? ' *'
|
||||
: ''}
|
||||
</Label>
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
<p>
|
||||
Required for regular expression. The expression the transformation will use.
|
||||
Logfmt does not use further specifications.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon name="info-circle" size="sm" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
}
|
||||
invalid={!!formState.errors?.config?.transformations?.[index]?.expression}
|
||||
error={formState.errors?.config?.transformations?.[index]?.expression?.message}
|
||||
>
|
||||
<Input
|
||||
{...register(`config.transformations.${index}.expression`, {
|
||||
required: getSupportedTransTypeDetails(watch(`config.transformations.${index}.type`))
|
||||
.expressionDetails.required
|
||||
? 'Please define an expression'
|
||||
: undefined,
|
||||
})}
|
||||
defaultValue={fieldVal.expression}
|
||||
readOnly={readOnly}
|
||||
disabled={
|
||||
!getSupportedTransTypeDetails(watch(`config.transformations.${index}.type`))
|
||||
.expressionDetails.show
|
||||
}
|
||||
id={`config.transformations.${fieldVal.id}.expression`}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label={
|
||||
<Stack gap={0.5}>
|
||||
<Label htmlFor={`config.transformations.${fieldVal.id}.mapValue`}>Map value</Label>
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
<p>
|
||||
Optional. Defines the name of the variable. This is currently only valid for
|
||||
regular expressions with a single, unnamed capture group.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon name="info-circle" size="sm" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
{...register(`config.transformations.${index}.mapValue`)}
|
||||
defaultValue={fieldVal.mapValue}
|
||||
readOnly={readOnly}
|
||||
disabled={
|
||||
!getSupportedTransTypeDetails(watch(`config.transformations.${index}.type`))
|
||||
.mapValueDetails.show
|
||||
}
|
||||
id={`config.transformations.${fieldVal.id}.mapValue`}
|
||||
/>
|
||||
</Field>
|
||||
{!readOnly && (
|
||||
<div className={styles.removeButton}>
|
||||
<IconButton
|
||||
tooltip="Remove transformation"
|
||||
name="trash-alt"
|
||||
onClick={() => {
|
||||
remove(index);
|
||||
const keptValsCopy: Array<{ expression?: string; mapValue?: string } | undefined> = [
|
||||
...keptVals,
|
||||
];
|
||||
keptValsCopy[index] = undefined;
|
||||
setKeptVals(compact(keptValsCopy));
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
</Stack>
|
||||
<TransformationsEditorRow
|
||||
key={index}
|
||||
value={fieldVal}
|
||||
index={index}
|
||||
readOnly={readOnly}
|
||||
remove={remove}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user