aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2022-09-13 17:10:26 +0200
committersonartech <sonartech@sonarsource.com>2022-09-16 20:03:14 +0000
commit227f5d3bfc797c8ecc7ebe7e283af3b68522c1ad (patch)
tree4034b980dd5be42cbd58ea8a3a7c508f0c60d953
parent6f2881c71996715ba3a9a7b2451f008c2239d7eb (diff)
downloadsonarqube-227f5d3bfc797c8ecc7ebe7e283af3b68522c1ad.tar.gz
sonarqube-227f5d3bfc797c8ecc7ebe7e283af3b68522c1ad.zip
SONAR-17285 Display new data and execution type flows for issues
-rw-r--r--server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts93
-rw-r--r--server/sonar-web/src/main/js/api/webhooks.ts3
-rw-r--r--server/sonar-web/src/main/js/app/styles/components/boxed-group.css14
-rw-r--r--server/sonar-web/src/main/js/app/styles/init/misc.css4
-rw-r--r--server/sonar-web/src/main/js/app/theme.js3
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx52
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/actions-test.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/actions.ts52
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesList-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap44
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ListItem-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx72
-rw-r--r--server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationBadge.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap14
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewer-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/issues/styles.css4
-rw-r--r--server/sonar-web/src/main/js/apps/issues/utils.ts19
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx16
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotCategory.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.css11
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotListItem.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCategory-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotListItem-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotCategory-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotList-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotListItem-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSimpleList-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/App.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/DeleteWebhookForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/DeliveryAccordion.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/LatestDeliveryForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/WebhookActions.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/WebhookItem.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/WebhookItemLatestDelivery.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/WebhooksList.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveryAccordion-test.tsx64
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryAccordion-test.tsx.snap56
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/DeliveryItem-test.tsx.snap83
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewer-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx1
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssueList-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/__snapshots__/loadIssues-test.ts.snap1
-rw-r--r--server/sonar-web/src/main/js/components/common/LocationIndex.css1
-rw-r--r--server/sonar-web/src/main/js/components/common/LocationMessage.css32
-rw-r--r--server/sonar-web/src/main/js/components/common/LocationMessage.tsx8
-rw-r--r--server/sonar-web/src/main/js/components/controls/BoxedGroupAccordion.tsx77
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/BoxedGroupAccordion-test.tsx51
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedGroupAccordion-test.tsx.snap26
-rw-r--r--server/sonar-web/src/main/js/components/controls/buttons.css21
-rw-r--r--server/sonar-web/src/main/js/components/controls/buttons.tsx6
-rw-r--r--server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/issue-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx3
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap19
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap5
-rw-r--r--server/sonar-web/src/main/js/components/locations/CrossFileLocationNavigator.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/locations/FlowsList.css (renamed from server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveryItem-test.tsx)42
-rw-r--r--server/sonar-web/src/main/js/components/locations/FlowsList.tsx81
-rw-r--r--server/sonar-web/src/main/js/components/locations/LocationsList.tsx35
-rw-r--r--server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.css26
-rw-r--r--server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.tsx36
-rw-r--r--server/sonar-web/src/main/js/components/locations/__tests__/CrossFileLocationsNavigator-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/components/locations/__tests__/LocationsList-test.tsx17
-rw-r--r--server/sonar-web/src/main/js/components/locations/__tests__/SingleFileLocationsNavigator-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/CrossFileLocationsNavigator-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/LocationsList-test.tsx.snap73
-rw-r--r--server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/SingleFileLocationsNavigator-test.tsx.snap58
-rw-r--r--server/sonar-web/src/main/js/helpers/issues.ts32
-rw-r--r--server/sonar-web/src/main/js/helpers/mocks/webhook.ts33
-rw-r--r--server/sonar-web/src/main/js/helpers/testMocks.ts1
-rw-r--r--server/sonar-web/src/main/js/types/issues.ts3
-rw-r--r--server/sonar-web/src/main/js/types/types.ts28
-rw-r--r--server/sonar-web/src/main/js/types/webhook.ts35
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties7
93 files changed, 793 insertions, 708 deletions
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,
@@ -97,6 +98,98 @@ export default class IssuesServiceMock {
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',
component: 'project:file.foo',
message: 'Issue on file',
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<Props, State> {
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<Props, State> {
effortTotal,
facets: { ...state.facets, ...parseFacets(facets) },
loading: false,
+ locationsNavigator: true,
issues,
openIssue,
paging,
@@ -513,7 +514,7 @@ export class App extends React.PureComponent<Props, State> {
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<Props> {
messageElement?: HTMLElement | null;
- rootElement?: HTMLElement | null;
componentDidMount() {
if (this.props.selected) {
@@ -62,19 +61,7 @@ export default class ConciseIssueBox extends React.PureComponent<Props> {
};
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<Props> {
const locations = getLocations(issue, selectedFlowIndex);
- const locationComponents = [issue.component, ...locations.map(location => location.component)];
- const isCrossFile = uniq(locationComponents).length > 1;
-
return (
<div
className={classNames('concise-issue-box', 'clearfix', { selected })}
- ref={node => (this.rootElement = node)}
onClick={selected ? undefined : this.handleClick}>
<ButtonPlain
className="concise-issue-box-message"
@@ -101,21 +84,38 @@ export default class ConciseIssueBox extends React.PureComponent<Props> {
</ButtonPlain>
<div className="concise-issue-box-attributes">
<TypeHelper className="display-block little-spacer-right" type={issue.type} />
- <ConciseIssueLocations
- issue={issue}
- onFlowSelect={this.props.onFlowSelect}
- selectedFlowIndex={selectedFlowIndex}
- />
+ {issue.flowsWithType.length > 0 ? (
+ <span className="concise-issue-box-flow-indicator muted">
+ {translateWithParameters(
+ 'issue.x_data_flows',
+ issue.flowsWithType.filter(f => f.type === FlowType.DATA).length
+ )}
+ </span>
+ ) : (
+ <ConciseIssueLocations
+ issue={issue}
+ onFlowSelect={this.props.onFlowSelect}
+ selectedFlowIndex={selectedFlowIndex}
+ />
+ )}
</div>
- {selected && (
- <LocationsList
- locations={locations}
- isCrossFile={isCrossFile}
- onLocationSelect={this.props.onLocationSelect}
- scroll={this.props.scroll}
- selectedLocationIndex={selectedLocationIndex}
- />
- )}
+ {selected &&
+ (issue.flowsWithType.length > 0 ? (
+ <FlowsList
+ flows={issue.flowsWithType}
+ onLocationSelect={this.props.onLocationSelect}
+ onFlowSelect={this.props.onFlowSelect}
+ selectedLocationIndex={selectedLocationIndex}
+ selectedFlowIndex={selectedFlowIndex}
+ />
+ ) : (
+ <LocationsList
+ locations={locations}
+ componentKey={issue.component}
+ onLocationSelect={this.props.onLocationSelect}
+ selectedLocationIndex={selectedLocationIndex}
+ />
+ ))}
</div>
);
}
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 (
<Tooltip
mouseEnterDelay={0.5}
overlay={translateWithParameters(
- 'issue.this_issue_involves_x_code_locations',
- formatMeasure(props.count, 'INT')
+ flow
+ ? 'issue.this_flow_involves_x_code_locations'
+ : 'issue.this_issue_involves_x_code_locations',
+ formatMeasure(count, 'INT')
)}>
- <LocationIndex onClick={props.onClick} selected={props.selected}>
+ <LocationIndex onClick={props.onClick} selected={selected}>
{'+'}
- {props.count}
+ {count}
</LocationIndex>
</Tooltip>
);
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`] = `
/>
</div>
<LocationsList
- isCrossFile={false}
+ componentKey="main.js"
locations={
Array [
Object {
@@ -203,19 +205,9 @@ exports[`should render correctly 2`] = `
"startOffset": 1,
},
},
- Object {
- "component": "main.js",
- "textRange": Object {
- "endLine": 2,
- "endOffset": 2,
- "startLine": 1,
- "startOffset": 1,
- },
- },
]
}
onLocationSelect={[MockFunction]}
- scroll={[MockFunction]}
selectedLocationIndex={0}
/>
</div>
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<Issue, 'flows' | 'secondaryLocations'>,
+ {
+ flows,
+ secondaryLocations,
+ flowsWithType
+ }: Pick<Issue, 'flows' | 'secondaryLocations' | 'flowsWithType'>,
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, 'flows' | 'secondaryLocations'>,
+ issue: Pick<Issue, 'flows' | 'secondaryLocations' | 'flowsWithType'>,
selectedFlowIndex: number | undefined,
selectedLocationIndex: number | undefined
) {
@@ -230,7 +235,7 @@ export function getSelectedLocation(
}
export function allLocationsEmpty(
- issue: Pick<Issue, 'flows' | 'secondaryLocations'>,
+ issue: Pick<Issue, 'flows' | 'secondaryLocations' | 'flowsWithType'>,
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<Props, State> {
}
};
- 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<Props, State> {
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<void>;
- 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}
/>
)}
</div>
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<SecurityHotspotsAppRendererProps> = {}) {
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<Props, State> {
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 && (
<LocationsList
locations={locations}
- isCrossFile={false} // Currently we are not supporting cross file for security hotspot
+ showCrossFile={false} // To removed once we support multi file location
+ componentKey={hotspot.component}
onLocationSelect={props.onLocationClick}
selectedLocationIndex={selectedHotspotLocation}
- scroll={props.onScroll}
/>
)}
</ButtonPlain>
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<HotspotSimpleList
hotspot={h}
onClick={this.props.onHotspotClick}
onLocationClick={this.props.onLocationClick}
- onScroll={this.props.onScroll}
selected={h.key === selectedHotspot.key}
selectedHotspotLocation={selectedHotspotLocation}
/>
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<HotspotCategoryProps> = {}) {
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<HotspotList['props']> = {}) {
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<HotspotListItemProps> = {}) {
<HotspotListItem
hotspot={mockRawHotspot()}
onClick={jest.fn()}
- onScroll={jest.fn()}
onLocationClick={jest.fn}
selected={false}
{...props}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx
index 290851dab54..9a660270786 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx
@@ -65,7 +65,6 @@ function shallowRender(props: Partial<HotspotSimpleListProps> = {}) {
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}
/>
</li>
@@ -78,7 +77,6 @@ exports[`should render correctly with hotspots 1`] = `
}
onClick={[MockFunction]}
onLocationClick={[MockFunction]}
- onScroll={[MockFunction]}
selected={false}
/>
</li>
@@ -164,7 +162,6 @@ exports[`should render correctly with hotspots: contains selected 1`] = `
}
onClick={[MockFunction]}
onLocationClick={[MockFunction]}
- onScroll={[MockFunction]}
selected={true}
/>
</li>
@@ -192,7 +189,6 @@ exports[`should render correctly with hotspots: contains selected 1`] = `
}
onClick={[MockFunction]}
onLocationClick={[MockFunction]}
- onScroll={[MockFunction]}
selected={false}
/>
</li>
@@ -251,7 +247,6 @@ exports[`should render correctly with hotspots: lastAndIncomplete 1`] = `
}
onClick={[MockFunction]}
onLocationClick={[MockFunction]}
- onScroll={[MockFunction]}
selected={false}
/>
</li>
@@ -279,7 +274,6 @@ exports[`should render correctly with hotspots: lastAndIncomplete 1`] = `
}
onClick={[MockFunction]}
onLocationClick={[MockFunction]}
- onScroll={[MockFunction]}
selected={false}
/>
</li>
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`] = `
</div>
</div>
<LocationsList
- isCrossFile={false}
+ componentKey="com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest"
locations={Array []}
onLocationSelect={[Function]}
- scroll={[MockFunction]}
+ showCrossFile={false}
/>
</ButtonPlain>
`;
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}
/>
</li>
@@ -82,7 +81,6 @@ exports[`should render correctly: filter by both 1`] = `
}
onClick={[MockFunction]}
onLocationClick={[MockFunction]}
- onScroll={[MockFunction]}
selected={false}
/>
</li>
@@ -150,7 +148,6 @@ exports[`should render correctly: filter by category 1`] = `
}
onClick={[MockFunction]}
onLocationClick={[MockFunction]}
- onScroll={[MockFunction]}
selected={true}
/>
</li>
@@ -178,7 +175,6 @@ exports[`should render correctly: filter by category 1`] = `
}
onClick={[MockFunction]}
onLocationClick={[MockFunction]}
- onScroll={[MockFunction]}
selected={false}
/>
</li>
@@ -246,7 +242,6 @@ exports[`should render correctly: filter by cwe 1`] = `
}
onClick={[MockFunction]}
onLocationClick={[MockFunction]}
- onScroll={[MockFunction]}
selected={true}
/>
</li>
@@ -274,7 +269,6 @@ exports[`should render correctly: filter by cwe 1`] = `
}
onClick={[MockFunction]}
onLocationClick={[MockFunction]}
- onScroll={[MockFunction]}
selected={false}
/>
</li>
@@ -352,7 +346,6 @@ exports[`should render correctly: filter by file 1`] = `
}
onClick={[MockFunction]}
onLocationClick={[MockFunction]}
- onScroll={[MockFunction]}
selected={true}
/>
</li>
@@ -380,7 +373,6 @@ exports[`should render correctly: filter by file 1`] = `
}
onClick={[MockFunction]}
onLocationClick={[MockFunction]}
- onScroll={[MockFunction]}
selected={false}
/>
</li>
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 && (
<HealthItem
biggerHealth={biggerHealth}
- className="pull-right"
health={health}
healthCauses={healthCauses}
name={name}
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
index d495d911cd2..679e4315950 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
@@ -23,7 +23,8 @@ import { createWebhook, deleteWebhook, searchWebhooks, updateWebhook } from '../
import withComponentContext from '../../../app/components/componentContext/withComponentContext';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
import { translate } from '../../../helpers/l10n';
-import { Component, Webhook } from '../../../types/types';
+import { Component } from '../../../types/types';
+import { Webhook } from '../../../types/webhook';
import PageActions from './PageActions';
import PageHeader from './PageHeader';
import WebhooksList from './WebhooksList';
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx
index c82b916686a..52dc7938607 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx
@@ -24,7 +24,7 @@ import ValidationModal from '../../../components/controls/ValidationModal';
import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker';
import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
import { translate } 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/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<Props, State>
open={open}
renderHeader={() =>
delivery.success ? (
- <AlertSuccessIcon className="pull-right js-success" />
+ <AlertSuccessIcon aria-label={translate('success')} className="js-success" />
) : (
- <AlertErrorIcon className="pull-right js-error" />
+ <AlertErrorIcon aria-label={translate('error')} className="js-error" />
)
}
title={<DateTimeFormatter date={delivery.at} />}>
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<any>).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<Function>('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(<DeliveryAccordion delivery={delivery} {...props} />);
+function renderDeliveryAccordion(props: Partial<DeliveryAccordion['props']> = {}) {
+ return renderComponent(<DeliveryAccordion delivery={mockWebhookDelivery()} {...props} />);
}
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`] = `
-<BoxedGroupAccordion
- onClick={[Function]}
- open={false}
- renderHeader={[Function]}
- title={
- <DateTimeFormatter
- date="12.02.2018"
- />
- }
->
- <DeliveryItem
- className="big-spacer-left"
- delivery={
- Object {
- "at": "12.02.2018",
- "durationMs": 20,
- "httpStatus": 200,
- "id": "2",
- "success": true,
- }
- }
- loading={false}
- />
-</BoxedGroupAccordion>
-`;
-
-exports[`should render correctly 2`] = `
-<BoxedGroupAccordion
- onClick={[Function]}
- open={true}
- renderHeader={[Function]}
- title={
- <DateTimeFormatter
- date="12.02.2018"
- />
- }
->
- <DeliveryItem
- className="big-spacer-left"
- delivery={
- Object {
- "at": "12.02.2018",
- "durationMs": 20,
- "httpStatus": 200,
- "id": "2",
- "success": true,
- }
- }
- loading={false}
- payload="{ \\"success\\": true }"
- />
-</BoxedGroupAccordion>
-`;
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`] = `
-<div>
- <p
- className="spacer-bottom"
- >
- webhooks.delivery.response_x.200
- </p>
- <p
- className="spacer-bottom"
- >
- webhooks.delivery.duration_x.20ms
- </p>
- <p
- className="spacer-bottom"
- >
- webhooks.delivery.payload
- </p>
- <DeferredSpinner
- className="spacer-left spacer-top"
- loading={false}
- >
- <CodeSnippet
- noCopy={true}
- snippet="{ status: \\"SUCCESS\\" }"
- />
- </DeferredSpinner>
-</div>
-`;
-
-exports[`should render correctly when no http status 1`] = `
-<div>
- <p
- className="spacer-bottom"
- >
- webhooks.delivery.response_x.webhooks.delivery.server_unreachable
- </p>
- <p
- className="spacer-bottom"
- >
- webhooks.delivery.duration_x.20ms
- </p>
- <p
- className="spacer-bottom"
- >
- webhooks.delivery.payload
- </p>
- <DeferredSpinner
- className="spacer-left spacer-top"
- loading={false}
- >
- <CodeSnippet
- noCopy={true}
- snippet="{ status: \\"SUCCESS\\" }"
- />
- </DeferredSpinner>
-</div>
-`;
-
-exports[`should render correctly when no payload 1`] = `
-<div>
- <p
- className="spacer-bottom"
- >
- webhooks.delivery.response_x.200
- </p>
- <p
- className="spacer-bottom"
- >
- webhooks.delivery.duration_x.20ms
- </p>
- <p
- className="spacer-bottom"
- >
- webhooks.delivery.payload
- </p>
- <DeferredSpinner
- className="spacer-left spacer-top"
- loading={true}
- />
-</div>
-`;
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<React.PropsWithChildre
leading={leading}
onClick={onClick}
selected={selected}
+ aria-current={selected ? 'location' : false}
aria-label={message ? `${index + 1}-${message}` : index + 1}>
<IssueSourceViewerScrollContext.Consumer>
{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"
>
<LocationIndex
+ aria-current={false}
aria-label="2-secondary-location-msg"
leading={false}
onClick={[Function]}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssueList-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssueList-test.tsx.snap
index 358eecbf76e..b86ed52063c 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssueList-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssueList-test.tsx.snap
@@ -23,6 +23,7 @@ exports[`should render issues 1`] = `
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": Array [],
+ "flowsWithType": Array [],
"key": "issue",
"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/helpers/__tests__/__snapshots__/loadIssues-test.ts.snap b/server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/__snapshots__/loadIssues-test.ts.snap
index 24e1b6a9eb1..08768bed950 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/__snapshots__/loadIssues-test.ts.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/__snapshots__/loadIssues-test.ts.snap
@@ -24,6 +24,7 @@ Array [
"componentQualifier": "FIL",
"creationDate": "2016-08-15T15:25:38+0200",
"flows": Array [],
+ "flowsWithType": Array [],
"hash": "78417dcee7ba927b7e7c9161e29e02b8",
"key": "AWaqVGl3tut9VbnJvk6M",
"line": 62,
diff --git a/server/sonar-web/src/main/js/components/common/LocationIndex.css b/server/sonar-web/src/main/js/components/common/LocationIndex.css
index 6fcb019567b..0b05a89d85e 100644
--- a/server/sonar-web/src/main/js/components/common/LocationIndex.css
+++ b/server/sonar-web/src/main/js/components/common/LocationIndex.css
@@ -33,6 +33,7 @@
user-select: none;
}
+.selected > .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 (
- <div className={classNames('location-message', { selected: props.selected })}>
- {props.children}
- </div>
- );
+ return <div className="location-message">{props.children}</div>;
}
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<Props, State> {
- 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 (
- <div
- className={classNames('boxed-group boxed-group-accordion', className, {
- 'no-hover': this.state.hoveringInner
- })}>
- <div className="boxed-group-header" onClick={this.handleClick} role="listitem">
- <span className="boxed-group-accordion-title">
- <OpenCloseIcon className="little-spacer-right" open={open} />
- {title}
- </span>
- {renderHeader && renderHeader()}
- </div>
- {open && (
- <div
- className="boxed-group-inner"
- onMouseEnter={this.onDetailEnter}
- onMouseLeave={this.onDetailLeave}>
- {this.props.children}
- </div>
- )}
+ return (
+ <div
+ className={classNames('boxed-group boxed-group-accordion', className, {
+ 'no-border': noBorder,
+ open
+ })}
+ role="listitem">
+ {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
+ <div onClick={handleClick} className="display-flex-center boxed-group-header">
+ <ButtonPlain
+ stopPropagation={true}
+ className="boxed-group-accordion-title flex-grow"
+ onClick={handleClick}
+ id={`${id}-header`}
+ aria-controls={`${id}-panel`}
+ aria-expanded={open}>
+ {title}
+ </ButtonPlain>
+ {renderHeader && renderHeader()}
+ <OpenCloseIcon aria-hidden={true} className="spacer-left" open={open} />
+ </div>
+ <div id={`${id}-panel`} aria-labelledby={`${id}-header`} role="region">
+ {open && <div className="boxed-group-inner">{props.children}</div>}
</div>
- );
- }
+ </div>
+ );
}
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(() => <div>header</div>);
+ 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 (
+ <BoxedGroupAccordion
+ onClick={() => setOpen(!open)}
+ open={open}
+ title="test"
+ renderHeader={renderHeader}>
+ <div>children</div>
+ </BoxedGroupAccordion>
+ );
+ }
-function getWrapper(props = {}) {
- return shallow(
- <BoxedGroupAccordion
- data="foo"
- onClick={() => {}}
- open={false}
- renderHeader={() => <div>header content</div>}
- title="Foo"
- {...props}>
- <div>inner content</div>
- </BoxedGroupAccordion>
- );
+ return renderComponent(<AccordionTest />);
}
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`] = `
-<div
- className="boxed-group boxed-group-accordion"
->
- <div
- className="boxed-group-header"
- onClick={[Function]}
- role="listitem"
- >
- <span
- className="boxed-group-accordion-title"
- >
- <OpenCloseIcon
- className="little-spacer-right"
- open={false}
- />
- Foo
- </span>
- <div>
- header content
- </div>
- </div>
-</div>
-`;
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<ButtonProps> {
handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
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<ButtonProps> {
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 (
<button
{...props}
aria-disabled={disabled}
disabled={disabled}
- className={classNames('button', className, { disabled })}
+ className={classNames(isPlain ? '' : 'button', className, { disabled })}
id={this.props.id}
onClick={this.handleClick}
ref={this.props.innerRef}
diff --git a/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap
index c0843b958a6..35f36ffc66e 100644
--- a/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap
@@ -17,6 +17,7 @@ exports[`should render hotspots 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",
@@ -51,6 +52,7 @@ exports[`should render hotspots 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",
@@ -110,6 +112,7 @@ exports[`should render issues 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",
@@ -158,6 +161,7 @@ exports[`should render issues 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/issue/__tests__/__snapshots__/issue-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/issue-test.tsx.snap
index 383f9ff13dc..3b2f7d3baba 100644
--- a/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/issue-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/issue-test.tsx.snap
@@ -27,6 +27,7 @@ exports[`should render issues 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/issue/components/IssueTitleBar.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
index 2ada6ced27e..75fc9e1b3bc 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
@@ -50,7 +50,8 @@ export default function IssueTitleBar(props: IssueTitleBarProps) {
const locationsCount =
issue.secondaryLocations.length +
- issue.flows.reduce((sum, locations) => sum + locations.length, 0);
+ issue.flows.reduce((sum, locations) => sum + locations.length, 0) +
+ issue.flowsWithType.reduce((sum, { locations }) => sum + locations.length, 0);
const locationsBadge = (
<Tooltip
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap
index 958f8e72598..03560f28c24 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap
@@ -24,6 +24,7 @@ exports[`should render commentable 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",
@@ -66,6 +67,7 @@ exports[`should render commentable 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",
@@ -108,6 +110,7 @@ exports[`should render commentable 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",
@@ -150,6 +153,7 @@ exports[`should render commentable 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",
@@ -204,6 +208,7 @@ exports[`should render commentable 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",
@@ -256,6 +261,7 @@ exports[`should render effort correctly 1`] = `
"creationDate": "2017-03-01T09:36:01+0100",
"effort": "great",
"flows": Array [],
+ "flowsWithType": Array [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
@@ -297,6 +303,7 @@ exports[`should render effort correctly 1`] = `
"creationDate": "2017-03-01T09:36:01+0100",
"effort": "great",
"flows": Array [],
+ "flowsWithType": Array [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
@@ -338,6 +345,7 @@ exports[`should render effort correctly 1`] = `
"creationDate": "2017-03-01T09:36:01+0100",
"effort": "great",
"flows": Array [],
+ "flowsWithType": Array [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
@@ -379,6 +387,7 @@ exports[`should render effort correctly 1`] = `
"creationDate": "2017-03-01T09:36:01+0100",
"effort": "great",
"flows": Array [],
+ "flowsWithType": Array [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
@@ -433,6 +442,7 @@ exports[`should render effort correctly 1`] = `
"creationDate": "2017-03-01T09:36:01+0100",
"effort": "great",
"flows": Array [],
+ "flowsWithType": Array [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
@@ -484,6 +494,7 @@ exports[`should render issue 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",
@@ -524,6 +535,7 @@ exports[`should render issue 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",
@@ -564,6 +576,7 @@ exports[`should render issue 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",
@@ -604,6 +617,7 @@ exports[`should render issue 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",
@@ -648,6 +662,7 @@ exports[`should render issue 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",
@@ -699,6 +714,7 @@ exports[`should render security hotspot 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",
@@ -739,6 +755,7 @@ exports[`should render security hotspot 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",
@@ -779,6 +796,7 @@ exports[`should render security hotspot 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",
@@ -823,6 +841,7 @@ exports[`should render security hotspot 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/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap
index 8692df798b0..4cf04e124e1 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap
@@ -31,6 +31,7 @@ exports[`should render correctly: default 1`] = `
"creationDate": "2017-03-01T09:36:01+0100",
"externalRuleEngine": "foo",
"flows": Array [],
+ "flowsWithType": Array [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
@@ -120,6 +121,7 @@ exports[`should render correctly: with filter 1`] = `
"creationDate": "2017-03-01T09:36:01+0100",
"externalRuleEngine": "foo",
"flows": Array [],
+ "flowsWithType": Array [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
@@ -188,6 +190,7 @@ exports[`should render correctly: with filter 1`] = `
"creationDate": "2017-03-01T09:36:01+0100",
"externalRuleEngine": "foo",
"flows": Array [],
+ "flowsWithType": Array [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
@@ -297,6 +300,7 @@ exports[`should render correctly: with multi locations 1`] = `
},
],
],
+ "flowsWithType": Array [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
@@ -464,6 +468,7 @@ exports[`should render correctly: with multi locations and link 1`] = `
},
],
],
+ "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/locations/CrossFileLocationNavigator.tsx b/server/sonar-web/src/main/js/components/locations/CrossFileLocationNavigator.tsx
index 8254d91ef50..a724789cd44 100644
--- a/server/sonar-web/src/main/js/components/locations/CrossFileLocationNavigator.tsx
+++ b/server/sonar-web/src/main/js/components/locations/CrossFileLocationNavigator.tsx
@@ -27,7 +27,6 @@ import SingleFileLocationNavigator from './SingleFileLocationNavigator';
interface Props {
locations: FlowLocation[];
onLocationSelect: (index: number) => void;
- scroll: (element: Element) => void;
selectedLocationIndex: number | undefined;
}
@@ -113,7 +112,6 @@ export default class CrossFileLocationNavigator extends React.PureComponent<Prop
key={index}
message={message}
onClick={this.props.onLocationSelect}
- scroll={this.props.scroll}
selected={index === this.props.selectedLocationIndex}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveryItem-test.tsx b/server/sonar-web/src/main/js/components/locations/FlowsList.css
index 72317c29b71..0f348d33513 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/DeliveryItem-test.tsx
+++ b/server/sonar-web/src/main/js/components/locations/FlowsList.css
@@ -17,38 +17,18 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import DeliveryItem from '../DeliveryItem';
-
-const delivery = {
- at: '12.02.2018',
- durationMs: 20,
- httpStatus: 200,
- id: '2',
- success: true
-};
-
-it('should render correctly', () => {
- const wrapper = getWrapper();
- expect(wrapper).toMatchSnapshot();
-});
+.issue-flows .boxed-group-header {
+ padding: calc(1.5 * var(--gridSize));
+}
-it('should render correctly when no payload', () => {
- expect(getWrapper({ loading: true, payload: undefined })).toMatchSnapshot();
-});
+.issue-flows .boxed-group-inner {
+ padding: 0 var(--gridSize) var(--gridSize);
+}
-it('should render correctly when no http status', () => {
- expect(getWrapper({ delivery: { ...delivery, httpStatus: undefined } })).toMatchSnapshot();
-});
+.issue-flows .boxed-group-header .location-index {
+ background-color: var(--neutral600);
+}
-function getWrapper(props = {}) {
- return shallow(
- <DeliveryItem
- delivery={delivery}
- loading={false}
- payload={'{ status: "SUCCESS" }'}
- {...props}
- />
- );
+.issue-flows .boxed-group-header .location-index.selected {
+ background-color: var(--conciseIssueRedSelected);
}
diff --git a/server/sonar-web/src/main/js/components/locations/FlowsList.tsx b/server/sonar-web/src/main/js/components/locations/FlowsList.tsx
new file mode 100644
index 00000000000..6e2600da40d
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/locations/FlowsList.tsx
@@ -0,0 +1,81 @@
+/*
+ * 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 'FlowsList.css';
+import * as React from 'react';
+import ConciseIssueLocationBadge from '../../apps/issues/conciseIssuesList/ConciseIssueLocationBadge';
+import { translate } from '../../helpers/l10n';
+import { Flow, FlowType } from '../../types/types';
+import BoxedGroupAccordion from '../controls/BoxedGroupAccordion';
+import SingleFileLocationNavigator from './SingleFileLocationNavigator';
+
+export interface Props {
+ flows: Flow[];
+ selectedLocationIndex?: number;
+ selectedFlowIndex?: number;
+ onFlowSelect: (index?: number) => void;
+ onLocationSelect: (index: number) => void;
+}
+
+export default function FlowsList(props: Props) {
+ const { flows, selectedLocationIndex, selectedFlowIndex } = props;
+
+ return (
+ <div className="issue-flows little-padded-top" role="list">
+ {flows.map((flow, index) => {
+ const open = selectedFlowIndex === index;
+ return (
+ <BoxedGroupAccordion
+ className="spacer-top"
+ // eslint-disable-next-line react/no-array-index-key
+ key={index}
+ onClick={() => props.onFlowSelect(open ? undefined : index)}
+ open={open}
+ noBorder={flow.type === FlowType.EXECUTION}
+ title={
+ flow.type === FlowType.EXECUTION
+ ? translate('issue.execution_flow')
+ : flow.description
+ }
+ renderHeader={() => (
+ <ConciseIssueLocationBadge
+ count={flow.locations.length}
+ flow={true}
+ selected={open}
+ />
+ )}>
+ <ul>
+ {flow.locations.map((location, locIndex) => (
+ // eslint-disable-next-line react/no-array-index-key
+ <li className="display-flex-column" key={locIndex}>
+ <SingleFileLocationNavigator
+ index={locIndex}
+ message={location.msg}
+ onClick={props.onLocationSelect}
+ selected={locIndex === selectedLocationIndex}
+ />
+ </li>
+ ))}
+ </ul>
+ </BoxedGroupAccordion>
+ );
+ })}
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/components/locations/LocationsList.tsx b/server/sonar-web/src/main/js/components/locations/LocationsList.tsx
index 65e3c6ebdf2..043de9d030a 100644
--- a/server/sonar-web/src/main/js/components/locations/LocationsList.tsx
+++ b/server/sonar-web/src/main/js/components/locations/LocationsList.tsx
@@ -17,51 +17,54 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { uniq } from 'lodash';
import * as React from 'react';
import { FlowLocation } from '../../types/types';
import CrossFileLocationNavigator from './CrossFileLocationNavigator';
import SingleFileLocationNavigator from './SingleFileLocationNavigator';
interface Props {
- isCrossFile: boolean;
+ componentKey: string;
locations: FlowLocation[];
onLocationSelect: (index: number) => void;
- scroll: (element: Element) => void;
selectedLocationIndex?: number;
+ showCrossFile?: boolean;
}
export default class LocationsList extends React.PureComponent<Props> {
render() {
- const { isCrossFile, locations, selectedLocationIndex } = this.props;
+ const { locations, componentKey, selectedLocationIndex, showCrossFile = true } = this.props;
+
+ const locationComponents = [componentKey, ...locations.map(location => location.component)];
+ const isCrossFile = uniq(locationComponents).length > 1;
if (!locations || locations.length === 0 || locations.every(location => !location.msg)) {
return null;
}
- if (isCrossFile) {
+ if (isCrossFile && showCrossFile) {
return (
<CrossFileLocationNavigator
locations={locations}
onLocationSelect={this.props.onLocationSelect}
- scroll={this.props.scroll}
selectedLocationIndex={selectedLocationIndex}
/>
);
}
return (
- <div className="spacer-top">
+ <ul className="spacer-top ">
{locations.map((location, index) => (
- <SingleFileLocationNavigator
- index={index}
- // eslint-disable-next-line react/no-array-index-key
- key={index}
- message={location.msg}
- onClick={this.props.onLocationSelect}
- scroll={this.props.scroll}
- selected={index === selectedLocationIndex}
- />
+ // eslint-disable-next-line react/no-array-index-key
+ <li className="display-flex-column" key={index}>
+ <SingleFileLocationNavigator
+ index={index}
+ message={location.msg}
+ onClick={this.props.onLocationSelect}
+ selected={index === selectedLocationIndex}
+ />
+ </li>
))}
- </div>
+ </ul>
);
}
}
diff --git a/server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.css b/server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.css
index f3e60e4a969..d4bc578ecb6 100644
--- a/server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.css
+++ b/server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.css
@@ -18,10 +18,26 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
.locations-navigator {
- position: relative;
- z-index: var(--aboveNormalZIndex);
- display: inline-flex;
+ display: flex;
align-items: flex-start;
- max-width: 100%;
- border: none;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ padding: calc(1 / 2 * var(--gridSize));
+ margin-bottom: calc(1 / 4 * var(--gridSize));
+ color: inherit;
+ text-align: left;
+}
+
+.locations-navigator:hover,
+.locations-navigator:active {
+ border-color: var(--info400);
+}
+
+.locations-navigator:focus {
+ border-color: transparent;
+}
+
+button.locations-navigator.selected {
+ border-color: var(--info400);
+ background-color: var(--info50);
}
diff --git a/server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.tsx b/server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.tsx
index 07d329ed224..54be15b43ff 100644
--- a/server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.tsx
+++ b/server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.tsx
@@ -17,16 +17,17 @@
* 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 LocationIndex from '../common/LocationIndex';
import LocationMessage from '../common/LocationMessage';
+import { ButtonPlain } from '../controls/buttons';
import './SingleFileLocationNavigator.css';
interface Props {
index: number;
message: string | undefined;
onClick: (index: number) => void;
- scroll: (element: Element) => void;
selected: boolean;
}
@@ -35,18 +36,25 @@ export default class SingleFileLocationNavigator extends React.PureComponent<Pro
componentDidMount() {
if (this.props.selected && this.node) {
- this.props.scroll(this.node);
+ this.node.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center',
+ inline: 'center'
+ });
}
}
componentDidUpdate(prevProps: Props) {
if (this.props.selected && prevProps.selected !== this.props.selected && this.node) {
- this.props.scroll(this.node);
+ this.node.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center',
+ inline: 'center'
+ });
}
}
- handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
- event.preventDefault();
+ handleClick = () => {
this.props.onClick(this.props.index);
};
@@ -54,12 +62,18 @@ export default class SingleFileLocationNavigator extends React.PureComponent<Pro
const { index, message, selected } = this.props;
return (
- <div className="little-spacer-top" ref={node => (this.node = node)}>
- <a className="locations-navigator" href="#" onClick={this.handleClick}>
- <LocationIndex selected={selected}>{index + 1}</LocationIndex>
- <LocationMessage selected={selected}>{message}</LocationMessage>
- </a>
- </div>
+ <ButtonPlain
+ preventDefault={true}
+ stopPropagation={true}
+ aria-current={selected ? 'location' : false}
+ className={classNames('locations-navigator', { selected })}
+ innerRef={node => {
+ this.node = node;
+ }}
+ onClick={this.handleClick}>
+ <LocationIndex>{index + 1}</LocationIndex>
+ <LocationMessage>{message}</LocationMessage>
+ </ButtonPlain>
);
}
}
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<CrossFileLocationsNavigator['props']> = {}
<CrossFileLocationsNavigator
locations={[location1, location2, location3]}
onLocationSelect={jest.fn()}
- scroll={jest.fn()}
selectedLocationIndex={undefined}
{...props}
/>
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<LocationsList['props']> = {}) {
return shallow<LocationsList>(
<LocationsList
locations={mockIssue().secondaryLocations}
- isCrossFile={true}
+ componentKey="foo"
onLocationSelect={jest.fn()}
- scroll={jest.fn()}
selectedLocationIndex={undefined}
{...overrides}
/>
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<SingleFileLocationNavigator['props']> = {}
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}
/>
</div>
@@ -67,7 +66,6 @@ exports[`should render 1`] = `
key="2"
message="Do not use bar"
onClick={[MockFunction]}
- scroll={[MockFunction]}
selected={false}
/>
</div>
@@ -97,7 +95,6 @@ exports[`should render locations with no component name 1`] = `
index={0}
key="0"
onClick={[MockFunction]}
- scroll={[MockFunction]}
selected={false}
/>
</div>
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`] = `
-<CrossFileLocationNavigator
- locations={
- Array [
- Object {
- "component": "foo",
- "componentName": "src/foo.js",
- "msg": "Do not use foo",
- "textRange": Object {
- "endLine": 7,
- "endOffset": 8,
- "startLine": 7,
- "startOffset": 5,
- },
- },
- Object {
- "component": "bar",
- "componentName": "src/bar.js",
- "msg": "Do not use bar",
- "textRange": Object {
- "endLine": 16,
- "endOffset": 6,
- "startLine": 15,
- "startOffset": 4,
- },
- },
- ]
- }
- onLocationSelect={[MockFunction]}
- scroll={[MockFunction]}
-/>
-`;
-
exports[`should render locations in the same file 1`] = `
-<div
- className="spacer-top"
+<ul
+ className="spacer-top "
>
- <SingleFileLocationNavigator
- index={0}
+ <li
+ className="display-flex-column"
key="0"
- message="Do not use foo"
- onClick={[MockFunction]}
- scroll={[MockFunction]}
- selected={false}
- />
- <SingleFileLocationNavigator
- index={1}
+ >
+ <SingleFileLocationNavigator
+ index={0}
+ message="Do not use foo"
+ onClick={[MockFunction]}
+ selected={false}
+ />
+ </li>
+ <li
+ className="display-flex-column"
key="1"
- message="Do not use foo"
- onClick={[MockFunction]}
- scroll={[MockFunction]}
- selected={false}
- />
-</div>
+ >
+ <SingleFileLocationNavigator
+ index={1}
+ message="Do not use foo"
+ onClick={[MockFunction]}
+ selected={false}
+ />
+ </li>
+</ul>
`;
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`] = `
-<div
- className="little-spacer-top"
+<ButtonPlain
+ aria-current="location"
+ className="locations-navigator selected"
+ innerRef={[Function]}
+ onClick={[Function]}
+ preventDefault={true}
+ stopPropagation={true}
>
- <a
- className="locations-navigator"
- href="#"
- onClick={[Function]}
- >
- <LocationIndex
- selected={true}
- >
- 1
- </LocationIndex>
- <LocationMessage
- selected={true}
- />
- </a>
-</div>
+ <LocationIndex>
+ 1
+ </LocationIndex>
+ <LocationMessage />
+</ButtonPlain>
`;
exports[`should render correctly: index 2 1`] = `
-<div
- className="little-spacer-top"
+<ButtonPlain
+ aria-current="location"
+ className="locations-navigator selected"
+ innerRef={[Function]}
+ onClick={[Function]}
+ preventDefault={true}
+ stopPropagation={true}
>
- <a
- className="locations-navigator"
- href="#"
- onClick={[Function]}
- >
- <LocationIndex
- selected={true}
- >
- 2
- </LocationIndex>
- <LocationMessage
- selected={true}
- />
- </a>
-</div>
+ <LocationIndex>
+ 2
+ </LocationIndex>
+ <LocationMessage />
+</ButtonPlain>
`;
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> = {}): 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<Issue> = {})
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<Comment>;
component: string;
flows?: Array<{
- // `componentName` is not available in RawIssue
+ type?: string;
+ description?: string;
locations?: Array<Omit<FlowLocation, 'componentName'>>;
}>;
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<T = string> {
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.