From: David Cho-Lerat Date: Tue, 18 Jul 2023 14:47:42 +0000 (+0200) Subject: SONAR-19912 Display indexation project count instead of percentage X-Git-Tag: 10.2.0.77647~345 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=d3ea6786888bcdfef4eb9206fbc6ce20569a79f1;p=sonarqube.git SONAR-19912 Display indexation project count instead of percentage --- diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx b/server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx index c19b04f13e4..1a936598fb6 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx @@ -17,7 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + /* eslint-disable react/no-unused-state */ + import * as React from 'react'; import { AppState } from '../../../types/appstate'; import { IndexationContextInterface, IndexationStatus } from '../../../types/indexation'; @@ -41,7 +43,9 @@ export class IndexationContextProvider extends React.PureComponent< if (this.props.appState.needIssueSync) { IndexationNotificationHelper.startPolling(this.handleNewStatus); } else { - this.setState({ status: { isCompleted: true, percentCompleted: 100, hasFailures: false } }); + this.setState({ + status: { isCompleted: true, hasFailures: false }, + }); } } diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx index da2d32cea3e..eaee32ca95d 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import * as React from 'react'; import withIndexationContext, { WithIndexationContextProps, @@ -67,6 +68,7 @@ export class IndexationNotification extends React.PureComponent { if (!isCompleted) { IndexationNotificationHelper.markCompletedNotificationAsToDisplay(); + this.setState({ notificationType: hasFailures ? IndexationNotificationType.InProgressWithFailure @@ -78,6 +80,7 @@ export class IndexationNotification extends React.PureComponent { this.setState({ notificationType: IndexationNotificationType.Completed, }); + IndexationNotificationHelper.markCompletedNotificationAsDisplayed(); // Hide after some time @@ -91,17 +94,18 @@ export class IndexationNotification extends React.PureComponent { render() { const { notificationType } = this.state; + const { indexationContext: { - status: { percentCompleted }, + status: { completedCount, total }, }, } = this.props; return !this.isSystemAdmin ? null : ( ); } diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx index 924963867ae..bca3ce81789 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + /* eslint-disable react/no-unused-prop-types */ import classNames from 'classnames'; @@ -30,9 +31,9 @@ import { IndexationNotificationType } from '../../../types/indexation'; import { TaskStatuses, TaskTypes } from '../../../types/tasks'; export interface IndexationNotificationRendererProps { + completedCount?: number; + total?: number; type?: IndexationNotificationType; - percentCompleted: number; - isSystemAdmin: boolean; } const NOTIFICATION_VARIANTS: { [key in IndexationNotificationType]: AlertProps['variant'] } = { @@ -43,7 +44,7 @@ const NOTIFICATION_VARIANTS: { [key in IndexationNotificationType]: AlertProps[' }; export default function IndexationNotificationRenderer(props: IndexationNotificationRendererProps) { - const { type } = props; + const { completedCount, total, type } = props; return (
@@ -55,12 +56,16 @@ export default function IndexationNotificationRenderer(props: IndexationNotifica > {type !== undefined && (
- {type === IndexationNotificationType.Completed && renderCompletedBanner(props)} + {type === IndexationNotificationType.Completed && renderCompletedBanner()} + {type === IndexationNotificationType.CompletedWithFailure && - renderCompletedWithFailureBanner(props)} - {type === IndexationNotificationType.InProgress && renderInProgressBanner(props)} + renderCompletedWithFailureBanner()} + + {type === IndexationNotificationType.InProgress && + renderInProgressBanner(completedCount as number, total as number)} + {type === IndexationNotificationType.InProgressWithFailure && - renderInProgressWithFailureBanner(props)} + renderInProgressWithFailureBanner(completedCount as number, total as number)}
)} @@ -68,78 +73,77 @@ export default function IndexationNotificationRenderer(props: IndexationNotifica ); } -function renderCompletedBanner(_props: IndexationNotificationRendererProps) { +function renderCompletedBanner() { return {translate('indexation.completed')}; } -function renderCompletedWithFailureBanner(props: IndexationNotificationRendererProps) { - const { isSystemAdmin } = props; - +function renderCompletedWithFailureBanner() { return ( ); } -function renderInProgressBanner(props: IndexationNotificationRendererProps) { - const { percentCompleted, isSystemAdmin } = props; - +function renderInProgressBanner(completedCount: number, total: number) { return ( <> {`${translate('indexation.in_progress')} ${translate( 'indexation.projects_unavailable' )}`} + - {translateWithParameters('indexation.progression', percentCompleted)} + {translateWithParameters( + 'indexation.progression', + completedCount.toString(), + total.toString() + )} + + + + - {isSystemAdmin && ( - - - - )} ); } -function renderInProgressWithFailureBanner(props: IndexationNotificationRendererProps) { - const { percentCompleted, isSystemAdmin } = props; - +function renderInProgressWithFailureBanner(completedCount: number, total: number) { return ( <> {`${translate('indexation.in_progress')} ${translate( 'indexation.projects_unavailable' )}`} + diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationContextProvider-test.tsx b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationContextProvider-test.tsx index d8c052488ee..d5f09e7d59b 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationContextProvider-test.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationContextProvider-test.tsx @@ -17,11 +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 { mount } from 'enzyme'; import * as React from 'react'; import { mockAppState } from '../../../../helpers/testMocks'; import { IndexationStatus } from '../../../../types/indexation'; -import { IndexationContext } from '../IndexationContext'; import { IndexationContextProvider, IndexationContextProviderProps, @@ -46,22 +46,22 @@ it('should not start polling if no issue sync is needed', () => { expect(IndexationNotificationHelper.startPolling).not.toHaveBeenCalled(); const expectedStatus: IndexationStatus = { - isCompleted: true, - percentCompleted: 100, hasFailures: false, + isCompleted: true, }; + expect(wrapper.state().status).toEqual(expectedStatus); }); it('should update the state on new status', () => { const wrapper = mountRender(); - const triggerNewStatus = (IndexationNotificationHelper.startPolling as jest.Mock).mock + const triggerNewStatus = jest.mocked(IndexationNotificationHelper.startPolling).mock .calls[0][0] as (status: IndexationStatus) => void; + const newStatus: IndexationStatus = { - isCompleted: true, - percentCompleted: 100, hasFailures: false, + isCompleted: true, }; triggerNewStatus(newStatus); @@ -85,11 +85,6 @@ function mountRender(props?: IndexationContextProviderProps) { ); } -class TestComponent extends React.PureComponent { - context: IndexationStatus = { hasFailures: false, isCompleted: false, percentCompleted: 0 }; - static contextType = IndexationContext; - - render() { - return

TestComponent

; - } +function TestComponent() { + return

TestComponent

; } diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx index f20ce6a385b..2ca99d7af36 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { shallow } from 'enzyme'; import * as React from 'react'; import { mockCurrentUser } from '../../../../helpers/testMocks'; @@ -30,15 +31,15 @@ jest.mock('../IndexationNotificationHelper'); describe('Completed banner', () => { it('should be displayed', () => { - ( - IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock - ).mockReturnValueOnce(true); + jest + .mocked(IndexationNotificationHelper.shouldDisplayCompletedNotification) + .mockReturnValueOnce(true); const wrapper = shallowRender(); wrapper.setProps({ indexationContext: { - status: { isCompleted: true, percentCompleted: 100, hasFailures: false }, + status: { hasFailures: false, isCompleted: true }, }, }); @@ -47,13 +48,13 @@ describe('Completed banner', () => { }); it('should be displayed at startup', () => { - ( - IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock - ).mockReturnValueOnce(true); + jest + .mocked(IndexationNotificationHelper.shouldDisplayCompletedNotification) + .mockReturnValueOnce(true); const wrapper = shallowRender({ indexationContext: { - status: { isCompleted: true, percentCompleted: 100, hasFailures: false }, + status: { hasFailures: false, isCompleted: true }, }, }); @@ -61,15 +62,16 @@ describe('Completed banner', () => { expect(wrapper.state().notificationType).toBe(IndexationNotificationType.Completed); }); - it('should be hidden once displayed', () => { + it('should be hidden once completed without failure', () => { jest.useFakeTimers(); - ( - IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock - ).mockReturnValueOnce(true); + + jest + .mocked(IndexationNotificationHelper.shouldDisplayCompletedNotification) + .mockReturnValueOnce(true); const wrapper = shallowRender({ indexationContext: { - status: { isCompleted: true, percentCompleted: 100, hasFailures: false }, + status: { hasFailures: false, isCompleted: true }, }, }); @@ -77,6 +79,7 @@ describe('Completed banner', () => { expect(IndexationNotificationHelper.markCompletedNotificationAsDisplayed).toHaveBeenCalled(); jest.runAllTimers(); + expect(wrapper.state().notificationType).toBeUndefined(); jest.useRealTimers(); @@ -85,7 +88,9 @@ describe('Completed banner', () => { it('should display the completed-with-failure banner', () => { const wrapper = shallowRender({ - indexationContext: { status: { isCompleted: true, percentCompleted: 100, hasFailures: true } }, + indexationContext: { + status: { hasFailures: true, isCompleted: true }, + }, }); expect(wrapper.state().notificationType).toBe(IndexationNotificationType.CompletedWithFailure); @@ -93,7 +98,9 @@ it('should display the completed-with-failure banner', () => { it('should display the progress banner', () => { const wrapper = shallowRender({ - indexationContext: { status: { isCompleted: false, percentCompleted: 23, hasFailures: false } }, + indexationContext: { + status: { completedCount: 23, hasFailures: false, isCompleted: false, total: 42 }, + }, }); expect(IndexationNotificationHelper.markCompletedNotificationAsToDisplay).toHaveBeenCalled(); @@ -102,7 +109,9 @@ it('should display the progress banner', () => { it('should display the progress-with-failure banner', () => { const wrapper = shallowRender({ - indexationContext: { status: { isCompleted: false, percentCompleted: 23, hasFailures: true } }, + indexationContext: { + status: { completedCount: 23, hasFailures: true, isCompleted: false, total: 42 }, + }, }); expect(IndexationNotificationHelper.markCompletedNotificationAsToDisplay).toHaveBeenCalled(); @@ -114,7 +123,7 @@ function shallowRender(props?: Partial) { diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationHelper-test.tsx b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationHelper-test.tsx index a40d9767682..6b37aea7c42 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationHelper-test.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationHelper-test.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { setImmediate } from 'timers'; import { getIndexationStatus } from '../../../../api/ce'; import { get, remove, save } from '../../../../helpers/storage'; @@ -45,12 +46,15 @@ jest.mock('../../../../helpers/storage', () => ({ it('should properly start & stop polling for indexation status', async () => { const onNewStatus = jest.fn(); + const newStatus: IndexationStatus = { - isCompleted: false, - percentCompleted: 100, + completedCount: 23, hasFailures: false, + isCompleted: false, + total: 42, }; - (getIndexationStatus as jest.Mock).mockResolvedValueOnce(newStatus); + + jest.mocked(getIndexationStatus).mockResolvedValueOnce(newStatus); IndexationNotificationHelper.startPolling(onNewStatus); expect(getIndexationStatus).toHaveBeenCalled(); @@ -61,7 +65,7 @@ it('should properly start & stop polling for indexation status', async () => { jest.runOnlyPendingTimers(); expect(getIndexationStatus).toHaveBeenCalledTimes(2); - (getIndexationStatus as jest.Mock).mockClear(); + jest.mocked(getIndexationStatus).mockClear(); IndexationNotificationHelper.stopPolling(); jest.runAllTimers(); @@ -74,7 +78,7 @@ it('should properly handle the flag to show the completed banner', () => { expect(save).toHaveBeenCalledWith(expect.any(String), 'true'); - (get as jest.Mock).mockReturnValueOnce('true'); + jest.mocked(get).mockReturnValueOnce('true'); let shouldDisplay = IndexationNotificationHelper.shouldDisplayCompletedNotification(); expect(shouldDisplay).toBe(true); diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationRenderer-test.tsx b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationRenderer-test.tsx index 4c17c0b8352..0a807e30149 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationRenderer-test.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationRenderer-test.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { shallow } from 'enzyme'; import * as React from 'react'; import { IndexationNotificationType } from '../../../../types/indexation'; @@ -25,27 +26,20 @@ import IndexationNotificationRenderer, { } from '../IndexationNotificationRenderer'; it.each([ - [IndexationNotificationType.InProgress, false], - [IndexationNotificationType.InProgress, true], - [IndexationNotificationType.InProgressWithFailure, false], - [IndexationNotificationType.InProgressWithFailure, true], - [IndexationNotificationType.Completed, false], - [IndexationNotificationType.Completed, true], - [IndexationNotificationType.CompletedWithFailure, false], - [IndexationNotificationType.CompletedWithFailure, true], -])( - 'should render correctly for type=%p & isSystemAdmin=%p', - (type: IndexationNotificationType, isSystemAdmin: boolean) => { - expect(shallowRender({ type, isSystemAdmin })).toMatchSnapshot(); - } -); + [IndexationNotificationType.InProgress], + [IndexationNotificationType.InProgressWithFailure], + [IndexationNotificationType.Completed], + [IndexationNotificationType.CompletedWithFailure], +])('should render correctly for type=%p', (type: IndexationNotificationType) => { + expect(shallowRender({ type })).toMatchSnapshot(); +}); function shallowRender(props: Partial = {}) { return shallow( ); diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx b/server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx index 543fbdd4519..7ea9bc3c3c8 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx @@ -41,7 +41,9 @@ it('should not refresh the page once the indexation is complete if there were fa expect(reload).not.toHaveBeenCalled(); wrapper.setProps({ - indexationContext: { status: { isCompleted: true, percentCompleted: 100, hasFailures: true } }, + indexationContext: { + status: { hasFailures: true, isCompleted: true }, + }, }); wrapper.update(); @@ -62,7 +64,9 @@ it('should refresh the page once the indexation is complete if there were NO fai expect(reload).not.toHaveBeenCalled(); wrapper.setProps({ - indexationContext: { status: { isCompleted: true, percentCompleted: 100, hasFailures: false } }, + indexationContext: { + status: { hasFailures: false, isCompleted: true }, + }, }); wrapper.update(); @@ -74,7 +78,7 @@ function shallowRender() { return shallow( ); diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap index 6b8c3972439..2217b780edd 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should render correctly for type="Completed" & isSystemAdmin=false 1`] = ` +exports[`should render correctly for type="Completed" 1`] = `
@@ -23,61 +23,7 @@ exports[`should render correctly for type="Completed" & isSystemAdmin=false 1`]
`; -exports[`should render correctly for type="Completed" & isSystemAdmin=true 1`] = ` -
- -
- - indexation.completed - -
-
-
-`; - -exports[`should render correctly for type="CompletedWithFailure" & isSystemAdmin=false 1`] = ` -
- -
- - - -
-
-
-`; - -exports[`should render correctly for type="CompletedWithFailure" & isSystemAdmin=true 1`] = ` +exports[`should render correctly for type="CompletedWithFailure" 1`] = `
@@ -117,38 +63,7 @@ exports[`should render correctly for type="CompletedWithFailure" & isSystemAdmin
`; -exports[`should render correctly for type="InProgress" & isSystemAdmin=false 1`] = ` -
- -
- - indexation.in_progress indexation.projects_unavailable - - - - indexation.progression.25 - -
-
-
-`; - -exports[`should render correctly for type="InProgress" & isSystemAdmin=true 1`] = ` +exports[`should render correctly for type="InProgress" 1`] = `
@@ -172,7 +87,7 @@ exports[`should render correctly for type="InProgress" & isSystemAdmin=true 1`] - indexation.progression.25 + indexation.progression.23.42 `; -exports[`should render correctly for type="InProgressWithFailure" & isSystemAdmin=false 1`] = ` -
- -
- - indexation.in_progress indexation.projects_unavailable - - - - - -
-
-
-`; - -exports[`should render correctly for type="InProgressWithFailure" & isSystemAdmin=true 1`] = ` +exports[`should render correctly for type="InProgressWithFailure" 1`] = `
@@ -265,7 +141,7 @@ exports[`should render correctly for type="InProgressWithFailure" & isSystemAdmi className="spacer-right" > { const indexationContext: IndexationContextInterface = { - status: { isCompleted: true, percentCompleted: 87, hasFailures: false }, + status: { hasFailures: false, isCompleted: true }, }; const wrapper = mountRender(indexationContext); @@ -37,7 +38,7 @@ function mountRender(indexationContext?: Partial) { return mount( @@ -46,10 +47,8 @@ function mountRender(indexationContext?: Partial) { ); } -class TestComponent extends React.PureComponent { - render() { - return

TestComponent

; - } +function TestComponent(_props: WithIndexationContextProps) { + return

TestComponent

; } const TestComponentWithIndexationContext = withIndexationContext(TestComponent); diff --git a/server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx b/server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx index 4884755b7ad..518176becb9 100644 --- a/server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx +++ b/server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx @@ -46,7 +46,7 @@ function renderComponentWithIndexationGuard(showIndexationMessage: () => boolean return render( diff --git a/server/sonar-web/src/main/js/types/indexation.ts b/server/sonar-web/src/main/js/types/indexation.ts index 296d7ed701f..601edc295ba 100644 --- a/server/sonar-web/src/main/js/types/indexation.ts +++ b/server/sonar-web/src/main/js/types/indexation.ts @@ -17,12 +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 interface IndexationStatus { - isCompleted: boolean; - percentCompleted: number; - hasFailures: boolean; + +interface IndexationStatusInProgress { + completedCount: number; + isCompleted: false; + total: number; +} + +interface IndexationStatusCompleted { + completedCount?: number; + isCompleted: true; + total?: number; } +export type IndexationStatus = { + hasFailures: boolean; +} & (IndexationStatusInProgress | IndexationStatusCompleted); + export interface IndexationContextInterface { status: IndexationStatus; } 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 2066a81cdd8..b43d1ea5fc3 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -4726,8 +4726,8 @@ indexation.in_progress=SonarQube is reindexing project data. indexation.details_unavailable=Details are unavailable until this process is complete. indexation.link_unavailable=The link to these results is unavailable until this process is complete. indexation.projects_unavailable=Some projects will be unavailable until this process is complete. -indexation.progression={0}% complete. -indexation.progression_with_error={0}% complete with some {link}. +indexation.progression={0} out of {1} projects reindexed. +indexation.progression_with_error={0} out of {1} projects reindexed with some {link}. indexation.progression_with_error.link=tasks failing indexation.completed=All project data has been reloaded. indexation.completed_with_error=SonarQube completed the reload of project data. Some {link} causing some projects to remain unavailable.