[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:
parent
c7267b63df
commit
922abfa21e
|
@ -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 && (
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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={
|
||||
|
|
Loading…
Reference in a new issue