[ML] Transforms: Fixes available fields for sort options for latest configuration (#88617)

- Fixes the transform preview header to display the heading text in any case and the copy-to-clipboard button for latest configurations (the copy-to-clipboard option for pivot is displayed within the form).
- Fix to avoid listing all fields for the sort option for latest configuration and only show date fields
- Fixes ambiguous form field labels
This commit is contained in:
Walter Rafelsberger 2021-01-21 08:11:59 +01:00 committed by GitHub
parent c7267b63df
commit 922abfa21e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 158 additions and 58 deletions

View file

@ -260,20 +260,22 @@ export const DataGrid: FC<Props> = memo(
<EuiFlexItem>
<DataGridTitle title={props.title} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiCopy
beforeMessage={props.copyToClipboardDescription}
textToCopy={props.copyToClipboard}
>
{(copy: () => void) => (
<EuiButtonIcon
onClick={copy}
iconType="copyClipboard"
aria-label={props.copyToClipboardDescription}
/>
)}
</EuiCopy>
</EuiFlexItem>
{props.copyToClipboard && props.copyToClipboardDescription && (
<EuiFlexItem grow={false}>
<EuiCopy
beforeMessage={props.copyToClipboardDescription}
textToCopy={props.copyToClipboard}
>
{(copy: () => void) => (
<EuiButtonIcon
onClick={copy}
iconType="copyClipboard"
aria-label={props.copyToClipboardDescription}
/>
)}
</EuiCopy>
</EuiFlexItem>
)}
</EuiFlexGroup>
)}
{errorCallout !== undefined && (

View file

@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { LatestFunctionConfigUI } from '../../../../../../../common/types/transform';
import { latestConfigMapper, validateLatestConfig } from './use_latest_function_config';
describe('useLatestFunctionConfig', () => {
it('should return a valid configuration', () => {
const config: LatestFunctionConfigUI = {
unique_key: [{ label: 'the-unique-key-label', value: 'the-unique-key' }],
sort: { label: 'the-sort-label', value: 'the-sort' },
};
const apiConfig = latestConfigMapper.toAPIConfig(config);
expect(apiConfig).toEqual({
unique_key: ['the-unique-key'],
sort: 'the-sort',
});
expect(validateLatestConfig(apiConfig).isValid).toBe(true);
});
it('should return an invalid partial configuration', () => {
const config: LatestFunctionConfigUI = {
unique_key: [{ label: 'the-unique-key-label', value: 'the-unique-key' }],
sort: { label: 'the-sort-label', value: undefined },
};
const apiConfig = latestConfigMapper.toAPIConfig(config);
expect(apiConfig).toEqual({
unique_key: ['the-unique-key'],
sort: '',
});
expect(validateLatestConfig(apiConfig).isValid).toBe(false);
});
it('should return false for isValid if no configuration given', () => {
expect(validateLatestConfig().isValid).toBe(false);
});
});

View file

@ -18,14 +18,10 @@ import { useAppDependencies } from '../../../../../app_dependencies';
* Latest function config mapper between API and UI
*/
export const latestConfigMapper = {
toAPIConfig(uiConfig: LatestFunctionConfigUI): LatestFunctionConfig | undefined {
if (uiConfig.sort === undefined || !uiConfig.unique_key?.length) {
return;
}
toAPIConfig(uiConfig: LatestFunctionConfigUI): LatestFunctionConfig {
return {
unique_key: uiConfig.unique_key.map((v) => v.value!),
sort: uiConfig.sort.value!,
unique_key: uiConfig.unique_key?.length ? uiConfig.unique_key.map((v) => v.value!) : [],
sort: uiConfig.sort?.value !== undefined ? uiConfig.sort.value! : '',
};
},
toUIConfig() {},
@ -56,7 +52,8 @@ function getOptions(
}));
const sortFieldOptions: Array<EuiComboBoxOptionOption<string>> = indexPattern.fields
.filter((v) => !ignoreFieldNames.has(v.name) && v.sortable)
// The backend API for `latest` allows all field types for sort but the UI will be limited to `date`.
.filter((v) => !ignoreFieldNames.has(v.name) && v.sortable && v.type === 'date')
.map((v) => ({
label: v.displayName,
value: v.name,
@ -69,7 +66,8 @@ function getOptions(
* Validates latest function configuration
*/
export function validateLatestConfig(config?: LatestFunctionConfig) {
const isValid: boolean = !!config?.unique_key?.length && config?.sort !== undefined;
const isValid: boolean =
!!config?.unique_key?.length && typeof config?.sort === 'string' && config?.sort.length > 0;
return {
isValid,
...(isValid

View file

@ -7,14 +7,20 @@
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiComboBox, EuiFormRow } from '@elastic/eui';
import { EuiButtonIcon, EuiCallOut, EuiComboBox, EuiCopy, EuiFormRow } from '@elastic/eui';
import { LatestFunctionService } from './hooks/use_latest_function_config';
interface LatestFunctionFormProps {
copyToClipboard: string;
copyToClipboardDescription: string;
latestFunctionService: LatestFunctionService;
}
export const LatestFunctionForm: FC<LatestFunctionFormProps> = ({ latestFunctionService }) => {
export const LatestFunctionForm: FC<LatestFunctionFormProps> = ({
copyToClipboard,
copyToClipboardDescription,
latestFunctionService,
}) => {
return (
<>
<EuiFormRow
@ -51,25 +57,55 @@ export const LatestFunctionForm: FC<LatestFunctionFormProps> = ({ latestFunction
defaultMessage="Sort field"
/>
}
helpText={
latestFunctionService.sortFieldOptions.length > 0
? i18n.translate('xpack.transform.stepDefineForm.sortHelpText', {
defaultMessage: 'Select the date field to be used to identify the latest document.',
})
: undefined
}
>
<EuiComboBox
fullWidth
placeholder={i18n.translate('xpack.transform.stepDefineForm.sortPlaceholder', {
defaultMessage: 'Add a sort field ...',
})}
singleSelection={{ asPlainText: true }}
options={latestFunctionService.sortFieldOptions}
selectedOptions={
latestFunctionService.config.sort ? [latestFunctionService.config.sort] : []
}
onChange={(selected) => {
latestFunctionService.updateLatestFunctionConfig({
sort: { value: selected[0].value, label: selected[0].label as string },
});
}}
isClearable={false}
data-test-subj="transformWizardSortFieldSelector"
/>
<>
{latestFunctionService.sortFieldOptions.length > 0 && (
<EuiComboBox
fullWidth
placeholder={i18n.translate('xpack.transform.stepDefineForm.sortPlaceholder', {
defaultMessage: 'Add a date field ...',
})}
singleSelection={{ asPlainText: true }}
options={latestFunctionService.sortFieldOptions}
selectedOptions={
latestFunctionService.config.sort ? [latestFunctionService.config.sort] : []
}
onChange={(selected) => {
latestFunctionService.updateLatestFunctionConfig({
sort: { value: selected[0].value, label: selected[0].label as string },
});
}}
isClearable={false}
data-test-subj="transformWizardSortFieldSelector"
/>
)}
{latestFunctionService.sortFieldOptions.length === 0 && (
<EuiCallOut color="danger" iconType="alert" size="m">
<p>
<FormattedMessage
id="xpack.transform.stepDefineForm.sortFieldOptionsEmptyError"
defaultMessage="No date fields are available to sort on. To use another field type, copy the configuration to the clipboard and continue creating the transform in the Console."
/>{' '}
<EuiCopy beforeMessage={copyToClipboardDescription} textToCopy={copyToClipboard}>
{(copy: () => void) => (
<EuiButtonIcon
onClick={copy}
iconType="copyClipboard"
aria-label={copyToClipboardDescription}
/>
)}
</EuiCopy>
</p>
</EuiCallOut>
)}
</>
</EuiFormRow>
</>
);

View file

@ -104,12 +104,6 @@ export const StepDefineForm: FC<StepDefineFormProps> = React.memo((props) => {
: stepDefineForm.latestFunctionConfig.requestPayload
);
const pivotPreviewProps = {
...usePivotData(indexPattern.title, pivotQuery, validationStatus, requestPayload),
dataTestSubj: 'transformPivotPreview',
toastNotifications,
};
const copyToClipboardSource = getIndexDevConsoleStatement(pivotQuery, indexPattern.title);
const copyToClipboardSourceDescription = i18n.translate(
'xpack.transform.indexPreview.copyClipboardTooltip',
@ -122,10 +116,25 @@ export const StepDefineForm: FC<StepDefineFormProps> = React.memo((props) => {
const copyToClipboardPivotDescription = i18n.translate(
'xpack.transform.pivotPreview.copyClipboardTooltip',
{
defaultMessage: 'Copy Dev Console statement of the pivot preview to the clipboard.',
defaultMessage: 'Copy Dev Console statement of the transform preview to the clipboard.',
}
);
const pivotPreviewProps = {
...usePivotData(indexPattern.title, pivotQuery, validationStatus, requestPayload),
dataTestSubj: 'transformPivotPreview',
title: i18n.translate('xpack.transform.pivotPreview.transformPreviewTitle', {
defaultMessage: 'Transform preview',
}),
toastNotifications,
...(stepDefineForm.transformFunction === TRANSFORM_FUNCTION.LATEST
? {
copyToClipboard: copyToClipboardPivot,
copyToClipboardDescription: copyToClipboardPivotDescription,
}
: {}),
};
const applySourceChangesHandler = () => {
const sourceConfig = JSON.parse(advancedEditorSourceConfig);
stepDefineForm.searchBar.actions.setSearchQuery(sourceConfig);
@ -377,12 +386,21 @@ export const StepDefineForm: FC<StepDefineFormProps> = React.memo((props) => {
</EuiFlexGroup>
) : null}
{stepDefineForm.transformFunction === TRANSFORM_FUNCTION.LATEST ? (
<LatestFunctionForm latestFunctionService={stepDefineForm.latestFunctionConfig} />
<LatestFunctionForm
copyToClipboard={copyToClipboardPivot}
copyToClipboardDescription={copyToClipboardPivotDescription}
latestFunctionService={stepDefineForm.latestFunctionConfig}
/>
) : null}
</EuiForm>
<EuiSpacer size="m" />
<DataGrid {...pivotPreviewProps} />
<EuiSpacer size="m" />
{(stepDefineForm.transformFunction !== TRANSFORM_FUNCTION.LATEST ||
stepDefineForm.latestFunctionConfig.sortFieldOptions.length > 0) && (
<>
<DataGrid {...pivotPreviewProps} />
<EuiSpacer size="m" />
</>
)}
</div>
);
});

View file

@ -187,7 +187,8 @@ export const StepDefineSummary: FC<Props> = ({
copyToClipboardDescription={i18n.translate(
'xpack.transform.pivotPreview.copyClipboardTooltip',
{
defaultMessage: 'Copy Dev Console statement of the pivot preview to the clipboard.',
defaultMessage:
'Copy Dev Console statement of the transform preview to the clipboard.',
}
)}
dataTestSubj="transformPivotPreview"

View file

@ -485,7 +485,7 @@ export const StepDetailsForm: FC<Props> = React.memo(
<EuiSwitch
name="transformCreateIndexPattern"
label={i18n.translate('xpack.transform.stepCreateForm.createIndexPatternLabel', {
defaultMessage: 'Create index pattern',
defaultMessage: 'Create Kibana index pattern',
})}
checked={createIndexPattern === true}
onChange={() => setCreateIndexPattern(!createIndexPattern)}
@ -528,7 +528,7 @@ export const StepDetailsForm: FC<Props> = React.memo(
label={i18n.translate(
'xpack.transform.stepDetailsForm.continuousModeDateFieldLabel',
{
defaultMessage: 'Date field',
defaultMessage: 'Date field for continuous mode',
}
)}
helpText={i18n.translate(

View file

@ -23,7 +23,7 @@ export const StepDetailsTimeField: FC<Props> = ({
const noTimeFieldLabel = i18n.translate(
'xpack.transform.stepDetailsForm.noTimeFieldOptionLabel',
{
defaultMessage: "I don't want to use the time filter",
defaultMessage: "I don't want to use the time field option",
}
);
@ -43,7 +43,7 @@ export const StepDetailsTimeField: FC<Props> = ({
label={
<FormattedMessage
id="xpack.transform.stepDetailsForm.indexPatternTimeFieldLabel"
defaultMessage="Time field"
defaultMessage="Time field for Kibana index pattern"
/>
}
helpText={