aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2019-03-15 10:38:59 +0100
committersonartech <sonartech@sonarsource.com>2019-03-26 13:37:56 +0100
commit51cfe7eed3fa9e659f5e9e98d3e3ba9b9f0a6699 (patch)
treedcaa6ae58725241dfa09242a7d95b293e33b76f1
parent62a8287557d8ea46c6d09a9acd679f1e3c5f01cd (diff)
downloadsonarqube-51cfe7eed3fa9e659f5e9e98d3e3ba9b9f0a6699.tar.gz
sonarqube-51cfe7eed3fa9e659f5e9e98d3e3ba9b9f0a6699.zip
SONAR-11681 Add loader for issue list
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts57
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/App.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx33
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesList-test.tsx54
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesList-test.tsx.snap133
-rw-r--r--server/sonar-web/src/main/js/apps/issues/utils.ts8
6 files changed, 286 insertions, 9 deletions
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts
new file mode 100644
index 00000000000..5b3528a60b2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { scrollToElement } from '../../../helpers/scrolling';
+import { scrollToIssue } from '../utils';
+
+jest.mock('../../../helpers/scrolling', () => ({
+ scrollToElement: jest.fn()
+}));
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+describe('scrollToIssue', () => {
+ it('should scroll to the issue', () => {
+ document.querySelector = jest.fn(() => ({}));
+
+ scrollToIssue('issue1', false);
+ expect(scrollToElement).toHaveBeenCalled();
+ });
+ it("should ignore issue if it doesn't exist", () => {
+ document.querySelector = jest.fn(() => null);
+
+ scrollToIssue('issue1', false);
+ expect(scrollToElement).not.toHaveBeenCalled();
+ });
+ it('should scroll smoothly by default', () => {
+ document.querySelector = jest.fn(() => ({}));
+
+ scrollToIssue('issue1');
+ expect(scrollToElement).toHaveBeenCalledWith(
+ {},
+ {
+ bottomOffset: 100,
+ smooth: true,
+ topOffset: 250
+ }
+ );
+ });
+});
diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.tsx b/server/sonar-web/src/main/js/apps/issues/components/App.tsx
index 6219650e84a..632211d2af7 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/App.tsx
@@ -50,7 +50,8 @@ import {
saveMyIssues,
serializeQuery,
STANDARDS,
- ReferencedRule
+ ReferencedRule,
+ scrollToIssue
} from '../utils';
import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
import { Alert } from '../../../components/ui/Alert';
@@ -78,7 +79,6 @@ import {
removeSideBarClass,
removeWhitePageClass
} from '../../../helpers/pages';
-import { scrollToElement } from '../../../helpers/scrolling';
import { isSonarCloud } from '../../../helpers/system';
import { withRouter, Location, Router } from '../../../components/hoc/withRouter';
import '../../../components/search-navigator.css';
@@ -381,7 +381,6 @@ export class App extends React.PureComponent<Props, State> {
open: undefined
}
});
- this.scrollToSelectedIssue(false);
}
};
@@ -395,10 +394,7 @@ export class App extends React.PureComponent<Props, State> {
scrollToSelectedIssue = (smooth = true) => {
const { selected } = this.state;
if (selected) {
- const element = document.querySelector(`[data-issue="${selected}"]`);
- if (element) {
- scrollToElement(element, { topOffset: 250, bottomOffset: 100, smooth });
- }
+ scrollToIssue(selected, smooth);
}
};
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx
index da62b8a436c..66fec76a000 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import ListItem from './ListItem';
-import { Query } from '../utils';
+import { Query, scrollToIssue } from '../utils';
interface Props {
branchLike: T.BranchLike | undefined;
@@ -36,9 +36,38 @@ interface Props {
selectedIssue: T.Issue | undefined;
}
-export default class IssuesList extends React.PureComponent<Props> {
+interface State {
+ prerender: boolean;
+}
+
+export default class IssuesList extends React.PureComponent<Props, State> {
+ state: State = {
+ prerender: true
+ };
+
+ componentDidMount() {
+ // ! \\ This prerender state variable is to enable the page to be displayed
+ // immediately, displaying a loader before attempting to render the
+ // list of issues. See https://jira.sonarsource.com/browse/SONAR-11681
+ setTimeout(() => {
+ this.setState({ prerender: false });
+ if (this.props.selectedIssue) {
+ scrollToIssue(this.props.selectedIssue.key, false);
+ }
+ }, 42);
+ }
+
render() {
const { branchLike, checked, component, issues, openPopup, selectedIssue } = this.props;
+ const { prerender } = this.state;
+
+ if (prerender) {
+ return (
+ <div>
+ <i className="spinner" />
+ </div>
+ );
+ }
return (
<div>
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesList-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesList-test.tsx
new file mode 100644
index 00000000000..556824173d2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesList-test.tsx
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 * as React from 'react';
+import { shallow } from 'enzyme';
+import IssuesList from '../IssuesList';
+import { mockIssue } from '../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+
+it('should render correctly', async () => {
+ jest.useFakeTimers();
+
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+ jest.runAllTimers();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(overrides: Partial<IssuesList['props']> = {}) {
+ return shallow(
+ <IssuesList
+ branchLike={undefined}
+ checked={[]}
+ component={undefined}
+ issues={[mockIssue(), mockIssue(false, { key: 'AVsae-CQS-9G3txfbFN3' })]}
+ onFilterChange={jest.fn()}
+ onIssueChange={jest.fn()}
+ onIssueCheck={jest.fn()}
+ onIssueClick={jest.fn()}
+ onPopupToggle={jest.fn()}
+ openPopup={undefined}
+ organization={undefined}
+ selectedIssue={undefined}
+ {...overrides}
+ />
+ );
+}
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
new file mode 100644
index 00000000000..c7a30dd2379
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesList-test.tsx.snap
@@ -0,0 +1,133 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div>
+ <i
+ className="spinner"
+ />
+</div>
+`;
+
+exports[`should render correctly 2`] = `
+<div>
+ <ListItem
+ checked={false}
+ issue={
+ Object {
+ "actions": Array [],
+ "component": "main.js",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": Array [],
+ "fromHotspot": false,
+ "key": "AVsae-CQS-9G3txfbFN2",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "organization": "myorg",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "projectOrganization": "org",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": Array [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
+ }
+ }
+ key="AVsae-CQS-9G3txfbFN2"
+ onChange={[MockFunction]}
+ onCheck={[MockFunction]}
+ onClick={[MockFunction]}
+ onFilterChange={[MockFunction]}
+ onPopupToggle={[MockFunction]}
+ selected={false}
+ />
+ <ListItem
+ checked={false}
+ issue={
+ Object {
+ "actions": Array [],
+ "component": "main.js",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": Array [],
+ "fromHotspot": false,
+ "key": "AVsae-CQS-9G3txfbFN3",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "organization": "myorg",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "projectOrganization": "org",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": Array [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
+ }
+ }
+ key="AVsae-CQS-9G3txfbFN3"
+ onChange={[MockFunction]}
+ onCheck={[MockFunction]}
+ onClick={[MockFunction]}
+ onFilterChange={[MockFunction]}
+ onPopupToggle={[MockFunction]}
+ previousIssue={
+ Object {
+ "actions": Array [],
+ "component": "main.js",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": Array [],
+ "fromHotspot": false,
+ "key": "AVsae-CQS-9G3txfbFN2",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "organization": "myorg",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "projectOrganization": "org",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": Array [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
+ }
+ }
+ selected={false}
+ />
+</div>
+`;
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 a64a653fa86..1f5e3e31e03 100644
--- a/server/sonar-web/src/main/js/apps/issues/utils.ts
+++ b/server/sonar-web/src/main/js/apps/issues/utils.ts
@@ -33,6 +33,7 @@ import {
serializeDateShort,
RawQuery
} from '../../helpers/query';
+import { scrollToElement } from '../../helpers/scrolling';
export interface Query {
assigned: boolean;
@@ -267,3 +268,10 @@ export function allLocationsEmpty(
) {
return getLocations(issue, selectedFlowIndex).every(location => !location.msg);
}
+
+export function scrollToIssue(issue: string, smooth = true) {
+ const element = document.querySelector(`[data-issue="${issue}"]`);
+ if (element) {
+ scrollToElement(element, { topOffset: 250, bottomOffset: 100, smooth });
+ }
+}