Browse Source

rewrite permission templates app in ts

tags/7.5
Stas Vilchik 5 years ago
parent
commit
d878513061
22 changed files with 470 additions and 498 deletions
  1. 6
    0
      server/sonar-web/src/main/js/app/types.d.ts
  2. 23
    16
      server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx
  3. 2
    2
      server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.tsx
  4. 18
    24
      server/sonar-web/src/main/js/apps/permission-templates/components/Defaults.tsx
  5. 23
    30
      server/sonar-web/src/main/js/apps/permission-templates/components/Home.tsx
  6. 0
    55
      server/sonar-web/src/main/js/apps/permission-templates/components/List.js
  7. 51
    0
      server/sonar-web/src/main/js/apps/permission-templates/components/List.tsx
  8. 9
    9
      server/sonar-web/src/main/js/apps/permission-templates/components/ListHeader.tsx
  9. 0
    61
      server/sonar-web/src/main/js/apps/permission-templates/components/ListItem.js
  10. 53
    0
      server/sonar-web/src/main/js/apps/permission-templates/components/ListItem.tsx
  11. 0
    64
      server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.js
  12. 57
    0
      server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.tsx
  13. 0
    64
      server/sonar-web/src/main/js/apps/permission-templates/components/PermissionCell.js
  14. 64
    0
      server/sonar-web/src/main/js/apps/permission-templates/components/PermissionCell.tsx
  15. 33
    25
      server/sonar-web/src/main/js/apps/permission-templates/components/Template.tsx
  16. 0
    53
      server/sonar-web/src/main/js/apps/permission-templates/components/TemplateDetails.js
  17. 48
    0
      server/sonar-web/src/main/js/apps/permission-templates/components/TemplateDetails.tsx
  18. 0
    66
      server/sonar-web/src/main/js/apps/permission-templates/components/TemplateHeader.js
  19. 63
    0
      server/sonar-web/src/main/js/apps/permission-templates/components/TemplateHeader.tsx
  20. 10
    8
      server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Defaults-test.tsx
  21. 0
    0
      server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Defaults-test.tsx.snap
  22. 10
    21
      server/sonar-web/src/main/js/apps/permission-templates/utils.ts

+ 6
- 0
server/sonar-web/src/main/js/app/types.d.ts View File

@@ -538,6 +538,12 @@ declare namespace T {

export type PeriodMode = 'days' | 'date' | 'version' | 'previous_analysis' | 'previous_version';

export interface Permission {
description: string;
key: string;
name: string;
}

export interface PermissionDefinition {
key: string;
name: string;

server/sonar-web/src/main/js/apps/permission-templates/components/App.js → server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx View File

@@ -17,8 +17,8 @@
* 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 { Location } from 'history';
import Home from './Home';
import Template from './Template';
import OrganizationHelmet from '../../../components/common/OrganizationHelmet';
@@ -28,14 +28,21 @@ import { sortPermissions, mergePermissionsToTemplates, mergeDefaultsToTemplates
import { translate } from '../../../helpers/l10n';
import '../../permissions/styles.css';

export default class App extends React.PureComponent {
static propTypes = {
location: PropTypes.object.isRequired,
organization: PropTypes.object,
topQualifiers: PropTypes.array.isRequired
};
interface Props {
location: Location;
organization: T.Organization | undefined;
topQualifiers: string[];
}

state = {
interface State {
ready: boolean;
permissions: T.Permission[];
permissionTemplates: T.PermissionTemplate[];
}

export default class App extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
ready: false,
permissions: [],
permissionTemplates: []
@@ -62,21 +69,21 @@ export default class App extends React.PureComponent {
mergePermissionsToTemplates(r.permissionTemplates, permissions),
r.defaultTemplates
);
this.setState({
ready: true,
permissionTemplates,
permissions
});
this.setState({ ready: true, permissionTemplates, permissions });
}
});
};

renderTemplate(id) {
renderTemplate(id: string) {
if (!this.state.ready) {
return null;
}

const template = this.state.permissionTemplates.find(t => t.id === id);
if (!template) {
return null;
}

return (
<Template
organization={this.props.organization}
@@ -91,8 +98,8 @@ export default class App extends React.PureComponent {
return (
<Home
organization={this.props.organization}
permissions={this.state.permissions}
permissionTemplates={this.state.permissionTemplates}
permissions={this.state.permissions}
ready={this.state.ready}
refresh={this.requestPermissions}
topQualifiers={this.props.topQualifiers}

server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.js → server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.tsx View File

@@ -20,8 +20,8 @@
import { connect } from 'react-redux';
import App from './App';
import forSingleOrganization from '../../organizations/forSingleOrganization';
import { getAppState } from '../../../store/rootReducer';
import { getAppState, Store } from '../../../store/rootReducer';

const mapStateToProps = state => ({ topQualifiers: getAppState(state).qualifiers });
const mapStateToProps = (state: Store) => ({ topQualifiers: getAppState(state).qualifiers });

export default forSingleOrganization(connect(mapStateToProps)(App));

server/sonar-web/src/main/js/apps/permission-templates/components/Defaults.js → server/sonar-web/src/main/js/apps/permission-templates/components/Defaults.tsx View File

@@ -17,34 +17,28 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import PropTypes from 'prop-types';
import * as React from 'react';
import { sortBy } from 'lodash';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { PermissionTemplateType } from '../propTypes';

export default class Defaults extends React.PureComponent {
static propTypes = {
organization: PropTypes.object,
permissionTemplate: PermissionTemplateType.isRequired
};
interface Props {
organization: T.Organization | undefined;
template: T.PermissionTemplate;
}

render() {
const qualifiersToDisplay =
this.props.organization && !this.props.organization.isDefault
? ['TRK']
: this.props.permissionTemplate.defaultFor;
export default function Defaults({ organization, template }: Props) {
const qualifiersToDisplay =
organization && !organization.isDefault ? ['TRK'] : template.defaultFor;

const qualifiers = sortBy(qualifiersToDisplay)
.map(qualifier => translate('qualifiers', qualifier))
.join(', ');
const qualifiers = sortBy(qualifiersToDisplay)
.map(qualifier => translate('qualifiers', qualifier))
.join(', ');

return (
<div>
<span className="badge spacer-right">
{translateWithParameters('permission_template.default_for', qualifiers)}
</span>
</div>
);
}
return (
<div>
<span className="badge spacer-right">
{translateWithParameters('permission_template.default_for', qualifiers)}
</span>
</div>
);
}

server/sonar-web/src/main/js/apps/permission-templates/components/Home.js → server/sonar-web/src/main/js/apps/permission-templates/components/Home.tsx View File

@@ -17,42 +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 Helmet from 'react-helmet';
import Header from './Header';
import List from './List';
import { translate } from '../../../helpers/l10n';

export default class Home extends React.PureComponent {
static propTypes = {
organization: PropTypes.object,
topQualifiers: PropTypes.array.isRequired,
permissions: PropTypes.array.isRequired,
permissionTemplates: PropTypes.array.isRequired,
ready: PropTypes.bool.isRequired,
refresh: PropTypes.func.isRequired
};
interface Props {
organization: T.Organization | undefined;
permissionTemplates: T.PermissionTemplate[];
permissions: T.Permission[];
ready: boolean;
refresh: () => Promise<void>;
topQualifiers: string[];
}

render() {
return (
<div className="page page-limited">
<Helmet title={translate('permission_templates.page')} />
export default function Home(props: Props) {
return (
<div className="page page-limited">
<Helmet title={translate('permission_templates.page')} />

<Header
organization={this.props.organization}
ready={this.props.ready}
refresh={this.props.refresh}
/>
<Header organization={props.organization} ready={props.ready} refresh={props.refresh} />

<List
organization={this.props.organization}
permissions={this.props.permissions}
permissionTemplates={this.props.permissionTemplates}
refresh={this.props.refresh}
topQualifiers={this.props.topQualifiers}
/>
</div>
);
}
<List
organization={props.organization}
permissionTemplates={props.permissionTemplates}
permissions={props.permissions}
refresh={props.refresh}
topQualifiers={props.topQualifiers}
/>
</div>
);
}

+ 0
- 55
server/sonar-web/src/main/js/apps/permission-templates/components/List.js View File

@@ -1,55 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import PropTypes from 'prop-types';
import ListHeader from './ListHeader';
import ListItem from './ListItem';
import { PermissionTemplateType, CallbackType } from '../propTypes';

export default class List extends React.PureComponent {
static propTypes = {
organization: PropTypes.object,
permissionTemplates: PropTypes.arrayOf(PermissionTemplateType).isRequired,
permissions: PropTypes.array.isRequired,
topQualifiers: PropTypes.array.isRequired,
refresh: CallbackType
};

render() {
const permissionTemplates = this.props.permissionTemplates.map(p => (
<ListItem
key={p.id}
organization={this.props.organization}
permissionTemplate={p}
refresh={this.props.refresh}
topQualifiers={this.props.topQualifiers}
/>
));

return (
<div className="boxed-group boxed-group-inner">
<table className="data zebra permissions-table" id="permission-templates">
<ListHeader organization={this.props.organization} permissions={this.props.permissions} />
<tbody>{permissionTemplates}</tbody>
</table>
</div>
);
}
}

+ 51
- 0
server/sonar-web/src/main/js/apps/permission-templates/components/List.tsx View File

@@ -0,0 +1,51 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import ListHeader from './ListHeader';
import ListItem from './ListItem';

interface Props {
organization: T.Organization | undefined;
permissionTemplates: T.PermissionTemplate[];
permissions: T.Permission[];
refresh: () => Promise<void>;
topQualifiers: string[];
}

export default function List(props: Props) {
const permissionTemplates = props.permissionTemplates.map(p => (
<ListItem
key={p.id}
organization={props.organization}
refresh={props.refresh}
template={p}
topQualifiers={props.topQualifiers}
/>
));

return (
<div className="boxed-group boxed-group-inner">
<table className="data zebra permissions-table" id="permission-templates">
<ListHeader organization={props.organization} permissions={props.permissions} />
<tbody>{permissionTemplates}</tbody>
</table>
</div>
);
}

server/sonar-web/src/main/js/apps/permission-templates/components/ListHeader.js → server/sonar-web/src/main/js/apps/permission-templates/components/ListHeader.tsx View File

@@ -17,21 +17,20 @@
* 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 HelpTooltip from '../../../components/controls/HelpTooltip';
import { translate } from '../../../helpers/l10n';
import InstanceMessage from '../../../components/common/InstanceMessage';
import { Alert } from '../../../components/ui/Alert';

export default class ListHeader extends React.PureComponent {
static propTypes = {
organization: PropTypes.object,
permissions: PropTypes.array.isRequired
};
interface Props {
organization: T.Organization | undefined;
permissions: T.Permission[];
}

renderTooltip = permission =>
permission.key === 'user' || permission.key === 'codeviewer' ? (
export default class ListHeader extends React.PureComponent<Props> {
renderTooltip(permission: T.Permission) {
return permission.key === 'user' || permission.key === 'codeviewer' ? (
<div>
<InstanceMessage message={translate('projects_role', permission.key, 'desc')} />
<Alert className="spacer-top" variant="warning">
@@ -41,6 +40,7 @@ export default class ListHeader extends React.PureComponent {
) : (
<InstanceMessage message={translate('projects_role', permission.key, 'desc')} />
);
}

render() {
const cells = this.props.permissions.map(permission => (

+ 0
- 61
server/sonar-web/src/main/js/apps/permission-templates/components/ListItem.js View File

@@ -1,61 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import PropTypes from 'prop-types';
import NameCell from './NameCell';
import PermissionCell from './PermissionCell';
import ActionsCell from './ActionsCell';
import { PermissionTemplateType, CallbackType } from '../propTypes';

export default class ListItem extends React.PureComponent {
static propTypes = {
organization: PropTypes.object,
permissionTemplate: PermissionTemplateType.isRequired,
topQualifiers: PropTypes.array.isRequired,
refresh: CallbackType
};

render() {
const permissions = this.props.permissionTemplate.permissions.map(p => (
<PermissionCell key={p.key} permission={p} />
));

return (
<tr data-id={this.props.permissionTemplate.id} data-name={this.props.permissionTemplate.name}>
<NameCell
organization={this.props.organization}
permissionTemplate={this.props.permissionTemplate}
topQualifiers={this.props.topQualifiers}
/>

{permissions}

<td className="nowrap thin text-right">
<ActionsCell
organization={this.props.organization}
permissionTemplate={this.props.permissionTemplate}
refresh={this.props.refresh}
topQualifiers={this.props.topQualifiers}
/>
</td>
</tr>
);
}
}

+ 53
- 0
server/sonar-web/src/main/js/apps/permission-templates/components/ListItem.tsx View File

@@ -0,0 +1,53 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import NameCell from './NameCell';
import PermissionCell from './PermissionCell';
import ActionsCell from './ActionsCell';

interface Props {
organization: T.Organization | undefined;
refresh: () => Promise<void>;
template: T.PermissionTemplate;
topQualifiers: string[];
}

export default function ListItem(props: Props) {
const permissions = props.template.permissions.map(p => (
<PermissionCell key={p.key} permission={p} />
));

return (
<tr data-id={props.template.id} data-name={props.template.name}>
<NameCell organization={props.organization} template={props.template} />

{permissions}

<td className="nowrap thin text-right">
<ActionsCell
organization={props.organization}
permissionTemplate={props.template}
refresh={props.refresh}
topQualifiers={props.topQualifiers}
/>
</td>
</tr>
);
}

+ 0
- 64
server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.js View File

@@ -1,64 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router';
import Defaults from './Defaults';
import { PermissionTemplateType } from '../propTypes';

export default class NameCell extends React.PureComponent {
static propTypes = {
organization: PropTypes.object,
permissionTemplate: PermissionTemplateType.isRequired
};

render() {
const { permissionTemplate: t, organization } = this.props;

const pathname = organization
? `/organizations/${organization.key}/permission_templates`
: '/permission_templates';

return (
<td>
<Link to={{ pathname, query: { id: t.id } }}>
<strong className="js-name">{t.name}</strong>
</Link>

{t.defaultFor.length > 0 && (
<div className="spacer-top js-defaults">
<Defaults
organization={organization}
permissionTemplate={this.props.permissionTemplate}
/>
</div>
)}

{!!t.description && <div className="spacer-top js-description">{t.description}</div>}

{!!t.projectKeyPattern && (
<div className="spacer-top js-project-key-pattern">
Project Key Pattern: <code>{t.projectKeyPattern}</code>
</div>
)}
</td>
);
}
}

+ 57
- 0
server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.tsx View File

@@ -0,0 +1,57 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { Link } from 'react-router';
import Defaults from './Defaults';

interface Props {
organization: T.Organization | undefined;
template: T.PermissionTemplate;
}

export default function NameCell({ template, organization }: Props) {
const pathname = organization
? `/organizations/${organization.key}/permission_templates`
: '/permission_templates';

return (
<td>
<Link to={{ pathname, query: { id: template.id } }}>
<strong className="js-name">{template.name}</strong>
</Link>

{template.defaultFor.length > 0 && (
<div className="spacer-top js-defaults">
<Defaults organization={organization} template={template} />
</div>
)}

{!!template.description && (
<div className="spacer-top js-description">{template.description}</div>
)}

{!!template.projectKeyPattern && (
<div className="spacer-top js-project-key-pattern">
Project Key Pattern: <code>{template.projectKeyPattern}</code>
</div>
)}
</td>
);
}

+ 0
- 64
server/sonar-web/src/main/js/apps/permission-templates/components/PermissionCell.js View File

@@ -1,64 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import { PermissionType } from '../propTypes';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import { translate } from '../../../helpers/l10n';
import { isSonarCloud } from '../../../helpers/system';

export default class PermissionCell extends React.PureComponent {
static propTypes = {
permission: PermissionType.isRequired
};

render() {
const { permission: p } = this.props;

return (
<td className="permission-column" data-permission={p.key}>
<div className="permission-column-inner">
<ul>
{p.withProjectCreator && (
<li className="little-spacer-bottom display-flex-center">
{translate('permission_templates.project_creators')}
<HelpTooltip
className="little-spacer-left"
overlay={translate(
isSonarCloud()
? 'permission_templates.project_creators.explanation.sonarcloud'
: 'permission_templates.project_creators.explanation'
)}
/>
</li>
)}
<li className="little-spacer-bottom">
<strong>{p.usersCount}</strong>
{' user(s)'}
</li>
<li>
<strong>{p.groupsCount}</strong>
{' group(s)'}
</li>
</ul>
</div>
</td>
);
}
}

+ 64
- 0
server/sonar-web/src/main/js/apps/permission-templates/components/PermissionCell.tsx View File

@@ -0,0 +1,64 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import { translate } from '../../../helpers/l10n';
import { isSonarCloud } from '../../../helpers/system';

interface Props {
permission: {
key: string;
usersCount: number;
groupsCount: number;
withProjectCreator?: boolean;
};
}

export default function PermissionCell({ permission: p }: Props) {
return (
<td className="permission-column" data-permission={p.key}>
<div className="permission-column-inner">
<ul>
{p.withProjectCreator && (
<li className="little-spacer-bottom display-flex-center">
{translate('permission_templates.project_creators')}
<HelpTooltip
className="little-spacer-left"
overlay={translate(
isSonarCloud()
? 'permission_templates.project_creators.explanation.sonarcloud'
: 'permission_templates.project_creators.explanation'
)}
/>
</li>
)}
<li className="little-spacer-bottom">
<strong>{p.usersCount}</strong>
{' user(s)'}
</li>
<li>
<strong>{p.groupsCount}</strong>
{' group(s)'}
</li>
</ul>
</div>
</td>
);
}

server/sonar-web/src/main/js/apps/permission-templates/components/Template.js → server/sonar-web/src/main/js/apps/permission-templates/components/Template.tsx View File

@@ -17,8 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import PropTypes from 'prop-types';
import * as React from 'react';
import Helmet from 'react-helmet';
import TemplateHeader from './TemplateHeader';
import TemplateDetails from './TemplateDetails';
@@ -31,21 +30,30 @@ import {
import * as api from '../../../api/permissions';
import { translate } from '../../../helpers/l10n';

export default class Template extends React.PureComponent {
static propTypes = {
organization: PropTypes.object,
template: PropTypes.object.isRequired,
refresh: PropTypes.func.isRequired,
topQualifiers: PropTypes.array.isRequired
};
interface Props {
organization: T.Organization | undefined;
refresh: () => void;
template: T.PermissionTemplate;
topQualifiers: string[];
}

state = {
loading: false,
users: [],
interface State {
filter: string;
groups: T.PermissionGroup[];
loading: boolean;
query: string;
selectedPermission?: string;
users: T.PermissionUser[];
}

export default class Template extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
filter: 'all',
groups: [],
loading: false,
query: '',
filter: 'all',
selectedPermission: null
users: []
};

componentDidMount() {
@@ -57,7 +65,7 @@ export default class Template extends React.PureComponent {
this.mounted = false;
}

requestHolders = realQuery => {
requestHolders = (realQuery?: string) => {
this.setState({ loading: true });

const { template } = this.props;
@@ -89,13 +97,13 @@ export default class Template extends React.PureComponent {
});
};

handleToggleUser = (user, permission) => {
handleToggleUser = (user: T.PermissionUser, permission: string) => {
if (user.login === '<creator>') {
return this.handleToggleProjectCreator(user, permission);
}
const { template, organization } = this.props;
const hasPermission = user.permissions.includes(permission);
const data = {
const data: { templateId: string; login: string; permission: string; organization?: string } = {
templateId: template.id,
login: user.login,
permission
@@ -109,7 +117,7 @@ export default class Template extends React.PureComponent {
return request.then(() => this.requestHolders()).then(this.props.refresh);
};

handleToggleProjectCreator = (user, permission) => {
handleToggleProjectCreator = (user: T.PermissionUser, permission: string) => {
const { template } = this.props;
const hasPermission = user.permissions.includes(permission);
const request = hasPermission
@@ -118,7 +126,7 @@ export default class Template extends React.PureComponent {
return request.then(() => this.requestHolders()).then(this.props.refresh);
};

handleToggleGroup = (group, permission) => {
handleToggleGroup = (group: T.PermissionGroup, permission: string) => {
const { template, organization } = this.props;
const hasPermission = group.permissions.includes(permission);
const data = {
@@ -135,24 +143,24 @@ export default class Template extends React.PureComponent {
return request.then(() => this.requestHolders()).then(this.props.refresh);
};

handleSearch = query => {
handleSearch = (query: string) => {
this.setState({ query });
this.requestHolders(query);
};

handleFilter = filter => {
handleFilter = (filter: string) => {
this.setState({ filter }, this.requestHolders);
};

handleSelectPermission = selectedPermission => {
handleSelectPermission = (selectedPermission: string) => {
if (selectedPermission === this.state.selectedPermission) {
this.setState({ selectedPermission: null }, this.requestHolders);
this.setState({ selectedPermission: undefined }, this.requestHolders);
} else {
this.setState({ selectedPermission }, this.requestHolders);
}
};

shouldDisplayCreator = creatorPermissions => {
shouldDisplayCreator = (creatorPermissions: string[]) => {
const { filter, query, selectedPermission } = this.state;
const CREATOR_NAME = translate('permission_templates.project_creators');

@@ -161,7 +169,7 @@ export default class Template extends React.PureComponent {
const matchQuery = !query || CREATOR_NAME.toLocaleLowerCase().includes(query.toLowerCase());

const matchPermission =
selectedPermission == null || creatorPermissions.includes(selectedPermission);
selectedPermission === undefined || creatorPermissions.includes(selectedPermission);

return !isFiltered && matchQuery && matchPermission;
};

+ 0
- 53
server/sonar-web/src/main/js/apps/permission-templates/components/TemplateDetails.js View File

@@ -1,53 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import PropTypes from 'prop-types';
import Defaults from './Defaults';

export default class TemplateDetails extends React.PureComponent {
static propTypes = {
organization: PropTypes.object,
template: PropTypes.object.isRequired
};

render() {
const { template } = this.props;

return (
<div className="big-spacer-bottom">
{template.defaultFor.length > 0 && (
<div className="spacer-top js-defaults">
<Defaults organization={this.props.organization} permissionTemplate={template} />
</div>
)}

{!!template.description && (
<div className="spacer-top js-description">{template.description}</div>
)}

{!!template.projectKeyPattern && (
<div className="spacer-top js-project-key-pattern">
Project Key Pattern: <code>{template.projectKeyPattern}</code>
</div>
)}
</div>
);
}
}

+ 48
- 0
server/sonar-web/src/main/js/apps/permission-templates/components/TemplateDetails.tsx View File

@@ -0,0 +1,48 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import Defaults from './Defaults';

interface Props {
organization: T.Organization | undefined;
template: T.PermissionTemplate;
}

export default function TemplateDetails({ organization, template }: Props) {
return (
<div className="big-spacer-bottom">
{template.defaultFor.length > 0 && (
<div className="spacer-top js-defaults">
<Defaults organization={organization} template={template} />
</div>
)}

{!!template.description && (
<div className="spacer-top js-description">{template.description}</div>
)}

{!!template.projectKeyPattern && (
<div className="spacer-top js-project-key-pattern">
Project Key Pattern: <code>{template.projectKeyPattern}</code>
</div>
)}
</div>
);
}

+ 0
- 66
server/sonar-web/src/main/js/apps/permission-templates/components/TemplateHeader.js View File

@@ -1,66 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router';
import ActionsCell from './ActionsCell';
import { translate } from '../../../helpers/l10n';

export default class TemplateHeader extends React.PureComponent {
static propTypes = {
organization: PropTypes.object,
template: PropTypes.object.isRequired,
loading: PropTypes.bool.isRequired,
refresh: PropTypes.func.isRequired,
topQualifiers: PropTypes.array.isRequired
};

render() {
const { template, organization } = this.props;

const pathname = organization
? `/organizations/${organization.key}/permission_templates`
: '/permission_templates';

return (
<header className="page-header" id="project-permissions-header">
<div className="note spacer-bottom">
<Link className="text-muted" to={pathname}>
{translate('permission_templates.page')}
</Link>
</div>

<h1 className="page-title">{template.name}</h1>

{this.props.loading && <i className="spinner" />}

<div className="pull-right">
<ActionsCell
fromDetails={true}
organization={this.props.organization}
permissionTemplate={this.props.template}
refresh={this.props.refresh}
topQualifiers={this.props.topQualifiers}
/>
</div>
</header>
);
}
}

+ 63
- 0
server/sonar-web/src/main/js/apps/permission-templates/components/TemplateHeader.tsx View File

@@ -0,0 +1,63 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { Link } from 'react-router';
import ActionsCell from './ActionsCell';
import { translate } from '../../../helpers/l10n';

interface Props {
loading: boolean;
organization: T.Organization | undefined;
refresh: () => void;
template: T.PermissionTemplate;
topQualifiers: string[];
}

export default function TemplateHeader(props: Props) {
const { template, organization } = props;

const pathname = organization
? `/organizations/${organization.key}/permission_templates`
: '/permission_templates';

return (
<header className="page-header" id="project-permissions-header">
<div className="note spacer-bottom">
<Link className="text-muted" to={pathname}>
{translate('permission_templates.page')}
</Link>
</div>

<h1 className="page-title">{template.name}</h1>

{props.loading && <i className="spinner" />}

<div className="pull-right">
<ActionsCell
fromDetails={true}
organization={organization}
permissionTemplate={template}
refresh={props.refresh}
topQualifiers={props.topQualifiers}
/>
</div>
</header>
);
}

server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Defaults-test.js → server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Defaults-test.tsx View File

@@ -17,11 +17,13 @@
* 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 React from 'react';
import Defaults from '../Defaults';

const SAMPLE = {
const SAMPLE: T.PermissionTemplate = {
createdAt: '2018-01-01',
defaultFor: [],
id: 'id',
name: 'name',
permissions: []
@@ -29,26 +31,26 @@ const SAMPLE = {

it('should render one qualifier', () => {
const sample = { ...SAMPLE, defaultFor: ['DEV'] };
const output = shallow(<Defaults permissionTemplate={sample} />);
const output = shallow(<Defaults organization={undefined} template={sample} />);
expect(output).toMatchSnapshot();
});

it('should render several qualifiers', () => {
const sample = { ...SAMPLE, defaultFor: ['TRK', 'VW'] };
const output = shallow(<Defaults permissionTemplate={sample} />);
const output = shallow(<Defaults organization={undefined} template={sample} />);
expect(output).toMatchSnapshot();
});

it('should render several qualifiers for default organization', () => {
const sample = { ...SAMPLE, defaultFor: ['TRK', 'VW'] };
const organization = { isDefault: true };
const output = shallow(<Defaults organization={organization} permissionTemplate={sample} />);
const organization: T.Organization = { isDefault: true, key: 'org', name: 'org' };
const output = shallow(<Defaults organization={organization} template={sample} />);
expect(output).toMatchSnapshot();
});

it('should render only projects for custom organization', () => {
const sample = { ...SAMPLE, defaultFor: ['TRK', 'VW'] };
const organization = { isDefault: false };
const output = shallow(<Defaults organization={organization} permissionTemplate={sample} />);
const organization: T.Organization = { isDefault: false, key: 'org', name: 'org' };
const output = shallow(<Defaults organization={organization} template={sample} />);
expect(output).toMatchSnapshot();
});

server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Defaults-test.js.snap → server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Defaults-test.tsx.snap View File


server/sonar-web/src/main/js/apps/permission-templates/utils.js → server/sonar-web/src/main/js/apps/permission-templates/utils.ts View File

@@ -28,22 +28,14 @@ export const PERMISSIONS_ORDER = [
'scan'
];

/**
* Sort list of permissions based on predefined order
* @param {Array} permissions
* @returns {Array}
*/
export function sortPermissions(permissions) {
export function sortPermissions(permissions: T.Permission[]) {
return sortBy(permissions, p => PERMISSIONS_ORDER.indexOf(p.key));
}

/**
* Populate permissions' details in the list of permission templates
* @param {Array} permissionTemplates
* @param {Array} basePermissions
* @returns {Array}
*/
export function mergePermissionsToTemplates(permissionTemplates, basePermissions) {
export function mergePermissionsToTemplates(
permissionTemplates: T.PermissionTemplate[],
basePermissions: T.Permission[]
): T.PermissionTemplate[] {
return permissionTemplates.map(permissionTemplate => {
// it's important to keep the order of the permission template's permissions
// the same as the order of base permissions
@@ -58,15 +50,12 @@ export function mergePermissionsToTemplates(permissionTemplates, basePermissions
});
}

/**
* Mark default templates
* @param {Array} permissionTemplates
* @param {Array} defaultTemplates
* @returns {Array}
*/
export function mergeDefaultsToTemplates(permissionTemplates, defaultTemplates = []) {
export function mergeDefaultsToTemplates(
permissionTemplates: T.PermissionTemplate[],
defaultTemplates: Array<{ templateId: string; qualifier: string }> = []
): T.PermissionTemplate[] {
return permissionTemplates.map(permissionTemplate => {
const defaultFor = [];
const defaultFor: string[] = [];

defaultTemplates.forEach(defaultTemplate => {
if (defaultTemplate.templateId === permissionTemplate.id) {

Loading…
Cancel
Save