import { getJSON, post, postJSON, RequestData } from '../helpers/request';
import { BranchParameters } from '../types/branch-like';
import {
- SettingCategoryDefinition,
+ ExtendedSettingDefinition,
SettingDefinition,
SettingValue,
SettingValueResponse
} from '../types/settings';
-export function getDefinitions(component?: string): Promise<SettingCategoryDefinition[]> {
+export function getDefinitions(component?: string): Promise<ExtendedSettingDefinition[]> {
return getJSON('/api/settings/list_definitions', { component }).then(
r => r.definitions,
throwGlobalError
import { mockComponent } from '../../../helpers/mocks/component';
import { mockDefinition } from '../../../helpers/mocks/settings';
import {
+ ExtendedSettingDefinition,
Setting,
- SettingCategoryDefinition,
SettingFieldDefinition,
SettingType
} from '../../../types/settings';
{ key: 'bar', type: 'SINGLE_SELECT_LIST' } as SettingFieldDefinition
];
-const settingDefinition: SettingCategoryDefinition = {
+const settingDefinition: ExtendedSettingDefinition = {
category: 'test',
fields: [],
key: 'test',
describe('#getEmptyValue()', () => {
it('should work for property sets', () => {
- const setting: SettingCategoryDefinition = {
+ const setting: ExtendedSettingDefinition = {
...settingDefinition,
type: SettingType.PROPERTY_SET,
fields
});
it('should work for multi values string', () => {
- const setting: SettingCategoryDefinition = {
+ const setting: ExtendedSettingDefinition = {
...settingDefinition,
type: SettingType.STRING,
multiValues: true
});
it('should work for multi values boolean', () => {
- const setting: SettingCategoryDefinition = {
+ const setting: ExtendedSettingDefinition = {
...settingDefinition,
type: SettingType.BOOLEAN,
multiValues: true
*/
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
+import { ExtendedSettingDefinition } from '../../../types/settings';
import { Component } from '../../../types/types';
import {
ALM_INTEGRATION,
import PullRequestDecorationBinding from './pullRequestDecorationBinding/PRDecorationBinding';
export interface AdditionalCategoryComponentProps {
+ categories: string[];
+ definitions: ExtendedSettingDefinition[];
component: Component | undefined;
selectedCategory: string;
}
import { connect } from 'react-redux';
import { IndexLink } from 'react-router';
import { getGlobalSettingsUrl, getProjectSettingsUrl } from '../../../helpers/urls';
-import { getAppState, getSettingsAppAllCategories, Store } from '../../../store/rootReducer';
+import { getAppState, Store } from '../../../store/rootReducer';
import { Component } from '../../../types/types';
import { getCategoryName } from '../utils';
import { ADDITIONAL_CATEGORIES } from './AdditionalCategories';
}
const mapStateToProps = (state: Store) => ({
- categories: getSettingsAppAllCategories(state),
branchesEnabled: getAppState(state).branchesEnabled
});
import CategoryDefinitionsList from './CategoryDefinitionsList';
export function AnalysisScope(props: AdditionalCategoryComponentProps) {
- const { component, selectedCategory } = props;
+ const { component, definitions, selectedCategory } = props;
return (
<>
</table>
<div className="settings-sub-category">
- <CategoryDefinitionsList category={selectedCategory} component={component} />
+ <CategoryDefinitionsList
+ category={selectedCategory}
+ component={component}
+ definitions={definitions}
+ />
</div>
</>
);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { connect } from 'react-redux';
-import { getSettingsAppSettingsForCategory, Store } from '../../../store/rootReducer';
+import { keyBy } from 'lodash';
+import * as React from 'react';
+import { getValues } from '../../../api/settings';
+import {
+ ExtendedSettingDefinition,
+ SettingDefinitionAndValue,
+ SettingValue
+} from '../../../types/settings';
import { Component } from '../../../types/types';
-import { fetchValues } from '../store/actions';
import SubCategoryDefinitionsList from './SubCategoryDefinitionsList';
interface Props {
category: string;
component?: Component;
+ definitions: ExtendedSettingDefinition[];
+ subCategory?: string;
}
-const mapStateToProps = (state: Store, ownProps: Props) => ({
- settings: getSettingsAppSettingsForCategory(
- state,
- ownProps.category,
- ownProps.component && ownProps.component.key
- )
-});
+interface State {
+ settings: SettingDefinitionAndValue[];
+}
+
+export default class CategoryDefinitionsList extends React.PureComponent<Props, State> {
+ mounted = false;
+ state: State = { settings: [] };
+
+ componentDidMount() {
+ this.mounted = true;
+
+ this.loadSettingValues();
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.category !== this.props.category) {
+ this.loadSettingValues();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ async loadSettingValues() {
+ const { category, component, definitions } = this.props;
-const mapDispatchToProps = { fetchValues };
+ const categoryDefinitions = definitions.filter(
+ definition => definition.category.toLowerCase() === category.toLowerCase()
+ );
-export default connect(mapStateToProps, mapDispatchToProps)(SubCategoryDefinitionsList);
+ const keys = categoryDefinitions.map(definition => definition.key).join(',');
+
+ const values: SettingValue[] = await getValues({
+ keys,
+ component: component?.key
+ }).catch(() => []);
+ const valuesByDefinitionKey = keyBy(values, 'key');
+
+ const settings: SettingDefinitionAndValue[] = categoryDefinitions.map(definition => {
+ const settingValue = valuesByDefinitionKey[definition.key];
+ return {
+ definition,
+ settingValue
+ };
+ });
+
+ this.setState({ settings });
+ }
+
+ render() {
+ const { category, component, subCategory } = this.props;
+ const { settings } = this.state;
+
+ return (
+ <SubCategoryDefinitionsList
+ category={category}
+ component={component}
+ settings={settings}
+ subCategory={subCategory}
+ />
+ );
+ }
+}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import classNames from 'classnames';
import * as React from 'react';
-import { connect } from 'react-redux';
-import AlertErrorIcon from '../../../components/icons/AlertErrorIcon';
-import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
+import { getValues, resetSettingValue, setSettingValue } from '../../../api/settings';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { sanitizeStringRestricted } from '../../../helpers/sanitize';
-import {
- getSettingsAppChangedValue,
- getSettingsAppValidationMessage,
- isSettingsAppLoading,
- Store
-} from '../../../store/rootReducer';
-import { Setting } from '../../../types/settings';
+import { parseError } from '../../../helpers/request';
+import { ExtendedSettingDefinition, SettingType, SettingValue } from '../../../types/settings';
import { Component } from '../../../types/types';
-import { checkValue, resetValue, saveValue } from '../store/actions';
-import { cancelChange, changeValue, passValidation } from '../store/settingsPage';
-import {
- getPropertyDescription,
- getPropertyName,
- getSettingValue,
- isDefaultOrInherited
-} from '../utils';
-import DefinitionActions from './DefinitionActions';
-import Input from './inputs/Input';
+import { isEmptyValue, isURLKind } from '../utils';
+import DefinitionRenderer from './DefinitionRenderer';
interface Props {
- cancelChange: (key: string) => void;
- changeValue: (key: string, value: any) => void;
- changedValue: any;
- checkValue: (key: string) => boolean;
component?: Component;
- loading: boolean;
- passValidation: (key: string) => void;
- resetValue: (key: string, component?: string) => Promise<void>;
- saveValue: (key: string, component?: string) => Promise<void>;
- setting: Setting;
- validationMessage?: string;
+ definition: ExtendedSettingDefinition;
+ initialSettingValue?: SettingValue;
}
interface State {
+ changedValue?: string;
+ loading: boolean;
success: boolean;
+ validationMessage?: string;
+ settingValue?: SettingValue;
}
const SAFE_SET_STATE_DELAY = 3000;
-const formNoop = (e: React.FormEvent<HTMLFormElement>) => e.preventDefault();
-
-export class Definition extends React.PureComponent<Props, State> {
+export default class Definition extends React.PureComponent<Props, State> {
timeout?: number;
mounted = false;
- state = { success: false };
+
+ constructor(props: Props) {
+ super(props);
+
+ this.state = {
+ loading: false,
+ success: false,
+ settingValue: props.initialSettingValue
+ };
+ }
componentDidMount() {
this.mounted = true;
componentWillUnmount() {
this.mounted = false;
+ clearTimeout(this.timeout);
}
- safeSetState(changes: State) {
- if (this.mounted) {
- this.setState(changes);
- }
- }
-
- handleChange = (value: any) => {
+ handleChange = (changedValue: any) => {
clearTimeout(this.timeout);
- this.props.changeValue(this.props.setting.definition.key, value);
- this.handleCheck();
+
+ this.setState({ changedValue, success: false }, this.handleCheck);
};
- handleReset = () => {
- const { component, setting } = this.props;
- const { definition } = setting;
- const componentKey = component && component.key;
- return this.props.resetValue(definition.key, componentKey).then(() => {
- this.props.cancelChange(definition.key);
- this.safeSetState({ success: true });
+ handleReset = async () => {
+ const { component, definition } = this.props;
+
+ this.setState({ loading: true, success: false });
+
+ try {
+ await resetSettingValue({ keys: definition.key, component: component?.key });
+ const result = await getValues({ keys: definition.key, component: component?.key });
+ const settingValue = result[0];
+
+ this.setState({
+ changedValue: undefined,
+ loading: false,
+ success: true,
+ validationMessage: undefined,
+ settingValue
+ });
+
this.timeout = window.setTimeout(
- () => this.safeSetState({ success: false }),
+ () => this.setState({ success: false }),
SAFE_SET_STATE_DELAY
);
- });
+ } catch (e) {
+ const validationMessage = await parseError(e as Response);
+ this.setState({ loading: false, validationMessage });
+ }
};
handleCancel = () => {
- const { setting } = this.props;
- this.props.cancelChange(setting.definition.key);
- this.props.passValidation(setting.definition.key);
+ this.setState({ changedValue: undefined, validationMessage: undefined });
};
handleCheck = () => {
- const { setting } = this.props;
- this.props.checkValue(setting.definition.key);
+ const { definition } = this.props;
+ const { changedValue } = this.state;
+
+ if (isEmptyValue(definition, changedValue)) {
+ if (definition.defaultValue === undefined) {
+ this.setState({
+ validationMessage: translate('settings.state.value_cant_be_empty_no_default')
+ });
+ } else {
+ this.setState({ validationMessage: translate('settings.state.value_cant_be_empty') });
+ }
+ return false;
+ }
+
+ if (isURLKind(definition)) {
+ try {
+ // eslint-disable-next-line no-new
+ new URL(changedValue ?? '');
+ } catch (e) {
+ this.setState({
+ validationMessage: translateWithParameters(
+ 'settings.state.url_not_valid',
+ changedValue ?? ''
+ )
+ });
+ return false;
+ }
+ }
+
+ if (definition.type === SettingType.JSON) {
+ try {
+ JSON.parse(changedValue ?? '');
+ } catch (e) {
+ this.setState({ validationMessage: (e as Error).message });
+
+ return false;
+ }
+ }
+
+ this.setState({ validationMessage: undefined });
+ return true;
};
- handleSave = () => {
- if (this.props.changedValue != null) {
- this.safeSetState({ success: false });
- const { component, setting } = this.props;
- this.props.saveValue(setting.definition.key, component && component.key).then(
- () => {
- this.safeSetState({ success: true });
- this.timeout = window.setTimeout(
- () => this.safeSetState({ success: false }),
- SAFE_SET_STATE_DELAY
- );
- },
- () => {
- /* Do nothing */
- }
- );
+ handleSave = async () => {
+ const { component, definition } = this.props;
+ const { changedValue } = this.state;
+
+ if (changedValue !== undefined) {
+ this.setState({ success: false });
+
+ if (isEmptyValue(definition, changedValue)) {
+ this.setState({ validationMessage: translate('settings.state.value_cant_be_empty') });
+
+ return;
+ }
+
+ this.setState({ loading: true });
+
+ try {
+ await setSettingValue(definition, changedValue, component?.key);
+ const result = await getValues({ keys: definition.key, component: component?.key });
+ const settingValue = result[0];
+
+ this.setState({
+ changedValue: undefined,
+ loading: false,
+ success: true,
+ settingValue
+ });
+
+ this.timeout = window.setTimeout(
+ () => this.setState({ success: false }),
+ SAFE_SET_STATE_DELAY
+ );
+ } catch (e) {
+ const validationMessage = await parseError(e as Response);
+ this.setState({ loading: false, validationMessage });
+ }
}
};
render() {
- const { changedValue, loading, setting, validationMessage } = this.props;
- const { definition } = setting;
- const propertyName = getPropertyName(definition);
- const hasError = validationMessage != null;
- const hasValueChanged = changedValue != null;
- const effectiveValue = hasValueChanged ? changedValue : getSettingValue(setting);
- const isDefault = isDefaultOrInherited(setting);
- const description = getPropertyDescription(definition);
+ const { definition } = this.props;
return (
- <div
- className={classNames('settings-definition', {
- 'settings-definition-changed': hasValueChanged
- })}
- data-key={definition.key}>
- <div className="settings-definition-left">
- <h3 className="settings-definition-name" title={propertyName}>
- {propertyName}
- </h3>
-
- {description && (
- <div
- className="markdown small spacer-top"
- // eslint-disable-next-line react/no-danger
- dangerouslySetInnerHTML={{ __html: sanitizeStringRestricted(description) }}
- />
- )}
-
- <div className="settings-definition-key note little-spacer-top">
- {translateWithParameters('settings.key_x', definition.key)}
- </div>
- </div>
-
- <div className="settings-definition-right">
- <div className="settings-definition-state">
- {loading && (
- <span className="text-info">
- <i className="spinner spacer-right" />
- {translate('settings.state.saving')}
- </span>
- )}
-
- {!loading && validationMessage && (
- <span className="text-danger">
- <AlertErrorIcon className="spacer-right" />
- <span>
- {translateWithParameters('settings.state.validation_failed', validationMessage)}
- </span>
- </span>
- )}
-
- {!loading && !hasError && this.state.success && (
- <span className="text-success">
- <AlertSuccessIcon className="spacer-right" />
- {translate('settings.state.saved')}
- </span>
- )}
- </div>
- <form onSubmit={formNoop}>
- <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>
+ <DefinitionRenderer
+ definition={definition}
+ onCancel={this.handleCancel}
+ onChange={this.handleChange}
+ onReset={this.handleReset}
+ onSave={this.handleSave}
+ {...this.state}
+ />
);
}
}
-
-const mapStateToProps = (state: Store, ownProps: Pick<Props, 'setting'>) => ({
- changedValue: getSettingsAppChangedValue(state, ownProps.setting.definition.key),
- loading: isSettingsAppLoading(state, ownProps.setting.definition.key),
- validationMessage: getSettingsAppValidationMessage(state, ownProps.setting.definition.key)
-});
-
-const mapDispatchToProps = {
- cancelChange: cancelChange as any,
- changeValue: changeValue as any,
- checkValue: checkValue as any,
- passValidation: passValidation as any,
- resetValue: resetValue as any,
- saveValue: saveValue as any
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(Definition);
import { getDefaultValue, isEmptyValue } from '../utils';
type Props = {
- changedValue: string;
+ changedValue?: string;
hasError: boolean;
hasValueChanged: boolean;
isDefault: boolean;
render() {
const { setting, changedValue, isDefault, hasValueChanged } = this.props;
const hasBeenChangedToEmptyValue =
- changedValue != null && isEmptyValue(setting.definition, changedValue);
+ changedValue !== undefined && isEmptyValue(setting.definition, changedValue);
const showReset = hasBeenChangedToEmptyValue || (!isDefault && setting.hasValue);
return (
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 classNames from 'classnames';
+import * as React from 'react';
+import AlertErrorIcon from '../../../components/icons/AlertErrorIcon';
+import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { sanitizeStringRestricted } from '../../../helpers/sanitize';
+import { ExtendedSettingDefinition, SettingValue } from '../../../types/settings';
+import {
+ combineDefinitionAndSettingValue,
+ getPropertyDescription,
+ getPropertyName,
+ getSettingValue,
+ isDefaultOrInherited
+} from '../utils';
+import DefinitionActions from './DefinitionActions';
+import Input from './inputs/Input';
+
+export interface DefinitionRendererProps {
+ definition: ExtendedSettingDefinition;
+ changedValue?: string;
+ loading: boolean;
+ success: boolean;
+ validationMessage?: string;
+ settingValue?: SettingValue;
+ onCancel: () => void;
+ onChange: (value: any) => void;
+ onSave: () => void;
+ onReset: () => void;
+}
+
+const formNoop = (e: React.FormEvent<HTMLFormElement>) => e.preventDefault();
+
+export default function DefinitionRenderer(props: DefinitionRendererProps) {
+ const { changedValue, loading, validationMessage, settingValue, success, definition } = props;
+
+ const propertyName = getPropertyName(definition);
+ const hasError = validationMessage != null;
+ const hasValueChanged = changedValue != null;
+ const effectiveValue = hasValueChanged ? changedValue : getSettingValue(definition, settingValue);
+ const isDefault = isDefaultOrInherited(settingValue);
+ const description = getPropertyDescription(definition);
+
+ const settingDefinitionAndValue = combineDefinitionAndSettingValue(definition, settingValue);
+
+ return (
+ <div
+ className={classNames('settings-definition', {
+ 'settings-definition-changed': hasValueChanged
+ })}
+ data-key={definition.key}>
+ <div className="settings-definition-left">
+ <h3 className="settings-definition-name" title={propertyName}>
+ {propertyName}
+ </h3>
+
+ {description && (
+ <div
+ className="markdown small spacer-top"
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: sanitizeStringRestricted(description) }}
+ />
+ )}
+
+ <div className="settings-definition-key note little-spacer-top">
+ {translateWithParameters('settings.key_x', definition.key)}
+ </div>
+ </div>
+
+ <div className="settings-definition-right">
+ <div className="settings-definition-state">
+ {loading && (
+ <span className="text-info">
+ <i className="spinner spacer-right" />
+ {translate('settings.state.saving')}
+ </span>
+ )}
+
+ {!loading && validationMessage && (
+ <span className="text-danger">
+ <AlertErrorIcon className="spacer-right" />
+ <span>
+ {translateWithParameters('settings.state.validation_failed', validationMessage)}
+ </span>
+ </span>
+ )}
+
+ {!loading && !hasError && success && (
+ <span className="text-success">
+ <AlertSuccessIcon className="spacer-right" />
+ {translate('settings.state.saved')}
+ </span>
+ )}
+ </div>
+ <form onSubmit={formNoop}>
+ <Input
+ hasValueChanged={hasValueChanged}
+ onCancel={props.onCancel}
+ onChange={props.onChange}
+ onSave={props.onSave}
+ setting={settingDefinitionAndValue}
+ value={effectiveValue}
+ />
+ <DefinitionActions
+ changedValue={changedValue}
+ hasError={hasError}
+ hasValueChanged={hasValueChanged}
+ isDefault={isDefault}
+ onCancel={props.onCancel}
+ onReset={props.onReset}
+ onSave={props.onSave}
+ setting={settingDefinitionAndValue}
+ />
+ </form>
+ </div>
+ </div>
+ );
+}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Setting } from '../../../types/settings';
+import { SettingDefinitionAndValue } from '../../../types/settings';
import { Component } from '../../../types/types';
import Definition from './Definition';
interface Props {
component?: Component;
scrollToDefinition: (element: HTMLLIElement) => void;
- settings: Setting[];
+ settings: SettingDefinitionAndValue[];
}
export default function DefinitionsList(props: Props) {
key={setting.definition.key}
data-key={setting.definition.key}
ref={props.scrollToDefinition}>
- <Definition component={component} setting={setting} />
+ <Definition
+ component={component}
+ definition={setting.definition}
+ initialSettingValue={setting.settingValue}
+ />
</li>
))}
</ul>
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
import SelectLegacy from '../../../components/controls/SelectLegacy';
import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import { translate } from '../../../helpers/l10n';
-import { getSettingsAppAllCategories, Store } from '../../../store/rootReducer';
import { getCategoryName } from '../utils';
import { AdditionalCategoryComponentProps } from './AdditionalCategories';
import { LANGUAGES_CATEGORY } from './AdditionalCategoryKeys';
import CATEGORY_OVERRIDES from './CategoryOverrides';
export interface LanguagesProps extends AdditionalCategoryComponentProps {
- categories: string[];
location: Location;
router: Router;
}
}
export function Languages(props: LanguagesProps) {
- const { categories, component, location, router, selectedCategory } = props;
+ const { categories, component, definitions, location, router, selectedCategory } = props;
const { availableLanguages, selectedLanguage } = getLanguages(categories, selectedCategory);
const handleOnChange = (newOption: SelectOption) => {
</div>
{selectedLanguage && (
<div className="settings-sub-category">
- <CategoryDefinitionsList category={selectedLanguage} component={component} />
+ <CategoryDefinitionsList
+ category={selectedLanguage}
+ component={component}
+ definitions={definitions}
+ />
</div>
)}
</>
};
}
-export default withRouter(
- connect((state: Store) => ({
- categories: getSettingsAppAllCategories(state)
- }))(Languages)
-);
+export default withRouter(Languages);
import * as React from 'react';
import InstanceMessage from '../../../components/common/InstanceMessage';
import { translate } from '../../../helpers/l10n';
+import { ExtendedSettingDefinition } from '../../../types/settings';
import { Component } from '../../../types/types';
import SettingsSearch from './SettingsSearch';
export interface PageHeaderProps {
component?: Component;
+ definitions: ExtendedSettingDefinition[];
}
-export default function PageHeader({ component }: PageHeaderProps) {
+export default function PageHeader({ component, definitions }: PageHeaderProps) {
const title = component ? translate('project_settings.page') : translate('settings.page');
const description = component ? (
<div className="top-bar-inner bordered-bottom big-padded-top padded-bottom">
<h1 className="page-title">{title}</h1>
<div className="page-description spacer-top">{description}</div>
- <SettingsSearch className="big-spacer-top" component={component} />
+ <SettingsSearch
+ className="big-spacer-top"
+ component={component}
+ definitions={definitions}
+ />
</div>
</div>
</header>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { find } from 'lodash';
import * as React from 'react';
-import { Helmet } from 'react-helmet-async';
-import { connect } from 'react-redux';
-import { WithRouterProps } from 'react-router';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
-import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
-import { translate } from '../../../helpers/l10n';
+import { getDefinitions } from '../../../api/settings';
import {
addSideBarClass,
addWhitePageClass,
removeSideBarClass,
removeWhitePageClass
} from '../../../helpers/pages';
-import { getSettingsAppDefaultCategory, Store } from '../../../store/rootReducer';
+import { ExtendedSettingDefinition } from '../../../types/settings';
import { Component } from '../../../types/types';
-import { fetchSettings } from '../store/actions';
import '../styles.css';
-import { ADDITIONAL_CATEGORIES } from './AdditionalCategories';
-import AllCategoriesList from './AllCategoriesList';
-import CategoryDefinitionsList from './CategoryDefinitionsList';
-import CATEGORY_OVERRIDES from './CategoryOverrides';
-import PageHeader from './PageHeader';
+import SettingsAppRenderer from './SettingsAppRenderer';
interface Props {
component?: Component;
- defaultCategory: string;
- fetchSettings(component?: string): Promise<void>;
}
interface State {
+ definitions: ExtendedSettingDefinition[];
loading: boolean;
}
-export class SettingsApp extends React.PureComponent<Props & WithRouterProps, State> {
+export default class SettingsApp extends React.PureComponent<Props, State> {
mounted = false;
- state: State = { loading: true };
+ state: State = { definitions: [], loading: true };
componentDidMount() {
this.mounted = true;
removeWhitePageClass();
}
- fetchSettings = () => {
+ fetchSettings = async () => {
const { component } = this.props;
- this.props.fetchSettings(component && component.key).then(this.stopLoading, this.stopLoading);
- };
- stopLoading = () => {
+ const definitions: ExtendedSettingDefinition[] = await getDefinitions(
+ component?.key
+ ).catch(() => []);
+
if (this.mounted) {
- this.setState({ loading: false });
+ this.setState({ definitions, loading: false });
}
};
render() {
- if (this.state.loading) {
- return null;
- }
-
- const { query } = this.props.location;
- const originalCategory = (query.category as string) || this.props.defaultCategory;
- const overriddenCategory = CATEGORY_OVERRIDES[originalCategory.toLowerCase()];
- const selectedCategory = overriddenCategory || originalCategory;
- const foundAdditionalCategory = find(ADDITIONAL_CATEGORIES, c => c.key === selectedCategory);
- const isProjectSettings = this.props.component;
- const shouldRenderAdditionalCategory =
- foundAdditionalCategory &&
- ((isProjectSettings && foundAdditionalCategory.availableForProject) ||
- (!isProjectSettings && foundAdditionalCategory.availableGlobally));
-
- return (
- <div id="settings-page">
- <Suggestions suggestions="settings" />
- <Helmet defer={false} title={translate('settings.page')} />
- <PageHeader component={this.props.component} />
-
- <div className="layout-page">
- <ScreenPositionHelper className="layout-page-side-outer">
- {({ top }) => (
- <div className="layout-page-side" style={{ top }}>
- <div className="layout-page-side-inner">
- <AllCategoriesList
- component={this.props.component}
- defaultCategory={this.props.defaultCategory}
- selectedCategory={selectedCategory}
- />
- </div>
- </div>
- )}
- </ScreenPositionHelper>
-
- <div className="layout-page-main">
- <div className="layout-page-main-inner">
- <div className="big-padded">
- {foundAdditionalCategory && shouldRenderAdditionalCategory ? (
- foundAdditionalCategory.renderComponent({
- component: this.props.component,
- selectedCategory: originalCategory
- })
- ) : (
- <CategoryDefinitionsList
- category={selectedCategory}
- component={this.props.component}
- />
- )}
- </div>
- </div>
- </div>
- </div>
- </div>
- );
+ const { component } = this.props;
+ return <SettingsAppRenderer component={component} {...this.state} />;
}
}
-
-const mapStateToProps = (state: Store) => ({
- defaultCategory: getSettingsAppDefaultCategory(state)
-});
-
-const mapDispatchToProps = { fetchSettings: fetchSettings as any };
-
-export default connect(mapStateToProps, mapDispatchToProps)(SettingsApp);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { uniqBy } from 'lodash';
+import * as React from 'react';
+import { Helmet } from 'react-helmet-async';
+import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
+import { Location, withRouter } from '../../../components/hoc/withRouter';
+import { translate } from '../../../helpers/l10n';
+import { ExtendedSettingDefinition } from '../../../types/settings';
+import { Component } from '../../../types/types';
+import { getDefaultCategory } from '../utils';
+import { ADDITIONAL_CATEGORIES } from './AdditionalCategories';
+import AllCategoriesList from './AllCategoriesList';
+import CategoryDefinitionsList from './CategoryDefinitionsList';
+import CATEGORY_OVERRIDES from './CategoryOverrides';
+import PageHeader from './PageHeader';
+
+export interface SettingsAppRendererProps {
+ definitions: ExtendedSettingDefinition[];
+ component?: Component;
+ loading: boolean;
+ location: Location;
+}
+
+export function SettingsAppRenderer(props: SettingsAppRendererProps) {
+ const { definitions, component, loading, location } = props;
+
+ const categories = React.useMemo(() => {
+ return uniqBy(
+ definitions.map(definition => definition.category),
+ category => category.toLowerCase()
+ );
+ }, [definitions]);
+
+ if (loading) {
+ return null;
+ }
+
+ const { query } = location;
+ const defaultCategory = getDefaultCategory(categories);
+ const originalCategory = (query.category as string) || defaultCategory;
+ const overriddenCategory = CATEGORY_OVERRIDES[originalCategory.toLowerCase()];
+ const selectedCategory = overriddenCategory || originalCategory;
+ const foundAdditionalCategory = ADDITIONAL_CATEGORIES.find(c => c.key === selectedCategory);
+ const isProjectSettings = component;
+ const shouldRenderAdditionalCategory =
+ foundAdditionalCategory &&
+ ((isProjectSettings && foundAdditionalCategory.availableForProject) ||
+ (!isProjectSettings && foundAdditionalCategory.availableGlobally));
+
+ return (
+ <div id="settings-page">
+ <Suggestions suggestions="settings" />
+ <Helmet defer={false} title={translate('settings.page')} />
+ <PageHeader component={component} definitions={definitions} />
+
+ <div className="layout-page">
+ <ScreenPositionHelper className="layout-page-side-outer">
+ {({ top }) => (
+ <div className="layout-page-side" style={{ top }}>
+ <div className="layout-page-side-inner">
+ <AllCategoriesList
+ categories={categories}
+ component={component}
+ defaultCategory={defaultCategory}
+ selectedCategory={selectedCategory}
+ />
+ </div>
+ </div>
+ )}
+ </ScreenPositionHelper>
+
+ <div className="layout-page-main">
+ <div className="layout-page-main-inner">
+ <div className="big-padded">
+ {foundAdditionalCategory && shouldRenderAdditionalCategory ? (
+ foundAdditionalCategory.renderComponent({
+ categories,
+ component,
+ definitions,
+ selectedCategory: originalCategory
+ })
+ ) : (
+ <CategoryDefinitionsList
+ category={selectedCategory}
+ component={component}
+ definitions={definitions}
+ />
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
+
+export default withRouter(SettingsAppRenderer);
import { debounce, keyBy } from 'lodash';
import lunr, { LunrIndex } from 'lunr';
import * as React from 'react';
-import { connect } from 'react-redux';
import { InjectedRouter } from 'react-router';
import { withRouter } from '../../../components/hoc/withRouter';
import { KeyboardCodes } from '../../../helpers/keycodes';
-import { getSettingsAppAllDefinitions, Store } from '../../../store/rootReducer';
-import { SettingCategoryDefinition } from '../../../types/settings';
+import { ExtendedSettingDefinition } from '../../../types/settings';
import { Component, Dict } from '../../../types/types';
import {
ADDITIONAL_PROJECT_SETTING_DEFINITIONS,
interface Props {
className?: string;
component?: Component;
- definitions: SettingCategoryDefinition[];
+ definitions: ExtendedSettingDefinition[];
router: InjectedRouter;
}
interface State {
- results?: SettingCategoryDefinition[];
+ results?: ExtendedSettingDefinition[];
searchQuery: string;
selectedResult?: string;
showResults: boolean;
const DEBOUNCE_DELAY = 250;
export class SettingsSearch extends React.Component<Props, State> {
- definitionsByKey: Dict<SettingCategoryDefinition>;
+ definitionsByKey: Dict<ExtendedSettingDefinition>;
index: LunrIndex;
state: State = {
searchQuery: '',
this.definitionsByKey = keyBy(definitions, 'key');
}
- buildSearchIndex(definitions: SettingCategoryDefinition[]) {
+ buildSearchIndex(definitions: ExtendedSettingDefinition[]) {
return lunr(function() {
this.ref('key');
this.field('key');
}
}
-const mapStateToProps = (state: Store) => ({
- definitions: getSettingsAppAllDefinitions(state)
-});
-
-export default withRouter(connect(mapStateToProps)(SettingsSearch));
+export default withRouter(SettingsSearch);
import SearchBox from '../../../components/controls/SearchBox';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { scrollToElement } from '../../../helpers/scrolling';
-import { SettingCategoryDefinition } from '../../../types/settings';
+import { ExtendedSettingDefinition } from '../../../types/settings';
import { Component } from '../../../types/types';
import { buildSettingLink, isRealSettingKey } from '../utils';
export interface SettingsSearchRendererProps {
className?: string;
component?: Component;
- results?: SettingCategoryDefinition[];
+ results?: ExtendedSettingDefinition[];
searchQuery: string;
selectedResult?: string;
showResults: boolean;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { groupBy, isEqual, sortBy } from 'lodash';
+import { groupBy, sortBy } from 'lodash';
import * as React from 'react';
import { Location, withRouter } from '../../../components/hoc/withRouter';
import { sanitizeStringRestricted } from '../../../helpers/sanitize';
import { scrollToElement } from '../../../helpers/scrolling';
-import { SettingWithCategory } from '../../../types/settings';
+import { SettingDefinitionAndValue } from '../../../types/settings';
import { Component } from '../../../types/types';
import { getSubCategoryDescription, getSubCategoryName } from '../utils';
import DefinitionsList from './DefinitionsList';
export interface SubCategoryDefinitionsListProps {
category: string;
component?: Component;
- fetchValues: Function;
location: Location;
- settings: Array<SettingWithCategory>;
+ settings: Array<SettingDefinitionAndValue>;
subCategory?: string;
}
export class SubCategoryDefinitionsList extends React.PureComponent<
SubCategoryDefinitionsListProps
> {
- componentDidMount() {
- this.fetchValues();
- }
-
componentDidUpdate(prevProps: SubCategoryDefinitionsListProps) {
- const prevKeys = prevProps.settings.map(setting => setting.definition.key);
- const keys = this.props.settings.map(setting => setting.definition.key);
- if (prevProps.component !== this.props.component || !isEqual(prevKeys, keys)) {
- this.fetchValues();
- }
-
const { hash } = this.props.location;
if (hash && prevProps.location.hash !== hash) {
const query = `[data-key=${hash.substr(1).replace(/[.#/]/g, '\\$&')}]`;
}
};
- fetchValues() {
- const keys = this.props.settings.map(setting => setting.definition.key);
- return this.props.fetchValues(keys, this.props.component && this.props.component.key);
- }
-
renderEmailForm = (subCategoryKey: string) => {
const isEmailSettings = this.props.category === 'general' && subCategoryKey === 'email';
if (!isEmailSettings) {
ADDITIONAL_CATEGORIES.forEach(cat => {
expect(
cat.renderComponent({
+ categories: [],
component: mockComponent(),
+ definitions: [],
selectedCategory: 'TEST'
})
).toMatchSnapshot();
c => c.key === PULL_REQUEST_DECORATION_BINDING_CATEGORY
);
- expect(category!.renderComponent({ component: undefined, selectedCategory: '' })).toBeUndefined();
+ expect(
+ category!.renderComponent({
+ categories: [],
+ component: undefined,
+ definitions: [],
+ selectedCategory: ''
+ })
+ ).toBeUndefined();
});
});
function shallowRender() {
- return shallow(<AnalysisScope component={mockComponent()} selectedCategory="TEST" />);
+ return shallow(
+ <AnalysisScope
+ categories={[]}
+ component={mockComponent()}
+ definitions={[]}
+ selectedCategory="TEST"
+ />
+ );
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { getValues } from '../../../../api/settings';
+import { mockComponent } from '../../../../helpers/mocks/component';
+import { mockDefinition, mockSettingValue } from '../../../../helpers/mocks/settings';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import CategoryDefinitionsList from '../CategoryDefinitionsList';
+
+jest.mock('../../../../api/settings', () => ({
+ getValues: jest.fn().mockResolvedValue([])
+}));
+
+it('should load settings values', async () => {
+ const settings = [mockSettingValue({ key: 'yes' }), mockSettingValue({ key: 'yesagain' })];
+ (getValues as jest.Mock).mockResolvedValueOnce(settings);
+
+ const definitions = [
+ mockDefinition({ category: 'general', key: 'yes' }),
+ mockDefinition({ category: 'other', key: 'nope' }),
+ mockDefinition({ category: 'general', key: 'yesagain' })
+ ];
+
+ const wrapper = shallowRender({
+ definitions
+ });
+
+ await waitAndUpdate(wrapper);
+
+ expect(getValues).toBeCalledWith({ keys: 'yes,yesagain', component: undefined });
+
+ expect(wrapper.state().settings).toEqual([
+ { definition: definitions[0], settingValue: settings[0] },
+ { definition: definitions[2], settingValue: settings[1] }
+ ]);
+});
+
+it('should reload on category change', async () => {
+ const definitions = [
+ mockDefinition({ category: 'general', key: 'yes' }),
+ mockDefinition({ category: 'other', key: 'nope' }),
+ mockDefinition({ category: 'general', key: 'yesagain' })
+ ];
+ const wrapper = shallowRender({ component: mockComponent({ key: 'comp-key' }), definitions });
+
+ await waitAndUpdate(wrapper);
+
+ expect(getValues).toBeCalledWith({ keys: 'yes,yesagain', component: 'comp-key' });
+
+ wrapper.setProps({ category: 'other' });
+
+ await waitAndUpdate(wrapper);
+
+ expect(getValues).toBeCalledWith({ keys: 'nope', component: 'comp-key' });
+});
+
+function shallowRender(props: Partial<CategoryDefinitionsList['props']> = {}) {
+ return shallow<CategoryDefinitionsList>(
+ <CategoryDefinitionsList category="general" definitions={[]} {...props} />
+ );
+}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockSetting } from '../../../../helpers/mocks/settings';
+import { getValues, resetSettingValue, setSettingValue } from '../../../../api/settings';
+import { mockDefinition, mockSettingValue } from '../../../../helpers/mocks/settings';
import { waitAndUpdate } from '../../../../helpers/testUtils';
-import { Definition } from '../Definition';
-import DefinitionActions from '../DefinitionActions';
-import Input from '../inputs/Input';
+import { SettingType } from '../../../../types/settings';
+import Definition from '../Definition';
-const setting = mockSetting();
+jest.mock('../../../../api/settings', () => ({
+ getValues: jest.fn().mockResolvedValue([]),
+ resetSettingValue: jest.fn().mockResolvedValue(undefined),
+ setSettingValue: jest.fn().mockResolvedValue(undefined)
+}));
beforeAll(() => {
jest.useFakeTimers();
jest.useRealTimers();
});
-it('should render correctly', () => {
- const wrapper = shallowRender();
- expect(wrapper).toMatchSnapshot();
+beforeEach(() => {
+ jest.clearAllMocks();
});
-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);
- expect(changeValue).toHaveBeenCalledWith(setting.definition.key, 5);
- expect(checkValue).toHaveBeenCalledWith(setting.definition.key);
+describe('Handle change (and check)', () => {
+ it.each([
+ ['empty, no default', mockDefinition(), '', 'settings.state.value_cant_be_empty_no_default'],
+ [
+ 'empty, default',
+ mockDefinition({ defaultValue: 'dflt' }),
+ '',
+ 'settings.state.value_cant_be_empty'
+ ],
+ [
+ 'invalid url',
+ mockDefinition({ key: 'sonar.core.serverBaseURL' }),
+ '%invalid',
+ 'settings.state.url_not_valid.%invalid'
+ ],
+ [
+ 'valid url',
+ mockDefinition({ key: 'sonar.core.serverBaseURL' }),
+ 'http://www.sonarqube.org',
+ undefined
+ ],
+ [
+ 'invalid JSON',
+ mockDefinition({ type: SettingType.JSON }),
+ '{{broken: "json}',
+ 'Unexpected token { in JSON at position 1'
+ ],
+ ['valid JSON', mockDefinition({ type: SettingType.JSON }), '{"validJson": true}', undefined]
+ ])(
+ 'should handle change (and check value): %s',
+ (_caseName, definition, changedValue, expectedValidationMessage) => {
+ const wrapper = shallowRender({ definition });
+
+ wrapper.instance().handleChange(changedValue);
+
+ expect(wrapper.state().changedValue).toBe(changedValue);
+ expect(wrapper.state().success).toBe(false);
+ expect(wrapper.state().validationMessage).toBe(expectedValidationMessage);
+ }
+ );
});
-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')();
- expect(cancelChange).toHaveBeenCalledWith(setting.definition.key);
- expect(passValidation).toHaveBeenCalledWith(setting.definition.key);
+it('should handle cancel', () => {
+ const wrapper = shallowRender();
+ wrapper.setState({ changedValue: 'whatever', validationMessage: 'something wrong' });
+
+ wrapper.instance().handleCancel();
+
+ expect(wrapper.state().changedValue).toBeUndefined();
+ expect(wrapper.state().validationMessage).toBeUndefined();
});
-it('should correctly save value change', async () => {
- const saveValue = jest.fn().mockResolvedValue({});
- const wrapper = shallowRender({ changedValue: 10, saveValue });
- wrapper.find(DefinitionActions).prop('onSave')();
- await waitAndUpdate(wrapper);
- expect(saveValue).toHaveBeenCalledWith(setting.definition.key, undefined);
- expect(wrapper.find('AlertSuccessIcon').exists()).toBe(true);
- expect(wrapper.state().success).toBe(true);
- jest.runAllTimers();
- expect(wrapper.state().success).toBe(false);
+describe('handleSave', () => {
+ it('should ignore when value unchanged', () => {
+ const wrapper = shallowRender();
+
+ wrapper.instance().handleSave();
+
+ expect(wrapper.state().loading).toBe(false);
+ expect(setSettingValue).not.toBeCalled();
+ });
+
+ it('should handle an empty value', () => {
+ const wrapper = shallowRender();
+
+ wrapper.setState({ changedValue: '' });
+
+ wrapper.instance().handleSave();
+
+ expect(wrapper.state().loading).toBe(false);
+ expect(wrapper.state().validationMessage).toBe('settings.state.value_cant_be_empty');
+ expect(setSettingValue).not.toBeCalled();
+ });
+
+ it('should save and update setting value', async () => {
+ const settingValue = mockSettingValue();
+ (getValues as jest.Mock).mockResolvedValueOnce([settingValue]);
+ const definition = mockDefinition();
+ const wrapper = shallowRender({ definition });
+
+ wrapper.setState({ changedValue: 'new value' });
+
+ wrapper.instance().handleSave();
+
+ expect(wrapper.state().loading).toBe(true);
+
+ await waitAndUpdate(wrapper);
+
+ expect(setSettingValue).toBeCalledWith(definition, 'new value', undefined);
+ expect(getValues).toBeCalledWith({ keys: definition.key, component: undefined });
+ expect(wrapper.state().changedValue).toBeUndefined();
+ expect(wrapper.state().loading).toBe(false);
+ expect(wrapper.state().success).toBe(true);
+ expect(wrapper.state().settingValue).toBe(settingValue);
+
+ jest.runAllTimers();
+ expect(wrapper.state().success).toBe(false);
+ });
});
-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('onReset')();
+it('should reset and update setting value', async () => {
+ const settingValue = mockSettingValue();
+ (getValues as jest.Mock).mockResolvedValueOnce([settingValue]);
+ const definition = mockDefinition();
+ const wrapper = shallowRender({ definition });
+
+ wrapper.instance().handleReset();
+
+ expect(wrapper.state().loading).toBe(true);
+
await waitAndUpdate(wrapper);
- expect(resetValue).toHaveBeenCalledWith(setting.definition.key, undefined);
- expect(cancelChange).toHaveBeenCalledWith(setting.definition.key);
+
+ expect(resetSettingValue).toBeCalledWith({ keys: definition.key, component: undefined });
+ expect(getValues).toBeCalledWith({ keys: definition.key, component: undefined });
+ expect(wrapper.state().changedValue).toBeUndefined();
+ expect(wrapper.state().loading).toBe(false);
expect(wrapper.state().success).toBe(true);
+ expect(wrapper.state().settingValue).toBe(settingValue);
+
jest.runAllTimers();
expect(wrapper.state().success).toBe(false);
});
function shallowRender(props: Partial<Definition['props']> = {}) {
- return shallow<Definition>(
- <Definition
- cancelChange={jest.fn()}
- changeValue={jest.fn()}
- changedValue={null}
- checkValue={jest.fn()}
- loading={false}
- passValidation={jest.fn()}
- resetValue={jest.fn().mockResolvedValue({})}
- saveValue={jest.fn().mockResolvedValue({})}
- setting={setting}
- {...props}
- />
- );
+ return shallow<Definition>(<Definition definition={mockDefinition()} {...props} />);
}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { SettingCategoryDefinition, SettingType } from '../../../../types/settings';
+import { ExtendedSettingDefinition, SettingType } from '../../../../types/settings';
import DefinitionActions from '../DefinitionActions';
-const definition: SettingCategoryDefinition = {
+const definition: ExtendedSettingDefinition = {
category: 'baz',
description: 'lorem',
fields: [],
hasError={hasError}
hasValueChanged={changedValue !== ''}
isDefault={isDefault}
- onCancel={() => {}}
- onReset={() => {}}
- onSave={() => {}}
+ onCancel={jest.fn()}
+ onReset={jest.fn()}
+ onSave={jest.fn()}
setting={settings}
/>
);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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, mockSettingValue } from '../../../../helpers/mocks/settings';
+import DefinitionRenderer, { DefinitionRendererProps } from '../DefinitionRenderer';
+
+it('should render correctly', () => {
+ expect(shallowRender({ loading: true })).toMatchSnapshot('loading');
+ expect(
+ shallowRender({ definition: mockDefinition({ description: 'description' }) })
+ ).toMatchSnapshot('with description');
+ expect(
+ shallowRender({
+ validationMessage: 'validation message'
+ })
+ ).toMatchSnapshot('in error');
+ expect(shallowRender({ success: true })).toMatchSnapshot('success');
+ expect(
+ shallowRender({ settingValue: mockSettingValue({ key: 'foo', value: 'original value' }) })
+ ).toMatchSnapshot('original value');
+
+ expect(shallowRender({ changedValue: 'new value' })).toMatchSnapshot('changed value');
+});
+
+function shallowRender(props: Partial<DefinitionRendererProps> = {}) {
+ return shallow<DefinitionRendererProps>(
+ <DefinitionRenderer
+ definition={mockDefinition()}
+ loading={false}
+ onCancel={jest.fn()}
+ onChange={jest.fn()}
+ onReset={jest.fn()}
+ onSave={jest.fn()}
+ success={false}
+ {...props}
+ />
+ );
+}
<Languages
categories={['Java', 'JavaScript', 'COBOL']}
component={undefined}
+ definitions={[]}
location={mockLocation()}
router={mockRouter()}
selectedCategory="java"
});
function shallowRender(props: Partial<PageHeaderProps> = {}) {
- return shallow<PageHeaderProps>(<PageHeader {...props} />);
+ return shallow<PageHeaderProps>(<PageHeader definitions={[]} {...props} />);
}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper';
+import { getDefinitions } from '../../../../api/settings';
+import { mockComponent } from '../../../../helpers/mocks/component';
import {
addSideBarClass,
addWhitePageClass,
removeSideBarClass,
removeWhitePageClass
} from '../../../../helpers/pages';
-import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
-import {
- ALM_INTEGRATION,
- ANALYSIS_SCOPE_CATEGORY,
- LANGUAGES_CATEGORY,
- NEW_CODE_PERIOD_CATEGORY,
- PULL_REQUEST_DECORATION_BINDING_CATEGORY
-} from '../AdditionalCategoryKeys';
-import { SettingsApp } from '../SettingsApp';
+import SettingsApp from '../SettingsApp';
jest.mock('../../../../helpers/pages', () => ({
addSideBarClass: jest.fn(),
removeWhitePageClass: jest.fn()
}));
+jest.mock('../../../../api/settings', () => ({
+ getDefinitions: jest.fn().mockResolvedValue([])
+}));
+
it('should render default view correctly', async () => {
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
- expect(wrapper.find(ScreenPositionHelper).dive()).toMatchSnapshot();
+
+ expect(getDefinitions).toBeCalledWith(undefined);
wrapper.unmount();
expect(removeWhitePageClass).toBeCalled();
});
-it('should render newCodePeriod correctly', async () => {
- const wrapper = shallowRender({
- location: mockLocation({ query: { category: NEW_CODE_PERIOD_CATEGORY } })
- });
+it('should fetch definitions for component', async () => {
+ const key = 'component-key';
+ const wrapper = shallowRender({ component: mockComponent({ key }) });
await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should render languages correctly', async () => {
- const wrapper = shallowRender({
- location: mockLocation({ query: { category: LANGUAGES_CATEGORY } })
- });
-
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should render analysis scope correctly', async () => {
- const wrapper = shallowRender({
- location: mockLocation({ query: { category: ANALYSIS_SCOPE_CATEGORY } })
- });
-
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should render ALM integration correctly', async () => {
- const wrapper = shallowRender({
- location: mockLocation({ query: { category: ALM_INTEGRATION } })
- });
-
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should render pull request decoration binding correctly', async () => {
- const wrapper = shallowRender({
- location: mockLocation({ query: { category: PULL_REQUEST_DECORATION_BINDING_CATEGORY } })
- });
-
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
+ expect(getDefinitions).toBeCalledWith(key);
});
function shallowRender(props: Partial<SettingsApp['props']> = {}) {
- return shallow(
- <SettingsApp
- defaultCategory="general"
- fetchSettings={jest.fn().mockResolvedValue({})}
- location={mockLocation()}
- params={{}}
- router={mockRouter()}
- routes={[]}
- {...props}
- />
- );
+ return shallow(<SettingsApp {...props} />);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper';
+import { mockDefinition } from '../../../../helpers/mocks/settings';
+import { mockLocation } from '../../../../helpers/testMocks';
+import {
+ ALM_INTEGRATION,
+ ANALYSIS_SCOPE_CATEGORY,
+ LANGUAGES_CATEGORY,
+ NEW_CODE_PERIOD_CATEGORY,
+ PULL_REQUEST_DECORATION_BINDING_CATEGORY
+} from '../AdditionalCategoryKeys';
+import { SettingsAppRenderer, SettingsAppRendererProps } from '../SettingsAppRenderer';
+
+it('should render loading correctly', () => {
+ expect(shallowRender({ loading: true }).type()).toBeNull();
+});
+
+it('should render default view correctly', () => {
+ const wrapper = shallowRender();
+
+ expect(wrapper).toMatchSnapshot();
+ expect(wrapper.find(ScreenPositionHelper).dive()).toMatchSnapshot('All Categories List');
+});
+
+it.each([
+ [NEW_CODE_PERIOD_CATEGORY],
+ [LANGUAGES_CATEGORY],
+ [ANALYSIS_SCOPE_CATEGORY],
+ [ALM_INTEGRATION],
+ [PULL_REQUEST_DECORATION_BINDING_CATEGORY]
+])('should render %s correctly', category => {
+ const wrapper = shallowRender({
+ location: mockLocation({ query: { category } })
+ });
+
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<SettingsAppRendererProps> = {}) {
+ const definitions = [mockDefinition(), mockDefinition({ key: 'bar', category: 'general' })];
+ return shallow(
+ <SettingsAppRenderer
+ definitions={definitions}
+ loading={false}
+ location={mockLocation()}
+ {...props}
+ />
+ );
+}
return shallow<SubCategoryDefinitionsListProps>(
<SubCategoryDefinitionsList
category="general"
- fetchValues={jest.fn().mockResolvedValue({})}
location={mockLocation()}
settings={[
mockSettingWithCategory(),
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render additional categories component correctly 1`] = `
-<withRouter(Connect(Languages))
+<withRouter(Languages)
+ categories={Array []}
component={
Object {
"breadcrumbs": Array [],
"tags": Array [],
}
}
+ definitions={Array []}
selectedCategory="TEST"
/>
`;
exports[`should render additional categories component correctly 3`] = `
<AnalysisScope
+ categories={Array []}
component={
Object {
"breadcrumbs": Array [],
"tags": Array [],
}
}
+ definitions={Array []}
selectedCategory="TEST"
/>
`;
exports[`should render additional categories component correctly 4`] = `
<withRouter(Connect(withAppState(AlmIntegration)))
+ categories={Array []}
component={
Object {
"breadcrumbs": Array [],
"tags": Array [],
}
}
+ definitions={Array []}
selectedCategory="TEST"
/>
`;
<div
className="settings-sub-category"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="TEST"
component={
Object {
"tags": Array [],
}
}
+ definitions={Array []}
/>
</div>
</Fragment>
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div
- className="settings-definition"
- data-key="foo"
->
- <div
- className="settings-definition-left"
- >
- <h3
- className="settings-definition-name"
- title="Foo setting"
- >
- Foo setting
- </h3>
- <div
- className="markdown small spacer-top"
- dangerouslySetInnerHTML={
- Object {
- "__html": "When Foo then Bar",
- }
- }
- />
- <div
- className="settings-definition-key note little-spacer-top"
- >
- settings.key_x.foo
- </div>
- </div>
- <div
- className="settings-definition-right"
- >
- <div
- className="settings-definition-state"
- />
- <form
- onSubmit={[Function]}
- >
- <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",
- "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",
- "key": "foo",
- "name": "Foo setting",
- "options": Array [],
- "type": "INTEGER",
- },
- "hasValue": true,
- "inherited": true,
- "key": "foo",
- "value": "42",
- }
- }
- />
- </form>
- </div>
-</div>
-`;
<Button
className="spacer-right button-success"
disabled={true}
- onClick={[Function]}
+ onClick={[MockFunction]}
>
save
</Button>
<ResetButtonLink
className="spacer-right"
- onClick={[Function]}
+ onClick={[MockFunction]}
>
cancel
</ResetButtonLink>
<Button
className="spacer-right button-success"
disabled={true}
- onClick={[Function]}
+ onClick={[MockFunction]}
>
save
</Button>
<ResetButtonLink
className="spacer-right"
- onClick={[Function]}
+ onClick={[MockFunction]}
>
cancel
</ResetButtonLink>
<Button
className="spacer-right button-success"
disabled={false}
- onClick={[Function]}
+ onClick={[MockFunction]}
>
save
</Button>
<ResetButtonLink
className="spacer-right"
- onClick={[Function]}
+ onClick={[MockFunction]}
>
cancel
</ResetButtonLink>
<Button
className="spacer-right button-success"
disabled={false}
- onClick={[Function]}
+ onClick={[MockFunction]}
>
save
</Button>
<ResetButtonLink
className="spacer-right"
- onClick={[Function]}
+ onClick={[MockFunction]}
>
cancel
</ResetButtonLink>
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: changed value 1`] = `
+<div
+ className="settings-definition settings-definition-changed"
+ data-key="foo"
+>
+ <div
+ className="settings-definition-left"
+ >
+ <h3
+ className="settings-definition-name"
+ />
+ <div
+ className="settings-definition-key note little-spacer-top"
+ >
+ settings.key_x.foo
+ </div>
+ </div>
+ <div
+ className="settings-definition-right"
+ >
+ <div
+ className="settings-definition-state"
+ />
+ <form
+ onSubmit={[Function]}
+ >
+ <Input
+ hasValueChanged={true}
+ onCancel={[MockFunction]}
+ onChange={[MockFunction]}
+ onSave={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ "hasValue": false,
+ "key": "foo",
+ }
+ }
+ value="new value"
+ />
+ <DefinitionActions
+ changedValue="new value"
+ hasError={false}
+ hasValueChanged={true}
+ isDefault={false}
+ onCancel={[MockFunction]}
+ onReset={[MockFunction]}
+ onSave={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ "hasValue": false,
+ "key": "foo",
+ }
+ }
+ />
+ </form>
+ </div>
+</div>
+`;
+
+exports[`should render correctly: in error 1`] = `
+<div
+ className="settings-definition"
+ data-key="foo"
+>
+ <div
+ className="settings-definition-left"
+ >
+ <h3
+ className="settings-definition-name"
+ />
+ <div
+ className="settings-definition-key note little-spacer-top"
+ >
+ settings.key_x.foo
+ </div>
+ </div>
+ <div
+ className="settings-definition-right"
+ >
+ <div
+ className="settings-definition-state"
+ >
+ <span
+ className="text-danger"
+ >
+ <AlertErrorIcon
+ className="spacer-right"
+ />
+ <span>
+ settings.state.validation_failed.validation message
+ </span>
+ </span>
+ </div>
+ <form
+ onSubmit={[Function]}
+ >
+ <Input
+ hasValueChanged={false}
+ onCancel={[MockFunction]}
+ onChange={[MockFunction]}
+ onSave={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ "hasValue": false,
+ "key": "foo",
+ }
+ }
+ />
+ <DefinitionActions
+ hasError={true}
+ hasValueChanged={false}
+ isDefault={false}
+ onCancel={[MockFunction]}
+ onReset={[MockFunction]}
+ onSave={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ "hasValue": false,
+ "key": "foo",
+ }
+ }
+ />
+ </form>
+ </div>
+</div>
+`;
+
+exports[`should render correctly: loading 1`] = `
+<div
+ className="settings-definition"
+ data-key="foo"
+>
+ <div
+ className="settings-definition-left"
+ >
+ <h3
+ className="settings-definition-name"
+ />
+ <div
+ className="settings-definition-key note little-spacer-top"
+ >
+ settings.key_x.foo
+ </div>
+ </div>
+ <div
+ className="settings-definition-right"
+ >
+ <div
+ className="settings-definition-state"
+ >
+ <span
+ className="text-info"
+ >
+ <i
+ className="spinner spacer-right"
+ />
+ settings.state.saving
+ </span>
+ </div>
+ <form
+ onSubmit={[Function]}
+ >
+ <Input
+ hasValueChanged={false}
+ onCancel={[MockFunction]}
+ onChange={[MockFunction]}
+ onSave={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ "hasValue": false,
+ "key": "foo",
+ }
+ }
+ />
+ <DefinitionActions
+ hasError={false}
+ hasValueChanged={false}
+ isDefault={false}
+ onCancel={[MockFunction]}
+ onReset={[MockFunction]}
+ onSave={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ "hasValue": false,
+ "key": "foo",
+ }
+ }
+ />
+ </form>
+ </div>
+</div>
+`;
+
+exports[`should render correctly: original value 1`] = `
+<div
+ className="settings-definition"
+ data-key="foo"
+>
+ <div
+ className="settings-definition-left"
+ >
+ <h3
+ className="settings-definition-name"
+ />
+ <div
+ className="settings-definition-key note little-spacer-top"
+ >
+ settings.key_x.foo
+ </div>
+ </div>
+ <div
+ className="settings-definition-right"
+ >
+ <div
+ className="settings-definition-state"
+ />
+ <form
+ onSubmit={[Function]}
+ >
+ <Input
+ hasValueChanged={false}
+ onCancel={[MockFunction]}
+ onChange={[MockFunction]}
+ onSave={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ "hasValue": true,
+ "key": "foo",
+ "value": "original value",
+ }
+ }
+ value="original value"
+ />
+ <DefinitionActions
+ hasError={false}
+ hasValueChanged={false}
+ isDefault={false}
+ onCancel={[MockFunction]}
+ onReset={[MockFunction]}
+ onSave={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ "hasValue": true,
+ "key": "foo",
+ "value": "original value",
+ }
+ }
+ />
+ </form>
+ </div>
+</div>
+`;
+
+exports[`should render correctly: success 1`] = `
+<div
+ className="settings-definition"
+ data-key="foo"
+>
+ <div
+ className="settings-definition-left"
+ >
+ <h3
+ className="settings-definition-name"
+ />
+ <div
+ className="settings-definition-key note little-spacer-top"
+ >
+ settings.key_x.foo
+ </div>
+ </div>
+ <div
+ className="settings-definition-right"
+ >
+ <div
+ className="settings-definition-state"
+ >
+ <span
+ className="text-success"
+ >
+ <AlertSuccessIcon
+ className="spacer-right"
+ />
+ settings.state.saved
+ </span>
+ </div>
+ <form
+ onSubmit={[Function]}
+ >
+ <Input
+ hasValueChanged={false}
+ onCancel={[MockFunction]}
+ onChange={[MockFunction]}
+ onSave={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ "hasValue": false,
+ "key": "foo",
+ }
+ }
+ />
+ <DefinitionActions
+ hasError={false}
+ hasValueChanged={false}
+ isDefault={false}
+ onCancel={[MockFunction]}
+ onReset={[MockFunction]}
+ onSave={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ "hasValue": false,
+ "key": "foo",
+ }
+ }
+ />
+ </form>
+ </div>
+</div>
+`;
+
+exports[`should render correctly: with description 1`] = `
+<div
+ className="settings-definition"
+ data-key="foo"
+>
+ <div
+ className="settings-definition-left"
+ >
+ <h3
+ className="settings-definition-name"
+ />
+ <div
+ className="markdown small spacer-top"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "description",
+ }
+ }
+ />
+ <div
+ className="settings-definition-key note little-spacer-top"
+ >
+ settings.key_x.foo
+ </div>
+ </div>
+ <div
+ className="settings-definition-right"
+ >
+ <div
+ className="settings-definition-state"
+ />
+ <form
+ onSubmit={[Function]}
+ >
+ <Input
+ hasValueChanged={false}
+ onCancel={[MockFunction]}
+ onChange={[MockFunction]}
+ onSave={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "description": "description",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ "hasValue": false,
+ "key": "foo",
+ }
+ }
+ />
+ <DefinitionActions
+ hasError={false}
+ hasValueChanged={false}
+ isDefault={false}
+ onCancel={[MockFunction]}
+ onReset={[MockFunction]}
+ onSave={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "description": "description",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ "hasValue": false,
+ "key": "foo",
+ }
+ }
+ />
+ </form>
+ </div>
+</div>
+`;
<div
className="settings-sub-category"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="java"
+ definitions={Array []}
/>
</div>
</Fragment>
>
project_settings.page.description
</div>
- <withRouter(Connect(SettingsSearch))
+ <withRouter(SettingsSearch)
className="big-spacer-top"
component={
Object {
"tags": Array [],
}
}
+ definitions={Array []}
/>
</div>
</div>
message="settings.page.description"
/>
</div>
- <withRouter(Connect(SettingsSearch))
+ <withRouter(SettingsSearch)
className="big-spacer-top"
+ definitions={Array []}
/>
</div>
</div>
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render ALM integration correctly 1`] = `
-<div
- id="settings-page"
->
- <Suggestions
- suggestions="settings"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- prioritizeSeoTags={false}
- title="settings.page"
- />
- <PageHeader />
- <div
- className="layout-page"
- >
- <ScreenPositionHelper
- className="layout-page-side-outer"
- >
- <Component />
- </ScreenPositionHelper>
- <div
- className="layout-page-main"
- >
- <div
- className="layout-page-main-inner"
- >
- <div
- className="big-padded"
- >
- <withRouter(Connect(withAppState(AlmIntegration)))
- selectedCategory="almintegration"
- />
- </div>
- </div>
- </div>
- </div>
-</div>
-`;
-
-exports[`should render analysis scope correctly 1`] = `
-<div
- id="settings-page"
->
- <Suggestions
- suggestions="settings"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- prioritizeSeoTags={false}
- title="settings.page"
- />
- <PageHeader />
- <div
- className="layout-page"
- >
- <ScreenPositionHelper
- className="layout-page-side-outer"
- >
- <Component />
- </ScreenPositionHelper>
- <div
- className="layout-page-main"
- >
- <div
- className="layout-page-main-inner"
- >
- <div
- className="big-padded"
- >
- <AnalysisScope
- selectedCategory="exclusions"
- />
- </div>
- </div>
- </div>
- </div>
-</div>
-`;
-
exports[`should render default view correctly 1`] = `
-<div
- id="settings-page"
->
- <Suggestions
- suggestions="settings"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- prioritizeSeoTags={false}
- title="settings.page"
- />
- <PageHeader />
- <div
- className="layout-page"
- >
- <ScreenPositionHelper
- className="layout-page-side-outer"
- >
- <Component />
- </ScreenPositionHelper>
- <div
- className="layout-page-main"
- >
- <div
- className="layout-page-main-inner"
- >
- <div
- className="big-padded"
- >
- <Connect(withRouter(SubCategoryDefinitionsList))
- category="general"
- />
- </div>
- </div>
- </div>
- </div>
-</div>
-`;
-
-exports[`should render default view correctly 2`] = `
-<div
- className="layout-page-side-outer"
->
- <div
- className="layout-page-side"
- style={
- Object {
- "top": 0,
- }
- }
- >
- <div
- className="layout-page-side-inner"
- >
- <Connect(CategoriesList)
- defaultCategory="general"
- selectedCategory="general"
- />
- </div>
- </div>
-</div>
-`;
-
-exports[`should render languages correctly 1`] = `
-<div
- id="settings-page"
->
- <Suggestions
- suggestions="settings"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- prioritizeSeoTags={false}
- title="settings.page"
- />
- <PageHeader />
- <div
- className="layout-page"
- >
- <ScreenPositionHelper
- className="layout-page-side-outer"
- >
- <Component />
- </ScreenPositionHelper>
- <div
- className="layout-page-main"
- >
- <div
- className="layout-page-main-inner"
- >
- <div
- className="big-padded"
- >
- <withRouter(Connect(Languages))
- selectedCategory="languages"
- />
- </div>
- </div>
- </div>
- </div>
-</div>
-`;
-
-exports[`should render newCodePeriod correctly 1`] = `
-<div
- id="settings-page"
->
- <Suggestions
- suggestions="settings"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- prioritizeSeoTags={false}
- title="settings.page"
- />
- <PageHeader />
- <div
- className="layout-page"
- >
- <ScreenPositionHelper
- className="layout-page-side-outer"
- >
- <Component />
- </ScreenPositionHelper>
- <div
- className="layout-page-main"
- >
- <div
- className="layout-page-main-inner"
- >
- <div
- className="big-padded"
- >
- <NewCodePeriod />
- </div>
- </div>
- </div>
- </div>
-</div>
-`;
-
-exports[`should render pull request decoration binding correctly 1`] = `
-<div
- id="settings-page"
->
- <Suggestions
- suggestions="settings"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- prioritizeSeoTags={false}
- title="settings.page"
- />
- <PageHeader />
- <div
- className="layout-page"
- >
- <ScreenPositionHelper
- className="layout-page-side-outer"
- >
- <Component />
- </ScreenPositionHelper>
- <div
- className="layout-page-main"
- >
- <div
- className="layout-page-main-inner"
- >
- <div
- className="big-padded"
- >
- <Connect(withRouter(SubCategoryDefinitionsList))
- category="pull_request_decoration_binding"
- />
- </div>
- </div>
- </div>
- </div>
-</div>
+<withRouter(SettingsAppRenderer)
+ definitions={Array []}
+ loading={false}
+/>
`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render almintegration correctly 1`] = `
+<div
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ prioritizeSeoTags={false}
+ title="settings.page"
+ />
+ <PageHeader
+ definitions={
+ Array [
+ Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ Object {
+ "category": "general",
+ "fields": Array [],
+ "key": "bar",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ ]
+ }
+ />
+ <div
+ className="layout-page"
+ >
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ >
+ <Component />
+ </ScreenPositionHelper>
+ <div
+ className="layout-page-main"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <withRouter(Connect(withAppState(AlmIntegration)))
+ categories={
+ Array [
+ "foo category",
+ "general",
+ ]
+ }
+ definitions={
+ Array [
+ Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ Object {
+ "category": "general",
+ "fields": Array [],
+ "key": "bar",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ ]
+ }
+ selectedCategory="almintegration"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render default view correctly 1`] = `
+<div
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ prioritizeSeoTags={false}
+ title="settings.page"
+ />
+ <PageHeader
+ definitions={
+ Array [
+ Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ Object {
+ "category": "general",
+ "fields": Array [],
+ "key": "bar",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ ]
+ }
+ />
+ <div
+ className="layout-page"
+ >
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ >
+ <Component />
+ </ScreenPositionHelper>
+ <div
+ className="layout-page-main"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <CategoryDefinitionsList
+ category="general"
+ definitions={
+ Array [
+ Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ Object {
+ "category": "general",
+ "fields": Array [],
+ "key": "bar",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ ]
+ }
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render default view correctly: All Categories List 1`] = `
+<div
+ className="layout-page-side-outer"
+>
+ <div
+ className="layout-page-side"
+ style={
+ Object {
+ "top": 0,
+ }
+ }
+ >
+ <div
+ className="layout-page-side-inner"
+ >
+ <Connect(CategoriesList)
+ categories={
+ Array [
+ "foo category",
+ "general",
+ ]
+ }
+ defaultCategory="general"
+ selectedCategory="general"
+ />
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render exclusions correctly 1`] = `
+<div
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ prioritizeSeoTags={false}
+ title="settings.page"
+ />
+ <PageHeader
+ definitions={
+ Array [
+ Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ Object {
+ "category": "general",
+ "fields": Array [],
+ "key": "bar",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ ]
+ }
+ />
+ <div
+ className="layout-page"
+ >
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ >
+ <Component />
+ </ScreenPositionHelper>
+ <div
+ className="layout-page-main"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <AnalysisScope
+ categories={
+ Array [
+ "foo category",
+ "general",
+ ]
+ }
+ definitions={
+ Array [
+ Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ Object {
+ "category": "general",
+ "fields": Array [],
+ "key": "bar",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ ]
+ }
+ selectedCategory="exclusions"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render languages correctly 1`] = `
+<div
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ prioritizeSeoTags={false}
+ title="settings.page"
+ />
+ <PageHeader
+ definitions={
+ Array [
+ Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ Object {
+ "category": "general",
+ "fields": Array [],
+ "key": "bar",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ ]
+ }
+ />
+ <div
+ className="layout-page"
+ >
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ >
+ <Component />
+ </ScreenPositionHelper>
+ <div
+ className="layout-page-main"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <withRouter(Languages)
+ categories={
+ Array [
+ "foo category",
+ "general",
+ ]
+ }
+ definitions={
+ Array [
+ Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ Object {
+ "category": "general",
+ "fields": Array [],
+ "key": "bar",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ ]
+ }
+ selectedCategory="languages"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render new_code_period correctly 1`] = `
+<div
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ prioritizeSeoTags={false}
+ title="settings.page"
+ />
+ <PageHeader
+ definitions={
+ Array [
+ Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ Object {
+ "category": "general",
+ "fields": Array [],
+ "key": "bar",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ ]
+ }
+ />
+ <div
+ className="layout-page"
+ >
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ >
+ <Component />
+ </ScreenPositionHelper>
+ <div
+ className="layout-page-main"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <NewCodePeriod />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render pull_request_decoration_binding correctly 1`] = `
+<div
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ prioritizeSeoTags={false}
+ title="settings.page"
+ />
+ <PageHeader
+ definitions={
+ Array [
+ Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ Object {
+ "category": "general",
+ "fields": Array [],
+ "key": "bar",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ ]
+ }
+ />
+ <div
+ className="layout-page"
+ >
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ >
+ <Component />
+ </ScreenPositionHelper>
+ <div
+ className="layout-page-main"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <CategoryDefinitionsList
+ category="pull_request_decoration_binding"
+ definitions={
+ Array [
+ Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ Object {
+ "category": "general",
+ "fields": Array [],
+ "key": "bar",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ },
+ ]
+ }
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
AlmSettingsBindingStatus,
AlmSettingsBindingStatusType
} from '../../../../types/alm-settings';
+import { ExtendedSettingDefinition } from '../../../../types/settings';
import { AppState, Dict } from '../../../../types/types';
import AlmIntegrationRenderer from './AlmIntegrationRenderer';
interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
appState: Pick<AppState, 'branchesEnabled' | 'multipleAlmEnabled'>;
+ definitions: ExtendedSettingDefinition[];
}
export type AlmTabs = AlmKeys.Azure | AlmKeys.GitHub | AlmKeys.GitLab | AlmKeys.BitbucketServer;
render() {
const {
- appState: { branchesEnabled, multipleAlmEnabled }
+ appState: { branchesEnabled, multipleAlmEnabled },
+ definitions: settingsDefinitions
} = this.props;
const {
currentAlmTab,
loadingAlmDefinitions={loadingAlmDefinitions}
loadingProjectCount={loadingProjectCount}
projectCount={projectCount}
+ settingsDefinitions={settingsDefinitions}
/>
);
}
AlmSettingsBindingDefinitions,
AlmSettingsBindingStatus
} from '../../../../types/alm-settings';
+import { ExtendedSettingDefinition } from '../../../../types/settings';
import { Dict } from '../../../../types/types';
import { AlmTabs } from './AlmIntegration';
import AlmTab from './AlmTab';
onSelectAlmTab: (alm: AlmTabs) => void;
onUpdateDefinitions: () => void;
projectCount?: number;
+ settingsDefinitions: ExtendedSettingDefinition[];
}
const tabs = [
loadingProjectCount,
branchesEnabled,
multipleAlmEnabled,
- projectCount
+ projectCount,
+ settingsDefinitions
} = props;
const bindingDefinitions = {
onCheck={props.onCheckConfiguration}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
+ settingsDefinitions={settingsDefinitions}
/>
{definitionKeyForDeletion && (
AlmBindingDefinitionBase,
AlmSettingsBindingStatus
} from '../../../../types/alm-settings';
+import { ExtendedSettingDefinition } from '../../../../types/settings';
import { Dict } from '../../../../types/types';
import { AlmTabs } from './AlmIntegration';
import AlmTabRenderer from './AlmTabRenderer';
onCheck: (definitionKey: string) => void;
onDelete: (definitionKey: string) => void;
onUpdateDefinitions: () => void;
+ settingsDefinitions: ExtendedSettingDefinition[];
}
interface State {
definitionStatus,
loadingAlmDefinitions,
loadingProjectCount,
- multipleAlmEnabled
+ multipleAlmEnabled,
+ settingsDefinitions
} = this.props;
const { editDefinition, editedDefinition } = this.state;
onEdit={this.handleEdit}
onCancel={this.handleCancel}
afterSubmit={this.handleAfterSubmit}
+ settingsDefinitions={settingsDefinitions}
/>
);
}
AlmSettingsBindingStatus,
isBitbucketCloudBindingDefinition
} from '../../../../types/alm-settings';
+import { ExtendedSettingDefinition } from '../../../../types/settings';
import { Dict } from '../../../../types/types';
import { ALM_INTEGRATION } from '../AdditionalCategoryKeys';
import CategoryDefinitionsList from '../CategoryDefinitionsList';
onDelete: (definitionKey: string) => void;
onEdit: (definitionKey: string) => void;
afterSubmit: (config: AlmBindingDefinitionBase) => void;
+ settingsDefinitions: ExtendedSettingDefinition[];
}
export default function AlmTabRenderer(props: AlmTabRendererProps) {
editedDefinition,
loadingAlmDefinitions,
loadingProjectCount,
- multipleAlmEnabled
+ multipleAlmEnabled,
+ settingsDefinitions
} = props;
const preventCreation = loadingProjectCount || (!multipleAlmEnabled && definitions.length > 0);
<div className="huge-spacer-top huge-spacer-bottom bordered-top" />
<div className="big-padded">
- <CategoryDefinitionsList category={ALM_INTEGRATION} subCategory={almTab} />
+ <CategoryDefinitionsList
+ category={ALM_INTEGRATION}
+ definitions={settingsDefinitions}
+ subCategory={almTab}
+ />
</div>
</div>
);
return shallow<AlmIntegration>(
<AlmIntegration
appState={{ branchesEnabled: true }}
+ definitions={[]}
location={mockLocation()}
router={mockRouter()}
{...props}
onDelete={jest.fn()}
onSelectAlmTab={jest.fn()}
onUpdateDefinitions={jest.fn()}
+ settingsDefinitions={[]}
{...props}
/>
);
onCheck={jest.fn()}
onDelete={jest.fn()}
onUpdateDefinitions={jest.fn()}
+ settingsDefinitions={[]}
{...props}
/>
);
onDelete={jest.fn()}
onEdit={jest.fn()}
afterSubmit={jest.fn()}
+ settingsDefinitions={[]}
{...props}
/>
);
onDelete={[Function]}
onSelectAlmTab={[Function]}
onUpdateDefinitions={[Function]}
+ settingsDefinitions={Array []}
/>
`;
onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
+ settingsDefinitions={Array []}
/>
</Fragment>
`;
onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
+ settingsDefinitions={Array []}
/>
</Fragment>
`;
onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
+ settingsDefinitions={Array []}
/>
</Fragment>
`;
onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
+ settingsDefinitions={Array []}
/>
<DeleteModal
id="keyToDelete"
onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
+ settingsDefinitions={Array []}
/>
</Fragment>
`;
onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
+ settingsDefinitions={Array []}
/>
</Fragment>
`;
onCreate={[Function]}
onDelete={[MockFunction]}
onEdit={[Function]}
+ settingsDefinitions={Array []}
/>
`;
<div
className="big-padded"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="almintegration"
+ definitions={Array []}
subCategory="azure"
/>
</div>
<div
className="big-padded"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="almintegration"
+ definitions={Array []}
subCategory="azure"
/>
</div>
<div
className="big-padded"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="almintegration"
+ definitions={Array []}
subCategory="azure"
/>
</div>
<div
className="big-padded"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="almintegration"
+ definitions={Array []}
subCategory="azure"
/>
</div>
<div
className="big-padded"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="almintegration"
+ definitions={Array []}
subCategory="azure"
/>
</div>
<div
className="big-padded"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="almintegration"
+ definitions={Array []}
subCategory="azure"
/>
</div>
<div
className="big-padded"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="almintegration"
+ definitions={Array []}
subCategory="azure"
/>
</div>
<div
className="big-padded"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="almintegration"
+ definitions={Array []}
subCategory="azure"
/>
</div>
<div
className="big-padded"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="almintegration"
+ definitions={Array []}
subCategory="azure"
/>
</div>
<div
className="big-padded"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="almintegration"
+ definitions={Array []}
subCategory="azure"
/>
</div>
<div
className="big-padded"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="almintegration"
+ definitions={Array []}
subCategory="azure"
/>
</div>
<div
className="big-padded"
>
- <Connect(withRouter(SubCategoryDefinitionsList))
+ <CategoryDefinitionsList
category="almintegration"
+ definitions={Array []}
subCategory="bitbucket"
/>
</div>
*/
import * as React from 'react';
import SelectLegacy from '../../../../components/controls/SelectLegacy';
-import { SettingCategoryDefinition } from '../../../../types/settings';
+import { ExtendedSettingDefinition } from '../../../../types/settings';
import { DefaultSpecializedInputProps } from '../../utils';
-type Props = DefaultSpecializedInputProps & Pick<SettingCategoryDefinition, 'options'>;
+type Props = DefaultSpecializedInputProps & Pick<ExtendedSettingDefinition, 'options'>;
export default class InputForSingleSelectList extends React.PureComponent<Props> {
handleInputChange = ({ value }: { value: string }) => {
import { shallow, ShallowWrapper } from 'enzyme';
import * as React from 'react';
import { click } from '../../../../../helpers/testUtils';
-import { SettingCategoryDefinition, SettingType } from '../../../../../types/settings';
+import { ExtendedSettingDefinition, SettingType } from '../../../../../types/settings';
import { DefaultSpecializedInputProps } from '../../../utils';
import MultiValueInput from '../MultiValueInput';
import PrimitiveInput from '../PrimitiveInput';
hasValue: true
};
-const settingDefinition: SettingCategoryDefinition = {
+const settingDefinition: ExtendedSettingDefinition = {
category: 'general',
fields: [],
key: 'example',
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import {
- getSettingsAppChangedValue,
- getSettingsAppDefinition
-} from '../../../../store/rootReducer';
-import { checkValue, fetchSettings, fetchValues } from '../actions';
-import { receiveDefinitions } from '../definitions';
+import { fetchValues } from '../actions';
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'
- }
- ])
+ getValues: jest.fn().mockResolvedValue([mockSettingValue()])
};
});
-jest.mock('../definitions', () => ({
- receiveDefinitions: jest.fn()
-}));
-
-jest.mock('../../../../store/rootReducer', () => ({
- getSettingsAppDefinition: jest.fn(),
- getSettingsAppChangedValue: jest.fn()
-}));
-
-it('#fetchSettings should filter LICENSE type settings', async () => {
- const dispatch = jest.fn();
-
- await fetchSettings()(dispatch);
-
- expect(receiveDefinitions).toHaveBeenCalledWith([
- {
- key: 'SETTINGS_1_KEY',
- type: 'SETTINGS_1_TYPE'
- }
- ]);
-});
-
it('should fetchValue correclty', async () => {
const dispatch = jest.fn();
await fetchValues(['test'], 'foo')(dispatch);
});
expect(dispatch).toHaveBeenCalledWith({ type: 'CLOSE_ALL_GLOBAL_MESSAGES' });
});
-
-describe('checkValue', () => {
- const dispatch = jest.fn();
-
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- it('should correctly identify empty strings', () => {
- (getSettingsAppDefinition as jest.Mock).mockReturnValue({
- defaultValue: 'hello',
- type: 'TEXT'
- });
-
- (getSettingsAppChangedValue as jest.Mock).mockReturnValue(undefined);
- const key = 'key';
- expect(checkValue(key)(dispatch, jest.fn())).toBe(false);
- expect(dispatch).toBeCalledWith({
- type: 'settingsPage/FAIL_VALIDATION',
- key,
- message: 'settings.state.value_cant_be_empty'
- });
- });
-
- it('should correctly identify empty with no default', () => {
- (getSettingsAppDefinition as jest.Mock).mockReturnValue({
- type: 'TEXT'
- });
-
- (getSettingsAppChangedValue as jest.Mock).mockReturnValue(undefined);
-
- const key = 'key';
- expect(checkValue(key)(dispatch, jest.fn())).toBe(false);
- expect(dispatch).toBeCalledWith({
- type: 'settingsPage/FAIL_VALIDATION',
- key,
- message: 'settings.state.value_cant_be_empty_no_default'
- });
- });
-
- it('should correctly identify non-empty strings', () => {
- (getSettingsAppDefinition as jest.Mock).mockReturnValue({
- type: 'TEXT'
- });
-
- (getSettingsAppChangedValue as jest.Mock).mockReturnValue('not empty');
- const key = 'key';
- expect(checkValue(key)(dispatch, jest.fn())).toBe(true);
- expect(dispatch).toBeCalledWith({
- type: 'settingsPage/PASS_VALIDATION',
- key
- });
- });
-
- it('should correctly identify misformed JSON', () => {
- (getSettingsAppDefinition as jest.Mock).mockReturnValue({
- type: 'JSON'
- });
-
- (getSettingsAppChangedValue as jest.Mock).mockReturnValue('{JSON: "asd;{');
- const key = 'key';
- expect(checkValue(key)(dispatch, jest.fn())).toBe(false);
- expect(dispatch).toBeCalledWith({
- type: 'settingsPage/FAIL_VALIDATION',
- key,
- message: 'Unexpected token J in JSON at position 1'
- });
- });
-
- it('should correctly identify correct JSON', () => {
- (getSettingsAppDefinition as jest.Mock).mockReturnValue({
- type: 'JSON'
- });
-
- (getSettingsAppChangedValue as jest.Mock).mockReturnValue(
- '{"number": 42, "question": "answer"}'
- );
- const key = 'key';
- expect(checkValue(key)(dispatch, jest.fn())).toBe(true);
- expect(dispatch).toBeCalledWith({
- type: 'settingsPage/PASS_VALIDATION',
- key
- });
- });
-
- it('should correctly identify URL', () => {
- (getSettingsAppDefinition as jest.Mock).mockReturnValue({
- key: 'sonar.core.serverBaseURL'
- });
-
- (getSettingsAppChangedValue as jest.Mock).mockReturnValue('http://test');
- const key = 'sonar.core.serverBaseURL';
- expect(checkValue(key)(dispatch, jest.fn())).toBe(true);
- expect(dispatch).toBeCalledWith({
- type: 'settingsPage/PASS_VALIDATION',
- key
- });
-
- (getSettingsAppChangedValue as jest.Mock).mockReturnValue('not valid');
- expect(checkValue(key)(dispatch, jest.fn())).toBe(false);
- expect(dispatch).toBeCalledWith({
- type: 'settingsPage/PASS_VALIDATION',
- key
- });
- });
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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);
-});
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { Dispatch } from 'redux';
-import {
- getDefinitions,
- getValues,
- resetSettingValue,
- setSettingValue
-} from '../../../api/settings';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { parseError } from '../../../helpers/request';
+import { getValues } from '../../../api/settings';
import { closeAllGlobalMessages } from '../../../store/globalMessages';
-import {
- getSettingsAppChangedValue,
- getSettingsAppDefinition,
- Store
-} from '../../../store/rootReducer';
-import { SettingDefinition } from '../../../types/settings';
-import { isEmptyValue } from '../utils';
-import { receiveDefinitions } from './definitions';
-import {
- cancelChange,
- failValidation,
- passValidation,
- startLoading,
- stopLoading
-} from './settingsPage';
import { receiveValues } from './values';
-function isURLKind(definition: SettingDefinition) {
- return [
- 'sonar.core.serverBaseURL',
- 'sonar.auth.github.apiUrl',
- 'sonar.auth.github.webUrl',
- 'sonar.auth.gitlab.url',
- 'sonar.lf.gravatarServerUrl',
- 'sonar.lf.logoUrl',
- 'sonar.auth.saml.loginUrl'
- ].includes(definition.key);
-}
-
-export function fetchSettings(component?: string) {
- return (dispatch: Dispatch) => {
- return getDefinitions(component).then(definitions => {
- const filtered = definitions.filter(definition => definition.type !== 'LICENSE');
- dispatch(receiveDefinitions(filtered));
- });
- };
-}
-
export function fetchValues(keys: string[], component?: string) {
return (dispatch: Dispatch) =>
getValues({ keys: keys.join(), component }).then(settings => {
dispatch(closeAllGlobalMessages());
});
}
-
-export function checkValue(key: string) {
- return (dispatch: Dispatch, getState: () => Store) => {
- const state = getState();
- const definition = getSettingsAppDefinition(state, key);
- const value = getSettingsAppChangedValue(state, key);
-
- if (isEmptyValue(definition, value)) {
- if (definition.defaultValue === undefined) {
- dispatch(failValidation(key, translate('settings.state.value_cant_be_empty_no_default')));
- } else {
- dispatch(failValidation(key, translate('settings.state.value_cant_be_empty')));
- }
- return false;
- }
-
- if (isURLKind(definition)) {
- try {
- // eslint-disable-next-line no-new
- new URL(value);
- } catch (e) {
- dispatch(
- failValidation(key, translateWithParameters('settings.state.url_not_valid', value))
- );
- return false;
- }
- }
-
- if (definition.type === 'JSON') {
- try {
- JSON.parse(value);
- } catch (e) {
- if (e instanceof Error) {
- dispatch(failValidation(key, e.message));
- }
- return false;
- }
- }
-
- dispatch(passValidation(key));
- return true;
- };
-}
-
-export function saveValue(key: string, component?: string) {
- return (dispatch: Dispatch, getState: () => Store) => {
- dispatch(startLoading(key));
- const state = getState();
- const definition = getSettingsAppDefinition(state, key);
- const value = getSettingsAppChangedValue(state, key);
-
- if (isEmptyValue(definition, value)) {
- dispatch(failValidation(key, translate('settings.state.value_cant_be_empty')));
- dispatch(stopLoading(key));
- return Promise.reject();
- }
-
- return setSettingValue(definition, value, component)
- .then(() => getValues({ keys: key, component }))
- .then(values => {
- dispatch(receiveValues([key], values, component));
- dispatch(cancelChange(key));
- dispatch(passValidation(key));
- dispatch(stopLoading(key));
- })
- .catch(handleError(key, dispatch));
- };
-}
-
-export function resetValue(key: string, component?: string) {
- return (dispatch: Dispatch) => {
- dispatch(startLoading(key));
-
- return resetSettingValue({ keys: key, component })
- .then(() => getValues({ keys: key, component }))
- .then(values => {
- if (values.length > 0) {
- dispatch(receiveValues([key], values, component));
- } else {
- dispatch(receiveValues([key], [], component));
- }
- dispatch(passValidation(key));
- dispatch(stopLoading(key));
- })
- .catch(handleError(key, dispatch));
- };
-}
-
-function handleError(key: string, dispatch: Dispatch) {
- return (response: Response) => {
- dispatch(stopLoading(key));
- return parseError(response).then(message => {
- dispatch(failValidation(key, message));
- return Promise.reject();
- });
- };
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { keyBy, sortBy, uniqBy } from 'lodash';
-import { ActionType } from '../../../store/utils/actions';
-import { SettingCategoryDefinition } from '../../../types/settings';
-import { Dict } from '../../../types/types';
-import { DEFAULT_CATEGORY, getCategoryName } from '../utils';
-
-const enum Actions {
- ReceiveDefinitions = 'RECEIVE_DEFINITIONS'
-}
-
-type Action = ActionType<typeof receiveDefinitions, Actions.ReceiveDefinitions>;
-
-export type State = Dict<SettingCategoryDefinition>;
-
-export function receiveDefinitions(definitions: SettingCategoryDefinition[]) {
- return { type: Actions.ReceiveDefinitions, definitions };
-}
-
-export default function components(state: State = {}, action: Action) {
- if (action.type === Actions.ReceiveDefinitions) {
- return keyBy(action.definitions, 'key');
- }
- return state;
-}
-
-export function getDefinition(state: State, key: string) {
- return state[key];
-}
-
-export function getAllDefinitions(state: State) {
- return Object.keys(state).map(key => state[key]);
-}
-
-export function getDefinitionsForCategory(state: State, category: string) {
- return getAllDefinitions(state).filter(
- definition => definition.category.toLowerCase() === category.toLowerCase()
- );
-}
-
-export function getAllCategories(state: State) {
- return uniqBy(
- getAllDefinitions(state).map(definition => definition.category),
- category => category.toLowerCase()
- );
-}
-
-export function getDefaultCategory(state: State) {
- const categories = getAllCategories(state);
- if (categories.includes(DEFAULT_CATEGORY)) {
- return DEFAULT_CATEGORY;
- } else {
- const sortedCategories = sortBy(categories, category =>
- getCategoryName(category).toLowerCase()
- );
- return sortedCategories[0];
- }
-}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { combineReducers } from 'redux';
-import globalMessages, * as fromGlobalMessages from '../../../store/globalMessages';
-import definitions, * as fromDefinitions from './definitions';
-import settingsPage, * as fromSettingsPage from './settingsPage';
import values, * as fromValues from './values';
interface State {
- definitions: fromDefinitions.State;
- globalMessages: fromGlobalMessages.State;
- settingsPage: fromSettingsPage.State;
values: fromValues.State;
}
-export default combineReducers({ definitions, values, settingsPage, globalMessages });
-
-export function getDefinition(state: State, key: string) {
- return fromDefinitions.getDefinition(state.definitions, key);
-}
-
-export function getAllDefinitions(state: State) {
- return fromDefinitions.getAllDefinitions(state.definitions);
-}
-
-export function getAllCategories(state: State) {
- return fromDefinitions.getAllCategories(state.definitions);
-}
-
-export function getDefaultCategory(state: State) {
- return fromDefinitions.getDefaultCategory(state.definitions);
-}
+export default combineReducers({ values });
export function getValue(state: State, key: string, component?: string) {
return fromValues.getValue(state.values, key, component);
}
-
-export function getSettingsForCategory(state: State, category: string, component?: string) {
- 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) {
- return fromSettingsPage.getChangedValue(state.settingsPage, key);
-}
-
-export function isLoading(state: State, key: string) {
- return fromSettingsPage.isLoading(state.settingsPage, key);
-}
-
-export function getValidationMessage(state: State, key: string) {
- return fromSettingsPage.getValidationMessage(state.settingsPage, key);
-}
-
-export function getGlobalMessages(state: State) {
- return fromGlobalMessages.getGlobalMessages(state.globalMessages);
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { omit } from 'lodash';
-import { combineReducers } from 'redux';
-import { ActionType } from '../../../store/utils/actions';
-import { Dict } from '../../../types/types';
-
-const enum Actions {
- CancelChange = 'settingsPage/CANCEL_CHANGE',
- ChangeValue = 'settingsPage/CHANGE_VALUE',
- FailValidation = 'settingsPage/FAIL_VALIDATION',
- PassValidation = 'settingsPage/PASS_VALIDATION',
- StartLoading = 'settingsPage/START_LOADING',
- StopLoading = 'settingsPage/STOP_LOADING'
-}
-
-type Action =
- | ActionType<typeof cancelChange, Actions.CancelChange>
- | ActionType<typeof changeValue, Actions.ChangeValue>
- | ActionType<typeof failValidation, Actions.FailValidation>
- | ActionType<typeof passValidation, Actions.PassValidation>
- | ActionType<typeof startLoading, Actions.StartLoading>
- | ActionType<typeof stopLoading, Actions.StopLoading>;
-
-export interface State {
- changedValues: Dict<any>;
- loading: Dict<boolean>;
- validationMessages: Dict<string>;
-}
-
-export function cancelChange(key: string) {
- return { type: Actions.CancelChange, key };
-}
-
-export function changeValue(key: string, value: any) {
- return { type: Actions.ChangeValue, key, value };
-}
-
-function changedValues(state: State['changedValues'] = {}, action: Action) {
- if (action.type === Actions.ChangeValue) {
- return { ...state, [action.key]: action.value };
- }
- if (action.type === Actions.CancelChange) {
- return omit(state, action.key);
- }
- return state;
-}
-
-export function failValidation(key: string, message: string) {
- return { type: Actions.FailValidation, key, message };
-}
-
-export function passValidation(key: string) {
- return { type: Actions.PassValidation, key };
-}
-
-function validationMessages(state: State['validationMessages'] = {}, action: Action) {
- if (action.type === Actions.FailValidation) {
- return { ...state, [action.key]: action.message };
- }
- if (action.type === Actions.PassValidation) {
- return omit(state, action.key);
- }
- return state;
-}
-
-export function startLoading(key: string) {
- return { type: Actions.StartLoading, key };
-}
-
-export function stopLoading(key: string) {
- return { type: Actions.StopLoading, key };
-}
-
-function loading(state: State['loading'] = {}, action: Action) {
- if (action.type === Actions.StartLoading) {
- return { ...state, [action.key]: true };
- }
- if (action.type === Actions.StopLoading) {
- return { ...state, [action.key]: false };
- }
- return state;
-}
-
-export default combineReducers({ changedValues, loading, validationMessages });
-
-export function getChangedValue(state: State, key: string) {
- return state.changedValues[key];
-}
-
-export function getValidationMessage(state: State, key: string): string | undefined {
- return state.validationMessages[key];
-}
-
-export function isLoading(state: State, key: string) {
- return Boolean(state.loading[key]);
-}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { LocationDescriptor } from 'history';
+import { sortBy } from 'lodash';
import { hasMessage, translate } from '../../helpers/l10n';
import { getGlobalSettingsUrl, getProjectSettingsUrl } from '../../helpers/urls';
import { AlmKeys } from '../../types/alm-settings';
-import { Setting, SettingCategoryDefinition, SettingDefinition } from '../../types/settings';
+import {
+ ExtendedSettingDefinition,
+ Setting,
+ SettingDefinition,
+ SettingValue,
+ SettingWithCategory
+} from '../../types/settings';
import { Component, Dict } from '../../types/types';
export const DEFAULT_CATEGORY = 'general';
return `settings[${definition.key}]${indexSuffix}`;
}
-export function getSettingValue({ definition, fieldValues, value, values }: Setting) {
+export function getSettingValue(definition: SettingDefinition, settingValue?: SettingValue) {
+ const { fieldValues, value, values } = settingValue || {};
if (isCategoryDefinition(definition) && definition.multiValues) {
return values;
} else if (definition.type === 'PROPERTY_SET') {
return fieldValues;
- } else {
- return value;
}
+ return value;
+}
+
+export function combineDefinitionAndSettingValue(
+ definition: ExtendedSettingDefinition,
+ value?: SettingValue
+): SettingWithCategory {
+ const hasValue = value !== undefined && value.inherited !== true;
+ return {
+ key: definition.key,
+ hasValue,
+ ...value,
+ definition
+ };
+}
+
+export function getDefaultCategory(categories: string[]) {
+ if (categories.includes(DEFAULT_CATEGORY)) {
+ return DEFAULT_CATEGORY;
+ }
+ const sortedCategories = sortBy(categories, category => getCategoryName(category).toLowerCase());
+ return sortedCategories[0];
}
export function isEmptyValue(definition: SettingDefinition, value: any) {
return true;
} else if (definition.type === 'BOOLEAN') {
return false;
- } else {
- return value.length === 0;
}
+
+ return value.length === 0;
+}
+
+export function isURLKind(definition: SettingDefinition) {
+ return [
+ 'sonar.core.serverBaseURL',
+ 'sonar.auth.github.apiUrl',
+ 'sonar.auth.github.webUrl',
+ 'sonar.auth.gitlab.url',
+ 'sonar.lf.gravatarServerUrl',
+ 'sonar.lf.logoUrl',
+ 'sonar.auth.saml.loginUrl'
+ ].includes(definition.key);
}
export function isSecuredDefinition(item: SettingDefinition): boolean {
return item.key.endsWith('.secured');
}
-export function isCategoryDefinition(item: SettingDefinition): item is SettingCategoryDefinition {
+export function isCategoryDefinition(item: SettingDefinition): item is ExtendedSettingDefinition {
return Boolean((item as any).fields);
}
-export function getEmptyValue(item: SettingDefinition | SettingCategoryDefinition): any {
+export function getEmptyValue(item: SettingDefinition | ExtendedSettingDefinition): any {
if (isCategoryDefinition(item)) {
if (item.multiValues) {
return [getEmptyValue({ ...item, multiValues: false })];
return '';
}
-export function isDefaultOrInherited(setting: Setting) {
- return Boolean(setting.inherited);
+export function isDefaultOrInherited(setting?: Pick<SettingValue, 'inherited'>) {
+ return Boolean(setting && setting.inherited);
}
export function getDefaultValue(setting: Setting) {
}
export function buildSettingLink(
- definition: SettingCategoryDefinition,
+ definition: ExtendedSettingDefinition,
component?: Component
): LocationDescriptor {
const { category, key } = definition;
};
}
-export const ADDITIONAL_PROJECT_SETTING_DEFINITIONS: SettingCategoryDefinition[] = [
+export const ADDITIONAL_PROJECT_SETTING_DEFINITIONS: ExtendedSettingDefinition[] = [
{
name: 'DevOps Platform Integration',
description: `
}
];
-export const ADDITIONAL_SETTING_DEFINITIONS: SettingCategoryDefinition[] = [
+export const ADDITIONAL_SETTING_DEFINITIONS: ExtendedSettingDefinition[] = [
{
name: 'Default New Code behavior',
description: `
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import {
+ ExtendedSettingDefinition,
Setting,
- SettingCategoryDefinition,
SettingType,
SettingValue,
SettingWithCategory
} from '../../types/settings';
export function mockDefinition(
- overrides: Partial<SettingCategoryDefinition> = {}
-): SettingCategoryDefinition {
+ overrides: Partial<ExtendedSettingDefinition> = {}
+): ExtendedSettingDefinition {
return {
key: 'foo',
category: 'foo category',
return fromSettingsApp.getValue(state.settingsApp, key);
}
-export function getSettingsAppAllDefinitions(state: Store) {
- return fromSettingsApp.getAllDefinitions(state.settingsApp);
-}
-
-export function getSettingsAppDefinition(state: Store, key: string) {
- return fromSettingsApp.getDefinition(state.settingsApp, key);
-}
-
-export function getSettingsAppAllCategories(state: Store) {
- return fromSettingsApp.getAllCategories(state.settingsApp);
-}
-
-export function getSettingsAppDefaultCategory(state: Store) {
- return fromSettingsApp.getDefaultCategory(state.settingsApp);
-}
-
-export function getSettingsAppSettingsForCategory(
- state: Store,
- category: string,
- component?: string
-) {
- return fromSettingsApp.getSettingsForCategory(state.settingsApp, category, component);
-}
-
-export function getSettingsAppChangedValue(state: Store, key: string) {
- return fromSettingsApp.getChangedValue(state.settingsApp, key);
-}
-
-export function isSettingsAppLoading(state: Store, key: string) {
- return fromSettingsApp.isLoading(state.settingsApp, key);
-}
-
-export function getSettingsAppValidationMessage(state: Store, key: string) {
- return fromSettingsApp.getValidationMessage(state.settingsApp, key);
-}
-
export function getBranchStatusByBranchLike(
state: Store,
component: string,
PluginRiskConsent = 'sonar.plugins.risk.consent'
}
+export type SettingDefinitionAndValue = {
+ definition: ExtendedSettingDefinition;
+ settingValue?: SettingValue;
+};
+
export type Setting = SettingValue & { definition: SettingDefinition; hasValue: boolean };
-export type SettingWithCategory = Setting & { definition: SettingCategoryDefinition };
+export type SettingWithCategory = Setting & { definition: ExtendedSettingDefinition };
export enum SettingType {
STRING = 'STRING',
name: string;
}
-export interface SettingCategoryDefinition extends SettingDefinition {
+export interface ExtendedSettingDefinition extends SettingDefinition {
category: string;
defaultValue?: string;
deprecatedKey?: string;