diff options
Diffstat (limited to 'server/sonar-web/src/main/js')
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> |