mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ML] Data frames: Advanced editor (#39659)
Adds an advanced editor to the data frame transform pivot wizard to allow adding custom group_by and aggregation configurations not supported natively by the UI. The regular UI and the advanced editor stay in sync. Aggregations not editable by the UI display the configuration instead of an editable form. The aggregation name can still be edited.
This commit is contained in:
parent
e522e757ba
commit
cbe91c29fe
24 changed files with 593 additions and 171 deletions
|
@ -8,6 +8,7 @@ export * from './aggregations';
|
|||
export * from './fields';
|
||||
export * from './dropdown';
|
||||
export * from './kibana_context';
|
||||
export * from './job';
|
||||
export * from './navigation';
|
||||
export * from './pivot_aggs';
|
||||
export * from './pivot_group_by';
|
||||
|
|
33
x-pack/legacy/plugins/ml/public/data_frame/common/job.ts
Normal file
33
x-pack/legacy/plugins/ml/public/data_frame/common/job.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { PivotAggDict } from './pivot_aggs';
|
||||
import { PivotGroupByDict } from './pivot_group_by';
|
||||
|
||||
export type IndexName = string;
|
||||
export type IndexPattern = string;
|
||||
export type JobId = string;
|
||||
|
||||
export interface DataFrameJob {
|
||||
dest: {
|
||||
index: IndexName;
|
||||
};
|
||||
source: {
|
||||
index: IndexPattern;
|
||||
};
|
||||
sync?: object;
|
||||
}
|
||||
|
||||
export interface DataFrameTransform extends DataFrameJob {
|
||||
pivot: {
|
||||
aggregations: PivotAggDict;
|
||||
group_by: PivotGroupByDict;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DataFrameTransformWithId extends DataFrameTransform {
|
||||
id: string;
|
||||
}
|
|
@ -54,7 +54,7 @@ export const pivotAggsFieldSupport = {
|
|||
[KBN_FIELD_TYPES.CONFLICT]: [PIVOT_SUPPORTED_AGGS.VALUE_COUNT],
|
||||
};
|
||||
|
||||
type PivotAgg = {
|
||||
export type PivotAgg = {
|
||||
[key in PIVOT_SUPPORTED_AGGS]?: {
|
||||
field: FieldName;
|
||||
}
|
||||
|
@ -63,11 +63,39 @@ type PivotAgg = {
|
|||
export type PivotAggDict = { [key in AggName]: PivotAgg };
|
||||
|
||||
// The internal representation of an aggregation definition.
|
||||
export interface PivotAggsConfig {
|
||||
export interface PivotAggsConfigBase {
|
||||
agg: PIVOT_SUPPORTED_AGGS;
|
||||
field: FieldName;
|
||||
aggName: AggName;
|
||||
dropDownName: string;
|
||||
}
|
||||
|
||||
export interface PivotAggsConfigWithUiSupport extends PivotAggsConfigBase {
|
||||
field: FieldName;
|
||||
}
|
||||
|
||||
export function isPivotAggsConfigWithUiSupport(arg: any): arg is PivotAggsConfigWithUiSupport {
|
||||
return (
|
||||
arg.hasOwnProperty('agg') &&
|
||||
arg.hasOwnProperty('aggName') &&
|
||||
arg.hasOwnProperty('dropDownName') &&
|
||||
arg.hasOwnProperty('field') &&
|
||||
pivotSupportedAggs.includes(arg.agg)
|
||||
);
|
||||
}
|
||||
|
||||
export type PivotAggsConfig = PivotAggsConfigBase | PivotAggsConfigWithUiSupport;
|
||||
|
||||
export type PivotAggsConfigWithUiSupportDict = Dictionary<PivotAggsConfigWithUiSupport>;
|
||||
export type PivotAggsConfigDict = Dictionary<PivotAggsConfig>;
|
||||
|
||||
export function getEsAggFromAggConfig(groupByConfig: PivotAggsConfigBase): PivotAgg {
|
||||
const esAgg = { ...groupByConfig };
|
||||
|
||||
delete esAgg.agg;
|
||||
delete esAgg.aggName;
|
||||
delete esAgg.dropDownName;
|
||||
|
||||
return {
|
||||
[groupByConfig.agg]: esAgg,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export const pivotGroupByFieldSupport = {
|
|||
};
|
||||
|
||||
interface GroupByConfigBase {
|
||||
field: FieldName;
|
||||
agg: PIVOT_SUPPORTED_GROUP_BY_AGGS;
|
||||
aggName: AggName;
|
||||
dropDownName: string;
|
||||
}
|
||||
|
@ -69,31 +69,65 @@ export enum DATE_HISTOGRAM_FORMAT {
|
|||
|
||||
interface GroupByDateHistogram extends GroupByConfigBase {
|
||||
agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.DATE_HISTOGRAM;
|
||||
field: FieldName;
|
||||
format?: DATE_HISTOGRAM_FORMAT;
|
||||
calendar_interval: string;
|
||||
}
|
||||
|
||||
interface GroupByHistogram extends GroupByConfigBase {
|
||||
agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.HISTOGRAM;
|
||||
field: FieldName;
|
||||
interval: string;
|
||||
}
|
||||
|
||||
interface GroupByTerms extends GroupByConfigBase {
|
||||
agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS;
|
||||
field: FieldName;
|
||||
}
|
||||
|
||||
export type GroupByConfigWithInterval = GroupByDateHistogram | GroupByHistogram;
|
||||
export type PivotGroupByConfig = GroupByDateHistogram | GroupByHistogram | GroupByTerms;
|
||||
export type GroupByConfigWithUiSupport = GroupByDateHistogram | GroupByHistogram | GroupByTerms;
|
||||
|
||||
export type PivotGroupByConfig =
|
||||
| GroupByConfigBase
|
||||
| GroupByDateHistogram
|
||||
| GroupByHistogram
|
||||
| GroupByTerms;
|
||||
export type PivotGroupByConfigWithUiSupportDict = Dictionary<GroupByConfigWithUiSupport>;
|
||||
export type PivotGroupByConfigDict = Dictionary<PivotGroupByConfig>;
|
||||
|
||||
export function isGroupByDateHistogram(arg: any): arg is GroupByDateHistogram {
|
||||
return arg.hasOwnProperty('calendar_interval');
|
||||
return (
|
||||
arg.hasOwnProperty('agg') &&
|
||||
arg.hasOwnProperty('field') &&
|
||||
arg.hasOwnProperty('calendar_interval') &&
|
||||
arg.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.DATE_HISTOGRAM
|
||||
);
|
||||
}
|
||||
|
||||
export function isGroupByHistogram(arg: any): arg is GroupByHistogram {
|
||||
return arg.hasOwnProperty('interval');
|
||||
return (
|
||||
arg.hasOwnProperty('agg') &&
|
||||
arg.hasOwnProperty('field') &&
|
||||
arg.hasOwnProperty('interval') &&
|
||||
arg.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.HISTOGRAM
|
||||
);
|
||||
}
|
||||
|
||||
export function isGroupByTerms(arg: any): arg is GroupByTerms {
|
||||
return (
|
||||
arg.hasOwnProperty('agg') &&
|
||||
arg.hasOwnProperty('field') &&
|
||||
arg.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS
|
||||
);
|
||||
}
|
||||
|
||||
export function isPivotGroupByConfigWithUiSupport(arg: any): arg is GroupByConfigWithUiSupport {
|
||||
return isGroupByDateHistogram(arg) || isGroupByHistogram(arg) || isGroupByTerms(arg);
|
||||
}
|
||||
|
||||
export type GenericAgg = object;
|
||||
|
||||
export interface TermsAgg {
|
||||
terms: {
|
||||
field: FieldName;
|
||||
|
@ -115,5 +149,17 @@ export interface DateHistogramAgg {
|
|||
};
|
||||
}
|
||||
|
||||
type PivotGroupBy = TermsAgg | HistogramAgg | DateHistogramAgg;
|
||||
export type PivotGroupBy = GenericAgg | TermsAgg | HistogramAgg | DateHistogramAgg;
|
||||
export type PivotGroupByDict = Dictionary<PivotGroupBy>;
|
||||
|
||||
export function getEsAggFromGroupByConfig(groupByConfig: GroupByConfigBase): GenericAgg {
|
||||
const esAgg = { ...groupByConfig };
|
||||
|
||||
delete esAgg.agg;
|
||||
delete esAgg.aggName;
|
||||
delete esAgg.dropDownName;
|
||||
|
||||
return {
|
||||
[groupByConfig.agg]: esAgg,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -96,6 +96,7 @@ describe('Data Frame: Common', () => {
|
|||
const pivotState: DefinePivotExposedState = {
|
||||
aggList: { 'the-agg-name': agg },
|
||||
groupByList: { 'the-group-by-name': groupBy },
|
||||
isAdvancedEditorEnabled: false,
|
||||
search: 'the-query',
|
||||
valid: true,
|
||||
};
|
||||
|
|
|
@ -13,13 +13,16 @@ import { dictionaryToArray } from '../../../common/types/common';
|
|||
import { DefinePivotExposedState } from '../components/define_pivot/define_pivot_form';
|
||||
import { JobDetailsExposedState } from '../components/job_details/job_details_form';
|
||||
|
||||
import { PivotGroupByConfig } from '../common';
|
||||
|
||||
import {
|
||||
dateHistogramIntervalFormatRegex,
|
||||
DATE_HISTOGRAM_FORMAT,
|
||||
PIVOT_SUPPORTED_GROUP_BY_AGGS,
|
||||
} from './pivot_group_by';
|
||||
getEsAggFromAggConfig,
|
||||
getEsAggFromGroupByConfig,
|
||||
isGroupByDateHistogram,
|
||||
isGroupByHistogram,
|
||||
isGroupByTerms,
|
||||
PivotGroupByConfig,
|
||||
} from '../common';
|
||||
|
||||
import { dateHistogramIntervalFormatRegex, DATE_HISTOGRAM_FORMAT } from './pivot_group_by';
|
||||
|
||||
import { PivotAggDict, PivotAggsConfig } from './pivot_aggs';
|
||||
import { DateHistogramAgg, HistogramAgg, PivotGroupByDict, TermsAgg } from './pivot_group_by';
|
||||
|
@ -97,14 +100,14 @@ export function getDataFramePreviewRequest(
|
|||
}
|
||||
|
||||
groupBy.forEach(g => {
|
||||
if (g.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS) {
|
||||
if (isGroupByTerms(g)) {
|
||||
const termsAgg: TermsAgg = {
|
||||
terms: {
|
||||
field: g.field,
|
||||
},
|
||||
};
|
||||
request.pivot.group_by[g.aggName] = termsAgg;
|
||||
} else if (g.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.HISTOGRAM) {
|
||||
} else if (isGroupByHistogram(g)) {
|
||||
const histogramAgg: HistogramAgg = {
|
||||
histogram: {
|
||||
field: g.field,
|
||||
|
@ -112,7 +115,7 @@ export function getDataFramePreviewRequest(
|
|||
},
|
||||
};
|
||||
request.pivot.group_by[g.aggName] = histogramAgg;
|
||||
} else if (g.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.DATE_HISTOGRAM) {
|
||||
} else if (isGroupByDateHistogram(g)) {
|
||||
const dateHistogramAgg: DateHistogramAgg = {
|
||||
date_histogram: {
|
||||
field: g.field,
|
||||
|
@ -135,15 +138,13 @@ export function getDataFramePreviewRequest(
|
|||
}
|
||||
}
|
||||
request.pivot.group_by[g.aggName] = dateHistogramAgg;
|
||||
} else {
|
||||
request.pivot.group_by[g.aggName] = getEsAggFromGroupByConfig(g);
|
||||
}
|
||||
});
|
||||
|
||||
aggs.forEach(agg => {
|
||||
request.pivot.aggregations[agg.aggName] = {
|
||||
[agg.agg]: {
|
||||
field: agg.field,
|
||||
},
|
||||
};
|
||||
request.pivot.aggregations[agg.aggName] = getEsAggFromAggConfig(agg);
|
||||
});
|
||||
|
||||
return request;
|
||||
|
|
|
@ -13,6 +13,7 @@ exports[`Data Frame: Aggregation <PopoverForm /> Minimal initialization 1`] = `
|
|||
error={false}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText=""
|
||||
isInvalid={false}
|
||||
label="Aggregation name"
|
||||
labelType="label"
|
||||
|
|
|
@ -10,14 +10,14 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/eui';
|
||||
|
||||
import { AggName, PivotAggsConfig, PivotAggsConfigDict } from '../../common';
|
||||
import { AggName, PivotAggsConfig, PivotAggsConfigWithUiSupportDict } from '../../common';
|
||||
|
||||
import { PopoverForm } from './popover_form';
|
||||
|
||||
interface Props {
|
||||
item: PivotAggsConfig;
|
||||
otherAggNames: AggName[];
|
||||
options: PivotAggsConfigDict;
|
||||
options: PivotAggsConfigWithUiSupportDict;
|
||||
deleteHandler(l: AggName): void;
|
||||
onChange(item: PivotAggsConfig): void;
|
||||
}
|
||||
|
|
|
@ -8,13 +8,18 @@ import React, { Fragment } from 'react';
|
|||
|
||||
import { EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { AggName, PivotAggsConfig, PivotAggsConfigDict } from '../../common';
|
||||
import {
|
||||
AggName,
|
||||
PivotAggsConfig,
|
||||
PivotAggsConfigDict,
|
||||
PivotAggsConfigWithUiSupportDict,
|
||||
} from '../../common';
|
||||
|
||||
import { AggLabelForm } from './agg_label_form';
|
||||
|
||||
export interface ListProps {
|
||||
list: PivotAggsConfigDict;
|
||||
options: PivotAggsConfigDict;
|
||||
options: PivotAggsConfigWithUiSupportDict;
|
||||
deleteHandler(l: string): void;
|
||||
onChange(previousAggName: AggName, item: PivotAggsConfig): void;
|
||||
}
|
||||
|
|
|
@ -8,15 +8,24 @@ import React, { useState } from 'react';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiButton, EuiFieldText, EuiForm, EuiFormRow, EuiSelect } from '@elastic/eui';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCodeEditor,
|
||||
EuiFieldText,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiSelect,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { dictionaryToArray } from '../../../../common/types/common';
|
||||
|
||||
import {
|
||||
AggName,
|
||||
isAggName,
|
||||
isPivotAggsConfigWithUiSupport,
|
||||
getEsAggFromAggConfig,
|
||||
PivotAggsConfig,
|
||||
PivotAggsConfigDict,
|
||||
PivotAggsConfigWithUiSupportDict,
|
||||
PIVOT_SUPPORTED_AGGS,
|
||||
} from '../../common';
|
||||
|
||||
|
@ -27,7 +36,7 @@ interface SelectOption {
|
|||
interface Props {
|
||||
defaultData: PivotAggsConfig;
|
||||
otherAggNames: AggName[];
|
||||
options: PivotAggsConfigDict;
|
||||
options: PivotAggsConfigWithUiSupportDict;
|
||||
onChange(d: PivotAggsConfig): void;
|
||||
}
|
||||
|
||||
|
@ -37,21 +46,30 @@ export const PopoverForm: React.SFC<Props> = ({
|
|||
onChange,
|
||||
options,
|
||||
}) => {
|
||||
const isUnsupportedAgg = !isPivotAggsConfigWithUiSupport(defaultData);
|
||||
|
||||
const [aggName, setAggName] = useState(defaultData.aggName);
|
||||
const [agg, setAgg] = useState(defaultData.agg);
|
||||
const [field, setField] = useState(defaultData.field);
|
||||
const [field, setField] = useState(
|
||||
isPivotAggsConfigWithUiSupport(defaultData) ? defaultData.field : ''
|
||||
);
|
||||
|
||||
const optionsArr = dictionaryToArray(options);
|
||||
const availableFields: SelectOption[] = optionsArr
|
||||
.filter(o => o.agg === defaultData.agg)
|
||||
.map(o => {
|
||||
return { text: o.field };
|
||||
});
|
||||
const availableAggs: SelectOption[] = optionsArr
|
||||
.filter(o => o.field === defaultData.field)
|
||||
.map(o => {
|
||||
return { text: o.agg };
|
||||
});
|
||||
const availableFields: SelectOption[] = [];
|
||||
const availableAggs: SelectOption[] = [];
|
||||
|
||||
if (!isUnsupportedAgg) {
|
||||
const optionsArr = dictionaryToArray(options);
|
||||
optionsArr
|
||||
.filter(o => o.agg === defaultData.agg)
|
||||
.forEach(o => {
|
||||
availableFields.push({ text: o.field });
|
||||
});
|
||||
optionsArr
|
||||
.filter(o => isPivotAggsConfigWithUiSupport(defaultData) && o.field === defaultData.field)
|
||||
.forEach(o => {
|
||||
availableAggs.push({ text: o.agg });
|
||||
});
|
||||
}
|
||||
|
||||
let aggNameError = '';
|
||||
|
||||
|
@ -77,6 +95,14 @@ export const PopoverForm: React.SFC<Props> = ({
|
|||
<EuiFormRow
|
||||
error={!validAggName && [aggNameError]}
|
||||
isInvalid={!validAggName}
|
||||
helpText={
|
||||
isUnsupportedAgg
|
||||
? i18n.translate('xpack.ml.dataframe.agg.popoverForm.unsupportedAggregationHelpText', {
|
||||
defaultMessage:
|
||||
'Only the aggregation name can be edited in this form. Please use the advanced editor to edit the other parts of the aggregation.',
|
||||
})
|
||||
: ''
|
||||
}
|
||||
label={i18n.translate('xpack.ml.dataframe.agg.popoverForm.nameLabel', {
|
||||
defaultMessage: 'Aggregation name',
|
||||
})}
|
||||
|
@ -113,6 +139,18 @@ export const PopoverForm: React.SFC<Props> = ({
|
|||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
{isUnsupportedAgg && (
|
||||
<EuiCodeEditor
|
||||
mode="json"
|
||||
theme="github"
|
||||
width="100%"
|
||||
height="200px"
|
||||
value={JSON.stringify(getEsAggFromAggConfig(defaultData), null, 2)}
|
||||
setOptions={{ fontSize: '12px', showLineNumbers: false }}
|
||||
isReadOnly
|
||||
aria-label="Read only code editor"
|
||||
/>
|
||||
)}
|
||||
<EuiFormRow hasEmptyLabelSpace>
|
||||
<EuiButton
|
||||
isDisabled={!formValid}
|
||||
|
|
|
@ -39,6 +39,7 @@ exports[`Data Frame: <DefinePivotSummary /> Minimal initialization 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
isAdvancedEditorEnabled={false}
|
||||
search="the-query"
|
||||
valid={true}
|
||||
/>
|
||||
|
|
|
@ -15,10 +15,10 @@ import {
|
|||
DropDownLabel,
|
||||
DropDownOption,
|
||||
FieldName,
|
||||
PivotAggsConfigDict,
|
||||
GroupByConfigWithUiSupport,
|
||||
PivotAggsConfigWithUiSupportDict,
|
||||
pivotAggsFieldSupport,
|
||||
PivotGroupByConfig,
|
||||
PivotGroupByConfigDict,
|
||||
PivotGroupByConfigWithUiSupportDict,
|
||||
pivotGroupByFieldSupport,
|
||||
PIVOT_SUPPORTED_GROUP_BY_AGGS,
|
||||
} from '../../common';
|
||||
|
@ -33,7 +33,7 @@ function getDefaultGroupByConfig(
|
|||
dropDownName: string,
|
||||
fieldName: FieldName,
|
||||
groupByAgg: PIVOT_SUPPORTED_GROUP_BY_AGGS
|
||||
): PivotGroupByConfig {
|
||||
): GroupByConfigWithUiSupport {
|
||||
switch (groupByAgg) {
|
||||
case PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS:
|
||||
return {
|
||||
|
@ -66,11 +66,11 @@ const illegalEsAggNameChars = /[[\]>]/g;
|
|||
export function getPivotDropdownOptions(indexPattern: IndexPattern) {
|
||||
// The available group by options
|
||||
const groupByOptions: EuiComboBoxOptionProps[] = [];
|
||||
const groupByOptionsData: PivotGroupByConfigDict = {};
|
||||
const groupByOptionsData: PivotGroupByConfigWithUiSupportDict = {};
|
||||
|
||||
// The available aggregations
|
||||
const aggOptions: EuiComboBoxOptionProps[] = [];
|
||||
const aggOptionsData: PivotAggsConfigDict = {};
|
||||
const aggOptionsData: PivotAggsConfigWithUiSupportDict = {};
|
||||
|
||||
const ignoreFieldNames = ['_id', '_index', '_type'];
|
||||
const fields = indexPattern.fields
|
||||
|
|
|
@ -8,16 +8,24 @@ import React, { ChangeEvent, Fragment, SFC, useContext, useEffect, useState } fr
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { metadata } from 'ui/metadata';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCodeEditor,
|
||||
EuiConfirmModal,
|
||||
EuiFieldSearch,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiFormHelpText,
|
||||
EuiFormRow,
|
||||
EuiLink,
|
||||
EuiOverlayMask,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { dictionaryToArray } from '../../../../common/types/common';
|
||||
|
@ -31,15 +39,18 @@ import {
|
|||
AggName,
|
||||
DropDownLabel,
|
||||
getPivotQuery,
|
||||
isGroupByDateHistogram,
|
||||
isGroupByHistogram,
|
||||
getDataFramePreviewRequest,
|
||||
isKibanaContext,
|
||||
KibanaContext,
|
||||
KibanaContextValue,
|
||||
PivotAggDict,
|
||||
PivotAggsConfig,
|
||||
PivotAggsConfigDict,
|
||||
PivotGroupByDict,
|
||||
PivotGroupByConfig,
|
||||
PivotGroupByConfigDict,
|
||||
PivotSupportedGroupByAggs,
|
||||
PIVOT_SUPPORTED_AGGS,
|
||||
SavedSearchQuery,
|
||||
} from '../../common';
|
||||
|
||||
|
@ -48,6 +59,7 @@ import { getPivotDropdownOptions } from './common';
|
|||
export interface DefinePivotExposedState {
|
||||
aggList: PivotAggsConfigDict;
|
||||
groupByList: PivotGroupByConfigDict;
|
||||
isAdvancedEditorEnabled: boolean;
|
||||
search: string | SavedSearchQuery;
|
||||
valid: boolean;
|
||||
}
|
||||
|
@ -59,6 +71,7 @@ export function getDefaultPivotState(kibanaContext: KibanaContextValue): DefineP
|
|||
return {
|
||||
aggList: {} as PivotAggsConfigDict,
|
||||
groupByList: {} as PivotGroupByConfigDict,
|
||||
isAdvancedEditorEnabled: false,
|
||||
search:
|
||||
kibanaContext.currentSavedSearch.id !== undefined
|
||||
? kibanaContext.combinedQuery
|
||||
|
@ -268,21 +281,121 @@ export const DefinePivotForm: SFC<Props> = React.memo(({ overrides = {}, onChang
|
|||
const pivotGroupByArr = dictionaryToArray(groupByList);
|
||||
const pivotQuery = getPivotQuery(search);
|
||||
|
||||
// Advanced editor state
|
||||
const [isAdvancedEditorSwitchModalVisible, setAdvancedEditorSwitchModalVisible] = useState(false);
|
||||
const [isAdvancedEditorApplyButtonEnabled, setAdvancedEditorApplyButtonEnabled] = useState(false);
|
||||
const [isAdvancedEditorEnabled, setAdvancedEditorEnabled] = useState(
|
||||
defaults.isAdvancedEditorEnabled
|
||||
);
|
||||
|
||||
const previewRequest = getDataFramePreviewRequest(
|
||||
indexPattern.title,
|
||||
pivotQuery,
|
||||
pivotGroupByArr,
|
||||
pivotAggsArr
|
||||
);
|
||||
const stringifiedPivotConfig = JSON.stringify(previewRequest.pivot, null, 2);
|
||||
const [advancedEditorConfigLastApplied, setAdvancedEditorConfigLastApplied] = useState(
|
||||
stringifiedPivotConfig
|
||||
);
|
||||
const [advancedEditorConfig, setAdvancedEditorConfig] = useState(stringifiedPivotConfig);
|
||||
|
||||
const applyAdvancedEditorChanges = () => {
|
||||
const pivotConfig = JSON.parse(advancedEditorConfig);
|
||||
|
||||
const newGroupByList: PivotGroupByConfigDict = {};
|
||||
if (pivotConfig !== undefined && pivotConfig.group_by !== undefined) {
|
||||
Object.entries(pivotConfig.group_by).forEach(d => {
|
||||
const aggName = d[0];
|
||||
const aggConfig = d[1] as PivotGroupByDict;
|
||||
const aggConfigKeys = Object.keys(aggConfig);
|
||||
const agg = aggConfigKeys[0] as PivotSupportedGroupByAggs;
|
||||
newGroupByList[aggName] = {
|
||||
agg,
|
||||
aggName,
|
||||
dropDownName: '',
|
||||
...aggConfig[agg],
|
||||
};
|
||||
});
|
||||
}
|
||||
setGroupByList(newGroupByList);
|
||||
|
||||
const newAggList: PivotAggsConfigDict = {};
|
||||
if (pivotConfig !== undefined && pivotConfig.aggregations !== undefined) {
|
||||
Object.entries(pivotConfig.aggregations).forEach(d => {
|
||||
const aggName = d[0];
|
||||
const aggConfig = d[1] as PivotAggDict;
|
||||
const aggConfigKeys = Object.keys(aggConfig);
|
||||
const agg = aggConfigKeys[0] as PIVOT_SUPPORTED_AGGS;
|
||||
newAggList[aggName] = {
|
||||
agg,
|
||||
aggName,
|
||||
dropDownName: '',
|
||||
...aggConfig[agg],
|
||||
};
|
||||
});
|
||||
}
|
||||
setAggList(newAggList);
|
||||
|
||||
const prettyPivotConfig = JSON.stringify(pivotConfig, null, 2);
|
||||
setAdvancedEditorConfig(prettyPivotConfig);
|
||||
setAdvancedEditorConfigLastApplied(prettyPivotConfig);
|
||||
setAdvancedEditorApplyButtonEnabled(false);
|
||||
};
|
||||
|
||||
const toggleAdvancedEditor = () => {
|
||||
setAdvancedEditorConfig(advancedEditorConfig);
|
||||
setAdvancedEditorEnabled(!isAdvancedEditorEnabled);
|
||||
setAdvancedEditorApplyButtonEnabled(false);
|
||||
if (isAdvancedEditorEnabled === false) {
|
||||
setAdvancedEditorConfigLastApplied(advancedEditorConfig);
|
||||
}
|
||||
};
|
||||
|
||||
// metadata.branch corresponds to the version used in documentation links.
|
||||
const docsUrl = `https://www.elastic.co/guide/en/elasticsearch/reference/${
|
||||
metadata.branch
|
||||
}/data-frame-transform-pivot.html`;
|
||||
const advancedEditorHelpText = (
|
||||
<Fragment>
|
||||
{i18n.translate('xpack.ml.dataframe.definePivotForm.advancedEditorHelpText', {
|
||||
defaultMessage:
|
||||
'The advanced editor allows you to edit the pivot configuration of the data frame transform.',
|
||||
})}{' '}
|
||||
<EuiLink href={docsUrl} target="_blank">
|
||||
{i18n.translate('xpack.ml.dataframe.definePivotForm.advancedEditorHelpTextLink', {
|
||||
defaultMessage: 'Learn more about available options.',
|
||||
})}
|
||||
</EuiLink>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const valid = pivotGroupByArr.length > 0 && pivotAggsArr.length > 0;
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
onChange({ aggList, groupByList, search, valid });
|
||||
const previewRequestUpdate = getDataFramePreviewRequest(
|
||||
indexPattern.title,
|
||||
pivotQuery,
|
||||
pivotGroupByArr,
|
||||
pivotAggsArr
|
||||
);
|
||||
|
||||
const stringifiedPivotConfigUpdate = JSON.stringify(previewRequestUpdate.pivot, null, 2);
|
||||
setAdvancedEditorConfig(stringifiedPivotConfigUpdate);
|
||||
|
||||
onChange({
|
||||
aggList,
|
||||
groupByList,
|
||||
isAdvancedEditorEnabled,
|
||||
search,
|
||||
valid,
|
||||
});
|
||||
},
|
||||
[
|
||||
pivotAggsArr.map(d => `${d.agg} ${d.field} ${d.aggName}`).join(' '),
|
||||
pivotGroupByArr
|
||||
.map(
|
||||
d =>
|
||||
`${d.agg} ${d.field} ${isGroupByHistogram(d) ? d.interval : ''} ${
|
||||
isGroupByDateHistogram(d) ? d.calendar_interval : ''
|
||||
} ${d.aggName}`
|
||||
)
|
||||
.join(' '),
|
||||
JSON.stringify(pivotAggsArr),
|
||||
JSON.stringify(pivotGroupByArr),
|
||||
isAdvancedEditorEnabled,
|
||||
search,
|
||||
valid,
|
||||
]
|
||||
|
@ -352,62 +465,196 @@ export const DefinePivotForm: SFC<Props> = React.memo(({ overrides = {}, onChang
|
|||
</EuiFormRow>
|
||||
)}
|
||||
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.ml.dataframe.definePivotForm.groupByLabel', {
|
||||
defaultMessage: 'Group by',
|
||||
})}
|
||||
>
|
||||
{!isAdvancedEditorEnabled && (
|
||||
<Fragment>
|
||||
<GroupByListForm
|
||||
list={groupByList}
|
||||
options={groupByOptionsData}
|
||||
onChange={updateGroupBy}
|
||||
deleteHandler={deleteGroupBy}
|
||||
/>
|
||||
<DropDown
|
||||
changeHandler={addGroupBy}
|
||||
options={groupByOptions}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.ml.dataframe.definePivotForm.groupByPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Add a group by field ...',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.ml.dataframe.definePivotForm.groupByLabel', {
|
||||
defaultMessage: 'Group by',
|
||||
})}
|
||||
>
|
||||
<Fragment>
|
||||
<GroupByListForm
|
||||
list={groupByList}
|
||||
options={groupByOptionsData}
|
||||
onChange={updateGroupBy}
|
||||
deleteHandler={deleteGroupBy}
|
||||
/>
|
||||
<DropDown
|
||||
changeHandler={addGroupBy}
|
||||
options={groupByOptions}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.ml.dataframe.definePivotForm.groupByPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Add a group by field ...',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.ml.dataframe.definePivotForm.aggregationsLabel', {
|
||||
defaultMessage: 'Aggregations',
|
||||
})}
|
||||
>
|
||||
<Fragment>
|
||||
<AggListForm
|
||||
list={aggList}
|
||||
options={aggOptionsData}
|
||||
onChange={updateAggregation}
|
||||
deleteHandler={deleteAggregation}
|
||||
/>
|
||||
<DropDown
|
||||
changeHandler={addAggregation}
|
||||
options={aggOptions}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.ml.dataframe.definePivotForm.aggregationsPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Add an aggregation ...',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.ml.dataframe.definePivotForm.aggregationsLabel', {
|
||||
defaultMessage: 'Aggregations',
|
||||
})}
|
||||
>
|
||||
<Fragment>
|
||||
<AggListForm
|
||||
list={aggList}
|
||||
options={aggOptionsData}
|
||||
onChange={updateAggregation}
|
||||
deleteHandler={deleteAggregation}
|
||||
/>
|
||||
<DropDown
|
||||
changeHandler={addAggregation}
|
||||
options={aggOptions}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.ml.dataframe.definePivotForm.aggregationsPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Add an aggregation ...',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
</EuiFormRow>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{isAdvancedEditorEnabled && (
|
||||
<Fragment>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.ml.dataframe.definePivotForm.advancedEditorLabel', {
|
||||
defaultMessage: 'Pivot configuration object',
|
||||
})}
|
||||
helpText={advancedEditorHelpText}
|
||||
>
|
||||
<EuiPanel grow={false} paddingSize="none">
|
||||
<EuiCodeEditor
|
||||
mode="json"
|
||||
width="100%"
|
||||
value={advancedEditorConfig}
|
||||
onChange={(d: string) => {
|
||||
setAdvancedEditorConfig(d);
|
||||
|
||||
// Disable the "Apply"-Button if the config hasn't changed.
|
||||
if (advancedEditorConfigLastApplied === d) {
|
||||
setAdvancedEditorApplyButtonEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to parse the string passed on from the editor.
|
||||
// If parsing fails, the "Apply"-Button will be disabled
|
||||
try {
|
||||
JSON.parse(d);
|
||||
setAdvancedEditorApplyButtonEnabled(true);
|
||||
} catch (e) {
|
||||
setAdvancedEditorApplyButtonEnabled(false);
|
||||
}
|
||||
}}
|
||||
setOptions={{
|
||||
fontSize: '12px',
|
||||
}}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.ml.dataframe.definePivotForm.advancedEditorAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Advanced editor',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFormRow>
|
||||
</Fragment>
|
||||
)}
|
||||
<EuiFormRow>
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<EuiSwitch
|
||||
label={i18n.translate(
|
||||
'xpack.ml.dataframe.definePivotForm.advancedEditorSwitchLabel',
|
||||
{
|
||||
defaultMessage: 'Advanced editor',
|
||||
}
|
||||
)}
|
||||
checked={isAdvancedEditorEnabled}
|
||||
onChange={() => {
|
||||
if (
|
||||
isAdvancedEditorEnabled &&
|
||||
(isAdvancedEditorApplyButtonEnabled ||
|
||||
advancedEditorConfig !== advancedEditorConfigLastApplied)
|
||||
) {
|
||||
setAdvancedEditorSwitchModalVisible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
toggleAdvancedEditor();
|
||||
}}
|
||||
/>
|
||||
{isAdvancedEditorSwitchModalVisible && (
|
||||
<EuiOverlayMask>
|
||||
<EuiConfirmModal
|
||||
title={i18n.translate(
|
||||
'xpack.ml.dataframe.definePivotForm.advancedEditorSwitchModalTitle',
|
||||
{
|
||||
defaultMessage: 'Unapplied changes',
|
||||
}
|
||||
)}
|
||||
onCancel={() => setAdvancedEditorSwitchModalVisible(false)}
|
||||
onConfirm={() => {
|
||||
setAdvancedEditorSwitchModalVisible(false);
|
||||
toggleAdvancedEditor();
|
||||
}}
|
||||
cancelButtonText={i18n.translate(
|
||||
'xpack.ml.dataframe.definePivotForm.advancedEditorSwitchModalCancelButtonText',
|
||||
{
|
||||
defaultMessage: 'Cancel',
|
||||
}
|
||||
)}
|
||||
confirmButtonText={i18n.translate(
|
||||
'xpack.ml.dataframe.definePivotForm.advancedEditorSwitchModalConfirmButtonText',
|
||||
{
|
||||
defaultMessage: 'Disable advanced editor',
|
||||
}
|
||||
)}
|
||||
buttonColor="danger"
|
||||
defaultFocusedButton="confirm"
|
||||
>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.ml.dataframe.definePivotForm.advancedEditorSwitchModalBodyText',
|
||||
{
|
||||
defaultMessage: `The changes in the advanced editor haven't been applied yet. By disabling the advanced editor you will lose your edits.`,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiConfirmModal>
|
||||
</EuiOverlayMask>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{isAdvancedEditorEnabled && (
|
||||
<EuiButton
|
||||
size="s"
|
||||
fill
|
||||
onClick={applyAdvancedEditorChanges}
|
||||
disabled={!isAdvancedEditorApplyButtonEnabled}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.ml.dataframe.definePivotForm.advancedEditorApplyButtonText',
|
||||
{
|
||||
defaultMessage: 'Apply changes',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
{!valid && (
|
||||
<EuiFormHelpText style={{ maxWidth: '320px' }}>
|
||||
{i18n.translate('xpack.ml.dataframe.definePivotForm.formHelp', {
|
||||
defaultMessage:
|
||||
'Data frame transforms are scalable and automated processes for pivoting. Choose at least one group-by and aggregation to get started.',
|
||||
})}
|
||||
</EuiFormHelpText>
|
||||
<Fragment>
|
||||
<EuiFormHelpText style={{ maxWidth: '320px' }}>
|
||||
{i18n.translate('xpack.ml.dataframe.definePivotForm.formHelp', {
|
||||
defaultMessage:
|
||||
'Data frame transforms are scalable and automated processes for pivoting. Choose at least one group-by and aggregation to get started.',
|
||||
})}
|
||||
</EuiFormHelpText>
|
||||
</Fragment>
|
||||
)}
|
||||
</EuiForm>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -47,6 +47,7 @@ describe('Data Frame: <DefinePivotSummary />', () => {
|
|||
const props: DefinePivotExposedState = {
|
||||
aggList: { 'the-agg-name': agg },
|
||||
groupByList: { 'the-group-by-name': groupBy },
|
||||
isAdvancedEditorEnabled: false,
|
||||
search: 'the-query',
|
||||
valid: true,
|
||||
};
|
||||
|
|
|
@ -15,8 +15,6 @@ import { Dictionary } from '../../../../common/types/common';
|
|||
import {
|
||||
DataFramePreviewRequest,
|
||||
getDataFramePreviewRequest,
|
||||
isGroupByDateHistogram,
|
||||
isGroupByHistogram,
|
||||
PivotAggsConfigDict,
|
||||
PivotGroupByConfigDict,
|
||||
PivotQuery,
|
||||
|
@ -75,19 +73,7 @@ export const usePivotPreviewData = (
|
|||
() => {
|
||||
getDataFramePreviewData();
|
||||
},
|
||||
[
|
||||
indexPattern.title,
|
||||
aggsArr.map(a => `${a.agg} ${a.field} ${a.aggName}`).join(' '),
|
||||
groupByArr
|
||||
.map(
|
||||
g =>
|
||||
`${g.agg} ${g.field} ${g.aggName} ${
|
||||
isGroupByDateHistogram(g) ? g.calendar_interval : ''
|
||||
} ${isGroupByHistogram(g) ? g.interval : ''}`
|
||||
)
|
||||
.join(' '),
|
||||
JSON.stringify(query),
|
||||
]
|
||||
[indexPattern.title, JSON.stringify(aggsArr), JSON.stringify(groupByArr), JSON.stringify(query)]
|
||||
);
|
||||
return { errorMessage, status, dataFramePreviewData, previewRequest };
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@ exports[`Data Frame: Group By <PopoverForm /> Minimal initialization 1`] = `
|
|||
error={false}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText=""
|
||||
isInvalid={false}
|
||||
label="Group by name"
|
||||
labelType="label"
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
isGroupByDateHistogram,
|
||||
isGroupByHistogram,
|
||||
PivotGroupByConfig,
|
||||
PivotGroupByConfigDict,
|
||||
PivotGroupByConfigWithUiSupportDict,
|
||||
} from '../../common';
|
||||
|
||||
import { PopoverForm } from './popover_form';
|
||||
|
@ -23,7 +23,7 @@ import { PopoverForm } from './popover_form';
|
|||
interface Props {
|
||||
item: PivotGroupByConfig;
|
||||
otherAggNames: AggName[];
|
||||
options: PivotGroupByConfigDict;
|
||||
options: PivotGroupByConfigWithUiSupportDict;
|
||||
deleteHandler(l: string): void;
|
||||
onChange(item: PivotGroupByConfig): void;
|
||||
}
|
||||
|
|
|
@ -8,13 +8,18 @@ import React, { Fragment } from 'react';
|
|||
|
||||
import { EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { AggName, PivotGroupByConfig, PivotGroupByConfigDict } from '../../common';
|
||||
import {
|
||||
AggName,
|
||||
PivotGroupByConfig,
|
||||
PivotGroupByConfigDict,
|
||||
PivotGroupByConfigWithUiSupportDict,
|
||||
} from '../../common';
|
||||
|
||||
import { GroupByLabelForm } from './group_by_label_form';
|
||||
|
||||
interface ListProps {
|
||||
list: PivotGroupByConfigDict;
|
||||
options: PivotGroupByConfigDict;
|
||||
options: PivotGroupByConfigWithUiSupportDict;
|
||||
deleteHandler(l: string): void;
|
||||
onChange(id: string, item: PivotGroupByConfig): void;
|
||||
}
|
||||
|
|
|
@ -8,19 +8,28 @@ import React, { Fragment, useState } from 'react';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiButton, EuiFieldText, EuiForm, EuiFormRow, EuiSelect } from '@elastic/eui';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCodeEditor,
|
||||
EuiFieldText,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiSelect,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { dictionaryToArray } from '../../../../common/types/common';
|
||||
|
||||
import {
|
||||
AggName,
|
||||
dateHistogramIntervalFormatRegex,
|
||||
getEsAggFromGroupByConfig,
|
||||
isGroupByDateHistogram,
|
||||
isGroupByHistogram,
|
||||
isPivotGroupByConfigWithUiSupport,
|
||||
histogramIntervalFormatRegex,
|
||||
isAggName,
|
||||
PivotGroupByConfig,
|
||||
PivotGroupByConfigDict,
|
||||
PivotGroupByConfigWithUiSupportDict,
|
||||
PivotSupportedGroupByAggs,
|
||||
PivotSupportedGroupByAggsWithInterval,
|
||||
PIVOT_SUPPORTED_GROUP_BY_AGGS,
|
||||
|
@ -78,7 +87,7 @@ function getDefaultInterval(defaultData: PivotGroupByConfig): string | undefined
|
|||
interface Props {
|
||||
defaultData: PivotGroupByConfig;
|
||||
otherAggNames: AggName[];
|
||||
options: PivotGroupByConfigDict;
|
||||
options: PivotGroupByConfigWithUiSupportDict;
|
||||
onChange(item: PivotGroupByConfig): void;
|
||||
}
|
||||
|
||||
|
@ -88,9 +97,13 @@ export const PopoverForm: React.SFC<Props> = ({
|
|||
onChange,
|
||||
options,
|
||||
}) => {
|
||||
const isUnsupportedAgg = !isPivotGroupByConfigWithUiSupport(defaultData);
|
||||
|
||||
const [agg, setAgg] = useState(defaultData.agg);
|
||||
const [aggName, setAggName] = useState(defaultData.aggName);
|
||||
const [field, setField] = useState(defaultData.field);
|
||||
const [field, setField] = useState(
|
||||
isPivotGroupByConfigWithUiSupport(defaultData) ? defaultData.field : ''
|
||||
);
|
||||
const [interval, setInterval] = useState(getDefaultInterval(defaultData));
|
||||
|
||||
function getUpdatedItem(): PivotGroupByConfig {
|
||||
|
@ -107,17 +120,22 @@ export const PopoverForm: React.SFC<Props> = ({
|
|||
return updatedItem as PivotGroupByConfig;
|
||||
}
|
||||
|
||||
const optionsArr = dictionaryToArray(options);
|
||||
const availableFields: SelectOption[] = optionsArr
|
||||
.filter(o => o.agg === defaultData.agg)
|
||||
.map(o => {
|
||||
return { text: o.field };
|
||||
});
|
||||
const availableAggs: SelectOption[] = optionsArr
|
||||
.filter(o => o.field === defaultData.field)
|
||||
.map(o => {
|
||||
return { text: o.agg };
|
||||
});
|
||||
const availableFields: SelectOption[] = [];
|
||||
const availableAggs: SelectOption[] = [];
|
||||
|
||||
if (!isUnsupportedAgg) {
|
||||
const optionsArr = dictionaryToArray(options);
|
||||
optionsArr
|
||||
.filter(o => o.agg === defaultData.agg)
|
||||
.forEach(o => {
|
||||
availableFields.push({ text: o.field });
|
||||
});
|
||||
optionsArr
|
||||
.filter(o => isPivotGroupByConfigWithUiSupport(defaultData) && o.field === defaultData.field)
|
||||
.forEach(o => {
|
||||
availableAggs.push({ text: o.agg });
|
||||
});
|
||||
}
|
||||
|
||||
let aggNameError = '';
|
||||
|
||||
|
@ -156,6 +174,14 @@ export const PopoverForm: React.SFC<Props> = ({
|
|||
<EuiFormRow
|
||||
error={!validAggName && [aggNameError]}
|
||||
isInvalid={!validAggName}
|
||||
helpText={
|
||||
isUnsupportedAgg
|
||||
? i18n.translate('xpack.ml.dataframe.groupBy.popoverForm.unsupportedGroupByHelpText', {
|
||||
defaultMessage:
|
||||
'Only the group_by name can be edited in this form. Please use the advanced editor to edit the other parts of the group_by configuration.',
|
||||
})
|
||||
: ''
|
||||
}
|
||||
label={i18n.translate('xpack.ml.dataframe.groupBy.popoverForm.nameLabel', {
|
||||
defaultMessage: 'Group by name',
|
||||
})}
|
||||
|
@ -232,6 +258,18 @@ export const PopoverForm: React.SFC<Props> = ({
|
|||
</Fragment>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
{isUnsupportedAgg && (
|
||||
<EuiCodeEditor
|
||||
mode="json"
|
||||
theme="github"
|
||||
width="100%"
|
||||
height="200px"
|
||||
value={JSON.stringify(getEsAggFromGroupByConfig(defaultData), null, 2)}
|
||||
setOptions={{ fontSize: '12px', showLineNumbers: false }}
|
||||
isReadOnly
|
||||
aria-label="Read only code editor"
|
||||
/>
|
||||
)}
|
||||
<EuiFormRow hasEmptyLabelSpace>
|
||||
<EuiButton isDisabled={!formValid} onClick={() => onChange(getUpdatedItem())}>
|
||||
{i18n.translate('xpack.ml.dataframe.groupBy.popoverForm.submitButtonLabel', {
|
||||
|
|
|
@ -42,6 +42,7 @@ enum WIZARD_STEPS {
|
|||
|
||||
interface DefinePivotStepProps {
|
||||
isCurrentStep: boolean;
|
||||
jobConfig: any;
|
||||
pivotState: DefinePivotExposedState;
|
||||
setCurrentStep: React.Dispatch<React.SetStateAction<WIZARD_STEPS>>;
|
||||
setPivot: React.Dispatch<React.SetStateAction<DefinePivotExposedState>>;
|
||||
|
@ -49,6 +50,7 @@ interface DefinePivotStepProps {
|
|||
|
||||
const DefinePivotStep: SFC<DefinePivotStepProps> = ({
|
||||
isCurrentStep,
|
||||
jobConfig,
|
||||
pivotState,
|
||||
setCurrentStep,
|
||||
setPivot,
|
||||
|
@ -60,7 +62,7 @@ const DefinePivotStep: SFC<DefinePivotStepProps> = ({
|
|||
<div ref={definePivotRef} />
|
||||
{isCurrentStep && (
|
||||
<Fragment>
|
||||
<DefinePivotForm onChange={setPivot} overrides={pivotState} />
|
||||
<DefinePivotForm onChange={setPivot} overrides={{ ...pivotState, jobConfig }} />
|
||||
<WizardNav
|
||||
next={() => setCurrentStep(WIZARD_STEPS.JOB_DETAILS)}
|
||||
nextActive={pivotState.valid}
|
||||
|
@ -97,6 +99,8 @@ export const Wizard: SFC = React.memo(() => {
|
|||
<JobDetailsSummary {...jobDetailsState} />
|
||||
);
|
||||
|
||||
const jobConfig = getDataFrameRequest(indexPattern.title, pivotState, jobDetailsState);
|
||||
|
||||
// The JOB_CREATE state
|
||||
const [jobCreateState, setJobCreate] = useState(getDefaultJobCreateState);
|
||||
|
||||
|
@ -105,7 +109,7 @@ export const Wizard: SFC = React.memo(() => {
|
|||
<JobCreateForm
|
||||
createIndexPattern={jobDetailsState.createIndexPattern}
|
||||
jobId={jobDetailsState.jobId}
|
||||
jobConfig={getDataFrameRequest(indexPattern.title, pivotState, jobDetailsState)}
|
||||
jobConfig={jobConfig}
|
||||
onChange={setJobCreate}
|
||||
overrides={jobCreateState}
|
||||
/>
|
||||
|
@ -133,6 +137,7 @@ export const Wizard: SFC = React.memo(() => {
|
|||
children: (
|
||||
<DefinePivotStep
|
||||
isCurrentStep={currentStep === WIZARD_STEPS.DEFINE_PIVOT}
|
||||
jobConfig={jobConfig}
|
||||
pivotState={pivotState}
|
||||
setCurrentStep={setCurrentStep}
|
||||
setPivot={setPivot}
|
||||
|
|
|
@ -16,7 +16,8 @@ import {
|
|||
RIGHT_ALIGNMENT,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { DataFrameJobListColumn, DataFrameJobListRow, JobId } from './common';
|
||||
import { JobId } from '../../../../common';
|
||||
import { DataFrameJobListColumn, DataFrameJobListRow } from './common';
|
||||
import { getActions } from './actions';
|
||||
|
||||
export const getColumns = (
|
||||
|
|
|
@ -6,14 +6,7 @@
|
|||
|
||||
import { Dictionary } from '../../../../../../common/types/common';
|
||||
|
||||
export type JobId = string;
|
||||
|
||||
export interface DataFrameJob {
|
||||
dest: string;
|
||||
id: JobId;
|
||||
source: string;
|
||||
sync?: object;
|
||||
}
|
||||
import { JobId, DataFrameTransformWithId } from '../../../../common';
|
||||
|
||||
export enum DATA_FRAME_RUNNING_STATE {
|
||||
STARTED = 'started',
|
||||
|
@ -54,7 +47,7 @@ export interface DataFrameJobListRow {
|
|||
id: JobId;
|
||||
state: DataFrameJobState;
|
||||
stats: DataFrameJobStats;
|
||||
config: DataFrameJob;
|
||||
config: DataFrameTransformWithId;
|
||||
}
|
||||
|
||||
// Used to pass on attribute names to table columns
|
||||
|
|
|
@ -14,14 +14,9 @@ import {
|
|||
SortDirection,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { moveToDataFrameWizard } from '../../../../common';
|
||||
import { JobId, moveToDataFrameWizard } from '../../../../common';
|
||||
|
||||
import {
|
||||
DataFrameJobListColumn,
|
||||
DataFrameJobListRow,
|
||||
ItemIdToExpandedRowMap,
|
||||
JobId,
|
||||
} from './common';
|
||||
import { DataFrameJobListColumn, DataFrameJobListRow, ItemIdToExpandedRowMap } from './common';
|
||||
import { getJobsFactory } from './job_service';
|
||||
import { getColumns } from './columns';
|
||||
import { ExpandedRow } from './expanded_row';
|
||||
|
|
|
@ -7,13 +7,8 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import { ml } from '../../../../../../services/ml_api_service';
|
||||
import {
|
||||
DataFrameJob,
|
||||
DataFrameJobListRow,
|
||||
DataFrameJobState,
|
||||
DataFrameJobStats,
|
||||
JobId,
|
||||
} from '../common';
|
||||
import { DataFrameTransformWithId, JobId } from '../../../../../common';
|
||||
import { DataFrameJobListRow, DataFrameJobState, DataFrameJobStats } from '../common';
|
||||
|
||||
interface DataFrameJobStateStats {
|
||||
id: JobId;
|
||||
|
@ -23,7 +18,7 @@ interface DataFrameJobStateStats {
|
|||
|
||||
interface GetDataFrameTransformsResponse {
|
||||
count: number;
|
||||
transforms: DataFrameJob[];
|
||||
transforms: DataFrameTransformWithId[];
|
||||
}
|
||||
|
||||
interface GetDataFrameTransformsStatsResponse {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue