@@ -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> |
@@ -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> |
@@ -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" |
@@ -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(); |
@@ -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> | |||
`; |
@@ -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> | |||
); |
@@ -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} /> | |||
)} |
@@ -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> |
@@ -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> | |||
); |
@@ -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> | |||
); |
@@ -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> | |||
); |
@@ -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> | |||
); |
@@ -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> |
@@ -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> | |||
); |
@@ -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> |
@@ -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> | |||
); |
@@ -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> |
@@ -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> | |||
); |
@@ -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} |
@@ -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> |
@@ -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> | |||
); |
@@ -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> | |||
); |
@@ -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> | |||
); |
@@ -29,6 +29,7 @@ const renderAssigneeFacet = (props?: Partial<Props>) => | |||
assigned={true} | |||
assignees={[]} | |||
component={undefined} | |||
fetching={false} | |||
onChange={jest.fn()} | |||
onToggle={jest.fn()} | |||
open={true} |
@@ -29,6 +29,7 @@ const renderSidebar = (props?: Partial<Props>) => | |||
<Sidebar | |||
component={undefined} | |||
facets={{}} | |||
loadingFacets={{}} | |||
myIssues={false} | |||
onFacetToggle={jest.fn()} | |||
onFilterChange={jest.fn()} |
@@ -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} |
@@ -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} |
@@ -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} |
@@ -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 ( |
@@ -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')} |
@@ -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> | |||
`; |
@@ -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" |
@@ -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; |