aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-11-29 14:27:13 +0100
committerJulien Lancelot <julien.lancelot@sonarsource.com>2017-12-14 17:03:35 +0100
commitab2a5de55004f8ac50a99fae9840da26ce125088 (patch)
tree2c028a982112d1a5e854ca38b676e27a419c663c
parentfa6663db4630550b3c27c71bb62fea4c4b9c262c (diff)
downloadsonarqube-ab2a5de55004f8ac50a99fae9840da26ce125088.tar.gz
sonarqube-ab2a5de55004f8ac50a99fae9840da26ce125088.zip
Rewrite part of the permissions app to Typescript
-rw-r--r--server/sonar-web/src/main/js/api/permissions.ts23
-rw-r--r--server/sonar-web/src/main/js/app/types.ts6
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationPermissions.tsx (renamed from server/sonar-web/src/main/js/apps/organizations/components/OrganizationPermissions.js)19
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx (renamed from server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js)24
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.tsx (renamed from server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.js)47
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap)0
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx (renamed from server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.js)89
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersListContainer.tsx95
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx (renamed from server/sonar-web/src/main/js/apps/permissions/global/components/App.js)16
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx (renamed from server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.js)46
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx (renamed from server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.js)84
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/PageError.tsx (renamed from server/sonar-web/src/main/js/apps/permissions/shared/components/PageError.js)25
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionHeader.tsx87
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/SearchForm.tsx (renamed from server/sonar-web/src/main/js/apps/permissions/shared/components/SearchForm.js)14
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.tsx (renamed from server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.js)88
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ChangeVisibilityForm-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx3
20 files changed, 405 insertions, 272 deletions
diff --git a/server/sonar-web/src/main/js/api/permissions.ts b/server/sonar-web/src/main/js/api/permissions.ts
index 8d2cf3e5596..39d77276f02 100644
--- a/server/sonar-web/src/main/js/api/permissions.ts
+++ b/server/sonar-web/src/main/js/api/permissions.ts
@@ -178,12 +178,20 @@ export function removeProjectCreatorFromTemplate(
return post('/api/permissions/remove_project_creator_from_template', { templateId, permission });
}
+export interface PermissionUser {
+ login: string;
+ name: string;
+ email?: string;
+ permissions: string[];
+ avatar?: string;
+}
+
export function getPermissionsUsersForComponent(
projectKey: string,
query?: string,
permission?: string,
organization?: string
-): Promise<any> {
+): Promise<PermissionUser[]> {
const data: RequestData = { projectKey, ps: PAGE_SIZE };
if (query) {
data.q = query;
@@ -197,12 +205,19 @@ export function getPermissionsUsersForComponent(
return getJSON('/api/permissions/users', data).then(r => r.users);
}
+export interface PermissionGroup {
+ id: string;
+ name: string;
+ description?: string;
+ permissions: string[];
+}
+
export function getPermissionsGroupsForComponent(
projectKey: string,
query: string = '',
permission?: string,
organization?: string
-): Promise<any> {
+): Promise<PermissionGroup[]> {
const data: RequestData = { projectKey, ps: PAGE_SIZE };
if (query) {
data.q = query;
@@ -220,7 +235,7 @@ export function getGlobalPermissionsUsers(
query?: string,
permission?: string,
organization?: string
-): Promise<any> {
+): Promise<PermissionUser[]> {
const data: RequestData = { ps: PAGE_SIZE };
if (query) {
data.q = query;
@@ -238,7 +253,7 @@ export function getGlobalPermissionsGroups(
query?: string,
permission?: string,
organization?: string
-): Promise<any> {
+): Promise<PermissionGroup[]> {
const data: RequestData = { ps: PAGE_SIZE };
if (query) {
data.q = query;
diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts
index f18764601e4..a180856c47a 100644
--- a/server/sonar-web/src/main/js/app/types.ts
+++ b/server/sonar-web/src/main/js/app/types.ts
@@ -108,7 +108,7 @@ export interface Metric {
}
export interface Organization {
- adminPages?: Array<{ key: string; name: string }>;
+ adminPages?: { key: string; name: string }[];
avatar?: string;
canAdmin?: boolean;
canDelete?: boolean;
@@ -118,8 +118,8 @@ export interface Organization {
isDefault?: boolean;
key: string;
name: string;
- pages?: Array<{ key: string; name: string }>;
- projectVisibility: string;
+ pages?: { key: string; name: string }[];
+ projectVisibility: Visibility;
url?: string;
}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPermissions.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPermissions.tsx
index cab087b3e55..b2136ea2f35 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPermissions.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPermissions.tsx
@@ -17,24 +17,21 @@
* 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 { connect } from 'react-redux';
import GlobalPermissionsApp from '../../permissions/global/components/App';
import { getOrganizationByKey } from '../../../store/rootReducer';
-/*:: import type { Organization } from '../../../store/organizations/duck'; */
+import { Organization } from '../../../app/types';
-/*::
-type Props = {
- organization: Organization
-};
-*/
+interface Props {
+ organization: Organization;
+}
-function OrganizationPermissions(props /*: Props */) {
- return <GlobalPermissionsApp organization={props.organization} />;
+function OrganizationPermissions({ organization }: Props) {
+ return <GlobalPermissionsApp organization={organization} />;
}
-const mapStateToProps = (state, ownProps) => ({
+const mapStateToProps = (state: any, ownProps: any) => ({
organization: getOrganizationByKey(state, ownProps.params.organizationKey)
});
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx
index 507d43bbfb0..4cffcc47d80 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx
@@ -17,17 +17,16 @@
* 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 { Link } from 'react-router';
-import classNames from 'classnames';
import * as theme from '../../../app/theme';
import { translate } from '../../../helpers/l10n';
import ContextNavBar from '../../../components/nav/ContextNavBar';
import NavBarTabs from '../../../components/nav/NavBarTabs';
import OrganizationAvatar from '../../../components/common/OrganizationAvatar';
import { getQualityGatesUrl } from '../../../helpers/urls';
-/*:: import type { Organization } from '../../../store/organizations/duck'; */
+import { Extension, Organization } from '../../../app/types';
const ADMIN_PATHS = [
'edit',
@@ -38,14 +37,13 @@ const ADMIN_PATHS = [
'projects_management'
];
-export default class OrganizationNavigation extends React.PureComponent {
- /*:: props: {
- location: { pathname: string },
- organization: Organization
- };
-*/
+interface Props {
+ location: { pathname: string };
+ organization: Organization;
+}
- renderAdministration(adminActive /*: boolean */) {
+export default class OrganizationNavigation extends React.PureComponent<Props> {
+ renderAdministration(adminActive: boolean) {
const { organization } = this.props;
return (
@@ -105,7 +103,7 @@ export default class OrganizationNavigation extends React.PureComponent {
return extensions.map(this.renderExtension);
}
- renderExtension = (extension /*: { key: string, name: string } */) => {
+ renderExtension = (extension: Extension) => {
const { organization } = this.props;
const pathname = `/organizations/${organization.key}/extension/${extension.key}`;
return (
@@ -117,7 +115,7 @@ export default class OrganizationNavigation extends React.PureComponent {
);
};
- renderExtensions(moreActive /*: boolean */) {
+ renderExtensions(moreActive: boolean) {
const extensions = this.props.organization.pages || [];
if (extensions.length > 0) {
return (
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.js b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.tsx
index 3f337c032d5..e4884f83545 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.js
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.tsx
@@ -17,46 +17,45 @@
* 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 { shallow } from 'enzyme';
import OrganizationNavigation from '../OrganizationNavigation';
+import { Visibility } from '../../../../app/types';
jest.mock('../../../issues/utils', () => ({
isMySet: () => false
}));
+const organization = {
+ key: 'foo',
+ name: 'Foo',
+ canAdmin: false,
+ canDelete: false,
+ projectVisibility: Visibility.Public
+};
+
it('regular user', () => {
- const organization = { key: 'foo', name: 'Foo', canAdmin: false, canDelete: false };
- expect(
- shallow(
- <OrganizationNavigation
- location={{ pathname: '/organizations/foo' }}
- organization={organization}
- />
- )
- ).toMatchSnapshot();
+ expect(getWrapper()).toMatchSnapshot();
});
it('admin', () => {
- const organization = { key: 'foo', name: 'Foo', canAdmin: true, canDelete: true };
expect(
- shallow(
- <OrganizationNavigation
- location={{ pathname: '/organizations/foo' }}
- organization={organization}
- />
- )
+ getWrapper({ organization: { ...organization, canAdmin: true, canDelete: true } })
).toMatchSnapshot();
});
it('undeletable org', () => {
- const organization = { key: 'foo', name: 'Foo', canAdmin: true, canDelete: false };
expect(
- shallow(
- <OrganizationNavigation
- location={{ pathname: '/organizations/foo' }}
- organization={organization}
- />
- )
+ getWrapper({ organization: { ...organization, canAdmin: true, canDelete: false } })
).toMatchSnapshot();
});
+
+function getWrapper(props = {}) {
+ return shallow(
+ <OrganizationNavigation
+ location={{ pathname: '/organizations/foo' }}
+ organization={organization}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap
index 66184bbc5d4..66184bbc5d4 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap
diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.js b/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx
index f340d7e27c1..b9ee74940d0 100644
--- a/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.js
+++ b/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx
@@ -17,40 +17,39 @@
* 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 * as React from 'react';
import SearchForm from '../../shared/components/SearchForm';
import HoldersList from '../../shared/components/HoldersList';
-import {
- loadHolders,
- grantToUser,
- revokeFromUser,
- grantToGroup,
- revokeFromGroup,
- updateFilter,
- updateQuery,
- selectPermission
-} from '../store/actions';
import { translate } from '../../../../helpers/l10n';
-import {
- getPermissionsAppUsers,
- getPermissionsAppGroups,
- getPermissionsAppQuery,
- getPermissionsAppFilter,
- getPermissionsAppSelectedPermission
-} from '../../../../store/rootReducer';
+import { Organization } from '../../../../app/types';
+import { PermissionUser, PermissionGroup } from '../../../../api/permissions';
const PERMISSIONS_ORDER = ['admin', 'profileadmin', 'gateadmin', 'scan', 'provisioning'];
-
const PERMISSIONS_FOR_CUSTOM_ORG = ['admin', 'profileadmin', 'scan', 'provisioning'];
-class AllHoldersList extends React.PureComponent {
+interface Props {
+ filter: string;
+ grantPermissionToGroup: (groupName: string, permission: string) => void;
+ grantPermissionToUser: (login: string, permission: string) => void;
+ groups: PermissionGroup[];
+ loadHolders: () => void;
+ onFilter: (filter: string) => void;
+ onSearch: (query: string) => void;
+ onSelectPermission: (permission: string) => void;
+ organization?: Organization;
+ query: string;
+ revokePermissionFromGroup: (groupName: string, permission: string) => void;
+ revokePermissionFromUser: (login: string, permission: string) => void;
+ selectedPermission?: string;
+ users: PermissionUser[];
+}
+
+export default class AllHoldersList extends React.PureComponent<Props> {
componentDidMount() {
this.props.loadHolders();
}
- handleToggleUser(user, permission) {
+ handleToggleUser = (user: PermissionUser, permission: string) => {
const hasPermission = user.permissions.includes(permission);
if (hasPermission) {
@@ -58,9 +57,9 @@ class AllHoldersList extends React.PureComponent {
} else {
this.props.grantPermissionToUser(user.login, permission);
}
- }
+ };
- handleToggleGroup(group, permission) {
+ handleToggleGroup = (group: PermissionGroup, permission: string) => {
const hasPermission = group.permissions.includes(permission);
if (hasPermission) {
@@ -68,7 +67,7 @@ class AllHoldersList extends React.PureComponent {
} else {
this.props.grantPermissionToGroup(group.name, permission);
}
- }
+ };
render() {
const order =
@@ -91,8 +90,8 @@ class AllHoldersList extends React.PureComponent {
users={this.props.users}
groups={this.props.groups}
onSelectPermission={this.props.onSelectPermission}
- onToggleUser={this.handleToggleUser.bind(this)}
- onToggleGroup={this.handleToggleGroup.bind(this)}>
+ onToggleUser={this.handleToggleUser}
+ onToggleGroup={this.handleToggleGroup}>
<SearchForm
query={this.props.query}
filter={this.props.filter}
@@ -103,37 +102,3 @@ class AllHoldersList extends React.PureComponent {
);
}
}
-
-const mapStateToProps = state => ({
- users: getPermissionsAppUsers(state),
- groups: getPermissionsAppGroups(state),
- query: getPermissionsAppQuery(state),
- filter: getPermissionsAppFilter(state),
- selectedPermission: getPermissionsAppSelectedPermission(state)
-});
-
-/*::
-type OwnProps = {
- organization?: { key: string }
-};
-*/
-
-const mapDispatchToProps = (dispatch /*: Function */, ownProps /*: OwnProps */) => {
- const organizationKey = ownProps.organization ? ownProps.organization.key : undefined;
- return {
- loadHolders: () => dispatch(loadHolders(organizationKey)),
- onSearch: query => dispatch(updateQuery(query, organizationKey)),
- onFilter: filter => dispatch(updateFilter(filter, organizationKey)),
- onSelectPermission: permission => dispatch(selectPermission(permission, organizationKey)),
- grantPermissionToUser: (login, permission) =>
- dispatch(grantToUser(login, permission, organizationKey)),
- revokePermissionFromUser: (login, permission) =>
- dispatch(revokeFromUser(login, permission, organizationKey)),
- grantPermissionToGroup: (groupName, permission) =>
- dispatch(grantToGroup(groupName, permission, organizationKey)),
- revokePermissionFromGroup: (groupName, permission) =>
- dispatch(revokeFromGroup(groupName, permission, organizationKey))
- };
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(AllHoldersList);
diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersListContainer.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersListContainer.tsx
new file mode 100644
index 00000000000..bb0647549eb
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersListContainer.tsx
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 {
+ loadHolders,
+ grantToUser,
+ revokeFromUser,
+ grantToGroup,
+ revokeFromGroup,
+ updateFilter,
+ updateQuery,
+ selectPermission
+} from '../store/actions';
+import {
+ getPermissionsAppUsers,
+ getPermissionsAppGroups,
+ getPermissionsAppQuery,
+ getPermissionsAppFilter,
+ getPermissionsAppSelectedPermission
+} from '../../../../store/rootReducer';
+import AllHoldersList from './AllHoldersList';
+import { Organization } from '../../../../app/types';
+import { PermissionUser, PermissionGroup } from '../../../../api/permissions';
+
+interface OwnProps {
+ organization?: Organization;
+}
+
+interface StateToProps {
+ filter: string;
+ groups: PermissionGroup[];
+ query: string;
+ selectedPermission?: string;
+ users: PermissionUser[];
+}
+
+interface DispatchToProps {
+ grantPermissionToGroup: (groupName: string, permission: string) => void;
+ grantPermissionToUser: (login: string, permission: string) => void;
+ loadHolders: () => void;
+ onFilter: (filter: string) => void;
+ onSearch: (query: string) => void;
+ onSelectPermission: (permission: string) => void;
+ revokePermissionFromGroup: (groupName: string, permission: string) => void;
+ revokePermissionFromUser: (login: string, permission: string) => void;
+}
+
+const mapStateToProps = (state: any) => ({
+ filter: getPermissionsAppFilter(state),
+ groups: getPermissionsAppGroups(state),
+ query: getPermissionsAppQuery(state),
+ selectedPermission: getPermissionsAppSelectedPermission(state),
+ users: getPermissionsAppUsers(state)
+});
+
+const mapDispatchToProps = (dispatch: Function, ownProps: OwnProps) => {
+ const organizationKey = ownProps.organization ? ownProps.organization.key : undefined;
+ return {
+ grantPermissionToGroup: (groupName: string, permission: string) =>
+ dispatch(grantToGroup(groupName, permission, organizationKey)),
+ grantPermissionToUser: (login: string, permission: string) =>
+ dispatch(grantToUser(login, permission, organizationKey)),
+ loadHolders: () => dispatch(loadHolders(organizationKey)),
+ onFilter: (filter: string) => dispatch(updateFilter(filter, organizationKey)),
+ onSearch: (query: string) => dispatch(updateQuery(query, organizationKey)),
+ onSelectPermission: (permission: string) =>
+ dispatch(selectPermission(permission, organizationKey)),
+ revokePermissionFromGroup: (groupName: string, permission: string) =>
+ dispatch(revokeFromGroup(groupName, permission, organizationKey)),
+ revokePermissionFromUser: (login: string, permission: string) =>
+ dispatch(revokeFromUser(login, permission, organizationKey))
+ };
+};
+
+export default connect<StateToProps, DispatchToProps, OwnProps>(
+ mapStateToProps,
+ mapDispatchToProps
+)(AllHoldersList);
diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/App.js b/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx
index 4b7ea9f15e1..fdd4aec3dcf 100644
--- a/server/sonar-web/src/main/js/apps/permissions/global/components/App.js
+++ b/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx
@@ -17,22 +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 * as React from 'react';
import Helmet from 'react-helmet';
import PageHeader from './PageHeader';
-import AllHoldersList from './AllHoldersList';
+import AllHoldersListContainer from './AllHoldersListContainer';
import PageError from '../../shared/components/PageError';
import { translate } from '../../../../helpers/l10n';
+import { Organization } from '../../../../app/types';
import '../../styles.css';
-export default function App(props /*: { organization?: {} } */) {
+interface Props {
+ organization?: Organization;
+}
+
+export default function App({ organization }: Props) {
return (
<div className="page page-limited">
<Helmet title={translate('global_permissions.permission')} />
- <PageHeader organization={props.organization} />
+ <PageHeader organization={organization} />
<PageError />
- <AllHoldersList organization={props.organization} />
+ <AllHoldersListContainer organization={organization} />
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.js b/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx
index 12a9c2af1ba..3fc076c002e 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.js
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx
@@ -17,39 +17,35 @@
* 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 PropTypes from 'prop-types';
+import * as React from 'react';
+import Checkbox from '../../../../components/controls/Checkbox';
import GroupIcon from '../../../../components/icons-components/GroupIcon';
+import { PermissionGroup } from '../../../../api/permissions';
-export default class GroupHolder extends React.PureComponent {
- static propTypes = {
- group: PropTypes.object.isRequired,
- permissions: PropTypes.array.isRequired,
- selectedPermission: PropTypes.string,
- permissionsOrder: PropTypes.array.isRequired,
- onToggle: PropTypes.func.isRequired
- };
+interface Props {
+ group: PermissionGroup;
+ permissions: string[];
+ selectedPermission?: string;
+ permissionsOrder: string[];
+ onToggle: (group: PermissionGroup, permission: string) => void;
+}
- handleClick(permission, e) {
- e.preventDefault();
- e.target.blur();
- this.props.onToggle(this.props.group, permission);
- }
+export default class GroupHolder extends React.PureComponent<Props> {
+ handleCheck = (_checked: boolean, permission?: string) =>
+ permission && this.props.onToggle(this.props.group, permission);
render() {
const { selectedPermission } = this.props;
- const permissionCells = this.props.permissionsOrder.map(p => (
+ const permissionCells = this.props.permissionsOrder.map(permission => (
<td
- key={p.key}
+ key={permission}
className="text-center text-middle"
- style={{ backgroundColor: p.key === selectedPermission ? '#d9edf7' : 'transparent' }}>
- <button className="button-clean" onClick={this.handleClick.bind(this, p.key)}>
- {this.props.permissions.includes(p.key) ? (
- <i className="icon-checkbox icon-checkbox-checked" />
- ) : (
- <i className="icon-checkbox" />
- )}
- </button>
+ style={{ backgroundColor: permission === selectedPermission ? '#d9edf7' : 'transparent' }}>
+ <Checkbox
+ checked={this.props.permissions.includes(permission)}
+ id={permission}
+ onCheck={this.handleCheck}
+ />
</td>
));
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.js b/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx
index da8bc9aa11f..189351ba346 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.js
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx
@@ -17,69 +17,35 @@
* 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 PropTypes from 'prop-types';
+import * as React from 'react';
import UserHolder from './UserHolder';
import GroupHolder from './GroupHolder';
-import Tooltip from '../../../../components/controls/Tooltip';
-import { translate, translateWithParameters } from '../../../../helpers/l10n';
+import PermissionHeader, { Permission } from './PermissionHeader';
+import { PermissionUser, PermissionGroup } from '../../../../api/permissions';
+import { translate } from '../../../../helpers/l10n';
-export default class HoldersList extends React.PureComponent {
- static propTypes = {
- permissions: PropTypes.array.isRequired,
- users: PropTypes.array.isRequired,
- groups: PropTypes.array.isRequired,
- selectedPermission: PropTypes.string,
- showPublicProjectsWarning: PropTypes.bool,
- onSelectPermission: PropTypes.func.isRequired,
- onToggleUser: PropTypes.func.isRequired,
- onToggleGroup: PropTypes.func.isRequired
- };
-
- static defaultProps = {
- showPublicProjectsWarning: false
- };
-
- handlePermissionClick = event => {
- event.preventDefault();
- event.currentTarget.blur();
- this.props.onSelectPermission(event.currentTarget.dataset.key);
- };
-
- renderTooltip = permission =>
- this.props.showPublicProjectsWarning &&
- (permission.key === 'user' || permission.key === 'codeviewer') ? (
- <div>
- {permission.description}
- <div className="alert alert-warning spacer-top">
- {translate('projects_role.public_projects_warning')}
- </div>
- </div>
- ) : (
- permission.description
- );
+interface Props {
+ permissions: Permission[];
+ users: PermissionUser[];
+ groups: PermissionGroup[];
+ selectedPermission?: string;
+ showPublicProjectsWarning?: boolean;
+ onSelectPermission: (permission: string) => void;
+ onToggleUser: (user: PermissionUser, permission: string) => void;
+ onToggleGroup: (group: PermissionGroup, permission: string) => void;
+}
+export default class HoldersList extends React.PureComponent<Props> {
renderTableHeader() {
- const { selectedPermission } = this.props;
+ const { onSelectPermission, selectedPermission, showPublicProjectsWarning } = this.props;
const cells = this.props.permissions.map(p => (
- <th
+ <PermissionHeader
key={p.key}
- className="permission-column text-center"
- style={{
- backgroundColor: p.key === selectedPermission ? '#d9edf7' : 'transparent'
- }}>
- <div className="permission-column-inner">
- <Tooltip
- overlay={translateWithParameters('global_permissions.filter_by_x_permission', p.name)}>
- <a data-key={p.key} href="#" onClick={this.handlePermissionClick}>
- {p.name}
- </a>
- </Tooltip>
- <Tooltip overlay={this.renderTooltip(p)}>
- <i className="icon-help little-spacer-left" />
- </Tooltip>
- </div>
- </th>
+ onSelectPermission={onSelectPermission}
+ permission={p}
+ selectedPermission={selectedPermission}
+ showPublicProjectsWarning={showPublicProjectsWarning}
+ />
));
return (
<thead>
@@ -101,13 +67,15 @@ export default class HoldersList extends React.PureComponent {
}
render() {
+ const permissionsOrder = this.props.permissions.map(p => p.key);
+
const users = this.props.users.map(user => (
<UserHolder
key={'user-' + user.login}
user={user}
permissions={user.permissions}
selectedPermission={this.props.selectedPermission}
- permissionsOrder={this.props.permissions}
+ permissionsOrder={permissionsOrder}
onToggle={this.props.onToggleUser}
/>
));
@@ -118,7 +86,7 @@ export default class HoldersList extends React.PureComponent {
group={group}
permissions={group.permissions}
selectedPermission={this.props.selectedPermission}
- permissionsOrder={this.props.permissions}
+ permissionsOrder={permissionsOrder}
onToggle={this.props.onToggleGroup}
/>
));
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/PageError.js b/server/sonar-web/src/main/js/apps/permissions/shared/components/PageError.tsx
index e3d6266d94b..4f3878329c3 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/PageError.js
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/PageError.tsx
@@ -17,28 +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.
*/
-import React from 'react';
-import PropTypes from 'prop-types';
+import * as React from 'react';
import { connect } from 'react-redux';
import { getPermissionsAppError } from '../../../../store/rootReducer';
-class PageError extends React.PureComponent {
- static propTypes = {
- message: PropTypes.string
- };
-
- render() {
- const { message } = this.props;
-
- if (!message) {
- return null;
- }
+interface Props {
+ message: string;
+}
- return <div className="alert alert-danger">{message}</div>;
+function PageError({ message }: Props) {
+ if (!message) {
+ return null;
}
+
+ return <div className="alert alert-danger">{message}</div>;
}
-const mapStateToProps = state => ({
+const mapStateToProps = (state: any) => ({
message: getPermissionsAppError(state)
});
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionHeader.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionHeader.tsx
new file mode 100644
index 00000000000..96d9e40d1c2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionHeader.tsx
@@ -0,0 +1,87 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import Tooltip from '../../../../components/controls/Tooltip';
+import { translate, translateWithParameters } from '../../../../helpers/l10n';
+
+export interface Permission {
+ key: string;
+ name: string;
+ description: string;
+}
+
+interface Props {
+ onSelectPermission: (permission: string) => void;
+ permission: Permission;
+ selectedPermission?: string;
+ showPublicProjectsWarning?: boolean;
+}
+
+export default class PermissionHeader extends React.PureComponent<Props> {
+ static defaultProps = {
+ showPublicProjectsWarning: false
+ };
+
+ handlePermissionClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.props.onSelectPermission(this.props.permission.key);
+ };
+
+ renderTooltip = (permission: Permission) => {
+ if (this.props.showPublicProjectsWarning && ['user', 'codeviewer'].includes(permission.key)) {
+ return (
+ <div>
+ {permission.description}
+ <div className="alert alert-warning spacer-top">
+ {translate('projects_role.public_projects_warning')}
+ </div>
+ </div>
+ );
+ }
+ return permission.description;
+ };
+
+ render() {
+ const { permission, selectedPermission } = this.props;
+ return (
+ <th
+ className="permission-column text-center"
+ style={{
+ backgroundColor: permission.key === selectedPermission ? '#d9edf7' : 'transparent'
+ }}>
+ <div className="permission-column-inner">
+ <Tooltip
+ overlay={translateWithParameters(
+ 'global_permissions.filter_by_x_permission',
+ permission.name
+ )}>
+ <a href="#" onClick={this.handlePermissionClick}>
+ {permission.name}
+ </a>
+ </Tooltip>
+ <Tooltip overlay={this.renderTooltip(permission)}>
+ <i className="icon-help little-spacer-left" />
+ </Tooltip>
+ </div>
+ </th>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/SearchForm.js b/server/sonar-web/src/main/js/apps/permissions/shared/components/SearchForm.tsx
index b680b491fbd..903d3be562d 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/SearchForm.js
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/SearchForm.tsx
@@ -17,13 +17,19 @@
* 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 PropTypes from 'prop-types';
+import * as React from 'react';
import RadioToggle from '../../../../components/controls/RadioToggle';
import SearchBox from '../../../../components/controls/SearchBox';
-import { translate, translateWithParameters } from '../../../../helpers/l10n';
+import { translate } from '../../../../helpers/l10n';
-export default function SearchForm(props) {
+interface Props {
+ filter: string;
+ onFilter: (value: string) => void;
+ onSearch: (value: string) => void;
+ query: string;
+}
+
+export default function SearchForm(props: Props) {
const filterOptions = [
{ value: 'all', label: translate('all') },
{ value: 'users', label: translate('users.page') },
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.js b/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.tsx
index d0b97ae588b..068c98f67af 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.js
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.tsx
@@ -17,69 +17,73 @@
* 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 PropTypes from 'prop-types';
+import * as React from 'react';
import Avatar from '../../../../components/ui/Avatar';
+import Checkbox from '../../../../components/controls/Checkbox';
+import { PermissionUser } from '../../../../api/permissions';
import { translate } from '../../../../helpers/l10n';
-export default class UserHolder extends React.PureComponent {
- static propTypes = {
- user: PropTypes.object.isRequired,
- permissions: PropTypes.array.isRequired,
- selectedPermission: PropTypes.string,
- permissionsOrder: PropTypes.array.isRequired,
- onToggle: PropTypes.func.isRequired
- };
+interface Props {
+ user: PermissionUser;
+ permissions: string[];
+ selectedPermission?: string;
+ permissionsOrder: string[];
+ onToggle: (user: PermissionUser, permission: string) => void;
+}
- handleClick(permission, e) {
- e.preventDefault();
- e.target.blur();
- this.props.onToggle(this.props.user, permission);
- }
+export default class UserHolder extends React.PureComponent<Props> {
+ handleCheck = (_checked: boolean, permission?: string) =>
+ permission && this.props.onToggle(this.props.user, permission);
render() {
const { selectedPermission } = this.props;
- const permissionCells = this.props.permissionsOrder.map(p => (
+ const permissionCells = this.props.permissionsOrder.map(permission => (
<td
- key={p.key}
+ key={permission}
className="text-center text-middle"
- style={{ backgroundColor: p.key === selectedPermission ? '#d9edf7' : 'transparent' }}>
- <button className="button-clean" onClick={this.handleClick.bind(this, p.key)}>
- {this.props.permissions.includes(p.key) ? (
- <i className="icon-checkbox icon-checkbox-checked" />
- ) : (
- <i className="icon-checkbox" />
- )}
- </button>
+ style={{ backgroundColor: permission === selectedPermission ? '#d9edf7' : 'transparent' }}>
+ <Checkbox
+ checked={this.props.permissions.includes(permission)}
+ id={permission}
+ onCheck={this.handleCheck}
+ />
</td>
));
const { user } = this.props;
-
- const isCreator = user.login === '<creator>';
+ if (user.login === '<creator>') {
+ return (
+ <tr>
+ <td className="nowrap">
+ <div className="display-inline-block text-middle">
+ <div>
+ <strong>{user.name}</strong>
+ </div>
+ <div className="little-spacer-top" style={{ whiteSpace: 'normal' }}>
+ {translate('permission_templates.project_creators.explanation')}
+ </div>
+ </div>
+ </td>
+ {permissionCells}
+ </tr>
+ );
+ }
return (
<tr>
<td className="nowrap">
- {!isCreator && (
- <Avatar
- hash={user.avatar}
- name={user.name}
- size={36}
- className="text-middle big-spacer-right"
- />
- )}
+ <Avatar
+ hash={user.avatar}
+ name={user.name}
+ size={36}
+ className="text-middle big-spacer-right"
+ />
<div className="display-inline-block text-middle">
<div>
<strong>{user.name}</strong>
- {!isCreator && <span className="note spacer-left">{user.login}</span>}
+ <span className="note spacer-left">{user.login}</span>
</div>
- {!isCreator && <div className="little-spacer-top">{user.email}</div>}
- {isCreator && (
- <div className="little-spacer-top" style={{ whiteSpace: 'normal' }}>
- {translate('permission_templates.project_creators.explanation')}
- </div>
- )}
+ <div className="little-spacer-top">{user.email}</div>
</div>
</td>
{permissionCells}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
index 00014d3b253..e17f7697ac1 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
@@ -21,6 +21,7 @@
import * as React from 'react';
import { mount } from 'enzyme';
import App, { Props } from '../App';
+import { Visibility } from '../../../app/types';
jest.mock('react-dom');
@@ -42,7 +43,7 @@ jest.mock('../../../api/components', () => ({ getComponents: jest.fn() }));
const getComponents = require('../../../api/components').getComponents as jest.Mock<any>;
-const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
+const organization = { key: 'org', name: 'org', projectVisibility: Visibility.Public };
const defaultSearchParameters = {
organization: 'org',
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ChangeVisibilityForm-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ChangeVisibilityForm-test.tsx
index 61dce6dbfe4..4d3505afd22 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ChangeVisibilityForm-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ChangeVisibilityForm-test.tsx
@@ -21,12 +21,13 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import ChangeVisibilityForm, { Props } from '../ChangeVisibilityForm';
import { click } from '../../../helpers/testUtils';
+import { Visibility } from '../../../app/types';
const organization = {
canUpdateProjectsVisibilityToPrivate: true,
key: 'org',
name: 'org',
- projectVisibility: 'public'
+ projectVisibility: Visibility.Public
};
it('renders disabled', () => {
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx
index c514345b78a..2682a771619 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx
@@ -28,10 +28,11 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import CreateProjectForm from '../CreateProjectForm';
import { change, submit, click } from '../../../helpers/testUtils';
+import { Visibility } from '../../../app/types';
const createProject = require('../../../api/components').createProject as jest.Mock<any>;
-const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
+const organization = { key: 'org', name: 'org', projectVisibility: Visibility.Public };
it('creates project', async () => {
const wrapper = shallow(
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx
index b7cb62ab9a4..7d5b637949b 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx
@@ -23,7 +23,7 @@ import Header, { Props } from '../Header';
import { Visibility } from '../../../app/types';
import { click } from '../../../helpers/testUtils';
-const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
+const organization = { key: 'org', name: 'org', projectVisibility: Visibility.Public };
it('renders', () => {
expect(shallowRender()).toMatchSnapshot();
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx
index 824c6ab2a5e..71cf3dcbe80 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx
@@ -21,8 +21,9 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import Search, { Props } from '../Search';
import { click } from '../../../helpers/testUtils';
+import { Visibility } from '../../../app/types';
-const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
+const organization = { key: 'org', name: 'org', projectVisibility: Visibility.Public };
it('renders', () => {
expect(shallowRender()).toMatchSnapshot();