aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2021-09-08 16:51:02 +0200
committersonartech <sonartech@sonarsource.com>2021-09-15 20:03:23 +0000
commit805294024fab786fca6076daeadd18afaadef26b (patch)
tree57aae18c8cceff39aa91a065b8a6ce098cade991 /server/sonar-web/src/main/js
parentb8ed6aa4f3025e69be637ac47d0fb26643a69377 (diff)
downloadsonarqube-805294024fab786fca6076daeadd18afaadef26b.tar.gz
sonarqube-805294024fab786fca6076daeadd18afaadef26b.zip
SONAR-15376 Make secured settings hidden when set
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/apps/about/actions.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts12
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/Definition.tsx40
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/SettingsApp.tsx (renamed from server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx)4
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-test.tsx (renamed from server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx)6
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Definition-test.tsx.snap84
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsApp-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap)0
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/Input.tsx29
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/InputForNumber.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.tsx75
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSecured.tsx103
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/InputForString.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.tsx63
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.tsx41
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/Input-test.tsx60
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForBoolean-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForJSON-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForNumber-test.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.tsx80
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSecured-test.tsx96
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForString-test.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/MultiValueInput-test.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/PrimitiveInput-test.tsx46
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/__snapshots__/PrimitiveInput-test.tsx.snap320
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap60
-rw-r--r--server/sonar-web/src/main/js/apps/settings/routes.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/store/__tests__/actions-test.ts42
-rw-r--r--server/sonar-web/src/main/js/apps/settings/store/__tests__/rootReducer-test.ts42
-rw-r--r--server/sonar-web/src/main/js/apps/settings/store/actions.ts12
-rw-r--r--server/sonar-web/src/main/js/apps/settings/store/rootReducer.ts15
-rw-r--r--server/sonar-web/src/main/js/apps/settings/store/values.ts10
-rw-r--r--server/sonar-web/src/main/js/apps/settings/utils.ts10
-rw-r--r--server/sonar-web/src/main/js/helpers/mocks/settings.ts21
-rw-r--r--server/sonar-web/src/main/js/types/settings.ts28
46 files changed, 1025 insertions, 418 deletions
diff --git a/server/sonar-web/src/main/js/apps/about/actions.ts b/server/sonar-web/src/main/js/apps/about/actions.ts
index 02c0e9fea40..b1cd8ec733f 100644
--- a/server/sonar-web/src/main/js/apps/about/actions.ts
+++ b/server/sonar-web/src/main/js/apps/about/actions.ts
@@ -25,7 +25,7 @@ export function fetchAboutPageSettings() {
return (dispatch: Dispatch) => {
const keys = ['sonar.lf.aboutText'];
return getValues({ keys: keys.join() }).then(values => {
- dispatch(receiveValues(values));
+ dispatch(receiveValues(keys, values));
});
};
}
diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx b/server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx
index b33011d4050..651c516b531 100644
--- a/server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx
+++ b/server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx
@@ -47,7 +47,7 @@ export class AuditApp extends React.PureComponent<Props, State> {
componentDidMount() {
const { hasGovernanceExtension } = this.props;
if (hasGovernanceExtension) {
- this.props.fetchValues('sonar.dbcleaner.auditHousekeeping');
+ this.props.fetchValues(['sonar.dbcleaner.auditHousekeeping']);
}
}
diff --git a/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts
index 38d70015b55..5e9861a8cdd 100644
--- a/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts
+++ b/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts
@@ -21,7 +21,8 @@ import { mockDefinition } from '../../../helpers/mocks/settings';
import {
Setting,
SettingCategoryDefinition,
- SettingFieldDefinition
+ SettingFieldDefinition,
+ SettingType
} from '../../../types/settings';
import { buildSettingLink, getDefaultValue, getEmptyValue } from '../utils';
@@ -42,7 +43,7 @@ describe('#getEmptyValue()', () => {
it('should work for property sets', () => {
const setting: SettingCategoryDefinition = {
...settingDefinition,
- type: 'PROPERTY_SET',
+ type: SettingType.PROPERTY_SET,
fields
};
expect(getEmptyValue(setting)).toEqual([{ foo: '', bar: null }]);
@@ -51,7 +52,7 @@ describe('#getEmptyValue()', () => {
it('should work for multi values string', () => {
const setting: SettingCategoryDefinition = {
...settingDefinition,
- type: 'STRING',
+ type: SettingType.STRING,
multiValues: true
};
expect(getEmptyValue(setting)).toEqual(['']);
@@ -60,7 +61,7 @@ describe('#getEmptyValue()', () => {
it('should work for multi values boolean', () => {
const setting: SettingCategoryDefinition = {
...settingDefinition,
- type: 'BOOLEAN',
+ type: SettingType.BOOLEAN,
multiValues: true
};
expect(getEmptyValue(setting)).toEqual([null]);
@@ -75,7 +76,8 @@ describe('#getDefaultValue()', () => {
'should work for boolean field when passing "%s"',
(parentValue?: string, expected?: string) => {
const setting: Setting = {
- definition: { key: 'test', options: [], type: 'BOOLEAN' },
+ hasValue: true,
+ definition: { key: 'test', options: [], type: SettingType.BOOLEAN },
parentValue,
key: 'test'
};
diff --git a/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx b/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx
index 7f9994de439..f237d48a304 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx
@@ -189,26 +189,26 @@ export class Definition extends React.PureComponent<Props, State> {
</span>
)}
</div>
-
- <Input
- hasValueChanged={hasValueChanged}
- onCancel={this.handleCancel}
- onChange={this.handleChange}
- onSave={this.handleSave}
- setting={setting}
- value={effectiveValue}
- />
-
- <DefinitionActions
- changedValue={changedValue}
- hasError={hasError}
- hasValueChanged={hasValueChanged}
- isDefault={isDefault}
- onCancel={this.handleCancel}
- onReset={this.handleReset}
- onSave={this.handleSave}
- setting={setting}
- />
+ <form>
+ <Input
+ hasValueChanged={hasValueChanged}
+ onCancel={this.handleCancel}
+ onChange={this.handleChange}
+ onSave={this.handleSave}
+ setting={setting}
+ value={effectiveValue}
+ />
+ <DefinitionActions
+ changedValue={changedValue}
+ hasError={hasError}
+ hasValueChanged={hasValueChanged}
+ isDefault={isDefault}
+ onCancel={this.handleCancel}
+ onReset={this.handleReset}
+ onSave={this.handleSave}
+ setting={setting}
+ />
+ </form>
</div>
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx b/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx
index 6ccf74d70a2..c995f082b7f 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx
@@ -22,7 +22,7 @@ import { Button, ResetButtonLink, SubmitButton } from '../../../components/contr
import Modal from '../../../components/controls/Modal';
import { translate } from '../../../helpers/l10n';
import { Setting } from '../../../types/settings';
-import { getDefaultValue, getSettingValue, isEmptyValue } from '../utils';
+import { getDefaultValue, isEmptyValue } from '../utils';
type Props = {
changedValue: string;
@@ -74,13 +74,10 @@ export default class DefinitionActions extends React.PureComponent<Props, State>
}
render() {
- const { setting, isDefault, changedValue, hasValueChanged } = this.props;
-
- const hasValueToResetTo = !isEmptyValue(setting.definition, getSettingValue(setting));
+ const { setting, changedValue, isDefault, hasValueChanged } = this.props;
const hasBeenChangedToEmptyValue =
changedValue != null && isEmptyValue(setting.definition, changedValue);
- const showReset =
- hasValueToResetTo && (hasBeenChangedToEmptyValue || (!isDefault && !hasValueChanged));
+ const showReset = hasBeenChangedToEmptyValue || (!isDefault && setting.hasValue);
return (
<>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx b/server/sonar-web/src/main/js/apps/settings/components/SettingsApp.tsx
index 685699d15a6..5149787010a 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/SettingsApp.tsx
@@ -50,7 +50,7 @@ interface State {
loading: boolean;
}
-export class App extends React.PureComponent<Props & WithRouterProps, State> {
+export class SettingsApp extends React.PureComponent<Props & WithRouterProps, State> {
mounted = false;
state: State = { loading: true };
@@ -150,4 +150,4 @@ const mapStateToProps = (state: Store) => ({
const mapDispatchToProps = { fetchSettings: fetchSettings as any };
-export default connect(mapStateToProps, mapDispatchToProps)(App);
+export default connect(mapStateToProps, mapDispatchToProps)(SettingsApp);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx
index dfec70e0e86..be3b88a7c78 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx
@@ -75,7 +75,7 @@ export class SubCategoryDefinitionsList extends React.PureComponent<
};
fetchValues() {
- const keys = this.props.settings.map(setting => setting.definition.key).join();
+ const keys = this.props.settings.map(setting => setting.definition.key);
return this.props.fetchValues(keys, this.props.component && this.props.component.key);
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-test.tsx
index 56841809b8b..06ec031c4fb 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-test.tsx
@@ -22,6 +22,8 @@ import * as React from 'react';
import { mockSetting } from '../../../../helpers/mocks/settings';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { Definition } from '../Definition';
+import DefinitionActions from '../DefinitionActions';
+import Input from '../inputs/Input';
const setting = mockSetting();
@@ -42,7 +44,7 @@ it('should correctly handle change of value', () => {
const changeValue = jest.fn();
const checkValue = jest.fn();
const wrapper = shallowRender({ changeValue, checkValue });
- wrapper.find('Input').prop<Function>('onChange')(5);
+ wrapper.find(Input).prop<Function>('onChange')(5);
expect(changeValue).toHaveBeenCalledWith(setting.definition.key, 5);
expect(checkValue).toHaveBeenCalledWith(setting.definition.key);
});
@@ -51,7 +53,7 @@ it('should correctly cancel value change', () => {
const cancelChange = jest.fn();
const passValidation = jest.fn();
const wrapper = shallowRender({ cancelChange, passValidation });
- wrapper.find('Input').prop<Function>('onCancel')();
+ wrapper.find(Input).prop<Function>('onCancel')();
expect(cancelChange).toHaveBeenCalledWith(setting.definition.key);
expect(passValidation).toHaveBeenCalledWith(setting.definition.key);
});
@@ -59,7 +61,7 @@ it('should correctly cancel value change', () => {
it('should correctly save value change', async () => {
const saveValue = jest.fn().mockResolvedValue({});
const wrapper = shallowRender({ changedValue: 10, saveValue });
- wrapper.find('DefinitionActions').prop<Function>('onSave')();
+ wrapper.find(DefinitionActions).prop('onSave')();
await waitAndUpdate(wrapper);
expect(saveValue).toHaveBeenCalledWith(setting.definition.key, undefined);
expect(wrapper.find('AlertSuccessIcon').exists()).toBe(true);
@@ -72,7 +74,7 @@ it('should correctly reset', async () => {
const cancelChange = jest.fn();
const resetValue = jest.fn().mockResolvedValue({});
const wrapper = shallowRender({ cancelChange, changedValue: 10, resetValue });
- wrapper.find('DefinitionActions').prop<Function>('onReset')();
+ wrapper.find(DefinitionActions).prop('onReset')();
await waitAndUpdate(wrapper);
expect(resetValue).toHaveBeenCalledWith(setting.definition.key, undefined);
expect(cancelChange).toHaveBeenCalledWith(setting.definition.key);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx
index 76efb7f237d..cd408abedae 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx
@@ -19,7 +19,7 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { SettingCategoryDefinition } from '../../../../types/settings';
+import { SettingCategoryDefinition, SettingType } from '../../../../types/settings';
import DefinitionActions from '../DefinitionActions';
const definition: SettingCategoryDefinition = {
@@ -30,11 +30,12 @@ const definition: SettingCategoryDefinition = {
name: 'foobar',
options: [],
subCategory: 'bar',
- type: 'STRING'
+ type: SettingType.STRING
};
const settings = {
key: 'key',
+ hasValue: true,
definition,
value: 'baz'
};
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-test.tsx
index 03a755a6277..71752a0ae87 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-test.tsx
@@ -35,7 +35,7 @@ import {
NEW_CODE_PERIOD_CATEGORY,
PULL_REQUEST_DECORATION_BINDING_CATEGORY
} from '../AdditionalCategoryKeys';
-import { App } from '../AppContainer';
+import { SettingsApp } from '../SettingsApp';
jest.mock('../../../../helpers/pages', () => ({
addSideBarClass: jest.fn(),
@@ -105,9 +105,9 @@ it('should render pull request decoration binding correctly', async () => {
expect(wrapper).toMatchSnapshot();
});
-function shallowRender(props: Partial<App['props']> = {}) {
+function shallowRender(props: Partial<SettingsApp['props']> = {}) {
return shallow(
- <App
+ <SettingsApp
defaultCategory="general"
fetchSettings={jest.fn().mockResolvedValue({})}
location={mockLocation()}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Definition-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Definition-test.tsx.snap
index 1e27f6b5fa9..c7425dafd23 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Definition-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Definition-test.tsx.snap
@@ -34,50 +34,54 @@ exports[`should render correctly 1`] = `
<div
className="settings-definition-state"
/>
- <Input
- hasValueChanged={false}
- onCancel={[Function]}
- onChange={[Function]}
- onSave={[Function]}
- setting={
- Object {
- "definition": Object {
- "description": "When Foo then Bar",
+ <form>
+ <Input
+ hasValueChanged={false}
+ onCancel={[Function]}
+ onChange={[Function]}
+ onSave={[Function]}
+ setting={
+ Object {
+ "definition": Object {
+ "description": "When Foo then Bar",
+ "key": "foo",
+ "name": "Foo setting",
+ "options": Array [],
+ "type": "INTEGER",
+ },
+ "hasValue": true,
+ "inherited": true,
"key": "foo",
- "name": "Foo setting",
- "options": Array [],
- "type": "INTEGER",
- },
- "inherited": true,
- "key": "foo",
- "value": "42",
+ "value": "42",
+ }
}
- }
- value="42"
- />
- <DefinitionActions
- changedValue={null}
- hasError={false}
- hasValueChanged={false}
- isDefault={true}
- onCancel={[Function]}
- onReset={[Function]}
- onSave={[Function]}
- setting={
- Object {
- "definition": Object {
- "description": "When Foo then Bar",
+ value="42"
+ />
+ <DefinitionActions
+ changedValue={null}
+ hasError={false}
+ hasValueChanged={false}
+ isDefault={true}
+ onCancel={[Function]}
+ onReset={[Function]}
+ onSave={[Function]}
+ setting={
+ Object {
+ "definition": Object {
+ "description": "When Foo then Bar",
+ "key": "foo",
+ "name": "Foo setting",
+ "options": Array [],
+ "type": "INTEGER",
+ },
+ "hasValue": true,
+ "inherited": true,
"key": "foo",
- "name": "Foo setting",
- "options": Array [],
- "type": "INTEGER",
- },
- "inherited": true,
- "key": "foo",
- "value": "42",
+ "value": "42",
+ }
}
- }
- />
+ />
+ </form>
</div>
</div>
`;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsApp-test.tsx.snap
index 03291c1f12b..03291c1f12b 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsApp-test.tsx.snap
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap
index cc29119b1e9..c0449cc8a04 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap
@@ -28,6 +28,7 @@ exports[`should render correctly 1`] = `
"subCategory": "email",
"type": "INTEGER",
},
+ "hasValue": true,
"inherited": true,
"key": "foo",
"value": "42",
@@ -59,6 +60,7 @@ exports[`should render correctly 1`] = `
"options": Array [],
"subCategory": "qg",
},
+ "hasValue": true,
"inherited": true,
"key": "foo",
"value": "42",
@@ -96,6 +98,7 @@ exports[`should render correctly: subcategory 1`] = `
"options": Array [],
"subCategory": "qg",
},
+ "hasValue": true,
"inherited": true,
"key": "foo",
"value": "42",
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/Input.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/Input.tsx
index 32e18f71f19..953c4212556 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/Input.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/Input.tsx
@@ -18,21 +18,38 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { DefaultInputProps, isCategoryDefinition } from '../../utils';
+import { SettingType } from '../../../../types/settings';
+import {
+ DefaultInputProps,
+ DefaultSpecializedInputProps,
+ getUniqueName,
+ isCategoryDefinition,
+ isDefaultOrInherited,
+ isSecuredDefinition
+} from '../../utils';
+import InputForSecured from './InputForSecured';
import MultiValueInput from './MultiValueInput';
import PrimitiveInput from './PrimitiveInput';
import PropertySetInput from './PropertySetInput';
export default function Input(props: DefaultInputProps) {
- const { definition } = props.setting;
+ const { setting } = props;
+ const { definition } = setting;
+ const name = getUniqueName(definition);
+
+ let Input: React.ComponentType<DefaultSpecializedInputProps> = PrimitiveInput;
if (isCategoryDefinition(definition) && definition.multiValues) {
- return <MultiValueInput {...props} />;
+ Input = MultiValueInput;
+ }
+
+ if (definition.type === SettingType.PROPERTY_SET) {
+ Input = PropertySetInput;
}
- if (definition.type === 'PROPERTY_SET') {
- return <PropertySetInput {...props} />;
+ if (isSecuredDefinition(definition)) {
+ return <InputForSecured input={Input} {...props} />;
}
- return <PrimitiveInput {...props} />;
+ return <Input {...props} name={name} isDefault={isDefaultOrInherited(setting)} />;
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForNumber.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForNumber.tsx
index 2c5818efcdd..b7be3d7573c 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForNumber.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForNumber.tsx
@@ -22,5 +22,5 @@ import { DefaultSpecializedInputProps } from '../../utils';
import SimpleInput from './SimpleInput';
export default function InputForNumber(props: DefaultSpecializedInputProps) {
- return <SimpleInput {...props} className="input-small" type="text" />;
+ return <SimpleInput className="input-small" type="text" {...props} />;
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.tsx
index c63b3106ea4..b93e4ac2384 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.tsx
@@ -18,76 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { colors } from '../../../../app/theme';
-import { Button } from '../../../../components/controls/buttons';
-import LockIcon from '../../../../components/icons/LockIcon';
-import { translate } from '../../../../helpers/l10n';
import { DefaultSpecializedInputProps } from '../../utils';
+import SimpleInput from './SimpleInput';
-interface State {
- changing: boolean;
-}
-
-export default class InputForPassword extends React.PureComponent<
- DefaultSpecializedInputProps,
- State
-> {
- state: State = {
- changing: !this.props.value
- };
-
- componentWillReceiveProps(nextProps: DefaultSpecializedInputProps) {
- /*
- * Reset `changing` if:
- * - the value is reset (valueChanged -> !valueChanged)
- * or
- * - the value changes from outside the input (i.e. store update/reset/cancel)
- */
- if (
- (this.props.hasValueChanged || this.props.value !== nextProps.value) &&
- !nextProps.hasValueChanged
- ) {
- this.setState({ changing: !nextProps.value });
- }
- }
-
- handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
- this.props.onChange(event.target.value);
- };
-
- handleChangeClick = () => {
- this.setState({ changing: true });
- };
-
- renderInput() {
- return (
- <form>
- <input className="hidden" type="password" />
- <input
- autoComplete="off"
- autoFocus={this.state.changing && this.props.value}
- className="js-password-input settings-large-input text-top"
- name={this.props.name}
- onChange={this.handleInputChange}
- type="password"
- value={this.props.value}
- />
- </form>
- );
- }
-
- render() {
- if (this.state.changing) {
- return this.renderInput();
- }
-
- return (
- <>
- <LockIcon className="text-middle big-spacer-right" fill={colors.gray60} />
- <Button className="text-middle" onClick={this.handleChangeClick}>
- {translate('change_verb')}
- </Button>
- </>
- );
- }
+export default function InputForPassword(props: DefaultSpecializedInputProps) {
+ return (
+ <SimpleInput {...props} className="settings-large-input" type="password" autoComplete="off" />
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSecured.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSecured.tsx
new file mode 100644
index 00000000000..eddff155556
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSecured.tsx
@@ -0,0 +1,103 @@
+/*
+ * 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 { colors } from '../../../../app/theme';
+import { Button } from '../../../../components/controls/buttons';
+import LockIcon from '../../../../components/icons/LockIcon';
+import { translate } from '../../../../helpers/l10n';
+import {
+ DefaultInputProps,
+ DefaultSpecializedInputProps,
+ getUniqueName,
+ isDefaultOrInherited
+} from '../../utils';
+
+interface State {
+ changing: boolean;
+}
+
+interface Props extends DefaultInputProps {
+ input: React.ComponentType<DefaultSpecializedInputProps>;
+}
+
+export default class InputForSecured extends React.PureComponent<Props, State> {
+ state: State = {
+ changing: !this.props.setting.hasValue
+ };
+
+ componentWillReceiveProps(nextProps: Props) {
+ /*
+ * Reset `changing` if:
+ * - the value is reset (valueChanged -> !valueChanged)
+ * or
+ * - the value changes from outside the input (i.e. store update/reset/cancel)
+ */
+ if (
+ (this.props.hasValueChanged || this.props.setting !== nextProps.setting) &&
+ !nextProps.hasValueChanged
+ ) {
+ this.setState({ changing: !nextProps.setting.hasValue });
+ }
+ }
+
+ handleInputChange = (value: string) => {
+ this.props.onChange(value);
+ };
+
+ handleChangeClick = () => {
+ this.setState({ changing: true });
+ };
+
+ renderInput() {
+ const { input: Input, setting, value } = this.props;
+ const name = getUniqueName(setting.definition);
+ return (
+ // The input hidden will prevent browser asking for saving login information
+ <>
+ <input className="hidden" type="password" />
+ <Input
+ autoComplete="off"
+ className="js-setting-input settings-large-input"
+ isDefault={isDefaultOrInherited(setting)}
+ name={name}
+ onChange={this.handleInputChange}
+ setting={setting}
+ type="password"
+ value={value}
+ />
+ </>
+ );
+ }
+
+ render() {
+ if (this.state.changing) {
+ return this.renderInput();
+ }
+
+ return (
+ <>
+ <LockIcon className="text-middle big-spacer-right" fill={colors.gray60} />
+ <Button className="text-middle" onClick={this.handleChangeClick}>
+ {translate('change_verb')}
+ </Button>
+ </>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForString.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForString.tsx
index 32ab643f671..0084038c5de 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForString.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForString.tsx
@@ -22,5 +22,5 @@ import { DefaultSpecializedInputProps } from '../../utils';
import SimpleInput from './SimpleInput';
export default function InputForString(props: DefaultSpecializedInputProps) {
- return <SimpleInput {...props} className="settings-large-input" type="text" />;
+ return <SimpleInput className="settings-large-input" type="text" {...props} />;
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx
index e6bb87e44ae..6a40749310d 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx
@@ -19,10 +19,10 @@
*/
import * as React from 'react';
import { DeleteButton } from '../../../../components/controls/buttons';
-import { DefaultInputProps, getEmptyValue } from '../../utils';
+import { DefaultSpecializedInputProps, getEmptyValue } from '../../utils';
import PrimitiveInput from './PrimitiveInput';
-export default class MultiValueInput extends React.PureComponent<DefaultInputProps> {
+export default class MultiValueInput extends React.PureComponent<DefaultSpecializedInputProps> {
ensureValue = () => {
return this.props.value || [];
};
@@ -40,17 +40,15 @@ export default class MultiValueInput extends React.PureComponent<DefaultInputPro
};
renderInput(value: any, index: number, isLast: boolean) {
- const { setting } = this.props;
+ const { setting, isDefault, name } = this.props;
return (
<li className="spacer-bottom" key={index}>
<PrimitiveInput
+ isDefault={isDefault}
+ name={name}
hasValueChanged={this.props.hasValueChanged}
onChange={value => this.handleSingleInputChange(index, value)}
- setting={{
- ...setting,
- definition: { ...setting.definition, multiValues: false },
- values: undefined
- }}
+ setting={setting}
value={value}
/>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.tsx
index 59a221e3bc7..e3b94af38e8 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.tsx
@@ -19,12 +19,7 @@
*/
import * as React from 'react';
import { SettingType } from '../../../../types/settings';
-import {
- DefaultInputProps,
- DefaultSpecializedInputProps,
- getUniqueName,
- isDefaultOrInherited
-} from '../../utils';
+import { DefaultSpecializedInputProps } from '../../utils';
import InputForBoolean from './InputForBoolean';
import InputForJSON from './InputForJSON';
import InputForNumber from './InputForNumber';
@@ -33,42 +28,30 @@ import InputForSingleSelectList from './InputForSingleSelectList';
import InputForString from './InputForString';
import InputForText from './InputForText';
-const typeMapping: {
- [type in SettingType]?: React.ComponentType<DefaultSpecializedInputProps>;
-} = {
- STRING: InputForString,
- TEXT: InputForText,
- JSON: InputForJSON,
- PASSWORD: InputForPassword,
- BOOLEAN: InputForBoolean,
- INTEGER: InputForNumber,
- LONG: InputForNumber,
- FLOAT: InputForNumber
-};
-
-interface Props extends DefaultInputProps {
- name?: string;
+function withOptions(options: string[]): React.ComponentType<DefaultSpecializedInputProps> {
+ return function Wrapped(props: DefaultSpecializedInputProps) {
+ return <InputForSingleSelectList options={options} {...props} />;
+ };
}
-export default class PrimitiveInput extends React.PureComponent<Props> {
- render() {
- const { setting, ...other } = this.props;
- const { definition } = setting;
-
- const name = this.props.name || getUniqueName(definition);
+export default function PrimitiveInput(props: DefaultSpecializedInputProps) {
+ const { setting, name, isDefault, ...other } = props;
+ const { definition } = setting;
+ const typeMapping: {
+ [type in SettingType]?: React.ComponentType<DefaultSpecializedInputProps>;
+ } = {
+ STRING: InputForString,
+ TEXT: InputForText,
+ JSON: InputForJSON,
+ PASSWORD: InputForPassword,
+ BOOLEAN: InputForBoolean,
+ INTEGER: InputForNumber,
+ LONG: InputForNumber,
+ FLOAT: InputForNumber,
+ SINGLE_SELECT_LIST: withOptions(definition.options)
+ };
- if (definition.type === 'SINGLE_SELECT_LIST') {
- return (
- <InputForSingleSelectList
- isDefault={isDefaultOrInherited(setting)}
- name={name}
- options={definition.options}
- {...other}
- />
- );
- }
+ const InputComponent = (definition.type && typeMapping[definition.type]) || InputForString;
- const InputComponent = (definition.type && typeMapping[definition.type]) || InputForString;
- return <InputComponent isDefault={isDefaultOrInherited(setting)} name={name} {...other} />;
- }
+ return <InputComponent isDefault={isDefault} name={name} setting={setting} {...other} />;
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.tsx
index 7db52e8b289..2b9efd2e7d9 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.tsx
@@ -19,10 +19,15 @@
*/
import * as React from 'react';
import { DeleteButton } from '../../../../components/controls/buttons';
-import { DefaultInputProps, getEmptyValue, getUniqueName, isCategoryDefinition } from '../../utils';
+import {
+ DefaultSpecializedInputProps,
+ getEmptyValue,
+ getUniqueName,
+ isCategoryDefinition
+} from '../../utils';
import PrimitiveInput from './PrimitiveInput';
-export default class PropertySetInput extends React.PureComponent<DefaultInputProps> {
+export default class PropertySetInput extends React.PureComponent<DefaultSpecializedInputProps> {
ensureValue() {
return this.props.value || [];
}
@@ -42,23 +47,31 @@ export default class PropertySetInput extends React.PureComponent<DefaultInputPr
};
renderFields(fieldValues: any, index: number, isLast: boolean) {
- const { setting } = this.props;
+ const { setting, isDefault } = this.props;
const { definition } = setting;
return (
<tr key={index}>
{isCategoryDefinition(definition) &&
- definition.fields.map(field => (
- <td key={field.key}>
- <PrimitiveInput
- hasValueChanged={this.props.hasValueChanged}
- name={getUniqueName(definition, field.key)}
- onChange={value => this.handleInputChange(index, field.key, value)}
- setting={{ ...setting, definition: field, value: fieldValues[field.key] }}
- value={fieldValues[field.key]}
- />
- </td>
- ))}
+ definition.fields.map(field => {
+ const newSetting = {
+ ...setting,
+ definition: field,
+ value: fieldValues[field.key]
+ };
+ return (
+ <td key={field.key}>
+ <PrimitiveInput
+ isDefault={isDefault}
+ hasValueChanged={this.props.hasValueChanged}
+ name={getUniqueName(definition, field.key)}
+ onChange={value => this.handleInputChange(index, field.key, value)}
+ setting={newSetting}
+ value={fieldValues[field.key]}
+ />
+ </td>
+ );
+ })}
<td className="thin nowrap text-middle">
{!isLast && (
<DeleteButton
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.tsx
index 20c1d0bad81..2c5df6a506f 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.tsx
@@ -22,8 +22,6 @@ import * as React from 'react';
import { DefaultSpecializedInputProps } from '../../utils';
interface Props extends DefaultSpecializedInputProps {
- className?: string;
- type: string;
value: string | number;
}
@@ -41,14 +39,17 @@ export default class SimpleInput extends React.PureComponent<Props> {
};
render() {
+ const { autoComplete, autoFocus, className, name, value = '', type } = this.props;
return (
<input
- className={classNames('text-top', this.props.className)}
- name={this.props.name}
+ autoComplete={autoComplete}
+ autoFocus={autoFocus}
+ className={classNames('text-top', className)}
+ name={name}
onChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
- type={this.props.type}
- value={this.props.value || ''}
+ type={type}
+ value={value}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/Input-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/Input-test.tsx
index c800f03dee0..4e5aac8760b 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/Input-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/Input-test.tsx
@@ -19,38 +19,42 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { Setting, SettingCategoryDefinition } from '../../../../../types/settings';
+import { mockDefinition, mockSetting } from '../../../../../helpers/mocks/settings';
+import { Setting, SettingType } from '../../../../../types/settings';
import { DefaultInputProps } from '../../../utils';
import Input from '../Input';
-
-const settingValue = {
- key: 'example'
-};
-
-const settingDefinition: SettingCategoryDefinition = {
- category: 'general',
- fields: [],
- key: 'example',
- options: [],
- subCategory: 'Branches',
- type: 'STRING'
-};
+import InputForSecured from '../InputForSecured';
+import MultiValueInput from '../MultiValueInput';
+import PrimitiveInput from '../PrimitiveInput';
+import PropertySetInput from '../PropertySetInput';
it('should render PrimitiveInput', () => {
- const setting = { ...settingValue, definition: settingDefinition };
const onChange = jest.fn();
- const input = shallowRender({ onChange, setting }).find('PrimitiveInput');
+ const input = shallowRender({ onChange }).find(PrimitiveInput);
+ expect(input.length).toBe(1);
+ expect(input.prop('value')).toBe('foo');
+ expect(input.prop('onChange')).toBe(onChange);
+});
+
+it('should render Secured input', () => {
+ const setting: Setting = mockSetting({
+ key: 'foo.secured',
+ definition: mockDefinition({ key: 'foo.secured', type: SettingType.PROPERTY_SET })
+ });
+ const onChange = jest.fn();
+ const input = shallowRender({ onChange, setting }).find(InputForSecured);
expect(input.length).toBe(1);
- expect(input.prop('setting')).toBe(setting);
expect(input.prop('value')).toBe('foo');
expect(input.prop('onChange')).toBe(onChange);
});
it('should render MultiValueInput', () => {
- const setting = { ...settingValue, definition: { ...settingDefinition, multiValues: true } };
+ const setting = mockSetting({
+ definition: mockDefinition({ multiValues: true })
+ });
const onChange = jest.fn();
const value = ['foo', 'bar'];
- const input = shallowRender({ onChange, setting, value }).find('MultiValueInput');
+ const input = shallowRender({ onChange, setting, value }).find(MultiValueInput);
expect(input.length).toBe(1);
expect(input.prop('setting')).toBe(setting);
expect(input.prop('value')).toBe(value);
@@ -58,14 +62,13 @@ it('should render MultiValueInput', () => {
});
it('should render PropertySetInput', () => {
- const setting: Setting = {
- ...settingValue,
- definition: { ...settingDefinition, type: 'PROPERTY_SET' }
- };
+ const setting: Setting = mockSetting({
+ definition: mockDefinition({ type: SettingType.PROPERTY_SET })
+ });
const onChange = jest.fn();
const value = [{ foo: 'bar' }];
- const input = shallowRender({ onChange, setting, value }).find('PropertySetInput');
+ const input = shallowRender({ onChange, setting, value }).find(PropertySetInput);
expect(input.length).toBe(1);
expect(input.prop('setting')).toBe(setting);
expect(input.prop('value')).toBe(value);
@@ -73,12 +76,5 @@ it('should render PropertySetInput', () => {
});
function shallowRender(props: Partial<DefaultInputProps> = {}) {
- return shallow(
- <Input
- onChange={jest.fn()}
- setting={{ ...settingValue, definition: settingDefinition }}
- value="foo"
- {...props}
- />
- );
+ return shallow(<Input onChange={jest.fn()} setting={mockSetting()} value="foo" {...props} />);
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForBoolean-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForBoolean-test.tsx
index 31685630cc7..65a8da2f87d 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForBoolean-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForBoolean-test.tsx
@@ -19,6 +19,7 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockSetting } from '../../../../../helpers/mocks/settings';
import { DefaultSpecializedInputProps } from '../../../utils';
import InputForBoolean from '../InputForBoolean';
@@ -57,6 +58,13 @@ it('should call onChange', () => {
function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
return shallow(
- <InputForBoolean isDefault={false} name="foo" onChange={jest.fn()} value={true} {...props} />
+ <InputForBoolean
+ isDefault={false}
+ name="foo"
+ onChange={jest.fn()}
+ setting={mockSetting()}
+ value={true}
+ {...props}
+ />
);
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForJSON-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForJSON-test.tsx
index 2884097b537..f91b74b8716 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForJSON-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForJSON-test.tsx
@@ -19,6 +19,7 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockSetting } from '../../../../../helpers/mocks/settings';
import { change } from '../../../../../helpers/testUtils';
import { DefaultSpecializedInputProps } from '../../../utils';
import InputForJSON from '../InputForJSON';
@@ -67,6 +68,13 @@ it('should handle ignore formatting if empty', () => {
function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
return shallow<InputForJSON>(
- <InputForJSON isDefault={false} name="foo" onChange={jest.fn()} value="" {...props} />
+ <InputForJSON
+ isDefault={false}
+ name="foo"
+ onChange={jest.fn()}
+ setting={mockSetting()}
+ value=""
+ {...props}
+ />
);
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForNumber-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForNumber-test.tsx
index ca1682699e2..ec65800f021 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForNumber-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForNumber-test.tsx
@@ -19,13 +19,20 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockSetting } from '../../../../../helpers/mocks/settings';
import InputForNumber from '../InputForNumber';
import SimpleInput from '../SimpleInput';
it('should render SimpleInput', () => {
const onChange = jest.fn();
const simpleInput = shallow(
- <InputForNumber isDefault={false} name="foo" onChange={onChange} value={17} />
+ <InputForNumber
+ isDefault={false}
+ name="foo"
+ onChange={onChange}
+ setting={mockSetting()}
+ value={17}
+ />
).find(SimpleInput);
expect(simpleInput.length).toBe(1);
expect(simpleInput.prop('name')).toBe('foo');
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.tsx
index bb1716d9879..216332ca6a2 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.tsx
@@ -19,78 +19,24 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { change, click, submit } from '../../../../../helpers/testUtils';
-import { DefaultSpecializedInputProps } from '../../../utils';
+import { mockSetting } from '../../../../../helpers/mocks/settings';
import InputForPassword from '../InputForPassword';
+import SimpleInput from '../SimpleInput';
-it('should render lock icon, but no form', () => {
+it('should render SimpleInput', () => {
const onChange = jest.fn();
- const input = shallowRender({ onChange });
-
- expect(input.find('LockIcon').length).toBe(1);
- expect(input.find('form').length).toBe(0);
-});
-
-it('should open form', () => {
- const onChange = jest.fn();
- const input = shallowRender({ onChange });
- const button = input.find('Button');
- expect(button.length).toBe(1);
-
- click(button);
- expect(input.find('form').length).toBe(1);
-});
-
-it('should set value', () => {
- const onChange = jest.fn(() => Promise.resolve());
- const input = shallowRender({ onChange });
-
- click(input.find('Button'));
- change(input.find('.js-password-input'), 'secret');
- submit(input.find('form'));
- expect(onChange).toBeCalledWith('secret');
-});
-
-it('should show form when empty, and enable handle typing', () => {
- const input = shallowRender({ value: '' });
- const onChange = (value: string) => input.setProps({ hasValueChanged: true, value });
- input.setProps({ onChange });
-
- expect(input.find('form').length).toBe(1);
- change(input.find('input.js-password-input'), 'hello');
- expect(input.find('form').length).toBe(1);
- expect(input.find('input.js-password-input').prop('value')).toBe('hello');
-});
-
-it('should handle value reset', () => {
- const input = shallowRender({ hasValueChanged: true, value: 'whatever' });
- input.setState({ changing: true });
-
- // reset
- input.setProps({ hasValueChanged: false, value: 'original' });
-
- expect(input.state('changing')).toBe(false);
-});
-
-it('should handle value reset to empty', () => {
- const input = shallowRender({ hasValueChanged: true, value: 'whatever' });
- input.setState({ changing: true });
-
- // outside change
- input.setProps({ hasValueChanged: false, value: '' });
-
- expect(input.state('changing')).toBe(true);
-});
-
-function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
- return shallow<InputForPassword>(
+ const simpleInput = shallow(
<InputForPassword
- hasValueChanged={false}
isDefault={false}
name="foo"
- onChange={jest.fn()}
+ onChange={onChange}
+ setting={mockSetting()}
value="bar"
- {...props}
/>
- );
-}
+ ).find(SimpleInput);
+ expect(simpleInput.length).toBe(1);
+ expect(simpleInput.prop('name')).toBe('foo');
+ expect(simpleInput.prop('value')).toBe('bar');
+ expect(simpleInput.prop('type')).toBe('password');
+ expect(simpleInput.prop('onChange')).toBeDefined();
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSecured-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSecured-test.tsx
new file mode 100644
index 00000000000..1d9eeb30f56
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSecured-test.tsx
@@ -0,0 +1,96 @@
+/*
+ * 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 { mockSetting } from '../../../../../helpers/mocks/settings';
+import { change, click } from '../../../../../helpers/testUtils';
+import InputForSecured from '../InputForSecured';
+import InputForString from '../InputForString';
+
+it('should render lock icon, but no form', () => {
+ const onChange = jest.fn();
+ const input = shallowRender({ onChange });
+
+ expect(input.find('LockIcon').length).toBe(1);
+ expect(input.find('input').length).toBe(0);
+});
+
+it('should open form', () => {
+ const onChange = jest.fn();
+ const input = shallowRender({ onChange });
+ const button = input.find('Button');
+ expect(button.length).toBe(1);
+
+ click(button);
+ expect(input.find('input').length).toBe(1);
+});
+
+it('should set value', () => {
+ const onChange = jest.fn(() => Promise.resolve());
+ const input = shallowRender({ onChange });
+
+ click(input.find('Button'));
+ change(input.find(InputForString), 'secret');
+ expect(onChange).toBeCalledWith('secret');
+});
+
+it('should show input when empty, and enable handle typing', () => {
+ const input = shallowRender({ setting: mockSetting({ hasValue: false }) });
+ const onChange = (value: string) => input.setProps({ hasValueChanged: true, value });
+ input.setProps({ onChange });
+
+ expect(input.find('input').length).toBe(1);
+ change(input.find(InputForString), 'hello');
+ expect(input.find('input').length).toBe(1);
+ expect(input.find(InputForString).prop('value')).toBe('hello');
+});
+
+it('should handle value reset', () => {
+ const input = shallowRender({ hasValueChanged: true, value: 'whatever' });
+ input.setState({ changing: true });
+
+ // reset
+ input.setProps({ hasValueChanged: false, value: 'original' });
+
+ expect(input.state('changing')).toBe(false);
+});
+
+it('should handle value reset to empty', () => {
+ const input = shallowRender({ hasValueChanged: true, value: 'whatever' });
+ input.setState({ changing: true });
+
+ // outside change
+ input.setProps({ hasValueChanged: false, setting: mockSetting({ hasValue: false }) });
+
+ expect(input.state('changing')).toBe(true);
+});
+
+function shallowRender(props: Partial<InputForSecured['props']> = {}) {
+ return shallow<InputForSecured>(
+ <InputForSecured
+ input={InputForString}
+ hasValueChanged={false}
+ onChange={jest.fn()}
+ setting={mockSetting()}
+ value="bar"
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.tsx
index 8dde11bfbc0..00d5fcb467a 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.tsx
@@ -19,6 +19,7 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockSetting } from '../../../../../helpers/mocks/settings';
import { DefaultSpecializedInputProps } from '../../../utils';
import InputForSingleSelectList from '../InputForSingleSelectList';
@@ -53,6 +54,7 @@ function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
name="foo"
onChange={jest.fn()}
options={['foo', 'bar', 'baz']}
+ setting={mockSetting()}
value="bar"
{...props}
/>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForString-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForString-test.tsx
index 154d4dcd3d4..65e5235c4be 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForString-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForString-test.tsx
@@ -19,13 +19,20 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockSetting } from '../../../../../helpers/mocks/settings';
import InputForString from '../InputForString';
import SimpleInput from '../SimpleInput';
it('should render SimpleInput', () => {
const onChange = jest.fn();
const simpleInput = shallow(
- <InputForString isDefault={false} name="foo" onChange={onChange} value="bar" />
+ <InputForString
+ isDefault={false}
+ name="foo"
+ onChange={onChange}
+ setting={mockSetting()}
+ value="bar"
+ />
).find(SimpleInput);
expect(simpleInput.length).toBe(1);
expect(simpleInput.prop('name')).toBe('foo');
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.tsx
index 4b9a42653fb..1d12b2d64a0 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.tsx
@@ -19,6 +19,7 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockSetting } from '../../../../../helpers/mocks/settings';
import { change } from '../../../../../helpers/testUtils';
import { DefaultSpecializedInputProps } from '../../../utils';
import InputForText from '../InputForText';
@@ -44,6 +45,13 @@ it('should call onChange', () => {
function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
return shallow(
- <InputForText isDefault={false} name="foo" onChange={jest.fn()} value="bar" {...props} />
+ <InputForText
+ isDefault={false}
+ name="foo"
+ onChange={jest.fn()}
+ value="bar"
+ {...props}
+ setting={mockSetting()}
+ />
);
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/MultiValueInput-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/MultiValueInput-test.tsx
index 7f5dd70dc64..483dc0c30b7 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/MultiValueInput-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/MultiValueInput-test.tsx
@@ -20,13 +20,14 @@
import { shallow, ShallowWrapper } from 'enzyme';
import * as React from 'react';
import { click } from '../../../../../helpers/testUtils';
-import { SettingCategoryDefinition } from '../../../../../types/settings';
-import { DefaultInputProps } from '../../../utils';
+import { SettingCategoryDefinition, SettingType } from '../../../../../types/settings';
+import { DefaultSpecializedInputProps } from '../../../utils';
import MultiValueInput from '../MultiValueInput';
import PrimitiveInput from '../PrimitiveInput';
const settingValue = {
- key: 'example'
+ key: 'example',
+ hasValue: true
};
const settingDefinition: SettingCategoryDefinition = {
@@ -36,7 +37,7 @@ const settingDefinition: SettingCategoryDefinition = {
multiValues: true,
options: [],
subCategory: 'Branches',
- type: 'STRING'
+ type: SettingType.STRING
};
const assertValues = (inputs: ShallowWrapper<any>, values: string[]) => {
@@ -87,9 +88,11 @@ it('should add new value', () => {
expect(onChange).toBeCalledWith(['foo', 'bar']);
});
-function shallowRender(props: Partial<DefaultInputProps> = {}) {
+function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
return shallow(
<MultiValueInput
+ isDefault={true}
+ name="bar"
onChange={jest.fn()}
setting={{ ...settingValue, definition: settingDefinition }}
value={['foo']}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/PrimitiveInput-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/PrimitiveInput-test.tsx
new file mode 100644
index 00000000000..a09e8814af8
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/PrimitiveInput-test.tsx
@@ -0,0 +1,46 @@
+/*
+ * 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 { mockDefinition, mockSetting } from '../../../../../helpers/mocks/settings';
+import { SettingType } from '../../../../../types/settings';
+import { DefaultSpecializedInputProps } from '../../../utils';
+import PrimitiveInput from '../PrimitiveInput';
+
+it.each(Object.values(SettingType).map(Array.of))(
+ 'should render correctly for %s',
+ (type: SettingType) => {
+ const setting = mockSetting({ definition: mockDefinition({ type }) });
+ expect(shallowRender({ setting })).toMatchSnapshot();
+ }
+);
+
+function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
+ return shallow(
+ <PrimitiveInput
+ isDefault={true}
+ name="name"
+ onChange={jest.fn()}
+ setting={mockSetting()}
+ value={['foo']}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.tsx
index 2f6125c6732..f61a4ee4ce5 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.tsx
@@ -19,6 +19,7 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockSetting } from '../../../../../helpers/mocks/settings';
import { change } from '../../../../../helpers/testUtils';
import SimpleInput from '../SimpleInput';
@@ -31,6 +32,7 @@ it('should render input', () => {
name="foo"
onChange={onChange}
type="text"
+ setting={mockSetting()}
value="bar"
/>
).find('input');
@@ -51,6 +53,7 @@ it('should call onChange', () => {
name="foo"
onChange={onChange}
type="text"
+ setting={mockSetting()}
value="bar"
/>
).find('input');
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/__snapshots__/PrimitiveInput-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/__snapshots__/PrimitiveInput-test.tsx.snap
new file mode 100644
index 00000000000..f8968e76239
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/__snapshots__/PrimitiveInput-test.tsx.snap
@@ -0,0 +1,320 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly for BOOLEAN 1`] = `
+<InputForBoolean
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "BOOLEAN",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for FLOAT 1`] = `
+<InputForNumber
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "FLOAT",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for INTEGER 1`] = `
+<InputForNumber
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "INTEGER",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for JSON 1`] = `
+<InputForJSON
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "JSON",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for LICENSE 1`] = `
+<InputForString
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "LICENSE",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for LONG 1`] = `
+<InputForNumber
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "LONG",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for PASSWORD 1`] = `
+<InputForPassword
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "PASSWORD",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for PROPERTY_SET 1`] = `
+<InputForString
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "PROPERTY_SET",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for SINGLE_SELECT_LIST 1`] = `
+<Wrapped
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "SINGLE_SELECT_LIST",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for STRING 1`] = `
+<InputForString
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "STRING",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for TEXT 1`] = `
+<InputForText
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "TEXT",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx
index db66a5ebe05..ddd645f28a0 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx
@@ -20,6 +20,7 @@
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
+import Toggle from '../../../../components/controls/Toggle';
import { Alert } from '../../../../components/ui/Alert';
import MandatoryFieldMarker from '../../../../components/ui/MandatoryFieldMarker';
import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
@@ -30,7 +31,6 @@ import {
AlmSettingsInstance,
ProjectAlmBindingResponse
} from '../../../../types/alm-settings';
-import InputForBoolean from '../inputs/InputForBoolean';
export interface AlmSpecificFormProps {
alm: AlmKeys;
@@ -108,12 +108,10 @@ function renderBooleanField(
return renderFieldWrapper(
renderLabel({ ...props, optional: true }),
<div className="display-flex-center big-spacer-top">
- <InputForBoolean
- isDefault={true}
- name={id}
- onChange={v => onFieldChange(propKey, v)}
- value={value}
- />
+ <div className="display-inline-block text-top">
+ <Toggle name={id} onChange={v => onFieldChange(propKey, v)} value={value} />
+ {value == null && <span className="spacer-left note">{translate('settings.not_set')}</span>}
+ </div>
{inputExtra}
</div>,
renderHelp(props)
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap
index 1cb1269f6dd..77a82861402 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap
@@ -444,12 +444,15 @@ exports[`it should render correctly for github 1`] = `
<div
className="display-flex-center big-spacer-top"
>
- <InputForBoolean
- isDefault={true}
- name="github.summary_comment_setting"
- onChange={[Function]}
- value={true}
- />
+ <div
+ className="display-inline-block text-top"
+ >
+ <Toggle
+ name="github.summary_comment_setting"
+ onChange={[Function]}
+ value={true}
+ />
+ </div>
</div>
</div>
</div>
@@ -535,12 +538,15 @@ exports[`it should render correctly for github if an instance URL is provided 1`
<div
className="display-flex-center big-spacer-top"
>
- <InputForBoolean
- isDefault={true}
- name="github.summary_comment_setting"
- onChange={[Function]}
- value={true}
- />
+ <div
+ className="display-inline-block text-top"
+ >
+ <Toggle
+ name="github.summary_comment_setting"
+ onChange={[Function]}
+ value={true}
+ />
+ </div>
</div>
</div>
</div>
@@ -626,12 +632,15 @@ exports[`it should render correctly for github if an instance URL is provided 2`
<div
className="display-flex-center big-spacer-top"
>
- <InputForBoolean
- isDefault={true}
- name="github.summary_comment_setting"
- onChange={[Function]}
- value={true}
- />
+ <div
+ className="display-inline-block text-top"
+ >
+ <Toggle
+ name="github.summary_comment_setting"
+ onChange={[Function]}
+ value={true}
+ />
+ </div>
</div>
</div>
</div>
@@ -828,12 +837,15 @@ exports[`should render the monorepo field when the feature is supported 1`] = `
<div
className="display-flex-center big-spacer-top"
>
- <InputForBoolean
- isDefault={true}
- name="monorepo"
- onChange={[Function]}
- value={false}
- />
+ <div
+ className="display-inline-block text-top"
+ >
+ <Toggle
+ name="monorepo"
+ onChange={[Function]}
+ value={false}
+ />
+ </div>
</div>
</div>
</div>
diff --git a/server/sonar-web/src/main/js/apps/settings/routes.ts b/server/sonar-web/src/main/js/apps/settings/routes.ts
index 53d18a0ed1a..326d588b054 100644
--- a/server/sonar-web/src/main/js/apps/settings/routes.ts
+++ b/server/sonar-web/src/main/js/apps/settings/routes.ts
@@ -21,7 +21,7 @@ import { lazyLoadComponent } from '../../components/lazyLoadComponent';
const routes = [
{
- indexRoute: { component: lazyLoadComponent(() => import('./components/AppContainer')) }
+ indexRoute: { component: lazyLoadComponent(() => import('./components/SettingsApp')) }
},
{
path: 'encryption',
diff --git a/server/sonar-web/src/main/js/apps/settings/store/__tests__/actions-test.ts b/server/sonar-web/src/main/js/apps/settings/store/__tests__/actions-test.ts
index 9944b748fc9..8862df66f2d 100644
--- a/server/sonar-web/src/main/js/apps/settings/store/__tests__/actions-test.ts
+++ b/server/sonar-web/src/main/js/apps/settings/store/__tests__/actions-test.ts
@@ -21,21 +21,25 @@ import {
getSettingsAppChangedValue,
getSettingsAppDefinition
} from '../../../../store/rootReducer';
-import { checkValue, fetchSettings } from '../actions';
+import { checkValue, fetchSettings, fetchValues } from '../actions';
import { receiveDefinitions } from '../definitions';
-jest.mock('../../../../api/settings', () => ({
- getDefinitions: jest.fn().mockResolvedValue([
- {
- key: 'SETTINGS_1_KEY',
- type: 'SETTINGS_1_TYPE'
- },
- {
- key: 'SETTINGS_2_KEY',
- type: 'LICENSE'
- }
- ])
-}));
+jest.mock('../../../../api/settings', () => {
+ const { mockSettingValue } = jest.requireActual('../../../../helpers/mocks/settings');
+ return {
+ getValues: jest.fn().mockResolvedValue([mockSettingValue()]),
+ getDefinitions: jest.fn().mockResolvedValue([
+ {
+ key: 'SETTINGS_1_KEY',
+ type: 'SETTINGS_1_TYPE'
+ },
+ {
+ key: 'SETTINGS_2_KEY',
+ type: 'LICENSE'
+ }
+ ])
+ };
+});
jest.mock('../definitions', () => ({
receiveDefinitions: jest.fn()
@@ -59,6 +63,18 @@ it('#fetchSettings should filter LICENSE type settings', async () => {
]);
});
+it('should fetchValue correclty', async () => {
+ const dispatch = jest.fn();
+ await fetchValues(['test'], 'foo')(dispatch);
+ expect(dispatch).toHaveBeenCalledWith({
+ component: 'foo',
+ settings: [{ key: 'test' }],
+ type: 'RECEIVE_VALUES',
+ updateKeys: ['test']
+ });
+ expect(dispatch).toHaveBeenCalledWith({ type: 'CLOSE_ALL_GLOBAL_MESSAGES' });
+});
+
describe('checkValue', () => {
const dispatch = jest.fn();
diff --git a/server/sonar-web/src/main/js/apps/settings/store/__tests__/rootReducer-test.ts b/server/sonar-web/src/main/js/apps/settings/store/__tests__/rootReducer-test.ts
new file mode 100644
index 00000000000..6dbfdc3f7e3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/store/__tests__/rootReducer-test.ts
@@ -0,0 +1,42 @@
+/*
+ * 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 { getSettingsForCategory } from '../rootReducer';
+
+it('Should correclty assert if value is set', () => {
+ const settings = getSettingsForCategory(
+ {
+ definitions: {
+ foo: { category: 'cat', key: 'foo', fields: [], options: [], subCategory: 'test' },
+ bar: { category: 'cat', key: 'bar', fields: [], options: [], subCategory: 'test' }
+ },
+ globalMessages: [],
+ settingsPage: {
+ changedValues: {},
+ loading: {},
+ validationMessages: {}
+ },
+ values: { components: {}, global: { foo: { key: 'foo' } } }
+ },
+ 'cat'
+ );
+ expect(settings[0].hasValue).toBe(true);
+ expect(settings[1].hasValue).toBe(false);
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/store/actions.ts b/server/sonar-web/src/main/js/apps/settings/store/actions.ts
index ae7dc188a00..44b67e835e1 100644
--- a/server/sonar-web/src/main/js/apps/settings/store/actions.ts
+++ b/server/sonar-web/src/main/js/apps/settings/store/actions.ts
@@ -65,10 +65,10 @@ export function fetchSettings(component?: string) {
};
}
-export function fetchValues(keys: string, component?: string) {
+export function fetchValues(keys: string[], component?: string) {
return (dispatch: Dispatch) =>
- getValues({ keys, component }).then(settings => {
- dispatch(receiveValues(settings, component));
+ getValues({ keys: keys.join(), component }).then(settings => {
+ dispatch(receiveValues(keys, settings, component));
dispatch(closeAllGlobalMessages());
});
}
@@ -131,7 +131,7 @@ export function saveValue(key: string, component?: string) {
return setSettingValue(definition, value, component)
.then(() => getValues({ keys: key, component }))
.then(values => {
- dispatch(receiveValues(values, component));
+ dispatch(receiveValues([key], values, component));
dispatch(cancelChange(key));
dispatch(passValidation(key));
dispatch(stopLoading(key));
@@ -148,9 +148,9 @@ export function resetValue(key: string, component?: string) {
.then(() => getValues({ keys: key, component }))
.then(values => {
if (values.length > 0) {
- dispatch(receiveValues(values, component));
+ dispatch(receiveValues([key], values, component));
} else {
- dispatch(receiveValues([{ key }], component));
+ dispatch(receiveValues([key], [], component));
}
dispatch(passValidation(key));
dispatch(stopLoading(key));
diff --git a/server/sonar-web/src/main/js/apps/settings/store/rootReducer.ts b/server/sonar-web/src/main/js/apps/settings/store/rootReducer.ts
index 0f8f236c802..3b00673beb6 100644
--- a/server/sonar-web/src/main/js/apps/settings/store/rootReducer.ts
+++ b/server/sonar-web/src/main/js/apps/settings/store/rootReducer.ts
@@ -53,11 +53,16 @@ export function getValue(state: State, key: string, component?: string) {
}
export function getSettingsForCategory(state: State, category: string, component?: string) {
- return fromDefinitions.getDefinitionsForCategory(state.definitions, category).map(definition => ({
- key: definition.key,
- ...getValue(state, definition.key, component),
- definition
- }));
+ return fromDefinitions.getDefinitionsForCategory(state.definitions, category).map(definition => {
+ const value = getValue(state, definition.key, component);
+ const hasValue = value !== undefined && value.inherited !== true;
+ return {
+ key: definition.key,
+ hasValue,
+ ...value,
+ definition
+ };
+ });
}
export function getChangedValue(state: State, key: string) {
diff --git a/server/sonar-web/src/main/js/apps/settings/store/values.ts b/server/sonar-web/src/main/js/apps/settings/store/values.ts
index 8b00453b9b6..1146d12b359 100644
--- a/server/sonar-web/src/main/js/apps/settings/store/values.ts
+++ b/server/sonar-web/src/main/js/apps/settings/store/values.ts
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { keyBy } from 'lodash';
+import { keyBy, omit } from 'lodash';
import { combineReducers } from 'redux';
import { Action as AppStateAction, Actions as AppStateActions } from '../../../store/appState';
import { ActionType } from '../../../store/utils/actions';
@@ -37,10 +37,11 @@ export interface State {
}
export function receiveValues(
+ updateKeys: string[],
settings: Array<{ key: string; value?: string }>,
component?: string
) {
- return { type: Actions.receiveValues, settings, component };
+ return { type: Actions.receiveValues, updateKeys, settings, component };
}
function components(state: State['components'] = {}, action: Action) {
@@ -50,7 +51,7 @@ function components(state: State['components'] = {}, action: Action) {
}
if (action.type === Actions.receiveValues) {
const settingsByKey = keyBy(action.settings, 'key');
- return { ...state, [key]: { ...(state[key] || {}), ...settingsByKey } };
+ return { ...state, [key]: { ...omit(state[key] || {}, action.updateKeys), ...settingsByKey } };
}
return state;
}
@@ -61,8 +62,9 @@ function global(state: State['components'] = {}, action: Action | AppStateAction
return state;
}
const settingsByKey = keyBy(action.settings, 'key');
- return { ...state, ...settingsByKey };
+ return { ...omit(state, action.updateKeys), ...settingsByKey };
}
+
if (action.type === AppStateActions.SetAppState) {
const settingsByKey: SettingsState = {};
Object.keys(action.appState.settings).forEach(
diff --git a/server/sonar-web/src/main/js/apps/settings/utils.ts b/server/sonar-web/src/main/js/apps/settings/utils.ts
index 9fd4d106634..5589c5e532d 100644
--- a/server/sonar-web/src/main/js/apps/settings/utils.ts
+++ b/server/sonar-web/src/main/js/apps/settings/utils.ts
@@ -25,12 +25,16 @@ import { Setting, SettingCategoryDefinition, SettingDefinition } from '../../typ
export const DEFAULT_CATEGORY = 'general';
-export type DefaultSpecializedInputProps = T.Omit<DefaultInputProps, 'setting'> & {
+export type DefaultSpecializedInputProps = DefaultInputProps & {
+ className?: string;
+ autoComplete?: string;
isDefault: boolean;
name: string;
+ type?: string;
};
export interface DefaultInputProps {
+ autoFocus?: boolean;
hasValueChanged?: boolean;
onCancel?: () => void;
onChange: (value: any) => void;
@@ -89,6 +93,10 @@ export function isEmptyValue(definition: SettingDefinition, value: any) {
}
}
+export function isSecuredDefinition(item: SettingDefinition): boolean {
+ return item.key.endsWith('.secured');
+}
+
export function isCategoryDefinition(item: SettingDefinition): item is SettingCategoryDefinition {
return Boolean((item as any).fields);
}
diff --git a/server/sonar-web/src/main/js/helpers/mocks/settings.ts b/server/sonar-web/src/main/js/helpers/mocks/settings.ts
index 14065d55238..898122d1d4d 100644
--- a/server/sonar-web/src/main/js/helpers/mocks/settings.ts
+++ b/server/sonar-web/src/main/js/helpers/mocks/settings.ts
@@ -17,7 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Setting, SettingCategoryDefinition, SettingWithCategory } from '../../types/settings';
+import {
+ Setting,
+ SettingCategoryDefinition,
+ SettingType,
+ SettingValue,
+ SettingWithCategory
+} from '../../types/settings';
export function mockDefinition(
overrides: Partial<SettingCategoryDefinition> = {}
@@ -36,30 +42,39 @@ export function mockSetting(overrides: Partial<Setting> = {}): Setting {
return {
key: 'foo',
value: '42',
+ hasValue: true,
inherited: true,
definition: {
key: 'foo',
name: 'Foo setting',
description: 'When Foo then Bar',
- type: 'INTEGER',
+ type: SettingType.INTEGER,
options: []
},
...overrides
};
}
+export function mockSettingValue(overrides: Partial<SettingValue> = {}) {
+ return {
+ key: 'test',
+ ...overrides
+ };
+}
+
export function mockSettingWithCategory(
overrides: Partial<SettingWithCategory> = {}
): SettingWithCategory {
return {
key: 'foo',
value: '42',
+ hasValue: true,
inherited: true,
definition: {
key: 'foo',
name: 'Foo setting',
description: 'When Foo then Bar',
- type: 'INTEGER',
+ type: SettingType.INTEGER,
options: [],
category: 'general',
fields: [],
diff --git a/server/sonar-web/src/main/js/types/settings.ts b/server/sonar-web/src/main/js/types/settings.ts
index b272b5b9943..db89872a345 100644
--- a/server/sonar-web/src/main/js/types/settings.ts
+++ b/server/sonar-web/src/main/js/types/settings.ts
@@ -25,22 +25,22 @@ export const enum SettingsKey {
ProjectReportFrequency = 'sonar.governance.report.project.branch.frequency'
}
-export type Setting = SettingValue & { definition: SettingDefinition };
+export type Setting = SettingValue & { definition: SettingDefinition; hasValue: boolean };
export type SettingWithCategory = Setting & { definition: SettingCategoryDefinition };
-export type SettingType =
- | 'STRING'
- | 'TEXT'
- | 'JSON'
- | 'PASSWORD'
- | 'BOOLEAN'
- | 'FLOAT'
- | 'INTEGER'
- | 'LICENSE'
- | 'LONG'
- | 'SINGLE_SELECT_LIST'
- | 'PROPERTY_SET';
-
+export enum SettingType {
+ STRING = 'STRING',
+ TEXT = 'TEXT',
+ JSON = 'JSON',
+ PASSWORD = 'PASSWORD',
+ BOOLEAN = 'BOOLEAN',
+ FLOAT = 'FLOAT',
+ INTEGER = 'INTEGER',
+ LICENSE = 'LICENSE',
+ LONG = 'LONG',
+ SINGLE_SELECT_LIST = 'SINGLE_SELECT_LIST',
+ PROPERTY_SET = 'PROPERTY_SET'
+}
export interface SettingDefinition {
description?: string;
key: string;