* Fix ts and eslint issues * Drop forSingleOrganization * Update Typscript on extensionstags/7.8
@@ -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": { |
@@ -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" |
@@ -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" |
@@ -19,6 +19,7 @@ | |||
*/ | |||
declare module 'react-countup' { | |||
interface Props { | |||
children: (data: { countUpRef?: React.RefObject<any> }) => JSX.Element; | |||
decimal?: string; | |||
decimals?: number; | |||
delay?: number; |
@@ -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} /> |
@@ -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) { |
@@ -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); |
@@ -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); |
@@ -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> | |||
</> | |||
); | |||
} | |||
); | |||
} |
@@ -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} />); | |||
} |
@@ -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> | |||
`; |
@@ -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" |
@@ -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)); | |||
} |
@@ -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', |
@@ -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); |
@@ -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)); |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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> | |||
`; |
@@ -21,7 +21,7 @@ import { lazyLoad } from '../../components/lazyLoad'; | |||
const routes = [ | |||
{ | |||
indexRoute: { component: lazyLoad(() => import('./components/AppContainer')) } | |||
indexRoute: { component: lazyLoad(() => import('./components/App')) } | |||
} | |||
]; | |||
@@ -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); |
@@ -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> |
@@ -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); |
@@ -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() | |||
}); |
@@ -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); |
@@ -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); |
@@ -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, |
@@ -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); |
@@ -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 | |||
) { |
@@ -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} |
@@ -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); | |||
} |
@@ -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); |
@@ -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} />; | |||
} | |||
}; | |||
} |
@@ -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 |
@@ -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: {}, |
@@ -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") |