Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [ML] Fix runtime mapping texts to runtime fields, add transform switch modal (#97008) #97508

Merged
merged 1 commit into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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