From 74097ddec3a38608c424d95e73162b742fe3bea5 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 20 May 2019 10:01:20 +0300 Subject: [PATCH 01/15] Create IpRangeType and IpRanges controls --- .../ui/public/agg_types/buckets/ip_range.js | 5 +- .../controls/components/from_to_list.tsx | 125 ++++++++++++++++++ .../agg_types/controls/ip_range_type.tsx | 53 ++++++++ .../public/agg_types/controls/ip_ranges.tsx | 91 +++++++++++++ 4 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx create mode 100644 src/legacy/ui/public/agg_types/controls/ip_range_type.tsx create mode 100644 src/legacy/ui/public/agg_types/controls/ip_ranges.tsx diff --git a/src/legacy/ui/public/agg_types/buckets/ip_range.js b/src/legacy/ui/public/agg_types/buckets/ip_range.js index 621e52e1a99ec..afcc727d150df 100644 --- a/src/legacy/ui/public/agg_types/buckets/ip_range.js +++ b/src/legacy/ui/public/agg_types/buckets/ip_range.js @@ -23,6 +23,8 @@ import '../directives/validate_cidr_mask'; import { BucketAggType } from './_bucket_agg_type'; import { createFilterIpRange } from './create_filter/ip_range'; import ipRangesTemplate from '../controls/ip_ranges.html'; +import { IpRangeTypeParamEditor } from '../controls/ip_range_type'; +import { IpRangesParamEditor } from '../controls/ip_ranges'; import { i18n } from '@kbn/i18n'; export const ipRangeBucketAgg = new BucketAggType({ @@ -52,6 +54,7 @@ export const ipRangeBucketAgg = new BucketAggType({ filterFieldTypes: 'ip' }, { name: 'ipRangeType', + editorComponent: IpRangeTypeParamEditor, default: 'fromTo', write: _.noop }, { @@ -66,7 +69,7 @@ export const ipRangeBucketAgg = new BucketAggType({ { mask: '128.0.0.0/2' } ] }, - editor: ipRangesTemplate, + editorComponent: IpRangesParamEditor, write: function (aggConfig, output) { const ipRangeType = aggConfig.params.ipRangeType; let ranges = aggConfig.params.ranges[ipRangeType]; diff --git a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx new file mode 100644 index 0000000000000..dcb6fde469f5e --- /dev/null +++ b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx @@ -0,0 +1,125 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useState, useEffect } from 'react'; +import { + EuiButtonIcon, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + htmlIdGenerator, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export interface FromToObject { + from: string; + to: string; +} + +interface FromToModel extends FromToObject { + id: string; +} + +interface FromToListProps { + labelledbyId: string; + list: FromToObject[]; + onChange(list: FromToObject[]): void; +} + +const generateId = htmlIdGenerator(); + +function FromToList({ labelledbyId, list, onChange }: FromToListProps) { + const [models, setModels] = useState(list.map(item => ({ ...item, id: generateId() }))); + const deleteBtnAriaLabel = i18n.translate('common.ui.aggTypes.ipRanges.removeRangeAriaLabel', { + defaultMessage: 'Remove this range', + }); + + const onUpdate = (modelList: FromToModel[]) => { + setModels(modelList); + onChange(modelList.map(({ from, to }) => ({ from, to }))); + }; + + const onChangeValue = (modelName: 'from' | 'to', index: number, value: string) => { + models[index][modelName] = value; + onUpdate(models); + }; + const onDelete = (id: string) => { + const newArray = models.filter(model => model.id !== id); + onUpdate(newArray); + }; + + const getUpdatedModels = (objList: FromToObject[], modelList: FromToModel[]) => { + return objList.map((item, index) => { + const model = modelList[index] || { id: generateId() }; + return { + ...model, + ...item, + }; + }); + }; + + useEffect( + () => { + setModels(getUpdatedModels(list, models)); + }, + [list] + ); + + return ( + <> + {models.map((item, index) => ( + + + { + onChangeValue('from', index, ev.target.value); + }} + value={item.from} + // onBlur={onBlur} + /> + + + { + onChangeValue('to', index, ev.target.value); + }} + value={item.to} + // onBlur={onBlur} + /> + + + onDelete(item.id)} + /> + + + ))} + + ); +} + +export { FromToList }; diff --git a/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx b/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx new file mode 100644 index 0000000000000..41dd2240396ce --- /dev/null +++ b/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; + +import { EuiButton, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { AggParamEditorProps } from '../../vis/editors/default'; + +enum IpRangeTypes { + MASK = 'mask', + FROM_TO = 'fromTo', +} + +function IpRangeTypeParamEditor({ value, setValue }: AggParamEditorProps) { + const useFromToLabel = i18n.translate('common.ui.aggTypes.ipRanges.useFromToButtonLabel', { + defaultMessage: 'Use From/To', + }); + const useCidrMasksLabel = i18n.translate('common.ui.aggTypes.ipRanges.useCidrMasksButtonLabel', { + defaultMessage: 'Use CIDR Masks', + }); + + const onClick = () => { + setValue(value === IpRangeTypes.MASK ? IpRangeTypes.FROM_TO : IpRangeTypes.MASK); + }; + + return ( + <> + + {value === IpRangeTypes.MASK ? useFromToLabel : useCidrMasksLabel} + + + + ); +} + +export { IpRangeTypeParamEditor, IpRangeTypes }; diff --git a/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx b/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx new file mode 100644 index 0000000000000..a95de32abf5fc --- /dev/null +++ b/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSpacer, EuiButton } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { AggParamEditorProps } from 'ui/vis/editors/default'; +import { FromToList, FromToObject } from './components/from_to_list'; +import { IpRangeTypes } from './ip_range_type'; +interface IpRange { + fromTo: FromToObject[]; + mask: Array<{ mask: string }>; +} + +function IpRangesParamEditor({ agg, value, setValue, setValidity }: AggParamEditorProps) { + const isValid = true; + const labels = ( + + + + + + + + + ); + + useEffect( + () => { + setValidity(isValid); + + return () => setValidity(true); + }, + [isValid] + ); + + const handleChange = (modelName: IpRangeTypes, items: FromToObject[]) => { + setValue({ + ...value, + [modelName]: items, + }); + }; + + const onAdd = () => { + const type = agg.params.ipRangeType; + setValue({ ...value, [type]: [...value[type], {}] }); + }; + + return ( + + <> + {labels} + {agg.params.ipRangeType === IpRangeTypes.FROM_TO ? ( + handleChange(IpRangeTypes.FROM_TO, items)} + /> + ) : null} + + + + + + + + + ); +} + +export { IpRangesParamEditor }; From 0f2abdc8c0a5d9c22238e91a2bee829cd41d6861 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 20 May 2019 15:40:29 +0300 Subject: [PATCH 02/15] Add validation --- .../controls/components/from_to_list.tsx | 149 +++++++++++----- .../controls/components/mask_list.tsx | 162 ++++++++++++++++++ .../public/agg_types/controls/ip_ranges.tsx | 45 +++-- 3 files changed, 295 insertions(+), 61 deletions(-) create mode 100644 src/legacy/ui/public/agg_types/controls/components/mask_list.tsx diff --git a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx index dcb6fde469f5e..22f9619e01b7c 100644 --- a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx @@ -17,46 +17,65 @@ * under the License. */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, Fragment } from 'react'; import { EuiButtonIcon, + EuiFormLabel, EuiFieldText, EuiFlexGroup, EuiFlexItem, + EuiSpacer, htmlIdGenerator, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import Ipv4Address from '../../../utils/ipv4_address'; export interface FromToObject { from: string; to: string; } -interface FromToModel extends FromToObject { +interface FromToItem { + value: string; + isInvalid: boolean; +} + +interface FromToModel { id: string; + from: FromToItem; + to: FromToItem; } interface FromToListProps { labelledbyId: string; list: FromToObject[]; + showValidation: boolean; + onBlur(): void; onChange(list: FromToObject[]): void; } const generateId = htmlIdGenerator(); -function FromToList({ labelledbyId, list, onChange }: FromToListProps) { - const [models, setModels] = useState(list.map(item => ({ ...item, id: generateId() }))); +function FromToList({ labelledbyId, list, showValidation, onBlur, onChange }: FromToListProps) { + const [models, setModels] = useState( + list.map(item => ({ + id: generateId(), + from: { value: item.from, isInvalid: false }, + to: { value: item.to, isInvalid: false }, + })) + ); const deleteBtnAriaLabel = i18n.translate('common.ui.aggTypes.ipRanges.removeRangeAriaLabel', { defaultMessage: 'Remove this range', }); const onUpdate = (modelList: FromToModel[]) => { setModels(modelList); - onChange(modelList.map(({ from, to }) => ({ from, to }))); + onChange(modelList.map(({ from, to }) => ({ from: from.value, to: to.value }))); }; const onChangeValue = (modelName: 'from' | 'to', index: number, value: string) => { - models[index][modelName] = value; + models[index][modelName].value = value; onUpdate(models); }; const onDelete = (id: string) => { @@ -66,14 +85,40 @@ function FromToList({ labelledbyId, list, onChange }: FromToListProps) { const getUpdatedModels = (objList: FromToObject[], modelList: FromToModel[]) => { return objList.map((item, index) => { - const model = modelList[index] || { id: generateId() }; + const model = modelList[index] || { + id: generateId(), + from: { value: '', isInvalid: false }, + to: { value: '', isInvalid: false }, + }; + const from = validateValue(model.from.value); + const to = validateValue(model.to.value); return { - ...model, - ...item, + id: model.id, + from, + to, }; }); }; + const validateValue = (ipAddress: string) => { + const result = { + value: ipAddress, + isInvalid: false, + }; + if (!ipAddress) { + result.isInvalid = true; + return result; + } + try { + new Ipv4Address(ipAddress); + result.isInvalid = false; + return result; + } catch (e) { + result.isInvalid = true; + return result; + } + }; + useEffect( () => { setModels(getUpdatedModels(list, models)); @@ -81,42 +126,62 @@ function FromToList({ labelledbyId, list, onChange }: FromToListProps) { [list] ); + if (!list || !list.length) { + return null; + } + return ( <> + + + + + + + + + + + + + {models.map((item, index) => ( - - - { - onChangeValue('from', index, ev.target.value); - }} - value={item.from} - // onBlur={onBlur} - /> - - - { - onChangeValue('to', index, ev.target.value); - }} - value={item.to} - // onBlur={onBlur} - /> - - - onDelete(item.id)} - /> - - + + + + { + onChangeValue('from', index, ev.target.value); + }} + value={item.from.value} + onBlur={onBlur} + /> + + + { + onChangeValue('to', index, ev.target.value); + }} + value={item.to.value} + onBlur={onBlur} + /> + + + onDelete(item.id)} + /> + + + + ))} ); diff --git a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx new file mode 100644 index 0000000000000..92014e10ccaab --- /dev/null +++ b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx @@ -0,0 +1,162 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useState, useEffect, Fragment } from 'react'; +import { + EuiButtonIcon, + EuiFieldText, + EuiFormLabel, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + htmlIdGenerator, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { CidrMask } from '../../../utils/cidr_mask'; + +export interface MaskObject { + mask: string; +} + +interface MaskModel extends MaskObject { + id: string; + isInvalid: boolean; +} + +interface MaskListProps { + labelledbyId: string; + list: MaskObject[]; + showValidation: boolean; + onBlur(): void; + onChange(list: MaskObject[]): void; +} + +const generateId = htmlIdGenerator(); + +function MaskList({ labelledbyId, list, showValidation, onBlur, onChange }: MaskListProps) { + const [models, setModels] = useState( + list.map(item => ({ ...item, id: generateId(), isInvalid: false })) + ); + const deleteBtnAriaLabel = i18n.translate( + 'common.ui.aggTypes.ipRanges.removeCidrMaskButtonAriaLabel', + { + defaultMessage: 'Remove this CIDR mask', + } + ); + + const onUpdate = (modelList: MaskModel[]) => { + setModels(modelList); + onChange(modelList.map(({ mask }) => ({ mask }))); + }; + + const onChangeValue = (index: number, value: string) => { + models[index].mask = value; + onUpdate(models); + }; + const onDelete = (id: string) => { + const newArray = models.filter(model => model.id !== id); + onUpdate(newArray); + }; + + const validateValue = (mask: string) => { + const result = { + value: mask, + isInvalid: false, + }; + if (!mask) { + result.isInvalid = true; + return result; + } + try { + new CidrMask(mask); + result.isInvalid = false; + return result; + } catch (e) { + result.isInvalid = true; + return result; + } + }; + + const getUpdatedModels = (objList: MaskObject[], modelList: MaskModel[]) => { + return objList.map((item, index) => { + const model = modelList[index] || { id: generateId(), mask: '', isInvalid: false }; + const { value, isInvalid } = validateValue(model.mask); + return { + ...model, + value, + isInvalid, + }; + }); + }; + + useEffect( + () => { + setModels(getUpdatedModels(list, models)); + }, + [list] + ); + + if (!list || !list.length) { + return null; + } + + return ( + <> + + + + + + + {models.map((item, index) => ( + + + + { + onChangeValue(index, ev.target.value); + }} + value={item.mask} + onBlur={onBlur} + /> + + + onDelete(item.id)} + /> + + + + + ))} + + ); +} + +export { MaskList }; diff --git a/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx b/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx index a95de32abf5fc..922e2e804d271 100644 --- a/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx +++ b/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx @@ -18,29 +18,27 @@ */ import React, { useEffect } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSpacer, EuiButton } from '@elastic/eui'; +import { EuiFlexItem, EuiFormRow, EuiSpacer, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { AggParamEditorProps } from 'ui/vis/editors/default'; import { FromToList, FromToObject } from './components/from_to_list'; +import { MaskList, MaskObject } from './components/mask_list'; import { IpRangeTypes } from './ip_range_type'; interface IpRange { fromTo: FromToObject[]; - mask: Array<{ mask: string }>; + mask: MaskObject[]; } -function IpRangesParamEditor({ agg, value, setValue, setValidity }: AggParamEditorProps) { +function IpRangesParamEditor({ + agg, + value, + setTouched, + setValue, + setValidity, + showValidation, +}: AggParamEditorProps) { const isValid = true; - const labels = ( - - - - - - - - - ); useEffect( () => { @@ -51,7 +49,7 @@ function IpRangesParamEditor({ agg, value, setValue, setValidity }: AggParamEdit [isValid] ); - const handleChange = (modelName: IpRangeTypes, items: FromToObject[]) => { + const handleChange = (modelName: IpRangeTypes, items: Array) => { setValue({ ...value, [modelName]: items, @@ -59,21 +57,30 @@ function IpRangesParamEditor({ agg, value, setValue, setValidity }: AggParamEdit }; const onAdd = () => { - const type = agg.params.ipRangeType; + const type = agg.params.ipRangeType as IpRangeTypes; setValue({ ...value, [type]: [...value[type], {}] }); }; return ( <> - {labels} - {agg.params.ipRangeType === IpRangeTypes.FROM_TO ? ( + {agg.params.ipRangeType === IpRangeTypes.MASK ? ( + handleChange(IpRangeTypes.MASK, items)} + /> + ) : ( handleChange(IpRangeTypes.FROM_TO, items)} /> - ) : null} + )} From b84928d7542b2f7c02f5ee39da4bfe58a3fe6910 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 21 May 2019 16:41:56 +0300 Subject: [PATCH 03/15] Refactoring --- src/legacy/ui/public/agg_types/buckets/ip_range.js | 1 - .../agg_types/controls/components/from_to_list.tsx | 14 ++++++++++---- .../agg_types/controls/components/mask_list.tsx | 13 ++++++++++++- .../ui/public/agg_types/controls/ip_ranges.tsx | 6 +++--- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/legacy/ui/public/agg_types/buckets/ip_range.js b/src/legacy/ui/public/agg_types/buckets/ip_range.js index afcc727d150df..dcf0a80e0c1ad 100644 --- a/src/legacy/ui/public/agg_types/buckets/ip_range.js +++ b/src/legacy/ui/public/agg_types/buckets/ip_range.js @@ -22,7 +22,6 @@ import '../directives/validate_ip'; import '../directives/validate_cidr_mask'; import { BucketAggType } from './_bucket_agg_type'; import { createFilterIpRange } from './create_filter/ip_range'; -import ipRangesTemplate from '../controls/ip_ranges.html'; import { IpRangeTypeParamEditor } from '../controls/ip_range_type'; import { IpRangesParamEditor } from '../controls/ip_ranges'; import { i18n } from '@kbn/i18n'; diff --git a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx index 22f9619e01b7c..d0f91d441155e 100644 --- a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx @@ -65,9 +65,6 @@ function FromToList({ labelledbyId, list, showValidation, onBlur, onChange }: Fr to: { value: item.to, isInvalid: false }, })) ); - const deleteBtnAriaLabel = i18n.translate('common.ui.aggTypes.ipRanges.removeRangeAriaLabel', { - defaultMessage: 'Remove this range', - }); const onUpdate = (modelList: FromToModel[]) => { setModels(modelList); @@ -151,6 +148,7 @@ function FromToList({ labelledbyId, list, showValidation, onBlur, onChange }: Fr { onChangeValue('from', index, ev.target.value); @@ -162,6 +160,7 @@ function FromToList({ labelledbyId, list, showValidation, onBlur, onChange }: Fr { onChangeValue('to', index, ev.target.value); @@ -172,7 +171,14 @@ function FromToList({ labelledbyId, list, showValidation, onBlur, onChange }: Fr { onChangeValue(index, ev.target.value); @@ -144,7 +145,17 @@ function MaskList({ labelledbyId, list, showValidation, onBlur, onChange }: Mask - + - + From 8c130cdd895da261a65443e4310635acdb21a94f Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 22 May 2019 13:55:46 +0300 Subject: [PATCH 04/15] Add behavior when discarding changes --- .../controls/components/from_to_list.tsx | 101 +++++++++++++----- .../controls/components/mask_list.tsx | 82 ++++++++++---- .../public/agg_types/controls/ip_ranges.tsx | 17 +-- 3 files changed, 141 insertions(+), 59 deletions(-) diff --git a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx index d0f91d441155e..66b26334bf1b6 100644 --- a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx @@ -37,6 +37,7 @@ export interface FromToObject { } interface FromToItem { + model: string; value: string; isInvalid: boolean; } @@ -53,53 +54,85 @@ interface FromToListProps { showValidation: boolean; onBlur(): void; onChange(list: FromToObject[]): void; + setValidity(isValid: boolean): void; } const generateId = htmlIdGenerator(); -function FromToList({ labelledbyId, list, showValidation, onBlur, onChange }: FromToListProps) { - const [models, setModels] = useState( - list.map(item => ({ - id: generateId(), - from: { value: item.from, isInvalid: false }, - to: { value: item.to, isInvalid: false }, - })) +function FromToList({ + labelledbyId, + list, + showValidation, + onBlur, + onChange, + setValidity, +}: FromToListProps) { + const [ranges, setRanges] = useState( + list.length + ? list.map(item => ({ + id: generateId(), + from: { value: item.from, model: item.from, isInvalid: false }, + to: { value: item.to, model: item.to, isInvalid: false }, + })) + : [ + { + id: generateId(), + from: { value: '0.0.0.0', model: '0.0.0.0', isInvalid: false }, + to: { value: '255.255.255.255', model: '255.255.255.255', isInvalid: false }, + }, + ] ); const onUpdate = (modelList: FromToModel[]) => { - setModels(modelList); - onChange(modelList.map(({ from, to }) => ({ from: from.value, to: to.value }))); + setRanges(modelList); + onChange(modelList.map(({ from, to }) => ({ from: from.model, to: to.model }))); }; const onChangeValue = (modelName: 'from' | 'to', index: number, value: string) => { - models[index][modelName].value = value; - onUpdate(models); + const range = ranges[index][modelName]; + const { model, isInvalid } = validateValue(value); + range.value = value; + range.model = model; + range.isInvalid = isInvalid; + onUpdate(ranges); }; const onDelete = (id: string) => { - const newArray = models.filter(model => model.id !== id); + const newArray = ranges.filter(model => model.id !== id); onUpdate(newArray); }; - const getUpdatedModels = (objList: FromToObject[], modelList: FromToModel[]) => { + const getUpdatedModels = (objList: FromToObject[], rangeList: FromToModel[]) => { + if (!objList.length) { + return rangeList; + } return objList.map((item, index) => { - const model = modelList[index] || { + const range = rangeList[index] || { id: generateId(), - from: { value: '', isInvalid: false }, - to: { value: '', isInvalid: false }, + from: { value: item.from, model: item.from, isInvalid: false }, + to: { value: item.to, model: item.to, isInvalid: false }, }; - const from = validateValue(model.from.value); - const to = validateValue(model.to.value); + + validateItem(item.from, range.from); + validateItem(item.to, range.to); + return { - id: model.id, - from, - to, + ...range, }; }); }; + const validateItem = (value: string, modelObj: FromToItem) => { + const { model, isInvalid } = validateValue(value); + if (value !== modelObj.model) { + modelObj.value = model; + } + modelObj.model = model; + modelObj.isInvalid = isInvalid; + }; + const validateValue = (ipAddress: string) => { const result = { - value: ipAddress, + model: ipAddress, isInvalid: false, }; if (!ipAddress) { @@ -107,7 +140,7 @@ function FromToList({ labelledbyId, list, showValidation, onBlur, onChange }: Fr return result; } try { - new Ipv4Address(ipAddress); + result.model = new Ipv4Address(ipAddress).toString(); result.isInvalid = false; return result; } catch (e) { @@ -116,13 +149,29 @@ function FromToList({ labelledbyId, list, showValidation, onBlur, onChange }: Fr } }; + const hasInvalidValues = (modelList: FromToModel[]) => { + return !!modelList.find(({ from, to }) => from.isInvalid || to.isInvalid); + }; + useEffect( () => { - setModels(getUpdatedModels(list, models)); + setRanges(getUpdatedModels(list, ranges)); }, [list] ); + useEffect( + () => { + setValidity(!hasInvalidValues(ranges)); + }, + [ranges] + ); + + // resposible for setting up an initial value ([from: '0.0.0.0', to: '255.255.255.255' ]) when there is no default value + useEffect(() => { + onChange(ranges.map(({ from, to }) => ({ from: from.model, to: to.model }))); + }, []); + if (!list || !list.length) { return null; } @@ -142,7 +191,7 @@ function FromToList({ labelledbyId, list, showValidation, onBlur, onChange }: Fr - {models.map((item, index) => ( + {ranges.map((item, index) => ( @@ -179,7 +228,7 @@ function FromToList({ labelledbyId, list, showValidation, onBlur, onChange }: Fr defaultMessage: 'Remove the range of {from} to {to}', values: { from: item.from.value, to: item.to.value }, })} - disabled={models.length === 1} + disabled={ranges.length === 1} color="danger" iconType="trash" onClick={() => onDelete(item.id)} diff --git a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx index fb8d6016e3199..afd3f248ccf3d 100644 --- a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx @@ -35,8 +35,10 @@ export interface MaskObject { mask: string; } -interface MaskModel extends MaskObject { +interface MaskModel { id: string; + model: string; + value: string; isInvalid: boolean; } @@ -46,28 +48,41 @@ interface MaskListProps { showValidation: boolean; onBlur(): void; onChange(list: MaskObject[]): void; + setValidity(isValid: boolean): void; } const generateId = htmlIdGenerator(); -function MaskList({ labelledbyId, list, showValidation, onBlur, onChange }: MaskListProps) { +function MaskList({ + labelledbyId, + list, + showValidation, + onBlur, + onChange, + setValidity, +}: MaskListProps) { const [models, setModels] = useState( - list.map(item => ({ ...item, id: generateId(), isInvalid: false })) - ); - const deleteBtnAriaLabel = i18n.translate( - 'common.ui.aggTypes.ipRanges.removeCidrMaskButtonAriaLabel', - { - defaultMessage: 'Remove this CIDR mask', - } + list.length + ? list.map(item => ({ + model: item.mask, + value: item.mask, + id: generateId(), + isInvalid: false, + })) + : [{ id: generateId(), model: '0.0.0.0/1', value: '0.0.0.0/1', isInvalid: false }] ); const onUpdate = (modelList: MaskModel[]) => { setModels(modelList); - onChange(modelList.map(({ mask }) => ({ mask }))); + onChange(modelList.map(({ model }) => ({ mask: model }))); }; const onChangeValue = (index: number, value: string) => { - models[index].mask = value; + const mask = models[index]; + const { model, isInvalid } = validateValue(value); + mask.value = value; + mask.model = model; + mask.isInvalid = isInvalid; onUpdate(models); }; const onDelete = (id: string) => { @@ -77,7 +92,7 @@ function MaskList({ labelledbyId, list, showValidation, onBlur, onChange }: Mask const validateValue = (mask: string) => { const result = { - value: mask, + model: mask, isInvalid: false, }; if (!mask) { @@ -85,7 +100,7 @@ function MaskList({ labelledbyId, list, showValidation, onBlur, onChange }: Mask return result; } try { - new CidrMask(mask); + result.model = new CidrMask(mask).toString(); result.isInvalid = false; return result; } catch (e) { @@ -95,17 +110,32 @@ function MaskList({ labelledbyId, list, showValidation, onBlur, onChange }: Mask }; const getUpdatedModels = (objList: MaskObject[], modelList: MaskModel[]) => { + if (!objList.length) { + return modelList; + } return objList.map((item, index) => { - const model = modelList[index] || { id: generateId(), mask: '', isInvalid: false }; - const { value, isInvalid } = validateValue(model.mask); + const maskModel = modelList[index] || { + id: generateId(), + value: item.mask, + model: item.mask, + isInvalid: false, + }; + const { model, isInvalid } = validateValue(item.mask); + if (item.mask !== maskModel.model) { + maskModel.value = model; + } return { - ...model, - value, + ...maskModel, + model, isInvalid, }; }); }; + const hasInvalidValues = (modelList: MaskModel[]) => { + return !!modelList.find(({ isInvalid }) => isInvalid); + }; + useEffect( () => { setModels(getUpdatedModels(list, models)); @@ -113,6 +143,18 @@ function MaskList({ labelledbyId, list, showValidation, onBlur, onChange }: Mask [list] ); + useEffect( + () => { + setValidity(!hasInvalidValues(models)); + }, + [models] + ); + + // resposible for setting up an initial value ([mask: '0.0.0.0/1']) when there is no default value + useEffect(() => { + onChange(models.map(({ model }) => ({ mask: model }))); + }, []); + if (!list || !list.length) { return null; } @@ -139,7 +181,7 @@ function MaskList({ labelledbyId, list, showValidation, onBlur, onChange }: Mask onChange={ev => { onChangeValue(index, ev.target.value); }} - value={item.mask} + value={item.value} onBlur={onBlur} /> @@ -149,12 +191,12 @@ function MaskList({ labelledbyId, list, showValidation, onBlur, onChange }: Mask 'common.ui.aggTypes.ipRanges.removeCidrMaskButtonAriaLabel', { defaultMessage: 'Remove the CIDR mask value of {mask}', - values: { mask: item.mask }, + values: { mask: item.value }, } )} title={i18n.translate('common.ui.aggTypes.ipRanges.removeCidrMaskButtonTitle', { defaultMessage: 'Remove the CIDR mask value of {mask}', - values: { mask: item.mask }, + values: { mask: item.value }, })} disabled={models.length === 1} color="danger" diff --git a/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx b/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx index 3b8a59754cc9b..c283a8ff97e1c 100644 --- a/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx +++ b/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { EuiFlexItem, EuiFormRow, EuiSpacer, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -32,23 +32,12 @@ interface IpRange { function IpRangesParamEditor({ agg, - value, + value = { fromTo: [] as FromToObject[], mask: [] as MaskObject[] }, setTouched, setValue, setValidity, showValidation, }: AggParamEditorProps) { - const isValid = true; - - useEffect( - () => { - setValidity(isValid); - - return () => setValidity(true); - }, - [isValid] - ); - const handleChange = (modelName: IpRangeTypes, items: Array) => { setValue({ ...value, @@ -71,6 +60,7 @@ function IpRangesParamEditor({ showValidation={showValidation} onBlur={setTouched} onChange={items => handleChange(IpRangeTypes.MASK, items)} + setValidity={setValidity} /> ) : ( handleChange(IpRangeTypes.FROM_TO, items)} + setValidity={setValidity} /> )} From 5faa3d1572fc7a056d2d631080433087aa7d0d60 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 22 May 2019 17:20:06 +0300 Subject: [PATCH 05/15] Refactoring: create common input list --- .../controls/components/from_to_list.tsx | 257 +++++------------- .../controls/components/input_list.tsx | 204 ++++++++++++++ .../controls/components/mask_list.tsx | 216 ++++----------- 3 files changed, 320 insertions(+), 357 deletions(-) create mode 100644 src/legacy/ui/public/agg_types/controls/components/input_list.tsx diff --git a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx index 66b26334bf1b6..cea82e08406af 100644 --- a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx @@ -17,24 +17,17 @@ * under the License. */ -import React, { useState, useEffect, Fragment } from 'react'; -import { - EuiButtonIcon, - EuiFormLabel, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - htmlIdGenerator, -} from '@elastic/eui'; +import React from 'react'; +import { EuiFormLabel, EuiFieldText, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import Ipv4Address from '../../../utils/ipv4_address'; +import { InputList, InputListConfig, InputModel, InputObject } from './input_list'; -export interface FromToObject { +export type FromToObject = InputObject & { from: string; to: string; -} +}; interface FromToItem { model: string; @@ -42,11 +35,10 @@ interface FromToItem { isInvalid: boolean; } -interface FromToModel { - id: string; +type FromToModel = InputModel & { from: FromToItem; to: FromToItem; -} +}; interface FromToListProps { labelledbyId: string; @@ -57,189 +49,72 @@ interface FromToListProps { setValidity(isValid: boolean): void; } -const generateId = htmlIdGenerator(); - -function FromToList({ - labelledbyId, - list, - showValidation, - onBlur, - onChange, - setValidity, -}: FromToListProps) { - const [ranges, setRanges] = useState( - list.length - ? list.map(item => ({ - id: generateId(), - from: { value: item.from, model: item.from, isInvalid: false }, - to: { value: item.to, model: item.to, isInvalid: false }, - })) - : [ - { - id: generateId(), - from: { value: '0.0.0.0', model: '0.0.0.0', isInvalid: false }, - to: { value: '255.255.255.255', model: '255.255.255.255', isInvalid: false }, - }, - ] - ); - - const onUpdate = (modelList: FromToModel[]) => { - setRanges(modelList); - onChange(modelList.map(({ from, to }) => ({ from: from.model, to: to.model }))); - }; - - const onChangeValue = (modelName: 'from' | 'to', index: number, value: string) => { - const range = ranges[index][modelName]; - const { model, isInvalid } = validateValue(value); - range.value = value; - range.model = model; - range.isInvalid = isInvalid; - onUpdate(ranges); - }; - const onDelete = (id: string) => { - const newArray = ranges.filter(model => model.id !== id); - onUpdate(newArray); - }; - - const getUpdatedModels = (objList: FromToObject[], rangeList: FromToModel[]) => { - if (!objList.length) { - return rangeList; - } - return objList.map((item, index) => { - const range = rangeList[index] || { - id: generateId(), - from: { value: item.from, model: item.from, isInvalid: false }, - to: { value: item.to, model: item.to, isInvalid: false }, - }; - - validateItem(item.from, range.from); - validateItem(item.to, range.to); - - return { - ...range, - }; - }); - }; - - const validateItem = (value: string, modelObj: FromToItem) => { - const { model, isInvalid } = validateValue(value); - if (value !== modelObj.model) { - modelObj.value = model; - } - modelObj.model = model; - modelObj.isInvalid = isInvalid; - }; - - const validateValue = (ipAddress: string) => { - const result = { - model: ipAddress, - isInvalid: false, - }; - if (!ipAddress) { - result.isInvalid = true; - return result; - } - try { - result.model = new Ipv4Address(ipAddress).toString(); - result.isInvalid = false; - return result; - } catch (e) { - result.isInvalid = true; - return result; - } - }; - - const hasInvalidValues = (modelList: FromToModel[]) => { - return !!modelList.find(({ from, to }) => from.isInvalid || to.isInvalid); - }; - - useEffect( - () => { - setRanges(getUpdatedModels(list, ranges)); +function FromToList({ labelledbyId, showValidation, onBlur, ...rest }: FromToListProps) { + const fromToListConfig: InputListConfig = { + defaultValue: { + from: { value: '0.0.0.0', model: '0.0.0.0', isInvalid: false }, + to: { value: '255.255.255.255', model: '255.255.255.255', isInvalid: false }, }, - [list] - ); - - useEffect( - () => { - setValidity(!hasInvalidValues(ranges)); - }, - [ranges] - ); - - // resposible for setting up an initial value ([from: '0.0.0.0', to: '255.255.255.255' ]) when there is no default value - useEffect(() => { - onChange(ranges.map(({ from, to }) => ({ from: from.model, to: to.model }))); - }, []); - - if (!list || !list.length) { - return null; - } - - return ( - <> - + validateClass: Ipv4Address, + getModelValue: item => ({ + from: { value: item.from, model: item.from, isInvalid: false }, + to: { value: item.to, model: item.to, isInvalid: false }, + }), + getModel: (models: FromToModel[], index, modelName: 'from' | 'to') => models[index][modelName], + getRemoveBtnAriaLabel: (item: FromToModel) => + i18n.translate('common.ui.aggTypes.ipRanges.removeRangeAriaLabel', { + defaultMessage: 'Remove the range of {from} to {to}', + values: { from: item.from.value, to: item.to.value }, + }), + onChangeFn: ({ from, to }: FromToModel) => ({ from: from.model, to: to.model }), + hasInvalidValuesFn: ({ from, to }: FromToModel) => from.isInvalid || to.isInvalid, + renderInputRow: (item: FromToModel, index, onChangeValue) => ( + <> - - - + { + onChangeValue(index, ev.target.value, 'from'); + }} + value={item.from.value} + onBlur={onBlur} + /> - - - + { + onChangeValue(index, ev.target.value, 'to'); + }} + value={item.to.value} + onBlur={onBlur} + /> - - - {ranges.map((item, index) => ( - - - - { - onChangeValue('from', index, ev.target.value); - }} - value={item.from.value} - onBlur={onBlur} - /> - - - { - onChangeValue('to', index, ev.target.value); - }} - value={item.to.value} - onBlur={onBlur} - /> - - - onDelete(item.id)} - /> - - - - - ))} + + ), + validateModel: (validateFn, object: FromToObject, model: FromToModel) => { + validateFn(object.from, model.from); + validateFn(object.to, model.to); + }, + }; + const header = ( + <> + + + + + + + + + + ); + + return ; } export { FromToList }; diff --git a/src/legacy/ui/public/agg_types/controls/components/input_list.tsx b/src/legacy/ui/public/agg_types/controls/components/input_list.tsx new file mode 100644 index 0000000000000..a06fb3adcdff7 --- /dev/null +++ b/src/legacy/ui/public/agg_types/controls/components/input_list.tsx @@ -0,0 +1,204 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useState, useEffect, Fragment } from 'react'; +import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiSpacer, htmlIdGenerator } from '@elastic/eui'; + +export interface InputListConfig { + defaultValue: {}; + validateClass: new (value: string) => object; + getModelValue(item: InputObject): {}; + getModel(models: InputModel[], index: number, modelName?: string): InputModel | InputItem; + getRemoveBtnAriaLabel(model: InputModel): string; + onChangeFn(model: InputModel): InputObject; + hasInvalidValuesFn(model: InputModel): boolean; + renderInputRow( + model: InputModel, + index: number, + onChangeFn: (index: number, value: string, modelName?: string) => void + ): React.ReactNode; + validateModel( + validateFn: (value: string, modelObj: InputItem) => void, + object: InputObject, + model: InputModel + ): void; +} +interface InputModelBase { + id: string; + [key: string]: any; +} +export interface InputObject { + [prop: string]: string; +} +interface InputItem { + model: string; + value: string; + isInvalid: boolean; +} + +export type InputModel = + | InputModelBase & { [model: string]: InputItem } + | InputModelBase & InputItem; + +interface InputListProps { + config: InputListConfig; + header: React.ReactNode; + list: InputObject[]; + onChange(list: InputObject[]): void; + setValidity(isValid: boolean): void; +} + +const generateId = htmlIdGenerator(); + +function InputList({ config, header, list, onChange, setValidity }: InputListProps) { + const [models, setModels] = useState( + list.length + ? list.map( + item => + ({ + id: generateId(), + ...config.getModelValue(item), + } as InputModel) + ) + : [ + { + id: generateId(), + ...config.defaultValue, + } as InputModel, + ] + ); + + const onUpdate = (modelList: InputModel[]) => { + setModels(modelList); + onChange(modelList.map(config.onChangeFn)); + }; + + const onChangeValue = (index: number, value: string, modelName?: string) => { + const range = config.getModel(models, index, modelName); + const { model, isInvalid } = validateValue(value); + range.value = value; + range.model = model; + range.isInvalid = isInvalid; + onUpdate(models); + }; + const onDelete = (id: string) => { + const newArray = models.filter(model => model.id !== id); + onUpdate(newArray); + }; + + const getUpdatedModels = (objList: InputObject[], modelList: InputModel[]) => { + if (!objList.length) { + return modelList; + } + return objList.map((item, index) => { + const model = modelList[index] || { + id: generateId(), + ...config.getModelValue(item), + }; + + config.validateModel(validateItem, item, model); + + return model; + }); + }; + + const validateItem = (value: string, modelObj: InputItem) => { + const { model, isInvalid } = validateValue(value); + if (value !== modelObj.model) { + modelObj.value = model; + } + modelObj.model = model; + modelObj.isInvalid = isInvalid; + }; + + const validateValue = (inputValue: string) => { + const result = { + model: inputValue, + isInvalid: false, + }; + if (!inputValue) { + result.isInvalid = true; + return result; + } + try { + result.model = new config.validateClass(inputValue).toString(); + result.isInvalid = false; + return result; + } catch (e) { + result.isInvalid = true; + return result; + } + }; + + const hasInvalidValues = (modelList: InputModel[]) => { + return !!modelList.find(config.hasInvalidValuesFn); + }; + + useEffect( + () => { + setModels(getUpdatedModels(list, models)); + }, + [list] + ); + + useEffect( + () => { + setValidity(!hasInvalidValues(models)); + }, + [models] + ); + + // resposible for setting up an initial value ([from: '0.0.0.0', to: '255.255.255.255' ]) when there is no default value + useEffect(() => { + onChange(models.map(config.onChangeFn)); + }, []); + + if (!list || !list.length) { + return null; + } + + return ( + <> + + {header} + + + {models.map((item, index) => ( + + + {config.renderInputRow(item, index, onChangeValue)} + + onDelete(item.id)} + /> + + + + + ))} + + ); +} + +export { InputList }; diff --git a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx index afd3f248ccf3d..a630b21cf4687 100644 --- a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx @@ -17,23 +17,16 @@ * under the License. */ -import React, { useState, useEffect, Fragment } from 'react'; -import { - EuiButtonIcon, - EuiFieldText, - EuiFormLabel, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - htmlIdGenerator, -} from '@elastic/eui'; +import React from 'react'; +import { EuiFieldText, EuiFormLabel, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { CidrMask } from '../../../utils/cidr_mask'; +import { InputList, InputListConfig, InputObject } from './input_list'; -export interface MaskObject { +export type MaskObject = InputObject & { mask: string; -} +}; interface MaskModel { id: string; @@ -51,165 +44,56 @@ interface MaskListProps { setValidity(isValid: boolean): void; } -const generateId = htmlIdGenerator(); - -function MaskList({ - labelledbyId, - list, - showValidation, - onBlur, - onChange, - setValidity, -}: MaskListProps) { - const [models, setModels] = useState( - list.length - ? list.map(item => ({ - model: item.mask, - value: item.mask, - id: generateId(), - isInvalid: false, - })) - : [{ id: generateId(), model: '0.0.0.0/1', value: '0.0.0.0/1', isInvalid: false }] - ); - - const onUpdate = (modelList: MaskModel[]) => { - setModels(modelList); - onChange(modelList.map(({ model }) => ({ mask: model }))); - }; - - const onChangeValue = (index: number, value: string) => { - const mask = models[index]; - const { model, isInvalid } = validateValue(value); - mask.value = value; - mask.model = model; - mask.isInvalid = isInvalid; - onUpdate(models); - }; - const onDelete = (id: string) => { - const newArray = models.filter(model => model.id !== id); - onUpdate(newArray); - }; - - const validateValue = (mask: string) => { - const result = { - model: mask, +function MaskList({ labelledbyId, showValidation, onBlur, ...rest }: MaskListProps) { + const maskListConfig: InputListConfig = { + defaultValue: { + model: '0.0.0.0/1', + value: '0.0.0.0/1', isInvalid: false, - }; - if (!mask) { - result.isInvalid = true; - return result; - } - try { - result.model = new CidrMask(mask).toString(); - result.isInvalid = false; - return result; - } catch (e) { - result.isInvalid = true; - return result; - } - }; - - const getUpdatedModels = (objList: MaskObject[], modelList: MaskModel[]) => { - if (!objList.length) { - return modelList; - } - return objList.map((item, index) => { - const maskModel = modelList[index] || { - id: generateId(), - value: item.mask, - model: item.mask, - isInvalid: false, - }; - const { model, isInvalid } = validateValue(item.mask); - if (item.mask !== maskModel.model) { - maskModel.value = model; - } - return { - ...maskModel, - model, - isInvalid, - }; - }); - }; - - const hasInvalidValues = (modelList: MaskModel[]) => { - return !!modelList.find(({ isInvalid }) => isInvalid); - }; - - useEffect( - () => { - setModels(getUpdatedModels(list, models)); }, - [list] - ); - - useEffect( - () => { - setValidity(!hasInvalidValues(models)); + validateClass: CidrMask, + getModelValue: item => ({ + model: item.mask, + value: item.mask, + isInvalid: false, + }), + getModel: (models: MaskModel[], index) => models[index], + getRemoveBtnAriaLabel: (item: MaskModel) => + i18n.translate('common.ui.aggTypes.ipRanges.removeCidrMaskButtonAriaLabel', { + defaultMessage: 'Remove the CIDR mask value of {mask}', + values: { mask: item.value }, + }), + onChangeFn: ({ model }: MaskModel) => ({ mask: model }), + hasInvalidValuesFn: ({ isInvalid }) => isInvalid, + renderInputRow: (item: MaskModel, index, onChangeValue) => ( + + { + onChangeValue(index, ev.target.value); + }} + value={item.value} + onBlur={onBlur} + /> + + ), + validateModel: (validateFn, object: MaskObject, model: MaskModel) => { + validateFn(object.mask, model); }, - [models] + }; + const header = ( + + + + + ); - // resposible for setting up an initial value ([mask: '0.0.0.0/1']) when there is no default value - useEffect(() => { - onChange(models.map(({ model }) => ({ mask: model }))); - }, []); - - if (!list || !list.length) { - return null; - } - - return ( - <> - - - - - - - {models.map((item, index) => ( - - - - { - onChangeValue(index, ev.target.value); - }} - value={item.value} - onBlur={onBlur} - /> - - - onDelete(item.id)} - /> - - - - - ))} - - ); + return ; } export { MaskList }; From 5d358c17a6c60f126e8b25c13cb52f8c1a7fca68 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 22 May 2019 18:53:26 +0300 Subject: [PATCH 06/15] Remove old template --- .../public/agg_types/controls/ip_ranges.html | 154 ------------------ 1 file changed, 154 deletions(-) delete mode 100644 src/legacy/ui/public/agg_types/controls/ip_ranges.html diff --git a/src/legacy/ui/public/agg_types/controls/ip_ranges.html b/src/legacy/ui/public/agg_types/controls/ip_ranges.html deleted file mode 100644 index 5450fae62cb19..0000000000000 --- a/src/legacy/ui/public/agg_types/controls/ip_ranges.html +++ /dev/null @@ -1,154 +0,0 @@ -
- -

- - -

- -
- -
- - - - - - - - - - - -
- - - -
- - - - - -
- - -
-

- - - -

-
- - -
- -
- - - - - - - - - -
- -
- - - -
- - -
-

- - - -

-
- - -
-
From 702d5b3dcd08e1ff06aa48547dae7bb12ea71faa Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 24 May 2019 16:53:47 +0300 Subject: [PATCH 07/15] Move add btn to input_list, add placeholder --- .../controls/components/from_to_list.tsx | 31 +++++++--- .../controls/components/input_list.tsx | 48 ++++++++++++---- .../controls/components/mask_list.tsx | 25 +++++--- .../public/agg_types/controls/ip_ranges.tsx | 57 +++++++------------ 4 files changed, 98 insertions(+), 63 deletions(-) diff --git a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx index cea82e08406af..4f032a67dc105 100644 --- a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx @@ -24,10 +24,10 @@ import { FormattedMessage } from '@kbn/i18n/react'; import Ipv4Address from '../../../utils/ipv4_address'; import { InputList, InputListConfig, InputModel, InputObject } from './input_list'; -export type FromToObject = InputObject & { - from: string; - to: string; -}; +export interface FromToObject extends InputObject { + from?: string; + to?: string; +} interface FromToItem { model: string; @@ -55,10 +55,14 @@ function FromToList({ labelledbyId, showValidation, onBlur, ...rest }: FromToLis from: { value: '0.0.0.0', model: '0.0.0.0', isInvalid: false }, to: { value: '255.255.255.255', model: '255.255.255.255', isInvalid: false }, }, + defaultEmptyValue: { + from: { value: '', model: '', isInvalid: false }, + to: { value: '', model: '', isInvalid: false }, + }, validateClass: Ipv4Address, - getModelValue: item => ({ - from: { value: item.from, model: item.from, isInvalid: false }, - to: { value: item.to, model: item.to, isInvalid: false }, + getModelValue: (item: FromToObject) => ({ + from: { value: item.from || '', model: item.from || '', isInvalid: false }, + to: { value: item.to || '', model: item.to || '', isInvalid: false }, }), getModel: (models: FromToModel[], index, modelName: 'from' | 'to') => models[index][modelName], getRemoveBtnAriaLabel: (item: FromToModel) => @@ -66,7 +70,16 @@ function FromToList({ labelledbyId, showValidation, onBlur, ...rest }: FromToLis defaultMessage: 'Remove the range of {from} to {to}', values: { from: item.from.value, to: item.to.value }, }), - onChangeFn: ({ from, to }: FromToModel) => ({ from: from.model, to: to.model }), + onChangeFn: ({ from, to }: FromToModel) => { + const result: FromToObject = {}; + if (from.model) { + result.from = from.model; + } + if (to.model) { + result.to = to.model; + } + return result; + }, hasInvalidValuesFn: ({ from, to }: FromToModel) => from.isInvalid || to.isInvalid, renderInputRow: (item: FromToModel, index, onChangeValue) => ( <> @@ -74,6 +87,7 @@ function FromToList({ labelledbyId, showValidation, onBlur, ...rest }: FromToLis { onChangeValue(index, ev.target.value, 'from'); }} @@ -85,6 +99,7 @@ function FromToList({ labelledbyId, showValidation, onBlur, ...rest }: FromToLis { onChangeValue(index, ev.target.value, 'to'); }} diff --git a/src/legacy/ui/public/agg_types/controls/components/input_list.tsx b/src/legacy/ui/public/agg_types/controls/components/input_list.tsx index a06fb3adcdff7..8321ccc03626f 100644 --- a/src/legacy/ui/public/agg_types/controls/components/input_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/input_list.tsx @@ -18,10 +18,19 @@ */ import React, { useState, useEffect, Fragment } from 'react'; -import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiSpacer, htmlIdGenerator } from '@elastic/eui'; +import { + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + htmlIdGenerator, + EuiButtonEmpty, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; export interface InputListConfig { defaultValue: {}; + defaultEmptyValue: {}; validateClass: new (value: string) => object; getModelValue(item: InputObject): {}; getModel(models: InputModel[], index: number, modelName?: string): InputModel | InputItem; @@ -34,7 +43,7 @@ export interface InputListConfig { onChangeFn: (index: number, value: string, modelName?: string) => void ): React.ReactNode; validateModel( - validateFn: (value: string, modelObj: InputItem) => void, + validateFn: (value: string | undefined, modelObj: InputItem) => void, object: InputObject, model: InputModel ): void; @@ -43,9 +52,7 @@ interface InputModelBase { id: string; [key: string]: any; } -export interface InputObject { - [prop: string]: string; -} +export type InputObject = object; interface InputItem { model: string; value: string; @@ -102,6 +109,17 @@ function InputList({ config, header, list, onChange, setValidity }: InputListPro onUpdate(newArray); }; + const onAdd = () => { + const newArray = [ + ...models, + { + id: generateId(), + ...config.defaultEmptyValue, + } as InputModel, + ]; + onUpdate(newArray); + }; + const getUpdatedModels = (objList: InputObject[], modelList: InputModel[]) => { if (!objList.length) { return modelList; @@ -118,7 +136,7 @@ function InputList({ config, header, list, onChange, setValidity }: InputListPro }); }; - const validateItem = (value: string, modelObj: InputItem) => { + const validateItem = (value: string | undefined, modelObj: InputItem) => { const { model, isInvalid } = validateValue(value); if (value !== modelObj.model) { modelObj.value = model; @@ -127,13 +145,13 @@ function InputList({ config, header, list, onChange, setValidity }: InputListPro modelObj.isInvalid = isInvalid; }; - const validateValue = (inputValue: string) => { + const validateValue = (inputValue: string | undefined) => { const result = { - model: inputValue, + model: inputValue || '', isInvalid: false, }; if (!inputValue) { - result.isInvalid = true; + result.isInvalid = false; return result; } try { @@ -164,7 +182,7 @@ function InputList({ config, header, list, onChange, setValidity }: InputListPro [models] ); - // resposible for setting up an initial value ([from: '0.0.0.0', to: '255.255.255.255' ]) when there is no default value + // resposible for setting up an initial value when there is no default value useEffect(() => { onChange(models.map(config.onChangeFn)); }, []); @@ -178,7 +196,6 @@ function InputList({ config, header, list, onChange, setValidity }: InputListPro {header} - {models.map((item, index) => ( @@ -197,6 +214,15 @@ function InputList({ config, header, list, onChange, setValidity }: InputListPro ))} + + + + + + ); } diff --git a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx index a630b21cf4687..3dc68b4746a57 100644 --- a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx @@ -24,9 +24,9 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { CidrMask } from '../../../utils/cidr_mask'; import { InputList, InputListConfig, InputObject } from './input_list'; -export type MaskObject = InputObject & { - mask: string; -}; +export interface MaskObject extends InputObject { + mask?: string; +} interface MaskModel { id: string; @@ -51,10 +51,15 @@ function MaskList({ labelledbyId, showValidation, onBlur, ...rest }: MaskListPro value: '0.0.0.0/1', isInvalid: false, }, + defaultEmptyValue: { + model: '', + value: '', + isInvalid: false, + }, validateClass: CidrMask, - getModelValue: item => ({ - model: item.mask, - value: item.mask, + getModelValue: (item: MaskObject) => ({ + model: item.mask || '', + value: item.mask || '', isInvalid: false, }), getModel: (models: MaskModel[], index) => models[index], @@ -63,13 +68,19 @@ function MaskList({ labelledbyId, showValidation, onBlur, ...rest }: MaskListPro defaultMessage: 'Remove the CIDR mask value of {mask}', values: { mask: item.value }, }), - onChangeFn: ({ model }: MaskModel) => ({ mask: model }), + onChangeFn: ({ model }: MaskModel) => { + if (model) { + return { mask: model }; + } + return {}; + }, hasInvalidValuesFn: ({ isInvalid }) => isInvalid, renderInputRow: (item: MaskModel, index, onChangeValue) => ( { onChangeValue(index, ev.target.value); }} diff --git a/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx b/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx index c283a8ff97e1c..a7d2c68407340 100644 --- a/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx +++ b/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx @@ -18,8 +18,7 @@ */ import React from 'react'; -import { EuiFlexItem, EuiFormRow, EuiSpacer, EuiButtonEmpty } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFormRow } from '@elastic/eui'; import { AggParamEditorProps } from 'ui/vis/editors/default'; import { FromToList, FromToObject } from './components/from_to_list'; @@ -45,43 +44,27 @@ function IpRangesParamEditor({ }); }; - const onAdd = () => { - const type = agg.params.ipRangeType as IpRangeTypes; - setValue({ ...value, [type]: [...value[type], {}] }); - }; - return ( - <> - {agg.params.ipRangeType === IpRangeTypes.MASK ? ( - handleChange(IpRangeTypes.MASK, items)} - setValidity={setValidity} - /> - ) : ( - handleChange(IpRangeTypes.FROM_TO, items)} - setValidity={setValidity} - /> - )} - - - - - - - + {agg.params.ipRangeType === IpRangeTypes.MASK ? ( + handleChange(IpRangeTypes.MASK, items)} + setValidity={setValidity} + /> + ) : ( + handleChange(IpRangeTypes.FROM_TO, items)} + setValidity={setValidity} + /> + )} ); } From fc4d91e6f1ac616c5a45af0da9801c38c1c3d7f2 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 24 May 2019 16:59:01 +0300 Subject: [PATCH 08/15] Remove unused directives --- .../directives/validate_cidr_mask.js | 104 ------------------ .../__tests__/directives/validate_ip.js | 96 ---------------- .../ui/public/agg_types/buckets/ip_range.js | 2 - .../directives/validate_cidr_mask.js | 50 --------- .../agg_types/directives/validate_ip.js | 55 --------- 5 files changed, 307 deletions(-) delete mode 100644 src/legacy/ui/public/agg_types/__tests__/directives/validate_cidr_mask.js delete mode 100644 src/legacy/ui/public/agg_types/__tests__/directives/validate_ip.js delete mode 100644 src/legacy/ui/public/agg_types/directives/validate_cidr_mask.js delete mode 100644 src/legacy/ui/public/agg_types/directives/validate_ip.js diff --git a/src/legacy/ui/public/agg_types/__tests__/directives/validate_cidr_mask.js b/src/legacy/ui/public/agg_types/__tests__/directives/validate_cidr_mask.js deleted file mode 100644 index 3d5d2531ee85f..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/directives/validate_cidr_mask.js +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import '../../directives/validate_cidr_mask'; - - -describe('Validate CIDR mask directive', function () { - let $compile; - let $rootScope; - const html = ''; - - beforeEach(ngMock.module('kibana')); - - beforeEach(ngMock.inject(function (_$compile_, _$rootScope_) { - $compile = _$compile_; - $rootScope = _$rootScope_; - })); - - it('should allow empty input', function () { - const element = $compile(html)($rootScope); - - $rootScope.value = ''; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - - $rootScope.value = null; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - - $rootScope.value = undefined; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - }); - - it('should allow valid CIDR masks', function () { - const element = $compile(html)($rootScope); - - $rootScope.value = '0.0.0.0/1'; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - - $rootScope.value = '128.0.0.1/31'; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - - $rootScope.value = '1.2.3.4/2'; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - - $rootScope.value = '67.129.65.201/27'; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - }); - - it('should disallow invalid CIDR masks', function () { - const element = $compile(html)($rootScope); - - $rootScope.value = 'hello, world'; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - - $rootScope.value = '0.0.0.0'; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - - $rootScope.value = '0.0.0.0/0'; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - - $rootScope.value = '0.0.0.0/33'; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - - $rootScope.value = '256.0.0.0/32'; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - - $rootScope.value = '0.0.0.0/32/32'; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - - $rootScope.value = '1.2.3/1'; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/directives/validate_ip.js b/src/legacy/ui/public/agg_types/__tests__/directives/validate_ip.js deleted file mode 100644 index ad4c91e9081bf..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/directives/validate_ip.js +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import '../../directives/validate_ip'; - - -describe('Validate IP directive', function () { - let $compile; - let $rootScope; - const html = ''; - - beforeEach(ngMock.module('kibana')); - - beforeEach(ngMock.inject(function (_$compile_, _$rootScope_) { - $compile = _$compile_; - $rootScope = _$rootScope_; - })); - - it('should allow empty input', function () { - const element = $compile(html)($rootScope); - - $rootScope.value = ''; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - - $rootScope.value = null; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - - $rootScope.value = undefined; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - }); - - it('should allow valid IP addresses', function () { - const element = $compile(html)($rootScope); - - $rootScope.value = '0.0.0.0'; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - - $rootScope.value = '0.0.0.1'; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - - $rootScope.value = '126.45.211.34'; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - - $rootScope.value = '255.255.255.255'; - $rootScope.$digest(); - expect(element.hasClass('ng-valid')).to.be.ok(); - }); - - it('should disallow invalid IP addresses', function () { - const element = $compile(html)($rootScope); - - $rootScope.value = 'hello, world'; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - - $rootScope.value = '0.0.0'; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - - $rootScope.value = '256.0.0.0'; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - - $rootScope.value = '-1.0.0.0'; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - - $rootScope.value = Number.MAX_VALUE; - $rootScope.$digest(); - expect(element.hasClass('ng-invalid')).to.be.ok(); - }); -}); diff --git a/src/legacy/ui/public/agg_types/buckets/ip_range.js b/src/legacy/ui/public/agg_types/buckets/ip_range.js index dcf0a80e0c1ad..023f2d3cdb520 100644 --- a/src/legacy/ui/public/agg_types/buckets/ip_range.js +++ b/src/legacy/ui/public/agg_types/buckets/ip_range.js @@ -18,8 +18,6 @@ */ import _ from 'lodash'; -import '../directives/validate_ip'; -import '../directives/validate_cidr_mask'; import { BucketAggType } from './_bucket_agg_type'; import { createFilterIpRange } from './create_filter/ip_range'; import { IpRangeTypeParamEditor } from '../controls/ip_range_type'; diff --git a/src/legacy/ui/public/agg_types/directives/validate_cidr_mask.js b/src/legacy/ui/public/agg_types/directives/validate_cidr_mask.js deleted file mode 100644 index 6e33f8a141865..0000000000000 --- a/src/legacy/ui/public/agg_types/directives/validate_cidr_mask.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { CidrMask } from '../../utils/cidr_mask'; -import { uiModules } from '../../modules'; - -uiModules.get('kibana').directive('validateCidrMask', function () { - return { - restrict: 'A', - require: 'ngModel', - scope: { - 'ngModel': '=' - }, - link: function ($scope, elem, attr, ngModel) { - ngModel.$parsers.unshift(validateCidrMask); - ngModel.$formatters.unshift(validateCidrMask); - - function validateCidrMask(mask) { - if (mask == null || mask === '') { - ngModel.$setValidity('cidrMaskInput', true); - return null; - } - - try { - mask = new CidrMask(mask); - ngModel.$setValidity('cidrMaskInput', true); - return mask.toString(); - } catch (e) { - ngModel.$setValidity('cidrMaskInput', false); - } - } - } - }; -}); diff --git a/src/legacy/ui/public/agg_types/directives/validate_ip.js b/src/legacy/ui/public/agg_types/directives/validate_ip.js deleted file mode 100644 index a5e8704fb45fc..0000000000000 --- a/src/legacy/ui/public/agg_types/directives/validate_ip.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Ipv4Address from '../../utils/ipv4_address'; -import { uiModules } from '../../modules'; - -uiModules - .get('kibana') - .directive('validateIp', function () { - return { - restrict: 'A', - require: 'ngModel', - scope: { - 'ngModel': '=', - }, - link: function ($scope, elem, attr, ngModel) { - function validateIp(ipAddress) { - if (ipAddress == null || ipAddress === '') { - ngModel.$setValidity('ipInput', true); - return null; - } - - try { - ipAddress = new Ipv4Address(ipAddress); - ngModel.$setValidity('ipInput', true); - return ipAddress.toString(); - } catch (e) { - ngModel.$setValidity('ipInput', false); - } - } - - // From User - ngModel.$parsers.unshift(validateIp); - - // To user - ngModel.$formatters.unshift(validateIp); - } - }; - }); From fb73c67ee1451d91df2a1787ecc5ca5e2cdd5f4c Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 24 May 2019 17:17:56 +0300 Subject: [PATCH 09/15] Remove unused translations --- x-pack/plugins/translations/translations/ja-JP.json | 12 ------------ x-pack/plugins/translations/translations/zh-CN.json | 10 +--------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 36dca19b37081..aa783af51bd10 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -145,16 +145,8 @@ "common.ui.aggTypes.filters.removeFilterButtonAriaLabel": "このフィルターを削除", "common.ui.aggTypes.filters.toggleFilterButtonAriaLabel": "フィルターラベルを切り替える", "common.ui.aggTypes.histogram.missingMaxMinValuesWarning": "自動スケールヒストグラムバケットから最高値と最低値を取得できません。これによりビジュアライゼーションのパフォーマンスが低下する可能性があります。", - "common.ui.aggTypes.ipRanges.cidrMask.addRangeButtonLabel": "範囲を追加", - "common.ui.aggTypes.ipRanges.cidrMask.requiredIpRangeDescription": "IP 範囲を最低 1 つ指定する必要があります。", - "common.ui.aggTypes.ipRanges.cidrMask.requiredIpRangeLabel": "必須:", "common.ui.aggTypes.ipRanges.cidrMaskLabel": "CIDR マスク", "common.ui.aggTypes.ipRanges.fromLabel": "開始:", - "common.ui.aggTypes.ipRanges.fromTo.addRangeButtonLabel": "範囲を追加", - "common.ui.aggTypes.ipRanges.fromTo.requiredIpRangeDescription": "IP 範囲を最低 1 つ指定する必要があります。", - "common.ui.aggTypes.ipRanges.fromTo.requiredIpRangeLabel": "必須:", - "common.ui.aggTypes.ipRanges.removeCidrMaskButtonAriaLabel": "CIDR マスクを削除", - "common.ui.aggTypes.ipRanges.removeRangeAriaLabel": "この範囲を削除", "common.ui.aggTypes.ipRanges.toLabel": "終了:", "common.ui.aggTypes.ipRanges.useCidrMasksButtonLabel": "CIDR マスクを使用", "common.ui.aggTypes.ipRanges.useFromToButtonLabel": "開始/終了を使用", @@ -547,10 +539,6 @@ "common.ui.flotCharts.tueLabel": "火", "common.ui.flotCharts.wedLabel": "水", "common.ui.indexPattern.bannerLabel": "Kibana でデータの可視化と閲覧を行うには、Elasticsearch からデータを取得するためのインデックスパターンの作成が必要です。", - "common.ui.indexPattern.confirmOverwriteButton": "上書き", - "common.ui.indexPattern.confirmOverwriteLabel": "「{title}」に上書きしてよろしいですか?", - "common.ui.indexPattern.confirmOverwriteTitle": "{type} を上書きしますか?", - "common.ui.indexPattern.titleExistsLabel": "「{title}」というタイトルのインデックスパターンが既に存在します。", "common.ui.indexPattern.unableWriteLabel": "インデックスパターンを書き込めません!このインデックスパターンへの最新の変更を取得するにな、ページを更新してください。", "common.ui.indexPattern.unknownFieldErrorMessage": "インデックスパターン「{title}」のフィールド「{name}」が不明なフィールドタイプを使用しています。", "common.ui.indexPattern.unknownFieldHeader": "不明なフィールドタイプ {type}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index cb61069ccf38b..b033b0a27ffb2 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -130,16 +130,8 @@ "common.ui.aggTypes.filters.removeFilterButtonAriaLabel": "移除此筛选", "common.ui.aggTypes.filters.toggleFilterButtonAriaLabel": "切换筛选标签", "common.ui.aggTypes.histogram.missingMaxMinValuesWarning": "无法检索最大值和最小值以自动缩放直方图存储桶。这可能会导致可视化性能低下。", - "common.ui.aggTypes.ipRanges.cidrMask.addRangeButtonLabel": "添加范围", - "common.ui.aggTypes.ipRanges.cidrMask.requiredIpRangeDescription": "必须指定至少一个 IP 范围。", - "common.ui.aggTypes.ipRanges.cidrMask.requiredIpRangeLabel": "必需:", "common.ui.aggTypes.ipRanges.cidrMaskLabel": "CIDR 掩码", "common.ui.aggTypes.ipRanges.fromLabel": "从", - "common.ui.aggTypes.ipRanges.fromTo.addRangeButtonLabel": "添加范围", - "common.ui.aggTypes.ipRanges.fromTo.requiredIpRangeDescription": "必须指定至少一个 IP 范围。", - "common.ui.aggTypes.ipRanges.fromTo.requiredIpRangeLabel": "必需:", - "common.ui.aggTypes.ipRanges.removeCidrMaskButtonAriaLabel": "移除此 CIDR 掩码", - "common.ui.aggTypes.ipRanges.removeRangeAriaLabel": "移除此范围", "common.ui.aggTypes.ipRanges.toLabel": "到", "common.ui.aggTypes.ipRanges.useCidrMasksButtonLabel": "使用 CIDR 掩码", "common.ui.aggTypes.ipRanges.useFromToButtonLabel": "使用“从”/“到”", @@ -7929,4 +7921,4 @@ "xpack.watcher.watchActionsTitle": "满足后将执行 {watchActionsCount, plural, one{# 个操作} other {# 个操作}}", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} +} \ No newline at end of file From c0e269ac7898d060dada470ab61aaa3bc4e258c6 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 27 May 2019 10:24:45 +0300 Subject: [PATCH 10/15] Refactoring --- .../agg_types/controls/components/from_to_list.tsx | 14 ++++++++++---- .../agg_types/controls/components/input_list.tsx | 4 +++- .../agg_types/controls/components/mask_list.tsx | 10 ++++++---- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx index 4f032a67dc105..041bfcd4b01a1 100644 --- a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx @@ -24,6 +24,8 @@ import { FormattedMessage } from '@kbn/i18n/react'; import Ipv4Address from '../../../utils/ipv4_address'; import { InputList, InputListConfig, InputModel, InputObject } from './input_list'; +const EMPTY_STRING = ''; + export interface FromToObject extends InputObject { from?: string; to?: string; @@ -56,13 +58,17 @@ function FromToList({ labelledbyId, showValidation, onBlur, ...rest }: FromToLis to: { value: '255.255.255.255', model: '255.255.255.255', isInvalid: false }, }, defaultEmptyValue: { - from: { value: '', model: '', isInvalid: false }, - to: { value: '', model: '', isInvalid: false }, + from: { value: EMPTY_STRING, model: EMPTY_STRING, isInvalid: false }, + to: { value: EMPTY_STRING, model: EMPTY_STRING, isInvalid: false }, }, validateClass: Ipv4Address, getModelValue: (item: FromToObject) => ({ - from: { value: item.from || '', model: item.from || '', isInvalid: false }, - to: { value: item.to || '', model: item.to || '', isInvalid: false }, + from: { + value: item.from || EMPTY_STRING, + model: item.from || EMPTY_STRING, + isInvalid: false, + }, + to: { value: item.to || EMPTY_STRING, model: item.to || EMPTY_STRING, isInvalid: false }, }), getModel: (models: FromToModel[], index, modelName: 'from' | 'to') => models[index][modelName], getRemoveBtnAriaLabel: (item: FromToModel) => diff --git a/src/legacy/ui/public/agg_types/controls/components/input_list.tsx b/src/legacy/ui/public/agg_types/controls/components/input_list.tsx index 8321ccc03626f..c4f4c164614ca 100644 --- a/src/legacy/ui/public/agg_types/controls/components/input_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/input_list.tsx @@ -155,7 +155,8 @@ function InputList({ config, header, list, onChange, setValidity }: InputListPro return result; } try { - result.model = new config.validateClass(inputValue).toString(); + const InputObject = config.validateClass; + result.model = new InputObject(inputValue).toString(); result.isInvalid = false; return result; } catch (e) { @@ -168,6 +169,7 @@ function InputList({ config, header, list, onChange, setValidity }: InputListPro return !!modelList.find(config.hasInvalidValuesFn); }; + // responsible for discarding changes useEffect( () => { setModels(getUpdatedModels(list, models)); diff --git a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx index 3dc68b4746a57..00dfc2c319c6e 100644 --- a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx @@ -24,6 +24,8 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { CidrMask } from '../../../utils/cidr_mask'; import { InputList, InputListConfig, InputObject } from './input_list'; +const EMPTY_STRING = ''; + export interface MaskObject extends InputObject { mask?: string; } @@ -52,14 +54,14 @@ function MaskList({ labelledbyId, showValidation, onBlur, ...rest }: MaskListPro isInvalid: false, }, defaultEmptyValue: { - model: '', - value: '', + model: EMPTY_STRING, + value: EMPTY_STRING, isInvalid: false, }, validateClass: CidrMask, getModelValue: (item: MaskObject) => ({ - model: item.mask || '', - value: item.mask || '', + model: item.mask || EMPTY_STRING, + value: item.mask || EMPTY_STRING, isInvalid: false, }), getModel: (models: MaskModel[], index) => models[index], From 865afa11bd541d797247592986cc7ac7d6eb9e26 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 29 May 2019 14:44:18 +0300 Subject: [PATCH 11/15] Use EuiButtonGroup instead of toggle button --- .../controls/components/from_to_list.tsx | 16 +------- .../controls/components/input_list.tsx | 8 +--- .../controls/components/mask_list.tsx | 12 +----- .../agg_types/controls/ip_range_type.tsx | 38 +++++++++++++------ .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 6 files changed, 30 insertions(+), 48 deletions(-) diff --git a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx index 041bfcd4b01a1..6a883d419f2bf 100644 --- a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx @@ -120,22 +120,8 @@ function FromToList({ labelledbyId, showValidation, onBlur, ...rest }: FromToLis validateFn(object.to, model.to); }, }; - const header = ( - <> - - - - - - - - - - - - ); - return ; + return ; } export { FromToList }; diff --git a/src/legacy/ui/public/agg_types/controls/components/input_list.tsx b/src/legacy/ui/public/agg_types/controls/components/input_list.tsx index c4f4c164614ca..20ba60f750ee5 100644 --- a/src/legacy/ui/public/agg_types/controls/components/input_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/input_list.tsx @@ -65,7 +65,6 @@ export type InputModel = interface InputListProps { config: InputListConfig; - header: React.ReactNode; list: InputObject[]; onChange(list: InputObject[]): void; setValidity(isValid: boolean): void; @@ -73,7 +72,7 @@ interface InputListProps { const generateId = htmlIdGenerator(); -function InputList({ config, header, list, onChange, setValidity }: InputListProps) { +function InputList({ config, list, onChange, setValidity }: InputListProps) { const [models, setModels] = useState( list.length ? list.map( @@ -195,9 +194,6 @@ function InputList({ config, header, list, onChange, setValidity }: InputListPro return ( <> - - {header} - {models.map((item, index) => ( @@ -221,7 +217,7 @@ function InputList({ config, header, list, onChange, setValidity }: InputListPro diff --git a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx index 00dfc2c319c6e..9ed2662658274 100644 --- a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx @@ -95,18 +95,8 @@ function MaskList({ labelledbyId, showValidation, onBlur, ...rest }: MaskListPro validateFn(object.mask, model); }, }; - const header = ( - - - - - - ); - return ; + return ; } export { MaskList }; diff --git a/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx b/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx index 41dd2240396ce..a6aefd506655b 100644 --- a/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx +++ b/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx @@ -19,7 +19,7 @@ import React from 'react'; -import { EuiButton, EuiSpacer } from '@elastic/eui'; +import { EuiButtonGroup, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { AggParamEditorProps } from '../../vis/editors/default'; @@ -29,22 +29,36 @@ enum IpRangeTypes { } function IpRangeTypeParamEditor({ value, setValue }: AggParamEditorProps) { - const useFromToLabel = i18n.translate('common.ui.aggTypes.ipRanges.useFromToButtonLabel', { - defaultMessage: 'Use From/To', - }); - const useCidrMasksLabel = i18n.translate('common.ui.aggTypes.ipRanges.useCidrMasksButtonLabel', { - defaultMessage: 'Use CIDR Masks', - }); + const options = [ + { + id: IpRangeTypes.FROM_TO, + label: i18n.translate('common.ui.aggTypes.ipRanges.fromToButtonLabel', { + defaultMessage: 'From/to', + }), + }, + { + id: IpRangeTypes.MASK, + label: i18n.translate('common.ui.aggTypes.ipRanges.cidrMasksButtonLabel', { + defaultMessage: 'CIDR masks', + }), + }, + ]; - const onClick = () => { - setValue(value === IpRangeTypes.MASK ? IpRangeTypes.FROM_TO : IpRangeTypes.MASK); + const onClick = (optionId: string) => { + setValue(optionId as IpRangeTypes); }; return ( <> - - {value === IpRangeTypes.MASK ? useFromToLabel : useCidrMasksLabel} - + ); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8df4c8d18df09..7632c21191abb 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -148,8 +148,6 @@ "common.ui.aggTypes.ipRanges.cidrMaskLabel": "CIDR マスク", "common.ui.aggTypes.ipRanges.fromLabel": "開始:", "common.ui.aggTypes.ipRanges.toLabel": "終了:", - "common.ui.aggTypes.ipRanges.useCidrMasksButtonLabel": "CIDR マスクを使用", - "common.ui.aggTypes.ipRanges.useFromToButtonLabel": "開始/終了を使用", "common.ui.aggTypes.jsonInputLabel": "JSON インプット", "common.ui.aggTypes.jsonInputTooltip": "ここに追加された JSON フォーマットのプロパティは、すべてこのセクションの Elasticsearch アグリゲーション定義に融合されます。用語集約における「shard_size」がその例です。", "common.ui.aggTypes.metricLabel": "メトリック", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 8a9f71dd32216..68acf20d58acf 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -133,8 +133,6 @@ "common.ui.aggTypes.ipRanges.cidrMaskLabel": "CIDR 掩码", "common.ui.aggTypes.ipRanges.fromLabel": "从", "common.ui.aggTypes.ipRanges.toLabel": "到", - "common.ui.aggTypes.ipRanges.useCidrMasksButtonLabel": "使用 CIDR 掩码", - "common.ui.aggTypes.ipRanges.useFromToButtonLabel": "使用“从”/“到”", "common.ui.aggTypes.jsonInputTooltip": "此处以 JSON 格式添加的任何属性将与此部分的 elasticsearch 聚合定义合并。例如,词聚合上的“shard_size”。", "common.ui.aggTypes.metricLabel": "指标", "common.ui.aggTypes.metrics.aggNotValidErrorMessage": "- 聚合无效 -", From 7a9e8ae67045ac3c25af2984fcaa6e1618016799 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 30 May 2019 13:49:33 +0300 Subject: [PATCH 12/15] Update options ids, add aria-labels --- .../controls/components/from_to_list.tsx | 16 ++++++++++------ .../agg_types/controls/components/mask_list.tsx | 11 ++++++----- .../public/agg_types/controls/ip_range_type.tsx | 10 +++++----- .../ui/public/agg_types/controls/ip_ranges.tsx | 4 +--- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx index 6a883d419f2bf..2f594838d1041 100644 --- a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx @@ -18,9 +18,8 @@ */ import React from 'react'; -import { EuiFormLabel, EuiFieldText, EuiFlexItem } from '@elastic/eui'; +import { EuiFieldText, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; import Ipv4Address from '../../../utils/ipv4_address'; import { InputList, InputListConfig, InputModel, InputObject } from './input_list'; @@ -43,7 +42,6 @@ type FromToModel = InputModel & { }; interface FromToListProps { - labelledbyId: string; list: FromToObject[]; showValidation: boolean; onBlur(): void; @@ -51,7 +49,7 @@ interface FromToListProps { setValidity(isValid: boolean): void; } -function FromToList({ labelledbyId, showValidation, onBlur, ...rest }: FromToListProps) { +function FromToList({ showValidation, onBlur, ...rest }: FromToListProps) { const fromToListConfig: InputListConfig = { defaultValue: { from: { value: '0.0.0.0', model: '0.0.0.0', isInvalid: false }, @@ -91,7 +89,10 @@ function FromToList({ labelledbyId, showValidation, onBlur, ...rest }: FromToLis <> { @@ -103,7 +104,10 @@ function FromToList({ labelledbyId, showValidation, onBlur, ...rest }: FromToLis { diff --git a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx index 9ed2662658274..84fb6b5a4f868 100644 --- a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx @@ -18,9 +18,8 @@ */ import React from 'react'; -import { EuiFieldText, EuiFormLabel, EuiFlexItem } from '@elastic/eui'; +import { EuiFieldText, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; import { CidrMask } from '../../../utils/cidr_mask'; import { InputList, InputListConfig, InputObject } from './input_list'; @@ -38,7 +37,6 @@ interface MaskModel { } interface MaskListProps { - labelledbyId: string; list: MaskObject[]; showValidation: boolean; onBlur(): void; @@ -46,7 +44,7 @@ interface MaskListProps { setValidity(isValid: boolean): void; } -function MaskList({ labelledbyId, showValidation, onBlur, ...rest }: MaskListProps) { +function MaskList({ showValidation, onBlur, ...rest }: MaskListProps) { const maskListConfig: InputListConfig = { defaultValue: { model: '0.0.0.0/1', @@ -80,7 +78,10 @@ function MaskList({ labelledbyId, showValidation, onBlur, ...rest }: MaskListPro renderInputRow: (item: MaskModel, index, onChangeValue) => ( { diff --git a/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx b/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx index a6aefd506655b..24964785b72df 100644 --- a/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx +++ b/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx @@ -28,16 +28,16 @@ enum IpRangeTypes { FROM_TO = 'fromTo', } -function IpRangeTypeParamEditor({ value, setValue }: AggParamEditorProps) { +function IpRangeTypeParamEditor({ agg, value, setValue }: AggParamEditorProps) { const options = [ { - id: IpRangeTypes.FROM_TO, + id: `visEditorIpRangeFromToLabel${agg.id}`, label: i18n.translate('common.ui.aggTypes.ipRanges.fromToButtonLabel', { defaultMessage: 'From/to', }), }, { - id: IpRangeTypes.MASK, + id: `visEditorIpRangeCidrLabel${agg.id}`, label: i18n.translate('common.ui.aggTypes.ipRanges.cidrMasksButtonLabel', { defaultMessage: 'CIDR masks', }), @@ -45,7 +45,7 @@ function IpRangeTypeParamEditor({ value, setValue }: AggParamEditorProps { - setValue(optionId as IpRangeTypes); + setValue(optionId === options[0].id ? IpRangeTypes.FROM_TO : IpRangeTypes.MASK); }; return ( @@ -53,7 +53,7 @@ function IpRangeTypeParamEditor({ value, setValue }: AggParamEditorProps + {agg.params.ipRangeType === IpRangeTypes.MASK ? ( ) : ( Date: Thu, 30 May 2019 14:58:10 +0300 Subject: [PATCH 13/15] Remove unused translations --- x-pack/plugins/translations/translations/ja-JP.json | 3 --- x-pack/plugins/translations/translations/zh-CN.json | 3 --- 2 files changed, 6 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d303d07fb34a7..ad3e5b8db778a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -145,9 +145,6 @@ "common.ui.aggTypes.filters.removeFilterButtonAriaLabel": "このフィルターを削除", "common.ui.aggTypes.filters.toggleFilterButtonAriaLabel": "フィルターラベルを切り替える", "common.ui.aggTypes.histogram.missingMaxMinValuesWarning": "自動スケールヒストグラムバケットから最高値と最低値を取得できません。これによりビジュアライゼーションのパフォーマンスが低下する可能性があります。", - "common.ui.aggTypes.ipRanges.cidrMaskLabel": "CIDR マスク", - "common.ui.aggTypes.ipRanges.fromLabel": "開始:", - "common.ui.aggTypes.ipRanges.toLabel": "終了:", "common.ui.aggTypes.jsonInputLabel": "JSON インプット", "common.ui.aggTypes.jsonInputTooltip": "ここに追加された JSON フォーマットのプロパティは、すべてこのセクションの Elasticsearch アグリゲーション定義に融合されます。用語集約における「shard_size」がその例です。", "common.ui.aggTypes.metricLabel": "メトリック", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 344229c5977aa..76bf94aac8988 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -129,9 +129,6 @@ "common.ui.aggTypes.filters.removeFilterButtonAriaLabel": "移除此筛选", "common.ui.aggTypes.filters.toggleFilterButtonAriaLabel": "切换筛选标签", "common.ui.aggTypes.histogram.missingMaxMinValuesWarning": "无法检索最大值和最小值以自动缩放直方图存储桶。这可能会导致可视化性能低下。", - "common.ui.aggTypes.ipRanges.cidrMaskLabel": "CIDR 掩码", - "common.ui.aggTypes.ipRanges.fromLabel": "从", - "common.ui.aggTypes.ipRanges.toLabel": "到", "common.ui.aggTypes.jsonInputTooltip": "此处以 JSON 格式添加的任何属性将与此部分的 elasticsearch 聚合定义合并。例如,词聚合上的“shard_size”。", "common.ui.aggTypes.metricLabel": "指标", "common.ui.aggTypes.metrics.aggNotValidErrorMessage": "- 聚合无效 -", From e8b711d10ff7951f94cb9bb76658632fcf048308 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 3 Jun 2019 11:54:05 +0300 Subject: [PATCH 14/15] Update mask model, update TS, update aria labels --- .../controls/components/from_to_list.tsx | 19 ++---- .../controls/components/input_list.tsx | 29 +++++---- .../controls/components/mask_list.tsx | 60 +++++++++---------- 3 files changed, 51 insertions(+), 57 deletions(-) diff --git a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx index 2f594838d1041..99d689199e24c 100644 --- a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { EuiFieldText, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import Ipv4Address from '../../../utils/ipv4_address'; -import { InputList, InputListConfig, InputModel, InputObject } from './input_list'; +import { InputList, InputListConfig, InputModel, InputObject, InputItem } from './input_list'; const EMPTY_STRING = ''; @@ -30,15 +30,9 @@ export interface FromToObject extends InputObject { to?: string; } -interface FromToItem { - model: string; - value: string; - isInvalid: boolean; -} - type FromToModel = InputModel & { - from: FromToItem; - to: FromToItem; + from: InputItem; + to: InputItem; }; interface FromToListProps { @@ -68,11 +62,10 @@ function FromToList({ showValidation, onBlur, ...rest }: FromToListProps) { }, to: { value: item.to || EMPTY_STRING, model: item.to || EMPTY_STRING, isInvalid: false }, }), - getModel: (models: FromToModel[], index, modelName: 'from' | 'to') => models[index][modelName], getRemoveBtnAriaLabel: (item: FromToModel) => i18n.translate('common.ui.aggTypes.ipRanges.removeRangeAriaLabel', { defaultMessage: 'Remove the range of {from} to {to}', - values: { from: item.from.value, to: item.to.value }, + values: { from: item.from.value || '*', to: item.to.value || '*' }, }), onChangeFn: ({ from, to }: FromToModel) => { const result: FromToObject = {}; @@ -91,7 +84,7 @@ function FromToList({ showValidation, onBlur, ...rest }: FromToListProps) { object; - getModelValue(item: InputObject): {}; - getModel(models: InputModel[], index: number, modelName?: string): InputModel | InputItem; + defaultValue: InputItemModel; + defaultEmptyValue: InputItemModel; + validateClass: new (value: string) => { toString(): string }; + getModelValue(item: InputObject): InputItemModel; getRemoveBtnAriaLabel(model: InputModel): string; onChangeFn(model: InputModel): InputObject; hasInvalidValuesFn(model: InputModel): boolean; renderInputRow( model: InputModel, index: number, - onChangeFn: (index: number, value: string, modelName?: string) => void + onChangeFn: (index: number, value: string, modelName: string) => void ): React.ReactNode; validateModel( validateFn: (value: string | undefined, modelObj: InputItem) => void, @@ -50,18 +49,22 @@ export interface InputListConfig { } interface InputModelBase { id: string; - [key: string]: any; } export type InputObject = object; -interface InputItem { +export interface InputItem { model: string; value: string; isInvalid: boolean; } -export type InputModel = - | InputModelBase & { [model: string]: InputItem } - | InputModelBase & InputItem; +interface InputItemModel { + [model: string]: InputItem; +} + +// InputModel can have the following implementations: +// for Mask List - { id: 'someId', mask: { model: '', value: '', isInvalid: false }} +// for FromTo List - { id: 'someId', from: { model: '', value: '', isInvalid: false }, to: { model: '', value: '', isInvalid: false }} +export type InputModel = InputModelBase & InputItemModel; interface InputListProps { config: InputListConfig; @@ -95,8 +98,8 @@ function InputList({ config, list, onChange, setValidity }: InputListProps) { onChange(modelList.map(config.onChangeFn)); }; - const onChangeValue = (index: number, value: string, modelName?: string) => { - const range = config.getModel(models, index, modelName); + const onChangeValue = (index: number, value: string, modelName: string) => { + const range = models[index][modelName]; const { model, isInvalid } = validateValue(value); range.value = value; range.model = model; diff --git a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx index 84fb6b5a4f868..efb5ca7643c93 100644 --- a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx +++ b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { EuiFieldText, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { CidrMask } from '../../../utils/cidr_mask'; -import { InputList, InputListConfig, InputObject } from './input_list'; +import { InputList, InputListConfig, InputObject, InputModel, InputItem } from './input_list'; const EMPTY_STRING = ''; @@ -29,12 +29,9 @@ export interface MaskObject extends InputObject { mask?: string; } -interface MaskModel { - id: string; - model: string; - value: string; - isInvalid: boolean; -} +type MaskModel = InputModel & { + mask: InputItem; +}; interface MaskListProps { list: MaskObject[]; @@ -47,53 +44,54 @@ interface MaskListProps { function MaskList({ showValidation, onBlur, ...rest }: MaskListProps) { const maskListConfig: InputListConfig = { defaultValue: { - model: '0.0.0.0/1', - value: '0.0.0.0/1', - isInvalid: false, + mask: { model: '0.0.0.0/1', value: '0.0.0.0/1', isInvalid: false }, }, defaultEmptyValue: { - model: EMPTY_STRING, - value: EMPTY_STRING, - isInvalid: false, + mask: { model: EMPTY_STRING, value: EMPTY_STRING, isInvalid: false }, }, validateClass: CidrMask, getModelValue: (item: MaskObject) => ({ - model: item.mask || EMPTY_STRING, - value: item.mask || EMPTY_STRING, - isInvalid: false, + mask: { + model: item.mask || EMPTY_STRING, + value: item.mask || EMPTY_STRING, + isInvalid: false, + }, }), - getModel: (models: MaskModel[], index) => models[index], getRemoveBtnAriaLabel: (item: MaskModel) => - i18n.translate('common.ui.aggTypes.ipRanges.removeCidrMaskButtonAriaLabel', { - defaultMessage: 'Remove the CIDR mask value of {mask}', - values: { mask: item.value }, - }), - onChangeFn: ({ model }: MaskModel) => { - if (model) { - return { mask: model }; + item.mask.value + ? i18n.translate('common.ui.aggTypes.ipRanges.removeCidrMaskButtonAriaLabel', { + defaultMessage: 'Remove the CIDR mask value of {mask}', + values: { mask: item.mask.value }, + }) + : i18n.translate('common.ui.aggTypes.ipRanges.removeEmptyCidrMaskButtonAriaLabel', { + defaultMessage: 'Remove the CIDR mask default value', + }), + onChangeFn: ({ mask }: MaskModel) => { + if (mask.model) { + return { mask: mask.model }; } return {}; }, - hasInvalidValuesFn: ({ isInvalid }) => isInvalid, - renderInputRow: (item: MaskModel, index, onChangeValue) => ( + hasInvalidValuesFn: ({ mask }) => mask.isInvalid, + renderInputRow: ({ mask }: MaskModel, index, onChangeValue) => ( { - onChangeValue(index, ev.target.value); + onChangeValue(index, ev.target.value, 'mask'); }} - value={item.value} + value={mask.value} onBlur={onBlur} /> ), validateModel: (validateFn, object: MaskObject, model: MaskModel) => { - validateFn(object.mask, model); + validateFn(object.mask, model.mask); }, }; From 640d07547aeffbbfc6aa8ce163a68e51cbf369b8 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 3 Jun 2019 15:04:04 +0300 Subject: [PATCH 15/15] Add validation for CIRD mask --- src/legacy/ui/public/utils/__tests__/cidr_mask.ts | 2 ++ src/legacy/ui/public/utils/cidr_mask.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/legacy/ui/public/utils/__tests__/cidr_mask.ts b/src/legacy/ui/public/utils/__tests__/cidr_mask.ts index 5343328e55786..5277344448bd8 100644 --- a/src/legacy/ui/public/utils/__tests__/cidr_mask.ts +++ b/src/legacy/ui/public/utils/__tests__/cidr_mask.ts @@ -43,6 +43,8 @@ describe('CidrMask', () => { expect(() => new CidrMask('0.0.0.0/32/32')).to.throwError(); expect(() => new CidrMask('1.2.3/1')).to.throwError(); + + expect(() => new CidrMask('0.0.0.0/123d')).to.throwError(); }); it('should correctly grab IP address and prefix length', () => { diff --git a/src/legacy/ui/public/utils/cidr_mask.ts b/src/legacy/ui/public/utils/cidr_mask.ts index 8dee0ab4e0c12..856fe6fd221eb 100644 --- a/src/legacy/ui/public/utils/cidr_mask.ts +++ b/src/legacy/ui/public/utils/cidr_mask.ts @@ -35,7 +35,7 @@ export class CidrMask { } this.initialAddress = new Ipv4Address(splits[0]); this.prefixLength = Number(splits[1]); - if (this.prefixLength < 1 || this.prefixLength > NUM_BITS) { + if (isNaN(this.prefixLength) || this.prefixLength < 1 || this.prefixLength > NUM_BITS) { throwError(mask); } }