return getJSON('/api/projects/ghosts', data);
}
-export function deleteComponents(data: { projects: string; organization?: string }): Promise<void> {
- return post('/api/projects/bulk_delete', data);
+export function deleteComponents(projects: string[], organization: string): Promise<void> {
+ return post('/api/projects/bulk_delete', { projects: projects.join(), organization });
}
export function deleteProject(project: string): Promise<void> {
return post('/api/permissions/remove_group', data);
}
-/**
- * Get list of permission templates
- */
-export function getPermissionTemplates(organization?: string) {
+export interface PermissionTemplate {
+ id: string;
+ name: string;
+ description?: string;
+ projectKeyPattern?: string;
+ createdAt: string;
+ updatedAt?: string;
+ permissions: Array<{
+ key: string;
+ usersCount: number;
+ groupsCount: number;
+ withProjectCreator?: boolean;
+ }>;
+}
+
+interface GetPermissionTemplatesResponse {
+ permissionTemplates: PermissionTemplate[];
+ defaultTemplates: Array<{ templateId: string; qualifier: string }>;
+ permissions: Array<{ key: string; name: string; description: string }>;
+}
+
+export function getPermissionTemplates(
+ organization?: string
+): Promise<GetPermissionTemplatesResponse> {
const url = '/api/permissions/search_templates';
return organization ? getJSON(url, { organization }) : getJSON(url);
}
}
isProjectsActive() {
- const urls = ['/projects_admin', '/background_tasks'];
+ const urls = ['/admin/projects_management', '/background_tasks'];
return this.isSomethingActive(urls);
}
<ul className="dropdown-menu">
{!this.props.customOrganizations &&
<li>
- <IndexLink to="/projects_admin" activeClassName="active">
+ <IndexLink to="/admin/projects_management" activeClassName="active">
Management
</IndexLink>
</li>}
<li>
<IndexLink
activeClassName="active"
- to="/projects_admin"
+ to="/admin/projects_management"
>
Management
</IndexLink>
--- /dev/null
+/*
+ * 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.
+ */
+export enum BranchType {
+ LONG = 'LONG',
+ SHORT = 'SHORT'
+}
+
+export interface MainBranch {
+ isMain: true;
+ name: string;
+ status?: {
+ qualityGateStatus: string;
+ };
+}
+
+export interface LongLivingBranch {
+ isMain: false;
+ name: string;
+ status?: {
+ qualityGateStatus: string;
+ };
+ type: BranchType.LONG;
+}
+
+export interface ShortLivingBranch {
+ isMain: false;
+ isOrphan?: true;
+ mergeBranch: string;
+ name: string;
+ status?: {
+ bugs: number;
+ codeSmells: number;
+ vulnerabilities: number;
+ };
+ type: BranchType.SHORT;
+}
+
+export type Branch = MainBranch | LongLivingBranch | ShortLivingBranch;
+
+export interface ComponentExtension {
+ key: string;
+ name: string;
+}
+
+export interface Component {
+ analysisDate?: string;
+ breadcrumbs: Array<{
+ key: string;
+ name: string;
+ qualifier: string;
+ }>;
+ configuration?: ComponentConfiguration;
+ extensions?: ComponentExtension[];
+ isFavorite?: boolean;
+ key: string;
+ name: string;
+ organization: string;
+ path?: string;
+ qualifier: string;
+ refKey?: string;
+ version?: string;
+}
+
+export interface ComponentConfiguration {
+ extensions?: ComponentExtension[];
+ showBackgroundTasks?: boolean;
+ showLinks?: boolean;
+ showManualMeasures?: boolean;
+ showQualityGates?: boolean;
+ showQualityProfiles?: boolean;
+ showPermissions?: boolean;
+ showSettings?: boolean;
+ showUpdateKey?: boolean;
+}
+
+export interface Metric {
+ custom?: boolean;
+ decimalScale?: number;
+ description?: string;
+ direction?: number;
+ domain?: string;
+ hidden?: boolean;
+ key: string;
+ name: string;
+ qualitative?: boolean;
+ type: string;
+}
+
+export interface Organization {
+ adminPages?: Array<{ key: string; name: string }>;
+ avatar?: string;
+ canAdmin?: boolean;
+ canDelete?: boolean;
+ canProvisionProjects?: boolean;
+ canUpdateProjectsVisibilityToPrivate?: boolean;
+ description?: string;
+ isDefault?: boolean;
+ key: string;
+ name: string;
+ pages?: Array<{ key: string; name: string }>;
+ projectVisibility: string;
+ url?: string;
+}
import projectActivityRoutes from '../../apps/projectActivity/routes';
import projectAdminRoutes from '../../apps/project-admin/routes';
import projectsRoutes from '../../apps/projects/routes';
-import projectsAdminRoutes from '../../apps/projects-admin/routes';
+import projectsManagementRoutes from '../../apps/projectsManagement/routes';
import qualityGatesRoutes from '../../apps/quality-gates/routes';
import qualityProfilesRoutes from '../../apps/quality-profiles/routes';
import sessionsRoutes from '../../apps/sessions/routes';
}}
/>
+ <Redirect from="/projects_admin" to="/admin/projects_management" />
<Redirect from="/component/index" to="/component" />
<Redirect from="/component_issues" to="/project/issues" />
<Redirect from="/dashboard/index" to="/dashboard" />
<Route path="groups" childRoutes={groupsRoutes} />
<Route path="metrics" childRoutes={metricsRoutes} />
<Route path="permission_templates" childRoutes={permissionTemplatesRoutes} />
- <Route path="projects_admin" childRoutes={projectsAdminRoutes} />
+ <Route
+ path="admin/projects_management"
+ childRoutes={projectsManagementRoutes}
+ />
<Route path="roles/global" childRoutes={globalPermissionsRoutes} />
<Route path="settings" childRoutes={settingsRoutes} />
<Route path="system" childRoutes={systemRoutes} />
// @flow
import React from 'react';
import { connect } from 'react-redux';
-import AppContainer from '../../projects-admin/AppContainer';
+import AppContainer from '../../projectsManagement/AppContainer';
import { getOrganizationByKey } from '../../../store/rootReducer';
/*:: import type { Organization } from '../../../store/organizations/duck'; */
<form onSubmit={this.handleSubmit}>
<div className="modal-head">
<h2>
- {translate('qualifiers.delete.TRK')}
+ {translate('qualifier.delete.TRK')}
</h2>
</div>
<div className="modal-body">
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import { connect } from 'react-redux';
-import Main from './main';
-import { onFail } from '../../store/rootActions';
-import { getAppState, getOrganizationByKey } from '../../store/rootReducer';
-import { receiveOrganizations } from '../../store/organizations/duck';
-import { changeProjectVisibility } from '../../api/organizations';
-import { fetchOrganization } from '../../apps/organizations/actions';
-
-class AppContainer extends React.PureComponent {
- componentDidMount() {
- // if there is no organization, that means we are in the global scope
- // let's fetch defails for the default organization in this case
- if (!this.props.organization || !this.props.organization.projectVisibility) {
- this.props.fetchOrganization(this.props.appState.defaultOrganization);
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- handleVisibilityChange = visibility => {
- this.props.onVisibilityChange(this.props.organization, visibility);
- };
-
- render() {
- const { organization } = this.props;
-
- if (!organization) {
- return null;
- }
-
- const topLevelQualifiers = organization.isDefault ? this.props.appState.qualifiers : ['TRK'];
-
- return (
- <Main
- hasProvisionPermission={organization.canProvisionProjects}
- topLevelQualifiers={topLevelQualifiers}
- onVisibilityChange={this.handleVisibilityChange}
- onRequestFail={this.props.onRequestFail}
- organization={organization}
- />
- );
- }
-}
-
-const mapStateToProps = (state, ownProps) => ({
- appState: getAppState(state),
- organization:
- ownProps.organization || getOrganizationByKey(state, getAppState(state).defaultOrganization)
-});
-
-const onVisibilityChange = (organization, visibility) => dispatch => {
- const currentVisibility = organization.projectVisibility;
- dispatch(receiveOrganizations([{ ...organization, projectVisibility: visibility }]));
- changeProjectVisibility(organization.key, visibility).catch(error => {
- onFail(dispatch)(error);
- dispatch(receiveOrganizations([{ ...organization, projectVisibility: currentVisibility }]));
- });
-};
-
-const mapDispatchToProps = dispatch => ({
- fetchOrganization: key => dispatch(fetchOrganization(key)),
- onVisibilityChange: (organization, visibility) =>
- dispatch(onVisibilityChange(organization, visibility)),
- onRequestFail: error => onFail(dispatch)(error)
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(AppContainer);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import Modal from 'react-modal';
-import classNames from 'classnames';
-import UpgradeOrganizationBox from '../../components/common/UpgradeOrganizationBox';
-import { translate } from '../../helpers/l10n';
-/*:: import type { Organization } from '../../store/organizations/duck'; */
-
-/*::
-type Props = {
- onClose: () => void,
- onConfirm: string => void,
- organization: Organization
-};
-*/
-
-/*::
-type State = {
- visibility: string
-};
-*/
-
-export default class ChangeVisibilityForm extends React.PureComponent {
- /*:: props: Props; */
- /*:: state: State; */
-
- constructor(props /*: Props */) {
- super(props);
- this.state = { visibility: props.organization.projectVisibility };
- }
-
- handleCancelClick = (event /*: Event */) => {
- event.preventDefault();
- this.props.onClose();
- };
-
- handleConfirmClick = (event /*: Event */) => {
- event.preventDefault();
- this.props.onConfirm(this.state.visibility);
- this.props.onClose();
- };
-
- handleVisibilityClick = (visibility /*: string */) => (
- event /*: Event & { currentTarget: HTMLElement } */
- ) => {
- event.preventDefault();
- event.currentTarget.blur();
- this.setState({ visibility });
- };
-
- render() {
- const { canUpdateProjectsVisibilityToPrivate } = this.props.organization;
-
- return (
- <Modal
- isOpen={true}
- contentLabel="modal form"
- className="modal"
- overlayClassName="modal-overlay"
- onRequestClose={this.props.onClose}>
- <header className="modal-head">
- <h2>
- {translate('organization.change_visibility_form.header')}
- </h2>
- </header>
-
- <div className="modal-body">
- {['public', 'private'].map(visibility =>
- <div className="big-spacer-bottom" key={visibility}>
- <p>
- {visibility === 'private' && !canUpdateProjectsVisibilityToPrivate
- ? <span className="text-muted cursor-not-allowed">
- <i
- className={classNames('icon-radio', 'spacer-right', {
- 'is-checked': this.state.visibility === visibility
- })}
- />
- {translate('visibility', visibility)}
- </span>
- : <a
- className="link-base-color link-no-underline"
- href="#"
- onClick={this.handleVisibilityClick(visibility)}>
- <i
- className={classNames('icon-radio', 'spacer-right', {
- 'is-checked': this.state.visibility === visibility
- })}
- />
- {translate('visibility', visibility)}
- </a>}
- </p>
- <p className="text-muted spacer-top" style={{ paddingLeft: 22 }}>
- {translate('visibility', visibility, 'description.short')}
- </p>
- </div>
- )}
-
- {canUpdateProjectsVisibilityToPrivate
- ? <div className="alert alert-warning">
- {translate('organization.change_visibility_form.warning')}
- </div>
- : <UpgradeOrganizationBox organization={this.props.organization.key} />}
- </div>
-
- <footer className="modal-foot">
- <button onClick={this.handleConfirmClick}>
- {translate('organization.change_visibility_form.submit')}
- </button>
- <a href="#" onClick={this.handleCancelClick}>
- {translate('cancel')}
- </a>
- </footer>
- </Modal>
- );
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import Modal from 'react-modal';
-import { Link } from 'react-router';
-import UpgradeOrganizationBox from '../../components/common/UpgradeOrganizationBox';
-import VisibilitySelector from '../../components/common/VisibilitySelector';
-import { createProject } from '../../api/components';
-import { translate } from '../../helpers/l10n';
-import { getProjectUrl } from '../../helpers/urls';
-/*:: import type { Organization } from '../../store/organizations/duck'; */
-
-/*::
-type Props = {|
- onClose: () => void,
- onProjectCreated: () => void,
- onRequestFail: Object => void,
- organization?: Organization
-|};
-*/
-
-/*::
-type State = {
- branch: string,
- createdProject?: Object,
- key: string,
- loading: boolean,
- name: string,
- visibility: string
-};
-*/
-
-export default class CreateProjectForm extends React.PureComponent {
- /*:: mounted: boolean; */
- /*:: props: Props; */
- /*:: state: State; */
-
- constructor(props /*: Props */) {
- super(props);
- this.state = {
- branch: '',
- key: '',
- loading: false,
- name: '',
- visibility: props.organization ? props.organization.projectVisibility : 'public'
- };
- }
-
- componentDidMount() {
- this.mounted = true;
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- handleCancelClick = (event /*: Event */) => {
- event.preventDefault();
- this.props.onClose();
- };
-
- handleInputChange = (event /*: { currentTarget: HTMLInputElement } */) => {
- const { name, value } = event.currentTarget;
- this.setState({ [name]: value });
- };
-
- handleVisibilityChange = (visibility /*: string */) => {
- this.setState({ visibility });
- };
-
- handleFormSubmit = (event /*: Event */) => {
- event.preventDefault();
-
- const data /*: { [string]: string } */ = {
- name: this.state.name,
- branch: this.state.branch,
- project: this.state.key,
- visibility: this.state.visibility
- };
- if (this.props.organization) {
- data.organization = this.props.organization.key;
- }
-
- this.setState({ loading: true });
- createProject(data).then(
- response => {
- if (this.mounted) {
- this.setState({ createdProject: response.project, loading: false });
- this.props.onProjectCreated();
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
- );
- };
-
- render() {
- const { organization } = this.props;
- const { createdProject } = this.state;
-
- return (
- <Modal
- isOpen={true}
- contentLabel="modal form"
- className="modal"
- overlayClassName="modal-overlay"
- onRequestClose={this.props.onClose}>
- {createdProject
- ? <div>
- <header className="modal-head">
- <h2>
- {translate('qualifiers.create.TRK')}
- </h2>
- </header>
-
- <div className="modal-body">
- <div className="alert alert-success">
- Project <Link to={getProjectUrl(createdProject.key)}>
- {createdProject.name}
- </Link>{' '}
- has been successfully created.
- </div>
- </div>
-
- <footer className="modal-foot">
- <a href="#" id="create-project-close" onClick={this.handleCancelClick}>
- {translate('close')}
- </a>
- </footer>
- </div>
- : <form id="create-project-form" onSubmit={this.handleFormSubmit}>
- <header className="modal-head">
- <h2>
- {translate('qualifiers.create.TRK')}
- </h2>
- </header>
-
- <div className="modal-body">
- <div className="modal-field">
- <label htmlFor="create-project-name">
- {translate('name')}
- <em className="mandatory">*</em>
- </label>
- <input
- autoFocus={true}
- id="create-project-name"
- maxLength="2000"
- name="name"
- onChange={this.handleInputChange}
- required={true}
- type="text"
- value={this.state.name}
- />
- </div>
- <div className="modal-field">
- <label htmlFor="create-project-branch">
- {translate('branch')}
- </label>
- <input
- id="create-project-branch"
- maxLength="200"
- name="branch"
- onChange={this.handleInputChange}
- type="text"
- value={this.state.branch}
- />
- </div>
- <div className="modal-field">
- <label htmlFor="create-project-key">
- {translate('key')}
- <em className="mandatory">*</em>
- </label>
- <input
- id="create-project-key"
- maxLength="400"
- name="key"
- onChange={this.handleInputChange}
- required={true}
- type="text"
- value={this.state.key}
- />
- </div>
- <div className="modal-field">
- <label>
- {' '}{translate('visibility')}{' '}
- </label>
- <VisibilitySelector
- canTurnToPrivate={
- organization == null || organization.canUpdateProjectsVisibilityToPrivate
- }
- className="little-spacer-top"
- onChange={this.handleVisibilityChange}
- visibility={this.state.visibility}
- />
- {organization != null &&
- !organization.canUpdateProjectsVisibilityToPrivate &&
- <div className="spacer-top">
- <UpgradeOrganizationBox organization={organization.key} />
- </div>}
- </div>
- </div>
-
- <footer className="modal-foot">
- {this.state.loading && <i className="spinner spacer-right" />}
- <button disabled={this.state.loading} id="create-project-submit" type="submit">
- {translate('create')}
- </button>
- <a href="#" id="create-project-cancel" onClick={this.handleCancelClick}>
- {translate('cancel')}
- </a>
- </footer>
- </form>}
- </Modal>
- );
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import { shallow } from 'enzyme';
-import Projects from '../projects';
-import Checkbox from '../../../components/controls/Checkbox';
-
-it('should render list of projects with no selection', () => {
- const projects = [
- { id: '1', key: 'a', name: 'A', qualifier: 'TRK' },
- { id: '2', key: 'b', name: 'B', qualifier: 'TRK' }
- ];
-
- const result = shallow(
- <Projects
- organization={{ key: 'foo' }}
- projects={projects}
- selection={[]}
- refresh={jest.fn()}
- />
- );
- expect(result.find('tr').length).toBe(2);
- expect(result.find(Checkbox).filterWhere(n => n.prop('checked')).length).toBe(0);
-});
-
-it('should render list of projects with one selected', () => {
- const projects = [
- { id: '1', key: 'a', name: 'A', qualifier: 'TRK' },
- { id: '2', key: 'b', name: 'B', qualifier: 'TRK' }
- ];
- const selection = ['a'];
-
- const result = shallow(
- <Projects
- organization={{ key: 'foo' }}
- projects={projects}
- selection={selection}
- refresh={jest.fn()}
- />
- );
- expect(result.find('tr').length).toBe(2);
- expect(result.find(Checkbox).filterWhere(n => n.prop('checked')).length).toBe(1);
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-export const PAGE_SIZE = 50;
-
-export const QUALIFIERS_ORDER = ['TRK', 'VW', 'APP', 'DEV'];
-
-export const TYPE = {
- ALL: 'ALL',
- PROVISIONED: 'PROVISIONED',
- GHOSTS: 'GHOSTS'
-};
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import ModalForm from '../../components/common/modal-form';
-import Template from './templates/projects-delete.hbs';
-
-export default ModalForm.extend({
- template: Template,
-
- onFormSubmit() {
- ModalForm.prototype.onFormSubmit.apply(this, arguments);
- this.options.deleteProjects();
- this.destroy();
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import ModalForm from '../../components/common/modal-form';
-
-export default ModalForm.extend({
- onRender() {
- ModalForm.prototype.onRender.apply(this, arguments);
- this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
- },
-
- onDestroy() {
- ModalForm.prototype.onDestroy.apply(this, arguments);
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- },
-
- onFormSubmit() {
- ModalForm.prototype.onFormSubmit.apply(this, arguments);
- this.sendRequest();
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import ChangeVisibilityForm from './ChangeVisibilityForm';
-import { translate } from '../../helpers/l10n';
-/*:: import type { Organization } from '../../store/organizations/duck'; */
-
-/*::
-type Props = {|
- hasProvisionPermission: boolean,
- onProjectCreate: () => void,
- onVisibilityChange: string => void,
- organization: Organization
-|};
-*/
-
-/*::
-type State = {
- visibilityForm: boolean
-};
-*/
-
-export default class Header extends React.PureComponent {
- /*:: props: Props; */
- state /*: State */ = { visibilityForm: false };
-
- handleCreateProjectClick = (event /*: Event */) => {
- event.preventDefault();
- this.props.onProjectCreate();
- };
-
- handleChangeVisibilityClick = (event /*: Event */) => {
- event.preventDefault();
- this.setState({ visibilityForm: true });
- };
-
- closeVisiblityForm = () => {
- this.setState({ visibilityForm: false });
- };
-
- render() {
- const { organization } = this.props;
-
- return (
- <header className="page-header">
- <h1 className="page-title">
- {translate('projects_management')}
- </h1>
- <div className="page-actions">
- <span className="big-spacer-right">
- {translate('organization.default_visibility_of_new_projects')}{' '}
- <strong>{translate('visibility', organization.projectVisibility)}</strong>
- <a
- className="spacer-left icon-edit"
- href="#"
- onClick={this.handleChangeVisibilityClick}
- />
- </span>
- {this.props.hasProvisionPermission &&
- <button id="create-project" onClick={this.handleCreateProjectClick}>
- {translate('qualifiers.create.TRK')}
- </button>}
- </div>
- <p className="page-description">
- {translate('projects_management.page.description')}
- </p>
-
- {this.state.visibilityForm &&
- <ChangeVisibilityForm
- onClose={this.closeVisiblityForm}
- onConfirm={this.props.onVisibilityChange}
- organization={organization}
- />}
- </header>
- );
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import Helmet from 'react-helmet';
-import { debounce, uniq, without } from 'lodash';
-import Header from './header';
-import Search from './search';
-import Projects from './projects';
-import CreateProjectForm from './CreateProjectForm';
-import ListFooter from '../../components/controls/ListFooter';
-import { PAGE_SIZE, TYPE } from './constants';
-import { getComponents, getProvisioned, getGhosts, deleteComponents } from '../../api/components';
-import { translate } from '../../helpers/l10n';
-/*:: import type { Organization } from '../../store/organizations/duck'; */
-
-/*::
-type Props = {|
- hasProvisionPermission: boolean,
- onVisibilityChange: string => void,
- onRequestFail: Object => void,
- organization: Organization
-|};
-*/
-
-/*::
-type State = {
- createProjectForm: boolean,
- ready: boolean,
- projects: Array<{ key: string }>,
- total: number,
- page: number,
- query: string,
- qualifiers: string,
- type: string,
- selection: Array<string>
-};
-*/
-
-export default class Main extends React.PureComponent {
- /*:: props: Props; */
- /*:: state: State; */
-
- constructor(props /*: Props */) {
- super(props);
- this.state = {
- createProjectForm: false,
- ready: false,
- projects: [],
- total: 0,
- page: 1,
- query: '',
- qualifiers: 'TRK',
- type: TYPE.ALL,
- selection: []
- };
- this.requestProjects = debounce(this.requestProjects, 250);
- }
-
- componentDidMount() {
- this.requestProjects();
- }
-
- getFilters = () => {
- const filters /*: { [string]: string | number } */ = {
- organization: this.props.organization.key,
- ps: PAGE_SIZE
- };
- if (this.state.page !== 1) {
- filters.p = this.state.page;
- }
- if (this.state.query) {
- filters.q = this.state.query;
- }
- return filters;
- };
-
- requestProjects = () => {
- switch (this.state.type) {
- case TYPE.ALL:
- this.requestAllProjects();
- break;
- case TYPE.PROVISIONED:
- this.requestProvisioned();
- break;
- case TYPE.GHOSTS:
- this.requestGhosts();
- break;
- default:
-
- // should never happen
- }
- };
-
- requestGhosts = () => {
- const data = this.getFilters();
- getGhosts(data).then(r => {
- let projects = r.projects.map(project => ({
- ...project,
- id: project.uuid,
- qualifier: 'TRK'
- }));
- if (this.state.page > 1) {
- projects = [].concat(this.state.projects, projects);
- }
- this.setState({ ready: true, projects, total: r.total });
- });
- };
-
- requestProvisioned = () => {
- const data = this.getFilters();
- getProvisioned(data).then(r => {
- let projects = r.projects.map(project => ({
- ...project,
- id: project.uuid,
- qualifier: 'TRK'
- }));
- if (this.state.page > 1) {
- projects = [].concat(this.state.projects, projects);
- }
- this.setState({ ready: true, projects, total: r.paging.total });
- });
- };
-
- requestAllProjects = () => {
- const data = this.getFilters();
- data.qualifiers = this.state.qualifiers;
- getComponents(data).then(r => {
- let projects = r.components;
- if (this.state.page > 1) {
- projects = [].concat(this.state.projects, projects);
- }
- this.setState({ ready: true, projects, total: r.paging.total });
- });
- };
-
- loadMore = () => {
- this.setState({ ready: false, page: this.state.page + 1 }, this.requestProjects);
- };
-
- onSearch = (query /*: string */) => {
- this.setState(
- {
- ready: false,
- page: 1,
- query,
- selection: []
- },
- this.requestProjects
- );
- };
-
- onTypeChanged = (newType /*: string */) => {
- this.setState(
- {
- ready: false,
- page: 1,
- query: '',
- type: newType,
- qualifiers: 'TRK',
- selection: []
- },
- this.requestProjects
- );
- };
-
- onQualifierChanged = (newQualifier /*: string */) => {
- this.setState(
- {
- ready: false,
- page: 1,
- query: '',
- type: TYPE.ALL,
- qualifiers: newQualifier,
- selection: []
- },
- this.requestProjects
- );
- };
-
- onProjectSelected = (project /*: { key: string } */) => {
- const newSelection = uniq([].concat(this.state.selection, project.key));
- this.setState({ selection: newSelection });
- };
-
- onProjectDeselected = (project /*: { key: string } */) => {
- const newSelection = without(this.state.selection, project.key);
- this.setState({ selection: newSelection });
- };
-
- onAllSelected = () => {
- const newSelection = this.state.projects.map(project => project.key);
- this.setState({ selection: newSelection });
- };
-
- onAllDeselected = () => {
- this.setState({ selection: [] });
- };
-
- deleteProjects = () => {
- this.setState({ ready: false });
- const projects = this.state.selection.join(',');
- const data = {
- organization: this.props.organization.key,
- projects
- };
- deleteComponents(data).then(() => {
- this.setState({ page: 1, selection: [] }, this.requestProjects);
- });
- };
-
- openCreateProjectForm = () => {
- this.setState({ createProjectForm: true });
- };
-
- closeCreateProjectForm = () => {
- this.setState({ createProjectForm: false });
- };
-
- render() {
- return (
- <div className="page page-limited" id="projects-management-page">
- <Helmet title={translate('projects_management')} />
-
- <Header
- hasProvisionPermission={this.props.hasProvisionPermission}
- onProjectCreate={this.openCreateProjectForm}
- onVisibilityChange={this.props.onVisibilityChange}
- organization={this.props.organization}
- />
-
- <Search
- {...this.props}
- {...this.state}
- onSearch={this.onSearch}
- onTypeChanged={this.onTypeChanged}
- onQualifierChanged={this.onQualifierChanged}
- onAllSelected={this.onAllSelected}
- onAllDeselected={this.onAllDeselected}
- deleteProjects={this.deleteProjects}
- />
-
- <Projects
- ready={this.state.ready}
- projects={this.state.projects}
- refresh={this.requestProjects}
- selection={this.state.selection}
- onProjectSelected={this.onProjectSelected}
- onProjectDeselected={this.onProjectDeselected}
- organization={this.props.organization}
- />
-
- <ListFooter
- ready={this.state.ready}
- count={this.state.projects.length}
- total={this.state.total}
- loadMore={this.loadMore}
- />
-
- {this.state.createProjectForm &&
- <CreateProjectForm
- onClose={this.closeCreateProjectForm}
- onProjectCreated={this.requestProjects}
- onRequestFail={this.props.onRequestFail}
- organization={this.props.organization}
- />}
- </div>
- );
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import classNames from 'classnames';
-import React from 'react';
-import PropTypes from 'prop-types';
-import { Link } from 'react-router';
-import { getComponentPermissionsUrl } from '../../helpers/urls';
-import ApplyTemplateView from '../permissions/project/views/ApplyTemplateView';
-import Checkbox from '../../components/controls/Checkbox';
-import QualifierIcon from '../../components/shared/QualifierIcon';
-import PrivateBadge from '../../components/common/PrivateBadge';
-import { translate } from '../../helpers/l10n';
-
-export default class Projects extends React.PureComponent {
- static propTypes = {
- projects: PropTypes.array.isRequired,
- selection: PropTypes.array.isRequired,
- organization: PropTypes.object.isRequired
- };
-
- componentWillMount() {
- this.renderProject = this.renderProject.bind(this);
- }
-
- onProjectCheck(project, checked) {
- if (checked) {
- this.props.onProjectSelected(project);
- } else {
- this.props.onProjectDeselected(project);
- }
- }
-
- onApplyTemplateClick(project, e) {
- e.preventDefault();
- e.target.blur();
- new ApplyTemplateView({
- project,
- organization: this.props.organization
- }).render();
- }
-
- isProjectSelected(project) {
- return this.props.selection.indexOf(project.key) !== -1;
- }
-
- renderProject(project) {
- const permissionsUrl = getComponentPermissionsUrl(project.key);
-
- return (
- <tr key={project.key}>
- <td className="thin">
- <Checkbox
- checked={this.isProjectSelected(project)}
- onCheck={this.onProjectCheck.bind(this, project)}
- />
- </td>
- <td className="nowrap">
- <Link
- to={{ pathname: '/dashboard', query: { id: project.key } }}
- className="link-with-icon">
- <QualifierIcon qualifier={project.qualifier} /> <span>{project.name}</span>
- </Link>
- </td>
- <td className="nowrap">
- <span className="note">
- {project.key}
- </span>
- </td>
- <td className="width-20">
- {project.visibility === 'private' && <PrivateBadge />}
- </td>
- <td className="thin nowrap">
- <div className="dropdown">
- <button className="dropdown-toggle" data-toggle="dropdown">
- {translate('actions')} <i className="icon-dropdown" />
- </button>
- <ul className="dropdown-menu dropdown-menu-right">
- <li>
- <Link to={permissionsUrl}>
- {translate('edit_permissions')}
- </Link>
- </li>
- <li>
- <a href="#" onClick={this.onApplyTemplateClick.bind(this, project)}>
- {translate('projects_role.apply_template')}
- </a>
- </li>
- </ul>
- </div>
- </td>
- </tr>
- );
- }
-
- render() {
- const className = classNames('data', 'zebra', { 'new-loading': !this.props.ready });
-
- return (
- <table className={className} id="projects-management-page-projects">
- <tbody>
- {this.props.projects.map(this.renderProject)}
- </tbody>
- </table>
- );
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { RouterState, IndexRouteProps } from 'react-router';
-
-const routes = [
- {
- getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
- Promise.all([
- import('./AppContainer').then(i => i.default),
- import('../organizations/forSingleOrganization').then(i => i.default)
- ]).then(([AppContainer, forSingleOrganization]) =>
- callback(null, { component: forSingleOrganization(AppContainer) })
- );
- }
- }
-];
-
-export default routes;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import { sortBy } from 'lodash';
-import { TYPE, QUALIFIERS_ORDER } from './constants';
-import DeleteView from './delete-view';
-import BulkApplyTemplateView from './views/BulkApplyTemplateView';
-import RadioToggle from '../../components/controls/RadioToggle';
-import Checkbox from '../../components/controls/Checkbox';
-import { translate } from '../../helpers/l10n';
-
-export default class Search extends React.PureComponent {
- static propTypes = {
- onSearch: PropTypes.func.isRequired
- };
-
- onSubmit = e => {
- e.preventDefault();
- this.search();
- };
-
- search = () => {
- const q = this.refs.input.value;
- this.props.onSearch(q);
- };
-
- getTypeOptions = () => {
- return [
- { value: TYPE.ALL, label: 'All' },
- { value: TYPE.PROVISIONED, label: 'Provisioned' },
- { value: TYPE.GHOSTS, label: 'Ghosts' }
- ];
- };
-
- getQualifierOptions = () => {
- const options = this.props.topLevelQualifiers.map(q => {
- return { value: q, label: translate('qualifiers', q) };
- });
- return sortBy(options, option => QUALIFIERS_ORDER.indexOf(option.value));
- };
-
- onCheck = checked => {
- if (checked) {
- this.props.onAllSelected();
- } else {
- this.props.onAllDeselected();
- }
- };
-
- deleteProjects = () => {
- new DeleteView({
- deleteProjects: this.props.deleteProjects
- }).render();
- };
-
- bulkApplyTemplate = () => {
- new BulkApplyTemplateView({
- total: this.props.total,
- selection: this.props.selection,
- query: this.props.query,
- qualifier: this.props.qualifier,
- organization: this.props.organization
- }).render();
- };
-
- renderCheckbox = () => {
- const isAllChecked =
- this.props.projects.length > 0 && this.props.selection.length === this.props.projects.length;
- const thirdState =
- this.props.projects.length > 0 &&
- this.props.selection.length > 0 &&
- this.props.selection.length < this.props.projects.length;
- const checked = isAllChecked || thirdState;
- return <Checkbox checked={checked} thirdState={thirdState} onCheck={this.onCheck} />;
- };
-
- renderGhostsDescription = () => {
- if (this.props.type !== TYPE.GHOSTS || !this.props.ready) {
- return null;
- }
- return (
- <div className="spacer-top alert alert-info">
- {translate('bulk_deletion.ghosts.description')}
- </div>
- );
- };
-
- renderQualifierFilter = () => {
- const options = this.getQualifierOptions();
- if (options.length < 2) {
- return null;
- }
- return (
- <td className="thin nowrap text-middle">
- <RadioToggle
- options={this.getQualifierOptions()}
- value={this.props.qualifiers}
- name="projects-qualifier"
- onCheck={this.props.onQualifierChanged}
- />
- </td>
- );
- };
-
- renderSpinner = () => <i className="spinner" />;
-
- render() {
- const isSomethingSelected = this.props.projects.length > 0 && this.props.selection.length > 0;
- return (
- <div className="panel panel-vertical bordered-bottom spacer-bottom">
- <table className="data">
- <tbody>
- <tr>
- <td className="thin text-middle">
- {this.props.ready ? this.renderCheckbox() : this.renderSpinner()}
- </td>
- {this.renderQualifierFilter()}
- <td className="thin nowrap text-middle">
- <RadioToggle
- options={this.getTypeOptions()}
- value={this.props.type}
- name="projects-type"
- onCheck={this.props.onTypeChanged}
- />
- </td>
- <td className="text-middle">
- <form onSubmit={this.onSubmit} className="search-box">
- <button className="search-box-submit button-clean">
- <i className="icon-search" />
- </button>
- <input
- onChange={this.search}
- value={this.props.query}
- ref="input"
- className="search-box-input input-medium"
- type="search"
- placeholder="Search"
- />
- </form>
- </td>
- <td className="thin nowrap text-middle">
- <button
- className="spacer-right js-bulk-apply-permission-template"
- onClick={this.bulkApplyTemplate}>
- {translate('permission_templates.bulk_apply_permission_template')}
- </button>
- <button
- onClick={this.deleteProjects}
- className="button-red"
- disabled={!isSomethingSelected}>
- {translate('delete')}
- </button>
- </td>
- </tr>
- </tbody>
- </table>
- {this.renderGhostsDescription()}
- </div>
- );
- }
-}
+++ /dev/null
-<form class="js-form" autocomplete="off">
- <div class="modal-head">
- <h2>Bulk Apply Permission Template</h2>
- </div>
-
- <div class="modal-body">
- <div class="js-modal-messages"></div>
-
- {{#if done}}
- <div class="alert alert-success">
- {{t 'projects_role.apply_template.success'}}
- </div>
- {{/if}}
-
- {{#unless done}}
- {{#notNull permissionTemplates}}
- <div class="modal-field">
- <label for="project-permissions-template">
- Template<em class="mandatory">*</em>
- </label>
- <select id="project-permissions-template">
- {{#each permissionTemplates}}
- <option value="{{id}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
- {{else}}
- <i class="spinner"></i>
- {{/notNull}}
-
-
- <div class="modal-field">
- <label>Apply To</label>
- <ul style="padding-top: 4px;">
- {{#if selectionTotal}}
- <li>
- <input value="selected" id="apply-to-selected" name="apply-to"
- type="radio" checked>
- <label
- for="apply-to-selected"
- style="float: none; left: 0; display: inline; padding: 0;">
- Only Selected ({{selectionTotal}})
- </label>
- </li>
- {{/if}}
- <li>
- <input value="all" id="apply-to-all" name="apply-to" type="radio"
- {{#unless selectionTotal}}checked{{/unless}}>
- <label
- for="apply-to-all"
- style="float: none; left: 0; display: inline; padding: 0;">
- All ({{total}})
- </label>
- </li>
- </ul>
- </div>
- {{/unless}}
- </div>
-
- <div class="modal-foot">
- {{#unless done}}
- {{#notNull permissionTemplates}}
- <button class="js-apply">Apply</button>
- {{/notNull}}
- {{/unless}}
- <a href="#" class="js-modal-close">Close</a>
- </div>
-</form>
+++ /dev/null
-<form id="delete-project-form">
- <div class="modal-head">
- <h2>Delete Projects</h2>
- </div>
- <div class="modal-body">
- <div class="js-modal-messages"></div>
- Are you sure you want to delete selected projects?
- </div>
- <div class="modal-foot">
- <button id="delete-project-submit" class="button-red">Delete</button>
- <a href="#" class="js-modal-close" id="delete-project-cancel">Cancel</a>
- </div>
-</form>
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import ModalForm from '../../../components/common/modal-form';
-import {
- applyTemplateToProject,
- bulkApplyTemplate,
- getPermissionTemplates
-} from '../../../api/permissions';
-import Template from '../templates/BulkApplyTemplateTemplate.hbs';
-
-export default ModalForm.extend({
- template: Template,
-
- initialize() {
- this.loadPermissionTemplates();
- this.done = false;
- },
-
- loadPermissionTemplates() {
- const request = this.options.organization
- ? getPermissionTemplates(this.options.organization.key)
- : getPermissionTemplates();
- return request.then(r => {
- this.permissionTemplates = r.permissionTemplates;
- this.render();
- });
- },
-
- onRender() {
- ModalForm.prototype.onRender.apply(this, arguments);
- this.$('#project-permissions-template').select2({
- width: '250px',
- minimumResultsForSearch: 20
- });
- },
-
- bulkApplyToAll(permissionTemplate) {
- const data = { templateId: permissionTemplate };
-
- if (this.options.query) {
- data.q = this.options.query;
- }
-
- if (this.options.qualifier) {
- data.qualifier = this.options.qualifier;
- }
-
- if (this.options.organization) {
- data.organization = this.options.organization.key;
- }
-
- return bulkApplyTemplate(data);
- },
-
- bulkApplyToSelected(permissionTemplate) {
- const { selection } = this.options;
- let lastRequest = Promise.resolve();
-
- selection.forEach(projectKey => {
- const data = { templateId: permissionTemplate, projectKey };
- if (this.options.organization) {
- data.organization = this.options.organization.key;
- }
- lastRequest = lastRequest.then(() => applyTemplateToProject(data));
- });
-
- return lastRequest;
- },
-
- onFormSubmit() {
- ModalForm.prototype.onFormSubmit.apply(this, arguments);
- const permissionTemplate = this.$('#project-permissions-template').val();
- const applyTo = this.$('[name="apply-to"]:checked').val();
- this.disableForm();
-
- const request =
- applyTo === 'all'
- ? this.bulkApplyToAll(permissionTemplate)
- : this.bulkApplyToSelected(permissionTemplate);
-
- request
- .then(() => {
- this.trigger('done');
- this.done = true;
- this.render();
- })
- .catch(e => {
- e.response.json().then(r => {
- this.showErrors(r.errors, r.warnings);
- this.enableForm();
- });
- });
- },
-
- serializeData() {
- return {
- permissionTemplates: this.permissionTemplates,
- selection: this.options.selection,
- selectionTotal: this.options.selection.length,
- total: this.options.total,
- done: this.done
- };
- }
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import Helmet from 'react-helmet';
+import { debounce, uniq, without } from 'lodash';
+import Header from './Header';
+import Search from './Search';
+import Projects from './Projects';
+import CreateProjectForm from './CreateProjectForm';
+import ListFooter from '../../components/controls/ListFooter';
+import { PAGE_SIZE, Type, Project } from './utils';
+import { getComponents, getProvisioned, getGhosts } from '../../api/components';
+import { Organization } from '../../app/types';
+import { translate } from '../../helpers/l10n';
+
+export interface Props {
+ hasProvisionPermission?: boolean;
+ onVisibilityChange: (visibility: string) => void;
+ organization: Organization;
+ topLevelQualifiers: string[];
+}
+
+interface State {
+ createProjectForm: boolean;
+ page: number;
+ projects: Project[];
+ qualifiers: string;
+ query: string;
+ ready: boolean;
+ selection: string[];
+ total: number;
+ type: Type;
+}
+
+export default class App extends React.PureComponent<Props, State> {
+ mounted: boolean;
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ createProjectForm: false,
+ ready: false,
+ projects: [],
+ total: 0,
+ page: 1,
+ query: '',
+ qualifiers: 'TRK',
+ type: Type.All,
+ selection: []
+ };
+ this.requestProjects = debounce(this.requestProjects, 250);
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ this.requestProjects();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ getFilters = () => ({
+ organization: this.props.organization.key,
+ p: this.state.page !== 1 ? this.state.page : undefined,
+ ps: PAGE_SIZE,
+ q: this.state.query ? this.state.query : undefined
+ });
+
+ requestProjects = () => {
+ switch (this.state.type) {
+ case Type.All:
+ this.requestAllProjects();
+ break;
+ case Type.Provisioned:
+ this.requestProvisioned();
+ break;
+ case Type.Ghosts:
+ this.requestGhosts();
+ break;
+ }
+ };
+
+ requestGhosts = () => {
+ const data = this.getFilters();
+ getGhosts(data).then(r => {
+ if (this.mounted) {
+ let projects: Project[] = r.projects.map((project: any) => ({
+ ...project,
+ id: project.uuid,
+ qualifier: 'TRK'
+ }));
+ if (this.state.page > 1) {
+ projects = [...this.state.projects, ...projects];
+ }
+ this.setState({ ready: true, projects, selection: [], total: r.total });
+ }
+ });
+ };
+
+ requestProvisioned = () => {
+ const data = this.getFilters();
+ getProvisioned(data).then(r => {
+ if (this.mounted) {
+ let projects: Project[] = r.projects.map((project: any) => ({
+ ...project,
+ id: project.uuid,
+ qualifier: 'TRK'
+ }));
+ if (this.state.page > 1) {
+ projects = [...this.state.projects, ...projects];
+ }
+ this.setState({ ready: true, projects, selection: [], total: r.paging.total });
+ }
+ });
+ };
+
+ requestAllProjects = () => {
+ const data = this.getFilters();
+ Object.assign(data, { qualifiers: this.state.qualifiers });
+ getComponents(data).then(r => {
+ if (this.mounted) {
+ let projects: Project[] = r.components;
+ if (this.state.page > 1) {
+ projects = [...this.state.projects, ...projects];
+ }
+ this.setState({ ready: true, projects, selection: [], total: r.paging.total });
+ }
+ });
+ };
+
+ loadMore = () => {
+ this.setState({ ready: false, page: this.state.page + 1 }, this.requestProjects);
+ };
+
+ onSearch = (query: string) => {
+ this.setState({ ready: false, page: 1, query, selection: [] }, this.requestProjects);
+ };
+
+ onTypeChanged = (newType: Type) => {
+ this.setState(
+ { ready: false, page: 1, query: '', type: newType, qualifiers: 'TRK', selection: [] },
+ this.requestProjects
+ );
+ };
+
+ onQualifierChanged = (newQualifier: string) => {
+ this.setState(
+ { ready: false, page: 1, query: '', type: Type.All, qualifiers: newQualifier, selection: [] },
+ this.requestProjects
+ );
+ };
+
+ onProjectSelected = (project: string) => {
+ const newSelection = uniq([...this.state.selection, project]);
+ this.setState({ selection: newSelection });
+ };
+
+ onProjectDeselected = (project: string) => {
+ const newSelection = without(this.state.selection, project);
+ this.setState({ selection: newSelection });
+ };
+
+ onAllSelected = () => {
+ const newSelection = this.state.projects.map(project => project.key);
+ this.setState({ selection: newSelection });
+ };
+
+ onAllDeselected = () => {
+ this.setState({ selection: [] });
+ };
+
+ openCreateProjectForm = () => {
+ this.setState({ createProjectForm: true });
+ };
+
+ closeCreateProjectForm = () => {
+ this.setState({ createProjectForm: false });
+ };
+
+ render() {
+ return (
+ <div className="page page-limited" id="projects-management-page">
+ <Helmet title={translate('projects_management')} />
+
+ <Header
+ hasProvisionPermission={this.props.hasProvisionPermission}
+ onProjectCreate={this.openCreateProjectForm}
+ onVisibilityChange={this.props.onVisibilityChange}
+ organization={this.props.organization}
+ />
+
+ <Search
+ {...this.props}
+ {...this.state}
+ onAllSelected={this.onAllSelected}
+ onAllDeselected={this.onAllDeselected}
+ onDeleteProjects={this.requestProjects}
+ onQualifierChanged={this.onQualifierChanged}
+ onSearch={this.onSearch}
+ onTypeChanged={this.onTypeChanged}
+ />
+
+ <Projects
+ ready={this.state.ready}
+ projects={this.state.projects}
+ selection={this.state.selection}
+ onProjectSelected={this.onProjectSelected}
+ onProjectDeselected={this.onProjectDeselected}
+ organization={this.props.organization}
+ />
+
+ <ListFooter
+ ready={this.state.ready}
+ count={this.state.projects.length}
+ total={this.state.total}
+ loadMore={this.loadMore}
+ />
+
+ {this.state.createProjectForm &&
+ <CreateProjectForm
+ onClose={this.closeCreateProjectForm}
+ onProjectCreated={this.requestProjects}
+ organization={this.props.organization}
+ />}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { 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 { receiveOrganizations } from '../../store/organizations/duck';
+import { changeProjectVisibility } from '../../api/organizations';
+import { fetchOrganization } from '../../apps/organizations/actions';
+
+interface Props {
+ appState: {
+ defaultOrganization: string;
+ qualifiers: string[];
+ };
+ fetchOrganization: (organization: string) => void;
+ onVisibilityChange: (organization: Organization, visibility: string) => void;
+ onRequestFail: (error: any) => void;
+ organization?: Organization;
+}
+
+class AppContainer extends React.PureComponent<Props> {
+ componentDidMount() {
+ // if there is no organization, that means we are in the global scope
+ // let's fetch defails for the default organization in this case
+ if (!this.props.organization || !this.props.organization.projectVisibility) {
+ this.props.fetchOrganization(this.props.appState.defaultOrganization);
+ }
+ }
+
+ handleVisibilityChange = (visibility: string) => {
+ if (this.props.organization) {
+ this.props.onVisibilityChange(this.props.organization, visibility);
+ }
+ };
+
+ render() {
+ const { organization } = this.props;
+
+ if (!organization) {
+ return null;
+ }
+
+ const topLevelQualifiers = organization.isDefault ? this.props.appState.qualifiers : ['TRK'];
+
+ return (
+ <App
+ hasProvisionPermission={organization.canProvisionProjects}
+ onVisibilityChange={this.handleVisibilityChange}
+ organization={organization}
+ topLevelQualifiers={topLevelQualifiers}
+ />
+ );
+ }
+}
+
+const mapStateToProps = (state: any, ownProps: Props) => ({
+ appState: getAppState(state),
+ organization:
+ ownProps.organization || getOrganizationByKey(state, getAppState(state).defaultOrganization)
+});
+
+const onVisibilityChange = (organization: Organization, visibility: string) => (
+ dispatch: Function
+) => {
+ const currentVisibility = organization.projectVisibility;
+ dispatch(receiveOrganizations([{ ...organization, projectVisibility: visibility }]));
+ changeProjectVisibility(organization.key, visibility).catch(error => {
+ onFail(dispatch)(error);
+ dispatch(receiveOrganizations([{ ...organization, projectVisibility: currentVisibility }]));
+ });
+};
+
+const mapDispatchToProps = (dispatch: Function) => ({
+ fetchOrganization: (key: string) => dispatch(fetchOrganization(key)),
+ onVisibilityChange: (organization: Organization, visibility: string) =>
+ dispatch(onVisibilityChange(organization, visibility))
+});
+
+export default connect<any, any, any>(mapStateToProps, mapDispatchToProps)(AppContainer);
--- /dev/null
+/*
+ * 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 Modal from 'react-modal';
+import * as Select from 'react-select';
+import { Type } from './utils';
+import {
+ getPermissionTemplates,
+ PermissionTemplate,
+ bulkApplyTemplate,
+ applyTemplateToProject
+} from '../../api/permissions';
+import { translate, translateWithParameters } from '../../helpers/l10n';
+
+export interface Props {
+ onClose: () => void;
+ organization: string;
+ qualifier: string;
+ query: string;
+ selection: string[];
+ total: number;
+ type: Type;
+}
+
+interface State {
+ done: boolean;
+ loading: boolean;
+ permissionTemplate?: string;
+ permissionTemplates?: PermissionTemplate[];
+ submitting: boolean;
+}
+
+export default class BulkApplyTemplateModal extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { done: false, loading: true, submitting: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.loadPermissionTemplates();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ loadPermissionTemplates() {
+ this.setState({ loading: true });
+ getPermissionTemplates(this.props.organization).then(
+ ({ permissionTemplates }) => {
+ if (this.mounted) {
+ this.setState({
+ loading: false,
+ permissionTemplate:
+ permissionTemplates.length > 0 ? permissionTemplates[0].id : undefined,
+ permissionTemplates: permissionTemplates
+ });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ }
+
+ bulkApplyToAll = (permissionTemplate: string) => {
+ const data = {
+ organization: this.props.organization,
+ q: this.props.query ? this.props.query : undefined,
+ qualifier: this.props.qualifier,
+ templateId: permissionTemplate
+ };
+ return bulkApplyTemplate(data);
+ };
+
+ bulkApplyToSelected = (permissionTemplate: string) => {
+ const { selection } = this.props;
+ let lastRequest = Promise.resolve();
+
+ selection.forEach(projectKey => {
+ const data = {
+ organization: this.props.organization,
+ projectKey,
+ templateId: permissionTemplate
+ };
+ lastRequest = lastRequest.then(() => applyTemplateToProject(data));
+ });
+
+ return lastRequest;
+ };
+
+ handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ this.props.onClose();
+ };
+
+ handleConfirmClick = () => {
+ const { permissionTemplate } = this.state;
+ if (permissionTemplate) {
+ this.setState({ submitting: true });
+ const request = this.props.selection.length
+ ? this.bulkApplyToSelected(permissionTemplate)
+ : this.bulkApplyToAll(permissionTemplate);
+ request.then(
+ () => {
+ if (this.mounted) {
+ this.setState({ done: true, submitting: false });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ submitting: false });
+ }
+ }
+ );
+ }
+ };
+
+ handlePermissionTemplateChange = ({ value }: { value: string }) => {
+ this.setState({ permissionTemplate: value });
+ };
+
+ renderWarning = () => {
+ return this.props.selection.length
+ ? <div className="alert alert-info">
+ {translateWithParameters(
+ 'permission_templates.bulk_apply_permission_template.apply_to_selected',
+ this.props.selection.length
+ )}
+ </div>
+ : <div className="alert alert-warning">
+ {translateWithParameters(
+ 'permission_templates.bulk_apply_permission_template.apply_to_all',
+ this.props.total
+ )}
+ </div>;
+ };
+
+ renderSelect = () =>
+ <div className="modal-field">
+ <label>
+ {translate('template')}
+ <em className="mandatory">*</em>
+ </label>
+ <Select
+ clearable={false}
+ disabled={this.state.submitting}
+ onChange={this.handlePermissionTemplateChange}
+ options={this.state.permissionTemplates!.map(t => ({ label: t.name, value: t.id }))}
+ searchable={false}
+ value={this.state.permissionTemplate}
+ />
+ </div>;
+
+ render() {
+ const { done, loading, permissionTemplates, submitting } = this.state;
+ const header = translate('permission_templates.bulk_apply_permission_template');
+
+ return (
+ <Modal
+ isOpen={true}
+ contentLabel={header}
+ className="modal"
+ overlayClassName="modal-overlay"
+ onRequestClose={this.props.onClose}>
+ <header className="modal-head">
+ <h2>
+ {header}
+ </h2>
+ </header>
+
+ <div className="modal-body">
+ {done &&
+ <div className="alert alert-success">
+ {translate('projects_role.apply_template.success')}
+ </div>}
+
+ {loading && <i className="spinner" />}
+
+ {!loading && !done && permissionTemplates && this.renderWarning()}
+ {!loading && !done && permissionTemplates && this.renderSelect()}
+ </div>
+
+ <footer className="modal-foot">
+ {submitting && <i className="spinner spacer-right" />}
+ {!loading &&
+ !done &&
+ permissionTemplates &&
+ <button disabled={submitting} onClick={this.handleConfirmClick}>
+ {translate('apply')}
+ </button>}
+ <a className="js-modal-close" href="#" onClick={this.handleCancelClick}>
+ {done ? translate('close') : translate('cancel')}
+ </a>
+ </footer>
+ </Modal>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import Modal from 'react-modal';
+import * as classNames from 'classnames';
+import { Organization } from '../../app/types';
+import UpgradeOrganizationBox from '../../components/common/UpgradeOrganizationBox';
+import { translate } from '../../helpers/l10n';
+import { Visibility } from './utils';
+
+export interface Props {
+ onClose: () => void;
+ onConfirm: (visiblity: Visibility) => void;
+ organization: Organization;
+}
+
+interface State {
+ visibility: Visibility;
+}
+
+export default class ChangeVisibilityForm extends React.PureComponent<Props, State> {
+ constructor(props: Props) {
+ super(props);
+ this.state = { visibility: props.organization.projectVisibility as Visibility };
+ }
+
+ handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ this.props.onClose();
+ };
+
+ handleConfirmClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ this.props.onConfirm(this.state.visibility);
+ this.props.onClose();
+ };
+
+ handleVisibilityClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ const visibility = event.currentTarget.dataset.visibility as Visibility;
+ this.setState({ visibility });
+ };
+
+ render() {
+ const { canUpdateProjectsVisibilityToPrivate } = this.props.organization;
+
+ return (
+ <Modal
+ isOpen={true}
+ contentLabel="modal form"
+ className="modal"
+ overlayClassName="modal-overlay"
+ onRequestClose={this.props.onClose}>
+ <header className="modal-head">
+ <h2>
+ {translate('organization.change_visibility_form.header')}
+ </h2>
+ </header>
+
+ <div className="modal-body">
+ {[Visibility.Public, Visibility.Private].map(visibility =>
+ <div className="big-spacer-bottom" key={visibility}>
+ <p>
+ {visibility === Visibility.Private && !canUpdateProjectsVisibilityToPrivate
+ ? <span className="text-muted cursor-not-allowed">
+ <i
+ className={classNames('icon-radio', 'spacer-right', {
+ 'is-checked': this.state.visibility === visibility
+ })}
+ />
+ {translate('visibility', visibility)}
+ </span>
+ : <a
+ className="link-base-color link-no-underline"
+ data-visibility={visibility}
+ href="#"
+ onClick={this.handleVisibilityClick}>
+ <i
+ className={classNames('icon-radio', 'spacer-right', {
+ 'is-checked': this.state.visibility === visibility
+ })}
+ />
+ {translate('visibility', visibility)}
+ </a>}
+ </p>
+ <p className="text-muted spacer-top" style={{ paddingLeft: 22 }}>
+ {translate('visibility', visibility, 'description.short')}
+ </p>
+ </div>
+ )}
+
+ {canUpdateProjectsVisibilityToPrivate
+ ? <div className="alert alert-warning">
+ {translate('organization.change_visibility_form.warning')}
+ </div>
+ : <UpgradeOrganizationBox organization={this.props.organization.key} />}
+ </div>
+
+ <footer className="modal-foot">
+ <button className="js-confirm" onClick={this.handleConfirmClick}>
+ {translate('organization.change_visibility_form.submit')}
+ </button>
+ <a className="js-modal-close" href="#" onClick={this.handleCancelClick}>
+ {translate('cancel')}
+ </a>
+ </footer>
+ </Modal>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import Modal from 'react-modal';
+import { Link } from 'react-router';
+import { Organization } from '../../app/types';
+import UpgradeOrganizationBox from '../../components/common/UpgradeOrganizationBox';
+import VisibilitySelector from '../../components/common/VisibilitySelector';
+import { createProject } from '../../api/components';
+import { translate } from '../../helpers/l10n';
+import { getProjectUrl } from '../../helpers/urls';
+
+interface Props {
+ onClose: () => void;
+ onProjectCreated: () => void;
+ organization: Organization;
+}
+
+interface State {
+ branch: string;
+ createdProject?: { key: string; name: string };
+ key: string;
+ loading: boolean;
+ name: string;
+ visibility: string;
+ // add index declaration to be able to do `this.setState({ [name]: value });`
+ [x: string]: any;
+}
+
+export default class CreateProjectForm extends React.PureComponent<Props, State> {
+ mounted: boolean;
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ branch: '',
+ key: '',
+ loading: false,
+ name: '',
+ visibility: props.organization.projectVisibility
+ };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ this.props.onClose();
+ };
+
+ handleInputChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
+ const { name, value } = event.currentTarget;
+ this.setState({ [name]: value });
+ };
+
+ handleVisibilityChange = (visibility: string) => {
+ this.setState({ visibility });
+ };
+
+ handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
+
+ const data = {
+ name: this.state.name,
+ branch: this.state.branch,
+ organization: this.props.organization && this.props.organization.key,
+ project: this.state.key,
+ visibility: this.state.visibility
+ };
+
+ this.setState({ loading: true });
+ createProject(data).then(
+ response => {
+ if (this.mounted) {
+ this.setState({ createdProject: response.project, loading: false });
+ this.props.onProjectCreated();
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ render() {
+ const { organization } = this.props;
+ const { createdProject } = this.state;
+
+ return (
+ <Modal
+ isOpen={true}
+ contentLabel="modal form"
+ className="modal"
+ overlayClassName="modal-overlay"
+ onRequestClose={this.props.onClose}>
+ {createdProject
+ ? <div>
+ <header className="modal-head">
+ <h2>
+ {translate('qualifiers.create.TRK')}
+ </h2>
+ </header>
+
+ <div className="modal-body">
+ <div className="alert alert-success">
+ Project <Link to={getProjectUrl(createdProject.key)}>
+ {createdProject.name}
+ </Link>{' '}
+ has been successfully created.
+ </div>
+ </div>
+
+ <footer className="modal-foot">
+ <a href="#" id="create-project-close" onClick={this.handleCancelClick}>
+ {translate('close')}
+ </a>
+ </footer>
+ </div>
+ : <form id="create-project-form" onSubmit={this.handleFormSubmit}>
+ <header className="modal-head">
+ <h2>
+ {translate('qualifiers.create.TRK')}
+ </h2>
+ </header>
+
+ <div className="modal-body">
+ <div className="modal-field">
+ <label htmlFor="create-project-name">
+ {translate('name')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ autoFocus={true}
+ id="create-project-name"
+ maxLength={2000}
+ name="name"
+ onChange={this.handleInputChange}
+ required={true}
+ type="text"
+ value={this.state.name}
+ />
+ </div>
+ <div className="modal-field">
+ <label htmlFor="create-project-branch">
+ {translate('branch')}
+ </label>
+ <input
+ id="create-project-branch"
+ maxLength={200}
+ name="branch"
+ onChange={this.handleInputChange}
+ type="text"
+ value={this.state.branch}
+ />
+ </div>
+ <div className="modal-field">
+ <label htmlFor="create-project-key">
+ {translate('key')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ id="create-project-key"
+ maxLength={400}
+ name="key"
+ onChange={this.handleInputChange}
+ required={true}
+ type="text"
+ value={this.state.key}
+ />
+ </div>
+ <div className="modal-field">
+ <label>
+ {translate('visibility')}
+ </label>
+ <VisibilitySelector
+ canTurnToPrivate={organization.canUpdateProjectsVisibilityToPrivate}
+ className="little-spacer-top"
+ onChange={this.handleVisibilityChange}
+ visibility={this.state.visibility}
+ />
+ {!organization.canUpdateProjectsVisibilityToPrivate &&
+ <div className="spacer-top">
+ <UpgradeOrganizationBox organization={organization.key} />
+ </div>}
+ </div>
+ </div>
+
+ <footer className="modal-foot">
+ {this.state.loading && <i className="spinner spacer-right" />}
+ <button disabled={this.state.loading} id="create-project-submit" type="submit">
+ {translate('create')}
+ </button>
+ <a href="#" id="create-project-cancel" onClick={this.handleCancelClick}>
+ {translate('cancel')}
+ </a>
+ </footer>
+ </form>}
+ </Modal>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 Modal from 'react-modal';
+import { deleteComponents } from '../../api/components';
+import { translate } from '../../helpers/l10n';
+
+export interface Props {
+ onClose: () => void;
+ onConfirm: () => void;
+ organization: string;
+ qualifier: string;
+ selection: string[];
+}
+
+interface State {
+ loading: boolean;
+}
+
+export default class DeleteModal 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();
+ };
+
+ handleConfirmClick = () => {
+ this.setState({ loading: true });
+ deleteComponents(this.props.selection, this.props.organization).then(
+ () => {
+ if (this.mounted) {
+ this.props.onConfirm();
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ render() {
+ const header = translate('qualifiers.delete', this.props.qualifier);
+
+ return (
+ <Modal
+ isOpen={true}
+ contentLabel={header}
+ className="modal"
+ overlayClassName="modal-overlay"
+ onRequestClose={this.props.onClose}>
+ <header className="modal-head">
+ <h2>
+ {header}
+ </h2>
+ </header>
+
+ <div className="modal-body">
+ {translate('qualifiers.delete_confirm', this.props.qualifier)}
+ </div>
+
+ <footer className="modal-foot">
+ {this.state.loading && <i className="spinner spacer-right" />}
+ <button
+ className="button-red"
+ disabled={this.state.loading}
+ onClick={this.handleConfirmClick}>
+ {translate('delete')}
+ </button>
+ <a className="js-modal-close" href="#" onClick={this.handleCancelClick}>
+ {translate('cancel')}
+ </a>
+ </footer>
+ </Modal>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import ChangeVisibilityForm from './ChangeVisibilityForm';
+import { Visibility } from './utils';
+import { Organization } from '../../app/types';
+import { translate } from '../../helpers/l10n';
+
+export interface Props {
+ hasProvisionPermission?: boolean;
+ onProjectCreate: () => void;
+ onVisibilityChange: (visibility: Visibility) => void;
+ organization: Organization;
+}
+
+interface State {
+ visibilityForm: boolean;
+}
+
+export default class Header extends React.PureComponent<Props, State> {
+ state: State = { visibilityForm: false };
+
+ handleCreateProjectClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ this.props.onProjectCreate();
+ };
+
+ handleChangeVisibilityClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ this.setState({ visibilityForm: true });
+ };
+
+ closeVisiblityForm = () => {
+ this.setState({ visibilityForm: false });
+ };
+
+ render() {
+ const { organization } = this.props;
+
+ return (
+ <header className="page-header">
+ <h1 className="page-title">
+ {translate('projects_management')}
+ </h1>
+
+ <div className="page-actions">
+ <span className="big-spacer-right">
+ {translate('organization.default_visibility_of_new_projects')}{' '}
+ <strong>{translate('visibility', organization.projectVisibility)}</strong>
+ <a
+ className="js-change-visibility spacer-left icon-edit"
+ href="#"
+ onClick={this.handleChangeVisibilityClick}
+ />
+ </span>
+ {this.props.hasProvisionPermission &&
+ <button id="create-project" onClick={this.handleCreateProjectClick}>
+ {translate('qualifiers.create.TRK')}
+ </button>}
+ </div>
+
+ <p className="page-description">
+ {translate('projects_management.page.description')}
+ </p>
+
+ {this.state.visibilityForm &&
+ <ChangeVisibilityForm
+ onClose={this.closeVisiblityForm}
+ onConfirm={this.props.onVisibilityChange}
+ organization={organization}
+ />}
+ </header>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 { Link } from 'react-router';
+import { Project, Visibility } from './utils';
+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';
+
+interface Props {
+ onApplyTemplateClick: (project: Project) => void;
+ onProjectCheck: (project: Project, checked: boolean) => void;
+ project: Project;
+ selected: boolean;
+}
+
+export default class ProjectRow extends React.PureComponent<Props> {
+ handleProjectCheck = (checked: boolean) => {
+ this.props.onProjectCheck(this.props.project, checked);
+ };
+
+ handleApplyTemplateClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.props.onApplyTemplateClick(this.props.project);
+ };
+
+ render() {
+ const { project, selected } = this.props;
+
+ return (
+ <tr>
+ <td className="thin">
+ <Checkbox checked={selected} onCheck={this.handleProjectCheck} />
+ </td>
+
+ <td className="nowrap">
+ <Link
+ to={{ pathname: '/dashboard', query: { id: project.key } }}
+ className="link-with-icon">
+ <QualifierIcon qualifier={project.qualifier} /> <span>{project.name}</span>
+ </Link>
+ </td>
+
+ <td className="nowrap">
+ <span className="note">
+ {project.key}
+ </span>
+ </td>
+
+ <td className="width-20">
+ {project.visibility === Visibility.Private && <PrivateBadge />}
+ </td>
+
+ <td className="thin nowrap">
+ <div className="dropdown">
+ <button className="dropdown-toggle" data-toggle="dropdown">
+ {translate('actions')} <i className="icon-dropdown" />
+ </button>
+ <ul className="dropdown-menu dropdown-menu-right">
+ <li>
+ <Link to={getComponentPermissionsUrl(project.key)}>
+ {translate('edit_permissions')}
+ </Link>
+ </li>
+ <li>
+ <a className="js-apply-template" href="#" onClick={this.handleApplyTemplateClick}>
+ {translate('projects_role.apply_template')}
+ </a>
+ </li>
+ </ul>
+ </div>
+ </td>
+ </tr>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import ProjectRow from './ProjectRow';
+import { Project } from './utils';
+import ApplyTemplateView from '../permissions/project/views/ApplyTemplateView';
+import { Organization } from '../../app/types';
+
+interface Props {
+ onProjectDeselected: (project: string) => void;
+ onProjectSelected: (project: string) => void;
+ organization: Organization;
+ projects: Project[];
+ ready?: boolean;
+ selection: string[];
+}
+
+export default class Projects extends React.PureComponent<Props> {
+ onProjectCheck = (project: Project, checked: boolean) => {
+ if (checked) {
+ this.props.onProjectSelected(project.key);
+ } else {
+ this.props.onProjectDeselected(project.key);
+ }
+ };
+
+ onApplyTemplateClick = (project: Project) => {
+ new ApplyTemplateView({ project, organization: this.props.organization }).render();
+ };
+
+ render() {
+ return (
+ <table
+ className={classNames('data', 'zebra', { 'new-loading': !this.props.ready })}
+ id="projects-management-page-projects">
+ <tbody>
+ {this.props.projects.map(project =>
+ <ProjectRow
+ key={project.key}
+ onApplyTemplateClick={this.onApplyTemplateClick}
+ onProjectCheck={this.onProjectCheck}
+ project={project}
+ selected={this.props.selection.includes(project.key)}
+ />
+ )}
+ </tbody>
+ </table>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { sortBy } from 'lodash';
+import BulkApplyTemplateModal from './BulkApplyTemplateModal';
+import DeleteModal from './DeleteModal';
+import { Type, QUALIFIERS_ORDER } from './utils';
+import { Project } from './utils';
+import { Organization } from '../../app/types';
+import RadioToggle from '../../components/controls/RadioToggle';
+import Checkbox from '../../components/controls/Checkbox';
+import { translate } from '../../helpers/l10n';
+
+export interface Props {
+ onAllDeselected: () => void;
+ onAllSelected: () => void;
+ onDeleteProjects: () => void;
+ onQualifierChanged: (qualifier: string) => void;
+ onSearch: (query: string) => void;
+ onTypeChanged: (type: Type) => void;
+ organization: Organization;
+ projects: Project[];
+ qualifiers: string;
+ query: string;
+ ready: boolean;
+ selection: any[];
+ topLevelQualifiers: string[];
+ total: number;
+ type: Type;
+}
+
+interface State {
+ bulkApplyTemplateModal: boolean;
+ deleteModal: boolean;
+}
+
+export default class Search extends React.PureComponent<Props, State> {
+ input: HTMLInputElement;
+ mounted: boolean;
+ state: State = { bulkApplyTemplateModal: false, deleteModal: false };
+
+ onSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
+ this.search();
+ };
+
+ search = (event?: React.SyntheticEvent<HTMLInputElement>) => {
+ const q = event ? event.currentTarget.value : this.input.value;
+ this.props.onSearch(q);
+ };
+
+ getTypeOptions = () => [
+ { value: Type.All, label: 'All' },
+ { value: Type.Provisioned, label: 'Provisioned' },
+ { value: Type.Ghosts, label: 'Ghosts' }
+ ];
+
+ getQualifierOptions = () => {
+ const options = this.props.topLevelQualifiers.map(q => {
+ return { value: q, label: translate('qualifiers', q) };
+ });
+ return sortBy(options, option => QUALIFIERS_ORDER.indexOf(option.value));
+ };
+
+ onCheck = (checked: boolean) => {
+ if (checked) {
+ this.props.onAllSelected();
+ } else {
+ this.props.onAllDeselected();
+ }
+ };
+
+ handleDeleteClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ deleteModal: true });
+ };
+
+ closeDeleteModal = () => {
+ this.setState({ deleteModal: false });
+ };
+
+ handleDeleteConfirm = () => {
+ this.closeDeleteModal();
+ this.props.onDeleteProjects();
+ };
+
+ handleBulkApplyTemplateClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ bulkApplyTemplateModal: true });
+ };
+
+ closeBulkApplyTemplateModal = () => {
+ this.setState({ bulkApplyTemplateModal: false });
+ };
+
+ renderCheckbox = () => {
+ const isAllChecked =
+ this.props.projects.length > 0 && this.props.selection.length === this.props.projects.length;
+ const thirdState =
+ this.props.projects.length > 0 &&
+ this.props.selection.length > 0 &&
+ this.props.selection.length < this.props.projects.length;
+ const checked = isAllChecked || thirdState;
+ return <Checkbox checked={checked} thirdState={thirdState} onCheck={this.onCheck} />;
+ };
+
+ renderGhostsDescription = () => {
+ if (this.props.type !== Type.Ghosts || !this.props.ready) {
+ return null;
+ }
+ return (
+ <div className="spacer-top alert alert-info">
+ {translate('bulk_deletion.ghosts.description')}
+ </div>
+ );
+ };
+
+ renderQualifierFilter = () => {
+ const options = this.getQualifierOptions();
+ if (options.length < 2) {
+ return null;
+ }
+ return (
+ <td className="thin nowrap text-middle">
+ <RadioToggle
+ options={this.getQualifierOptions()}
+ value={this.props.qualifiers}
+ name="projects-qualifier"
+ onCheck={this.props.onQualifierChanged}
+ />
+ </td>
+ );
+ };
+
+ render() {
+ const isSomethingSelected = this.props.projects.length > 0 && this.props.selection.length > 0;
+ return (
+ <div className="panel panel-vertical bordered-bottom spacer-bottom">
+ <table className="data">
+ <tbody>
+ <tr>
+ <td className="thin text-middle">
+ {this.props.ready ? this.renderCheckbox() : <i className="spinner" />}
+ </td>
+ {this.renderQualifierFilter()}
+ <td className="thin nowrap text-middle">
+ <RadioToggle
+ options={this.getTypeOptions()}
+ value={this.props.type}
+ name="projects-type"
+ onCheck={this.props.onTypeChanged}
+ />
+ </td>
+ <td className="text-middle">
+ <form onSubmit={this.onSubmit} className="search-box">
+ <button className="search-box-submit button-clean">
+ <i className="icon-search" />
+ </button>
+ <input
+ onChange={this.search}
+ value={this.props.query}
+ ref={node => (this.input = node!)}
+ className="search-box-input input-medium"
+ type="search"
+ placeholder="Search"
+ />
+ </form>
+ </td>
+ <td className="thin nowrap text-middle">
+ <button
+ className="spacer-right js-bulk-apply-permission-template"
+ onClick={this.handleBulkApplyTemplateClick}>
+ {translate('permission_templates.bulk_apply_permission_template')}
+ </button>
+ <button
+ onClick={this.handleDeleteClick}
+ className="js-delete button-red"
+ disabled={!isSomethingSelected}>
+ {translate('delete')}
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ {this.renderGhostsDescription()}
+
+ {this.state.bulkApplyTemplateModal &&
+ <BulkApplyTemplateModal
+ onClose={this.closeBulkApplyTemplateModal}
+ organization={this.props.organization.key}
+ qualifier={this.props.qualifiers}
+ query={this.props.query}
+ selection={this.props.selection}
+ total={this.props.total}
+ type={this.props.type}
+ />}
+
+ {this.state.deleteModal &&
+ <DeleteModal
+ onClose={this.closeDeleteModal}
+ onConfirm={this.handleDeleteConfirm}
+ organization={this.props.organization.key}
+ qualifier={this.props.qualifiers}
+ selection={this.props.selection}
+ />}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+jest.mock('lodash', () => {
+ const lodash = require.requireActual('lodash');
+ lodash.debounce = (fn: Function) => (...args: any[]) => fn(args);
+ return lodash;
+});
+
+jest.mock('../../../api/components', () => ({
+ getComponents: jest.fn(),
+ getProvisioned: jest.fn(() => Promise.resolve({ paging: { total: 0 }, projects: [] })),
+ getGhosts: jest.fn(() => Promise.resolve({ projects: [], total: 0 }))
+}));
+
+import * as React from 'react';
+import { mount } from 'enzyme';
+import App, { Props } from '../App';
+import { Type } from '../utils';
+
+const getComponents = require('../../../api/components').getComponents as jest.Mock<any>;
+const getProvisioned = require('../../../api/components').getProvisioned as jest.Mock<any>;
+const getGhosts = require('../../../api/components').getGhosts as jest.Mock<any>;
+
+const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
+
+const defaultSearchParameters = {
+ organization: 'org',
+ p: undefined,
+ ps: 50,
+ q: undefined
+};
+
+beforeEach(() => {
+ getComponents
+ .mockImplementation(() => Promise.resolve({ paging: { total: 0 }, components: [] }))
+ .mockClear();
+ getProvisioned.mockClear();
+ getGhosts.mockClear();
+});
+
+it('fetches all projects on mount', () => {
+ mountRender();
+ expect(getComponents).lastCalledWith({ ...defaultSearchParameters, qualifiers: 'TRK' });
+});
+
+it('changes type', () => {
+ const wrapper = mountRender();
+ wrapper.find('Search').prop<Function>('onTypeChanged')(Type.Provisioned);
+ expect(getProvisioned).lastCalledWith(defaultSearchParameters);
+ wrapper.find('Search').prop<Function>('onTypeChanged')(Type.Ghosts);
+ expect(getGhosts).lastCalledWith(defaultSearchParameters);
+});
+
+it('changes qualifier and resets type', () => {
+ const wrapper = mountRender();
+ wrapper.setState({ type: Type.Provisioned });
+ wrapper.find('Search').prop<Function>('onQualifierChanged')('VW');
+ expect(getComponents).lastCalledWith({ ...defaultSearchParameters, qualifiers: 'VW' });
+});
+
+it('searches', () => {
+ const wrapper = mountRender();
+ wrapper.find('Search').prop<Function>('onSearch')('foo');
+ expect(getComponents).lastCalledWith({ ...defaultSearchParameters, q: 'foo', qualifiers: 'TRK' });
+});
+
+it('loads more', async () => {
+ const wrapper = mountRender();
+ wrapper.find('ListFooter').prop<Function>('loadMore')();
+ expect(getComponents).lastCalledWith({ ...defaultSearchParameters, p: 2, qualifiers: 'TRK' });
+});
+
+it('selects and deselects projects', async () => {
+ getComponents.mockImplementation(() =>
+ Promise.resolve({ paging: { total: 2 }, components: [{ key: 'foo' }, { key: 'bar' }] })
+ );
+ const wrapper = mountRender();
+ await new Promise(setImmediate);
+
+ wrapper.find('Projects').prop<Function>('onProjectSelected')('foo');
+ expect(wrapper.state('selection')).toEqual(['foo']);
+
+ wrapper.find('Projects').prop<Function>('onProjectSelected')('bar');
+ expect(wrapper.state('selection')).toEqual(['foo', 'bar']);
+
+ // should not select already selected project
+ wrapper.find('Projects').prop<Function>('onProjectSelected')('bar');
+ expect(wrapper.state('selection')).toEqual(['foo', 'bar']);
+
+ wrapper.find('Projects').prop<Function>('onProjectDeselected')('foo');
+ expect(wrapper.state('selection')).toEqual(['bar']);
+
+ wrapper.find('Search').prop<Function>('onAllDeselected')();
+ expect(wrapper.state('selection')).toEqual([]);
+
+ wrapper.find('Search').prop<Function>('onAllSelected')();
+ expect(wrapper.state('selection')).toEqual(['foo', 'bar']);
+});
+
+it('creates project', () => {
+ const wrapper = mountRender();
+ expect(wrapper.find('CreateProjectForm').exists()).toBeFalsy();
+
+ wrapper.find('Header').prop<Function>('onProjectCreate')();
+ expect(wrapper.find('CreateProjectForm').exists()).toBeTruthy();
+
+ wrapper.find('CreateProjectForm').prop<Function>('onProjectCreated')();
+ expect(getComponents.mock.calls).toHaveLength(2);
+
+ wrapper.find('CreateProjectForm').prop<Function>('onClose')();
+ expect(wrapper.find('CreateProjectForm').exists()).toBeFalsy();
+});
+
+it('changes default project visibility', () => {
+ const onVisibilityChange = jest.fn();
+ const wrapper = mountRender({ onVisibilityChange });
+ wrapper.find('Header').prop<Function>('onVisibilityChange')('private');
+ expect(onVisibilityChange).toBeCalledWith('private');
+});
+
+function mountRender(props?: { [P in keyof Props]?: Props[P] }) {
+ return mount(
+ <App
+ hasProvisionPermission={true}
+ onVisibilityChange={jest.fn()}
+ organization={organization}
+ topLevelQualifiers={['TRK', 'VW', 'APP']}
+ {...props}
+ />
+ );
+}
--- /dev/null
+/*
+ * 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.
+ */
+jest.mock('../../../api/permissions', () => ({
+ applyTemplateToProject: jest.fn(),
+ bulkApplyTemplate: jest.fn(),
+ getPermissionTemplates: jest.fn()
+}));
+
+import * as React from 'react';
+import { mount, shallow } from 'enzyme';
+import BulkApplyTemplateModal, { Props } from '../BulkApplyTemplateModal';
+import { Type } from '../utils';
+import { click } from '../../../helpers/testUtils';
+
+const applyTemplateToProject = require('../../../api/permissions')
+ .applyTemplateToProject as jest.Mock<any>;
+const bulkApplyTemplate = require('../../../api/permissions').bulkApplyTemplate as jest.Mock<any>;
+const getPermissionTemplates = require('../../../api/permissions')
+ .getPermissionTemplates as jest.Mock<any>;
+
+beforeEach(() => {
+ applyTemplateToProject.mockImplementation(() => Promise.resolve()).mockClear();
+ bulkApplyTemplate.mockImplementation(() => Promise.resolve()).mockClear();
+ getPermissionTemplates
+ .mockImplementation(() => Promise.resolve({ permissionTemplates: [] }))
+ .mockClear();
+});
+
+it('fetches permission templates on mount', () => {
+ mount(render());
+ expect(getPermissionTemplates).toBeCalledWith('org');
+});
+
+it('bulk applies template to all results', async () => {
+ const wrapper = shallow(render());
+ (wrapper.instance() as BulkApplyTemplateModal).mounted = true;
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper.setState({
+ loading: false,
+ permissionTemplate: 'foo',
+ permissionTemplates: [{ id: 'foo', name: 'Foo' }, { id: 'bar', name: 'Bar' }]
+ });
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('button'));
+ expect(bulkApplyTemplate).toBeCalledWith({
+ organization: 'org',
+ q: 'bla',
+ qualifier: 'TRK',
+ templateId: 'foo'
+ });
+ expect(wrapper).toMatchSnapshot();
+
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('bulk applies template to selected results', async () => {
+ const wrapper = shallow(render({ selection: ['proj1', 'proj2'] }));
+ (wrapper.instance() as BulkApplyTemplateModal).mounted = true;
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper.setState({
+ loading: false,
+ permissionTemplate: 'foo',
+ permissionTemplates: [{ id: 'foo', name: 'Foo' }, { id: 'bar', name: 'Bar' }]
+ });
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('button'));
+ expect(wrapper).toMatchSnapshot();
+ await new Promise(setImmediate);
+ expect(applyTemplateToProject.mock.calls).toHaveLength(2);
+ expect(applyTemplateToProject).toBeCalledWith({
+ organization: 'org',
+ projectKey: 'proj1',
+ templateId: 'foo'
+ });
+ expect(applyTemplateToProject).toBeCalledWith({
+ organization: 'org',
+ projectKey: 'proj2',
+ templateId: 'foo'
+ });
+
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('closes', () => {
+ const onClose = jest.fn();
+ const wrapper = shallow(render({ onClose }));
+ click(wrapper.find('.js-modal-close'));
+ expect(onClose).toBeCalled();
+});
+
+function render(props?: { [P in keyof Props]?: Props[P] }) {
+ return (
+ <BulkApplyTemplateModal
+ onClose={jest.fn()}
+ organization="org"
+ qualifier="TRK"
+ query="bla"
+ selection={[]}
+ total={17}
+ type={Type.All}
+ {...props}
+ />
+ );
+}
--- /dev/null
+/*
+ * 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 { shallow } from 'enzyme';
+import ChangeVisibilityForm, { Props } from '../ChangeVisibilityForm';
+import { click } from '../../../helpers/testUtils';
+
+const organization = {
+ canUpdateProjectsVisibilityToPrivate: true,
+ key: 'org',
+ name: 'org',
+ projectVisibility: 'public'
+};
+
+it('renders disabled', () => {
+ expect(
+ shallowRender({
+ organization: { ...organization, canUpdateProjectsVisibilityToPrivate: false }
+ })
+ ).toMatchSnapshot();
+});
+
+it('closes', () => {
+ const onClose = jest.fn();
+ const wrapper = shallowRender({ onClose });
+ click(wrapper.find('.js-modal-close'));
+ expect(onClose).toBeCalled();
+});
+
+it('changes visibility', () => {
+ const onConfirm = jest.fn();
+ const wrapper = shallowRender({ onConfirm });
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('a[data-visibility="private"]'), {
+ currentTarget: {
+ blur() {},
+ dataset: { visibility: 'private' }
+ }
+ });
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('.js-confirm'));
+ expect(onConfirm).toBeCalledWith('private');
+});
+
+function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
+ return shallow(
+ <ChangeVisibilityForm
+ onClose={jest.fn()}
+ onConfirm={jest.fn()}
+ organization={organization}
+ {...props}
+ />
+ );
+}
--- /dev/null
+/*
+ * 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.
+ */
+jest.mock('../../../api/components', () => ({
+ createProject: jest.fn(({ name }: { name: string }) =>
+ Promise.resolve({ project: { key: name, name } })
+ )
+}));
+
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import CreateProjectForm from '../CreateProjectForm';
+import { change, submit } from '../../../helpers/testUtils';
+
+const createProject = require('../../../api/components').createProject as jest.Mock<any>;
+
+const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
+
+it('creates project', async () => {
+ const wrapper = shallow(
+ <CreateProjectForm
+ onClose={jest.fn()}
+ onProjectCreated={jest.fn()}
+ organization={organization}
+ />
+ );
+ (wrapper.instance() as CreateProjectForm).mounted = true;
+ expect(wrapper).toMatchSnapshot();
+
+ change(wrapper.find('input[name="name"]'), 'name', {
+ currentTarget: { name: 'name', value: 'name' }
+ });
+ change(wrapper.find('input[name="branch"]'), 'branch', {
+ currentTarget: { name: 'branch', value: 'branch' }
+ });
+ change(wrapper.find('input[name="key"]'), 'key', {
+ currentTarget: { name: 'key', value: 'key' }
+ });
+ wrapper.find('VisibilitySelector').prop<Function>('onChange')('private');
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+
+ submit(wrapper.find('form'));
+ expect(createProject).toBeCalledWith({
+ branch: 'branch',
+ name: 'name',
+ organization: 'org',
+ project: 'key',
+ visibility: 'private'
+ });
+ expect(wrapper).toMatchSnapshot();
+
+ await new Promise(resolve => setImmediate(resolve));
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+});
--- /dev/null
+/*
+ * 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.
+ */
+jest.mock('../../../api/components', () => ({
+ deleteComponents: jest.fn(() => Promise.resolve())
+}));
+
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import DeleteModal, { Props } from '../DeleteModal';
+import { click } from '../../../helpers/testUtils';
+
+const deleteComponents = require('../../../api/components').deleteComponents as jest.Mock<any>;
+
+it('deletes projects', async () => {
+ const onConfirm = jest.fn();
+ const wrapper = shallowRender({ onConfirm });
+ (wrapper.instance() as DeleteModal).mounted = true;
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('button'));
+ expect(wrapper).toMatchSnapshot();
+ expect(deleteComponents).toBeCalledWith(['foo', 'bar'], 'org');
+
+ await new Promise(setImmediate);
+ expect(onConfirm).toBeCalled();
+});
+
+it('closes', () => {
+ const onClose = jest.fn();
+ const wrapper = shallowRender({ onClose });
+ click(wrapper.find('.js-modal-close'));
+ expect(onClose).toBeCalled();
+});
+
+function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
+ return shallow(
+ <DeleteModal
+ onClose={jest.fn()}
+ onConfirm={jest.fn()}
+ organization="org"
+ qualifier="TRK"
+ selection={['foo', 'bar']}
+ {...props}
+ />
+ );
+}
--- /dev/null
+/*
+ * 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 { shallow } from 'enzyme';
+import Header, { Props } from '../Header';
+import { Visibility } from '../utils';
+import { click } from '../../../helpers/testUtils';
+
+const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
+
+it('renders', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+it('creates project', () => {
+ const onProjectCreate = jest.fn();
+ const wrapper = shallowRender({ onProjectCreate });
+ click(wrapper.find('#create-project'));
+ expect(onProjectCreate).toBeCalledWith();
+});
+
+it('changes default visibility', () => {
+ const onVisibilityChange = jest.fn();
+ const wrapper = shallowRender({ onVisibilityChange });
+
+ click(wrapper.find('.js-change-visibility'));
+
+ const modalWrapper = wrapper.find('ChangeVisibilityForm');
+ expect(modalWrapper).toMatchSnapshot();
+ modalWrapper.prop<Function>('onConfirm')(Visibility.Private);
+ expect(onVisibilityChange).toBeCalledWith(Visibility.Private);
+
+ modalWrapper.prop<Function>('onClose')();
+ expect(wrapper.find('ChangeVisibilityForm').exists()).toBeFalsy();
+});
+
+function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
+ return shallow(
+ <Header
+ hasProvisionPermission={true}
+ onProjectCreate={jest.fn()}
+ onVisibilityChange={jest.fn()}
+ organization={organization}
+ {...props}
+ />
+ );
+}
--- /dev/null
+/*
+ * 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 { shallow } from 'enzyme';
+import ProjectRow from '../ProjectRow';
+import { Visibility } from '../utils';
+import { click } from '../../../helpers/testUtils';
+
+const project = {
+ key: 'project',
+ name: 'Project',
+ qualifier: 'TRK',
+ visibility: Visibility.Private
+};
+
+it('renders', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+it('checks project', () => {
+ const onProjectCheck = jest.fn();
+ const wrapper = shallowRender({ onProjectCheck });
+ wrapper.find('Checkbox').prop<Function>('onCheck')(false);
+ 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()}
+ onProjectCheck={jest.fn()}
+ project={project}
+ selected={true}
+ {...props}
+ />
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+jest.mock('../../permissions/project/views/ApplyTemplateView');
+
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Projects from '../Projects';
+import { Visibility } from '../utils';
+import ApplyTemplateView from '../../permissions/project/views/ApplyTemplateView';
+
+const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
+const projects = [
+ { key: 'a', name: 'A', qualifier: 'TRK', visibility: Visibility.Public },
+ { key: 'b', name: 'B', qualifier: 'TRK', visibility: Visibility.Public }
+];
+const selection = ['a'];
+
+it('renders list of projects', () => {
+ expect(shallowRender({ projects, selection })).toMatchSnapshot();
+});
+
+it('selects and deselects project', () => {
+ const onProjectDeselected = jest.fn();
+ const onProjectSelected = jest.fn();
+ const wrapper = shallowRender({ onProjectDeselected, onProjectSelected, projects });
+
+ wrapper.find('ProjectRow').first().prop<Function>('onProjectCheck')(projects[0], true);
+ expect(onProjectSelected).toBeCalledWith('a');
+
+ wrapper.find('ProjectRow').first().prop<Function>('onProjectCheck')(projects[0], false);
+ expect(onProjectDeselected).toBeCalledWith('a');
+});
+
+it('opens modal to apply permission template', () => {
+ const wrapper = shallowRender({ projects });
+ wrapper.find('ProjectRow').first().prop<Function>('onApplyTemplateClick')(projects[0]);
+ expect(ApplyTemplateView).toBeCalledWith({ organization, project: projects[0] });
+});
+
+function shallowRender(props?: any) {
+ return shallow(
+ <Projects
+ onProjectDeselected={jest.fn()}
+ onProjectSelected={jest.fn()}
+ organization={organization}
+ selection={[]}
+ {...props}
+ />
+ );
+}
--- /dev/null
+/*
+ * 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 { shallow } from 'enzyme';
+import Search, { Props } from '../Search';
+import { Type } from '../utils';
+import { change, click } from '../../../helpers/testUtils';
+
+const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
+
+it('renders', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+it('render qualifiers filter', () => {
+ expect(shallowRender({ topLevelQualifiers: ['TRK', 'VW', 'APP'] })).toMatchSnapshot();
+});
+
+it('updates qualifier', () => {
+ const onQualifierChanged = jest.fn();
+ const wrapper = shallowRender({ onQualifierChanged, topLevelQualifiers: ['TRK', 'VW', 'APP'] });
+ wrapper.find('RadioToggle[name="projects-qualifier"]').prop<Function>('onCheck')('VW');
+ expect(onQualifierChanged).toBeCalledWith('VW');
+});
+
+it('updates type', () => {
+ const onTypeChanged = jest.fn();
+ const wrapper = shallowRender({ onTypeChanged });
+ wrapper.find('RadioToggle[name="projects-type"]').prop<Function>('onCheck')(Type.Provisioned);
+ expect(onTypeChanged).toBeCalledWith(Type.Provisioned);
+});
+
+it('searches', () => {
+ const onSearch = jest.fn();
+ const wrapper = shallowRender({ onSearch });
+ change(wrapper.find('input[type="search"]'), 'foo');
+ expect(onSearch).toBeCalledWith('foo');
+});
+
+it('checks all or none projects', () => {
+ const onAllDeselected = jest.fn();
+ const onAllSelected = jest.fn();
+ const wrapper = shallowRender({ onAllDeselected, onAllSelected });
+
+ wrapper.find('Checkbox').prop<Function>('onCheck')(true);
+ expect(onAllSelected).toBeCalled();
+
+ wrapper.find('Checkbox').prop<Function>('onCheck')(false);
+ expect(onAllDeselected).toBeCalled();
+});
+
+it('deletes projects', () => {
+ const onDeleteProjects = jest.fn();
+ const wrapper = shallowRender({ onDeleteProjects, selection: ['foo', 'bar'] });
+ click(wrapper.find('.js-delete'));
+ expect(wrapper.find('DeleteModal')).toMatchSnapshot();
+ wrapper.find('DeleteModal').prop<Function>('onConfirm')();
+ expect(onDeleteProjects).toBeCalled();
+});
+
+it('bulk applies permission template', () => {
+ const wrapper = shallowRender({});
+ click(wrapper.find('.js-bulk-apply-permission-template'));
+ expect(wrapper.find('BulkApplyTemplateModal')).toMatchSnapshot();
+ wrapper.find('BulkApplyTemplateModal').prop<Function>('onClose')();
+ expect(wrapper.find('BulkApplyTemplateModal').exists()).toBeFalsy();
+});
+
+function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
+ return shallow(
+ <Search
+ onAllDeselected={jest.fn()}
+ onAllSelected={jest.fn()}
+ onDeleteProjects={jest.fn()}
+ onQualifierChanged={jest.fn()}
+ onSearch={jest.fn()}
+ onTypeChanged={jest.fn()}
+ organization={organization}
+ projects={[]}
+ qualifiers="TRK"
+ query=""
+ ready={true}
+ selection={[]}
+ topLevelQualifiers={['TRK']}
+ total={0}
+ type={Type.All}
+ {...props}
+ />
+ );
+}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`bulk applies template to all results 1`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="permission_templates.bulk_apply_permission_template"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ permission_templates.bulk_apply_permission_template
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <i
+ className="spinner"
+ />
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</Modal>
+`;
+
+exports[`bulk applies template to all results 2`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="permission_templates.bulk_apply_permission_template"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ permission_templates.bulk_apply_permission_template
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="alert alert-warning"
+ >
+ permission_templates.bulk_apply_permission_template.apply_to_all.17
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label>
+ template
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <Select
+ addLabelText="Add \\"{label}\\"?"
+ arrowRenderer={[Function]}
+ autosize={true}
+ backspaceRemoves={true}
+ backspaceToRemoveMessage="Press backspace to remove {label}"
+ clearAllText="Clear all"
+ clearRenderer={[Function]}
+ clearValueText="Clear value"
+ clearable={false}
+ deleteRemoves={true}
+ delimiter=","
+ disabled={false}
+ escapeClearsValue={true}
+ filterOptions={[Function]}
+ ignoreAccents={true}
+ ignoreCase={true}
+ inputProps={Object {}}
+ isLoading={false}
+ joinValues={false}
+ labelKey="label"
+ matchPos="any"
+ matchProp="any"
+ menuBuffer={0}
+ menuRenderer={[Function]}
+ multi={false}
+ noResultsText="No results found"
+ onBlurResetsInput={true}
+ onChange={[Function]}
+ onCloseResetsInput={true}
+ optionComponent={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "Foo",
+ "value": "foo",
+ },
+ Object {
+ "label": "Bar",
+ "value": "bar",
+ },
+ ]
+ }
+ pageSize={5}
+ placeholder="Select..."
+ required={false}
+ scrollMenuIntoView={true}
+ searchable={false}
+ simpleValue={false}
+ tabSelectsValue={true}
+ value="foo"
+ valueComponent={[Function]}
+ valueKey="value"
+ />
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <button
+ disabled={false}
+ onClick={[Function]}
+ >
+ apply
+ </button>
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</Modal>
+`;
+
+exports[`bulk applies template to all results 3`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="permission_templates.bulk_apply_permission_template"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ permission_templates.bulk_apply_permission_template
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="alert alert-warning"
+ >
+ permission_templates.bulk_apply_permission_template.apply_to_all.17
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label>
+ template
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <Select
+ addLabelText="Add \\"{label}\\"?"
+ arrowRenderer={[Function]}
+ autosize={true}
+ backspaceRemoves={true}
+ backspaceToRemoveMessage="Press backspace to remove {label}"
+ clearAllText="Clear all"
+ clearRenderer={[Function]}
+ clearValueText="Clear value"
+ clearable={false}
+ deleteRemoves={true}
+ delimiter=","
+ disabled={true}
+ escapeClearsValue={true}
+ filterOptions={[Function]}
+ ignoreAccents={true}
+ ignoreCase={true}
+ inputProps={Object {}}
+ isLoading={false}
+ joinValues={false}
+ labelKey="label"
+ matchPos="any"
+ matchProp="any"
+ menuBuffer={0}
+ menuRenderer={[Function]}
+ multi={false}
+ noResultsText="No results found"
+ onBlurResetsInput={true}
+ onChange={[Function]}
+ onCloseResetsInput={true}
+ optionComponent={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "Foo",
+ "value": "foo",
+ },
+ Object {
+ "label": "Bar",
+ "value": "bar",
+ },
+ ]
+ }
+ pageSize={5}
+ placeholder="Select..."
+ required={false}
+ scrollMenuIntoView={true}
+ searchable={false}
+ simpleValue={false}
+ tabSelectsValue={true}
+ value="foo"
+ valueComponent={[Function]}
+ valueKey="value"
+ />
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <i
+ className="spinner spacer-right"
+ />
+ <button
+ disabled={true}
+ onClick={[Function]}
+ >
+ apply
+ </button>
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</Modal>
+`;
+
+exports[`bulk applies template to all results 4`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="permission_templates.bulk_apply_permission_template"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ permission_templates.bulk_apply_permission_template
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="alert alert-success"
+ >
+ projects_role.apply_template.success
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ close
+ </a>
+ </footer>
+</Modal>
+`;
+
+exports[`bulk applies template to selected results 1`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="permission_templates.bulk_apply_permission_template"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ permission_templates.bulk_apply_permission_template
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <i
+ className="spinner"
+ />
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</Modal>
+`;
+
+exports[`bulk applies template to selected results 2`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="permission_templates.bulk_apply_permission_template"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ permission_templates.bulk_apply_permission_template
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="alert alert-info"
+ >
+ permission_templates.bulk_apply_permission_template.apply_to_selected.2
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label>
+ template
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <Select
+ addLabelText="Add \\"{label}\\"?"
+ arrowRenderer={[Function]}
+ autosize={true}
+ backspaceRemoves={true}
+ backspaceToRemoveMessage="Press backspace to remove {label}"
+ clearAllText="Clear all"
+ clearRenderer={[Function]}
+ clearValueText="Clear value"
+ clearable={false}
+ deleteRemoves={true}
+ delimiter=","
+ disabled={false}
+ escapeClearsValue={true}
+ filterOptions={[Function]}
+ ignoreAccents={true}
+ ignoreCase={true}
+ inputProps={Object {}}
+ isLoading={false}
+ joinValues={false}
+ labelKey="label"
+ matchPos="any"
+ matchProp="any"
+ menuBuffer={0}
+ menuRenderer={[Function]}
+ multi={false}
+ noResultsText="No results found"
+ onBlurResetsInput={true}
+ onChange={[Function]}
+ onCloseResetsInput={true}
+ optionComponent={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "Foo",
+ "value": "foo",
+ },
+ Object {
+ "label": "Bar",
+ "value": "bar",
+ },
+ ]
+ }
+ pageSize={5}
+ placeholder="Select..."
+ required={false}
+ scrollMenuIntoView={true}
+ searchable={false}
+ simpleValue={false}
+ tabSelectsValue={true}
+ value="foo"
+ valueComponent={[Function]}
+ valueKey="value"
+ />
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <button
+ disabled={false}
+ onClick={[Function]}
+ >
+ apply
+ </button>
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</Modal>
+`;
+
+exports[`bulk applies template to selected results 3`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="permission_templates.bulk_apply_permission_template"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ permission_templates.bulk_apply_permission_template
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="alert alert-info"
+ >
+ permission_templates.bulk_apply_permission_template.apply_to_selected.2
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label>
+ template
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <Select
+ addLabelText="Add \\"{label}\\"?"
+ arrowRenderer={[Function]}
+ autosize={true}
+ backspaceRemoves={true}
+ backspaceToRemoveMessage="Press backspace to remove {label}"
+ clearAllText="Clear all"
+ clearRenderer={[Function]}
+ clearValueText="Clear value"
+ clearable={false}
+ deleteRemoves={true}
+ delimiter=","
+ disabled={true}
+ escapeClearsValue={true}
+ filterOptions={[Function]}
+ ignoreAccents={true}
+ ignoreCase={true}
+ inputProps={Object {}}
+ isLoading={false}
+ joinValues={false}
+ labelKey="label"
+ matchPos="any"
+ matchProp="any"
+ menuBuffer={0}
+ menuRenderer={[Function]}
+ multi={false}
+ noResultsText="No results found"
+ onBlurResetsInput={true}
+ onChange={[Function]}
+ onCloseResetsInput={true}
+ optionComponent={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "Foo",
+ "value": "foo",
+ },
+ Object {
+ "label": "Bar",
+ "value": "bar",
+ },
+ ]
+ }
+ pageSize={5}
+ placeholder="Select..."
+ required={false}
+ scrollMenuIntoView={true}
+ searchable={false}
+ simpleValue={false}
+ tabSelectsValue={true}
+ value="foo"
+ valueComponent={[Function]}
+ valueKey="value"
+ />
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <i
+ className="spinner spacer-right"
+ />
+ <button
+ disabled={true}
+ onClick={[Function]}
+ >
+ apply
+ </button>
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</Modal>
+`;
+
+exports[`bulk applies template to selected results 4`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="permission_templates.bulk_apply_permission_template"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ permission_templates.bulk_apply_permission_template
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="alert alert-success"
+ >
+ projects_role.apply_template.success
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ close
+ </a>
+ </footer>
+</Modal>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`changes visibility 1`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="modal form"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ organization.change_visibility_form.header
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="big-spacer-bottom"
+ >
+ <p>
+ <a
+ className="link-base-color link-no-underline"
+ data-visibility="public"
+ href="#"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio spacer-right is-checked"
+ />
+ visibility.public
+ </a>
+ </p>
+ <p
+ className="text-muted spacer-top"
+ style={
+ Object {
+ "paddingLeft": 22,
+ }
+ }
+ >
+ visibility.public.description.short
+ </p>
+ </div>
+ <div
+ className="big-spacer-bottom"
+ >
+ <p>
+ <a
+ className="link-base-color link-no-underline"
+ data-visibility="private"
+ href="#"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio spacer-right"
+ />
+ visibility.private
+ </a>
+ </p>
+ <p
+ className="text-muted spacer-top"
+ style={
+ Object {
+ "paddingLeft": 22,
+ }
+ }
+ >
+ visibility.private.description.short
+ </p>
+ </div>
+ <div
+ className="alert alert-warning"
+ >
+ organization.change_visibility_form.warning
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <button
+ className="js-confirm"
+ onClick={[Function]}
+ >
+ organization.change_visibility_form.submit
+ </button>
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</Modal>
+`;
+
+exports[`changes visibility 2`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="modal form"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ organization.change_visibility_form.header
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="big-spacer-bottom"
+ >
+ <p>
+ <a
+ className="link-base-color link-no-underline"
+ data-visibility="public"
+ href="#"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio spacer-right"
+ />
+ visibility.public
+ </a>
+ </p>
+ <p
+ className="text-muted spacer-top"
+ style={
+ Object {
+ "paddingLeft": 22,
+ }
+ }
+ >
+ visibility.public.description.short
+ </p>
+ </div>
+ <div
+ className="big-spacer-bottom"
+ >
+ <p>
+ <a
+ className="link-base-color link-no-underline"
+ data-visibility="private"
+ href="#"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio spacer-right is-checked"
+ />
+ visibility.private
+ </a>
+ </p>
+ <p
+ className="text-muted spacer-top"
+ style={
+ Object {
+ "paddingLeft": 22,
+ }
+ }
+ >
+ visibility.private.description.short
+ </p>
+ </div>
+ <div
+ className="alert alert-warning"
+ >
+ organization.change_visibility_form.warning
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <button
+ className="js-confirm"
+ onClick={[Function]}
+ >
+ organization.change_visibility_form.submit
+ </button>
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</Modal>
+`;
+
+exports[`renders disabled 1`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="modal form"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ organization.change_visibility_form.header
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="big-spacer-bottom"
+ >
+ <p>
+ <a
+ className="link-base-color link-no-underline"
+ data-visibility="public"
+ href="#"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio spacer-right is-checked"
+ />
+ visibility.public
+ </a>
+ </p>
+ <p
+ className="text-muted spacer-top"
+ style={
+ Object {
+ "paddingLeft": 22,
+ }
+ }
+ >
+ visibility.public.description.short
+ </p>
+ </div>
+ <div
+ className="big-spacer-bottom"
+ >
+ <p>
+ <span
+ className="text-muted cursor-not-allowed"
+ >
+ <i
+ className="icon-radio spacer-right"
+ />
+ visibility.private
+ </span>
+ </p>
+ <p
+ className="text-muted spacer-top"
+ style={
+ Object {
+ "paddingLeft": 22,
+ }
+ }
+ >
+ visibility.private.description.short
+ </p>
+ </div>
+ <UpgradeOrganizationBox
+ organization="org"
+ />
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <button
+ className="js-confirm"
+ onClick={[Function]}
+ >
+ organization.change_visibility_form.submit
+ </button>
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</Modal>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`creates project 1`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="modal form"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <form
+ id="create-project-form"
+ onSubmit={[Function]}
+ >
+ <header
+ className="modal-head"
+ >
+ <h2>
+ qualifiers.create.TRK
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-project-name"
+ >
+ name
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <input
+ autoFocus={true}
+ id="create-project-name"
+ maxLength={2000}
+ name="name"
+ onChange={[Function]}
+ required={true}
+ type="text"
+ value=""
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-project-branch"
+ >
+ branch
+ </label>
+ <input
+ id="create-project-branch"
+ maxLength={200}
+ name="branch"
+ onChange={[Function]}
+ type="text"
+ value=""
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-project-key"
+ >
+ key
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <input
+ id="create-project-key"
+ maxLength={400}
+ name="key"
+ onChange={[Function]}
+ required={true}
+ type="text"
+ value=""
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label>
+ visibility
+ </label>
+ <VisibilitySelector
+ className="little-spacer-top"
+ onChange={[Function]}
+ visibility="public"
+ />
+ <div
+ className="spacer-top"
+ >
+ <UpgradeOrganizationBox
+ organization="org"
+ />
+ </div>
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <button
+ disabled={false}
+ id="create-project-submit"
+ type="submit"
+ >
+ create
+ </button>
+ <a
+ href="#"
+ id="create-project-cancel"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+ </form>
+</Modal>
+`;
+
+exports[`creates project 2`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="modal form"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <form
+ id="create-project-form"
+ onSubmit={[Function]}
+ >
+ <header
+ className="modal-head"
+ >
+ <h2>
+ qualifiers.create.TRK
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-project-name"
+ >
+ name
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <input
+ autoFocus={true}
+ id="create-project-name"
+ maxLength={2000}
+ name="name"
+ onChange={[Function]}
+ required={true}
+ type="text"
+ value="name"
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-project-branch"
+ >
+ branch
+ </label>
+ <input
+ id="create-project-branch"
+ maxLength={200}
+ name="branch"
+ onChange={[Function]}
+ type="text"
+ value="branch"
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-project-key"
+ >
+ key
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <input
+ id="create-project-key"
+ maxLength={400}
+ name="key"
+ onChange={[Function]}
+ required={true}
+ type="text"
+ value="key"
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label>
+ visibility
+ </label>
+ <VisibilitySelector
+ className="little-spacer-top"
+ onChange={[Function]}
+ visibility="private"
+ />
+ <div
+ className="spacer-top"
+ >
+ <UpgradeOrganizationBox
+ organization="org"
+ />
+ </div>
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <button
+ disabled={false}
+ id="create-project-submit"
+ type="submit"
+ >
+ create
+ </button>
+ <a
+ href="#"
+ id="create-project-cancel"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+ </form>
+</Modal>
+`;
+
+exports[`creates project 3`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="modal form"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <form
+ id="create-project-form"
+ onSubmit={[Function]}
+ >
+ <header
+ className="modal-head"
+ >
+ <h2>
+ qualifiers.create.TRK
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-project-name"
+ >
+ name
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <input
+ autoFocus={true}
+ id="create-project-name"
+ maxLength={2000}
+ name="name"
+ onChange={[Function]}
+ required={true}
+ type="text"
+ value="name"
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-project-branch"
+ >
+ branch
+ </label>
+ <input
+ id="create-project-branch"
+ maxLength={200}
+ name="branch"
+ onChange={[Function]}
+ type="text"
+ value="branch"
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-project-key"
+ >
+ key
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <input
+ id="create-project-key"
+ maxLength={400}
+ name="key"
+ onChange={[Function]}
+ required={true}
+ type="text"
+ value="key"
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label>
+ visibility
+ </label>
+ <VisibilitySelector
+ className="little-spacer-top"
+ onChange={[Function]}
+ visibility="private"
+ />
+ <div
+ className="spacer-top"
+ >
+ <UpgradeOrganizationBox
+ organization="org"
+ />
+ </div>
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <i
+ className="spinner spacer-right"
+ />
+ <button
+ disabled={true}
+ id="create-project-submit"
+ type="submit"
+ >
+ create
+ </button>
+ <a
+ href="#"
+ id="create-project-cancel"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+ </form>
+</Modal>
+`;
+
+exports[`creates project 4`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="modal form"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <div>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ qualifiers.create.TRK
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="alert alert-success"
+ >
+ Project
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "id": "name",
+ },
+ }
+ }
+ >
+ name
+ </Link>
+
+ has been successfully created.
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <a
+ href="#"
+ id="create-project-close"
+ onClick={[Function]}
+ >
+ close
+ </a>
+ </footer>
+ </div>
+</Modal>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`deletes projects 1`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="qualifiers.delete.TRK"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ qualifiers.delete.TRK
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ qualifiers.delete_confirm.TRK
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <button
+ className="button-red"
+ disabled={false}
+ onClick={[Function]}
+ >
+ delete
+ </button>
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</Modal>
+`;
+
+exports[`deletes projects 2`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="qualifiers.delete.TRK"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ qualifiers.delete.TRK
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ qualifiers.delete_confirm.TRK
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <i
+ className="spinner spacer-right"
+ />
+ <button
+ className="button-red"
+ disabled={true}
+ onClick={[Function]}
+ >
+ delete
+ </button>
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</Modal>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`changes default visibility 1`] = `
+<ChangeVisibilityForm
+ onClose={[Function]}
+ onConfirm={[Function]}
+ organization={
+ Object {
+ "key": "org",
+ "name": "org",
+ "projectVisibility": "public",
+ }
+ }
+/>
+`;
+
+exports[`renders 1`] = `
+<header
+ className="page-header"
+>
+ <h1
+ className="page-title"
+ >
+ projects_management
+ </h1>
+ <div
+ className="page-actions"
+ >
+ <span
+ className="big-spacer-right"
+ >
+ organization.default_visibility_of_new_projects
+
+ <strong>
+ visibility.public
+ </strong>
+ <a
+ className="js-change-visibility spacer-left icon-edit"
+ href="#"
+ onClick={[Function]}
+ />
+ </span>
+ <button
+ id="create-project"
+ onClick={[Function]}
+ >
+ qualifiers.create.TRK
+ </button>
+ </div>
+ <p
+ className="page-description"
+ >
+ projects_management.page.description
+ </p>
+</header>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<tr>
+ <td
+ className="thin"
+ >
+ <Checkbox
+ checked={true}
+ onCheck={[Function]}
+ thirdState={false}
+ />
+ </td>
+ <td
+ className="nowrap"
+ >
+ <Link
+ className="link-with-icon"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "id": "project",
+ },
+ }
+ }
+ >
+ <QualifierIcon
+ qualifier="TRK"
+ />
+
+ <span>
+ Project
+ </span>
+ </Link>
+ </td>
+ <td
+ className="nowrap"
+ >
+ <span
+ className="note"
+ >
+ project
+ </span>
+ </td>
+ <td
+ className="width-20"
+ >
+ <PrivateBadge />
+ </td>
+ <td
+ className="thin nowrap"
+ >
+ <div
+ className="dropdown"
+ >
+ <button
+ className="dropdown-toggle"
+ data-toggle="dropdown"
+ >
+ actions
+
+ <i
+ className="icon-dropdown"
+ />
+ </button>
+ <ul
+ className="dropdown-menu dropdown-menu-right"
+ >
+ <li>
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project_roles",
+ "query": Object {
+ "id": "project",
+ },
+ }
+ }
+ >
+ edit_permissions
+ </Link>
+ </li>
+ <li>
+ <a
+ className="js-apply-template"
+ href="#"
+ onClick={[Function]}
+ >
+ projects_role.apply_template
+ </a>
+ </li>
+ </ul>
+ </div>
+ </td>
+</tr>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders list of projects 1`] = `
+<table
+ className="data zebra new-loading"
+ id="projects-management-page-projects"
+>
+ <tbody>
+ <ProjectRow
+ onApplyTemplateClick={[Function]}
+ onProjectCheck={[Function]}
+ project={
+ Object {
+ "key": "a",
+ "name": "A",
+ "qualifier": "TRK",
+ "visibility": "public",
+ }
+ }
+ selected={true}
+ />
+ <ProjectRow
+ onApplyTemplateClick={[Function]}
+ onProjectCheck={[Function]}
+ project={
+ Object {
+ "key": "b",
+ "name": "B",
+ "qualifier": "TRK",
+ "visibility": "public",
+ }
+ }
+ selected={false}
+ />
+ </tbody>
+</table>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`bulk applies permission template 1`] = `
+<BulkApplyTemplateModal
+ onClose={[Function]}
+ organization="org"
+ qualifier="TRK"
+ query=""
+ selection={Array []}
+ total={0}
+ type="ALL"
+/>
+`;
+
+exports[`deletes projects 1`] = `
+<DeleteModal
+ onClose={[Function]}
+ onConfirm={[Function]}
+ organization="org"
+ qualifier="TRK"
+ selection={
+ Array [
+ "foo",
+ "bar",
+ ]
+ }
+/>
+`;
+
+exports[`render qualifiers filter 1`] = `
+<div
+ className="panel panel-vertical bordered-bottom spacer-bottom"
+>
+ <table
+ className="data"
+ >
+ <tbody>
+ <tr>
+ <td
+ className="thin text-middle"
+ >
+ <Checkbox
+ checked={false}
+ onCheck={[Function]}
+ thirdState={false}
+ />
+ </td>
+ <td
+ className="thin nowrap text-middle"
+ >
+ <RadioToggle
+ disabled={false}
+ name="projects-qualifier"
+ onCheck={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "qualifiers.TRK",
+ "value": "TRK",
+ },
+ Object {
+ "label": "qualifiers.VW",
+ "value": "VW",
+ },
+ Object {
+ "label": "qualifiers.APP",
+ "value": "APP",
+ },
+ ]
+ }
+ value="TRK"
+ />
+ </td>
+ <td
+ className="thin nowrap text-middle"
+ >
+ <RadioToggle
+ disabled={false}
+ name="projects-type"
+ onCheck={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "All",
+ "value": "ALL",
+ },
+ Object {
+ "label": "Provisioned",
+ "value": "PROVISIONED",
+ },
+ Object {
+ "label": "Ghosts",
+ "value": "GHOSTS",
+ },
+ ]
+ }
+ value="ALL"
+ />
+ </td>
+ <td
+ className="text-middle"
+ >
+ <form
+ className="search-box"
+ onSubmit={[Function]}
+ >
+ <button
+ className="search-box-submit button-clean"
+ >
+ <i
+ className="icon-search"
+ />
+ </button>
+ <input
+ className="search-box-input input-medium"
+ onChange={[Function]}
+ placeholder="Search"
+ type="search"
+ value=""
+ />
+ </form>
+ </td>
+ <td
+ className="thin nowrap text-middle"
+ >
+ <button
+ className="spacer-right js-bulk-apply-permission-template"
+ onClick={[Function]}
+ >
+ permission_templates.bulk_apply_permission_template
+ </button>
+ <button
+ className="js-delete button-red"
+ disabled={true}
+ onClick={[Function]}
+ >
+ delete
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+`;
+
+exports[`renders 1`] = `
+<div
+ className="panel panel-vertical bordered-bottom spacer-bottom"
+>
+ <table
+ className="data"
+ >
+ <tbody>
+ <tr>
+ <td
+ className="thin text-middle"
+ >
+ <Checkbox
+ checked={false}
+ onCheck={[Function]}
+ thirdState={false}
+ />
+ </td>
+ <td
+ className="thin nowrap text-middle"
+ >
+ <RadioToggle
+ disabled={false}
+ name="projects-type"
+ onCheck={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "All",
+ "value": "ALL",
+ },
+ Object {
+ "label": "Provisioned",
+ "value": "PROVISIONED",
+ },
+ Object {
+ "label": "Ghosts",
+ "value": "GHOSTS",
+ },
+ ]
+ }
+ value="ALL"
+ />
+ </td>
+ <td
+ className="text-middle"
+ >
+ <form
+ className="search-box"
+ onSubmit={[Function]}
+ >
+ <button
+ className="search-box-submit button-clean"
+ >
+ <i
+ className="icon-search"
+ />
+ </button>
+ <input
+ className="search-box-input input-medium"
+ onChange={[Function]}
+ placeholder="Search"
+ type="search"
+ value=""
+ />
+ </form>
+ </td>
+ <td
+ className="thin nowrap text-middle"
+ >
+ <button
+ className="spacer-right js-bulk-apply-permission-template"
+ onClick={[Function]}
+ >
+ permission_templates.bulk_apply_permission_template
+ </button>
+ <button
+ className="js-delete button-red"
+ disabled={true}
+ onClick={[Function]}
+ >
+ delete
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { RouterState, IndexRouteProps } from 'react-router';
+
+const routes = [
+ {
+ getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+ Promise.all([
+ import('./AppContainer').then(i => i.default),
+ import('../organizations/forSingleOrganization').then(i => i.default)
+ ]).then(([AppContainer, forSingleOrganization]) =>
+ callback(null, { component: forSingleOrganization(AppContainer) })
+ );
+ }
+ }
+];
+
+export default routes;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+export const PAGE_SIZE = 50;
+
+export const QUALIFIERS_ORDER = ['TRK', 'VW', 'APP', 'DEV'];
+
+export enum Type {
+ All = 'ALL',
+ Provisioned = 'PROVISIONED',
+ Ghosts = 'GHOSTS'
+}
+
+export interface Project {
+ key: string;
+ name: string;
+ qualifier: string;
+ visibility: Visibility;
+}
+
+export enum Visibility {
+ Public = 'public',
+ Private = 'private'
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import Tooltip from '../controls/Tooltip';
-import { translate } from '../../helpers/l10n';
-
-/*::
-type Props = {
- className?: string,
- tooltipPlacement?: string
-};
-*/
-
-export default function PrivateBadge({ className, tooltipPlacement = 'bottom' } /*: Props */) {
- return (
- <Tooltip overlay={translate('visibility.private.description')} placement={tooltipPlacement}>
- <div className={classNames('outline-badge', className)}>
- {translate('visibility.private')}
- </div>
- </Tooltip>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import Tooltip from '../controls/Tooltip';
+import { translate } from '../../helpers/l10n';
+
+interface Props {
+ className?: string;
+ tooltipPlacement?: string;
+}
+
+export default function PrivateBadge({ className, tooltipPlacement = 'bottom' }: Props) {
+ return (
+ <Tooltip overlay={translate('visibility.private.description')} placement={tooltipPlacement}>
+ <div className={classNames('outline-badge', className)}>
+ {translate('visibility.private')}
+ </div>
+ </Tooltip>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import { translate } from '../../helpers/l10n';
-
-/*::
-type Props = {|
- canTurnToPrivate: boolean,
- className?: string,
- onChange: string => void,
- visibility: string
-|};
-*/
-
-export default class VisibilitySelector extends React.PureComponent {
- /*:: props: Props; */
-
- handlePublicClick = (event /*: Event & { currentTarget: HTMLElement } */) => {
- event.preventDefault();
- event.currentTarget.blur();
- this.props.onChange('public');
- };
-
- handlePrivateClick = (event /*: Event & { currentTarget: HTMLElement } */) => {
- event.preventDefault();
- event.currentTarget.blur();
- this.props.onChange('private');
- };
-
- render() {
- return (
- <div className={this.props.className}>
- <a
- className="link-base-color link-no-underline"
- id="visibility-public"
- href="#"
- onClick={this.handlePublicClick}>
- <i
- className={classNames('icon-radio', {
- 'is-checked': this.props.visibility === 'public'
- })}
- />
- <span className="spacer-left">
- {translate('visibility.public')}
- </span>
- </a>
-
- {this.props.canTurnToPrivate
- ? <a
- className="link-base-color link-no-underline huge-spacer-left"
- id="visibility-private"
- href="#"
- onClick={this.handlePrivateClick}>
- <i
- className={classNames('icon-radio', {
- 'is-checked': this.props.visibility === 'private'
- })}
- />
- <span className="spacer-left">
- {translate('visibility.private')}
- </span>
- </a>
- : <span
- className="huge-spacer-left text-muted cursor-not-allowed"
- id="visibility-private">
- <i
- className={classNames('icon-radio', {
- 'is-checked': this.props.visibility === 'private'
- })}
- />
- <span className="spacer-left">
- {translate('visibility.private')}
- </span>
- </span>}
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { translate } from '../../helpers/l10n';
+
+interface Props {
+ canTurnToPrivate?: boolean;
+ className?: string;
+ onChange: (x: string) => void;
+ visibility: string;
+}
+
+export default class VisibilitySelector extends React.PureComponent<Props> {
+ handlePublicClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.props.onChange('public');
+ };
+
+ handlePrivateClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.props.onChange('private');
+ };
+
+ render() {
+ return (
+ <div className={this.props.className}>
+ <a
+ className="link-base-color link-no-underline"
+ id="visibility-public"
+ href="#"
+ onClick={this.handlePublicClick}>
+ <i
+ className={classNames('icon-radio', {
+ 'is-checked': this.props.visibility === 'public'
+ })}
+ />
+ <span className="spacer-left">
+ {translate('visibility.public')}
+ </span>
+ </a>
+
+ {this.props.canTurnToPrivate
+ ? <a
+ className="link-base-color link-no-underline huge-spacer-left"
+ id="visibility-private"
+ href="#"
+ onClick={this.handlePrivateClick}>
+ <i
+ className={classNames('icon-radio', {
+ 'is-checked': this.props.visibility === 'private'
+ })}
+ />
+ <span className="spacer-left">
+ {translate('visibility.private')}
+ </span>
+ </a>
+ : <span
+ className="huge-spacer-left text-muted cursor-not-allowed"
+ id="visibility-private">
+ <i
+ className={classNames('icon-radio', {
+ 'is-checked': this.props.visibility === 'private'
+ })}
+ />
+ <span className="spacer-left">
+ {translate('visibility.private')}
+ </span>
+ </span>}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 { shallow } from 'enzyme';
+import PrivateBadge from '../PrivateBadge';
+
+it('renders', () => {
+ expect(shallow(<PrivateBadge />)).toMatchSnapshot();
+});
--- /dev/null
+/*
+ * 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 { shallow } from 'enzyme';
+import VisibilitySelector from '../VisibilitySelector';
+import { click } from '../../../helpers/testUtils';
+
+it('changes visibility', () => {
+ const onChange = jest.fn();
+ const wrapper = shallow(
+ <VisibilitySelector canTurnToPrivate={true} onChange={onChange} visibility="public" />
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('#visibility-private'));
+ expect(onChange).toBeCalledWith('private');
+
+ wrapper.setProps({ visibility: 'private' });
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('#visibility-public'));
+ expect(onChange).toBeCalledWith('public');
+});
+
+it('renders disabled', () => {
+ expect(
+ shallow(
+ <VisibilitySelector canTurnToPrivate={false} onChange={jest.fn()} visibility="public" />
+ )
+ ).toMatchSnapshot();
+});
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<Tooltip
+ overlay="visibility.private.description"
+ placement="bottom"
+>
+ <div
+ className="outline-badge"
+ >
+ visibility.private
+ </div>
+</Tooltip>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`changes visibility 1`] = `
+<div>
+ <a
+ className="link-base-color link-no-underline"
+ href="#"
+ id="visibility-public"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio is-checked"
+ />
+ <span
+ className="spacer-left"
+ >
+ visibility.public
+ </span>
+ </a>
+ <a
+ className="link-base-color link-no-underline huge-spacer-left"
+ href="#"
+ id="visibility-private"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio"
+ />
+ <span
+ className="spacer-left"
+ >
+ visibility.private
+ </span>
+ </a>
+</div>
+`;
+
+exports[`changes visibility 2`] = `
+<div>
+ <a
+ className="link-base-color link-no-underline"
+ href="#"
+ id="visibility-public"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio"
+ />
+ <span
+ className="spacer-left"
+ >
+ visibility.public
+ </span>
+ </a>
+ <a
+ className="link-base-color link-no-underline huge-spacer-left"
+ href="#"
+ id="visibility-private"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio is-checked"
+ />
+ <span
+ className="spacer-left"
+ >
+ visibility.private
+ </span>
+ </a>
+</div>
+`;
+
+exports[`renders disabled 1`] = `
+<div>
+ <a
+ className="link-base-color link-no-underline"
+ href="#"
+ id="visibility-public"
+ onClick={[Function]}
+ >
+ <i
+ className="icon-radio is-checked"
+ />
+ <span
+ className="spacer-left"
+ >
+ visibility.public
+ </span>
+ </a>
+ <span
+ className="huge-spacer-left text-muted cursor-not-allowed"
+ id="visibility-private"
+ >
+ <i
+ className="icon-radio"
+ />
+ <span
+ className="spacer-left"
+ >
+ visibility.private
+ </span>
+ </span>
+</div>
+`;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class RadioToggle extends React.PureComponent {
- static propTypes = {
- value: PropTypes.string,
- options: PropTypes.arrayOf(
- PropTypes.shape({
- value: PropTypes.string.isRequired,
- label: PropTypes.string.isRequired
- })
- ).isRequired,
- name: PropTypes.string.isRequired,
- onCheck: PropTypes.func.isRequired
- };
-
- static defaultProps = {
- disabled: false,
- value: null
- };
-
- componentWillMount() {
- this.renderOption = this.renderOption.bind(this);
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(e) {
- const newValue = e.currentTarget.value;
- this.props.onCheck(newValue);
- }
-
- renderOption(option) {
- const checked = option.value === this.props.value;
- const htmlId = this.props.name + '__' + option.value;
- return (
- <li key={option.value}>
- <input
- type="radio"
- name={this.props.name}
- value={option.value}
- id={htmlId}
- checked={checked}
- onChange={this.handleChange}
- />
-
- <label htmlFor={htmlId}>
- {option.label}
- </label>
- </li>
- );
- }
-
- render() {
- return (
- <ul className="radio-toggle">
- {this.props.options.map(this.renderOption)}
- </ul>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+
+interface Props {
+ name: string;
+ onCheck: (value: string) => void;
+ options: Array<{ label: string; value: string }>;
+ value?: string;
+}
+
+export default class RadioToggle extends React.PureComponent<Props> {
+ static defaultProps = {
+ disabled: false,
+ value: null
+ };
+
+ handleChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
+ const newValue = e.currentTarget.value;
+ this.props.onCheck(newValue);
+ };
+
+ renderOption = (option: { label: string; value: string }) => {
+ const checked = option.value === this.props.value;
+ const htmlId = this.props.name + '__' + option.value;
+ return (
+ <li key={option.value}>
+ <input
+ type="radio"
+ name={this.props.name}
+ value={option.value}
+ id={htmlId}
+ checked={checked}
+ onChange={this.handleChange}
+ />
+
+ <label htmlFor={htmlId}>
+ {option.label}
+ </label>
+ </li>
+ );
+ };
+
+ render() {
+ return (
+ <ul className="radio-toggle">
+ {this.props.options.map(this.renderOption)}
+ </ul>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import React from 'react';
-import RadioToggle from '../RadioToggle';
-import { change } from '../../../helpers/testUtils';
-
-function getSample(props) {
- const options = [{ value: 'one', label: 'first' }, { value: 'two', label: 'second' }];
- return <RadioToggle options={options} name="sample" onCheck={() => true} {...props} />;
-}
-
-it('should render', () => {
- const radioToggle = shallow(getSample());
- expect(radioToggle.find('input[type="radio"]').length).toBe(2);
- expect(radioToggle.find('label').length).toBe(2);
-});
-
-it('should call onCheck', () => {
- const onCheck = jest.fn();
- const radioToggle = shallow(getSample({ onCheck }));
- change(radioToggle.find('input[value="two"]'), 'two');
- expect(onCheck).toBeCalledWith('two');
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import RadioToggle from '../RadioToggle';
+import { change } from '../../../helpers/testUtils';
+
+it('renders', () => {
+ expect(shallow(getSample())).toMatchSnapshot();
+});
+
+it('calls onCheck', () => {
+ const onCheck = jest.fn();
+ const wrapper = shallow(getSample({ onCheck }));
+ change(wrapper.find('input[value="two"]'), 'two');
+ expect(onCheck).toBeCalledWith('two');
+});
+
+function getSample(props?: any) {
+ const options = [{ value: 'one', label: 'first' }, { value: 'two', label: 'second' }];
+ return <RadioToggle options={options} name="sample" onCheck={() => true} {...props} />;
+}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<ul
+ className="radio-toggle"
+>
+ <li>
+ <input
+ checked={false}
+ id="sample__one"
+ name="sample"
+ onChange={[Function]}
+ type="radio"
+ value="one"
+ />
+ <label
+ htmlFor="sample__one"
+ >
+ first
+ </label>
+ </li>
+ <li>
+ <input
+ checked={false}
+ id="sample__two"
+ name="sample"
+ onChange={[Function]}
+ type="radio"
+ value="two"
+ />
+ <label
+ htmlFor="sample__two"
+ >
+ second
+ </label>
+ </li>
+</ul>
+`;
});
}
-export function change(element: ShallowWrapper, value: string): void {
+export function change(element: ShallowWrapper, value: string, event = {}): void {
element.simulate('change', {
target: { value },
- currentTarget: { value }
+ currentTarget: { value },
+ ...event
});
}
qualifiers.new.DEV=New Developer
qualifiers.new.APP=New Application
-qualifiers.delete.TRK=Delete Project
-qualifiers.delete.VW=Delete Portfolio
-qualifiers.delete.DEV=Delete Developer
-qualifiers.delete.APP=Delete Application
+qualifier.delete.TRK=Delete Project
+qualifier.delete.VW=Delete Portfolio
+qualifier.delete.APP=Delete Application
-qualifiers.delete_confirm.TRK=Do you want to delete this project?
-qualifiers.delete_confirm.VW=Do you want to delete this portfolio?
-qualifiers.delete_confirm.DEV=Do you want to delete this developer?
-qualifiers.delete_confirm.APP=Do you want to delete this application?
+qualifiers.delete.TRK=Delete Projects
+qualifiers.delete.VW=Delete Portfolios
+qualifiers.delete.APP=Delete Applications
+
+qualifier.delete_confirm.TRK=Do you want to delete this project?
+qualifier.delete_confirm.VW=Do you want to delete this portfolio?
+qualifier.delete_confirm.APP=Do you want to delete this application?
+
+qualifiers.delete_confirm.TRK=Do you want to delete these projects?
+qualifiers.delete_confirm.VW=Do you want to delete these portfolios?
+qualifiers.delete_confirm.APP=Do you want to delete these applications?
qualifiers.create.TRK=Create Project
qualifiers.create.VW=Create Portfolio
#------------------------------------------------------------------------------
project_deletion.delete_resource_confirmation=Are you sure you want to delete "{0}"?
+projects_management.delete_resource_confirmation=Are you sure you want to delete "{0}"?
#------------------------------------------------------------------------------
permission_templates.project_creators.explanation=When a new project is created, the user who creates the project will receive this permission on the project.
permission_templates.grant_permission_to_project_creators=Grant the "{0}" permission to project creators
permission_templates.bulk_apply_permission_template=Bulk Apply Permission Template
+permission_templates.bulk_apply_permission_template.apply_to_selected=You're about to apply the selected permission template to {0} selected item(s).
+permission_templates.bulk_apply_permission_template.apply_to_all=You're about to apply the selected permission template to {0} item(s).
#------------------------------------------------------------------------------
*/
package org.sonarqube.pageobjects;
-import com.codeborne.selenide.CollectionCondition;
-
import static com.codeborne.selenide.Condition.exist;
import static com.codeborne.selenide.Condition.text;
+import static com.codeborne.selenide.Condition.visible;
import static com.codeborne.selenide.Selenide.$;
import static com.codeborne.selenide.Selenide.$$;
}
public ProjectsManagementPage bulkApplyPermissionTemplate(String template) {
- $(".js-bulk-apply-permission-template").should(exist).click();
- $(".modal .select2-choice").should(exist).click();
- $$(".select2-results li")
- .shouldHave(CollectionCondition.sizeGreaterThan(0))
- .findBy(text("foo-template")).should(exist).click();
- $(".modal .js-apply").should(exist).click();
- $(".modal-body .alert-success").should(exist);
+ $(".js-bulk-apply-permission-template").click();
+ $(".modal .Select-value").click();
+ $$(".modal .Select-option").findBy(text(template)).click();
+ $(".modal-foot button").click();
+ $(".modal-body .alert-success").shouldBe(visible);
return this;
}
}
* SONAR-4709
*/
@Test
- public void organization_administrator_cannot_provision_project_if_he_doesnt_have_provisioning_permission() {
+ public void organization_administrator_cannot_provision_project_if_he_does_not_have_provisioning_permission() {
runSelenese(orchestrator, "/authorisation/ProvisioningPermissionTest/should-not-be-able-to-provision-project.html");
}
* SONAR-4709
*/
@Test
- public void user_cannot_provision_project_through_ws_if_he_doesnt_have_provisioning_permission() {
+ public void user_cannot_provision_project_through_ws_if_he_does_not_have_provisioning_permission() {
thrown.expect(HttpException.class);
thrown.expectMessage("403");
</tr>
<tr>
<td>open</td>
- <td>/projects_admin</td>
+ <td>/admin/projects_management</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
- <td>css=#projects-type__ALL</td>
+ <td>css=#create-project</td>
<td></td>
</tr>
- <tr>
- <td>assertText</td>
- <td>css=.page-actions button</td>
- <td>*Create Project*</td>
- </tr>
</tbody>
</table>
</body>
</tr>
<tr>
<td>open</td>
- <td>/projects_admin</td>
+ <td>/admin/projects_management</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
- <td>css=#projects-type__ALL</td>
+ <td>css=#projects-management-page</td>
<td></td>
</tr>
<tr>
- <td>assertNotText</td>
- <td>css=.page-actions button</td>
- <td>*Create Project*</td>
+ <td>assertElementNotPresent</td>
+ <td>css=#create-project</td>
+ <td></td>
</tr>
</tbody>
</table>