소스 검색

Rewrite remaining of the settings page to TS

tags/7.5
Grégoire Aubert 5 년 전
부모
커밋
50c708c1b6
47개의 변경된 파일867개의 추가작업 그리고 1043개의 파일을 삭제
  1. 13
    4
      server/sonar-web/src/main/js/api/settings.ts
  2. 13
    10
      server/sonar-web/src/main/js/app/components/App.tsx
  3. 6
    3
      server/sonar-web/src/main/js/app/components/PageTracker.tsx
  4. 6
    3
      server/sonar-web/src/main/js/app/components/embed-docs-modal/ProductNewsMenuItem.tsx
  5. 8
    4
      server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx
  6. 2
    0
      server/sonar-web/src/main/js/app/types.ts
  7. 1
    2
      server/sonar-web/src/main/js/apps/about/actions.ts
  8. 12
    13
      server/sonar-web/src/main/js/apps/about/components/AboutApp.tsx
  9. 10
    9
      server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx
  10. 2
    2
      server/sonar-web/src/main/js/apps/account/organizations/actions.ts
  11. 2
    2
      server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx
  12. 0
    34
      server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js
  13. 34
    36
      server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
  14. 0
    34
      server/sonar-web/src/main/js/apps/settings/components/AppContainer.js
  15. 52
    28
      server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx
  16. 9
    4
      server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.tsx
  17. 75
    83
      server/sonar-web/src/main/js/apps/settings/components/Definition.tsx
  18. 0
    42
      server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js
  19. 17
    7
      server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.tsx
  20. 62
    23
      server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx
  21. 18
    25
      server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx
  22. 12
    13
      server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx
  23. 33
    32
      server/sonar-web/src/main/js/apps/settings/components/WildcardsHelp.tsx
  24. 2
    2
      server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx
  25. 0
    0
      server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionActions-test.tsx.snap
  26. 0
    127
      server/sonar-web/src/main/js/apps/settings/store/actions.js
  27. 141
    0
      server/sonar-web/src/main/js/apps/settings/store/actions.ts
  28. 28
    24
      server/sonar-web/src/main/js/apps/settings/store/definitions.ts
  29. 0
    33
      server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js
  30. 0
    79
      server/sonar-web/src/main/js/apps/settings/store/rootReducer.js
  31. 73
    0
      server/sonar-web/src/main/js/apps/settings/store/rootReducer.ts
  32. 113
    0
      server/sonar-web/src/main/js/apps/settings/store/settingsPage.ts
  33. 0
    33
      server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/actions.js
  34. 0
    37
      server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/reducer.js
  35. 0
    31
      server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/actions.js
  36. 0
    36
      server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/reducer.js
  37. 0
    37
      server/sonar-web/src/main/js/apps/settings/store/settingsPage/reducer.js
  38. 0
    33
      server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/actions.js
  39. 0
    36
      server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/reducer.js
  40. 85
    0
      server/sonar-web/src/main/js/apps/settings/store/values.ts
  41. 0
    74
      server/sonar-web/src/main/js/apps/settings/store/values/reducer.js
  42. 0
    26
      server/sonar-web/src/main/js/apps/settings/types.js
  43. 8
    4
      server/sonar-web/src/main/js/components/ui/Avatar.tsx
  44. 17
    13
      server/sonar-web/src/main/js/store/appState.ts
  45. 2
    2
      server/sonar-web/src/main/js/store/globalMessages.ts
  46. 2
    2
      server/sonar-web/src/main/js/store/rootReducer.ts
  47. 9
    1
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 13
- 4
server/sonar-web/src/main/js/api/settings.ts 파일 보기

@@ -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

+ 13
- 10
server/sonar-web/src/main/js/app/components/App.tsx 파일 보기

@@ -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,

+ 6
- 3
server/sonar-web/src/main/js/app/components/PageTracker.tsx 파일 보기

@@ -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));

+ 6
- 3
server/sonar-web/src/main/js/app/components/embed-docs-modal/ProductNewsMenuItem.tsx 파일 보기

@@ -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);

+ 8
- 4
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx 파일 보기

@@ -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);

+ 2
- 0
server/sonar-web/src/main/js/app/types.ts 파일 보기

@@ -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'

+ 1
- 2
server/sonar-web/src/main/js/apps/about/actions.ts 파일 보기

@@ -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));
});

+ 12
- 13
server/sonar-web/src/main/js/apps/about/components/AboutApp.tsx 파일 보기

@@ -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;


+ 10
- 9
server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx 파일 보기

@@ -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

+ 2
- 2
server/sonar-web/src/main/js/apps/account/organizations/actions.ts 파일 보기

@@ -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));
});
};

+ 2
- 2
server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx 파일 보기

@@ -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')
};
};


+ 0
- 34
server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js 파일 보기

@@ -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);

server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js → server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx 파일 보기

@@ -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);

+ 0
- 34
server/sonar-web/src/main/js/apps/settings/components/AppContainer.js 파일 보기

@@ -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);

server/sonar-web/src/main/js/apps/settings/components/App.js → server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx 파일 보기

@@ -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);

server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js → server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.tsx 파일 보기

@@ -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
)
});


server/sonar-web/src/main/js/apps/settings/components/Definition.js → server/sonar-web/src/main/js/apps/settings/components/Definition.tsx 파일 보기

@@ -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);

+ 0
- 42
server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js 파일 보기

@@ -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>
);
}
}

server/sonar-web/src/main/js/apps/settings/store/values/actions.ts → server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.tsx 파일 보기

@@ -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>
);
}

server/sonar-web/src/main/js/apps/settings/components/EmailForm.js → server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx 파일 보기

@@ -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);

server/sonar-web/src/main/js/apps/settings/components/PageHeader.js → server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx 파일 보기

@@ -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>
);
}

server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js → server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx 파일 보기

@@ -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 => (

server/sonar-web/src/main/js/apps/settings/components/WildcardsHelp.js → server/sonar-web/src/main/js/apps/settings/components/WildcardsHelp.tsx 파일 보기

@@ -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>

server/sonar-web/src/main/js/apps/settings/__tests__/DefinitionActions-test.tsx → server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx 파일 보기

@@ -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',

server/sonar-web/src/main/js/apps/settings/__tests__/__snapshots__/DefinitionActions-test.tsx.snap → server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionActions-test.tsx.snap 파일 보기


+ 0
- 127
server/sonar-web/src/main/js/apps/settings/store/actions.js 파일 보기

@@ -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();
});
};

+ 141
- 0
server/sonar-web/src/main/js/apps/settings/store/actions.ts 파일 보기

@@ -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();
});
};
}

server/sonar-web/src/main/js/apps/settings/store/definitions/reducer.js → server/sonar-web/src/main/js/apps/settings/store/definitions.ts 파일 보기

@@ -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];
}
};
}

+ 0
- 33
server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js 파일 보기

@@ -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
});

+ 0
- 79
server/sonar-web/src/main/js/apps/settings/store/rootReducer.js 파일 보기

@@ -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);

+ 73
- 0
server/sonar-web/src/main/js/apps/settings/store/rootReducer.ts 파일 보기

@@ -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);
}

+ 113
- 0
server/sonar-web/src/main/js/apps/settings/store/settingsPage.ts 파일 보기

@@ -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]);
}

+ 0
- 33
server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/actions.js 파일 보기

@@ -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
});

+ 0
- 37
server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/reducer.js 파일 보기

@@ -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];

+ 0
- 31
server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/actions.js 파일 보기

@@ -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
});

+ 0
- 36
server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/reducer.js 파일 보기

@@ -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];

+ 0
- 37
server/sonar-web/src/main/js/apps/settings/store/settingsPage/reducer.js 파일 보기

@@ -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);

+ 0
- 33
server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/actions.js 파일 보기

@@ -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
});

+ 0
- 36
server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/reducer.js 파일 보기

@@ -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];

+ 85
- 0
server/sonar-web/src/main/js/apps/settings/store/values.ts 파일 보기

@@ -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];
}

+ 0
- 74
server/sonar-web/src/main/js/apps/settings/store/values/reducer.js 파일 보기

@@ -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];
};

+ 0
- 26
server/sonar-web/src/main/js/apps/settings/types.js 파일 보기

@@ -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
};
*/

+ 8
- 4
server/sonar-web/src/main/js/components/ui/Avatar.tsx 파일 보기

@@ -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);


+ 17
- 13
server/sonar-web/src/main/js/store/appState.ts 파일 보기

@@ -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;
}

+ 2
- 2
server/sonar-web/src/main/js/store/globalMessages.ts 파일 보기

@@ -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 =

+ 2
- 2
server/sonar-web/src/main/js/store/rootReducer.ts 파일 보기

@@ -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) {

+ 9
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties 파일 보기

@@ -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

Loading…
취소
저장