diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2019-03-22 14:18:52 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2019-03-29 09:44:59 +0100 |
commit | 17bbc381c796efc51326c96e77c0fe7294c6a010 (patch) | |
tree | 7f996381da21d22e128d81aa288f8c3335cc704c | |
parent | 616f0b1daf61d1040052906a649a201e26a4f376 (diff) | |
download | sonarqube-17bbc381c796efc51326c96e77c0fe7294c6a010.tar.gz sonarqube-17bbc381c796efc51326c96e77c0fe7294c6a010.zip |
Update React, Typescript and Eslint dependencies
* Fix ts and eslint issues
* Drop forSingleOrganization
* Update Typscript on extensions
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") |