]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9181 bulk delete projects or bulk apply template in one go
authorStas Vilchik <stas.vilchik@sonarsource.com>
Wed, 6 Sep 2017 15:53:37 +0000 (17:53 +0200)
committerStas Vilchik <stas.vilchik@sonarsource.com>
Mon, 11 Sep 2017 09:28:29 +0000 (11:28 +0200)
23 files changed:
server/sonar-web/src/main/js/api/components.ts
server/sonar-web/src/main/js/api/permissions.ts
server/sonar-web/src/main/js/app/types.ts
server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx
server/sonar-web/src/main/js/apps/projectsManagement/ChangeVisibilityForm.tsx
server/sonar-web/src/main/js/apps/projectsManagement/DeleteModal.tsx
server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx
server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap
server/sonar-web/src/main/js/apps/projectsManagement/utils.ts
server/sonar-web/src/main/less/components/alerts.less
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 572875aa7905f687352d3949b4b2a2dc0a40af31..50faefec07d02b1f9bfd8cb65b6a282b60a659d7 100644 (file)
  */
 import { getJSON, postJSON, post, RequestData } from '../helpers/request';
 import throwGlobalError from '../app/utils/throwGlobalError';
+import { Paging, Visibility } from '../app/types';
 
-export function getComponents(data: RequestData): Promise<any> {
-  return getJSON('/api/projects/search', data);
+export interface BaseSearchProjectsParameters {
+  analyzedBefore?: string;
+  onProvisionedOnly?: boolean;
+  organization: string;
+  projects?: string;
+  q?: string;
+  qualifiers?: string;
+  visibility?: Visibility;
+}
+
+export interface SearchProjectsParameters extends BaseSearchProjectsParameters {
+  p?: number;
+  ps?: number;
+}
+
+export interface SearchProjectsResponseComponent {
+  id: string;
+  key: string;
+  lastAnalysisDate?: string;
+  name: string;
+  organization: string;
+  qualifier: string;
+  visibility: Visibility;
+}
+
+export interface SearchProjectsResponse {
+  components: SearchProjectsResponseComponent[];
+  paging: Paging;
 }
 
-export function getProvisioned(data: RequestData): Promise<any> {
-  return getJSON('/api/projects/provisioned', data);
+export function getComponents(
+  parameters: SearchProjectsParameters
+): Promise<SearchProjectsResponse> {
+  return getJSON('/api/projects/search', parameters);
 }
 
-export function deleteComponents(projects: string[], organization: string): Promise<void> {
-  return post('/api/projects/bulk_delete', { projects: projects.join(), organization });
+export function bulkDeleteProjects(parameters: BaseSearchProjectsParameters): Promise<void> {
+  return post('/api/projects/bulk_delete', parameters);
 }
 
 export function deleteProject(project: string): Promise<void> {
index 5c927bc369258562ce99448ec2605affa565416d..8d2cf3e55968bd539caca24443649b56193c25db 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { getJSON, post, postJSON, RequestData } from '../helpers/request';
+import { BaseSearchProjectsParameters } from './components';
 
 const PAGE_SIZE = 100;
 
@@ -136,7 +137,7 @@ export function applyTemplateToProject(data: RequestData): Promise<void> {
   return post('/api/permissions/apply_template', data);
 }
 
-export function bulkApplyTemplate(data: RequestData): Promise<void> {
+export function bulkApplyTemplate(data: BaseSearchProjectsParameters): Promise<void> {
   return post('/api/permissions/bulk_apply_template', data);
 }
 
index d5ff471384532b1aa153d17cf1d759a0ad701c9c..2d0bc46db369e70b55c4c9bb9a6d755910872073 100644 (file)
@@ -118,3 +118,14 @@ export interface Organization {
   projectVisibility: string;
   url?: string;
 }
+
+export interface Paging {
+  pageIndex: number;
+  pageSize: number;
+  total: number;
+}
+
+export enum Visibility {
+  Public = 'public',
+  Private = 'private'
+}
index da819c20f4f41406661261007cc463308e42b161..6ede35e891740579c42f3e011fa93bb73a0480c2 100644 (file)
@@ -26,7 +26,7 @@ import Projects from './Projects';
 import CreateProjectForm from './CreateProjectForm';
 import ListFooter from '../../components/controls/ListFooter';
 import { PAGE_SIZE, Project } from './utils';
-import { getComponents, getProvisioned } from '../../api/components';
+import { getComponents } from '../../api/components';
 import { Organization } from '../../app/types';
 import { translate } from '../../helpers/l10n';
 
@@ -78,38 +78,17 @@ export default class App extends React.PureComponent<Props, State> {
     this.mounted = false;
   }
 
-  getFilters = () => ({
-    analyzedBefore: this.state.analyzedBefore,
-    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 = () =>
-    this.state.provisioned ? this.requestProvisioned() : this.requestAllProjects();
-
-  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 => {
+  requestProjects = () => {
+    const parameters = {
+      analyzedBefore: this.state.analyzedBefore,
+      onProvisionedOnly: this.state.provisioned || undefined,
+      organization: this.props.organization.key,
+      p: this.state.page !== 1 ? this.state.page : undefined,
+      ps: PAGE_SIZE,
+      q: this.state.query || undefined,
+      qualifiers: this.state.qualifiers
+    };
+    getComponents(parameters).then(r => {
       if (this.mounted) {
         let projects: Project[] = r.components;
         if (this.state.page > 1) {
index 60c55b7f5fc620d696bb28bde9f471f1d684ae47..60a361c1dbb4c6b0e26aca36d22046a76f2651d1 100644 (file)
@@ -22,13 +22,14 @@ import Modal from 'react-modal';
 import * as Select from 'react-select';
 import {
   getPermissionTemplates,
-  PermissionTemplate,
   bulkApplyTemplate,
-  applyTemplateToProject
+  PermissionTemplate
 } from '../../api/permissions';
 import { translate, translateWithParameters } from '../../helpers/l10n';
+import AlertWarnIcon from '../../components/icons-components/AlertWarnIcon';
 
 export interface Props {
+  analyzedBefore?: string;
   onClose: () => void;
   organization: string;
   provisioned: boolean;
@@ -80,32 +81,6 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S
     );
   }
 
-  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();
@@ -115,10 +90,21 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S
     const { permissionTemplate } = this.state;
     if (permissionTemplate) {
       this.setState({ submitting: true });
-      const request = this.props.selection.length
-        ? this.bulkApplyToSelected(permissionTemplate)
-        : this.bulkApplyToAll(permissionTemplate);
-      request.then(
+      const parameters = this.props.selection.length
+        ? {
+            organization: this.props.organization,
+            projects: this.props.selection.join(),
+            templateId: permissionTemplate
+          }
+        : {
+            analyzedBefore: this.props.analyzedBefore,
+            onProvisionedOnly: this.props.provisioned || undefined,
+            organization: this.props.organization,
+            qualifiers: this.props.qualifier,
+            q: this.props.query || undefined,
+            templateId: permissionTemplate
+          };
+      bulkApplyTemplate(parameters).then(
         () => {
           if (this.mounted) {
             this.setState({ done: true, submitting: false });
@@ -137,21 +123,19 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S
     this.setState({ permissionTemplate: value });
   };
 
-  renderWarning = () => {
-    return this.props.selection.length
-      ? <div className="alert alert-info">
-          {translateWithParameters(
+  renderWarning = () =>
+    <div className="alert alert-warning modal-alert">
+      <AlertWarnIcon className="spacer-right" />
+      {this.props.selection.length
+        ? translateWithParameters(
             'permission_templates.bulk_apply_permission_template.apply_to_selected',
             this.props.selection.length
-          )}
-        </div>
-      : <div className="alert alert-warning">
-          {translateWithParameters(
+          )
+        : translateWithParameters(
             'permission_templates.bulk_apply_permission_template.apply_to_all',
             this.props.total
           )}
-        </div>;
-  };
+    </div>;
 
   renderSelect = () =>
     <div className="modal-field">
index 9c04349c6883a00f9db2efae9bf6c3a49cbc13a3..41261da71e3d2245bfc25740a321d770fb4acd83 100644 (file)
 import * as React from 'react';
 import Modal from 'react-modal';
 import * as classNames from 'classnames';
-import { Organization } from '../../app/types';
+import { Organization, Visibility } from '../../app/types';
 import UpgradeOrganizationBox from '../../components/common/UpgradeOrganizationBox';
 import { translate } from '../../helpers/l10n';
-import { Visibility } from './utils';
 
 export interface Props {
   onClose: () => void;
index 7b4a45fc61bd297176b02f1a9cc92aaff1ebc41d..c04cd202ab42cddf5cffccbfe8356c8b1354f315 100644 (file)
  */
 import * as React from 'react';
 import Modal from 'react-modal';
-import { deleteComponents } from '../../api/components';
-import { translate } from '../../helpers/l10n';
+import { bulkDeleteProjects } from '../../api/components';
+import { translate, translateWithParameters } from '../../helpers/l10n';
+import AlertWarnIcon from '../../components/icons-components/AlertWarnIcon';
 
 export interface Props {
+  analyzedBefore?: string;
   onClose: () => void;
   onConfirm: () => void;
   organization: string;
+  provisioned: boolean;
   qualifier: string;
+  query: string;
   selection: string[];
+  total: number;
 }
 
 interface State {
@@ -53,7 +58,19 @@ export default class DeleteModal extends React.PureComponent<Props, State> {
 
   handleConfirmClick = () => {
     this.setState({ loading: true });
-    deleteComponents(this.props.selection, this.props.organization).then(
+    const parameters = this.props.selection.length
+      ? {
+          organization: this.props.organization,
+          projects: this.props.selection.join()
+        }
+      : {
+          analyzedBefore: this.props.analyzedBefore,
+          onProvisionedOnly: this.props.provisioned || undefined,
+          organization: this.props.organization,
+          qualifiers: this.props.qualifier,
+          q: this.props.query || undefined
+        };
+    bulkDeleteProjects(parameters).then(
       () => {
         if (this.mounted) {
           this.props.onConfirm();
@@ -67,6 +84,17 @@ export default class DeleteModal extends React.PureComponent<Props, State> {
     );
   };
 
+  renderWarning = () =>
+    <div className="alert alert-warning modal-alert">
+      <AlertWarnIcon className="spacer-right" />
+      {this.props.selection.length
+        ? translateWithParameters(
+            'projects_management.delete_selected_warning',
+            this.props.selection.length
+          )
+        : translateWithParameters('projects_management.delete_all_warning', this.props.total)}
+    </div>;
+
   render() {
     const header = translate('qualifiers.delete', this.props.qualifier);
 
@@ -84,6 +112,7 @@ export default class DeleteModal extends React.PureComponent<Props, State> {
         </header>
 
         <div className="modal-body">
+          {this.renderWarning()}
           {translate('qualifiers.delete_confirm', this.props.qualifier)}
         </div>
 
index ef77181c740b1de33385e6c726e12e29c1bbc933..905c873d42eeced2934dbdae378e150a36bd7ea9 100644 (file)
@@ -19,8 +19,7 @@
  */
 import * as React from 'react';
 import ChangeVisibilityForm from './ChangeVisibilityForm';
-import { Visibility } from './utils';
-import { Organization } from '../../app/types';
+import { Organization, Visibility } from '../../app/types';
 import { translate } from '../../helpers/l10n';
 
 export interface Props {
index 9ddf15b20a55d80ca163223e4dad0baa2c3503a8..e6612a26d196fad799e175854483e1e3d450c235 100644 (file)
@@ -19,7 +19,8 @@
  */
 import * as React from 'react';
 import { Link } from 'react-router';
-import { Project, Visibility } from './utils';
+import { Project } from './utils';
+import { Visibility } from '../../app/types';
 import PrivateBadge from '../../components/common/PrivateBadge';
 import Checkbox from '../../components/controls/Checkbox';
 import QualifierIcon from '../../components/shared/QualifierIcon';
index eecfd7b491705024f4b1e0824c77afd666ceef06..50e943423966907ff08529c5605deb84e916b51e 100644 (file)
@@ -186,7 +186,7 @@ export default class Search extends React.PureComponent<Props, State> {
           inputClassName="input-medium"
           name="analyzed-before"
           onChange={this.props.onDateChanged}
-          placeholder={translate('analyzed_before')}
+          placeholder={translate('last_analysis_before')}
           value={this.props.analyzedBefore}
         />
       </td>
@@ -194,7 +194,6 @@ export default class Search extends React.PureComponent<Props, State> {
   };
 
   render() {
-    const isSomethingSelected = this.props.projects.length > 0 && this.props.selection.length > 0;
     return (
       <div className="big-spacer-bottom">
         <table className="data">
@@ -224,13 +223,14 @@ export default class Search extends React.PureComponent<Props, State> {
               <td className="thin nowrap text-middle">
                 <button
                   className="spacer-right js-bulk-apply-permission-template"
+                  disabled={this.props.total === 0}
                   onClick={this.handleBulkApplyTemplateClick}>
                   {translate('permission_templates.bulk_apply_permission_template')}
                 </button>
                 <button
-                  onClick={this.handleDeleteClick}
                   className="js-delete button-red"
-                  disabled={!isSomethingSelected}>
+                  disabled={this.props.total === 0}
+                  onClick={this.handleDeleteClick}>
                   {translate('delete')}
                 </button>
               </td>
@@ -240,6 +240,7 @@ export default class Search extends React.PureComponent<Props, State> {
 
         {this.state.bulkApplyTemplateModal &&
           <BulkApplyTemplateModal
+            analyzedBefore={this.props.analyzedBefore}
             onClose={this.closeBulkApplyTemplateModal}
             organization={this.props.organization.key}
             provisioned={this.props.provisioned}
@@ -251,11 +252,15 @@ export default class Search extends React.PureComponent<Props, State> {
 
         {this.state.deleteModal &&
           <DeleteModal
+            analyzedBefore={this.props.analyzedBefore}
             onClose={this.closeDeleteModal}
             onConfirm={this.handleDeleteConfirm}
             organization={this.props.organization.key}
+            provisioned={this.props.provisioned}
             qualifier={this.props.qualifiers}
+            query={this.props.query}
             selection={this.props.selection}
+            total={this.props.total}
           />}
       </div>
     );
index 07339957c4cc768a3513081b29b56f858b210227..0fb6b1d21d057e220584a5425b97a3da13f6e644 100644 (file)
@@ -30,17 +30,13 @@ jest.mock('rc-tooltip', () => ({
   }
 }));
 
-jest.mock('../../../api/components', () => ({
-  getComponents: jest.fn(),
-  getProvisioned: jest.fn(() => Promise.resolve({ paging: { total: 0 }, projects: [] }))
-}));
+jest.mock('../../../api/components', () => ({ getComponents: jest.fn() }));
 
 import * as React from 'react';
 import { mount } from 'enzyme';
 import App, { Props } from '../App';
 
 const getComponents = require('../../../api/components').getComponents as jest.Mock<any>;
-const getProvisioned = require('../../../api/components').getProvisioned as jest.Mock<any>;
 
 const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
 
@@ -55,7 +51,6 @@ beforeEach(() => {
   getComponents
     .mockImplementation(() => Promise.resolve({ paging: { total: 0 }, components: [] }))
     .mockClear();
-  getProvisioned.mockClear();
 });
 
 it('fetches all projects on mount', () => {
@@ -66,7 +61,11 @@ it('fetches all projects on mount', () => {
 it('selects provisioned', () => {
   const wrapper = mountRender();
   wrapper.find('Search').prop<Function>('onProvisionedChanged')(true);
-  expect(getProvisioned).lastCalledWith(defaultSearchParameters);
+  expect(getComponents).lastCalledWith({
+    ...defaultSearchParameters,
+    onProvisionedOnly: true,
+    qualifiers: 'TRK'
+  });
 });
 
 it('changes qualifier and resets provisioned', () => {
index 6f11a5d48daefdb28986b676ee2aa49613bff87a..cdb33cf7f3321c05a766ed50c076b51c3026900a 100644 (file)
@@ -18,9 +18,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 jest.mock('../../../api/permissions', () => ({
-  applyTemplateToProject: jest.fn(),
-  bulkApplyTemplate: jest.fn(),
-  getPermissionTemplates: jest.fn()
+  bulkApplyTemplate: jest.fn(() => Promise.resolve()),
+  getPermissionTemplates: jest.fn(() => Promise.resolve({ permissionTemplates: [] }))
 }));
 
 import * as React from 'react';
@@ -28,18 +27,13 @@ import { mount, shallow } from 'enzyme';
 import BulkApplyTemplateModal, { Props } from '../BulkApplyTemplateModal';
 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();
+  bulkApplyTemplate.mockClear();
+  getPermissionTemplates.mockClear();
 });
 
 it('fetches permission templates on mount', () => {
@@ -61,9 +55,11 @@ it('bulk applies template to all results', async () => {
 
   click(wrapper.find('button'));
   expect(bulkApplyTemplate).toBeCalledWith({
+    analyzedBefore: '2017-04-08T00:00:00.000Z',
+    onProvisionedOnly: true,
     organization: 'org',
     q: 'bla',
-    qualifier: 'TRK',
+    qualifiers: 'TRK',
     templateId: 'foo'
   });
   expect(wrapper).toMatchSnapshot();
@@ -88,15 +84,9 @@ it('bulk applies template to selected results', async () => {
   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({
+  expect(bulkApplyTemplate).toBeCalledWith({
     organization: 'org',
-    projectKey: 'proj2',
+    projects: 'proj1,proj2',
     templateId: 'foo'
   });
 
@@ -114,6 +104,7 @@ it('closes', () => {
 function render(props?: { [P in keyof Props]?: Props[P] }) {
   return (
     <BulkApplyTemplateModal
+      analyzedBefore="2017-04-08T00:00:00.000Z"
       onClose={jest.fn()}
       organization="org"
       provisioned={true}
index d24f30e9cdfa2589e7bbc7280946af51fbb40167..976be4403a8377066f011ea6d95e815c0839d22d 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 jest.mock('../../../api/components', () => ({
-  deleteComponents: jest.fn(() => Promise.resolve())
+  bulkDeleteProjects: jest.fn(() => Promise.resolve())
 }));
 
 import * as React from 'react';
@@ -26,9 +26,13 @@ import { shallow } from 'enzyme';
 import DeleteModal, { Props } from '../DeleteModal';
 import { click } from '../../../helpers/testUtils';
 
-const deleteComponents = require('../../../api/components').deleteComponents as jest.Mock<any>;
+const bulkDeleteProjects = require('../../../api/components').bulkDeleteProjects as jest.Mock<any>;
 
-it('deletes projects', async () => {
+beforeEach(() => {
+  bulkDeleteProjects.mockClear();
+});
+
+it('deletes all projects', async () => {
   const onConfirm = jest.fn();
   const wrapper = shallowRender({ onConfirm });
   (wrapper.instance() as DeleteModal).mounted = true;
@@ -36,7 +40,27 @@ it('deletes projects', async () => {
 
   click(wrapper.find('button'));
   expect(wrapper).toMatchSnapshot();
-  expect(deleteComponents).toBeCalledWith(['foo', 'bar'], 'org');
+  expect(bulkDeleteProjects).toBeCalledWith({
+    analyzedBefore: '2017-04-08T00:00:00.000Z',
+    onProvisionedOnly: undefined,
+    organization: 'org',
+    q: 'bla',
+    qualifiers: 'TRK'
+  });
+
+  await new Promise(setImmediate);
+  expect(onConfirm).toBeCalled();
+});
+
+it('deletes selected projects', async () => {
+  const onConfirm = jest.fn();
+  const wrapper = shallowRender({ onConfirm, selection: ['proj1', 'proj2'] });
+  (wrapper.instance() as DeleteModal).mounted = true;
+  expect(wrapper).toMatchSnapshot();
+
+  click(wrapper.find('button'));
+  expect(wrapper).toMatchSnapshot();
+  expect(bulkDeleteProjects).toBeCalledWith({ organization: 'org', projects: 'proj1,proj2' });
 
   await new Promise(setImmediate);
   expect(onConfirm).toBeCalled();
@@ -52,11 +76,15 @@ it('closes', () => {
 function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
   return shallow(
     <DeleteModal
+      analyzedBefore="2017-04-08T00:00:00.000Z"
       onClose={jest.fn()}
       onConfirm={jest.fn()}
       organization="org"
+      provisioned={false}
       qualifier="TRK"
-      selection={['foo', 'bar']}
+      query="bla"
+      selection={[]}
+      total={17}
       {...props}
     />
   );
index 726c6b186cb360abd0199e67434bd6b64585d70d..64b7d42366b2117987f088819ff1e5ea6b85e98d 100644 (file)
@@ -20,7 +20,7 @@
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import Header, { Props } from '../Header';
-import { Visibility } from '../utils';
+import { Visibility } from '../../../app/types';
 import { click } from '../../../helpers/testUtils';
 
 const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
index 982ef5ce35a3267c76887a9caeaff51bc2824d00..b1d052dbb86e347a94ba14489946e2ec357a1236 100644 (file)
@@ -20,7 +20,7 @@
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import ProjectRow from '../ProjectRow';
-import { Visibility } from '../utils';
+import { Visibility } from '../../../app/types';
 import { click } from '../../../helpers/testUtils';
 
 const project = {
index b1f165cde7afa6feefb94259536b6f446a5d82fe..2cc0ec841ef2ecfff57fa0f8084139ffd43f47d9 100644 (file)
@@ -22,8 +22,8 @@ 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';
+import { Visibility } from '../../../app/types';
 
 const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
 const projects = [
index 66e0439de3f5bcae50b4c2755497296c98e15e94..e7ec8d71e94ddd5c7c28eae66a2496c022026ec4 100644 (file)
@@ -118,7 +118,7 @@ function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
       ready={true}
       selection={[]}
       topLevelQualifiers={['TRK']}
-      total={0}
+      total={17}
       {...props}
     />
   );
index bc3e39d7f64a15a2cd5e06a35fdf8f5366796ca7..4cadeebb4a06fd0499b7949fa9a7b9bca30b2ca3 100644 (file)
@@ -67,8 +67,11 @@ exports[`bulk applies template to all results 2`] = `
     className="modal-body"
   >
     <div
-      className="alert alert-warning"
+      className="alert alert-warning modal-alert"
     >
+      <AlertWarnIcon
+        className="spacer-right"
+      />
       permission_templates.bulk_apply_permission_template.apply_to_all.17
     </div>
     <div
@@ -183,8 +186,11 @@ exports[`bulk applies template to all results 3`] = `
     className="modal-body"
   >
     <div
-      className="alert alert-warning"
+      className="alert alert-warning modal-alert"
     >
+      <AlertWarnIcon
+        className="spacer-right"
+      />
       permission_templates.bulk_apply_permission_template.apply_to_all.17
     </div>
     <div
@@ -388,8 +394,11 @@ exports[`bulk applies template to selected results 2`] = `
     className="modal-body"
   >
     <div
-      className="alert alert-info"
+      className="alert alert-warning modal-alert"
     >
+      <AlertWarnIcon
+        className="spacer-right"
+      />
       permission_templates.bulk_apply_permission_template.apply_to_selected.2
     </div>
     <div
@@ -504,8 +513,11 @@ exports[`bulk applies template to selected results 3`] = `
     className="modal-body"
   >
     <div
-      className="alert alert-info"
+      className="alert alert-warning modal-alert"
     >
+      <AlertWarnIcon
+        className="spacer-right"
+      />
       permission_templates.bulk_apply_permission_template.apply_to_selected.2
     </div>
     <div
index e57788fbc926abe98e5a32579306aa54768372ec..12d6e222a77c2121f3e820c9cf2416002ad71368 100644 (file)
@@ -1,6 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`deletes projects 1`] = `
+exports[`deletes all projects 1`] = `
 <Modal
   ariaHideApp={true}
   bodyOpenClassName="ReactModal__Body--open"
@@ -24,6 +24,14 @@ exports[`deletes projects 1`] = `
   <div
     className="modal-body"
   >
+    <div
+      className="alert alert-warning modal-alert"
+    >
+      <AlertWarnIcon
+        className="spacer-right"
+      />
+      projects_management.delete_all_warning.17
+    </div>
     qualifiers.delete_confirm.TRK
   </div>
   <footer
@@ -47,7 +55,7 @@ exports[`deletes projects 1`] = `
 </Modal>
 `;
 
-exports[`deletes projects 2`] = `
+exports[`deletes all projects 2`] = `
 <Modal
   ariaHideApp={true}
   bodyOpenClassName="ReactModal__Body--open"
@@ -71,6 +79,127 @@ exports[`deletes projects 2`] = `
   <div
     className="modal-body"
   >
+    <div
+      className="alert alert-warning modal-alert"
+    >
+      <AlertWarnIcon
+        className="spacer-right"
+      />
+      projects_management.delete_all_warning.17
+    </div>
+    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>
+`;
+
+exports[`deletes selected 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"
+  >
+    <div
+      className="alert alert-warning modal-alert"
+    >
+      <AlertWarnIcon
+        className="spacer-right"
+      />
+      projects_management.delete_selected_warning.2
+    </div>
+    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 selected 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"
+  >
+    <div
+      className="alert alert-warning modal-alert"
+    >
+      <AlertWarnIcon
+        className="spacer-right"
+      />
+      projects_management.delete_selected_warning.2
+    </div>
     qualifiers.delete_confirm.TRK
   </div>
   <footer
index 89a31381713c42f1d2a12529fcb60f5df94e18c7..9e0a31ce941cdac417c2ad0e0695a3b8774f2bd7 100644 (file)
@@ -8,7 +8,7 @@ exports[`bulk applies permission template 1`] = `
   qualifier="TRK"
   query=""
   selection={Array []}
-  total={0}
+  total={17}
 />
 `;
 
@@ -17,13 +17,16 @@ exports[`deletes projects 1`] = `
   onClose={[Function]}
   onConfirm={[Function]}
   organization="org"
+  provisioned={false}
   qualifier="TRK"
+  query=""
   selection={
     Array [
       "foo",
       "bar",
     ]
   }
+  total={17}
 />
 `;
 
@@ -176,13 +179,14 @@ exports[`render qualifiers filter 1`] = `
         >
           <button
             className="spacer-right js-bulk-apply-permission-template"
+            disabled={false}
             onClick={[Function]}
           >
             permission_templates.bulk_apply_permission_template
           </button>
           <button
             className="js-delete button-red"
-            disabled={true}
+            disabled={false}
             onClick={[Function]}
           >
             delete
@@ -277,13 +281,14 @@ exports[`renders 1`] = `
         >
           <button
             className="spacer-right js-bulk-apply-permission-template"
+            disabled={false}
             onClick={[Function]}
           >
             permission_templates.bulk_apply_permission_template
           </button>
           <button
             className="js-delete button-red"
-            disabled={true}
+            disabled={false}
             onClick={[Function]}
           >
             delete
index aa549df26c1d69d045b122723a56236195daa4f2..0bbc7d4084338846363d98112ad9a8d272645a1c 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { SearchProjectsResponseComponent } from '../../api/components';
+
 export const PAGE_SIZE = 50;
 
 export const QUALIFIERS_ORDER = ['TRK', 'VW', 'APP'];
 
-export interface Project {
-  key: string;
-  lastAnalysisDate?: string;
-  name: string;
-  qualifier: string;
-  visibility: Visibility;
-}
-
-export enum Visibility {
-  Public = 'public',
-  Private = 'private'
-}
+export type Project = SearchProjectsResponseComponent;
index fb874b2ecab3eeba7635c0f263f26c9501f72567..656940462611b4383fe9aa2efdcb9645fa7556b0 100644 (file)
   vertical-align: middle;
 }
 
+.modal-alert {
+  margin: -10px -10px 16px;
+  padding: 10px;
+  border-top: none;
+  border-left: none;
+  border-right: none;
+}
+
 // Color
 
 .alert-emphasis-variant(@color, @background-color, @border-color) {
index 0345b483fb77b4bb279eba50dbda0cab1eef3be0..a14132565a9087276b15a5cee58e8e5ef03b77b8 100644 (file)
@@ -225,7 +225,6 @@ added_since_previous_version_detailed=Added since previous version ({0})
 added_since_version=Added since version {0}
 all_violations=All violations
 all_issues=All issues
-analyzed_before=Analyzed before
 and_worse=and worse
 are_you_sure=Are you sure?
 assigned_to=Assigned to
@@ -253,6 +252,7 @@ full_source=Full source
 greater_or_equals=Greater or equals
 greater_than=Greater than
 help_tips=Help tips
+last_analysis_before=Last analysis before
 less_or_equals=Less or equals
 less_than=Less than
 logging_out=You're logging out, please wait...
@@ -1406,6 +1406,8 @@ project_quality_gate.successfully_updated=Quality gate has been successfully upd
 
 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}"?
+projects_management.delete_selected_warning=You're about to delete {0} selected items.
+projects_management.delete_all_warning=You're about to delete all {0} items.
 
 
 #------------------------------------------------------------------------------