align-items: center;
}
+.position-absolute {
+ position: absolute !important;
+}
+
.rounded {
border-radius: 2px;
}
*/
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';
+++ /dev/null
-/*
- * 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 = (
- <svg height="24" viewBox="0 0 18 24" width="18">
- <path d="M16.6454 8.1084c-.3-.5-.9-.7-1.4-.4-.5.3-.7.9-.4 1.4.9 1.6 1.1 3.4.6 5.1-.5 1.7-1.7 3.2-3.2 4-3.3 1.8-7.4.6-9.1-2.7-1.8-3.1-.8-6.9 2.1-8.8v3.3h2v-7h-7v2h3.9c-3.7 2.5-5 7.5-2.8 11.4 1.6 3 4.6 4.6 7.7 4.6 1.4 0 2.8-.3 4.2-1.1 2-1.1 3.5-3 4.2-5.2.6-2.2.3-4.6-.8-6.6z" />
- </svg>
-);
-/* eslint-enable max-len */
-
-export default function ReloadButton(props: Props) {
- const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
- event.preventDefault();
- props.onClick();
- };
-
- return (
- <Tooltip overlay={translate('reload')}>
- <a
- className={classNames('concise-issues-list-header-button', props.className)}
- href="#"
- onClick={handleClick}>
- {icon}
- </a>
- </Tooltip>
- );
-}
+++ /dev/null
-/*
- * 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.
- */
-// @flow
-import * as React from 'react';
-import { shallow, mount } from 'enzyme';
-import App from '../App';
-import { shallowWithIntl, waitAndUpdate } from '../../../../helpers/testUtils';
-
-const replace = jest.fn();
-const issues = [{ key: 'foo' }, { key: 'bar' }, { key: 'third' }, { key: 'fourth' }];
-const facets = [{ property: 'severities', values: [{ val: 'MINOR', count: 4 }] }];
-const paging = [{ pageIndex: 1, pageSize: 100, total: 4 }];
-
-const eventNoShiftKey = { shiftKey: false };
-const eventWithShiftKey = { shiftKey: true };
-
-const PROPS = {
- branch: { isMain: true, name: 'master' },
- currentUser: {
- isLoggedIn: true,
- avatar: 'foo',
- email: 'forr@bar.com',
- login: 'JohnDoe',
- name: 'John Doe'
- },
- component: { key: 'foo', name: 'bar', organization: 'John', qualifier: 'Doe' },
- location: { pathname: '/issues', query: {} },
- fetchIssues: () => Promise.resolve({ facets, issues, paging }),
- onBranchesChange: () => {},
- onSonarCloud: false,
- organization: { key: 'foo' }
-};
-
-it('should render a list of issue', async () => {
- const wrapper = shallowWithIntl(<App {...PROPS} />, {
- context: { router: { replace } }
- });
-
- await waitAndUpdate(wrapper);
- expect(wrapper.state().issues.length).toBe(4);
-});
-
-it('should be able to check/uncheck a group of issues with the Shift key', async () => {
- const wrapper = shallowWithIntl(<App {...PROPS} />, {
- context: { router: { replace } }
- });
-
- await waitAndUpdate(wrapper);
- expect(wrapper.state().issues.length).toBe(4);
-
- wrapper.instance().handleIssueCheck('foo', eventNoShiftKey);
- expect(wrapper.state().checked.length).toBe(1);
-
- wrapper.instance().handleIssueCheck('fourth', eventWithShiftKey);
- expect(wrapper.state().checked.length).toBe(4);
-
- wrapper.instance().handleIssueCheck('third', eventNoShiftKey);
- expect(wrapper.state().checked.length).toBe(3);
-
- wrapper.instance().handleIssueCheck('foo', eventWithShiftKey);
- expect(wrapper.state().checked.length).toBe(1);
-});
-
-it('should avoid non-existing keys', async () => {
- const wrapper = shallowWithIntl(<App {...PROPS} />, {
- context: { router: { replace } }
- });
-
- await waitAndUpdate(wrapper);
- expect(wrapper.state().issues.length).toBe(4);
-
- wrapper.instance().handleIssueCheck('foo', eventNoShiftKey);
- expect(wrapper.state().checked.length).toBe(1);
-
- wrapper.instance().handleIssueCheck('non-existing-key', eventWithShiftKey);
- expect(wrapper.state().checked.length).toBe(1);
-});
-
-it('should be able to uncheck all issue with global checkbox', async () => {
- const wrapper = shallowWithIntl(<App {...PROPS} />, {
- context: { router: { replace } }
- });
-
- await waitAndUpdate(wrapper);
- expect(wrapper.state().issues.length).toBe(4);
-
- wrapper.instance().handleIssueCheck('foo', eventNoShiftKey);
- wrapper.instance().handleIssueCheck('bar', eventNoShiftKey);
- expect(wrapper.state().checked.length).toBe(2);
-
- wrapper.instance().onCheckAll(false);
- expect(wrapper.state().checked.length).toBe(0);
-});
-
-it('should be able to check all issue with global checkbox', async () => {
- const wrapper = shallowWithIntl(<App {...PROPS} />, {
- context: { router: { replace } }
- });
-
- await waitAndUpdate(wrapper);
-
- expect(wrapper.state().checked.length).toBe(0);
- wrapper.instance().onCheckAll(true);
- expect(wrapper.state().checked.length).toBe(wrapper.state().issues.length);
-});
--- /dev/null
+/*
+ * 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 App from '../App';
+import { shallowWithIntl, waitAndUpdate } from '../../../../helpers/testUtils';
+import { Issue } from '../../../../app/types';
+
+const replace = jest.fn();
+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 eventNoShiftKey = { shiftKey: false } as MouseEvent;
+const eventWithShiftKey = { shiftKey: true } as MouseEvent;
+
+const PROPS = {
+ branch: { isMain: true, name: 'master' },
+ currentUser: {
+ isLoggedIn: true,
+ avatar: 'foo',
+ email: 'forr@bar.com',
+ login: 'JohnDoe',
+ name: 'John Doe'
+ },
+ component: { breadcrumbs: [], key: 'foo', name: 'bar', organization: 'John', qualifier: 'Doe' },
+ location: { pathname: '/issues', query: {} },
+ fetchIssues: () =>
+ Promise.resolve({
+ components: [],
+ facets,
+ issues,
+ languages: [],
+ paging,
+ rules: [],
+ users: []
+ }),
+ onBranchesChange: () => {},
+ onSonarCloud: false,
+ organization: { key: 'foo' },
+ userOrganizations: []
+};
+
+it('should render a list of issue', async () => {
+ const wrapper = shallowWithIntl(<App {...PROPS} />, {
+ context: { router: { replace } }
+ });
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().issues.length).toBe(4);
+});
+
+it('should be able to check/uncheck a group of issues with the Shift key', async () => {
+ const wrapper = shallowWithIntl(<App {...PROPS} />, {
+ context: { router: { replace } }
+ });
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().issues.length).toBe(4);
+
+ const instance = wrapper.instance() as App;
+ instance.handleIssueCheck('foo', eventNoShiftKey);
+ expect(wrapper.state().checked.length).toBe(1);
+
+ instance.handleIssueCheck('fourth', eventWithShiftKey);
+ expect(wrapper.state().checked.length).toBe(4);
+
+ instance.handleIssueCheck('third', eventNoShiftKey);
+ expect(wrapper.state().checked.length).toBe(3);
+
+ instance.handleIssueCheck('foo', eventWithShiftKey);
+ expect(wrapper.state().checked.length).toBe(1);
+});
+
+it('should avoid non-existing keys', async () => {
+ const wrapper = shallowWithIntl(<App {...PROPS} />, {
+ context: { router: { replace } }
+ });
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().issues.length).toBe(4);
+
+ const instance = wrapper.instance() as App;
+ instance.handleIssueCheck('foo', eventNoShiftKey);
+ expect(wrapper.state().checked.length).toBe(1);
+
+ instance.handleIssueCheck('non-existing-key', eventWithShiftKey);
+ expect(wrapper.state().checked.length).toBe(1);
+});
+
+it('should be able to uncheck all issue with global checkbox', async () => {
+ const wrapper = shallowWithIntl(<App {...PROPS} />, {
+ context: { router: { replace } }
+ });
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().issues.length).toBe(4);
+
+ const instance = wrapper.instance() as App;
+ instance.handleIssueCheck('foo', eventNoShiftKey);
+ instance.handleIssueCheck('bar', eventNoShiftKey);
+ expect(wrapper.state().checked.length).toBe(2);
+
+ instance.onCheckAll(false);
+ expect(wrapper.state().checked.length).toBe(0);
+});
+
+it('should be able to check all issue with global checkbox', async () => {
+ const wrapper = shallowWithIntl(<App {...PROPS} />, {
+ context: { router: { replace } }
+ });
+
+ await waitAndUpdate(wrapper);
+
+ const instance = wrapper.instance() as App;
+ expect(wrapper.state().checked.length).toBe(0);
+ instance.onCheckAll(true);
+ expect(wrapper.state().checked.length).toBe(wrapper.state().issues.length);
+});
+++ /dev/null
-/*
- * 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 = (
- <svg height="24" viewBox="0 0 21 24" width="21">
- <path d="M3.845 12.9992l5.993 5.993.052.056c.049.061.093.122.129.191.082.159.121.339.111.518-.006.102-.028.203-.064.298-.149.39-.537.652-.954.644-.102-.002-.204-.019-.301-.052-.148-.05-.273-.135-.387-.241l-8.407-8.407 8.407-8.407.056-.052c.061-.048.121-.092.19-.128.116-.06.237-.091.366-.108.076-.004.075-.004.153-.003.155.015.3.052.437.129.088.051.169.115.239.19.246.266.33.656.214.999-.051.149-.135.273-.241.387l-5.983 5.984c5.287-.044 10.577-.206 15.859.013.073.009.091.009.163.027.187.047.359.15.49.292.075.081.136.175.18.276.044.101.072.209.081.319.032.391-.175.775-.521.962-.097.052-.202.089-.311.107-.073.012-.091.01-.165.013H3.845z" />
- </svg>
-);
-/* eslint-enable max-len */
-
-export default function BackButton(props: Props) {
- const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
- event.preventDefault();
- props.onClick();
- };
-
- return (
- <Tooltip overlay={translate('issues.return_to_list')}>
- <a
- className={classNames('concise-issues-list-header-button', props.className)}
- href="#"
- onClick={handleClick}>
- {icon}
- </a>
- </Tooltip>
- );
-}
* 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';
return (
<header className="layout-page-header-panel concise-issues-list-header">
<div className="layout-page-header-panel-inner concise-issues-list-header-inner">
- {displayBackButton && <BackButton className="pull-left" onClick={props.onBackClick} />}
+ {displayBackButton && (
+ <BackButton className="pull-left" disabled={props.loading} onClick={props.onBackClick} />
+ )}
{props.loading ? (
<i className="spinner pull-right" />
) : (
--- /dev/null
+/*
+ * 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<Props> {
+ handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ if (!this.props.disabled) {
+ this.props.onClick();
+ }
+ };
+
+ renderIcon = () => (
+ <svg height="24" viewBox="0 0 21 24" width="21">
+ <path
+ d="M3.845 12.9992l5.993 5.993.052.056c.049.061.093.122.129.191.082.159.121.339.111.518-.006.102-.028.203-.064.298-.149.39-.537.652-.954.644-.102-.002-.204-.019-.301-.052-.148-.05-.273-.135-.387-.241l-8.407-8.407 8.407-8.407.056-.052c.061-.048.121-.092.19-.128.116-.06.237-.091.366-.108.076-.004.075-.004.153-.003.155.015.3.052.437.129.088.051.169.115.239.19.246.266.33.656.214.999-.051.149-.135.273-.241.387l-5.983 5.984c5.287-.044 10.577-.206 15.859.013.073.009.091.009.163.027.187.047.359.15.49.292.075.081.136.175.18.276.044.101.072.209.081.319.032.391-.175.775-.521.962-.097.052-.202.089-.311.107-.073.012-.091.01-.165.013H3.845z"
+ fill={this.props.disabled ? theme.disableGrayText : theme.secondFontColor}
+ />
+ </svg>
+ );
+
+ render() {
+ const { tooltip = translate('issues.return_to_list') } = this.props;
+ return (
+ <Tooltip overlay={tooltip}>
+ <a
+ className={classNames(
+ 'link-no-underline',
+ { 'cursor-not-allowed': this.props.disabled },
+ this.props.className
+ )}
+ href="#"
+ onClick={this.handleClick}>
+ {this.renderIcon()}
+ </a>
+ </Tooltip>
+ );
+ }
+}
*/
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;
formatMeasure(props.total, 'INT', null)
)}
{props.loadMore != null && hasMore ? loadMoreLink : null}
+ {props.loading && (
+ <DeferredSpinner className="vertical-bottom spacer-left position-absolute" />
+ )}
</footer>
);
}
click(link);
expect(loadMore).toBeCalled();
});
+
+it('should display spinner while loading', () => {
+ expect(
+ shallow(<ListFooter count={3} loadMore={jest.fn()} loading={true} total={10} />)
+ .find('DeferredSpinner')
+ .exists()
+ ).toBe(true);
+});