Browse Source

SONAR-11609 Update the Issues bulkchange action

tags/7.7
Wouter Admiraal 5 years ago
parent
commit
92c3efb421

+ 64
- 61
server/sonar-web/src/main/js/apps/issues/components/App.tsx View File

@@ -18,10 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import * as key from 'keymaster';
import Helmet from 'react-helmet';
import { keyBy, omit, union, without } from 'lodash';
import BulkChangeModal from './BulkChangeModal';
import BulkChangeModal, { MAX_PAGE_SIZE } from './BulkChangeModal';
import ComponentBreadcrumbs from './ComponentBreadcrumbs';
import IssuesList from './IssuesList';
import IssuesSourceViewer from './IssuesSourceViewer';
@@ -51,11 +52,10 @@ import {
STANDARDS,
ReferencedRule
} from '../utils';
import { Alert } from '../../../components/ui/Alert';
import { Button } from '../../../components/ui/buttons';
import Checkbox from '../../../components/controls/Checkbox';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import Dropdown from '../../../components/controls/Dropdown';
import DropdownIcon from '../../../components/icons-components/DropdownIcon';
import EmptySearch from '../../../components/common/EmptySearch';
import FiltersHeader from '../../../components/common/FiltersHeader';
import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
@@ -110,7 +110,8 @@ interface Props {
}

export interface State {
bulkChange?: 'all' | 'selected';
bulkChangeModal: boolean;
checkAll?: boolean;
checked: string[];
effortTotal?: number;
facets: { [facet: string]: Facet };
@@ -144,6 +145,7 @@ export class App extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
bulkChangeModal: false,
checked: [],
facets: {},
issues: [],
@@ -210,6 +212,7 @@ export class App extends React.PureComponent<Props, State> {
areMyIssuesSelected(prevQuery) !== areMyIssuesSelected(query)
) {
this.fetchFirstIssues();
this.setState({ checkAll: false });
} else if (
!this.state.openIssue &&
(prevState.selected !== this.state.selected || prevState.openIssue)
@@ -515,7 +518,7 @@ export class App extends React.PureComponent<Props, State> {

const p = paging.pageIndex + 1;

this.setState({ loadingMore: true });
this.setState({ checkAll: false, loadingMore: true });
this.fetchIssuesPage(p).then(
response => {
if (this.mounted) {
@@ -623,6 +626,21 @@ export class App extends React.PureComponent<Props, State> {
return Promise.resolve({ issues, paging });
};

getButtonLabel = (checked: string[], checkAll?: boolean, paging?: T.Paging) => {
if (checked.length > 0) {
let count;
if (checkAll && paging) {
count = paging.total > MAX_PAGE_SIZE ? MAX_PAGE_SIZE : paging.total;
} else {
count = Math.min(checked.length, MAX_PAGE_SIZE);
}

return translateWithParameters('issues.bulk_change_X_issues', count);
} else {
return translate('bulk_change');
}
};

handleFilterChange = (changes: Partial<Query>) => {
this.setState({ loading: true });
this.props.router.push({
@@ -739,10 +757,11 @@ export class App extends React.PureComponent<Props, State> {
? union(checked, [state.issues[i].key])
: without(checked, state.issues[i].key);
}
return { checked };
return { checkAll: false, checked };
});
} else {
this.setState(state => ({
checkAll: false,
lastChecked: issue,
checked: state.checked.includes(issue)
? without(state.checked, issue)
@@ -757,29 +776,20 @@ export class App extends React.PureComponent<Props, State> {
}));
};

openBulkChange = (mode: 'all' | 'selected') => {
this.setState({ bulkChange: mode });
handleOpenBulkChange = () => {
key.setScope('issues-bulk-change');
this.setState({ bulkChangeModal: true });
};

closeBulkChange = () => {
handleCloseBulkChange = () => {
key.setScope('issues');
this.setState({ bulkChange: undefined });
};

handleBulkChangeClick = () => {
this.openBulkChange('all');
};

handleBulkChangeSelectedClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
event.currentTarget.blur();
this.openBulkChange('selected');
this.setState({ bulkChangeModal: false });
};

handleBulkChangeDone = () => {
this.setState({ checkAll: false });
this.fetchFirstIssues();
this.closeBulkChange();
this.handleCloseBulkChange();
};

handleReload = () => {
@@ -812,11 +822,14 @@ export class App extends React.PureComponent<Props, State> {
this.setState(actions.selectPreviousLocation);
};

onCheckAll = (checked: boolean) => {
handleCheckAll = (checked: boolean) => {
if (checked) {
this.setState(state => ({ checked: state.issues.map(issue => issue.key) }));
this.setState(state => ({
checkAll: true,
checked: state.issues.map(issue => issue.key)
}));
} else {
this.setState({ checked: [] });
this.setState({ checkAll: false, checked: [] });
}
};

@@ -834,7 +847,7 @@ export class App extends React.PureComponent<Props, State> {

renderBulkChange(openIssue: T.Issue | undefined) {
const { component, currentUser } = this.props;
const { bulkChange, checked, paging, issues } = this.state;
const { checkAll, bulkChangeModal, checked, issues, paging } = this.state;

const isAllChecked = checked.length > 0 && issues.length === checked.length;
const thirdState = checked.length > 0 && !isAllChecked;
@@ -851,45 +864,22 @@ export class App extends React.PureComponent<Props, State> {
className="spacer-right vertical-middle"
disabled={issues.length === 0}
id="issues-selection"
onCheck={this.onCheckAll}
onCheck={this.handleCheckAll}
thirdState={thirdState}
/>
{checked.length > 0 ? (
<Dropdown
className="display-inline-block"
overlay={
<ul className="menu">
<li>
<a href="#" onClick={this.handleBulkChangeClick}>
{translateWithParameters('issues.bulk_change', paging ? paging.total : 0)}
</a>
</li>
<li>
<a href="#" onClick={this.handleBulkChangeSelectedClick}>
{translateWithParameters('issues.bulk_change_selected', checked.length)}
</a>
</li>
</ul>
}>
<Button id="issues-bulk-change">
{translate('bulk_change')}
<DropdownIcon className="little-spacer-left" />
</Button>
</Dropdown>
) : (
<Button
disabled={issues.length === 0}
id="issues-bulk-change"
onClick={this.handleBulkChangeClick}>
{translate('bulk_change')}
</Button>
)}
{bulkChange && (
<Button
disabled={checked.length === 0}
id="issues-bulk-change"
onClick={this.handleOpenBulkChange}>
{this.getButtonLabel(checked, checkAll, paging)}
</Button>

{bulkChangeModal && (
<BulkChangeModal
component={component}
currentUser={currentUser}
fetchIssues={bulkChange === 'all' ? this.fetchIssues : this.getCheckedIssues}
onClose={this.closeBulkChange}
fetchIssues={checkAll ? this.fetchIssues : this.getCheckedIssues}
onClose={this.handleCloseBulkChange}
onDone={this.handleBulkChangeDone}
organization={this.props.organization}
/>
@@ -1073,7 +1063,7 @@ export class App extends React.PureComponent<Props, State> {
}

renderPage() {
const { loading, openIssue } = this.state;
const { checkAll, loading, openIssue, paging } = this.state;
return (
<div className="layout-page-main-inner">
{openIssue ? (
@@ -1089,7 +1079,20 @@ export class App extends React.PureComponent<Props, State> {
selectedLocationIndex={this.state.selectedLocationIndex}
/>
) : (
<DeferredSpinner loading={loading}>{this.renderList()}</DeferredSpinner>
<DeferredSpinner loading={loading}>
{checkAll &&
paging &&
paging.total > MAX_PAGE_SIZE && (
<Alert className="big-spacer-bottom" variant="warning">
<FormattedMessage
defaultMessage={translate('issue_bulk_change.max_issues_reached')}
id="issue_bulk_change.max_issues_reached"
values={{ max: <strong>{MAX_PAGE_SIZE}</strong> }}
/>
</Alert>
)}
{this.renderList()}
</DeferredSpinner>
)}
</div>
);

+ 24
- 21
server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx View File

@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { pickBy, sortBy } from 'lodash';
import { searchAssignees } from '../utils';
import { searchIssueTags, bulkChangeIssues } from '../../../api/issues';
@@ -30,7 +31,7 @@ import Select from '../../../components/controls/Select';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import SeverityHelper from '../../../components/shared/SeverityHelper';
import Avatar from '../../../components/ui/Avatar';
import { SubmitButton } from '../../../components/ui/buttons';
import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Alert } from '../../../components/ui/Alert';
@@ -85,6 +86,8 @@ const AssigneeSelect = SearchSelect as AssigneeSelectType;
type TagSelectType = new () => SearchSelect<TagOption>;
const TagSelect = SearchSelect as TagSelectType;

export const MAX_PAGE_SIZE = 500;

export default class BulkChangeModal extends React.PureComponent<Props, State> {
mounted = false;

@@ -104,13 +107,17 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> {
this.loadIssues(),
searchIssueTags({ organization: this.state.organization })
]).then(
([issues, tags]) => {
([{ issues, paging }, tags]) => {
if (this.mounted) {
if (issues.length > MAX_PAGE_SIZE) {
issues = issues.slice(0, MAX_PAGE_SIZE);
}

this.setState({
initialTags: tags.map(tag => ({ label: tag, value: tag })),
issues: issues.issues,
issues,
loading: false,
paging: issues.paging
paging
});
}
},
@@ -122,7 +129,9 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> {
this.mounted = false;
}

loadIssues = () => this.props.fetchIssues({ additionalFields: 'actions,transitions', ps: 250 });
loadIssues = () => {
return this.props.fetchIssues({ additionalFields: 'actions,transitions', ps: MAX_PAGE_SIZE });
};

getDefaultAssignee = () => {
const { currentUser } = this.props;
@@ -149,12 +158,6 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> {
return options;
};

handleCloseClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
event.currentTarget.blur();
this.props.onClose();
};

handleAssigneeSearch = (query: string) => {
return searchAssignees(query, this.state.organization).then(({ results }) =>
results.map(r => ({ avatar: r.avatar, label: r.name, value: r.login }))
@@ -250,12 +253,6 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> {
}));
}

renderCancelButton = () => (
<a href="#" id="bulk-change-cancel" onClick={this.handleCloseClick}>
{translate('cancel')}
</a>
);

renderLoading = () => (
<div>
<div className="modal-head">
@@ -266,7 +263,9 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> {
<i className="spinner spinner-margin" />
</div>
</div>
<div className="modal-foot">{this.renderCancelButton()}</div>
<div className="modal-foot">
<ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
</div>
</div>
);

@@ -496,7 +495,7 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> {
renderForm = () => {
const { issues, paging, submitting } = this.state;

const limitReached = paging !== undefined && paging.total > paging.pageIndex * paging.pageSize;
const limitReached = paging && paging.total > MAX_PAGE_SIZE;

return (
<form id="bulk-change-form" onSubmit={this.handleSubmit}>
@@ -507,7 +506,11 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> {
<div className="modal-body">
{limitReached && (
<Alert variant="warning">
{translateWithParameters('issue_bulk_change.max_issues_reached', issues.length)}
<FormattedMessage
defaultMessage={translate('issue_bulk_change.max_issues_reached')}
id="issue_bulk_change.max_issues_reached"
values={{ max: <strong>{MAX_PAGE_SIZE}</strong> }}
/>
</Alert>
)}

@@ -529,7 +532,7 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> {
<SubmitButton disabled={submitting || issues.length === 0} id="bulk-change-submit">
{translate('apply')}
</SubmitButton>
{this.renderCancelButton()}
<ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
</div>
</form>
);

+ 87
- 38
server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx View File

@@ -20,52 +20,25 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import { App } from '../App';
import { mockCurrentUser, mockRouter } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';

const issues = [
const ISSUES = [
{ key: 'foo' } as T.Issue,
{ key: 'bar' } as T.Issue,
{ key: 'third' } as T.Issue,
{ key: 'fourth' } as T.Issue
];
const facets = [{ property: 'severities', values: [{ val: 'MINOR', count: 4 }] }];
const paging = { pageIndex: 1, pageSize: 100, total: 4 };
const FACETS = [{ property: 'severities', values: [{ val: 'MINOR', count: 4 }] }];
const PAGING = { pageIndex: 1, pageSize: 100, total: 4 };

const eventNoShiftKey = { shiftKey: false } as MouseEvent;
const eventWithShiftKey = { shiftKey: true } as MouseEvent;

const referencedComponent = { key: 'foo-key', name: 'bar', organization: 'John', uuid: 'foo-uuid' };
const PROPS = {
branch: { isMain: true, name: 'master' },
currentUser: {
isLoggedIn: true,
avatar: 'foo',
email: 'forr@bar.com',
login: 'JohnDoe',
name: 'John Doe'
},
component: { breadcrumbs: [], key: 'foo', name: 'bar', organization: 'John', qualifier: 'Doe' },
location: { pathname: '/issues', query: {} },
fetchIssues: () =>
Promise.resolve({
components: [referencedComponent],
effortTotal: 1,
facets,
issues,
languages: [],
paging,
rules: [],
users: []
}),
onBranchesChange: () => {},
onSonarCloud: false,
organization: { key: 'foo' },
router: { push: jest.fn(), replace: jest.fn() },
userOrganizations: []
};

it('should render a list of issue', async () => {
const wrapper = shallow<App>(<App {...PROPS} />);
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper.state().issues.length).toBe(4);
expect(wrapper.state().referencedComponentsById).toEqual({ 'foo-uuid': referencedComponent });
@@ -73,7 +46,7 @@ it('should render a list of issue', async () => {
});

it('should be able to check/uncheck a group of issues with the Shift key', async () => {
const wrapper = shallow<App>(<App {...PROPS} />);
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper.state().issues.length).toBe(4);

@@ -92,7 +65,7 @@ it('should be able to check/uncheck a group of issues with the Shift key', async
});

it('should avoid non-existing keys', async () => {
const wrapper = shallow<App>(<App {...PROPS} />);
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper.state().issues.length).toBe(4);

@@ -105,7 +78,7 @@ it('should avoid non-existing keys', async () => {
});

it('should be able to uncheck all issue with global checkbox', async () => {
const wrapper = shallow<App>(<App {...PROPS} />);
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper.state().issues.length).toBe(4);

@@ -114,16 +87,92 @@ it('should be able to uncheck all issue with global checkbox', async () => {
instance.handleIssueCheck('bar', eventNoShiftKey);
expect(wrapper.state().checked.length).toBe(2);

instance.onCheckAll(false);
instance.handleCheckAll(false);
expect(wrapper.state().checked.length).toBe(0);
});

it('should be able to check all issue with global checkbox', async () => {
const wrapper = shallow<App>(<App {...PROPS} />);
const wrapper = shallowRender();
await waitAndUpdate(wrapper);

const instance = wrapper.instance();
expect(wrapper.state().checked.length).toBe(0);
instance.onCheckAll(true);
instance.handleCheckAll(true);
expect(wrapper.state().checked.length).toBe(wrapper.state().issues.length);
});

it('should check all issues, even the ones that are not visible', async () => {
const wrapper = shallowRender({
fetchIssues: jest.fn().mockResolvedValue({
components: [referencedComponent],
effortTotal: 1,
facets: FACETS,
issues: ISSUES,
languages: [],
paging: { pageIndex: 1, pageSize: 100, total: 250 },
rules: [],
users: []
})
});
const instance = wrapper.instance();
await waitAndUpdate(wrapper);

// Checking all issues should show the correct count in the Bulk Change button.
instance.handleCheckAll(true);
waitAndUpdate(wrapper);
expect(wrapper.find('#issues-bulk-change')).toMatchSnapshot();
});

it('should check max 500 issues', async () => {
const wrapper = shallowRender({
fetchIssues: jest.fn().mockResolvedValue({
components: [referencedComponent],
effortTotal: 1,
facets: FACETS,
issues: ISSUES,
languages: [],
paging: { pageIndex: 1, pageSize: 100, total: 1000 },
rules: [],
users: []
})
});
const instance = wrapper.instance();
await waitAndUpdate(wrapper);

// Checking all issues should show 500 in the Bulk Change button, and display
// a warning.
instance.handleCheckAll(true);
waitAndUpdate(wrapper);
expect(wrapper.find('#issues-bulk-change')).toMatchSnapshot();
});

function shallowRender(props: Partial<App['props']> = {}) {
return shallow<App>(
<App
component={{
breadcrumbs: [],
key: 'foo',
name: 'bar',
organization: 'John',
qualifier: 'Doe'
}}
currentUser={mockCurrentUser()}
fetchIssues={jest.fn().mockResolvedValue({
components: [referencedComponent],
effortTotal: 1,
facets: FACETS,
issues: ISSUES,
languages: [],
paging: PAGING,
rules: [],
users: []
})}
location={{ pathname: '/issues', query: {} }}
onBranchesChange={() => {}}
organization={{ key: 'foo' }}
router={mockRouter()}
userOrganizations={[]}
{...props}
/>
);
}

+ 24
- 30
server/sonar-web/src/main/js/apps/issues/components/__tests__/BulkChangeModal-test.tsx View File

@@ -19,13 +19,20 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import BulkChangeModal from '../BulkChangeModal';
import BulkChangeModal, { MAX_PAGE_SIZE } from '../BulkChangeModal';
import { mockIssue } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';

jest.mock('../../../../api/issues', () => ({
searchIssueTags: () => Promise.resolve([undefined, []])
}));

jest.mock('../BulkChangeModal', () => {
const mock = require.requireActual('../BulkChangeModal');
mock.MAX_PAGE_SIZE = 1;
return mock;
});

it('should display error message when no issues available', async () => {
const wrapper = getWrapper([]);
await waitAndUpdate(wrapper);
@@ -33,36 +40,23 @@ it('should display error message when no issues available', async () => {
});

it('should display form when issues are present', async () => {
const wrapper = getWrapper([
{
actions: [],
component: 'foo',
componentLongName: 'foo',
componentQualifier: 'foo',
componentUuid: 'foo',
creationDate: 'foo',
key: 'foo',
flows: [],
fromHotspot: false,
message: 'foo',
organization: 'foo',
project: 'foo',
projectName: 'foo',
projectOrganization: 'foo',
projectKey: 'foo',
rule: 'foo',
ruleName: 'foo',
secondaryLocations: [],
severity: 'foo',
status: 'foo',
transitions: [],
type: 'BUG'
}
]);
const wrapper = getWrapper([mockIssue()]);
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});

it('should display warning when too many issues are passed', async () => {
const issues: T.Issue[] = [];
for (let i = MAX_PAGE_SIZE + 1; i > 0; i--) {
issues.push(mockIssue());
}

const wrapper = getWrapper(issues);
await waitAndUpdate(wrapper);
expect(wrapper.find('h2')).toMatchSnapshot();
expect(wrapper.find('Alert')).toMatchSnapshot();
});

const getWrapper = (issues: T.Issue[]) => {
return shallow(
<BulkChangeModal
@@ -72,9 +66,9 @@ const getWrapper = (issues: T.Issue[]) => {
Promise.resolve({
issues,
paging: {
pageIndex: 0,
pageSize: 0,
total: 0
pageIndex: issues.length,
pageSize: issues.length,
total: issues.length
}
})
}

+ 21
- 0
server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/App-test.tsx.snap View File

@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should check all issues, even the ones that are not visible 1`] = `
<Button
disabled={false}
id="issues-bulk-change"
onClick={[Function]}
>
issues.bulk_change_X_issues.250
</Button>
`;

exports[`should check max 500 issues 1`] = `
<Button
disabled={false}
id="issues-bulk-change"
onClick={[Function]}
>
issues.bulk_change_X_issues.500
</Button>
`;

+ 28
- 8
server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap View File

@@ -34,13 +34,11 @@ exports[`should display error message when no issues available 1`] = `
>
apply
</SubmitButton>
<a
href="#"
id="bulk-change-cancel"
<ResetButtonLink
onClick={[Function]}
>
cancel
</a>
</ResetButtonLink>
</div>
</form>
</Modal>
@@ -90,14 +88,36 @@ exports[`should display form when issues are present 1`] = `
>
apply
</SubmitButton>
<a
href="#"
id="bulk-change-cancel"
<ResetButtonLink
onClick={[Function]}
>
cancel
</a>
</ResetButtonLink>
</div>
</form>
</Modal>
`;

exports[`should display warning when too many issues are passed 1`] = `
<h2>
issue_bulk_change.form.title.1
</h2>
`;

exports[`should display warning when too many issues are passed 2`] = `
<Alert
variant="warning"
>
<FormattedMessage
defaultMessage="issue_bulk_change.max_issues_reached"
id="issue_bulk_change.max_issues_reached"
values={
Object {
"max": <strong>
1
</strong>,
}
}
/>
</Alert>
`;

+ 0
- 7
server/sonar-web/src/main/js/apps/issues/styles.css View File

@@ -25,13 +25,6 @@
margin-right: 2px;
}

.concise-issues-list-header,
.concise-issues-list-header-inner {
}

.concise-issues-list-header {
}

.concise-issues-list-header-inner {
width: 260px;
text-align: center;

+ 28
- 0
server/sonar-web/src/main/js/helpers/testMocks.ts View File

@@ -71,6 +71,34 @@ export function mockEvent(overrides = {}) {
} as any;
}

export function mockIssue(overrides = {}): T.Issue {
return {
actions: [],
component: 'my-component',
componentLongName: 'My Component',
componentQualifier: 'my-component',
componentUuid: 'uuid',
creationDate: 'date',
key: 'foo',
flows: [],
fromHotspot: false,
message: 'Message',
organization: 'foo',
project: 'my-project',
projectName: 'My Project',
projectOrganization: 'org',
projectKey: 'key',
rule: 'rule',
ruleName: 'Rule',
secondaryLocations: [],
severity: 'severity',
status: 'status',
transitions: [],
type: 'BUG',
...overrides
};
}

export function mockLocation(overrides: Partial<Location> = {}): Location {
return {
action: 'PUSH',

+ 2
- 3
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -665,8 +665,7 @@ issue.this_issue_involves_x_code_locations=This issue involves {0} code location
issue.from_external_rule_engine=Issue detected by an external rule engine: {0}
issue.external_issue_description=This is external rule {0}. No details are available.
issues.return_to_list=Return to List
issues.bulk_change=All Issues ({0})
issues.bulk_change_selected=Selected Issues ({0})
issues.bulk_change_X_issues=Bulk Change {0} Issue(s)
issues.issues=issues
issues.to_select_issues=to select issues
issues.to_navigate=to navigate
@@ -745,7 +744,7 @@ issues.facet.cwe=CWE

issue_bulk_change.form.title=Change {0} issues
issue_bulk_change.comment.help=This comment will be applied only to issues that will effectively be modified
issue_bulk_change.max_issues_reached=As too many issues have been selected, only the first {0} issues will be updated.
issue_bulk_change.max_issues_reached=There are more issues available than can be treated by a single bulk action. Your changes will only be applied to the first {max} issues.
issue_bulk_change.x_issues={0} issues
issue_bulk_change.no_match=There is no issue matching your filter selection


Loading…
Cancel
Save