aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2018-12-06 15:53:55 +0100
committerSonarTech <sonartech@sonarsource.com>2018-12-11 20:20:58 +0100
commitce80f2115245688992a727beeecd46ac43dca703 (patch)
tree93d7716eb310d5baad9c5b58bd84073774b3f4a2
parent0a3fef5c6baba580a558b996bd23b435dfc9e4aa (diff)
downloadsonarqube-ce80f2115245688992a727beeecd46ac43dca703.tar.gz
sonarqube-ce80f2115245688992a727beeecd46ac43dca703.zip
remove some usages of legacy react context
-rw-r--r--server/sonar-web/src/main/js/app/components/AdminContainer.tsx10
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContainer.tsx21
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalContainer.tsx26
-rw-r--r--server/sonar-web/src/main/js/app/components/Landing.tsx16
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx47
-rw-r--r--server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx13
-rw-r--r--server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx10
-rw-r--r--server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx33
-rw-r--r--server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts11
-rw-r--r--server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx54
-rw-r--r--server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx23
-rw-r--r--server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx50
-rw-r--r--server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap160
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/Extension.tsx57
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.tsx8
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx8
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx4
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx4
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.tsx4
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx21
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx18
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx14
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx13
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx30
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavLicenseNotif-test.tsx22
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx46
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBgTaskNotif-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx6
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx13
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx7
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx24
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNav-test.tsx.snap26
-rw-r--r--server/sonar-web/src/main/js/app/types.d.ts6
-rw-r--r--server/sonar-web/src/main/js/apps/account/notifications/Notifications.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/account/notifications/__tests__/Notifications-test.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/App.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/Search.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/App.tsx27
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/App.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx19
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationDelete-test.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/App.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx16
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx23
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx24
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/DetailsApp.tsx19
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx27
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx15
-rwxr-xr-xserver/sonar-web/src/main/js/apps/securityReports/components/App.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx65
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/App.tsx23
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingPage.tsx20
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectOnboarding-test.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/users/UsersApp.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx28
-rw-r--r--server/sonar-web/src/main/js/components/docs/DocLink.tsx14
-rw-r--r--server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx72
-rw-r--r--server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap10
-rw-r--r--server/sonar-web/src/main/js/components/hoc/withRouter.tsx (renamed from server/sonar-web/src/main/js/app/components/extensions/ExtensionContainer.tsx)25
-rw-r--r--server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.tsx13
83 files changed, 787 insertions, 873 deletions
diff --git a/server/sonar-web/src/main/js/app/components/AdminContainer.tsx b/server/sonar-web/src/main/js/app/components/AdminContainer.tsx
index 6f65e1d4c26..8890e985cfc 100644
--- a/server/sonar-web/src/main/js/app/components/AdminContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/AdminContainer.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import MarketplaceContext, { defaultPendingPlugins } from './MarketplaceContext';
@@ -31,7 +30,7 @@ import { PluginPendingResult, getPendingPlugins } from '../../api/plugins';
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
interface StateProps {
- appState: Pick<T.AppState, 'adminPages' | 'organizationsEnabled'>;
+ appState: Pick<T.AppState, 'adminPages' | 'canAdmin' | 'organizationsEnabled'>;
}
interface DispatchToProps {
@@ -50,18 +49,13 @@ interface State {
class AdminContainer extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- canAdmin: PropTypes.bool.isRequired
- };
-
state: State = {
pendingPlugins: defaultPendingPlugins
};
componentDidMount() {
this.mounted = true;
- if (!this.context.canAdmin) {
+ if (!this.props.appState.canAdmin) {
handleRequiredAuthorization();
} else {
this.fetchNavigationSettings();
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
index 0bc2bfa614e..a031c478169 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { differenceBy } from 'lodash';
import { ComponentContext } from './ComponentContext';
@@ -40,8 +39,10 @@ import {
isShortLivingBranch,
getBranchLikeQuery
} from '../../helpers/branches';
+import { Store, getAppState } from '../../store/rootReducer';
interface Props {
+ appState: Pick<T.AppState, 'organizationsEnabled'>;
children: any;
fetchOrganizations: (organizations: string[]) => void;
location: {
@@ -66,15 +67,7 @@ const FETCH_STATUS_WAIT_TIME = 3000;
export class ComponentContainer extends React.PureComponent<Props, State> {
watchStatusTimer?: number;
mounted = false;
-
- static contextTypes = {
- organizationsEnabled: PropTypes.bool
- };
-
- constructor(props: Props) {
- super(props);
- this.state = { branchLikes: [], isPending: false, loading: true, warnings: [] };
- }
+ state: State = { branchLikes: [], isPending: false, loading: true, warnings: [] };
componentDidMount() {
this.mounted = true;
@@ -122,7 +115,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
.then(([nav, data]) => {
const component = this.addQualifier({ ...nav, ...data });
- if (this.context.organizationsEnabled) {
+ if (this.props.appState.organizationsEnabled) {
this.props.fetchOrganizations([component.organization]);
}
return component;
@@ -382,9 +375,13 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
}
}
+const mapStateToProps = (state: Store) => ({
+ appState: getAppState(state)
+});
+
const mapDispatchToProps = { fetchOrganizations };
export default connect(
- null,
+ mapStateToProps,
mapDispatchToProps
)(ComponentContainer);
diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
index 32ee2c13079..f5187c585be 100644
--- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
@@ -36,22 +36,20 @@ export default function GlobalContainer(props: Props) {
const { footer = <GlobalFooterContainer /> } = props;
return (
<SuggestionsProvider>
- {({ suggestions }) => (
- <StartupModal>
- <div className="global-container">
- <div className="page-wrapper" id="container">
- <div className="page-container">
- <Workspace>
- <GlobalNav location={props.location} suggestions={suggestions} />
- <GlobalMessagesContainer />
- {props.children}
- </Workspace>
- </div>
+ <StartupModal>
+ <div className="global-container">
+ <div className="page-wrapper" id="container">
+ <div className="page-container">
+ <Workspace>
+ <GlobalNav location={props.location} />
+ <GlobalMessagesContainer />
+ {props.children}
+ </Workspace>
</div>
- {footer}
</div>
- </StartupModal>
- )}
+ {footer}
+ </div>
+ </StartupModal>
</SuggestionsProvider>
);
}
diff --git a/server/sonar-web/src/main/js/app/components/Landing.tsx b/server/sonar-web/src/main/js/app/components/Landing.tsx
index 310d2f27629..c5634b10a71 100644
--- a/server/sonar-web/src/main/js/app/components/Landing.tsx
+++ b/server/sonar-web/src/main/js/app/components/Landing.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
+import { withRouter, WithRouterProps } from 'react-router';
import { connect } from 'react-redux';
import { Location } from 'history';
import { getCurrentUser, Store } from '../../store/rootReducer';
@@ -33,22 +33,18 @@ interface OwnProps {
location: Location;
}
-class Landing extends React.PureComponent<StateProps & OwnProps> {
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
+class Landing extends React.PureComponent<StateProps & OwnProps & WithRouterProps> {
componentDidMount() {
const { currentUser } = this.props;
if (currentUser && isLoggedIn(currentUser)) {
if (currentUser.homepage) {
const homepage = getHomePageUrl(currentUser.homepage);
- this.context.router.replace(homepage);
+ this.props.router.replace(homepage);
} else {
- this.context.router.replace('/projects');
+ this.props.router.replace('/projects');
}
} else {
- this.context.router.replace('/about');
+ this.props.router.replace('/about');
}
}
@@ -61,4 +57,4 @@ const mapStateToProps = (state: Store) => ({
currentUser: getCurrentUser(state)
});
-export default connect(mapStateToProps)(Landing);
+export default withRouter(connect(mapStateToProps)(Landing));
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
index b661f086095..34d513e1b17 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
@@ -76,7 +76,10 @@ beforeEach(() => {
it('changes component', () => {
const wrapper = shallow<ComponentContainer>(
- <ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'foo' } }}>
+ <ComponentContainer
+ appState={{ organizationsEnabled: false }}
+ fetchOrganizations={jest.fn()}
+ location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
);
@@ -100,7 +103,10 @@ it("loads branches for module's project", async () => {
});
mount(
- <ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'moduleKey' } }}>
+ <ComponentContainer
+ appState={{ organizationsEnabled: false }}
+ fetchOrganizations={jest.fn()}
+ location={{ query: { id: 'moduleKey' } }}>
<Inner />
</ComponentContainer>
);
@@ -114,7 +120,10 @@ it("loads branches for module's project", async () => {
it("doesn't load branches portfolio", async () => {
const wrapper = mount(
- <ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'portfolioKey' } }}>
+ <ComponentContainer
+ appState={{ organizationsEnabled: false }}
+ fetchOrganizations={jest.fn()}
+ location={{ query: { id: 'portfolioKey' } }}>
<Inner />
</ComponentContainer>
);
@@ -130,7 +139,10 @@ it("doesn't load branches portfolio", async () => {
it('updates branches on change', () => {
const wrapper = shallow(
- <ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'portfolioKey' } }}>
+ <ComponentContainer
+ appState={{ organizationsEnabled: false }}
+ fetchOrganizations={jest.fn()}
+ location={{ query: { id: 'portfolioKey' } }}>
<Inner />
</ComponentContainer>
);
@@ -156,6 +168,7 @@ it('updates the branch measures', async () => {
(getPullRequests as jest.Mock<any>).mockResolvedValueOnce([]);
const wrapper = shallow(
<ComponentContainer
+ appState={{ organizationsEnabled: false }}
fetchOrganizations={jest.fn()}
location={{ query: { id: 'foo', branch: 'feature' } }}>
<Inner />
@@ -184,10 +197,12 @@ it('loads organization', async () => {
const fetchOrganizations = jest.fn();
mount(
- <ComponentContainer fetchOrganizations={fetchOrganizations} location={{ query: { id: 'foo' } }}>
+ <ComponentContainer
+ appState={{ organizationsEnabled: true }}
+ fetchOrganizations={fetchOrganizations}
+ location={{ query: { id: 'foo' } }}>
<Inner />
- </ComponentContainer>,
- { context: { organizationsEnabled: true } }
+ </ComponentContainer>
);
await new Promise(setImmediate);
@@ -198,10 +213,12 @@ it('fetches status', async () => {
(getComponentData as jest.Mock<any>).mockResolvedValueOnce({ organization: 'org' });
mount(
- <ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'foo' } }}>
+ <ComponentContainer
+ appState={{ organizationsEnabled: true }}
+ fetchOrganizations={jest.fn()}
+ location={{ query: { id: 'foo' } }}>
<Inner />
- </ComponentContainer>,
- { context: { organizationsEnabled: true } }
+ </ComponentContainer>
);
await new Promise(setImmediate);
@@ -210,7 +227,10 @@ it('fetches status', async () => {
it('filters correctly the pending tasks for a main branch', () => {
const wrapper = shallow(
- <ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'foo' } }}>
+ <ComponentContainer
+ appState={{ organizationsEnabled: false }}
+ fetchOrganizations={jest.fn()}
+ location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
);
@@ -275,7 +295,10 @@ it('reload component after task progress finished', async () => {
const inProgressTask = { id: 'foo', status: STATUSES.IN_PROGRESS } as T.Task;
(getTasksForComponent as jest.Mock<any>).mockResolvedValueOnce({ queue: [inProgressTask] });
const wrapper = shallow(
- <ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'foo' } }}>
+ <ComponentContainer
+ appState={{ organizationsEnabled: false }}
+ fetchOrganizations={jest.fn()}
+ location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
);
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx
index 5cc79d2d179..b191d8879d9 100644
--- a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx
+++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { Link } from 'react-router';
import ProductNewsMenuItem from './ProductNewsMenuItem';
-import { SuggestionLink } from './SuggestionsProvider';
+import { SuggestionsContext } from './SuggestionsContext';
import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/urls';
import { isSonarCloud } from '../../../helpers/system';
@@ -28,7 +28,6 @@ import { DropdownOverlay } from '../../../components/controls/Dropdown';
interface Props {
onClose: () => void;
- suggestions: Array<SuggestionLink>;
}
export default class EmbedDocsPopup extends React.PureComponent<Props> {
@@ -36,14 +35,14 @@ export default class EmbedDocsPopup extends React.PureComponent<Props> {
return <li className="menu-header">{text}</li>;
}
- renderSuggestions() {
- if (this.props.suggestions.length === 0) {
+ renderSuggestions = ({ suggestions }: { suggestions: T.SuggestionLink[] }) => {
+ if (suggestions.length === 0) {
return null;
}
return (
<>
{this.renderTitle(translate('embed_docs.suggestion'))}
- {this.props.suggestions.map((suggestion, index) => (
+ {suggestions.map((suggestion, index) => (
<li key={index}>
<Link onClick={this.props.onClose} target="_blank" to={suggestion.link}>
{suggestion.text}
@@ -53,7 +52,7 @@ export default class EmbedDocsPopup extends React.PureComponent<Props> {
<li className="divider" />
</>
);
- }
+ };
renderIconLink(link: string, icon: string, text: string) {
return (
@@ -138,7 +137,7 @@ export default class EmbedDocsPopup extends React.PureComponent<Props> {
return (
<DropdownOverlay>
<ul className="menu abs-width-240">
- {this.renderSuggestions()}
+ <SuggestionsContext.Consumer>{this.renderSuggestions}</SuggestionsContext.Consumer>
<li>
<Link onClick={this.props.onClose} target="_blank" to="/documentation">
{translate('embed_docs.documentation')}
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx
index 8e2486b5025..d02c95f18d7 100644
--- a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx
+++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { SuggestionLink } from './SuggestionsProvider';
import Toggler from '../../../components/controls/Toggler';
import HelpIcon from '../../../components/icons-components/HelpIcon';
import { lazyLoad } from '../../../components/lazyLoad';
@@ -26,14 +25,11 @@ import { translate } from '../../../helpers/l10n';
const EmbedDocsPopup = lazyLoad(() => import('./EmbedDocsPopup'));
-interface Props {
- suggestions: Array<SuggestionLink>;
-}
interface State {
helpOpen: boolean;
}
-export default class EmbedDocsPopupHelper extends React.PureComponent<Props, State> {
+export default class EmbedDocsPopupHelper extends React.PureComponent<{}, State> {
mounted = false;
state: State = { helpOpen: false };
@@ -81,9 +77,7 @@ export default class EmbedDocsPopupHelper extends React.PureComponent<Props, Sta
<Toggler
onRequestClose={this.closeHelp}
open={this.state.helpOpen}
- overlay={
- <EmbedDocsPopup onClose={this.closeHelp} suggestions={this.props.suggestions} />
- }>
+ overlay={<EmbedDocsPopup onClose={this.closeHelp} />}>
<a className="navbar-help" href="#" onClick={this.handleClick} title={translate('help')}>
<HelpIcon />
</a>
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx
index 7ffd50b40cb..450bc63547c 100644
--- a/server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx
+++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx
@@ -18,33 +18,46 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import { SuggestionsContext } from './SuggestionsContext';
interface Props {
suggestions: string;
}
-export default class Suggestions extends React.PureComponent<Props> {
- context!: { suggestions: SuggestionsContext };
+export default function Suggestions({ suggestions }: Props) {
+ return (
+ <SuggestionsContext.Consumer>
+ {({ addSuggestions, removeSuggestions }) => (
+ <SuggestionsInner
+ addSuggestions={addSuggestions}
+ removeSuggestions={removeSuggestions}
+ suggestions={suggestions}
+ />
+ )}
+ </SuggestionsContext.Consumer>
+ );
+}
- static contextTypes = {
- suggestions: PropTypes.object.isRequired
- };
+interface SuggestionsInnerProps {
+ addSuggestions: (key: string) => void;
+ removeSuggestions: (key: string) => void;
+ suggestions: string;
+}
+class SuggestionsInner extends React.PureComponent<SuggestionsInnerProps> {
componentDidMount() {
- this.context.suggestions.addSuggestions(this.props.suggestions);
+ this.props.addSuggestions(this.props.suggestions);
}
componentDidUpdate(prevProps: Props) {
if (prevProps.suggestions !== this.props.suggestions) {
- this.context.suggestions.removeSuggestions(this.props.suggestions);
- this.context.suggestions.addSuggestions(prevProps.suggestions);
+ this.props.removeSuggestions(this.props.suggestions);
+ this.props.addSuggestions(prevProps.suggestions);
}
}
componentWillUnmount() {
- this.context.suggestions.removeSuggestions(this.props.suggestions);
+ this.props.removeSuggestions(this.props.suggestions);
}
render() {
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts b/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts
index 8292383c5db..c97e2e8a476 100644
--- a/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts
+++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts
@@ -17,7 +17,16 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-export interface SuggestionsContext {
+import { createContext } from 'react';
+
+interface SuggestionsContextShape {
addSuggestions: (key: string) => void;
removeSuggestions: (key: string) => void;
+ suggestions: T.SuggestionLink[];
}
+
+export const SuggestionsContext = createContext<SuggestionsContextShape>({
+ addSuggestions: () => {},
+ removeSuggestions: () => {},
+ suggestions: []
+});
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx
index 909c6a5d1d4..1095071b8c3 100644
--- a/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx
+++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx
@@ -18,56 +18,33 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
-// eslint-disable-next-line import/no-extraneous-dependencies
-import * as suggestionsJson from 'Docs/EmbedDocsSuggestions.json';
+import suggestionsJson from 'Docs/EmbedDocsSuggestions.json';
import { SuggestionsContext } from './SuggestionsContext';
import { isSonarCloud } from '../../../helpers/system';
-export interface SuggestionLink {
- link: string;
- scope?: 'sonarcloud';
- text: string;
-}
-
interface SuggestionsJson {
- [key: string]: SuggestionLink[];
-}
-
-interface Props {
- children: ({ suggestions }: { suggestions: SuggestionLink[] }) => React.ReactNode;
+ [key: string]: T.SuggestionLink[];
}
interface State {
- suggestions: SuggestionLink[];
+ suggestions: T.SuggestionLink[];
}
-export default class SuggestionsProvider extends React.Component<Props, State> {
+export default class SuggestionsProvider extends React.Component<{}, State> {
keys: string[] = [];
-
- static childContextTypes = {
- suggestions: PropTypes.object
- };
-
state: State = { suggestions: [] };
- getChildContext = (): { suggestions: SuggestionsContext } => {
- return {
- suggestions: {
- addSuggestions: this.addSuggestions,
- removeSuggestions: this.removeSuggestions
- }
- };
- };
-
fetchSuggestions = () => {
const jsonList = suggestionsJson as SuggestionsJson;
- let suggestions: SuggestionLink[] = [];
+ let suggestions: T.SuggestionLink[] = [];
this.keys.forEach(key => {
if (jsonList[key]) {
suggestions = [...jsonList[key], ...suggestions];
}
});
+ if (!isSonarCloud()) {
+ suggestions = suggestions.filter(suggestion => suggestion.scope !== 'sonarcloud');
+ }
this.setState({ suggestions });
};
@@ -82,10 +59,15 @@ export default class SuggestionsProvider extends React.Component<Props, State> {
};
render() {
- const suggestions = isSonarCloud()
- ? this.state.suggestions
- : this.state.suggestions.filter(suggestion => suggestion.scope !== 'sonarcloud');
-
- return this.props.children({ suggestions });
+ return (
+ <SuggestionsContext.Provider
+ value={{
+ addSuggestions: this.addSuggestions,
+ removeSuggestions: this.removeSuggestions,
+ suggestions: this.state.suggestions
+ }}>
+ {this.props.children}
+ </SuggestionsContext.Provider>
+ );
}
}
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx
index e21c57fb020..0520bde7738 100644
--- a/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx
@@ -20,27 +20,8 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import EmbedDocsPopup from '../EmbedDocsPopup';
-import { isSonarCloud } from '../../../../helpers/system';
-jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn().mockReturnValue(false) }));
-
-const suggestions = [{ link: '#', text: 'foo' }, { link: '#', text: 'bar' }];
-
-it('should display suggestion links', () => {
- const context = {};
- const wrapper = shallow(<EmbedDocsPopup onClose={jest.fn()} suggestions={suggestions} />, {
- context
- });
- wrapper.update();
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should display correct links for SonarCloud', () => {
- (isSonarCloud as jest.Mock<any>).mockReturnValueOnce(true);
- const context = {};
- const wrapper = shallow(<EmbedDocsPopup onClose={jest.fn()} suggestions={suggestions} />, {
- context
- });
- wrapper.update();
+it('should render', () => {
+ const wrapper = shallow(<EmbedDocsPopup onClose={jest.fn()} />);
expect(wrapper).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx
index 2092c80a14d..eee016635fd 100644
--- a/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx
@@ -25,8 +25,10 @@ import { isSonarCloud } from '../../../../helpers/system';
jest.mock(
'Docs/EmbedDocsSuggestions.json',
() => ({
- pageA: [{ link: '/foo', text: 'Foo' }, { link: '/bar', text: 'Bar', scope: 'sonarcloud' }],
- pageB: [{ link: '/qux', text: 'Qux' }]
+ default: {
+ pageA: [{ link: '/foo', text: 'Foo' }, { link: '/bar', text: 'Bar', scope: 'sonarcloud' }],
+ pageB: [{ link: '/qux', text: 'Qux' }]
+ }
}),
{ virtual: true }
);
@@ -34,33 +36,41 @@ jest.mock(
jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
it('should add & remove suggestions', () => {
- (isSonarCloud as jest.Mock).mockImplementation(() => false);
- const children = jest.fn();
- const wrapper = shallow(<SuggestionsProvider>{children}</SuggestionsProvider>);
- const instance = wrapper.instance() as SuggestionsProvider;
- expect(children).lastCalledWith({ suggestions: [] });
+ (isSonarCloud as jest.Mock).mockReturnValue(false);
+ const wrapper = shallow<SuggestionsProvider>(
+ <SuggestionsProvider>
+ <div />
+ </SuggestionsProvider>
+ );
+ const instance = wrapper.instance();
+ expect(wrapper.state('suggestions')).toEqual([]);
instance.addSuggestions('pageA');
- expect(children).lastCalledWith({ suggestions: [{ link: '/foo', text: 'Foo' }] });
+ expect(wrapper.state('suggestions')).toEqual([{ link: '/foo', text: 'Foo' }]);
instance.addSuggestions('pageB');
- expect(children).lastCalledWith({
- suggestions: [{ link: '/qux', text: 'Qux' }, { link: '/foo', text: 'Foo' }]
- });
+ expect(wrapper.state('suggestions')).toEqual([
+ { link: '/qux', text: 'Qux' },
+ { link: '/foo', text: 'Foo' }
+ ]);
instance.removeSuggestions('pageA');
- expect(children).lastCalledWith({ suggestions: [{ link: '/qux', text: 'Qux' }] });
+ expect(wrapper.state('suggestions')).toEqual([{ link: '/qux', text: 'Qux' }]);
});
it('should show sonarcloud pages', () => {
- (isSonarCloud as jest.Mock).mockImplementation(() => true);
- const children = jest.fn();
- const wrapper = shallow(<SuggestionsProvider>{children}</SuggestionsProvider>);
- const instance = wrapper.instance() as SuggestionsProvider;
- expect(children).lastCalledWith({ suggestions: [] });
+ (isSonarCloud as jest.Mock).mockReturnValue(true);
+ const wrapper = shallow<SuggestionsProvider>(
+ <SuggestionsProvider>
+ <div />
+ </SuggestionsProvider>
+ );
+ const instance = wrapper.instance();
+ expect(wrapper.state('suggestions')).toEqual([]);
instance.addSuggestions('pageA');
- expect(children).lastCalledWith({
- suggestions: [{ link: '/foo', text: 'Foo' }, { link: '/bar', text: 'Bar', scope: 'sonarcloud' }]
- });
+ expect(wrapper.state('suggestions')).toEqual([
+ { link: '/foo', text: 'Foo' },
+ { link: '/bar', text: 'Bar', scope: 'sonarcloud' }
+ ]);
});
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap
index 434b4be4f7b..d02e45c3e85 100644
--- a/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap
@@ -1,165 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should display correct links for SonarCloud 1`] = `
+exports[`should render 1`] = `
<DropdownOverlay>
<ul
className="menu abs-width-240"
>
- <li
- className="menu-header"
- >
- embed_docs.suggestion
- </li>
- <li
- key="0"
- >
- <Link
- onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
- target="_blank"
- to="#"
- >
- foo
- </Link>
- </li>
- <li
- key="1"
- >
- <Link
- onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
- target="_blank"
- to="#"
- >
- bar
- </Link>
- </li>
- <li
- className="divider"
- />
- <li>
- <Link
- onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
- target="_blank"
- to="/documentation"
- >
- embed_docs.documentation
- </Link>
- </li>
- <li>
- <Link
- onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/web_api"
- >
- api_documentation.page
- </Link>
- </li>
- <li
- className="divider"
- />
- <li>
- <a
- href="https://community.sonarsource.com/c/help/sc"
- rel="noopener noreferrer"
- target="_blank"
- >
- embed_docs.get_help
- </a>
- </li>
- <li
- className="divider"
- />
- <li
- className="menu-header"
- >
- embed_docs.stay_connected
- </li>
- <li>
- <a
- href="https://twitter.com/sonarcloud"
- rel="noopener noreferrer"
- target="_blank"
- >
- <img
- alt="Twitter"
- className="spacer-right"
- height="18"
- src="/images/embed-doc/twitter-icon.svg"
- width="18"
- />
- Twitter
- </a>
- </li>
- <li>
- <a
- href="https://blog.sonarsource.com/product/SonarCloud"
- rel="noopener noreferrer"
- target="_blank"
- >
- <img
- alt="embed_docs.news"
- className="spacer-right"
- height="18"
- src="/images/sonarcloud-square-logo.svg"
- width="18"
- />
- embed_docs.news
- </a>
- </li>
- <li>
- <Connect(ProductNewsMenuItem)
- tag="SonarCloud"
- />
- </li>
- </ul>
-</DropdownOverlay>
-`;
-
-exports[`should display suggestion links 1`] = `
-<DropdownOverlay>
- <ul
- className="menu abs-width-240"
- >
- <li
- className="menu-header"
- >
- embed_docs.suggestion
- </li>
- <li
- key="0"
- >
- <Link
- onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
- target="_blank"
- to="#"
- >
- foo
- </Link>
- </li>
- <li
- key="1"
- >
- <Link
- onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
- target="_blank"
- to="#"
- >
- bar
- </Link>
- </li>
- <li
- className="divider"
- />
+ <ContextConsumer>
+ <Component />
+ </ContextConsumer>
<li>
<Link
onClick={[MockFunction]}
diff --git a/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx b/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx
index b3e6c9343ec..c02b5962601 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx
@@ -19,29 +19,38 @@
*/
import * as React from 'react';
import Helmet from 'react-helmet';
-import * as PropTypes from 'prop-types';
import { withRouter, WithRouterProps } from 'react-router';
import { injectIntl, InjectedIntlProps } from 'react-intl';
+import { connect } from 'react-redux';
import { getExtensionStart } from './utils';
import { translate } from '../../../helpers/l10n';
import getStore from '../../utils/getStore';
+import { addGlobalErrorMessage } from '../../../store/globalMessages';
+import { Store, getCurrentUser } from '../../../store/rootReducer';
interface OwnProps {
- currentUser: T.CurrentUser;
extension: { key: string; name: string };
- onFail: (message: string) => void;
options?: {};
}
-type Props = OwnProps & WithRouterProps & InjectedIntlProps;
+interface StateProps {
+ currentUser: T.CurrentUser;
+}
-class Extension extends React.PureComponent<Props> {
+interface DispatchProps {
+ onFail: (message: string) => void;
+}
+
+type Props = OwnProps & WithRouterProps & InjectedIntlProps & StateProps & DispatchProps;
+
+interface State {
+ extensionElement?: React.ReactElement<any>;
+}
+
+class Extension extends React.PureComponent<Props, State> {
container?: HTMLElement | null;
stop?: Function;
-
- static contextTypes = {
- suggestions: PropTypes.object.isRequired
- };
+ state: State = {};
componentDidMount() {
this.startExtension();
@@ -62,16 +71,21 @@ class Extension extends React.PureComponent<Props> {
handleStart = (start: Function) => {
const store = getStore();
- this.stop = start({
+ const result = start({
store,
el: this.container,
currentUser: this.props.currentUser,
intl: this.props.intl,
location: this.props.location,
router: this.props.router,
- suggestions: this.context.suggestions,
...this.props.options
});
+
+ if (React.isValidElement(result)) {
+ this.setState({ extensionElement: result });
+ } else {
+ this.stop = result;
+ }
};
handleFailure = () => {
@@ -94,10 +108,27 @@ class Extension extends React.PureComponent<Props> {
return (
<div>
<Helmet title={this.props.extension.name} />
- <div ref={container => (this.container = container)} />
+ {this.state.extensionElement ? (
+ this.state.extensionElement
+ ) : (
+ <div ref={container => (this.container = container)} />
+ )}
</div>
);
}
}
-export default injectIntl(withRouter(Extension));
+function mapStateToProps(state: Store): StateProps {
+ return { currentUser: getCurrentUser(state) };
+}
+
+const mapDispatchToProps: DispatchProps = { onFail: addGlobalErrorMessage };
+
+export default injectIntl<OwnProps & InjectedIntlProps>(
+ withRouter(
+ connect(
+ mapStateToProps,
+ mapDispatchToProps
+ )(Extension)
+ )
+);
diff --git a/server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.tsx b/server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.tsx
index 8db930dd62b..17119b71b6c 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { connect } from 'react-redux';
-import ExtensionContainer from './ExtensionContainer';
+import Extension from './Extension';
import NotFound from '../NotFound';
import { getAppState, Store } from '../../../store/rootReducer';
@@ -31,11 +31,7 @@ interface Props {
function GlobalAdminPageExtension(props: Props) {
const { extensionKey, pluginKey } = props.params;
const extension = (props.adminPages || []).find(p => p.key === `${pluginKey}/${extensionKey}`);
- return extension ? (
- <ExtensionContainer extension={extension} />
- ) : (
- <NotFound withContainer={false} />
- );
+ return extension ? <Extension extension={extension} /> : <NotFound withContainer={false} />;
}
const mapStateToProps = (state: Store) => ({
diff --git a/server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx b/server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx
index b37ac541bea..e1b967b6c96 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { connect } from 'react-redux';
-import ExtensionContainer from './ExtensionContainer';
+import Extension from './Extension';
import NotFound from '../NotFound';
import { getAppState, Store } from '../../../store/rootReducer';
@@ -31,11 +31,7 @@ interface Props {
function GlobalPageExtension(props: Props) {
const { extensionKey, pluginKey } = props.params;
const extension = (props.globalPages || []).find(p => p.key === `${pluginKey}/${extensionKey}`);
- return extension ? (
- <ExtensionContainer extension={extension} />
- ) : (
- <NotFound withContainer={false} />
- );
+ return extension ? <Extension extension={extension} /> : <NotFound withContainer={false} />;
}
const mapStateToProps = (state: Store) => ({
diff --git a/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx b/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx
index 16ae71708ac..a372d0f1f48 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { connect } from 'react-redux';
-import ExtensionContainer from './ExtensionContainer';
+import Extension from './Extension';
import NotFound from '../NotFound';
import { getOrganizationByKey, Store } from '../../../store/rootReducer';
import { fetchOrganization } from '../../../apps/organizations/actions';
@@ -63,7 +63,7 @@ class OrganizationPageExtension extends React.PureComponent<Props> {
const extension = pages.find(p => p.key === `${pluginKey}/${extensionKey}`);
return extension ? (
- <ExtensionContainer
+ <Extension
extension={extension}
options={{ organization, refreshOrganization: this.refreshOrganization }}
/>
diff --git a/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx b/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx
index 9502e405b63..7f2e85d30fe 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { connect } from 'react-redux';
import { Location } from 'history';
-import ExtensionContainer from './ExtensionContainer';
+import Extension from './Extension';
import NotFound from '../NotFound';
import { addGlobalErrorMessage } from '../../../store/globalMessages';
@@ -37,7 +37,7 @@ function ProjectAdminPageExtension(props: Props) {
component.configuration &&
(component.configuration.extensions || []).find(p => p.key === `${pluginKey}/${extensionKey}`);
return extension ? (
- <ExtensionContainer extension={extension} options={{ component }} />
+ <Extension extension={extension} options={{ component }} />
) : (
<NotFound withContainer={false} />
);
diff --git a/server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.tsx b/server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.tsx
index ada4b4f3e69..c98752e7a32 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import ExtensionContainer from './ExtensionContainer';
+import Extension from './Extension';
import NotFound from '../NotFound';
interface Props {
@@ -37,7 +37,7 @@ export default function ProjectPageExtension(props: Props) {
component.extensions &&
component.extensions.find(p => p.key === `${pluginKey}/${extensionKey}`);
return extension ? (
- <ExtensionContainer extension={extension} options={{ component }} />
+ <Extension extension={extension} options={{ component }} />
) : (
<NotFound withContainer={false} />
);
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
index 8fee54db550..ba26c68f3b2 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import ComponentNavBranchesMenu from './ComponentNavBranchesMenu';
@@ -38,8 +37,10 @@ import Toggler from '../../../../components/controls/Toggler';
import DropdownIcon from '../../../../components/icons-components/DropdownIcon';
import { isSonarCloud } from '../../../../helpers/system';
import { getPortfolioAdminUrl } from '../../../../helpers/urls';
+import { withAppState } from '../../../../components/withAppState';
interface Props {
+ appState: Pick<T.AppState, 'branchesEnabled'>;
branchLikes: T.BranchLike[];
component: T.Component;
currentBranchLike: T.BranchLike;
@@ -50,17 +51,9 @@ interface State {
dropdownOpen: boolean;
}
-export default class ComponentNavBranch extends React.PureComponent<Props, State> {
+export class ComponentNavBranch extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- branchesEnabled: PropTypes.bool.isRequired,
- canAdmin: PropTypes.bool.isRequired
- };
-
- state: State = {
- dropdownOpen: false
- };
+ state: State = { dropdownOpen: false };
componentDidMount() {
this.mounted = true;
@@ -145,7 +138,7 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State
const { branchLikes, currentBranchLike } = this.props;
const { configuration, breadcrumbs } = this.props.component;
- if (isSonarCloud() && !this.context.branchesEnabled) {
+ if (isSonarCloud() && !this.props.appState.branchesEnabled) {
return null;
}
@@ -170,7 +163,7 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State
</div>
);
} else {
- if (!this.context.branchesEnabled) {
+ if (!this.props.appState.branchesEnabled) {
return (
<div className="navbar-context-branches">
<BranchIcon
@@ -235,3 +228,5 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State
);
}
}
+
+export default withAppState(ComponentNavBranch);
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx
index 5f876d1a6f6..ca027fdfa48 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import { Link } from 'react-router';
import ComponentNavBranchesMenuItem from './ComponentNavBranchesMenuItem';
import {
@@ -36,6 +35,7 @@ import { getBranchLikeUrl } from '../../../../helpers/urls';
import SearchBox from '../../../../components/controls/SearchBox';
import HelpTooltip from '../../../../components/controls/HelpTooltip';
import { DropdownOverlay } from '../../../../components/controls/Dropdown';
+import { withRouter, Router } from '../../../../components/hoc/withRouter';
interface Props {
branchLikes: T.BranchLike[];
@@ -43,6 +43,7 @@ interface Props {
component: T.Component;
currentBranchLike: T.BranchLike;
onClose: () => void;
+ router: Pick<Router, 'push'>;
}
interface State {
@@ -50,14 +51,9 @@ interface State {
selected: T.BranchLike | undefined;
}
-export default class ComponentNavBranchesMenu extends React.PureComponent<Props, State> {
- private listNode?: HTMLUListElement | null;
- private selectedBranchNode?: HTMLLIElement | null;
-
- static contextTypes = {
- router: PropTypes.object
- };
-
+export class ComponentNavBranchesMenu extends React.PureComponent<Props, State> {
+ listNode?: HTMLUListElement | null;
+ selectedBranchNode?: HTMLLIElement | null;
state: State = { query: '', selected: undefined };
componentDidMount() {
@@ -113,7 +109,7 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props,
openSelected = () => {
const selected = this.getSelected();
if (selected) {
- this.context.router.push(this.getProjectBranchUrl(selected));
+ this.props.router.push(this.getProjectBranchUrl(selected));
}
};
@@ -263,3 +259,5 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props,
);
}
}
+
+export default withRouter(ComponentNavBranchesMenu);
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx
index 116cb1489fa..bcc3ac9f057 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx
@@ -19,12 +19,13 @@
*/
import * as React from 'react';
import { Link } from 'react-router';
-import * as PropTypes from 'prop-types';
import NavBarNotif from '../../../../components/nav/NavBarNotif';
import { translate } from '../../../../helpers/l10n';
import { isValidLicense } from '../../../../api/marketplace';
+import { withAppState } from '../../../../components/withAppState';
interface Props {
+ appState: Pick<T.AppState, 'canAdmin'>;
currentTask?: T.Task;
}
@@ -33,13 +34,8 @@ interface State {
loading: boolean;
}
-export default class ComponentNavLicenseNotif extends React.PureComponent<Props, State> {
+export class ComponentNavLicenseNotif extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- canAdmin: PropTypes.bool.isRequired
- };
-
state: State = { loading: false };
componentDidMount() {
@@ -88,7 +84,7 @@ export default class ComponentNavLicenseNotif extends React.PureComponent<Props,
return (
<NavBarNotif variant="error">
<span className="little-spacer-right">{currentTask.errorMessage}</span>
- {this.context.canAdmin ? (
+ {this.props.appState.canAdmin ? (
<Link to="/admin/extension/license/app">
{translate('license.component_navigation.button', currentTask.errorType)}.
</Link>
@@ -99,3 +95,5 @@ export default class ComponentNavLicenseNotif extends React.PureComponent<Props,
);
}
}
+
+export default withAppState(ComponentNavLicenseNotif);
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
index 1499680b2a8..7aeec61ee84 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
@@ -20,7 +20,6 @@
import * as React from 'react';
import { Link } from 'react-router';
import * as classNames from 'classnames';
-import * as PropTypes from 'prop-types';
import Dropdown from '../../../../components/controls/Dropdown';
import NavBarTabs from '../../../../components/nav/NavBarTabs';
import {
@@ -31,6 +30,7 @@ import {
} from '../../../../helpers/branches';
import { translate } from '../../../../helpers/l10n';
import DropdownIcon from '../../../../components/icons-components/DropdownIcon';
+import { withAppState } from '../../../../components/withAppState';
const SETTINGS_URLS = [
'/project/admin',
@@ -49,16 +49,13 @@ const SETTINGS_URLS = [
];
interface Props {
+ appState: Pick<T.AppState, 'branchesEnabled'>;
branchLike: T.BranchLike | undefined;
component: T.Component;
location?: any;
}
-export default class ComponentNavMenu extends React.PureComponent<Props> {
- static contextTypes = {
- branchesEnabled: PropTypes.bool.isRequired
- };
-
+export class ComponentNavMenu extends React.PureComponent<Props> {
isProject() {
return this.props.component.qualifier === 'TRK';
}
@@ -282,7 +279,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
renderBranchesLink() {
if (
- !this.context.branchesEnabled ||
+ !this.props.appState.branchesEnabled ||
!this.isProject() ||
!this.getConfiguration().showSettings
) {
@@ -504,3 +501,5 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
);
}
}
+
+export default withAppState(ComponentNavMenu);
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
index 17c44b1beb9..083d976a86a 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import ComponentNavBranch from '../ComponentNavBranch';
+import { ComponentNavBranch } from '../ComponentNavBranch';
import { click } from '../../../../../helpers/testUtils';
import { isSonarCloud } from '../../../../../helpers/system';
@@ -37,11 +37,11 @@ it('renders main branch', () => {
expect(
shallow(
<ComponentNavBranch
+ appState={{ branchesEnabled: true }}
branchLikes={[mainBranch, fooBranch]}
component={component}
currentBranchLike={mainBranch}
- />,
- { context: { branchesEnabled: true, canAdmin: true } }
+ />
)
).toMatchSnapshot();
});
@@ -58,11 +58,11 @@ it('renders short-living branch', () => {
expect(
shallow(
<ComponentNavBranch
+ appState={{ branchesEnabled: true }}
branchLikes={[branch, fooBranch]}
component={component}
currentBranchLike={branch}
- />,
- { context: { branchesEnabled: true, canAdmin: true } }
+ />
)
).toMatchSnapshot();
});
@@ -79,11 +79,11 @@ it('renders pull request', () => {
expect(
shallow(
<ComponentNavBranch
+ appState={{ branchesEnabled: true }}
branchLikes={[pullRequest, fooBranch]}
component={component}
currentBranchLike={pullRequest}
- />,
- { context: { branchesEnabled: true, canAdmin: true } }
+ />
)
).toMatchSnapshot();
});
@@ -92,11 +92,11 @@ it('opens menu', () => {
const component = {} as T.Component;
const wrapper = shallow(
<ComponentNavBranch
+ appState={{ branchesEnabled: true }}
branchLikes={[mainBranch, fooBranch]}
component={component}
currentBranchLike={mainBranch}
- />,
- { context: { branchesEnabled: true, canAdmin: true } }
+ />
);
expect(wrapper.find('Toggler').prop('open')).toBe(false);
click(wrapper.find('a'));
@@ -107,11 +107,11 @@ it('renders single branch popup', () => {
const component = {} as T.Component;
const wrapper = shallow(
<ComponentNavBranch
+ appState={{ branchesEnabled: true }}
branchLikes={[mainBranch]}
component={component}
currentBranchLike={mainBranch}
- />,
- { context: { branchesEnabled: true, canAdmin: true } }
+ />
);
expect(wrapper.find('DocTooltip')).toMatchSnapshot();
});
@@ -120,11 +120,11 @@ it('renders no branch support popup', () => {
const component = {} as T.Component;
const wrapper = shallow(
<ComponentNavBranch
+ appState={{ branchesEnabled: false }}
branchLikes={[mainBranch, fooBranch]}
component={component}
currentBranchLike={mainBranch}
- />,
- { context: { branchesEnabled: false, canAdmin: true } }
+ />
);
expect(wrapper.find('DocTooltip')).toMatchSnapshot();
});
@@ -134,11 +134,11 @@ it('renders nothing on SonarCloud without branch support', () => {
const component = {} as T.Component;
const wrapper = shallow(
<ComponentNavBranch
+ appState={{ branchesEnabled: false }}
branchLikes={[mainBranch]}
component={component}
currentBranchLike={mainBranch}
- />,
- { context: { branchesEnabled: false, onSonarCloud: true, canAdmin: true } }
+ />
);
expect(wrapper.type()).toBeNull();
});
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx
index 8f35a1c252f..1410ea5f74f 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import ComponentNavBranchesMenu from '../ComponentNavBranchesMenu';
+import { ComponentNavBranchesMenu } from '../ComponentNavBranchesMenu';
import { elementKeydown } from '../../../../../helpers/testUtils';
const component = { key: 'component' } as T.Component;
@@ -38,6 +38,7 @@ it('renders list', () => {
component={component}
currentBranchLike={mainBranch()}
onClose={jest.fn()}
+ router={{ push: jest.fn() }}
/>
)
).toMatchSnapshot();
@@ -56,6 +57,7 @@ it('searches', () => {
component={component}
currentBranchLike={mainBranch()}
onClose={jest.fn()}
+ router={{ push: jest.fn() }}
/>
);
wrapper.setState({ query: 'bar' });
@@ -69,6 +71,7 @@ it('selects next & previous', () => {
component={component}
currentBranchLike={mainBranch()}
onClose={jest.fn()}
+ router={{ push: jest.fn() }}
/>
);
elementKeydown(wrapper.find('SearchBox'), 40);
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavLicenseNotif-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavLicenseNotif-test.tsx
index 8528a2701f7..6c90f7a9c8d 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavLicenseNotif-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavLicenseNotif-test.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import ComponentNavLicenseNotif from '../ComponentNavLicenseNotif';
+import { ComponentNavLicenseNotif } from '../ComponentNavLicenseNotif';
import { isValidLicense } from '../../../../../api/marketplace';
import { waitAndUpdate } from '../../../../../helpers/testUtils';
@@ -39,15 +39,15 @@ beforeEach(() => {
it('renders background task license info correctly', async () => {
let wrapper = getWrapper({
- currentTask: { status: 'FAILED', errorType: 'LICENSING', errorMessage: 'Foo' }
+ currentTask: { status: 'FAILED', errorType: 'LICENSING', errorMessage: 'Foo' } as T.Task
});
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
- wrapper = getWrapper(
- { currentTask: { status: 'FAILED', errorType: 'LICENSING', errorMessage: 'Foo' } },
- { canAdmin: false }
- );
+ wrapper = getWrapper({
+ appState: { canAdmin: false },
+ currentTask: { status: 'FAILED', errorType: 'LICENSING', errorMessage: 'Foo' } as T.Task
+ });
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});
@@ -55,7 +55,7 @@ it('renders background task license info correctly', async () => {
it('renders a different message if the license is valid', async () => {
(isValidLicense as jest.Mock<any>).mockResolvedValueOnce({ isValidLicense: true });
const wrapper = getWrapper({
- currentTask: { status: 'FAILED', errorType: 'LICENSING', errorMessage: 'Foo' }
+ currentTask: { status: 'FAILED', errorType: 'LICENSING', errorMessage: 'Foo' } as T.Task
});
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
@@ -64,18 +64,18 @@ it('renders a different message if the license is valid', async () => {
it('renders correctly for LICENSING_LOC error', async () => {
(isValidLicense as jest.Mock<any>).mockResolvedValueOnce({ isValidLicense: true });
const wrapper = getWrapper({
- currentTask: { status: 'FAILED', errorType: 'LICENSING_LOC', errorMessage: 'Foo' }
+ currentTask: { status: 'FAILED', errorType: 'LICENSING_LOC', errorMessage: 'Foo' } as T.Task
});
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});
-function getWrapper(props = {}, context = {}) {
+function getWrapper(props: Partial<ComponentNavLicenseNotif['props']> = {}) {
return shallow(
<ComponentNavLicenseNotif
+ appState={{ canAdmin: true }}
currentTask={{ errorMessage: 'Foo', errorType: 'LICENSING' } as T.Task}
{...props}
- />,
- { context: { canAdmin: true, ...context } }
+ />
);
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx
index 992673fb9b3..75166e4b509 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import ComponentNavMenu from '../ComponentNavMenu';
+import { ComponentNavMenu } from '../ComponentNavMenu';
const mainBranch: T.MainBranch = { isMain: true, name: 'master' };
@@ -37,9 +37,13 @@ it('should work with extensions', () => {
configuration: { showSettings: true, extensions: [{ key: 'foo', name: 'Foo' }] },
extensions: [{ key: 'component-foo', name: 'ComponentFoo' }]
};
- const wrapper = shallow(<ComponentNavMenu branchLike={mainBranch} component={component} />, {
- context: { branchesEnabled: true }
- });
+ const wrapper = shallow(
+ <ComponentNavMenu
+ appState={{ branchesEnabled: true }}
+ branchLike={mainBranch}
+ component={component}
+ />
+ );
expect(wrapper.find('Dropdown[data-test="extensions"]')).toMatchSnapshot();
expect(wrapper.find('Dropdown[data-test="administration"]')).toMatchSnapshot();
});
@@ -56,9 +60,13 @@ it('should work with multiple extensions', () => {
{ key: 'component-bar', name: 'ComponentBar' }
]
};
- const wrapper = shallow(<ComponentNavMenu branchLike={mainBranch} component={component} />, {
- context: { branchesEnabled: true }
- });
+ const wrapper = shallow(
+ <ComponentNavMenu
+ appState={{ branchesEnabled: true }}
+ branchLike={mainBranch}
+ component={component}
+ />
+ );
expect(wrapper.find('Dropdown[data-test="extensions"]')).toMatchSnapshot();
expect(wrapper.find('Dropdown[data-test="administration"]')).toMatchSnapshot();
});
@@ -76,9 +84,13 @@ it('should work for short-living branches', () => {
extensions: [{ key: 'component-foo', name: 'ComponentFoo' }]
};
expect(
- shallow(<ComponentNavMenu branchLike={branch} component={component} />, {
- context: { branchesEnabled: true }
- })
+ shallow(
+ <ComponentNavMenu
+ appState={{ branchesEnabled: true }}
+ branchLike={branch}
+ component={component}
+ />
+ )
).toMatchSnapshot();
});
@@ -88,14 +100,14 @@ it('should work for long-living branches', () => {
expect(
shallow(
<ComponentNavMenu
+ appState={{ branchesEnabled: true }}
branchLike={branch}
component={{
...baseComponent,
configuration: { showSettings },
extensions: [{ key: 'component-foo', name: 'ComponentFoo' }]
}}
- />,
- { context: { branchesEnabled: true } }
+ />
)
).toMatchSnapshot()
);
@@ -108,9 +120,13 @@ it('should work for all qualifiers', () => {
function checkWithQualifier(qualifier: string) {
const component = { ...baseComponent, configuration: { showSettings: true }, qualifier };
expect(
- shallow(<ComponentNavMenu branchLike={mainBranch} component={component} />, {
- context: { branchesEnabled: true }
- })
+ shallow(
+ <ComponentNavMenu
+ appState={{ branchesEnabled: true }}
+ branchLike={mainBranch}
+ component={component}
+ />
+ )
).toMatchSnapshot();
}
});
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap
index 3e0cf719787..65d8dd19a62 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap
@@ -46,7 +46,7 @@ exports[`renders 1`] = `
warnings={Array []}
/>
</div>
- <ComponentNavMenu
+ <Connect(withAppState(ComponentNavMenu))
component={
Object {
"breadcrumbs": Array [
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBgTaskNotif-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBgTaskNotif-test.tsx.snap
index 1386465802b..1eb0a5d137b 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBgTaskNotif-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBgTaskNotif-test.tsx.snap
@@ -72,7 +72,7 @@ exports[`renders background task in progress info correctly 1`] = `
`;
exports[`renders background task license info correctly 1`] = `
-<ComponentNavLicenseNotif
+<Connect(withAppState(ComponentNavLicenseNotif))
currentTask={
Object {
"errorMessage": "Foo",
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap
index 86a8372f886..6bad17ad6b4 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap
@@ -11,7 +11,7 @@ exports[`renders main branch 1`] = `
onRequestClose={[Function]}
open={false}
overlay={
- <ComponentNavBranchesMenu
+ <withRouter(ComponentNavBranchesMenu)
branchLikes={
Array [
Object {
@@ -88,7 +88,7 @@ exports[`renders pull request 1`] = `
onRequestClose={[Function]}
open={false}
overlay={
- <ComponentNavBranchesMenu
+ <withRouter(ComponentNavBranchesMenu)
branchLikes={
Array [
Object {
@@ -180,7 +180,7 @@ exports[`renders short-living branch 1`] = `
onRequestClose={[Function]}
open={false}
overlay={
- <ComponentNavBranchesMenu
+ <withRouter(ComponentNavBranchesMenu)
branchLikes={
Array [
Object {
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx
index e5b1ff86885..a136ce19518 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx
@@ -30,7 +30,6 @@ import * as theme from '../../../theme';
import NavBar from '../../../../components/nav/NavBar';
import { lazyLoad } from '../../../../components/lazyLoad';
import { getCurrentUser, getAppState, Store } from '../../../../store/rootReducer';
-import { SuggestionLink } from '../../embed-docs-modal/SuggestionsProvider';
import { isSonarCloud } from '../../../../helpers/system';
import { isLoggedIn } from '../../../../helpers/users';
import './GlobalNav.css';
@@ -44,7 +43,6 @@ interface StateProps {
interface OwnProps {
location: { pathname: string };
- suggestions: Array<SuggestionLink>;
}
type Props = StateProps & OwnProps;
@@ -62,7 +60,7 @@ export class GlobalNav extends React.PureComponent<Props> {
<ul className="global-navbar-menu global-navbar-menu-right">
{isSonarCloud() && <GlobalNavExplore location={this.props.location} />}
- <EmbedDocsPopupHelper suggestions={this.props.suggestions} />
+ <EmbedDocsPopupHelper />
<Search appState={appState} currentUser={currentUser} />
{isLoggedIn(currentUser) && (
<GlobalNavPlus
@@ -71,7 +69,7 @@ export class GlobalNav extends React.PureComponent<Props> {
openProjectOnboarding={this.context.openProjectOnboarding}
/>
)}
- <GlobalNavUserContainer {...this.props} />
+ <GlobalNavUserContainer appState={appState} currentUser={currentUser} />
</ul>
</NavBar>
);
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx
index 488a642a771..fe1b048163e 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx
@@ -19,7 +19,6 @@
*/
import * as React from 'react';
import { sortBy } from 'lodash';
-import * as PropTypes from 'prop-types';
import { Link } from 'react-router';
import * as theme from '../../../theme';
import Avatar from '../../../../components/ui/Avatar';
@@ -28,18 +27,16 @@ import { translate } from '../../../../helpers/l10n';
import { getBaseUrl } from '../../../../helpers/urls';
import Dropdown from '../../../../components/controls/Dropdown';
import { isLoggedIn } from '../../../../helpers/users';
+import { withRouter, Router } from '../../../../components/hoc/withRouter';
interface Props {
appState: { organizationsEnabled?: boolean };
currentUser: T.CurrentUser;
organizations: T.Organization[];
+ router: Pick<Router, 'push'>;
}
-export default class GlobalNavUser extends React.PureComponent<Props> {
- static contextTypes = {
- router: PropTypes.object
- };
-
+export class GlobalNavUser extends React.PureComponent<Props> {
handleLogin = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
event.preventDefault();
const shouldReturnToCurrentPage = window.location.pathname !== `${getBaseUrl()}/about`;
@@ -54,7 +51,7 @@ export default class GlobalNavUser extends React.PureComponent<Props> {
handleLogout = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
event.preventDefault();
- this.context.router.push('/sessions/logout');
+ this.props.router.push('/sessions/logout');
};
renderAuthenticated() {
@@ -126,3 +123,5 @@ export default class GlobalNavUser extends React.PureComponent<Props> {
return isLoggedIn(this.props.currentUser) ? this.renderAuthenticated() : this.renderAnonymous();
}
}
+
+export default withRouter(GlobalNavUser);
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx
index 99130935150..1656f723eda 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx
@@ -43,12 +43,7 @@ it('should render for SonarCloud', () => {
function runTest(mockedIsSonarCloud: boolean) {
(isSonarCloud as jest.Mock).mockImplementation(() => mockedIsSonarCloud);
const wrapper = shallow(
- <GlobalNav
- appState={appState}
- currentUser={{ isLoggedIn: false }}
- location={location}
- suggestions={[]}
- />
+ <GlobalNav appState={appState} currentUser={{ isLoggedIn: false }} location={location} />
);
expect(wrapper).toMatchSnapshot();
wrapper.setProps({ currentUser: { isLoggedIn: true } });
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx
index 49e6ef10666..4b2f85fa03c 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import GlobalNavUser from '../GlobalNavUser';
+import { GlobalNavUser } from '../GlobalNavUser';
const currentUser = { avatar: 'abcd1234', isLoggedIn: true, name: 'foo', email: 'foo@bar.baz' };
const organizations: T.Organization[] = [
@@ -32,14 +32,24 @@ const appState = { organizationsEnabled: true };
it('should render the right interface for anonymous user', () => {
const currentUser = { isLoggedIn: false };
const wrapper = shallow(
- <GlobalNavUser appState={appState} currentUser={currentUser} organizations={[]} />
+ <GlobalNavUser
+ appState={appState}
+ currentUser={currentUser}
+ organizations={[]}
+ router={{ push: jest.fn() }}
+ />
);
expect(wrapper).toMatchSnapshot();
});
it('should render the right interface for logged in user', () => {
const wrapper = shallow(
- <GlobalNavUser appState={appState} currentUser={currentUser} organizations={[]} />
+ <GlobalNavUser
+ appState={appState}
+ currentUser={currentUser}
+ organizations={[]}
+ router={{ push: jest.fn() }}
+ />
);
wrapper.setState({ open: true });
expect(wrapper.find('Dropdown')).toMatchSnapshot();
@@ -47,7 +57,12 @@ it('should render the right interface for logged in user', () => {
it('should render user organizations', () => {
const wrapper = shallow(
- <GlobalNavUser appState={appState} currentUser={currentUser} organizations={organizations} />
+ <GlobalNavUser
+ appState={appState}
+ currentUser={currentUser}
+ organizations={organizations}
+ router={{ push: jest.fn() }}
+ />
);
wrapper.setState({ open: true });
expect(wrapper.find('Dropdown')).toMatchSnapshot();
@@ -59,6 +74,7 @@ it('should not render user organizations when they are not activated', () => {
appState={{ organizationsEnabled: false }}
currentUser={currentUser}
organizations={organizations}
+ router={{ push: jest.fn() }}
/>
);
wrapper.setState({ open: true });
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNav-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNav-test.tsx.snap
index 62c2757d57b..2f92e6871af 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNav-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNav-test.tsx.snap
@@ -26,7 +26,6 @@ exports[`should render for SonarCloud 1`] = `
"pathname": "",
}
}
- suggestions={Array []}
/>
<ul
className="global-navbar-menu global-navbar-menu-right"
@@ -38,9 +37,7 @@ exports[`should render for SonarCloud 1`] = `
}
}
/>
- <EmbedDocsPopupHelper
- suggestions={Array []}
- />
+ <EmbedDocsPopupHelper />
<withRouter(Search)
appState={
Object {
@@ -56,7 +53,7 @@ exports[`should render for SonarCloud 1`] = `
}
}
/>
- <Connect(GlobalNavUser)
+ <Connect(withRouter(GlobalNavUser))
appState={
Object {
"canAdmin": false,
@@ -70,12 +67,6 @@ exports[`should render for SonarCloud 1`] = `
"isLoggedIn": false,
}
}
- location={
- Object {
- "pathname": "",
- }
- }
- suggestions={Array []}
/>
</ul>
</NavBar>
@@ -107,14 +98,11 @@ exports[`should render for SonarQube 1`] = `
"pathname": "",
}
}
- suggestions={Array []}
/>
<ul
className="global-navbar-menu global-navbar-menu-right"
>
- <EmbedDocsPopupHelper
- suggestions={Array []}
- />
+ <EmbedDocsPopupHelper />
<withRouter(Search)
appState={
Object {
@@ -130,7 +118,7 @@ exports[`should render for SonarQube 1`] = `
}
}
/>
- <Connect(GlobalNavUser)
+ <Connect(withRouter(GlobalNavUser))
appState={
Object {
"canAdmin": false,
@@ -144,12 +132,6 @@ exports[`should render for SonarQube 1`] = `
"isLoggedIn": false,
}
}
- location={
- Object {
- "pathname": "",
- }
- }
- suggestions={Array []}
/>
</ul>
</NavBar>
diff --git a/server/sonar-web/src/main/js/app/types.d.ts b/server/sonar-web/src/main/js/app/types.d.ts
index c7ab0e8a71c..16cae8e6cee 100644
--- a/server/sonar-web/src/main/js/app/types.d.ts
+++ b/server/sonar-web/src/main/js/app/types.d.ts
@@ -793,6 +793,12 @@ declare namespace T {
price: number;
}
+ export interface SuggestionLink {
+ link: string;
+ scope?: 'sonarcloud';
+ text: string;
+ }
+
export interface Task {
analysisId?: string;
branch?: string;
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/Notifications.tsx b/server/sonar-web/src/main/js/apps/account/notifications/Notifications.tsx
index a6eadc5ff65..e98edcad03f 100644
--- a/server/sonar-web/src/main/js/apps/account/notifications/Notifications.tsx
+++ b/server/sonar-web/src/main/js/apps/account/notifications/Notifications.tsx
@@ -20,7 +20,6 @@
import * as React from 'react';
import Helmet from 'react-helmet';
import { groupBy, partition, uniq, uniqBy, uniqWith } from 'lodash';
-import * as PropTypes from 'prop-types';
import GlobalNotifications from './GlobalNotifications';
import Projects from './Projects';
import { NotificationProject } from './types';
@@ -28,8 +27,10 @@ import * as api from '../../../api/notifications';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
import { Alert } from '../../../components/ui/Alert';
+import { withAppState } from '../../../components/withAppState';
export interface Props {
+ appState: Pick<T.AppState, 'organizationsEnabled'>;
fetchOrganizations: (organizations: string[]) => void;
}
@@ -41,13 +42,8 @@ interface State {
perProjectTypes: string[];
}
-export default class Notifications extends React.PureComponent<Props, State> {
+export class Notifications extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- organizationsEnabled: PropTypes.bool
- };
-
state: State = {
channels: [],
globalTypes: [],
@@ -69,7 +65,7 @@ export default class Notifications extends React.PureComponent<Props, State> {
api.getNotifications().then(
response => {
if (this.mounted) {
- if (this.context.organizationsEnabled) {
+ if (this.props.appState.organizationsEnabled) {
const organizations = uniq(response.notifications
.filter(n => n.organization)
.map(n => n.organization) as string[]);
@@ -174,6 +170,8 @@ export default class Notifications extends React.PureComponent<Props, State> {
}
}
+export default withAppState(Notifications);
+
function areNotificationsEqual(a: T.Notification, b: T.Notification) {
return a.channel === b.channel && a.type === b.type && a.project === b.project;
}
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/Notifications-test.tsx b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/Notifications-test.tsx
index e0124a0806c..98f46b51ac1 100644
--- a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/Notifications-test.tsx
+++ b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/Notifications-test.tsx
@@ -20,7 +20,7 @@
/* eslint-disable import/order */
import * as React from 'react';
import { shallow } from 'enzyme';
-import Notifications, { Props } from '../Notifications';
+import { Notifications } from '../Notifications';
import { waitAndUpdate } from '../../../../helpers/testUtils';
jest.mock('../../../../api/notifications', () => ({
@@ -96,13 +96,19 @@ it('should NOT fetch organizations', async () => {
it('should fetch organizations', async () => {
const fetchOrganizations = jest.fn();
- await shallowRender({ fetchOrganizations }, { organizationsEnabled: true });
+ await shallowRender({ appState: { organizationsEnabled: true }, fetchOrganizations });
expect(getNotifications).toBeCalled();
expect(fetchOrganizations).toBeCalledWith(['org']);
});
-async function shallowRender(props?: Partial<Props>, context?: any) {
- const wrapper = shallow(<Notifications fetchOrganizations={jest.fn()} {...props} />, { context });
+async function shallowRender(props?: Partial<Notifications['props']>) {
+ const wrapper = shallow(
+ <Notifications
+ appState={{ organizationsEnabled: false }}
+ fetchOrganizations={jest.fn()}
+ {...props}
+ />
+ );
await waitAndUpdate(wrapper);
return wrapper;
}
diff --git a/server/sonar-web/src/main/js/apps/code/components/App.tsx b/server/sonar-web/src/main/js/apps/code/components/App.tsx
index 9aa8d3a9e97..15a551aa714 100644
--- a/server/sonar-web/src/main/js/apps/code/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/App.tsx
@@ -175,7 +175,7 @@ export class App extends React.PureComponent<Props, State> {
};
render() {
- const { branchLike, component, location } = this.props;
+ const { branchLike, component } = this.props;
const { loading, baseComponent, components, breadcrumbs, total, sourceViewer } = this.state;
const shouldShowBreadcrumbs = breadcrumbs.length > 1;
@@ -193,7 +193,7 @@ export class App extends React.PureComponent<Props, State> {
<Suggestions suggestions="code" />
<Helmet title={sourceViewer !== undefined ? sourceViewer.name : defaultTitle} />
- <Search branchLike={branchLike} component={component} location={location} />
+ <Search branchLike={branchLike} component={component} />
<div className="code-components">
{shouldShowBreadcrumbs && (
diff --git a/server/sonar-web/src/main/js/apps/code/components/Search.tsx b/server/sonar-web/src/main/js/apps/code/components/Search.tsx
index f8ae8822fd5..4f67a985671 100644
--- a/server/sonar-web/src/main/js/apps/code/components/Search.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/Search.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import * as classNames from 'classnames';
import Components from './Components';
import { getTree } from '../../../api/components';
@@ -26,11 +25,13 @@ import SearchBox from '../../../components/controls/SearchBox';
import { getBranchLikeQuery } from '../../../helpers/branches';
import { translate } from '../../../helpers/l10n';
import { getProjectUrl } from '../../../helpers/urls';
+import { withRouter, Router, Location } from '../../../components/hoc/withRouter';
interface Props {
branchLike?: T.BranchLike;
component: T.ComponentMeasure;
- location: {};
+ location: Location;
+ router: Pick<Router, 'push'>;
}
interface State {
@@ -40,13 +41,8 @@ interface State {
selectedIndex?: number;
}
-export default class Search extends React.PureComponent<Props, State> {
+class Search extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
state: State = {
query: '',
loading: false
@@ -93,9 +89,9 @@ export default class Search extends React.PureComponent<Props, State> {
const selected = results[selectedIndex];
if (selected.refKey) {
- this.context.router.push(getProjectUrl(selected.refKey));
+ this.props.router.push(getProjectUrl(selected.refKey));
} else {
- this.context.router.push({
+ this.props.router.push({
pathname: '/code',
query: { id: component.key, selected: selected.key, ...getBranchLikeQuery(branchLike) }
});
@@ -200,3 +196,5 @@ export default class Search extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(Search);
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx
index 89d1f04d5de..7fc402e7416 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx
@@ -21,7 +21,6 @@ import * as React from 'react';
import { Helmet } from 'react-helmet';
import { connect } from 'react-redux';
import { withRouter, WithRouterProps } from 'react-router';
-import * as PropTypes from 'prop-types';
import * as key from 'keymaster';
import { keyBy } from 'lodash';
import BulkChange from './BulkChange';
@@ -55,7 +54,8 @@ import {
getCurrentUser,
getLanguages,
getMyOrganizations,
- Store
+ Store,
+ getAppState
} from '../../../store/rootReducer';
import { translate } from '../../../helpers/l10n';
import { RawQuery } from '../../../helpers/query';
@@ -68,6 +68,7 @@ const PAGE_SIZE = 100;
const LIMIT_BEFORE_LOAD_MORE = 5;
interface StateToProps {
+ appState: T.AppState;
currentUser: T.CurrentUser;
languages: T.Languages;
userOrganizations: T.Organization[];
@@ -99,10 +100,6 @@ interface State {
export class App extends React.PureComponent<Props, State> {
mounted = false;
- static contextTypes = {
- organizationsEnabled: PropTypes.bool
- };
-
constructor(props: Props) {
super(props);
this.state = {
@@ -528,7 +525,7 @@ export class App extends React.PureComponent<Props, State> {
onFilterChange={this.handleFilterChange}
openFacets={this.state.openFacets}
organization={organization}
- organizationsEnabled={this.context.organizationsEnabled}
+ organizationsEnabled={this.props.appState.organizationsEnabled}
query={this.state.query}
referencedProfiles={this.state.referencedProfiles}
referencedRepositories={this.state.referencedRepositories}
@@ -572,7 +569,7 @@ export class App extends React.PureComponent<Props, State> {
<div className="layout-page-main-inner">
{this.state.openRule ? (
<RuleDetails
- allowCustomRules={!this.context.organizationsEnabled}
+ allowCustomRules={!this.props.appState.organizationsEnabled}
canWrite={this.state.canWrite}
hideQualityProfiles={hideQualityProfiles}
onActivate={this.handleRuleActivate}
@@ -643,6 +640,7 @@ function parseFacets(rawFacets: { property: string; values: { count: number; val
}
const mapStateToProps = (state: Store) => ({
+ appState: getAppState(state),
currentUser: getCurrentUser(state),
languages: getLanguages(state),
userOrganizations: getMyOrganizations(state)
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
index 8f0d290c3eb..820f62f1ec9 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import { Link } from 'react-router';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import Tooltip from '../../../components/controls/Tooltip';
@@ -26,8 +25,10 @@ import { getFacet } from '../../../api/issues';
import { getIssuesUrl } from '../../../helpers/urls';
import { formatMeasure } from '../../../helpers/measures';
import { translate } from '../../../helpers/l10n';
+import { withAppState } from '../../../components/withAppState';
interface Props {
+ appState: Pick<T.AppState, 'branchesEnabled'>;
organization: string | undefined;
ruleDetails: Pick<T.RuleDetails, 'key' | 'type'>;
}
@@ -44,13 +45,8 @@ interface State {
total?: number;
}
-export default class RuleDetailsIssues extends React.PureComponent<Props, State> {
+export class RuleDetailsIssues extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- branchesEnabled: PropTypes.bool
- };
-
state: State = { loading: true };
componentDidMount() {
@@ -119,7 +115,7 @@ export default class RuleDetailsIssues extends React.PureComponent<Props, State>
</span>
);
- if (!this.context.branchesEnabled) {
+ if (!this.props.appState.branchesEnabled) {
return totalItem;
}
@@ -173,3 +169,5 @@ export default class RuleDetailsIssues extends React.PureComponent<Props, State>
);
}
}
+
+export default withAppState(RuleDetailsIssues);
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx
index 731cddcfeed..675e7f90d75 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import RuleDetailsIssues from '../RuleDetailsIssues';
+import { RuleDetailsIssues } from '../RuleDetailsIssues';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { getFacet } from '../../../../api/issues';
@@ -47,7 +47,11 @@ it('should handle hotspot rules', async () => {
async function check(ruleType: T.RuleType, requestedTypes: T.RuleType[] | undefined) {
const wrapper = shallow(
- <RuleDetailsIssues organization="org" ruleDetails={{ key: 'foo', type: ruleType }} />
+ <RuleDetailsIssues
+ appState={{ branchesEnabled: false }}
+ organization="org"
+ ruleDetails={{ key: 'foo', type: ruleType }}
+ />
);
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.tsx b/server/sonar-web/src/main/js/apps/issues/components/App.tsx
index 4faebc2f57f..275eeb83903 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/App.tsx
@@ -21,7 +21,6 @@ import * as React from 'react';
import Helmet from 'react-helmet';
import * as key from 'keymaster';
import { keyBy, omit, union, without } from 'lodash';
-import * as PropTypes from 'prop-types';
import BulkChangeModal from './BulkChangeModal';
import ComponentBreadcrumbs from './ComponentBreadcrumbs';
import IssuesList from './IssuesList';
@@ -73,9 +72,10 @@ import EmptySearch from '../../../components/common/EmptySearch';
import Checkbox from '../../../components/controls/Checkbox';
import DropdownIcon from '../../../components/icons-components/DropdownIcon';
import { isSonarCloud } from '../../../helpers/system';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import { withRouter, Location, Router } from '../../../components/hoc/withRouter';
import '../../../components/search-navigator.css';
import '../styles.css';
-import DeferredSpinner from '../../../components/common/DeferredSpinner';
interface FetchIssuesPromise {
components: ReferencedComponent[];
@@ -94,10 +94,11 @@ interface Props {
currentUser: T.CurrentUser;
fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise<FetchIssuesPromise>;
hideAuthorFacet?: boolean;
- location: { pathname: string; query: RawQuery };
+ location: Pick<Location, 'pathname' | 'query'>;
myIssues?: boolean;
onBranchesChange: () => void;
organization?: { key: string };
+ router: Pick<Router, 'push' | 'replace'>;
userOrganizations: T.Organization[];
}
@@ -130,13 +131,9 @@ export interface State {
const DEFAULT_QUERY = { resolved: 'false' };
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted = false;
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
constructor(props: Props) {
super(props);
this.state = {
@@ -372,16 +369,16 @@ export default class App extends React.PureComponent<Props, State> {
this.scrollToSelectedIssue
);
} else {
- this.context.router.replace(path);
+ this.props.router.replace(path);
}
} else {
- this.context.router.push(path);
+ this.props.router.push(path);
}
};
closeIssue = () => {
if (this.state.query) {
- this.context.router.push({
+ this.props.router.push({
pathname: this.props.location.pathname,
query: {
...serializeQuery(this.state.query),
@@ -635,7 +632,7 @@ export default class App extends React.PureComponent<Props, State> {
handleFilterChange = (changes: Partial<Query>) => {
this.setState({ loading: true });
- this.context.router.push({
+ this.props.router.push({
pathname: this.props.location.pathname,
query: {
...serializeQuery({ ...this.state.query, ...changes }),
@@ -651,7 +648,7 @@ export default class App extends React.PureComponent<Props, State> {
if (!this.props.component) {
saveMyIssues(myIssues);
}
- this.context.router.push({
+ this.props.router.push({
pathname: this.props.location.pathname,
query: {
...serializeQuery({ ...this.state.query, assigned: true, assignees: [] }),
@@ -705,7 +702,7 @@ export default class App extends React.PureComponent<Props, State> {
};
handleReset = () => {
- this.context.router.push({
+ this.props.router.push({
pathname: this.props.location.pathname,
query: {
...DEFAULT_QUERY,
@@ -1156,3 +1153,5 @@ export default class App extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(App);
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx
index 7a14512a402..ff574566f0d 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import App from '../App';
+import { App } from '../App';
import { shallowWithIntl, waitAndUpdate } from '../../../../helpers/testUtils';
const replace = jest.fn();
@@ -60,6 +60,7 @@ const PROPS = {
onBranchesChange: () => {},
onSonarCloud: false,
organization: { key: 'foo' },
+ router: { push: jest.fn(), replace: jest.fn() },
userOrganizations: []
};
diff --git a/server/sonar-web/src/main/js/apps/marketplace/App.tsx b/server/sonar-web/src/main/js/apps/marketplace/App.tsx
index ca09d9a5ddf..6bd1920e6be 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/App.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/App.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import { sortBy, uniqBy } from 'lodash';
import Helmet from 'react-helmet';
import Header from './Header';
@@ -36,15 +35,16 @@ import {
PluginPendingResult,
getInstalledPlugins
} from '../../api/plugins';
-import { RawQuery } from '../../helpers/query';
import { translate } from '../../helpers/l10n';
+import { withRouter, Location, Router } from '../../components/hoc/withRouter';
import './style.css';
export interface Props {
currentEdition?: T.EditionKey;
fetchPendingPlugins: () => void;
- location: { pathname: string; query: RawQuery };
pendingPlugins: PluginPendingResult;
+ location: Location;
+ router: Pick<Router, 'push'>;
standaloneMode?: boolean;
updateCenterActive: boolean;
}
@@ -54,13 +54,8 @@ interface State {
plugins: Plugin[];
}
-export default class App extends React.PureComponent<Props, State> {
+class App extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
state: State = { loadingPlugins: true, plugins: [] };
componentDidMount() {
@@ -108,7 +103,7 @@ export default class App extends React.PureComponent<Props, State> {
updateQuery = (newQuery: Partial<Query>) => {
const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery });
- this.context.router.push({ pathname: this.props.location.pathname, query });
+ this.props.router.push({ pathname: this.props.location.pathname, query });
};
stopLoadingPlugins = () => {
@@ -151,3 +146,5 @@ export default class App extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(App);
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx
index 613f2e8a631..5efb5710d7f 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx
+++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import ConfirmButton from '../../../components/controls/ConfirmButton';
@@ -29,6 +28,7 @@ import { Button } from '../../../components/ui/buttons';
import { getOrganizationBilling } from '../../../api/organizations';
import { isSonarCloud } from '../../../helpers/system';
import { Alert } from '../../../components/ui/Alert';
+import { withRouter, Router } from '../../../components/hoc/withRouter';
interface DispatchToProps {
deleteOrganization: (key: string) => Promise<void>;
@@ -36,6 +36,7 @@ interface DispatchToProps {
interface OwnProps {
organization: Pick<T.Organization, 'key' | 'name'>;
+ router: Pick<Router, 'replace'>;
}
type Props = OwnProps & DispatchToProps;
@@ -46,10 +47,6 @@ interface State {
export class OrganizationDelete extends React.PureComponent<Props, State> {
mounted = false;
- static contextTypes = {
- router: PropTypes.object
- };
-
state: State = {};
componentDidMount() {
@@ -82,7 +79,7 @@ export class OrganizationDelete extends React.PureComponent<Props, State> {
onDelete = () => {
return this.props.deleteOrganization(this.props.organization.key).then(() => {
- this.context.router.replace('/');
+ this.props.router.replace('/');
});
};
@@ -128,7 +125,9 @@ export class OrganizationDelete extends React.PureComponent<Props, State> {
const mapDispatchToProps: DispatchToProps = { deleteOrganization: deleteOrganization as any };
-export default connect(
- null,
- mapDispatchToProps
-)(OrganizationDelete);
+export default withRouter(
+ connect(
+ null,
+ mapDispatchToProps
+ )(OrganizationDelete)
+);
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationDelete-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationDelete-test.tsx
index 04cc3670168..e699a4e4397 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationDelete-test.tsx
+++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationDelete-test.tsx
@@ -44,7 +44,7 @@ it('should redirect the page', async () => {
(isSonarCloud as jest.Mock).mockImplementation(() => false);
const deleteOrganization = jest.fn(() => Promise.resolve());
const replace = jest.fn();
- const wrapper = getWrapper({ deleteOrganization }, { router: { replace } });
+ const wrapper = getWrapper({ deleteOrganization, router: { replace } });
(wrapper.instance() as OrganizationDelete).onDelete();
await waitAndUpdate(wrapper);
expect(deleteOrganization).toHaveBeenCalledWith('foo');
@@ -53,20 +53,19 @@ it('should redirect the page', async () => {
it('should show a info message for paying organization', async () => {
(isSonarCloud as jest.Mock).mockImplementation(() => true);
- const wrapper = getWrapper({}, { onSonarCloud: true });
+ const wrapper = getWrapper({});
await waitAndUpdate(wrapper);
expect(getOrganizationBilling).toHaveBeenCalledWith('foo');
expect(wrapper).toMatchSnapshot();
});
-function getWrapper(props = {}, context = {}) {
+function getWrapper(props: Partial<OrganizationDelete['props']> = {}) {
return shallow(
<OrganizationDelete
deleteOrganization={jest.fn(() => Promise.resolve())}
organization={{ key: 'foo', name: 'Foo' }}
+ router={{ replace: jest.fn() }}
{...props}
- />,
-
- { context: { router: { replace: jest.fn() }, ...context } }
+ />
);
}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/App.tsx b/server/sonar-web/src/main/js/apps/overview/components/App.tsx
index 74ec5533243..330cc2d5b20 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/App.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import EmptyOverview from './EmptyOverview';
import OverviewApp from './OverviewApp';
@@ -33,6 +32,7 @@ import {
getPathUrlAsString
} from '../../../helpers/urls';
import { isSonarCloud } from '../../../helpers/system';
+import { withRouter, Router } from '../../../components/hoc/withRouter';
interface Props {
branchLike?: T.BranchLike;
@@ -41,27 +41,24 @@ interface Props {
isInProgress?: boolean;
isPending?: boolean;
onComponentChange: (changes: Partial<T.Component>) => void;
+ router: Pick<Router, 'replace'>;
}
-export default class App extends React.PureComponent<Props> {
- static contextTypes = {
- router: PropTypes.object
- };
-
+export class App extends React.PureComponent<Props> {
componentDidMount() {
const { branchLike, component } = this.props;
if (this.isPortfolio()) {
- this.context.router.replace({
+ this.props.router.replace({
pathname: '/portfolio',
query: { id: component.key }
});
} else if (this.isFile()) {
- this.context.router.replace(
+ this.props.router.replace(
getCodeUrl(component.breadcrumbs[0].key, branchLike, component.key)
);
} else if (isShortLivingBranch(branchLike)) {
- this.context.router.replace(getShortLivingBranchUrl(component.key, branchLike.name));
+ this.props.router.replace(getShortLivingBranchUrl(component.key, branchLike.name));
}
}
@@ -116,3 +113,5 @@ export default class App extends React.PureComponent<Props> {
);
}
}
+
+export default withRouter(App);
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx
index afac1a5750e..376cf0f1fcf 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { mount, shallow } from 'enzyme';
-import App from '../App';
+import { App } from '../App';
import { isSonarCloud } from '../../../../helpers/system';
jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
@@ -49,7 +49,7 @@ it('should render OverviewApp', () => {
it('should render EmptyOverview', () => {
expect(
- getWrapper({ component: { key: 'foo' } })
+ getWrapper({ component: { key: 'foo' } as T.Component })
.find('EmptyOverview')
.exists()
).toBeTruthy();
@@ -58,7 +58,7 @@ it('should render EmptyOverview', () => {
it('should render SonarCloudEmptyOverview', () => {
(isSonarCloud as jest.Mock<any>).mockReturnValue(true);
expect(
- getWrapper({ component: { key: 'foo' } })
+ getWrapper({ component: { key: 'foo' } as T.Component })
.find('Connect(SonarCloudEmptyOverview)')
.exists()
).toBeTruthy();
@@ -81,10 +81,8 @@ it('redirects on Code page for files', () => {
branchLikes={[branch]}
component={newComponent}
onComponentChange={jest.fn()}
- />,
- {
- context: { router: { replace } }
- }
+ router={{ replace }}
+ />
);
expect(replace).toBeCalledWith({
pathname: '/code',
@@ -92,8 +90,14 @@ it('redirects on Code page for files', () => {
});
});
-function getWrapper(props = {}) {
+function getWrapper(props: Partial<App['props']> = {}) {
return shallow(
- <App branchLikes={[]} component={component} onComponentChange={jest.fn()} {...props} />
+ <App
+ branchLikes={[]}
+ component={component}
+ onComponentChange={jest.fn()}
+ router={{ replace: jest.fn() }}
+ {...props}
+ />
);
}
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx
index f4a9b938b37..b955ae314fc 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import { connect } from 'react-redux';
import MetaKey from './MetaKey';
import MetaOrganizationKey from './MetaOrganizationKey';
@@ -35,11 +34,13 @@ import {
getCurrentUser,
getMyOrganizations,
getOrganizationByKey,
- Store
+ Store,
+ getAppState
} from '../../../store/rootReducer';
import PrivacyBadgeContainer from '../../../components/common/PrivacyBadgeContainer';
interface StateToProps {
+ appState: T.AppState;
currentUser: T.CurrentUser;
organization?: T.Organization;
userOrganizations: T.Organization[];
@@ -59,12 +60,8 @@ interface OwnProps {
type Props = OwnProps & StateToProps;
export class Meta extends React.PureComponent<Props> {
- static contextTypes = {
- organizationsEnabled: PropTypes.bool
- };
-
renderQualityInfos() {
- const { organizationsEnabled } = this.context;
+ const { organizationsEnabled } = this.props.appState;
const { component, currentUser, organization, userOrganizations } = this.props;
const { qualifier, qualityProfiles, qualityGate } = component;
const isProject = qualifier === 'TRK';
@@ -98,7 +95,7 @@ export class Meta extends React.PureComponent<Props> {
}
render() {
- const { organizationsEnabled } = this.context;
+ const { organizationsEnabled } = this.props.appState;
const { branchLike, component, measures, metrics, organization } = this.props;
const { qualifier, description, visibility } = component;
@@ -164,6 +161,7 @@ export class Meta extends React.PureComponent<Props> {
}
const mapStateToProps = (state: Store, { component }: OwnProps) => ({
+ appState: getAppState(state),
currentUser: getCurrentUser(state),
organization: getOrganizationByKey(state, component.organization),
userOrganizations: getMyOrganizations(state)
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx
index ce073542841..19b59711f72 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import { difference } from 'lodash';
import DeleteForm from './DeleteForm';
import Form from './Form';
@@ -30,12 +29,14 @@ import {
import ActionsDropdown, { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown';
import QualifierIcon from '../../../components/icons-components/QualifierIcon';
import { translate } from '../../../helpers/l10n';
+import { withRouter, Router } from '../../../components/hoc/withRouter';
-export interface Props {
+interface Props {
fromDetails?: boolean;
organization?: { isDefault?: boolean; key: string };
permissionTemplate: T.PermissionTemplate;
refresh: () => void;
+ router: Pick<Router, 'replace'>;
topQualifiers: string[];
}
@@ -44,13 +45,8 @@ interface State {
updateModal: boolean;
}
-export default class ActionsCell extends React.PureComponent<Props, State> {
+export class ActionsCell extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- router: PropTypes.object
- };
-
state: State = { deleteForm: false, updateModal: false };
componentDidMount() {
@@ -96,7 +92,7 @@ export default class ActionsCell extends React.PureComponent<Props, State> {
const pathname = this.props.organization
? `/organizations/${this.props.organization.key}/permission_templates`
: '/permission_templates';
- this.context.router.replace(pathname);
+ this.props.router.replace(pathname);
this.props.refresh();
});
};
@@ -214,3 +210,5 @@ export default class ActionsCell extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(ActionsCell);
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx
index e6d9b84dc88..f6204e2e727 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx
@@ -18,29 +18,25 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import Form from './Form';
import { createPermissionTemplate } from '../../../api/permissions';
import { Button } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
+import { withRouter, Router } from '../../../components/hoc/withRouter';
interface Props {
organization?: { key: string };
ready?: boolean;
refresh: () => Promise<void>;
+ router: Pick<Router, 'push'>;
}
interface State {
createModal: boolean;
}
-export default class Header extends React.PureComponent<Props, State> {
+class Header extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- router: PropTypes.object
- };
-
state: State = { createModal: false };
componentDidMount() {
@@ -72,7 +68,7 @@ export default class Header extends React.PureComponent<Props, State> {
const pathname = organization
? `/organizations/${organization}/permission_templates`
: '/permission_templates';
- this.context.router.push({ pathname, query: { id: response.permissionTemplate.id } });
+ this.props.router.push({ pathname, query: { id: response.permissionTemplate.id } });
});
});
};
@@ -102,3 +98,5 @@ export default class Header extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(Header);
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.tsx
index a3ea017810c..615d1e8ff5d 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.tsx
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import ActionsCell, { Props } from '../ActionsCell';
+import { ActionsCell } from '../ActionsCell';
const SAMPLE = {
createdAt: '2018-01-01',
@@ -29,11 +29,12 @@ const SAMPLE = {
defaultFor: []
};
-function renderActionsCell(props?: Partial<Props>) {
+function renderActionsCell(props?: Partial<ActionsCell['props']>) {
return shallow(
<ActionsCell
permissionTemplate={SAMPLE}
refresh={() => true}
+ router={{ replace: jest.fn() }}
topQualifiers={['TRK', 'VW']}
{...props}
/>
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap
index d48d3089a5d..66a2f952e56 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Activity-test.tsx.snap
@@ -7,7 +7,7 @@ exports[`renders 1`] = `
<h4>
project_activity.page
</h4>
- <PreviewGraph
+ <withRouter(PreviewGraph)
history={
Object {
"coverage": Array [
diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
index 9094431da5d..7c3fa2102a8 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import { omitBy } from 'lodash';
import PageHeader from './PageHeader';
@@ -38,15 +37,17 @@ import { fetchProjects, parseSorting, SORTING_SWITCH } from '../utils';
import { parseUrlQuery, Query, hasFilterParams, hasVisualizationParams } from '../query';
import { isSonarCloud } from '../../../helpers/system';
import { isLoggedIn } from '../../../helpers/users';
+import { withRouter, Location, Router } from '../../../components/hoc/withRouter';
import '../../../components/search-navigator.css';
import '../styles.css';
-export interface Props {
+interface Props {
currentUser: T.CurrentUser;
isFavorite: boolean;
- location: { pathname: string; query: RawQuery };
+ location: Pick<Location, 'pathname' | 'query'>;
organization: T.Organization | undefined;
organizationsEnabled?: boolean;
+ router: Pick<Router, 'push' | 'replace'>;
storageOptionsSuffix?: string;
}
@@ -63,13 +64,9 @@ const PROJECTS_SORT = 'sonarqube.projects.sort';
const PROJECTS_VIEW = 'sonarqube.projects.view';
const PROJECTS_VISUALIZATION = 'sonarqube.projects.visualization';
-export default class AllProjects extends React.PureComponent<Props, State> {
+export class AllProjects extends React.PureComponent<Props, State> {
mounted = false;
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
constructor(props: Props) {
super(props);
this.state = { loading: true, query: {} };
@@ -187,7 +184,7 @@ export default class AllProjects extends React.PureComponent<Props, State> {
query.sort = (sort.sortDesc ? '-' : '') + SORTING_SWITCH[sort.sortValue];
}
}
- this.context.router.push({ pathname: this.props.location.pathname, query });
+ this.props.router.push({ pathname: this.props.location.pathname, query });
} else {
this.updateLocationQuery(query);
}
@@ -210,7 +207,7 @@ export default class AllProjects extends React.PureComponent<Props, State> {
// if there is no visualization parameters (sort, view, visualization), but there are saved preferences in the localStorage
if (initialMount && !hasVisualizationParams(query) && savedOptionsSet) {
- this.context.router.replace({ pathname: this.props.location.pathname, query: savedOptions });
+ this.props.router.replace({ pathname: this.props.location.pathname, query: savedOptions });
} else {
this.fetchProjects(query);
}
@@ -218,11 +215,11 @@ export default class AllProjects extends React.PureComponent<Props, State> {
updateLocationQuery = (newQuery: RawQuery) => {
const query = omitBy({ ...this.props.location.query, ...newQuery }, x => !x);
- this.context.router.push({ pathname: this.props.location.pathname, query });
+ this.props.router.push({ pathname: this.props.location.pathname, query });
};
handleClearAll = () => {
- this.context.router.push({ pathname: this.props.location.pathname });
+ this.props.router.push({ pathname: this.props.location.pathname });
};
renderSide = () => (
@@ -328,3 +325,5 @@ export default class AllProjects extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(AllProjects);
diff --git a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx
index dd86e540523..34cd6b76449 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx
@@ -18,17 +18,18 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import AllProjectsContainer from './AllProjectsContainer';
import { PROJECTS_DEFAULT_FILTER, PROJECTS_FAVORITE, PROJECTS_ALL } from '../utils';
import { get } from '../../../helpers/storage';
import { searchProjects } from '../../../api/components';
import { isSonarCloud } from '../../../helpers/system';
import { isLoggedIn } from '../../../helpers/users';
+import { withRouter, Location, Router } from '../../../components/hoc/withRouter';
interface Props {
currentUser: T.CurrentUser;
- location: { pathname: string; query: { [x: string]: string } };
+ location: Pick<Location, 'pathname' | 'query'>;
+ router: Pick<Router, 'replace'>;
}
interface State {
@@ -36,19 +37,12 @@ interface State {
shouldForceSorting?: string;
}
-export default class DefaultPageSelector extends React.PureComponent<Props, State> {
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
- constructor(props: Props) {
- super(props);
- this.state = {};
- }
+export class DefaultPageSelector extends React.PureComponent<Props, State> {
+ state: State = {};
componentDidMount() {
if (isSonarCloud() && !isLoggedIn(this.props.currentUser)) {
- this.context.router.replace('/explore/projects');
+ this.props.router.replace('/explore/projects');
}
if (!isSonarCloud()) {
@@ -61,9 +55,9 @@ export default class DefaultPageSelector extends React.PureComponent<Props, Stat
if (prevProps.location !== this.props.location) {
this.defineIfShouldBeRedirected();
} else if (this.state.shouldBeRedirected === true) {
- this.context.router.replace({ ...this.props.location, pathname: '/projects/favorite' });
+ this.props.router.replace({ ...this.props.location, pathname: '/projects/favorite' });
} else if (this.state.shouldForceSorting != null) {
- this.context.router.replace({
+ this.props.router.replace({
...this.props.location,
query: {
...this.props.location.query,
@@ -142,3 +136,5 @@ export default class DefaultPageSelector extends React.PureComponent<Props, Stat
return null;
}
}
+
+export default withRouter(DefaultPageSelector);
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
index babf7c999e0..12e7e5d81e5 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
@@ -20,7 +20,7 @@
/* eslint-disable import/order */
import * as React from 'react';
import { shallow } from 'enzyme';
-import AllProjects, { Props } from '../AllProjects';
+import { AllProjects } from '../AllProjects';
import { get, save } from '../../../../helpers/storage';
jest.mock('../ProjectsList', () => ({
@@ -162,9 +162,9 @@ it('changes perspective to risk visualization', () => {
});
function shallowRender(
- props: Partial<Props> = {},
- push: Function = jest.fn(),
- replace: Function = jest.fn()
+ props: Partial<AllProjects['props']> = {},
+ push = jest.fn(),
+ replace = jest.fn()
) {
const wrapper = shallow(
<AllProjects
@@ -173,9 +173,9 @@ function shallowRender(
location={{ pathname: '/projects', query: {} }}
organization={undefined}
organizationsEnabled={false}
+ router={{ push, replace }}
{...props}
- />,
- { context: { router: { push, replace } } }
+ />
);
wrapper.setState({
loading: false,
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx
index e3d269914db..ce99e965fdc 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx
@@ -35,7 +35,7 @@ jest.mock('../../../../api/components', () => ({
import * as React from 'react';
import { mount } from 'enzyme';
-import DefaultPageSelector from '../DefaultPageSelector';
+import { DefaultPageSelector } from '../DefaultPageSelector';
import { doAsync } from '../../../../helpers/testUtils';
const get = require('../../../../helpers/storage').get as jest.Mock<any>;
@@ -87,7 +87,10 @@ function mountRender(
replace: any = jest.fn()
) {
return mount(
- <DefaultPageSelector currentUser={currentUser} location={{ pathname: '/projects', query }} />,
- { context: { router: { replace } } }
+ <DefaultPageSelector
+ currentUser={currentUser}
+ location={{ pathname: '/projects', query }}
+ router={{ replace }}
+ />
);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx
index b7e2c9831d2..d91c838bd27 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx
@@ -18,28 +18,25 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import { copyQualityGate } from '../../../api/quality-gates';
import ConfirmModal from '../../../components/controls/ConfirmModal';
import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls';
+import { withRouter, Router } from '../../../components/hoc/withRouter';
interface Props {
onClose: () => void;
onCopy: () => Promise<void>;
organization?: string;
qualityGate: T.QualityGate;
+ router: Pick<Router, 'push'>;
}
interface State {
name: string;
}
-export default class CopyQualityGateForm extends React.PureComponent<Props, State> {
- static contextTypes = {
- router: PropTypes.object
- };
-
+class CopyQualityGateForm extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = { name: props.qualityGate.name };
@@ -59,7 +56,7 @@ export default class CopyQualityGateForm extends React.PureComponent<Props, Stat
return copyQualityGate({ id: qualityGate.id, name, organization }).then(qualityGate => {
this.props.onCopy();
- this.context.router.push(getQualityGateUrl(String(qualityGate.id), this.props.organization));
+ this.props.router.push(getQualityGateUrl(String(qualityGate.id), this.props.organization));
});
};
@@ -95,3 +92,5 @@ export default class CopyQualityGateForm extends React.PureComponent<Props, Stat
);
}
}
+
+export default withRouter(CopyQualityGateForm);
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx
index e9b20ed5a64..558cf854834 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx
@@ -18,28 +18,25 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import { createQualityGate } from '../../../api/quality-gates';
import ConfirmModal from '../../../components/controls/ConfirmModal';
import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls';
+import { withRouter, Router } from '../../../components/hoc/withRouter';
interface Props {
onClose: () => void;
onCreate: () => Promise<void>;
organization?: string;
+ router: Pick<Router, 'push'>;
}
interface State {
name: string;
}
-export default class CreateQualityGateForm extends React.PureComponent<Props, State> {
- static contextTypes = {
- router: PropTypes.object
- };
-
- state = { name: '' };
+class CreateQualityGateForm extends React.PureComponent<Props, State> {
+ state: State = { name: '' };
handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
this.setState({ name: event.currentTarget.value });
@@ -58,7 +55,7 @@ export default class CreateQualityGateForm extends React.PureComponent<Props, St
return this.props.onCreate().then(() => qualityGate);
})
.then(qualityGate => {
- this.context.router.push(getQualityGateUrl(String(qualityGate.id), organization));
+ this.props.router.push(getQualityGateUrl(String(qualityGate.id), organization));
});
};
@@ -91,3 +88,5 @@ export default class CreateQualityGateForm extends React.PureComponent<Props, St
);
}
}
+
+export default withRouter(CreateQualityGateForm);
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx
index f523a4f12f1..84d9f38bdc9 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx
@@ -18,30 +18,27 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import { deleteQualityGate } from '../../../api/quality-gates';
import ConfirmButton from '../../../components/controls/ConfirmButton';
import { Button } from '../../../components/ui/buttons';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getQualityGatesUrl } from '../../../helpers/urls';
+import { withRouter, Router } from '../../../components/hoc/withRouter';
interface Props {
onDelete: () => Promise<void>;
organization?: string;
qualityGate: T.QualityGate;
+ router: Pick<Router, 'push'>;
}
-export default class DeleteQualityGateForm extends React.PureComponent<Props> {
- static contextTypes = {
- router: PropTypes.object
- };
-
+class DeleteQualityGateForm extends React.PureComponent<Props> {
onDelete = () => {
const { organization, qualityGate } = this.props;
return deleteQualityGate({ id: qualityGate.id, organization })
.then(this.props.onDelete)
.then(() => {
- this.context.router.push(getQualityGatesUrl(organization));
+ this.props.router.push(getQualityGatesUrl(organization));
});
};
@@ -70,3 +67,5 @@ export default class DeleteQualityGateForm extends React.PureComponent<Props> {
);
}
}
+
+export default withRouter(DeleteQualityGateForm);
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsApp.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsApp.tsx
index 95bb2bcfb76..5756d0cda7d 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsApp.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsApp.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
+import { withRouter, WithRouterProps } from 'react-router';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import DetailsHeader from './DetailsHeader';
@@ -44,7 +44,7 @@ interface DispatchToProps {
fetchMetrics: () => void;
}
-type Props = StateToProps & DispatchToProps & OwnProps;
+type Props = StateToProps & DispatchToProps & OwnProps & WithRouterProps;
interface State {
loading: boolean;
@@ -53,11 +53,6 @@ interface State {
export class DetailsApp extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
state: State = { loading: true };
componentDidMount() {
@@ -173,7 +168,9 @@ const mapStateToProps = (state: Store): StateToProps => ({
metrics: getMetrics(state)
});
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(DetailsApp);
+export default withRouter(
+ connect(
+ mapStateToProps,
+ mapDispatchToProps
+ )(DetailsApp)
+);
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.tsx
index 56e3986f26c..8961c35c9c2 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
+import { withRouter, WithRouterProps } from 'react-router';
import Helmet from 'react-helmet';
import ListHeader from './ListHeader';
import List from './List';
@@ -30,7 +30,7 @@ import { getQualityGateUrl } from '../../../helpers/urls';
import '../../../components/search-navigator.css';
import '../styles.css';
-interface Props {
+interface Props extends WithRouterProps {
children: React.ReactElement<{
organization?: string;
refreshQualityGates: () => Promise<void>;
@@ -44,13 +44,8 @@ interface State {
qualityGates: T.QualityGate[];
}
-export default class QualityGatesApp extends React.PureComponent<Props, State> {
+class QualityGatesApp extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
state: State = { canCreate: false, loading: true, qualityGates: [] };
componentDidMount() {
@@ -87,7 +82,7 @@ export default class QualityGatesApp extends React.PureComponent<Props, State> {
this.setState({ canCreate: actions.create, loading: false, qualityGates });
if (qualityGates && qualityGates.length === 1 && !actions.create) {
- this.context.router.replace(
+ this.props.router.replace(
getQualityGateUrl(String(qualityGates[0].id), organization && organization.key)
);
}
@@ -156,3 +151,5 @@ export default class QualityGatesApp extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(QualityGatesApp);
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
index 7c690dbc3df..f30e37ebfdc 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
+import { withRouter, WithRouterProps } from 'react-router';
import Changelog from './Changelog';
import ChangelogSearch from './ChangelogSearch';
import ChangelogEmpty from './ChangelogEmpty';
@@ -28,13 +28,7 @@ import { getProfileChangelogPath } from '../utils';
import { Profile, ProfileChangelogEvent } from '../types';
import { parseDate, toShortNotSoISOString } from '../../../helpers/dates';
-interface Props {
- location: {
- query: {
- since?: string;
- to?: string;
- };
- };
+interface Props extends WithRouterProps {
organization: string | null;
profile: Profile;
}
@@ -46,16 +40,9 @@ interface State {
total?: number;
}
-export default class ChangelogContainer extends React.PureComponent<Props, State> {
+class ChangelogContainer extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- router: PropTypes.object
- };
-
- state: State = {
- loading: true
- };
+ state: State = { loading: true };
componentDidMount() {
this.mounted = true;
@@ -136,7 +123,7 @@ export default class ChangelogContainer extends React.PureComponent<Props, State
to: to && toShortNotSoISOString(to)
}
);
- this.context.router.push(path);
+ this.props.router.push(path);
};
handleReset = () => {
@@ -145,7 +132,7 @@ export default class ChangelogContainer extends React.PureComponent<Props, State
this.props.profile.language,
this.props.organization
);
- this.context.router.push(path);
+ this.props.router.push(path);
};
render() {
@@ -189,3 +176,5 @@ export default class ChangelogContainer extends React.PureComponent<Props, State
);
}
}
+
+export default withRouter(ChangelogContainer);
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx
index bb3fa09aa81..9e28e800200 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx
@@ -18,15 +18,14 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
+import { withRouter, WithRouterProps } from 'react-router';
import ComparisonForm from './ComparisonForm';
import ComparisonResults from './ComparisonResults';
import { compareProfiles } from '../../../api/quality-profiles';
import { getProfileComparePath } from '../utils';
import { Profile } from '../types';
-interface Props {
- location: { query: { withKey?: string } };
+interface Props extends WithRouterProps {
organization: string | null;
profile: Profile;
profiles: Profile[];
@@ -48,17 +47,9 @@ interface State {
}>;
}
-export default class ComparisonContainer extends React.PureComponent<Props, State> {
+class ComparisonContainer extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- router: PropTypes.object
- };
-
- constructor(props: Props) {
- super(props);
- this.state = { loading: false };
- }
+ state: State = { loading: false };
componentDidMount() {
this.mounted = true;
@@ -104,7 +95,7 @@ export default class ComparisonContainer extends React.PureComponent<Props, Stat
this.props.organization,
withKey
);
- this.context.router.push(path);
+ this.props.router.push(path);
};
render() {
@@ -145,3 +136,5 @@ export default class ComparisonContainer extends React.PureComponent<Props, Stat
);
}
}
+
+export default withRouter(ComparisonContainer);
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
index ac464d475c8..8947a6fd0ed 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import RenameProfileForm from './RenameProfileForm';
import CopyProfileForm from './CopyProfileForm';
import DeleteProfileForm from './DeleteProfileForm';
@@ -31,12 +30,14 @@ import ActionsDropdown, {
ActionsDropdownItem,
ActionsDropdownDivider
} from '../../../components/controls/ActionsDropdown';
+import { withRouter, Router } from '../../../components/hoc/withRouter';
interface Props {
className?: string;
fromList?: boolean;
organization: string | null;
profile: Profile;
+ router: Pick<Router, 'push' | 'replace'>;
updateProfiles: () => Promise<void>;
}
@@ -46,20 +47,13 @@ interface State {
renameFormOpen: boolean;
}
-export default class ProfileActions extends React.PureComponent<Props, State> {
- static contextTypes = {
- router: PropTypes.object
+export class ProfileActions extends React.PureComponent<Props, State> {
+ state: State = {
+ copyFormOpen: false,
+ deleteFormOpen: false,
+ renameFormOpen: false
};
- constructor(props: Props) {
- super(props);
- this.state = {
- copyFormOpen: false,
- deleteFormOpen: false,
- renameFormOpen: false
- };
- }
-
handleRenameClick = () => {
this.setState({ renameFormOpen: true });
};
@@ -69,7 +63,7 @@ export default class ProfileActions extends React.PureComponent<Props, State> {
this.props.updateProfiles().then(
() => {
if (!this.props.fromList) {
- this.context.router.replace(
+ this.props.router.replace(
getProfilePath(name, this.props.profile.language, this.props.organization)
);
}
@@ -90,7 +84,7 @@ export default class ProfileActions extends React.PureComponent<Props, State> {
this.closeCopyForm();
this.props.updateProfiles().then(
() => {
- this.context.router.push(
+ this.props.router.push(
getProfilePath(name, this.props.profile.language, this.props.organization)
);
},
@@ -111,7 +105,7 @@ export default class ProfileActions extends React.PureComponent<Props, State> {
};
handleProfileDelete = () => {
- this.context.router.replace(getProfilesPath(this.props.organization));
+ this.props.router.replace(getProfilesPath(this.props.organization));
this.props.updateProfiles();
};
@@ -220,3 +214,5 @@ export default class ProfileActions extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(ProfileActions);
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx
index 6e12e93f278..a14dff85288 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import ProfileActions from '../ProfileActions';
+import { ProfileActions } from '../ProfileActions';
import { click, waitAndUpdate } from '../../../../helpers/testUtils';
const PROFILE = {
@@ -40,7 +40,14 @@ const PROFILE = {
it('renders with no permissions', () => {
expect(
- shallow(<ProfileActions organization="org" profile={PROFILE} updateProfiles={jest.fn()} />)
+ shallow(
+ <ProfileActions
+ organization="org"
+ profile={PROFILE}
+ router={{ push: jest.fn(), replace: jest.fn() }}
+ updateProfiles={jest.fn()}
+ />
+ )
).toMatchSnapshot();
});
@@ -50,6 +57,7 @@ it('renders with permission to edit only', () => {
<ProfileActions
organization="org"
profile={{ ...PROFILE, actions: { edit: true } }}
+ router={{ push: jest.fn(), replace: jest.fn() }}
updateProfiles={jest.fn()}
/>
)
@@ -71,6 +79,7 @@ it('renders with all permissions', () => {
associateProjects: true
}
}}
+ router={{ push: jest.fn(), replace: jest.fn() }}
updateProfiles={jest.fn()}
/>
)
@@ -84,9 +93,9 @@ it('should copy profile', async () => {
<ProfileActions
organization="org"
profile={{ ...PROFILE, actions: { copy: true } }}
+ router={{ push, replace: jest.fn() }}
updateProfiles={updateProfiles}
- />,
- { context: { router: { push } } }
+ />
);
click(wrapper.find('[id="quality-profile-copy"]'));
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx
index b674d118d13..216cb7daa2f 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import { Link } from 'react-router';
import CreateProfileForm from './CreateProfileForm';
import RestoreProfileForm from './RestoreProfileForm';
@@ -27,11 +26,13 @@ import { getProfilePath } from '../utils';
import { Actions } from '../../../api/quality-profiles';
import { Button } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
+import { withRouter, Router } from '../../../components/hoc/withRouter';
interface Props {
actions: Actions;
languages: Array<{ key: string; name: string }>;
organization: string | null;
+ router: Pick<Router, 'push'>;
updateProfiles: () => Promise<void>;
}
@@ -40,12 +41,8 @@ interface State {
restoreFormOpen: boolean;
}
-export default class PageHeader extends React.PureComponent<Props, State> {
- static contextTypes = {
- router: PropTypes.object
- };
-
- state = {
+class PageHeader extends React.PureComponent<Props, State> {
+ state: State = {
createFormOpen: false,
restoreFormOpen: false
};
@@ -57,7 +54,7 @@ export default class PageHeader extends React.PureComponent<Props, State> {
handleCreate = (profile: Profile) => {
this.props.updateProfiles().then(
() => {
- this.context.router.push(
+ this.props.router.push(
getProfilePath(profile.name, profile.language, this.props.organization)
);
},
@@ -130,3 +127,5 @@ export default class PageHeader extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(PageHeader);
diff --git a/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx b/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx
index 8ab102b5744..8a479d2e1c6 100755
--- a/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import { Link } from 'react-router';
import VulnerabilityList from './VulnerabilityList';
@@ -26,20 +25,21 @@ import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import { translate } from '../../../helpers/l10n';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import Checkbox from '../../../components/controls/Checkbox';
-import { RawQuery } from '../../../helpers/query';
import NotFound from '../../../app/components/NotFound';
import { getSecurityHotspots } from '../../../api/security-reports';
import { isLongLivingBranch } from '../../../helpers/branches';
import DocTooltip from '../../../components/docs/DocTooltip';
import { StandardType } from '../utils';
import { Alert } from '../../../components/ui/Alert';
+import { withRouter, Location, Router } from '../../../components/hoc/withRouter';
import '../style.css';
interface Props {
branchLike?: T.BranchLike;
component: T.Component;
- location: { pathname: string; query: RawQuery };
+ location: Pick<Location, 'pathname' | 'query'>;
params: { type: string };
+ router: Pick<Router, 'push'>;
}
interface State {
@@ -50,13 +50,9 @@ interface State {
showCWE: boolean;
}
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted = false;
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
constructor(props: Props) {
super(props);
this.state = {
@@ -115,8 +111,7 @@ export default class App extends React.PureComponent<Props, State> {
};
handleCheck = (checked: boolean) => {
- const { router } = this.context;
- router.push({
+ this.props.router.push({
pathname: this.props.location.pathname,
query: { id: this.props.component.key, showCWE: checked }
});
@@ -194,3 +189,5 @@ export default class App extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(App);
diff --git a/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx
index b934385a71b..983786ae8df 100644
--- a/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx
@@ -78,7 +78,7 @@ jest.mock('../../../../api/security-reports', () => ({
import * as React from 'react';
import { shallow } from 'enzyme';
-import App from '../App';
+import { App } from '../App';
import { waitAndUpdate } from '../../../../helpers/testUtils';
const getSecurityHotspots = require('../../../../api/security-reports')
@@ -97,16 +97,32 @@ beforeEach(() => {
});
it('renders error on wrong type parameters', () => {
- const wrapper = shallow(<App component={component} location={location} params={wrongParams} />, {
- context
- });
+ const wrapper = shallow(
+ <App
+ component={component}
+ location={location}
+ params={wrongParams}
+ router={{ push: jest.fn() }}
+ />,
+ {
+ context
+ }
+ );
expect(wrapper).toMatchSnapshot();
});
it('renders owaspTop10', async () => {
- const wrapper = shallow(<App component={component} location={location} params={owaspParams} />, {
- context
- });
+ const wrapper = shallow(
+ <App
+ component={component}
+ location={location}
+ params={owaspParams}
+ router={{ push: jest.fn() }}
+ />,
+ {
+ context
+ }
+ );
await waitAndUpdate(wrapper);
expect(getSecurityHotspots).toBeCalledWith({
project: 'foo',
@@ -119,7 +135,12 @@ it('renders owaspTop10', async () => {
it('renders with cwe', () => {
const wrapper = shallow(
- <App component={component} location={locationWithCWE} params={owaspParams} />,
+ <App
+ component={component}
+ location={locationWithCWE}
+ params={owaspParams}
+ router={{ push: jest.fn() }}
+ />,
{ context }
);
expect(getSecurityHotspots).toBeCalledWith({
@@ -132,9 +153,17 @@ it('renders with cwe', () => {
});
it('handle checkbox for cwe display', async () => {
- const wrapper = shallow(<App component={component} location={location} params={owaspParams} />, {
- context
- });
+ const wrapper = shallow(
+ <App
+ component={component}
+ location={location}
+ params={owaspParams}
+ router={{ push: jest.fn() }}
+ />,
+ {
+ context
+ }
+ );
expect(getSecurityHotspots).toBeCalledWith({
project: 'foo',
standard: 'owaspTop10',
@@ -156,9 +185,17 @@ it('handle checkbox for cwe display', async () => {
});
it('renders sansTop25', () => {
- const wrapper = shallow(<App component={component} location={location} params={sansParams} />, {
- context
- });
+ const wrapper = shallow(
+ <App
+ component={component}
+ location={location}
+ params={sansParams}
+ router={{ push: jest.fn() }}
+ />,
+ {
+ context
+ }
+ );
expect(getSecurityHotspots).toBeCalledWith({
project: 'foo',
standard: 'sansTop25',
diff --git a/server/sonar-web/src/main/js/apps/system/components/App.tsx b/server/sonar-web/src/main/js/apps/system/components/App.tsx
index 2d4b850ae93..960a1d39469 100644
--- a/server/sonar-web/src/main/js/apps/system/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/system/components/App.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
+import { withRouter, WithRouterProps } from 'react-router';
import Helmet from 'react-helmet';
import ClusterSysInfos from './ClusterSysInfos';
import PageHeader from './PageHeader';
@@ -35,29 +35,18 @@ import {
Query,
serializeQuery
} from '../utils';
-import { RawQuery } from '../../../helpers/query';
import '../styles.css';
-interface Props {
- location: { pathname: string; query: RawQuery };
-}
+type Props = WithRouterProps;
interface State {
loading: boolean;
sysInfoData?: SysInfo;
}
-export default class App extends React.PureComponent<Props, State> {
+class App extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- router: PropTypes.object
- };
-
- constructor(props: Props) {
- super(props);
- this.state = { loading: true };
- }
+ state: State = { loading: true };
componentDidMount() {
this.mounted = true;
@@ -97,7 +86,7 @@ export default class App extends React.PureComponent<Props, State> {
updateQuery = (newQuery: Query) => {
const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery });
- this.context.router.replace({ pathname: this.props.location.pathname, query });
+ this.props.router.replace({ pathname: this.props.location.pathname, query });
};
renderSysInfo() {
@@ -145,3 +134,5 @@ export default class App extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(App);
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx
index e21b74aa1bb..9081375b868 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx
+++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import ProjectWatcher from './ProjectWatcher';
@@ -32,11 +31,13 @@ import { getProjectUrl } from '../../../helpers/urls';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { isSonarCloud } from '../../../helpers/system';
import { isLoggedIn } from '../../../helpers/users';
+import { withRouter, Router } from '../../../components/hoc/withRouter';
import '../styles.css';
interface OwnProps {
automatic?: boolean;
onFinish: () => void;
+ router: Pick<Router, 'push'>;
}
interface StateProps {
@@ -56,9 +57,6 @@ interface State {
export class ProjectOnboarding extends React.PureComponent<Props, State> {
mounted = false;
- static contextTypes = {
- router: PropTypes.object
- };
constructor(props: Props) {
super(props);
@@ -93,7 +91,7 @@ export class ProjectOnboarding extends React.PureComponent<Props, State> {
finishOnboarding = () => {
this.props.onFinish();
if (this.state.projectKey) {
- this.context.router.push(getProjectUrl(this.state.projectKey));
+ this.props.router.push(getProjectUrl(this.state.projectKey));
}
};
@@ -203,4 +201,4 @@ const mapStateToProps = (state: Store): StateProps => {
};
};
-export default connect(mapStateToProps)(ProjectOnboarding);
+export default withRouter(connect(mapStateToProps)(ProjectOnboarding));
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingPage.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingPage.tsx
index d22ab7bcf52..04715cb8f19 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingPage.tsx
+++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboardingPage.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
+import { withRouter, WithRouterProps } from 'react-router';
import { connect } from 'react-redux';
import ProjectOnboardingModal from './ProjectOnboardingModal';
import { skipOnboarding } from '../../../store/users';
@@ -27,14 +27,12 @@ interface DispatchProps {
skipOnboarding: () => void;
}
-export class ProjectOnboardingPage extends React.PureComponent<DispatchProps> {
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
+type Props = DispatchProps & WithRouterProps;
+export class ProjectOnboardingPage extends React.PureComponent<Props> {
onSkipOnboardingTutorial = () => {
this.props.skipOnboarding();
- this.context.router.replace('/');
+ this.props.router.replace('/');
};
render() {
@@ -44,7 +42,9 @@ export class ProjectOnboardingPage extends React.PureComponent<DispatchProps> {
const mapDispatchToProps: DispatchProps = { skipOnboarding };
-export default connect(
- null,
- mapDispatchToProps
-)(ProjectOnboardingPage);
+export default withRouter(
+ connect(
+ null,
+ mapDispatchToProps
+ )(ProjectOnboardingPage)
+);
diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectOnboarding-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectOnboarding-test.tsx
index 96bbc79475a..2259a3460ee 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectOnboarding-test.tsx
+++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/ProjectOnboarding-test.tsx
@@ -42,6 +42,7 @@ it('guides for on-premise', () => {
currentUser={currentUser}
onFinish={jest.fn()}
organizationsEnabled={false}
+ router={{ push: jest.fn() }}
/>
);
expect(wrapper).toMatchSnapshot();
@@ -55,7 +56,12 @@ it('guides for sonarcloud', () => {
(getInstance as jest.Mock<any>).mockImplementation(() => 'SonarCloud');
(isSonarCloud as jest.Mock<any>).mockImplementation(() => true);
const wrapper = shallow(
- <ProjectOnboarding currentUser={currentUser} onFinish={jest.fn()} organizationsEnabled={true} />
+ <ProjectOnboarding
+ currentUser={currentUser}
+ onFinish={jest.fn()}
+ organizationsEnabled={true}
+ router={{ push: jest.fn() }}
+ />
);
expect(wrapper).toMatchSnapshot();
@@ -73,7 +79,12 @@ it('finishes', () => {
(isSonarCloud as jest.Mock<any>).mockImplementation(() => false);
const onFinish = jest.fn();
const wrapper = shallow(
- <ProjectOnboarding currentUser={currentUser} onFinish={onFinish} organizationsEnabled={false} />
+ <ProjectOnboarding
+ currentUser={currentUser}
+ onFinish={onFinish}
+ organizationsEnabled={false}
+ router={{ push: jest.fn() }}
+ />
);
click(wrapper.find('ResetButtonLink'));
return doAsync(() => {
diff --git a/server/sonar-web/src/main/js/apps/users/UsersApp.tsx b/server/sonar-web/src/main/js/apps/users/UsersApp.tsx
index e833d41cd55..5cec1105cc4 100644
--- a/server/sonar-web/src/main/js/apps/users/UsersApp.tsx
+++ b/server/sonar-web/src/main/js/apps/users/UsersApp.tsx
@@ -18,9 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import Helmet from 'react-helmet';
-import { Location } from 'history';
import Header from './Header';
import Search from './Search';
import UsersList from './UsersList';
@@ -29,11 +27,13 @@ import ListFooter from '../../components/controls/ListFooter';
import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
import { getIdentityProviders, searchUsers } from '../../api/users';
import { translate } from '../../helpers/l10n';
+import { withRouter, Location, Router } from '../../components/hoc/withRouter';
interface Props {
currentUser: { isLoggedIn: boolean; login?: string };
- location: Location;
+ location: Pick<Location, 'query'>;
organizationsEnabled?: boolean;
+ router: Pick<Router, 'push'>;
}
interface State {
@@ -43,13 +43,8 @@ interface State {
users: T.User[];
}
-export default class UsersApp extends React.PureComponent<Props, State> {
+export class UsersApp extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
state: State = { identityProviders: [], loading: true, users: [] };
componentDidMount() {
@@ -110,7 +105,7 @@ export default class UsersApp extends React.PureComponent<Props, State> {
updateQuery = (newQuery: Partial<Query>) => {
const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery });
- this.context.router.push({ ...this.props.location, query });
+ this.props.router.push({ ...this.props.location, query });
};
updateTokensCount = (login: string, tokensCount: number) => {
@@ -148,3 +143,5 @@ export default class UsersApp extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(UsersApp);
diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx
index 5adb4d2dae6..b706ad6a175 100644
--- a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx
@@ -19,10 +19,10 @@
*/
/* eslint-disable import/order */
import * as React from 'react';
-import { Location } from 'history';
import { shallow } from 'enzyme';
-import UsersApp from '../UsersApp';
+import { UsersApp } from '../UsersApp';
import { waitAndUpdate } from '../../../helpers/testUtils';
+import { Location } from '../../../components/hoc/withRouter';
jest.mock('../../../api/users', () => ({
getIdentityProviders: jest.fn(() =>
@@ -77,12 +77,13 @@ it('should render correctly', async () => {
expect(wrapper).toMatchSnapshot();
});
-function getWrapper(props = {}) {
+function getWrapper(props: Partial<UsersApp['props']> = {}) {
return shallow(
<UsersApp
currentUser={currentUser}
location={location}
organizationsEnabled={true}
+ router={{ push: jest.fn() }}
{...props}
/>,
{
diff --git a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx
index 77630d3f8a4..437b2b78fbd 100644
--- a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx
+++ b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx
@@ -18,9 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as PropTypes from 'prop-types';
import Helmet from 'react-helmet';
-import { Link } from 'react-router';
+import { Link, withRouter, WithRouterProps } from 'react-router';
import Menu from './Menu';
import Search from './Search';
import Domain from './Domain';
@@ -30,29 +29,17 @@ import { getActionKey, isDomainPathActive, Query, serializeQuery, parseQuery } f
import { scrollToElement } from '../../../helpers/scrolling';
import { translate } from '../../../helpers/l10n';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
-import { RawQuery } from '../../../helpers/query';
import '../styles/web-api.css';
-interface Props {
- location: { pathname: string; query: RawQuery };
- params: { splat?: string };
-}
+type Props = WithRouterProps;
interface State {
domains: DomainType[];
}
-export default class WebApiApp extends React.PureComponent<Props, State> {
+class WebApiApp extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
- constructor(props: Props) {
- super(props);
- this.state = { domains: [] };
- }
+ state: State = { domains: [] };
componentDidMount() {
this.mounted = true;
@@ -99,7 +86,7 @@ export default class WebApiApp extends React.PureComponent<Props, State> {
updateQuery = (newQuery: Partial<Query>) => {
const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery });
- this.context.router.push({ pathname: this.props.location.pathname, query });
+ this.props.router.push({ pathname: this.props.location.pathname, query });
};
toggleInternalInitially() {
@@ -127,14 +114,13 @@ export default class WebApiApp extends React.PureComponent<Props, State> {
handleToggleInternal = () => {
const splat = this.props.params.splat || '';
- const { router } = this.context;
const { domains } = this.state;
const domain = domains.find(domain => isDomainPathActive(domain.path, splat));
const query = parseQuery(this.props.location.query);
const internal = !query.internal;
if (domain && domain.internal && !internal) {
- router.push({
+ this.props.router.push({
pathname: '/web_api',
query: { ...serializeQuery(query), internal: false }
});
@@ -194,3 +180,5 @@ export default class WebApiApp extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(WebApiApp);
diff --git a/server/sonar-web/src/main/js/components/docs/DocLink.tsx b/server/sonar-web/src/main/js/components/docs/DocLink.tsx
index f091b342294..c632c7eac07 100644
--- a/server/sonar-web/src/main/js/components/docs/DocLink.tsx
+++ b/server/sonar-web/src/main/js/components/docs/DocLink.tsx
@@ -21,8 +21,10 @@ import * as React from 'react';
import { Link } from 'react-router';
import DetachIcon from '../icons-components/DetachIcon';
import { isSonarCloud } from '../../helpers/system';
+import { withAppState } from '../withAppState';
interface OwnProps {
+ appState: Pick<T.AppState, 'canAdmin'>;
customProps?: {
[k: string]: any;
};
@@ -34,11 +36,7 @@ const SONARCLOUD_LINK = '/#sonarcloud#/';
const SONARQUBE_LINK = '/#sonarqube#/';
const SONARQUBE_ADMIN_LINK = '/#sonarqube-admin#/';
-export default class DocLink extends React.PureComponent<Props> {
- static contextTypes = {
- canAdmin: () => null
- };
-
+export class DocLink extends React.PureComponent<Props> {
handleClickOnAnchor = (event: React.MouseEvent<HTMLAnchorElement>) => {
const { customProps, href = '#' } = this.props;
if (customProps && customProps.onAnchorClick) {
@@ -63,7 +61,7 @@ export default class DocLink extends React.PureComponent<Props> {
return <SonarQubeLink url={href}>{children}</SonarQubeLink>;
} else if (href.startsWith(SONARQUBE_ADMIN_LINK)) {
return (
- <SonarQubeAdminLink canAdmin={this.context.canAdmin} url={href}>
+ <SonarQubeAdminLink canAdmin={this.props.appState.canAdmin} url={href}>
{children}
</SonarQubeAdminLink>
);
@@ -91,6 +89,8 @@ export default class DocLink extends React.PureComponent<Props> {
}
}
+export default withAppState(DocLink);
+
interface SonarCloudLinkProps {
children: React.ReactNode;
url: string;
@@ -124,7 +124,7 @@ function SonarQubeLink({ children, url }: SonarQubeLinkProps) {
}
interface SonarQubeAdminLinkProps {
- canAdmin: boolean;
+ canAdmin?: boolean;
children: React.ReactNode;
url: string;
}
diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx b/server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx
index 54871ff53f3..d2f06d237fb 100644
--- a/server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx
+++ b/server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import DocLink from '../DocLink';
+import { DocLink } from '../DocLink';
import { isSonarCloud } from '../../../helpers/system';
jest.mock('../../../helpers/system', () => ({
@@ -27,59 +27,101 @@ jest.mock('../../../helpers/system', () => ({
}));
it('should render simple link', () => {
- expect(shallow(<DocLink href="http://sample.com">link text</DocLink>)).toMatchSnapshot();
+ expect(
+ shallow(
+ <DocLink appState={{ canAdmin: false }} href="http://sample.com">
+ link text
+ </DocLink>
+ )
+ ).toMatchSnapshot();
});
it('should render documentation link', () => {
- expect(shallow(<DocLink href="/foo/bar">link text</DocLink>)).toMatchSnapshot();
+ expect(
+ shallow(
+ <DocLink appState={{ canAdmin: false }} href="/foo/bar">
+ link text
+ </DocLink>
+ )
+ ).toMatchSnapshot();
});
it('should render sonarcloud link on sonarcloud', () => {
(isSonarCloud as jest.Mock).mockImplementationOnce(() => true);
- const wrapper = shallow(<DocLink href="/#sonarcloud#/foo/bar">link text</DocLink>);
+ const wrapper = shallow(
+ <DocLink appState={{ canAdmin: false }} href="/#sonarcloud#/foo/bar">
+ link text
+ </DocLink>
+ );
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('SonarCloudLink').dive()).toMatchSnapshot();
});
it('should not render sonarcloud link on sonarcloud', () => {
(isSonarCloud as jest.Mock).mockImplementationOnce(() => false);
- const wrapper = shallow(<DocLink href="/#sonarcloud#/foo/bar">link text</DocLink>);
+ const wrapper = shallow(
+ <DocLink appState={{ canAdmin: false }} href="/#sonarcloud#/foo/bar">
+ link text
+ </DocLink>
+ );
expect(wrapper.find('SonarCloudLink').dive()).toMatchSnapshot();
});
it('should render sonarqube link on sonarqube', () => {
- const wrapper = shallow(<DocLink href="/#sonarqube#/foo/bar">link text</DocLink>);
+ const wrapper = shallow(
+ <DocLink appState={{ canAdmin: false }} href="/#sonarqube#/foo/bar">
+ link text
+ </DocLink>
+ );
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('SonarQubeLink').dive()).toMatchSnapshot();
});
it('should not render sonarqube link on sonarcloud', () => {
(isSonarCloud as jest.Mock).mockImplementationOnce(() => true);
- const wrapper = shallow(<DocLink href="/#sonarqube#/foo/bar">link text</DocLink>);
+ const wrapper = shallow(
+ <DocLink appState={{ canAdmin: false }} href="/#sonarqube#/foo/bar">
+ link text
+ </DocLink>
+ );
expect(wrapper.find('SonarQubeLink').dive()).toMatchSnapshot();
});
it('should render sonarqube admin link on sonarqube for admin', () => {
- const wrapper = shallow(<DocLink href="/#sonarqube-admin#/foo/bar">link text</DocLink>, {
- context: { canAdmin: true }
- });
+ const wrapper = shallow(
+ <DocLink appState={{ canAdmin: true }} href="/#sonarqube-admin#/foo/bar">
+ link text
+ </DocLink>
+ );
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('SonarQubeAdminLink').dive()).toMatchSnapshot();
});
it('should not render sonarqube admin link on sonarqube for non-admin', () => {
- const wrapper = shallow(<DocLink href="/#sonarqube-admin#/foo/bar">link text</DocLink>);
+ const wrapper = shallow(
+ <DocLink appState={{ canAdmin: false }} href="/#sonarqube-admin#/foo/bar">
+ link text
+ </DocLink>
+ );
expect(wrapper.find('SonarQubeAdminLink').dive()).toMatchSnapshot();
});
it('should not render sonarqube admin link on sonarcloud', () => {
(isSonarCloud as jest.Mock).mockImplementationOnce(() => true);
- const wrapper = shallow(<DocLink href="/#sonarqube-admin#/foo/bar">link text</DocLink>, {
- context: { canAdmin: true }
- });
+ const wrapper = shallow(
+ <DocLink appState={{ canAdmin: true }} href="/#sonarqube-admin#/foo/bar">
+ link text
+ </DocLink>
+ );
expect(wrapper.find('SonarQubeAdminLink').dive()).toMatchSnapshot();
});
it.skip('should render documentation anchor', () => {
- expect(shallow(<DocLink href="#quality-profiles">link text</DocLink>)).toMatchSnapshot();
+ expect(
+ shallow(
+ <DocLink appState={{ canAdmin: false }} href="#quality-profiles">
+ link text
+ </DocLink>
+ )
+ ).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap
index 7b07cbda797..94a51e8b63b 100644
--- a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap
@@ -26,6 +26,11 @@ exports[`should not render sonarqube link on sonarcloud 1`] = `
exports[`should render documentation link 1`] = `
<Link
+ appState={
+ Object {
+ "canAdmin": false,
+ }
+ }
onlyActiveOnIndex={false}
style={Object {}}
to="/documentation/foo/bar"
@@ -37,6 +42,11 @@ exports[`should render documentation link 1`] = `
exports[`should render simple link 1`] = `
<Fragment>
<a
+ appState={
+ Object {
+ "canAdmin": false,
+ }
+ }
href="http://sample.com"
rel="noopener noreferrer"
target="_blank"
diff --git a/server/sonar-web/src/main/js/app/components/extensions/ExtensionContainer.tsx b/server/sonar-web/src/main/js/components/hoc/withRouter.tsx
index a3e36e6c76a..1eb0e97588e 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/ExtensionContainer.tsx
+++ b/server/sonar-web/src/main/js/components/hoc/withRouter.tsx
@@ -17,18 +17,19 @@
* 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 Extension from './Extension';
-import { getCurrentUser, Store } from '../../../store/rootReducer';
-import { addGlobalErrorMessage } from '../../../store/globalMessages';
+import * as React from 'react';
+import { withRouter as originalWithRouter, WithRouterProps } from 'react-router';
-const mapStateToProps = (state: Store) => ({
- currentUser: getCurrentUser(state)
-});
+export type Location = WithRouterProps['location'];
+export type Router = WithRouterProps['router'];
-const mapDispatchToProps = { onFail: addGlobalErrorMessage };
+interface InjectedProps {
+ location?: Partial<Location>;
+ router?: Partial<Router>;
+}
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(Extension);
+export function withRouter<P extends InjectedProps, S>(
+ WrappedComponent: React.ComponentClass<P & InjectedProps>
+): React.ComponentClass<T.Omit<P, keyof InjectedProps>, S> {
+ return originalWithRouter(WrappedComponent as any);
+}
diff --git a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.tsx b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.tsx
index 177af3e2515..0fffacc9150 100644
--- a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.tsx
+++ b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.tsx
@@ -19,7 +19,6 @@
*/
import * as React from 'react';
import { minBy } from 'lodash';
-import * as PropTypes from 'prop-types';
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
import PreviewGraphTooltips from './PreviewGraphTooltips';
import AdvancedTimeline from '../charts/AdvancedTimeline';
@@ -37,6 +36,7 @@ import {
import { get } from '../../helpers/storage';
import { formatMeasure, getShortType } from '../../helpers/measures';
import { getBranchLikeQuery } from '../../helpers/branches';
+import { withRouter, Router } from '../hoc/withRouter';
interface History {
[x: string]: Array<{ date: Date; value?: string }>;
@@ -48,6 +48,7 @@ interface Props {
metrics: { [key: string]: T.Metric };
project: string;
renderWhenEmpty?: () => React.ReactNode;
+ router: Pick<Router, 'push'>;
}
interface State {
@@ -63,11 +64,7 @@ const GRAPH_PADDING = [4, 0, 4, 0];
const MAX_GRAPH_NB = 1;
const MAX_SERIES_PER_GRAPH = 3;
-export default class PreviewGraph extends React.PureComponent<Props, State> {
- static contextTypes = {
- router: PropTypes.object
- };
-
+class PreviewGraph extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM);
@@ -140,7 +137,7 @@ export default class PreviewGraph extends React.PureComponent<Props, State> {
};
handleClick = () => {
- this.context.router.push({
+ this.props.router.push({
pathname: '/project/activity',
query: { id: this.props.project, ...getBranchLikeQuery(this.props.branchLike) }
});
@@ -202,3 +199,5 @@ export default class PreviewGraph extends React.PureComponent<Props, State> {
);
}
}
+
+export default withRouter(PreviewGraph);