Browse Source

SONAR-9181 bulk delete projects or bulk apply template in one go

tags/6.6-RC1
Stas Vilchik 6 years ago
parent
commit
3ac9340f01
23 changed files with 353 additions and 151 deletions
  1. 35
    6
      server/sonar-web/src/main/js/api/components.ts
  2. 2
    1
      server/sonar-web/src/main/js/api/permissions.ts
  3. 11
    0
      server/sonar-web/src/main/js/app/types.ts
  4. 12
    33
      server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
  5. 26
    42
      server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx
  6. 1
    2
      server/sonar-web/src/main/js/apps/projectsManagement/ChangeVisibilityForm.tsx
  7. 32
    3
      server/sonar-web/src/main/js/apps/projectsManagement/DeleteModal.tsx
  8. 1
    2
      server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx
  9. 2
    1
      server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
  10. 9
    4
      server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx
  11. 6
    7
      server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx
  12. 10
    19
      server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx
  13. 33
    5
      server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx
  14. 1
    1
      server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx
  15. 1
    1
      server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx
  16. 1
    1
      server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx
  17. 1
    1
      server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx
  18. 16
    4
      server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap
  19. 131
    2
      server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap
  20. 8
    3
      server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap
  21. 3
    12
      server/sonar-web/src/main/js/apps/projectsManagement/utils.ts
  22. 8
    0
      server/sonar-web/src/main/less/components/alerts.less
  23. 3
    1
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 35
- 6
server/sonar-web/src/main/js/api/components.ts View File

@@ -19,17 +19,46 @@
*/
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> {

+ 2
- 1
server/sonar-web/src/main/js/api/permissions.ts View 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);
}


+ 11
- 0
server/sonar-web/src/main/js/app/types.ts View 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'
}

+ 12
- 33
server/sonar-web/src/main/js/apps/projectsManagement/App.tsx View 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) {

+ 26
- 42
server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx View 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">

+ 1
- 2
server/sonar-web/src/main/js/apps/projectsManagement/ChangeVisibilityForm.tsx View File

@@ -20,10 +20,9 @@
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;

+ 32
- 3
server/sonar-web/src/main/js/apps/projectsManagement/DeleteModal.tsx View File

@@ -19,15 +19,20 @@
*/
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>


+ 1
- 2
server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx View 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 {

+ 2
- 1
server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx View 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';

+ 9
- 4
server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx View 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>
);

+ 6
- 7
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx View 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', () => {

+ 10
- 19
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx View 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}

+ 33
- 5
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx View 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}
/>
);

+ 1
- 1
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx View 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' };

+ 1
- 1
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx View 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 = {

+ 1
- 1
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx View 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 = [

+ 1
- 1
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx View File

@@ -118,7 +118,7 @@ function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
ready={true}
selection={[]}
topLevelQualifiers={['TRK']}
total={0}
total={17}
{...props}
/>
);

+ 16
- 4
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap View 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

+ 131
- 2
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap View 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

+ 8
- 3
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap View 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

+ 3
- 12
server/sonar-web/src/main/js/apps/projectsManagement/utils.ts View File

@@ -17,19 +17,10 @@
* 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;

+ 8
- 0
server/sonar-web/src/main/less/components/alerts.less View File

@@ -38,6 +38,14 @@
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) {

+ 3
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties View 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.


#------------------------------------------------------------------------------

Loading…
Cancel
Save