diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2017-09-06 17:53:37 +0200 |
---|---|---|
committer | Stas Vilchik <stas.vilchik@sonarsource.com> | 2017-09-11 11:28:29 +0200 |
commit | 3ac9340f013701017f8dbab5b0686d2682f1768f (patch) | |
tree | 2be2f877ee0a92fe2e3ed652c730474dc239ba4a /server/sonar-web/src | |
parent | 048982bb3d8d5b2c715f95bc7818c90314e72a14 (diff) | |
download | sonarqube-3ac9340f013701017f8dbab5b0686d2682f1768f.tar.gz sonarqube-3ac9340f013701017f8dbab5b0686d2682f1768f.zip |
SONAR-9181 bulk delete projects or bulk apply template in one go
Diffstat (limited to 'server/sonar-web/src')
22 files changed, 350 insertions, 150 deletions
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts index 572875aa790..50faefec07d 100644 --- a/server/sonar-web/src/main/js/api/components.ts +++ b/server/sonar-web/src/main/js/api/components.ts @@ -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> { diff --git a/server/sonar-web/src/main/js/api/permissions.ts b/server/sonar-web/src/main/js/api/permissions.ts index 5c927bc3692..8d2cf3e5596 100644 --- a/server/sonar-web/src/main/js/api/permissions.ts +++ b/server/sonar-web/src/main/js/api/permissions.ts @@ -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); } diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index d5ff4713845..2d0bc46db36 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -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' +} diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx index da819c20f4f..6ede35e8917 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx @@ -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) { diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx index 60c55b7f5fc..60a361c1dbb 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx @@ -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"> diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ChangeVisibilityForm.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ChangeVisibilityForm.tsx index 9c04349c688..41261da71e3 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/ChangeVisibilityForm.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/ChangeVisibilityForm.tsx @@ -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; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/DeleteModal.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/DeleteModal.tsx index 7b4a45fc61b..c04cd202ab4 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/DeleteModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/DeleteModal.tsx @@ -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> diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx index ef77181c740..905c873d42e 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx @@ -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 { diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx index 9ddf15b20a5..e6612a26d19 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx @@ -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'; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx index eecfd7b4917..50e94342396 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx @@ -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> ); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx index 07339957c4c..0fb6b1d21d0 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx @@ -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', () => { diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx index 6f11a5d48da..cdb33cf7f33 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx @@ -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} diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx index d24f30e9cdf..976be4403a8 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx @@ -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} /> ); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx index 726c6b186cb..64b7d42366b 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx @@ -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' }; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx index 982ef5ce35a..b1d052dbb86 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx @@ -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 = { diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx index b1f165cde7a..2cc0ec841ef 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx @@ -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 = [ diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx index 66e0439de3f..e7ec8d71e94 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx @@ -118,7 +118,7 @@ function shallowRender(props?: { [P in keyof Props]?: Props[P] }) { ready={true} selection={[]} topLevelQualifiers={['TRK']} - total={0} + total={17} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap index bc3e39d7f64..4cadeebb4a0 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap @@ -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 diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap index e57788fbc92..12d6e222a77 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap @@ -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 diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap index 89a31381713..9e0a31ce941 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap @@ -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 diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/utils.ts b/server/sonar-web/src/main/js/apps/projectsManagement/utils.ts index aa549df26c1..0bbc7d40843 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/utils.ts +++ b/server/sonar-web/src/main/js/apps/projectsManagement/utils.ts @@ -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; diff --git a/server/sonar-web/src/main/less/components/alerts.less b/server/sonar-web/src/main/less/components/alerts.less index fb874b2ecab..65694046261 100644 --- a/server/sonar-web/src/main/less/components/alerts.less +++ b/server/sonar-web/src/main/less/components/alerts.less @@ -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) { |