extractStatusConditionsFromApplicationStatusChildProject, | extractStatusConditionsFromApplicationStatusChildProject, | ||||
extractStatusConditionsFromProjectStatus | extractStatusConditionsFromProjectStatus | ||||
} from '../../../helpers/qualityGates'; | } from '../../../helpers/qualityGates'; | ||||
import { ProjectAlmBindingResponse } from '../../../types/alm-settings'; | |||||
import { ApplicationPeriod } from '../../../types/application'; | import { ApplicationPeriod } from '../../../types/application'; | ||||
import { Branch, BranchLike } from '../../../types/branch-like'; | import { Branch, BranchLike } from '../../../types/branch-like'; | ||||
import { ComponentQualifier } from '../../../types/component'; | import { ComponentQualifier } from '../../../types/component'; | ||||
interface Props { | interface Props { | ||||
branch?: Branch; | branch?: Branch; | ||||
branchesEnabled?: boolean; | |||||
component: T.Component; | component: T.Component; | ||||
projectBinding?: ProjectAlmBindingResponse; | |||||
} | } | ||||
interface State { | interface State { | ||||
analyses?: T.Analysis[]; | analyses?: T.Analysis[]; | ||||
appLeak?: ApplicationPeriod; | appLeak?: ApplicationPeriod; | ||||
detectedCIOnLastAnalysis?: boolean; | |||||
graph: GraphType; | graph: GraphType; | ||||
loadingHistory?: boolean; | loadingHistory?: boolean; | ||||
loadingStatus?: boolean; | loadingStatus?: boolean; | ||||
} | } | ||||
export const BRANCH_OVERVIEW_ACTIVITY_GRAPH = 'sonar_branch_overview.graph'; | export const BRANCH_OVERVIEW_ACTIVITY_GRAPH = 'sonar_branch_overview.graph'; | ||||
export const NO_CI_DETECTED = 'undetected'; | |||||
// Get all history data over the past year. | // Get all history data over the past year. | ||||
const FROM_DATE = toNotSoISOString(new Date().setFullYear(new Date().getFullYear() - 1)); | const FROM_DATE = toNotSoISOString(new Date().setFullYear(new Date().getFullYear() - 1)); | ||||
({ analyses }) => { | ({ analyses }) => { | ||||
if (this.mounted) { | if (this.mounted) { | ||||
this.setState({ | this.setState({ | ||||
detectedCIOnLastAnalysis: | |||||
analyses.length > 0 | |||||
? analyses[0].detectedCI !== undefined && analyses[0].detectedCI !== NO_CI_DETECTED | |||||
: undefined, | |||||
analyses | analyses | ||||
}); | }); | ||||
} | } | ||||
}; | }; | ||||
render() { | render() { | ||||
const { branch, component } = this.props; | |||||
const { branch, branchesEnabled, component, projectBinding } = this.props; | |||||
const { | const { | ||||
analyses, | analyses, | ||||
appLeak, | appLeak, | ||||
detectedCIOnLastAnalysis, | |||||
graph, | graph, | ||||
loadingStatus, | loadingStatus, | ||||
loadingHistory, | loadingHistory, | ||||
analyses={analyses} | analyses={analyses} | ||||
appLeak={appLeak} | appLeak={appLeak} | ||||
branch={branch} | branch={branch} | ||||
branchesEnabled={branchesEnabled} | |||||
component={component} | component={component} | ||||
detectedCIOnLastAnalysis={detectedCIOnLastAnalysis} | |||||
graph={graph} | graph={graph} | ||||
loadingHistory={loadingHistory} | loadingHistory={loadingHistory} | ||||
loadingStatus={loadingStatus} | loadingStatus={loadingStatus} | ||||
metrics={metrics} | metrics={metrics} | ||||
onGraphChange={this.handleGraphChange} | onGraphChange={this.handleGraphChange} | ||||
period={period} | period={period} | ||||
projectBinding={projectBinding} | |||||
projectIsEmpty={projectIsEmpty} | projectIsEmpty={projectIsEmpty} | ||||
qgStatuses={qgStatuses} | qgStatuses={qgStatuses} | ||||
/> | /> |
import * as React from 'react'; | import * as React from 'react'; | ||||
import { parseDate } from 'sonar-ui-common/helpers/dates'; | import { parseDate } from 'sonar-ui-common/helpers/dates'; | ||||
import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; | import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; | ||||
import { ProjectAlmBindingResponse } from '../../../types/alm-settings'; | |||||
import { ApplicationPeriod } from '../../../types/application'; | import { ApplicationPeriod } from '../../../types/application'; | ||||
import { Branch } from '../../../types/branch-like'; | import { Branch } from '../../../types/branch-like'; | ||||
import { ComponentQualifier } from '../../../types/component'; | import { ComponentQualifier } from '../../../types/component'; | ||||
import { GraphType, MeasureHistory } from '../../../types/project-activity'; | import { GraphType, MeasureHistory } from '../../../types/project-activity'; | ||||
import { QualityGateStatus } from '../../../types/quality-gates'; | import { QualityGateStatus } from '../../../types/quality-gates'; | ||||
import ActivityPanel from './ActivityPanel'; | import ActivityPanel from './ActivityPanel'; | ||||
import FirstAnalysisNextStepsNotif from './FirstAnalysisNextStepsNotif'; | |||||
import { MeasuresPanel } from './MeasuresPanel'; | import { MeasuresPanel } from './MeasuresPanel'; | ||||
import NoCodeWarning from './NoCodeWarning'; | import NoCodeWarning from './NoCodeWarning'; | ||||
import QualityGatePanel from './QualityGatePanel'; | import QualityGatePanel from './QualityGatePanel'; | ||||
analyses?: T.Analysis[]; | analyses?: T.Analysis[]; | ||||
appLeak?: ApplicationPeriod; | appLeak?: ApplicationPeriod; | ||||
branch?: Branch; | branch?: Branch; | ||||
branchesEnabled?: boolean; | |||||
component: T.Component; | component: T.Component; | ||||
detectedCIOnLastAnalysis?: boolean; | |||||
graph?: GraphType; | graph?: GraphType; | ||||
loadingHistory?: boolean; | loadingHistory?: boolean; | ||||
loadingStatus?: boolean; | loadingStatus?: boolean; | ||||
metrics?: T.Metric[]; | metrics?: T.Metric[]; | ||||
onGraphChange: (graph: GraphType) => void; | onGraphChange: (graph: GraphType) => void; | ||||
period?: T.Period; | period?: T.Period; | ||||
projectBinding?: ProjectAlmBindingResponse; | |||||
projectIsEmpty?: boolean; | projectIsEmpty?: boolean; | ||||
qgStatuses?: QualityGateStatus[]; | qgStatuses?: QualityGateStatus[]; | ||||
} | } | ||||
analyses, | analyses, | ||||
appLeak, | appLeak, | ||||
branch, | branch, | ||||
branchesEnabled, | |||||
component, | component, | ||||
detectedCIOnLastAnalysis, | |||||
graph, | graph, | ||||
loadingHistory, | loadingHistory, | ||||
loadingStatus, | loadingStatus, | ||||
metrics = [], | metrics = [], | ||||
onGraphChange, | onGraphChange, | ||||
period, | period, | ||||
projectBinding, | |||||
projectIsEmpty, | projectIsEmpty, | ||||
qgStatuses | qgStatuses | ||||
} = props; | } = props; | ||||
const leakPeriod = component.qualifier === ComponentQualifier.Application ? appLeak : period; | const leakPeriod = component.qualifier === ComponentQualifier.Application ? appLeak : period; | ||||
return ( | return ( | ||||
<div className="page page-limited"> | |||||
<div className="overview"> | |||||
<A11ySkipTarget anchor="overview_main" /> | |||||
<> | |||||
<FirstAnalysisNextStepsNotif | |||||
component={component} | |||||
branchesEnabled={branchesEnabled} | |||||
detectedCIOnLastAnalysis={detectedCIOnLastAnalysis} | |||||
projectBinding={projectBinding} | |||||
/> | |||||
<div className="page page-limited"> | |||||
<div className="overview"> | |||||
<A11ySkipTarget anchor="overview_main" /> | |||||
{projectIsEmpty ? ( | |||||
<NoCodeWarning branchLike={branch} component={component} measures={measures} /> | |||||
) : ( | |||||
<div className="display-flex-row"> | |||||
<div className="width-25 big-spacer-right"> | |||||
<QualityGatePanel | |||||
component={component} | |||||
loading={loadingStatus} | |||||
qgStatuses={qgStatuses} | |||||
/> | |||||
</div> | |||||
<div className="flex-1"> | |||||
<div className="display-flex-column"> | |||||
<MeasuresPanel | |||||
appLeak={appLeak} | |||||
branch={branch} | |||||
{projectIsEmpty ? ( | |||||
<NoCodeWarning branchLike={branch} component={component} measures={measures} /> | |||||
) : ( | |||||
<div className="display-flex-row"> | |||||
<div className="width-25 big-spacer-right"> | |||||
<QualityGatePanel | |||||
component={component} | component={component} | ||||
loading={loadingStatus} | loading={loadingStatus} | ||||
measures={measures} | |||||
period={period} | |||||
qgStatuses={qgStatuses} | |||||
/> | /> | ||||
</div> | |||||
<ActivityPanel | |||||
analyses={analyses} | |||||
branchLike={branch} | |||||
component={component} | |||||
graph={graph} | |||||
leakPeriodDate={leakPeriod && parseDate(leakPeriod.date)} | |||||
loading={loadingHistory} | |||||
measuresHistory={measuresHistory} | |||||
metrics={metrics} | |||||
onGraphChange={onGraphChange} | |||||
/> | |||||
<div className="flex-1"> | |||||
<div className="display-flex-column"> | |||||
<MeasuresPanel | |||||
appLeak={appLeak} | |||||
branch={branch} | |||||
component={component} | |||||
loading={loadingStatus} | |||||
measures={measures} | |||||
period={period} | |||||
/> | |||||
<ActivityPanel | |||||
analyses={analyses} | |||||
branchLike={branch} | |||||
component={component} | |||||
graph={graph} | |||||
leakPeriodDate={leakPeriod && parseDate(leakPeriod.date)} | |||||
loading={loadingHistory} | |||||
measuresHistory={measuresHistory} | |||||
metrics={metrics} | |||||
onGraphChange={onGraphChange} | |||||
/> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | |||||
)} | |||||
)} | |||||
</div> | |||||
</div> | </div> | ||||
</div> | |||||
</> | |||||
); | ); | ||||
} | } | ||||
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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 { FormattedMessage } from 'react-intl'; | |||||
import { Link } from 'react-router'; | |||||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||||
import { withCurrentUser } from '../../../components/hoc/withCurrentUser'; | |||||
import DismissableAlert from '../../../components/ui/DismissableAlert'; | |||||
import { isLoggedIn } from '../../../helpers/users'; | |||||
import { ProjectAlmBindingResponse } from '../../../types/alm-settings'; | |||||
import { PULL_REQUEST_DECORATION_BINDING_CATEGORY } from '../../settings/components/AdditionalCategoryKeys'; | |||||
export interface FirstAnalysisNextStepsNotifProps { | |||||
branchesEnabled?: boolean; | |||||
component: T.Component; | |||||
currentUser: T.CurrentUser; | |||||
detectedCIOnLastAnalysis?: boolean; | |||||
projectBinding?: ProjectAlmBindingResponse; | |||||
} | |||||
export function FirstAnalysisNextStepsNotif(props: FirstAnalysisNextStepsNotifProps) { | |||||
const { | |||||
component, | |||||
currentUser, | |||||
branchesEnabled, | |||||
detectedCIOnLastAnalysis, | |||||
projectBinding | |||||
} = props; | |||||
if (!isLoggedIn(currentUser)) { | |||||
return null; | |||||
} | |||||
const showConfigurePullRequestDecoNotif = branchesEnabled && projectBinding === undefined; | |||||
const showConfigureCINotif = | |||||
detectedCIOnLastAnalysis !== undefined ? !detectedCIOnLastAnalysis : false; | |||||
if (!showConfigureCINotif && !showConfigurePullRequestDecoNotif) { | |||||
return null; | |||||
} | |||||
const showOnlyConfigureCI = showConfigureCINotif && !showConfigurePullRequestDecoNotif; | |||||
const showOnlyConfigurePR = showConfigurePullRequestDecoNotif && !showConfigureCINotif; | |||||
const showBoth = showConfigureCINotif && showConfigurePullRequestDecoNotif; | |||||
const isProjectAdmin = component.configuration?.showSettings; | |||||
const tutorialsLink = ( | |||||
<Link | |||||
to={{ | |||||
pathname: '/tutorials', | |||||
query: { id: component.key } | |||||
}}> | |||||
{translate('overview.project.next_steps.links.set_up_ci')} | |||||
</Link> | |||||
); | |||||
const projectSettingsLink = ( | |||||
<Link | |||||
to={{ | |||||
pathname: '/project/settings', | |||||
query: { | |||||
id: component.key, | |||||
category: PULL_REQUEST_DECORATION_BINDING_CATEGORY | |||||
} | |||||
}}> | |||||
{translate('overview.project.next_steps.links.project_settings')} | |||||
</Link> | |||||
); | |||||
return ( | |||||
<DismissableAlert alertKey={`config_ci_pr_deco.${component.key}`} variant="info"> | |||||
{showOnlyConfigureCI && ( | |||||
<FormattedMessage | |||||
defaultMessage={translate('overview.project.next_steps.set_up_ci')} | |||||
id="overview.project.next_steps.set_up_ci" | |||||
values={{ | |||||
link: tutorialsLink | |||||
}} | |||||
/> | |||||
)} | |||||
{showOnlyConfigurePR && | |||||
(isProjectAdmin ? ( | |||||
<FormattedMessage | |||||
defaultMessage={translate('overview.project.next_steps.set_up_pr_deco.admin')} | |||||
id="overview.project.next_steps.set_up_pr_deco.admin" | |||||
values={{ | |||||
link_project_settings: projectSettingsLink | |||||
}} | |||||
/> | |||||
) : ( | |||||
translate('overview.project.next_steps.set_up_pr_deco') | |||||
))} | |||||
{showBoth && | |||||
(isProjectAdmin ? ( | |||||
<FormattedMessage | |||||
defaultMessage={translate('overview.project.next_steps.set_up_pr_deco_and_ci.admin')} | |||||
id="overview.project.next_steps.set_up_pr_deco_and_ci.admin" | |||||
values={{ | |||||
link_ci: tutorialsLink, | |||||
link_project_settings: projectSettingsLink | |||||
}} | |||||
/> | |||||
) : ( | |||||
<FormattedMessage | |||||
defaultMessage={translate('overview.project.next_steps.set_up_pr_deco_and_ci')} | |||||
id="overview.project.next_steps.set_up_pr_deco_and_ci" | |||||
values={{ link_ci: tutorialsLink }} | |||||
/> | |||||
))} | |||||
</DismissableAlert> | |||||
); | |||||
} | |||||
export default withCurrentUser(FirstAnalysisNextStepsNotif); |
import { getAllTimeMachineData } from '../../../../api/time-machine'; | import { getAllTimeMachineData } from '../../../../api/time-machine'; | ||||
import { getActivityGraph, saveActivityGraph } from '../../../../components/activity-graph/utils'; | import { getActivityGraph, saveActivityGraph } from '../../../../components/activity-graph/utils'; | ||||
import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like'; | import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like'; | ||||
import { mockComponent } from '../../../../helpers/testMocks'; | |||||
import { mockAnalysis, mockComponent } from '../../../../helpers/testMocks'; | |||||
import { ComponentQualifier } from '../../../../types/component'; | import { ComponentQualifier } from '../../../../types/component'; | ||||
import { MetricKey } from '../../../../types/metrics'; | import { MetricKey } from '../../../../types/metrics'; | ||||
import { GraphType } from '../../../../types/project-activity'; | import { GraphType } from '../../../../types/project-activity'; | ||||
import BranchOverview, { BRANCH_OVERVIEW_ACTIVITY_GRAPH } from '../BranchOverview'; | |||||
import BranchOverview, { BRANCH_OVERVIEW_ACTIVITY_GRAPH, NO_CI_DETECTED } from '../BranchOverview'; | |||||
import BranchOverviewRenderer from '../BranchOverviewRenderer'; | import BranchOverviewRenderer from '../BranchOverviewRenderer'; | ||||
jest.mock('sonar-ui-common/helpers/dates', () => ({ | jest.mock('sonar-ui-common/helpers/dates', () => ({ | ||||
const { mockAnalysis } = jest.requireActual('../../../../helpers/testMocks'); | const { mockAnalysis } = jest.requireActual('../../../../helpers/testMocks'); | ||||
return { | return { | ||||
getProjectActivity: jest.fn().mockResolvedValue({ | getProjectActivity: jest.fn().mockResolvedValue({ | ||||
analyses: [mockAnalysis(), mockAnalysis(), mockAnalysis(), mockAnalysis(), mockAnalysis()] | |||||
analyses: [ | |||||
mockAnalysis({ detectedCI: 'Cirrus CI' }), | |||||
mockAnalysis(), | |||||
mockAnalysis(), | |||||
mockAnalysis(), | |||||
mockAnalysis() | |||||
] | |||||
}) | }) | ||||
}; | }; | ||||
}); | }); | ||||
); | ); | ||||
}); | }); | ||||
it.each([ | |||||
['no analysis', [], undefined], | |||||
['1 analysis, no CI data', [mockAnalysis()], false], | |||||
['1 analysis, no CI detected', [mockAnalysis({ detectedCI: NO_CI_DETECTED })], false], | |||||
['1 analysis, CI detected', [mockAnalysis({ detectedCI: 'Cirrus CI' })], true] | |||||
])( | |||||
"should correctly flag a project that wasn't analyzed using a CI (%s)", | |||||
async (_, analyses, expected) => { | |||||
(getProjectActivity as jest.Mock).mockResolvedValueOnce({ analyses }); | |||||
const wrapper = shallowRender(); | |||||
await waitAndUpdate(wrapper); | |||||
expect(wrapper.state().detectedCIOnLastAnalysis).toBe(expected); | |||||
} | |||||
); | |||||
it('should correctly handle graph type storage', () => { | it('should correctly handle graph type storage', () => { | ||||
const wrapper = shallowRender(); | const wrapper = shallowRender(); | ||||
expect(getActivityGraph).toBeCalledWith(BRANCH_OVERVIEW_ACTIVITY_GRAPH, 'foo'); | expect(getActivityGraph).toBeCalledWith(BRANCH_OVERVIEW_ACTIVITY_GRAPH, 'foo'); |
import { BranchOverviewRenderer, BranchOverviewRendererProps } from '../BranchOverviewRenderer'; | import { BranchOverviewRenderer, BranchOverviewRendererProps } from '../BranchOverviewRenderer'; | ||||
it('should render correctly', () => { | it('should render correctly', () => { | ||||
expect(shallowRender()).toMatchSnapshot(); | |||||
expect(shallowRender({ projectIsEmpty: true })).toMatchSnapshot(); | |||||
expect(shallowRender({ loadingHistory: true, loadingStatus: true })).toMatchSnapshot(); | |||||
expect(shallowRender()).toMatchSnapshot('default'); | |||||
expect(shallowRender({ projectIsEmpty: true })).toMatchSnapshot('empty project'); | |||||
expect(shallowRender({ loadingHistory: true, loadingStatus: true })).toMatchSnapshot('loading'); | |||||
}); | }); | ||||
function shallowRender(props: Partial<BranchOverviewRendererProps> = {}) { | function shallowRender(props: Partial<BranchOverviewRendererProps> = {}) { |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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 { shallow } from 'enzyme'; | |||||
import * as React from 'react'; | |||||
import { mockProjectAlmBindingResponse } from '../../../../helpers/mocks/alm-settings'; | |||||
import { mockComponent, mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; | |||||
import { | |||||
FirstAnalysisNextStepsNotif, | |||||
FirstAnalysisNextStepsNotifProps | |||||
} from '../FirstAnalysisNextStepsNotif'; | |||||
it('should render correctly', () => { | |||||
expect(shallowRender({ currentUser: mockCurrentUser() }).type()).toBeNull(); | |||||
expect(shallowRender({ detectedCIOnLastAnalysis: false })).toMatchSnapshot( | |||||
'show prompt to configure CI' | |||||
); | |||||
expect( | |||||
shallowRender({ | |||||
projectBinding: undefined | |||||
}) | |||||
).toMatchSnapshot('show prompt to configure PR decoration, regular user'); | |||||
expect( | |||||
shallowRender({ | |||||
component: mockComponent({ configuration: { showSettings: true } }), | |||||
projectBinding: undefined | |||||
}) | |||||
).toMatchSnapshot('show prompt to configure PR decoration, project admin'); | |||||
expect( | |||||
shallowRender({ | |||||
projectBinding: undefined, | |||||
detectedCIOnLastAnalysis: false | |||||
}) | |||||
).toMatchSnapshot('show prompt to configure PR decoration + CI, regular user'); | |||||
expect( | |||||
shallowRender({ | |||||
component: mockComponent({ configuration: { showSettings: true } }), | |||||
projectBinding: undefined, | |||||
detectedCIOnLastAnalysis: false | |||||
}) | |||||
).toMatchSnapshot('show prompt to configure PR decoration + CI, project admin'); | |||||
}); | |||||
function shallowRender(props: Partial<FirstAnalysisNextStepsNotifProps> = {}) { | |||||
return shallow<FirstAnalysisNextStepsNotifProps>( | |||||
<FirstAnalysisNextStepsNotif | |||||
component={mockComponent()} | |||||
branchesEnabled={true} | |||||
currentUser={mockLoggedInUser()} | |||||
detectedCIOnLastAnalysis={true} | |||||
projectBinding={mockProjectAlmBindingResponse()} | |||||
{...props} | |||||
/> | |||||
); | |||||
} |
Array [ | Array [ | ||||
Object { | Object { | ||||
"date": "2017-03-01T09:36:01+0100", | "date": "2017-03-01T09:36:01+0100", | ||||
"detectedCI": "Cirrus CI", | |||||
"events": Array [], | "events": Array [], | ||||
"key": "foo", | "key": "foo", | ||||
"projectVersion": "1.0", | "projectVersion": "1.0", | ||||
"tags": Array [], | "tags": Array [], | ||||
} | } | ||||
} | } | ||||
detectedCIOnLastAnalysis={true} | |||||
graph="coverage" | graph="coverage" | ||||
loadingHistory={false} | loadingHistory={false} | ||||
loadingStatus={false} | loadingStatus={false} | ||||
Array [ | Array [ | ||||
Object { | Object { | ||||
"date": "2017-03-01T09:36:01+0100", | "date": "2017-03-01T09:36:01+0100", | ||||
"detectedCI": "Cirrus CI", | |||||
"events": Array [], | "events": Array [], | ||||
"key": "foo", | "key": "foo", | ||||
"projectVersion": "1.0", | "projectVersion": "1.0", | ||||
"tags": Array [], | "tags": Array [], | ||||
} | } | ||||
} | } | ||||
detectedCIOnLastAnalysis={true} | |||||
graph="coverage" | graph="coverage" | ||||
loadingHistory={false} | loadingHistory={false} | ||||
loadingStatus={false} | loadingStatus={false} | ||||
Array [ | Array [ | ||||
Object { | Object { | ||||
"date": "2017-03-01T09:36:01+0100", | "date": "2017-03-01T09:36:01+0100", | ||||
"detectedCI": "Cirrus CI", | |||||
"events": Array [], | "events": Array [], | ||||
"key": "foo", | "key": "foo", | ||||
"projectVersion": "1.0", | "projectVersion": "1.0", | ||||
"tags": Array [], | "tags": Array [], | ||||
} | } | ||||
} | } | ||||
detectedCIOnLastAnalysis={true} | |||||
graph="coverage" | graph="coverage" | ||||
loadingHistory={false} | loadingHistory={false} | ||||
loadingStatus={false} | loadingStatus={false} |
// Jest Snapshot v1, https://goo.gl/fbAQLP | // Jest Snapshot v1, https://goo.gl/fbAQLP | ||||
exports[`should render correctly 1`] = ` | |||||
<div | |||||
className="page page-limited" | |||||
> | |||||
exports[`should render correctly: default 1`] = ` | |||||
<Fragment> | |||||
<Connect(withCurrentUser(FirstAnalysisNextStepsNotif)) | |||||
component={ | |||||
Object { | |||||
"breadcrumbs": Array [], | |||||
"key": "my-project", | |||||
"name": "MyProject", | |||||
"qualifier": "TRK", | |||||
"qualityGate": Object { | |||||
"isDefault": true, | |||||
"key": "30", | |||||
"name": "Sonar way", | |||||
}, | |||||
"qualityProfiles": Array [ | |||||
Object { | |||||
"deleted": false, | |||||
"key": "my-qp", | |||||
"language": "ts", | |||||
"name": "Sonar way", | |||||
}, | |||||
], | |||||
"tags": Array [], | |||||
} | |||||
} | |||||
/> | |||||
<div | <div | ||||
className="overview" | |||||
className="page page-limited" | |||||
> | > | ||||
<A11ySkipTarget | |||||
anchor="overview_main" | |||||
/> | |||||
<div | <div | ||||
className="display-flex-row" | |||||
className="overview" | |||||
> | > | ||||
<A11ySkipTarget | |||||
anchor="overview_main" | |||||
/> | |||||
<div | <div | ||||
className="width-25 big-spacer-right" | |||||
> | |||||
<Memo(QualityGatePanel) | |||||
component={ | |||||
Object { | |||||
"breadcrumbs": Array [], | |||||
"key": "my-project", | |||||
"name": "MyProject", | |||||
"qualifier": "TRK", | |||||
"qualityGate": Object { | |||||
"isDefault": true, | |||||
"key": "30", | |||||
"name": "Sonar way", | |||||
}, | |||||
"qualityProfiles": Array [ | |||||
Object { | |||||
"deleted": false, | |||||
"key": "my-qp", | |||||
"language": "ts", | |||||
"name": "Sonar way", | |||||
}, | |||||
], | |||||
"tags": Array [], | |||||
} | |||||
} | |||||
loading={false} | |||||
/> | |||||
</div> | |||||
<div | |||||
className="flex-1" | |||||
className="display-flex-row" | |||||
> | > | ||||
<div | <div | ||||
className="display-flex-column" | |||||
className="width-25 big-spacer-right" | |||||
> | > | ||||
<MeasuresPanel | |||||
branch={ | |||||
Object { | |||||
"analysisDate": "2018-01-01", | |||||
"excludedFromPurge": true, | |||||
"isMain": true, | |||||
"name": "master", | |||||
} | |||||
} | |||||
<Memo(QualityGatePanel) | |||||
component={ | component={ | ||||
Object { | Object { | ||||
"breadcrumbs": Array [], | "breadcrumbs": Array [], | ||||
} | } | ||||
} | } | ||||
loading={false} | loading={false} | ||||
measures={ | |||||
Array [ | |||||
/> | |||||
</div> | |||||
<div | |||||
className="flex-1" | |||||
> | |||||
<div | |||||
className="display-flex-column" | |||||
> | |||||
<MeasuresPanel | |||||
branch={ | |||||
Object { | Object { | ||||
"bestValue": true, | |||||
"leak": "1", | |||||
"metric": Object { | |||||
"id": "coverage", | |||||
"key": "coverage", | |||||
"name": "Coverage", | |||||
"type": "PERCENT", | |||||
"analysisDate": "2018-01-01", | |||||
"excludedFromPurge": true, | |||||
"isMain": true, | |||||
"name": "master", | |||||
} | |||||
} | |||||
component={ | |||||
Object { | |||||
"breadcrumbs": Array [], | |||||
"key": "my-project", | |||||
"name": "MyProject", | |||||
"qualifier": "TRK", | |||||
"qualityGate": Object { | |||||
"isDefault": true, | |||||
"key": "30", | |||||
"name": "Sonar way", | |||||
}, | }, | ||||
"period": Object { | |||||
"qualityProfiles": Array [ | |||||
Object { | |||||
"deleted": false, | |||||
"key": "my-qp", | |||||
"language": "ts", | |||||
"name": "Sonar way", | |||||
}, | |||||
], | |||||
"tags": Array [], | |||||
} | |||||
} | |||||
loading={false} | |||||
measures={ | |||||
Array [ | |||||
Object { | |||||
"bestValue": true, | "bestValue": true, | ||||
"index": 1, | |||||
"leak": "1", | |||||
"metric": Object { | |||||
"id": "coverage", | |||||
"key": "coverage", | |||||
"name": "Coverage", | |||||
"type": "PERCENT", | |||||
}, | |||||
"period": Object { | |||||
"bestValue": true, | |||||
"index": 1, | |||||
"value": "1.0", | |||||
}, | |||||
"value": "1.0", | "value": "1.0", | ||||
}, | }, | ||||
"value": "1.0", | |||||
}, | |||||
] | |||||
} | |||||
/> | |||||
<Memo(ActivityPanel) | |||||
branchLike={ | |||||
Object { | |||||
"analysisDate": "2018-01-01", | |||||
"excludedFromPurge": true, | |||||
"isMain": true, | |||||
"name": "master", | |||||
] | |||||
} | } | ||||
} | |||||
component={ | |||||
Object { | |||||
"breadcrumbs": Array [], | |||||
"key": "my-project", | |||||
"name": "MyProject", | |||||
"qualifier": "TRK", | |||||
"qualityGate": Object { | |||||
"isDefault": true, | |||||
"key": "30", | |||||
"name": "Sonar way", | |||||
}, | |||||
"qualityProfiles": Array [ | |||||
Object { | |||||
"deleted": false, | |||||
"key": "my-qp", | |||||
"language": "ts", | |||||
/> | |||||
<Memo(ActivityPanel) | |||||
branchLike={ | |||||
Object { | |||||
"analysisDate": "2018-01-01", | |||||
"excludedFromPurge": true, | |||||
"isMain": true, | |||||
"name": "master", | |||||
} | |||||
} | |||||
component={ | |||||
Object { | |||||
"breadcrumbs": Array [], | |||||
"key": "my-project", | |||||
"name": "MyProject", | |||||
"qualifier": "TRK", | |||||
"qualityGate": Object { | |||||
"isDefault": true, | |||||
"key": "30", | |||||
"name": "Sonar way", | "name": "Sonar way", | ||||
}, | }, | ||||
], | |||||
"tags": Array [], | |||||
"qualityProfiles": Array [ | |||||
Object { | |||||
"deleted": false, | |||||
"key": "my-qp", | |||||
"language": "ts", | |||||
"name": "Sonar way", | |||||
}, | |||||
], | |||||
"tags": Array [], | |||||
} | |||||
} | } | ||||
} | |||||
graph="issues" | |||||
loading={false} | |||||
measuresHistory={Array []} | |||||
metrics={Array []} | |||||
onGraphChange={[MockFunction]} | |||||
/> | |||||
graph="issues" | |||||
loading={false} | |||||
measuresHistory={Array []} | |||||
metrics={Array []} | |||||
onGraphChange={[MockFunction]} | |||||
/> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | |||||
</Fragment> | |||||
`; | `; | ||||
exports[`should render correctly 2`] = ` | |||||
<div | |||||
className="page page-limited" | |||||
> | |||||
exports[`should render correctly: empty project 1`] = ` | |||||
<Fragment> | |||||
<Connect(withCurrentUser(FirstAnalysisNextStepsNotif)) | |||||
component={ | |||||
Object { | |||||
"breadcrumbs": Array [], | |||||
"key": "my-project", | |||||
"name": "MyProject", | |||||
"qualifier": "TRK", | |||||
"qualityGate": Object { | |||||
"isDefault": true, | |||||
"key": "30", | |||||
"name": "Sonar way", | |||||
}, | |||||
"qualityProfiles": Array [ | |||||
Object { | |||||
"deleted": false, | |||||
"key": "my-qp", | |||||
"language": "ts", | |||||
"name": "Sonar way", | |||||
}, | |||||
], | |||||
"tags": Array [], | |||||
} | |||||
} | |||||
/> | |||||
<div | <div | ||||
className="overview" | |||||
className="page page-limited" | |||||
> | > | ||||
<A11ySkipTarget | |||||
anchor="overview_main" | |||||
/> | |||||
<Memo(NoCodeWarning) | |||||
branchLike={ | |||||
Object { | |||||
"analysisDate": "2018-01-01", | |||||
"excludedFromPurge": true, | |||||
"isMain": true, | |||||
"name": "master", | |||||
<div | |||||
className="overview" | |||||
> | |||||
<A11ySkipTarget | |||||
anchor="overview_main" | |||||
/> | |||||
<Memo(NoCodeWarning) | |||||
branchLike={ | |||||
Object { | |||||
"analysisDate": "2018-01-01", | |||||
"excludedFromPurge": true, | |||||
"isMain": true, | |||||
"name": "master", | |||||
} | |||||
} | } | ||||
} | |||||
component={ | |||||
Object { | |||||
"breadcrumbs": Array [], | |||||
"key": "my-project", | |||||
"name": "MyProject", | |||||
"qualifier": "TRK", | |||||
"qualityGate": Object { | |||||
"isDefault": true, | |||||
"key": "30", | |||||
"name": "Sonar way", | |||||
}, | |||||
"qualityProfiles": Array [ | |||||
Object { | |||||
"deleted": false, | |||||
"key": "my-qp", | |||||
"language": "ts", | |||||
component={ | |||||
Object { | |||||
"breadcrumbs": Array [], | |||||
"key": "my-project", | |||||
"name": "MyProject", | |||||
"qualifier": "TRK", | |||||
"qualityGate": Object { | |||||
"isDefault": true, | |||||
"key": "30", | |||||
"name": "Sonar way", | "name": "Sonar way", | ||||
}, | }, | ||||
], | |||||
"tags": Array [], | |||||
"qualityProfiles": Array [ | |||||
Object { | |||||
"deleted": false, | |||||
"key": "my-qp", | |||||
"language": "ts", | |||||
"name": "Sonar way", | |||||
}, | |||||
], | |||||
"tags": Array [], | |||||
} | |||||
} | } | ||||
} | |||||
measures={ | |||||
Array [ | |||||
Object { | |||||
"bestValue": true, | |||||
"leak": "1", | |||||
"metric": Object { | |||||
"id": "coverage", | |||||
"key": "coverage", | |||||
"name": "Coverage", | |||||
"type": "PERCENT", | |||||
}, | |||||
"period": Object { | |||||
measures={ | |||||
Array [ | |||||
Object { | |||||
"bestValue": true, | "bestValue": true, | ||||
"index": 1, | |||||
"leak": "1", | |||||
"metric": Object { | |||||
"id": "coverage", | |||||
"key": "coverage", | |||||
"name": "Coverage", | |||||
"type": "PERCENT", | |||||
}, | |||||
"period": Object { | |||||
"bestValue": true, | |||||
"index": 1, | |||||
"value": "1.0", | |||||
}, | |||||
"value": "1.0", | "value": "1.0", | ||||
}, | }, | ||||
"value": "1.0", | |||||
}, | |||||
] | |||||
} | |||||
/> | |||||
] | |||||
} | |||||
/> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | |||||
</Fragment> | |||||
`; | `; | ||||
exports[`should render correctly 3`] = ` | |||||
<div | |||||
className="page page-limited" | |||||
> | |||||
exports[`should render correctly: loading 1`] = ` | |||||
<Fragment> | |||||
<Connect(withCurrentUser(FirstAnalysisNextStepsNotif)) | |||||
component={ | |||||
Object { | |||||
"breadcrumbs": Array [], | |||||
"key": "my-project", | |||||
"name": "MyProject", | |||||
"qualifier": "TRK", | |||||
"qualityGate": Object { | |||||
"isDefault": true, | |||||
"key": "30", | |||||
"name": "Sonar way", | |||||
}, | |||||
"qualityProfiles": Array [ | |||||
Object { | |||||
"deleted": false, | |||||
"key": "my-qp", | |||||
"language": "ts", | |||||
"name": "Sonar way", | |||||
}, | |||||
], | |||||
"tags": Array [], | |||||
} | |||||
} | |||||
/> | |||||
<div | <div | ||||
className="overview" | |||||
className="page page-limited" | |||||
> | > | ||||
<A11ySkipTarget | |||||
anchor="overview_main" | |||||
/> | |||||
<div | <div | ||||
className="display-flex-row" | |||||
className="overview" | |||||
> | > | ||||
<A11ySkipTarget | |||||
anchor="overview_main" | |||||
/> | |||||
<div | <div | ||||
className="width-25 big-spacer-right" | |||||
> | |||||
<Memo(QualityGatePanel) | |||||
component={ | |||||
Object { | |||||
"breadcrumbs": Array [], | |||||
"key": "my-project", | |||||
"name": "MyProject", | |||||
"qualifier": "TRK", | |||||
"qualityGate": Object { | |||||
"isDefault": true, | |||||
"key": "30", | |||||
"name": "Sonar way", | |||||
}, | |||||
"qualityProfiles": Array [ | |||||
Object { | |||||
"deleted": false, | |||||
"key": "my-qp", | |||||
"language": "ts", | |||||
"name": "Sonar way", | |||||
}, | |||||
], | |||||
"tags": Array [], | |||||
} | |||||
} | |||||
loading={true} | |||||
/> | |||||
</div> | |||||
<div | |||||
className="flex-1" | |||||
className="display-flex-row" | |||||
> | > | ||||
<div | <div | ||||
className="display-flex-column" | |||||
className="width-25 big-spacer-right" | |||||
> | > | ||||
<MeasuresPanel | |||||
branch={ | |||||
Object { | |||||
"analysisDate": "2018-01-01", | |||||
"excludedFromPurge": true, | |||||
"isMain": true, | |||||
"name": "master", | |||||
} | |||||
} | |||||
<Memo(QualityGatePanel) | |||||
component={ | component={ | ||||
Object { | Object { | ||||
"breadcrumbs": Array [], | "breadcrumbs": Array [], | ||||
} | } | ||||
} | } | ||||
loading={true} | loading={true} | ||||
measures={ | |||||
Array [ | |||||
/> | |||||
</div> | |||||
<div | |||||
className="flex-1" | |||||
> | |||||
<div | |||||
className="display-flex-column" | |||||
> | |||||
<MeasuresPanel | |||||
branch={ | |||||
Object { | Object { | ||||
"bestValue": true, | |||||
"leak": "1", | |||||
"metric": Object { | |||||
"id": "coverage", | |||||
"key": "coverage", | |||||
"name": "Coverage", | |||||
"type": "PERCENT", | |||||
"analysisDate": "2018-01-01", | |||||
"excludedFromPurge": true, | |||||
"isMain": true, | |||||
"name": "master", | |||||
} | |||||
} | |||||
component={ | |||||
Object { | |||||
"breadcrumbs": Array [], | |||||
"key": "my-project", | |||||
"name": "MyProject", | |||||
"qualifier": "TRK", | |||||
"qualityGate": Object { | |||||
"isDefault": true, | |||||
"key": "30", | |||||
"name": "Sonar way", | |||||
}, | }, | ||||
"period": Object { | |||||
"qualityProfiles": Array [ | |||||
Object { | |||||
"deleted": false, | |||||
"key": "my-qp", | |||||
"language": "ts", | |||||
"name": "Sonar way", | |||||
}, | |||||
], | |||||
"tags": Array [], | |||||
} | |||||
} | |||||
loading={true} | |||||
measures={ | |||||
Array [ | |||||
Object { | |||||
"bestValue": true, | "bestValue": true, | ||||
"index": 1, | |||||
"leak": "1", | |||||
"metric": Object { | |||||
"id": "coverage", | |||||
"key": "coverage", | |||||
"name": "Coverage", | |||||
"type": "PERCENT", | |||||
}, | |||||
"period": Object { | |||||
"bestValue": true, | |||||
"index": 1, | |||||
"value": "1.0", | |||||
}, | |||||
"value": "1.0", | "value": "1.0", | ||||
}, | }, | ||||
"value": "1.0", | |||||
}, | |||||
] | |||||
} | |||||
/> | |||||
<Memo(ActivityPanel) | |||||
branchLike={ | |||||
Object { | |||||
"analysisDate": "2018-01-01", | |||||
"excludedFromPurge": true, | |||||
"isMain": true, | |||||
"name": "master", | |||||
] | |||||
} | } | ||||
} | |||||
component={ | |||||
Object { | |||||
"breadcrumbs": Array [], | |||||
"key": "my-project", | |||||
"name": "MyProject", | |||||
"qualifier": "TRK", | |||||
"qualityGate": Object { | |||||
"isDefault": true, | |||||
"key": "30", | |||||
"name": "Sonar way", | |||||
}, | |||||
"qualityProfiles": Array [ | |||||
Object { | |||||
"deleted": false, | |||||
"key": "my-qp", | |||||
"language": "ts", | |||||
/> | |||||
<Memo(ActivityPanel) | |||||
branchLike={ | |||||
Object { | |||||
"analysisDate": "2018-01-01", | |||||
"excludedFromPurge": true, | |||||
"isMain": true, | |||||
"name": "master", | |||||
} | |||||
} | |||||
component={ | |||||
Object { | |||||
"breadcrumbs": Array [], | |||||
"key": "my-project", | |||||
"name": "MyProject", | |||||
"qualifier": "TRK", | |||||
"qualityGate": Object { | |||||
"isDefault": true, | |||||
"key": "30", | |||||
"name": "Sonar way", | "name": "Sonar way", | ||||
}, | }, | ||||
], | |||||
"tags": Array [], | |||||
"qualityProfiles": Array [ | |||||
Object { | |||||
"deleted": false, | |||||
"key": "my-qp", | |||||
"language": "ts", | |||||
"name": "Sonar way", | |||||
}, | |||||
], | |||||
"tags": Array [], | |||||
} | |||||
} | } | ||||
} | |||||
graph="issues" | |||||
loading={true} | |||||
measuresHistory={Array []} | |||||
metrics={Array []} | |||||
onGraphChange={[MockFunction]} | |||||
/> | |||||
graph="issues" | |||||
loading={true} | |||||
measuresHistory={Array []} | |||||
metrics={Array []} | |||||
onGraphChange={[MockFunction]} | |||||
/> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | |||||
</Fragment> | |||||
`; | `; |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should render correctly: show prompt to configure CI 1`] = ` | |||||
<DismissableAlert | |||||
alertKey="config_ci_pr_deco.my-project" | |||||
variant="info" | |||||
> | |||||
<FormattedMessage | |||||
defaultMessage="overview.project.next_steps.set_up_ci" | |||||
id="overview.project.next_steps.set_up_ci" | |||||
values={ | |||||
Object { | |||||
"link": <Link | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/tutorials", | |||||
"query": Object { | |||||
"id": "my-project", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
overview.project.next_steps.links.set_up_ci | |||||
</Link>, | |||||
} | |||||
} | |||||
/> | |||||
</DismissableAlert> | |||||
`; | |||||
exports[`should render correctly: show prompt to configure PR decoration + CI, project admin 1`] = ` | |||||
<DismissableAlert | |||||
alertKey="config_ci_pr_deco.my-project" | |||||
variant="info" | |||||
> | |||||
<FormattedMessage | |||||
defaultMessage="overview.project.next_steps.set_up_pr_deco_and_ci.admin" | |||||
id="overview.project.next_steps.set_up_pr_deco_and_ci.admin" | |||||
values={ | |||||
Object { | |||||
"link_ci": <Link | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/tutorials", | |||||
"query": Object { | |||||
"id": "my-project", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
overview.project.next_steps.links.set_up_ci | |||||
</Link>, | |||||
"link_project_settings": <Link | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/project/settings", | |||||
"query": Object { | |||||
"category": "pull_request_decoration_binding", | |||||
"id": "my-project", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
overview.project.next_steps.links.project_settings | |||||
</Link>, | |||||
} | |||||
} | |||||
/> | |||||
</DismissableAlert> | |||||
`; | |||||
exports[`should render correctly: show prompt to configure PR decoration + CI, regular user 1`] = ` | |||||
<DismissableAlert | |||||
alertKey="config_ci_pr_deco.my-project" | |||||
variant="info" | |||||
> | |||||
<FormattedMessage | |||||
defaultMessage="overview.project.next_steps.set_up_pr_deco_and_ci" | |||||
id="overview.project.next_steps.set_up_pr_deco_and_ci" | |||||
values={ | |||||
Object { | |||||
"link_ci": <Link | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/tutorials", | |||||
"query": Object { | |||||
"id": "my-project", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
overview.project.next_steps.links.set_up_ci | |||||
</Link>, | |||||
} | |||||
} | |||||
/> | |||||
</DismissableAlert> | |||||
`; | |||||
exports[`should render correctly: show prompt to configure PR decoration, project admin 1`] = ` | |||||
<DismissableAlert | |||||
alertKey="config_ci_pr_deco.my-project" | |||||
variant="info" | |||||
> | |||||
<FormattedMessage | |||||
defaultMessage="overview.project.next_steps.set_up_pr_deco.admin" | |||||
id="overview.project.next_steps.set_up_pr_deco.admin" | |||||
values={ | |||||
Object { | |||||
"link_project_settings": <Link | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/project/settings", | |||||
"query": Object { | |||||
"category": "pull_request_decoration_binding", | |||||
"id": "my-project", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
overview.project.next_steps.links.project_settings | |||||
</Link>, | |||||
} | |||||
} | |||||
/> | |||||
</DismissableAlert> | |||||
`; | |||||
exports[`should render correctly: show prompt to configure PR decoration, regular user 1`] = ` | |||||
<DismissableAlert | |||||
alertKey="config_ci_pr_deco.my-project" | |||||
variant="info" | |||||
> | |||||
overview.project.next_steps.set_up_pr_deco | |||||
</DismissableAlert> | |||||
`; |
import * as React from 'react'; | import * as React from 'react'; | ||||
import { lazyLoadComponent } from 'sonar-ui-common/components/lazyLoadComponent'; | import { lazyLoadComponent } from 'sonar-ui-common/components/lazyLoadComponent'; | ||||
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; | import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; | ||||
import { withAppState } from '../../../components/hoc/withAppState'; | |||||
import { Router, withRouter } from '../../../components/hoc/withRouter'; | import { Router, withRouter } from '../../../components/hoc/withRouter'; | ||||
import { isPullRequest } from '../../../helpers/branch-like'; | import { isPullRequest } from '../../../helpers/branch-like'; | ||||
import { ProjectAlmBindingResponse } from '../../../types/alm-settings'; | import { ProjectAlmBindingResponse } from '../../../types/alm-settings'; | ||||
const PullRequestOverview = lazyLoadComponent(() => import('../pullRequests/PullRequestOverview')); | const PullRequestOverview = lazyLoadComponent(() => import('../pullRequests/PullRequestOverview')); | ||||
interface Props { | interface Props { | ||||
appState: Pick<T.AppState, 'branchesEnabled'>; | |||||
branchLike?: BranchLike; | branchLike?: BranchLike; | ||||
branchLikes: BranchLike[]; | branchLikes: BranchLike[]; | ||||
component: T.Component; | component: T.Component; | ||||
}; | }; | ||||
render() { | render() { | ||||
const { branchLike, branchLikes, component, projectBinding } = this.props; | |||||
const { | |||||
appState: { branchesEnabled }, | |||||
branchLike, | |||||
branchLikes, | |||||
component, | |||||
projectBinding | |||||
} = this.props; | |||||
if (this.isPortfolio()) { | if (this.isPortfolio()) { | ||||
return null; | return null; | ||||
<> | <> | ||||
<Suggestions suggestions="overview" /> | <Suggestions suggestions="overview" /> | ||||
{!component.analysisDate ? ( | |||||
{!component.analysisDate && ( | |||||
<EmptyOverview | <EmptyOverview | ||||
branchLike={branchLike} | branchLike={branchLike} | ||||
branchLikes={branchLikes} | branchLikes={branchLikes} | ||||
hasAnalyses={this.props.isPending || this.props.isInProgress} | hasAnalyses={this.props.isPending || this.props.isInProgress} | ||||
projectBinding={projectBinding} | projectBinding={projectBinding} | ||||
/> | /> | ||||
) : ( | |||||
<BranchOverview branch={branchLike} component={component} /> | |||||
)} | |||||
{component.analysisDate && ( | |||||
<BranchOverview | |||||
branch={branchLike} | |||||
branchesEnabled={branchesEnabled} | |||||
component={component} | |||||
projectBinding={projectBinding} | |||||
/> | |||||
)} | )} | ||||
</> | </> | ||||
); | ); | ||||
} | } | ||||
} | } | ||||
export default withRouter(App); | |||||
export default withRouter(withAppState(App)); |
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { isSonarCloud } from '../../../../helpers/system'; | import { isSonarCloud } from '../../../../helpers/system'; | ||||
import { mockAppState } from '../../../../helpers/testMocks'; | |||||
import BranchOverview from '../../branches/BranchOverview'; | import BranchOverview from '../../branches/BranchOverview'; | ||||
import { App } from '../App'; | import { App } from '../App'; | ||||
function getWrapper(props = {}) { | function getWrapper(props = {}) { | ||||
return shallow( | return shallow( | ||||
<App branchLikes={[]} component={component} router={{ replace: jest.fn() }} {...props} /> | |||||
<App | |||||
appState={mockAppState()} | |||||
branchLikes={[]} | |||||
component={component} | |||||
router={{ replace: jest.fn() }} | |||||
{...props} | |||||
/> | |||||
); | ); | ||||
} | } |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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. | |||||
*/ | |||||
.dismissable-alert-banner .dismissable-alert-content { | |||||
max-width: var(--maxPageWidth); | |||||
min-width: var(--minPageWidth); | |||||
} | |||||
.dismissable-alert-banner .button-icon { | |||||
height: var(--tinyControlHeight); | |||||
width: var(--tinyControlHeight); | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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 classNames from 'classnames'; | |||||
import * as React from 'react'; | |||||
import { ButtonIcon } from 'sonar-ui-common/components/controls/buttons'; | |||||
import ClearIcon from 'sonar-ui-common/components/icons/ClearIcon'; | |||||
import { Alert, AlertProps } from 'sonar-ui-common/components/ui/Alert'; | |||||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||||
import { get, save } from 'sonar-ui-common/helpers/storage'; | |||||
import './DismissableAlert.css'; | |||||
export interface DismissableAlertProps extends AlertProps { | |||||
alertKey: string; | |||||
children?: React.ReactNode; | |||||
className?: string; | |||||
} | |||||
export const DISMISSED_ALERT_STORAGE_KEY = 'sonarqube.dismissed_alert'; | |||||
export default function DismissableAlert(props: DismissableAlertProps) { | |||||
const { alertKey, children, className, display = 'banner', variant } = props; | |||||
const [show, setShow] = React.useState(false); | |||||
React.useEffect(() => { | |||||
if (get(DISMISSED_ALERT_STORAGE_KEY, alertKey) !== 'true') { | |||||
setShow(true); | |||||
} | |||||
}, [alertKey]); | |||||
const hideAlert = () => { | |||||
save(DISMISSED_ALERT_STORAGE_KEY, 'true', alertKey); | |||||
}; | |||||
return !show ? null : ( | |||||
<Alert | |||||
className={classNames(`dismissable-alert-${display}`, className)} | |||||
display={display} | |||||
variant={variant}> | |||||
<div className="display-flex-center dismissable-alert-content"> | |||||
<div className="flex-1">{children}</div> | |||||
<ButtonIcon | |||||
aria-label={translate('alert.dismiss')} | |||||
onClick={() => { | |||||
hideAlert(); | |||||
setShow(false); | |||||
}}> | |||||
<ClearIcon size={12} thin={true} /> | |||||
</ButtonIcon> | |||||
</div> | |||||
</Alert> | |||||
); | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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 { shallow } from 'enzyme'; | |||||
import * as React from 'react'; | |||||
import { ButtonIcon } from 'sonar-ui-common/components/controls/buttons'; | |||||
import { save } from 'sonar-ui-common/helpers/storage'; | |||||
import { click } from 'sonar-ui-common/helpers/testUtils'; | |||||
import DismissableAlert, { | |||||
DismissableAlertProps, | |||||
DISMISSED_ALERT_STORAGE_KEY | |||||
} from '../DismissableAlert'; | |||||
jest.mock('sonar-ui-common/helpers/storage', () => ({ | |||||
get: jest.fn((_: string, suffix: string) => (suffix === 'bar' ? 'true' : undefined)), | |||||
save: jest.fn() | |||||
})); | |||||
beforeEach(() => { | |||||
jest.clearAllMocks(); | |||||
jest.spyOn(React, 'useEffect').mockImplementationOnce(f => f()); | |||||
}); | |||||
it('should render correctly', () => { | |||||
expect(shallowRender()).toMatchSnapshot(); | |||||
}); | |||||
it('should render correctly with a non-default display', () => { | |||||
expect(shallowRender({ display: 'block' })).toMatchSnapshot(); | |||||
}); | |||||
it('should not render if it was dismissed', () => { | |||||
expect(shallowRender({ alertKey: 'bar' }).type()).toBeNull(); | |||||
}); | |||||
it('should correctly allow dismissing', () => { | |||||
const wrapper = shallowRender(); | |||||
click(wrapper.find(ButtonIcon)); | |||||
expect(save).toBeCalledWith(DISMISSED_ALERT_STORAGE_KEY, 'true', 'foo'); | |||||
}); | |||||
function shallowRender(props: Partial<DismissableAlertProps> = {}) { | |||||
return shallow<DismissableAlertProps>( | |||||
<DismissableAlert alertKey="foo" variant="info" {...props}> | |||||
<div>My content</div> | |||||
</DismissableAlert> | |||||
); | |||||
} |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should render correctly 1`] = ` | |||||
<Alert | |||||
className="dismissable-alert-banner" | |||||
display="banner" | |||||
variant="info" | |||||
> | |||||
<div | |||||
className="display-flex-center dismissable-alert-content" | |||||
> | |||||
<div | |||||
className="flex-1" | |||||
> | |||||
<div> | |||||
My content | |||||
</div> | |||||
</div> | |||||
<ButtonIcon | |||||
aria-label="alert.dismiss" | |||||
onClick={[Function]} | |||||
> | |||||
<ClearIcon | |||||
size={12} | |||||
thin={true} | |||||
/> | |||||
</ButtonIcon> | |||||
</div> | |||||
</Alert> | |||||
`; | |||||
exports[`should render correctly with a non-default display 1`] = ` | |||||
<Alert | |||||
className="dismissable-alert-block" | |||||
display="block" | |||||
variant="info" | |||||
> | |||||
<div | |||||
className="display-flex-center dismissable-alert-content" | |||||
> | |||||
<div | |||||
className="flex-1" | |||||
> | |||||
<div> | |||||
My content | |||||
</div> | |||||
</div> | |||||
<ButtonIcon | |||||
aria-label="alert.dismiss" | |||||
onClick={[Function]} | |||||
> | |||||
<ClearIcon | |||||
size={12} | |||||
thin={true} | |||||
/> | |||||
</ButtonIcon> | |||||
</div> | |||||
</Alert> | |||||
`; |
interface BaseAnalysis { | interface BaseAnalysis { | ||||
buildString?: string; | buildString?: string; | ||||
detectedCI?: string; | |||||
events: AnalysisEvent[]; | events: AnalysisEvent[]; | ||||
key: string; | key: string; | ||||
manualNewCodePeriodBaseline?: boolean; | manualNewCodePeriodBaseline?: boolean; |
alert.tooltip.success=This is a success message. | alert.tooltip.success=This is a success message. | ||||
alert.tooltip.info=This is an info message. | alert.tooltip.info=This is an info message. | ||||
alert.dismiss=Dismiss this message | |||||
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ | ||||
# | # | ||||
overview.project.main_branch_no_lines_of_code=The main branch has no lines of code. | overview.project.main_branch_no_lines_of_code=The main branch has no lines of code. | ||||
overview.project.main_branch_empty=The main branch of this project is empty. | overview.project.main_branch_empty=The main branch of this project is empty. | ||||
overview.project.branch_needs_new_analysis=The branch data is incomplete. Run a new analysis to update it. | overview.project.branch_needs_new_analysis=The branch data is incomplete. Run a new analysis to update it. | ||||
overview.project.next_steps.set_up_pr_deco_and_ci.admin=To benefit from more of SonarQube's features, {link_ci} and set up DevOps platform integration in your {link_project_settings}. | |||||
overview.project.next_steps.set_up_pr_deco_and_ci=To benefit from more of SonarQube's features, {link_ci} and ask a project administrator to set up DevOps platform integration. | |||||
overview.project.next_steps.set_up_pr_deco.admin=To benefit from more of SonarQube's features, set up DevOps platform integration in your {link_project_settings}. | |||||
overview.project.next_steps.set_up_pr_deco=To benefit from more of SonarQube's features, ask a project administrator to set up DevOps platform integration. | |||||
overview.project.next_steps.set_up_ci=To benefit from more of SonarQube's features, {link}. | |||||
overview.project.next_steps.links.project_settings=project settings | |||||
overview.project.next_steps.links.set_up_ci=set up analysis in your favorite CI | |||||
overview.coverage_on=Coverage on | overview.coverage_on=Coverage on | ||||
overview.coverage_on_X_lines=Coverage on {count} Lines to cover | overview.coverage_on_X_lines=Coverage on {count} Lines to cover |