diff options
15 files changed, 123 insertions, 95 deletions
diff --git a/server/sonar-web/src/main/js/api/mocks/ComputeEngineServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/ComputeEngineServiceMock.ts index f6557aee6c1..0b5d6ba1938 100644 --- a/server/sonar-web/src/main/js/api/mocks/ComputeEngineServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/ComputeEngineServiceMock.ts @@ -19,6 +19,7 @@ */ import { differenceInMilliseconds, isAfter, isBefore } from 'date-fns'; import { cloneDeep, groupBy, sortBy } from 'lodash'; +import { PAGE_SIZE } from '../../apps/background-tasks/constants'; import { parseDate } from '../../helpers/dates'; import { mockTask } from '../../helpers/mocks/tasks'; import { ActivityRequestParameters, Task, TaskStatuses, TaskTypes } from '../../types/tasks'; @@ -35,8 +36,6 @@ import { const RANDOM_RADIX = 36; const RANDOM_PREFIX = 2; -const PAGE_SIZE = 100; - const TASK_TYPES = [ TaskTypes.Report, TaskTypes.IssueSync, diff --git a/server/sonar-web/src/main/js/api/mocks/NotificationsMock.ts b/server/sonar-web/src/main/js/api/mocks/NotificationsMock.ts index 7e50a368fef..c7a3a974e34 100644 --- a/server/sonar-web/src/main/js/api/mocks/NotificationsMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/NotificationsMock.ts @@ -22,22 +22,14 @@ import { cloneDeep } from 'lodash'; import { AddRemoveNotificationParameters, Notification, + NotificationGlobalType, + NotificationProjectType, NotificationsResponse, } from '../../types/notifications'; import { addNotification, getNotifications, removeNotification } from '../notifications'; /* Constants */ const channels = ['EmailNotificationChannel']; -const globalTypes = ['CeReportTaskFailure', 'ChangesOnMyIssue', 'NewAlerts', 'SQ-MyNewIssues']; -const perProjectTypes = [ - 'CeReportTaskFailure', - 'ChangesOnMyIssue', - 'NewAlerts', - 'NewFalsePositiveIssue', - 'NewIssues', - 'SQ-MyNewIssues', -]; - const defaultNotifications: Notification[] = [ { channel: 'EmailNotificationChannel', type: 'ChangesOnMyIssue' }, ]; @@ -56,9 +48,9 @@ export default class NotificationsMock { handleGetNotifications: () => Promise<NotificationsResponse> = () => { return Promise.resolve({ channels: [...channels], - globalTypes: [...globalTypes], + globalTypes: Object.values(NotificationGlobalType), notifications: cloneDeep(this.notifications), - perProjectTypes: [...perProjectTypes], + perProjectTypes: Object.values(NotificationProjectType), }); }; diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/notifications/__tests__/ProjectNotifications-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/notifications/__tests__/ProjectNotifications-test.tsx index fc9aca67652..f78bb556f5f 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/notifications/__tests__/ProjectNotifications-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/notifications/__tests__/ProjectNotifications-test.tsx @@ -20,6 +20,10 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { mockComponent } from '../../../../../../../helpers/mocks/component'; +import { + NotificationGlobalType, + NotificationProjectType, +} from '../../../../../../../types/notifications'; import { ProjectNotifications } from '../ProjectNotifications'; jest.mock('react', () => { @@ -65,7 +69,7 @@ function shallowRender(props = {}) { addNotification={jest.fn()} channels={['channel1', 'channel2']} component={mockComponent({ key: 'foo' })} - globalTypes={['type-global', 'type-common']} + globalTypes={[NotificationGlobalType.CeReportTaskFailure, NotificationGlobalType.NewAlerts]} loading={false} notifications={[ { @@ -87,7 +91,7 @@ function shallowRender(props = {}) { projectName: 'Qux', }, ]} - perProjectTypes={['type-common']} + perProjectTypes={[NotificationProjectType.CeReportTaskFailure]} removeNotification={jest.fn()} {...props} /> diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/notifications/__tests__/__snapshots__/ProjectNotifications-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/notifications/__tests__/__snapshots__/ProjectNotifications-test.tsx.snap index e5cbd52d210..3a02b975e48 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/notifications/__tests__/__snapshots__/ProjectNotifications-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/notifications/__tests__/__snapshots__/ProjectNotifications-test.tsx.snap @@ -66,7 +66,7 @@ exports[`should render correctly 1`] = ` project={true} types={ Array [ - "type-common", + "CeReportTaskFailure", ] } /> diff --git a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx index e5b10e019ea..2afa53b8540 100644 --- a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx +++ b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx @@ -17,10 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { screen, waitFor, within } from '@testing-library/react'; +import { screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { UserEvent } from '@testing-library/user-event/dist/types/setup'; import selectEvent from 'react-select-event'; +import { byLabelText, byRole, byText } from 'testing-library-selector'; import { getMyProjects, getScannableProjects } from '../../../api/components'; import NotificationsMock from '../../../api/mocks/NotificationsMock'; import UserTokensMock from '../../../api/mocks/UserTokensMock'; @@ -28,6 +29,7 @@ import { mockUserToken } from '../../../helpers/mocks/token'; import { setKeyboardShortcutEnabled } from '../../../helpers/preferences'; import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks'; import { renderAppRoutes } from '../../../helpers/testReactTestingUtils'; +import { NotificationGlobalType, NotificationProjectType } from '../../../types/notifications'; import { Permissions } from '../../../types/permissions'; import { TokenType } from '../../../types/token'; import { CurrentUser } from '../../../types/users'; @@ -463,6 +465,27 @@ describe('security page', () => { }); describe('notifications page', () => { + const projectUI = { + title: byRole('button', { name: 'my_profile.per_project_notifications.add' }), + addButton: byRole('button', { name: 'my_profile.per_project_notifications.add' }), + addModalButton: byRole('button', { name: 'add_verb' }), + searchInput: byLabelText('search_verb', { selector: 'input' }), + sonarQubeProject: byRole('heading', { name: 'SonarQube' }), + checkbox: (type: NotificationProjectType) => + byRole('checkbox', { + name: `notification.dispatcher.descrption_x.notification.dispatcher.${type}.project`, + }), + }; + + const globalUI = { + title: byRole('heading', { name: 'my_profile.overall_notifications.title' }), + noNotificationForProject: byText('my_account.no_project_notifications'), + checkbox: (type: NotificationGlobalType) => + byRole('checkbox', { + name: `notification.dispatcher.descrption_x.notification.dispatcher.${type}`, + }), + }; + let notificationsMock: NotificationsMock; beforeAll(() => { notificationsMock = new NotificationsMock(); @@ -475,43 +498,28 @@ describe('notifications page', () => { const notificationsPagePath = 'account/notifications'; it('should display global notifications status and allow edits', async () => { - const user = userEvent.setup(); + const user = userEvent.setup({ delay: null }); renderAccountApp(mockLoggedInUser(), notificationsPagePath); - expect( - await screen.findByRole('heading', { name: 'my_profile.overall_notifications.title' }) - ).toBeInTheDocument(); - - expect(screen.getAllByRole('row')).toHaveLength(5); // 4 + header + expect(await globalUI.title.find()).toBeInTheDocument(); /* * Verify Checkbox statuses */ - expect(getCheckboxByRowName('notification.dispatcher.ChangesOnMyIssue')).toBeChecked(); - - // first row is the header: skip it! - const otherRows = screen - .getAllByRole('row', { - name: (n: string) => n !== 'notification.dispatcher.ChangesOnMyIssue', - }) - .slice(1); - - otherRows.forEach((row) => { - expect(within(row).getByRole('checkbox')).not.toBeChecked(); - }); - - // Make sure the second block is empty - expect(screen.getByText('my_account.no_project_notifications')).toBeInTheDocument(); + expect(globalUI.checkbox(NotificationGlobalType.ChangesOnMyIssue).get()).toBeChecked(); + expect(globalUI.checkbox(NotificationGlobalType.CeReportTaskFailure).get()).not.toBeChecked(); + expect(globalUI.checkbox(NotificationGlobalType.NewAlerts).get()).not.toBeChecked(); + expect(globalUI.checkbox(NotificationGlobalType.MyNewIssues).get()).not.toBeChecked(); /* * Update notifications */ - await user.click(getCheckboxByRowName('notification.dispatcher.ChangesOnMyIssue')); - expect(getCheckboxByRowName('notification.dispatcher.ChangesOnMyIssue')).not.toBeChecked(); + await user.click(globalUI.checkbox(NotificationGlobalType.ChangesOnMyIssue).get()); + expect(globalUI.checkbox(NotificationGlobalType.ChangesOnMyIssue).get()).not.toBeChecked(); - await user.click(getCheckboxByRowName('notification.dispatcher.NewAlerts')); - expect(getCheckboxByRowName('notification.dispatcher.NewAlerts')).toBeChecked(); + await user.click(globalUI.checkbox(NotificationGlobalType.NewAlerts).get()); + expect(globalUI.checkbox(NotificationGlobalType.NewAlerts).get()).toBeChecked(); }); it('should allow adding notifications for a project', async () => { @@ -519,35 +527,23 @@ describe('notifications page', () => { renderAccountApp(mockLoggedInUser(), notificationsPagePath); - await user.click( - await screen.findByRole('button', { name: 'my_profile.per_project_notifications.add' }) - ); - - expect(await screen.findByLabelText('search_verb', { selector: 'input' })).toBeInTheDocument(); - - expect(screen.getByRole('button', { name: 'add_verb' })).toBeDisabled(); - - await user.keyboard('sonar'); + await user.click(await projectUI.addButton.find()); + expect(projectUI.addModalButton.get()).toBeDisabled(); + await user.type(projectUI.searchInput.get(), 'sonar'); // navigate within the two results, choose the first: await user.keyboard('[ArrowDown][ArrowDown][ArrowUp][Enter]'); + await user.click(projectUI.addModalButton.get()); - const addButton = screen.getByRole('button', { name: 'add_verb' }); - expect(addButton).toBeEnabled(); - - await user.click(addButton); - - expect(screen.getByRole('heading', { name: 'SonarQube' })).toBeInTheDocument(); + expect(projectUI.sonarQubeProject.get()).toBeInTheDocument(); expect( - getCheckboxByRowName('notification.dispatcher.NewFalsePositiveIssue.project') + projectUI.checkbox(NotificationProjectType.NewFalsePositiveIssue).get() ).toBeInTheDocument(); - await user.click(getCheckboxByRowName('notification.dispatcher.NewAlerts.project')); - expect(getCheckboxByRowName('notification.dispatcher.NewAlerts.project')).toBeChecked(); - - expect(screen.getAllByRole('checkbox', { checked: true })).toHaveLength(2); + await user.click(projectUI.checkbox(NotificationProjectType.NewAlerts).get()); + expect(projectUI.checkbox(NotificationProjectType.NewAlerts).get()).toBeChecked(); - await user.click(getCheckboxByRowName('notification.dispatcher.NewAlerts.project')); - expect(getCheckboxByRowName('notification.dispatcher.NewAlerts.project')).not.toBeChecked(); + await user.click(projectUI.checkbox(NotificationProjectType.NewAlerts).get()); + expect(projectUI.checkbox(NotificationProjectType.NewAlerts).get()).not.toBeChecked(); }); it('should allow searching for projects', async () => { @@ -571,9 +567,7 @@ describe('notifications page', () => { await user.click(screen.getByRole('searchbox')); await user.keyboard('bla'); - await waitFor(() => { - expect(screen.queryByRole('heading', { name: 'SonarQube' })).not.toBeInTheDocument(); - }); + expect(screen.queryByRole('heading', { name: 'SonarQube' })).not.toBeInTheDocument(); await user.keyboard('[Backspace>3/]'); @@ -667,10 +661,6 @@ function getProjectBlock(projectName: string) { return result; } -function getCheckboxByRowName(name: string) { - return within(screen.getByRole('row', { name })).getByRole('checkbox'); -} - function renderAccountApp(currentUser: CurrentUser, navigateTo?: string) { renderAppRoutes('account', routes, { currentUser, navigateTo }); } diff --git a/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx b/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx index 44dbc35be40..c628b209c2f 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx +++ b/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { translate } from '../../../helpers/l10n'; -import { Notification } from '../../../types/notifications'; +import { Notification, NotificationGlobalType } from '../../../types/notifications'; import NotificationsList from './NotificationsList'; interface Props { @@ -27,7 +27,7 @@ interface Props { channels: string[]; notifications: Notification[]; removeNotification: (n: Notification) => void; - types: string[]; + types: NotificationGlobalType[]; } export default function GlobalNotifications(props: Props) { diff --git a/server/sonar-web/src/main/js/apps/account/notifications/NotificationsList.tsx b/server/sonar-web/src/main/js/apps/account/notifications/NotificationsList.tsx index 4724837223f..62787863478 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/NotificationsList.tsx +++ b/server/sonar-web/src/main/js/apps/account/notifications/NotificationsList.tsx @@ -19,8 +19,12 @@ */ import * as React from 'react'; import Checkbox from '../../../components/controls/Checkbox'; -import { hasMessage, translate } from '../../../helpers/l10n'; -import { Notification } from '../../../types/notifications'; +import { hasMessage, translate, translateWithParameters } from '../../../helpers/l10n'; +import { + Notification, + NotificationGlobalType, + NotificationProjectType, +} from '../../../types/notifications'; interface Props { onAdd: (n: Notification) => void; @@ -28,7 +32,7 @@ interface Props { channels: string[]; checkboxId: (type: string, channel: string) => string; project?: boolean; - types: string[]; + types: (NotificationGlobalType | NotificationProjectType)[]; notifications: Notification[]; } @@ -67,6 +71,10 @@ export default class NotificationsList extends React.PureComponent<Props> { {channels.map((channel) => ( <td className="text-center" key={channel}> <Checkbox + label={translateWithParameters( + 'notification.dispatcher.descrption_x', + this.getDispatcherLabel(type) + )} checked={this.isEnabled(type, channel)} id={checkboxId(type, channel)} onCheck={(checked) => this.handleCheck(type, channel, checked)} diff --git a/server/sonar-web/src/main/js/apps/account/notifications/ProjectNotifications.tsx b/server/sonar-web/src/main/js/apps/account/notifications/ProjectNotifications.tsx index f97ac66cf14..45b6bc0ab1f 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/ProjectNotifications.tsx +++ b/server/sonar-web/src/main/js/apps/account/notifications/ProjectNotifications.tsx @@ -20,7 +20,11 @@ import * as React from 'react'; import BoxedGroupAccordion from '../../../components/controls/BoxedGroupAccordion'; import { translate } from '../../../helpers/l10n'; -import { Notification, NotificationProject } from '../../../types/notifications'; +import { + Notification, + NotificationProject, + NotificationProjectType, +} from '../../../types/notifications'; import NotificationsList from './NotificationsList'; interface Props { @@ -30,7 +34,7 @@ interface Props { notifications: Notification[]; project: NotificationProject; removeNotification: (n: Notification) => void; - types: string[]; + types: NotificationProjectType[]; } export default function ProjectNotifications(props: Props) { diff --git a/server/sonar-web/src/main/js/apps/account/notifications/Projects.tsx b/server/sonar-web/src/main/js/apps/account/notifications/Projects.tsx index c8b4218b86f..de2ed9d3125 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/Projects.tsx +++ b/server/sonar-web/src/main/js/apps/account/notifications/Projects.tsx @@ -22,7 +22,11 @@ import * as React from 'react'; import { Button } from '../../../components/controls/buttons'; import SearchBox from '../../../components/controls/SearchBox'; import { translate } from '../../../helpers/l10n'; -import { Notification, NotificationProject } from '../../../types/notifications'; +import { + Notification, + NotificationProject, + NotificationProjectType, +} from '../../../types/notifications'; import ProjectModal from './ProjectModal'; import ProjectNotifications from './ProjectNotifications'; @@ -31,7 +35,7 @@ export interface Props { channels: string[]; notifications: Notification[]; removeNotification: (n: Notification) => void; - types: string[]; + types: NotificationProjectType[]; } const THRESHOLD_COLLAPSED = 3; diff --git a/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx b/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx index d17e204a9a0..12c62a410f0 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx @@ -28,6 +28,12 @@ import routes from '../routes'; jest.mock('../../../api/ce'); +jest.mock('../constants', () => { + const contants = jest.requireActual('../constants'); + + return { ...contants, PAGE_SIZE: 9 }; +}); + let computeEngineServiceMock: ComputeEngineServiceMock; beforeAll(() => { @@ -139,7 +145,7 @@ describe('The Global background task page', () => { const user = userEvent.setup(); computeEngineServiceMock.clearTasks(); - computeEngineServiceMock.createTasks(101); + computeEngineServiceMock.createTasks(10); renderGlobalBackgroundTasksApp(); @@ -147,12 +153,12 @@ describe('The Global background task page', () => { await screen.findByRole('heading', { name: 'background_tasks.page' }) ).toBeInTheDocument(); - expect(screen.getAllByRole('row')).toHaveLength(101); // including header + expect(screen.getAllByRole('row')).toHaveLength(10); // including header user.click(screen.getByRole('button', { name: 'show_more' })); await waitFor(() => { - expect(screen.getAllByRole('row')).toHaveLength(102); // including header + expect(screen.getAllByRole('row')).toHaveLength(11); // including header }); }); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx index c4d5ffac300..baeb0650bb0 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx @@ -37,7 +37,7 @@ import { parseAsDate } from '../../../helpers/query'; import { Task, TaskStatuses } from '../../../types/tasks'; import { Component, Paging, RawQuery } from '../../../types/types'; import '../background-tasks.css'; -import { CURRENTS, DEBOUNCE_DELAY, DEFAULT_FILTERS } from '../constants'; +import { CURRENTS, DEBOUNCE_DELAY, DEFAULT_FILTERS, PAGE_SIZE } from '../constants'; import { mapFiltersToParameters, Query, updateTask } from '../utils'; import Header from './Header'; import Search from './Search'; @@ -60,8 +60,6 @@ interface State { types?: string[]; } -const PAGE_SIZE = 100; - export class BackgroundTasksApp extends React.PureComponent<Props, State> { loadTasksDebounced: () => void; mounted = false; diff --git a/server/sonar-web/src/main/js/apps/background-tasks/constants.ts b/server/sonar-web/src/main/js/apps/background-tasks/constants.ts index aed5b4cff77..68b002559e4 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/constants.ts +++ b/server/sonar-web/src/main/js/apps/background-tasks/constants.ts @@ -49,3 +49,5 @@ export const DEFAULT_FILTERS: Query = { export const DATE_FORMAT = 'YYYY-MM-DD'; export const DEBOUNCE_DELAY = 250; + +export const PAGE_SIZE = 100; diff --git a/server/sonar-web/src/main/js/components/hoc/withNotifications.tsx b/server/sonar-web/src/main/js/components/hoc/withNotifications.tsx index 1ab261350f5..ad63bd6a22b 100644 --- a/server/sonar-web/src/main/js/components/hoc/withNotifications.tsx +++ b/server/sonar-web/src/main/js/components/hoc/withNotifications.tsx @@ -20,24 +20,28 @@ import { uniqWith } from 'lodash'; import * as React from 'react'; import { addNotification, getNotifications, removeNotification } from '../../api/notifications'; -import { Notification } from '../../types/notifications'; +import { + Notification, + NotificationGlobalType, + NotificationProjectType, +} from '../../types/notifications'; import { getWrappedDisplayName } from './utils'; interface State { channels: string[]; - globalTypes: string[]; + globalTypes: NotificationGlobalType[]; loading: boolean; notifications: Notification[]; - perProjectTypes: string[]; + perProjectTypes: NotificationProjectType[]; } export interface WithNotificationsProps { addNotification: (added: Notification) => void; channels: string[]; - globalTypes: string[]; + globalTypes: NotificationGlobalType[]; loading: boolean; notifications: Notification[]; - perProjectTypes: string[]; + perProjectTypes: NotificationProjectType[]; removeNotification: (removed: Notification) => void; } diff --git a/server/sonar-web/src/main/js/types/notifications.ts b/server/sonar-web/src/main/js/types/notifications.ts index ab3d9809f3c..45522f862a0 100644 --- a/server/sonar-web/src/main/js/types/notifications.ts +++ b/server/sonar-web/src/main/js/types/notifications.ts @@ -17,6 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +export enum NotificationGlobalType { + CeReportTaskFailure = 'CeReportTaskFailure', + ChangesOnMyIssue = 'ChangesOnMyIssue', + NewAlerts = 'NewAlerts', + MyNewIssues = 'SQ-MyNewIssues', +} + +export enum NotificationProjectType { + CeReportTaskFailure = 'CeReportTaskFailure', + ChangesOnMyIssue = 'ChangesOnMyIssue', + NewAlerts = 'NewAlerts', + NewFalsePositiveIssue = 'NewFalsePositiveIssue', + NewIssues = 'NewIssues', + MyNewIssues = 'SQ-MyNewIssues', +} + export interface Notification { channel: string; project?: string; @@ -31,9 +48,9 @@ export interface NotificationProject { export interface NotificationsResponse { channels: string[]; - globalTypes: string[]; + globalTypes: NotificationGlobalType[]; notifications: Notification[]; - perProjectTypes: string[]; + perProjectTypes: NotificationProjectType[]; } export interface AddRemoveNotificationParameters { diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 4360fd4b5fb..f59cbb70b43 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2043,7 +2043,7 @@ notification.dispatcher.NewFalsePositiveIssue=Issues resolved as false positive notification.dispatcher.SQ-MyNewIssues=My new issues notification.dispatcher.CeReportTaskFailure=Background tasks in failure on my administered projects notification.dispatcher.CeReportTaskFailure.project=Background tasks in failure - +notification.dispatcher.descrption_x=Check to receive notification for {0} #------------------------------------------------------------------------------ # |