From 9ad0e56f42abd45343f16be66edae1f1a4f688b5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Mon, 13 Aug 2018 15:19:24 +0200 Subject: [PATCH] SONAR-11149 Fix loading problems in issues page --- .../src/main/js/app/styles/init/misc.css | 4 ++ .../js/apps/issues/components/PageActions.tsx | 2 +- .../apps/issues/components/ReloadButton.tsx | 54 --------------- .../__tests__/{App-test.js => App-test.tsx} | 56 +++++++++------ .../issues/conciseIssuesList/BackButton.tsx | 54 --------------- .../ConciseIssuesListHeader.tsx | 8 ++- .../js/components/controls/BackButton.tsx | 68 +++++++++++++++++++ .../js/components/controls/ListFooter.tsx | 5 ++ .../controls/__tests__/ListFooter-test.tsx | 8 +++ 9 files changed, 128 insertions(+), 131 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/issues/components/ReloadButton.tsx rename server/sonar-web/src/main/js/apps/issues/components/__tests__/{App-test.js => App-test.tsx} (69%) delete mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/BackButton.tsx create mode 100644 server/sonar-web/src/main/js/components/controls/BackButton.tsx 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 11bbc4bb9f7..9b7c566d681 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 @@ -308,6 +308,10 @@ td.big-spacer-top { align-items: center; } +.position-absolute { + position: absolute !important; +} + .rounded { border-radius: 2px; } diff --git a/server/sonar-web/src/main/js/apps/issues/components/PageActions.tsx b/server/sonar-web/src/main/js/apps/issues/components/PageActions.tsx index 330409caa95..1850b50dbda 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/PageActions.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/PageActions.tsx @@ -19,9 +19,9 @@ */ import * as React from 'react'; import IssuesCounter from './IssuesCounter'; -import ReloadButton from './ReloadButton'; import { HomePageType, Paging } from '../../../app/types'; import HomePageSelect from '../../../components/controls/HomePageSelect'; +import ReloadButton from '../../../components/controls/ReloadButton'; import { translate } from '../../../helpers/l10n'; import { isSonarCloud } from '../../../helpers/system'; diff --git a/server/sonar-web/src/main/js/apps/issues/components/ReloadButton.tsx b/server/sonar-web/src/main/js/apps/issues/components/ReloadButton.tsx deleted file mode 100644 index 3af99da5480..00000000000 --- a/server/sonar-web/src/main/js/apps/issues/components/ReloadButton.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as classNames from 'classnames'; -import Tooltip from '../../../components/controls/Tooltip'; -import { translate } from '../../../helpers/l10n'; - -interface Props { - className?: string; - onClick: () => void; -} - -/* eslint-disable max-len */ -const icon = ( - - - -); -/* eslint-enable max-len */ - -export default function ReloadButton(props: Props) { - const handleClick = (event: React.MouseEvent) => { - event.preventDefault(); - props.onClick(); - }; - - return ( - - - {icon} - - - ); -} diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.js b/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx similarity index 69% rename from server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.js rename to server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx index dfc109dcb2b..bd16df2c7d1 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.js +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx @@ -17,19 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow import * as React from 'react'; -import { shallow, mount } from 'enzyme'; import App from '../App'; import { shallowWithIntl, waitAndUpdate } from '../../../../helpers/testUtils'; +import { Issue } from '../../../../app/types'; const replace = jest.fn(); -const issues = [{ key: 'foo' }, { key: 'bar' }, { key: 'third' }, { key: 'fourth' }]; +const issues = [ + { key: 'foo' } as Issue, + { key: 'bar' } as Issue, + { key: 'third' } as Issue, + { key: 'fourth' } as Issue +]; const facets = [{ property: 'severities', values: [{ val: 'MINOR', count: 4 }] }]; -const paging = [{ pageIndex: 1, pageSize: 100, total: 4 }]; +const paging = { pageIndex: 1, pageSize: 100, total: 4 }; -const eventNoShiftKey = { shiftKey: false }; -const eventWithShiftKey = { shiftKey: true }; +const eventNoShiftKey = { shiftKey: false } as MouseEvent; +const eventWithShiftKey = { shiftKey: true } as MouseEvent; const PROPS = { branch: { isMain: true, name: 'master' }, @@ -40,12 +44,22 @@ const PROPS = { login: 'JohnDoe', name: 'John Doe' }, - component: { key: 'foo', name: 'bar', organization: 'John', qualifier: 'Doe' }, + component: { breadcrumbs: [], key: 'foo', name: 'bar', organization: 'John', qualifier: 'Doe' }, location: { pathname: '/issues', query: {} }, - fetchIssues: () => Promise.resolve({ facets, issues, paging }), + fetchIssues: () => + Promise.resolve({ + components: [], + facets, + issues, + languages: [], + paging, + rules: [], + users: [] + }), onBranchesChange: () => {}, onSonarCloud: false, - organization: { key: 'foo' } + organization: { key: 'foo' }, + userOrganizations: [] }; it('should render a list of issue', async () => { @@ -65,16 +79,17 @@ it('should be able to check/uncheck a group of issues with the Shift key', async await waitAndUpdate(wrapper); expect(wrapper.state().issues.length).toBe(4); - wrapper.instance().handleIssueCheck('foo', eventNoShiftKey); + const instance = wrapper.instance() as App; + instance.handleIssueCheck('foo', eventNoShiftKey); expect(wrapper.state().checked.length).toBe(1); - wrapper.instance().handleIssueCheck('fourth', eventWithShiftKey); + instance.handleIssueCheck('fourth', eventWithShiftKey); expect(wrapper.state().checked.length).toBe(4); - wrapper.instance().handleIssueCheck('third', eventNoShiftKey); + instance.handleIssueCheck('third', eventNoShiftKey); expect(wrapper.state().checked.length).toBe(3); - wrapper.instance().handleIssueCheck('foo', eventWithShiftKey); + instance.handleIssueCheck('foo', eventWithShiftKey); expect(wrapper.state().checked.length).toBe(1); }); @@ -86,10 +101,11 @@ it('should avoid non-existing keys', async () => { await waitAndUpdate(wrapper); expect(wrapper.state().issues.length).toBe(4); - wrapper.instance().handleIssueCheck('foo', eventNoShiftKey); + const instance = wrapper.instance() as App; + instance.handleIssueCheck('foo', eventNoShiftKey); expect(wrapper.state().checked.length).toBe(1); - wrapper.instance().handleIssueCheck('non-existing-key', eventWithShiftKey); + instance.handleIssueCheck('non-existing-key', eventWithShiftKey); expect(wrapper.state().checked.length).toBe(1); }); @@ -101,11 +117,12 @@ it('should be able to uncheck all issue with global checkbox', async () => { await waitAndUpdate(wrapper); expect(wrapper.state().issues.length).toBe(4); - wrapper.instance().handleIssueCheck('foo', eventNoShiftKey); - wrapper.instance().handleIssueCheck('bar', eventNoShiftKey); + const instance = wrapper.instance() as App; + instance.handleIssueCheck('foo', eventNoShiftKey); + instance.handleIssueCheck('bar', eventNoShiftKey); expect(wrapper.state().checked.length).toBe(2); - wrapper.instance().onCheckAll(false); + instance.onCheckAll(false); expect(wrapper.state().checked.length).toBe(0); }); @@ -116,7 +133,8 @@ it('should be able to check all issue with global checkbox', async () => { await waitAndUpdate(wrapper); + const instance = wrapper.instance() as App; expect(wrapper.state().checked.length).toBe(0); - wrapper.instance().onCheckAll(true); + instance.onCheckAll(true); expect(wrapper.state().checked.length).toBe(wrapper.state().issues.length); }); diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/BackButton.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/BackButton.tsx deleted file mode 100644 index d2a9637e310..00000000000 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/BackButton.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as classNames from 'classnames'; -import Tooltip from '../../../components/controls/Tooltip'; -import { translate } from '../../../helpers/l10n'; - -interface Props { - className?: string; - onClick: () => void; -} - -/* eslint-disable max-len */ -const icon = ( - - - -); -/* eslint-enable max-len */ - -export default function BackButton(props: Props) { - const handleClick = (event: React.MouseEvent) => { - event.preventDefault(); - props.onClick(); - }; - - return ( - - - {icon} - - - ); -} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesListHeader.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesListHeader.tsx index fb50f4f0fec..ffffdeb0a55 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesListHeader.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesListHeader.tsx @@ -18,8 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import BackButton from './BackButton'; -import ReloadButton from '../components/ReloadButton'; +import BackButton from '../../../components/controls/BackButton'; +import ReloadButton from '../../../components/controls/ReloadButton'; import IssuesCounter from '../components/IssuesCounter'; import { Paging } from '../../../app/types'; @@ -38,7 +38,9 @@ export default function ConciseIssuesListHeader(props: Props) { return (
- {displayBackButton && } + {displayBackButton && ( + + )} {props.loading ? ( ) : ( diff --git a/server/sonar-web/src/main/js/components/controls/BackButton.tsx b/server/sonar-web/src/main/js/components/controls/BackButton.tsx new file mode 100644 index 00000000000..d8d24cc1fbd --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/BackButton.tsx @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as classNames from 'classnames'; +import Tooltip from './Tooltip'; +import * as theme from '../../app/theme'; +import { translate } from '../../helpers/l10n'; + +interface Props { + className?: string; + disabled?: boolean; + tooltip?: string; + onClick: () => void; +} + +export default class BackButton extends React.PureComponent { + handleClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.currentTarget.blur(); + if (!this.props.disabled) { + this.props.onClick(); + } + }; + + renderIcon = () => ( + + + + ); + + render() { + const { tooltip = translate('issues.return_to_list') } = this.props; + return ( + + + {this.renderIcon()} + + + ); + } +} diff --git a/server/sonar-web/src/main/js/components/controls/ListFooter.tsx b/server/sonar-web/src/main/js/components/controls/ListFooter.tsx index 29113a8edbb..9ac22f64c89 100644 --- a/server/sonar-web/src/main/js/components/controls/ListFooter.tsx +++ b/server/sonar-web/src/main/js/components/controls/ListFooter.tsx @@ -19,12 +19,14 @@ */ import * as React from 'react'; import * as classNames from 'classnames'; +import DeferredSpinner from '../common/DeferredSpinner'; import { translate, translateWithParameters } from '../../helpers/l10n'; import { formatMeasure } from '../../helpers/measures'; interface Props { count: number; className?: string; + loading?: boolean; loadMore?: () => void; ready?: boolean; total?: number; @@ -59,6 +61,9 @@ export default function ListFooter({ ready = true, ...props }: Props) { formatMeasure(props.total, 'INT', null) )} {props.loadMore != null && hasMore ? loadMoreLink : null} + {props.loading && ( + + )} ); } diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.tsx index b8de25e41f1..dab8c4d33ab 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.tsx +++ b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.tsx @@ -45,3 +45,11 @@ it('should "show more"', () => { click(link); expect(loadMore).toBeCalled(); }); + +it('should display spinner while loading', () => { + expect( + shallow() + .find('DeferredSpinner') + .exists() + ).toBe(true); +}); -- 2.39.5