Browse Source

Update React, Typescript and Eslint dependencies

* Fix ts and eslint issues
* Drop forSingleOrganization
* Update Typscript on extensions
tags/7.8
Grégoire Aubert 5 years ago
parent
commit
17bbc381c7
35 changed files with 636 additions and 393 deletions
  1. 10
    10
      server/sonar-docs/package.json
  2. 15
    15
      server/sonar-vsts/package.json
  3. 30
    30
      server/sonar-web/package.json
  4. 1
    0
      server/sonar-web/src/main/js/@types/react-countup.d.ts
  5. 21
    12
      server/sonar-web/src/main/js/app/utils/startReactApp.tsx
  6. 2
    2
      server/sonar-web/src/main/js/apps/about/sonarcloud/Pricing.tsx
  7. 0
    2
      server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.tsx
  8. 1
    1
      server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx
  9. 127
    130
      server/sonar-web/src/main/js/apps/groups/components/App.tsx
  10. 60
    0
      server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx
  11. 96
    0
      server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap
  12. 1
    1
      server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap
  13. 0
    56
      server/sonar-web/src/main/js/apps/organizations/forSingleOrganization.tsx
  14. 1
    1
      server/sonar-web/src/main/js/apps/organizations/routes.ts
  15. 8
    2
      server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx
  16. 0
    27
      server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.tsx
  17. 65
    0
      server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/App-test.tsx
  18. 113
    0
      server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/App-test.tsx.snap
  19. 1
    1
      server/sonar-web/src/main/js/apps/permission-templates/routes.ts
  20. 1
    4
      server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx
  21. 7
    5
      server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityEventSelectOption.tsx
  22. 39
    38
      server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
  23. 8
    21
      server/sonar-web/src/main/js/apps/projectDeletion/__tests__/Form-test.tsx
  24. 6
    9
      server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx
  25. 1
    2
      server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.tsx
  26. 1
    3
      server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.tsx
  27. 1
    1
      server/sonar-web/src/main/js/components/common/SelectListItem.tsx
  28. 5
    5
      server/sonar-web/src/main/js/components/common/__tests__/InstanceMessage-test.tsx
  29. 1
    1
      server/sonar-web/src/main/js/components/docs/DocToc.tsx
  30. 3
    3
      server/sonar-web/src/main/js/components/hoc/withRouter.tsx
  31. 2
    2
      server/sonar-web/src/main/js/components/issue/IssueView.tsx
  32. 5
    6
      server/sonar-web/src/main/js/components/lazyLoad.tsx
  33. 1
    1
      server/sonar-web/src/main/js/components/ui/Alert.tsx
  34. 1
    0
      server/sonar-web/src/main/js/helpers/testMocks.ts
  35. 2
    2
      sonar-application/build.gradle

+ 10
- 10
server/sonar-docs/package.json View File

@@ -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": {

+ 15
- 15
server/sonar-vsts/package.json View File

@@ -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"

+ 30
- 30
server/sonar-web/package.json View File

@@ -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"

+ 1
- 0
server/sonar-web/src/main/js/@types/react-countup.d.ts View File

@@ -19,6 +19,7 @@
*/
declare module 'react-countup' {
interface Props {
children: (data: { countUpRef?: React.RefObject<any> }) => JSX.Element;
decimal?: string;
decimals?: number;
delay?: number;

+ 21
- 12
server/sonar-web/src/main/js/app/utils/startReactApp.tsx View File

@@ -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} />

+ 2
- 2
server/sonar-web/src/main/js/apps/about/sonarcloud/Pricing.tsx View File

@@ -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) {

+ 0
- 2
server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.tsx View File

@@ -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);

+ 1
- 1
server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx View File

@@ -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);

+ 127
- 130
server/sonar-web/src/main/js/apps/groups/components/App.tsx View File

@@ -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>
</>
);
}
);
}

+ 60
- 0
server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx View File

@@ -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} />);
}

+ 96
- 0
server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap View File

@@ -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>
`;

+ 1
- 1
server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap View File

@@ -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"

+ 0
- 56
server/sonar-web/src/main/js/apps/organizations/forSingleOrganization.tsx View File

@@ -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));
}

+ 1
- 1
server/sonar-web/src/main/js/apps/organizations/routes.ts View File

@@ -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',

+ 8
- 2
server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx View File

@@ -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);

+ 0
- 27
server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.tsx View File

@@ -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));

+ 65
- 0
server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/App-test.tsx View File

@@ -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}
/>
);
}

+ 113
- 0
server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/App-test.tsx.snap View File

@@ -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>
`;

+ 1
- 1
server/sonar-web/src/main/js/apps/permission-templates/routes.ts View File

@@ -21,7 +21,7 @@ import { lazyLoad } from '../../components/lazyLoad';

const routes = [
{
indexRoute: { component: lazyLoad(() => import('./components/AppContainer')) }
indexRoute: { component: lazyLoad(() => import('./components/App')) }
}
];


+ 1
- 4
server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx View File

@@ -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);

+ 7
- 5
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityEventSelectOption.tsx View File

@@ -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>

+ 39
- 38
server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx View File

@@ -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);

+ 8
- 21
server/sonar-web/src/main/js/apps/projectDeletion/__tests__/Form-test.tsx View File

@@ -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()
});

+ 6
- 9
server/sonar-web/src/main/js/apps/projectsManagement/AppContainer.tsx View File

@@ -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);

+ 1
- 2
server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.tsx View File

@@ -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);

+ 1
- 3
server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.tsx View File

@@ -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,

+ 1
- 1
server/sonar-web/src/main/js/components/common/SelectListItem.tsx View File

@@ -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);

+ 5
- 5
server/sonar-web/src/main/js/components/common/__tests__/InstanceMessage-test.tsx View File

@@ -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
) {

+ 1
- 1
server/sonar-web/src/main/js/components/docs/DocToc.tsx View File

@@ -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}

+ 3
- 3
server/sonar-web/src/main/js/components/hoc/withRouter.tsx View File

@@ -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);
}

+ 2
- 2
server/sonar-web/src/main/js/components/issue/IssueView.tsx View File

@@ -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);

+ 5
- 6
server/sonar-web/src/main/js/components/lazyLoad.tsx View File

@@ -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} />;
}
};
}

+ 1
- 1
server/sonar-web/src/main/js/components/ui/Alert.tsx View File

@@ -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

+ 1
- 0
server/sonar-web/src/main/js/helpers/testMocks.ts View File

@@ -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: {},

+ 2
- 2
sonar-application/build.gradle View File

@@ -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")

Loading…
Cancel
Save