[Vis: Default editor] EUIficate raw json control (#32888) (#34039)

* EUIficate raw json control

* Remove unused validate-json directive

* Update tests

* Update styles

* Move validation logic down to control

* Fix type
This commit is contained in:
Maryia Lapata 2019-03-28 12:59:41 +03:00 committed by GitHub
parent 8e7f1a3ce2
commit 4f7ba1111e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 203 additions and 204 deletions

View file

@ -28,7 +28,6 @@ import dateMath from '@elastic/datemath';
import 'ui/doc_table';
import 'ui/visualize';
import 'ui/fixed_scroll';
import 'ui/directives/validate_json';
import 'ui/filters/moment';
import 'ui/index_patterns';
import 'ui/state_management/app_state';

View file

@ -0,0 +1,49 @@
/*
* 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 { isValidJson } from '../utils';
const input = {
valid: '{ "test": "json input" }',
invalid: 'strings are not json',
};
describe('AggType utils', () => {
describe('isValidJson', () => {
it('should return true when empty string', () => {
expect(isValidJson('')).toBe(true);
});
it('should return true when undefine', () => {
expect(isValidJson(undefined as any)).toBe(true);
});
it('should return false when invalid string', () => {
expect(isValidJson(input.invalid)).toBe(false);
});
it('should return true when valid string', () => {
expect(isValidJson(input.valid)).toBe(true);
});
it('should return false if a number', () => {
expect(isValidJson('0')).toBe(false);
});
});
});

View file

@ -1,23 +0,0 @@
<div class="form-group regex">
<span class="hintbox-label">
<label for="visEditorRawJson{{agg.id}}">
<span
i18n-id="common.ui.aggTypes.jsonInputLabel"
i18n-default-message="JSON Input"
></span>
<icon-tip
position="'right'"
content="::'common.ui.aggTypes.jsonInputTooltip' | i18n: { defaultMessage: 'Any JSON formatted properties you add here will be merged with the elasticsearch aggregation definition for this section. For example \'shard_size\' on a terms aggregation.' }"
></icon-tip>
</label>
</span>
<p>
<textarea
type="text"
id="visEditorRawJson{{agg.id}}"
class="form-control"
ng-model="agg.params.json"
validate-json
></textarea>
</p>
</div>

View file

@ -0,0 +1,76 @@
/*
* 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 { EuiFormRow, EuiIconTip, EuiTextArea } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { AggParamEditorProps } from '../../vis/editors/default';
import { isValidJson } from '../utils';
function RawJsonParamEditor({
agg,
value,
setValue,
isInvalid,
setValidity,
}: AggParamEditorProps<string>) {
const label = (
<>
<FormattedMessage id="common.ui.aggTypes.jsonInputLabel" defaultMessage="JSON input" />{' '}
<EuiIconTip
position="right"
content={i18n.translate('common.ui.aggTypes.jsonInputTooltip', {
defaultMessage:
"Any JSON formatted properties you add here will be merged with the elasticsearch aggregation definition for this section. For example 'shard_size' on a terms aggregation.",
})}
type="questionInCircle"
/>
</>
);
const onChange = (ev: React.ChangeEvent<HTMLTextAreaElement>) => {
const textValue = ev.target.value;
setValue(textValue);
setValidity(isValidJson(textValue));
};
setValidity(isValidJson(value));
return (
<EuiFormRow
label={label}
isInvalid={isInvalid}
fullWidth={true}
className="visEditorSidebar__aggParamFormRow"
>
<EuiTextArea
id={`visEditorRawJson${agg.id}`}
isInvalid={isInvalid}
value={value || ''}
onChange={onChange}
rows={2}
fullWidth={true}
/>
</EuiFormRow>
);
}
export { RawJsonParamEditor };

View file

@ -18,7 +18,7 @@
*/
import _ from 'lodash';
import editorHtml from '../controls/raw_json.html';
import { RawJsonParamEditor } from '../controls/raw_json';
import { BaseParamType } from './base';
import { createLegacyClass } from '../../utils/legacy_class';
@ -30,7 +30,7 @@ function JsonParamType(config) {
JsonParamType.Super.call(this, config);
}
JsonParamType.prototype.editor = editorHtml;
JsonParamType.prototype.editorComponent = RawJsonParamEditor;
/**
* Write the aggregation parameter.

View file

@ -0,0 +1,39 @@
/*
* 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.
*/
function isValidJson(value: string): boolean {
if (!value || value.length === 0) {
return true;
}
const trimmedValue = value.trim();
if (trimmedValue[0] === '{' || trimmedValue[0] === '[') {
try {
JSON.parse(trimmedValue);
return true;
} catch (e) {
return false;
}
} else {
return false;
}
}
export { isValidJson };

View file

@ -1,111 +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 angular from 'angular';
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import '../validate_json';
// Load the kibana app dependencies.
let $parentScope;
let $elemScope;
let $elem;
const mockScope = '';
const input = {
valid: '{ "test": "json input" }',
invalid: 'strings are not json'
};
const markup = {
textarea: '<textarea ng-model="mockModel" validate-json></textarea>',
input: '<input type="text" ng-model="mockModel" validate-json>'
};
const init = function (type) {
// Load the application
ngMock.module('kibana');
type = type || 'input';
const elMarkup = markup[type];
// Create the scope
ngMock.inject(function ($injector, $rootScope, $compile) {
// Give us a scope
$parentScope = $rootScope;
$parentScope.mockModel = mockScope;
$elem = angular.element(elMarkup);
$compile($elem)($parentScope);
$elemScope = $elem.isolateScope();
});
};
describe('validate-json directive', function () {
const checkValid = function (inputVal, className) {
$parentScope.mockModel = inputVal;
$elem.scope().$digest();
expect($elem.hasClass(className)).to.be(true);
};
describe('initialization', function () {
beforeEach(function () {
init();
});
it('should use the model', function () {
expect($elemScope).to.have.property('ngModel');
});
});
Object.keys(markup).forEach(function (inputType) {
describe(inputType, function () {
beforeEach(function () {
init(inputType);
});
it('should be an input', function () {
expect($elem.get(0).tagName).to.be(inputType.toUpperCase());
});
it('should set valid state', function () {
checkValid(input.valid, 'ng-valid');
});
it('should be valid when empty', function () {
checkValid('', 'ng-valid');
});
it('should set invalid state', function () {
checkValid(input.invalid, 'ng-invalid');
});
it('should be invalid if a number', function () {
checkValid('0', 'ng-invalid');
});
it('should update validity on changes', function () {
checkValid(input.valid, 'ng-valid');
checkValid(input.invalid, 'ng-invalid');
checkValid(input.valid, 'ng-valid');
});
});
});
});

View file

@ -1,64 +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 { uiModules } from '../modules';
const module = uiModules.get('kibana');
module.directive('validateJson', function () {
return {
restrict: 'A',
require: 'ngModel',
scope: {
'ngModel': '=',
'queryInput': '=?',
},
link: function ($scope, $elem, attr, ngModel) {
$scope.$watch('ngModel', validator);
function validator(newValue) {
if (!newValue || newValue.length === 0) {
setValid();
return;
}
// We actually need a proper object in all JSON inputs
newValue = (newValue || '').trim();
if (newValue[0] === '{' || newValue[0] === '[') {
try {
JSON.parse(newValue);
setValid();
} catch (e) {
setInvalid();
}
} else {
setInvalid();
}
}
function setValid() {
ngModel.$setValidity('jsonInput', true);
}
function setInvalid() {
ngModel.$setValidity('jsonInput', false);
}
}
};
});

View file

@ -221,3 +221,6 @@
}
}
.visEditorSidebar__aggParamFormRow {
margin-bottom: $euiSizeS;
}

View file

@ -29,7 +29,9 @@ uiModules
['aggParam', { watchDepth: 'reference' }],
['paramEditor', { wrapApply: false }],
['onChange', { watchDepth: 'reference' }],
['setValidity', { watchDepth: 'reference' }],
'value',
'isInvalid'
]))
.directive('visAggParamEditor', function (config) {
return {
@ -54,6 +56,8 @@ uiModules
agg-param="aggParam"
on-change="onChange"
value="paramValue"
is-invalid="isInvalid"
set-validity="setValidity"
></vis-agg-param-react-wrapper>`;
}
@ -93,6 +97,13 @@ uiModules
ngModelCtrl.$setDirty();
}
};
$scope.setValidity = (isValid) => {
if(ngModelCtrl) {
$scope.isInvalid = !isValid;
ngModelCtrl.$setValidity(`agg${$scope.agg.id}${$scope.aggParam.name}`, isValid);
}
};
}
}
};

View file

@ -28,5 +28,7 @@ export interface AggParamEditorProps<T> {
agg: AggConfig;
aggParam: AggParam;
value: T;
isInvalid: boolean;
setValue(value: T): void;
setValidity(isValid: boolean): void;
}

View file

@ -28,12 +28,31 @@ interface AggParamReactWrapperProps<T> {
aggParam: AggParam;
paramEditor: React.FunctionComponent<AggParamEditorProps<T>>;
value: T;
isInvalid: boolean;
onChange(value: T): void;
setValidity(isValid: boolean): void;
}
function AggParamReactWrapper<T>(props: AggParamReactWrapperProps<T>) {
const { agg, aggParam, paramEditor: ParamEditor, onChange, value } = props;
return <ParamEditor value={value} setValue={onChange} aggParam={aggParam} agg={agg} />;
const {
agg,
aggParam,
paramEditor: ParamEditor,
onChange,
value,
isInvalid,
setValidity,
} = props;
return (
<ParamEditor
value={value}
setValue={onChange}
aggParam={aggParam}
agg={agg}
isInvalid={isInvalid}
setValidity={setValidity}
/>
);
}
export { AggParamReactWrapper };

View file

@ -161,7 +161,6 @@
"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": "指标",
"common.ui.aggTypes.metrics.aggNotValidErrorMessage": "- 聚合无效 -",