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;
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;
query?: string,
permission?: string,
organization?: string
-): Promise<any> {
+): Promise<PermissionUser[]> {
const data: RequestData = { ps: PAGE_SIZE };
if (query) {
data.q = query;
query?: string,
permission?: string,
organization?: string
-): Promise<any> {
+): Promise<PermissionGroup[]> {
const data: RequestData = { ps: PAGE_SIZE };
if (query) {
data.q = query;
}
export interface Organization {
- adminPages?: Array<{ key: string; name: string }>;
+ adminPages?: { key: string; name: string }[];
avatar?: string;
canAdmin?: boolean;
canDelete?: boolean;
isDefault?: boolean;
key: string;
name: string;
- pages?: Array<{ key: string; name: string }>;
- projectVisibility: string;
+ pages?: { key: string; name: string }[];
+ projectVisibility: Visibility;
url?: string;
}
+++ /dev/null
-/*
- * 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.
- */
-// @flow
-import 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'; */
-
-/*::
-type Props = {
- organization: Organization
-};
-*/
-
-function OrganizationPermissions(props /*: Props */) {
- return <GlobalPermissionsApp organization={props.organization} />;
-}
-
-const mapStateToProps = (state, ownProps) => ({
- organization: getOrganizationByKey(state, ownProps.params.organizationKey)
-});
-
-export default connect(mapStateToProps)(OrganizationPermissions);
--- /dev/null
+/*
+ * 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 { connect } from 'react-redux';
+import GlobalPermissionsApp from '../../permissions/global/components/App';
+import { getOrganizationByKey } from '../../../store/rootReducer';
+import { Organization } from '../../../app/types';
+
+interface Props {
+ organization: Organization;
+}
+
+function OrganizationPermissions({ organization }: Props) {
+ return <GlobalPermissionsApp organization={organization} />;
+}
+
+const mapStateToProps = (state: any, ownProps: any) => ({
+ organization: getOrganizationByKey(state, ownProps.params.organizationKey)
+});
+
+export default connect(mapStateToProps)(OrganizationPermissions);
+++ /dev/null
-/*
- * 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.
- */
-// @flow
-import React from 'react';
-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'; */
-
-const ADMIN_PATHS = [
- 'edit',
- 'groups',
- 'delete',
- 'permissions',
- 'permission_templates',
- 'projects_management'
-];
-
-export default class OrganizationNavigation extends React.PureComponent {
- /*:: props: {
- location: { pathname: string },
- organization: Organization
- };
-*/
-
- renderAdministration(adminActive /*: boolean */) {
- const { organization } = this.props;
-
- return (
- <li className="dropdown">
- <a
- className={classNames('dropdown-toggle', { active: adminActive })}
- data-toggle="dropdown"
- id="organization-navigation-admin"
- href="#">
- {translate('layout.settings')} <i className="icon-dropdown" />
- </a>
- <ul className="dropdown-menu">
- {this.renderAdminExtensions()}
- <li>
- <Link to={`/organizations/${organization.key}/groups`} activeClassName="active">
- {translate('user_groups.page')}
- </Link>
- </li>
- <li>
- <Link to={`/organizations/${organization.key}/permissions`} activeClassName="active">
- {translate('permissions.page')}
- </Link>
- </li>
- <li>
- <Link
- to={`/organizations/${organization.key}/permission_templates`}
- activeClassName="active">
- {translate('permission_templates')}
- </Link>
- </li>
- <li>
- <Link
- to={`/organizations/${organization.key}/projects_management`}
- activeClassName="active">
- {translate('projects_management')}
- </Link>
- </li>
- <li>
- <Link to={`/organizations/${organization.key}/edit`} activeClassName="active">
- {translate('edit')}
- </Link>
- </li>
- {organization.canDelete && (
- <li>
- <Link to={`/organizations/${organization.key}/delete`} activeClassName="active">
- {translate('delete')}
- </Link>
- </li>
- )}
- </ul>
- </li>
- );
- }
-
- renderAdminExtensions() {
- const extensions = this.props.organization.adminPages || [];
- return extensions.map(this.renderExtension);
- }
-
- renderExtension = (extension /*: { key: string, name: string } */) => {
- const { organization } = this.props;
- const pathname = `/organizations/${organization.key}/extension/${extension.key}`;
- return (
- <li key={extension.key}>
- <Link to={pathname} activeClassName="active">
- {extension.name}
- </Link>
- </li>
- );
- };
-
- renderExtensions(moreActive /*: boolean */) {
- const extensions = this.props.organization.pages || [];
- if (extensions.length > 0) {
- return (
- <li className={moreActive ? 'active' : ''}>
- <a
- className="dropdown-toggle"
- id="organization-navigation-more"
- data-toggle="dropdown"
- href="#">
- {translate('more')} <i className="icon-dropdown" />
- </a>
- <ul className="dropdown-menu">{extensions.map(this.renderExtension)}</ul>
- </li>
- );
- } else {
- return null;
- }
- }
-
- render() {
- const { organization, location } = this.props;
-
- const isHomeActive =
- location.pathname === `organizations/${organization.key}/projects` ||
- location.pathname === `organizations/${organization.key}/projects/favorite`;
-
- const adminPathsWithExtensions = (organization.adminPages || [])
- .map(e => `extension/${e.key}`)
- .concat(ADMIN_PATHS);
-
- const adminActive = adminPathsWithExtensions.some(path =>
- location.pathname.endsWith(`organizations/${organization.key}/${path}`)
- );
- const moreActive = !adminActive && location.pathname.includes('/extension/');
-
- return (
- <ContextNavBar id="context-navigation" height={theme.contextNavHeightRaw}>
- <div className="navbar-context-header">
- <h1 className="display-inline-block">
- <OrganizationAvatar organization={organization} />
- <Link
- to={`/organizations/${organization.key}`}
- className="link-base-color link-no-underline spacer-left">
- {organization.name}
- </Link>
- </h1>
- {organization.description != null && (
- <div className="navbar-context-description">
- <p className="text-limited text-top" title={organization.description}>
- {organization.description}
- </p>
- </div>
- )}
- </div>
-
- <div className="navbar-context-meta">
- <div className="text-muted">
- <strong>{translate('organization.key')}:</strong> {organization.key}
- </div>
- {organization.url != null && (
- <div>
- <p className="text-limited text-top">
- <a
- className="link-underline"
- href={organization.url}
- title={organization.url}
- rel="nofollow">
- {organization.url}
- </a>
- </p>
- </div>
- )}
- </div>
-
- <NavBarTabs className="navbar-context-tabs">
- <li>
- <Link
- to={`/organizations/${organization.key}/projects`}
- className={isHomeActive ? 'active' : ''}>
- {translate('projects.page')}
- </Link>
- </li>
- <li>
- <Link
- to={{
- pathname: `/organizations/${organization.key}/issues`,
- query: { resolved: 'false' }
- }}
- activeClassName="active">
- {translate('issues.page')}
- </Link>
- </li>
- <li>
- <Link
- to={`/organizations/${organization.key}/quality_profiles`}
- activeClassName="active">
- {translate('quality_profiles.page')}
- </Link>
- </li>
- <li>
- <Link to={`/organizations/${organization.key}/rules`} activeClassName="active">
- {translate('coding_rules.page')}
- </Link>
- </li>
- <li>
- <Link to={getQualityGatesUrl(organization.key)} activeClassName="active">
- {translate('quality_gates.page')}
- </Link>
- </li>
- <li>
- <Link to={`/organizations/${organization.key}/members`} activeClassName="active">
- {translate('organization.members.page')}
- </Link>
- </li>
- {this.renderExtensions(moreActive)}
- {organization.canAdmin && this.renderAdministration(adminActive)}
- </NavBarTabs>
- </ContextNavBar>
- );
- }
-}
--- /dev/null
+/*
+ * 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 * as classNames from 'classnames';
+import { Link } from 'react-router';
+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 { Extension, Organization } from '../../../app/types';
+
+const ADMIN_PATHS = [
+ 'edit',
+ 'groups',
+ 'delete',
+ 'permissions',
+ 'permission_templates',
+ 'projects_management'
+];
+
+interface Props {
+ location: { pathname: string };
+ organization: Organization;
+}
+
+export default class OrganizationNavigation extends React.PureComponent<Props> {
+ renderAdministration(adminActive: boolean) {
+ const { organization } = this.props;
+
+ return (
+ <li className="dropdown">
+ <a
+ className={classNames('dropdown-toggle', { active: adminActive })}
+ data-toggle="dropdown"
+ id="organization-navigation-admin"
+ href="#">
+ {translate('layout.settings')} <i className="icon-dropdown" />
+ </a>
+ <ul className="dropdown-menu">
+ {this.renderAdminExtensions()}
+ <li>
+ <Link to={`/organizations/${organization.key}/groups`} activeClassName="active">
+ {translate('user_groups.page')}
+ </Link>
+ </li>
+ <li>
+ <Link to={`/organizations/${organization.key}/permissions`} activeClassName="active">
+ {translate('permissions.page')}
+ </Link>
+ </li>
+ <li>
+ <Link
+ to={`/organizations/${organization.key}/permission_templates`}
+ activeClassName="active">
+ {translate('permission_templates')}
+ </Link>
+ </li>
+ <li>
+ <Link
+ to={`/organizations/${organization.key}/projects_management`}
+ activeClassName="active">
+ {translate('projects_management')}
+ </Link>
+ </li>
+ <li>
+ <Link to={`/organizations/${organization.key}/edit`} activeClassName="active">
+ {translate('edit')}
+ </Link>
+ </li>
+ {organization.canDelete && (
+ <li>
+ <Link to={`/organizations/${organization.key}/delete`} activeClassName="active">
+ {translate('delete')}
+ </Link>
+ </li>
+ )}
+ </ul>
+ </li>
+ );
+ }
+
+ renderAdminExtensions() {
+ const extensions = this.props.organization.adminPages || [];
+ return extensions.map(this.renderExtension);
+ }
+
+ renderExtension = (extension: Extension) => {
+ const { organization } = this.props;
+ const pathname = `/organizations/${organization.key}/extension/${extension.key}`;
+ return (
+ <li key={extension.key}>
+ <Link to={pathname} activeClassName="active">
+ {extension.name}
+ </Link>
+ </li>
+ );
+ };
+
+ renderExtensions(moreActive: boolean) {
+ const extensions = this.props.organization.pages || [];
+ if (extensions.length > 0) {
+ return (
+ <li className={moreActive ? 'active' : ''}>
+ <a
+ className="dropdown-toggle"
+ id="organization-navigation-more"
+ data-toggle="dropdown"
+ href="#">
+ {translate('more')} <i className="icon-dropdown" />
+ </a>
+ <ul className="dropdown-menu">{extensions.map(this.renderExtension)}</ul>
+ </li>
+ );
+ } else {
+ return null;
+ }
+ }
+
+ render() {
+ const { organization, location } = this.props;
+
+ const isHomeActive =
+ location.pathname === `organizations/${organization.key}/projects` ||
+ location.pathname === `organizations/${organization.key}/projects/favorite`;
+
+ const adminPathsWithExtensions = (organization.adminPages || [])
+ .map(e => `extension/${e.key}`)
+ .concat(ADMIN_PATHS);
+
+ const adminActive = adminPathsWithExtensions.some(path =>
+ location.pathname.endsWith(`organizations/${organization.key}/${path}`)
+ );
+ const moreActive = !adminActive && location.pathname.includes('/extension/');
+
+ return (
+ <ContextNavBar id="context-navigation" height={theme.contextNavHeightRaw}>
+ <div className="navbar-context-header">
+ <h1 className="display-inline-block">
+ <OrganizationAvatar organization={organization} />
+ <Link
+ to={`/organizations/${organization.key}`}
+ className="link-base-color link-no-underline spacer-left">
+ {organization.name}
+ </Link>
+ </h1>
+ {organization.description != null && (
+ <div className="navbar-context-description">
+ <p className="text-limited text-top" title={organization.description}>
+ {organization.description}
+ </p>
+ </div>
+ )}
+ </div>
+
+ <div className="navbar-context-meta">
+ <div className="text-muted">
+ <strong>{translate('organization.key')}:</strong> {organization.key}
+ </div>
+ {organization.url != null && (
+ <div>
+ <p className="text-limited text-top">
+ <a
+ className="link-underline"
+ href={organization.url}
+ title={organization.url}
+ rel="nofollow">
+ {organization.url}
+ </a>
+ </p>
+ </div>
+ )}
+ </div>
+
+ <NavBarTabs className="navbar-context-tabs">
+ <li>
+ <Link
+ to={`/organizations/${organization.key}/projects`}
+ className={isHomeActive ? 'active' : ''}>
+ {translate('projects.page')}
+ </Link>
+ </li>
+ <li>
+ <Link
+ to={{
+ pathname: `/organizations/${organization.key}/issues`,
+ query: { resolved: 'false' }
+ }}
+ activeClassName="active">
+ {translate('issues.page')}
+ </Link>
+ </li>
+ <li>
+ <Link
+ to={`/organizations/${organization.key}/quality_profiles`}
+ activeClassName="active">
+ {translate('quality_profiles.page')}
+ </Link>
+ </li>
+ <li>
+ <Link to={`/organizations/${organization.key}/rules`} activeClassName="active">
+ {translate('coding_rules.page')}
+ </Link>
+ </li>
+ <li>
+ <Link to={getQualityGatesUrl(organization.key)} activeClassName="active">
+ {translate('quality_gates.page')}
+ </Link>
+ </li>
+ <li>
+ <Link to={`/organizations/${organization.key}/members`} activeClassName="active">
+ {translate('organization.members.page')}
+ </Link>
+ </li>
+ {this.renderExtensions(moreActive)}
+ {organization.canAdmin && this.renderAdministration(adminActive)}
+ </NavBarTabs>
+ </ContextNavBar>
+ );
+ }
+}
+++ /dev/null
-/*
- * 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 React from 'react';
-import { shallow } from 'enzyme';
-import OrganizationNavigation from '../OrganizationNavigation';
-
-jest.mock('../../../issues/utils', () => ({
- isMySet: () => false
-}));
-
-it('regular user', () => {
- const organization = { key: 'foo', name: 'Foo', canAdmin: false, canDelete: false };
- expect(
- shallow(
- <OrganizationNavigation
- location={{ pathname: '/organizations/foo' }}
- organization={organization}
- />
- )
- ).toMatchSnapshot();
-});
-
-it('admin', () => {
- const organization = { key: 'foo', name: 'Foo', canAdmin: true, canDelete: true };
- expect(
- shallow(
- <OrganizationNavigation
- location={{ pathname: '/organizations/foo' }}
- organization={organization}
- />
- )
- ).toMatchSnapshot();
-});
-
-it('undeletable org', () => {
- const organization = { key: 'foo', name: 'Foo', canAdmin: true, canDelete: false };
- expect(
- shallow(
- <OrganizationNavigation
- location={{ pathname: '/organizations/foo' }}
- organization={organization}
- />
- )
- ).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * 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 { 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', () => {
+ expect(getWrapper()).toMatchSnapshot();
+});
+
+it('admin', () => {
+ expect(
+ getWrapper({ organization: { ...organization, canAdmin: true, canDelete: true } })
+ ).toMatchSnapshot();
+});
+
+it('undeletable org', () => {
+ expect(
+ getWrapper({ organization: { ...organization, canAdmin: true, canDelete: false } })
+ ).toMatchSnapshot();
+});
+
+function getWrapper(props = {}) {
+ return shallow(
+ <OrganizationNavigation
+ location={{ pathname: '/organizations/foo' }}
+ organization={organization}
+ {...props}
+ />
+ );
+}
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`admin 1`] = `
-<ContextNavBar
- height={72}
- id="context-navigation"
->
- <div
- className="navbar-context-header"
- >
- <h1
- className="display-inline-block"
- >
- <OrganizationAvatar
- organization={
- Object {
- "canAdmin": true,
- "canDelete": true,
- "key": "foo",
- "name": "Foo",
- }
- }
- />
- <Link
- className="link-base-color link-no-underline spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo"
- >
- Foo
- </Link>
- </h1>
- </div>
- <div
- className="navbar-context-meta"
- >
- <div
- className="text-muted"
- >
- <strong>
- organization.key
- :
- </strong>
-
- foo
- </div>
- </div>
- <NavBarTabs
- className="navbar-context-tabs"
- >
- <li>
- <Link
- className=""
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/projects"
- >
- projects.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/issues",
- "query": Object {
- "resolved": "false",
- },
- }
- }
- >
- issues.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/quality_profiles"
- >
- quality_profiles.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/rules"
- >
- coding_rules.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/quality_gates",
- }
- }
- >
- quality_gates.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/members"
- >
- organization.members.page
- </Link>
- </li>
- <li
- className="dropdown"
- >
- <a
- className="dropdown-toggle"
- data-toggle="dropdown"
- href="#"
- id="organization-navigation-admin"
- >
- layout.settings
- Â
- <i
- className="icon-dropdown"
- />
- </a>
- <ul
- className="dropdown-menu"
- >
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/groups"
- >
- user_groups.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/permissions"
- >
- permissions.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/permission_templates"
- >
- permission_templates
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/projects_management"
- >
- projects_management
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/edit"
- >
- edit
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/delete"
- >
- delete
- </Link>
- </li>
- </ul>
- </li>
- </NavBarTabs>
-</ContextNavBar>
-`;
-
-exports[`regular user 1`] = `
-<ContextNavBar
- height={72}
- id="context-navigation"
->
- <div
- className="navbar-context-header"
- >
- <h1
- className="display-inline-block"
- >
- <OrganizationAvatar
- organization={
- Object {
- "canAdmin": false,
- "canDelete": false,
- "key": "foo",
- "name": "Foo",
- }
- }
- />
- <Link
- className="link-base-color link-no-underline spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo"
- >
- Foo
- </Link>
- </h1>
- </div>
- <div
- className="navbar-context-meta"
- >
- <div
- className="text-muted"
- >
- <strong>
- organization.key
- :
- </strong>
-
- foo
- </div>
- </div>
- <NavBarTabs
- className="navbar-context-tabs"
- >
- <li>
- <Link
- className=""
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/projects"
- >
- projects.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/issues",
- "query": Object {
- "resolved": "false",
- },
- }
- }
- >
- issues.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/quality_profiles"
- >
- quality_profiles.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/rules"
- >
- coding_rules.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/quality_gates",
- }
- }
- >
- quality_gates.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/members"
- >
- organization.members.page
- </Link>
- </li>
- </NavBarTabs>
-</ContextNavBar>
-`;
-
-exports[`undeletable org 1`] = `
-<ContextNavBar
- height={72}
- id="context-navigation"
->
- <div
- className="navbar-context-header"
- >
- <h1
- className="display-inline-block"
- >
- <OrganizationAvatar
- organization={
- Object {
- "canAdmin": true,
- "canDelete": false,
- "key": "foo",
- "name": "Foo",
- }
- }
- />
- <Link
- className="link-base-color link-no-underline spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo"
- >
- Foo
- </Link>
- </h1>
- </div>
- <div
- className="navbar-context-meta"
- >
- <div
- className="text-muted"
- >
- <strong>
- organization.key
- :
- </strong>
-
- foo
- </div>
- </div>
- <NavBarTabs
- className="navbar-context-tabs"
- >
- <li>
- <Link
- className=""
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/projects"
- >
- projects.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/issues",
- "query": Object {
- "resolved": "false",
- },
- }
- }
- >
- issues.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/quality_profiles"
- >
- quality_profiles.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/rules"
- >
- coding_rules.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/quality_gates",
- }
- }
- >
- quality_gates.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/members"
- >
- organization.members.page
- </Link>
- </li>
- <li
- className="dropdown"
- >
- <a
- className="dropdown-toggle"
- data-toggle="dropdown"
- href="#"
- id="organization-navigation-admin"
- >
- layout.settings
- Â
- <i
- className="icon-dropdown"
- />
- </a>
- <ul
- className="dropdown-menu"
- >
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/groups"
- >
- user_groups.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/permissions"
- >
- permissions.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/permission_templates"
- >
- permission_templates
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/projects_management"
- >
- projects_management
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/edit"
- >
- edit
- </Link>
- </li>
- </ul>
- </li>
- </NavBarTabs>
-</ContextNavBar>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`admin 1`] = `
+<ContextNavBar
+ height={72}
+ id="context-navigation"
+>
+ <div
+ className="navbar-context-header"
+ >
+ <h1
+ className="display-inline-block"
+ >
+ <OrganizationAvatar
+ organization={
+ Object {
+ "canAdmin": true,
+ "canDelete": true,
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
+ />
+ <Link
+ className="link-base-color link-no-underline spacer-left"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo"
+ >
+ Foo
+ </Link>
+ </h1>
+ </div>
+ <div
+ className="navbar-context-meta"
+ >
+ <div
+ className="text-muted"
+ >
+ <strong>
+ organization.key
+ :
+ </strong>
+
+ foo
+ </div>
+ </div>
+ <NavBarTabs
+ className="navbar-context-tabs"
+ >
+ <li>
+ <Link
+ className=""
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/projects"
+ >
+ projects.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/issues",
+ "query": Object {
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ issues.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/quality_profiles"
+ >
+ quality_profiles.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/rules"
+ >
+ coding_rules.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/quality_gates",
+ }
+ }
+ >
+ quality_gates.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/members"
+ >
+ organization.members.page
+ </Link>
+ </li>
+ <li
+ className="dropdown"
+ >
+ <a
+ className="dropdown-toggle"
+ data-toggle="dropdown"
+ href="#"
+ id="organization-navigation-admin"
+ >
+ layout.settings
+ Â
+ <i
+ className="icon-dropdown"
+ />
+ </a>
+ <ul
+ className="dropdown-menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/groups"
+ >
+ user_groups.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/permissions"
+ >
+ permissions.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/permission_templates"
+ >
+ permission_templates
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/projects_management"
+ >
+ projects_management
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/edit"
+ >
+ edit
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/delete"
+ >
+ delete
+ </Link>
+ </li>
+ </ul>
+ </li>
+ </NavBarTabs>
+</ContextNavBar>
+`;
+
+exports[`regular user 1`] = `
+<ContextNavBar
+ height={72}
+ id="context-navigation"
+>
+ <div
+ className="navbar-context-header"
+ >
+ <h1
+ className="display-inline-block"
+ >
+ <OrganizationAvatar
+ organization={
+ Object {
+ "canAdmin": false,
+ "canDelete": false,
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
+ />
+ <Link
+ className="link-base-color link-no-underline spacer-left"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo"
+ >
+ Foo
+ </Link>
+ </h1>
+ </div>
+ <div
+ className="navbar-context-meta"
+ >
+ <div
+ className="text-muted"
+ >
+ <strong>
+ organization.key
+ :
+ </strong>
+
+ foo
+ </div>
+ </div>
+ <NavBarTabs
+ className="navbar-context-tabs"
+ >
+ <li>
+ <Link
+ className=""
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/projects"
+ >
+ projects.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/issues",
+ "query": Object {
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ issues.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/quality_profiles"
+ >
+ quality_profiles.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/rules"
+ >
+ coding_rules.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/quality_gates",
+ }
+ }
+ >
+ quality_gates.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/members"
+ >
+ organization.members.page
+ </Link>
+ </li>
+ </NavBarTabs>
+</ContextNavBar>
+`;
+
+exports[`undeletable org 1`] = `
+<ContextNavBar
+ height={72}
+ id="context-navigation"
+>
+ <div
+ className="navbar-context-header"
+ >
+ <h1
+ className="display-inline-block"
+ >
+ <OrganizationAvatar
+ organization={
+ Object {
+ "canAdmin": true,
+ "canDelete": false,
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
+ />
+ <Link
+ className="link-base-color link-no-underline spacer-left"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo"
+ >
+ Foo
+ </Link>
+ </h1>
+ </div>
+ <div
+ className="navbar-context-meta"
+ >
+ <div
+ className="text-muted"
+ >
+ <strong>
+ organization.key
+ :
+ </strong>
+
+ foo
+ </div>
+ </div>
+ <NavBarTabs
+ className="navbar-context-tabs"
+ >
+ <li>
+ <Link
+ className=""
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/projects"
+ >
+ projects.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/issues",
+ "query": Object {
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ issues.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/quality_profiles"
+ >
+ quality_profiles.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/rules"
+ >
+ coding_rules.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/quality_gates",
+ }
+ }
+ >
+ quality_gates.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/members"
+ >
+ organization.members.page
+ </Link>
+ </li>
+ <li
+ className="dropdown"
+ >
+ <a
+ className="dropdown-toggle"
+ data-toggle="dropdown"
+ href="#"
+ id="organization-navigation-admin"
+ >
+ layout.settings
+ Â
+ <i
+ className="icon-dropdown"
+ />
+ </a>
+ <ul
+ className="dropdown-menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/groups"
+ >
+ user_groups.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/permissions"
+ >
+ permissions.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/permission_templates"
+ >
+ permission_templates
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/projects_management"
+ >
+ projects_management
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/edit"
+ >
+ edit
+ </Link>
+ </li>
+ </ul>
+ </li>
+ </NavBarTabs>
+</ContextNavBar>
+`;
+++ /dev/null
-/*
- * 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.
- */
-// @flow
-import React from 'react';
-import { connect } from 'react-redux';
-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';
-
-const PERMISSIONS_ORDER = ['admin', 'profileadmin', 'gateadmin', 'scan', 'provisioning'];
-
-const PERMISSIONS_FOR_CUSTOM_ORG = ['admin', 'profileadmin', 'scan', 'provisioning'];
-
-class AllHoldersList extends React.PureComponent {
- componentDidMount() {
- this.props.loadHolders();
- }
-
- handleToggleUser(user, permission) {
- const hasPermission = user.permissions.includes(permission);
-
- if (hasPermission) {
- this.props.revokePermissionFromUser(user.login, permission);
- } else {
- this.props.grantPermissionToUser(user.login, permission);
- }
- }
-
- handleToggleGroup(group, permission) {
- const hasPermission = group.permissions.includes(permission);
-
- if (hasPermission) {
- this.props.revokePermissionFromGroup(group.name, permission);
- } else {
- this.props.grantPermissionToGroup(group.name, permission);
- }
- }
-
- render() {
- const order =
- this.props.organization && !this.props.organization.isDefault
- ? PERMISSIONS_FOR_CUSTOM_ORG
- : PERMISSIONS_ORDER;
-
- const l10nPrefix = this.props.organization ? 'organizations_permissions' : 'global_permissions';
-
- const permissions = order.map(p => ({
- key: p,
- name: translate(l10nPrefix, p),
- description: translate(l10nPrefix, p, 'desc')
- }));
-
- return (
- <HoldersList
- permissions={permissions}
- selectedPermission={this.props.selectedPermission}
- users={this.props.users}
- groups={this.props.groups}
- onSelectPermission={this.props.onSelectPermission}
- onToggleUser={this.handleToggleUser.bind(this)}
- onToggleGroup={this.handleToggleGroup.bind(this)}>
- <SearchForm
- query={this.props.query}
- filter={this.props.filter}
- onSearch={this.props.onSearch}
- onFilter={this.props.onFilter}
- />
- </HoldersList>
- );
- }
-}
-
-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);
--- /dev/null
+/*
+ * 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 SearchForm from '../../shared/components/SearchForm';
+import HoldersList from '../../shared/components/HoldersList';
+import { translate } from '../../../../helpers/l10n';
+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'];
+
+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: PermissionUser, permission: string) => {
+ const hasPermission = user.permissions.includes(permission);
+
+ if (hasPermission) {
+ this.props.revokePermissionFromUser(user.login, permission);
+ } else {
+ this.props.grantPermissionToUser(user.login, permission);
+ }
+ };
+
+ handleToggleGroup = (group: PermissionGroup, permission: string) => {
+ const hasPermission = group.permissions.includes(permission);
+
+ if (hasPermission) {
+ this.props.revokePermissionFromGroup(group.name, permission);
+ } else {
+ this.props.grantPermissionToGroup(group.name, permission);
+ }
+ };
+
+ render() {
+ const order =
+ this.props.organization && !this.props.organization.isDefault
+ ? PERMISSIONS_FOR_CUSTOM_ORG
+ : PERMISSIONS_ORDER;
+
+ const l10nPrefix = this.props.organization ? 'organizations_permissions' : 'global_permissions';
+
+ const permissions = order.map(p => ({
+ key: p,
+ name: translate(l10nPrefix, p),
+ description: translate(l10nPrefix, p, 'desc')
+ }));
+
+ return (
+ <HoldersList
+ permissions={permissions}
+ selectedPermission={this.props.selectedPermission}
+ users={this.props.users}
+ groups={this.props.groups}
+ onSelectPermission={this.props.onSelectPermission}
+ onToggleUser={this.handleToggleUser}
+ onToggleGroup={this.handleToggleGroup}>
+ <SearchForm
+ query={this.props.query}
+ filter={this.props.filter}
+ onSearch={this.props.onSearch}
+ onFilter={this.props.onFilter}
+ />
+ </HoldersList>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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);
+++ /dev/null
-/*
- * 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.
- */
-// @flow
-import React from 'react';
-import Helmet from 'react-helmet';
-import PageHeader from './PageHeader';
-import AllHoldersList from './AllHoldersList';
-import PageError from '../../shared/components/PageError';
-import { translate } from '../../../../helpers/l10n';
-import '../../styles.css';
-
-export default function App(props /*: { organization?: {} } */) {
- return (
- <div className="page page-limited">
- <Helmet title={translate('global_permissions.permission')} />
- <PageHeader organization={props.organization} />
- <PageError />
- <AllHoldersList organization={props.organization} />
- </div>
- );
-}
--- /dev/null
+/*
+ * 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 Helmet from 'react-helmet';
+import PageHeader from './PageHeader';
+import AllHoldersListContainer from './AllHoldersListContainer';
+import PageError from '../../shared/components/PageError';
+import { translate } from '../../../../helpers/l10n';
+import { Organization } from '../../../../app/types';
+import '../../styles.css';
+
+interface Props {
+ organization?: Organization;
+}
+
+export default function App({ organization }: Props) {
+ return (
+ <div className="page page-limited">
+ <Helmet title={translate('global_permissions.permission')} />
+ <PageHeader organization={organization} />
+ <PageError />
+ <AllHoldersListContainer organization={organization} />
+ </div>
+ );
+}
+++ /dev/null
-/*
- * 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 React from 'react';
-import PropTypes from 'prop-types';
-import GroupIcon from '../../../../components/icons-components/GroupIcon';
-
-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
- };
-
- handleClick(permission, e) {
- e.preventDefault();
- e.target.blur();
- this.props.onToggle(this.props.group, permission);
- }
-
- render() {
- const { selectedPermission } = this.props;
- const permissionCells = this.props.permissionsOrder.map(p => (
- <td
- key={p.key}
- 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>
- </td>
- ));
-
- const { group } = this.props;
-
- return (
- <tr>
- <td className="nowrap">
- <div className="display-inline-block text-middle big-spacer-right">
- <GroupIcon />
- </div>
- <div className="display-inline-block text-middle">
- <div>
- <strong>{group.name}</strong>
- </div>
- <div className="little-spacer-top" style={{ whiteSpace: 'normal' }}>
- {group.description}
- </div>
- </div>
- </td>
- {permissionCells}
- </tr>
- );
- }
-}
--- /dev/null
+/*
+ * 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 Checkbox from '../../../../components/controls/Checkbox';
+import GroupIcon from '../../../../components/icons-components/GroupIcon';
+import { PermissionGroup } from '../../../../api/permissions';
+
+interface Props {
+ group: PermissionGroup;
+ permissions: string[];
+ selectedPermission?: string;
+ permissionsOrder: string[];
+ onToggle: (group: PermissionGroup, permission: string) => void;
+}
+
+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(permission => (
+ <td
+ key={permission}
+ className="text-center text-middle"
+ style={{ backgroundColor: permission === selectedPermission ? '#d9edf7' : 'transparent' }}>
+ <Checkbox
+ checked={this.props.permissions.includes(permission)}
+ id={permission}
+ onCheck={this.handleCheck}
+ />
+ </td>
+ ));
+
+ const { group } = this.props;
+
+ return (
+ <tr>
+ <td className="nowrap">
+ <div className="display-inline-block text-middle big-spacer-right">
+ <GroupIcon />
+ </div>
+ <div className="display-inline-block text-middle">
+ <div>
+ <strong>{group.name}</strong>
+ </div>
+ <div className="little-spacer-top" style={{ whiteSpace: 'normal' }}>
+ {group.description}
+ </div>
+ </div>
+ </td>
+ {permissionCells}
+ </tr>
+ );
+ }
+}
+++ /dev/null
-/*
- * 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 React from 'react';
-import PropTypes from 'prop-types';
-import UserHolder from './UserHolder';
-import GroupHolder from './GroupHolder';
-import Tooltip from '../../../../components/controls/Tooltip';
-import { translate, translateWithParameters } 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
- );
-
- renderTableHeader() {
- const { selectedPermission } = this.props;
- const cells = this.props.permissions.map(p => (
- <th
- 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>
- ));
- return (
- <thead>
- <tr>
- <td className="nowrap bordered-bottom">{this.props.children}</td>
- {cells}
- </tr>
- </thead>
- );
- }
-
- renderEmpty() {
- const columns = this.props.permissions.length + 1;
- return (
- <tr>
- <td colSpan={columns}>{translate('no_results_search')}</td>
- </tr>
- );
- }
-
- render() {
- 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}
- onToggle={this.props.onToggleUser}
- />
- ));
-
- const groups = this.props.groups.map(group => (
- <GroupHolder
- key={'group-' + group.id}
- group={group}
- permissions={group.permissions}
- selectedPermission={this.props.selectedPermission}
- permissionsOrder={this.props.permissions}
- onToggle={this.props.onToggleGroup}
- />
- ));
-
- return (
- <table className="data zebra permissions-table">
- {this.renderTableHeader()}
- <tbody>
- {users.length === 0 && groups.length === 0 && this.renderEmpty()}
- {users}
- {groups}
- </tbody>
- </table>
- );
- }
-}
--- /dev/null
+/*
+ * 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 UserHolder from './UserHolder';
+import GroupHolder from './GroupHolder';
+import PermissionHeader, { Permission } from './PermissionHeader';
+import { PermissionUser, PermissionGroup } from '../../../../api/permissions';
+import { translate } from '../../../../helpers/l10n';
+
+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 { onSelectPermission, selectedPermission, showPublicProjectsWarning } = this.props;
+ const cells = this.props.permissions.map(p => (
+ <PermissionHeader
+ key={p.key}
+ onSelectPermission={onSelectPermission}
+ permission={p}
+ selectedPermission={selectedPermission}
+ showPublicProjectsWarning={showPublicProjectsWarning}
+ />
+ ));
+ return (
+ <thead>
+ <tr>
+ <td className="nowrap bordered-bottom">{this.props.children}</td>
+ {cells}
+ </tr>
+ </thead>
+ );
+ }
+
+ renderEmpty() {
+ const columns = this.props.permissions.length + 1;
+ return (
+ <tr>
+ <td colSpan={columns}>{translate('no_results_search')}</td>
+ </tr>
+ );
+ }
+
+ 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={permissionsOrder}
+ onToggle={this.props.onToggleUser}
+ />
+ ));
+
+ const groups = this.props.groups.map(group => (
+ <GroupHolder
+ key={'group-' + group.id}
+ group={group}
+ permissions={group.permissions}
+ selectedPermission={this.props.selectedPermission}
+ permissionsOrder={permissionsOrder}
+ onToggle={this.props.onToggleGroup}
+ />
+ ));
+
+ return (
+ <table className="data zebra permissions-table">
+ {this.renderTableHeader()}
+ <tbody>
+ {users.length === 0 && groups.length === 0 && this.renderEmpty()}
+ {users}
+ {groups}
+ </tbody>
+ </table>
+ );
+ }
+}
+++ /dev/null
-/*
- * 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 React from 'react';
-import PropTypes from 'prop-types';
-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;
- }
-
- return <div className="alert alert-danger">{message}</div>;
- }
-}
-
-const mapStateToProps = state => ({
- message: getPermissionsAppError(state)
-});
-
-export default connect(mapStateToProps)(PageError);
--- /dev/null
+/*
+ * 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 { connect } from 'react-redux';
+import { getPermissionsAppError } from '../../../../store/rootReducer';
+
+interface Props {
+ message: string;
+}
+
+function PageError({ message }: Props) {
+ if (!message) {
+ return null;
+ }
+
+ return <div className="alert alert-danger">{message}</div>;
+}
+
+const mapStateToProps = (state: any) => ({
+ message: getPermissionsAppError(state)
+});
+
+export default connect(mapStateToProps)(PageError);
--- /dev/null
+/*
+ * 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>
+ );
+ }
+}
+++ /dev/null
-/*
- * 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 React from 'react';
-import PropTypes from 'prop-types';
-import RadioToggle from '../../../../components/controls/RadioToggle';
-import SearchBox from '../../../../components/controls/SearchBox';
-import { translate, translateWithParameters } from '../../../../helpers/l10n';
-
-export default function SearchForm(props) {
- const filterOptions = [
- { value: 'all', label: translate('all') },
- { value: 'users', label: translate('users.page') },
- { value: 'groups', label: translate('user_groups.page') }
- ];
-
- return (
- <div className="display-flex-row">
- <RadioToggle
- name="users-or-groups"
- onCheck={props.onFilter}
- options={filterOptions}
- value={props.filter}
- />
-
- <div className="flex-1 spacer-left">
- <SearchBox
- minLength={3}
- onChange={props.onSearch}
- placeholder={translate('search.search_for_users_or_groups')}
- value={props.query}
- />
- </div>
- </div>
- );
-}
--- /dev/null
+/*
+ * 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 RadioToggle from '../../../../components/controls/RadioToggle';
+import SearchBox from '../../../../components/controls/SearchBox';
+import { translate } from '../../../../helpers/l10n';
+
+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') },
+ { value: 'groups', label: translate('user_groups.page') }
+ ];
+
+ return (
+ <div className="display-flex-row">
+ <RadioToggle
+ name="users-or-groups"
+ onCheck={props.onFilter}
+ options={filterOptions}
+ value={props.filter}
+ />
+
+ <div className="flex-1 spacer-left">
+ <SearchBox
+ minLength={3}
+ onChange={props.onSearch}
+ placeholder={translate('search.search_for_users_or_groups')}
+ value={props.query}
+ />
+ </div>
+ </div>
+ );
+}
+++ /dev/null
-/*
- * 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 React from 'react';
-import PropTypes from 'prop-types';
-import Avatar from '../../../../components/ui/Avatar';
-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
- };
-
- handleClick(permission, e) {
- e.preventDefault();
- e.target.blur();
- this.props.onToggle(this.props.user, permission);
- }
-
- render() {
- const { selectedPermission } = this.props;
- const permissionCells = this.props.permissionsOrder.map(p => (
- <td
- key={p.key}
- 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>
- </td>
- ));
-
- const { user } = this.props;
-
- const isCreator = user.login === '<creator>';
-
- return (
- <tr>
- <td className="nowrap">
- {!isCreator && (
- <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>}
- </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>
- </td>
- {permissionCells}
- </tr>
- );
- }
-}
--- /dev/null
+/*
+ * 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 Avatar from '../../../../components/ui/Avatar';
+import Checkbox from '../../../../components/controls/Checkbox';
+import { PermissionUser } from '../../../../api/permissions';
+import { translate } from '../../../../helpers/l10n';
+
+interface Props {
+ user: PermissionUser;
+ permissions: string[];
+ selectedPermission?: string;
+ permissionsOrder: string[];
+ onToggle: (user: PermissionUser, permission: string) => void;
+}
+
+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(permission => (
+ <td
+ key={permission}
+ className="text-center text-middle"
+ style={{ backgroundColor: permission === selectedPermission ? '#d9edf7' : 'transparent' }}>
+ <Checkbox
+ checked={this.props.permissions.includes(permission)}
+ id={permission}
+ onCheck={this.handleCheck}
+ />
+ </td>
+ ));
+
+ const { user } = this.props;
+ 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">
+ <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>
+ <span className="note spacer-left">{user.login}</span>
+ </div>
+ <div className="little-spacer-top">{user.email}</div>
+ </div>
+ </td>
+ {permissionCells}
+ </tr>
+ );
+ }
+}
import * as React from 'react';
import { mount } from 'enzyme';
import App, { Props } from '../App';
+import { Visibility } from '../../../app/types';
jest.mock('react-dom');
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',
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', () => {
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(
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();
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();