diff options
author | Wouter Admiraal <wouter.admiraal@sonarsource.com> | 2019-03-08 10:40:24 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2019-03-29 09:44:57 +0100 |
commit | c23117124c41e0ad019db3b2c354e55eb3e891fd (patch) | |
tree | 32e64bfdd8961f24fc49012874cb02898e439257 | |
parent | c19e3ba3d999c51e3d7ef1d4b9f7ec3129a9a12f (diff) | |
download | sonarqube-c23117124c41e0ad019db3b2c354e55eb3e891fd.tar.gz sonarqube-c23117124c41e0ad019db3b2c354e55eb3e891fd.zip |
SONAR-10994 Connect ReviewApp component to new branch store
4 files changed, 485 insertions, 14 deletions
diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/LargeQualityGateBadge.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/LargeQualityGateBadge.tsx index 78bd963a29b..550a137af4b 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/LargeQualityGateBadge.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/LargeQualityGateBadge.tsx @@ -30,11 +30,11 @@ import { transparentWhite } from '../../../app/theme'; interface Props { component: T.Component; - level?: string; + level?: T.Status; } export default function LargeQualityGateBadge({ component, level }: Props) { - const success = level !== 'ERROR'; + const success = level === 'OK'; let path; if (isSonarCloud()) { diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/ReviewApp.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/ReviewApp.tsx index a0e47c2853e..f1d413efed9 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/ReviewApp.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/ReviewApp.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import * as classNames from 'classnames'; +import { connect } from 'react-redux'; import AfterMergeEstimate from './AfterMergeEstimate'; import LargeQualityGateBadge from './LargeQualityGateBadge'; import IssueLabel from './IssueLabel'; @@ -31,23 +32,25 @@ import { getMeasures } from '../../../api/measures'; import { getQualityGateProjectStatus } from '../../../api/quality-gates'; import { PR_METRICS, IssueType, MeasurementType } from '../utils'; import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branches'; -import { translate } from '../../../helpers/l10n'; import { extractStatusConditionsFromProjectStatus } from '../../../helpers/qualityGates'; +import { registerBranchStatus } from '../../../store/rootActions'; +import { translate } from '../../../helpers/l10n'; import '../styles.css'; interface Props { - branchLike?: T.PullRequest | T.ShortLivingBranch; + branchLike: T.PullRequest | T.ShortLivingBranch; component: T.Component; + registerBranchStatus: (branchLike: T.BranchLike, component: string, status: T.Status) => void; } interface State { conditions: T.QualityGateStatusCondition[]; loading: boolean; measures: T.Measure[]; - status?: string; + status?: T.Status; } -export default class ReviewApp extends React.Component<Props, State> { +export class ReviewApp extends React.Component<Props, State> { mounted = false; state: State = { @@ -89,14 +92,17 @@ export default class ReviewApp extends React.Component<Props, State> { }), getQualityGateProjectStatus(data) ]).then( - ([measures, status]) => { - if (this.mounted && measures && status) { + ([measures, qualityGateStatus]) => { + if (this.mounted && measures && qualityGateStatus) { + const { status } = qualityGateStatus.projectStatus; this.setState({ - conditions: extractStatusConditionsFromProjectStatus(status), + conditions: extractStatusConditionsFromProjectStatus(qualityGateStatus), loading: false, measures, - status: status.projectStatus.status + status }); + + this.props.registerBranchStatus(branchLike, component.key, status); } }, () => { @@ -109,7 +115,7 @@ export default class ReviewApp extends React.Component<Props, State> { render() { const { branchLike, component } = this.props; - const { loading, measures, conditions, status } = this.state; + const { conditions = [], loading, measures, status } = this.state; const erroredConditions = conditions.filter(condition => condition.level === 'ERROR'); return ( @@ -192,3 +198,10 @@ export default class ReviewApp extends React.Component<Props, State> { ); } } + +const mapDispatchToProps = { registerBranchStatus }; + +export default connect( + null, + mapDispatchToProps +)(ReviewApp); diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/ReviewApp-test.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/ReviewApp-test.tsx index 53e9cbeff46..de6cd3d4a9d 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/ReviewApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/ReviewApp-test.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; -import ReviewApp from '../ReviewApp'; +import { ReviewApp } from '../ReviewApp'; import { getMeasures } from '../../../../api/measures'; import { getQualityGateProjectStatus } from '../../../../api/quality-gates'; import { mockComponent, mockPullRequest } from '../../../../helpers/testMocks'; @@ -49,8 +49,9 @@ beforeEach(() => { it('should render correctly for a passed QG', async () => { const { mockQualityGateProjectStatus } = getMockHelpers(); (getQualityGateProjectStatus as jest.Mock).mockResolvedValue(mockQualityGateProjectStatus()); + const registerBranchStatus = jest.fn(); + const wrapper = shallowRender({ registerBranchStatus }); - const wrapper = shallowRender(); await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); @@ -58,6 +59,7 @@ it('should render correctly for a passed QG', async () => { expect(getMeasures).toBeCalled(); expect(getQualityGateProjectStatus).toBeCalled(); + expect(registerBranchStatus).toBeCalled(); }); it('should render correctly for a failed QG', async () => { @@ -93,6 +95,33 @@ it('should render correctly for a failed QG', async () => { expect(wrapper.find('QualityGateConditions').exists()).toBe(true); }); +it('should correctly refresh data if certain props change', () => { + const wrapper = shallowRender(); + + jest.clearAllMocks(); + wrapper.setProps({ + component: mockComponent({ key: 'foo' }) + }); + expect(getMeasures).toBeCalled(); + expect(getQualityGateProjectStatus).toBeCalled(); + + jest.clearAllMocks(); + wrapper.setProps({ + branchLike: mockPullRequest({ key: '1002' }) + }); + expect(getMeasures).toBeCalled(); + expect(getQualityGateProjectStatus).toBeCalled(); +}); + +it('should correctly handle a WS failure', async () => { + (getMeasures as jest.Mock).mockRejectedValue({}); + (getQualityGateProjectStatus as jest.Mock).mockRejectedValue({}); + const wrapper = shallowRender(); + + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); +}); + function getMockHelpers() { // We use this little "force-requiring" instead of an import statement in // order to prevent a hoisting race condition while mocking. If we want to use @@ -104,6 +133,11 @@ function getMockHelpers() { function shallowRender(props: Partial<ReviewApp['props']> = {}) { return shallow( - <ReviewApp branchLike={mockPullRequest()} component={mockComponent()} {...props} /> + <ReviewApp + branchLike={mockPullRequest()} + component={mockComponent()} + registerBranchStatus={jest.fn()} + {...props} + /> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/ReviewApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/ReviewApp-test.tsx.snap index 248765d7dee..a850b2f999b 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/ReviewApp-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/ReviewApp-test.tsx.snap @@ -1,5 +1,429 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`should correctly handle a WS failure 1`] = ` +<div + className="page page-limited" +> + <div + className="pr-overview" + > + <div + className="pr-overview-quality-gate big-spacer-right" + > + <h3 + className="spacer-bottom small" + > + overview.quality_gate + <DocTooltip + className="spacer-left" + doc={Promise {}} + /> + </h3> + <LargeQualityGateBadge + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "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="pr-overview-measurements flex-1" + > + <h3 + className="spacer-bottom small" + > + overview.metrics + </h3> + <div + className="pr-overview-measurements-row display-flex-row" + key="BUG" + > + <div + className="pr-overview-measurements-value flex-1 small display-flex-center" + > + <IssueLabel + branchLike={ + Object { + "analysisDate": "2018-01-01", + "base": "master", + "branch": "feature/foo/bar", + "key": "1001", + "title": "Foo Bar feature", + } + } + className="overview-domain-measure-value" + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "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 [], + } + } + measures={Array []} + type="BUG" + /> + </div> + <div + className="pr-overview-measurements-rating display-flex-center" + > + <IssueRating + branchLike={ + Object { + "analysisDate": "2018-01-01", + "base": "master", + "branch": "feature/foo/bar", + "key": "1001", + "title": "Foo Bar feature", + } + } + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "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 [], + } + } + measures={Array []} + type="BUG" + /> + </div> + </div> + <div + className="pr-overview-measurements-row display-flex-row" + key="VULNERABILITY" + > + <div + className="pr-overview-measurements-value flex-1 small display-flex-center" + > + <IssueLabel + branchLike={ + Object { + "analysisDate": "2018-01-01", + "base": "master", + "branch": "feature/foo/bar", + "key": "1001", + "title": "Foo Bar feature", + } + } + className="overview-domain-measure-value" + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "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 [], + } + } + measures={Array []} + type="VULNERABILITY" + /> + </div> + <div + className="pr-overview-measurements-rating display-flex-center" + > + <IssueRating + branchLike={ + Object { + "analysisDate": "2018-01-01", + "base": "master", + "branch": "feature/foo/bar", + "key": "1001", + "title": "Foo Bar feature", + } + } + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "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 [], + } + } + measures={Array []} + type="VULNERABILITY" + /> + </div> + </div> + <div + className="pr-overview-measurements-row display-flex-row" + key="CODE_SMELL" + > + <div + className="pr-overview-measurements-value flex-1 small display-flex-center" + > + <IssueLabel + branchLike={ + Object { + "analysisDate": "2018-01-01", + "base": "master", + "branch": "feature/foo/bar", + "key": "1001", + "title": "Foo Bar feature", + } + } + className="overview-domain-measure-value" + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "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 [], + } + } + measures={Array []} + type="CODE_SMELL" + /> + </div> + <div + className="pr-overview-measurements-rating display-flex-center" + > + <IssueRating + branchLike={ + Object { + "analysisDate": "2018-01-01", + "base": "master", + "branch": "feature/foo/bar", + "key": "1001", + "title": "Foo Bar feature", + } + } + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "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 [], + } + } + measures={Array []} + type="CODE_SMELL" + /> + </div> + </div> + <div + className="pr-overview-measurements-row display-flex-row" + key="COVERAGE" + > + <div + className="pr-overview-measurements-value flex-1 small display-flex-center" + > + <MeasurementLabel + branchLike={ + Object { + "analysisDate": "2018-01-01", + "base": "master", + "branch": "feature/foo/bar", + "key": "1001", + "title": "Foo Bar feature", + } + } + className="overview-domain-measure-value" + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "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 [], + } + } + measures={Array []} + type="COVERAGE" + /> + </div> + <div + className="pr-overview-measurements-estimate display-flex-center" + > + <AfterMergeEstimate + measures={Array []} + type="COVERAGE" + /> + </div> + </div> + <div + className="pr-overview-measurements-row display-flex-row" + key="DUPLICATION" + > + <div + className="pr-overview-measurements-value flex-1 small display-flex-center" + > + <MeasurementLabel + branchLike={ + Object { + "analysisDate": "2018-01-01", + "base": "master", + "branch": "feature/foo/bar", + "key": "1001", + "title": "Foo Bar feature", + } + } + className="overview-domain-measure-value" + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "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 [], + } + } + measures={Array []} + type="DUPLICATION" + /> + </div> + <div + className="pr-overview-measurements-estimate display-flex-center" + > + <AfterMergeEstimate + measures={Array []} + type="DUPLICATION" + /> + </div> + </div> + </div> + </div> +</div> +`; + exports[`should render correctly for a failed QG 1`] = ` <div className="page page-limited" |