@@ -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)); | |||
}); | |||
}; | |||
} |
@@ -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']); | |||
} | |||
} | |||
@@ -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' | |||
}; |
@@ -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> | |||
); |
@@ -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 ( | |||
<> |
@@ -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); |
@@ -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); | |||
} | |||
@@ -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); |
@@ -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' | |||
}; |
@@ -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()} |
@@ -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> | |||
`; |
@@ -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", |
@@ -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)} />; | |||
} |
@@ -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} />; | |||
} |
@@ -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" /> | |||
); | |||
} |
@@ -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> | |||
</> | |||
); | |||
} | |||
} |
@@ -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} />; | |||
} |
@@ -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} | |||
/> | |||
@@ -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} />; | |||
} |
@@ -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 |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} />); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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'); |
@@ -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(); | |||
}); |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} | |||
/> |
@@ -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'); |
@@ -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()} | |||
/> | |||
); | |||
} |
@@ -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']} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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'); |
@@ -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", | |||
] | |||
} | |||
/> | |||
`; |
@@ -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) |
@@ -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> |
@@ -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', |
@@ -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(); | |||
@@ -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); | |||
}); |
@@ -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)); |
@@ -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) { |
@@ -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( |
@@ -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); | |||
} |
@@ -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: [], |
@@ -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; |