Browse Source

SONAR-14498 Better JSON field

tags/8.8.0.42792
Jeremy Davis 3 years ago
parent
commit
17f2321385

+ 71
- 0
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForJSON.tsx View File

@@ -0,0 +1,71 @@
/*
* SonarQube
* Copyright (C) 2009-2021 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { Button } from 'sonar-ui-common/components/controls/buttons';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { DefaultSpecializedInputProps } from '../../utils';

const JSON_SPACE_SIZE = 4;

interface State {
formatError: boolean;
}

export default class InputForJSON extends React.PureComponent<DefaultSpecializedInputProps, State> {
state: State = { formatError: false };

handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
this.setState({ formatError: false });
this.props.onChange(event.target.value);
};

format = () => {
const { value } = this.props;
try {
if (value) {
this.props.onChange(JSON.stringify(JSON.parse(value), undefined, JSON_SPACE_SIZE));
}
} catch (e) {
this.setState({ formatError: true });
}
};

render() {
const { formatError } = this.state;
return (
<div className="display-flex-end">
<textarea
className="settings-large-input text-top monospaced spacer-right"
name={this.props.name}
onChange={this.handleInputChange}
rows={5}
value={this.props.value || ''}
/>
<div>
{formatError && <Alert variant="info">{translate('settings.json.format_error')} </Alert>}
<Button className="spacer-top" onClick={this.format}>
{translate('settings.json.format')}
</Button>
</div>
</div>
);
}
}

+ 2
- 1
server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.tsx View File

@@ -26,6 +26,7 @@ import {
isDefaultOrInherited
} from '../../utils';
import InputForBoolean from './InputForBoolean';
import InputForJSON from './InputForJSON';
import InputForNumber from './InputForNumber';
import InputForPassword from './InputForPassword';
import InputForSingleSelectList from './InputForSingleSelectList';
@@ -37,7 +38,7 @@ const typeMapping: {
} = {
STRING: InputForString,
TEXT: InputForText,
JSON: InputForText,
JSON: InputForJSON,
PASSWORD: InputForPassword,
BOOLEAN: InputForBoolean,
INTEGER: InputForNumber,

+ 72
- 0
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForJSON-test.tsx View File

@@ -0,0 +1,72 @@
/*
* SonarQube
* Copyright (C) 2009-2021 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { change } from 'sonar-ui-common/helpers/testUtils';
import { DefaultSpecializedInputProps } from '../../../utils';
import InputForJSON from '../InputForJSON';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should call onChange', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange });

change(wrapper.find('textarea'), '{"a": 1}');
expect(onChange).toBeCalledWith('{"a": 1}');
});

it('should handle formatting for invalid JSON', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange, value: '{"a": 1b}' });
wrapper.instance().format();
expect(onChange).not.toBeCalled();

expect(wrapper.state().formatError).toBe(true);
expect(wrapper).toMatchSnapshot();
});

it('should handle formatting for valid JSON', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange, value: '{"a": 1}' });
wrapper.instance().format();
expect(onChange).toBeCalledWith(`{
"a": 1
}`);

expect(wrapper.state().formatError).toBe(false);
});

it('should handle ignore formatting if empty', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange, value: '' });
wrapper.instance().format();
expect(onChange).not.toBeCalled();

expect(wrapper.state().formatError).toBe(false);
});

function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
return shallow<InputForJSON>(
<InputForJSON isDefault={false} name="foo" onChange={jest.fn()} value="" {...props} />
);
}

+ 51
- 0
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/__snapshots__/InputForJSON-test.tsx.snap View File

@@ -0,0 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should handle formatting for invalid JSON 1`] = `
<div
className="display-flex-end"
>
<textarea
className="settings-large-input text-top monospaced spacer-right"
name="foo"
onChange={[Function]}
rows={5}
value="{\\"a\\": 1b}"
/>
<div>
<Alert
variant="info"
>
settings.json.format_error
</Alert>
<Button
className="spacer-top"
onClick={[Function]}
>
settings.json.format
</Button>
</div>
</div>
`;

exports[`should render correctly 1`] = `
<div
className="display-flex-end"
>
<textarea
className="settings-large-input text-top monospaced spacer-right"
name="foo"
onChange={[Function]}
rows={5}
value=""
/>
<div>
<Button
className="spacer-top"
onClick={[Function]}
>
settings.json.format
</Button>
</div>
</div>
`;

+ 3
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -1050,6 +1050,9 @@ settings.default.password=<password>
settings.reset_confirm.title=Reset Setting
settings.reset_confirm.description=Are you sure that you want to reset this setting?

settings.json.format=Format JSON
settings.json.format_error=Formatting requires valid JSON. Please fix it and retry.

settings.analysis_scope.wildcards.introduction=You can use the following wildcards.
settings.analysis_scope.wildcards.zero_more_char=Match zero or more characters
settings.analysis_scope.wildcards.zero_more_dir=Match zero or more directories

Loading…
Cancel
Save