]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9167 Allow to set a visibility when creating a project
authorStas Vilchik <vilchiks@gmail.com>
Tue, 2 May 2017 08:15:46 +0000 (10:15 +0200)
committerStas Vilchik <stas-vilchik@users.noreply.github.com>
Tue, 2 May 2017 12:45:47 +0000 (14:45 +0200)
14 files changed:
it/it-tests/src/test/java/it/administration/ProjectsAdministrationTest.java
it/it-tests/src/test/java/pageobjects/Navigation.java
it/it-tests/src/test/java/pageobjects/ProjectsManagementPage.java [new file with mode: 0644]
server/sonar-web/src/main/js/apps/permissions/project/components/App.js
server/sonar-web/src/main/js/apps/permissions/project/components/VisibilitySelector.js [deleted file]
server/sonar-web/src/main/js/apps/projects-admin/AppContainer.js
server/sonar-web/src/main/js/apps/projects-admin/CreateProjectForm.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/create-view.js [deleted file]
server/sonar-web/src/main/js/apps/projects-admin/header.js
server/sonar-web/src/main/js/apps/projects-admin/main.js
server/sonar-web/src/main/js/apps/projects-admin/projects.js
server/sonar-web/src/main/js/apps/projects-admin/templates/projects-create-form.hbs [deleted file]
server/sonar-web/src/main/js/components/common/VisibilitySelector.js [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index c574563a4aead0dcddfeae3a9180ed3c1ca82f9f..b0c86666fa96fc1bf24baf8a763fb844e04fbef4 100644 (file)
@@ -27,13 +27,15 @@ import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.sonarqube.ws.WsComponents;
+import org.sonarqube.ws.client.component.SearchProjectsRequest;
 import org.sonarqube.ws.client.permission.RemoveGroupWsRequest;
 import org.sonarqube.ws.client.project.UpdateVisibilityRequest;
 import pageobjects.Navigation;
+import pageobjects.ProjectsManagementPage;
 import util.ItUtils;
 
-import static com.codeborne.selenide.Condition.text;
-import static com.codeborne.selenide.Selenide.$;
+import static org.assertj.core.api.Assertions.assertThat;
 import static util.ItUtils.newAdminWsClient;
 import static util.ItUtils.projectDir;
 
@@ -58,10 +60,34 @@ public class ProjectsAdministrationTest {
     // Remove 'Admin' permission for admin group on project 2 -> No one can access or admin this project, expect System Admin
     newAdminWsClient(orchestrator).permissions().removeGroup(new RemoveGroupWsRequest().setProjectKey("sample2").setGroupName("sonar-administrators").setPermission("admin"));
 
-    nav.logIn().asAdmin().open("/projects_admin");
-    $(".data.zebra")
-      .shouldHave(text("sample1"))
-      .shouldHave(text("sample2"));
+    nav.logIn().asAdmin().openProjectsManagement()
+      .shouldHaveProject("sample1")
+      .shouldHaveProject("sample2");
+  }
+
+  @Test
+  public void create_public_project() {
+    createProjectAndVerify("public");
+  }
+
+  @Test
+  public void create_private_project() {
+    createProjectAndVerify("private");
+  }
+
+  private void createProjectAndVerify(String visibility) {
+    ProjectsManagementPage page = nav.logIn().asAdmin().openProjectsManagement();
+    page
+      .shouldHaveProjectsCount(0)
+      .createProject("foo", "foo", visibility)
+      .shouldHaveProjectsCount(1);
+
+    WsComponents.SearchProjectsWsResponse response = newAdminWsClient(orchestrator).components().searchProjects(
+      SearchProjectsRequest.builder().build());
+    assertThat(response.getComponentsCount()).isEqualTo(1);
+    assertThat(response.getComponents(0).getKey()).isEqualTo("foo");
+    assertThat(response.getComponents(0).getName()).isEqualTo("foo");
+    assertThat(response.getComponents(0).getVisibility()).isEqualTo(visibility);
   }
 
 }
index 412698a7ea453491f9b978c026c6737121b1689f..09474474a04945098f06565d8fb9007710b969a5 100644 (file)
@@ -136,6 +136,10 @@ public class Navigation extends ExternalResource {
     return open(url, ProjectPermissionsPage.class);
   }
 
+  public ProjectsManagementPage openProjectsManagement() {
+    return open("/projects_admin", ProjectsManagementPage.class);
+  }
+
   public LoginPage openLogin() {
     return open("/sessions/login", LoginPage.class);
   }
diff --git a/it/it-tests/src/test/java/pageobjects/ProjectsManagementPage.java b/it/it-tests/src/test/java/pageobjects/ProjectsManagementPage.java
new file mode 100644 (file)
index 0000000..2a93733
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+package pageobjects;
+
+import static com.codeborne.selenide.Condition.exist;
+import static com.codeborne.selenide.Condition.text;
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.$$;
+
+public class ProjectsManagementPage {
+
+  public ProjectsManagementPage() {
+    $("#projects-management-page").should(exist);
+  }
+
+  public ProjectsManagementPage shouldHaveProjectsCount(int count) {
+    $$("#projects-management-page-projects tr").shouldHaveSize(count);
+    return this;
+  }
+
+  public ProjectsManagementPage shouldHaveProject(String key) {
+    $("#projects-management-page-projects").shouldHave(text(key));
+    return this;
+  }
+
+  public ProjectsManagementPage createProject(String key, String name, String visibility) {
+    $("#create-project").click();
+    $("#create-project-name").val(key);
+    $("#create-project-key").val(name);
+    $("#visibility-" + visibility).click();
+    $("#create-project-submit").submit();
+    return this;
+  }
+}
index 7579c1d2f4d04aacbda10494b0fd415595b6e31a..67aad792163d4cfae36d0e2a3ee43a3089095416 100644 (file)
@@ -21,7 +21,7 @@
 import React from 'react';
 import { without } from 'lodash';
 import PageHeader from './PageHeader';
-import VisibilitySelector from './VisibilitySelector';
+import VisibilitySelector from '../../../../components/common/VisibilitySelector';
 import AllHoldersList from './AllHoldersList';
 import PublicProjectDisclaimer from './PublicProjectDisclaimer';
 import PageError from '../../shared/components/PageError';
@@ -331,6 +331,7 @@ export default class App extends React.PureComponent {
         <PageError />
         {this.props.component.qualifier === 'TRK' &&
           <VisibilitySelector
+            className="big-spacer-top big-spacer-bottom"
             onChange={this.handleVisibilityChange}
             visibility={this.props.component.visibility}
           />}
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/VisibilitySelector.js b/server/sonar-web/src/main/js/apps/permissions/project/components/VisibilitySelector.js
deleted file mode 100644 (file)
index da7b0f4..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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 = {
-  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="big-spacer-top big-spacer-bottom">
-        <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>
-
-        <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>
-      </div>
-    );
-  }
-}
index 8507504ea8501fab35637bbc5dad7f4a1cdc3430..98bbe16a4f84570c62fe8147bba93199b74e57a2 100644 (file)
@@ -40,6 +40,7 @@ function AppContainer(props) {
       hasProvisionPermission={hasProvisionPermission}
       topLevelQualifiers={topLevelQualifiers}
       onVisibilityChange={props.onVisibilityChange}
+      onRequestFail={props.onRequestFail}
       organization={props.organization}
     />
   );
@@ -60,7 +61,8 @@ const onVisibilityChange = (organization, visibility) => dispatch => {
 };
 
 const mapDispatchToProps = (dispatch, ownProps) => ({
-  onVisibilityChange: visibility => dispatch(onVisibilityChange(ownProps.organization, visibility))
+  onVisibilityChange: visibility => dispatch(onVisibilityChange(ownProps.organization, visibility)),
+  onRequestFail: error => onFail(dispatch)(error)
 });
 
 export default connect(mapStateToProps, mapDispatchToProps)(AppContainer);
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/CreateProjectForm.js b/server/sonar-web/src/main/js/apps/projects-admin/CreateProjectForm.js
new file mode 100644 (file)
index 0000000..25f72ad
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * 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 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();
+        }
+      },
+      error => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+          this.props.onRequestFail(error);
+        }
+      }
+    );
+  };
+
+  render() {
+    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
+                    className="little-spacer-top"
+                    onChange={this.handleVisibilityChange}
+                    visibility={this.state.visibility}
+                  />
+                </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>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/create-view.js b/server/sonar-web/src/main/js/apps/projects-admin/create-view.js
deleted file mode 100644 (file)
index 6d9de3d..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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 { createProject } from '../../api/components';
-import Template from './templates/projects-create-form.hbs';
-
-export default ModalForm.extend({
-  template: Template,
-
-  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();
-  },
-
-  sendRequest() {
-    const data = {
-      name: this.$('#create-project-name').val(),
-      branch: this.$('#create-project-branch').val(),
-      project: this.$('#create-project-key').val()
-    };
-    if (this.options.organization) {
-      data.organization = this.options.organization.key;
-    }
-    this.disableForm();
-    return createProject(data)
-      .then(project => {
-        if (this.options.refresh) {
-          this.options.refresh();
-        }
-        this.enableForm();
-        this.createdProject = project;
-        this.render();
-      })
-      .catch(error => {
-        this.enableForm();
-        error.response.json().then(r => this.showErrors(r.errors, r.warnings));
-      });
-  },
-
-  serializeData() {
-    return {
-      ...ModalForm.prototype.serializeData.apply(this, arguments),
-      createdProject: this.createdProject
-    };
-  }
-});
index ba3ee6265619a96165d07a8919ec5115a941ea01..c7fbd4e473c25274761ddd5bcfa42822eb9c7196 100644 (file)
  */
 // @flow
 import React from 'react';
-import CreateView from './create-view';
 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,
-  refresh: () => void
+  organization?: Organization
 |};
 
 type State = {
@@ -39,12 +38,10 @@ export default class Header extends React.PureComponent {
   props: Props;
   state: State = { visibilityForm: false };
 
-  createProject() {
-    new CreateView({
-      refresh: this.props.refresh,
-      organization: this.props.organization
-    }).render();
-  }
+  handleCreateProjectClick = (event: Event) => {
+    event.preventDefault();
+    this.props.onProjectCreate();
+  };
 
   handleChangeVisibilityClick = (event: Event) => {
     event.preventDefault();
@@ -55,17 +52,6 @@ export default class Header extends React.PureComponent {
     this.setState({ visibilityForm: false });
   };
 
-  renderCreateButton() {
-    if (!this.props.hasProvisionPermission) {
-      return null;
-    }
-    return (
-      <button onClick={this.createProject.bind(this)}>
-        Create Project
-      </button>
-    );
-  }
-
   render() {
     const { organization } = this.props;
 
@@ -86,7 +72,10 @@ export default class Header extends React.PureComponent {
                 onClick={this.handleChangeVisibilityClick}
               />
             </span>}
-          {this.renderCreateButton()}
+          {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')}
index 60942300cf4d294a8bb52f2bf614aa01df4295bb..3d6a154acf881dc51016e0a281036303666d9233 100644 (file)
@@ -23,6 +23,7 @@ import { debounce, uniq, without } from 'lodash';
 import Header from './header';
 import Search from './search';
 import Projects from './projects';
+import CreateProjectForm from './CreateProjectForm';
 import { PAGE_SIZE, TYPE } from './constants';
 import { getComponents, getProvisioned, getGhosts, deleteComponents } from '../../api/components';
 import ListFooter from '../../components/controls/ListFooter';
@@ -31,10 +32,12 @@ 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,
@@ -52,6 +55,7 @@ export default class Main extends React.PureComponent {
   constructor(props: Props) {
     super(props);
     this.state = {
+      createProjectForm: false,
       ready: false,
       projects: [],
       total: 0,
@@ -216,12 +220,20 @@ export default class Main extends React.PureComponent {
     });
   };
 
+  openCreateProjectForm = () => {
+    this.setState({ createProjectForm: true });
+  };
+
+  closeCreateProjectForm = () => {
+    this.setState({ createProjectForm: false });
+  };
+
   render() {
     return (
-      <div className="page page-limited">
+      <div className="page page-limited" id="projects-management-page">
         <Header
           hasProvisionPermission={this.props.hasProvisionPermission}
-          refresh={this.requestProjects}
+          onProjectCreate={this.openCreateProjectForm}
           onVisibilityChange={this.props.onVisibilityChange}
           organization={this.props.organization}
         />
@@ -253,6 +265,14 @@ export default class Main extends React.PureComponent {
           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>
     );
   }
index bcb3b1c82e00b3e414d14caad4ae56af94c58d4d..bc1f94eda6382a987d1af5d52d0331c219862f08 100644 (file)
@@ -114,7 +114,7 @@ export default class Projects extends React.PureComponent {
     const className = classNames('data', 'zebra', { 'new-loading': !this.props.ready });
 
     return (
-      <table className={className}>
+      <table className={className} id="projects-management-page-projects">
         <tbody>{this.props.projects.map(this.renderProject)}</tbody>
       </table>
     );
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/templates/projects-create-form.hbs b/server/sonar-web/src/main/js/apps/projects-admin/templates/projects-create-form.hbs
deleted file mode 100644 (file)
index f94b546..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<form id="create-project-form" autocomplete="off">
-  <div class="modal-head">
-    <h2>Create Project</h2>
-  </div>
-  <div class="modal-body">
-    <div class="js-modal-messages"></div>
-    {{#if createdProject}}
-      <div class="alert alert-success">
-        Project <a href="{{componentPermalink createdProject.k}}">{{createdProject.nm}}</a> has been successfully
-        created.
-      </div>
-    {{else}}
-      <div class="modal-field">
-        <label for="create-project-name">Name<em class="mandatory">*</em></label>
-        {{! keep this fake field to hack browser autofill }}
-        <input id="create-project-name-fake" name="name-fake" type="text" class="hidden">
-        <input id="create-project-name" name="name" type="text" maxlength="2000" required>
-      </div>
-      <div class="modal-field">
-        <label for="create-project-branch">Branch</label>
-        {{! keep this fake field to hack browser autofill }}
-        <input id="create-project-branch-fake" name="branch-fake" type="text" class="hidden">
-        <input id="create-project-branch" name="branch" type="text" maxlength="200">
-      </div>
-      <div class="modal-field">
-        <label for="create-project-key">Key<em class="mandatory">*</em></label>
-        {{! keep this fake field to hack browser autofill }}
-        <input id="create-project-key-fake" name="key-fake" type="text" class="hidden">
-        <input id="create-project-key" name="key" type="text" maxlength="400" required>
-      </div>
-    {{/if}}
-  </div>
-  <div class="modal-foot">
-    {{#if createdProject}}
-      <a href="#" class="js-modal-close">{{t 'close'}}</a>
-    {{else}}
-      <button id="create-project-submit">Create</button>
-      <a href="#" class="js-modal-close" id="create-project-cancel">Cancel</a>
-    {{/if}}
-  </div>
-</form>
diff --git a/server/sonar-web/src/main/js/components/common/VisibilitySelector.js b/server/sonar-web/src/main/js/components/common/VisibilitySelector.js
new file mode 100644 (file)
index 0000000..5a9e453
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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 = {|
+  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>
+
+        <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>
+      </div>
+    );
+  }
+}
index abc8eb8f5c53d4ce05e334f16124c79d77792276..c2207803e9d262a95854eafbdedaee6694c06f74 100644 (file)
@@ -199,6 +199,7 @@ version=Version
 view=View
 views=Views
 violations=Violations
+visibility=Visibility
 with=With
 worst=Worst