aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/App.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/App.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx132
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx110
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx75
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap69
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap75
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap20
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx5
17 files changed, 476 insertions, 83 deletions
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
index 993345e2319..9f1ee5f2a76 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
@@ -26,12 +26,12 @@ import ReliabilityBox from './ReliabilityBox';
import SecurityBox from './SecurityBox';
import MaintainabilityBox from './MaintainabilityBox';
import Activity from './Activity';
+import { SubComponent } from '../types';
+import { PORTFOLIO_METRICS, SUB_COMPONENTS_METRICS, convertMeasures } from '../utils';
import { getMeasures } from '../../../api/measures';
import { getChildren } from '../../../api/components';
-import { PORTFOLIO_METRICS, SUB_COMPONENTS_METRICS, convertMeasures } from '../utils';
-import { SubComponent } from '../types';
-import '../styles.css';
import { translate } from '../../../helpers/l10n';
+import '../styles.css';
interface Props {
component: { key: string; name: string };
@@ -75,7 +75,7 @@ export default class App extends React.PureComponent<Props, State> {
this.setState({ loading: true });
Promise.all([
getMeasures(this.props.component.key, PORTFOLIO_METRICS),
- getChildren(this.props.component.key, SUB_COMPONENTS_METRICS, { ps: 20 })
+ getChildren(this.props.component.key, SUB_COMPONENTS_METRICS, { ps: 20, s: 'qualifier' })
]).then(
([measures, subComponents]) => {
if (this.mounted) {
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx
index 0a894272da3..d1ae13e8626 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx
@@ -104,6 +104,6 @@ it('fetches measures and children components', () => {
'sqale_rating',
'alert_status'
],
- { ps: 20 }
+ { ps: 20, s: 'qualifier' }
);
});
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
index f92519a6a9e..c714647d45a 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
@@ -31,6 +31,7 @@ import { Organization } from '../../app/types';
import { translate } from '../../helpers/l10n';
export interface Props {
+ currentUser: { login: string };
hasProvisionPermission?: boolean;
onVisibilityChange: (visibility: string) => void;
organization: Organization;
@@ -191,6 +192,7 @@ export default class App extends React.PureComponent<Props, State> {
/>
<Projects
+ currentUser={this.props.currentUser}
ready={this.state.ready}
projects={this.state.projects}
selection={this.state.selection}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx
index 0b00f3eba76..199132d1a0b 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx
@@ -22,7 +22,7 @@ import { connect } from 'react-redux';
import App from './App';
import { Organization } from '../../app/types';
import { onFail } from '../../store/rootActions';
-import { getAppState, getOrganizationByKey } from '../../store/rootReducer';
+import { getAppState, getOrganizationByKey, getCurrentUser } from '../../store/rootReducer';
import { receiveOrganizations } from '../../store/organizations/duck';
import { changeProjectVisibility } from '../../api/organizations';
import { fetchOrganization } from '../../apps/organizations/actions';
@@ -32,6 +32,7 @@ interface Props {
defaultOrganization: string;
qualifiers: string[];
};
+ currentUser: { login: string };
fetchOrganization: (organization: string) => void;
onVisibilityChange: (organization: Organization, visibility: string) => void;
onRequestFail: (error: any) => void;
@@ -64,6 +65,7 @@ class AppContainer extends React.PureComponent<Props> {
return (
<App
+ currentUser={this.props.currentUser}
hasProvisionPermission={organization.canProvisionProjects}
onVisibilityChange={this.handleVisibilityChange}
organization={organization}
@@ -75,6 +77,7 @@ class AppContainer extends React.PureComponent<Props> {
const mapStateToProps = (state: any, ownProps: Props) => ({
appState: getAppState(state),
+ currentUser: getCurrentUser(state),
organization:
ownProps.organization || getOrganizationByKey(state, getAppState(state).defaultOrganization)
});
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
index 2f3693f0630..9b243f7ec6b 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
@@ -19,18 +19,17 @@
*/
import * as React from 'react';
import { Link } from 'react-router';
+import ProjectRowActions from './ProjectRowActions';
import { Project } from './utils';
import { Visibility } from '../../app/types';
import PrivateBadge from '../../components/common/PrivateBadge';
import Checkbox from '../../components/controls/Checkbox';
import QualifierIcon from '../../components/shared/QualifierIcon';
-import { translate } from '../../helpers/l10n';
-import { getComponentPermissionsUrl } from '../../helpers/urls';
import DateTooltipFormatter from '../../components/intl/DateTooltipFormatter';
-import ActionsDropdown, { ActionsDropdownItem } from '../../components/controls/ActionsDropdown';
interface Props {
- onApplyTemplateClick: (project: Project) => void;
+ currentUser: { login: string };
+ onApplyTemplate: (project: Project) => void;
onProjectCheck: (project: Project, checked: boolean) => void;
project: Project;
selected: boolean;
@@ -41,10 +40,6 @@ export default class ProjectRow extends React.PureComponent<Props> {
this.props.onProjectCheck(this.props.project, checked);
};
- handleApplyTemplateClick = () => {
- this.props.onApplyTemplateClick(this.props.project);
- };
-
render() {
const { project, selected } = this.props;
@@ -81,16 +76,11 @@ export default class ProjectRow extends React.PureComponent<Props> {
</td>
<td className="thin nowrap">
- <ActionsDropdown>
- <ActionsDropdownItem to={getComponentPermissionsUrl(project.key)}>
- {translate('edit_permissions')}
- </ActionsDropdownItem>
- <ActionsDropdownItem
- className="js-apply-template"
- onClick={this.handleApplyTemplateClick}>
- {translate('projects_role.apply_template')}
- </ActionsDropdownItem>
- </ActionsDropdown>
+ <ProjectRowActions
+ currentUser={this.props.currentUser}
+ onApplyTemplate={this.props.onApplyTemplate}
+ project={project}
+ />
</td>
</tr>
);
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
new file mode 100644
index 00000000000..f5be8f05f05
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
@@ -0,0 +1,132 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact 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 RestoreAccessModal from './RestoreAccessModal';
+import { Project } from './utils';
+import { getComponentShow } from '../../api/components';
+import { getComponentNavigation } from '../../api/nav';
+import ActionsDropdown, { ActionsDropdownItem } from '../../components/controls/ActionsDropdown';
+import { translate } from '../../helpers/l10n';
+import { getComponentPermissionsUrl } from '../../helpers/urls';
+
+export interface Props {
+ currentUser: { login: string };
+ onApplyTemplate: (project: Project) => void;
+ project: Project;
+}
+
+interface State {
+ hasAccess?: boolean;
+ loading: boolean;
+ restoreAccessModal: boolean;
+}
+
+export default class ProjectRowActions extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { loading: false, restoreAccessModal: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchPermissions = () => {
+ this.setState({ loading: false });
+ // call `getComponentNavigation` to check if user has the "Administer" permission
+ // call `getComponentShow` to check if user has the "Browse" permission
+ Promise.all([
+ getComponentNavigation(this.props.project.key),
+ getComponentShow(this.props.project.key)
+ ]).then(
+ ([navResponse]) => {
+ if (this.mounted) {
+ const hasAccess = Boolean(
+ navResponse.configuration && navResponse.configuration.showPermissions
+ );
+ this.setState({ hasAccess, loading: false });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ hasAccess: false, loading: false });
+ }
+ }
+ );
+ };
+
+ handleDropdownClick = () => {
+ if (this.state.hasAccess === undefined && !this.state.loading) {
+ this.fetchPermissions();
+ }
+ };
+
+ handleApplyTemplateClick = () => {
+ this.props.onApplyTemplate(this.props.project);
+ };
+
+ handleRestoreAccessClick = () => {
+ this.setState({ restoreAccessModal: true });
+ };
+
+ handleRestoreAccessClose = () => this.setState({ restoreAccessModal: false });
+
+ handleRestoreAccessDone = () => {
+ this.setState({ hasAccess: true, restoreAccessModal: false });
+ };
+
+ render() {
+ const { hasAccess } = this.state;
+
+ return (
+ <ActionsDropdown key="dropdown" onToggleClick={this.handleDropdownClick}>
+ {hasAccess === true && (
+ <ActionsDropdownItem to={getComponentPermissionsUrl(this.props.project.key)}>
+ {translate('edit_permissions')}
+ </ActionsDropdownItem>
+ )}
+
+ {hasAccess === false && (
+ <ActionsDropdownItem
+ className="js-restore-access"
+ onClick={this.handleRestoreAccessClick}>
+ {translate('global_permissions.restore_access')}
+ </ActionsDropdownItem>
+ )}
+
+ <ActionsDropdownItem className="js-apply-template" onClick={this.handleApplyTemplateClick}>
+ {translate('projects_role.apply_template')}
+ </ActionsDropdownItem>
+
+ {this.state.restoreAccessModal && (
+ <RestoreAccessModal
+ currentUser={this.props.currentUser}
+ key="restore-access-modal"
+ onClose={this.handleRestoreAccessClose}
+ onRestoreAccess={this.handleRestoreAccessDone}
+ project={this.props.project}
+ />
+ )}
+ </ActionsDropdown>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx
index 2a2b3c1cb2c..d3399685066 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx
@@ -23,8 +23,10 @@ import ProjectRow from './ProjectRow';
import { Project } from './utils';
import ApplyTemplateView from '../permissions/project/views/ApplyTemplateView';
import { Organization } from '../../app/types';
+import { translate } from '../../helpers/l10n';
interface Props {
+ currentUser: { login: string };
onProjectDeselected: (project: string) => void;
onProjectSelected: (project: string) => void;
organization: Organization;
@@ -42,7 +44,7 @@ export default class Projects extends React.PureComponent<Props> {
}
};
- onApplyTemplateClick = (project: Project) => {
+ handleApplyTemplate = (project: Project) => {
new ApplyTemplateView({ project, organization: this.props.organization }).render();
};
@@ -54,18 +56,19 @@ export default class Projects extends React.PureComponent<Props> {
<thead>
<tr>
<th />
- <th>Name</th>
+ <th>{translate('name')}</th>
<th />
- <th>Key</th>
- <th className="thin nowrap text-right">Last Analysis</th>
+ <th>{translate('key')}</th>
+ <th className="thin nowrap text-right">{translate('last_analysis')}</th>
<th />
</tr>
</thead>
<tbody>
{this.props.projects.map(project => (
<ProjectRow
+ currentUser={this.props.currentUser}
key={project.key}
- onApplyTemplateClick={this.onApplyTemplateClick}
+ onApplyTemplate={this.handleApplyTemplate}
onProjectCheck={this.onProjectCheck}
project={project}
selected={this.props.selection.includes(project.key)}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx
new file mode 100644
index 00000000000..6b1958f6fc5
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx
@@ -0,0 +1,110 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 { FormattedMessage } from 'react-intl';
+import { Project } from './utils';
+import { grantPermissionToUser } from '../../api/permissions';
+import Modal from '../../components/controls/Modal';
+import { translate } from '../../helpers/l10n';
+
+interface Props {
+ currentUser: { login: string };
+ onClose: () => void;
+ onRestoreAccess: () => void;
+ project: Project;
+}
+
+interface State {
+ loading: boolean;
+}
+
+export default class RestoreAccessModal extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { loading: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ this.props.onClose();
+ };
+
+ handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
+ this.setState({ loading: true });
+ Promise.all([this.grantPermission('user'), this.grantPermission('admin')]).then(
+ this.props.onRestoreAccess,
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ grantPermission = (permission: string) =>
+ grantPermissionToUser(
+ this.props.project.key,
+ this.props.currentUser.login,
+ permission,
+ this.props.project.organization
+ );
+
+ render() {
+ const header = translate('global_permissions.restore_access');
+
+ return (
+ <Modal contentLabel={header} onRequestClose={this.props.onClose}>
+ <form onSubmit={this.handleFormSubmit}>
+ <header className="modal-head">
+ <h2>{header}</h2>
+ </header>
+
+ <div className="modal-body">
+ <FormattedMessage
+ defaultMessage={translate('global_permissions.restore_access.message')}
+ id="global_permissions.restore_access.message"
+ values={{
+ browse: <strong>{translate('projects_role.user')}</strong>,
+ administer: <strong>{translate('projects_role.admin')}</strong>
+ }}
+ />
+ </div>
+
+ <footer className="modal-foot">
+ {this.state.loading && <i className="spinner spacer-right" />}
+ <button disabled={this.state.loading} type="submit">
+ {translate('restore')}
+ </button>
+ <a className="js-modal-close" href="#" onClick={this.handleCancelClick}>
+ {translate('cancel')}
+ </a>
+ </footer>
+ </form>
+ </Modal>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
index 3cab9ca7f6d..00014d3b253 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
@@ -145,6 +145,7 @@ it('changes default project visibility', () => {
function mountRender(props?: { [P in keyof Props]?: Props[P] }) {
return mount(
<App
+ currentUser={{ login: 'foo' }}
hasProvisionPermission={true}
onVisibilityChange={jest.fn()}
organization={organization}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx
index b1d052dbb86..20e180df5cd 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx
@@ -21,7 +21,6 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import ProjectRow from '../ProjectRow';
import { Visibility } from '../../../app/types';
-import { click } from '../../../helpers/testUtils';
const project = {
key: 'project',
@@ -44,17 +43,11 @@ it('checks project', () => {
expect(onProjectCheck).toBeCalledWith(project, false);
});
-it('applies permission template', () => {
- const onApplyTemplateClick = jest.fn();
- const wrapper = shallowRender({ onApplyTemplateClick });
- click(wrapper.find('.js-apply-template'));
- expect(onApplyTemplateClick).toBeCalledWith(project);
-});
-
function shallowRender(props?: any) {
return shallow(
<ProjectRow
- onApplyTemplateClick={jest.fn()}
+ currentUser={{ login: 'foo' }}
+ onApplyTemplate={jest.fn()}
onProjectCheck={jest.fn()}
project={project}
selected={true}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx
new file mode 100644
index 00000000000..ac55dd97de9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact 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 ProjectRowActions, { Props } from '../ProjectRowActions';
+import { Visibility } from '../../../app/types';
+import { click } from '../../../helpers/testUtils';
+
+jest.mock('../../../api/components', () => ({
+ getComponentShow: jest.fn(() => Promise.reject(undefined))
+}));
+
+jest.mock('../../../api/nav', () => ({
+ getComponentNavigation: jest.fn(() => Promise.resolve())
+}));
+
+const project = {
+ id: '',
+ key: 'project',
+ name: 'Project',
+ organization: 'org',
+ qualifier: 'TRK',
+ visibility: Visibility.Private
+};
+
+it('restores access', async () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper.prop<Function>('onToggleClick')();
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('.js-restore-access'));
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('applies permission template', () => {
+ const onApplyTemplate = jest.fn();
+ const wrapper = shallowRender({ onApplyTemplate });
+ click(wrapper.find('.js-apply-template'));
+ expect(onApplyTemplate).toBeCalledWith(project);
+});
+
+function shallowRender(props: Partial<Props> = {}) {
+ const wrapper = shallow(
+ <ProjectRowActions
+ currentUser={{ login: 'admin' }}
+ onApplyTemplate={jest.fn()}
+ project={project}
+ {...props}
+ />
+ );
+ (wrapper.instance() as ProjectRowActions).mounted = true;
+ return wrapper;
+}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx
index cc29a0e243d..8777d23b6dc 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx
@@ -60,13 +60,14 @@ it('opens modal to apply permission template', () => {
wrapper
.find('ProjectRow')
.first()
- .prop<Function>('onApplyTemplateClick')(projects[0]);
+ .prop<Function>('onApplyTemplate')(projects[0]);
expect(ApplyTemplateView).toBeCalledWith({ organization, project: projects[0] });
});
function shallowRender(props?: any) {
return shallow(
<Projects
+ currentUser={{ login: 'foo' }}
onProjectDeselected={jest.fn()}
onProjectSelected={jest.fn()}
organization={organization}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
index a76855dee44..49178bc7abd 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
@@ -64,26 +64,22 @@ exports[`renders 1`] = `
<td
className="thin nowrap"
>
- <ActionsDropdown>
- <ActionsDropdownItem
- to={
- Object {
- "pathname": "/project_roles",
- "query": Object {
- "id": "project",
- },
- }
+ <ProjectRowActions
+ currentUser={
+ Object {
+ "login": "foo",
+ }
+ }
+ onApplyTemplate={[Function]}
+ project={
+ Object {
+ "key": "project",
+ "name": "Project",
+ "qualifier": "TRK",
+ "visibility": "private",
}
- >
- edit_permissions
- </ActionsDropdownItem>
- <ActionsDropdownItem
- className="js-apply-template"
- onClick={[Function]}
- >
- projects_role.apply_template
- </ActionsDropdownItem>
- </ActionsDropdown>
+ }
+ />
</td>
</tr>
`;
@@ -150,26 +146,23 @@ exports[`renders 2`] = `
<td
className="thin nowrap"
>
- <ActionsDropdown>
- <ActionsDropdownItem
- to={
- Object {
- "pathname": "/project_roles",
- "query": Object {
- "id": "project",
- },
- }
+ <ProjectRowActions
+ currentUser={
+ Object {
+ "login": "foo",
+ }
+ }
+ onApplyTemplate={[Function]}
+ project={
+ Object {
+ "key": "project",
+ "lastAnalysisDate": "2017-04-08T00:00:00.000Z",
+ "name": "Project",
+ "qualifier": "TRK",
+ "visibility": "private",
}
- >
- edit_permissions
- </ActionsDropdownItem>
- <ActionsDropdownItem
- className="js-apply-template"
- onClick={[Function]}
- >
- projects_role.apply_template
- </ActionsDropdownItem>
- </ActionsDropdown>
+ }
+ />
</td>
</tr>
`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap
new file mode 100644
index 00000000000..a3bae67199d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap
@@ -0,0 +1,75 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`restores access 1`] = `
+<ActionsDropdown
+ key="dropdown"
+ onToggleClick={[Function]}
+>
+ <ActionsDropdownItem
+ className="js-apply-template"
+ onClick={[Function]}
+ >
+ projects_role.apply_template
+ </ActionsDropdownItem>
+</ActionsDropdown>
+`;
+
+exports[`restores access 2`] = `
+<ActionsDropdown
+ key="dropdown"
+ onToggleClick={[Function]}
+>
+ <ActionsDropdownItem
+ className="js-restore-access"
+ onClick={[Function]}
+ >
+ global_permissions.restore_access
+ </ActionsDropdownItem>
+ <ActionsDropdownItem
+ className="js-apply-template"
+ onClick={[Function]}
+ >
+ projects_role.apply_template
+ </ActionsDropdownItem>
+</ActionsDropdown>
+`;
+
+exports[`restores access 3`] = `
+<ActionsDropdown
+ key="dropdown"
+ onToggleClick={[Function]}
+>
+ <ActionsDropdownItem
+ className="js-restore-access"
+ onClick={[Function]}
+ >
+ global_permissions.restore_access
+ </ActionsDropdownItem>
+ <ActionsDropdownItem
+ className="js-apply-template"
+ onClick={[Function]}
+ >
+ projects_role.apply_template
+ </ActionsDropdownItem>
+ <RestoreAccessModal
+ currentUser={
+ Object {
+ "login": "admin",
+ }
+ }
+ key="restore-access-modal"
+ onClose={[Function]}
+ onRestoreAccess={[Function]}
+ project={
+ Object {
+ "id": "",
+ "key": "project",
+ "name": "Project",
+ "organization": "org",
+ "qualifier": "TRK",
+ "visibility": "private",
+ }
+ }
+ />
+</ActionsDropdown>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap
index 6705b982c5a..7a3728a28ad 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap
@@ -9,24 +9,29 @@ exports[`renders list of projects 1`] = `
<tr>
<th />
<th>
- Name
+ name
</th>
<th />
<th>
- Key
+ key
</th>
<th
className="thin nowrap text-right"
>
- Last Analysis
+ last_analysis
</th>
<th />
</tr>
</thead>
<tbody>
<ProjectRow
+ currentUser={
+ Object {
+ "login": "foo",
+ }
+ }
key="a"
- onApplyTemplateClick={[Function]}
+ onApplyTemplate={[Function]}
onProjectCheck={[Function]}
project={
Object {
@@ -39,8 +44,13 @@ exports[`renders list of projects 1`] = `
selected={true}
/>
<ProjectRow
+ currentUser={
+ Object {
+ "login": "foo",
+ }
+ }
key="b"
- onApplyTemplateClick={[Function]}
+ onApplyTemplate={[Function]}
onProjectCheck={[Function]}
project={
Object {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
index 9ebaa56feab..4c180f5a699 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
@@ -171,6 +171,8 @@ export default class CreateProfileForm extends React.PureComponent<Props, State>
</p>
</div>
))}
+ {/* drop me when we stop supporting ie11 */}
+ <input type="hidden" name="hello-ie11" value="" />
</div>
)}
diff --git a/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx b/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx
index a21b0f533a4..0b4194d26d6 100644
--- a/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx
+++ b/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx
@@ -28,6 +28,8 @@ interface Props {
children: React.ReactNode;
menuClassName?: string;
menuPosition?: 'left' | 'right';
+ // TODO: replace with `onOpen` & `onClose`
+ onToggleClick?: () => void;
small?: boolean;
toggleClassName?: string;
}
@@ -39,7 +41,8 @@ export default function ActionsDropdown({ menuPosition = 'right', ...props }: Pr
className={classNames('dropdown-toggle', props.toggleClassName, {
'button-small': props.small
})}
- data-toggle="dropdown">
+ data-toggle="dropdown"
+ onClick={props.onToggleClick}>
<SettingsIcon className="text-text-bottom" />
<i className="icon-dropdown little-spacer-left" />
</button>