From 227f5d3bfc797c8ecc7ebe7e283af3b68522c1ad Mon Sep 17 00:00:00 2001 From: Mathieu Suen Date: Tue, 13 Sep 2022 17:10:26 +0200 Subject: [PATCH] SONAR-17285 Display new data and execution type flows for issues --- .../main/js/api/mocks/IssuesServiceMock.ts | 93 +++++++++++++++++++ server/sonar-web/src/main/js/api/webhooks.ts | 3 +- .../js/app/styles/components/boxed-group.css | 14 +-- .../src/main/js/app/styles/init/misc.css | 4 + server/sonar-web/src/main/js/app/theme.js | 3 + .../js/apps/issues/__tests__/IssueApp-it.tsx | 52 ++++++++++- .../js/apps/issues/__tests__/actions-test.ts | 2 +- .../src/main/js/apps/issues/actions.ts | 52 +++++++---- .../js/apps/issues/components/IssuesApp.tsx | 7 +- .../__snapshots__/IssuesList-test.tsx.snap | 3 + .../IssuesSourceViewer-test.tsx.snap | 44 +++++---- .../__snapshots__/ListItem-test.tsx.snap | 2 + .../issues/conciseIssuesList/ConciseIssue.tsx | 2 +- .../conciseIssuesList/ConciseIssueBox.tsx | 72 +++++++------- .../ConciseIssueLocationBadge.tsx | 12 ++- .../conciseIssuesList/ConciseIssuesList.tsx | 2 +- .../__tests__/ConciseIssue-test.tsx | 1 + .../__snapshots__/ConciseIssue-test.tsx.snap | 1 + .../ConciseIssueBox-test.tsx.snap | 14 +-- .../CrossComponentSourceViewer-test.tsx.snap | 3 + .../src/main/js/apps/issues/styles.css | 4 + .../src/main/js/apps/issues/utils.ts | 19 ++-- .../security-hotspots/SecurityHotspotsApp.tsx | 9 -- .../SecurityHotspotsAppRenderer.tsx | 3 - .../__tests__/SecurityHotspotsApp-test.tsx | 16 ---- .../SecurityHotspotsAppRenderer-test.tsx | 1 - .../SecurityHotspotsApp-test.tsx.snap | 1 - .../SecurityHotspotsAppRenderer-test.tsx.snap | 3 - .../components/HotspotCategory.tsx | 2 - .../components/HotspotList.css | 11 ++- .../components/HotspotList.tsx | 2 - .../components/HotspotListItem.tsx | 5 +- .../components/HotspotSimpleList.tsx | 2 - .../__tests__/HotspotCategory-test.tsx | 1 - .../components/__tests__/HotspotList-test.tsx | 1 - .../__tests__/HotspotListItem-test.tsx | 1 - .../__tests__/HotspotSimpleList-test.tsx | 1 - .../HotspotCategory-test.tsx.snap | 6 -- .../__snapshots__/HotspotList-test.tsx.snap | 8 -- .../HotspotListItem-test.tsx.snap | 4 +- .../HotspotSimpleList-test.tsx.snap | 8 -- .../components/info-items/HealthCard.tsx | 1 - .../main/js/apps/webhooks/components/App.tsx | 3 +- .../webhooks/components/CreateWebhookForm.tsx | 2 +- .../webhooks/components/DeleteWebhookForm.tsx | 2 +- .../webhooks/components/DeliveriesForm.tsx | 3 +- .../webhooks/components/DeliveryAccordion.tsx | 7 +- .../apps/webhooks/components/DeliveryItem.tsx | 2 +- .../components/LatestDeliveryForm.tsx | 2 +- .../webhooks/components/WebhookActions.tsx | 2 +- .../apps/webhooks/components/WebhookItem.tsx | 2 +- .../components/WebhookItemLatestDelivery.tsx | 2 +- .../apps/webhooks/components/WebhooksList.tsx | 2 +- .../__tests__/DeliveryAccordion-test.tsx | 64 ++++++++----- .../DeliveryAccordion-test.tsx.snap | 56 ----------- .../__snapshots__/DeliveryItem-test.tsx.snap | 83 ----------------- .../__snapshots__/SourceViewer-test.tsx.snap | 2 + .../SourceViewer/components/LineCode.tsx | 1 + .../__snapshots__/LineCode-test.tsx.snap | 1 + .../__snapshots__/LineIssueList-test.tsx.snap | 1 + .../__snapshots__/loadIssues-test.ts.snap | 1 + .../js/components/common/LocationIndex.css | 1 + .../js/components/common/LocationMessage.css | 32 ------- .../js/components/common/LocationMessage.tsx | 8 +- .../controls/BoxedGroupAccordion.tsx | 77 +++++++-------- .../__tests__/BoxedGroupAccordion-test.tsx | 51 +++++----- .../BoxedGroupAccordion-test.tsx.snap | 26 ------ .../main/js/components/controls/buttons.css | 21 +---- .../main/js/components/controls/buttons.tsx | 6 +- .../__snapshots__/IssueView-test.tsx.snap | 4 + .../__snapshots__/issue-test.tsx.snap | 1 + .../issue/components/IssueTitleBar.tsx | 3 +- .../IssueActionsBar-test.tsx.snap | 19 ++++ .../__snapshots__/IssueTitleBar-test.tsx.snap | 5 + .../locations/CrossFileLocationNavigator.tsx | 2 - .../locations/FlowsList.css} | 42 +++------ .../js/components/locations/FlowsList.tsx | 81 ++++++++++++++++ .../js/components/locations/LocationsList.tsx | 35 +++---- .../locations/SingleFileLocationNavigator.css | 26 +++++- .../locations/SingleFileLocationNavigator.tsx | 36 ++++--- .../CrossFileLocationsNavigator-test.tsx | 1 - .../__tests__/LocationsList-test.tsx | 17 +--- .../SingleFileLocationsNavigator-test.tsx | 1 - .../CrossFileLocationsNavigator-test.tsx.snap | 3 - .../__snapshots__/LocationsList-test.tsx.snap | 73 +++++---------- ...SingleFileLocationsNavigator-test.tsx.snap | 58 +++++------- .../sonar-web/src/main/js/helpers/issues.ts | 32 ++++--- .../src/main/js/helpers/mocks/webhook.ts | 33 +++++++ .../src/main/js/helpers/testMocks.ts | 1 + server/sonar-web/src/main/js/types/issues.ts | 3 +- server/sonar-web/src/main/js/types/types.ts | 28 +++--- server/sonar-web/src/main/js/types/webhook.ts | 35 +++++++ .../resources/org/sonar/l10n/core.properties | 7 +- 93 files changed, 793 insertions(+), 708 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryAccordion-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryItem-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedGroupAccordion-test.tsx.snap rename server/sonar-web/src/main/js/{apps/webhooks/components/__tests__/DeliveryItem-test.tsx => components/locations/FlowsList.css} (51%) create mode 100644 server/sonar-web/src/main/js/components/locations/FlowsList.tsx create mode 100644 server/sonar-web/src/main/js/helpers/mocks/webhook.ts create mode 100644 server/sonar-web/src/main/js/types/webhook.ts diff --git a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts index cca51bbd35f..243c522293a 100644 --- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts @@ -44,6 +44,7 @@ import { import { Standards } from '../../types/security'; import { Dict, + FlowType, RuleActivation, RuleDetails, SnippetsByComponent, @@ -95,6 +96,98 @@ export default class IssuesServiceMock { mockSourceViewerFile('file.bar', 'project') ]; this.list = [ + { + issue: mockRawIssue(false, { + key: 'issue11', + component: 'project:file.foo', + message: 'FlowIssue', + rule: 'simpleRuleId', + textRange: { + startLine: 10, + endLine: 10, + startOffset: 0, + endOffset: 2 + }, + flows: [ + { + type: FlowType.DATA, + description: 'Backtracking 1', + locations: [ + { + component: 'project:file.foo', + msg: 'Data location 1', + textRange: { + startLine: 20, + endLine: 20, + startOffset: 0, + endOffset: 1 + } + }, + { + component: 'project:file.foo', + msg: 'Data location 2', + textRange: { + startLine: 21, + endLine: 21, + startOffset: 0, + endOffset: 1 + } + } + ] + }, + { + type: FlowType.EXECUTION, + locations: [ + { + component: 'project:file.bar', + msg: 'Execution location 1', + textRange: { + startLine: 20, + endLine: 20, + startOffset: 0, + endOffset: 1 + } + }, + { + component: 'project:file.bar', + msg: 'Execution location 2', + textRange: { + startLine: 22, + endLine: 22, + startOffset: 0, + endOffset: 1 + } + }, + { + component: 'project:file.bar', + msg: 'Execution location 3', + textRange: { + startLine: 5, + endLine: 5, + startOffset: 0, + endOffset: 1 + } + } + ] + } + ] + }), + snippets: keyBy( + [ + mockSnippetsByComponent( + 'file.foo', + 'project', + times(40, i => i + 1) + ), + mockSnippetsByComponent( + 'file.bar', + 'project', + times(40, i => i + 1) + ) + ], + 'component.key' + ) + }, { issue: mockRawIssue(false, { key: 'issue0', diff --git a/server/sonar-web/src/main/js/api/webhooks.ts b/server/sonar-web/src/main/js/api/webhooks.ts index 3c9fe1300ef..b61b549b526 100644 --- a/server/sonar-web/src/main/js/api/webhooks.ts +++ b/server/sonar-web/src/main/js/api/webhooks.ts @@ -19,7 +19,8 @@ */ import { throwGlobalError } from '../helpers/error'; import { getJSON, post, postJSON } from '../helpers/request'; -import { Paging, Webhook, WebhookDelivery } from '../types/types'; +import { Paging } from '../types/types'; +import { Webhook, WebhookDelivery } from '../types/webhook'; export function createWebhook(data: { name: string; diff --git a/server/sonar-web/src/main/js/app/styles/components/boxed-group.css b/server/sonar-web/src/main/js/app/styles/components/boxed-group.css index c132dc87917..ac963312e42 100644 --- a/server/sonar-web/src/main/js/app/styles/components/boxed-group.css +++ b/server/sonar-web/src/main/js/app/styles/components/boxed-group.css @@ -24,6 +24,10 @@ background-color: #fff; } +.boxed-group.no-border { + border-color: transparent; +} + .boxed-group-centered { margin-left: auto; margin-right: auto; @@ -87,16 +91,14 @@ } .boxed-group-accordion { + border-color: var(--neutral200); margin-bottom: var(--gridSize); transition: border-color 0.3s ease; } -.boxed-group-accordion:not(.no-hover):hover { - border-color: var(--blue); -} - -.boxed-group-accordion:not(.no-hover):hover .boxed-group-accordion-title { - color: var(--blue); +.boxed-group-accordion:hover, +.boxed-group-accordion.open { + border-color: var(--info400); } .boxed-group-accordion .boxed-group-header { diff --git a/server/sonar-web/src/main/js/app/styles/init/misc.css b/server/sonar-web/src/main/js/app/styles/init/misc.css index 7c626a19ffd..defcfbeae9c 100644 --- a/server/sonar-web/src/main/js/app/styles/init/misc.css +++ b/server/sonar-web/src/main/js/app/styles/init/misc.css @@ -638,6 +638,10 @@ th.huge-spacer-right { color: inherit; } +.muted { + color: var(--neutral600); +} + .leak-box { background-color: var(--leakPrimaryColor); border: 1px solid var(--leakSecondaryColor); diff --git a/server/sonar-web/src/main/js/app/theme.js b/server/sonar-web/src/main/js/app/theme.js index 6bfe13d93b9..f51756fb1b3 100644 --- a/server/sonar-web/src/main/js/app/theme.js +++ b/server/sonar-web/src/main/js/app/theme.js @@ -148,7 +148,9 @@ module.exports = { primarya40: 'rgba(35, 107, 151, 0.40)', primary400: '#297BAE', + info50: '#ECF6FE', info500: '#0271B9', + info400: '#4B9FD5', success500: '#008A25', success500a20: 'rgba(0, 138, 37, 0.20)', @@ -166,6 +168,7 @@ module.exports = { error500: '#D02F3A', error500a20: 'rgba(208, 47, 58, 0.20)', + neutral200: '#CCCCCC', neutral600: '#666666', neutral800: '#333333', diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx index 65d2281e84c..990b61db226 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx @@ -89,6 +89,54 @@ it('should be able to bulk change', async () => { ).toBeInTheDocument(); }); +it('should interact with flows and locations', async () => { + const user = userEvent.setup(); + renderProjectIssuesApp('project/issues?issues=issue11&open=issue11&id=myproject'); + const dataFlowButton = await screen.findByRole('button', { name: 'Backtracking 1' }); + const exectionFlowButton = await screen.findByRole('button', { name: 'issue.execution_flow' }); + + let dataLocation1Button = screen.getByRole('button', { name: '1 Data location 1' }); + let dataLocation2Button = screen.getByRole('button', { name: '2 Data location 2' }); + + expect(dataFlowButton).toBeInTheDocument(); + expect(dataLocation1Button).toBeInTheDocument(); + expect(dataLocation2Button).toBeInTheDocument(); + + await user.click(dataFlowButton); + // Colapsing flow + expect(dataLocation1Button).not.toBeInTheDocument(); + expect(dataLocation2Button).not.toBeInTheDocument(); + + await user.click(exectionFlowButton); + expect(screen.getByRole('button', { name: '1 Execution location 1' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '2 Execution location 2' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '3 Execution location 3' })).toBeInTheDocument(); + + // Keyboard interaction + await user.click(dataFlowButton); + dataLocation1Button = screen.getByRole('button', { name: '1 Data location 1' }); + dataLocation2Button = screen.getByRole('button', { name: '2 Data location 2' }); + + //Location navigation + await user.keyboard('{Alt>}{ArrowDown}{/Alt}'); + expect(dataLocation1Button).toHaveClass('selected'); + await user.keyboard('{Alt>}{ArrowDown}{/Alt}'); + expect(dataLocation1Button).not.toHaveClass('selected'); + expect(dataLocation2Button).toHaveClass('selected'); + await user.keyboard('{Alt>}{ArrowDown}{/Alt}'); + expect(dataLocation1Button).not.toHaveClass('selected'); + expect(dataLocation2Button).not.toHaveClass('selected'); + await user.keyboard('{Alt>}{ArrowUp}{/Alt}'); + expect(dataLocation1Button).not.toHaveClass('selected'); + expect(dataLocation2Button).toHaveClass('selected'); + + //Flow navigation + await user.keyboard('{Alt>}{ArrowRight}{/Alt}'); + expect(screen.getByRole('button', { name: '1 Execution location 1' })).toHaveClass('selected'); + await user.keyboard('{Alt>}{ArrowLeft}{/Alt}'); + expect(screen.getByRole('button', { name: '1 Data location 1' })).toHaveClass('selected'); +}); + it('should show education principles', async () => { const user = userEvent.setup(); renderProjectIssuesApp('project/issues?issues=issue2&open=issue2&id=myproject'); @@ -468,7 +516,7 @@ it('should show code tabs when any secondary location is selected', async () => }) ).not.toBeInTheDocument(); - await user.click(screen.getByRole('link', { name: '1 location 1' })); + await user.click(screen.getByRole('button', { name: '1 location 1' })); expect( screen.getByRole('row', { name: '2 source_viewer.tooltip.covered import java.util. ArrayList ;' @@ -485,7 +533,7 @@ it('should show code tabs when any secondary location is selected', async () => }) ).not.toBeInTheDocument(); - await user.click(screen.getByRole('link', { name: '1 location 1' })); + await user.click(screen.getByRole('button', { name: '1 location 1' })); expect( screen.getByRole('row', { name: '2 source_viewer.tooltip.covered import java.util. ArrayList ;' diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/actions-test.ts b/server/sonar-web/src/main/js/apps/issues/__tests__/actions-test.ts index 9fd0d4f26fc..1a5b23ef3f2 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/actions-test.ts +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/actions-test.ts @@ -26,7 +26,7 @@ describe('selectFlow', () => { expect(selectFlow(5)()).toEqual({ locationsNavigator: true, selectedFlowIndex: 5, - selectedLocationIndex: 0 + selectedLocationIndex: undefined }); }); }); diff --git a/server/sonar-web/src/main/js/apps/issues/actions.ts b/server/sonar-web/src/main/js/apps/issues/actions.ts index 94a84475d07..86077870ebe 100644 --- a/server/sonar-web/src/main/js/apps/issues/actions.ts +++ b/server/sonar-web/src/main/js/apps/issues/actions.ts @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { State } from './components/IssuesApp'; +import { getLocations } from './utils'; export function enableLocationsNavigator(state: State) { const { openIssue, selectedLocationIndex } = state; @@ -45,17 +46,16 @@ export function selectNextLocation( ) { const { selectedFlowIndex, selectedLocationIndex: index = -1, openIssue } = state; if (openIssue) { - const locations = - selectedFlowIndex !== undefined - ? openIssue.flows[selectedFlowIndex] - : openIssue.secondaryLocations; + const locations = getLocations(openIssue, selectedFlowIndex); + const lastLocationIdx = locations.length - 1; if (index === lastLocationIdx) { // -1 to jump back to the issue itself - return { selectedLocationIndex: -1 }; + return { selectedLocationIndex: -1, locationsNavigator: true }; } return { - selectedLocationIndex: index !== undefined && index < lastLocationIdx ? index + 1 : index + selectedLocationIndex: index !== undefined && index < lastLocationIdx ? index + 1 : index, + locationsNavigator: true }; } return null; @@ -65,32 +65,48 @@ export function selectPreviousLocation(state: State) { const { selectedFlowIndex, selectedLocationIndex: index, openIssue } = state; if (openIssue) { if (index === -1) { - const locations = - selectedFlowIndex !== undefined - ? openIssue.flows[selectedFlowIndex] - : openIssue.secondaryLocations; + const locations = getLocations(openIssue, selectedFlowIndex); const lastLocationIdx = locations.length - 1; - return { selectedLocationIndex: lastLocationIdx }; + return { selectedLocationIndex: lastLocationIdx, locationsNavigator: true }; } - return { selectedLocationIndex: index !== undefined && index > 0 ? index - 1 : index }; + return { + selectedLocationIndex: index !== undefined && index > 0 ? index - 1 : index, + locationsNavigator: true + }; } return null; } export function selectFlow(nextIndex?: number) { return () => { - return { locationsNavigator: true, selectedFlowIndex: nextIndex, selectedLocationIndex: 0 }; + return { + locationsNavigator: true, + selectedFlowIndex: nextIndex, + selectedLocationIndex: undefined + }; }; } export function selectNextFlow(state: State) { const { openIssue, selectedFlowIndex } = state; + if ( openIssue && selectedFlowIndex !== undefined && - openIssue.flows.length > selectedFlowIndex + 1 + (openIssue.flows.length > selectedFlowIndex + 1 || + openIssue.flowsWithType.length > selectedFlowIndex + 1) ) { - return { selectedFlowIndex: selectedFlowIndex + 1, selectedLocationIndex: 0 }; + return { + selectedFlowIndex: selectedFlowIndex + 1, + selectedLocationIndex: 0, + locationsNavigator: true + }; + } else if ( + openIssue && + selectedFlowIndex === undefined && + (openIssue.flows.length > 0 || openIssue.flowsWithType.length > 0) + ) { + return { selectedFlowIndex: 0, selectedLocationIndex: 0, locationsNavigator: true }; } return null; } @@ -98,7 +114,11 @@ export function selectNextFlow(state: State) { export function selectPreviousFlow(state: State) { const { openIssue, selectedFlowIndex } = state; if (openIssue && selectedFlowIndex !== undefined && selectedFlowIndex > 0) { - return { selectedFlowIndex: selectedFlowIndex - 1, selectedLocationIndex: 0 }; + return { + selectedFlowIndex: selectedFlowIndex - 1, + selectedLocationIndex: 0, + locationsNavigator: true + }; } return null; } diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx index ec5dce35d59..4c9aee53cec 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx @@ -230,9 +230,9 @@ export class App extends React.PureComponent { this.setState({ checkAll: false }); } else if (openIssue && openIssue.key !== this.state.selected) { this.setState({ - locationsNavigator: false, + locationsNavigator: true, selected: openIssue.key, - selectedFlowIndex: undefined, + selectedFlowIndex: 0, selectedLocationIndex: undefined }); } @@ -504,6 +504,7 @@ export class App extends React.PureComponent { effortTotal, facets: { ...state.facets, ...parseFacets(facets) }, loading: false, + locationsNavigator: true, issues, openIssue, paging, @@ -513,7 +514,7 @@ export class App extends React.PureComponent { referencedRules: keyBy(other.rules, 'key'), referencedUsers: keyBy(other.users, 'login'), selected, - selectedFlowIndex: undefined, + selectedFlowIndex: 0, selectedLocationIndex: undefined })); } diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesList-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesList-test.tsx.snap index 7f9e4bae817..0d8e3039633 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesList-test.tsx.snap @@ -21,6 +21,7 @@ exports[`should render correctly 2`] = ` "componentUuid": "foo1234", "creationDate": "2017-03-01T09:36:01+0100", "flows": Array [], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -61,6 +62,7 @@ exports[`should render correctly 2`] = ` "componentUuid": "foo1234", "creationDate": "2017-03-01T09:36:01+0100", "flows": Array [], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN3", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -97,6 +99,7 @@ exports[`should render correctly 2`] = ` "componentUuid": "foo1234", "creationDate": "2017-03-01T09:36:01+0100", "flows": Array [], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap index f835fb8a7b3..7e70e983693 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap @@ -30,7 +30,6 @@ exports[`should render CrossComponentSourceViewer correctly 1`] = ` Array [ Object { "component": "main.js", - "index": 0, "textRange": Object { "endLine": 2, "endOffset": 2, @@ -40,7 +39,6 @@ exports[`should render CrossComponentSourceViewer correctly 1`] = ` }, Object { "component": "main.js", - "index": 1, "textRange": Object { "endLine": 12, "endOffset": 2, @@ -50,6 +48,7 @@ exports[`should render CrossComponentSourceViewer correctly 1`] = ` }, ], ], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -61,6 +60,7 @@ exports[`should render CrossComponentSourceViewer correctly 1`] = ` "secondaryLocations": Array [ Object { "component": "main.js", + "index": 0, "textRange": Object { "endLine": 2, "endOffset": 2, @@ -70,6 +70,7 @@ exports[`should render CrossComponentSourceViewer correctly 1`] = ` }, Object { "component": "main.js", + "index": 1, "textRange": Object { "endLine": 2, "endOffset": 2, @@ -150,6 +151,7 @@ exports[`should render CrossComponentSourceViewer correctly 1`] = ` }, ], ], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -207,9 +209,9 @@ exports[`should render CrossComponentSourceViewer correctly 1`] = ` "component": "main.js", "index": 1, "textRange": Object { - "endLine": 12, + "endLine": 2, "endOffset": 2, - "startLine": 10, + "startLine": 1, "startOffset": 1, }, }, @@ -251,7 +253,6 @@ exports[`should render SourceViewer correctly: all secondary locations on same l Array [ Object { "component": "main.js", - "index": 0, "textRange": Object { "endLine": 2, "endOffset": 2, @@ -261,7 +262,6 @@ exports[`should render SourceViewer correctly: all secondary locations on same l }, Object { "component": "main.js", - "index": 1, "textRange": Object { "endLine": 2, "endOffset": 2, @@ -271,7 +271,6 @@ exports[`should render SourceViewer correctly: all secondary locations on same l }, Object { "component": "main.js", - "index": 2, "textRange": Object { "endLine": 2, "endOffset": 2, @@ -281,6 +280,7 @@ exports[`should render SourceViewer correctly: all secondary locations on same l }, ], ], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -292,6 +292,7 @@ exports[`should render SourceViewer correctly: all secondary locations on same l "secondaryLocations": Array [ Object { "component": "main.js", + "index": 0, "textRange": Object { "endLine": 2, "endOffset": 2, @@ -301,6 +302,7 @@ exports[`should render SourceViewer correctly: all secondary locations on same l }, Object { "component": "main.js", + "index": 1, "textRange": Object { "endLine": 2, "endOffset": 2, @@ -381,6 +383,7 @@ exports[`should render SourceViewer correctly: all secondary locations on same l }, ], ], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -444,16 +447,6 @@ exports[`should render SourceViewer correctly: all secondary locations on same l "startOffset": 1, }, }, - Object { - "component": "main.js", - "index": 2, - "textRange": Object { - "endLine": 2, - "endOffset": 2, - "startLine": 1, - "startOffset": 1, - }, - }, ] } onIssueSelect={[MockFunction]} @@ -489,6 +482,7 @@ exports[`should render SourceViewer correctly: default 1`] = ` "componentUuid": "foo1234", "creationDate": "2017-03-01T09:36:01+0100", "flows": Array [], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -520,6 +514,7 @@ exports[`should render SourceViewer correctly: default 1`] = ` "componentUuid": "foo1234", "creationDate": "2017-03-01T09:36:01+0100", "flows": Array [], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -579,7 +574,6 @@ exports[`should render SourceViewer correctly: single secondary location 1`] = ` Array [ Object { "component": "main.js", - "index": 0, "textRange": Object { "endLine": 2, "endOffset": 2, @@ -589,6 +583,7 @@ exports[`should render SourceViewer correctly: single secondary location 1`] = ` }, ], ], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -600,6 +595,7 @@ exports[`should render SourceViewer correctly: single secondary location 1`] = ` "secondaryLocations": Array [ Object { "component": "main.js", + "index": 0, "textRange": Object { "endLine": 2, "endOffset": 2, @@ -609,6 +605,7 @@ exports[`should render SourceViewer correctly: single secondary location 1`] = ` }, Object { "component": "main.js", + "index": 1, "textRange": Object { "endLine": 2, "endOffset": 2, @@ -689,6 +686,7 @@ exports[`should render SourceViewer correctly: single secondary location 1`] = ` }, ], ], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -742,6 +740,16 @@ exports[`should render SourceViewer correctly: single secondary location 1`] = ` "startOffset": 1, }, }, + Object { + "component": "main.js", + "index": 1, + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, ] } onIssueSelect={[MockFunction]} diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ListItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ListItem-test.tsx.snap index 08db4edb2b8..c56504a36a7 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ListItem-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ListItem-test.tsx.snap @@ -39,6 +39,7 @@ exports[`should render correctly 1`] = ` "componentUuid": "foo1234", "creationDate": "2017-03-01T09:36:01+0100", "flows": Array [], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -81,6 +82,7 @@ exports[`should render correctly 1`] = ` "componentUuid": "foo1234", "creationDate": "2017-03-01T09:36:01+0100", "flows": Array [], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.tsx index 7ef7e23b8d3..22c9233fa22 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.tsx @@ -24,7 +24,7 @@ import ConciseIssueComponent from './ConciseIssueComponent'; export interface ConciseIssueProps { issue: Issue; - onFlowSelect: (index: number) => void; + onFlowSelect: (index?: number) => void; onLocationSelect: (index: number) => void; onSelect: (issueKey: string) => void; previousIssue: Issue | undefined; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx index 2480741ff10..936aa79e414 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx @@ -18,19 +18,20 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import classNames from 'classnames'; -import { uniq } from 'lodash'; import * as React from 'react'; import { ButtonPlain } from '../../../components/controls/buttons'; +import FlowsList from '../../../components/locations/FlowsList'; import LocationsList from '../../../components/locations/LocationsList'; import TypeHelper from '../../../components/shared/TypeHelper'; -import { Issue } from '../../../types/types'; +import { translateWithParameters } from '../../../helpers/l10n'; +import { FlowType, Issue } from '../../../types/types'; import { getLocations } from '../utils'; import ConciseIssueLocations from './ConciseIssueLocations'; interface Props { issue: Issue; onClick: (issueKey: string) => void; - onFlowSelect: (index: number) => void; + onFlowSelect: (index?: number) => void; onLocationSelect: (index: number) => void; scroll: (element: Element, bottomOffset?: number) => void; selected: boolean; @@ -38,12 +39,10 @@ interface Props { selectedLocationIndex: number | undefined; } -const MAX_LOCATIONS_SCROLL = 15; const SCROLL_TOP_OFFSET = 250; export default class ConciseIssueBox extends React.PureComponent { messageElement?: HTMLElement | null; - rootElement?: HTMLElement | null; componentDidMount() { if (this.props.selected) { @@ -62,19 +61,7 @@ export default class ConciseIssueBox extends React.PureComponent { }; handleScroll = () => { - const { selectedFlowIndex } = this.props; - const { flows, secondaryLocations } = this.props.issue; - - const locations = flows.length > 0 ? flows[selectedFlowIndex || 0] : secondaryLocations; - - if (!locations || locations.length < MAX_LOCATIONS_SCROLL) { - // if there are no locations, or there are just few - // then ensuse that the whole box is visible - if (this.rootElement) { - this.props.scroll(this.rootElement); - } - } else if (this.messageElement) { - // otherwise scroll until the the message element is located on top + if (this.messageElement) { this.props.scroll(this.messageElement, window.innerHeight - SCROLL_TOP_OFFSET); } }; @@ -84,13 +71,9 @@ export default class ConciseIssueBox extends React.PureComponent { const locations = getLocations(issue, selectedFlowIndex); - const locationComponents = [issue.component, ...locations.map(location => location.component)]; - const isCrossFile = uniq(locationComponents).length > 1; - return (
(this.rootElement = node)} onClick={selected ? undefined : this.handleClick}> {
- + {issue.flowsWithType.length > 0 ? ( + + {translateWithParameters( + 'issue.x_data_flows', + issue.flowsWithType.filter(f => f.type === FlowType.DATA).length + )} + + ) : ( + + )}
- {selected && ( - - )} + {selected && + (issue.flowsWithType.length > 0 ? ( + + ) : ( + + ))}
); } diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationBadge.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationBadge.tsx index 9869805e862..679f5bdc0ce 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationBadge.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationBadge.tsx @@ -25,21 +25,25 @@ import { formatMeasure } from '../../../helpers/measures'; interface Props { count: number; + flow?: boolean; onClick?: () => void; selected: boolean; } export default function ConciseIssueLocationBadge(props: Props) { + const { count, flow, selected } = props; return ( - + {'+'} - {props.count} + {count} ); diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.tsx index a735dbe413a..9e372f2f59a 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.tsx @@ -24,7 +24,7 @@ import ConciseIssue from './ConciseIssue'; export interface ConciseIssuesListProps { issues: Issue[]; - onFlowSelect: (index: number) => void; + onFlowSelect: (index?: number) => void; onIssueSelect: (issueKey: string) => void; onLocationSelect: (index: number) => void; selected: string | undefined; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx index a46e6ecb53f..206217baf01 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx @@ -30,6 +30,7 @@ const issue: Issue = { componentUuid: '', creationDate: '', flows: [], + flowsWithType: [], key: '', message: '', project: '', diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap index 6125edf1b87..9e47281b714 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap @@ -18,6 +18,7 @@ exports[`should render 1`] = ` "componentUuid": "", "creationDate": "", "flows": Array [], + "flowsWithType": Array [], "key": "", "message": "", "project": "", diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap index b7b98c491a5..bce2e4edbab 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap @@ -30,6 +30,7 @@ exports[`should render correctly 1`] = ` "componentUuid": "foo1234", "creationDate": "2017-03-01T09:36:01+0100", "flows": Array [], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -137,6 +138,7 @@ exports[`should render correctly 2`] = ` }, ], ], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -182,7 +184,7 @@ exports[`should render correctly 2`] = ` /> diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewer-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewer-test.tsx.snap index beeb3c28c2b..38a4c317e8d 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewer-test.tsx.snap @@ -96,6 +96,7 @@ exports[`should render correctly 2`] = ` }, ], ], + "flowsWithType": Array [], "key": "1", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -252,6 +253,7 @@ exports[`should render correctly: no component found 1`] = ` }, ], ], + "flowsWithType": Array [], "key": "unknown", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -379,6 +381,7 @@ exports[`should render correctly: no component found 1`] = ` }, ], ], + "flowsWithType": Array [], "key": "unknown", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", diff --git a/server/sonar-web/src/main/js/apps/issues/styles.css b/server/sonar-web/src/main/js/apps/issues/styles.css index 48e40f1d674..b544224e116 100644 --- a/server/sonar-web/src/main/js/apps/issues/styles.css +++ b/server/sonar-web/src/main/js/apps/issues/styles.css @@ -105,6 +105,10 @@ justify-content: flex-start; } +.concise-issue-box-attributes .concise-issue-box-flow-indicator { + margin-left: auto; +} + .concise-issue-box:not(.selected) .location-index { background-color: var(--secondFontColor); } diff --git a/server/sonar-web/src/main/js/apps/issues/utils.ts b/server/sonar-web/src/main/js/apps/issues/utils.ts index d7266b3d8fe..c6b9719e9aa 100644 --- a/server/sonar-web/src/main/js/apps/issues/utils.ts +++ b/server/sonar-web/src/main/js/apps/issues/utils.ts @@ -202,18 +202,23 @@ export const saveMyIssues = (myIssues: boolean) => save(ISSUES_DEFAULT, myIssues ? LOCALSTORAGE_MY : LOCALSTORAGE_ALL); export function getLocations( - { flows, secondaryLocations }: Pick, + { + flows, + secondaryLocations, + flowsWithType + }: Pick, selectedFlowIndex: number | undefined ) { - if (selectedFlowIndex !== undefined) { - return flows[selectedFlowIndex] || []; - } else { - return flows.length > 0 ? flows[0] : secondaryLocations; + if (secondaryLocations.length > 0) { + return secondaryLocations; + } else if (selectedFlowIndex !== undefined) { + return flows[selectedFlowIndex] || flowsWithType[selectedFlowIndex]?.locations || []; } + return []; } export function getSelectedLocation( - issue: Pick, + issue: Pick, selectedFlowIndex: number | undefined, selectedLocationIndex: number | undefined ) { @@ -230,7 +235,7 @@ export function getSelectedLocation( } export function allLocationsEmpty( - issue: Pick, + issue: Pick, selectedFlowIndex: number | undefined ) { return getLocations(issue, selectedFlowIndex).every(location => !location.msg); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx index bbae623ea3b..260afe05f41 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx @@ -28,7 +28,6 @@ import { Location, Router, withRouter } from '../../components/hoc/withRouter'; import { getLeakValue } from '../../components/measure/utils'; import { getBranchLikeQuery, isPullRequest, isSameBranchLike } from '../../helpers/branch-like'; import { KeyboardKeys } from '../../helpers/keycodes'; -import { scrollToElement } from '../../helpers/scrolling'; import { getStandards } from '../../helpers/security-standard'; import { BranchLike } from '../../types/branch-like'; import { SecurityStandard, Standards } from '../../types/security'; @@ -494,13 +493,6 @@ export class SecurityHotspotsApp extends React.PureComponent { } }; - handleScroll = (element: Element, bottomOffset = 100) => { - const scrollableElement = document.querySelector('.layout-page-side'); - if (element && scrollableElement) { - scrollToElement(element, { topOffset: 150, bottomOffset, parent: scrollableElement }); - } - }; - render() { const { branchLike, component } = this.props; const { @@ -544,7 +536,6 @@ export class SecurityHotspotsApp extends React.PureComponent { onSwitchStatusFilter={this.handleChangeStatusFilter} onUpdateHotspot={this.handleHotspotUpdate} onLocationClick={this.handleLocationClick} - onScroll={this.handleScroll} securityCategories={standards[SecurityStandard.SONARSOURCE]} selectedHotspot={selectedHotspot} selectedHotspotLocation={selectedHotspotLocationIndex} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx index 0ebff755db0..5de54085163 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx @@ -61,7 +61,6 @@ export interface SecurityHotspotsAppRendererProps { onShowAllHotspots: () => void; onSwitchStatusFilter: (option: HotspotStatusFilter) => void; onUpdateHotspot: (hotspotKey: string) => Promise; - onScroll: (element: Element) => void; selectedHotspot?: RawHotspot; selectedHotspotLocation?: number; securityCategories: StandardSecurityCategories; @@ -153,7 +152,6 @@ export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRe onHotspotClick={props.onHotspotClick} onLoadMore={props.onLoadMore} onLocationClick={props.onLocationClick} - onScroll={props.onScroll} selectedHotspotLocation={selectedHotspotLocation} selectedHotspot={selectedHotspot} standards={standards} @@ -171,7 +169,6 @@ export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRe selectedHotspot={selectedHotspot} selectedHotspotLocation={selectedHotspotLocation} statusFilter={filters.status} - onScroll={props.onScroll} /> )} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx index 78f58e927e1..afa9a24a863 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx @@ -24,9 +24,7 @@ import { getSecurityHotspotList, getSecurityHotspots } from '../../../api/securi import { KeyboardKeys } from '../../../helpers/keycodes'; import { mockBranch, mockPullRequest } from '../../../helpers/mocks/branch-like'; import { mockComponent } from '../../../helpers/mocks/component'; -import { mockHtmlElement } from '../../../helpers/mocks/dom'; import { mockRawHotspot, mockStandards } from '../../../helpers/mocks/security-hotspots'; -import { scrollToElement } from '../../../helpers/scrolling'; import { getStandards } from '../../../helpers/security-standard'; import { mockCurrentUser, @@ -414,20 +412,6 @@ it('should handle secondary location click', () => { expect(wrapper.instance().state.selectedHotspotLocationIndex).toBeUndefined(); }); -it('should handle scroll properly', async () => { - const fakeElement = document.createElement('div'); - jest.spyOn(document, 'querySelector').mockImplementationOnce(() => fakeElement); - const wrapper = shallowRender(); - const element = mockHtmlElement(); - wrapper.instance().handleScroll(element); - await waitAndUpdate(wrapper); - expect(scrollToElement).toBeCalledWith(element, { - bottomOffset: 100, - parent: fakeElement, - topOffset: 150 - }); -}); - describe('keyboard navigation', () => { const hotspots = [ mockRawHotspot({ key: 'k1' }), diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx index 224eabe8688..d0c82bf5a59 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx @@ -155,7 +155,6 @@ function shallowRender(props: Partial = {}) { onSwitchStatusFilter={jest.fn()} onUpdateHotspot={jest.fn()} onLocationClick={jest.fn()} - onScroll={jest.fn()} securityCategories={{}} selectedHotspot={undefined} standards={mockStandards()} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap index 434ed274597..4b6a0206bfb 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap @@ -49,7 +49,6 @@ exports[`should render correctly 1`] = ` onHotspotClick={[Function]} onLoadMore={[Function]} onLocationClick={[Function]} - onScroll={[Function]} onShowAllHotspots={[Function]} onSwitchStatusFilter={[Function]} onUpdateHotspot={[Function]} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap index c9edd7eb43b..9a9fc621891 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap @@ -118,7 +118,6 @@ exports[`should render correctly when filtered by category or cwe: category 1`] onHotspotClick={[MockFunction]} onLoadMore={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selectedHotspot={ Object { "author": "Developer 1", @@ -260,7 +259,6 @@ exports[`should render correctly when filtered by category or cwe: cwe 1`] = ` onHotspotClick={[MockFunction]} onLoadMore={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selectedHotspot={ Object { "author": "Developer 1", @@ -462,7 +460,6 @@ exports[`should render correctly with hotspots 2`] = ` onHotspotClick={[MockFunction]} onLoadMore={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} securityCategories={Object {}} selectedHotspot={ Object { diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotCategory.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotCategory.tsx index f6328c13298..8197e305607 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotCategory.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotCategory.tsx @@ -32,7 +32,6 @@ export interface HotspotCategoryProps { onHotspotClick: (hotspot: RawHotspot) => void; onToggleExpand?: (categoryKey: string, value: boolean) => void; onLocationClick: (index: number) => void; - onScroll: (element: Element) => void; selectedHotspot: RawHotspot; selectedHotspotLocation?: number; title: string; @@ -92,7 +91,6 @@ export default function HotspotCategory(props: HotspotCategoryProps) { hotspot={h} onClick={props.onHotspotClick} onLocationClick={props.onLocationClick} - onScroll={props.onScroll} selectedHotspotLocation={selectedHotspotLocation} selected={h.key === selectedHotspot.key} /> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.css b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.css index 1521875add6..f4a2082dfad 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.css +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.css @@ -63,7 +63,7 @@ color: var(--baseFontColor); display: block; padding: var(--gridSize) calc(2 * var(--gridSize)); - border: 1px solid transparent; + border: 2px solid transparent; border-top-color: var(--barBorderColor); transition: padding 0s, border 0s; width: 100%; @@ -76,14 +76,17 @@ .hotspot-category .hotspot-item:hover { background-color: var(--veryLightBlue); - border: 1px dashed var(--blue); + border: 2px dashed var(--blue); color: var(--baseFontColor); } +.hotspot-category .hotspot-item.highlight:hover { + background-color: transparent; +} + .hotspot-category .hotspot-item.highlight { - background-color: var(--veryLightBlue); color: var(--baseFontColor); - border: 1px solid var(--blue); + border: 2px solid var(--blue); cursor: unset; } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx index 3520a85c673..a8d298b0d04 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx @@ -38,7 +38,6 @@ interface Props { onHotspotClick: (hotspot: RawHotspot) => void; onLoadMore: () => void; onLocationClick: (index?: number) => void; - onScroll: (element: Element) => void; securityCategories: StandardSecurityCategories; selectedHotspot: RawHotspot; selectedHotspotLocation?: number; @@ -162,7 +161,6 @@ export default class HotspotList extends React.Component { onHotspotClick={this.props.onHotspotClick} onToggleExpand={this.handleToggleCategory} onLocationClick={this.props.onLocationClick} - onScroll={this.props.onScroll} selectedHotspot={selectedHotspot} selectedHotspotLocation={selectedHotspotLocation} title={cat.title} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotListItem.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotListItem.tsx index 23135c4f6af..81ed5460eda 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotListItem.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotListItem.tsx @@ -30,7 +30,6 @@ export interface HotspotListItemProps { hotspot: RawHotspot; onClick: (hotspot: RawHotspot) => void; onLocationClick: (index?: number) => void; - onScroll: (element: Element) => void; selected: boolean; selectedHotspotLocation?: number; } @@ -63,10 +62,10 @@ export default function HotspotListItem(props: HotspotListItemProps) { {selected && ( )} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx index 8f012882eb5..98128701214 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx @@ -43,7 +43,6 @@ export interface HotspotSimpleListProps { loadingMore: boolean; onHotspotClick: (hotspot: RawHotspot) => void; onLocationClick: (index?: number) => void; - onScroll: (element: Element) => void; onLoadMore: () => void; selectedHotspot: RawHotspot; selectedHotspotLocation?: number; @@ -115,7 +114,6 @@ export default class HotspotSimpleList extends React.Component diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCategory-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCategory-test.tsx index 55f7f5c6dc0..293b318af5b 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCategory-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCategory-test.tsx @@ -76,7 +76,6 @@ function shallowRender(props: Partial = {}) { title="Class Injection" isLastAndIncomplete={false} onLocationClick={jest.fn()} - onScroll={jest.fn()} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx index 574d0d4df6c..2876fa8dfa9 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx @@ -118,7 +118,6 @@ function shallowRender(props: Partial = {}) { onHotspotClick={jest.fn()} onLoadMore={jest.fn()} onLocationClick={jest.fn()} - onScroll={jest.fn()} securityCategories={{}} selectedHotspot={mockRawHotspot({ key: 'h2' })} statusFilter={HotspotStatusFilter.TO_REVIEW} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotListItem-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotListItem-test.tsx index 99cfe471fbd..96d2c23adf2 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotListItem-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotListItem-test.tsx @@ -52,7 +52,6 @@ function shallowRender(props: Partial = {}) { = {}) { onHotspotClick={jest.fn()} onLoadMore={jest.fn()} onLocationClick={jest.fn()} - onScroll={jest.fn()} selectedHotspot={hotspots[0]} standards={{ cwe: { 327: { title: 'Use of a Broken or Risky Cryptographic Algorithm' } }, diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotCategory-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotCategory-test.tsx.snap index f165b400e5c..1b3be4b5acf 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotCategory-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotCategory-test.tsx.snap @@ -50,7 +50,6 @@ exports[`should render correctly with hotspots 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={false} /> @@ -78,7 +77,6 @@ exports[`should render correctly with hotspots 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={false} /> @@ -164,7 +162,6 @@ exports[`should render correctly with hotspots: contains selected 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={true} /> @@ -192,7 +189,6 @@ exports[`should render correctly with hotspots: contains selected 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={false} /> @@ -251,7 +247,6 @@ exports[`should render correctly with hotspots: lastAndIncomplete 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={false} /> @@ -279,7 +274,6 @@ exports[`should render correctly with hotspots: lastAndIncomplete 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={false} /> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotList-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotList-test.tsx.snap index f526a63a918..99ff86d6edf 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotList-test.tsx.snap @@ -132,7 +132,6 @@ exports[`should render correctly with hotspots: no pagination 1`] = ` isLastAndIncomplete={false} onHotspotClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -182,7 +181,6 @@ exports[`should render correctly with hotspots: no pagination 1`] = ` isLastAndIncomplete={false} onHotspotClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -267,7 +265,6 @@ exports[`should render correctly with hotspots: no pagination 1`] = ` isLastAndIncomplete={false} onHotspotClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -317,7 +314,6 @@ exports[`should render correctly with hotspots: no pagination 1`] = ` isLastAndIncomplete={false} onHotspotClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -412,7 +408,6 @@ exports[`should render correctly with hotspots: pagination 1`] = ` isLastAndIncomplete={false} onHotspotClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -462,7 +457,6 @@ exports[`should render correctly with hotspots: pagination 1`] = ` isLastAndIncomplete={false} onHotspotClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -547,7 +541,6 @@ exports[`should render correctly with hotspots: pagination 1`] = ` isLastAndIncomplete={false} onHotspotClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -597,7 +590,6 @@ exports[`should render correctly with hotspots: pagination 1`] = ` isLastAndIncomplete={true} onHotspotClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotListItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotListItem-test.tsx.snap index b9a3f92bbbf..f9e251cc5ee 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotListItem-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotListItem-test.tsx.snap @@ -57,10 +57,10 @@ exports[`should render correctly 2`] = ` `; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSimpleList-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSimpleList-test.tsx.snap index b7e685bd3f0..1c547037892 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSimpleList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSimpleList-test.tsx.snap @@ -54,7 +54,6 @@ exports[`should render correctly: filter by both 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={true} /> @@ -82,7 +81,6 @@ exports[`should render correctly: filter by both 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={false} /> @@ -150,7 +148,6 @@ exports[`should render correctly: filter by category 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={true} /> @@ -178,7 +175,6 @@ exports[`should render correctly: filter by category 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={false} /> @@ -246,7 +242,6 @@ exports[`should render correctly: filter by cwe 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={true} /> @@ -274,7 +269,6 @@ exports[`should render correctly: filter by cwe 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={false} /> @@ -352,7 +346,6 @@ exports[`should render correctly: filter by file 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={true} /> @@ -380,7 +373,6 @@ exports[`should render correctly: filter by file 1`] = ` } onClick={[MockFunction]} onLocationClick={[MockFunction]} - onScroll={[MockFunction]} selected={false} /> diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx index fe5581aae58..db4ffc207b2 100644 --- a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx @@ -69,7 +69,6 @@ export default function HealthCard({ {health && ( void; diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/DeleteWebhookForm.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/DeleteWebhookForm.tsx index 0d8b7f96a8c..155076facd1 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/DeleteWebhookForm.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/DeleteWebhookForm.tsx @@ -22,7 +22,7 @@ import { ResetButtonLink, SubmitButton } from '../../../components/controls/butt import SimpleModal from '../../../components/controls/SimpleModal'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { Webhook } from '../../../types/types'; +import { Webhook } from '../../../types/webhook'; interface Props { onClose: () => void; diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx index e1dc74b309d..3851b830630 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx @@ -24,7 +24,8 @@ import ListFooter from '../../../components/controls/ListFooter'; import Modal from '../../../components/controls/Modal'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { Paging, Webhook, WebhookDelivery } from '../../../types/types'; +import { Paging } from '../../../types/types'; +import { Webhook, WebhookDelivery } from '../../../types/webhook'; import DeliveryAccordion from './DeliveryAccordion'; interface Props { diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryAccordion.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryAccordion.tsx index 2efed7808d1..9fa865fe7da 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryAccordion.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryAccordion.tsx @@ -23,7 +23,8 @@ import BoxedGroupAccordion from '../../../components/controls/BoxedGroupAccordio import AlertErrorIcon from '../../../components/icons/AlertErrorIcon'; import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; -import { WebhookDelivery } from '../../../types/types'; +import { translate } from '../../../helpers/l10n'; +import { WebhookDelivery } from '../../../types/webhook'; import DeliveryItem from './DeliveryItem'; interface Props { @@ -89,9 +90,9 @@ export default class DeliveryAccordion extends React.PureComponent open={open} renderHeader={() => delivery.success ? ( - + ) : ( - + ) } title={}> diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx index cc37ab37e0e..fa6405dc905 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx @@ -22,7 +22,7 @@ import CodeSnippet from '../../../components/common/CodeSnippet'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; -import { WebhookDelivery } from '../../../types/types'; +import { WebhookDelivery } from '../../../types/webhook'; interface Props { className?: string; diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx index c2ff1d6ab17..e23479b2785 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx @@ -22,7 +22,7 @@ import { getDelivery } from '../../../api/webhooks'; import { ResetButtonLink } from '../../../components/controls/buttons'; import Modal from '../../../components/controls/Modal'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { Webhook, WebhookDelivery } from '../../../types/types'; +import { Webhook, WebhookDelivery } from '../../../types/webhook'; import DeliveryItem from './DeliveryItem'; interface Props { diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx index 506961aaa2d..246b31fb0ff 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx @@ -23,7 +23,7 @@ import ActionsDropdown, { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown'; import { translate } from '../../../helpers/l10n'; -import { Webhook } from '../../../types/types'; +import { Webhook } from '../../../types/webhook'; import CreateWebhookForm from './CreateWebhookForm'; import DeleteWebhookForm from './DeleteWebhookForm'; import DeliveriesForm from './DeliveriesForm'; diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx index 89923988540..53790fd4dff 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { translate } from '../../../helpers/l10n'; -import { Webhook } from '../../../types/types'; +import { Webhook } from '../../../types/webhook'; import WebhookActions from './WebhookActions'; import WebhookItemLatestDelivery from './WebhookItemLatestDelivery'; diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx index 63c28bbc643..44a608c9fc3 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx @@ -24,7 +24,7 @@ import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon'; import BulletListIcon from '../../../components/icons/BulletListIcon'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import { translate } from '../../../helpers/l10n'; -import { Webhook } from '../../../types/types'; +import { Webhook } from '../../../types/webhook'; import LatestDeliveryForm from './LatestDeliveryForm'; interface Props { diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx index f8605363746..4ef486ec09a 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx @@ -20,7 +20,7 @@ import { sortBy } from 'lodash'; import * as React from 'react'; import { translate } from '../../../helpers/l10n'; -import { Webhook } from '../../../types/types'; +import { Webhook } from '../../../types/webhook'; import WebhookItem from './WebhookItem'; interface Props { diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveryAccordion-test.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveryAccordion-test.tsx index 394794236dd..d9377f7974c 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveryAccordion-test.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveryAccordion-test.tsx @@ -17,42 +17,56 @@ * 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 { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import * as React from 'react'; import { getDelivery } from '../../../../api/webhooks'; +import { mockWebhookDelivery } from '../../../../helpers/mocks/webhook'; +import { HttpStatus } from '../../../../helpers/request'; +import { renderComponent } from '../../../../helpers/testReactTestingUtils'; import DeliveryAccordion from '../DeliveryAccordion'; jest.mock('../../../../api/webhooks', () => ({ - getDelivery: jest.fn(() => - Promise.resolve({ - delivery: { payload: '{ "success": true }' } - }) - ) + getDelivery: jest.fn().mockResolvedValue({ + delivery: { payload: '{ "message": "This was successful" }' } + }) })); -const delivery = { - at: '12.02.2018', - durationMs: 20, - httpStatus: 200, - id: '2', - success: true -}; +beforeEach(jest.clearAllMocks); -beforeEach(() => { - (getDelivery as jest.Mock).mockClear(); +it('should render correctly for successful payloads', async () => { + const user = userEvent.setup(); + renderDeliveryAccordion(); + expect(screen.getByLabelText('success')).toBeInTheDocument(); + + await user.click(screen.getByRole('button')); + expect(screen.getByText(`webhooks.delivery.response_x.${HttpStatus.Ok}`)).toBeInTheDocument(); + expect(screen.getByText('webhooks.delivery.duration_x.20ms')).toBeInTheDocument(); + expect(screen.getByText('webhooks.delivery.payload')).toBeInTheDocument(); + + const codeSnippet = await screen.findByText('{ "message": "This was successful" }'); + expect(codeSnippet).toBeInTheDocument(); }); -it('should render correctly', async () => { - const wrapper = getWrapper(); - expect(wrapper).toMatchSnapshot(); +it('should render correctly for errored payloads', async () => { + const user = userEvent.setup(); + (getDelivery as jest.Mock).mockResolvedValueOnce({ + delivery: { payload: '503 Service Unavailable' } + }); + renderDeliveryAccordion({ + delivery: mockWebhookDelivery({ httpStatus: undefined, success: false }) + }); + expect(screen.getByLabelText('error')).toBeInTheDocument(); + + await user.click(screen.getByRole('button')); + expect( + screen.getByText('webhooks.delivery.response_x.webhooks.delivery.server_unreachable') + ).toBeInTheDocument(); - wrapper.find('BoxedGroupAccordion').prop('onClick')(); - await new Promise(setImmediate); - expect(getDelivery).lastCalledWith({ deliveryId: delivery.id }); - wrapper.update(); - expect(wrapper).toMatchSnapshot(); + const codeSnippet = await screen.findByText('503 Service Unavailable'); + expect(codeSnippet).toBeInTheDocument(); }); -function getWrapper(props = {}) { - return shallow(); +function renderDeliveryAccordion(props: Partial = {}) { + return renderComponent(); } diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryAccordion-test.tsx.snap b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryAccordion-test.tsx.snap deleted file mode 100644 index 75c25d02d2c..00000000000 --- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryAccordion-test.tsx.snap +++ /dev/null @@ -1,56 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` - - } -> - - -`; - -exports[`should render correctly 2`] = ` - - } -> - - -`; diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryItem-test.tsx.snap deleted file mode 100644 index d645fae5197..00000000000 --- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryItem-test.tsx.snap +++ /dev/null @@ -1,83 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -
-

- webhooks.delivery.response_x.200 -

-

- webhooks.delivery.duration_x.20ms -

-

- webhooks.delivery.payload -

- - - -
-`; - -exports[`should render correctly when no http status 1`] = ` -
-

- webhooks.delivery.response_x.webhooks.delivery.server_unreachable -

-

- webhooks.delivery.duration_x.20ms -

-

- webhooks.delivery.payload -

- - - -
-`; - -exports[`should render correctly when no payload 1`] = ` -
-

- webhooks.delivery.response_x.200 -

-

- webhooks.delivery.duration_x.20ms -

-

- webhooks.delivery.payload -

- -
-`; diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewer-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewer-test.tsx.snap index 64cd5ee5bbe..a9b7f2b3143 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewer-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewer-test.tsx.snap @@ -83,6 +83,7 @@ exports[`should render correctly 1`] = ` "componentUuid": "foo1234", "creationDate": "2017-03-01T09:36:01+0100", "flows": Array [], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", @@ -116,6 +117,7 @@ exports[`should render correctly 1`] = ` "componentUuid": "foo1234", "creationDate": "2017-03-01T09:36:01+0100", "flows": Array [], + "flowsWithType": Array [], "key": "AVsae-CQS-9G3txfbFN2", "line": 25, "message": "Reduce the number of conditional operators (4) used in the expression", diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx index 713c0ea288b..3604fee6804 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx @@ -86,6 +86,7 @@ export default class LineCode extends React.PureComponent {ctx => ( diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap index 2dcef7709c8..308cb3b368f 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap @@ -112,6 +112,7 @@ exports[`render code: with secondary location 1`] = ` placement="top" > .location-index, .location-index.selected { background-color: var(--conciseIssueRedSelected); } diff --git a/server/sonar-web/src/main/js/components/common/LocationMessage.css b/server/sonar-web/src/main/js/components/common/LocationMessage.css index 24359d9a257..a9269cd242a 100644 --- a/server/sonar-web/src/main/js/components/common/LocationMessage.css +++ b/server/sonar-web/src/main/js/components/common/LocationMessage.css @@ -22,47 +22,15 @@ vertical-align: top; line-height: 16px; padding: 0 6px; - border-radius: 2px; - background-color: #9e9e9e; - color: #fff; - font-family: var(--baseFontFamily); font-size: var(--smallFontSize); text-overflow: ellipsis; overflow: hidden; - transition: background-color 0.3s ease; -} - -.location-message.selected { - background-color: #475760; } .location-index + .location-message { margin-left: 4px; } -.location-index > .location-message { - display: none; - position: absolute; - bottom: calc(100% + 4px); - left: 0; -} - -.location-index:hover > .location-message { - display: block; -} - -.location-index > .location-message::after { - position: absolute; - bottom: -5px; - left: 4px; - width: 0; - height: 0; - border-top: 5px solid #475760; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - content: ''; -} - .source-line-code .location-message { padding-top: 2px; padding-bottom: 2px; diff --git a/server/sonar-web/src/main/js/components/common/LocationMessage.tsx b/server/sonar-web/src/main/js/components/common/LocationMessage.tsx index d38e7f34d81..8681baa93e5 100644 --- a/server/sonar-web/src/main/js/components/common/LocationMessage.tsx +++ b/server/sonar-web/src/main/js/components/common/LocationMessage.tsx @@ -17,19 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import classNames from 'classnames'; import * as React from 'react'; import './LocationMessage.css'; interface Props { children?: React.ReactNode; - selected: boolean; } export default function LocationMessage(props: Props) { - return ( -
- {props.children} -
- ); + return
{props.children}
; } diff --git a/server/sonar-web/src/main/js/components/controls/BoxedGroupAccordion.tsx b/server/sonar-web/src/main/js/components/controls/BoxedGroupAccordion.tsx index 6ce64cf530a..a7056898971 100644 --- a/server/sonar-web/src/main/js/components/controls/BoxedGroupAccordion.tsx +++ b/server/sonar-web/src/main/js/components/controls/BoxedGroupAccordion.tsx @@ -18,11 +18,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import classNames from 'classnames'; +import { uniqueId } from 'lodash'; import * as React from 'react'; import OpenCloseIcon from '../icons/OpenCloseIcon'; +import { ButtonPlain } from './buttons'; -interface Props { +interface BoxedGroupAccordionProps { children: React.ReactNode; + noBorder?: boolean; className?: string; data?: string; onClick: (data?: string) => void; @@ -31,48 +34,38 @@ interface Props { title: React.ReactNode; } -interface State { - hoveringInner: boolean; -} - -export default class BoxedGroupAccordion extends React.PureComponent { - state: State = { hoveringInner: false }; - - handleClick = () => { - this.props.onClick(this.props.data); - }; +export default function BoxedGroupAccordion(props: BoxedGroupAccordionProps) { + const { className, noBorder, open, renderHeader, title, data, onClick } = props; - onDetailEnter = () => { - this.setState({ hoveringInner: true }); - }; + const id = React.useMemo(() => uniqueId('accordion-'), []); + const handleClick = React.useCallback(() => { + onClick(data); + }, [onClick, data]); - onDetailLeave = () => { - this.setState({ hoveringInner: false }); - }; - - render() { - const { className, open, renderHeader, title } = this.props; - return ( -
-
- - - {title} - - {renderHeader && renderHeader()} -
- {open && ( -
- {this.props.children} -
- )} + return ( +
+ {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} +
+ + {title} + + {renderHeader && renderHeader()} + +
+
+ {open &&
{props.children}
}
- ); - } +
+ ); } diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/BoxedGroupAccordion-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/BoxedGroupAccordion-test.tsx index 9e1a386e1b7..c1569132cbe 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/BoxedGroupAccordion-test.tsx +++ b/server/sonar-web/src/main/js/components/controls/__tests__/BoxedGroupAccordion-test.tsx @@ -17,36 +17,39 @@ * 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 { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import * as React from 'react'; -import { click } from '../../../helpers/testUtils'; +import { renderComponent } from '../../../helpers/testReactTestingUtils'; import BoxedGroupAccordion from '../BoxedGroupAccordion'; -it('should render correctly', () => { - expect(getWrapper()).toMatchSnapshot(); +it('should behave correctly', async () => { + const user = userEvent.setup(); + renderDeliveryAccordion(); + expect(screen.queryByText('children')).not.toBeInTheDocument(); + await user.click(screen.getByRole('button', { expanded: false })); + expect(screen.queryByText('children')).toBeInTheDocument(); }); -it('should show the inner content after a click', () => { - const onClick = jest.fn(); - const wrapper = getWrapper({ onClick }); - click(wrapper.find('.boxed-group-header')); +it('should render header correctly', () => { + renderDeliveryAccordion(() =>
header
); + expect(screen.getByText('header')).toBeInTheDocument(); +}); - expect(onClick).lastCalledWith('foo'); - wrapper.setProps({ open: true }); +function renderDeliveryAccordion(renderHeader?: () => React.ReactNode) { + function AccordionTest() { + const [open, setOpen] = React.useState(false); - expect(wrapper.find('.boxed-group-inner').exists()).toBe(true); -}); + return ( + setOpen(!open)} + open={open} + title="test" + renderHeader={renderHeader}> +
children
+
+ ); + } -function getWrapper(props = {}) { - return shallow( - {}} - open={false} - renderHeader={() =>
header content
} - title="Foo" - {...props}> -
inner content
-
- ); + return renderComponent(); } diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedGroupAccordion-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedGroupAccordion-test.tsx.snap deleted file mode 100644 index 0c7b74b79d9..00000000000 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedGroupAccordion-test.tsx.snap +++ /dev/null @@ -1,26 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -
-
- - - Foo - -
- header content -
-
-
-`; diff --git a/server/sonar-web/src/main/js/components/controls/buttons.css b/server/sonar-web/src/main/js/components/controls/buttons.css index e0b88676247..aa468fb4648 100644 --- a/server/sonar-web/src/main/js/components/controls/buttons.css +++ b/server/sonar-web/src/main/js/components/controls/buttons.css @@ -103,23 +103,12 @@ /* #region .button-plain */ .button-plain { - display: inline-flex; - height: auto; - line-height: inherit; - margin: 0; - padding: 0; - border: none; - border-radius: 0; - background: transparent; + box-sizing: border-box; + background: inherit; color: inherit; - border-bottom: 0; - font-weight: inherit; - font-size: inherit; -} - -.button-plain:hover { - color: var(--blue); - background-color: transparent; + cursor: pointer; + outline: none; + border: 0; } /* #endregion */ diff --git a/server/sonar-web/src/main/js/components/controls/buttons.tsx b/server/sonar-web/src/main/js/components/controls/buttons.tsx index e4cae47e83a..ad0d068724e 100644 --- a/server/sonar-web/src/main/js/components/controls/buttons.tsx +++ b/server/sonar-web/src/main/js/components/controls/buttons.tsx @@ -48,7 +48,6 @@ export class Button extends React.PureComponent { handleClick = (event: React.MouseEvent) => { const { disabled, onClick, preventDefault = true, stopPropagation = false } = this.props; - event.currentTarget.blur(); if (preventDefault || disabled) { event.preventDefault(); } @@ -72,12 +71,15 @@ export class Button extends React.PureComponent { type = 'button', ...props } = this.props; + + // Instead of undoing button style we simply not apply the class. + const isPlain = className && className.indexOf('button-plain') !== -1; return (
+ { + this.node = node; + }} + onClick={this.handleClick}> + {index + 1} + {message} + ); } } diff --git a/server/sonar-web/src/main/js/components/locations/__tests__/CrossFileLocationsNavigator-test.tsx b/server/sonar-web/src/main/js/components/locations/__tests__/CrossFileLocationsNavigator-test.tsx index 2147bba7ad4..26ce5f7e0d0 100644 --- a/server/sonar-web/src/main/js/components/locations/__tests__/CrossFileLocationsNavigator-test.tsx +++ b/server/sonar-web/src/main/js/components/locations/__tests__/CrossFileLocationsNavigator-test.tsx @@ -82,7 +82,6 @@ function shallowRender(props: Partial = {} diff --git a/server/sonar-web/src/main/js/components/locations/__tests__/LocationsList-test.tsx b/server/sonar-web/src/main/js/components/locations/__tests__/LocationsList-test.tsx index a5edeafdcf8..74ca4eaa5ee 100644 --- a/server/sonar-web/src/main/js/components/locations/__tests__/LocationsList-test.tsx +++ b/server/sonar-web/src/main/js/components/locations/__tests__/LocationsList-test.tsx @@ -37,21 +37,9 @@ const location2: FlowLocation = { textRange: { startLine: 8, endLine: 8, startOffset: 0, endOffset: 5 } }; -const location3: FlowLocation = { - component: 'bar', - componentName: 'src/bar.js', - msg: 'Do not use bar', - textRange: { startLine: 15, endLine: 16, startOffset: 4, endOffset: 6 } -}; - it('should render locations in the same file', () => { const locations = [location1, location2]; - expect(shallowRender({ locations, isCrossFile: false })).toMatchSnapshot(); -}); - -it('should render flow locations in different file', () => { - const locations = [location1, location3]; - expect(shallowRender({ locations, isCrossFile: true })).toMatchSnapshot(); + expect(shallowRender({ locations })).toMatchSnapshot(); }); it('should not render locations', () => { @@ -63,9 +51,8 @@ function shallowRender(overrides: Partial = {}) { return shallow( diff --git a/server/sonar-web/src/main/js/components/locations/__tests__/SingleFileLocationsNavigator-test.tsx b/server/sonar-web/src/main/js/components/locations/__tests__/SingleFileLocationsNavigator-test.tsx index 9410e6ac13e..58c0ea6fb50 100644 --- a/server/sonar-web/src/main/js/components/locations/__tests__/SingleFileLocationsNavigator-test.tsx +++ b/server/sonar-web/src/main/js/components/locations/__tests__/SingleFileLocationsNavigator-test.tsx @@ -32,7 +32,6 @@ function shallowRender(props: Partial = {} index={0} message="" onClick={jest.fn()} - scroll={jest.fn()} selected={true} {...props} /> diff --git a/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/CrossFileLocationsNavigator-test.tsx.snap b/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/CrossFileLocationsNavigator-test.tsx.snap index 53ee06d1024..c8e4b4eef3e 100644 --- a/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/CrossFileLocationsNavigator-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/CrossFileLocationsNavigator-test.tsx.snap @@ -24,7 +24,6 @@ exports[`should render 1`] = ` key="0" message="Do not use foo" onClick={[MockFunction]} - scroll={[MockFunction]} selected={false} /> @@ -67,7 +66,6 @@ exports[`should render 1`] = ` key="2" message="Do not use bar" onClick={[MockFunction]} - scroll={[MockFunction]} selected={false} /> @@ -97,7 +95,6 @@ exports[`should render locations with no component name 1`] = ` index={0} key="0" onClick={[MockFunction]} - scroll={[MockFunction]} selected={false} /> diff --git a/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/LocationsList-test.tsx.snap b/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/LocationsList-test.tsx.snap index eb41475e47b..7e992bb77ff 100644 --- a/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/LocationsList-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/LocationsList-test.tsx.snap @@ -1,57 +1,30 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should render flow locations in different file 1`] = ` - -`; - exports[`should render locations in the same file 1`] = ` -
- - + + +
  • -
  • + > + + + `; diff --git a/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/SingleFileLocationsNavigator-test.tsx.snap b/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/SingleFileLocationsNavigator-test.tsx.snap index 46f03290c53..ec2618e04cc 100644 --- a/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/SingleFileLocationsNavigator-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/SingleFileLocationsNavigator-test.tsx.snap @@ -1,43 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should render correctly: index 1 1`] = ` - + + 1 + + + `; exports[`should render correctly: index 2 1`] = ` - + + 2 + + + `; diff --git a/server/sonar-web/src/main/js/helpers/issues.ts b/server/sonar-web/src/main/js/helpers/issues.ts index 1e72f660c58..2b0588f46a9 100644 --- a/server/sonar-web/src/main/js/helpers/issues.ts +++ b/server/sonar-web/src/main/js/helpers/issues.ts @@ -24,7 +24,7 @@ import SecurityHotspotIcon from '../components/icons/SecurityHotspotIcon'; import VulnerabilityIcon from '../components/icons/VulnerabilityIcon'; import { IssueType, RawIssue } from '../types/issues'; import { MetricKey } from '../types/metrics'; -import { Dict, FlowLocation, Issue, TextRange } from '../types/types'; +import { Dict, Flow, FlowLocation, Issue, TextRange } from '../types/types'; import { UserBase } from '../types/users'; import { ISSUE_TYPES } from './constants'; @@ -73,14 +73,10 @@ function injectCommentsRelational(issue: RawIssue, users?: UserBase[]) { return { comments }; } -function prepareClosed( - issue: RawIssue, - secondaryLocations: FlowLocation[], - flows: FlowLocation[][] -) { +function prepareClosed(issue: RawIssue) { return issue.status === 'CLOSED' ? { flows: [], line: undefined, textRange: undefined, secondaryLocations: [] } - : { flows, secondaryLocations }; + : {}; } function ensureTextRange(issue: RawIssue): { textRange?: TextRange } { @@ -105,7 +101,15 @@ function reverseLocations(locations: FlowLocation[]): FlowLocation[] { function splitFlows( issue: RawIssue, components: Component[] = [] -): { secondaryLocations: FlowLocation[]; flows: FlowLocation[][] } { +): { secondaryLocations: FlowLocation[]; flows: FlowLocation[][]; flowsWithType: Flow[] } { + if (issue.flows?.some(flow => flow.type !== undefined)) { + return { + flows: [], + flowsWithType: issue.flows.filter(flow => flow.type !== undefined) as Flow[], + secondaryLocations: [] + }; + } + const parsedFlows: FlowLocation[][] = (issue.flows || []) .filter(flow => flow.locations !== undefined) .map(flow => flow.locations!.filter(location => location.textRange != null)) @@ -119,8 +123,12 @@ function splitFlows( const onlySecondaryLocations = parsedFlows.every(flow => flow.length === 1); return onlySecondaryLocations - ? { secondaryLocations: orderLocations(flatten(parsedFlows)), flows: [] } - : { secondaryLocations: [], flows: parsedFlows.map(reverseLocations) }; + ? { secondaryLocations: orderLocations(flatten(parsedFlows)), flowsWithType: [], flows: [] } + : { + secondaryLocations: [], + flowsWithType: [], + flows: parsedFlows.map(reverseLocations) + }; } function orderLocations(locations: FlowLocation[]) { @@ -137,7 +145,6 @@ export function parseIssueFromResponse( users?: UserBase[], rules?: Rule[] ): Issue { - const { secondaryLocations, flows } = splitFlows(issue, components); return { ...issue, ...injectRelational(issue, components, 'component', 'key'), @@ -145,7 +152,8 @@ export function parseIssueFromResponse( ...injectRelational(issue, rules, 'rule', 'key'), ...injectRelational(issue, users, 'assignee', 'login'), ...injectCommentsRelational(issue, users), - ...prepareClosed(issue, secondaryLocations, flows), + ...splitFlows(issue, components), + ...prepareClosed(issue), ...ensureTextRange(issue) } as Issue; } diff --git a/server/sonar-web/src/main/js/helpers/mocks/webhook.ts b/server/sonar-web/src/main/js/helpers/mocks/webhook.ts new file mode 100644 index 00000000000..3f4859f4708 --- /dev/null +++ b/server/sonar-web/src/main/js/helpers/mocks/webhook.ts @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { WebhookDelivery } from '../../types/webhook'; +import { HttpStatus } from '../request'; + +export function mockWebhookDelivery(overrides: Partial = {}): WebhookDelivery { + return { + at: '2019-06-14T09:45:52+0200', + durationMs: 20, + httpStatus: HttpStatus.Ok, + id: '1', + success: true, + ...overrides + }; +} diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts index e666c701020..4c46878abcb 100644 --- a/server/sonar-web/src/main/js/helpers/testMocks.ts +++ b/server/sonar-web/src/main/js/helpers/testMocks.ts @@ -373,6 +373,7 @@ export function mockIssue(withLocations = false, overrides: Partial = {}) componentUuid: 'foo1234', creationDate: '2017-03-01T09:36:01+0100', flows: [], + flowsWithType: [], key: 'AVsae-CQS-9G3txfbFN2', line: 25, message: 'Reduce the number of conditional operators (4) used in the expression', diff --git a/server/sonar-web/src/main/js/types/issues.ts b/server/sonar-web/src/main/js/types/issues.ts index 2c106f1ba60..3ca63bfd492 100644 --- a/server/sonar-web/src/main/js/types/issues.ts +++ b/server/sonar-web/src/main/js/types/issues.ts @@ -50,7 +50,8 @@ export interface RawIssue { comments?: Array; component: string; flows?: Array<{ - // `componentName` is not available in RawIssue + type?: string; + description?: string; locations?: Array>; }>; key: string; diff --git a/server/sonar-web/src/main/js/types/types.ts b/server/sonar-web/src/main/js/types/types.ts index 83e2f6dd6ac..aabe02cedc5 100644 --- a/server/sonar-web/src/main/js/types/types.ts +++ b/server/sonar-web/src/main/js/types/types.ts @@ -229,6 +229,17 @@ export interface FacetValue { val: T; } +export enum FlowType { + DATA = 'DATA', + EXECUTION = 'EXECUTION' +} + +export interface Flow { + type: FlowType; + description?: string; + locations: FlowLocation[]; +} + export interface FlowLocation { component: string; componentName?: string; @@ -276,6 +287,7 @@ export interface Issue { quickFixAvailable?: boolean; key: string; flows: FlowLocation[][]; + flowsWithType: Flow[]; line?: number; message: string; project: string; @@ -784,22 +796,6 @@ export interface UserSelected extends UserActive { export type Visibility = 'public' | 'private'; -export interface Webhook { - key: string; - latestDelivery?: WebhookDelivery; - name: string; - secret?: string; - url: string; -} - -export interface WebhookDelivery { - at: string; - durationMs: number; - httpStatus?: number; - id: string; - success: boolean; -} - export namespace WebApi { export interface Action { key: string; diff --git a/server/sonar-web/src/main/js/types/webhook.ts b/server/sonar-web/src/main/js/types/webhook.ts new file mode 100644 index 00000000000..6517194927f --- /dev/null +++ b/server/sonar-web/src/main/js/types/webhook.ts @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * 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 Webhook { + key: string; + latestDelivery?: WebhookDelivery; + name: string; + secret?: string; + url: string; +} + +export interface WebhookDelivery { + at: string; + durationMs: number; + httpStatus?: number; + id: string; + success: boolean; +} 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 75584c852e3..95279591fd2 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -73,6 +73,7 @@ download_verb=Download duplications=Duplications end_date=End Date edit=Edit +error=Error events=Events example=Example expand_all=Expand all @@ -177,6 +178,7 @@ rules=Rules save=Save search_results=Search results search_verb=Search +secondary=Secondary see_all=See all see_x=See {0} select_verb=Select @@ -191,12 +193,12 @@ start_date=Start Date x_show={0} shown x_selected={0} selected x_of_y_shown={0} of {1} shown -secondary=Secondary size=Size skip=Skip skip_to_content=Skip to main content status=Status support=Support +success=Success table=Table tags=Tags tags_list_x=Tags list: {0} @@ -867,6 +869,8 @@ issue.transition.resolveasreviewed.description=There is no Vulnerability in the issue.transition.resetastoreview=Reset as To Review issue.transition.resetastoreview.description=The Security Hotspot should be analyzed again issue.tabs.code=Where is the issue? +issue.x_data_flows={0} data flow(s) +issue.execution_flow=Full execution flow issues.action_select=Select issue issues.action_select.label=Select issue {0} @@ -911,6 +915,7 @@ issue.effort=Effort: issue.x_effort={0} effort issue.filter_similar_issues=Filter Similar Issues issue.this_issue_involves_x_code_locations=This issue involves {0} code location(s) +issue.this_flow_involves_x_code_locations=This flow involves {0} code location(s) 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.cannot_open_issue_max_initial_X_fetched=Cannot open selected issue, as it's not part of the initial {0} loaded issues. -- 2.39.5