Browse Source

SONAR-9178 Make spinner noticeable on loading projects issues or measure

tags/7.5
Pascal Mugnier 5 years ago
parent
commit
d0242b05cd
33 changed files with 276 additions and 204 deletions
  1. 4
    2
      server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js
  2. 2
    1
      server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js
  3. 0
    5
      server/sonar-web/src/main/js/apps/component-measures/components/PageActions.js
  4. 7
    11
      server/sonar-web/src/main/js/apps/component-measures/components/__tests__/PageActions-test.js
  5. 3
    54
      server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/PageActions-test.js.snap
  6. 37
    22
      server/sonar-web/src/main/js/apps/issues/components/App.tsx
  7. 1
    5
      server/sonar-web/src/main/js/apps/issues/components/PageActions.tsx
  8. 3
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/AssigneeFacet.tsx
  9. 3
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/AuthorFacet.tsx
  10. 3
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx
  11. 3
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/DirectoryFacet.tsx
  12. 3
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/FileFacet.tsx
  13. 3
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/LanguageFacet.tsx
  14. 3
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/ModuleFacet.tsx
  15. 3
    1
      server/sonar-web/src/main/js/apps/issues/sidebar/ProjectFacet.tsx
  16. 3
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx
  17. 3
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/RuleFacet.tsx
  18. 3
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx
  19. 18
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx
  20. 7
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx
  21. 3
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx
  22. 3
    1
      server/sonar-web/src/main/js/apps/issues/sidebar/TagFacet.tsx
  23. 3
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx
  24. 1
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/AssigneeFacet-test.tsx
  25. 1
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-test.tsx
  26. 3
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StandardFacet-test.tsx
  27. 16
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/AssigneeFacet-test.tsx.snap
  28. 16
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StandardFacet-test.tsx.snap
  29. 38
    32
      server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
  30. 0
    2
      server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx
  31. 75
    65
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap
  32. 0
    3
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
  33. 5
    0
      server/sonar-web/src/main/js/components/search-navigator.css

+ 4
- 2
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js View File

@@ -35,6 +35,7 @@ import { enhanceComponent, isFileType, isViewType } from '../utils';
import { getProjectUrl } from '../../../helpers/urls';
import { isDiffMetric } from '../../../helpers/measures';
import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
/*:: import type { Component, ComponentEnhanced, Paging, Period } from '../types'; */
/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */
/*:: import type { Metric } from '../../../store/metrics/actions'; */
@@ -319,7 +320,6 @@ export default class MeasureContent extends React.PureComponent {
)}
<PageActions
current={selectedIdx != null && view !== 'treemap' ? selectedIdx + 1 : null}
loading={this.props.loading}
isFile={isFile}
paging={this.state.paging}
totalLoadedComponents={this.state.components.length}
@@ -343,7 +343,9 @@ export default class MeasureContent extends React.PureComponent {
metric={metric}
secondaryMeasure={this.props.secondaryMeasure}
/>
{isFileType(component) ? this.renderCode() : this.renderMeasure()}
<DeferredSpinner loading={this.props.loading}>
{isFileType(component) ? this.renderCode() : this.renderMeasure()}
</DeferredSpinner>
</div>
)}
</div>

+ 2
- 1
server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js View File

@@ -28,6 +28,7 @@ import SourceViewer from '../../../components/SourceViewer/SourceViewer';
import { getComponentLeaves } from '../../../api/components';
import { enhanceComponent, getBubbleMetrics, isFileType } from '../utils';
import { getBranchLikeQuery } from '../../../helpers/branches';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
/*:: import type { Component, ComponentEnhanced, Paging, Period } from '../types'; */
/*:: import type { Metric } from '../../../store/metrics/actions'; */

@@ -163,7 +164,6 @@ export default class MeasureOverview extends React.PureComponent {
<PageActions
current={this.state.components.length}
isFile={isFile}
loading={this.props.loading}
paging={this.state.paging}
/>
</div>
@@ -175,6 +175,7 @@ export default class MeasureOverview extends React.PureComponent {
<LeakPeriodLegend className="pull-right" component={component} period={leakPeriod} />
)}
</div>
<DeferredSpinner loading={this.props.loading} />
{!this.props.loading && this.renderContent()}
</div>
</div>

+ 0
- 5
server/sonar-web/src/main/js/apps/component-measures/components/PageActions.js View File

@@ -20,13 +20,11 @@
// @flow
import React from 'react';
import FilesCounter from './FilesCounter';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
/*:: import type { Paging } from '../types'; */

/*:: type Props = {|
current: ?number,
loading: boolean,
isFile: ?boolean,
paging: ?Paging,
totalLoadedComponents?: number,
@@ -41,9 +39,6 @@ export default function PageActions(props /*: Props */) {
{!isFile && showShortcuts && renderShortcuts()}
{isFile && paging && renderFileShortcuts()}
<div className="measure-details-page-actions">
<DeferredSpinner loading={props.loading}>
<i className="spinner-placeholder" />
</DeferredSpinner>
{paging != null && (
<FilesCounter
className="spacer-left"

+ 7
- 11
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/PageActions-test.js View File

@@ -23,14 +23,12 @@ import PageActions from '../PageActions';

it('should display correctly for a project', () => {
expect(
shallow(<PageActions loading={true} isFile={false} view="list" totalLoadedComponents={20} />)
shallow(<PageActions isFile={false} totalLoadedComponents={20} view="list" />)
).toMatchSnapshot();
});

it('should display correctly for a file', () => {
const wrapper = shallow(
<PageActions loading={false} isFile={true} view="tree" totalLoadedComponents={10} />
);
const wrapper = shallow(<PageActions isFile={true} totalLoadedComponents={10} view="tree" />);
expect(wrapper).toMatchSnapshot();
wrapper.setProps({ paging: { total: 100 } });
expect(wrapper).toMatchSnapshot();
@@ -38,7 +36,7 @@ it('should display correctly for a file', () => {

it('should not display shortcuts for treemap', () => {
expect(
shallow(<PageActions loading={true} isFile={false} view="treemap" totalLoadedComponents={20} />)
shallow(<PageActions isFile={false} totalLoadedComponents={20} view="treemap" />)
).toMatchSnapshot();
});

@@ -47,11 +45,10 @@ it('should display the total of files', () => {
shallow(
<PageActions
current={12}
loading={true}
isFile={false}
view="treemap"
totalLoadedComponents={20}
paging={{ total: 120 }}
totalLoadedComponents={20}
view="treemap"
/>
)
).toMatchSnapshot();
@@ -59,11 +56,10 @@ it('should display the total of files', () => {
shallow(
<PageActions
current={12}
loading={false}
isFile={true}
view="list"
totalLoadedComponents={20}
paging={{ total: 120 }}
totalLoadedComponents={20}
view="list"
/>
)
).toMatchSnapshot();

+ 3
- 54
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/PageActions-test.js.snap View File

@@ -6,16 +6,7 @@ exports[`should display correctly for a file 1`] = `
>
<div
className="measure-details-page-actions"
>
<DeferredSpinner
loading={false}
timeout={100}
>
<i
className="spinner-placeholder"
/>
</DeferredSpinner>
</div>
/>
</div>
`;

@@ -43,14 +34,6 @@ exports[`should display correctly for a file 2`] = `
<div
className="measure-details-page-actions"
>
<DeferredSpinner
loading={false}
timeout={100}
>
<i
className="spinner-placeholder"
/>
</DeferredSpinner>
<FilesCounter
className="spacer-left"
total={10}
@@ -97,16 +80,7 @@ exports[`should display correctly for a project 1`] = `
</span>
<div
className="measure-details-page-actions"
>
<DeferredSpinner
loading={true}
timeout={100}
>
<i
className="spinner-placeholder"
/>
</DeferredSpinner>
</div>
/>
</div>
`;

@@ -117,14 +91,6 @@ exports[`should display the total of files 1`] = `
<div
className="measure-details-page-actions"
>
<DeferredSpinner
loading={true}
timeout={100}
>
<i
className="spinner-placeholder"
/>
</DeferredSpinner>
<FilesCounter
className="spacer-left"
current={12}
@@ -158,14 +124,6 @@ exports[`should display the total of files 2`] = `
<div
className="measure-details-page-actions"
>
<DeferredSpinner
loading={false}
timeout={100}
>
<i
className="spinner-placeholder"
/>
</DeferredSpinner>
<FilesCounter
className="spacer-left"
current={12}
@@ -181,15 +139,6 @@ exports[`should not display shortcuts for treemap 1`] = `
>
<div
className="measure-details-page-actions"
>
<DeferredSpinner
loading={true}
timeout={100}
>
<i
className="spinner-placeholder"
/>
</DeferredSpinner>
</div>
/>
</div>
`;

+ 37
- 22
server/sonar-web/src/main/js/apps/issues/components/App.tsx View File

@@ -20,7 +20,7 @@
import * as React from 'react';
import Helmet from 'react-helmet';
import * as key from 'keymaster';
import { keyBy, union, without } from 'lodash';
import { keyBy, omit, union, without } from 'lodash';
import * as PropTypes from 'prop-types';
import BulkChangeModal from './BulkChangeModal';
import ComponentBreadcrumbs from './ComponentBreadcrumbs';
@@ -75,6 +75,7 @@ import DropdownIcon from '../../../components/icons-components/DropdownIcon';
import { isSonarCloud } from '../../../helpers/system';
import '../../../components/search-navigator.css';
import '../styles.css';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface FetchIssuesPromise {
components: ReferencedComponent[];
@@ -104,6 +105,7 @@ export interface State {
issues: Issue[];
lastChecked?: string;
loading: boolean;
loadingFacets: { [key: string]: boolean };
locationsNavigator: boolean;
myIssues: boolean;
openFacets: { [facet: string]: boolean };
@@ -136,6 +138,7 @@ export default class App extends React.PureComponent<Props, State> {
facets: {},
issues: [],
loading: true,
loadingFacets: {},
locationsNavigator: false,
myIssues: props.myIssues || areMyIssuesSelected(props.location.query),
openFacets: { severities: true, types: true },
@@ -571,16 +574,21 @@ export default class App extends React.PureComponent<Props, State> {

fetchFacet = (facet: string) => {
const requestOrganizations = facet === 'projects';
this.setState(state => ({ loadingFacets: { ...state.loadingFacets, [facet]: true } }));
return this.fetchIssues({ ps: 1, facets: mapFacet(facet) }, false, requestOrganizations).then(
({ facets, ...other }) => {
if (this.mounted) {
this.setState(state => ({
facets: { ...state.facets, ...parseFacets(facets) },
loadingFacets: omit(state.loadingFacets, facet),
referencedComponents: {
...state.referencedComponents,
...keyBy(other.components, 'uuid')
},
referencedLanguages: { ...state.referencedLanguages, ...keyBy(other.languages, 'key') },
referencedLanguages: {
...state.referencedLanguages,
...keyBy(other.languages, 'key')
},
referencedRules: { ...state.referencedRules, ...keyBy(other.rules, 'key') },
referencedUsers: { ...state.referencedUsers, ...keyBy(other.users, 'login') }
}));
@@ -869,6 +877,7 @@ export default class App extends React.PureComponent<Props, State> {
component={component}
facets={this.state.facets}
loading={this.state.loading}
loadingFacets={this.state.loadingFacets}
myIssues={this.state.myIssues}
onFacetToggle={this.handleFacetToggle}
onFilterChange={this.handleFilterChange}
@@ -1004,6 +1013,31 @@ export default class App extends React.PureComponent<Props, State> {
);
}

renderPage() {
const { openIssue } = this.state;
return (
<div className="layout-page-main-inner">
<DeferredSpinner loading={this.state.loading}>
{openIssue ? (
<IssuesSourceViewer
branchLike={fillBranchLike(openIssue.branch, openIssue.pullRequest)}
loadIssues={this.fetchIssuesForComponent}
locationsNavigator={this.state.locationsNavigator}
onIssueChange={this.handleIssueChange}
onIssueSelect={this.openIssue}
onLocationSelect={this.selectLocation}
openIssue={openIssue}
selectedFlowIndex={this.state.selectedFlowIndex}
selectedLocationIndex={this.state.selectedLocationIndex}
/>
) : (
this.renderList()
)}
</DeferredSpinner>
</div>
);
}

render() {
const { component } = this.props;
const { openIssue, paging } = this.state;
@@ -1038,7 +1072,6 @@ export default class App extends React.PureComponent<Props, State> {
!this.props.component &&
(!isSonarCloud() || this.props.myIssues)
)}
loading={this.state.loading}
onReload={this.handleReload}
paging={paging}
selectedIndex={selectedIndex}
@@ -1049,25 +1082,7 @@ export default class App extends React.PureComponent<Props, State> {
</div>
</div>

<div className="layout-page-main-inner">
<div>
{openIssue ? (
<IssuesSourceViewer
branchLike={fillBranchLike(openIssue.branch, openIssue.pullRequest)}
loadIssues={this.fetchIssuesForComponent}
locationsNavigator={this.state.locationsNavigator}
onIssueChange={this.handleIssueChange}
onIssueSelect={this.openIssue}
onLocationSelect={this.selectLocation}
openIssue={openIssue}
selectedFlowIndex={this.state.selectedFlowIndex}
selectedLocationIndex={this.state.selectedLocationIndex}
/>
) : (
this.renderList()
)}
</div>
</div>
{this.renderPage()}
</div>
</div>
);

+ 1
- 5
server/sonar-web/src/main/js/apps/issues/components/PageActions.tsx View File

@@ -21,14 +21,12 @@ import * as React from 'react';
import IssuesCounter from './IssuesCounter';
import ReloadButton from './ReloadButton';
import { HomePageType, Paging } from '../../../app/types';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import HomePageSelect from '../../../components/controls/HomePageSelect';
import { translate } from '../../../helpers/l10n';
import { isSonarCloud } from '../../../helpers/system';

interface Props {
canSetHome: boolean;
loading: boolean;
onReload: () => void;
paging: Paging | undefined;
selectedIndex: number | undefined;
@@ -61,9 +59,7 @@ export default class PageActions extends React.PureComponent<Props> {
{this.renderShortcuts()}

<div className="issues-page-actions">
<DeferredSpinner className="issues-main-header-spinner" loading={this.props.loading}>
<ReloadButton onClick={this.props.onReload} />
</DeferredSpinner>
<ReloadButton onClick={this.props.onReload} />
{paging != null && (
<IssuesCounter className="spacer-left" current={selectedIndex} total={paging.total} />
)}

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/AssigneeFacet.tsx View File

@@ -28,11 +28,13 @@ import FacetItem from '../../../components/facet/FacetItem';
import FacetItemsList from '../../../components/facet/FacetItemsList';
import Avatar from '../../../components/ui/Avatar';
import { translate } from '../../../helpers/l10n';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

export interface Props {
assigned: boolean;
assignees: string[];
component: Component | undefined;
fetching: boolean;
loading?: boolean;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
@@ -201,6 +203,7 @@ export default class AssigneeFacet extends React.PureComponent<Props> {
values={this.getValues()}
/>

<DeferredSpinner loading={this.props.fetching} />
{this.props.open && this.renderList()}
{this.props.open && this.renderFooter()}
</FacetBox>

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/AuthorFacet.tsx View File

@@ -25,8 +25,10 @@ import FacetHeader from '../../../components/facet/FacetHeader';
import FacetItem from '../../../components/facet/FacetItem';
import FacetItemsList from '../../../components/facet/FacetItemsList';
import { translate } from '../../../helpers/l10n';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
fetching: boolean;
loading?: boolean;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
@@ -107,6 +109,7 @@ export default class AuthorFacet extends React.PureComponent<Props> {
values={this.props.authors}
/>

<DeferredSpinner loading={this.props.fetching} />
{this.props.open && this.renderList()}
</FacetBox>
);

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx View File

@@ -33,6 +33,7 @@ import DateRangeInput from '../../../components/controls/DateRangeInput';
import { isSameDay, parseDate } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
component: Component | undefined;
@@ -40,6 +41,7 @@ interface Props {
createdAt: string;
createdBefore: Date | undefined;
createdInLast: string;
fetching: boolean;
loading?: boolean;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
@@ -290,6 +292,7 @@ export default class CreationDateFacet extends React.PureComponent<Props> {
values={this.getValues()}
/>

<DeferredSpinner loading={this.props.fetching} />
{this.props.open && this.renderInner()}
</FacetBox>
);

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/DirectoryFacet.tsx View File

@@ -27,8 +27,10 @@ import FacetItemsList from '../../../components/facet/FacetItemsList';
import QualifierIcon from '../../../components/icons-components/QualifierIcon';
import { translate } from '../../../helpers/l10n';
import { collapsePath } from '../../../helpers/path';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
fetching: boolean;
directories: string[];
loading?: boolean;
onChange: (changes: Partial<Query>) => void;
@@ -125,6 +127,7 @@ export default class DirectoryFacet extends React.PureComponent<Props> {
values={values}
/>

<DeferredSpinner loading={this.props.fetching} />
{this.props.open && this.renderList()}
</FacetBox>
);

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/FileFacet.tsx View File

@@ -27,8 +27,10 @@ import FacetItemsList from '../../../components/facet/FacetItemsList';
import QualifierIcon from '../../../components/icons-components/QualifierIcon';
import { translate } from '../../../helpers/l10n';
import { collapsePath } from '../../../helpers/path';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
fetching: boolean;
files: string[];
loading?: boolean;
onChange: (changes: Partial<Query>) => void;
@@ -126,6 +128,7 @@ export default class FileFacet extends React.PureComponent<Props> {
values={values}
/>

<DeferredSpinner loading={this.props.fetching} />
{this.props.open && this.renderList()}
</FacetBox>
);

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/LanguageFacet.tsx View File

@@ -26,8 +26,10 @@ import FacetHeader from '../../../components/facet/FacetHeader';
import FacetItem from '../../../components/facet/FacetItem';
import FacetItemsList from '../../../components/facet/FacetItemsList';
import { translate } from '../../../helpers/l10n';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
fetching: boolean;
languages: string[];
loading?: boolean;
onChange: (changes: Partial<Query>) => void;
@@ -130,6 +132,7 @@ export default class LanguageFacet extends React.PureComponent<Props> {
values={values}
/>

<DeferredSpinner loading={this.props.fetching} />
{this.props.open && this.renderList()}
{this.props.open && this.renderFooter()}
</FacetBox>

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/ModuleFacet.tsx View File

@@ -26,8 +26,10 @@ import FacetItem from '../../../components/facet/FacetItem';
import FacetItemsList from '../../../components/facet/FacetItemsList';
import QualifierIcon from '../../../components/icons-components/QualifierIcon';
import { translate } from '../../../helpers/l10n';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
fetching: boolean;
loading?: boolean;
modules: string[];
onChange: (changes: Partial<Query>) => void;
@@ -124,6 +126,7 @@ export default class ModuleFacet extends React.PureComponent<Props> {
values={values}
/>

<DeferredSpinner loading={this.props.fetching} />
{this.props.open && this.renderList()}
</FacetBox>
);

+ 3
- 1
server/sonar-web/src/main/js/apps/issues/sidebar/ProjectFacet.tsx View File

@@ -30,10 +30,12 @@ import FacetFooter from '../../../components/facet/FacetFooter';
import Organization from '../../../components/shared/Organization';
import QualifierIcon from '../../../components/icons-components/QualifierIcon';
import { translate } from '../../../helpers/l10n';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
component: Component | undefined;
loading?: boolean;
fetching: boolean;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
open: boolean;
@@ -192,7 +194,7 @@ export default class ProjectFacet extends React.PureComponent<Props> {
open={this.props.open}
values={values}
/>
<DeferredSpinner loading={this.props.fetching} />
{this.props.open && this.renderList()}
{this.props.open && this.renderFooter()}
</FacetBox>

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx View File

@@ -25,8 +25,10 @@ import FacetHeader from '../../../components/facet/FacetHeader';
import FacetItem from '../../../components/facet/FacetItem';
import FacetItemsList from '../../../components/facet/FacetItemsList';
import { translate } from '../../../helpers/l10n';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
fetching: boolean;
loading?: boolean;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
@@ -124,6 +126,7 @@ export default class ResolutionFacet extends React.PureComponent<Props> {
values={values}
/>

<DeferredSpinner loading={this.props.fetching} />
{this.props.open && <FacetItemsList>{resolutions.map(this.renderItem)}</FacetItemsList>}
</FacetBox>
);

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/RuleFacet.tsx View File

@@ -27,8 +27,10 @@ import FacetItem from '../../../components/facet/FacetItem';
import FacetItemsList from '../../../components/facet/FacetItemsList';
import FacetFooter from '../../../components/facet/FacetFooter';
import { translate } from '../../../helpers/l10n';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
fetching: boolean;
languages: string[];
loading?: boolean;
onChange: (changes: Partial<Query>) => void;
@@ -144,6 +146,7 @@ export default class RuleFacet extends React.PureComponent<Props> {
values={values}
/>

<DeferredSpinner loading={this.props.fetching} />
{this.props.open && this.renderList()}
{this.props.open && this.renderFooter()}
</FacetBox>

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx View File

@@ -26,8 +26,10 @@ import FacetItem from '../../../components/facet/FacetItem';
import FacetItemsList from '../../../components/facet/FacetItemsList';
import SeverityHelper from '../../../components/shared/SeverityHelper';
import { translate } from '../../../helpers/l10n';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
fetching: boolean;
loading?: boolean;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
@@ -104,6 +106,7 @@ export default class SeverityFacet extends React.PureComponent<Props> {
values={values}
/>

<DeferredSpinner loading={this.props.fetching} />
{this.props.open && <FacetItemsList>{severities.map(this.renderItem)}</FacetItemsList>}
</FacetBox>
);

+ 18
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx View File

@@ -47,6 +47,7 @@ export interface Props {
component: Component | undefined;
facets: { [facet: string]: Facet };
loading?: boolean;
loadingFacets: { [key: string]: boolean };
myIssues: boolean;
onFacetToggle: (property: string) => void;
onFilterChange: (changes: Partial<Query>) => void;
@@ -77,6 +78,7 @@ export default class Sidebar extends React.PureComponent<Props> {
return (
<div className="search-navigator-facets-list">
<TypeFacet
fetching={this.props.loadingFacets.types === true}
loading={this.props.loading}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
@@ -85,6 +87,7 @@ export default class Sidebar extends React.PureComponent<Props> {
types={query.types}
/>
<SeverityFacet
fetching={this.props.loadingFacets.severities === true}
loading={this.props.loading}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
@@ -93,6 +96,7 @@ export default class Sidebar extends React.PureComponent<Props> {
stats={facets.severities}
/>
<ResolutionFacet
fetching={this.props.loadingFacets.resolutions === true}
loading={this.props.loading}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
@@ -102,6 +106,7 @@ export default class Sidebar extends React.PureComponent<Props> {
stats={facets.resolutions}
/>
<StatusFacet
fetching={this.props.loadingFacets.statuses === true}
loading={this.props.loading}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
@@ -115,6 +120,7 @@ export default class Sidebar extends React.PureComponent<Props> {
createdAt={query.createdAt}
createdBefore={query.createdBefore}
createdInLast={query.createdInLast}
fetching={this.props.loadingFacets.createdAt === true}
loading={this.props.loading}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
@@ -123,6 +129,7 @@ export default class Sidebar extends React.PureComponent<Props> {
stats={facets.createdAt}
/>
<LanguageFacet
fetching={this.props.loadingFacets.languages === true}
languages={query.languages}
loading={this.props.loading}
onChange={this.props.onFilterChange}
@@ -132,6 +139,7 @@ export default class Sidebar extends React.PureComponent<Props> {
stats={facets.languages}
/>
<RuleFacet
fetching={this.props.loadingFacets.rules === true}
languages={query.languages}
loading={this.props.loading}
onChange={this.props.onFilterChange}
@@ -146,6 +154,9 @@ export default class Sidebar extends React.PureComponent<Props> {
cwe={query.cwe}
cweOpen={!!openFacets.cwe}
cweStats={facets.cwe}
fetchingCwe={this.props.loadingFacets.cwe === true}
fetchingOwaspTop10={this.props.loadingFacets.owaspTop10 === true}
fetchingSansTop25={this.props.loadingFacets.sansTop25 === true}
loading={this.props.loading}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
@@ -159,6 +170,7 @@ export default class Sidebar extends React.PureComponent<Props> {
/>
<TagFacet
component={component}
fetching={this.props.loadingFacets.tags === true}
loading={this.props.loading}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
@@ -170,6 +182,7 @@ export default class Sidebar extends React.PureComponent<Props> {
{displayProjectsFacet && (
<ProjectFacet
component={component}
fetching={this.props.loadingFacets.projects === true}
loading={this.props.loading}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
@@ -182,6 +195,7 @@ export default class Sidebar extends React.PureComponent<Props> {
)}
{displayModulesFacet && (
<ModuleFacet
fetching={this.props.loadingFacets.modules === true}
loading={this.props.loading}
modules={query.modules}
onChange={this.props.onFilterChange}
@@ -194,6 +208,7 @@ export default class Sidebar extends React.PureComponent<Props> {
{displayDirectoriesFacet && (
<DirectoryFacet
directories={query.directories}
fetching={this.props.loadingFacets.directories === true}
loading={this.props.loading}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
@@ -204,6 +219,7 @@ export default class Sidebar extends React.PureComponent<Props> {
)}
{displayFilesFacet && (
<FileFacet
fetching={this.props.loadingFacets.files === true}
files={query.files}
loading={this.props.loading}
onChange={this.props.onFilterChange}
@@ -218,6 +234,7 @@ export default class Sidebar extends React.PureComponent<Props> {
assigned={query.assigned}
assignees={query.assignees}
component={component}
fetching={this.props.loadingFacets.assignees === true}
loading={this.props.loading}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
@@ -230,6 +247,7 @@ export default class Sidebar extends React.PureComponent<Props> {
{displayAuthorFacet && (
<AuthorFacet
authors={query.authors}
fetching={this.props.loadingFacets.authors === true}
loading={this.props.loading}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}

+ 7
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx View File

@@ -32,11 +32,15 @@ import {
renderCWECategory,
Standards
} from '../../securityReports/utils';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

export interface Props {
cwe: string[];
cweOpen: boolean;
cweStats: { [x: string]: number } | undefined;
fetchingOwaspTop10: boolean;
fetchingSansTop25: boolean;
fetchingCwe: boolean;
loading?: boolean;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
@@ -260,6 +264,7 @@ export default class StandardFacet extends React.PureComponent<Props, State> {
renderOwaspTop10Category(this.state.standards, item)
)}
/>
<DeferredSpinner loading={this.props.fetchingOwaspTop10} />
{this.props.owaspTop10Open && this.renderOwaspTop10List()}
</FacetBox>
<FacetBox className="is-inner" property="sansTop25">
@@ -271,6 +276,7 @@ export default class StandardFacet extends React.PureComponent<Props, State> {
renderSansTop25Category(this.state.standards, item)
)}
/>
<DeferredSpinner loading={this.props.fetchingSansTop25} />
{this.props.sansTop25Open && this.renderSansTop25List()}
</FacetBox>
<FacetBox className="is-inner" property="cwe">
@@ -280,6 +286,7 @@ export default class StandardFacet extends React.PureComponent<Props, State> {
open={this.props.cweOpen}
values={this.props.cwe.map(item => renderCWECategory(this.state.standards, item))}
/>
<DeferredSpinner loading={this.props.fetchingCwe} />
{this.props.cweOpen && this.renderCWEList()}
{this.props.cweOpen && this.renderCWESearch()}
</FacetBox>

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx View File

@@ -26,8 +26,10 @@ import FacetItem from '../../../components/facet/FacetItem';
import FacetItemsList from '../../../components/facet/FacetItemsList';
import StatusHelper from '../../../components/shared/StatusHelper';
import { translate } from '../../../helpers/l10n';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
fetching: boolean;
loading?: boolean;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
@@ -104,6 +106,7 @@ export default class StatusFacet extends React.PureComponent<Props> {
values={values}
/>

<DeferredSpinner loading={this.props.fetching} />
{this.props.open && <FacetItemsList>{statuses.map(this.renderItem)}</FacetItemsList>}
</FacetBox>
);

+ 3
- 1
server/sonar-web/src/main/js/apps/issues/sidebar/TagFacet.tsx View File

@@ -30,9 +30,11 @@ import FacetItem from '../../../components/facet/FacetItem';
import FacetItemsList from '../../../components/facet/FacetItemsList';
import TagsIcon from '../../../components/icons-components/TagsIcon';
import { translate } from '../../../helpers/l10n';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
component: Component | undefined;
fetching: boolean;
loading?: boolean;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
@@ -143,8 +145,8 @@ export default class TagFacet extends React.PureComponent<Props> {
values={this.props.tags}
/>

<DeferredSpinner loading={this.props.fetching} />
{this.props.open && this.renderList()}

{this.props.open && this.renderFooter()}
</FacetBox>
);

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx View File

@@ -26,8 +26,10 @@ import FacetItem from '../../../components/facet/FacetItem';
import FacetItemsList from '../../../components/facet/FacetItemsList';
import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
import { translate } from '../../../helpers/l10n';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

interface Props {
fetching: boolean;
loading?: boolean;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
@@ -118,6 +120,7 @@ export default class TypeFacet extends React.PureComponent<Props> {
values={values}
/>

<DeferredSpinner loading={this.props.fetching} />
{this.props.open && <FacetItemsList>{types.map(this.renderItem)}</FacetItemsList>}
</FacetBox>
);

+ 1
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/AssigneeFacet-test.tsx View File

@@ -29,6 +29,7 @@ const renderAssigneeFacet = (props?: Partial<Props>) =>
assigned={true}
assignees={[]}
component={undefined}
fetching={false}
onChange={jest.fn()}
onToggle={jest.fn()}
open={true}

+ 1
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-test.tsx View File

@@ -29,6 +29,7 @@ const renderSidebar = (props?: Partial<Props>) =>
<Sidebar
component={undefined}
facets={{}}
loadingFacets={{}}
myIssues={false}
onFacetToggle={jest.fn()}
onFilterChange={jest.fn()}

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StandardFacet-test.tsx View File

@@ -153,6 +153,9 @@ function shallowRender(props: Partial<Props> = {}) {
cwe={[]}
cweOpen={false}
cweStats={{}}
fetchingCwe={false}
fetchingOwaspTop10={false}
fetchingSansTop25={false}
onChange={jest.fn()}
onToggle={jest.fn()}
open={false}

+ 16
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/AssigneeFacet-test.tsx.snap View File

@@ -11,6 +11,10 @@ exports[`should render 1`] = `
open={true}
values={Array []}
/>
<DeferredSpinner
loading={false}
timeout={100}
/>
<FacetItemsList>
<FacetItem
active={false}
@@ -101,6 +105,10 @@ exports[`should render without stats 1`] = `
open={true}
values={Array []}
/>
<DeferredSpinner
loading={false}
timeout={100}
/>
</FacetBox>
`;

@@ -119,6 +127,10 @@ exports[`should select unassigned 1`] = `
]
}
/>
<DeferredSpinner
loading={false}
timeout={100}
/>
<FacetItemsList>
<FacetItem
active={true}
@@ -201,6 +213,10 @@ exports[`should select user 1`] = `
]
}
/>
<DeferredSpinner
loading={false}
timeout={100}
/>
<FacetItemsList>
<FacetItem
active={false}

+ 16
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StandardFacet-test.tsx.snap View File

@@ -25,6 +25,10 @@ exports[`should render empty sub-facet 1`] = `
open={true}
values={Array []}
/>
<DeferredSpinner
loading={false}
timeout={100}
/>
<div
className="search-navigator-facet-empty little-spacer-top"
>
@@ -65,6 +69,10 @@ exports[`should render sub-facets 1`] = `
]
}
/>
<DeferredSpinner
loading={false}
timeout={100}
/>
<FacetItemsList>
<FacetItem
active={false}
@@ -106,6 +114,10 @@ exports[`should render sub-facets 1`] = `
]
}
/>
<DeferredSpinner
loading={false}
timeout={100}
/>
<FacetItemsList>
<FacetItem
active={false}
@@ -147,6 +159,10 @@ exports[`should render sub-facets 1`] = `
]
}
/>
<DeferredSpinner
loading={false}
timeout={100}
/>
<FacetItemsList>
<FacetItem
active={true}

+ 38
- 32
server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx View File

@@ -39,6 +39,7 @@ import { parseUrlQuery, Query, hasFilterParams, hasVisualizationParams } from '.
import { isSonarCloud } from '../../../helpers/system';
import '../../../components/search-navigator.css';
import '../styles.css';
import DeferredSpinner from '../../../components/common/DeferredSpinner';

export interface Props {
currentUser: CurrentUser;
@@ -277,40 +278,45 @@ export default class AllProjects extends React.PureComponent<Props, State> {
</div>
);

renderMain = () =>
this.getView() === 'visualizations' ? (
<div className="layout-page-main-inner">
{this.state.projects && (
<Visualizations
displayOrganizations={!this.props.organization && this.props.organizationsEnabled}
projects={this.state.projects}
sort={this.state.query.sort}
total={this.state.total}
visualization={this.getVisualization()}
/>
)}
</div>
) : (
<div className="layout-page-main-inner">
{this.state.projects && (
<ProjectsList
cardType={this.getView()}
currentUser={this.props.currentUser}
isFavorite={this.props.isFavorite}
isFiltered={hasFilterParams(this.state.query)}
organization={this.props.organization}
projects={this.state.projects}
query={this.state.query}
/>
renderMain = () => {
return (
<DeferredSpinner loading={this.state.loading}>
{this.getView() === 'visualizations' ? (
<div className="layout-page-main-inner">
{this.state.projects && (
<Visualizations
displayOrganizations={!this.props.organization && this.props.organizationsEnabled}
projects={this.state.projects}
sort={this.state.query.sort}
total={this.state.total}
visualization={this.getVisualization()}
/>
)}
</div>
) : (
<div className="layout-page-main-inner">
{this.state.projects && (
<ProjectsList
cardType={this.getView()}
currentUser={this.props.currentUser}
isFavorite={this.props.isFavorite}
isFiltered={hasFilterParams(this.state.query)}
organization={this.props.organization}
projects={this.state.projects}
query={this.state.query}
/>
)}
<ListFooter
count={this.state.projects !== undefined ? this.state.projects.length : 0}
loadMore={this.fetchMoreProjects}
ready={!this.state.loading}
total={this.state.total !== undefined ? this.state.total : 0}
/>
</div>
)}
<ListFooter
count={this.state.projects !== undefined ? this.state.projects.length : 0}
loadMore={this.fetchMoreProjects}
ready={!this.state.loading}
total={this.state.total !== undefined ? this.state.total : 0}
/>
</div>
</DeferredSpinner>
);
};

render() {
return (

+ 0
- 2
server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx View File

@@ -93,8 +93,6 @@ export default function PageHeader(props: Props) {
className={classNames('projects-topbar-item', 'is-last', {
'is-loading': loading
})}>
{loading && <i className="spinner spacer-right" />}

{total != null && (
<span>
<strong id="projects-total">{total}</strong> {translate('projects._projects')}

+ 75
- 65
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap View File

@@ -79,58 +79,63 @@ exports[`renders 1`] = `
</div>
</div>
</div>
<div
className="layout-page-main-inner"
<DeferredSpinner
loading={false}
timeout={100}
>
<ProjectsList
cardType="overall"
currentUser={
Object {
"isLoggedIn": true,
<div
className="layout-page-main-inner"
>
<ProjectsList
cardType="overall"
currentUser={
Object {
"isLoggedIn": true,
}
}
}
isFavorite={false}
isFiltered={false}
projects={
Array [
isFavorite={false}
isFiltered={false}
projects={
Array [
Object {
"key": "foo",
"measures": Object {},
"name": "Foo",
},
]
}
query={
Object {
"key": "foo",
"measures": Object {},
"name": "Foo",
},
]
}
query={
Object {
"coverage": undefined,
"duplications": undefined,
"gate": undefined,
"languages": undefined,
"maintainability": undefined,
"new_coverage": undefined,
"new_duplications": undefined,
"new_lines": undefined,
"new_maintainability": undefined,
"new_reliability": undefined,
"new_security": undefined,
"reliability": undefined,
"search": undefined,
"security": undefined,
"size": undefined,
"sort": undefined,
"tags": undefined,
"view": undefined,
"visualization": undefined,
"coverage": undefined,
"duplications": undefined,
"gate": undefined,
"languages": undefined,
"maintainability": undefined,
"new_coverage": undefined,
"new_duplications": undefined,
"new_lines": undefined,
"new_maintainability": undefined,
"new_reliability": undefined,
"new_security": undefined,
"reliability": undefined,
"search": undefined,
"security": undefined,
"size": undefined,
"sort": undefined,
"tags": undefined,
"view": undefined,
"visualization": undefined,
}
}
}
/>
<ListFooter
count={1}
loadMore={[Function]}
ready={true}
total={0}
/>
</div>
/>
<ListFooter
count={1}
loadMore={[Function]}
ready={true}
total={0}
/>
</div>
</DeferredSpinner>
</div>
</div>
`;
@@ -196,24 +201,29 @@ exports[`renders 2`] = `
</div>
</div>
</div>
<div
className="layout-page-main-inner"
<DeferredSpinner
loading={false}
timeout={100}
>
<Visualizations
displayOrganizations={false}
projects={
Array [
Object {
"key": "foo",
"measures": Object {},
"name": "Foo",
},
]
}
total={0}
visualization="risk"
/>
</div>
<div
className="layout-page-main-inner"
>
<Visualizations
displayOrganizations={false}
projects={
Array [
Object {
"key": "foo",
"measures": Object {},
"name": "Foo",
},
]
}
total={0}
visualization="risk"
/>
</div>
</DeferredSpinner>
</div>
</div>
`;

+ 0
- 3
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap View File

@@ -75,9 +75,6 @@ exports[`should render correctly while loading 1`] = `
<div
className="projects-topbar-item is-last is-loading"
>
<i
className="spinner spacer-right"
/>
<span>
<strong
id="projects-total"

+ 5
- 0
server/sonar-web/src/main/js/components/search-navigator.css View File

@@ -85,6 +85,11 @@
color: var(--secondFontColor);
}

.search-navigator-facet-box > .spinner {
float: right;
margin-top: -24px;
}

.search-navigator-facet {
position: relative;
display: inline-block;

Loading…
Cancel
Save