diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index c499d1153a238..4eaf33c6f1570 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -260,20 +260,22 @@ export const DataGrid: FC = memo( - - - {(copy: () => void) => ( - - )} - - + {props.copyToClipboard && props.copyToClipboardDescription && ( + + + {(copy: () => void) => ( + + )} + + + )} )} {errorCallout !== undefined && ( diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.test.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.test.ts new file mode 100644 index 0000000000000..593e37175a2e5 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.test.ts @@ -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); + }); +}); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts index 7df6b11dc27ec..5c2d0cd1b1042 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts @@ -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> = 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 diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx index b4d035940192d..0a64b6803f19c 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/latest_function_form.tsx @@ -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 = ({ latestFunctionService }) => { +export const LatestFunctionForm: FC = ({ + copyToClipboard, + copyToClipboardDescription, + latestFunctionService, +}) => { return ( <> = ({ 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 + } > - { - latestFunctionService.updateLatestFunctionConfig({ - sort: { value: selected[0].value, label: selected[0].label as string }, - }); - }} - isClearable={false} - data-test-subj="transformWizardSortFieldSelector" - /> + <> + {latestFunctionService.sortFieldOptions.length > 0 && ( + { + latestFunctionService.updateLatestFunctionConfig({ + sort: { value: selected[0].value, label: selected[0].label as string }, + }); + }} + isClearable={false} + data-test-subj="transformWizardSortFieldSelector" + /> + )} + {latestFunctionService.sortFieldOptions.length === 0 && ( + +

+ {' '} + + {(copy: () => void) => ( + + )} + +

+
+ )} +
); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index d5c83357f4db5..743572632b5af 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -104,12 +104,6 @@ export const StepDefineForm: FC = 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 = 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 = React.memo((props) => { ) : null} {stepDefineForm.transformFunction === TRANSFORM_FUNCTION.LATEST ? ( - + ) : null} - - + {(stepDefineForm.transformFunction !== TRANSFORM_FUNCTION.LATEST || + stepDefineForm.latestFunctionConfig.sortFieldOptions.length > 0) && ( + <> + + + + )} ); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx index 4d2cb0e71a021..17deaa58ccb71 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx @@ -187,7 +187,8 @@ export const StepDefineSummary: FC = ({ 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" diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index b72d70295b10f..97af8135f3899 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -485,7 +485,7 @@ export const StepDetailsForm: FC = React.memo( setCreateIndexPattern(!createIndexPattern)} @@ -528,7 +528,7 @@ export const StepDetailsForm: FC = React.memo( label={i18n.translate( 'xpack.transform.stepDetailsForm.continuousModeDateFieldLabel', { - defaultMessage: 'Date field', + defaultMessage: 'Date field for continuous mode', } )} helpText={i18n.translate( diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx index 8ee2093a1e802..4af92c2147aaa 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx @@ -23,7 +23,7 @@ export const StepDetailsTimeField: FC = ({ 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 = ({ label={ } helpText={