@@ -23,12 +23,17 @@ import { | |||
BranchParameters, | |||
SettingCategoryDefinition, | |||
SettingValue, | |||
SettingType | |||
SettingType, | |||
SettingDefinition | |||
} from '../app/types'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
import { isCategoryDefinition } from '../apps/settings/utils'; | |||
export function getDefinitions(component?: string): Promise<SettingCategoryDefinition[]> { | |||
return getJSON('/api/settings/list_definitions', { component }).then(r => r.definitions); | |||
return getJSON('/api/settings/list_definitions', { component }).then( | |||
r => r.definitions, | |||
throwGlobalError | |||
); | |||
} | |||
export function getValues( | |||
@@ -37,11 +42,15 @@ export function getValues( | |||
return getJSON('/api/settings/values', data).then(r => r.settings); | |||
} | |||
export function setSettingValue(definition: any, value: any, component?: string): Promise<void> { | |||
export function setSettingValue( | |||
definition: SettingDefinition, | |||
value: any, | |||
component?: string | |||
): Promise<void> { | |||
const { key } = definition; | |||
const data: RequestData = { key, component }; | |||
if (definition.multiValues) { | |||
if (isCategoryDefinition(definition) && definition.multiValues) { | |||
data.values = value; | |||
} else if (definition.type === SettingType.PropertySet) { | |||
data.fieldValues = value |
@@ -27,6 +27,7 @@ import { fetchMyOrganizations } from '../../apps/account/organizations/actions'; | |||
import { getInstance, isSonarCloud } from '../../helpers/system'; | |||
import { lazyLoad } from '../../components/lazyLoad'; | |||
import { getCurrentUser, getAppState, getGlobalSettingValue, Store } from '../../store/rootReducer'; | |||
import { isLoggedIn } from '../../helpers/users'; | |||
const PageTracker = lazyLoad(() => import('./PageTracker')); | |||
@@ -70,10 +71,8 @@ class App extends React.PureComponent<Props> { | |||
this.mounted = true; | |||
this.props.fetchLanguages(); | |||
const { appState, currentUser } = this.props; | |||
if (appState && currentUser) { | |||
if (appState.organizationsEnabled && currentUser.isLoggedIn) { | |||
this.props.fetchMyOrganizations(); | |||
} | |||
if (appState && isSonarCloud() && currentUser && isLoggedIn(currentUser)) { | |||
this.props.fetchMyOrganizations(); | |||
} | |||
} | |||
@@ -104,12 +103,16 @@ class App extends React.PureComponent<Props> { | |||
} | |||
} | |||
const mapStateToProps = (state: Store): StateProps => ({ | |||
appState: getAppState(state), | |||
currentUser: getCurrentUser(state), | |||
enableGravatar: (getGlobalSettingValue(state, 'sonar.lf.enableGravatar') || {}).value === 'true', | |||
gravatarServerUrl: (getGlobalSettingValue(state, 'sonar.lf.gravatarServerUrl') || {}).value || '' | |||
}); | |||
const mapStateToProps = (state: Store): StateProps => { | |||
const enableGravatar = getGlobalSettingValue(state, 'sonar.lf.enableGravatar'); | |||
const gravatarServerUrl = getGlobalSettingValue(state, 'sonar.lf.gravatarServerUrl'); | |||
return { | |||
appState: getAppState(state), | |||
currentUser: getCurrentUser(state), | |||
enableGravatar: Boolean(enableGravatar && enableGravatar.value === 'true'), | |||
gravatarServerUrl: (gravatarServerUrl && gravatarServerUrl.value) || '' | |||
}; | |||
}; | |||
const mapDispatchToProps = ({ | |||
fetchLanguages, |
@@ -59,8 +59,11 @@ export class PageTracker extends React.PureComponent<Props> { | |||
} | |||
} | |||
const mapStateToProps = (state: Store): StateProps => ({ | |||
trackingId: (getGlobalSettingValue(state, 'sonar.analytics.trackingId') || {}).value | |||
}); | |||
const mapStateToProps = (state: Store): StateProps => { | |||
const trackingId = getGlobalSettingValue(state, 'sonar.analytics.trackingId'); | |||
return { | |||
trackingId: trackingId && trackingId.value | |||
}; | |||
}; | |||
export default withRouter(connect(mapStateToProps)(PageTracker)); |
@@ -124,8 +124,11 @@ export class ProductNewsMenuItem extends React.PureComponent<Props, State> { | |||
} | |||
} | |||
const mapStateToProps = (state: Store): StateProps => ({ | |||
accessToken: (getGlobalSettingValue(state, 'sonar.prismic.accessToken') || {}).value | |||
}); | |||
const mapStateToProps = (state: Store): StateProps => { | |||
const accessToken = getGlobalSettingValue(state, 'sonar.prismic.accessToken'); | |||
return { | |||
accessToken: accessToken && accessToken.value | |||
}; | |||
}; | |||
export default connect(mapStateToProps)(ProductNewsMenuItem); |
@@ -50,9 +50,13 @@ export function SonarCloudNavBranding() { | |||
); | |||
} | |||
const mapStateToProps = (state: Store): StateProps => ({ | |||
customLogoUrl: (getGlobalSettingValue(state, 'sonar.lf.logoUrl') || {}).value, | |||
customLogoWidth: (getGlobalSettingValue(state, 'sonar.lf.logoWidthPx') || {}).value | |||
}); | |||
const mapStateToProps = (state: Store): StateProps => { | |||
const customLogoUrl = getGlobalSettingValue(state, 'sonar.lf.logoUrl'); | |||
const customLogoWidth = getGlobalSettingValue(state, 'sonar.lf.logoWidthPx'); | |||
return { | |||
customLogoUrl: customLogoUrl && customLogoUrl.value, | |||
customLogoWidth: customLogoWidth && customLogoWidth.value | |||
}; | |||
}; | |||
export default connect(mapStateToProps)(GlobalNavBranding); |
@@ -86,6 +86,7 @@ export interface AppState { | |||
organizationsEnabled?: boolean; | |||
productionDatabase: boolean; | |||
qualifiers: string[]; | |||
settings: { [key: string]: string }; | |||
standalone?: boolean; | |||
version: string; | |||
} | |||
@@ -739,6 +740,7 @@ export enum SettingType { | |||
Boolean = 'BOOLEAN', | |||
Float = 'FLOAT', | |||
Integer = 'INTEGER', | |||
License = 'LICENSE', | |||
Long = 'LONG', | |||
SingleSelectList = 'SINGLE_SELECT_LIST', | |||
PropertySet = 'PROPERTY_SET' |
@@ -19,12 +19,11 @@ | |||
*/ | |||
import { Dispatch } from 'redux'; | |||
import { getValues } from '../../api/settings'; | |||
import { receiveValues } from '../settings/store/values/actions'; | |||
import { receiveValues } from '../settings/store/values'; | |||
export function fetchAboutPageSettings() { | |||
return (dispatch: Dispatch) => { | |||
const keys = ['sonar.lf.aboutText']; | |||
return getValues({ keys: keys.join() }).then(values => { | |||
dispatch(receiveValues(values)); | |||
}); |
@@ -48,7 +48,7 @@ import '../styles.css'; | |||
interface Props { | |||
appState: Pick<AppState, 'defaultOrganization' | 'organizationsEnabled'>; | |||
currentUser: CurrentUser; | |||
customText?: { value: string }; | |||
customText?: string; | |||
fetchAboutPageSettings: () => Promise<void>; | |||
location: Location; | |||
} | |||
@@ -158,13 +158,9 @@ class AboutApp extends React.PureComponent<Props, State> { | |||
</div> | |||
</div> | |||
{customText != null && | |||
customText.value && ( | |||
<div | |||
className="about-page-section" | |||
dangerouslySetInnerHTML={{ __html: customText.value }} | |||
/> | |||
)} | |||
{customText && ( | |||
<div className="about-page-section" dangerouslySetInnerHTML={{ __html: customText }} /> | |||
)} | |||
<AboutLanguages /> | |||
@@ -195,11 +191,14 @@ class AboutApp extends React.PureComponent<Props, State> { | |||
} | |||
} | |||
const mapStateToProps = (state: Store) => ({ | |||
appState: getAppState(state), | |||
currentUser: getCurrentUser(state), | |||
customText: getGlobalSettingValue(state, 'sonar.lf.aboutText') | |||
}); | |||
const mapStateToProps = (state: Store) => { | |||
const customText = getGlobalSettingValue(state, 'sonar.lf.aboutText'); | |||
return { | |||
appState: getAppState(state), | |||
currentUser: getCurrentUser(state), | |||
customText: customText && customText.value | |||
}; | |||
}; | |||
const mapDispatchToProps = { fetchAboutPageSettings } as any; | |||
@@ -33,7 +33,7 @@ import { | |||
import { Organization } from '../../../app/types'; | |||
interface StateProps { | |||
anyoneCanCreate?: { value: string }; | |||
anyoneCanCreate: boolean; | |||
canAdmin?: boolean; | |||
organizations: Organization[]; | |||
} | |||
@@ -68,9 +68,7 @@ class UserOrganizations extends React.PureComponent<Props, State> { | |||
}; | |||
render() { | |||
const anyoneCanCreate = | |||
this.props.anyoneCanCreate != null && this.props.anyoneCanCreate.value === 'true'; | |||
const { anyoneCanCreate } = this.props; | |||
const canCreateOrganizations = !this.state.loading && (anyoneCanCreate || this.props.canAdmin); | |||
return ( | |||
@@ -100,11 +98,14 @@ class UserOrganizations extends React.PureComponent<Props, State> { | |||
} | |||
} | |||
const mapStateToProps = (state: Store): StateProps => ({ | |||
anyoneCanCreate: getGlobalSettingValue(state, 'sonar.organizations.anyoneCanCreate'), | |||
canAdmin: getAppState(state).canAdmin, | |||
organizations: getMyOrganizations(state) | |||
}); | |||
const mapStateToProps = (state: Store): StateProps => { | |||
const anyoneCanCreate = getGlobalSettingValue(state, 'sonar.organizations.anyoneCanCreate'); | |||
return { | |||
anyoneCanCreate: Boolean(anyoneCanCreate && anyoneCanCreate.value === 'true'), | |||
canAdmin: getAppState(state).canAdmin, | |||
organizations: getMyOrganizations(state) | |||
}; | |||
}; | |||
const mapDispatchToProps = { | |||
fetchIfAnyoneCanCreateOrganizations: fetchIfAnyoneCanCreateOrganizations as any |
@@ -21,7 +21,7 @@ import { Dispatch } from 'redux'; | |||
import { getOrganizations } from '../../../api/organizations'; | |||
import { receiveMyOrganizations } from '../../../store/organizations'; | |||
import { getValues } from '../../../api/settings'; | |||
import { receiveValues } from '../../settings/store/values/actions'; | |||
import { receiveValues } from '../../settings/store/values'; | |||
export const fetchMyOrganizations = () => (dispatch: Dispatch) => { | |||
return getOrganizations({ member: true }).then(({ organizations }) => { | |||
@@ -31,6 +31,6 @@ export const fetchMyOrganizations = () => (dispatch: Dispatch) => { | |||
export const fetchIfAnyoneCanCreateOrganizations = () => (dispatch: Dispatch) => { | |||
return getValues({ keys: 'sonar.organizations.anyoneCanCreate' }).then(values => { | |||
dispatch(receiveValues(values, undefined)); | |||
dispatch(receiveValues(values)); | |||
}); | |||
}; |
@@ -36,11 +36,11 @@ interface StateToProps { | |||
} | |||
const mapStateToProps = (state: Store) => { | |||
const updateCenterActive = getGlobalSettingValue(state, 'sonar.updatecenter.activate'); | |||
return { | |||
currentEdition: getAppState(state).edition, | |||
standaloneMode: getAppState(state).standalone, | |||
updateCenterActive: | |||
(getGlobalSettingValue(state, 'sonar.updatecenter.activate') || {}).value === 'true' | |||
updateCenterActive: Boolean(updateCenterActive && updateCenterActive.value === 'true') | |||
}; | |||
}; | |||
@@ -1,34 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import CategoriesList from './CategoriesList'; | |||
import { getSettingsAppAllCategories } from '../../../store/rootReducer'; | |||
function AllCategoriesList(props) { | |||
return <CategoriesList {...props} />; | |||
} | |||
const mapStateToProps = state => ({ | |||
categories: getSettingsAppAllCategories(state) | |||
}); | |||
export default connect(mapStateToProps)(AllCategoriesList); |
@@ -17,49 +17,42 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import { connect } from 'react-redux'; | |||
import { sortBy } from 'lodash'; | |||
import { IndexLink } from 'react-router'; | |||
import { getCategoryName } from '../utils'; | |||
import { Component } from '../../../app/types'; | |||
import { getSettingsAppAllCategories, Store } from '../../../store/rootReducer'; | |||
/*:: | |||
type Category = { | |||
key: string, | |||
name: string | |||
}; | |||
*/ | |||
/*:: | |||
type Props = { | |||
categories: Category[], | |||
component?: { key: string }, | |||
defaultCategory: string, | |||
selectedCategory: string | |||
}; | |||
*/ | |||
export default class CategoriesList extends React.PureComponent { | |||
/*:: rops: Props; */ | |||
renderLink(category /*: Category */) { | |||
const query /*: Object */ = {}; | |||
if (category.key !== this.props.defaultCategory) { | |||
query.category = category.key.toLowerCase(); | |||
} | |||
if (this.props.component) { | |||
query.id = this.props.component.key; | |||
} | |||
interface Category { | |||
key: string; | |||
name: string; | |||
} | |||
const className = | |||
category.key.toLowerCase() === this.props.selectedCategory.toLowerCase() ? 'active' : ''; | |||
interface Props { | |||
categories: string[]; | |||
component?: Component; | |||
defaultCategory: string; | |||
selectedCategory: string; | |||
} | |||
export class CategoriesList extends React.PureComponent<Props> { | |||
renderLink(category: Category) { | |||
const { component, defaultCategory, selectedCategory } = this.props; | |||
const pathname = this.props.component ? '/project/settings' : '/settings'; | |||
const query = { | |||
category: category.key !== defaultCategory ? category.key.toLowerCase() : undefined, | |||
id: component && component.key | |||
}; | |||
return ( | |||
<IndexLink className={className} title={category.name} to={{ pathname, query }}> | |||
<IndexLink | |||
className={classNames({ | |||
active: category.key.toLowerCase() === selectedCategory.toLowerCase() | |||
})} | |||
title={category.name} | |||
to={{ pathname, query }}> | |||
{category.name} | |||
</IndexLink> | |||
); | |||
@@ -71,7 +64,6 @@ export default class CategoriesList extends React.PureComponent { | |||
name: getCategoryName(key) | |||
})); | |||
const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase()); | |||
return ( | |||
<ul className="side-tabs-menu"> | |||
{sortedCategories.map(category => ( | |||
@@ -81,3 +73,9 @@ export default class CategoriesList extends React.PureComponent { | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state: Store) => ({ | |||
categories: getSettingsAppAllCategories(state) | |||
}); | |||
export default connect(mapStateToProps)(CategoriesList); |
@@ -1,34 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { connect } from 'react-redux'; | |||
import App from './App'; | |||
import { fetchSettings } from '../store/actions'; | |||
import { getSettingsAppDefaultCategory } from '../../../store/rootReducer'; | |||
const mapStateToProps = state => ({ | |||
defaultCategory: getSettingsAppDefaultCategory(state) | |||
}); | |||
const mapdispatchToProps = { fetchSettings }; | |||
export default connect( | |||
mapStateToProps, | |||
mapdispatchToProps | |||
)(App); |
@@ -17,51 +17,64 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import Helmet from 'react-helmet'; | |||
import PageHeader from './PageHeader'; | |||
import CategoryDefinitionsList from './CategoryDefinitionsList'; | |||
import { connect } from 'react-redux'; | |||
import { WithRouterProps } from 'react-router'; | |||
import AllCategoriesList from './AllCategoriesList'; | |||
import CategoryDefinitionsList from './CategoryDefinitionsList'; | |||
import PageHeader from './PageHeader'; | |||
import WildcardsHelp from './WildcardsHelp'; | |||
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; | |||
import { fetchSettings } from '../store/actions'; | |||
import { getSettingsAppDefaultCategory, Store } from '../../../store/rootReducer'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { Component } from '../../../app/types'; | |||
import '../styles.css'; | |||
import '../side-tabs.css'; | |||
/*:: | |||
type Props = { | |||
component?: { key: string }, | |||
defaultCategory: ?string, | |||
fetchSettings(componentKey: ?string): Promise<*>, | |||
location: { query: {} } | |||
}; | |||
*/ | |||
/*:: | |||
type State = { | |||
loaded: boolean | |||
}; | |||
*/ | |||
export default class App extends React.PureComponent { | |||
/*:: props: Props; */ | |||
state /*: State */ = { loaded: false }; | |||
interface Props { | |||
component?: Component; | |||
defaultCategory: string; | |||
fetchSettings(component?: string): Promise<void>; | |||
} | |||
interface State { | |||
loading: boolean; | |||
} | |||
export class App extends React.PureComponent<Props & WithRouterProps, State> { | |||
mounted = false; | |||
state: State = { loading: true }; | |||
componentDidMount() { | |||
const componentKey = this.props.component ? this.props.component.key : null; | |||
this.props.fetchSettings(componentKey).then(() => this.setState({ loaded: true })); | |||
this.mounted = true; | |||
this.fetchSettings(); | |||
} | |||
componentDidUpdate(prevProps /*: Props*/) { | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.component !== this.props.component) { | |||
const componentKey = this.props.component ? this.props.component.key : null; | |||
this.props.fetchSettings(componentKey); | |||
this.fetchSettings(); | |||
} | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
fetchSettings = () => { | |||
const { component } = this.props; | |||
this.props.fetchSettings(component && component.key).then(this.stopLoading, this.stopLoading); | |||
}; | |||
stopLoading = () => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
}; | |||
render() { | |||
if (!this.state.loaded) { | |||
if (this.state.loading) { | |||
return null; | |||
} | |||
@@ -92,3 +105,14 @@ export default class App extends React.PureComponent { | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state: Store) => ({ | |||
defaultCategory: getSettingsAppDefaultCategory(state) | |||
}); | |||
const mapDispatchToProps = { fetchSettings: fetchSettings as any }; | |||
export default connect( | |||
mapStateToProps, | |||
mapDispatchToProps | |||
)(App); |
@@ -17,17 +17,22 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import { connect } from 'react-redux'; | |||
import SubCategoryDefinitionsList from './SubCategoryDefinitionsList'; | |||
import { fetchValues } from '../store/actions'; | |||
import { getSettingsAppSettingsForCategory } from '../../../store/rootReducer'; | |||
import { getSettingsAppSettingsForCategory, Store } from '../../../store/rootReducer'; | |||
import { Component } from '../../../app/types'; | |||
const mapStateToProps = (state, ownProps) => ({ | |||
interface Props { | |||
category: string; | |||
component?: Component; | |||
} | |||
const mapStateToProps = (state: Store, ownProps: Props) => ({ | |||
settings: getSettingsAppSettingsForCategory( | |||
state, | |||
ownProps.category, | |||
ownProps.component ? ownProps.component.key : null | |||
ownProps.component && ownProps.component.key | |||
) | |||
}); | |||
@@ -17,9 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import classNames from 'classnames'; | |||
import Input from './inputs/Input'; | |||
@@ -34,35 +32,37 @@ import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon' | |||
import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon'; | |||
import { translateWithParameters, translate } from '../../../helpers/l10n'; | |||
import { resetValue, saveValue, checkValue } from '../store/actions'; | |||
import { passValidation } from '../store/settingsPage/validationMessages/actions'; | |||
import { cancelChange, changeValue } from '../store/settingsPage/changedValues/actions'; | |||
import { cancelChange, changeValue, passValidation } from '../store/settingsPage'; | |||
import { | |||
getSettingsAppChangedValue, | |||
getSettingsAppValidationMessage, | |||
isSettingsAppLoading, | |||
getSettingsAppValidationMessage | |||
Store | |||
} from '../../../store/rootReducer'; | |||
import { Component, Setting } from '../../../app/types'; | |||
interface Props { | |||
cancelChange: (key: string) => void; | |||
changedValue: any; | |||
changeValue: (key: string, value: any) => void; | |||
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; | |||
} | |||
class Definition extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: timeout: number; */ | |||
static propTypes = { | |||
component: PropTypes.object, | |||
setting: PropTypes.object.isRequired, | |||
changedValue: PropTypes.any, | |||
loading: PropTypes.bool.isRequired, | |||
validationMessage: PropTypes.string, | |||
changeValue: PropTypes.func.isRequired, | |||
cancelChange: PropTypes.func.isRequired, | |||
saveValue: PropTypes.func.isRequired, | |||
resetValue: PropTypes.func.isRequired, | |||
passValidation: PropTypes.func.isRequired | |||
}; | |||
interface State { | |||
success: boolean; | |||
} | |||
state = { | |||
success: false | |||
}; | |||
export class Definition extends React.PureComponent<Props, State> { | |||
timeout?: number; | |||
mounted = false; | |||
state = { success: false }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
@@ -72,87 +72,80 @@ class Definition extends React.PureComponent { | |||
this.mounted = false; | |||
} | |||
safeSetState(changes) { | |||
safeSetState(changes: State) { | |||
if (this.mounted) { | |||
this.setState(changes); | |||
} | |||
} | |||
handleChange = value => { | |||
handleChange = (value: any) => { | |||
clearTimeout(this.timeout); | |||
this.props.changeValue(this.props.setting.definition.key, value); | |||
this.handleCheck(); | |||
}; | |||
handleReset = () => { | |||
const componentKey = this.props.component ? this.props.component.key : null; | |||
const { definition } = this.props.setting; | |||
return this.props | |||
.resetValue(definition.key, componentKey) | |||
.then(() => { | |||
this.props.cancelChange(definition.key, componentKey); | |||
this.safeSetState({ success: true }); | |||
this.timeout = setTimeout(() => this.safeSetState({ success: false }), 3000); | |||
}) | |||
.catch(() => { | |||
/* do nothing */ | |||
}); | |||
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 }); | |||
this.timeout = window.setTimeout(() => this.safeSetState({ success: false }), 3000); | |||
}); | |||
}; | |||
handleCancel = () => { | |||
const componentKey = this.props.component ? this.props.component.key : null; | |||
this.props.cancelChange(this.props.setting.definition.key, componentKey); | |||
this.props.passValidation(this.props.setting.definition.key); | |||
const { setting } = this.props; | |||
this.props.cancelChange(setting.definition.key); | |||
this.props.passValidation(setting.definition.key); | |||
}; | |||
handleCheck = () => { | |||
const componentKey = this.props.component ? this.props.component.key : null; | |||
this.props.checkValue(this.props.setting.definition.key, componentKey); | |||
const { setting } = this.props; | |||
this.props.checkValue(setting.definition.key); | |||
}; | |||
handleSave = () => { | |||
if (this.props.changedValue != null) { | |||
this.safeSetState({ success: false }); | |||
const componentKey = this.props.component ? this.props.component.key : null; | |||
this.props | |||
.saveValue(this.props.setting.definition.key, componentKey) | |||
.then(() => { | |||
const { component, setting } = this.props; | |||
this.props.saveValue(setting.definition.key, component && component.key).then( | |||
() => { | |||
this.safeSetState({ success: true }); | |||
this.timeout = setTimeout(() => this.safeSetState({ success: false }), 3000); | |||
}) | |||
.catch(() => { | |||
/* do nothing */ | |||
}); | |||
this.timeout = window.setTimeout(() => this.safeSetState({ success: false }), 3000); | |||
}, | |||
() => {} | |||
); | |||
} | |||
}; | |||
render() { | |||
const { setting, changedValue, loading } = this.props; | |||
const { changedValue, loading, setting, validationMessage } = this.props; | |||
const { definition } = setting; | |||
const propertyName = getPropertyName(definition); | |||
const hasError = this.props.validationMessage != null; | |||
const hasError = validationMessage != null; | |||
const hasValueChanged = changedValue != null; | |||
const className = classNames('settings-definition', { | |||
'settings-definition-changed': hasValueChanged | |||
}); | |||
const effectiveValue = hasValueChanged ? changedValue : getSettingValue(setting); | |||
const isDefault = isDefaultOrInherited(setting); | |||
const description = getPropertyDescription(definition); | |||
return ( | |||
<div className={className} data-key={definition.key}> | |||
<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> | |||
<div | |||
className="markdown small spacer-top" | |||
dangerouslySetInnerHTML={{ __html: getPropertyDescription(definition) }} | |||
/> | |||
{description && ( | |||
<div | |||
className="markdown small spacer-top" | |||
dangerouslySetInnerHTML={{ __html: description }} | |||
/> | |||
)} | |||
<div className="settings-definition-key note little-spacer-top"> | |||
{translateWithParameters('settings.key_x', definition.key)} | |||
@@ -169,14 +162,11 @@ class Definition extends React.PureComponent { | |||
)} | |||
{!loading && | |||
hasError && ( | |||
validationMessage && ( | |||
<span className="text-danger"> | |||
<AlertErrorIcon className="spacer-right" /> | |||
<span> | |||
{translateWithParameters( | |||
'settings.state.validation_failed', | |||
this.props.validationMessage | |||
)} | |||
{translateWithParameters('settings.state.validation_failed', validationMessage)} | |||
</span> | |||
</span> | |||
)} | |||
@@ -216,20 +206,22 @@ class Definition extends React.PureComponent { | |||
} | |||
} | |||
const mapStateToProps = (state, ownProps) => ({ | |||
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, | |||
{ | |||
changeValue, | |||
saveValue, | |||
resetValue, | |||
passValidation, | |||
cancelChange, | |||
checkValue | |||
} | |||
mapDispatchToProps | |||
)(Definition); |
@@ -1,42 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import Definition from './Definition'; | |||
export default class DefinitionsList extends React.PureComponent { | |||
static propTypes = { | |||
component: PropTypes.object, | |||
settings: PropTypes.array.isRequired | |||
}; | |||
render() { | |||
return ( | |||
<ul className="settings-definitions-list"> | |||
{this.props.settings.map(setting => ( | |||
<li key={setting.definition.key}> | |||
<Definition component={this.props.component} setting={setting} /> | |||
</li> | |||
))} | |||
</ul> | |||
); | |||
} | |||
} |
@@ -17,13 +17,23 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
interface SettingValue { | |||
key: string; | |||
value?: string; | |||
} | |||
import * as React from 'react'; | |||
import Definition from './Definition'; | |||
import { Component, Setting } from '../../../app/types'; | |||
export const RECEIVE_VALUES = 'RECEIVE_VALUES'; | |||
interface Props { | |||
component?: Component; | |||
settings: Setting[]; | |||
} | |||
export function receiveValues(settings: SettingValue[], componentKey?: string) { | |||
return { type: RECEIVE_VALUES, settings, componentKey }; | |||
export default function DefinitionsList({ component, settings }: Props) { | |||
return ( | |||
<ul className="settings-definitions-list"> | |||
{settings.map(setting => ( | |||
<li key={setting.definition.key}> | |||
<Definition component={component} setting={setting} /> | |||
</li> | |||
))} | |||
</ul> | |||
); | |||
} |
@@ -17,36 +17,79 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import * as React from 'react'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { sendTestEmail } from '../../../api/settings'; | |||
import { parseError } from '../../../helpers/request'; | |||
import { getCurrentUser } from '../../../store/rootReducer'; | |||
import { SubmitButton } from '../../../components/ui/buttons'; | |||
import { Alert } from '../../../components/ui/Alert'; | |||
import { LoggedInUser } from '../../../app/types'; | |||
import { withCurrentUser } from '../../../components/hoc/withCurrentUser'; | |||
class EmailForm extends React.PureComponent { | |||
constructor(props) { | |||
interface Props { | |||
currentUser: LoggedInUser; | |||
} | |||
interface State { | |||
recipient: string; | |||
subject: string; | |||
message: string; | |||
loading: boolean; | |||
success: boolean; | |||
error?: string; | |||
} | |||
class EmailForm extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { | |||
recipient: this.props.currentUser.email, | |||
recipient: this.props.currentUser.email || '', | |||
subject: translate('email_configuration.test.subject'), | |||
message: translate('email_configuration.test.message_text'), | |||
loading: false, | |||
success: false, | |||
error: null | |||
success: false | |||
}; | |||
} | |||
handleFormSubmit = event => { | |||
componentDidMount() { | |||
this.mounted = true; | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
handleError = (error: { response: Response }) => { | |||
return parseError(error).then(message => { | |||
if (this.mounted) { | |||
this.setState({ error: message, loading: false }); | |||
} | |||
}); | |||
}; | |||
handleFormSubmit = (event: React.FormEvent) => { | |||
event.preventDefault(); | |||
this.setState({ success: false, error: null, loading: true }); | |||
this.setState({ success: false, error: undefined, loading: true }); | |||
const { recipient, subject, message } = this.state; | |||
sendTestEmail(recipient, subject, message).then( | |||
() => this.setState({ success: true, loading: false }), | |||
error => parseError(error).then(message => this.setState({ error: message, loading: false })) | |||
); | |||
sendTestEmail(recipient, subject, message).then(() => { | |||
if (this.mounted) { | |||
this.setState({ success: true, loading: false }); | |||
} | |||
}, this.handleError); | |||
}; | |||
onRecipientChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
this.setState({ recipient: event.target.value }); | |||
}; | |||
onSubjectChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
this.setState({ subject: event.target.value }); | |||
}; | |||
onMessageChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { | |||
this.setState({ message: event.target.value }); | |||
}; | |||
render() { | |||
@@ -81,7 +124,7 @@ class EmailForm extends React.PureComponent { | |||
className="settings-large-input" | |||
disabled={this.state.loading} | |||
id="test-email-to" | |||
onChange={e => this.setState({ recipient: e.target.value })} | |||
onChange={this.onRecipientChange} | |||
required={true} | |||
type="email" | |||
value={this.state.recipient} | |||
@@ -95,7 +138,7 @@ class EmailForm extends React.PureComponent { | |||
className="settings-large-input" | |||
disabled={this.state.loading} | |||
id="test-email-subject" | |||
onChange={e => this.setState({ subject: e.target.value })} | |||
onChange={this.onSubjectChange} | |||
type="text" | |||
value={this.state.subject} | |||
/> | |||
@@ -109,9 +152,9 @@ class EmailForm extends React.PureComponent { | |||
className="settings-large-input" | |||
disabled={this.state.loading} | |||
id="test-email-title" | |||
onChange={e => this.setState({ message: e.target.value })} | |||
onChange={this.onMessageChange} | |||
required={true} | |||
rows="5" | |||
rows={5} | |||
value={this.state.message} | |||
/> | |||
</div> | |||
@@ -128,8 +171,4 @@ class EmailForm extends React.PureComponent { | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
currentUser: getCurrentUser(state) | |||
}); | |||
export default connect(mapStateToProps)(EmailForm); | |||
export default withCurrentUser(EmailForm); |
@@ -17,35 +17,28 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import * as React from 'react'; | |||
import InstanceMessage from '../../../components/common/InstanceMessage'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { Component } from '../../../app/types'; | |||
export default class PageHeader extends React.PureComponent { | |||
static propTypes = { | |||
component: PropTypes.object | |||
}; | |||
interface Props { | |||
component?: Component; | |||
} | |||
render() { | |||
const title = | |||
this.props.component != null | |||
? translate('project_settings.page') | |||
: translate('settings.page'); | |||
export default function PageHeader({ component }: Props) { | |||
const title = component ? translate('project_settings.page') : translate('settings.page'); | |||
const description = | |||
this.props.component != null ? ( | |||
translate('project_settings.page.description') | |||
) : ( | |||
<InstanceMessage message={translate('settings.page.description')} /> | |||
); | |||
const description = component ? ( | |||
translate('project_settings.page.description') | |||
) : ( | |||
<InstanceMessage message={translate('settings.page.description')} /> | |||
); | |||
return ( | |||
<header className="page-header"> | |||
<h1 className="page-title">{title}</h1> | |||
<div className="page-description">{description}</div> | |||
</header> | |||
); | |||
} | |||
return ( | |||
<header className="page-header"> | |||
<h1 className="page-title">{title}</h1> | |||
<div className="page-description">{description}</div> | |||
</header> | |||
); | |||
} |
@@ -17,26 +17,26 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import * as React from 'react'; | |||
import { groupBy, isEqual, sortBy } from 'lodash'; | |||
import DefinitionsList from './DefinitionsList'; | |||
import EmailForm from './EmailForm'; | |||
import { getSubCategoryName, getSubCategoryDescription } from '../utils'; | |||
import { Component, SettingCategoryDefinition, Setting } from '../../../app/types'; | |||
export default class SubCategoryDefinitionsList extends React.PureComponent { | |||
static propTypes = { | |||
component: PropTypes.object, | |||
fetchValues: PropTypes.func, | |||
settings: PropTypes.array.isRequired | |||
}; | |||
interface Props { | |||
category: string; | |||
component?: Component; | |||
fetchValues: Function; | |||
settings: Array<Setting & { definition: SettingCategoryDefinition }>; | |||
} | |||
export default class SubCategoryDefinitionsList extends React.PureComponent<Props> { | |||
componentDidMount() { | |||
this.fetchValues(); | |||
} | |||
componentDidUpdate(prevProps /*: Object */) { | |||
componentDidUpdate(prevProps: Props) { | |||
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)) { | |||
@@ -49,13 +49,13 @@ export default class SubCategoryDefinitionsList extends React.PureComponent { | |||
this.props.fetchValues(keys, this.props.component && this.props.component.key); | |||
} | |||
renderEmailForm(subCategoryKey /*: string */) { | |||
renderEmailForm = (subCategoryKey: string) => { | |||
const isEmailSettings = this.props.category === 'general' && subCategoryKey === 'email'; | |||
if (!isEmailSettings) { | |||
return null; | |||
} | |||
return <EmailForm />; | |||
} | |||
}; | |||
render() { | |||
const bySubCategory = groupBy(this.props.settings, setting => setting.definition.subCategory); | |||
@@ -67,7 +67,6 @@ export default class SubCategoryDefinitionsList extends React.PureComponent { | |||
const sortedSubCategories = sortBy(subCategories, subCategory => | |||
subCategory.name.toLowerCase() | |||
); | |||
return ( | |||
<ul className="settings-sub-categories-list"> | |||
{sortedSubCategories.map(subCategory => ( |
@@ -17,27 +17,28 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default function WildcardsHelp() { | |||
return ( | |||
<div className="huge-spacer-top"> | |||
<h2 className="spacer-bottom">Wildcards</h2> | |||
<p className="spacer-bottom">Following rules are applied:</p> | |||
<h2 className="spacer-bottom">{translate('settings.wildcards')}</h2> | |||
<p className="spacer-bottom">{translate('settings.wildcards.following_rules_are_applied')}</p> | |||
<table className="data spacer-bottom"> | |||
<tbody> | |||
<tr> | |||
<td>*</td> | |||
<td>Match zero or more characters</td> | |||
<td>{translate('settings.wildcards.zero_more_char')}</td> | |||
</tr> | |||
<tr> | |||
<td>**</td> | |||
<td>Match zero or more directories</td> | |||
<td>{translate('settings.wildcards.zero_more_dir')}</td> | |||
</tr> | |||
<tr> | |||
<td>?</td> | |||
<td>Match a single character</td> | |||
<td>{translate('settings.wildcards.single_char')}</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
@@ -45,72 +46,72 @@ export default function WildcardsHelp() { | |||
<table className="data zebra"> | |||
<thead> | |||
<tr> | |||
<th>Example</th> | |||
<th>Matches</th> | |||
<th>Does not match</th> | |||
<th>{translate('example')}</th> | |||
<th>{translate('settings.wildcards.matches')}</th> | |||
<th>{translate('settings.wildcards.does_no_match')}</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr> | |||
<td>**/foo/*.js</td> | |||
<td>{'**/foo/*.js'}</td> | |||
<td> | |||
<ul> | |||
<li>src/foo/bar.js</li> | |||
<li>lib/ui/foo/bar.js</li> | |||
<li>{'src/foo/bar.js'}</li> | |||
<li>{'lib/ui/foo/bar.js'}</li> | |||
</ul> | |||
</td> | |||
<td> | |||
<ul> | |||
<li>src/bar.js</li> | |||
<li>src/foo2/bar.js</li> | |||
<li>{'src/bar.js'}</li> | |||
<li>{'src/foo2/bar.js'}</li> | |||
</ul> | |||
</td> | |||
</tr> | |||
<tr> | |||
<td>src/foo/*bar*.js</td> | |||
<td>{'src/foo/*bar*.js'}</td> | |||
<td> | |||
<ul> | |||
<li>src/foo/bar.js</li> | |||
<li>src/foo/bar1.js</li> | |||
<li>src/foo/bar123.js</li> | |||
<li>src/foo/123bar123.js</li> | |||
<li>{'src/foo/bar.js'}</li> | |||
<li>{'src/foo/bar1.js'}</li> | |||
<li>{'src/foo/bar123.js'}</li> | |||
<li>{'src/foo/123bar123.js'}</li> | |||
</ul> | |||
</td> | |||
<td> | |||
<ul> | |||
<li>src/foo/ui/bar.js</li> | |||
<li>src/bar.js</li> | |||
<li>{'src/foo/ui/bar.js'}</li> | |||
<li>{'src/bar.js'}</li> | |||
</ul> | |||
</td> | |||
</tr> | |||
<tr> | |||
<td>src/foo/**</td> | |||
<td>{'src/foo/**'}</td> | |||
<td> | |||
<ul> | |||
<li>src/foo/bar.js</li> | |||
<li>src/foo/ui/bar.js</li> | |||
<li>{'src/foo/bar.js'}</li> | |||
<li>{'src/foo/ui/bar.js'}</li> | |||
</ul> | |||
</td> | |||
<td> | |||
<ul> | |||
<li>src/bar/foo/bar.js</li> | |||
<li>src/bar.js</li> | |||
<li>{'src/bar/foo/bar.js'}</li> | |||
<li>{'src/bar.js'}</li> | |||
</ul> | |||
</td> | |||
</tr> | |||
<tr> | |||
<td>**/foo?.js</td> | |||
<td>{'**/foo?.js'}</td> | |||
<td> | |||
<ul> | |||
<li>src/foo1.js</li> | |||
<li>src/bar/foo1.js</li> | |||
<li>{'src/foo1.js'}</li> | |||
<li>{'src/bar/foo1.js'}</li> | |||
</ul> | |||
</td> | |||
<td> | |||
<ul> | |||
<li>src/foo.js</li> | |||
<li>src/foo12.js</li> | |||
<li>src/12foo3.js</li> | |||
<li>{'src/foo.js'}</li> | |||
<li>{'src/foo12.js'}</li> | |||
<li>{'src/12foo3.js'}</li> | |||
</ul> | |||
</td> | |||
</tr> |
@@ -20,8 +20,8 @@ | |||
/* eslint-disable import/order */ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import DefinitionActions from '../components/DefinitionActions'; | |||
import { SettingType } from '../../../app/types'; | |||
import DefinitionActions from '../DefinitionActions'; | |||
import { SettingType } from '../../../../app/types'; | |||
const definition = { | |||
category: 'baz', |
@@ -1,127 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { receiveValues } from './values/actions'; | |||
import { receiveDefinitions } from './definitions/actions'; | |||
import { startLoading, stopLoading } from './settingsPage/loading/actions'; | |||
import { passValidation, failValidation } from './settingsPage/validationMessages/actions'; | |||
import { cancelChange } from './settingsPage/changedValues/actions'; | |||
import { | |||
getDefinitions, | |||
getValues, | |||
setSettingValue, | |||
resetSettingValue | |||
} from '../../../api/settings'; | |||
import { parseError } from '../../../helpers/request'; | |||
import { addGlobalErrorMessage, closeAllGlobalMessages } from '../../../store/globalMessages'; | |||
import { isEmptyValue } from '../utils'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getSettingsAppDefinition, getSettingsAppChangedValue } from '../../../store/rootReducer'; | |||
export const fetchSettings = componentKey => dispatch => { | |||
return getDefinitions(componentKey).then( | |||
definitions => { | |||
const filtered = definitions | |||
.filter(definition => definition.type !== 'LICENSE') | |||
// do not display this setting on project level | |||
.filter( | |||
definition => | |||
componentKey == null || definition.key !== 'sonar.branch.longLivedBranches.regex' | |||
); | |||
dispatch(receiveDefinitions(filtered)); | |||
}, | |||
e => parseError(e).then(message => dispatch(addGlobalErrorMessage(message))) | |||
); | |||
}; | |||
export const fetchValues = (keys, component) => dispatch => | |||
getValues({ keys, component }).then( | |||
settings => { | |||
dispatch(receiveValues(settings, component)); | |||
dispatch(closeAllGlobalMessages()); | |||
}, | |||
() => {} | |||
); | |||
export const checkValue = (key, componentKey) => (dispatch, getState) => { | |||
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; | |||
} | |||
dispatch(passValidation(key)); | |||
return true; | |||
}; | |||
export const saveValue = (key, componentKey) => (dispatch, getState) => { | |||
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, componentKey) | |||
.then(() => getValues({ keys: key, component: componentKey })) | |||
.then(values => { | |||
dispatch(receiveValues(values, componentKey)); | |||
dispatch(cancelChange(key)); | |||
dispatch(passValidation(key)); | |||
dispatch(stopLoading(key)); | |||
}) | |||
.catch(e => { | |||
dispatch(stopLoading(key)); | |||
parseError(e).then(message => dispatch(failValidation(key, message))); | |||
return Promise.reject(); | |||
}); | |||
}; | |||
export const resetValue = (key, componentKey) => dispatch => { | |||
dispatch(startLoading(key)); | |||
return resetSettingValue({ keys: key, component: componentKey }) | |||
.then(() => getValues({ keys: key, component: componentKey })) | |||
.then(values => { | |||
if (values.length > 0) { | |||
dispatch(receiveValues(values, componentKey)); | |||
} else { | |||
dispatch(receiveValues([{ key }], componentKey)); | |||
} | |||
dispatch(passValidation(key)); | |||
dispatch(stopLoading(key)); | |||
}) | |||
.catch(e => { | |||
dispatch(stopLoading(key)); | |||
parseError(e).then(message => dispatch(failValidation(key, message))); | |||
return Promise.reject(); | |||
}); | |||
}; |
@@ -0,0 +1,141 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { Dispatch } from 'redux'; | |||
import { receiveValues } from './values'; | |||
import { receiveDefinitions } from './definitions'; | |||
import { | |||
cancelChange, | |||
failValidation, | |||
passValidation, | |||
startLoading, | |||
stopLoading | |||
} from './settingsPage'; | |||
import { | |||
getDefinitions, | |||
getValues, | |||
setSettingValue, | |||
resetSettingValue | |||
} from '../../../api/settings'; | |||
import { parseError } from '../../../helpers/request'; | |||
import { closeAllGlobalMessages } from '../../../store/globalMessages'; | |||
import { isEmptyValue } from '../utils'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { | |||
getSettingsAppDefinition, | |||
getSettingsAppChangedValue, | |||
Store | |||
} from '../../../store/rootReducer'; | |||
import { SettingType } from '../../../app/types'; | |||
export function fetchSettings(component?: string) { | |||
return (dispatch: Dispatch) => { | |||
return getDefinitions(component).then(definitions => { | |||
const filtered = definitions | |||
.filter(definition => definition.type !== SettingType.License) | |||
// do not display this setting on project level | |||
.filter( | |||
definition => !component || definition.key !== 'sonar.branch.longLivedBranches.regex' | |||
); | |||
dispatch(receiveDefinitions(filtered)); | |||
}); | |||
}; | |||
} | |||
export function fetchValues(keys: string, component?: string) { | |||
return (dispatch: Dispatch) => | |||
getValues({ keys, component }).then(settings => { | |||
dispatch(receiveValues(settings, component)); | |||
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; | |||
} | |||
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(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(values, component)); | |||
} else { | |||
dispatch(receiveValues([{ key }], component)); | |||
} | |||
dispatch(passValidation(key)); | |||
dispatch(stopLoading(key)); | |||
}) | |||
.catch(handleError(key, dispatch)); | |||
}; | |||
} | |||
function handleError(key: string, dispatch: Dispatch) { | |||
return (error: { response: Response }) => { | |||
dispatch(stopLoading(key)); | |||
return parseError(error).then(message => { | |||
dispatch(failValidation(key, message)); | |||
return Promise.reject(); | |||
}); | |||
}; | |||
} |
@@ -17,49 +17,53 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import { keyBy, sortBy, uniqBy } from 'lodash'; | |||
import { RECEIVE_DEFINITIONS } from './actions'; | |||
import { DEFAULT_CATEGORY, getCategoryName } from '../../utils'; | |||
/*:: import type { Definition } from '../../types'; */ | |||
import { ActionType } from '../../../store/utils/actions'; | |||
import { SettingCategoryDefinition } from '../../../app/types'; | |||
import { DEFAULT_CATEGORY, getCategoryName } from '../utils'; | |||
/*:: | |||
type State = { [key: string]: Definition }; | |||
*/ | |||
const enum Actions { | |||
ReceiveDefinitions = 'RECEIVE_DEFINITIONS' | |||
} | |||
type Action = ActionType<typeof receiveDefinitions, Actions.ReceiveDefinitions>; | |||
export interface State { | |||
[key: string]: SettingCategoryDefinition; | |||
} | |||
/*:: | |||
type Action = { type: string, definitions: Definition[] }; | |||
*/ | |||
export function receiveDefinitions(definitions: SettingCategoryDefinition[]) { | |||
return { type: Actions.ReceiveDefinitions, definitions }; | |||
} | |||
const reducer = (state /*: State */ = {}, action /*: Action */) => { | |||
if (action.type === RECEIVE_DEFINITIONS) { | |||
export default function components(state: State = {}, action: Action) { | |||
if (action.type === Actions.ReceiveDefinitions) { | |||
return keyBy(action.definitions, 'key'); | |||
} | |||
return state; | |||
}; | |||
export default reducer; | |||
} | |||
export function getDefinition(state /*: State */, key /*: string */) /*: Definition */ { | |||
export function getDefinition(state: State, key: string) { | |||
return state[key]; | |||
} | |||
export function getAllDefinitions(state /*: State */) /*: Definition[] */ { | |||
export function getAllDefinitions(state: State) { | |||
return Object.keys(state).map(key => state[key]); | |||
} | |||
export const getDefinitionsForCategory = (state /*: State */, category /*: string */) => | |||
getAllDefinitions(state).filter( | |||
export function getDefinitionsForCategory(state: State, category: string) { | |||
return getAllDefinitions(state).filter( | |||
definition => definition.category.toLowerCase() === category.toLowerCase() | |||
); | |||
} | |||
export const getAllCategories = (state /*: State */) => | |||
uniqBy(getAllDefinitions(state).map(definition => definition.category), category => | |||
export function getAllCategories(state: State) { | |||
return uniqBy(getAllDefinitions(state).map(definition => definition.category), category => | |||
category.toLowerCase() | |||
); | |||
} | |||
export const getDefaultCategory = (state /*: State */) => { | |||
export function getDefaultCategory(state: State) { | |||
const categories = getAllCategories(state); | |||
if (categories.includes(DEFAULT_CATEGORY)) { | |||
return DEFAULT_CATEGORY; | |||
@@ -69,4 +73,4 @@ export const getDefaultCategory = (state /*: State */) => { | |||
); | |||
return sortedCategories[0]; | |||
} | |||
}; | |||
} |
@@ -1,33 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
// @flow | |||
/*:: import type { Definition } from '../../types'; */ | |||
export const RECEIVE_DEFINITIONS /*: string */ = 'RECEIVE_DEFINITIONS'; | |||
/** | |||
* Receive definitions action creator | |||
* @param {Array} definitions | |||
* @returns {Object} | |||
*/ | |||
export const receiveDefinitions = (definitions /*: Definition[] */) => ({ | |||
type: RECEIVE_DEFINITIONS, | |||
definitions | |||
}); |
@@ -1,79 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
// @flow | |||
import { combineReducers } from 'redux'; | |||
import definitions, * as fromDefinitions from './definitions/reducer'; | |||
import values, * as fromValues from './values/reducer'; | |||
import settingsPage, * as fromSettingsPage from './settingsPage/reducer'; | |||
import globalMessages, * as fromGlobalMessages from '../../../store/globalMessages'; | |||
/*:: import type { State as GlobalMessagesState } from '../../../store/globalMessages'; */ | |||
/*:: import type { State as ValuesState } from './values/reducer'; */ | |||
/*:: | |||
type State = { | |||
definitions: {}, | |||
globalMessages: GlobalMessagesState, | |||
settingsPage: {}, | |||
values: ValuesState | |||
}; | |||
*/ | |||
const rootReducer = combineReducers({ | |||
definitions, | |||
values, | |||
settingsPage, | |||
globalMessages | |||
}); | |||
export default rootReducer; | |||
export const getDefinition = (state /*: State */, key /*: string */) => | |||
fromDefinitions.getDefinition(state.definitions, key); | |||
export const getAllCategories = (state /*: State */) => | |||
fromDefinitions.getAllCategories(state.definitions); | |||
export const getDefaultCategory = (state /*: State */) => | |||
fromDefinitions.getDefaultCategory(state.definitions); | |||
export const getValue = (state /*: State */, key /*: string */, componentKey /*: ?string */) => | |||
fromValues.getValue(state.values, key, componentKey); | |||
export const getSettingsForCategory = ( | |||
state /*: State */, | |||
category /*: string */, | |||
componentKey /*: ?string */ | |||
) => | |||
fromDefinitions.getDefinitionsForCategory(state.definitions, category).map(definition => ({ | |||
...getValue(state, definition.key, componentKey), | |||
definition | |||
})); | |||
export const getChangedValue = (state /*: State */, key /*: string */) => | |||
fromSettingsPage.getChangedValue(state.settingsPage, key); | |||
export const isLoading = (state /*: State */, key /*: string */) => | |||
fromSettingsPage.isLoading(state.settingsPage, key); | |||
export const getValidationMessage = (state /*: State */, key /*: string */) => | |||
fromSettingsPage.getValidationMessage(state.settingsPage, key); | |||
export const getGlobalMessages = (state /*: State */) => | |||
fromGlobalMessages.getGlobalMessages(state.globalMessages); |
@@ -0,0 +1,73 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { combineReducers } from 'redux'; | |||
import definitions, * as fromDefinitions from './definitions'; | |||
import values, * as fromValues from './values'; | |||
import settingsPage, * as fromSettingsPage from './settingsPage'; | |||
import globalMessages, * as fromGlobalMessages from '../../../store/globalMessages'; | |||
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 getAllCategories(state: State) { | |||
return fromDefinitions.getAllCategories(state.definitions); | |||
} | |||
export function getDefaultCategory(state: State) { | |||
return fromDefinitions.getDefaultCategory(state.definitions); | |||
} | |||
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 => ({ | |||
key: definition.key, | |||
...getValue(state, definition.key, component), | |||
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); | |||
} |
@@ -0,0 +1,113 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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'; | |||
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: { [key: string]: any }; | |||
loading: { [key: string]: boolean }; | |||
validationMessages: { [key: string]: 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]); | |||
} |
@@ -1,33 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
export const CHANGE_VALUE = 'settingsPage/CHANGE_VALUE'; | |||
export const changeValue = (key, value) => ({ | |||
type: CHANGE_VALUE, | |||
key, | |||
value | |||
}); | |||
export const CANCEL_CHANGE = 'settingsPage/CANCEL_CHANGE'; | |||
export const cancelChange = key => ({ | |||
type: CANCEL_CHANGE, | |||
key | |||
}); |
@@ -1,37 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { CHANGE_VALUE, CANCEL_CHANGE } from './actions'; | |||
const reducer = (state = {}, action = {}) => { | |||
if (action.type === CHANGE_VALUE) { | |||
return { ...state, [action.key]: action.value }; | |||
} | |||
if (action.type === CANCEL_CHANGE) { | |||
return omit(state, action.key); | |||
} | |||
return state; | |||
}; | |||
export default reducer; | |||
export const getChangedValue = (state, key) => state[key]; |
@@ -1,31 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
export const START_LOADING = 'settingsPage/START_LOADING'; | |||
export const startLoading = key => ({ | |||
type: START_LOADING, | |||
key | |||
}); | |||
export const STOP_LOADING = 'settingsPage/STOP_LOADING'; | |||
export const stopLoading = key => ({ | |||
type: STOP_LOADING, | |||
key | |||
}); |
@@ -1,36 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { START_LOADING, STOP_LOADING } from './actions'; | |||
const reducer = (state = {}, action = {}) => { | |||
if (action.type === START_LOADING) { | |||
return { ...state, [action.key]: true }; | |||
} | |||
if (action.type === STOP_LOADING) { | |||
return { ...state, [action.key]: false }; | |||
} | |||
return state; | |||
}; | |||
export default reducer; | |||
export const isLoading = (state, key) => !!state[key]; |
@@ -1,37 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { combineReducers } from 'redux'; | |||
import changedValues, * as fromChangedValues from './changedValues/reducer'; | |||
import validationMessages, * as fromValidationMessages from './validationMessages/reducer'; | |||
import loading, * as fromLoading from './loading/reducer'; | |||
export default combineReducers({ | |||
changedValues, | |||
validationMessages, | |||
loading | |||
}); | |||
export const getChangedValue = (state, key) => | |||
fromChangedValues.getChangedValue(state.changedValues, key); | |||
export const getValidationMessage = (state, key) => | |||
fromValidationMessages.getValidationMessage(state.validationMessages, key); | |||
export const isLoading = (state, key) => fromLoading.isLoading(state.loading, key); |
@@ -1,33 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
export const FAIL_VALIDATION = 'settingsPage/FAIL_VALIDATION'; | |||
export const failValidation = (key, message) => ({ | |||
type: FAIL_VALIDATION, | |||
key, | |||
message | |||
}); | |||
export const PASS_VALIDATION = 'settingsPage/PASS_VALIDATION'; | |||
export const passValidation = key => ({ | |||
type: PASS_VALIDATION, | |||
key | |||
}); |
@@ -1,36 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { FAIL_VALIDATION, PASS_VALIDATION } from './actions'; | |||
const reducer = (state = {}, action = {}) => { | |||
if (action.type === FAIL_VALIDATION) { | |||
return { ...state, [action.key]: action.message }; | |||
} | |||
if (action.type === PASS_VALIDATION) { | |||
return { ...state, [action.key]: null }; | |||
} | |||
return state; | |||
}; | |||
export default reducer; | |||
export const getValidationMessage = (state, key) => state[key]; |
@@ -0,0 +1,85 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { combineReducers } from 'redux'; | |||
import { keyBy } from 'lodash'; | |||
import { ActionType } from '../../../store/utils/actions'; | |||
import { Action as AppStateAction, Actions as AppStateActions } from '../../../store/appState'; | |||
import { SettingValue } from '../../../app/types'; | |||
enum Actions { | |||
receiveValues = 'RECEIVE_VALUES' | |||
} | |||
type Action = ActionType<typeof receiveValues, Actions.receiveValues>; | |||
interface SettingsState { | |||
[key: string]: SettingValue; | |||
} | |||
export interface State { | |||
components: { [component: string]: SettingsState }; | |||
global: SettingsState; | |||
} | |||
export function receiveValues( | |||
settings: Array<{ key: string; value?: string }>, | |||
component?: string | |||
) { | |||
return { type: Actions.receiveValues, settings, component }; | |||
} | |||
function components(state: State['components'] = {}, action: Action) { | |||
const { component: key } = action; | |||
if (!key) { | |||
return state; | |||
} | |||
if (action.type === Actions.receiveValues) { | |||
const settingsByKey = keyBy(action.settings, 'key'); | |||
return { ...state, [key]: { ...(state[key] || {}), ...settingsByKey } }; | |||
} | |||
return state; | |||
} | |||
function global(state: State['components'] = {}, action: Action | AppStateAction) { | |||
if (action.type === Actions.receiveValues) { | |||
if (action.component) { | |||
return state; | |||
} | |||
const settingsByKey = keyBy(action.settings, 'key'); | |||
return { ...state, ...settingsByKey }; | |||
} | |||
if (action.type === AppStateActions.SetAppState) { | |||
const settingsByKey: SettingsState = {}; | |||
Object.keys(action.appState.settings).forEach( | |||
key => (settingsByKey[key] = { key, value: action.appState.settings[key] }) | |||
); | |||
return { ...state, ...settingsByKey }; | |||
} | |||
return state; | |||
} | |||
export default combineReducers({ components, global }); | |||
export function getValue(state: State, key: string, component?: string): SettingValue | undefined { | |||
if (component) { | |||
return state.components[component] && state.components[component][key]; | |||
} | |||
return state.global[key]; | |||
} |
@@ -1,74 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
// @flow | |||
import { combineReducers } from 'redux'; | |||
import { keyBy } from 'lodash'; | |||
import { RECEIVE_VALUES } from './actions'; | |||
/*:: | |||
type SettingsState = { [key: string]: {} }; | |||
type ComponentsState = { [key: string]: SettingsState }; | |||
export type State = { components: ComponentsState, global: SettingsState }; | |||
*/ | |||
const componentsSettings = (state /*: ComponentsState */ = {}, action /*: Object */) => { | |||
if (!action.componentKey) { | |||
return state; | |||
} | |||
const key = action.componentKey; | |||
if (action.type === RECEIVE_VALUES) { | |||
const settingsByKey = keyBy(action.settings, 'key'); | |||
return { ...state, [key]: { ...(state[key] || {}), ...settingsByKey } }; | |||
} | |||
return state; | |||
}; | |||
const globalSettings = (state /*: SettingsState */ = {}, action /*: Object */) => { | |||
if (action.componentKey) { | |||
return state; | |||
} | |||
if (action.type === RECEIVE_VALUES) { | |||
const settingsByKey = keyBy(action.settings, 'key'); | |||
return { ...state, ...settingsByKey }; | |||
} | |||
if (action.type === 'SET_APP_STATE') { | |||
const settingsByKey = {}; | |||
Object.keys(action.appState.settings).forEach( | |||
key => (settingsByKey[key] = { value: action.appState.settings[key] }) | |||
); | |||
return { ...state, ...settingsByKey }; | |||
} | |||
return state; | |||
}; | |||
export default combineReducers({ components: componentsSettings, global: globalSettings }); | |||
export const getValue = (state /*: State */, key /*: string */, componentKey /*: ?string */) => { | |||
let settings = state.global; | |||
if (componentKey) { | |||
settings = state.components[componentKey]; | |||
} | |||
return settings && settings[key]; | |||
}; |
@@ -1,26 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
// @flow | |||
/*:: | |||
export type Definition = { | |||
key: string, | |||
category: string | |||
}; | |||
*/ |
@@ -55,10 +55,14 @@ function Avatar(props: Props) { | |||
); | |||
} | |||
const mapStateToProps = (state: Store) => ({ | |||
enableGravatar: (getGlobalSettingValue(state, 'sonar.lf.enableGravatar') || {}).value === 'true', | |||
gravatarServerUrl: (getGlobalSettingValue(state, 'sonar.lf.gravatarServerUrl') || {}).value | |||
}); | |||
const mapStateToProps = (state: Store) => { | |||
const enableGravatar = getGlobalSettingValue(state, 'sonar.lf.enableGravatar'); | |||
const gravatarServerUrl = getGlobalSettingValue(state, 'sonar.lf.gravatarServerUrl'); | |||
return { | |||
enableGravatar: Boolean(enableGravatar && enableGravatar.value === 'true'), | |||
gravatarServerUrl: (gravatarServerUrl && gravatarServerUrl.value) || '' | |||
}; | |||
}; | |||
export default connect(mapStateToProps)(Avatar); | |||
@@ -21,21 +21,27 @@ import { ActionType } from './utils/actions'; | |||
import { Extension, AppState } from '../app/types'; | |||
import { EditionKey } from '../apps/marketplace/utils'; | |||
type Action = | |||
| ActionType<typeof setAppState, 'SET_APP_STATE'> | |||
| ActionType<typeof setAdminPages, 'SET_ADMIN_PAGES'> | |||
| ActionType<typeof requireAuthorization, 'REQUIRE_AUTHORIZATION'>; | |||
export const enum Actions { | |||
SetAppState = 'SET_APP_STATE', | |||
SetAdminPages = 'SET_ADMIN_PAGES', | |||
RequireAuthorization = 'REQUIRE_AUTHORIZATION' | |||
} | |||
export type Action = | |||
| ActionType<typeof setAppState, Actions.SetAppState> | |||
| ActionType<typeof setAdminPages, Actions.SetAdminPages> | |||
| ActionType<typeof requireAuthorization, Actions.RequireAuthorization>; | |||
export function setAppState(appState: AppState) { | |||
return { type: 'SET_APP_STATE', appState }; | |||
return { type: Actions.SetAppState, appState }; | |||
} | |||
export function setAdminPages(adminPages: Extension[]) { | |||
return { type: 'SET_ADMIN_PAGES', adminPages }; | |||
return { type: Actions.SetAdminPages, adminPages }; | |||
} | |||
export function requireAuthorization() { | |||
return { type: 'REQUIRE_AUTHORIZATION' }; | |||
return { type: Actions.RequireAuthorization }; | |||
} | |||
const defaultValue: AppState = { | |||
@@ -46,21 +52,19 @@ const defaultValue: AppState = { | |||
organizationsEnabled: false, | |||
productionDatabase: true, | |||
qualifiers: [], | |||
settings: {}, | |||
version: '' | |||
}; | |||
export default function(state: AppState = defaultValue, action: Action): AppState { | |||
if (action.type === 'SET_APP_STATE') { | |||
if (action.type === Actions.SetAppState) { | |||
return { ...state, ...action.appState }; | |||
} | |||
if (action.type === 'SET_ADMIN_PAGES') { | |||
if (action.type === Actions.SetAdminPages) { | |||
return { ...state, adminPages: action.adminPages }; | |||
} | |||
if (action.type === 'REQUIRE_AUTHORIZATION') { | |||
if (action.type === Actions.RequireAuthorization) { | |||
return { ...state, authorizationError: true }; | |||
} | |||
return state; | |||
} |
@@ -41,8 +41,8 @@ export function closeGlobalMessage(id: string) { | |||
return { type: 'CLOSE_GLOBAL_MESSAGE', id }; | |||
} | |||
export function closeAllGlobalMessages(id: string) { | |||
return { type: 'CLOSE_ALL_GLOBAL_MESSAGES', id }; | |||
export function closeAllGlobalMessages() { | |||
return { type: 'CLOSE_ALL_GLOBAL_MESSAGES' }; | |||
} | |||
type Action = |
@@ -109,9 +109,9 @@ export function getSettingsAppDefaultCategory(state: Store) { | |||
export function getSettingsAppSettingsForCategory( | |||
state: Store, | |||
category: string, | |||
componentKey: string | |||
component?: string | |||
) { | |||
return fromSettingsApp.getSettingsForCategory(state.settingsApp, category, componentKey); | |||
return fromSettingsApp.getSettingsForCategory(state.settingsApp, category, component); | |||
} | |||
export function getSettingsAppChangedValue(state: Store, key: string) { |
@@ -63,6 +63,7 @@ duplications=Duplications | |||
end_date=End Date | |||
edit=Edit | |||
events=Events | |||
example=Example | |||
explore=Explore | |||
false=False | |||
favorite=Favorite | |||
@@ -855,7 +856,14 @@ settings.default.no_value=<no value> | |||
settings.default.complex_value=<complex value> | |||
settings.default.password=<password> | |||
settings.reset_confirm.title=Reset Setting | |||
settings.reset_confirm.description=Are you sure that you want to reset this setting ? | |||
settings.reset_confirm.description=Are you sure that you want to reset this setting? | |||
settings.wildcards=Wildcards | |||
settings.wildcards.following_rules_are_applied=Following rules are applied: | |||
settings.wildcards.zero_more_char=Match zero or more characters | |||
settings.wildcards.zero_more_dir=Match zero or more directories | |||
settings.wildcards.single_char=Match a single character | |||
settings.wildcards.matches=Matches | |||
settings.wildcards.does_no_match=Does not match | |||
property.category.general=General | |||
property.category.general.email=Email |