aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-docs/package.json2
-rw-r--r--server/sonar-vsts/package.json2
-rw-r--r--server/sonar-web/package.json5
-rw-r--r--server/sonar-web/scripts/utils/getMessages.js3
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContainer.tsx29
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalFooter.tsx16
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/GlobalContainer-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx11
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx54
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx28
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx44
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx15
-rw-r--r--server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx24
-rw-r--r--server/sonar-web/src/main/js/app/components/search/Search.tsx57
-rw-r--r--server/sonar-web/src/main/js/apps/about/routes.ts4
-rw-r--r--server/sonar-web/src/main/js/apps/account/profile/Profile.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/Workers.tsx41
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/App.tsx50
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx55
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx41
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx20
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx47
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx61
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChange-test.tsx64
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetails-test.tsx98
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChange-test.tsx.snap99
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetails-test.tsx.snap204
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx97
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureContent-test.tsx96
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureContent-test.tsx.snap312
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx47
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx39
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx23
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx82
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx61
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/App.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/App.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/Item.tsx35
-rw-r--r--server/sonar-web/src/main/js/apps/documentation/utils.ts5
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/App.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx79
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/App.tsx50
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx65
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx19
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginChangeLog-test.tsx57
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginChangeLog-test.tsx.snap72
-rw-r--r--server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx61
-rw-r--r--server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx52
-rw-r--r--server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx49
-rw-r--r--server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx33
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx36
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/App-test.tsx134
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/App-test.tsx.snap136
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx51
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/App-test.tsx162
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/App-test.tsx.snap224
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx19
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/App.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/ReleasabilityBox.tsx34
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/actions.ts4
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityEventSelectOption-test.tsx37
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityEventSelectOption-test.tsx.snap19
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx30
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx27
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/utils.ts18
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx38
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx64
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx31
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/Definition.tsx33
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-test.tsx89
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Definition-test.tsx.snap91
-rw-r--r--server/sonar-web/src/main/js/apps/settings/encryption/EncryptionApp.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/PageActions.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/routes.ts5
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/utils.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UserGroups.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/components/Action.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/components/Params.tsx23
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/App.tsx7
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx13
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx15
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx27
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx97
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlay-test.tsx27
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap491
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.ts9
-rw-r--r--server/sonar-web/src/main/js/components/charts/LineChart.tsx12
-rw-r--r--server/sonar-web/src/main/js/components/common/MultiSelect.tsx8
-rw-r--r--server/sonar-web/src/main/js/components/controls/Tooltip.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/facet/ListStyleFacet.tsx26
-rw-r--r--server/sonar-web/src/main/js/components/facet/ListStyleFacetFooter.tsx11
-rw-r--r--server/sonar-web/src/main/js/components/issue/IssueView.tsx25
-rw-r--r--server/sonar-web/src/main/js/components/issue/__tests__/IssueView-test.tsx57
-rw-r--r--server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap137
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx15
-rw-r--r--server/sonar-web/src/main/js/components/nav/NavBar.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/workspace/Workspace.tsx4
-rw-r--r--server/sonar-web/src/main/js/helpers/testMocks.ts56
128 files changed, 3891 insertions, 1376 deletions
diff --git a/server/sonar-docs/package.json b/server/sonar-docs/package.json
index 0ff9c9a7f0e..f85cb95950c 100644
--- a/server/sonar-docs/package.json
+++ b/server/sonar-docs/package.json
@@ -47,7 +47,7 @@
"glob-promise": "3.4.0",
"graphql-code-generator": "0.5.2",
"jest": "24.5.0",
- "prettier": "1.16.0",
+ "prettier": "1.16.4",
"react-test-renderer": "16.8.5",
"remark": "10.0.1",
"ts-jest": "24.0.0",
diff --git a/server/sonar-vsts/package.json b/server/sonar-vsts/package.json
index cd3f7b93433..0b2d4c227c9 100644
--- a/server/sonar-vsts/package.json
+++ b/server/sonar-vsts/package.json
@@ -57,7 +57,7 @@
"postcss-calc": "7.0.1",
"postcss-custom-properties": "8.0.9",
"postcss-loader": "3.0.0",
- "prettier": "1.14.3",
+ "prettier": "1.16.4",
"react-dev-utils": "5.0.0",
"react-error-overlay": "1.0.7",
"react-test-renderer": "16.8.5",
diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json
index 62aa05a7824..a53eaeabce7 100644
--- a/server/sonar-web/package.json
+++ b/server/sonar-web/package.json
@@ -114,7 +114,7 @@
"postcss-calc": "7.0.1",
"postcss-custom-properties": "8.0.9",
"postcss-loader": "3.0.0",
- "prettier": "1.14.3",
+ "prettier": "1.16.4",
"raw-loader": "2.0.0",
"react-dev-utils": "5.0.1",
"react-error-overlay": "1.0.7",
@@ -158,7 +158,8 @@
"src/main/js/**/*.{ts,tsx,js}"
],
"coverageReporters": [
- "lcovonly"
+ "lcovonly",
+ "text"
],
"globals": {
"ts-jest": {
diff --git a/server/sonar-web/scripts/utils/getMessages.js b/server/sonar-web/scripts/utils/getMessages.js
index 261442ff178..9c2ccd5553a 100644
--- a/server/sonar-web/scripts/utils/getMessages.js
+++ b/server/sonar-web/scripts/utils/getMessages.js
@@ -28,7 +28,8 @@ const filename = '../../../../sonar-core/src/main/resources/org/sonar/l10n/core.
const extensionsFilenames = [
'../../../../private/core-extension-billing/src/main/resources/org/sonar/l10n/billing.properties',
'../../../../private/core-extension-governance/src/main/resources/org/sonar/l10n/governance.properties',
- '../../../../private/core-extension-license/src/main/resources/org/sonar/l10n/license.properties'
+ '../../../../private/core-extension-license/src/main/resources/org/sonar/l10n/license.properties',
+ '../../../../private/core-extension-developer-server/src/main/resources/org/sonar/l10n/developer-server.properties'
];
function getFileMessage(filename) {
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
index a0c57751631..b5294b59414 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
@@ -45,7 +45,7 @@ import { isSonarCloud } from '../../helpers/system';
import { withRouter, Router, Location } from '../../components/hoc/withRouter';
interface Props {
- children: React.ReactElement<any>;
+ children: React.ReactElement;
fetchOrganization: (organization: string) => void;
location: Pick<Location, 'query'>;
registerBranchStatus: (branchLike: T.BranchLike, component: string, status: T.Status) => void;
@@ -319,20 +319,19 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
return (
<div>
- {component &&
- !['FIL', 'UTS'].includes(component.qualifier) && (
- <ComponentNav
- branchLikes={branchLikes}
- component={component}
- currentBranchLike={branchLike}
- currentTask={currentTask}
- currentTaskOnSameBranch={currentTask && this.isSameBranch(currentTask, branchLike)}
- isInProgress={isInProgress}
- isPending={isPending}
- location={this.props.location}
- warnings={this.state.warnings}
- />
- )}
+ {component && !['FIL', 'UTS'].includes(component.qualifier) && (
+ <ComponentNav
+ branchLikes={branchLikes}
+ component={component}
+ currentBranchLike={branchLike}
+ currentTask={currentTask}
+ currentTaskOnSameBranch={currentTask && this.isSameBranch(currentTask, branchLike)}
+ isInProgress={isInProgress}
+ isPending={isPending}
+ location={this.props.location}
+ warnings={this.state.warnings}
+ />
+ )}
{loading ? (
<div className="page page-limited">
<i className="spinner" />
diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx b/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx
index 814f7fc1c66..56354293a3f 100644
--- a/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx
+++ b/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx
@@ -60,14 +60,14 @@ export default function GlobalFooter({
<GlobalFooterBranding />
<ul className="page-footer-menu">
- {!hideLoggedInInfo &&
- currentEdition && <li className="page-footer-menu-item">{currentEdition.name}</li>}
- {!hideLoggedInInfo &&
- sonarqubeVersion && (
- <li className="page-footer-menu-item">
- {translateWithParameters('footer.version_x', sonarqubeVersion)}
- </li>
- )}
+ {!hideLoggedInInfo && currentEdition && (
+ <li className="page-footer-menu-item">{currentEdition.name}</li>
+ )}
+ {!hideLoggedInInfo && sonarqubeVersion && (
+ <li className="page-footer-menu-item">
+ {translateWithParameters('footer.version_x', sonarqubeVersion)}
+ </li>
+ )}
<li className="page-footer-menu-item">
<a href="http://www.gnu.org/licenses/lgpl-3.0.txt">{translate('footer.license')}</a>
</li>
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/GlobalContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/GlobalContainer-test.tsx
index 3c7c7492e29..ff7aba0434b 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/GlobalContainer-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/GlobalContainer-test.tsx
@@ -28,7 +28,7 @@ jest.mock('../embed-docs-modal/SuggestionsProvider', () => {
return this.props.children;
}
}
-
+
return { default: SuggestionsProvider };
});
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
index aabed93c940..0905f0143ef 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
@@ -154,12 +154,11 @@ export class ComponentNavBranch extends React.PureComponent<Props, State> {
fill={theme.gray80}
/>
<span className="note">{displayName}</span>
- {configuration &&
- configuration.showSettings && (
- <HelpTooltip className="spacer-left" overlay={this.renderOverlay()}>
- <PlusCircleIcon className="vertical-middle" fill={theme.blue} size={12} />
- </HelpTooltip>
- )}
+ {configuration && configuration.showSettings && (
+ <HelpTooltip className="spacer-left" overlay={this.renderOverlay()}>
+ <PlusCircleIcon className="vertical-middle" fill={theme.blue} size={12} />
+ </HelpTooltip>
+ )}
</div>
);
} else {
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx
index 045c005f322..a2a793eeaf6 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx
@@ -53,38 +53,36 @@ export function ComponentNavHeader(props: Props) {
organization={organization && isSonarCloud() ? organization : undefined}
title={component.name}
/>
- {organization &&
- isSonarCloud() && (
- <>
- <OrganizationAvatar organization={organization} />
- <OrganizationLink
- className="navbar-context-header-breadcrumb-link link-base-color link-no-underline spacer-left"
- organization={organization}>
- {organization.name}
- </OrganizationLink>
- <span className="slash-separator" />
- </>
- )}
+ {organization && isSonarCloud() && (
+ <>
+ <OrganizationAvatar organization={organization} />
+ <OrganizationLink
+ className="navbar-context-header-breadcrumb-link link-base-color link-no-underline spacer-left"
+ organization={organization}>
+ {organization.name}
+ </OrganizationLink>
+ <span className="slash-separator" />
+ </>
+ )}
{renderBreadcrumbs(
component.breadcrumbs,
props.currentBranchLike !== undefined && !isMainBranch(props.currentBranchLike)
)}
- {isSonarCloud() &&
- component.alm && (
- <a
- className="link-no-underline"
- href={component.alm.url}
- rel="noopener noreferrer"
- target="_blank">
- <img
- alt={sanitizeAlmId(component.alm.key)}
- className="text-text-top spacer-left"
- height={16}
- src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId(component.alm.key)}.svg`}
- width={16}
- />
- </a>
- )}
+ {isSonarCloud() && component.alm && (
+ <a
+ className="link-no-underline"
+ href={component.alm.url}
+ rel="noopener noreferrer"
+ target="_blank">
+ <img
+ alt={sanitizeAlmId(component.alm.key)}
+ className="text-text-top spacer-left"
+ height={16}
+ src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId(component.alm.key)}.svg`}
+ width={16}
+ />
+ </a>
+ )}
{props.currentBranchLike && (
<ComponentNavBranch
branchLikes={props.branchLikes}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
index f72338ef2e8..08ca22507ba 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
@@ -76,25 +76,23 @@ export function ComponentNavMeta({ branchLike, component, currentUser, warnings
qualifier={component.qualifier}
/>
)}
- {(mainBranch || longBranch) &&
- currentPage !== undefined && (
- <HomePageSelect className="spacer-left" currentPage={currentPage} />
- )}
+ {(mainBranch || longBranch) && currentPage !== undefined && (
+ <HomePageSelect className="spacer-left" currentPage={currentPage} />
+ )}
</div>
)}
{(isShortLivingBranch(branchLike) || isPullRequest(branchLike)) && (
<div className="navbar-context-meta-secondary display-inline-flex-center">
- {isPullRequest(branchLike) &&
- branchLike.url !== undefined && (
- <a
- className="display-inline-flex-center big-spacer-right"
- href={branchLike.url}
- rel="noopener noreferrer"
- target="_blank">
- {translate('branches.see_the_pr')}
- <DetachIcon className="little-spacer-left" size={12} />
- </a>
- )}
+ {isPullRequest(branchLike) && branchLike.url !== undefined && (
+ <a
+ className="display-inline-flex-center big-spacer-right"
+ href={branchLike.url}
+ rel="noopener noreferrer"
+ target="_blank">
+ {translate('branches.see_the_pr')}
+ <DetachIcon className="little-spacer-left" size={12} />
+ </a>
+ )}
<BranchStatus branchLike={branchLike} component={component.key} />
</div>
)}
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx
index 958a537094f..d106c1236d6 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx
@@ -180,17 +180,15 @@ export class GlobalNav extends React.PureComponent<Props, State> {
<GlobalNavMenu {...this.props} />
<ul className="global-navbar-menu global-navbar-menu-right">
- {isSonarCloud() &&
- isLoggedIn(currentUser) &&
- news.length > 0 && (
- <NavLatestNotification
- lastNews={news[0]}
- notificationsLastReadDate={this.props.notificationsLastReadDate}
- notificationsOptOut={this.props.notificationsOptOut}
- onClick={this.handleOpenNotificationSidebar}
- setCurrentUserSetting={this.props.setCurrentUserSetting}
- />
- )}
+ {isSonarCloud() && isLoggedIn(currentUser) && news.length > 0 && (
+ <NavLatestNotification
+ lastNews={news[0]}
+ notificationsLastReadDate={this.props.notificationsLastReadDate}
+ notificationsOptOut={this.props.notificationsOptOut}
+ onClick={this.handleOpenNotificationSidebar}
+ setCurrentUserSetting={this.props.setCurrentUserSetting}
+ />
+ )}
{isSonarCloud() && <GlobalNavExplore location={this.props.location} />}
<EmbedDocsPopupHelper />
<Search appState={appState} currentUser={currentUser} />
@@ -207,19 +205,17 @@ export class GlobalNav extends React.PureComponent<Props, State> {
)}
<GlobalNavUserContainer appState={appState} currentUser={currentUser} />
</ul>
- {isSonarCloud() &&
- isLoggedIn(currentUser) &&
- this.state.notificationSidebar && (
- <NotificationsSidebar
- fetchMoreFeatureNews={this.fetchMoreFeatureNews}
- loading={this.state.loadingNews}
- loadingMore={this.state.loadingMoreNews}
- news={news}
- notificationsLastReadDate={this.props.notificationsLastReadDate}
- onClose={this.handleCloseNotificationSidebar}
- paging={this.state.newsPaging}
- />
- )}
+ {isSonarCloud() && isLoggedIn(currentUser) && this.state.notificationSidebar && (
+ <NotificationsSidebar
+ fetchMoreFeatureNews={this.fetchMoreFeatureNews}
+ loading={this.state.loadingNews}
+ loadingMore={this.state.loadingMoreNews}
+ news={news}
+ notificationsLastReadDate={this.props.notificationsLastReadDate}
+ onClose={this.handleCloseNotificationSidebar}
+ paging={this.state.newsPaging}
+ />
+ )}
</NavBar>
);
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx
index b8a7c30bffb..01401510728 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx
@@ -188,14 +188,13 @@ export class GlobalNavPlus extends React.PureComponent<Props & WithRouterProps,
<PlusIcon />
</a>
</Dropdown>
- {this.state.governanceReady &&
- this.state.createPortfolio && (
- <CreateFormShim
- defaultQualifier={defaultQualifier}
- onClose={this.closeCreatePortfolioForm}
- onCreate={this.handleCreatePortfolio}
- />
- )}
+ {this.state.governanceReady && this.state.createPortfolio && (
+ <CreateFormShim
+ defaultQualifier={defaultQualifier}
+ onClose={this.closeCreatePortfolioForm}
+ onCreate={this.handleCreatePortfolio}
+ />
+ )}
</>
);
}
diff --git a/server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx b/server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx
index 648cafe3b77..b577dd086b0 100644
--- a/server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx
+++ b/server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx
@@ -63,20 +63,18 @@ export default function NotificationsSidebar(props: Props) {
))
)}
</div>
- {!loading &&
- paging &&
- paging.total > news.length && (
- <div className="notifications-sidebar-footer">
- <div className="spacer-top note text-center">
- <a className="spacer-left" href="#" onClick={props.fetchMoreFeatureNews}>
- {translate('show_more')}
- </a>
- {loadingMore && (
- <DeferredSpinner className="vertical-bottom spacer-left position-absolute" />
- )}
- </div>
+ {!loading && paging && paging.total > news.length && (
+ <div className="notifications-sidebar-footer">
+ <div className="spacer-top note text-center">
+ <a className="spacer-left" href="#" onClick={props.fetchMoreFeatureNews}>
+ {translate('show_more')}
+ </a>
+ {loadingMore && (
+ <DeferredSpinner className="vertical-bottom spacer-left position-absolute" />
+ )}
</div>
- )}
+ </div>
+ )}
</div>
</Modal>
);
diff --git a/server/sonar-web/src/main/js/app/components/search/Search.tsx b/server/sonar-web/src/main/js/app/components/search/Search.tsx
index 9d358998335..299dacc1f58 100644
--- a/server/sonar-web/src/main/js/app/components/search/Search.tsx
+++ b/server/sonar-web/src/main/js/app/components/search/Search.tsx
@@ -376,37 +376,36 @@ export class Search extends React.PureComponent<Props, State> {
</span>
)}
- {this.state.open &&
- Object.keys(this.state.results).length > 0 && (
- <DropdownOverlay noPadding={true}>
- <div className="global-navbar-search-dropdown" ref={node => (this.node = node)}>
- <SearchResults
- allowMore={this.state.query.length !== 1}
- loadingMore={this.state.loadingMore}
- more={this.state.more}
- onMoreClick={this.searchMore}
- onSelect={this.handleSelect}
- renderNoResults={this.renderNoResults}
- renderResult={this.renderResult}
- results={this.state.results}
- selected={this.state.selected}
- />
- <div className="dropdown-bottom-hint">
- <div className="pull-right">
- <ClockIcon className="little-spacer-right" size={12} />
- {translate('recently_browsed')}
- </div>
- <FormattedMessage
- defaultMessage={translate('search.shortcut_hint')}
- id="search.shortcut_hint"
- values={{
- shortcut: <span className="shortcut-button shortcut-button-small">s</span>
- }}
- />
+ {this.state.open && Object.keys(this.state.results).length > 0 && (
+ <DropdownOverlay noPadding={true}>
+ <div className="global-navbar-search-dropdown" ref={node => (this.node = node)}>
+ <SearchResults
+ allowMore={this.state.query.length !== 1}
+ loadingMore={this.state.loadingMore}
+ more={this.state.more}
+ onMoreClick={this.searchMore}
+ onSelect={this.handleSelect}
+ renderNoResults={this.renderNoResults}
+ renderResult={this.renderResult}
+ results={this.state.results}
+ selected={this.state.selected}
+ />
+ <div className="dropdown-bottom-hint">
+ <div className="pull-right">
+ <ClockIcon className="little-spacer-right" size={12} />
+ {translate('recently_browsed')}
</div>
+ <FormattedMessage
+ defaultMessage={translate('search.shortcut_hint')}
+ id="search.shortcut_hint"
+ values={{
+ shortcut: <span className="shortcut-button shortcut-button-small">s</span>
+ }}
+ />
</div>
- </DropdownOverlay>
- )}
+ </div>
+ </DropdownOverlay>
+ )}
</li>
);
diff --git a/server/sonar-web/src/main/js/apps/about/routes.ts b/server/sonar-web/src/main/js/apps/about/routes.ts
index ab744f1bec6..b0eba5f6853 100644
--- a/server/sonar-web/src/main/js/apps/about/routes.ts
+++ b/server/sonar-web/src/main/js/apps/about/routes.ts
@@ -23,8 +23,8 @@ import { isSonarCloud } from '../../helpers/system';
const routes = [
{
indexRoute: {
- component: lazyLoad(
- () => (isSonarCloud() ? import('./sonarcloud/Home') : import('./components/AboutApp'))
+ component: lazyLoad(() =>
+ isSonarCloud() ? import('./sonarcloud/Home') : import('./components/AboutApp')
)
},
childRoutes: isSonarCloud()
diff --git a/server/sonar-web/src/main/js/apps/account/profile/Profile.tsx b/server/sonar-web/src/main/js/apps/account/profile/Profile.tsx
index 079826ff6de..000c9dd86cb 100644
--- a/server/sonar-web/src/main/js/apps/account/profile/Profile.tsx
+++ b/server/sonar-web/src/main/js/apps/account/profile/Profile.tsx
@@ -38,12 +38,11 @@ function Profile({ customOrganizations, user }: Props) {
{translate('login')}: <strong id="login">{user.login}</strong>
</div>
- {!user.local &&
- user.externalProvider !== 'sonarqube' && (
- <div className="spacer-bottom" id="identity-provider">
- <UserExternalIdentity user={user} />
- </div>
- )}
+ {!user.local && user.externalProvider !== 'sonarqube' && (
+ <div className="spacer-bottom" id="identity-provider">
+ <UserExternalIdentity user={user} />
+ </div>
+ )}
{!!user.email && (
<div className="spacer-bottom">
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx
index db2f74440d6..4b154caabde 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx
@@ -110,15 +110,14 @@ export default class TaskActions extends React.PureComponent<Props, State> {
return (
<td className="thin nowrap">
<ActionsDropdown className="js-task-action">
- {canFilter &&
- task.componentName && (
- <ActionsDropdownItem className="js-task-filter" onClick={this.handleFilterClick}>
- {translateWithParameters(
- 'background_tasks.filter_by_component_x',
- task.componentName
- )}
- </ActionsDropdownItem>
- )}
+ {canFilter && task.componentName && (
+ <ActionsDropdownItem className="js-task-filter" onClick={this.handleFilterClick}>
+ {translateWithParameters(
+ 'background_tasks.filter_by_component_x',
+ task.componentName
+ )}
+ </ActionsDropdownItem>
+ )}
{canCancel && (
<ActionsDropdownItem
className="js-task-cancel"
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx
index da727a0c7ab..98db6667d97 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx
@@ -52,13 +52,11 @@ export default function TaskComponent({ task }: Props) {
{task.branchType === 'LONG' && <LongLivingBranchIcon className="little-spacer-right" />}
{task.pullRequest !== undefined && <PullRequestIcon className="little-spacer-right" />}
- {!task.branchType &&
- !task.pullRequest &&
- task.componentQualifier && (
- <span className="little-spacer-right">
- <QualifierIcon qualifier={task.componentQualifier} />
- </span>
- )}
+ {!task.branchType && !task.pullRequest && task.componentQualifier && (
+ <span className="little-spacer-right">
+ <QualifierIcon qualifier={task.componentQualifier} />
+ </span>
+ )}
{task.organization && <Organization organizationKey={task.organization} />}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Workers.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/Workers.tsx
index a1146103f79..eff62fff02a 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/Workers.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Workers.tsx
@@ -102,14 +102,13 @@ export default class Workers extends React.PureComponent<{}, State> {
return (
<div className="display-flex-center">
- {!loading &&
- workerCount > 1 && (
- <Tooltip overlay={translate('background_tasks.number_of_workers.warning')}>
- <span className="display-inline-flex-center little-spacer-right">
- <AlertWarnIcon fill="#d3d3d3" />
- </span>
- </Tooltip>
- )}
+ {!loading && workerCount > 1 && (
+ <Tooltip overlay={translate('background_tasks.number_of_workers.warning')}>
+ <span className="display-inline-flex-center little-spacer-right">
+ <AlertWarnIcon fill="#d3d3d3" />
+ </span>
+ </Tooltip>
+ )}
<span className="text-middle">
{translate('background_tasks.number_of_workers')}
@@ -121,20 +120,18 @@ export default class Workers extends React.PureComponent<{}, State> {
)}
</span>
- {!loading &&
- canSetWorkerCount && (
- <Tooltip overlay={translate('background_tasks.change_number_of_workers')}>
- <EditButton
- className="js-edit button-small spacer-left"
- onClick={this.handleChangeClick}
- />
- </Tooltip>
- )}
-
- {!loading &&
- !canSetWorkerCount && (
- <HelpTooltip className="spacer-left" overlay={<NoWorkersSupportPopup />} />
- )}
+ {!loading && canSetWorkerCount && (
+ <Tooltip overlay={translate('background_tasks.change_number_of_workers')}>
+ <EditButton
+ className="js-edit button-small spacer-left"
+ onClick={this.handleChangeClick}
+ />
+ </Tooltip>
+ )}
+
+ {!loading && !canSetWorkerCount && (
+ <HelpTooltip className="spacer-left" overlay={<NoWorkersSupportPopup />} />
+ )}
{formOpen && <WorkersForm onClose={this.closeForm} workerCount={this.state.workerCount} />}
</div>
diff --git a/server/sonar-web/src/main/js/apps/code/components/App.tsx b/server/sonar-web/src/main/js/apps/code/components/App.tsx
index 1c030f377a2..48efaf6c677 100644
--- a/server/sonar-web/src/main/js/apps/code/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/App.tsx
@@ -290,33 +290,31 @@ export class App extends React.PureComponent<Props, State> {
</>
)}
- {showSearch &&
- searchResults && (
- <div className={componentsClassName}>
- <Components
- branchLike={this.props.branchLike}
- components={searchResults}
- metrics={{}}
- onHighlight={this.handleHighlight}
- onSelect={this.handleSelect}
- rootComponent={component}
- selected={highlighted}
- />
- </div>
- )}
+ {showSearch && searchResults && (
+ <div className={componentsClassName}>
+ <Components
+ branchLike={this.props.branchLike}
+ components={searchResults}
+ metrics={{}}
+ onHighlight={this.handleHighlight}
+ onSelect={this.handleSelect}
+ rootComponent={component}
+ selected={highlighted}
+ />
+ </div>
+ )}
- {sourceViewer !== undefined &&
- !showSearch && (
- <div className="spacer-top">
- <SourceViewerWrapper
- branchLike={branchLike}
- component={sourceViewer.key}
- isFile={true}
- location={location}
- onGoToParent={this.handleGoToParent}
- />
- </div>
- )}
+ {sourceViewer !== undefined && !showSearch && (
+ <div className="spacer-top">
+ <SourceViewerWrapper
+ branchLike={branchLike}
+ component={sourceViewer.key}
+ isFile={true}
+ location={location}
+ onGoToParent={this.handleGoToParent}
+ />
+ </div>
+ )}
</div>
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx
index d5264e69c5d..ac5b6325bb9 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx
@@ -160,10 +160,9 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat
</div>
<div className="modal-body">
- {!isUpdateMode &&
- activeInAllProfiles && (
- <Alert variant="info">{translate('coding_rules.active_in_all_profiles')}</Alert>
- )}
+ {!isUpdateMode && activeInAllProfiles && (
+ <Alert variant="info">{translate('coding_rules.active_in_all_profiles')}</Alert>
+ )}
<div className="modal-field">
<label>{translate('coding_rules.quality_profile')}</label>
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx
index 508c9bfc621..4a7fe801eb5 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx
@@ -101,44 +101,41 @@ export default class BulkChange extends React.PureComponent<Props, State> {
{translate('coding_rules.activate_in')}…
</a>
</li>
- {allowActivateOnProfile &&
- profile && (
- <li>
- <a href="#" onClick={this.handleActivateInProfileClick}>
- {translate('coding_rules.activate_in')} <strong>{profile.name}</strong>
- </a>
- </li>
- )}
+ {allowActivateOnProfile && profile && (
+ <li>
+ <a href="#" onClick={this.handleActivateInProfileClick}>
+ {translate('coding_rules.activate_in')} <strong>{profile.name}</strong>
+ </a>
+ </li>
+ )}
<li>
<a href="#" onClick={this.handleDeactivateClick}>
{translate('coding_rules.deactivate_in')}…
</a>
</li>
- {allowDeactivateOnProfile &&
- profile && (
- <li>
- <a href="#" onClick={this.handleDeactivateInProfileClick}>
- {translate('coding_rules.deactivate_in')} <strong>{profile.name}</strong>
- </a>
- </li>
- )}
+ {allowDeactivateOnProfile && profile && (
+ <li>
+ <a href="#" onClick={this.handleDeactivateInProfileClick}>
+ {translate('coding_rules.deactivate_in')} <strong>{profile.name}</strong>
+ </a>
+ </li>
+ )}
</ul>
}>
<Button className="js-bulk-change">{translate('bulk_change')}</Button>
</Dropdown>
- {this.state.modal &&
- this.state.action && (
- <BulkChangeModal
- action={this.state.action}
- languages={this.props.languages}
- onClose={this.closeModal}
- organization={this.props.organization}
- profile={this.state.profile}
- query={this.props.query}
- referencedProfiles={this.props.referencedProfiles}
- total={this.props.total}
- />
- )}
+ {this.state.modal && this.state.action && (
+ <BulkChangeModal
+ action={this.state.action}
+ languages={this.props.languages}
+ onClose={this.closeModal}
+ organization={this.props.organization}
+ profile={this.state.profile}
+ query={this.props.query}
+ referencedProfiles={this.props.referencedProfiles}
+ total={this.props.total}
+ />
+ )}
</>
);
}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx
index a7df07d3130..922c6bb563c 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx
@@ -207,27 +207,26 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> {
<div className="modal-body">
{this.state.results.map(this.renderResult)}
- {!this.state.finished &&
- !this.state.submitting && (
- <div className="modal-field">
- <h3>
- <label htmlFor="coding-rules-bulk-change-profile">
- {action === 'activate'
- ? translate('coding_rules.activate_in')
- : translate('coding_rules.deactivate_in')}
- </label>
- </h3>
- {profile ? (
- <span>
- {profile.name}
- {' — '}
- {translate('are_you_sure')}
- </span>
- ) : (
- this.renderProfileSelect()
- )}
- </div>
- )}
+ {!this.state.finished && !this.state.submitting && (
+ <div className="modal-field">
+ <h3>
+ <label htmlFor="coding-rules-bulk-change-profile">
+ {action === 'activate'
+ ? translate('coding_rules.activate_in')
+ : translate('coding_rules.deactivate_in')}
+ </label>
+ </h3>
+ {profile ? (
+ <span>
+ {profile.name}
+ {' — '}
+ {translate('are_you_sure')}
+ </span>
+ ) : (
+ this.renderProfileSelect()
+ )}
+ </div>
+ )}
</div>
<footer className="modal-foot">
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx
index a39e77d2398..2a766236a32 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx
@@ -118,8 +118,9 @@ export default class Facet extends React.PureComponent<Props> {
{this.props.children}
</FacetHeader>
- {this.props.open &&
- items !== undefined && <FacetItemsList>{items.map(this.renderItem)}</FacetItemsList>}
+ {this.props.open && items !== undefined && (
+ <FacetItemsList>{items.map(this.renderItem)}</FacetItemsList>
+ )}
{this.props.open && this.props.renderFooter !== undefined && this.props.renderFooter()}
</FacetBox>
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx
index b56478416fe..a2a66f8050a 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx
@@ -103,8 +103,8 @@ export default class RuleDetails extends React.PureComponent<Props, State> {
handleTagsChange = (tags: string[]) => {
// optimistic update
const oldTags = this.state.ruleDetails && this.state.ruleDetails.tags;
- this.setState(
- state => (state.ruleDetails ? { ruleDetails: { ...state.ruleDetails, tags } } : null)
+ this.setState(state =>
+ state.ruleDetails ? { ruleDetails: { ...state.ruleDetails, tags } } : null
);
updateRule({
key: this.props.ruleKey,
@@ -112,9 +112,8 @@ export default class RuleDetails extends React.PureComponent<Props, State> {
tags: tags.join()
}).catch(() => {
if (this.mounted) {
- this.setState(
- state =>
- state.ruleDetails ? { ruleDetails: { ...state.ruleDetails, tags: oldTags } } : null
+ this.setState(state =>
+ state.ruleDetails ? { ruleDetails: { ...state.ruleDetails, tags: oldTags } } : null
);
}
});
@@ -239,18 +238,17 @@ export default class RuleDetails extends React.PureComponent<Props, State> {
/>
)}
- {!ruleDetails.isTemplate &&
- !hideQualityProfiles && (
- <RuleDetailsProfiles
- activations={this.state.actives}
- canWrite={canWrite}
- onActivate={this.handleActivate}
- onDeactivate={this.handleDeactivate}
- organization={organization}
- referencedProfiles={referencedProfiles}
- ruleDetails={ruleDetails}
- />
- )}
+ {!ruleDetails.isTemplate && !hideQualityProfiles && (
+ <RuleDetailsProfiles
+ activations={this.state.actives}
+ canWrite={canWrite}
+ onActivate={this.handleActivate}
+ onDeactivate={this.handleDeactivate}
+ organization={organization}
+ referencedProfiles={referencedProfiles}
+ ruleDetails={ruleDetails}
+ />
+ )}
{!ruleDetails.isTemplate && (
<RuleDetailsIssues organization={organization} ruleDetails={ruleDetails} />
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx
index 966a96ca51c..c0aceb9a116 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx
@@ -109,15 +109,17 @@ export default class RuleDetailsCustomRules extends React.PureComponent<Props, S
<td className="coding-rules-detail-list-parameters">
{rule.params &&
- rule.params.filter(param => param.defaultValue).map(param => (
- <div className="coding-rules-detail-list-parameter" key={param.key}>
- <span className="key">{param.key}</span>
- <span className="sep">:&nbsp;</span>
- <span className="value" title={param.defaultValue}>
- {param.defaultValue}
- </span>
- </div>
- ))}
+ rule.params
+ .filter(param => param.defaultValue)
+ .map(param => (
+ <div className="coding-rules-detail-list-parameter" key={param.key}>
+ <span className="key">{param.key}</span>
+ <span className="sep">:&nbsp;</span>
+ <span className="value" title={param.defaultValue}>
+ {param.defaultValue}
+ </span>
+ </div>
+ ))}
</td>
{this.props.canChange && (
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx
index f112a6edd95..58dae28f955 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx
@@ -42,30 +42,7 @@ interface Props {
ruleDetails: T.RuleDetails;
}
-interface State {
- loading: boolean;
-}
-
-export default class RuleDetailsProfiles extends React.PureComponent<Props, State> {
- mounted = false;
-
- componentDidMount() {
- this.mounted = true;
- this.fetchProfiles();
- }
-
- componentDidUpdate(prevProps: Props) {
- if (prevProps.ruleDetails.key !== this.props.ruleDetails.key) {
- this.fetchProfiles();
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- fetchProfiles = () => this.setState({ loading: true });
-
+export default class RuleDetailsProfiles extends React.PureComponent<Props> {
handleActivate = () => this.props.onActivate();
handleDeactivate = (key?: string) => {
@@ -119,12 +96,11 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props, Stat
<SeverityHelper className="display-inline-flex-center" severity={activation.severity} />
</span>
</Tooltip>
- {parentActivation !== undefined &&
- activation.severity !== parentActivation.severity && (
- <div className="coding-rules-detail-quality-profile-inheritance">
- {translate('coding_rules.original')} {translate('severity', parentActivation.severity)}
- </div>
- )}
+ {parentActivation !== undefined && activation.severity !== parentActivation.severity && (
+ <div className="coding-rules-detail-quality-profile-inheritance">
+ {translate('coding_rules.original')} {translate('severity', parentActivation.severity)}
+ </div>
+ )}
</td>
);
@@ -143,12 +119,11 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props, Stat
<span className="value" title={param.value}>
{param.value}
</span>
- {parentActivation &&
- param.value !== originalValue && (
- <div className="coding-rules-detail-quality-profile-inheritance">
- {translate('coding_rules.original')} <span className="value">{originalValue}</span>
- </div>
- )}
+ {parentActivation && param.value !== originalValue && (
+ <div className="coding-rules-detail-quality-profile-inheritance">
+ {translate('coding_rules.original')} <span className="value">{originalValue}</span>
+ </div>
+ )}
</div>
);
};
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx
index b48cbaa6d9c..faa9b262d8b 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx
@@ -89,37 +89,36 @@ export default class RuleListItem extends React.PureComponent<Props> {
return (
<td className="coding-rule-table-meta-cell coding-rule-activation">
<SeverityIcon severity={activation.severity} />
- {selectedProfile &&
- selectedProfile.parentName && (
- <>
- {activation.inherit === 'OVERRIDES' && (
- <Tooltip
- overlay={translateWithParameters(
- 'coding_rules.overrides',
- selectedProfile.name,
- selectedProfile.parentName
- )}>
- <RuleInheritanceIcon
- className="little-spacer-left"
- inheritance={activation.inherit}
- />
- </Tooltip>
- )}
- {activation.inherit === 'INHERITED' && (
- <Tooltip
- overlay={translateWithParameters(
- 'coding_rules.inherits',
- selectedProfile.name,
- selectedProfile.parentName
- )}>
- <RuleInheritanceIcon
- className="little-spacer-left"
- inheritance={activation.inherit}
- />
- </Tooltip>
- )}
- </>
- )}
+ {selectedProfile && selectedProfile.parentName && (
+ <>
+ {activation.inherit === 'OVERRIDES' && (
+ <Tooltip
+ overlay={translateWithParameters(
+ 'coding_rules.overrides',
+ selectedProfile.name,
+ selectedProfile.parentName
+ )}>
+ <RuleInheritanceIcon
+ className="little-spacer-left"
+ inheritance={activation.inherit}
+ />
+ </Tooltip>
+ )}
+ {activation.inherit === 'INHERITED' && (
+ <Tooltip
+ overlay={translateWithParameters(
+ 'coding_rules.inherits',
+ selectedProfile.name,
+ selectedProfile.parentName
+ )}>
+ <RuleInheritanceIcon
+ className="little-spacer-left"
+ inheritance={activation.inherit}
+ />
+ </Tooltip>
+ )}
+ </>
+ )}
</td>
);
};
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChange-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChange-test.tsx
new file mode 100644
index 00000000000..1a22fdf85b2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChange-test.tsx
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import BulkChange from '../BulkChange';
+import { mockEvent, mockQualityProfile } from '../../../../helpers/testMocks';
+
+const profile = mockQualityProfile({
+ actions: {
+ edit: true,
+ setAsDefault: true,
+ copy: true,
+ associateProjects: true,
+ delete: false
+ }
+});
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should not render anything', () => {
+ const wrapper = shallowRender({
+ referencedProfiles: { key: { ...profile, actions: { ...profile.actions, edit: false } } }
+ });
+ expect(wrapper.type()).toBeNull();
+});
+
+it('should display BulkChangeModal', () => {
+ const wrapper = shallowRender();
+ wrapper.instance().handleActivateClick(mockEvent());
+ expect(wrapper.find('BulkChangeModal')).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<BulkChange['props']> = {}) {
+ return shallow<BulkChange>(
+ <BulkChange
+ languages={{ js: { key: 'js', name: 'JavaScript' } }}
+ organization={undefined}
+ query={{ activation: false, profile: 'key' } as BulkChange['props']['query']}
+ referencedProfiles={{ key: profile }}
+ total={2}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetails-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetails-test.tsx
new file mode 100644
index 00000000000..3d2baaff55f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetails-test.tsx
@@ -0,0 +1,98 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import RuleDetails from '../RuleDetails';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { updateRule } from '../../../../api/rules';
+
+jest.mock('../../../../api/rules', () => ({
+ deleteRule: jest.fn(),
+ getRuleDetails: jest.fn().mockResolvedValue({
+ rule: getMockHelpers().mockRuleDetails(),
+ actives: [
+ {
+ qProfile: 'key',
+ inherit: 'NONE',
+ severity: 'MAJOR',
+ params: [],
+ createdAt: '2017-06-16T16:13:38+0200',
+ updatedAt: '2017-06-16T16:13:38+0200'
+ }
+ ]
+ }),
+ updateRule: jest.fn().mockResolvedValue({})
+}));
+
+const { mockQualityProfile } = getMockHelpers();
+const profile = mockQualityProfile();
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should correctly handle tag changes', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ wrapper.instance().handleTagsChange(['foo', 'bar']);
+ const ruleDetails = wrapper.state('ruleDetails');
+ expect(ruleDetails && ruleDetails.tags).toEqual(['foo', 'bar']);
+ await waitAndUpdate(wrapper);
+ expect(updateRule).toHaveBeenCalledWith({
+ key: 'squid:S1337',
+ organization: undefined,
+ tags: 'foo,bar'
+ });
+});
+
+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
+ // a mock helper in a Jest mock, we have to require it like this. Otherwise,
+ // we get errors like:
+ // ReferenceError: testMocks_1 is not defined
+ return require.requireActual('../../../../helpers/testMocks');
+}
+
+function shallowRender(props: Partial<RuleDetails['props']> = {}) {
+ return shallow<RuleDetails>(
+ <RuleDetails
+ onActivate={jest.fn()}
+ onDeactivate={jest.fn()}
+ onDelete={jest.fn()}
+ onFilterChange={jest.fn()}
+ organization={undefined}
+ referencedProfiles={{ key: profile }}
+ referencedRepositories={{
+ javascript: { key: 'javascript', language: 'js', name: 'SonarAnalyzer' }
+ }}
+ ruleKey="squid:S1337"
+ selectedProfile={profile}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChange-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChange-test.tsx.snap
new file mode 100644
index 00000000000..2cc9fb4b0d5
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChange-test.tsx.snap
@@ -0,0 +1,99 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display BulkChangeModal 1`] = `
+<BulkChangeModal
+ action="activate"
+ languages={
+ Object {
+ "js": Object {
+ "key": "js",
+ "name": "JavaScript",
+ },
+ }
+ }
+ onClose={[Function]}
+ query={
+ Object {
+ "activation": false,
+ "profile": "key",
+ }
+ }
+ referencedProfiles={
+ Object {
+ "key": Object {
+ "actions": Object {
+ "associateProjects": true,
+ "copy": true,
+ "delete": false,
+ "edit": true,
+ "setAsDefault": true,
+ },
+ "activeDeprecatedRuleCount": 2,
+ "activeRuleCount": 10,
+ "childrenCount": 0,
+ "depth": 1,
+ "isBuiltIn": false,
+ "isDefault": false,
+ "isInherited": false,
+ "key": "key",
+ "language": "js",
+ "languageName": "JavaScript",
+ "name": "name",
+ "organization": "foo",
+ "projectCount": 3,
+ },
+ }
+ }
+ total={2}
+/>
+`;
+
+exports[`should render correctly 1`] = `
+<Fragment>
+ <Dropdown
+ className="pull-left"
+ overlay={
+ <ul
+ className="menu"
+ >
+ <li>
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ coding_rules.activate_in
+ …
+ </a>
+ </li>
+ <li>
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ coding_rules.activate_in
+
+ <strong>
+ name
+ </strong>
+ </a>
+ </li>
+ <li>
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ coding_rules.deactivate_in
+ …
+ </a>
+ </li>
+ </ul>
+ }
+ >
+ <Button
+ className="js-bulk-change"
+ >
+ bulk_change
+ </Button>
+ </Dropdown>
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetails-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetails-test.tsx.snap
new file mode 100644
index 00000000000..46c661b1a68
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetails-test.tsx.snap
@@ -0,0 +1,204 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="coding-rule-details"
+/>
+`;
+
+exports[`should render correctly 2`] = `
+<div
+ className="coding-rule-details"
+>
+ <DeferredSpinner
+ loading={false}
+ timeout={100}
+ >
+ <RuleDetailsMeta
+ onFilterChange={[MockFunction]}
+ onTagsChange={[Function]}
+ referencedRepositories={
+ Object {
+ "javascript": Object {
+ "key": "javascript",
+ "language": "js",
+ "name": "SonarAnalyzer",
+ },
+ }
+ }
+ ruleDetails={
+ Object {
+ "createdAt": "2014-12-16T17:26:54+0100",
+ "debtOverloaded": false,
+ "debtRemFnOffset": "5min",
+ "debtRemFnType": "CONSTANT_ISSUE",
+ "defaultDebtRemFnOffset": "5min",
+ "defaultDebtRemFnType": "CONSTANT_ISSUE",
+ "defaultRemFnBaseEffort": "5min",
+ "defaultRemFnType": "CONSTANT_ISSUE",
+ "htmlDesc": "",
+ "isExternal": false,
+ "isTemplate": false,
+ "key": "squid:S1337",
+ "lang": "java",
+ "langName": "Java",
+ "mdDesc": "",
+ "name": "\\".equals()\\" should not be used to test the values of \\"Atomic\\" classes",
+ "params": Array [],
+ "remFnBaseEffort": "5min",
+ "remFnOverloaded": false,
+ "remFnType": "CONSTANT_ISSUE",
+ "repo": "squid",
+ "scope": "MAIN",
+ "severity": "MAJOR",
+ "status": "READY",
+ "sysTags": Array [
+ "multi-threading",
+ ],
+ "tags": Array [],
+ "type": "BUG",
+ }
+ }
+ />
+ <RuleDetailsDescription
+ onChange={[Function]}
+ ruleDetails={
+ Object {
+ "createdAt": "2014-12-16T17:26:54+0100",
+ "debtOverloaded": false,
+ "debtRemFnOffset": "5min",
+ "debtRemFnType": "CONSTANT_ISSUE",
+ "defaultDebtRemFnOffset": "5min",
+ "defaultDebtRemFnType": "CONSTANT_ISSUE",
+ "defaultRemFnBaseEffort": "5min",
+ "defaultRemFnType": "CONSTANT_ISSUE",
+ "htmlDesc": "",
+ "isExternal": false,
+ "isTemplate": false,
+ "key": "squid:S1337",
+ "lang": "java",
+ "langName": "Java",
+ "mdDesc": "",
+ "name": "\\".equals()\\" should not be used to test the values of \\"Atomic\\" classes",
+ "params": Array [],
+ "remFnBaseEffort": "5min",
+ "remFnOverloaded": false,
+ "remFnType": "CONSTANT_ISSUE",
+ "repo": "squid",
+ "scope": "MAIN",
+ "severity": "MAJOR",
+ "status": "READY",
+ "sysTags": Array [
+ "multi-threading",
+ ],
+ "tags": Array [],
+ "type": "BUG",
+ }
+ }
+ />
+ <RuleDetailsProfiles
+ activations={
+ Array [
+ Object {
+ "createdAt": "2017-06-16T16:13:38+0200",
+ "inherit": "NONE",
+ "params": Array [],
+ "qProfile": "key",
+ "severity": "MAJOR",
+ "updatedAt": "2017-06-16T16:13:38+0200",
+ },
+ ]
+ }
+ onActivate={[Function]}
+ onDeactivate={[Function]}
+ referencedProfiles={
+ Object {
+ "key": Object {
+ "activeDeprecatedRuleCount": 2,
+ "activeRuleCount": 10,
+ "childrenCount": 0,
+ "depth": 1,
+ "isBuiltIn": false,
+ "isDefault": false,
+ "isInherited": false,
+ "key": "key",
+ "language": "js",
+ "languageName": "JavaScript",
+ "name": "name",
+ "organization": "foo",
+ "projectCount": 3,
+ },
+ }
+ }
+ ruleDetails={
+ Object {
+ "createdAt": "2014-12-16T17:26:54+0100",
+ "debtOverloaded": false,
+ "debtRemFnOffset": "5min",
+ "debtRemFnType": "CONSTANT_ISSUE",
+ "defaultDebtRemFnOffset": "5min",
+ "defaultDebtRemFnType": "CONSTANT_ISSUE",
+ "defaultRemFnBaseEffort": "5min",
+ "defaultRemFnType": "CONSTANT_ISSUE",
+ "htmlDesc": "",
+ "isExternal": false,
+ "isTemplate": false,
+ "key": "squid:S1337",
+ "lang": "java",
+ "langName": "Java",
+ "mdDesc": "",
+ "name": "\\".equals()\\" should not be used to test the values of \\"Atomic\\" classes",
+ "params": Array [],
+ "remFnBaseEffort": "5min",
+ "remFnOverloaded": false,
+ "remFnType": "CONSTANT_ISSUE",
+ "repo": "squid",
+ "scope": "MAIN",
+ "severity": "MAJOR",
+ "status": "READY",
+ "sysTags": Array [
+ "multi-threading",
+ ],
+ "tags": Array [],
+ "type": "BUG",
+ }
+ }
+ />
+ <Connect(withAppState(RuleDetailsIssues))
+ ruleDetails={
+ Object {
+ "createdAt": "2014-12-16T17:26:54+0100",
+ "debtOverloaded": false,
+ "debtRemFnOffset": "5min",
+ "debtRemFnType": "CONSTANT_ISSUE",
+ "defaultDebtRemFnOffset": "5min",
+ "defaultDebtRemFnType": "CONSTANT_ISSUE",
+ "defaultRemFnBaseEffort": "5min",
+ "defaultRemFnType": "CONSTANT_ISSUE",
+ "htmlDesc": "",
+ "isExternal": false,
+ "isTemplate": false,
+ "key": "squid:S1337",
+ "lang": "java",
+ "langName": "Java",
+ "mdDesc": "",
+ "name": "\\".equals()\\" should not be used to test the values of \\"Atomic\\" classes",
+ "params": Array [],
+ "remFnBaseEffort": "5min",
+ "remFnOverloaded": false,
+ "remFnType": "CONSTANT_ISSUE",
+ "repo": "squid",
+ "scope": "MAIN",
+ "severity": "MAJOR",
+ "status": "READY",
+ "sysTags": Array [
+ "multi-threading",
+ ],
+ "tags": Array [],
+ "type": "BUG",
+ }
+ }
+ />
+ </DeferredSpinner>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
index e47aaf7ac34..ff9052a42d4 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
@@ -53,7 +53,6 @@ interface Props {
interface State {
baseComponent?: T.ComponentMeasure;
components: T.ComponentMeasureEnhanced[];
- loading: boolean;
loadingMoreComponents: boolean;
measure?: T.Measure;
metric?: T.Metric;
@@ -67,7 +66,6 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
components: [],
- loading: true,
loadingMoreComponents: false
};
@@ -94,7 +92,6 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
}
fetchComponentTree = () => {
- this.setState({ loading: true });
const { metricKeys, opts, strategy } = this.getComponentRequestParams(
this.props.view,
this.props.requestedMetric
@@ -111,41 +108,30 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
metricKeys: baseComponentMetrics.join(),
...getBranchLikeQuery(this.props.branchLike)
})
- ]).then(
- ([tree, measures]) => {
- if (this.mounted) {
- const metric = tree.metrics.find(m => m.key === this.props.requestedMetric.key);
- const components = tree.components.map(component =>
- enhanceComponent(component, metric, this.props.metrics)
- );
+ ]).then(([tree, measures]) => {
+ if (this.mounted) {
+ const metric = tree.metrics.find(m => m.key === this.props.requestedMetric.key);
+ const components = tree.components.map(component =>
+ enhanceComponent(component, metric, this.props.metrics)
+ );
- const measure = measures.find(
- measure => measure.metric === this.props.requestedMetric.key
- );
- const secondaryMeasure = measures.find(
- measure => measure.metric !== this.props.requestedMetric.key
- );
+ const measure = measures.find(measure => measure.metric === this.props.requestedMetric.key);
+ const secondaryMeasure = measures.find(
+ measure => measure.metric !== this.props.requestedMetric.key
+ );
- this.setState(({ selected }) => ({
- baseComponent: tree.baseComponent,
- components,
- measure,
- metric,
- paging: tree.paging,
- secondaryMeasure,
- selected:
- components.length > 0 && components.find(c => c.key === selected)
- ? selected
- : undefined
- }));
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
+ this.setState(({ selected }) => ({
+ baseComponent: tree.baseComponent,
+ components,
+ measure,
+ metric,
+ paging: tree.paging,
+ secondaryMeasure,
+ selected:
+ components.length > 0 && components.find(c => c.key === selected) ? selected : undefined
+ }));
}
- );
+ });
};
fetchMoreComponents = () => {
@@ -336,28 +322,27 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
}
right={
<div className="display-flex-center">
- {!isFile &&
- metric && (
- <>
- <div>{translate('component_measures.view_as')}</div>
- <MeasureViewSelect
- className="measure-view-select spacer-left big-spacer-right"
- handleViewChange={this.updateView}
- metric={metric}
- view={view}
- />
+ {!isFile && metric && (
+ <>
+ <div>{translate('component_measures.view_as')}</div>
+ <MeasureViewSelect
+ className="measure-view-select spacer-left big-spacer-right"
+ handleViewChange={this.updateView}
+ metric={metric}
+ view={view}
+ />
- <PageActions
- current={
- selectedIdx !== undefined && view !== 'treemap'
- ? selectedIdx + 1
- : undefined
- }
- showShortcuts={['list', 'tree'].includes(view)}
- total={paging && paging.total}
- />
- </>
- )}
+ <PageActions
+ current={
+ selectedIdx !== undefined && view !== 'treemap'
+ ? selectedIdx + 1
+ : undefined
+ }
+ showShortcuts={['list', 'tree'].includes(view)}
+ total={paging && paging.total}
+ />
+ </>
+ )}
</div>
}
/>
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx
index 5b9929b9447..1bfe27a0c5a 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx
@@ -61,22 +61,20 @@ export default function MeasureHeader(props: Props) {
/>
</strong>
</span>
- {!isDiff &&
- hasHistory && (
- <Tooltip overlay={translate('component_measures.show_metric_history')}>
- <Link
- className="js-show-history spacer-left button button-small"
- to={getMeasureHistoryUrl(component.key, metric.key, branchLike)}>
- <HistoryIcon />
- </Link>
- </Tooltip>
- )}
+ {!isDiff && hasHistory && (
+ <Tooltip overlay={translate('component_measures.show_metric_history')}>
+ <Link
+ className="js-show-history spacer-left button button-small"
+ to={getMeasureHistoryUrl(component.key, metric.key, branchLike)}>
+ <HistoryIcon />
+ </Link>
+ </Tooltip>
+ )}
</div>
<div className="measure-details-primary-actions">
- {displayLeak &&
- leakPeriod && (
- <LeakPeriodLegend className="spacer-left" component={component} period={leakPeriod} />
- )}
+ {displayLeak && leakPeriod && (
+ <LeakPeriodLegend className="spacer-left" component={component} period={leakPeriod} />
+ )}
</div>
</div>
{secondaryMeasure &&
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx
index d8b3a9c5075..53fc28de992 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx
@@ -167,14 +167,9 @@ export default class MeasureOverview extends React.PureComponent<Props, State> {
</div>
<div className="layout-page-main-inner measure-details-content">
<div className="clearfix big-spacer-bottom">
- {leakPeriod &&
- displayLeak && (
- <LeakPeriodLegend
- className="pull-right"
- component={component}
- period={leakPeriod}
- />
- )}
+ {leakPeriod && displayLeak && (
+ <LeakPeriodLegend className="pull-right" component={component} period={leakPeriod} />
+ )}
</div>
<DeferredSpinner loading={this.props.loading} />
{!this.props.loading && this.renderContent()}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureContent-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureContent-test.tsx
new file mode 100644
index 00000000000..85b1cca1f08
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureContent-test.tsx
@@ -0,0 +1,96 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import MeasureContent from '../MeasureContent';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { getComponentTree } from '../../../../api/components';
+import { mockComponentMeasure, mockRouter } from '../../../../helpers/testMocks';
+
+jest.mock('../../../../api/components', () => {
+ const { mockComponentMeasure } = require.requireActual('../../../../helpers/testMocks');
+ return {
+ getComponentTree: jest.fn().mockResolvedValue({
+ paging: { pageIndex: 1, pageSize: 500, total: 2 },
+ baseComponent: mockComponentMeasure(),
+ components: [mockComponentMeasure(true)],
+ metrics: [
+ {
+ bestValue: '0',
+ custom: false,
+ description: 'Bugs',
+ domain: 'Reliability',
+ hidden: false,
+ higherValuesAreBetter: false,
+ key: 'bugs',
+ name: 'Bugs',
+ qualitative: true,
+ type: 'INT'
+ }
+ ]
+ })
+ };
+});
+
+jest.mock('../../../../api/measures', () => ({
+ getMeasures: jest.fn().mockResolvedValue([{ metric: 'bugs', value: '12', bestValue: false }])
+}));
+
+const METRICS = {
+ bugs: { id: '1', key: 'bugs', type: 'INT', name: 'Bugs', domain: 'Reliability' }
+};
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+it('should render correctly for a project', async () => {
+ const wrapper = shallowRender();
+ expect(wrapper.type()).toBeNull();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render correctly for a file', async () => {
+ (getComponentTree as jest.Mock).mockResolvedValueOnce({
+ paging: { pageIndex: 1, pageSize: 500, total: 0 },
+ baseComponent: mockComponentMeasure(true),
+ components: [],
+ metrics: [METRICS.bugs]
+ });
+ const wrapper = shallowRender();
+ expect(wrapper.type()).toBeNull();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<MeasureContent['props']> = {}) {
+ return shallow(
+ <MeasureContent
+ metrics={METRICS}
+ requestedMetric={{ direction: 1, key: 'bugs' }}
+ rootComponent={mockComponentMeasure()}
+ router={mockRouter()}
+ updateQuery={jest.fn()}
+ view="list"
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureContent-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureContent-test.tsx.snap
new file mode 100644
index 00000000000..d25d9196349
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureContent-test.tsx.snap
@@ -0,0 +1,312 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly for a file 1`] = `
+<div
+ className="layout-page-main no-outline"
+>
+ <A11ySkipTarget
+ anchor="measures_main"
+ />
+ <div
+ className="layout-page-header-panel layout-page-main-header"
+ >
+ <div
+ className="layout-page-header-panel-inner layout-page-main-header-inner"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <MeasureContentHeader
+ left={
+ <Breadcrumbs
+ backToFirst={true}
+ className="text-ellipsis flex-1"
+ component={
+ Object {
+ "key": "foo:src/index.tsx",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "1",
+ },
+ ],
+ "name": "index.tsx",
+ "path": "src/index.tsx",
+ "qualifier": "FIL",
+ }
+ }
+ handleSelect={[Function]}
+ rootComponent={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ />
+ }
+ right={
+ <div
+ className="display-flex-center"
+ />
+ }
+ />
+ </div>
+ </div>
+ </div>
+ <div
+ className="layout-page-main-inner measure-details-content"
+ >
+ <MeasureHeader
+ component={
+ Object {
+ "key": "foo:src/index.tsx",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "1",
+ },
+ ],
+ "name": "index.tsx",
+ "path": "src/index.tsx",
+ "qualifier": "FIL",
+ }
+ }
+ measureValue="12"
+ metric={
+ Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ }
+ }
+ />
+ <div
+ className="measure-details-viewer"
+ >
+ <LazyLoader
+ component="foo:src/index.tsx"
+ />
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render correctly for a project 1`] = `
+<div
+ className="layout-page-main no-outline"
+>
+ <A11ySkipTarget
+ anchor="measures_main"
+ />
+ <div
+ className="layout-page-header-panel layout-page-main-header"
+ >
+ <div
+ className="layout-page-header-panel-inner layout-page-main-header-inner"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <MeasureContentHeader
+ left={
+ <Breadcrumbs
+ backToFirst={true}
+ className="text-ellipsis flex-1"
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ handleSelect={[Function]}
+ rootComponent={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ />
+ }
+ right={
+ <div
+ className="display-flex-center"
+ >
+ <React.Fragment>
+ <div>
+ component_measures.view_as
+ </div>
+ <MeasureViewSelect
+ className="measure-view-select spacer-left big-spacer-right"
+ handleViewChange={[Function]}
+ metric={
+ Object {
+ "bestValue": "0",
+ "custom": false,
+ "description": "Bugs",
+ "domain": "Reliability",
+ "hidden": false,
+ "higherValuesAreBetter": false,
+ "key": "bugs",
+ "name": "Bugs",
+ "qualitative": true,
+ "type": "INT",
+ }
+ }
+ view="list"
+ />
+ <PageActions
+ showShortcuts={true}
+ total={2}
+ />
+ </React.Fragment>
+ </div>
+ }
+ />
+ </div>
+ </div>
+ </div>
+ <div
+ className="layout-page-main-inner measure-details-content"
+ >
+ <MeasureHeader
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ measureValue="12"
+ metric={
+ Object {
+ "bestValue": "0",
+ "custom": false,
+ "description": "Bugs",
+ "domain": "Reliability",
+ "hidden": false,
+ "higherValuesAreBetter": false,
+ "key": "bugs",
+ "name": "Bugs",
+ "qualitative": true,
+ "type": "INT",
+ }
+ }
+ />
+ <FilesView
+ components={
+ Array [
+ Object {
+ "key": "foo:src/index.tsx",
+ "leak": undefined,
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "leak": undefined,
+ "metric": Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ },
+ "value": "1",
+ },
+ ],
+ "name": "index.tsx",
+ "path": "src/index.tsx",
+ "qualifier": "FIL",
+ "value": "1",
+ },
+ ]
+ }
+ defaultShowBestMeasures={false}
+ fetchMore={[Function]}
+ handleOpen={[Function]}
+ handleSelect={[Function]}
+ loadingMore={false}
+ metric={
+ Object {
+ "bestValue": "0",
+ "custom": false,
+ "description": "Bugs",
+ "domain": "Reliability",
+ "hidden": false,
+ "higherValuesAreBetter": false,
+ "key": "bugs",
+ "name": "Bugs",
+ "qualitative": true,
+ "type": "INT",
+ }
+ }
+ metrics={
+ Object {
+ "bugs": Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ },
+ }
+ }
+ paging={
+ Object {
+ "pageIndex": 1,
+ "pageSize": 500,
+ "total": 2,
+ }
+ }
+ rootComponent={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ view="list"
+ />
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx
index 52fc3caa4b1..3574f0008d5 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx
@@ -177,31 +177,28 @@ export default class FilesView extends React.PureComponent<Props, State> {
selectedComponent={this.props.selectedKey}
view={this.props.view}
/>
- {hidingBestMeasures &&
- this.props.paging && (
- <Alert className="spacer-top" variant="info">
- <div className="display-flex-center">
- {translateWithParameters(
- 'component_measures.hidden_best_score_metrics',
- formatMeasure(this.props.paging.total - filteredComponents.length, 'INT'),
- formatMeasure(this.props.metric.bestValue, this.props.metric.type)
- )}
- <Button className="button-small spacer-left" onClick={this.handleShowBestMeasures}>
- {translate('show_them')}
- </Button>
- </div>
- </Alert>
- )}
- {!hidingBestMeasures &&
- this.props.paging &&
- this.props.components.length > 0 && (
- <ListFooter
- count={this.props.components.length}
- loadMore={this.props.fetchMore}
- loading={this.props.loadingMore}
- total={this.props.paging.total}
- />
- )}
+ {hidingBestMeasures && this.props.paging && (
+ <Alert className="spacer-top" variant="info">
+ <div className="display-flex-center">
+ {translateWithParameters(
+ 'component_measures.hidden_best_score_metrics',
+ formatMeasure(this.props.paging.total - filteredComponents.length, 'INT'),
+ formatMeasure(this.props.metric.bestValue, this.props.metric.type)
+ )}
+ <Button className="button-small spacer-left" onClick={this.handleShowBestMeasures}>
+ {translate('show_them')}
+ </Button>
+ </div>
+ </Alert>
+ )}
+ {!hidingBestMeasures && this.props.paging && this.props.components.length > 0 && (
+ <ListFooter
+ count={this.props.components.length}
+ loadMore={this.props.fetchMore}
+ loading={this.props.loadingMore}
+ total={this.props.paging.total}
+ />
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx
index 3574939f942..ae48a9c3838 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx
@@ -105,26 +105,25 @@ export default class DomainFacet extends React.PureComponent<Props> {
);
});
- return sortedItems.map(
- item =>
- typeof item === 'string' ? (
- this.renderCategoryItem(item)
- ) : (
- <FacetItem
- active={item.metric.key === selected}
- disabled={false}
- key={item.metric.key}
- name={
- <span className="big-spacer-left" id={`measure-${item.metric.key}-name`}>
- {translateMetric(item.metric)}
- </span>
- }
- onClick={this.props.onChange}
- stat={this.renderItemFacetStat(item)}
- tooltip={translateMetric(item.metric)}
- value={item.metric.key}
- />
- )
+ return sortedItems.map(item =>
+ typeof item === 'string' ? (
+ this.renderCategoryItem(item)
+ ) : (
+ <FacetItem
+ active={item.metric.key === selected}
+ disabled={false}
+ key={item.metric.key}
+ name={
+ <span className="big-spacer-left" id={`measure-${item.metric.key}-name`}>
+ {translateMetric(item.metric)}
+ </span>
+ }
+ onClick={this.props.onChange}
+ stat={this.renderItemFacetStat(item)}
+ tooltip={translateMetric(item.metric)}
+ value={item.metric.key}
+ />
+ )
);
};
diff --git a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx b/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx
index 4df6b9d29d8..08ad2ed676f 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx
+++ b/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx
@@ -200,18 +200,17 @@ export default class AutoOrganizationCreate extends React.PureComponent<Props, S
)}
</OrganizationDetailsStep>
- {subscriptionPlans !== undefined &&
- filter !== Filters.Bind && (
- <PlanStep
- almApplication={this.props.almApplication}
- almOrganization={this.props.almOrganization}
- createOrganization={this.handleCreateOrganization}
- onDone={this.props.onDone}
- onUpgradeFail={this.props.onUpgradeFail}
- open={step === Step.Plan}
- subscriptionPlans={subscriptionPlans}
- />
- )}
+ {subscriptionPlans !== undefined && filter !== Filters.Bind && (
+ <PlanStep
+ almApplication={this.props.almApplication}
+ almOrganization={this.props.almOrganization}
+ createOrganization={this.handleCreateOrganization}
+ onDone={this.props.onDone}
+ onUpgradeFail={this.props.onUpgradeFail}
+ open={step === Step.Plan}
+ subscriptionPlans={subscriptionPlans}
+ />
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx b/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx
index 1ab5ef85948..a08e71d5859 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx
+++ b/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx
@@ -463,12 +463,11 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr
<h1 className="page-title huge big-spacer-bottom">
<strong>{header}</strong>
</h1>
- {!importPersonalOrg &&
- startedPrice !== undefined && (
- <p className="page-description">
- {translate('onboarding.create_organization.page.description')}
- </p>
- )}
+ {!importPersonalOrg && startedPrice !== undefined && (
+ <p className="page-description">
+ {translate('onboarding.create_organization.page.description')}
+ </p>
+ )}
</header>
{this.state.loading ? (
<DeferredSpinner />
diff --git a/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx b/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx
index 33d9fcc8d79..e86d9e0f131 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx
+++ b/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx
@@ -103,48 +103,46 @@ export class RemoteOrganizationChoose extends React.PureComponent<Props & WithRo
<h2>{translate('onboarding.import_organization.import_org_details')}</h2>
</div>
<div className="boxed-group-inner">
- {almInstallId &&
- !almOrganization && (
- <Alert className="big-spacer-bottom width-60" variant="error">
- <div className="markdown">
- {translate('onboarding.import_organization.org_not_found')}
- <ul>
- <li>{translate('onboarding.import_organization.org_not_found.tips_1')}</li>
- <li>{translate('onboarding.import_organization.org_not_found.tips_2')}</li>
- </ul>
- </div>
- </Alert>
- )}
- {almOrganization &&
- boundOrganization && (
- <Alert className="big-spacer-bottom width-60" variant="error">
- <FormattedMessage
- defaultMessage={translate('onboarding.import_organization.already_bound_x')}
- id="onboarding.import_organization.already_bound_x"
- values={{
- avatar: (
- <img
- alt={almApplication.name}
- className="little-spacer-left"
- src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId(
- almApplication.key
- )}.svg`}
- width={16}
- />
- ),
- name: <strong>{almOrganization.name}</strong>,
- boundAvatar: (
- <OrganizationAvatar
- className="little-spacer-left"
- organization={boundOrganization}
- small={true}
- />
- ),
- boundName: <strong>{boundOrganization.name}</strong>
- }}
- />
- </Alert>
- )}
+ {almInstallId && !almOrganization && (
+ <Alert className="big-spacer-bottom width-60" variant="error">
+ <div className="markdown">
+ {translate('onboarding.import_organization.org_not_found')}
+ <ul>
+ <li>{translate('onboarding.import_organization.org_not_found.tips_1')}</li>
+ <li>{translate('onboarding.import_organization.org_not_found.tips_2')}</li>
+ </ul>
+ </div>
+ </Alert>
+ )}
+ {almOrganization && boundOrganization && (
+ <Alert className="big-spacer-bottom width-60" variant="error">
+ <FormattedMessage
+ defaultMessage={translate('onboarding.import_organization.already_bound_x')}
+ id="onboarding.import_organization.already_bound_x"
+ values={{
+ avatar: (
+ <img
+ alt={almApplication.name}
+ className="little-spacer-left"
+ src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId(
+ almApplication.key
+ )}.svg`}
+ width={16}
+ />
+ ),
+ name: <strong>{almOrganization.name}</strong>,
+ boundAvatar: (
+ <OrganizationAvatar
+ className="little-spacer-left"
+ organization={boundOrganization}
+ small={true}
+ />
+ ),
+ boundName: <strong>{boundOrganization.name}</strong>
+ }}
+ />
+ </Alert>
+ )}
<div className="display-flex-center">
<div className="display-inline-block">
<IdentityProviderLink
diff --git a/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx
index 43c64e49837..ad302695458 100644
--- a/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx
@@ -173,14 +173,13 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
<div className="create-project">
<div className="flex-1 huge-spacer-right">
<form className="manual-project-create" onSubmit={this.handleFormSubmit}>
- {isSonarCloud() &&
- this.props.userOrganizations && (
- <OrganizationInput
- onChange={this.handleOrganizationSelect}
- organization={selectedOrganization ? selectedOrganization.key : ''}
- organizations={this.props.userOrganizations}
- />
- )}
+ {isSonarCloud() && this.props.userOrganizations && (
+ <OrganizationInput
+ onChange={this.handleOrganizationSelect}
+ organization={selectedOrganization ? selectedOrganization.key : ''}
+ organizations={this.props.userOrganizations}
+ />
+ )}
<ProjectKeyInput
className="form-field"
initialValue={this.state.projectKey}
@@ -212,20 +211,19 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
{translate('onboarding.create_project.display_name.description')}
</div>
</div>
- {isSonarCloud() &&
- selectedOrganization && (
- <div
- className={classNames('visibility-select-wrapper', {
- open: Boolean(this.state.selectedOrganization)
- })}>
- <VisibilitySelector
- canTurnToPrivate={canChoosePrivate}
- onChange={this.handleVisibilityChange}
- showDetails={true}
- visibility={canChoosePrivate ? this.state.selectedVisibility : 'public'}
- />
- </div>
- )}
+ {isSonarCloud() && selectedOrganization && (
+ <div
+ className={classNames('visibility-select-wrapper', {
+ open: Boolean(this.state.selectedOrganization)
+ })}>
+ <VisibilitySelector
+ canTurnToPrivate={canChoosePrivate}
+ onChange={this.handleVisibilityChange}
+ showDetails={true}
+ visibility={canChoosePrivate ? this.state.selectedVisibility : 'public'}
+ />
+ </div>
+ )}
<SubmitButton disabled={!this.canSubmit(this.state) || submitting}>
{translate('set_up')}
</SubmitButton>
@@ -233,16 +231,15 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
</form>
</div>
- {isSonarCloud() &&
- selectedOrganization && (
- <div className="create-project-side-sticky">
- <UpgradeOrganizationBox
- className={classNames('animated', { open: !canChoosePrivate })}
- onOrganizationUpgrade={this.handleOrganizationUpgrade}
- organization={selectedOrganization}
- />
- </div>
- )}
+ {isSonarCloud() && selectedOrganization && (
+ <div className="create-project-side-sticky">
+ <UpgradeOrganizationBox
+ className={classNames('animated', { open: !canChoosePrivate })}
+ onOrganizationUpgrade={this.handleOrganizationUpgrade}
+ organization={selectedOrganization}
+ />
+ </div>
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/App.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/App.tsx
index ff5fc8b7b47..7990ca2075c 100644
--- a/server/sonar-web/src/main/js/apps/custom-measures/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/App.tsx
@@ -109,8 +109,8 @@ export default class App extends React.PureComponent<Props, State> {
return updateCustomMeasure(data).then(() => {
if (this.mounted) {
this.setState(({ measures = [] }: State) => ({
- measures: measures.map(
- measure => (measure.id === data.id ? { ...measure, ...data } : measure)
+ measures: measures.map(measure =>
+ measure.id === data.id ? { ...measure, ...data } : measure
)
}));
}
@@ -144,15 +144,14 @@ export default class App extends React.PureComponent<Props, State> {
{measures && (
<List measures={measures} onDelete={this.handleDelete} onEdit={this.handleEdit} />
)}
- {measures &&
- paging && (
- <ListFooter
- count={measures.length}
- loadMore={this.fetchMore}
- ready={!loading}
- total={paging.total}
- />
- )}
+ {measures && paging && (
+ <ListFooter
+ count={measures.length}
+ loadMore={this.fetchMore}
+ ready={!loading}
+ total={paging.total}
+ />
+ )}
</div>
</>
);
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/App.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/App.tsx
index 37402728681..be55d08a49b 100644
--- a/server/sonar-web/src/main/js/apps/custom-metrics/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/App.tsx
@@ -159,15 +159,14 @@ export default class App extends React.PureComponent<Props, State> {
types={types}
/>
)}
- {metrics &&
- paging && (
- <ListFooter
- count={metrics.length}
- loadMore={this.fetchMore}
- ready={!loading}
- total={paging.total}
- />
- )}
+ {metrics && paging && (
+ <ListFooter
+ count={metrics.length}
+ loadMore={this.fetchMore}
+ ready={!loading}
+ total={paging.total}
+ />
+ )}
</div>
</>
);
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/Item.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/Item.tsx
index 6c8da043c69..3ad55e020d4 100644
--- a/server/sonar-web/src/main/js/apps/custom-metrics/components/Item.tsx
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/Item.tsx
@@ -106,12 +106,11 @@ export default class Item extends React.PureComponent<Props, State> {
<td className="thin nowrap">
<ActionsDropdown>
- {domains &&
- types && (
- <ActionsDropdownItem className="js-metric-update" onClick={this.handleEditClick}>
- {translate('update_details')}
- </ActionsDropdownItem>
- )}
+ {domains && types && (
+ <ActionsDropdownItem className="js-metric-update" onClick={this.handleEditClick}>
+ {translate('update_details')}
+ </ActionsDropdownItem>
+ )}
<ActionsDropdownDivider />
<ActionsDropdownItem
className="js-metric-delete"
@@ -122,19 +121,17 @@ export default class Item extends React.PureComponent<Props, State> {
</ActionsDropdown>
</td>
- {this.state.editForm &&
- domains &&
- types && (
- <Form
- confirmButtonText={translate('update_verb')}
- domains={domains}
- header={translate('custom_metrics.update_metric')}
- metric={metric}
- onClose={this.closeEditForm}
- onSubmit={this.handleEditFormSubmit}
- types={types}
- />
- )}
+ {this.state.editForm && domains && types && (
+ <Form
+ confirmButtonText={translate('update_verb')}
+ domains={domains}
+ header={translate('custom_metrics.update_metric')}
+ metric={metric}
+ onClose={this.closeEditForm}
+ onSubmit={this.handleEditFormSubmit}
+ types={types}
+ />
+ )}
{this.state.deleteForm && (
<DeleteForm
diff --git a/server/sonar-web/src/main/js/apps/documentation/utils.ts b/server/sonar-web/src/main/js/apps/documentation/utils.ts
index ce04d9d2e8c..88b831c7325 100644
--- a/server/sonar-web/src/main/js/apps/documentation/utils.ts
+++ b/server/sonar-web/src/main/js/apps/documentation/utils.ts
@@ -56,9 +56,8 @@ export function getUrlsList(navigation: DocsNavigationItem[]): string[] {
return flatten(
navigation
.filter(item => !isDocsNavigationExternalLink(item))
- .map(
- (item: string | DocsNavigationBlock) =>
- isDocsNavigationBlock(item) ? item.children : [item]
+ .map((item: string | DocsNavigationBlock) =>
+ isDocsNavigationBlock(item) ? item.children : [item]
)
);
}
diff --git a/server/sonar-web/src/main/js/apps/groups/components/App.tsx b/server/sonar-web/src/main/js/apps/groups/components/App.tsx
index 26f28e0365e..02d96255587 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/groups/components/App.tsx
@@ -165,17 +165,16 @@ export default class App extends React.PureComponent<Props, State> {
/>
)}
- {groups !== undefined &&
- paging !== undefined && (
- <div id="groups-list-footer">
- <ListFooter
- count={showAnyone ? groups.length + 1 : groups.length}
- loadMore={this.fetchMoreGroups}
- ready={!loading}
- total={showAnyone ? paging.total + 1 : paging.total}
- />
- </div>
- )}
+ {groups !== undefined && paging !== undefined && (
+ <div id="groups-list-footer">
+ <ListFooter
+ count={showAnyone ? groups.length + 1 : groups.length}
+ loadMore={this.fetchMoreGroups}
+ ready={!loading}
+ total={showAnyone ? paging.total + 1 : paging.total}
+ />
+ </div>
+ )}
</div>
</>
);
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx
index f8576bb6039..0c4e78abd65 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx
@@ -22,12 +22,24 @@ import { shallow } from 'enzyme';
import App from '../App';
import { mockOrganization } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
+import {
+ createGroup,
+ deleteGroup,
+ searchUsersGroups,
+ updateGroup
+} from '../../../../api/user_groups';
jest.mock('../../../../api/user_groups', () => ({
- createGroup: jest.fn(),
- deleteGroup: jest.fn(),
+ createGroup: jest.fn().mockResolvedValue({
+ default: false,
+ description: 'Desc foo',
+ id: 3,
+ membersCount: 0,
+ name: 'Foo'
+ }),
+ deleteGroup: jest.fn().mockResolvedValue({}),
searchUsersGroups: jest.fn().mockResolvedValue({
- paging: { pageIndex: 1, pageSize: 100, total: 2 },
+ paging: { pageIndex: 1, pageSize: 2, total: 4 },
groups: [
{
default: false,
@@ -45,16 +57,73 @@ jest.mock('../../../../api/user_groups', () => ({
}
]
}),
- updateGroup: jest.fn()
+ updateGroup: jest.fn().mockResolvedValue({})
}));
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
it('should render correctly', async () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
await waitAndUpdate(wrapper);
+ expect(searchUsersGroups).toHaveBeenCalledWith({ organization: 'foo', q: '' });
expect(wrapper).toMatchSnapshot();
});
+it('should correctly handle creation', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state('groups')).toHaveLength(2);
+ wrapper.instance().handleCreate({ description: 'Desc foo', name: 'foo' });
+ await waitAndUpdate(wrapper);
+ expect(createGroup).toHaveBeenCalled();
+ expect(wrapper.state('groups')).toHaveLength(3);
+});
+
+it('should correctly handle deletion', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state('groups')).toHaveLength(2);
+ wrapper.instance().handleDelete('Members');
+ await waitAndUpdate(wrapper);
+ expect(deleteGroup).toHaveBeenCalled();
+ expect(wrapper.state('groups')).toHaveLength(1);
+});
+
+it('should correctly handle edition', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ wrapper.instance().handleEdit({ id: 1, description: 'foo', name: 'bar' });
+ await waitAndUpdate(wrapper);
+ expect(updateGroup).toHaveBeenCalled();
+ expect(wrapper.state('groups')).toContainEqual({
+ default: false,
+ description: 'foo',
+ id: 1,
+ membersCount: 1,
+ name: 'bar'
+ });
+});
+
+it('should fetch more groups', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ wrapper.find('ListFooter').prop<Function>('loadMore')();
+ await waitAndUpdate(wrapper);
+ expect(searchUsersGroups).toHaveBeenCalledWith({ organization: 'foo', p: 2, q: '' });
+ expect(wrapper.state('groups')).toHaveLength(4);
+});
+
+it('should search for groups', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ wrapper.find('SearchBox').prop<Function>('onChange')('foo');
+ expect(searchUsersGroups).toBeCalledWith({ organization: 'foo', q: 'foo' });
+ expect(wrapper.state('query')).toBe('foo');
+});
+
function shallowRender(props: Partial<App['props']> = {}) {
- return shallow(<App organization={mockOrganization()} {...props} />);
+ return shallow<App>(<App organization={mockOrganization()} {...props} />);
}
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap
index 4f4ffb932d6..11190d8cf61 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -88,7 +88,7 @@ exports[`should render correctly 2`] = `
count={2}
loadMore={[Function]}
ready={true}
- total={2}
+ total={4}
/>
</div>
</div>
diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.tsx b/server/sonar-web/src/main/js/apps/issues/components/App.tsx
index 8e079deebbe..8fe9d110644 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/App.tsx
@@ -884,13 +884,12 @@ export class App extends React.PureComponent<Props, State> {
return (
<div className="layout-page-filters">
- {currentUser.isLoggedIn &&
- !isSonarCloud() && (
- <MyIssuesFilter
- myIssues={this.state.myIssues}
- onMyIssuesChange={this.handleMyIssuesChange}
- />
- )}
+ {currentUser.isLoggedIn && !isSonarCloud() && (
+ <MyIssuesFilter
+ myIssues={this.state.myIssues}
+ onMyIssuesChange={this.handleMyIssuesChange}
+ />
+ )}
<FiltersHeader displayReset={this.isFiltered()} onReset={this.handleReset} />
<Sidebar
component={component}
@@ -936,15 +935,14 @@ export class App extends React.PureComponent<Props, State> {
selectedFlowIndex={this.state.selectedFlowIndex}
selectedLocationIndex={this.state.selectedLocationIndex}
/>
- {paging &&
- paging.total > 0 && (
- <ListFooter
- count={issues.length}
- loadMore={this.fetchMoreIssues}
- loading={loadingMore}
- total={paging.total}
- />
- )}
+ {paging && paging.total > 0 && (
+ <ListFooter
+ count={issues.length}
+ loadMore={this.fetchMoreIssues}
+ loading={loadingMore}
+ total={paging.total}
+ />
+ )}
</div>
);
}
@@ -1065,17 +1063,15 @@ export class App extends React.PureComponent<Props, State> {
/>
) : (
<DeferredSpinner loading={loading}>
- {checkAll &&
- paging &&
- paging.total > MAX_PAGE_SIZE && (
- <Alert className="big-spacer-bottom" variant="warning">
- <FormattedMessage
- defaultMessage={translate('issue_bulk_change.max_issues_reached')}
- id="issue_bulk_change.max_issues_reached"
- values={{ max: <strong>{MAX_PAGE_SIZE}</strong> }}
- />
- </Alert>
- )}
+ {checkAll && paging && paging.total > MAX_PAGE_SIZE && (
+ <Alert className="big-spacer-bottom" variant="warning">
+ <FormattedMessage
+ defaultMessage={translate('issue_bulk_change.max_issues_reached')}
+ id="issue_bulk_change.max_issues_reached"
+ values={{ max: <strong>{MAX_PAGE_SIZE}</strong> }}
+ />
+ </Alert>
+ )}
{this.renderList()}
</DeferredSpinner>
)}
diff --git a/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx b/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx
index 7700efb6cf1..0b9cac8f4bb 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx
@@ -68,14 +68,12 @@ export default function ComponentBreadcrumbs({
</span>
)}
- {displaySubProject &&
- issue.subProject !== undefined &&
- issue.subProjectName !== undefined && (
- <span title={issue.subProjectName}>
- {limitComponentName(issue.subProjectName)}
- <span className="slash-separator" />
- </span>
- )}
+ {displaySubProject && issue.subProject !== undefined && issue.subProjectName !== undefined && (
+ <span title={issue.subProjectName}>
+ {limitComponentName(issue.subProjectName)}
+ <span className="slash-separator" />
+ </span>
+ )}
{collapsePath(componentName || '')}
</div>
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx
index 58bcdbaad93..fbaa4feb644 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx
@@ -88,8 +88,8 @@ export default class IssuesSourceViewer extends React.PureComponent<Props> {
: openIssue.textRange && openIssue.textRange.endLine;
// replace locations in another file with `undefined` to keep the same location indexes
- const highlightedLocations = locations.map(
- location => (location.component === component ? location : undefined)
+ const highlightedLocations = locations.map(location =>
+ location.component === component ? location : undefined
);
const highlightedLocationMessage =
diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx
index 21e9148a4bf..be24582def8 100644
--- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx
@@ -59,8 +59,8 @@ export default class ConciseIssueBox extends React.PureComponent<Props> {
selectedFlowIndex !== undefined
? flows[selectedFlowIndex]
: flows.length > 0
- ? flows[0]
- : secondaryLocations;
+ ? flows[0]
+ : secondaryLocations;
if (!locations || locations.length < 15) {
// if there are no locations, or there are just few
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx
index b33c490d370..996d73d4931 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx
@@ -91,20 +91,18 @@ export default class PluginActions extends React.PureComponent<Props, State> {
{translate('marketplace.installed')}
</p>
)}
- {isPluginInstalled(plugin) &&
- plugin.updates &&
- plugin.updates.length > 0 && (
- <div className="spacer-top">
- {plugin.updates.map((update, idx) => (
- <PluginUpdateButton
- disabled={this.state.loading}
- key={idx}
- onClick={this.handleUpdate}
- update={update}
- />
- ))}
- </div>
- )}
+ {isPluginInstalled(plugin) && plugin.updates && plugin.updates.length > 0 && (
+ <div className="spacer-top">
+ {plugin.updates.map((update, idx) => (
+ <PluginUpdateButton
+ disabled={this.state.loading}
+ key={idx}
+ onClick={this.handleUpdate}
+ update={update}
+ />
+ ))}
+ </div>
+ )}
</div>
);
}
@@ -119,26 +117,25 @@ export default class PluginActions extends React.PureComponent<Props, State> {
const { loading } = this.state;
return (
<div className="js-actions">
- {isPluginAvailable(plugin) &&
- plugin.termsAndConditionsUrl && (
- <p className="little-spacer-bottom">
- <Checkbox
- checked={this.state.acceptTerms}
- className="js-terms"
- id={'plugin-terms-' + plugin.key}
- onCheck={this.handleTermsCheck}>
- <label className="little-spacer-left" htmlFor={'plugin-terms-' + plugin.key}>
- {translate('marketplace.i_accept_the')}
- </label>
- </Checkbox>
- <a
- className="js-plugin-terms nowrap little-spacer-left"
- href={plugin.termsAndConditionsUrl}
- target="_blank">
- {translate('marketplace.terms_and_conditions')}
- </a>
- </p>
- )}
+ {isPluginAvailable(plugin) && plugin.termsAndConditionsUrl && (
+ <p className="little-spacer-bottom">
+ <Checkbox
+ checked={this.state.acceptTerms}
+ className="js-terms"
+ id={'plugin-terms-' + plugin.key}
+ onCheck={this.handleTermsCheck}>
+ <label className="little-spacer-left" htmlFor={'plugin-terms-' + plugin.key}>
+ {translate('marketplace.i_accept_the')}
+ </label>
+ </Checkbox>
+ <a
+ className="js-plugin-terms nowrap little-spacer-left"
+ href={plugin.termsAndConditionsUrl}
+ target="_blank">
+ {translate('marketplace.terms_and_conditions')}
+ </a>
+ </p>
+ )}
{loading && <i className="spinner spacer-right little-spacer-top little-spacer-bottom" />}
{isPluginInstalled(plugin) && (
<div className="display-inlin-block">
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx
index 621ad310048..827b17aad26 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx
@@ -22,7 +22,7 @@ import PluginChangeLogItem from './PluginChangeLogItem';
import { Release, Update } from '../../../api/plugins';
import { translate } from '../../../helpers/l10n';
-interface Props {
+export interface Props {
release: Release;
update: Update;
}
@@ -33,15 +33,14 @@ export default function PluginChangeLog({ release, update }: Props) {
<h6>{translate('changelog')}</h6>
<ul className="js-plugin-changelog-list">
{update.previousUpdates &&
- update.previousUpdates.map(
- previousUpdate =>
- previousUpdate.release ? (
- <PluginChangeLogItem
- key={previousUpdate.release.version}
- release={previousUpdate.release}
- update={previousUpdate}
- />
- ) : null
+ update.previousUpdates.map(previousUpdate =>
+ previousUpdate.release ? (
+ <PluginChangeLogItem
+ key={previousUpdate.release.version}
+ release={previousUpdate.release}
+ update={previousUpdate}
+ />
+ ) : null
)}
<PluginChangeLogItem release={release} update={update} />
</ul>
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx
index 6857a3d70ce..2f7d243cdb7 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx
@@ -34,15 +34,14 @@ export default function PluginUpdates({ updates }: Props) {
<li className="spacer-top">
<strong>{translate('marketplace.updates')}:</strong>
<ul className="little-spacer-top">
- {updates.map(
- update =>
- update.release ? (
- <PluginUpdateItem
- key={update.release.version}
- release={update.release}
- update={update}
- />
- ) : null
+ {updates.map(update =>
+ update.release ? (
+ <PluginUpdateItem
+ key={update.release.version}
+ release={update.release}
+ update={update}
+ />
+ ) : null
)}
</ul>
</li>
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginChangeLog-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginChangeLog-test.tsx
new file mode 100644
index 00000000000..30a3dacd6da
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginChangeLog-test.tsx
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import PluginChangeLog, { Props } from '../PluginChangeLog';
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<Props> = {}) {
+ return shallow(
+ <PluginChangeLog
+ release={{
+ version: '0.11',
+ date: '2018-11-05',
+ description: 'Change version description',
+ changeLogUrl: 'https://my.change.log/url'
+ }}
+ update={{
+ previousUpdates: [
+ {
+ release: {
+ version: '0.10',
+ date: '2018-06-05',
+ description: 'Change version description',
+ changeLogUrl: 'https://my.change.log/url'
+ },
+ requires: [],
+ status: 'COMPATIBLE'
+ }
+ ],
+ requires: [{ key: 'java', name: 'SonarJava', description: 'Code Analyzer for Java' }],
+ status: 'COMPATIBLE'
+ }}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginChangeLog-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginChangeLog-test.tsx.snap
new file mode 100644
index 00000000000..c45bedc60a9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginChangeLog-test.tsx.snap
@@ -0,0 +1,72 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="abs-width-300"
+>
+ <h6>
+ changelog
+ </h6>
+ <ul
+ className="js-plugin-changelog-list"
+ >
+ <PluginChangeLogItem
+ key="0.10"
+ release={
+ Object {
+ "changeLogUrl": "https://my.change.log/url",
+ "date": "2018-06-05",
+ "description": "Change version description",
+ "version": "0.10",
+ }
+ }
+ update={
+ Object {
+ "release": Object {
+ "changeLogUrl": "https://my.change.log/url",
+ "date": "2018-06-05",
+ "description": "Change version description",
+ "version": "0.10",
+ },
+ "requires": Array [],
+ "status": "COMPATIBLE",
+ }
+ }
+ />
+ <PluginChangeLogItem
+ release={
+ Object {
+ "changeLogUrl": "https://my.change.log/url",
+ "date": "2018-11-05",
+ "description": "Change version description",
+ "version": "0.11",
+ }
+ }
+ update={
+ Object {
+ "previousUpdates": Array [
+ Object {
+ "release": Object {
+ "changeLogUrl": "https://my.change.log/url",
+ "date": "2018-06-05",
+ "description": "Change version description",
+ "version": "0.10",
+ },
+ "requires": Array [],
+ "status": "COMPATIBLE",
+ },
+ ],
+ "requires": Array [
+ Object {
+ "description": "Code Analyzer for Java",
+ "key": "java",
+ "name": "SonarJava",
+ },
+ ],
+ "status": "COMPATIBLE",
+ }
+ }
+ />
+ </ul>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx
index beb8fe92be5..fa4f38355fb 100644
--- a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx
@@ -47,38 +47,37 @@ export default function MembersListHeader({
{total !== undefined && (
<span className="pull-right little-spacer-top">
<strong>{formatMeasure(total, 'INT')}</strong> {translate('organization.members.members')}
- {organization.alm &&
- organization.alm.membersSync && (
- <HelpTooltip
- className="spacer-left"
- overlay={
- <div className="abs-width-300 markdown cut-margins">
- <p>
- {translate(
- 'organization.members.auto_sync_total_help',
- sanitizeAlmId(organization.alm.key)
- )}
- </p>
- {currentUser.personalOrganization !== organization.key && (
- <>
- <hr />
- <p>
- <a
- href={getAlmMembersUrl(organization.alm.key, organization.alm.url)}
- rel="noopener noreferrer"
- target="_blank">
- {translateWithParameters(
- 'organization.members.see_all_members_on_x',
- translate(sanitizeAlmId(organization.alm.key))
- )}
- </a>
- </p>
- </>
+ {organization.alm && organization.alm.membersSync && (
+ <HelpTooltip
+ className="spacer-left"
+ overlay={
+ <div className="abs-width-300 markdown cut-margins">
+ <p>
+ {translate(
+ 'organization.members.auto_sync_total_help',
+ sanitizeAlmId(organization.alm.key)
)}
- </div>
- }
- />
- )}
+ </p>
+ {currentUser.personalOrganization !== organization.key && (
+ <>
+ <hr />
+ <p>
+ <a
+ href={getAlmMembersUrl(organization.alm.key, organization.alm.url)}
+ rel="noopener noreferrer"
+ target="_blank">
+ {translateWithParameters(
+ 'organization.members.see_all_members_on_x',
+ translate(sanitizeAlmId(organization.alm.key))
+ )}
+ </a>
+ </p>
+ </>
+ )}
+ </div>
+ }
+ />
+ )}
</span>
)}
</div>
diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx
index 4859e2f2ee5..66bd49ce5cd 100644
--- a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx
+++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx
@@ -127,15 +127,14 @@ export default class MembersListItem extends React.PureComponent<Props, State> {
/>
)}
- {removeMember &&
- this.state.removeMemberForm && (
- <RemoveMemberForm
- member={this.props.member}
- onClose={this.closeRemoveMemberForm}
- organization={this.props.organization}
- removeMember={removeMember}
- />
- )}
+ {removeMember && this.state.removeMemberForm && (
+ <RemoveMemberForm
+ member={this.props.member}
+ onClose={this.closeRemoveMemberForm}
+ organization={this.props.organization}
+ removeMember={removeMember}
+ />
+ )}
</>
)}
</tr>
diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx
index 89fe2e97ce9..a520f22a579 100644
--- a/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx
@@ -50,16 +50,14 @@ export default function MembersPageHeader(props: Props) {
<DeferredSpinner loading={props.loading} />
{isAdmin && (
<div className="page-actions text-right">
- {almKey &&
- isGithub(almKey) &&
- !showSyncNotif && (
- <SyncMemberForm
- buttonText={translate('organization.members.config_synchro')}
- hasOtherMembers={members && members.length > 1}
- organization={organization}
- refreshMembers={refreshMembers}
- />
- )}
+ {almKey && isGithub(almKey) && !showSyncNotif && (
+ <SyncMemberForm
+ buttonText={translate('organization.members.config_synchro')}
+ hasOtherMembers={members && members.length > 1}
+ organization={organization}
+ refreshMembers={refreshMembers}
+ />
+ )}
{!hasMemberSync && (
<div className="display-inline-block spacer-left spacer-bottom">
<AddMemberForm
@@ -87,24 +85,22 @@ export default function MembersPageHeader(props: Props) {
)
}}
/>
- {almKey &&
- isGithub(almKey) &&
- showSyncNotif && (
- <Alert className="spacer-top" display="inline" variant="info">
- {translateWithParameters(
- 'organization.members.auto_sync_members_from_org_x',
- translate('organization', almKey)
- )}
- <span className="spacer-left">
- <SyncMemberForm
- buttonText={translate('configure')}
- hasOtherMembers={members && members.length > 1}
- organization={organization}
- refreshMembers={refreshMembers}
- />
- </span>
- </Alert>
- )}
+ {almKey && isGithub(almKey) && showSyncNotif && (
+ <Alert className="spacer-top" display="inline" variant="info">
+ {translateWithParameters(
+ 'organization.members.auto_sync_members_from_org_x',
+ translate('organization', almKey)
+ )}
+ <span className="spacer-left">
+ <SyncMemberForm
+ buttonText={translate('configure')}
+ hasOtherMembers={members && members.length > 1}
+ organization={organization}
+ refreshMembers={refreshMembers}
+ />
+ </span>
+ </Alert>
+ )}
</div>
</header>
);
diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx
index ae0fae4bcbf..5440ef11efe 100644
--- a/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx
+++ b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx
@@ -209,33 +209,32 @@ export default class OrganizationMembers extends React.PureComponent<Props, Stat
organization={organization}
refreshMembers={this.refreshMembers}
/>
- {members !== undefined &&
- paging !== undefined && (
- <>
- <MembersListHeader
- currentUser={currentUser}
- handleSearch={this.handleSearchMembers}
- organization={organization}
+ {members !== undefined && paging !== undefined && (
+ <>
+ <MembersListHeader
+ currentUser={currentUser}
+ handleSearch={this.handleSearchMembers}
+ organization={organization}
+ total={paging.total}
+ />
+ <MembersList
+ currentUser={currentUser}
+ members={members}
+ organization={organization}
+ organizationGroups={groups}
+ removeMember={hasMemberSync ? undefined : this.handleRemoveMember}
+ updateMemberGroups={this.updateMemberGroups}
+ />
+ {paging.total !== 0 && (
+ <ListFooter
+ count={members.length}
+ loadMore={this.handleLoadMoreMembers}
+ ready={!loading}
total={paging.total}
/>
- <MembersList
- currentUser={currentUser}
- members={members}
- organization={organization}
- organizationGroups={groups}
- removeMember={hasMemberSync ? undefined : this.handleRemoveMember}
- updateMemberGroups={this.updateMemberGroups}
- />
- {paging.total !== 0 && (
- <ListFooter
- count={members.length}
- loadMore={this.handleLoadMoreMembers}
- ready={!loading}
- total={paging.total}
- />
- )}
- </>
- )}
+ )}
+ </>
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx
index 5f38c424a8f..c191b573b52 100644
--- a/server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx
+++ b/server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx
@@ -139,15 +139,14 @@ export class SyncMemberForm extends React.PureComponent<Props, State> {
<li>{translate('organization.members.management.choose_members_permissions')}</li>
</ul>
</div>
- {almKey &&
- showWarning && (
- <Alert className="big-spacer-top" variant="warning">
- {translateWithParameters(
- 'organization.members.management.automatic.warning_x',
- translate('organization', almKey)
- )}
- </Alert>
- )}
+ {almKey && showWarning && (
+ <Alert className="big-spacer-top" variant="warning">
+ {translateWithParameters(
+ 'organization.members.management.automatic.warning_x',
+ translate('organization', almKey)
+ )}
+ </Alert>
+ )}
</RadioCard>
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx b/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx
index 256a38d66f3..155ec59ce3a 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx
@@ -70,8 +70,9 @@ export function EmptyOverview({
}
/>
)}
- {!hasBranches &&
- !hasAnalyses && <AnalyzeTutorial component={component} currentUser={currentUser} />}
+ {!hasBranches && !hasAnalyses && (
+ <AnalyzeTutorial component={component} currentUser={currentUser} />
+ )}
</>
) : (
<WarningMessage
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx
index 709754c92b0..1a1117177a8 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx
@@ -82,14 +82,13 @@ export class Meta extends React.PureComponent<Props> {
/>
)}
- {qualityProfiles &&
- qualityProfiles.length > 0 && (
- <MetaQualityProfiles
- headerClassName={qualityGate ? 'big-spacer-top' : undefined}
- organization={organizationsEnabled ? component.organization : undefined}
- profiles={qualityProfiles}
- />
- )}
+ {qualityProfiles && qualityProfiles.length > 0 && (
+ <MetaQualityProfiles
+ headerClassName={qualityGate ? 'big-spacer-top' : undefined}
+ organization={organizationsEnabled ? component.organization : undefined}
+ profiles={qualityProfiles}
+ />
+ )}
</div>
);
}
@@ -145,16 +144,14 @@ export class Meta extends React.PureComponent<Props> {
{organizationsEnabled && <MetaOrganizationKey organization={component.organization} />}
</div>
- {!isPrivate &&
- (isProject || isApp) &&
- metrics && (
- <BadgesModal
- branchLike={branchLike}
- metrics={metrics}
- project={component.key}
- qualifier={component.qualifier}
- />
- )}
+ {!isPrivate && (isProject || isApp) && metrics && (
+ <BadgesModal
+ branchLike={branchLike}
+ metrics={metrics}
+ project={component.key}
+ qualifier={component.qualifier}
+ />
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx b/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx
index 7af63eba6fb..279dfca0c78 100644
--- a/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.tsx
@@ -104,20 +104,21 @@ export default class ApplicationQualityGate extends React.PureComponent<Props, S
)}
</h2>
- {projects &&
- metrics && (
- <div
- className="overview-quality-gate-conditions-list clearfix"
- id="overview-quality-gate-conditions-list">
- {projects.filter(project => project.status !== 'OK').map(project => (
+ {projects && metrics && (
+ <div
+ className="overview-quality-gate-conditions-list clearfix"
+ id="overview-quality-gate-conditions-list">
+ {projects
+ .filter(project => project.status !== 'OK')
+ .map(project => (
<ApplicationQualityGateProject
key={project.key}
metrics={metrics}
project={project}
/>
))}
- </div>
- )}
+ </div>
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.tsx b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.tsx
index c02add35b60..fb39c06bf48 100644
--- a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.tsx
@@ -160,12 +160,11 @@ export default class QualityGateCondition extends React.PureComponent<Props> {
<IssueTypeIcon className="little-spacer-right" query={metric.key} />
{metric.name}
</div>
- {!isDiff &&
- condition.period != null && (
- <div className="overview-quality-gate-condition-period">
- {translate('quality_gates.conditions.new_code')}
- </div>
- )}
+ {!isDiff && condition.period != null && (
+ <div className="overview-quality-gate-condition-period">
+ {translate('quality_gates.conditions.new_code')}
+ </div>
+ )}
<div className="overview-quality-gate-threshold">
{operator} {formatMeasure(threshold, metric.type)}
</div>
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx
index f3eb1a147d9..67b1ab5d959 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx
@@ -27,9 +27,11 @@ interface Props {
confirmButtonText: string;
header: string;
onClose: () => void;
- onSubmit: (
- data: { description: string; name: string; projectKeyPattern: string }
- ) => Promise<void>;
+ onSubmit: (data: {
+ description: string;
+ name: string;
+ projectKeyPattern: string;
+ }) => Promise<void>;
permissionTemplate?: { description?: string; name: string; projectKeyPattern?: string };
}
diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx
index b2c8473d413..f9808246cb9 100644
--- a/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx
@@ -132,38 +132,34 @@ export default class App extends React.PureComponent<Props, State> {
};
addPermissionToGroup = (groups: T.PermissionGroup[], group: string, permission: string) => {
- return groups.map(
- candidate =>
- candidate.name === group
- ? { ...candidate, permissions: [...candidate.permissions, permission] }
- : candidate
+ return groups.map(candidate =>
+ candidate.name === group
+ ? { ...candidate, permissions: [...candidate.permissions, permission] }
+ : candidate
);
};
addPermissionToUser = (users: T.PermissionUser[], user: string, permission: string) => {
- return users.map(
- candidate =>
- candidate.login === user
- ? { ...candidate, permissions: [...candidate.permissions, permission] }
- : candidate
+ return users.map(candidate =>
+ candidate.login === user
+ ? { ...candidate, permissions: [...candidate.permissions, permission] }
+ : candidate
);
};
removePermissionFromGroup = (groups: T.PermissionGroup[], group: string, permission: string) => {
- return groups.map(
- candidate =>
- candidate.name === group
- ? { ...candidate, permissions: without(candidate.permissions, permission) }
- : candidate
+ return groups.map(candidate =>
+ candidate.name === group
+ ? { ...candidate, permissions: without(candidate.permissions, permission) }
+ : candidate
);
};
removePermissionFromUser = (users: T.PermissionUser[], user: string, permission: string) => {
- return users.map(
- candidate =>
- candidate.login === user
- ? { ...candidate, permissions: without(candidate.permissions, permission) }
- : candidate
+ return users.map(candidate =>
+ candidate.login === user
+ ? { ...candidate, permissions: without(candidate.permissions, permission) }
+ : candidate
);
};
diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/App-test.tsx
new file mode 100644
index 00000000000..b1440190203
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/App-test.tsx
@@ -0,0 +1,134 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import App from '../App';
+import {
+ grantPermissionToGroup,
+ grantPermissionToUser,
+ revokePermissionFromGroup,
+ revokePermissionFromUser
+} from '../../../../../api/permissions';
+import { mockOrganization } from '../../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../../helpers/testUtils';
+
+jest.mock('../../../../../api/permissions', () => ({
+ getGlobalPermissionsGroups: jest.fn().mockResolvedValue({
+ paging: { pageIndex: 1, pageSize: 100, total: 2 },
+ groups: [
+ { name: 'Anyone', permissions: ['admin', 'codeviewer', 'issueadmin'] },
+ { id: '1', name: 'SonarSource', description: 'SonarSource team', permissions: [] }
+ ]
+ }),
+ getGlobalPermissionsUsers: jest.fn().mockResolvedValue({
+ paging: { pageIndex: 1, pageSize: 100, total: 3 },
+ users: [
+ {
+ avatar: 'admin-avatar',
+ email: 'admin@gmail.com',
+ login: 'admin',
+ name: 'Admin Admin',
+ permissions: ['admin']
+ },
+ {
+ avatar: 'user-avatar-1',
+ email: 'user1@gmail.com',
+ login: 'user1',
+ name: 'User Number 1',
+ permissions: []
+ },
+ {
+ avatar: 'user-avatar-2',
+ email: 'user2@gmail.com',
+ login: 'user2',
+ name: 'User Number 2',
+ permissions: []
+ }
+ ]
+ }),
+ grantPermissionToGroup: jest.fn().mockResolvedValue({}),
+ grantPermissionToUser: jest.fn().mockResolvedValue({}),
+ revokePermissionFromGroup: jest.fn().mockResolvedValue({}),
+ revokePermissionFromUser: jest.fn().mockResolvedValue({})
+}));
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+describe('should manage state correctly', () => {
+ it('should add and remove permission to a group', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ const instance = wrapper.instance();
+ const apiPayload = { groupName: 'Anyone', permission: 'foo', organization: 'foo' };
+
+ instance.grantPermissionToGroup('Anyone', 'foo');
+ const groupState = wrapper.state('groups');
+ expect(groupState[0].permissions).toHaveLength(4);
+ expect(groupState[0].permissions).toContain('foo');
+ await waitAndUpdate(wrapper);
+ expect(grantPermissionToGroup).toHaveBeenCalledWith(apiPayload);
+ expect(wrapper.state('groups')).toBe(groupState);
+
+ (grantPermissionToGroup as jest.Mock).mockRejectedValueOnce({});
+ instance.grantPermissionToGroup('Anyone', 'bar');
+ expect(wrapper.state('groups')[0].permissions).toHaveLength(5);
+ expect(wrapper.state('groups')[0].permissions).toContain('bar');
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state('groups')[0].permissions).toHaveLength(4);
+ expect(wrapper.state('groups')[0].permissions).not.toContain('bar');
+
+ instance.revokePermissionFromGroup('Anyone', 'foo');
+ expect(wrapper.state('groups')[0].permissions).toHaveLength(3);
+ expect(wrapper.state('groups')[0].permissions).not.toContain('foo');
+ await waitAndUpdate(wrapper);
+ expect(revokePermissionFromGroup).toHaveBeenCalledWith(apiPayload);
+ });
+
+ it('should add and remove permission to a user', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ const instance = wrapper.instance();
+ const apiPayload = { login: 'user1', permission: 'foo', organization: 'foo' };
+
+ instance.grantPermissionToUser('user1', 'foo');
+ expect(wrapper.state('users')[1].permissions).toHaveLength(1);
+ expect(wrapper.state('users')[1].permissions).toContain('foo');
+ await waitAndUpdate(wrapper);
+ expect(grantPermissionToUser).toHaveBeenCalledWith(apiPayload);
+
+ instance.revokePermissionFromUser('user1', 'foo');
+ expect(wrapper.state('users')[1].permissions).toHaveLength(0);
+ await waitAndUpdate(wrapper);
+ expect(revokePermissionFromUser).toHaveBeenCalledWith(apiPayload);
+ });
+});
+
+function shallowRender(props: Partial<App['props']> = {}) {
+ return shallow<App>(<App organization={mockOrganization()} {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/App-test.tsx.snap
new file mode 100644
index 00000000000..e204f06b3de
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -0,0 +1,136 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="page page-limited"
+>
+ <Suggestions
+ suggestions="global_permissions"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="global_permissions.permission"
+ />
+ <PageHeader
+ loading={true}
+ organization={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
+ />
+ <Connect(AllHoldersList)
+ filter="all"
+ grantPermissionToGroup={[Function]}
+ grantPermissionToUser={[Function]}
+ groups={Array []}
+ loadHolders={[Function]}
+ loading={true}
+ onFilter={[Function]}
+ onLoadMore={[Function]}
+ onSearch={[Function]}
+ query=""
+ revokePermissionFromGroup={[Function]}
+ revokePermissionFromUser={[Function]}
+ users={Array []}
+ />
+</div>
+`;
+
+exports[`should render correctly 2`] = `
+<div
+ className="page page-limited"
+>
+ <Suggestions
+ suggestions="global_permissions"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="global_permissions.permission"
+ />
+ <PageHeader
+ loading={false}
+ organization={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
+ />
+ <Connect(AllHoldersList)
+ filter="all"
+ grantPermissionToGroup={[Function]}
+ grantPermissionToUser={[Function]}
+ groups={
+ Array [
+ Object {
+ "name": "Anyone",
+ "permissions": Array [
+ "admin",
+ "codeviewer",
+ "issueadmin",
+ ],
+ },
+ Object {
+ "description": "SonarSource team",
+ "id": "1",
+ "name": "SonarSource",
+ "permissions": Array [],
+ },
+ ]
+ }
+ groupsPaging={
+ Object {
+ "pageIndex": 1,
+ "pageSize": 100,
+ "total": 2,
+ }
+ }
+ loadHolders={[Function]}
+ loading={false}
+ onFilter={[Function]}
+ onLoadMore={[Function]}
+ onSearch={[Function]}
+ query=""
+ revokePermissionFromGroup={[Function]}
+ revokePermissionFromUser={[Function]}
+ users={
+ Array [
+ Object {
+ "avatar": "admin-avatar",
+ "email": "admin@gmail.com",
+ "login": "admin",
+ "name": "Admin Admin",
+ "permissions": Array [
+ "admin",
+ ],
+ },
+ Object {
+ "avatar": "user-avatar-1",
+ "email": "user1@gmail.com",
+ "login": "user1",
+ "name": "User Number 1",
+ "permissions": Array [],
+ },
+ Object {
+ "avatar": "user-avatar-2",
+ "email": "user2@gmail.com",
+ "login": "user2",
+ "name": "User Number 2",
+ "permissions": Array [],
+ },
+ ]
+ }
+ usersPaging={
+ Object {
+ "pageIndex": 1,
+ "pageSize": 100,
+ "total": 3,
+ }
+ }
+ />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx
index 054cd538963..6beabeebc1f 100644
--- a/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx
@@ -166,38 +166,34 @@ export default class App extends React.PureComponent<Props, State> {
};
addPermissionToGroup = (group: string, permission: string) => {
- return this.state.groups.map(
- candidate =>
- candidate.name === group
- ? { ...candidate, permissions: [...candidate.permissions, permission] }
- : candidate
+ return this.state.groups.map(candidate =>
+ candidate.name === group
+ ? { ...candidate, permissions: [...candidate.permissions, permission] }
+ : candidate
);
};
addPermissionToUser = (user: string, permission: string) => {
- return this.state.users.map(
- candidate =>
- candidate.login === user
- ? { ...candidate, permissions: [...candidate.permissions, permission] }
- : candidate
+ return this.state.users.map(candidate =>
+ candidate.login === user
+ ? { ...candidate, permissions: [...candidate.permissions, permission] }
+ : candidate
);
};
removePermissionFromGroup = (group: string, permission: string) => {
- return this.state.groups.map(
- candidate =>
- candidate.name === group
- ? { ...candidate, permissions: without(candidate.permissions, permission) }
- : candidate
+ return this.state.groups.map(candidate =>
+ candidate.name === group
+ ? { ...candidate, permissions: without(candidate.permissions, permission) }
+ : candidate
);
};
removePermissionFromUser = (user: string, permission: string) => {
- return this.state.users.map(
- candidate =>
- candidate.login === user
- ? { ...candidate, permissions: without(candidate.permissions, permission) }
- : candidate
+ return this.state.users.map(candidate =>
+ candidate.login === user
+ ? { ...candidate, permissions: without(candidate.permissions, permission) }
+ : candidate
);
};
@@ -389,14 +385,13 @@ export default class App extends React.PureComponent<Props, State> {
onChange={this.handleVisibilityChange}
visibility={component.visibility}
/>
- {showUpgradeBox &&
- organization && (
- <UpgradeOrganizationBox
- className="big-spacer-bottom"
- onOrganizationUpgrade={this.handleOrganizationUpgrade}
- organization={organization}
- />
- )}
+ {showUpgradeBox && organization && (
+ <UpgradeOrganizationBox
+ className="big-spacer-bottom"
+ onOrganizationUpgrade={this.handleOrganizationUpgrade}
+ organization={organization}
+ />
+ )}
{this.state.disclaimer && (
<PublicProjectDisclaimer
component={component}
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/App-test.tsx
new file mode 100644
index 00000000000..47839ce1be2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/App-test.tsx
@@ -0,0 +1,162 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import App from '../App';
+import {
+ grantPermissionToGroup,
+ grantPermissionToUser,
+ revokePermissionFromGroup,
+ revokePermissionFromUser
+} from '../../../../../api/permissions';
+import { mockComponent, mockOrganization } from '../../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../../helpers/testUtils';
+
+jest.mock('../../../../../api/permissions', () => ({
+ getPermissionsGroupsForComponent: jest.fn().mockResolvedValue({
+ paging: { pageIndex: 1, pageSize: 100, total: 2 },
+ groups: [
+ { name: 'Anyone', permissions: ['admin', 'codeviewer', 'issueadmin'] },
+ { id: '1', name: 'SonarSource', description: 'SonarSource team', permissions: [] }
+ ]
+ }),
+ getPermissionsUsersForComponent: jest.fn().mockResolvedValue({
+ paging: { pageIndex: 1, pageSize: 100, total: 3 },
+ users: [
+ {
+ avatar: 'admin-avatar',
+ email: 'admin@gmail.com',
+ login: 'admin',
+ name: 'Admin Admin',
+ permissions: ['admin']
+ },
+ {
+ avatar: 'user-avatar-1',
+ email: 'user1@gmail.com',
+ login: 'user1',
+ name: 'User Number 1',
+ permissions: []
+ },
+ {
+ avatar: 'user-avatar-2',
+ email: 'user2@gmail.com',
+ login: 'user2',
+ name: 'User Number 2',
+ permissions: []
+ }
+ ]
+ }),
+ grantPermissionToGroup: jest.fn().mockResolvedValue({}),
+ grantPermissionToUser: jest.fn().mockResolvedValue({}),
+ revokePermissionFromGroup: jest.fn().mockResolvedValue({}),
+ revokePermissionFromUser: jest.fn().mockResolvedValue({})
+}));
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+describe('should manage state correctly', () => {
+ it('should handle permission select', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ const instance = wrapper.instance();
+ instance.handlePermissionSelect('foo');
+ expect(wrapper.state('selectedPermission')).toBe('foo');
+ instance.handlePermissionSelect('foo');
+ expect(wrapper.state('selectedPermission')).toBe(undefined);
+ });
+
+ it('should add and remove permission to a group', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ const instance = wrapper.instance();
+ const apiPayload = {
+ projectKey: 'my-project',
+ groupName: 'Anyone',
+ permission: 'foo',
+ organization: 'foo'
+ };
+
+ instance.grantPermissionToGroup('Anyone', 'foo');
+ const groupState = wrapper.state('groups');
+ expect(groupState[0].permissions).toHaveLength(4);
+ expect(groupState[0].permissions).toContain('foo');
+ await waitAndUpdate(wrapper);
+ expect(grantPermissionToGroup).toHaveBeenCalledWith(apiPayload);
+ expect(wrapper.state('groups')).toBe(groupState);
+
+ (grantPermissionToGroup as jest.Mock).mockRejectedValueOnce({});
+ instance.grantPermissionToGroup('Anyone', 'bar');
+ expect(wrapper.state('groups')[0].permissions).toHaveLength(5);
+ expect(wrapper.state('groups')[0].permissions).toContain('bar');
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state('groups')[0].permissions).toHaveLength(4);
+ expect(wrapper.state('groups')[0].permissions).not.toContain('bar');
+
+ instance.revokePermissionFromGroup('Anyone', 'foo');
+ expect(wrapper.state('groups')[0].permissions).toHaveLength(3);
+ expect(wrapper.state('groups')[0].permissions).not.toContain('foo');
+ await waitAndUpdate(wrapper);
+ expect(revokePermissionFromGroup).toHaveBeenCalledWith(apiPayload);
+ });
+
+ it('should add and remove permission to a user', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ const instance = wrapper.instance();
+ const apiPayload = {
+ projectKey: 'my-project',
+ login: 'user1',
+ permission: 'foo',
+ organization: 'foo'
+ };
+
+ instance.grantPermissionToUser('user1', 'foo');
+ expect(wrapper.state('users')[1].permissions).toHaveLength(1);
+ expect(wrapper.state('users')[1].permissions).toContain('foo');
+ await waitAndUpdate(wrapper);
+ expect(grantPermissionToUser).toHaveBeenCalledWith(apiPayload);
+
+ instance.revokePermissionFromUser('user1', 'foo');
+ expect(wrapper.state('users')[1].permissions).toHaveLength(0);
+ await waitAndUpdate(wrapper);
+ expect(revokePermissionFromUser).toHaveBeenCalledWith(apiPayload);
+ });
+});
+
+function shallowRender(props: Partial<App['props']> = {}) {
+ return shallow<App>(
+ <App
+ component={mockComponent()}
+ fetchOrganization={jest.fn()}
+ onComponentChange={jest.fn()}
+ organization={mockOrganization()}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/App-test.tsx.snap
new file mode 100644
index 00000000000..0cacc59dfc9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -0,0 +1,224 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="page page-limited"
+ id="project-permissions-page"
+>
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="permissions.page"
+ />
+ <PageHeader
+ 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 [],
+ }
+ }
+ loadHolders={[Function]}
+ loading={true}
+ />
+ <div>
+ <VisibilitySelector
+ className="big-spacer-top big-spacer-bottom"
+ onChange={[Function]}
+ />
+ </div>
+ <AllHoldersList
+ 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 [],
+ }
+ }
+ filter="all"
+ grantPermissionToGroup={[Function]}
+ grantPermissionToUser={[Function]}
+ groups={Array []}
+ onFilterChange={[Function]}
+ onLoadMore={[Function]}
+ onPermissionSelect={[Function]}
+ onQueryChange={[Function]}
+ query=""
+ revokePermissionFromGroup={[Function]}
+ revokePermissionFromUser={[Function]}
+ users={Array []}
+ />
+</div>
+`;
+
+exports[`should render correctly 2`] = `
+<div
+ className="page page-limited"
+ id="project-permissions-page"
+>
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="permissions.page"
+ />
+ <PageHeader
+ 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 [],
+ }
+ }
+ loadHolders={[Function]}
+ loading={false}
+ />
+ <div>
+ <VisibilitySelector
+ className="big-spacer-top big-spacer-bottom"
+ onChange={[Function]}
+ />
+ </div>
+ <AllHoldersList
+ 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 [],
+ }
+ }
+ filter="all"
+ grantPermissionToGroup={[Function]}
+ grantPermissionToUser={[Function]}
+ groups={
+ Array [
+ Object {
+ "name": "Anyone",
+ "permissions": Array [
+ "admin",
+ "codeviewer",
+ "issueadmin",
+ ],
+ },
+ Object {
+ "description": "SonarSource team",
+ "id": "1",
+ "name": "SonarSource",
+ "permissions": Array [],
+ },
+ ]
+ }
+ groupsPaging={
+ Object {
+ "pageIndex": 1,
+ "pageSize": 100,
+ "total": 2,
+ }
+ }
+ onFilterChange={[Function]}
+ onLoadMore={[Function]}
+ onPermissionSelect={[Function]}
+ onQueryChange={[Function]}
+ query=""
+ revokePermissionFromGroup={[Function]}
+ revokePermissionFromUser={[Function]}
+ users={
+ Array [
+ Object {
+ "avatar": "admin-avatar",
+ "email": "admin@gmail.com",
+ "login": "admin",
+ "name": "Admin Admin",
+ "permissions": Array [
+ "admin",
+ ],
+ },
+ Object {
+ "avatar": "user-avatar-1",
+ "email": "user1@gmail.com",
+ "login": "user1",
+ "name": "User Number 1",
+ "permissions": Array [],
+ },
+ Object {
+ "avatar": "user-avatar-2",
+ "email": "user2@gmail.com",
+ "login": "user2",
+ "name": "User Number 2",
+ "permissions": Array [],
+ },
+ ]
+ }
+ usersPaging={
+ Object {
+ "pageIndex": 1,
+ "pageSize": 100,
+ "total": 3,
+ }
+ }
+ />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx
index 5ad0b29ba3b..839351b2e64 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx
@@ -148,16 +148,15 @@ export default class HoldersList extends React.PureComponent<Props, State> {
<tbody>
{items.length === 0 && !this.props.loading && this.renderEmpty()}
{itemWithPermissions.map(item => this.renderItem(item, permissions))}
- {itemWithPermissions.length > 0 &&
- itemWithoutPermissions.length > 0 && (
- <>
- <tr>
- <td className="divider" colSpan={20} />
- </tr>
- <tr />
- {/* Keep correct zebra colors in the table */}
- </>
- )}
+ {itemWithPermissions.length > 0 && itemWithoutPermissions.length > 0 && (
+ <>
+ <tr>
+ <td className="divider" colSpan={20} />
+ </tr>
+ <tr />
+ {/* Keep correct zebra colors in the table */}
+ </>
+ )}
{itemWithoutPermissions.map(item => this.renderItem(item, permissions))}
</tbody>
</table>
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
index 7c18623172a..eb8b5f89ec1 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
@@ -161,14 +161,13 @@ export class App extends React.PureComponent<Props, State> {
<MaintainabilityBox component={component.key} measures={measures!} />
</div>
- {subComponents !== undefined &&
- totalSubComponents !== undefined && (
- <WorstProjects
- component={component.key}
- subComponents={subComponents}
- total={totalSubComponents}
- />
- )}
+ {subComponents !== undefined && totalSubComponents !== undefined && (
+ <WorstProjects
+ component={component.key}
+ subComponents={subComponents}
+ total={totalSubComponents}
+ />
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/ReleasabilityBox.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/ReleasabilityBox.tsx
index fdffce0d3dc..4fb6e1ad37a 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/ReleasabilityBox.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/ReleasabilityBox.tsx
@@ -50,24 +50,22 @@ export default function ReleasabilityBox({ component, measures }: Props) {
<RatingFreshness lastChange={lastReleasabilityChange} rating={rating} />
- {effort &&
- Number(effort) > 0 && (
- <div className="portfolio-effort">
- <Link
- to={getComponentDrilldownUrl({ componentKey: component, metric: 'alert_status' })}>
- <span>
- <Measure
- className="little-spacer-right"
- metricKey="projects"
- metricType="SHORT_INT"
- value={effort}
- />
- {Number(effort) === 1 ? 'project' : 'projects'}
- </span>
- </Link>{' '}
- <Level level="ERROR" small={true} />
- </div>
- )}
+ {effort && Number(effort) > 0 && (
+ <div className="portfolio-effort">
+ <Link to={getComponentDrilldownUrl({ componentKey: component, metric: 'alert_status' })}>
+ <span>
+ <Measure
+ className="little-spacer-right"
+ metricKey="projects"
+ metricType="SHORT_INT"
+ value={effort}
+ />
+ {Number(effort) === 1 ? 'project' : 'projects'}
+ </span>
+ </Link>{' '}
+ <Level level="ERROR" small={true} />
+ </div>
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/actions.ts b/server/sonar-web/src/main/js/apps/projectActivity/actions.ts
index 74489067d1a..8e0ed07ac0f 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/actions.ts
+++ b/server/sonar-web/src/main/js/apps/projectActivity/actions.ts
@@ -49,8 +49,8 @@ export function changeEvent(analysis: string, event: T.AnalysisEvent) {
}
return {
...item,
- events: item.events.map(
- eventItem => (eventItem.key === event.key ? { ...eventItem, ...event } : eventItem)
+ events: item.events.map(eventItem =>
+ eventItem.key === event.key ? { ...eventItem, ...event } : eventItem
)
};
})
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.tsx
index 31fbc3e5b15..ef8e1fc1bee 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.tsx
@@ -113,10 +113,9 @@ export default class GraphsTooltips extends React.PureComponent<Props> {
tooltipIdx={tooltipIdx}
/>
)}
- {events &&
- events.length > 0 && (
- <GraphsTooltipsContentEvents addSeparator={addSeparator} events={events} />
- )}
+ {events && events.length > 0 && (
+ <GraphsTooltipsContentEvents addSeparator={addSeparator} events={events} />
+ )}
</table>
</div>
</Popup>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityEventSelectOption-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityEventSelectOption-test.tsx
new file mode 100644
index 00000000000..15bf4126525
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityEventSelectOption-test.tsx
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import ProjectActivityEventSelectOption from '../ProjectActivityEventSelectOption';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<ProjectActivityEventSelectOption['props']> = {}) {
+ return shallow(
+ <ProjectActivityEventSelectOption
+ onFocus={jest.fn()}
+ onSelect={jest.fn()}
+ option={{ label: 'Foo', value: 'foo' }}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityEventSelectOption-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityEventSelectOption-test.tsx.snap
new file mode 100644
index 00000000000..a31966320b7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityEventSelectOption-test.tsx.snap
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ onMouseDown={[Function]}
+ onMouseEnter={[Function]}
+ onMouseMove={[Function]}
+ role="link"
+ tabIndex={0}
+ title="Foo"
+>
+ <ProjectEventIcon
+ className="project-activity-event-icon foo"
+ />
+ <span
+ className="little-spacer-left"
+ />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx
index 60011b07ab1..f75fcfb9461 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx
@@ -134,24 +134,22 @@ export default class BranchRow extends React.PureComponent<Props, State> {
/>
)}
- {this.state.renaming &&
- isMainBranch(branchLike) && (
- <RenameBranchModal
- branch={branchLike}
- component={component}
- onClose={this.handleRenamingStop}
- onRename={this.handleChange}
- />
- )}
+ {this.state.renaming && isMainBranch(branchLike) && (
+ <RenameBranchModal
+ branch={branchLike}
+ component={component}
+ onClose={this.handleRenamingStop}
+ onRename={this.handleChange}
+ />
+ )}
- {this.state.changingLeak &&
- isLongLivingBranch(branchLike) && (
- <LeakPeriodForm
- branch={branchLike.name}
- onClose={this.handleChangingLeakStop}
- project={component}
- />
- )}
+ {this.state.changingLeak && isLongLivingBranch(branchLike) && (
+ <LeakPeriodForm
+ branch={branchLike.name}
+ onClose={this.handleChangingLeakStop}
+ project={component}
+ />
+ )}
</td>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx
index 6cfbcee0d44..e27eda543b5 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx
@@ -110,25 +110,23 @@ export default class SettingForm extends React.PureComponent<Props, State> {
{setting.inherited && (
<div className="modal-field-description">{translate('settings._default')}</div>
)}
- {!setting.inherited &&
- setting.parentValue && (
- <div className="modal-field-description">
- {translateWithParameters('settings.default_x', setting.parentValue)}
- </div>
- )}
+ {!setting.inherited && setting.parentValue && (
+ <div className="modal-field-description">
+ {translateWithParameters('settings.default_x', setting.parentValue)}
+ </div>
+ )}
</div>
</div>
<footer className="modal-foot">
- {!setting.inherited &&
- setting.parentValue && (
- <Button
- className="pull-left"
- disabled={this.state.submitting}
- onClick={this.handleResetClick}
- type="reset">
- {translate('reset_to_default')}
- </Button>
- )}
+ {!setting.inherited && setting.parentValue && (
+ <Button
+ className="pull-left"
+ disabled={this.state.submitting}
+ onClick={this.handleResetClick}
+ type="reset">
+ {translate('reset_to_default')}
+ </Button>
+ )}
{this.state.submitting && <i className="spinner spacer-right" />}
<SubmitButton disabled={submitDisabled}>{translate('save')}</SubmitButton>
<ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx
index b448451b597..cf0bce1ac04 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx
@@ -74,21 +74,18 @@ export default function ProjectCardLeak({ height, organization, project }: Props
{hasTags && <TagsList className="spacer-left note" tags={project.tags} />}
</div>
</div>
- {project.analysisDate &&
- project.leakPeriodDate && (
- <div className="project-card-dates note text-right pull-right">
- <span className="project-card-leak-date pull-right">
- {translateWithParameters('projects.new_code_period_x', formatDuration(periodMs))}
- </span>
- <DateTimeFormatter date={project.analysisDate}>
- {formattedDate => (
- <span>
- {translateWithParameters('projects.last_analysis_on_x', formattedDate)}
- </span>
- )}
- </DateTimeFormatter>
- </div>
- )}
+ {project.analysisDate && project.leakPeriodDate && (
+ <div className="project-card-dates note text-right pull-right">
+ <span className="project-card-leak-date pull-right">
+ {translateWithParameters('projects.new_code_period_x', formatDuration(periodMs))}
+ </span>
+ <DateTimeFormatter date={project.analysisDate}>
+ {formattedDate => (
+ <span>{translateWithParameters('projects.last_analysis_on_x', formattedDate)}</span>
+ )}
+ </DateTimeFormatter>
+ </div>
+ )}
</div>
{project.analysisDate && project.leakPeriodDate ? (
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
index adeed9fb8ba..fac7ae2096c 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
@@ -105,8 +105,8 @@ it('fetches projects', () => {
});
it('redirects to the saved search', () => {
- (get as jest.Mock).mockImplementation(
- (key: string) => (key === 'sonarqube.projects.view' ? 'leak' : null)
+ (get as jest.Mock).mockImplementation((key: string) =>
+ key === 'sonarqube.projects.view' ? 'leak' : null
);
const replace = jest.fn();
shallowRender({}, jest.fn(), replace);
diff --git a/server/sonar-web/src/main/js/apps/projects/utils.ts b/server/sonar-web/src/main/js/apps/projects/utils.ts
index 80e034ded29..7f977c72b9c 100644
--- a/server/sonar-web/src/main/js/apps/projects/utils.ts
+++ b/server/sonar-web/src/main/js/apps/projects/utils.ts
@@ -187,14 +187,16 @@ export function fetchProjects(
projects: components
.map(component => {
const componentMeasures: T.Dict<string> = {};
- measures.filter(measure => measure.component === component.key).forEach(measure => {
- const value = isDiffMetric(measure.metric)
- ? getPeriodValue(measure, 1)
- : measure.value;
- if (value !== undefined) {
- componentMeasures[measure.metric] = value;
- }
- });
+ measures
+ .filter(measure => measure.component === component.key)
+ .forEach(measure => {
+ const value = isDiffMetric(measure.metric)
+ ? getPeriodValue(measure, 1)
+ : measure.value;
+ if (value !== undefined) {
+ componentMeasures[measure.metric] = value;
+ }
+ });
return { ...component, measures: componentMeasures };
})
.map(component => {
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx
index 86e8b4e1dce..17342a6a0e3 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx
@@ -170,13 +170,11 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S
<footer className="modal-foot">
{submitting && <i className="spinner spacer-right" />}
- {!loading &&
- !done &&
- permissionTemplates && (
- <Button disabled={submitting} onClick={this.handleConfirmClick}>
- {translate('apply')}
- </Button>
- )}
+ {!loading && !done && permissionTemplates && (
+ <Button disabled={submitting} onClick={this.handleConfirmClick}>
+ {translate('apply')}
+ </Button>
+ )}
<ResetButtonLink className="js-modal-close" onClick={this.props.onClose}>
{done ? translate('close') : translate('cancel')}
</ResetButtonLink>
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx
index 887d8de5ded..4aa4f650acf 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx
@@ -53,19 +53,18 @@ export default class Header extends React.PureComponent<Props, State> {
<h1 className="page-title">{translate('projects_management')}</h1>
<div className="page-actions">
- {!isSonarCloud() &&
- organization.projectVisibility && (
- <span className="big-spacer-right">
- <span className="text-middle">
- {translate('organization.default_visibility_of_new_projects')}{' '}
- <strong>{translate('visibility', organization.projectVisibility)}</strong>
- </span>
- <EditButton
- className="js-change-visibility spacer-left button-small"
- onClick={this.handleChangeVisibilityClick}
- />
+ {!isSonarCloud() && organization.projectVisibility && (
+ <span className="big-spacer-right">
+ <span className="text-middle">
+ {translate('organization.default_visibility_of_new_projects')}{' '}
+ <strong>{translate('visibility', organization.projectVisibility)}</strong>
</span>
- )}
+ <EditButton
+ className="js-change-visibility spacer-left button-small"
+ onClick={this.handleChangeVisibilityClick}
+ />
+ </span>
+ )}
{this.props.hasProvisionPermission && (
<Button id="create-project" onClick={this.props.onProjectCreate}>
{translate('qualifiers.create.TRK')}
@@ -75,14 +74,13 @@ export default class Header extends React.PureComponent<Props, State> {
<p className="page-description">{translate('projects_management.page.description')}</p>
- {!isSonarCloud() &&
- this.state.visibilityForm && (
- <ChangeDefaultVisibilityForm
- onClose={this.closeVisiblityForm}
- onConfirm={this.props.onVisibilityChange}
- organization={organization}
- />
- )}
+ {!isSonarCloud() && this.state.visibilityForm && (
+ <ChangeDefaultVisibilityForm
+ onClose={this.closeVisiblityForm}
+ onConfirm={this.props.onVisibilityChange}
+ organization={organization}
+ />
+ )}
</header>
);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
index 186d1648353..6124d4fcdc9 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
@@ -160,10 +160,9 @@ class ChangelogContainer extends React.PureComponent<Props, State> {
{this.state.events != null && this.state.events.length === 0 && <ChangelogEmpty />}
- {this.state.events != null &&
- this.state.events.length > 0 && (
- <Changelog events={this.state.events} organization={this.props.organization} />
- )}
+ {this.state.events != null && this.state.events.length > 0 && (
+ <Changelog events={this.state.events} organization={this.props.organization} />
+ )}
{shouldDisplayFooter && (
<footer className="text-center spacer-top small">
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx
index c206f54272b..a2befeebb08 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx
@@ -45,11 +45,9 @@ export default function ProfileDetails(props: Props) {
organization={organization}
profile={profile}
/>
- {profile.actions &&
- profile.actions.edit &&
- !profile.isBuiltIn && (
- <ProfilePermissions organization={organization || undefined} profile={profile} />
- )}
+ {profile.actions && profile.actions.edit && !profile.isBuiltIn && (
+ <ProfilePermissions organization={organization || undefined} profile={profile} />
+ )}
</div>
<div className="quality-profile-grid-right">
<ProfileInheritance
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx
index 968de095f73..7e41590a2ea 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx
@@ -130,17 +130,13 @@ export default class ProfileInheritance extends React.PureComponent<Props, State
return (
<div className="boxed-group quality-profile-inheritance">
- {profile.actions &&
- profile.actions.edit &&
- !profile.isBuiltIn && (
- <div className="boxed-group-actions">
- <Button
- className="pull-right js-change-parent"
- onClick={this.handleChangeParentClick}>
- {translate('quality_profiles.change_parent')}
- </Button>
- </div>
- )}
+ {profile.actions && profile.actions.edit && !profile.isBuiltIn && (
+ <div className="boxed-group-actions">
+ <Button className="pull-right js-change-parent" onClick={this.handleChangeParentClick}>
+ {translate('quality_profiles.change_parent')}
+ </Button>
+ </div>
+ )}
<header className="boxed-group-header">
<h2>{translate('quality_profiles.profile_inheritance')}</h2>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx
index 65171137d33..072e5626aae 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx
@@ -163,14 +163,13 @@ export default class ProfileProjects extends React.PureComponent<Props, State> {
const { profile } = this.props;
return (
<div className="boxed-group quality-profile-projects">
- {profile.actions &&
- profile.actions.associateProjects && (
- <div className="boxed-group-actions">
- <Button className="js-change-projects" onClick={this.handleChangeClick}>
- {translate('quality_profiles.change_projects')}
- </Button>
- </div>
- )}
+ {profile.actions && profile.actions.associateProjects && (
+ <div className="boxed-group-actions">
+ <Button className="js-change-projects" onClick={this.handleChangeClick}>
+ {translate('quality_profiles.change_projects')}
+ </Button>
+ </div>
+ )}
<header className="boxed-group-header">
<h2>{translate('projects')}</h2>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
index b9060b042c0..e3afc0d7641 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
@@ -49,7 +49,6 @@ interface State {
activatedByType: T.Dict<ByType>;
allByType: T.Dict<ByType>;
compareToSonarWay: { profile: string; profileName: string; missingRuleCount: number } | null;
- loading: boolean;
total: number | null;
}
@@ -61,7 +60,6 @@ export default class ProfileRules extends React.PureComponent<Props, State> {
activatedByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'),
allByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'),
compareToSonarWay: null,
- loading: true,
total: null
};
@@ -110,7 +108,6 @@ export default class ProfileRules extends React.PureComponent<Props, State> {
}
loadRules() {
- this.setState({ loading: true });
Promise.all([this.loadAllRules(), this.loadActivatedRules(), this.loadProfile()]).then(
responses => {
if (this.mounted) {
@@ -120,15 +117,9 @@ export default class ProfileRules extends React.PureComponent<Props, State> {
allByType: keyBy<ByType>(takeFacet(allRules, 'types'), 'val'),
activatedByType: keyBy<ByType>(takeFacet(activatedRules, 'types'), 'val'),
compareToSonarWay: showProfile && showProfile.compareToSonarWay,
- loading: false,
total: allRules.total
});
}
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
}
);
}
@@ -187,29 +178,27 @@ export default class ProfileRules extends React.PureComponent<Props, State> {
</tbody>
</table>
- {actions.edit &&
- !profile.isBuiltIn && (
- <div className="text-right big-spacer-top">
- <Link className="button js-activate-rules" to={activateMoreUrl}>
- {translate('quality_profiles.activate_more')}
- </Link>
- </div>
- )}
+ {actions.edit && !profile.isBuiltIn && (
+ <div className="text-right big-spacer-top">
+ <Link className="button js-activate-rules" to={activateMoreUrl}>
+ {translate('quality_profiles.activate_more')}
+ </Link>
+ </div>
+ )}
{/* if a user is allowed to `copy` a profile if they are a global or organization admin */}
{/* this user could potentially active more rules if the profile was not built-in */}
{/* in such cases it's better to show the button but disable it with a tooltip */}
- {actions.copy &&
- profile.isBuiltIn && (
- <div className="text-right big-spacer-top">
- <DocTooltip
- doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-profiles/activate-rules-in-built-in-profile.md')}>
- <Button className="disabled js-activate-rules">
- {translate('quality_profiles.activate_more')}
- </Button>
- </DocTooltip>
- </div>
- )}
+ {actions.copy && profile.isBuiltIn && (
+ <div className="text-right big-spacer-top">
+ <DocTooltip
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-profiles/activate-rules-in-built-in-profile.md')}>
+ <Button className="disabled js-activate-rules">
+ {translate('quality_profiles.activate_more')}
+ </Button>
+ </DocTooltip>
+ </div>
+ )}
</div>
{profile.activeDeprecatedRuleCount > 0 && (
<ProfileRulesDeprecatedWarning
@@ -218,16 +207,15 @@ export default class ProfileRules extends React.PureComponent<Props, State> {
profile={profile.key}
/>
)}
- {compareToSonarWay != null &&
- compareToSonarWay.missingRuleCount > 0 && (
- <ProfileRulesSonarWayComparison
- language={profile.language}
- organization={organization}
- profile={profile.key}
- sonarWayMissingRules={compareToSonarWay.missingRuleCount}
- sonarway={compareToSonarWay.profile}
- />
- )}
+ {compareToSonarWay != null && compareToSonarWay.missingRuleCount > 0 && (
+ <ProfileRulesSonarWayComparison
+ language={profile.language}
+ organization={organization}
+ profile={profile.key}
+ sonarWayMissingRules={compareToSonarWay.missingRuleCount}
+ sonarway={compareToSonarWay.profile}
+ />
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
index 7b19310ca2f..e129058fb20 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
@@ -180,22 +180,21 @@ export default class CreateProfileForm extends React.PureComponent<Props, State>
value={selectedLanguage}
/>
</div>
- {selectedLanguage &&
- profiles.length && (
- <div className="modal-field">
- <label htmlFor="create-profile-parent">
- {translate('quality_profiles.parent')}
- </label>
- <Select
- clearable={true}
- id="create-profile-parent"
- name="parentKey"
- onChange={this.handleParentChange}
- options={profiles}
- value={this.state.parent || ''}
- />
- </div>
- )}
+ {selectedLanguage && profiles.length && (
+ <div className="modal-field">
+ <label htmlFor="create-profile-parent">
+ {translate('quality_profiles.parent')}
+ </label>
+ <Select
+ clearable={true}
+ id="create-profile-parent"
+ name="parentKey"
+ onChange={this.handleParentChange}
+ options={profiles}
+ value={this.state.parent || ''}
+ />
+ </div>
+ )}
{importers.map(importer => (
<div
className="modal-field spacer-bottom js-importer"
diff --git a/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx b/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx
index 7d6fdc5b50c..d17d9ead8a1 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx
@@ -42,8 +42,8 @@ import {
interface Props {
cancelChange: (key: string) => void;
- changedValue: any;
changeValue: (key: string, value: any) => void;
+ changedValue: any;
checkValue: (key: string) => boolean;
component?: T.Component;
loading: boolean;
@@ -160,24 +160,21 @@ export class Definition extends React.PureComponent<Props, State> {
</span>
)}
- {!loading &&
- validationMessage && (
- <span className="text-danger">
- <AlertErrorIcon className="spacer-right" />
- <span>
- {translateWithParameters('settings.state.validation_failed', validationMessage)}
- </span>
+ {!loading && validationMessage && (
+ <span className="text-danger">
+ <AlertErrorIcon className="spacer-right" />
+ <span>
+ {translateWithParameters('settings.state.validation_failed', validationMessage)}
</span>
- )}
-
- {!loading &&
- !hasError &&
- this.state.success && (
- <span className="text-success">
- <AlertSuccessIcon className="spacer-right" />
- {translate('settings.state.saved')}
- </span>
- )}
+ </span>
+ )}
+
+ {!loading && !hasError && this.state.success && (
+ <span className="text-success">
+ <AlertSuccessIcon className="spacer-right" />
+ {translate('settings.state.saved')}
+ </span>
+ )}
</div>
<Input
diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx b/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx
index a4892c225bf..b7aae5bb572 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx
@@ -83,12 +83,11 @@ export default class DefinitionActions extends React.PureComponent<Props, State>
return (
<>
- {isDefault &&
- !hasValueChanged && (
- <div className="spacer-top note" style={{ lineHeight: '24px' }}>
- {translate('settings._default')}
- </div>
- )}
+ {isDefault && !hasValueChanged && (
+ <div className="spacer-top note" style={{ lineHeight: '24px' }}>
+ {translate('settings._default')}
+ </div>
+ )}
<div className="settings-definition-changes nowrap">
{hasValueChanged && (
<Button
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-test.tsx
new file mode 100644
index 00000000000..c614101d39f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-test.tsx
@@ -0,0 +1,89 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import { Definition } from '../Definition';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+
+const setting: T.Setting = {
+ key: 'foo',
+ value: '42',
+ inherited: true,
+ definition: {
+ key: 'foo',
+ name: 'Foo setting',
+ description: 'When Foo then Bar',
+ type: 'INTEGER',
+ category: 'general',
+ subCategory: 'SubFoo',
+ defaultValue: '42',
+ options: [],
+ fields: []
+ }
+};
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should correctly handle change of value', () => {
+ const changeValue = jest.fn();
+ const checkValue = jest.fn();
+ const wrapper = shallowRender({ changeValue, checkValue });
+ wrapper.find('Input').prop<Function>('onChange')(5);
+ expect(changeValue).toHaveBeenCalledWith(setting.definition.key, 5);
+ expect(checkValue).toHaveBeenCalledWith(setting.definition.key);
+});
+
+it('should correctly cancel value change', () => {
+ const cancelChange = jest.fn();
+ const passValidation = jest.fn();
+ const wrapper = shallowRender({ cancelChange, passValidation });
+ wrapper.find('Input').prop<Function>('onCancel')();
+ expect(cancelChange).toHaveBeenCalledWith(setting.definition.key);
+ expect(passValidation).toHaveBeenCalledWith(setting.definition.key);
+});
+
+it('should correctly save value change', async () => {
+ const saveValue = jest.fn().mockResolvedValue({});
+ const wrapper = shallowRender({ changedValue: 10, saveValue });
+ wrapper.find('DefinitionActions').prop<Function>('onSave')();
+ await waitAndUpdate(wrapper);
+ expect(saveValue).toHaveBeenCalledWith(setting.definition.key, undefined);
+ expect(wrapper.find('AlertSuccessIcon').exists()).toBe(true);
+});
+
+function shallowRender(props: Partial<Definition['props']> = {}) {
+ return shallow(
+ <Definition
+ cancelChange={jest.fn()}
+ changeValue={jest.fn()}
+ changedValue={null}
+ checkValue={jest.fn()}
+ loading={false}
+ passValidation={jest.fn()}
+ resetValue={jest.fn().mockResolvedValue({})}
+ saveValue={jest.fn().mockResolvedValue({})}
+ setting={setting}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Definition-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Definition-test.tsx.snap
new file mode 100644
index 00000000000..3ee6ede5338
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Definition-test.tsx.snap
@@ -0,0 +1,91 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="settings-definition"
+ data-key="foo"
+>
+ <div
+ className="settings-definition-left"
+ >
+ <h3
+ className="settings-definition-name"
+ title="Foo setting"
+ >
+ Foo setting
+ </h3>
+ <div
+ className="markdown small spacer-top"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "When Foo then Bar",
+ }
+ }
+ />
+ <div
+ className="settings-definition-key note little-spacer-top"
+ >
+ settings.key_x.foo
+ </div>
+ </div>
+ <div
+ className="settings-definition-right"
+ >
+ <div
+ className="settings-definition-state"
+ />
+ <Input
+ hasValueChanged={false}
+ onCancel={[Function]}
+ onChange={[Function]}
+ onSave={[Function]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "general",
+ "defaultValue": "42",
+ "description": "When Foo then Bar",
+ "fields": Array [],
+ "key": "foo",
+ "name": "Foo setting",
+ "options": Array [],
+ "subCategory": "SubFoo",
+ "type": "INTEGER",
+ },
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value="42"
+ />
+ <DefinitionActions
+ changedValue={null}
+ hasError={false}
+ hasValueChanged={false}
+ isDefault={true}
+ onCancel={[Function]}
+ onReset={[Function]}
+ onSave={[Function]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "general",
+ "defaultValue": "42",
+ "description": "When Foo then Bar",
+ "fields": Array [],
+ "key": "foo",
+ "name": "Foo setting",
+ "options": Array [],
+ "subCategory": "SubFoo",
+ "type": "INTEGER",
+ },
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ />
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionApp.tsx b/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionApp.tsx
index 4f952017b84..3769c5bdedf 100644
--- a/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionApp.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionApp.tsx
@@ -77,13 +77,9 @@ export default class EncryptionApp extends React.PureComponent<{}, State> {
<DeferredSpinner loading={loading} />
</header>
- {!loading &&
- !secretKeyAvailable && (
- <GenerateSecretKeyForm
- generateSecretKey={this.generateSecretKey}
- secretKey={secretKey}
- />
- )}
+ {!loading && !secretKeyAvailable && (
+ <GenerateSecretKeyForm generateSecretKey={this.generateSecretKey} secretKey={secretKey} />
+ )}
{secretKeyAvailable && <EncryptionForm generateSecretKey={this.generateSecretKey} />}
</div>
diff --git a/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx b/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx
index cb758e0b7b5..ef812abae95 100644
--- a/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx
+++ b/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx
@@ -167,8 +167,9 @@ export default class PageActions extends React.PureComponent<Props, State> {
{translate('system.restart_server')}
</Button>
)}
- {this.props.canRestart &&
- this.state.openRestartForm && <RestartForm onClose={this.handleServerRestartClose} />}
+ {this.props.canRestart && this.state.openRestartForm && (
+ <RestartForm onClose={this.handleServerRestartClose} />
+ )}
{this.state.openLogsLevelForm && (
<ChangeLogLevelForm
infoMsg={translate(
diff --git a/server/sonar-web/src/main/js/apps/tutorials/routes.ts b/server/sonar-web/src/main/js/apps/tutorials/routes.ts
index 7dd394424c5..d779c891054 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/routes.ts
+++ b/server/sonar-web/src/main/js/apps/tutorials/routes.ts
@@ -23,9 +23,8 @@ import { isSonarCloud } from '../../helpers/system';
const routes = [
{
indexRoute: {
- component: lazyLoad(
- () =>
- isSonarCloud() ? import('./onboarding/OnboardingPage') : import('./ProjectOnboardingPage')
+ component: lazyLoad(() =>
+ isSonarCloud() ? import('./onboarding/OnboardingPage') : import('./ProjectOnboardingPage')
)
}
}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/utils.ts b/server/sonar-web/src/main/js/apps/tutorials/utils.ts
index 59d9e9c75d2..d689b874efd 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/utils.ts
+++ b/server/sonar-web/src/main/js/apps/tutorials/utils.ts
@@ -39,6 +39,6 @@ export function isLanguageConfigured(config?: LanguageConfig) {
return isJavaConfigured || isDotNetConfigured || isCFamilyConfigured || isOtherConfigured;
}
-export function quote(os: string): ((s: string) => string) {
+export function quote(os: string): (s: string) => string {
return os === 'win' ? (s: string) => `"${s}"` : (s: string) => s;
}
diff --git a/server/sonar-web/src/main/js/apps/users/components/UserGroups.tsx b/server/sonar-web/src/main/js/apps/users/components/UserGroups.tsx
index 6fec17f995c..807e3a32ae4 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UserGroups.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/UserGroups.tsx
@@ -65,15 +65,11 @@ export default class UserGroups extends React.PureComponent<Props, State> {
</li>
))}
<li className="little-spacer-bottom">
- {groups.length > GROUPS_LIMIT &&
- !this.state.showMore && (
- <a
- className="js-user-more-groups spacer-right"
- href="#"
- onClick={this.toggleShowMore}>
- {translateWithParameters('more_x', groups.length - limit)}
- </a>
- )}
+ {groups.length > GROUPS_LIMIT && !this.state.showMore && (
+ <a className="js-user-more-groups spacer-right" href="#" onClick={this.toggleShowMore}>
+ {translateWithParameters('more_x', groups.length - limit)}
+ </a>
+ )}
<ButtonIcon
className="js-user-groups button-small"
onClick={this.handleOpenForm}
diff --git a/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx b/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx
index 48b93275fb1..9d6c85ebecf 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx
@@ -35,10 +35,9 @@ export default function UserListItemIdentity({ identityProvider, user }: Props)
<span className="js-user-login note little-spacer-left">{user.login}</span>
</div>
{user.email && <div className="js-user-email little-spacer-top">{user.email}</div>}
- {!user.local &&
- user.externalProvider !== 'sonarqube' && (
- <ExternalProvider identityProvider={identityProvider} user={user} />
- )}
+ {!user.local && user.externalProvider !== 'sonarqube' && (
+ <ExternalProvider identityProvider={identityProvider} user={user} />
+ )}
</td>
);
}
diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.tsx b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.tsx
index 475f3a1737a..5b19d4cf57d 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.tsx
@@ -185,14 +185,13 @@ interface ValueProps {
export function UsersSelectSearchValue({ children, value }: ValueProps) {
return (
<div className="Select-value" title={value ? value.name : ''}>
- {value &&
- value.login && (
- <div className="Select-value-label">
- <Avatar hash={value.avatar} name={value.name} size={AVATAR_SIZE} />
- <strong className="spacer-left">{children}</strong>
- <span className="note little-spacer-left">{value.login}</span>
- </div>
- )}
+ {value && value.login && (
+ <div className="Select-value-label">
+ <Avatar hash={value.avatar} name={value.name} size={AVATAR_SIZE} />
+ <strong className="spacer-left">{children}</strong>
+ <span className="note little-spacer-left">{value.login}</span>
+ </div>
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/web-api/components/Action.tsx b/server/sonar-web/src/main/js/apps/web-api/components/Action.tsx
index 2f1223cf2f0..304f28eb2f7 100644
--- a/server/sonar-web/src/main/js/apps/web-api/components/Action.tsx
+++ b/server/sonar-web/src/main/js/apps/web-api/components/Action.tsx
@@ -176,17 +176,17 @@ export default class Action extends React.PureComponent<Props, State> {
{this.renderTabs()}
- {showParams &&
- action.params && (
- <Params
- params={action.params}
- showDeprecated={this.props.showDeprecated}
- showInternal={this.props.showInternal}
- />
- )}
-
- {showResponse &&
- action.hasResponseExample && <ResponseExample action={action} domain={domain} />}
+ {showParams && action.params && (
+ <Params
+ params={action.params}
+ showDeprecated={this.props.showDeprecated}
+ showInternal={this.props.showInternal}
+ />
+ )}
+
+ {showResponse && action.hasResponseExample && (
+ <ResponseExample action={action} domain={domain} />
+ )}
{showChangelog && <ActionChangelog changelog={action.changelog} />}
</div>
diff --git a/server/sonar-web/src/main/js/apps/web-api/components/Params.tsx b/server/sonar-web/src/main/js/apps/web-api/components/Params.tsx
index 7a83bc387b8..5cf2d7c0a90 100644
--- a/server/sonar-web/src/main/js/apps/web-api/components/Params.tsx
+++ b/server/sonar-web/src/main/js/apps/web-api/components/Params.tsx
@@ -46,20 +46,17 @@ export default class Params extends React.PureComponent<Props> {
</div>
)}
- {this.props.showDeprecated &&
- param.deprecatedKey && (
- <div className="little-spacer-top">
- <code>{param.deprecatedKey}</code>
- </div>
- )}
+ {this.props.showDeprecated && param.deprecatedKey && (
+ <div className="little-spacer-top">
+ <code>{param.deprecatedKey}</code>
+ </div>
+ )}
- {this.props.showDeprecated &&
- param.deprecatedKey &&
- param.deprecatedKeySince && (
- <div className="little-spacer-top">
- <DeprecatedBadge since={param.deprecatedKeySince} />
- </div>
- )}
+ {this.props.showDeprecated && param.deprecatedKey && param.deprecatedKeySince && (
+ <div className="little-spacer-top">
+ <DeprecatedBadge since={param.deprecatedKeySince} />
+ </div>
+ )}
<div className="note little-spacer-top">{param.required ? 'required' : 'optional'}</div>
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
index 3d66a1e1126..dcf495329d4 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
@@ -97,11 +97,8 @@ export default class App extends React.PureComponent<Props, State> {
return updateWebhook(data).then(() => {
if (this.mounted) {
this.setState(({ webhooks }) => ({
- webhooks: webhooks.map(
- webhook =>
- webhook.key === data.webhook
- ? { ...webhook, name: data.name, url: data.url }
- : webhook
+ webhooks: webhooks.map(webhook =>
+ webhook.key === data.webhook ? { ...webhook, name: data.name, url: data.url } : webhook
)
}));
}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx
index f35ea78319e..90c2b80ff22 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx
@@ -128,13 +128,12 @@ export default class DuplicationPopup extends React.PureComponent<Props> {
{duplication.file.projectName}
</Link>
</div>
- {duplication.file.subProject &&
- duplication.file.subProjectName && (
- <div className="component-name-parent">
- <QualifierIcon className="little-spacer-right" qualifier="BRC" />
- {duplication.file.subProjectName}
- </div>
- )}
+ {duplication.file.subProject && duplication.file.subProjectName && (
+ <div className="component-name-parent">
+ <QualifierIcon className="little-spacer-right" qualifier="BRC" />
+ {duplication.file.subProjectName}
+ </div>
+ )}
</>
)}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx
index 7955f4df686..34da94c78b8 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx
@@ -141,14 +141,13 @@ export default class Line extends React.PureComponent<Props> {
/>
))}
- {this.props.displayIssues &&
- !this.props.displayAllIssues && (
- <LineIssuesIndicator
- issues={this.props.issues}
- line={line}
- onClick={this.handleIssuesIndicatorClick}
- />
- )}
+ {this.props.displayIssues && !this.props.displayAllIssues && (
+ <LineIssuesIndicator
+ issues={this.props.issues}
+ line={line}
+ onClick={this.handleIssuesIndicatorClick}
+ />
+ )}
<LineCode
branchLike={this.props.branchLike}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
index f60496935b4..af7a7b7200a 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
@@ -225,20 +225,19 @@ export default class LineCode extends React.PureComponent<Props, State> {
<div className="source-line-code-inner">
<pre ref={node => (this.codeNode = node)}>{renderedTokens}</pre>
</div>
- {showIssues &&
- issues.length > 0 && (
- <LineIssuesList
- branchLike={this.props.branchLike}
- displayIssueLocationsCount={this.props.displayIssueLocationsCount}
- displayIssueLocationsLink={this.props.displayIssueLocationsLink}
- issuePopup={this.props.issuePopup}
- issues={issues}
- onIssueChange={this.props.onIssueChange}
- onIssueClick={onIssueSelect}
- onIssuePopupToggle={this.props.onIssuePopupToggle}
- selectedIssue={selectedIssue}
- />
- )}
+ {showIssues && issues.length > 0 && (
+ <LineIssuesList
+ branchLike={this.props.branchLike}
+ displayIssueLocationsCount={this.props.displayIssueLocationsCount}
+ displayIssueLocationsLink={this.props.displayIssueLocationsLink}
+ issuePopup={this.props.issuePopup}
+ issues={issues}
+ onIssueChange={this.props.onIssueChange}
+ onIssueClick={onIssueSelect}
+ onIssuePopupToggle={this.props.onIssuePopupToggle}
+ selectedIssue={selectedIssue}
+ />
+ )}
</td>
);
}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx
index 9f6834cfa65..c7486388662 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx
@@ -189,61 +189,54 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
{this.renderBigMeasure(measures.violations)}
{this.renderBigMeasure(measures.sqale_index)}
</div>
- {measures.violations &&
- !!measures.violations.value && (
- <>
- {typesFacet && (
- <div className="measures">
- <div className="measures-list">
- {sortBy(typesFacet, f => ISSUE_TYPES.indexOf(f.val)).map(f => (
- <div className="measure measure-one-line" key={f.val}>
- <span className="measure-name">
- <IssueTypeIcon className="little-spacer-right" query={f.val} />
- {translate('issue.type', f.val)}
- </span>
- <span className="measure-value">
- {formatMeasure(f.count, 'SHORT_INT')}
- </span>
- </div>
- ))}
- </div>
+ {measures.violations && !!measures.violations.value && (
+ <>
+ {typesFacet && (
+ <div className="measures">
+ <div className="measures-list">
+ {sortBy(typesFacet, f => ISSUE_TYPES.indexOf(f.val)).map(f => (
+ <div className="measure measure-one-line" key={f.val}>
+ <span className="measure-name">
+ <IssueTypeIcon className="little-spacer-right" query={f.val} />
+ {translate('issue.type', f.val)}
+ </span>
+ <span className="measure-value">{formatMeasure(f.count, 'SHORT_INT')}</span>
+ </div>
+ ))}
</div>
- )}
- {severitiesFacet && (
- <div className="measures">
- <div className="measures-list">
- {sortBy(severitiesFacet, f => SEVERITIES.indexOf(f.val)).map(f => (
- <div className="measure measure-one-line" key={f.val}>
- <span className="measure-name">
- <SeverityHelper severity={f.val} />
- </span>
- <span className="measure-value">
- {formatMeasure(f.count, 'SHORT_INT')}
- </span>
- </div>
- ))}
- </div>
+ </div>
+ )}
+ {severitiesFacet && (
+ <div className="measures">
+ <div className="measures-list">
+ {sortBy(severitiesFacet, f => SEVERITIES.indexOf(f.val)).map(f => (
+ <div className="measure measure-one-line" key={f.val}>
+ <span className="measure-name">
+ <SeverityHelper severity={f.val} />
+ </span>
+ <span className="measure-value">{formatMeasure(f.count, 'SHORT_INT')}</span>
+ </div>
+ ))}
</div>
- )}
- {tagsFacet && (
- <div className="measures">
- <div className="measures-list">
- {tagsFacet.map(f => (
- <div className="measure measure-one-line" key={f.val}>
- <span className="measure-name">
- <TagsIcon className="little-spacer-right" />
- {f.val}
- </span>
- <span className="measure-value">
- {formatMeasure(f.count, 'SHORT_INT')}
- </span>
- </div>
- ))}
- </div>
+ </div>
+ )}
+ {tagsFacet && (
+ <div className="measures">
+ <div className="measures-list">
+ {tagsFacet.map(f => (
+ <div className="measure measure-one-line" key={f.val}>
+ <span className="measure-name">
+ <TagsIcon className="little-spacer-right" />
+ {f.val}
+ </span>
+ <span className="measure-value">{formatMeasure(f.count, 'SHORT_INT')}</span>
+ </div>
+ ))}
</div>
- )}
- </>
- )}
+ </div>
+ )}
+ </>
+ )}
</div>
</div>
);
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlay-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlay-test.tsx
index d3e5ff08330..0abc29938c7 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlay-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlay-test.tsx
@@ -91,7 +91,8 @@ jest.mock('../../../../api/measures', () => ({
{ metric: 'security_remediation_effort', value: '0' },
{ metric: 'statements', value: '3' },
{ metric: 'skipped_tests', value: '0' },
- { metric: 'test_failures', value: '0' }
+ { metric: 'test_failures', value: '0' },
+ { metric: 'violations', value: '1' }
])
}));
@@ -129,6 +130,7 @@ jest.mock('../../../../api/metrics', () => ({
{ key: 'statements', type: 'INT', domain: 'Size' },
{ key: 'skipped_tests', type: 'INT', domain: 'Tests' },
{ key: 'test_failures', type: 'INT', domain: 'Tests' },
+ { key: 'violations', type: 'INT', domain: 'Issues' },
// next two must be filtered out
{ key: 'data', type: 'DATA' },
{ key: 'hidden', hidden: true }
@@ -155,13 +157,7 @@ const branchLike: T.ShortLivingBranch = {
};
it('should render source file', async () => {
- const wrapper = shallow(
- <MeasuresOverlay
- branchLike={branchLike}
- onClose={jest.fn()}
- sourceViewerFile={sourceViewerFile}
- />
- );
+ const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
@@ -170,13 +166,18 @@ it('should render source file', async () => {
});
it('should render test file', async () => {
- const wrapper = shallow(
+ const wrapper = shallowRender({ sourceViewerFile: { ...sourceViewerFile, q: 'UTS' } });
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<MeasuresOverlay['props']> = {}) {
+ return shallow(
<MeasuresOverlay
branchLike={branchLike}
onClose={jest.fn()}
- sourceViewerFile={{ ...sourceViewerFile, q: 'UTS' }}
+ sourceViewerFile={sourceViewerFile}
+ {...props}
/>
);
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
+}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap
index 94cea5cdf1b..2472c038a73 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap
@@ -166,6 +166,25 @@ exports[`should render source file 1`] = `
>
<div
className="measure measure-big"
+ data-metric="violations"
+ >
+ <span
+ className="measure-value"
+ >
+ <Measure
+ metricKey="violations"
+ metricType="INT"
+ value="1"
+ />
+ </span>
+ <span
+ className="measure-name"
+ >
+ violations
+ </span>
+ </div>
+ <div
+ className="measure measure-big"
data-metric="sqale_index"
>
<span
@@ -184,6 +203,226 @@ exports[`should render source file 1`] = `
</span>
</div>
</div>
+ <div
+ className="measures"
+ >
+ <div
+ className="measures-list"
+ >
+ <div
+ className="measure measure-one-line"
+ key="BUG"
+ >
+ <span
+ className="measure-name"
+ >
+ <IssueTypeIcon
+ className="little-spacer-right"
+ query="BUG"
+ />
+ issue.type.BUG
+ </span>
+ <span
+ className="measure-value"
+ >
+ 1
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="VULNERABILITY"
+ >
+ <span
+ className="measure-name"
+ >
+ <IssueTypeIcon
+ className="little-spacer-right"
+ query="VULNERABILITY"
+ />
+ issue.type.VULNERABILITY
+ </span>
+ <span
+ className="measure-value"
+ >
+ 0
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="CODE_SMELL"
+ >
+ <span
+ className="measure-name"
+ >
+ <IssueTypeIcon
+ className="little-spacer-right"
+ query="CODE_SMELL"
+ />
+ issue.type.CODE_SMELL
+ </span>
+ <span
+ className="measure-value"
+ >
+ 2
+ </span>
+ </div>
+ </div>
+ </div>
+ <div
+ className="measures"
+ >
+ <div
+ className="measures-list"
+ >
+ <div
+ className="measure measure-one-line"
+ key="BLOCKER"
+ >
+ <span
+ className="measure-name"
+ >
+ <SeverityHelper
+ severity="BLOCKER"
+ />
+ </span>
+ <span
+ className="measure-value"
+ >
+ 5
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="CRITICAL"
+ >
+ <span
+ className="measure-name"
+ >
+ <SeverityHelper
+ severity="CRITICAL"
+ />
+ </span>
+ <span
+ className="measure-value"
+ >
+ 4
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="MAJOR"
+ >
+ <span
+ className="measure-name"
+ >
+ <SeverityHelper
+ severity="MAJOR"
+ />
+ </span>
+ <span
+ className="measure-value"
+ >
+ 1
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="MINOR"
+ >
+ <span
+ className="measure-name"
+ >
+ <SeverityHelper
+ severity="MINOR"
+ />
+ </span>
+ <span
+ className="measure-value"
+ >
+ 3
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="INFO"
+ >
+ <span
+ className="measure-name"
+ >
+ <SeverityHelper
+ severity="INFO"
+ />
+ </span>
+ <span
+ className="measure-value"
+ >
+ 2
+ </span>
+ </div>
+ </div>
+ </div>
+ <div
+ className="measures"
+ >
+ <div
+ className="measures-list"
+ >
+ <div
+ className="measure measure-one-line"
+ key="bad-practice"
+ >
+ <span
+ className="measure-name"
+ >
+ <TagsIcon
+ className="little-spacer-right"
+ />
+ bad-practice
+ </span>
+ <span
+ className="measure-value"
+ >
+ 1
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="cert"
+ >
+ <span
+ className="measure-name"
+ >
+ <TagsIcon
+ className="little-spacer-right"
+ />
+ cert
+ </span>
+ <span
+ className="measure-value"
+ >
+ 3
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="design"
+ >
+ <span
+ className="measure-name"
+ >
+ <TagsIcon
+ className="little-spacer-right"
+ />
+ design
+ </span>
+ <span
+ className="measure-value"
+ >
+ 1
+ </span>
+ </div>
+ </div>
+ </div>
</div>
</div>
<div
@@ -524,6 +763,25 @@ exports[`should render source file 2`] = `
>
<div
className="measure measure-big"
+ data-metric="violations"
+ >
+ <span
+ className="measure-value"
+ >
+ <Measure
+ metricKey="violations"
+ metricType="INT"
+ value="1"
+ />
+ </span>
+ <span
+ className="measure-name"
+ >
+ violations
+ </span>
+ </div>
+ <div
+ className="measure measure-big"
data-metric="sqale_index"
>
<span
@@ -542,6 +800,226 @@ exports[`should render source file 2`] = `
</span>
</div>
</div>
+ <div
+ className="measures"
+ >
+ <div
+ className="measures-list"
+ >
+ <div
+ className="measure measure-one-line"
+ key="BUG"
+ >
+ <span
+ className="measure-name"
+ >
+ <IssueTypeIcon
+ className="little-spacer-right"
+ query="BUG"
+ />
+ issue.type.BUG
+ </span>
+ <span
+ className="measure-value"
+ >
+ 1
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="VULNERABILITY"
+ >
+ <span
+ className="measure-name"
+ >
+ <IssueTypeIcon
+ className="little-spacer-right"
+ query="VULNERABILITY"
+ />
+ issue.type.VULNERABILITY
+ </span>
+ <span
+ className="measure-value"
+ >
+ 0
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="CODE_SMELL"
+ >
+ <span
+ className="measure-name"
+ >
+ <IssueTypeIcon
+ className="little-spacer-right"
+ query="CODE_SMELL"
+ />
+ issue.type.CODE_SMELL
+ </span>
+ <span
+ className="measure-value"
+ >
+ 2
+ </span>
+ </div>
+ </div>
+ </div>
+ <div
+ className="measures"
+ >
+ <div
+ className="measures-list"
+ >
+ <div
+ className="measure measure-one-line"
+ key="BLOCKER"
+ >
+ <span
+ className="measure-name"
+ >
+ <SeverityHelper
+ severity="BLOCKER"
+ />
+ </span>
+ <span
+ className="measure-value"
+ >
+ 5
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="CRITICAL"
+ >
+ <span
+ className="measure-name"
+ >
+ <SeverityHelper
+ severity="CRITICAL"
+ />
+ </span>
+ <span
+ className="measure-value"
+ >
+ 4
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="MAJOR"
+ >
+ <span
+ className="measure-name"
+ >
+ <SeverityHelper
+ severity="MAJOR"
+ />
+ </span>
+ <span
+ className="measure-value"
+ >
+ 1
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="MINOR"
+ >
+ <span
+ className="measure-name"
+ >
+ <SeverityHelper
+ severity="MINOR"
+ />
+ </span>
+ <span
+ className="measure-value"
+ >
+ 3
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="INFO"
+ >
+ <span
+ className="measure-name"
+ >
+ <SeverityHelper
+ severity="INFO"
+ />
+ </span>
+ <span
+ className="measure-value"
+ >
+ 2
+ </span>
+ </div>
+ </div>
+ </div>
+ <div
+ className="measures"
+ >
+ <div
+ className="measures-list"
+ >
+ <div
+ className="measure measure-one-line"
+ key="bad-practice"
+ >
+ <span
+ className="measure-name"
+ >
+ <TagsIcon
+ className="little-spacer-right"
+ />
+ bad-practice
+ </span>
+ <span
+ className="measure-value"
+ >
+ 1
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="cert"
+ >
+ <span
+ className="measure-name"
+ >
+ <TagsIcon
+ className="little-spacer-right"
+ />
+ cert
+ </span>
+ <span
+ className="measure-value"
+ >
+ 3
+ </span>
+ </div>
+ <div
+ className="measure measure-one-line"
+ key="design"
+ >
+ <span
+ className="measure-name"
+ >
+ <TagsIcon
+ className="little-spacer-right"
+ />
+ design
+ </span>
+ <span
+ className="measure-value"
+ >
+ 1
+ </span>
+ </div>
+ </div>
+ </div>
</div>
</div>
<div
@@ -1156,6 +1634,19 @@ exports[`should render source file 2`] = `
}
/>
<MeasuresOverlayMeasure
+ key="violations"
+ measure={
+ Object {
+ "metric": Object {
+ "domain": "Issues",
+ "key": "violations",
+ "type": "INT",
+ },
+ "value": "1",
+ }
+ }
+ />
+ <MeasuresOverlayMeasure
key="wont_fix_issues"
measure={
Object {
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.ts b/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.ts
index 6bd78badd50..6e0ad25582f 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.ts
+++ b/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.ts
@@ -48,11 +48,10 @@ export function splitByTokens(code: string, rootClassName = ''): Token[] {
export function highlightSymbol(tokens: Token[], symbol: string): Token[] {
const symbolRegExp = new RegExp(`\\b${symbol}\\b`);
- return tokens.map(
- token =>
- symbolRegExp.test(token.className)
- ? { ...token, className: `${token.className} highlighted` }
- : token
+ return tokens.map(token =>
+ symbolRegExp.test(token.className)
+ ? { ...token, className: `${token.className} highlighted` }
+ : token
);
}
diff --git a/server/sonar-web/src/main/js/components/charts/LineChart.tsx b/server/sonar-web/src/main/js/components/charts/LineChart.tsx
index 39aaf3d2f82..a937fa7e7fd 100644
--- a/server/sonar-web/src/main/js/components/charts/LineChart.tsx
+++ b/server/sonar-web/src/main/js/components/charts/LineChart.tsx
@@ -74,11 +74,13 @@ export default class LineChart extends React.PureComponent<Props> {
return null;
}
- const points = this.props.data.filter(point => point.y != null).map((point, index) => {
- const x = xScale(point.x);
- const y = yScale(point.y || 0);
- return <circle className="line-chart-point" cx={x} cy={y} key={index} r="3" />;
- });
+ const points = this.props.data
+ .filter(point => point.y != null)
+ .map((point, index) => {
+ const x = xScale(point.x);
+ const y = yScale(point.y || 0);
+ return <circle className="line-chart-point" cx={x} cy={y} key={index} r="3" />;
+ });
return <g>{points}</g>;
}
diff --git a/server/sonar-web/src/main/js/components/common/MultiSelect.tsx b/server/sonar-web/src/main/js/components/common/MultiSelect.tsx
index e2870be58d0..ab0d53888b6 100644
--- a/server/sonar-web/src/main/js/components/common/MultiSelect.tsx
+++ b/server/sonar-web/src/main/js/components/common/MultiSelect.tsx
@@ -314,11 +314,9 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
renderLabel={renderLabel}
/>
)}
- {!showNewElement &&
- selectedElements.length < 1 &&
- unselectedElements.length < 1 && (
- <li className="spacer-left">{translateWithParameters('no_results_for_x', query)}</li>
- )}
+ {!showNewElement && selectedElements.length < 1 && unselectedElements.length < 1 && (
+ <li className="spacer-left">{translateWithParameters('no_results_for_x', query)}</li>
+ )}
</ul>
{footerNode}
</div>
diff --git a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
index d1f85357ac5..7917b491ff8 100644
--- a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
+++ b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
@@ -66,7 +66,7 @@ export default function Tooltip(props: Props) {
}
export class TooltipInner extends React.Component<Props, State> {
- throttledPositionTooltip: (() => void);
+ throttledPositionTooltip: () => void;
mouseEnterTimeout?: number;
mouseLeaveTimeout?: number;
tooltipNode?: HTMLElement | null;
diff --git a/server/sonar-web/src/main/js/components/facet/ListStyleFacet.tsx b/server/sonar-web/src/main/js/components/facet/ListStyleFacet.tsx
index 0845c888738..a10f486cbe9 100644
--- a/server/sonar-web/src/main/js/components/facet/ListStyleFacet.tsx
+++ b/server/sonar-web/src/main/js/components/facet/ListStyleFacet.tsx
@@ -299,12 +299,11 @@ export default class ListStyleFacet<S> extends React.Component<Props<S>, State<S
showMore={this.showFullList}
total={sortedItems.length}
/>
- {mightHaveMoreResults &&
- this.state.showFullList && (
- <Alert className="spacer-top" variant="warning">
- {translate('facet_might_have_more_results')}
- </Alert>
- )}
+ {mightHaveMoreResults && this.state.showFullList && (
+ <Alert className="spacer-top" variant="warning">
+ {translate('facet_might_have_more_results')}
+ </Alert>
+ )}
</>
);
}
@@ -407,14 +406,13 @@ export default class ListStyleFacet<S> extends React.Component<Props<S>, State<S
/>
<DeferredSpinner loading={this.props.fetching} />
- {this.props.open &&
- !disabled && (
- <>
- {this.renderSearch()}
- {showList ? this.renderList() : this.renderSearchResults()}
- <MultipleSelectionHint options={Object.keys(stats).length} values={values.length} />
- </>
- )}
+ {this.props.open && !disabled && (
+ <>
+ {this.renderSearch()}
+ {showList ? this.renderList() : this.renderSearchResults()}
+ <MultipleSelectionHint options={Object.keys(stats).length} values={values.length} />
+ </>
+ )}
</FacetBox>
);
}
diff --git a/server/sonar-web/src/main/js/components/facet/ListStyleFacetFooter.tsx b/server/sonar-web/src/main/js/components/facet/ListStyleFacetFooter.tsx
index ccafff05264..f469684eeac 100644
--- a/server/sonar-web/src/main/js/components/facet/ListStyleFacetFooter.tsx
+++ b/server/sonar-web/src/main/js/components/facet/ListStyleFacetFooter.tsx
@@ -59,12 +59,11 @@ export default class ListStyleFacetFooter extends React.PureComponent<Props> {
</a>
)}
- {this.props.showLess &&
- allShown && (
- <a className="spacer-left text-muted" href="#" onClick={this.handleShowLessClick}>
- {translate('show_less')}
- </a>
- )}
+ {this.props.showLess && allShown && (
+ <a className="spacer-left text-muted" href="#" onClick={this.handleShowLessClick}>
+ {translate('show_less')}
+ </a>
+ )}
</footer>
);
}
diff --git a/server/sonar-web/src/main/js/components/issue/IssueView.tsx b/server/sonar-web/src/main/js/components/issue/IssueView.tsx
index 06d2eb4173d..f5cb65b0241 100644
--- a/server/sonar-web/src/main/js/components/issue/IssueView.tsx
+++ b/server/sonar-web/src/main/js/components/issue/IssueView.tsx
@@ -97,19 +97,18 @@ export default class IssueView extends React.PureComponent<Props> {
onChange={this.props.onChange}
togglePopup={this.props.togglePopup}
/>
- {issue.comments &&
- issue.comments.length > 0 && (
- <div className="issue-comments">
- {issue.comments.map(comment => (
- <IssueCommentLine
- comment={comment}
- key={comment.key}
- onDelete={this.deleteComment}
- onEdit={this.editComment}
- />
- ))}
- </div>
- )}
+ {issue.comments && issue.comments.length > 0 && (
+ <div className="issue-comments">
+ {issue.comments.map(comment => (
+ <IssueCommentLine
+ comment={comment}
+ key={comment.key}
+ onDelete={this.deleteComment}
+ onEdit={this.editComment}
+ />
+ ))}
+ </div>
+ )}
{hasCheckbox && (
<>
<Checkbox
diff --git a/server/sonar-web/src/main/js/components/issue/__tests__/IssueView-test.tsx b/server/sonar-web/src/main/js/components/issue/__tests__/IssueView-test.tsx
new file mode 100644
index 00000000000..59795351516
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/issue/__tests__/IssueView-test.tsx
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import IssueView from '../IssueView';
+import { mockIssue } from '../../../helpers/testMocks';
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<IssueView['props']> = {}) {
+ return shallow(
+ <IssueView
+ issue={mockIssue(false, {
+ comments: [
+ {
+ key: '1',
+ htmlText: 'My comment',
+ markdown: 'My comment',
+ updatable: false,
+ createdAt: '2017-07-05T09:33:29+0200',
+ author: 'admin',
+ authorLogin: 'admin',
+ authorName: 'Admin',
+ authorAvatar: 'admin-avatar',
+ authorActive: true
+ }
+ ]
+ })}
+ onAssign={jest.fn()}
+ onChange={jest.fn()}
+ onClick={jest.fn()}
+ selected={true}
+ togglePopup={jest.fn()}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap
new file mode 100644
index 00000000000..b76291398cd
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap
@@ -0,0 +1,137 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="issue selected"
+ data-issue="AVsae-CQS-9G3txfbFN2"
+ onClick={[Function]}
+ role="listitem"
+ tabIndex={0}
+>
+ <IssueTitleBar
+ issue={
+ Object {
+ "actions": Array [],
+ "comments": Array [
+ Object {
+ "author": "admin",
+ "authorActive": true,
+ "authorAvatar": "admin-avatar",
+ "authorLogin": "admin",
+ "authorName": "Admin",
+ "createdAt": "2017-07-05T09:33:29+0200",
+ "htmlText": "My comment",
+ "key": "1",
+ "markdown": "My comment",
+ "updatable": false,
+ },
+ ],
+ "component": "main.js",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": Array [],
+ "fromHotspot": false,
+ "key": "AVsae-CQS-9G3txfbFN2",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "organization": "myorg",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "projectOrganization": "org",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": Array [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
+ }
+ }
+ togglePopup={[MockFunction]}
+ />
+ <IssueActionsBar
+ issue={
+ Object {
+ "actions": Array [],
+ "comments": Array [
+ Object {
+ "author": "admin",
+ "authorActive": true,
+ "authorAvatar": "admin-avatar",
+ "authorLogin": "admin",
+ "authorName": "Admin",
+ "createdAt": "2017-07-05T09:33:29+0200",
+ "htmlText": "My comment",
+ "key": "1",
+ "markdown": "My comment",
+ "updatable": false,
+ },
+ ],
+ "component": "main.js",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": Array [],
+ "fromHotspot": false,
+ "key": "AVsae-CQS-9G3txfbFN2",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "organization": "myorg",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "projectOrganization": "org",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": Array [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
+ }
+ }
+ onAssign={[MockFunction]}
+ onChange={[MockFunction]}
+ togglePopup={[MockFunction]}
+ />
+ <div
+ className="issue-comments"
+ >
+ <IssueCommentLine
+ comment={
+ Object {
+ "author": "admin",
+ "authorActive": true,
+ "authorAvatar": "admin-avatar",
+ "authorLogin": "admin",
+ "authorName": "Admin",
+ "createdAt": "2017-07-05T09:33:29+0200",
+ "htmlText": "My comment",
+ "key": "1",
+ "markdown": "My comment",
+ "updatable": false,
+ }
+ }
+ key="1"
+ onDelete={[Function]}
+ onEdit={[Function]}
+ />
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
index c8da6cf250b..cdacdb47572 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
@@ -138,14 +138,13 @@ export default class IssueActionsBar extends React.PureComponent<Props, State> {
/>
</li>
)}
- {!isSecurityHotspot &&
- issue.effort && (
- <li className="issue-meta">
- <span className="issue-meta-label">
- {translateWithParameters('issue.x_effort', issue.effort)}
- </span>
- </li>
- )}
+ {!isSecurityHotspot && issue.effort && (
+ <li className="issue-meta">
+ <span className="issue-meta-label">
+ {translateWithParameters('issue.x_effort', issue.effort)}
+ </span>
+ </li>
+ )}
{canComment && (
<IssueCommentAction
commentAutoTriggered={this.state.commentAutoTriggered}
diff --git a/server/sonar-web/src/main/js/components/nav/NavBar.tsx b/server/sonar-web/src/main/js/components/nav/NavBar.tsx
index 35a4239c53e..af7cc7e7d0a 100644
--- a/server/sonar-web/src/main/js/components/nav/NavBar.tsx
+++ b/server/sonar-web/src/main/js/components/nav/NavBar.tsx
@@ -36,7 +36,7 @@ interface State {
}
export default class NavBar extends React.PureComponent<Props, State> {
- throttledFollowHorizontalScroll: (() => void);
+ throttledFollowHorizontalScroll: () => void;
constructor(props: Props) {
super(props);
diff --git a/server/sonar-web/src/main/js/components/workspace/Workspace.tsx b/server/sonar-web/src/main/js/components/workspace/Workspace.tsx
index 4647079df4b..38c419c105c 100644
--- a/server/sonar-web/src/main/js/components/workspace/Workspace.tsx
+++ b/server/sonar-web/src/main/js/components/workspace/Workspace.tsx
@@ -133,8 +133,8 @@ export default class Workspace extends React.PureComponent<{}, State> {
if (this.mounted) {
const { key, name, qualifier } = details;
this.setState((state: State) => ({
- components: state.components.map(
- component => (component.key === key ? { ...component, name, qualifier } : component)
+ components: state.components.map(component =>
+ component.key === key ? { ...component, name, qualifier } : component
)
}));
}
diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts
index 76f9a528015..030f3b09e2d 100644
--- a/server/sonar-web/src/main/js/helpers/testMocks.ts
+++ b/server/sonar-web/src/main/js/helpers/testMocks.ts
@@ -119,6 +119,29 @@ export function mockComponent(overrides: Partial<T.Component> = {}): T.Component
};
}
+export function mockComponentMeasure(
+ file = false,
+ overrides: Partial<T.ComponentMeasure> = {}
+): T.ComponentMeasure {
+ if (file) {
+ return {
+ key: 'foo:src/index.tsx',
+ name: 'index.tsx',
+ qualifier: 'FIL',
+ path: 'src/index.tsx',
+ measures: [{ metric: 'bugs', value: '1', bestValue: false }],
+ ...overrides
+ };
+ }
+ return {
+ key: 'foo',
+ name: 'Foo',
+ qualifier: 'TRK',
+ measures: [{ metric: 'bugs', value: '12', bestValue: false }],
+ ...overrides
+ };
+}
+
export function mockQualityGateStatusCondition(
overrides: Partial<T.QualityGateStatusCondition> = {}
): T.QualityGateStatusCondition {
@@ -354,6 +377,39 @@ export function mockRule(overrides: Partial<T.Rule> = {}): T.Rule {
} as T.Rule;
}
+export function mockRuleDetails(overrides: Partial<T.RuleDetails> = {}): T.RuleDetails {
+ return {
+ key: 'squid:S1337',
+ repo: 'squid',
+ name: '".equals()" should not be used to test the values of "Atomic" classes',
+ createdAt: '2014-12-16T17:26:54+0100',
+ htmlDesc: '',
+ mdDesc: '',
+ severity: 'MAJOR',
+ status: 'READY',
+ isTemplate: false,
+ tags: [],
+ sysTags: ['multi-threading'],
+ lang: 'java',
+ langName: 'Java',
+ params: [],
+ defaultDebtRemFnType: 'CONSTANT_ISSUE',
+ defaultDebtRemFnOffset: '5min',
+ debtOverloaded: false,
+ debtRemFnType: 'CONSTANT_ISSUE',
+ debtRemFnOffset: '5min',
+ defaultRemFnType: 'CONSTANT_ISSUE',
+ defaultRemFnBaseEffort: '5min',
+ remFnType: 'CONSTANT_ISSUE',
+ remFnBaseEffort: '5min',
+ remFnOverloaded: false,
+ scope: 'MAIN',
+ isExternal: false,
+ type: 'BUG',
+ ...overrides
+ };
+}
+
export function mockShortLivingBranch(
overrides: Partial<T.ShortLivingBranch> = {}
): T.ShortLivingBranch {