aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2019-03-22 14:18:52 +0100
committersonartech <sonartech@sonarsource.com>2019-03-29 09:44:59 +0100
commit17bbc381c796efc51326c96e77c0fe7294c6a010 (patch)
tree7f996381da21d22e128d81aa288f8c3335cc704c
parent616f0b1daf61d1040052906a649a201e26a4f376 (diff)
downloadsonarqube-17bbc381c796efc51326c96e77c0fe7294c6a010.tar.gz
sonarqube-17bbc381c796efc51326c96e77c0fe7294c6a010.zip
Update React, Typescript and Eslint dependencies
* Fix ts and eslint issues * Drop forSingleOrganization * Update Typscript on extensions
-rw-r--r--server/sonar-docs/package.json20
-rw-r--r--server/sonar-vsts/package.json30
-rw-r--r--server/sonar-web/package.json60
-rw-r--r--server/sonar-web/src/main/js/@types/react-countup.d.ts1
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.tsx33
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/Pricing.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/App.tsx257
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx60
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap96
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/forSingleOrganization.tsx56
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/routes.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.tsx27
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/App-test.tsx65
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/App-test.tsx.snap113
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/routes.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityEventSelectOption.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx77
-rw-r--r--server/sonar-web/src/main/js/apps/projectDeletion/__tests__/Form-test.tsx29
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/common/SelectListItem.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/InstanceMessage-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/components/docs/DocToc.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/hoc/withRouter.tsx6
-rw-r--r--server/sonar-web/src/main/js/components/issue/IssueView.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/lazyLoad.tsx11
-rw-r--r--server/sonar-web/src/main/js/components/ui/Alert.tsx2
-rw-r--r--server/sonar-web/src/main/js/helpers/testMocks.ts1
-rw-r--r--sonar-application/build.gradle4
35 files changed, 636 insertions, 393 deletions
diff --git a/server/sonar-docs/package.json b/server/sonar-docs/package.json
index 0946efe1d2d..5e7d9582c05 100644
--- a/server/sonar-docs/package.json
+++ b/server/sonar-docs/package.json
@@ -31,27 +31,27 @@
"@types/react": "16.7.18",
"@types/react-dom": "16.0.11",
"@types/react-helmet": "5.0.8",
+ "@typescript-eslint/parser": "1.5.0",
"babel-jest": "23.6.0",
"enzyme": "3.8.0",
"enzyme-adapter-react-16": "1.7.1",
"enzyme-to-json": "3.3.5",
- "eslint": "5.7.0",
- "eslint-config-sonarqube": "0.2.0",
- "eslint-plugin-import": "2.14.0",
- "eslint-plugin-jsx-a11y": "6.1.2",
+ "eslint": "5.15.3",
+ "eslint-config-sonarqube": "0.3.0",
+ "eslint-plugin-import": "2.16.0",
+ "eslint-plugin-jsx-a11y": "6.2.1",
"eslint-plugin-promise": "4.0.1",
- "eslint-plugin-react": "7.12.3",
- "eslint-plugin-sonarjs": "0.2.0",
+ "eslint-plugin-react": "7.12.4",
+ "eslint-plugin-sonarjs": "0.3.0",
"fs-extra": "7.0.1",
"glob-promise": "3.4.0",
"graphql-code-generator": "0.5.2",
"jest": "23.6.0",
"prettier": "1.16.0",
- "react-test-render": "1.1.1",
+ "react-test-renderer": "16.6.0",
"remark": "10.0.1",
- "ts-jest": "23.10.5",
- "typescript": "3.2.4",
- "typescript-eslint-parser": "22.0.0",
+ "ts-jest": "24.0.0",
+ "typescript": "3.3.3333",
"unist-util-visit": "1.4.0"
},
"scripts": {
diff --git a/server/sonar-vsts/package.json b/server/sonar-vsts/package.json
index b5880df936e..2fe83b0be2e 100644
--- a/server/sonar-vsts/package.json
+++ b/server/sonar-vsts/package.json
@@ -9,8 +9,8 @@
"@babel/polyfill": "7.0.0",
"classnames": "2.2.6",
"lodash": "4.17.11",
- "react": "16.6.0",
- "react-dom": "16.6.0",
+ "react": "16.8.5",
+ "react-dom": "16.8.5",
"whatwg-fetch": "2.0.3"
},
"devDependencies": {
@@ -27,8 +27,9 @@
"@types/enzyme": "3.1.14",
"@types/jest": "23.3.7",
"@types/lodash": "4.14.117",
- "@types/react": "16.4.18",
- "@types/react-dom": "16.0.9",
+ "@types/react": "16.8.8",
+ "@types/react-dom": "16.8.3",
+ "@typescript-eslint/parser": "1.5.0",
"autoprefixer": "9.3.1",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "23.6.0",
@@ -43,13 +44,13 @@
"enzyme-adapter-react-16": "1.6.0",
"enzyme-to-json": "3.3.4",
"escape-string-regexp": "1.0.5",
- "eslint": "5.7.0",
- "eslint-config-sonarqube": "0.2.0",
- "eslint-plugin-import": "2.14.0",
- "eslint-plugin-jsx-a11y": "6.1.2",
+ "eslint": "5.15.3",
+ "eslint-config-sonarqube": "0.3.0",
+ "eslint-plugin-import": "2.16.0",
+ "eslint-plugin-jsx-a11y": "6.2.1",
"eslint-plugin-promise": "4.0.1",
- "eslint-plugin-react": "7.11.1",
- "eslint-plugin-sonarjs": "0.2.0",
+ "eslint-plugin-react": "7.12.4",
+ "eslint-plugin-sonarjs": "0.3.0",
"html-webpack-plugin": "3.2.0",
"jest": "23.6.0",
"postcss-calc": "7.0.0",
@@ -58,12 +59,11 @@
"prettier": "1.14.3",
"react-dev-utils": "5.0.0",
"react-error-overlay": "1.0.7",
- "react-test-renderer": "16.6.0",
+ "react-test-renderer": "16.8.4",
"style-loader": "0.23.1",
- "ts-jest": "23.10.4",
- "ts-loader": "5.2.2",
- "typescript": "3.1.3",
- "typescript-eslint-parser": "20.0.0",
+ "ts-jest": "24.0.0",
+ "ts-loader": "5.3.3",
+ "typescript": "3.3.3333",
"webpack": "4.22.0",
"webpack-bundle-analyzer": "3.0.3",
"webpack-dev-server": "3.1.10"
diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json
index 3f7efd4aae0..4d0cb91ef7b 100644
--- a/server/sonar-web/package.json
+++ b/server/sonar-web/package.json
@@ -23,18 +23,18 @@
"lodash": "4.17.11",
"lunr": "2.3.4",
"mdast-util-toc": "2.1.0",
- "prop-types": "15.6.2",
- "react": "16.6.0",
- "react-countup": "4.0.0",
- "react-day-picker": "7.2.4",
- "react-dom": "16.6.0",
- "react-draggable": "3.0.5",
- "react-ga": "2.5.3",
+ "prop-types": "15.7.2",
+ "react": "16.8.5",
+ "react-countup": "4.1.1",
+ "react-day-picker": "7.3.0",
+ "react-dom": "16.8.5",
+ "react-draggable": "3.2.1",
+ "react-ga": "2.5.7",
"react-helmet": "5.2.0",
- "react-intl": "2.7.2",
- "react-modal": "3.6.1",
- "react-redux": "5.0.7",
- "react-router": "3.2.0",
+ "react-intl": "2.8.0",
+ "react-modal": "3.8.1",
+ "react-redux": "5.1.1",
+ "react-router": "3.2.1",
"react-select": "1.2.1",
"react-virtualized": "9.21.0",
"redux": "4.0.1",
@@ -69,17 +69,18 @@
"@types/jest": "23.3.7",
"@types/keymaster": "1.6.28",
"@types/lodash": "4.14.117",
- "@types/prop-types": "15.5.6",
- "@types/react": "16.4.18",
- "@types/react-dom": "16.0.9",
- "@types/react-helmet": "5.0.7",
- "@types/react-intl": "2.3.11",
- "@types/react-modal": "3.2.1",
+ "@types/prop-types": "15.7.0",
+ "@types/react": "16.8.8",
+ "@types/react-dom": "16.8.3",
+ "@types/react-helmet": "5.0.8",
+ "@types/react-intl": "2.3.17",
+ "@types/react-modal": "3.8.1",
"@types/react-redux": "6.0.6",
- "@types/react-router": "3.0.13",
+ "@types/react-router": "3.0.20",
"@types/react-select": "1.2.6",
- "@types/react-virtualized": "9.18.7",
+ "@types/react-virtualized": "9.21.0",
"@types/valid-url": "1.0.2",
+ "@typescript-eslint/parser": "1.5.0",
"autoprefixer": "9.3.1",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "23.6.0",
@@ -94,13 +95,13 @@
"enzyme-adapter-react-16": "1.6.0",
"enzyme-to-json": "3.3.4",
"escape-string-regexp": "1.0.5",
- "eslint": "5.7.0",
- "eslint-config-sonarqube": "0.2.0",
- "eslint-plugin-import": "2.14.0",
- "eslint-plugin-jsx-a11y": "6.1.2",
+ "eslint": "5.15.3",
+ "eslint-config-sonarqube": "0.3.0",
+ "eslint-plugin-import": "2.16.0",
+ "eslint-plugin-jsx-a11y": "6.2.1",
"eslint-plugin-promise": "4.0.1",
- "eslint-plugin-react": "7.11.1",
- "eslint-plugin-sonarjs": "0.2.0",
+ "eslint-plugin-react": "7.12.4",
+ "eslint-plugin-sonarjs": "0.3.0",
"expose-loader": "0.7.5",
"glob": "7.1.3",
"glob-promise": "3.4.0",
@@ -116,14 +117,13 @@
"raw-loader": "0.5.1",
"react-dev-utils": "5.0.1",
"react-error-overlay": "1.0.7",
- "react-test-renderer": "16.6.0",
+ "react-test-renderer": "16.8.4",
"remark": "9.0.0",
"remark-react": "4.0.3",
"style-loader": "0.23.1",
- "ts-jest": "23.10.4",
- "ts-loader": "5.2.2",
- "typescript": "3.1.3",
- "typescript-eslint-parser": "20.0.0",
+ "ts-jest": "24.0.0",
+ "ts-loader": "5.3.3",
+ "typescript": "3.3.3333",
"webpack": "4.27.1",
"webpack-bundle-analyzer": "3.0.3",
"webpack-dev-server": "3.1.10"
diff --git a/server/sonar-web/src/main/js/@types/react-countup.d.ts b/server/sonar-web/src/main/js/@types/react-countup.d.ts
index 46fbd672042..e4b7d274005 100644
--- a/server/sonar-web/src/main/js/@types/react-countup.d.ts
+++ b/server/sonar-web/src/main/js/@types/react-countup.d.ts
@@ -19,6 +19,7 @@
*/
declare module 'react-countup' {
interface Props {
+ children: (data: { countUpRef?: React.RefObject<any> }) => JSX.Element;
decimal?: string;
decimals?: number;
delay?: number;
diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
index 9af3dc05144..776fe74d0ce 100644
--- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
+++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
@@ -138,6 +138,7 @@ export default function startReactApp(
<Redirect from="/projects_admin" to="/admin/projects_management" />
<Redirect from="/quality_gates/index" to="/quality_gates" />
<Redirect from="/roles/global" to="/admin/permissions" />
+ <Redirect from="/admin/roles/global" to="/admin/permissions" />
<Redirect from="/settings" to="/admin/settings" />
<Redirect from="/settings/encryption" to="/admin/settings/encryption" />
<Redirect from="/settings/index" to="/admin/settings" />
@@ -205,7 +206,9 @@ export default function startReactApp(
path="portfolios"
component={lazyLoad(() => import('../components/extensions/PortfoliosPage'))}
/>
- <RouteWithChildRoutes path="profiles" childRoutes={qualityProfilesRoutes} />
+ {!isSonarCloud() && (
+ <RouteWithChildRoutes path="profiles" childRoutes={qualityProfilesRoutes} />
+ )}
<RouteWithChildRoutes path="web_api" childRoutes={webAPIRoutes} />
<Route component={lazyLoad(() => import('../components/ComponentContainer'))}>
@@ -295,17 +298,23 @@ export default function startReactApp(
childRoutes={backgroundTasksRoutes}
/>
<RouteWithChildRoutes path="custom_metrics" childRoutes={customMetricsRoutes} />
- <RouteWithChildRoutes path="groups" childRoutes={groupsRoutes} />
- <RouteWithChildRoutes
- path="permission_templates"
- childRoutes={permissionTemplatesRoutes}
- />
- <RouteWithChildRoutes path="roles/global" childRoutes={globalPermissionsRoutes} />
- <RouteWithChildRoutes path="permissions" childRoutes={globalPermissionsRoutes} />
- <RouteWithChildRoutes
- path="projects_management"
- childRoutes={projectsManagementRoutes}
- />
+ {!isSonarCloud() && (
+ <>
+ <RouteWithChildRoutes path="groups" childRoutes={groupsRoutes} />
+ <RouteWithChildRoutes
+ path="permission_templates"
+ childRoutes={permissionTemplatesRoutes}
+ />
+ <RouteWithChildRoutes
+ path="permissions"
+ childRoutes={globalPermissionsRoutes}
+ />
+ <RouteWithChildRoutes
+ path="projects_management"
+ childRoutes={projectsManagementRoutes}
+ />
+ </>
+ )}
<RouteWithChildRoutes path="settings" childRoutes={settingsRoutes} />
<RouteWithChildRoutes path="system" childRoutes={systemRoutes} />
<RouteWithChildRoutes path="marketplace" childRoutes={marketplaceRoutes} />
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/Pricing.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/Pricing.tsx
index 8ab99acbf40..da1b5b8e255 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/Pricing.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/Pricing.tsx
@@ -37,7 +37,7 @@ export default class Pricing extends React.PureComponent {
removeWhitePageClass();
}
- handleClick = (event: React.MouseEvent) => {
+ handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
event.stopPropagation();
if (this.container) {
@@ -85,7 +85,7 @@ function PageBackgroundHeader() {
}
interface ForEveryoneBlockProps {
- onClick: (event: React.MouseEvent) => void;
+ onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
}
function ForEveryoneBlock({ onClick }: ForEveryoneBlockProps) {
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.tsx
index 0030c35bd79..c5c617ade03 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.tsx
@@ -43,7 +43,6 @@ interface State {
project: FeaturedProject;
}>;
sliding: boolean;
- translate: number;
viewable: boolean;
}
@@ -57,7 +56,6 @@ export default class FeaturedProjects extends React.PureComponent<Props, State>
reversing: false,
slides: this.orderProjectsFromProps(),
sliding: false,
- translate: 0,
viewable: false
};
this.handleScroll = throttle(this.handleScroll, 10);
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 f31ec953db1..b48cbaa6d9c 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
@@ -68,7 +68,7 @@ export default class RuleListItem extends React.PureComponent<Props> {
return Promise.resolve();
};
- handleNameClick = (event: React.MouseEvent) => {
+ handleNameClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
// cmd(ctrl) + click should open a rule permalink in a new tab
const isLeftClickEvent = event.button === 0;
const isModifiedEvent = !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
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 21e7d54151d..26f28e0365e 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
@@ -21,15 +21,14 @@ import * as React from 'react';
import { Helmet } from 'react-helmet';
import Header from './Header';
import List from './List';
-import forSingleOrganization from '../../organizations/forSingleOrganization';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
-import { searchUsersGroups, deleteGroup, updateGroup, createGroup } from '../../../api/user_groups';
import ListFooter from '../../../components/controls/ListFooter';
import SearchBox from '../../../components/controls/SearchBox';
+import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import { searchUsersGroups, deleteGroup, updateGroup, createGroup } from '../../../api/user_groups';
import { translate } from '../../../helpers/l10n';
interface Props {
- organization?: { key: string };
+ organization?: Pick<T.Organization, 'key'>;
}
interface State {
@@ -39,148 +38,146 @@ interface State {
query: string;
}
-export default forSingleOrganization(
- class App extends React.PureComponent<Props, State> {
- mounted = false;
- state: State = { loading: true, query: '' };
+export default class App extends React.PureComponent<Props, State> {
+ mounted = false;
+ state: State = { loading: true, query: '' };
- componentDidMount() {
- this.mounted = true;
- this.fetchGroups();
- }
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchGroups();
+ }
- componentWillUnmount() {
- this.mounted = false;
- }
+ componentWillUnmount() {
+ this.mounted = false;
+ }
- get organization() {
- return this.props.organization && this.props.organization.key;
- }
+ get organization() {
+ return this.props.organization && this.props.organization.key;
+ }
- makeFetchGroupsRequest = (data?: { p?: number; q?: string }) => {
- this.setState({ loading: true });
- return searchUsersGroups({
- organization: this.organization,
- q: this.state.query,
- ...data
- });
- };
+ makeFetchGroupsRequest = (data?: { p?: number; q?: string }) => {
+ this.setState({ loading: true });
+ return searchUsersGroups({
+ organization: this.organization,
+ q: this.state.query,
+ ...data
+ });
+ };
+
+ stopLoading = () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ };
- stopLoading = () => {
+ fetchGroups = (data?: { p?: number; q?: string }) => {
+ this.makeFetchGroupsRequest(data).then(({ groups, paging }) => {
if (this.mounted) {
- this.setState({ loading: false });
+ this.setState({ groups, loading: false, paging });
}
- };
+ }, this.stopLoading);
+ };
- fetchGroups = (data?: { p?: number; q?: string }) => {
- this.makeFetchGroupsRequest(data).then(({ groups, paging }) => {
+ fetchMoreGroups = () => {
+ const { paging } = this.state;
+ if (paging && paging.total > paging.pageIndex * paging.pageSize) {
+ this.makeFetchGroupsRequest({ p: paging.pageIndex + 1 }).then(({ groups, paging }) => {
if (this.mounted) {
- this.setState({ groups, loading: false, paging });
+ this.setState(({ groups: existingGroups = [] }) => ({
+ groups: [...existingGroups, ...groups],
+ loading: false,
+ paging
+ }));
}
}, this.stopLoading);
- };
-
- fetchMoreGroups = () => {
- const { paging } = this.state;
- if (paging && paging.total > paging.pageIndex * paging.pageSize) {
- this.makeFetchGroupsRequest({ p: paging.pageIndex + 1 }).then(({ groups, paging }) => {
- if (this.mounted) {
- this.setState(({ groups: existingGroups = [] }) => ({
- groups: [...existingGroups, ...groups],
- loading: false,
- paging
- }));
- }
- }, this.stopLoading);
- }
- };
+ }
+ };
- search = (query: string) => {
- this.fetchGroups({ q: query });
- this.setState({ query });
- };
+ search = (query: string) => {
+ this.fetchGroups({ q: query });
+ this.setState({ query });
+ };
- refresh = () => {
- this.fetchGroups({ q: this.state.query });
- };
+ refresh = () => {
+ this.fetchGroups({ q: this.state.query });
+ };
- handleCreate = (data: { description: string; name: string }) => {
- return createGroup({ ...data, organization: this.organization }).then(group => {
- if (this.mounted) {
- this.setState(({ groups = [] }: State) => ({
- groups: [...groups, group]
- }));
- }
- });
- };
+ handleCreate = (data: { description: string; name: string }) => {
+ return createGroup({ ...data, organization: this.organization }).then(group => {
+ if (this.mounted) {
+ this.setState(({ groups = [] }: State) => ({
+ groups: [...groups, group]
+ }));
+ }
+ });
+ };
- handleDelete = (name: string) => {
- return deleteGroup({ name, organization: this.organization }).then(() => {
- if (this.mounted) {
- this.setState(({ groups = [] }: State) => ({
- groups: groups.filter(group => group.name !== name)
- }));
- }
- });
- };
+ handleDelete = (name: string) => {
+ return deleteGroup({ name, organization: this.organization }).then(() => {
+ if (this.mounted) {
+ this.setState(({ groups = [] }: State) => ({
+ groups: groups.filter(group => group.name !== name)
+ }));
+ }
+ });
+ };
- handleEdit = (data: { description?: string; id: number; name?: string }) => {
- return updateGroup(data).then(() => {
- if (this.mounted) {
- this.setState(({ groups = [] }: State) => ({
- groups: groups.map(group => (group.id === data.id ? { ...group, ...data } : group))
- }));
- }
- });
- };
-
- render() {
- const { groups, loading, paging, query } = this.state;
-
- const showAnyone =
- this.props.organization === undefined && 'anyone'.includes(query.toLowerCase());
-
- return (
- <>
- <Suggestions suggestions="user_groups" />
- <Helmet title={translate('user_groups.page')} />
- <div className="page page-limited" id="groups-page">
- <Header loading={loading} onCreate={this.handleCreate} />
-
- <SearchBox
- className="big-spacer-bottom"
- id="groups-search"
- minLength={2}
- onChange={this.search}
- placeholder={translate('search.search_by_name')}
- value={query}
+ handleEdit = (data: { description?: string; id: number; name?: string }) => {
+ return updateGroup(data).then(() => {
+ if (this.mounted) {
+ this.setState(({ groups = [] }: State) => ({
+ groups: groups.map(group => (group.id === data.id ? { ...group, ...data } : group))
+ }));
+ }
+ });
+ };
+
+ render() {
+ const { groups, loading, paging, query } = this.state;
+
+ const showAnyone =
+ this.props.organization === undefined && 'anyone'.includes(query.toLowerCase());
+
+ return (
+ <>
+ <Suggestions suggestions="user_groups" />
+ <Helmet title={translate('user_groups.page')} />
+ <div className="page page-limited" id="groups-page">
+ <Header loading={loading} onCreate={this.handleCreate} />
+
+ <SearchBox
+ className="big-spacer-bottom"
+ id="groups-search"
+ minLength={2}
+ onChange={this.search}
+ placeholder={translate('search.search_by_name')}
+ value={query}
+ />
+
+ {groups !== undefined && (
+ <List
+ groups={groups}
+ onDelete={this.handleDelete}
+ onEdit={this.handleEdit}
+ onEditMembers={this.refresh}
+ organization={this.organization}
+ showAnyone={showAnyone}
/>
-
- {groups !== undefined && (
- <List
- groups={groups}
- onDelete={this.handleDelete}
- onEdit={this.handleEdit}
- onEditMembers={this.refresh}
- organization={this.organization}
- showAnyone={showAnyone}
- />
+ )}
+
+ {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>
- </>
- );
- }
+ </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
new file mode 100644
index 00000000000..f8576bb6039
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx
@@ -0,0 +1,60 @@
+/*
+ * 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 { mockOrganization } from '../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+
+jest.mock('../../../../api/user_groups', () => ({
+ createGroup: jest.fn(),
+ deleteGroup: jest.fn(),
+ searchUsersGroups: jest.fn().mockResolvedValue({
+ paging: { pageIndex: 1, pageSize: 100, total: 2 },
+ groups: [
+ {
+ default: false,
+ description: 'Owners of organization foo',
+ id: 1,
+ membersCount: 1,
+ name: 'Owners'
+ },
+ {
+ default: true,
+ description: 'Members of organization foo',
+ id: 2,
+ membersCount: 2,
+ name: 'Members'
+ }
+ ]
+ }),
+ updateGroup: jest.fn()
+}));
+
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<App['props']> = {}) {
+ return shallow(<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
new file mode 100644
index 00000000000..4f4ffb932d6
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -0,0 +1,96 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+ <Suggestions
+ suggestions="user_groups"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="user_groups.page"
+ />
+ <div
+ className="page page-limited"
+ id="groups-page"
+ >
+ <Header
+ loading={true}
+ onCreate={[Function]}
+ />
+ <SearchBox
+ className="big-spacer-bottom"
+ id="groups-search"
+ minLength={2}
+ onChange={[Function]}
+ placeholder="search.search_by_name"
+ value=""
+ />
+ </div>
+</Fragment>
+`;
+
+exports[`should render correctly 2`] = `
+<Fragment>
+ <Suggestions
+ suggestions="user_groups"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="user_groups.page"
+ />
+ <div
+ className="page page-limited"
+ id="groups-page"
+ >
+ <Header
+ loading={false}
+ onCreate={[Function]}
+ />
+ <SearchBox
+ className="big-spacer-bottom"
+ id="groups-search"
+ minLength={2}
+ onChange={[Function]}
+ placeholder="search.search_by_name"
+ value=""
+ />
+ <List
+ groups={
+ Array [
+ Object {
+ "default": false,
+ "description": "Owners of organization foo",
+ "id": 1,
+ "membersCount": 1,
+ "name": "Owners",
+ },
+ Object {
+ "default": true,
+ "description": "Members of organization foo",
+ "id": 2,
+ "membersCount": 2,
+ "name": "Members",
+ },
+ ]
+ }
+ onDelete={[Function]}
+ onEdit={[Function]}
+ onEditMembers={[Function]}
+ organization="foo"
+ showAnyone={false}
+ />
+ <div
+ id="groups-list-footer"
+ >
+ <ListFooter
+ count={2}
+ loadMore={[Function]}
+ ready={true}
+ total={2}
+ />
+ </div>
+ </div>
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap
index 6a1c6b7e4c7..bbf778c3c50 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap
@@ -261,7 +261,7 @@ exports[`should edit members 2`] = `
<svg
class="search-box-magnifier"
height="16"
- style="fill-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 1.41421;"
+ style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 1.41421;"
version="1.1"
viewBox="0 0 16 16"
width="16"
diff --git a/server/sonar-web/src/main/js/apps/organizations/forSingleOrganization.tsx b/server/sonar-web/src/main/js/apps/organizations/forSingleOrganization.tsx
deleted file mode 100644
index 71b81cc0942..00000000000
--- a/server/sonar-web/src/main/js/apps/organizations/forSingleOrganization.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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 { connect } from 'react-redux';
-import { withRouter, WithRouterProps } from 'react-router';
-import { areThereCustomOrganizations, Store } from '../../store/rootReducer';
-import { getWrappedDisplayName } from '../../components/hoc/utils';
-
-type ReactComponent<P> = React.ComponentClass<P> | React.StatelessComponent<P>;
-
-export default function forSingleOrganization<P>(ComposedComponent: ReactComponent<P>) {
- interface StateProps {
- customOrganizations: boolean | undefined;
- }
-
- class ForSingleOrganization extends React.Component<StateProps & WithRouterProps> {
- static displayName = getWrappedDisplayName(
- ComposedComponent as React.ComponentClass,
- 'forSingleOrganization'
- );
-
- render() {
- const { customOrganizations, router, ...other } = this.props;
-
- if (!other.params.organizationKey && customOrganizations) {
- router.replace('/not_found');
- return null;
- }
-
- return <ComposedComponent {...other} />;
- }
- }
-
- const mapStateToProps = (state: Store) => ({
- customOrganizations: areThereCustomOrganizations(state)
- });
-
- return connect(mapStateToProps)(withRouter(ForSingleOrganization));
-}
diff --git a/server/sonar-web/src/main/js/apps/organizations/routes.ts b/server/sonar-web/src/main/js/apps/organizations/routes.ts
index 61f0af0132b..21f0dd439c7 100644
--- a/server/sonar-web/src/main/js/apps/organizations/routes.ts
+++ b/server/sonar-web/src/main/js/apps/organizations/routes.ts
@@ -87,7 +87,7 @@ const routes = [
},
{
path: 'permission_templates',
- component: lazyLoad(() => import('../permission-templates/components/AppContainer'))
+ component: lazyLoad(() => import('../permission-templates/components/App'))
},
{
path: 'projects_management',
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx
index 36e043b1adf..d8ecd3ffeb7 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx
@@ -18,14 +18,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { connect } from 'react-redux';
import { Location } from 'history';
import Home from './Home';
import Template from './Template';
import OrganizationHelmet from '../../../components/common/OrganizationHelmet';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
-import { getPermissionTemplates } from '../../../api/permissions';
import { sortPermissions, mergePermissionsToTemplates, mergeDefaultsToTemplates } from '../utils';
+import { getPermissionTemplates } from '../../../api/permissions';
import { translate } from '../../../helpers/l10n';
+import { getAppState, Store } from '../../../store/rootReducer';
import '../../permissions/styles.css';
interface Props {
@@ -40,7 +42,7 @@ interface State {
permissionTemplates: T.PermissionTemplate[];
}
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
ready: false,
@@ -123,3 +125,7 @@ export default class App extends React.PureComponent<Props, State> {
);
}
}
+
+const mapStateToProps = (state: Store) => ({ topQualifiers: getAppState(state).qualifiers });
+
+export default connect(mapStateToProps)(App);
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.tsx
deleted file mode 100644
index 156e490c839..00000000000
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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 { connect } from 'react-redux';
-import App from './App';
-import forSingleOrganization from '../../organizations/forSingleOrganization';
-import { getAppState, Store } from '../../../store/rootReducer';
-
-const mapStateToProps = (state: Store) => ({ topQualifiers: getAppState(state).qualifiers });
-
-export default forSingleOrganization(connect(mapStateToProps)(App));
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/App-test.tsx
new file mode 100644
index 00000000000..e3d0c710f76
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/App-test.tsx
@@ -0,0 +1,65 @@
+/*
+ * 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 { mockLocation, mockOrganization } from '../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+
+jest.mock('../../../../api/permissions', () => ({
+ getPermissionTemplates: jest.fn().mockResolvedValue({
+ permissionTemplates: [
+ {
+ id: '1',
+ name: 'Default template',
+ description: 'Default permission template of organization test',
+ createdAt: '2019-02-07T17:23:26+0100',
+ updatedAt: '2019-02-07T17:23:26+0100',
+ permissions: [
+ { key: 'admin', usersCount: 0, groupsCount: 1, withProjectCreator: false },
+ { key: 'codeviewer', usersCount: 0, groupsCount: 1, withProjectCreator: false }
+ ]
+ }
+ ],
+ defaultTemplates: [{ templateId: '1', qualifier: 'TRK' }],
+ permissions: [
+ { key: 'admin', name: 'Administer', description: 'Admin permission' },
+ { key: 'codeviewer', name: 'See Source Code', description: 'Code viewer permission' }
+ ]
+ })
+}));
+
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<App['props']> = {}) {
+ return shallow(
+ <App
+ location={mockLocation()}
+ organization={mockOrganization()}
+ topQualifiers={['TRK']}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/App-test.tsx.snap
new file mode 100644
index 00000000000..327c913c7f0
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -0,0 +1,113 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div>
+ <Suggestions
+ suggestions="permission_templates"
+ />
+ <OrganizationHelmet
+ organization={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
+ title="permission_templates.page"
+ />
+ <Home
+ organization={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
+ permissionTemplates={Array []}
+ permissions={Array []}
+ ready={false}
+ refresh={[Function]}
+ topQualifiers={
+ Array [
+ "TRK",
+ ]
+ }
+ />
+</div>
+`;
+
+exports[`should render correctly 2`] = `
+<div>
+ <Suggestions
+ suggestions="permission_templates"
+ />
+ <OrganizationHelmet
+ organization={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
+ title="permission_templates.page"
+ />
+ <Home
+ organization={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
+ permissionTemplates={
+ Array [
+ Object {
+ "createdAt": "2019-02-07T17:23:26+0100",
+ "defaultFor": Array [
+ "TRK",
+ ],
+ "description": "Default permission template of organization test",
+ "id": "1",
+ "name": "Default template",
+ "permissions": Array [
+ Object {
+ "description": "Code viewer permission",
+ "groupsCount": 1,
+ "key": "codeviewer",
+ "name": "See Source Code",
+ "usersCount": 0,
+ "withProjectCreator": false,
+ },
+ Object {
+ "description": "Admin permission",
+ "groupsCount": 1,
+ "key": "admin",
+ "name": "Administer",
+ "usersCount": 0,
+ "withProjectCreator": false,
+ },
+ ],
+ "updatedAt": "2019-02-07T17:23:26+0100",
+ },
+ ]
+ }
+ permissions={
+ Array [
+ Object {
+ "description": "Code viewer permission",
+ "key": "codeviewer",
+ "name": "See Source Code",
+ },
+ Object {
+ "description": "Admin permission",
+ "key": "admin",
+ "name": "Administer",
+ },
+ ]
+ }
+ ready={true}
+ refresh={[Function]}
+ topQualifiers={
+ Array [
+ "TRK",
+ ]
+ }
+ />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/routes.ts b/server/sonar-web/src/main/js/apps/permission-templates/routes.ts
index a7747c58bd8..094046e8466 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/routes.ts
+++ b/server/sonar-web/src/main/js/apps/permission-templates/routes.ts
@@ -21,7 +21,7 @@ import { lazyLoad } from '../../components/lazyLoad';
const routes = [
{
- indexRoute: { component: lazyLoad(() => import('./components/AppContainer')) }
+ indexRoute: { component: lazyLoad(() => import('./components/App')) }
}
];
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 52783b7421c..b2c8473d413 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
@@ -25,7 +25,6 @@ import AllHoldersList from './AllHoldersList';
import * as api from '../../../../api/permissions';
import Suggestions from '../../../../app/components/embed-docs-modal/Suggestions';
import { translate } from '../../../../helpers/l10n';
-import forSingleOrganization from '../../../organizations/forSingleOrganization';
import '../../styles.css';
interface Props {
@@ -42,7 +41,7 @@ interface State {
usersPaging?: T.Paging;
}
-export class App extends React.PureComponent<Props, State> {
+export default class App extends React.PureComponent<Props, State> {
mounted = false;
constructor(props: Props) {
@@ -301,5 +300,3 @@ export class App extends React.PureComponent<Props, State> {
);
}
}
-
-export default forSingleOrganization(App);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityEventSelectOption.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityEventSelectOption.tsx
index 6c2d5df39eb..ab51df202d8 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityEventSelectOption.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityEventSelectOption.tsx
@@ -30,22 +30,22 @@ interface Props {
children?: Element | Text;
className?: string;
isFocused?: boolean;
- onFocus: (option: Option, event: React.MouseEvent) => void;
- onSelect: (option: Option, event: React.MouseEvent) => void;
+ onFocus: (option: Option, event: React.MouseEvent<HTMLDivElement>) => void;
+ onSelect: (option: Option, event: React.MouseEvent<HTMLDivElement>) => void;
}
export default class ProjectActivityEventSelectOption extends React.PureComponent<Props> {
- handleMouseDown = (event: React.MouseEvent) => {
+ handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
this.props.onSelect(this.props.option, event);
};
- handleMouseEnter = (event: React.MouseEvent) => {
+ handleMouseEnter = (event: React.MouseEvent<HTMLDivElement>) => {
this.props.onFocus(this.props.option, event);
};
- handleMouseMove = (event: React.MouseEvent) => {
+ handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
if (this.props.isFocused) {
return;
}
@@ -60,6 +60,8 @@ export default class ProjectActivityEventSelectOption extends React.PureComponen
onMouseDown={this.handleMouseDown}
onMouseEnter={this.handleMouseEnter}
onMouseMove={this.handleMouseMove}
+ role="link"
+ tabIndex={0}
title={option.label}>
<ProjectEventIcon className={'project-activity-event-icon ' + option.value} />
<span className="little-spacer-left">{this.props.children}</span>
diff --git a/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx b/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
index d9f3a29bd32..a1d3e684a41 100644
--- a/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
+++ b/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
@@ -18,51 +18,52 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { withRouter, WithRouterProps } from 'react-router';
-import { deleteProject, deletePortfolio } from '../../api/components';
import { Button } from '../../components/ui/buttons';
-import { translate, translateWithParameters } from '../../helpers/l10n';
+import { withRouter, Router } from '../../components/hoc/withRouter';
import addGlobalSuccessMessage from '../../app/utils/addGlobalSuccessMessage';
import ConfirmButton from '../../components/controls/ConfirmButton';
+import { deleteProject, deletePortfolio } from '../../api/components';
+import { translate, translateWithParameters } from '../../helpers/l10n';
interface Props {
component: Pick<T.Component, 'key' | 'name' | 'qualifier'>;
+ router: Pick<Router, 'replace'>;
}
-export default withRouter(
- class Form extends React.PureComponent<Props & WithRouterProps> {
- handleDelete = () => {
- const { component } = this.props;
- const isProject = component.qualifier === 'TRK';
- const deleteMethod = isProject ? deleteProject : deletePortfolio;
- const redirectTo = isProject ? '/' : '/portfolios';
- return deleteMethod(component.key).then(() => {
- addGlobalSuccessMessage(
- translateWithParameters('project_deletion.resource_deleted', component.name)
- );
- this.props.router.replace(redirectTo);
- });
- };
-
- render() {
- const { component } = this.props;
- return (
- <ConfirmButton
- confirmButtonText={translate('delete')}
- isDestructive={true}
- modalBody={translateWithParameters(
- 'project_deletion.delete_resource_confirmation',
- component.name
- )}
- modalHeader={translate('qualifier.delete', component.qualifier)}
- onConfirm={this.handleDelete}>
- {({ onClick }) => (
- <Button className="button-red" id="delete-project" onClick={onClick}>
- {translate('delete')}
- </Button>
- )}
- </ConfirmButton>
+export class Form extends React.PureComponent<Props> {
+ handleDelete = () => {
+ const { component } = this.props;
+ const isProject = component.qualifier === 'TRK';
+ const deleteMethod = isProject ? deleteProject : deletePortfolio;
+ const redirectTo = isProject ? '/' : '/portfolios';
+ return deleteMethod(component.key).then(() => {
+ addGlobalSuccessMessage(
+ translateWithParameters('project_deletion.resource_deleted', component.name)
);
- }
+ this.props.router.replace(redirectTo);
+ });
+ };
+
+ render() {
+ const { component } = this.props;
+ return (
+ <ConfirmButton
+ confirmButtonText={translate('delete')}
+ isDestructive={true}
+ modalBody={translateWithParameters(
+ 'project_deletion.delete_resource_confirmation',
+ component.name
+ )}
+ modalHeader={translate('qualifier.delete', component.qualifier)}
+ onConfirm={this.handleDelete}>
+ {({ onClick }) => (
+ <Button className="button-red" id="delete-project" onClick={onClick}>
+ {translate('delete')}
+ </Button>
+ )}
+ </ConfirmButton>
+ );
}
-);
+}
+
+export default withRouter(Form);
diff --git a/server/sonar-web/src/main/js/apps/projectDeletion/__tests__/Form-test.tsx b/server/sonar-web/src/main/js/apps/projectDeletion/__tests__/Form-test.tsx
index 20a14b9ec6f..340e5ec4268 100644
--- a/server/sonar-web/src/main/js/apps/projectDeletion/__tests__/Form-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectDeletion/__tests__/Form-test.tsx
@@ -19,8 +19,9 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import Form from '../Form';
+import { Form } from '../Form';
import { deleteProject, deletePortfolio } from '../../../api/components';
+import { mockRouter } from '../../../helpers/testMocks';
jest.mock('../../../api/components', () => ({
deleteProject: jest.fn().mockResolvedValue(undefined),
@@ -28,21 +29,20 @@ jest.mock('../../../api/components', () => ({
}));
beforeEach(() => {
- (deleteProject as jest.Mock).mockClear();
- (deletePortfolio as jest.Mock).mockClear();
+ jest.clearAllMocks();
});
it('should render', () => {
const component = { key: 'foo', name: 'Foo', qualifier: 'TRK' };
- const form = shallow(<Form component={component} />).dive();
+ const form = shallow(<Form component={component} router={mockRouter()} />);
expect(form).toMatchSnapshot();
expect(form.prop<Function>('children')({ onClick: jest.fn() })).toMatchSnapshot();
});
it('should delete project', async () => {
const component = { key: 'foo', name: 'Foo', qualifier: 'TRK' };
- const router = getMockedRouter();
- const form = shallow(<Form component={component} router={router} />).dive();
+ const router = mockRouter();
+ const form = shallow(<Form component={component} router={router} />);
form.prop<Function>('onConfirm')();
expect(deleteProject).toBeCalledWith('foo');
await new Promise(setImmediate);
@@ -51,24 +51,11 @@ it('should delete project', async () => {
it('should delete portfolio', async () => {
const component = { key: 'foo', name: 'Foo', qualifier: 'VW' };
- const router = getMockedRouter();
- const form = shallow(<Form component={component} router={router} />).dive();
+ const router = mockRouter();
+ const form = shallow(<Form component={component} router={router} />);
form.prop<Function>('onConfirm')();
expect(deletePortfolio).toBeCalledWith('foo');
expect(deleteProject).not.toBeCalled();
await new Promise(setImmediate);
expect(router.replace).toBeCalledWith('/portfolios');
});
-
-// have to mock all properties to pass the prop types check
-const getMockedRouter = () => ({
- createHref: jest.fn(),
- createPath: jest.fn(),
- go: jest.fn(),
- goBack: jest.fn(),
- goForward: jest.fn(),
- isActive: jest.fn(),
- push: jest.fn(),
- replace: jest.fn(),
- setRouteLeaveHook: jest.fn()
-});
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx
index 36b4389d786..34bf91b190b 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx
@@ -20,11 +20,10 @@
import * as React from 'react';
import { connect } from 'react-redux';
import App from './App';
-import forSingleOrganization from '../organizations/forSingleOrganization';
-import { getAppState, getOrganizationByKey, getCurrentUser, Store } from '../../store/rootReducer';
-import { receiveOrganizations } from '../../store/organizations';
import { changeProjectDefaultVisibility } from '../../api/permissions';
+import { getAppState, getOrganizationByKey, getCurrentUser, Store } from '../../store/rootReducer';
import { fetchOrganization } from '../../store/rootActions';
+import { receiveOrganizations } from '../../store/organizations';
interface StateProps {
appState: { defaultOrganization: string; qualifiers: string[] };
@@ -107,9 +106,7 @@ const mapDispatchToProps = (dispatch: Function) => ({
dispatch(onVisibilityChange(organization, visibility))
});
-export default forSingleOrganization(
- connect(
- mapStateToProps,
- mapDispatchToProps
- )(AppContainer)
-);
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(AppContainer);
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.tsx
index 747add04f55..89bf7448f64 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.tsx
@@ -19,7 +19,6 @@
*/
import { connect } from 'react-redux';
import App from './App';
-import forSingleOrganization from '../../organizations/forSingleOrganization';
import { getLanguages, getOrganizationByKey, Store } from '../../../store/rootReducer';
const mapStateToProps = (state: Store, ownProps: any) => ({
@@ -29,4 +28,4 @@ const mapStateToProps = (state: Store, ownProps: any) => ({
: undefined
});
-export default forSingleOrganization(connect(mapStateToProps)(App));
+export default connect(mapStateToProps)(App);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.tsx
index a3eff359bd5..3744dc83ad6 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.tsx
@@ -32,9 +32,7 @@ import {
} from '../../utils';
const typeMapping: {
- [type in T.SettingType]?:
- | React.ComponentClass<DefaultSpecializedInputProps>
- | React.StatelessComponent<DefaultSpecializedInputProps>
+ [type in T.SettingType]?: React.ComponentType<DefaultSpecializedInputProps>
} = {
STRING: InputForString,
TEXT: InputForText,
diff --git a/server/sonar-web/src/main/js/components/common/SelectListItem.tsx b/server/sonar-web/src/main/js/components/common/SelectListItem.tsx
index 5c7cae85ba5..04896eaaf3a 100644
--- a/server/sonar-web/src/main/js/components/common/SelectListItem.tsx
+++ b/server/sonar-web/src/main/js/components/common/SelectListItem.tsx
@@ -30,7 +30,7 @@ interface Props {
}
export default class SelectListItem extends React.PureComponent<Props> {
- handleSelect = (event: React.MouseEvent) => {
+ handleSelect = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
if (this.props.onSelect) {
this.props.onSelect(this.props.item);
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/InstanceMessage-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/InstanceMessage-test.tsx
index e4ff33d67b5..390c0234c67 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/InstanceMessage-test.tsx
+++ b/server/sonar-web/src/main/js/components/common/__tests__/InstanceMessage-test.tsx
@@ -26,24 +26,24 @@ jest.mock('../../../helpers/system', () => ({ getInstance: jest.fn() }));
it('should replace {instance} with "SonarQube"', () => {
const childFunc = jest.fn();
- getWrapper(childFunc, 'foo {instance} bar');
+ shallowRender(childFunc, 'foo {instance} bar');
expect(childFunc).toHaveBeenCalledWith('foo SonarQube bar');
});
it('should replace {instance} with "SonarCloud"', () => {
const childFunc = jest.fn();
- getWrapper(childFunc, 'foo {instance} bar', true);
+ shallowRender(childFunc, 'foo {instance} bar', true);
expect(childFunc).toHaveBeenCalledWith('foo SonarCloud bar');
});
it('should return the same message', () => {
const childFunc = jest.fn();
- getWrapper(childFunc, 'no instance to replace');
+ shallowRender(childFunc, 'no instance to replace');
expect(childFunc).toHaveBeenCalledWith('no instance to replace');
});
-function getWrapper(
- children: (msg: string) => React.ReactNode,
+function shallowRender(
+ children: (msg: string) => React.ReactChild,
message: string,
onSonarCloud = false
) {
diff --git a/server/sonar-web/src/main/js/components/docs/DocToc.tsx b/server/sonar-web/src/main/js/components/docs/DocToc.tsx
index 6d1aed8b1f3..6f2f2cac2f3 100644
--- a/server/sonar-web/src/main/js/components/docs/DocToc.tsx
+++ b/server/sonar-web/src/main/js/components/docs/DocToc.tsx
@@ -141,7 +141,7 @@ export default class DocToc extends React.PureComponent<Props, State> {
className={classNames({ active: highlightAnchor === anchor.href })}
href={anchor.href}
key={anchor.title}
- onClick={event => {
+ onClick={(event: React.MouseEvent<HTMLAnchorElement>) => {
this.props.onAnchorClick(anchor.href, event);
}}>
{anchor.title}
diff --git a/server/sonar-web/src/main/js/components/hoc/withRouter.tsx b/server/sonar-web/src/main/js/components/hoc/withRouter.tsx
index b741ba172d9..4567cbfb7a5 100644
--- a/server/sonar-web/src/main/js/components/hoc/withRouter.tsx
+++ b/server/sonar-web/src/main/js/components/hoc/withRouter.tsx
@@ -28,8 +28,8 @@ interface InjectedProps {
router?: Partial<Router>;
}
-export function withRouter<P extends InjectedProps, S>(
- WrappedComponent: React.ComponentClass<P & InjectedProps>
-): React.ComponentClass<T.Omit<P, keyof InjectedProps>, S> {
+export function withRouter<P extends InjectedProps>(
+ WrappedComponent: React.ComponentType<P & InjectedProps>
+): React.ComponentType<T.Omit<P, keyof InjectedProps>> {
return originalWithRouter(WrappedComponent as any);
}
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 0526cd87d6c..06d2eb4173d 100644
--- a/server/sonar-web/src/main/js/components/issue/IssueView.tsx
+++ b/server/sonar-web/src/main/js/components/issue/IssueView.tsx
@@ -43,13 +43,13 @@ interface Props {
}
export default class IssueView extends React.PureComponent<Props> {
- handleCheck = (_checked: boolean) => {
+ handleCheck = () => {
if (this.props.onCheck) {
this.props.onCheck(this.props.issue.key);
}
};
- handleClick = (event: React.MouseEvent) => {
+ handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
if (!isClickable(event.target as HTMLElement) && this.props.onClick) {
event.preventDefault();
this.props.onClick(this.props.issue.key);
diff --git a/server/sonar-web/src/main/js/components/lazyLoad.tsx b/server/sonar-web/src/main/js/components/lazyLoad.tsx
index 4d00e36fe62..dad22b48f66 100644
--- a/server/sonar-web/src/main/js/components/lazyLoad.tsx
+++ b/server/sonar-web/src/main/js/components/lazyLoad.tsx
@@ -22,10 +22,8 @@ import { Alert } from './ui/Alert';
import { translate } from '../helpers/l10n';
import { get, save } from '../helpers/storage';
-type ReactComponent<P> = React.ComponentClass<P> | React.StatelessComponent<P>;
-
interface Loader<P> {
- (): Promise<{ default: ReactComponent<P> }>;
+ (): Promise<{ default: React.ComponentType<P> }>;
}
export const LAST_FAILED_CHUNK_STORAGE_KEY = 'sonarqube.last_failed_chunk';
@@ -36,12 +34,13 @@ export function lazyLoad<P>(loader: Loader<P>, displayName?: string) {
}
interface State {
- Component?: ReactComponent<P>;
+ Component?: React.ComponentType<P>;
error?: ImportError;
}
// use `React.Component`, not `React.PureComponent` to always re-render
// and let the child component decide if it needs to change
+ // also, use any instead of P because typescript doesn't cope correctly with default props
return class LazyLoader extends React.Component<any, State> {
mounted = false;
static displayName = displayName;
@@ -56,7 +55,7 @@ export function lazyLoad<P>(loader: Loader<P>, displayName?: string) {
this.mounted = false;
}
- receiveComponent = (Component: ReactComponent<P>) => {
+ receiveComponent = (Component: React.ComponentType<P>) => {
if (this.mounted) {
this.setState({ Component, error: undefined });
}
@@ -92,7 +91,7 @@ export function lazyLoad<P>(loader: Loader<P>, displayName?: string) {
return null;
}
- return <Component {...this.props} />;
+ return <Component {...this.props as any} />;
}
};
}
diff --git a/server/sonar-web/src/main/js/components/ui/Alert.tsx b/server/sonar-web/src/main/js/components/ui/Alert.tsx
index af8579d7a81..8e4e37cfe17 100644
--- a/server/sonar-web/src/main/js/components/ui/Alert.tsx
+++ b/server/sonar-web/src/main/js/components/ui/Alert.tsx
@@ -35,7 +35,7 @@ export interface AlertProps {
variant: AlertVariant;
}
-export function Alert(props: AlertProps & React.HTMLAttributes<HTMLElement>) {
+export function Alert(props: AlertProps & React.HTMLAttributes<HTMLDivElement>) {
const { className, display, variant, ...domProps } = props;
return (
<div
diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts
index d4d2bc5e45e..76f9a528015 100644
--- a/server/sonar-web/src/main/js/helpers/testMocks.ts
+++ b/server/sonar-web/src/main/js/helpers/testMocks.ts
@@ -209,6 +209,7 @@ export function mockIssue(withLocations = false, overrides: Partial<T.Issue> = {
export function mockLocation(overrides: Partial<Location> = {}): Location {
return {
action: 'PUSH',
+ hash: '',
key: 'key',
pathname: '/path',
query: {},
diff --git a/sonar-application/build.gradle b/sonar-application/build.gradle
index 5b8aa7d71bf..7c5dfe538ed 100644
--- a/sonar-application/build.gradle
+++ b/sonar-application/build.gradle
@@ -175,8 +175,8 @@ zip.doFirst {
}
// Check the size of the archive
zip.doLast {
- def minLength = 190000000
- def maxLength = 197000000
+ def minLength = 191000000
+ def maxLength = 198000000
def length = new File(distsDir, archiveName).length()
if (length < minLength)
throw new GradleException("$archiveName size ($length) too small. Min is $minLength")