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