Skip to content

Commit

Permalink
[ML] Fix runtime mapping texts to runtime fields, add transform switc…
Browse files Browse the repository at this point in the history
…h modal (#97008)
  • Loading branch information
qn895 authored Apr 19, 2021
1 parent 64f30a2 commit 71e8118
Show file tree
Hide file tree
Showing 25 changed files with 280 additions and 121 deletions.
6 changes: 3 additions & 3 deletions x-pack/plugins/ml/common/util/runtime_field_utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@ describe('ML runtime field utils', () => {
).toBe(false);
});

it('allows object with most basic runtime mapping', () => {
it('allows object with most basic runtime field', () => {
expect(isRuntimeMappings({ fieldName: { type: 'keyword' } })).toBe(true);
});
it('allows object with multiple most basic runtime mappings', () => {
it('allows object with multiple most basic runtime fields', () => {
expect(
isRuntimeMappings({ fieldName1: { type: 'keyword' }, fieldName2: { type: 'keyword' } })
).toBe(true);
});
it('allows object with runtime mappings including scripts', () => {
it('allows object with runtime fields including scripts', () => {
expect(
isRuntimeMappings({
fieldName1: { type: 'keyword' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ export const ConfigurationStepForm: FC<ConfigurationStepProps> = ({
}
return !option.key?.includes(runtimeMappingKey);
});
// Runtime mappings have been removed
// Runtime fields have been removed
if (runtimeMappings === undefined && runtimeMappingsUpdated === true) {
setDependentVariableOptions(filteredOptions);
} else if (runtimeMappings) {
Expand All @@ -374,7 +374,7 @@ export const ConfigurationStepForm: FC<ConfigurationStepProps> = ({
}
}

// Update includes - remove previous runtime mappings then add supported runtime fields to includes
// Update includes - remove previous runtime fields then add supported runtime fields to includes
const updatedIncludes = includes.filter((field) => {
const isRemovedRuntimeField = previousRuntimeMapping && previousRuntimeMapping[field];
return !isRemovedRuntimeField;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,48 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { XJsonMode } from '@kbn/ace';
import { RuntimeField } from '../../../../../../../../../../src/plugins/data/common/index_patterns';
import { useMlContext } from '../../../../../contexts/ml';
import { CreateAnalyticsFormProps } from '../../../analytics_management/hooks/use_create_analytics_form';
import { XJson } from '../../../../../../../../../../src/plugins/es_ui_shared/public';
import { getCombinedRuntimeMappings } from '../../../../../components/data_grid/common';
import { isPopulatedObject } from '../../../../../../../common/util/object_utils';
import { RuntimeMappingsEditor } from './runtime_mappings_editor';
import { isRuntimeMappings } from '../../../../../../../common';
import { SwitchModal } from './switch_modal';

const advancedEditorsSidebarWidth = '220px';
const COPY_TO_CLIPBOARD_RUNTIME_MAPPINGS = i18n.translate(
const COPY_RUNTIME_FIELDS_TO_CLIPBOARD_TEXT = i18n.translate(
'xpack.ml.dataframe.analytics.createWizard.indexPreview.copyRuntimeMappingsClipboardTooltip',
{
defaultMessage: 'Copy Dev Console statement of the runtime mappings to the clipboard.',
defaultMessage: 'Copy Dev Console statement of the runtime fields to the clipboard.',
}
);

const APPLY_CHANGES_TEXT = i18n.translate(
'xpack.ml.dataframe.analytics.createWizard.advancedSourceEditorApplyButtonText',
{
defaultMessage: 'Apply changes',
}
);

const RUNTIME_FIELDS_EDITOR_HELP_TEXT = i18n.translate(
'xpack.ml.dataframe.analytics.createWizard.advancedRuntimeFieldsEditorHelpText',
{
defaultMessage: 'The advanced editor allows you to edit the runtime fields of the source.',
}
);

const EDIT_SWITCH_LABEL_TEXT = i18n.translate(
'xpack.ml.dataframe.analytics.createWizard.advancedEditorRuntimeFieldsSwitchLabel',
{
defaultMessage: 'Edit runtime fields',
}
);

const RUNTIME_FIELDS_LABEL_TEXT = i18n.translate(
'xpack.ml.dataframe.analytics.createWizard.runtimeFieldsLabel',
{
defaultMessage: 'Runtime fields',
}
);

Expand All @@ -45,12 +74,15 @@ interface Props {
state: CreateAnalyticsFormProps['state'];
}

type RuntimeMappings = Record<string, RuntimeField>;

export const RuntimeMappings: FC<Props> = ({ actions, state }) => {
const [isRuntimeMappingsEditorEnabled, setIsRuntimeMappingsEditorEnabled] = useState<boolean>(
false
);
const [
isRuntimeMappingsEditorSwitchModalVisible,
setRuntimeMappingsEditorSwitchModalVisible,
] = useState<boolean>(false);

const [
isRuntimeMappingsEditorApplyButtonEnabled,
setIsRuntimeMappingsEditorApplyButtonEnabled,
Expand All @@ -59,7 +91,6 @@ export const RuntimeMappings: FC<Props> = ({ actions, state }) => {
advancedEditorRuntimeMappingsLastApplied,
setAdvancedEditorRuntimeMappingsLastApplied,
] = useState<string>();
const [advancedEditorRuntimeMappings, setAdvancedEditorRuntimeMappings] = useState<string>();

const { setFormState } = actions;
const { jobType, previousRuntimeMapping, runtimeMappings } = state.form;
Expand Down Expand Up @@ -90,22 +121,22 @@ export const RuntimeMappings: FC<Props> = ({ actions, state }) => {
runtimeMappingsUpdated: true,
previousRuntimeMapping: previous,
});
setAdvancedEditorRuntimeMappings(prettySourceConfig);
setAdvancedRuntimeMappingsConfig(prettySourceConfig);
setAdvancedEditorRuntimeMappingsLastApplied(prettySourceConfig);
setIsRuntimeMappingsEditorApplyButtonEnabled(false);
};

// If switching to KQL after updating via editor - reset search
const toggleEditorHandler = (reset = false) => {
if (reset === true) {
setFormState({ runtimeMappingsUpdated: false });
}
if (isRuntimeMappingsEditorEnabled === false) {
setAdvancedEditorRuntimeMappingsLastApplied(advancedEditorRuntimeMappings);
setFormState({
runtimeMappingsUpdated: false,
});

setAdvancedRuntimeMappingsConfig(advancedEditorRuntimeMappingsLastApplied ?? '');
}

setIsRuntimeMappingsEditorEnabled(!isRuntimeMappingsEditorEnabled);
setIsRuntimeMappingsEditorApplyButtonEnabled(false);
setIsRuntimeMappingsEditorApplyButtonEnabled(isRuntimeMappings(runtimeMappings));
};

useEffect(function getInitialRuntimeMappings() {
Expand All @@ -114,8 +145,11 @@ export const RuntimeMappings: FC<Props> = ({ actions, state }) => {
runtimeMappings
);

const prettySourceConfig = JSON.stringify(combinedRuntimeMappings, null, 2);

if (combinedRuntimeMappings) {
setAdvancedRuntimeMappingsConfig(JSON.stringify(combinedRuntimeMappings, null, 2));
setAdvancedRuntimeMappingsConfig(prettySourceConfig);
setAdvancedEditorRuntimeMappingsLastApplied(prettySourceConfig);
setFormState({
runtimeMappings: combinedRuntimeMappings,
});
Expand All @@ -125,12 +159,7 @@ export const RuntimeMappings: FC<Props> = ({ actions, state }) => {
return (
<>
<EuiSpacer size="s" />
<EuiFormRow
fullWidth={true}
label={i18n.translate('xpack.ml.dataframe.analytics.createWizard.runtimeMappingsLabel', {
defaultMessage: 'Runtime mappings',
})}
>
<EuiFormRow fullWidth={true} label={RUNTIME_FIELDS_LABEL_TEXT}>
<EuiFlexGroup alignItems="baseline" justifyContent="spaceBetween">
<EuiFlexItem grow={true}>
{isPopulatedObject(runtimeMappings) ? (
Expand All @@ -139,8 +168,8 @@ export const RuntimeMappings: FC<Props> = ({ actions, state }) => {
</EuiText>
) : (
<FormattedMessage
id="xpack.ml.dataframe.analytics.createWizard.noRuntimeMappingsLabel"
defaultMessage="No runtime mapping"
id="xpack.ml.dataframe.analytics.createWizard.noRuntimeFieldLabel"
defaultMessage="No runtime field"
/>
)}

Expand Down Expand Up @@ -170,27 +199,41 @@ export const RuntimeMappings: FC<Props> = ({ actions, state }) => {
<EuiFlexItem grow={false}>
<EuiSwitch
disabled={jobType === undefined}
label={i18n.translate(
'xpack.ml.dataframe.analytics.createWizard.advancedEditorRuntimeMappingsSwitchLabel',
{
defaultMessage: 'Edit runtime mappings',
}
)}
label={EDIT_SWITCH_LABEL_TEXT}
checked={isRuntimeMappingsEditorEnabled}
onChange={() => toggleEditorHandler()}
onChange={() => {
if (
isRuntimeMappingsEditorEnabled &&
advancedRuntimeMappingsConfig !== advancedEditorRuntimeMappingsLastApplied
) {
setRuntimeMappingsEditorSwitchModalVisible(true);
return;
}

toggleEditorHandler();
}}
data-test-subj="mlDataFrameAnalyticsRuntimeMappingsEditorSwitch"
/>
{isRuntimeMappingsEditorSwitchModalVisible && (
<SwitchModal
onCancel={() => setRuntimeMappingsEditorSwitchModalVisible(false)}
onConfirm={() => {
setRuntimeMappingsEditorSwitchModalVisible(false);
toggleEditorHandler(true);
}}
/>
)}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiCopy
beforeMessage={COPY_TO_CLIPBOARD_RUNTIME_MAPPINGS}
beforeMessage={COPY_RUNTIME_FIELDS_TO_CLIPBOARD_TEXT}
textToCopy={advancedRuntimeMappingsConfig ?? ''}
>
{(copy: () => void) => (
<EuiButtonIcon
onClick={copy}
iconType="copyClipboard"
aria-label={COPY_TO_CLIPBOARD_RUNTIME_MAPPINGS}
aria-label={COPY_RUNTIME_FIELDS_TO_CLIPBOARD_TEXT}
/>
)}
</EuiCopy>
Expand All @@ -201,15 +244,7 @@ export const RuntimeMappings: FC<Props> = ({ actions, state }) => {
{isRuntimeMappingsEditorEnabled && (
<EuiFlexItem style={{ width: advancedEditorsSidebarWidth }}>
<EuiSpacer size="s" />
<EuiText size="xs">
{i18n.translate(
'xpack.ml.dataframe.analytics.createWizard.advancedRuntimeMappingsEditorHelpText',
{
defaultMessage:
'The advanced editor allows you to edit the runtime mappings of the source.',
}
)}
</EuiText>
<EuiText size="xs">{RUNTIME_FIELDS_EDITOR_HELP_TEXT}</EuiText>
<EuiSpacer size="s" />
<EuiButton
style={{ width: 'fit-content' }}
Expand All @@ -219,12 +254,7 @@ export const RuntimeMappings: FC<Props> = ({ actions, state }) => {
disabled={!isRuntimeMappingsEditorApplyButtonEnabled}
data-test-subj="mlDataFrameAnalyticsRuntimeMappingsApplyButton"
>
{i18n.translate(
'xpack.ml.dataframe.analytics.createWizard.advancedSourceEditorApplyButtonText',
{
defaultMessage: 'Apply changes',
}
)}
{APPLY_CHANGES_TEXT}
</EuiButton>
</EuiFlexItem>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { FC } from 'react';
import { EuiConfirmModal } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

interface Props {
onCancel: () => void;
onConfirm: () => void;
}

const modalTitle = i18n.translate(
'xpack.ml.dataframe.analytics.createWizard.runtimeEditorSwitchModalTitle',
{
defaultMessage: 'Edits will be lost',
}
);

const cancelButtonText = i18n.translate(
'xpack.ml.dataframe.analytics.createWizard.runtimeEditorSwitchModalCancelButtonText',
{
defaultMessage: 'Cancel',
}
);

const applyChangesText = i18n.translate(
'xpack.ml.dataframe.analytics.createWizard.runtimeEditorSwitchModalConfirmButtonText',
{
defaultMessage: 'Close editor',
}
);
const modalMessage = i18n.translate(
'xpack.ml.dataframe.analytics.createWizard.runtimeEditorSwitchModalBodyText',
{
defaultMessage: `The changes in the editor haven't been applied yet. By closing the editor you will lose your edits.`,
}
);

export const SwitchModal: FC<Props> = ({ onCancel, onConfirm }) => (
<EuiConfirmModal
title={modalTitle}
onCancel={onCancel}
onConfirm={onConfirm}
cancelButtonText={cancelButtonText}
confirmButtonText={applyChangesText}
buttonColor="danger"
defaultFocusedButton="confirm"
>
<p>{modalMessage}</p>
</EuiConfirmModal>
);
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ describe('filter_runtime_mappings', () => {
datafeed = getDatafeed();
});

test('returns no runtime mappings, no mappings in aggs', () => {
test('returns no runtime fields, no mappings in aggs', () => {
const resp = filterRuntimeMappings(job, datafeed);
expect(Object.keys(resp.runtime_mappings).length).toEqual(0);

Expand All @@ -111,7 +111,7 @@ describe('filter_runtime_mappings', () => {
expect(resp.discarded_mappings.airline_lower).not.toEqual(undefined);
});

test('returns no runtime mappings, no runtime mappings in datafeed', () => {
test('returns no runtime fields, no runtime fields in datafeed', () => {
datafeed.runtime_mappings = undefined;
const resp = filterRuntimeMappings(job, datafeed);
expect(Object.keys(resp.runtime_mappings).length).toEqual(0);
Expand All @@ -131,7 +131,7 @@ describe('filter_runtime_mappings', () => {
expect(resp.discarded_mappings.airline_lower).not.toEqual(undefined);
});

test('return no runtime mappings, no mappings in aggs', () => {
test('return no runtime fields, no mappings in aggs', () => {
datafeed.aggregations = getAggs();
datafeed.aggregations!.buckets!.aggregations!.responsetime!.avg!.field! = 'responsetime';

Expand All @@ -154,7 +154,7 @@ describe('filter_runtime_mappings', () => {
expect(resp.discarded_mappings.airline_lower).not.toEqual(undefined);
});

test('return two runtime mappings, no mappings in aggs', () => {
test('return two runtime fields, no mappings in aggs', () => {
// set the detector field to be a runtime mapping
job.analysis_config.detectors[0].field_name = 'responsetime_big';
// set the detector by field to be a runtime mapping
Expand All @@ -167,7 +167,7 @@ describe('filter_runtime_mappings', () => {
expect(Object.keys(resp.discarded_mappings).length).toEqual(0);
});

test('return two runtime mappings, no mappings in aggs, categorization job', () => {
test('return two runtime fields, no mappings in aggs, categorization job', () => {
job.analysis_config.detectors[0].function = 'count';
// set the detector field to be a runtime mapping
job.analysis_config.detectors[0].field_name = undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,7 @@ export function resultsServiceProvider(mlApiServices) {
},
},
},
// Runtime mappings only needed to support when query includes a runtime field
// Runtime fields only needed to support when query includes a runtime field
// even though the default timeField can be a search time runtime field
// because currently Kibana doesn't support that
...(isPopulatedObject(runtimeMappings) && query
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ export class DataVisualizer {
// filter aggregation with exists query.
const aggs: Aggs = datafeedAggregations !== undefined ? { ...datafeedAggregations } : {};

// Combine runtime mappings from the index pattern as well as the datafeed
// Combine runtime fields from the index pattern as well as the datafeed
const combinedRuntimeMappings: RuntimeMappings = {
...(isPopulatedObject(runtimeMappings) ? runtimeMappings : {}),
...(isPopulatedObject(datafeedConfig) && isPopulatedObject(datafeedConfig.runtime_mappings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ const validateFactory = (client: IScopedClusterClient, job: CombinedJob): Valida
}
} else {
// only report uniqueFieldName as not aggregatable if it's not part
// of a valid categorization configuration and if it's not a scripted field or runtime mapping.
// of a valid categorization configuration and if it's not a scripted field or runtime field.
if (
!isValidCategorizationConfig(job, uniqueFieldName) &&
!isScriptField(job, uniqueFieldName) &&
Expand Down
Loading

0 comments on commit 71e8118

Please sign in to comment.