* 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';
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
interface StateProps {
- appState: Pick<T.AppState, 'adminPages' | 'organizationsEnabled'>;
+ appState: Pick<T.AppState, 'adminPages' | 'canAdmin' | 'organizationsEnabled'>;
}
interface DispatchToProps {
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();
* 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';
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: {
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;
.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;
}
}
+const mapStateToProps = (state: Store) => ({
+ appState: getAppState(state)
+});
+
const mapDispatchToProps = { fetchOrganizations };
export default connect(
- null,
+ mapStateToProps,
mapDispatchToProps
)(ComponentContainer);
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>
);
}
* 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';
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');
}
}
currentUser: getCurrentUser(state)
});
-export default connect(mapStateToProps)(Landing);
+export default withRouter(connect(mapStateToProps)(Landing));
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>
);
});
mount(
- <ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'moduleKey' } }}>
+ <ComponentContainer
+ appState={{ organizationsEnabled: false }}
+ fetchOrganizations={jest.fn()}
+ location={{ query: { id: 'moduleKey' } }}>
<Inner />
</ComponentContainer>
);
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>
);
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>
);
(getPullRequests as jest.Mock<any>).mockResolvedValueOnce([]);
const wrapper = shallow(
<ComponentContainer
+ appState={{ organizationsEnabled: false }}
fetchOrganizations={jest.fn()}
location={{ query: { id: 'foo', branch: 'feature' } }}>
<Inner />
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);
(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);
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>
);
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>
);
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';
interface Props {
onClose: () => void;
- suggestions: Array<SuggestionLink>;
}
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}
<li className="divider" />
</>
);
- }
+ };
renderIconLink(link: string, icon: string, text: string) {
return (
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')}
* 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';
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 };
<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>
* 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() {
* 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: []
+});
* 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 });
};
};
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>
+ );
}
}
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();
});
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 }
);
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' }
+ ]);
});
// 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]}
*/
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();
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 = () => {
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)
+ )
+);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { connect } from 'react-redux';
-import Extension from './Extension';
-import { getCurrentUser, Store } from '../../../store/rootReducer';
-import { addGlobalErrorMessage } from '../../../store/globalMessages';
-
-const mapStateToProps = (state: Store) => ({
- currentUser: getCurrentUser(state)
-});
-
-const mapDispatchToProps = { onFail: addGlobalErrorMessage };
-
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(Extension);
*/
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';
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) => ({
*/
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';
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) => ({
*/
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';
const extension = pages.find(p => p.key === `${pluginKey}/${extensionKey}`);
return extension ? (
- <ExtensionContainer
+ <Extension
extension={extension}
options={{ organization, refreshOrganization: this.refreshOrganization }}
/>
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';
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} />
);
* 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 {
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} />
);
* 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';
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;
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;
const { branchLikes, currentBranchLike } = this.props;
const { configuration, breadcrumbs } = this.props.component;
- if (isSonarCloud() && !this.context.branchesEnabled) {
+ if (isSonarCloud() && !this.props.appState.branchesEnabled) {
return null;
}
</div>
);
} else {
- if (!this.context.branchesEnabled) {
+ if (!this.props.appState.branchesEnabled) {
return (
<div className="navbar-context-branches">
<BranchIcon
);
}
}
+
+export default withAppState(ComponentNavBranch);
* 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 {
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[];
component: T.Component;
currentBranchLike: T.BranchLike;
onClose: () => void;
+ router: Pick<Router, 'push'>;
}
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() {
openSelected = () => {
const selected = this.getSelected();
if (selected) {
- this.context.router.push(this.getProjectBranchUrl(selected));
+ this.props.router.push(this.getProjectBranchUrl(selected));
}
};
);
}
}
+
+export default withRouter(ComponentNavBranchesMenu);
*/
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;
}
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() {
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>
);
}
}
+
+export default withAppState(ComponentNavLicenseNotif);
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 {
} 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',
];
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';
}
renderBranchesLink() {
if (
- !this.context.branchesEnabled ||
+ !this.props.appState.branchesEnabled ||
!this.isProject() ||
!this.getConfiguration().showSettings
) {
);
}
}
+
+export default withAppState(ComponentNavMenu);
*/
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';
expect(
shallow(
<ComponentNavBranch
+ appState={{ branchesEnabled: true }}
branchLikes={[mainBranch, fooBranch]}
component={component}
currentBranchLike={mainBranch}
- />,
- { context: { branchesEnabled: true, canAdmin: true } }
+ />
)
).toMatchSnapshot();
});
expect(
shallow(
<ComponentNavBranch
+ appState={{ branchesEnabled: true }}
branchLikes={[branch, fooBranch]}
component={component}
currentBranchLike={branch}
- />,
- { context: { branchesEnabled: true, canAdmin: true } }
+ />
)
).toMatchSnapshot();
});
expect(
shallow(
<ComponentNavBranch
+ appState={{ branchesEnabled: true }}
branchLikes={[pullRequest, fooBranch]}
component={component}
currentBranchLike={pullRequest}
- />,
- { context: { branchesEnabled: true, canAdmin: true } }
+ />
)
).toMatchSnapshot();
});
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'));
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();
});
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();
});
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();
});
*/
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;
component={component}
currentBranchLike={mainBranch()}
onClose={jest.fn()}
+ router={{ push: jest.fn() }}
/>
)
).toMatchSnapshot();
component={component}
currentBranchLike={mainBranch()}
onClose={jest.fn()}
+ router={{ push: jest.fn() }}
/>
);
wrapper.setState({ query: 'bar' });
component={component}
currentBranchLike={mainBranch()}
onClose={jest.fn()}
+ router={{ push: jest.fn() }}
/>
);
elementKeydown(wrapper.find('SearchBox'), 40);
*/
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';
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();
});
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();
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 } }
+ />
);
}
*/
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' };
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();
});
{ 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();
});
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();
});
expect(
shallow(
<ComponentNavMenu
+ appState={{ branchesEnabled: true }}
branchLike={branch}
component={{
...baseComponent,
configuration: { showSettings },
extensions: [{ key: 'component-foo', name: 'ComponentFoo' }]
}}
- />,
- { context: { branchesEnabled: true } }
+ />
)
).toMatchSnapshot()
);
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();
}
});
warnings={Array []}
/>
</div>
- <ComponentNavMenu
+ <Connect(withAppState(ComponentNavMenu))
component={
Object {
"breadcrumbs": Array [
`;
exports[`renders background task license info correctly 1`] = `
-<ComponentNavLicenseNotif
+<Connect(withAppState(ComponentNavLicenseNotif))
currentTask={
Object {
"errorMessage": "Foo",
onRequestClose={[Function]}
open={false}
overlay={
- <ComponentNavBranchesMenu
+ <withRouter(ComponentNavBranchesMenu)
branchLikes={
Array [
Object {
onRequestClose={[Function]}
open={false}
overlay={
- <ComponentNavBranchesMenu
+ <withRouter(ComponentNavBranchesMenu)
branchLikes={
Array [
Object {
onRequestClose={[Function]}
open={false}
overlay={
- <ComponentNavBranchesMenu
+ <withRouter(ComponentNavBranchesMenu)
branchLikes={
Array [
Object {
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';
interface OwnProps {
location: { pathname: string };
- suggestions: Array<SuggestionLink>;
}
type Props = StateProps & OwnProps;
<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
openProjectOnboarding={this.context.openProjectOnboarding}
/>
)}
- <GlobalNavUserContainer {...this.props} />
+ <GlobalNavUserContainer appState={appState} currentUser={currentUser} />
</ul>
</NavBar>
);
*/
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';
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`;
handleLogout = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
event.preventDefault();
- this.context.router.push('/sessions/logout');
+ this.props.router.push('/sessions/logout');
};
renderAuthenticated() {
return isLoggedIn(this.props.currentUser) ? this.renderAuthenticated() : this.renderAnonymous();
}
}
+
+export default withRouter(GlobalNavUser);
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 } });
*/
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[] = [
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();
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();
appState={{ organizationsEnabled: false }}
currentUser={currentUser}
organizations={organizations}
+ router={{ push: jest.fn() }}
/>
);
wrapper.setState({ open: true });
"pathname": "",
}
}
- suggestions={Array []}
/>
<ul
className="global-navbar-menu global-navbar-menu-right"
}
}
/>
- <EmbedDocsPopupHelper
- suggestions={Array []}
- />
+ <EmbedDocsPopupHelper />
<withRouter(Search)
appState={
Object {
}
}
/>
- <Connect(GlobalNavUser)
+ <Connect(withRouter(GlobalNavUser))
appState={
Object {
"canAdmin": false,
"isLoggedIn": false,
}
}
- location={
- Object {
- "pathname": "",
- }
- }
- suggestions={Array []}
/>
</ul>
</NavBar>
"pathname": "",
}
}
- suggestions={Array []}
/>
<ul
className="global-navbar-menu global-navbar-menu-right"
>
- <EmbedDocsPopupHelper
- suggestions={Array []}
- />
+ <EmbedDocsPopupHelper />
<withRouter(Search)
appState={
Object {
}
}
/>
- <Connect(GlobalNavUser)
+ <Connect(withRouter(GlobalNavUser))
appState={
Object {
"canAdmin": false,
"isLoggedIn": false,
}
}
- location={
- Object {
- "pathname": "",
- }
- }
- suggestions={Array []}
/>
</ul>
</NavBar>
price: number;
}
+ export interface SuggestionLink {
+ link: string;
+ scope?: 'sonarcloud';
+ text: string;
+ }
+
export interface Task {
analysisId?: string;
branch?: string;
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';
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;
}
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: [],
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[]);
}
}
+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;
}
/* 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', () => ({
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;
}
};
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;
<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 && (
* 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';
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 {
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
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) }
});
);
}
}
+
+export default withRouter(Search);
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';
getCurrentUser,
getLanguages,
getMyOrganizations,
- Store
+ Store,
+ getAppState
} from '../../../store/rootReducer';
import { translate } from '../../../helpers/l10n';
import { RawQuery } from '../../../helpers/query';
const LIMIT_BEFORE_LOAD_MORE = 5;
interface StateToProps {
+ appState: T.AppState;
currentUser: T.CurrentUser;
languages: T.Languages;
userOrganizations: T.Organization[];
export class App extends React.PureComponent<Props, State> {
mounted = false;
- static contextTypes = {
- organizationsEnabled: PropTypes.bool
- };
-
constructor(props: Props) {
super(props);
this.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}
<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}
}
const mapStateToProps = (state: Store) => ({
+ appState: getAppState(state),
currentUser: getCurrentUser(state),
languages: getLanguages(state),
userOrganizations: getMyOrganizations(state)
* 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';
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'>;
}
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() {
</span>
);
- if (!this.context.branchesEnabled) {
+ if (!this.props.appState.branchesEnabled) {
return totalItem;
}
);
}
}
+
+export default withAppState(RuleDetailsIssues);
*/
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';
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();
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';
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[];
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[];
}
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 = {
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),
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 }),
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: [] }),
};
handleReset = () => {
- this.context.router.push({
+ this.props.router.push({
pathname: this.props.location.pathname,
query: {
...DEFAULT_QUERY,
);
}
}
+
+export default withRouter(App);
* 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();
onBranchesChange: () => {},
onSonarCloud: false,
organization: { key: 'foo' },
+ router: { push: jest.fn(), replace: jest.fn() },
userOrganizations: []
};
* 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';
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;
}
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() {
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 = () => {
);
}
}
+
+export default withRouter(App);
* 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';
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>;
interface OwnProps {
organization: Pick<T.Organization, 'key' | 'name'>;
+ router: Pick<Router, 'replace'>;
}
type Props = OwnProps & DispatchToProps;
export class OrganizationDelete extends React.PureComponent<Props, State> {
mounted = false;
- static contextTypes = {
- router: PropTypes.object
- };
-
state: State = {};
componentDidMount() {
onDelete = () => {
return this.props.deleteOrganization(this.props.organization.key).then(() => {
- this.context.router.replace('/');
+ this.props.router.replace('/');
});
};
const mapDispatchToProps: DispatchToProps = { deleteOrganization: deleteOrganization as any };
-export default connect(
- null,
- mapDispatchToProps
-)(OrganizationDelete);
+export default withRouter(
+ connect(
+ null,
+ mapDispatchToProps
+ )(OrganizationDelete)
+);
(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');
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 } }
+ />
);
}
* 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';
getPathUrlAsString
} from '../../../helpers/urls';
import { isSonarCloud } from '../../../helpers/system';
+import { withRouter, Router } from '../../../components/hoc/withRouter';
interface Props {
branchLike?: T.BranchLike;
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));
}
}
);
}
}
+
+export default withRouter(App);
*/
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() }));
it('should render EmptyOverview', () => {
expect(
- getWrapper({ component: { key: 'foo' } })
+ getWrapper({ component: { key: 'foo' } as T.Component })
.find('EmptyOverview')
.exists()
).toBeTruthy();
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();
branchLikes={[branch]}
component={newComponent}
onComponentChange={jest.fn()}
- />,
- {
- context: { router: { replace } }
- }
+ router={{ replace }}
+ />
);
expect(replace).toBeCalledWith({
pathname: '/code',
});
});
-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}
+ />
);
}
* 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';
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[];
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';
}
render() {
- const { organizationsEnabled } = this.context;
+ const { organizationsEnabled } = this.props.appState;
const { branchLike, component, measures, metrics, organization } = this.props;
const { qualifier, description, visibility } = component;
}
const mapStateToProps = (state: Store, { component }: OwnProps) => ({
+ appState: getAppState(state),
currentUser: getCurrentUser(state),
organization: getOrganizationByKey(state, component.organization),
userOrganizations: getMyOrganizations(state)
* 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';
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[];
}
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() {
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();
});
};
);
}
}
+
+export default withRouter(ActionsCell);
* 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() {
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 } });
});
});
};
);
}
}
+
+export default withRouter(Header);
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import ActionsCell, { Props } from '../ActionsCell';
+import { ActionsCell } from '../ActionsCell';
const SAMPLE = {
createdAt: '2018-01-01',
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}
/>
<h4>
project_activity.page
</h4>
- <PreviewGraph
+ <withRouter(PreviewGraph)
history={
Object {
"coverage": Array [
* 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';
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;
}
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: {} };
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);
}
// 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);
}
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 = () => (
);
}
}
+
+export default withRouter(AllProjects);
* 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 {
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()) {
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,
return null;
}
}
+
+export default withRouter(DefaultPageSelector);
/* 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', () => ({
});
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
location={{ pathname: '/projects', query: {} }}
organization={undefined}
organizationsEnabled={false}
+ router={{ push, replace }}
{...props}
- />,
- { context: { router: { push, replace } } }
+ />
);
wrapper.setState({
loading: false,
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>;
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 }}
+ />
);
}
* 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 };
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));
});
};
);
}
}
+
+export default withRouter(CopyQualityGateForm);
* 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 });
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));
});
};
);
}
}
+
+export default withRouter(CreateQualityGateForm);
* 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));
});
};
);
}
}
+
+export default withRouter(DeleteQualityGateForm);
* 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';
fetchMetrics: () => void;
}
-type Props = StateToProps & DispatchToProps & OwnProps;
+type Props = StateToProps & DispatchToProps & OwnProps & WithRouterProps;
interface State {
loading: boolean;
export class DetailsApp extends React.PureComponent<Props, State> {
mounted = false;
-
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
state: State = { loading: true };
componentDidMount() {
metrics: getMetrics(state)
});
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(DetailsApp);
+export default withRouter(
+ connect(
+ mapStateToProps,
+ mapDispatchToProps
+ )(DetailsApp)
+);
* 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';
import '../../../components/search-navigator.css';
import '../styles.css';
-interface Props {
+interface Props extends WithRouterProps {
children: React.ReactElement<{
organization?: string;
refreshQualityGates: () => Promise<void>;
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() {
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)
);
}
);
}
}
+
+export default withRouter(QualityGatesApp);
* 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';
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;
}
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;
to: to && toShortNotSoISOString(to)
}
);
- this.context.router.push(path);
+ this.props.router.push(path);
};
handleReset = () => {
this.props.profile.language,
this.props.organization
);
- this.context.router.push(path);
+ this.props.router.push(path);
};
render() {
);
}
}
+
+export default withRouter(ChangelogContainer);
* 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[];
}>;
}
-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;
this.props.organization,
withKey
);
- this.context.router.push(path);
+ this.props.router.push(path);
};
render() {
);
}
}
+
+export default withRouter(ComparisonContainer);
* 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';
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>;
}
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 });
};
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)
);
}
this.closeCopyForm();
this.props.updateProfiles().then(
() => {
- this.context.router.push(
+ this.props.router.push(
getProfilePath(name, this.props.profile.language, this.props.organization)
);
},
};
handleProfileDelete = () => {
- this.context.router.replace(getProfilesPath(this.props.organization));
+ this.props.router.replace(getProfilesPath(this.props.organization));
this.props.updateProfiles();
};
);
}
}
+
+export default withRouter(ProfileActions);
*/
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 = {
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();
});
<ProfileActions
organization="org"
profile={{ ...PROFILE, actions: { edit: true } }}
+ router={{ push: jest.fn(), replace: jest.fn() }}
updateProfiles={jest.fn()}
/>
)
associateProjects: true
}
}}
+ router={{ push: jest.fn(), replace: jest.fn() }}
updateProfiles={jest.fn()}
/>
)
<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"]'));
* 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';
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>;
}
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
};
handleCreate = (profile: Profile) => {
this.props.updateProfiles().then(
() => {
- this.context.router.push(
+ this.props.router.push(
getProfilePath(profile.name, profile.language, this.props.organization)
);
},
);
}
}
+
+export default withRouter(PageHeader);
* 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';
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 {
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 = {
};
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 }
});
);
}
}
+
+export default withRouter(App);
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')
});
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',
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({
});
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',
});
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',
* 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';
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;
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() {
);
}
}
+
+export default withRouter(App);
* 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';
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 {
export class ProjectOnboarding extends React.PureComponent<Props, State> {
mounted = false;
- static contextTypes = {
- router: PropTypes.object
- };
constructor(props: Props) {
super(props);
finishOnboarding = () => {
this.props.onFinish();
if (this.state.projectKey) {
- this.context.router.push(getProjectUrl(this.state.projectKey));
+ this.props.router.push(getProjectUrl(this.state.projectKey));
}
};
};
};
-export default connect(mapStateToProps)(ProjectOnboarding);
+export default withRouter(connect(mapStateToProps)(ProjectOnboarding));
* 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';
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() {
const mapDispatchToProps: DispatchProps = { skipOnboarding };
-export default connect(
- null,
- mapDispatchToProps
-)(ProjectOnboardingPage);
+export default withRouter(
+ connect(
+ null,
+ mapDispatchToProps
+ )(ProjectOnboardingPage)
+);
currentUser={currentUser}
onFinish={jest.fn()}
organizationsEnabled={false}
+ router={{ push: jest.fn() }}
/>
);
expect(wrapper).toMatchSnapshot();
(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();
(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(() => {
* 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';
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 {
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() {
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) => {
);
}
}
+
+export default withRouter(UsersApp);
*/
/* 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(() =>
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}
/>,
{
* 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';
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;
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() {
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 }
});
);
}
}
+
+export default withRouter(WebApiApp);
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;
};
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) {
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>
);
}
}
+export default withAppState(DocLink);
+
interface SonarCloudLinkProps {
children: React.ReactNode;
url: string;
}
interface SonarQubeAdminLinkProps {
- canAdmin: boolean;
+ canAdmin?: boolean;
children: React.ReactNode;
url: string;
}
*/
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', () => ({
}));
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();
});
exports[`should render documentation link 1`] = `
<Link
+ appState={
+ Object {
+ "canAdmin": false,
+ }
+ }
onlyActiveOnIndex={false}
style={Object {}}
to="/documentation/foo/bar"
exports[`should render simple link 1`] = `
<Fragment>
<a
+ appState={
+ Object {
+ "canAdmin": false,
+ }
+ }
href="http://sample.com"
rel="noopener noreferrer"
target="_blank"
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { withRouter as originalWithRouter, WithRouterProps } from 'react-router';
+
+export type Location = WithRouterProps['location'];
+export type Router = WithRouterProps['router'];
+
+interface InjectedProps {
+ location?: Partial<Location>;
+ router?: Partial<Router>;
+}
+
+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);
+}
*/
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';
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 }>;
metrics: { [key: string]: T.Metric };
project: string;
renderWhenEmpty?: () => React.ReactNode;
+ router: Pick<Router, 'push'>;
}
interface State {
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);
};
handleClick = () => {
- this.context.router.push({
+ this.props.router.push({
pathname: '/project/activity',
query: { id: this.props.project, ...getBranchLikeQuery(this.props.branchLike) }
});
);
}
}
+
+export default withRouter(PreviewGraph);