import throwGlobalError from '../app/utils/throwGlobalError';
import { getJSON } from '../helpers/request';
import { BranchParameters } from '../types/branch-like';
-import { Component } from '../types/types';
+import { Component, Extension } from '../types/types';
type NavComponent = Omit<Component, 'alm' | 'qualifier' | 'leakPeriodDate' | 'path' | 'tags'>;
return getJSON('/api/navigation/marketplace').catch(throwGlobalError);
}
-export function getSettingsNavigation(): Promise<any> {
+export function getSettingsNavigation(): Promise<{
+ extensions: Extension[];
+ showUpdateCenter: boolean;
+}> {
return getJSON('/api/navigation/settings').catch(throwGlobalError);
}
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { connect } from 'react-redux';
import { getSettingsNavigation } from '../../api/nav';
import { getPendingPlugins } from '../../api/plugins';
import { getSystemStatus, waitSystemUPStatus } from '../../api/system';
import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
import { translate } from '../../helpers/l10n';
-import { setAdminPages } from '../../store/appState';
-import { getAppState, Store } from '../../store/rootReducer';
import { PendingPluginResult } from '../../types/plugins';
import { AppState, Extension, SysStatus } from '../../types/types';
import AdminContext, { defaultPendingPlugins, defaultSystemStatus } from './AdminContext';
+import withAppStateContext from './app-state/withAppStateContext';
import SettingsNav from './nav/settings/SettingsNav';
-interface Props {
- appState: Pick<AppState, 'adminPages' | 'canAdmin'>;
+export interface AdminContainerProps {
+ appState: AppState;
location: {};
- setAdminPages: (adminPages: Extension[]) => void;
+ children: React.ReactElement;
}
interface State {
pendingPlugins: PendingPluginResult;
systemStatus: SysStatus;
+ adminPages: Extension[];
}
-export class AdminContainer extends React.PureComponent<Props, State> {
+export class AdminContainer extends React.PureComponent<AdminContainerProps, State> {
mounted = false;
state: State = {
pendingPlugins: defaultPendingPlugins,
- systemStatus: defaultSystemStatus
+ systemStatus: defaultSystemStatus,
+ adminPages: []
};
componentDidMount() {
fetchNavigationSettings = () => {
getSettingsNavigation().then(
- r => this.props.setAdminPages(r.extensions),
+ r => this.setState({ adminPages: r.extensions }),
() => {}
);
};
};
render() {
- const { adminPages } = this.props.appState;
+ const { adminPages } = this.state;
// Check that the adminPages are loaded
if (!adminPages) {
pendingPlugins,
systemStatus
}}>
- {this.props.children}
+ {React.cloneElement(this.props.children, {
+ adminPages
+ })}
</AdminContext.Provider>
</div>
);
}
}
-const mapStateToProps = (state: Store) => ({ appState: getAppState(state) });
-
-const mapDispatchToProps = { setAdminPages };
-
-export default connect(mapStateToProps, mapDispatchToProps)(AdminContainer);
+export default withAppStateContext(AdminContainer);
parser.href = this.props.gravatarServerUrl;
if (parser.hostname !== window.location.hostname) {
return <link href={parser.origin} rel="preconnect" />;
- } else {
- return null;
}
+ return null;
};
render() {
import { getAnalysisStatus, getTasksForComponent } from '../../api/ce';
import { getComponentData } from '../../api/components';
import { getComponentNavigation } from '../../api/nav';
-import { withAppState } from '../../components/hoc/withAppState';
import { Location, Router, withRouter } from '../../components/hoc/withRouter';
import {
getBranchLikeQuery,
import { ComponentQualifier, isPortfolioLike } from '../../types/component';
import { Task, TaskStatuses, TaskTypes, TaskWarning } from '../../types/tasks';
import { AppState, Component, Status } from '../../types/types';
+import withAppStateContext from './app-state/withAppStateContext';
import ComponentContainerNotFound from './ComponentContainerNotFound';
import { ComponentContext } from './ComponentContext';
import PageUnavailableDueToIndexation from './indexation/PageUnavailableDueToIndexation';
import ComponentNav from './nav/component/ComponentNav';
interface Props {
- appState: Pick<AppState, 'branchesEnabled'>;
+ appState: AppState;
children: React.ReactElement;
location: Pick<Location, 'query' | 'pathname'>;
registerBranchStatus: (branchLike: BranchLike, component: string, status: Status) => void;
isPending,
projectBinding,
projectBindingErrors,
- tasksInProgress
+ tasksInProgress,
+ warnings
} = this.state;
const isInProgress = tasksInProgress && tasksInProgress.length > 0;
onWarningDismiss={this.handleWarningDismiss}
projectBinding={projectBinding}
projectBindingErrors={projectBindingErrors}
- warnings={this.state.warnings}
+ warnings={warnings}
/>
)}
{loading ? (
const mapDispatchToProps = { registerBranchStatus, requireAuthorization };
-export default withAppState(withRouter(connect(null, mapDispatchToProps)(ComponentContainer)));
+export default withRouter(
+ connect(null, mapDispatchToProps)(withAppStateContext(ComponentContainer))
+);
import A11yProvider from './a11y/A11yProvider';
import A11ySkipLinks from './a11y/A11ySkipLinks';
import SuggestionsProvider from './embed-docs-modal/SuggestionsProvider';
-import GlobalFooterContainer from './GlobalFooterContainer';
+import GlobalFooter from './GlobalFooter';
import GlobalMessagesContainer from './GlobalMessagesContainer';
import IndexationContextProvider from './indexation/IndexationContextProvider';
import IndexationNotification from './indexation/IndexationNotification';
export default function GlobalContainer(props: Props) {
// it is important to pass `location` down to `GlobalNav` to trigger render on url change
- const { footer = <GlobalFooterContainer /> } = props;
+ const { footer = <GlobalFooter /> } = props;
return (
<SuggestionsProvider>
<A11yProvider>
<StartupModal>
<A11ySkipLinks />
-
<div className="global-container">
<div className="page-wrapper" id="container">
<div className="page-container">
import { getEdition } from '../../helpers/editions';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { EditionKey } from '../../types/editions';
+import { AppState } from '../../types/types';
+import withAppStateContext from './app-state/withAppStateContext';
import GlobalFooterBranding from './GlobalFooterBranding';
-interface Props {
+export interface GlobalFooterProps {
hideLoggedInInfo?: boolean;
- productionDatabase: boolean;
- sonarqubeEdition?: EditionKey;
- sonarqubeVersion?: string;
+ appState?: AppState;
}
-export default function GlobalFooter({
- hideLoggedInInfo,
- productionDatabase,
- sonarqubeEdition,
- sonarqubeVersion
-}: Props) {
- const currentEdition = sonarqubeEdition && getEdition(sonarqubeEdition);
+export function GlobalFooter({ hideLoggedInInfo, appState }: GlobalFooterProps) {
+ const currentEdition = appState?.edition && getEdition(appState.edition as EditionKey);
return (
<div className="page-footer page-container" id="footer">
- {productionDatabase === false && (
+ {appState?.productionDatabase === false && (
<Alert display="inline" id="evaluation_warning" variant="warning">
<p className="big">{translate('footer.production_database_warning')}</p>
<p>
{!hideLoggedInInfo && currentEdition && (
<li className="page-footer-menu-item">{currentEdition.name}</li>
)}
- {!hideLoggedInInfo && sonarqubeVersion && (
+ {!hideLoggedInInfo && appState?.version && (
<li className="page-footer-menu-item">
- {translateWithParameters('footer.version_x', sonarqubeVersion)}
+ {translateWithParameters('footer.version_x', appState.version)}
</li>
)}
<li className="page-footer-menu-item">
</div>
);
}
+
+export default withAppStateContext(GlobalFooter);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { getAppState, Store } from '../../store/rootReducer';
-import { EditionKey } from '../../types/editions';
-import GlobalFooter from './GlobalFooter';
-
-interface StateProps {
- productionDatabase: boolean;
- sonarqubeEdition?: EditionKey;
- sonarqubeVersion?: string;
-}
-
-const mapStateToProps = (state: Store): StateProps => ({
- productionDatabase: getAppState(state).productionDatabase,
- sonarqubeEdition: getAppState(state).edition as EditionKey, // TODO: Fix once AppState is no longer ambiant.
- sonarqubeVersion: getAppState(state).version
-});
-
-export default connect(mapStateToProps)(GlobalFooter);
import { installScript } from '../../helpers/extensions';
import { getWebAnalyticsPageHandlerFromCache } from '../../helpers/extensionsHandler';
import { getInstance } from '../../helpers/system';
-import { getAppState, getGlobalSettingValue, Store } from '../../store/rootReducer';
+import { getGlobalSettingValue, Store } from '../../store/rootReducer';
+import { AppState } from '../../types/types';
+import withAppStateContext from './app-state/withAppStateContext';
interface Props {
location: Location;
trackingIdGTM?: string;
- webAnalytics?: string;
+ appState: AppState;
}
interface State {
state: State = {};
componentDidMount() {
- const { trackingIdGTM, webAnalytics } = this.props;
+ const { trackingIdGTM, appState } = this.props;
- if (webAnalytics && !getWebAnalyticsPageHandlerFromCache()) {
- installScript(webAnalytics, 'head');
+ if (appState.webAnalyticsJsPath && !getWebAnalyticsPageHandlerFromCache()) {
+ installScript(appState.webAnalyticsJsPath, 'head');
}
if (trackingIdGTM) {
};
render() {
- const { trackingIdGTM, webAnalytics } = this.props;
+ const { trackingIdGTM, appState } = this.props;
return (
<Helmet
defaultTitle={getInstance()}
defer={false}
- onChangeClientState={trackingIdGTM || webAnalytics ? this.trackPage : undefined}>
+ onChangeClientState={
+ trackingIdGTM || appState.webAnalyticsJsPath ? this.trackPage : undefined
+ }>
{this.props.children}
</Helmet>
);
const mapStateToProps = (state: Store) => {
const trackingIdGTM = getGlobalSettingValue(state, 'sonar.analytics.gtm.trackingId');
return {
- trackingIdGTM: trackingIdGTM && trackingIdGTM.value,
- webAnalytics: getAppState(state).webAnalyticsJsPath
+ trackingIdGTM: trackingIdGTM && trackingIdGTM.value
};
};
-export default withRouter(connect(mapStateToProps)(PageTracker));
+export default withRouter(connect(mapStateToProps)(withAppStateContext(PageTracker)));
import * as React from 'react';
import NavBar from '../../components/ui/NavBar';
import { rawSizes } from '../theme';
-import GlobalFooterContainer from './GlobalFooterContainer';
+import GlobalFooter from './GlobalFooter';
interface Props {
children?: React.ReactNode;
<NavBar className="navbar-global" height={rawSizes.globalNavHeightRaw} />
{children}
</div>
- <GlobalFooterContainer />
+ <GlobalFooter />
</div>
);
}
*/
import * as React from 'react';
import { lazyLoadComponent } from '../../components/lazyLoadComponent';
-import GlobalFooterContainer from './GlobalFooterContainer';
+import GlobalFooter from './GlobalFooter';
const PageTracker = lazyLoadComponent(() => import('./PageTracker'));
<div className="page-wrapper" id="container">
{children}
</div>
- <GlobalFooterContainer hideLoggedInInfo={true} />
+ <GlobalFooter hideLoggedInInfo={true} />
</div>
</>
);
import { hasMessage } from '../../helpers/l10n';
import { get, save } from '../../helpers/storage';
import { isLoggedIn } from '../../helpers/users';
-import { getAppState, getCurrentUser, Store } from '../../store/rootReducer';
+import { getCurrentUser, Store } from '../../store/rootReducer';
import { EditionKey } from '../../types/editions';
-import { CurrentUser } from '../../types/types';
+import { AppState, CurrentUser } from '../../types/types';
+import withAppStateContext from './app-state/withAppStateContext';
const LicensePromptModal = lazyLoadComponent(
() => import('../../apps/marketplace/components/LicensePromptModal'),
);
interface StateProps {
- canAdmin?: boolean;
- currentEdition?: EditionKey;
currentUser: CurrentUser;
}
-interface OwnProps {
+type Props = {
children?: React.ReactNode;
-}
-
-interface WithRouterProps {
location: Pick<Location, 'pathname'>;
router: Pick<Router, 'push'>;
-}
-
-type Props = StateProps & OwnProps & WithRouterProps;
+ appState: AppState;
+};
interface State {
open?: boolean;
const LICENSE_PROMPT = 'sonarqube.license.prompt';
-export class StartupModal extends React.PureComponent<Props, State> {
+export class StartupModal extends React.PureComponent<Props & StateProps, State> {
state: State = {};
componentDidMount() {
};
tryAutoOpenLicense = () => {
- const { canAdmin, currentEdition, currentUser } = this.props;
+ const { appState, currentUser } = this.props;
const hasLicenseManager = hasMessage('license.prompt.title');
- const hasLicensedEdition = currentEdition && currentEdition !== EditionKey.community;
+ const hasLicensedEdition = appState.edition && appState.edition !== EditionKey.community;
- if (canAdmin && hasLicensedEdition && isLoggedIn(currentUser) && hasLicenseManager) {
+ if (appState.canAdmin && hasLicensedEdition && isLoggedIn(currentUser) && hasLicenseManager) {
const lastPrompt = get(LICENSE_PROMPT, currentUser.login);
if (!lastPrompt || differenceInDays(new Date(), parseDate(lastPrompt)) >= 1) {
}
const mapStateToProps = (state: Store): StateProps => ({
- canAdmin: getAppState(state).canAdmin,
- currentEdition: getAppState(state).edition as EditionKey, // TODO: Fix once AppState is no longer ambiant.
currentUser: getCurrentUser(state)
});
-export default connect(mapStateToProps)(withRouter(StartupModal));
+export default connect(mapStateToProps)(withRouter(withAppStateContext(StartupModal)));
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockLocation } from '../../../helpers/testMocks';
-import { AdminContainer } from '../AdminContainer';
+import { mockAppState, mockLocation } from '../../../helpers/testMocks';
+import { AdminContainer, AdminContainerProps } from '../AdminContainer';
it('should render correctly', () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
});
-function shallowRender(props: Partial<AdminContainer['props']> = {}) {
+function shallowRender(props: Partial<AdminContainerProps> = {}) {
return shallow(
<AdminContainer
- appState={{
- adminPages: [{ key: 'foo', name: 'Foo' }],
+ appState={mockAppState({
canAdmin: true
- }}
+ })}
location={mockLocation()}
- setAdminPages={jest.fn()}
- {...props}
- />
+ {...props}>
+ <div />
+ </AdminContainer>
);
}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockAppState } from '../../../helpers/testMocks';
import { EditionKey } from '../../../types/editions';
-import GlobalFooter from '../GlobalFooter';
+import { GlobalFooter, GlobalFooterProps } from '../GlobalFooter';
jest.mock('../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
it('should not render the only logged in information', () => {
expect(
- getWrapper({ hideLoggedInInfo: true, sonarqubeVersion: '6.4-SNAPSHOT' })
+ getWrapper({
+ hideLoggedInInfo: true,
+ appState: mockAppState({ version: '6.4-SNAPSHOT' })
+ })
).toMatchSnapshot();
});
it('should show the db warning message', () => {
- expect(getWrapper({ productionDatabase: false }).find('Alert')).toMatchSnapshot();
+ expect(
+ getWrapper({
+ appState: mockAppState({ productionDatabase: false, edition: EditionKey.community })
+ }).find('Alert')
+ ).toMatchSnapshot();
});
it('should display the sq version', () => {
expect(
- getWrapper({ sonarqubeEdition: EditionKey.enterprise, sonarqubeVersion: '6.4-SNAPSHOT' })
+ getWrapper({
+ appState: mockAppState({ edition: EditionKey.enterprise, version: '6.4-SNAPSHOT' })
+ })
).toMatchSnapshot();
});
-function getWrapper(props = {}) {
+function getWrapper(props?: GlobalFooterProps) {
return shallow(
- <GlobalFooter productionDatabase={true} sonarqubeEdition={EditionKey.community} {...props} />
+ <GlobalFooter
+ appState={mockAppState({ productionDatabase: true, edition: EditionKey.community })}
+ {...props}
+ />
);
}
import { gtm } from '../../../helpers/analytics';
import { installScript } from '../../../helpers/extensions';
import { getWebAnalyticsPageHandlerFromCache } from '../../../helpers/extensionsHandler';
-import { mockLocation } from '../../../helpers/testMocks';
+import { mockAppState, mockLocation } from '../../../helpers/testMocks';
import { PageTracker } from '../PageTracker';
jest.mock('../../../helpers/extensions', () => ({
it('should work for WebAnalytics plugin', () => {
const pageChange = jest.fn();
- const webAnalytics = '/static/pluginKey/web_analytics.js';
- const wrapper = shallowRender({ webAnalytics });
+ const webAnalyticsJsPath = '/static/pluginKey/web_analytics.js';
+ const wrapper = shallowRender({ appState: mockAppState({ webAnalyticsJsPath }) });
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('Helmet').prop('onChangeClientState')).toBe(wrapper.instance().trackPage);
- expect(installScript).toBeCalledWith(webAnalytics, 'head');
+ expect(installScript).toBeCalledWith(webAnalyticsJsPath, 'head');
(getWebAnalyticsPageHandlerFromCache as jest.Mock).mockReturnValueOnce(pageChange);
wrapper.instance().trackPage();
});
function shallowRender(props: Partial<PageTracker['props']> = {}) {
- return shallow<PageTracker>(<PageTracker location={mockLocation()} {...props} />);
+ return shallow<PageTracker>(
+ <PageTracker appState={mockAppState()} location={mockLocation()} {...props} />
+ );
}
import { toShortNotSoISOString } from '../../../helpers/dates';
import { hasMessage } from '../../../helpers/l10n';
import { get, save } from '../../../helpers/storage';
+import { mockAppState } from '../../../helpers/testMocks';
import { waitAndUpdate } from '../../../helpers/testUtils';
import { EditionKey } from '../../../types/editions';
import { LoggedInUser } from '../../../types/types';
});
it('should render only the children', async () => {
- const wrapper = getWrapper({ currentEdition: EditionKey.community });
+ const wrapper = getWrapper({ appState: mockAppState({ edition: EditionKey.community }) });
await shouldNotHaveModals(wrapper);
expect(showLicense).toHaveBeenCalledTimes(0);
expect(wrapper.find('div').exists()).toBe(true);
- await shouldNotHaveModals(getWrapper({ canAdmin: false }));
+ await shouldNotHaveModals(getWrapper({ appState: mockAppState({ canAdmin: false }) }));
(hasMessage as jest.Mock<any>).mockReturnValueOnce(false);
await shouldNotHaveModals(getWrapper());
await shouldNotHaveModals(
getWrapper({
- canAdmin: false,
+ appState: mockAppState({ canAdmin: false }),
currentUser: { ...LOGGED_IN_USER },
location: { pathname: '/documentation/' }
})
await shouldNotHaveModals(
getWrapper({
- canAdmin: false,
+ appState: mockAppState({ canAdmin: false }),
currentUser: { ...LOGGED_IN_USER },
location: { pathname: '/create-organization' }
})
function getWrapper(props: Partial<StartupModal['props']> = {}) {
return shallow<StartupModal>(
<StartupModal
- canAdmin={true}
- currentEdition={EditionKey.enterprise}
+ appState={mockAppState({ edition: EditionKey.enterprise, canAdmin: true })}
currentUser={LOGGED_IN_USER}
location={{ pathname: 'foo/bar' }}
router={{ push: jest.fn() }}
titleTemplate="%s - layout.settings"
/>
<SettingsNav
- extensions={
- Array [
- Object {
- "key": "foo",
- "name": "Foo",
- },
- ]
- }
+ extensions={Array []}
fetchPendingPlugins={[Function]}
fetchSystemStatus={[Function]}
location={
"systemStatus": "UP",
}
}
- />
+ >
+ <div
+ adminPages={Array []}
+ />
+ </ContextProvider>
</div>
`;
exports[`should render correctly 1`] = `
<SuggestionsProvider>
<A11yProvider>
- <Connect(withRouter(StartupModal))>
+ <Connect(withRouter(withAppStateContext(StartupModal)))>
<A11ySkipLinks />
<div
className="global-container"
className="page-container"
>
<Workspace>
- <Connect(withAppState(IndexationContextProvider))>
+ <withAppStateContext(IndexationContextProvider)>
<LanguageContextProvider>
<MetricContextProvider>
<Connect(GlobalNav)
/>
<Connect(GlobalMessages) />
<Connect(withCurrentUser(withIndexationContext(IndexationNotification))) />
- <Connect(withCurrentUser(Connect(withAppState(UpdateNotification))))
+ <Connect(withCurrentUser(withAppStateContext(UpdateNotification)))
dismissable={true}
/>
<ChildComponent />
</MetricContextProvider>
</LanguageContextProvider>
- </Connect(withAppState(IndexationContextProvider))>
+ </withAppStateContext(IndexationContextProvider)>
</Workspace>
</div>
<Connect(Connect(withCurrentUser(PromotionNotification))) />
</div>
- <Connect(GlobalFooter) />
+ <withAppStateContext(GlobalFooter) />
</div>
- </Connect(withRouter(StartupModal))>
+ </Connect(withRouter(withAppStateContext(StartupModal)))>
</A11yProvider>
</SuggestionsProvider>
`;
>
Community Edition
</li>
+ <li
+ className="page-footer-menu-item"
+ >
+ footer.version_x.1.0
+ </li>
<li
className="page-footer-menu-item"
>
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { AppState } from '../../../types/types';
+
+const defaultAppState = {
+ authenticationError: false,
+ authorizationError: false,
+ edition: undefined,
+ productionDatabase: true,
+ qualifiers: [],
+ settings: {},
+ version: ''
+};
+export const AppStateContext = React.createContext<AppState>(defaultAppState);
--- /dev/null
+/* eslint-disable no-console */
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { AppState } from '../../../types/types';
+import { AppStateContext } from './AppStateContext';
+
+export interface AppStateContextProviderProps {
+ appState: AppState;
+}
+
+export default function AppStateContextProvider({
+ appState,
+ children
+}: React.PropsWithChildren<AppStateContextProviderProps>) {
+ return <AppStateContext.Provider value={appState}>{children}</AppStateContext.Provider>;
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockAppState } from '../../../../helpers/testMocks';
+import { AppState } from '../../../../types/types';
+import withAppStateContext from '../withAppStateContext';
+
+const appState = mockAppState();
+
+jest.mock('../AppStateContext', () => {
+ return {
+ AppStateContext: {
+ Consumer: ({ children }: { children: (props: {}) => React.ReactNode }) => {
+ return children(appState);
+ }
+ }
+ };
+});
+
+class Wrapped extends React.Component<{ appState: AppState }> {
+ render() {
+ return <div />;
+ }
+}
+
+const UnderTest = withAppStateContext(Wrapped);
+
+it('should inject appState', () => {
+ const wrapper = shallow(<UnderTest />);
+ expect(wrapper.dive().type()).toBe(Wrapped);
+ expect(wrapper.dive<Wrapped>().props().appState).toEqual(appState);
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { getWrappedDisplayName } from '../../../components/hoc/utils';
+import { AppState } from '../../../types/types';
+import { AppStateContext } from './AppStateContext';
+
+export interface WithAppStateContextProps {
+ appState: AppState;
+}
+
+export default function withAppStateContext<P>(
+ WrappedComponent: React.ComponentType<P & WithAppStateContextProps>
+) {
+ return class WithAppStateContext extends React.PureComponent<
+ Omit<P, keyof WithAppStateContextProps>
+ > {
+ static displayName = getWrappedDisplayName(WrappedComponent, 'withAppStateContext');
+
+ render() {
+ return (
+ <AppStateContext.Consumer>
+ {appState => <WrappedComponent appState={appState} {...(this.props as P)} />}
+ </AppStateContext.Consumer>
+ );
+ }
+ };
+}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
-import { getAppState, Store } from '../../../store/rootReducer';
import { Extension as TypeExtension } from '../../../types/types';
import NotFound from '../NotFound';
import Extension from './Extension';
params: { extensionKey: string; pluginKey: string };
}
-function GlobalAdminPageExtension(props: Props) {
- const { extensionKey, pluginKey } = props.params;
- const extension = (props.adminPages || []).find(p => p.key === `${pluginKey}/${extensionKey}`);
+export default function GlobalAdminPageExtension(props: Props) {
+ const {
+ params: { extensionKey, pluginKey },
+ adminPages
+ } = props;
+ const extension = (adminPages || []).find(p => p.key === `${pluginKey}/${extensionKey}`);
return extension ? <Extension extension={extension} /> : <NotFound withContainer={false} />;
}
-
-const mapStateToProps = (state: Store) => ({
- adminPages: getAppState(state).adminPages
-});
-
-export default connect(mapStateToProps)(GlobalAdminPageExtension);
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
-import { getAppState, Store } from '../../../store/rootReducer';
-import { Extension as TypeExtension } from '../../../types/types';
+import { AppState } from '../../../types/types';
+import withAppStateContext from '../app-state/withAppStateContext';
import NotFound from '../NotFound';
import Extension from './Extension';
interface Props {
- globalPages: TypeExtension[] | undefined;
+ appState: AppState;
params: { extensionKey: string; pluginKey: string };
}
function GlobalPageExtension(props: Props) {
- const { extensionKey, pluginKey } = props.params;
- const extension = (props.globalPages || []).find(p => p.key === `${pluginKey}/${extensionKey}`);
+ const {
+ params: { extensionKey, pluginKey },
+ appState: { globalPages }
+ } = props;
+ const extension = (globalPages || []).find(p => p.key === `${pluginKey}/${extensionKey}`);
return extension ? <Extension extension={extension} /> : <NotFound withContainer={false} />;
}
-const mapStateToProps = (state: Store) => ({
- globalPages: getAppState(state).globalPages
-});
-
-export default connect(mapStateToProps)(GlobalPageExtension);
+export default withAppStateContext(GlobalPageExtension);
*/
/* eslint-disable react/no-unused-state */
import * as React from 'react';
-import { withAppState } from '../../../components/hoc/withAppState';
import { IndexationContextInterface, IndexationStatus } from '../../../types/indexation';
import { AppState } from '../../../types/types';
+import withAppStateContext from '../app-state/withAppStateContext';
import { IndexationContext } from './IndexationContext';
import IndexationNotificationHelper from './IndexationNotificationHelper';
-interface Props {
- appState: Pick<AppState, 'needIssueSync'>;
+export interface IndexationContextProviderProps {
+ appState: AppState;
}
export class IndexationContextProvider extends React.PureComponent<
- React.PropsWithChildren<Props>,
+ React.PropsWithChildren<IndexationContextProviderProps>,
IndexationContextInterface
> {
mounted = false;
}
}
-export default withAppState(IndexationContextProvider);
+export default withAppStateContext(IndexationContextProvider);
*/
import { mount } from 'enzyme';
import * as React from 'react';
+import { mockAppState } from '../../../../helpers/testMocks';
import { IndexationStatus } from '../../../../types/indexation';
import { IndexationContext } from '../IndexationContext';
-import { IndexationContextProvider } from '../IndexationContextProvider';
+import {
+ IndexationContextProvider,
+ IndexationContextProviderProps
+} from '../IndexationContextProvider';
import IndexationNotificationHelper from '../IndexationNotificationHelper';
beforeEach(() => jest.clearAllMocks());
});
it('should not start polling if no issue sync is needed', () => {
- const wrapper = mountRender({ appState: { needIssueSync: false } });
+ const appState = mockAppState({ needIssueSync: false });
+ const wrapper = mountRender({ appState });
expect(IndexationNotificationHelper.startPolling).not.toHaveBeenCalled();
expect(IndexationNotificationHelper.stopPolling).toHaveBeenCalled();
});
-function mountRender(props?: IndexationContextProvider['props']) {
+function mountRender(props?: IndexationContextProviderProps) {
return mount<IndexationContextProvider>(
- <IndexationContextProvider appState={{ needIssueSync: true }} {...props}>
+ <IndexationContextProvider appState={mockAppState({ needIssueSync: true, ...props?.appState })}>
<TestComponent />
</IndexationContextProvider>
);
<IndexationContextProvider
appState={
Object {
+ "edition": "community",
"needIssueSync": true,
+ "productionDatabase": true,
+ "qualifiers": Array [
+ "TRK",
+ ],
+ "settings": Object {},
+ "version": "1.0",
}
}
>
import * as React from 'react';
import { Link } from 'react-router';
import { isValidLicense } from '../../../../api/marketplace';
-import { withAppState } from '../../../../components/hoc/withAppState';
import { Alert } from '../../../../components/ui/Alert';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
import { ComponentQualifier } from '../../../../types/component';
import { Task } from '../../../../types/tasks';
import { AppState } from '../../../../types/types';
+import withAppStateContext from '../../app-state/withAppStateContext';
interface Props {
- appState: Pick<AppState, 'canAdmin'>;
+ appState: AppState;
currentTask?: Task;
}
};
render() {
- const { currentTask } = this.props;
+ const { currentTask, appState } = this.props;
const { isValidLicense, loading } = this.state;
if (loading || !currentTask || !currentTask.errorType) {
return (
<Alert display="banner" variant="error">
<span className="little-spacer-right">{currentTask.errorMessage}</span>
- {this.props.appState.canAdmin ? (
+ {appState.canAdmin ? (
<Link to="/admin/extension/license/app">
{translate('license.component_navigation.button', currentTask.errorType)}.
</Link>
}
}
-export default withAppState(ComponentNavLicenseNotif);
+export default withAppStateContext(ComponentNavLicenseNotif);
import { Link, LinkProps } from 'react-router';
import Dropdown from '../../../../components/controls/Dropdown';
import Tooltip from '../../../../components/controls/Tooltip';
-import { withAppState } from '../../../../components/hoc/withAppState';
import BulletListIcon from '../../../../components/icons/BulletListIcon';
import DropdownIcon from '../../../../components/icons/DropdownIcon';
import NavBarTabs from '../../../../components/ui/NavBarTabs';
import { BranchLike, BranchParameters } from '../../../../types/branch-like';
import { ComponentQualifier, isPortfolioLike } from '../../../../types/component';
import { AppState, Component, Extension } from '../../../../types/types';
+import withAppStateContext from '../../app-state/withAppStateContext';
import './Menu.css';
const SETTINGS_URLS = [
];
interface Props {
- appState: Pick<AppState, 'branchesEnabled'>;
+ appState: AppState;
branchLike: BranchLike | undefined;
branchLikes: BranchLike[] | undefined;
component: Component;
}
}
-export default withAppState(Menu);
+export default withAppStateContext(Menu);
import * as React from 'react';
import { isValidLicense } from '../../../../../api/marketplace';
import { mockTask } from '../../../../../helpers/mocks/tasks';
+import { mockAppState } from '../../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../../helpers/testUtils';
import { TaskStatuses } from '../../../../../types/tasks';
import { ComponentNavLicenseNotif } from '../ComponentNavLicenseNotif';
expect(wrapper).toMatchSnapshot();
wrapper = getWrapper({
- appState: { canAdmin: false },
+ appState: mockAppState({ canAdmin: false }),
currentTask: mockTask({
status: TaskStatuses.Failed,
errorType: 'LICENSING',
function getWrapper(props: Partial<ComponentNavLicenseNotif['props']> = {}) {
return shallow(
<ComponentNavLicenseNotif
- appState={{ canAdmin: true }}
+ appState={mockAppState({ canAdmin: true })}
currentTask={mockTask({ errorMessage: 'Foo', errorType: 'LICENSING' })}
{...props}
/>
mockPullRequest
} from '../../../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../../../helpers/mocks/component';
+import { mockAppState } from '../../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../../types/component';
import { Menu } from '../Menu';
function shallowRender(props: Partial<Menu['props']>) {
return shallow<Menu>(
<Menu
- appState={{ branchesEnabled: true }}
+ appState={mockAppState({ branchesEnabled: true })}
branchLike={mainBranch}
branchLikes={[mainBranch]}
component={baseComponent}
warnings={Array []}
/>
</div>
- <Connect(withAppState(Menu))
+ <withAppStateContext(Menu)
branchLikes={Array []}
component={
Object {
warnings={Array []}
/>
</div>
- <Connect(withAppState(Menu))
+ <withAppStateContext(Menu)
branchLikes={Array []}
component={
Object {
warnings={Array []}
/>
</div>
- <Connect(withAppState(Menu))
+ <withAppStateContext(Menu)
branchLikes={Array []}
component={
Object {
warnings={Array []}
/>
</div>
- <Connect(withAppState(Menu))
+ <withAppStateContext(Menu)
branchLikes={Array []}
component={
Object {
warnings={Array []}
/>
</div>
- <Connect(withAppState(Menu))
+ <withAppStateContext(Menu)
branchLikes={Array []}
component={
Object {
}
/>
</div>
- <Connect(withAppState(Menu))
+ <withAppStateContext(Menu)
branchLikes={Array []}
component={
Object {
`;
exports[`renders correctly: license issue 1`] = `
-<Connect(withAppState(ComponentNavLicenseNotif))
+<withAppStateContext(ComponentNavLicenseNotif)
currentTask={
Object {
"analysisId": "x123",
favorite={false}
qualifier="TRK"
/>
- <Connect(withAppState(Component))
+ <withAppStateContext(Component)
branchLikes={
Array [
Object {
import classNames from 'classnames';
import * as React from 'react';
import Toggler from '../../../../../components/controls/Toggler';
-import { withAppState } from '../../../../../components/hoc/withAppState';
import { ProjectAlmBindingResponse } from '../../../../../types/alm-settings';
import { BranchLike } from '../../../../../types/branch-like';
import { AppState, Component } from '../../../../../types/types';
+import withAppStateContext from '../../../app-state/withAppStateContext';
import './BranchLikeNavigation.css';
import CurrentBranchLike from './CurrentBranchLike';
import Menu from './Menu';
export interface BranchLikeNavigationProps {
- appState: Pick<AppState, 'branchesEnabled'>;
+ appState: AppState;
branchLikes: BranchLike[];
component: Component;
currentBranchLike: BranchLike;
);
}
-export default withAppState(React.memo(BranchLikeNavigation));
+export default withAppStateContext(React.memo(BranchLikeNavigation));
import * as React from 'react';
import { connect } from 'react-redux';
import NavBar from '../../../../components/ui/NavBar';
-import { getAppState, getCurrentUser, Store } from '../../../../store/rootReducer';
-import { AppState, CurrentUser } from '../../../../types/types';
+import { getCurrentUser, Store } from '../../../../store/rootReducer';
+import { CurrentUser } from '../../../../types/types';
import { rawSizes } from '../../../theme';
import EmbedDocsPopupHelper from '../../embed-docs-modal/EmbedDocsPopupHelper';
import Search from '../../search/Search';
import GlobalNavUser from './GlobalNavUser';
export interface GlobalNavProps {
- appState: Pick<AppState, 'canAdmin' | 'globalPages' | 'qualifiers'>;
currentUser: CurrentUser;
location: { pathname: string };
}
export function GlobalNav(props: GlobalNavProps) {
- const { appState, currentUser, location } = props;
+ const { currentUser, location } = props;
return (
<NavBar className="navbar-global" height={rawSizes.globalNavHeightRaw} id="global-navigation">
<GlobalNavBranding />
- <GlobalNavMenu appState={appState} currentUser={currentUser} location={location} />
+ <GlobalNavMenu currentUser={currentUser} location={location} />
<ul className="global-navbar-menu global-navbar-menu-right">
<EmbedDocsPopupHelper />
const mapStateToProps = (state: Store) => {
return {
- currentUser: getCurrentUser(state),
- appState: getAppState(state)
+ currentUser: getCurrentUser(state)
};
};
import { getQualityGatesUrl } from '../../../../helpers/urls';
import { ComponentQualifier } from '../../../../types/component';
import { AppState, CurrentUser, Extension } from '../../../../types/types';
+import withAppStateContext from '../../app-state/withAppStateContext';
interface Props {
- appState: Pick<AppState, 'canAdmin' | 'globalPages' | 'qualifiers'>;
+ appState: AppState;
currentUser: CurrentUser;
location: { pathname: string };
}
-export default class GlobalNavMenu extends React.PureComponent<Props> {
+export class GlobalNavMenu extends React.PureComponent<Props> {
renderProjects() {
const active =
this.props.location.pathname.startsWith('/projects') &&
);
}
}
+
+export default withAppStateContext(GlobalNavMenu);
// https://stackoverflow.com/questions/43375079/redux-warning-only-appearing-in-tests
jest.mock('../../../../../store/rootReducer');
-const appState: GlobalNavProps['appState'] = {
- globalPages: [],
- canAdmin: false,
- qualifiers: []
-};
const location = { pathname: '' };
it('should render correctly', async () => {
});
function shallowRender(props: Partial<GlobalNavProps> = {}) {
- return shallow(
- <GlobalNav
- appState={appState}
- currentUser={{ isLoggedIn: false }}
- location={location}
- {...props}
- />
- );
+ return shallow(<GlobalNav currentUser={{ isLoggedIn: false }} location={location} {...props} />);
}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import GlobalNavMenu from '../GlobalNavMenu';
+import { mockAppState } from '../../../../../helpers/testMocks';
+import { GlobalNavMenu } from '../GlobalNavMenu';
it('should work with extensions', () => {
- const appState = {
+ const appState = mockAppState({
globalPages: [{ key: 'foo', name: 'Foo' }],
qualifiers: ['TRK']
- };
+ });
const currentUser = {
isLoggedIn: false
};
});
it('should show administration menu if the user has the rights', () => {
- const appState = {
+ const appState = mockAppState({
canAdmin: true,
globalPages: [],
qualifiers: ['TRK']
- };
+ });
const currentUser = {
isLoggedIn: false
};
id="global-navigation"
>
<Connect(GlobalNavBranding) />
- <GlobalNavMenu
- appState={
- Object {
- "canAdmin": false,
- "globalPages": Array [],
- "qualifiers": Array [],
- }
- }
+ <withAppStateContext(GlobalNavMenu)
currentUser={
Object {
"isLoggedIn": false,
id="global-navigation"
>
<Connect(GlobalNavBranding) />
- <GlobalNavMenu
- appState={
- Object {
- "canAdmin": false,
- "globalPages": Array [],
- "qualifiers": Array [],
- }
- }
+ <withAppStateContext(GlobalNavMenu)
currentUser={
Object {
"isLoggedIn": true,
import { groupBy, isEmpty, mapValues } from 'lodash';
import * as React from 'react';
import { getSystemUpgrades } from '../../../api/system';
-import { withAppState } from '../../../components/hoc/withAppState';
import { withCurrentUser } from '../../../components/hoc/withCurrentUser';
import { Alert, AlertVariant } from '../../../components/ui/Alert';
import DismissableAlert from '../../../components/ui/DismissableAlert';
import { Permissions } from '../../../types/permissions';
import { SystemUpgrade } from '../../../types/system';
import { AppState, CurrentUser, Dict } from '../../../types/types';
+import withAppStateContext from '../app-state/withAppStateContext';
import './UpdateNotification.css';
const MONTH_BEFOR_PREVIOUS_LTS_NOTIFICATION = 6;
interface Props {
dismissable: boolean;
- appState: Pick<AppState, 'version'>;
+ appState: AppState;
currentUser: CurrentUser;
}
}
}
-export default withCurrentUser(withAppState(UpdateNotification));
+export default withCurrentUser(withAppStateContext(UpdateNotification));
Promise.all([loadL10nBundle(), loadUser(), loadAppState(), loadApp()]).then(
([l10nBundle, user, appState, startReactApp]) => {
- startReactApp(l10nBundle.locale, user, appState);
+ startReactApp(l10nBundle.locale, appState, user);
},
error => {
if (isResponse(error) && error.status === 401) {
} else {
// login, maintenance or setup pages
- const appStatePromise: Promise<AppState | undefined> = new Promise(resolve => {
+ const appStatePromise: Promise<AppState> = new Promise(resolve => {
loadAppState()
.then(data => {
resolve(data);
})
.catch(() => {
- resolve(undefined);
+ resolve({
+ authenticationError: false,
+ authorizationError: false,
+ edition: undefined,
+ productionDatabase: true,
+ qualifiers: [],
+ settings: {},
+ version: ''
+ });
});
});
Promise.all([loadL10nBundle(), appStatePromise, loadApp()]).then(
([l10nBundle, appState, startReactApp]) => {
- startReactApp(l10nBundle.locale, undefined, appState);
+ startReactApp(l10nBundle.locale, appState);
},
error => {
logError(error);
import getHistory from '../../helpers/getHistory';
import { AppState, CurrentUser } from '../../types/types';
import App from '../components/App';
+import AppStateContextProvider from '../components/app-state/AppStateContextProvider';
import GlobalContainer from '../components/GlobalContainer';
import { PageContext } from '../components/indexation/PageUnavailableDueToIndexation';
import MigrationContainer from '../components/MigrationContainer';
);
}
-export default function startReactApp(
- lang: string,
- currentUser?: CurrentUser,
- appState?: AppState
-) {
+export default function startReactApp(lang: string, appState: AppState, currentUser?: CurrentUser) {
attachToGlobal();
const el = document.getElementById('content');
render(
<HelmetProvider>
<Provider store={store}>
- <IntlProvider defaultLocale={lang} locale={lang}>
- <Router history={history} onUpdate={handleUpdate}>
- {renderRedirects()}
+ <AppStateContextProvider appState={appState}>
+ <IntlProvider defaultLocale={lang} locale={lang}>
+ <Router history={history} onUpdate={handleUpdate}>
+ {renderRedirects()}
- <Route
- path="formatting/help"
- component={lazyLoadComponent(() => import('../components/FormattingHelp'))}
- />
-
- <Route component={lazyLoadComponent(() => import('../components/SimpleContainer'))}>
- <Route path="maintenance">{maintenanceRoutes}</Route>
- <Route path="setup">{setupRoutes}</Route>
- </Route>
-
- <Route component={MigrationContainer}>
<Route
- component={lazyLoadComponent(() =>
- import('../components/SimpleSessionsContainer')
- )}>
- <RouteWithChildRoutes path="/sessions" childRoutes={sessionsRoutes} />
+ path="formatting/help"
+ component={lazyLoadComponent(() => import('../components/FormattingHelp'))}
+ />
+
+ <Route component={lazyLoadComponent(() => import('../components/SimpleContainer'))}>
+ <Route path="maintenance">{maintenanceRoutes}</Route>
+ <Route path="setup">{setupRoutes}</Route>
</Route>
- <Route path="/" component={App}>
- <IndexRoute component={lazyLoadComponent(() => import('../components/Landing'))} />
+ <Route component={MigrationContainer}>
+ <Route
+ component={lazyLoadComponent(() =>
+ import('../components/SimpleSessionsContainer')
+ )}>
+ <RouteWithChildRoutes path="/sessions" childRoutes={sessionsRoutes} />
+ </Route>
+
+ <Route path="/" component={App}>
+ <IndexRoute
+ component={lazyLoadComponent(() => import('../components/Landing'))}
+ />
+
+ <Route component={GlobalContainer}>
+ <RouteWithChildRoutes path="account" childRoutes={accountRoutes} />
+ <RouteWithChildRoutes path="coding_rules" childRoutes={codingRulesRoutes} />
+ <RouteWithChildRoutes path="documentation" childRoutes={documentationRoutes} />
+ <Route
+ path="extension/:pluginKey/:extensionKey"
+ component={lazyLoadComponent(() =>
+ import('../components/extensions/GlobalPageExtension')
+ )}
+ />
+ <Route
+ path="issues"
+ component={withIndexationGuard(Issues, PageContext.Issues)}
+ />
+ <RouteWithChildRoutes path="projects" childRoutes={projectsRoutes} />
+ <RouteWithChildRoutes path="quality_gates" childRoutes={qualityGatesRoutes} />
+ <Route
+ path="portfolios"
+ component={lazyLoadComponent(() =>
+ import('../components/extensions/PortfoliosPage')
+ )}
+ />
+ <RouteWithChildRoutes path="profiles" childRoutes={qualityProfilesRoutes} />
+ <RouteWithChildRoutes path="web_api" childRoutes={webAPIRoutes} />
- <Route component={GlobalContainer}>
- <RouteWithChildRoutes path="account" childRoutes={accountRoutes} />
- <RouteWithChildRoutes path="coding_rules" childRoutes={codingRulesRoutes} />
- <RouteWithChildRoutes path="documentation" childRoutes={documentationRoutes} />
+ {renderComponentRoutes()}
+
+ {renderAdminRoutes()}
+ </Route>
+ <Route
+ // We don't want this route to have any menu.
+ // That is why we can not have it under the accountRoutes
+ path="account/reset_password"
+ component={lazyLoadComponent(() => import('../components/ResetPassword'))}
+ />
<Route
- path="extension/:pluginKey/:extensionKey"
+ // We don't want this route to have any menu. This is why we define it here
+ // rather than under the admin routes.
+ path="admin/change_admin_password"
component={lazyLoadComponent(() =>
- import('../components/extensions/GlobalPageExtension')
+ import('../../apps/change-admin-password/ChangeAdminPasswordApp')
)}
/>
<Route
- path="issues"
- component={withIndexationGuard(Issues, PageContext.Issues)}
+ // We don't want this route to have any menu. This is why we define it here
+ // rather than under the admin routes.
+ path="admin/plugin_risk_consent"
+ component={lazyLoadComponent(() => import('../components/PluginRiskConsent'))}
/>
- <RouteWithChildRoutes path="projects" childRoutes={projectsRoutes} />
- <RouteWithChildRoutes path="quality_gates" childRoutes={qualityGatesRoutes} />
<Route
- path="portfolios"
- component={lazyLoadComponent(() =>
- import('../components/extensions/PortfoliosPage')
- )}
+ path="not_found"
+ component={lazyLoadComponent(() => import('../components/NotFound'))}
+ />
+ <Route
+ path="*"
+ component={lazyLoadComponent(() => import('../components/NotFound'))}
/>
- <RouteWithChildRoutes path="profiles" childRoutes={qualityProfilesRoutes} />
- <RouteWithChildRoutes path="web_api" childRoutes={webAPIRoutes} />
-
- {renderComponentRoutes()}
-
- {renderAdminRoutes()}
</Route>
- <Route
- // We don't want this route to have any menu.
- // That is why we can not have it under the accountRoutes
- path="account/reset_password"
- component={lazyLoadComponent(() => import('../components/ResetPassword'))}
- />
- <Route
- // We don't want this route to have any menu. This is why we define it here
- // rather than under the admin routes.
- path="admin/change_admin_password"
- component={lazyLoadComponent(() =>
- import('../../apps/change-admin-password/ChangeAdminPasswordApp')
- )}
- />
- <Route
- // We don't want this route to have any menu. This is why we define it here
- // rather than under the admin routes.
- path="admin/plugin_risk_consent"
- component={lazyLoadComponent(() => import('../components/PluginRiskConsent'))}
- />
- <Route
- path="not_found"
- component={lazyLoadComponent(() => import('../components/NotFound'))}
- />
- <Route
- path="*"
- component={lazyLoadComponent(() => import('../components/NotFound'))}
- />
</Route>
- </Route>
- </Router>
- </IntlProvider>
+ </Router>
+ </IntlProvider>
+ </AppStateContextProvider>
</Provider>
</HelmetProvider>,
el
*/
import * as React from 'react';
import { connect } from 'react-redux';
-import { getAppState, getGlobalSettingValue, Store } from '../../../store/rootReducer';
+import { getGlobalSettingValue, Store } from '../../../store/rootReducer';
import { AdminPageExtension } from '../../../types/extension';
+import { Extension } from '../../../types/types';
import { fetchValues } from '../../settings/store/actions';
import '../style.css';
import { HousekeepingPolicy, RangeOption } from '../utils';
interface Props {
auditHousekeepingPolicy: HousekeepingPolicy;
fetchValues: typeof fetchValues;
- hasGovernanceExtension?: boolean;
+ adminPages: Extension[];
}
interface State {
dateRange?: { from?: Date; to?: Date };
+ hasGovernanceExtension?: boolean;
downloadStarted: boolean;
selection: RangeOption;
}
export class AuditApp extends React.PureComponent<Props, State> {
- state: State = {
- downloadStarted: false,
- selection: RangeOption.Today
- };
+ constructor(props: Props) {
+ super(props);
+ const hasGovernanceExtension = Boolean(
+ props.adminPages?.find(e => e.key === AdminPageExtension.GovernanceConsole)
+ );
+ this.state = {
+ downloadStarted: false,
+ selection: RangeOption.Today,
+ hasGovernanceExtension
+ };
+ }
componentDidMount() {
- const { hasGovernanceExtension } = this.props;
+ const { hasGovernanceExtension } = this.state;
+
if (hasGovernanceExtension) {
this.props.fetchValues(['sonar.dbcleaner.auditHousekeeping']);
}
};
render() {
- const { hasGovernanceExtension, auditHousekeepingPolicy } = this.props;
+ const { hasGovernanceExtension, ...auditAppRendererProps } = this.state;
+ const { auditHousekeepingPolicy } = this.props;
+
+ if (!hasGovernanceExtension) {
+ return null;
+ }
- return hasGovernanceExtension ? (
+ return (
<AuditAppRenderer
handleDateSelection={this.handleDateSelection}
handleOptionSelection={this.handleOptionSelection}
handleStartDownload={this.handleStartDownload}
housekeepingPolicy={auditHousekeepingPolicy || HousekeepingPolicy.Monthly}
- {...this.state}
+ {...auditAppRendererProps}
/>
- ) : null;
+ );
}
}
const mapStateToProps = (state: Store) => {
const settingValue = getGlobalSettingValue(state, 'sonar.dbcleaner.auditHousekeeping');
- const { adminPages } = getAppState(state);
- const hasGovernanceExtension = Boolean(
- adminPages?.find(e => e.key === AdminPageExtension.GovernanceConsole)
- );
return {
- auditHousekeepingPolicy: settingValue?.value as HousekeepingPolicy,
- hasGovernanceExtension
+ auditHousekeepingPolicy: settingValue?.value as HousekeepingPolicy
};
};
import { shallow } from 'enzyme';
import * as React from 'react';
import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { AdminPageExtension } from '../../../../types/extension';
import { HousekeepingPolicy, RangeOption } from '../../utils';
import { AuditApp } from '../AuditApp';
import AuditAppRenderer from '../AuditAppRenderer';
it('should do nothing if governance is not available', async () => {
const fetchValues = jest.fn();
- const wrapper = shallowRender({ fetchValues, hasGovernanceExtension: false });
+ const wrapper = shallowRender({ fetchValues, adminPages: [] });
await waitAndUpdate(wrapper);
expect(wrapper.type()).toBeNull();
<AuditApp
auditHousekeepingPolicy={HousekeepingPolicy.Monthly}
fetchValues={jest.fn()}
- hasGovernanceExtension={true}
+ adminPages={[{ key: AdminPageExtension.GovernanceConsole, name: 'name' }]}
{...props}
/>
);
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { colors } from '../../../app/theme';
import { ClearButton } from '../../../components/controls/buttons';
import ConfirmButton from '../../../components/controls/ConfirmButton';
import Tooltip from '../../../components/controls/Tooltip';
import { translate } from '../../../helpers/l10n';
-import { getAppState, Store } from '../../../store/rootReducer';
+import { AppState } from '../../../types/types';
export interface Props {
- isSystemAdmin?: boolean;
+ appState: AppState;
onCancelAllPending: () => void;
pendingCount?: number;
}
-export function StatPendingCount({ isSystemAdmin, onCancelAllPending, pendingCount }: Props) {
+export function StatPendingCount({ appState, onCancelAllPending, pendingCount }: Props) {
if (pendingCount === undefined) {
return null;
}
<span className="emphasised-measure">{pendingCount}</span>
<span className="little-spacer-left display-inline-flex-center">
{translate('background_tasks.pending')}
- {isSystemAdmin && pendingCount > 0 && (
+ {appState.canAdmin && pendingCount > 0 && (
<ConfirmButton
cancelButtonText={translate('close')}
confirmButtonText={translate('background_tasks.cancel_all_tasks.submit')}
);
}
-const mapStateToProps = (state: Store) => ({
- isSystemAdmin: getAppState(state).canAdmin
-});
-
-export default connect(mapStateToProps)(StatPendingCount);
+export default withAppStateContext(StatPendingCount);
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockAppState } from '../../../../helpers/testMocks';
import { Props, StatPendingCount } from '../StatPendingCount';
it('should render correctly', () => {
.exists()
).toBe(false);
expect(
- shallowRender({ isSystemAdmin: false })
+ shallowRender({ appState: mockAppState({ canAdmin: false }) })
.find('ConfirmButton')
.exists()
).toBe(false);
function shallowRender(props: Partial<Props> = {}) {
return shallow(
<StatPendingCount
- isSystemAdmin={true}
+ appState={mockAppState({ canAdmin: true })}
onCancelAllPending={jest.fn()}
pendingCount={5}
{...props}
<section
className="big-spacer-top big-spacer-bottom"
>
- <Connect(StatPendingCount)
+ <withAppStateContext(StatPendingCount)
onCancelAllPending={[MockFunction]}
pendingCount={2}
/>
<section
className="big-spacer-top big-spacer-bottom"
>
- <Connect(StatPendingCount)
+ <withAppStateContext(StatPendingCount)
onCancelAllPending={[MockFunction]}
pendingCount={2}
/>
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
import { changePassword } from '../../api/users';
+import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import { Location, withRouter } from '../../components/hoc/withRouter';
-import { getAppState, Store } from '../../store/rootReducer';
+import { AppState } from '../../types/types';
import ChangeAdminPasswordAppRenderer from './ChangeAdminPasswordAppRenderer';
import { DEFAULT_ADMIN_LOGIN, DEFAULT_ADMIN_PASSWORD } from './constants';
interface Props {
- canAdmin?: boolean;
- instanceUsesDefaultAdminCredentials?: boolean;
+ appState: AppState;
location: Location;
}
passwordValue: '',
confirmPasswordValue: '',
submitting: false,
- success: !props.instanceUsesDefaultAdminCredentials
+ success: !props.appState.instanceUsesDefaultAdminCredentials
};
}
};
render() {
- const { canAdmin, location } = this.props;
+ const {
+ appState: { canAdmin },
+ location
+ } = this.props;
const { canSubmit, confirmPasswordValue, passwordValue, submitting, success } = this.state;
return (
<ChangeAdminPasswordAppRenderer
}
}
-export const mapStateToProps = (state: Store) => {
- const { canAdmin, instanceUsesDefaultAdminCredentials } = getAppState(state);
- return { canAdmin, instanceUsesDefaultAdminCredentials };
-};
-
-export default connect(mapStateToProps)(withRouter(ChangeAdminPasswordApp));
+export default withRouter(withAppStateContext(ChangeAdminPasswordApp));
import { shallow } from 'enzyme';
import * as React from 'react';
import { changePassword } from '../../../api/users';
-import { mockLocation } from '../../../helpers/testMocks';
+import { mockAppState, mockLocation } from '../../../helpers/testMocks';
import { waitAndUpdate } from '../../../helpers/testUtils';
-import { getAppState, Store } from '../../../store/rootReducer';
-import { ChangeAdminPasswordApp, mapStateToProps } from '../ChangeAdminPasswordApp';
+import { ChangeAdminPasswordApp } from '../ChangeAdminPasswordApp';
import { DEFAULT_ADMIN_LOGIN, DEFAULT_ADMIN_PASSWORD } from '../constants';
jest.mock('react-redux', () => ({
connect: jest.fn(() => (a: any) => a)
}));
-jest.mock('../../../store/rootReducer', () => ({
- ...jest.requireActual('../../../store/rootReducer'),
- getAppState: jest.fn()
-}));
-
jest.mock('../../../api/users', () => ({
changePassword: jest.fn().mockResolvedValue(null)
}));
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
- expect(shallowRender({ instanceUsesDefaultAdminCredentials: undefined })).toMatchSnapshot(
- 'admin is not using the default password'
- );
+ expect(
+ shallowRender({ appState: mockAppState({ instanceUsesDefaultAdminCredentials: undefined }) })
+ ).toMatchSnapshot('admin is not using the default password');
});
it('should correctly handle input changes', () => {
expect(wrapper.state().success).toBe(false);
});
-describe('redux', () => {
- it('should correctly map state props', () => {
- (getAppState as jest.Mock)
- .mockReturnValueOnce({})
- .mockReturnValueOnce({ canAdmin: false, instanceUsesDefaultAdminCredentials: true });
-
- expect(mapStateToProps({} as Store)).toEqual({
- canAdmin: undefined,
- instanceUsesDefaultAdminCredentials: undefined
- });
-
- expect(mapStateToProps({} as Store)).toEqual({
- canAdmin: false,
- instanceUsesDefaultAdminCredentials: true
- });
- });
-});
-
function shallowRender(props: Partial<ChangeAdminPasswordApp['props']> = {}) {
return shallow<ChangeAdminPasswordApp>(
<ChangeAdminPasswordApp
- canAdmin={true}
- instanceUsesDefaultAdminCredentials={true}
+ appState={mockAppState({ canAdmin: true, instanceUsesDefaultAdminCredentials: true })}
location={mockLocation()}
{...props}
/>
exports[`should render correctly: admin is not using the default password 1`] = `
<ChangeAdminPasswordAppRenderer
- canAdmin={true}
confirmPasswordValue=""
location={
Object {
import * as React from 'react';
import { Link } from 'react-router';
import { getFacet } from '../../../api/issues';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import Tooltip from '../../../components/controls/Tooltip';
-import { withAppState } from '../../../components/hoc/withAppState';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import { AppState, RuleDetails } from '../../../types/types';
interface Props {
- appState: Pick<AppState, 'branchesEnabled'>;
+ appState: AppState;
ruleDetails: Pick<RuleDetails, 'key' | 'type'>;
}
}
}
-export default withAppState(RuleDetailsIssues);
+export default withAppStateContext(RuleDetailsIssues);
import { shallow } from 'enzyme';
import * as React from 'react';
import { getFacet } from '../../../../api/issues';
+import { mockAppState } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { RuleDetailsIssues } from '../RuleDetailsIssues';
function shallowRender(props: Partial<RuleDetailsIssues['props']> = {}) {
return shallow(
<RuleDetailsIssues
- appState={{ branchesEnabled: false }}
+ appState={mockAppState({ branchesEnabled: false })}
ruleDetails={{ key: 'foo', type: 'BUG' }}
{...props}
/>
}
}
/>
- <Connect(withAppState(RuleDetailsIssues))
+ <withAppStateContext(RuleDetailsIssues)
ruleDetails={
Object {
"createdAt": "2014-12-16T17:26:54+0100",
*/
import classNames from 'classnames';
import * as React from 'react';
-import { withAppState } from '../../../components/hoc/withAppState';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import ChevronsIcon from '../../../components/icons/ChevronsIcon';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
almCounts: {
[k in AlmKeys]: number;
};
- appState: Pick<AppState, 'canAdmin'>;
+ appState: AppState;
loadingBindings: boolean;
onSelectMode: (mode: CreateProjectModes) => void;
onConfigMode: (mode: AlmKeys) => void;
);
}
-export default withAppState(CreateProjectModeSelection);
+export default withAppStateContext(CreateProjectModeSelection);
import { WithRouterProps } from 'react-router';
import { getAlmSettings } from '../../../api/alm-settings';
import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn';
-import { withAppState } from '../../../components/hoc/withAppState';
import { translate } from '../../../helpers/l10n';
import { getProjectUrl } from '../../../helpers/urls';
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
import { CreateProjectModes } from './types';
interface Props extends Pick<WithRouterProps, 'router' | 'location'> {
- appState: Pick<AppState, 'canAdmin' | 'branchesEnabled'>;
+ appState: AppState;
currentUser: LoggedInUser;
}
}
}
-export default whenLoggedIn(withAppState(CreateProjectPage));
+export default whenLoggedIn(withAppStateContext(CreateProjectPage));
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockAppState } from '../../../../helpers/testMocks';
import { click } from '../../../../helpers/testUtils';
import { AlmKeys } from '../../../../types/alm-settings';
import {
);
expect(
shallowRender(
- { appState: { canAdmin: true } },
+ { appState: mockAppState({ canAdmin: true }) },
{ [AlmKeys.BitbucketServer]: 0, [AlmKeys.GitHub]: 2 }
)
).toMatchSnapshot('invalid configs, admin');
expect(
shallowRender(
- { appState: { canAdmin: true } },
+ { appState: mockAppState({ canAdmin: true }) },
{ [AlmKeys.BitbucketServer]: 0, [AlmKeys.BitbucketCloud]: 0, [AlmKeys.GitHub]: 2 }
)
).toMatchSnapshot('invalid configs, admin');
expect(
shallowRender(
- { appState: { canAdmin: true } },
+ { appState: mockAppState({ canAdmin: true }) },
{
[AlmKeys.Azure]: 0,
[AlmKeys.BitbucketCloud]: 0,
onSelectMode.mockClear();
wrapper = shallowRender(
- { onSelectMode, onConfigMode, appState: { canAdmin: true } },
+ { onSelectMode, onConfigMode, appState: mockAppState({ canAdmin: true }) },
{ [AlmKeys.Azure]: 0 }
);
return shallow<CreateProjectModeSelectionProps>(
<CreateProjectModeSelection
almCounts={almCounts}
- appState={{ canAdmin: false }}
+ appState={mockAppState({ canAdmin: false })}
loadingBindings={false}
onSelectMode={jest.fn()}
onConfigMode={jest.fn()}
import { shallow } from 'enzyme';
import * as React from 'react';
import { getAlmSettings } from '../../../../api/alm-settings';
-import { mockLocation, mockLoggedInUser, mockRouter } from '../../../../helpers/testMocks';
+import {
+ mockAppState,
+ mockLocation,
+ mockLoggedInUser,
+ mockRouter
+} from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { AlmKeys } from '../../../../types/alm-settings';
import AlmBindingDefinitionForm from '../../../settings/components/almIntegration/AlmBindingDefinitionForm';
function shallowRender(props: Partial<CreateProjectPage['props']> = {}) {
return shallow<CreateProjectPage>(
<CreateProjectPage
- appState={{}}
+ appState={mockAppState()}
currentUser={mockLoggedInUser()}
location={mockLocation()}
router={mockRouter()}
className="page page-limited huge-spacer-bottom position-relative"
id="create-project"
>
- <Connect(withAppState(CreateProjectModeSelection))
+ <withAppStateContext(CreateProjectModeSelection)
almCounts={
Object {
"azure": 0,
className="page page-limited huge-spacer-bottom position-relative"
id="create-project"
>
- <Connect(withAppState(CreateProjectModeSelection))
+ <withAppStateContext(CreateProjectModeSelection)
almCounts={
Object {
"azure": 0,
import * as React from 'react';
import { connect } from 'react-redux';
import AdminContext from '../../app/components/AdminContext';
-import { getAppState, getGlobalSettingValue, Store } from '../../store/rootReducer';
+import withAppStateContext from '../../app/components/app-state/withAppStateContext';
+import { getGlobalSettingValue, Store } from '../../store/rootReducer';
import { EditionKey } from '../../types/editions';
-import { RawQuery } from '../../types/types';
+import { AppState, RawQuery } from '../../types/types';
import App from './App';
interface OwnProps {
location: { pathname: string; query: RawQuery };
+ appState: AppState;
}
interface StateToProps {
- currentEdition?: EditionKey;
- standaloneMode?: boolean;
updateCenterActive: boolean;
}
const mapStateToProps = (state: Store) => {
const updateCenterActive = getGlobalSettingValue(state, 'sonar.updatecenter.activate');
return {
- currentEdition: getAppState(state).edition as EditionKey, // TODO: Fix once AppState is no longer ambiant.
- standaloneMode: getAppState(state).standalone,
updateCenterActive: Boolean(updateCenterActive && updateCenterActive.value === 'true')
};
};
function WithAdminContext(props: StateToProps & OwnProps) {
+ const propsToPass = {
+ location: props.location,
+ updateCenterActive: props.updateCenterActive,
+ currentEdition: props.appState.edition as EditionKey,
+ standaloneMode: props.appState.standalone
+ };
+
return (
<AdminContext.Consumer>
{({ fetchPendingPlugins, pendingPlugins }) => (
- <App fetchPendingPlugins={fetchPendingPlugins} pendingPlugins={pendingPlugins} {...props} />
+ <App
+ fetchPendingPlugins={fetchPendingPlugins}
+ pendingPlugins={pendingPlugins}
+ {...propsToPass}
+ />
)}
</AdminContext.Consumer>
);
}
-export default connect(mapStateToProps)(WithAdminContext);
+export default connect(mapStateToProps)(withAppStateContext(WithAdminContext));
*/
import { connect } from 'react-redux';
import { mockStore } from '../../../helpers/testMocks';
-import { getAppState, getGlobalSettingValue } from '../../../store/rootReducer';
-import { EditionKey } from '../../../types/editions';
+import { getGlobalSettingValue } from '../../../store/rootReducer';
import '../AppContainer';
jest.mock('react-redux', () => ({
jest.mock('../../../store/rootReducer', () => {
return {
- getAppState: jest.fn(),
getGlobalSettingValue: jest.fn()
};
});
describe('redux', () => {
it('should correctly map state and dispatch props', () => {
const store = mockStore();
- const edition = EditionKey.developer;
- const standalone = true;
const updateCenterActive = true;
- (getAppState as jest.Mock).mockReturnValue({ edition, standalone });
(getGlobalSettingValue as jest.Mock).mockReturnValueOnce({
value: `${updateCenterActive}`
});
const props = mapStateToProps(store);
expect(props).toEqual({
- currentEdition: edition,
- standaloneMode: standalone,
updateCenterActive
});
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
-import { withAppState } from '../../../components/hoc/withAppState';
import { Router, withRouter } from '../../../components/hoc/withRouter';
import { lazyLoadComponent } from '../../../components/lazyLoadComponent';
import { isPullRequest } from '../../../helpers/branch-like';
const PullRequestOverview = lazyLoadComponent(() => import('../pullRequests/PullRequestOverview'));
interface Props {
- appState: Pick<AppState, 'branchesEnabled'>;
+ appState: AppState;
branchLike?: BranchLike;
branchLikes: BranchLike[];
component: Component;
}
}
-export default withRouter(withAppState(App));
+export default withRouter(withAppStateContext(App));
import { Location } from 'history';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { connect } from 'react-redux';
import { getPermissionTemplates } from '../../../api/permissions';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import { translate } from '../../../helpers/l10n';
-import { getAppState, Store } from '../../../store/rootReducer';
-import { Permission, PermissionTemplate } from '../../../types/types';
+import { AppState, Permission, PermissionTemplate } from '../../../types/types';
import '../../permissions/styles.css';
import { mergeDefaultsToTemplates, mergePermissionsToTemplates, sortPermissions } from '../utils';
import Home from './Home';
interface Props {
location: Location;
- topQualifiers: string[];
+ appState: AppState;
}
interface State {
<Template
refresh={this.requestPermissions}
template={template}
- topQualifiers={this.props.topQualifiers}
+ topQualifiers={this.props.appState.qualifiers}
/>
);
}
permissions={this.state.permissions}
ready={this.state.ready}
refresh={this.requestPermissions}
- topQualifiers={this.props.topQualifiers}
+ topQualifiers={this.props.appState.qualifiers}
/>
);
}
}
}
-const mapStateToProps = (state: Store) => ({ topQualifiers: getAppState(state).qualifiers });
-
-export default connect(mapStateToProps)(App);
+export default withAppStateContext(App);
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockLocation } from '../../../../helpers/testMocks';
+import { mockAppState, mockLocation } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { App } from '../App';
});
function shallowRender(props: Partial<App['props']> = {}) {
- return shallow(<App location={mockLocation()} topQualifiers={['TRK']} {...props} />);
+ return shallow(
+ <App location={mockLocation()} appState={mockAppState({ qualifiers: ['TRK'] })} {...props} />
+ );
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
+import withAppStateContext from '../../../../app/components/app-state/withAppStateContext';
import ListFooter from '../../../../components/controls/ListFooter';
-import { getAppState, Store } from '../../../../store/rootReducer';
import { ComponentQualifier } from '../../../../types/component';
import { AppState, Paging, PermissionGroup, PermissionUser } from '../../../../types/types';
import HoldersList from '../../shared/components/HoldersList';
} from '../../utils';
interface StateProps {
- appState: Pick<AppState, 'qualifiers'>;
+ appState: AppState;
}
interface OwnProps {
const hasPermission = user.permissions.includes(permission);
if (hasPermission) {
return this.props.revokePermissionFromUser(user.login, permission);
- } else {
- return this.props.grantPermissionToUser(user.login, permission);
}
+ return this.props.grantPermissionToUser(user.login, permission);
};
handleToggleGroup = (group: PermissionGroup, permission: string) => {
if (hasPermission) {
return this.props.revokePermissionFromGroup(group.name, permission);
- } else {
- return this.props.grantPermissionToGroup(group.name, permission);
}
+ return this.props.grantPermissionToGroup(group.name, permission);
};
render() {
- const { appState, filter, groups, groupsPaging, users, usersPaging } = this.props;
+ const {
+ appState,
+ filter,
+ groups,
+ groupsPaging,
+ users,
+ usersPaging,
+ loading,
+ query
+ } = this.props;
const l10nPrefix = 'global_permissions';
const hasPortfoliosEnabled = appState.qualifiers.includes(ComponentQualifier.Portfolio);
return (
<>
<HoldersList
- filter={this.props.filter}
- groups={this.props.groups}
- loading={this.props.loading}
+ filter={filter}
+ groups={groups}
+ loading={loading}
onToggleGroup={this.handleToggleGroup}
onToggleUser={this.handleToggleUser}
permissions={permissions}
- query={this.props.query}
- users={this.props.users}>
+ query={query}
+ users={users}>
<SearchForm
- filter={this.props.filter}
+ filter={filter}
onFilter={this.props.onFilter}
onSearch={this.props.onSearch}
- query={this.props.query}
+ query={query}
/>
</HoldersList>
<ListFooter count={count} loadMore={this.props.onLoadMore} total={total} />
}
}
-const mapStateToProps = (state: Store): StateProps => ({
- appState: getAppState(state)
-});
-
-export default connect(mapStateToProps)(AllHoldersList);
+export default withAppStateContext(AllHoldersList);
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockPermissionGroup, mockPermissionUser } from '../../../../../helpers/mocks/permissions';
+import { mockAppState } from '../../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../../types/component';
import { AllHoldersList } from '../AllHoldersList';
expect(shallowRender({ filter: 'groups' })).toMatchSnapshot('filter groups');
expect(
shallowRender({
- appState: { qualifiers: [ComponentQualifier.Project, ComponentQualifier.Application] }
+ appState: mockAppState({
+ qualifiers: [ComponentQualifier.Project, ComponentQualifier.Application]
+ })
})
).toMatchSnapshot('applications available');
expect(
shallowRender({
- appState: { qualifiers: [ComponentQualifier.Project, ComponentQualifier.Portfolio] }
+ appState: mockAppState({
+ qualifiers: [ComponentQualifier.Project, ComponentQualifier.Portfolio]
+ })
})
).toMatchSnapshot('portfolios available');
});
function shallowRender(props: Partial<AllHoldersList['props']> = {}) {
return shallow<AllHoldersList>(
<AllHoldersList
- appState={{ qualifiers: [ComponentQualifier.Project] }}
+ appState={mockAppState({ qualifiers: [ComponentQualifier.Project] })}
filter=""
grantPermissionToGroup={jest.fn()}
grantPermissionToUser={jest.fn()}
<PageHeader
loading={true}
/>
- <Connect(AllHoldersList)
+ <withAppStateContext(AllHoldersList)
filter="all"
grantPermissionToGroup={[Function]}
grantPermissionToUser={[Function]}
<PageHeader
loading={false}
/>
- <Connect(AllHoldersList)
+ <withAppStateContext(AllHoldersList)
filter="all"
grantPermissionToGroup={[Function]}
grantPermissionToUser={[Function]}
import { debounce } from 'lodash';
import * as React from 'react';
import { getNewCodePeriod, resetNewCodePeriod, setNewCodePeriod } from '../../../api/newCodePeriod';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
import { Branch, BranchLike } from '../../../types/branch-like';
import {
+ AppState,
Component,
NewCodePeriod,
NewCodePeriodSettingType,
interface Props {
branchLike: Branch;
branchLikes: BranchLike[];
- branchesEnabled?: boolean;
- canAdmin?: boolean;
component: Component;
+ appState: AppState;
}
interface State {
type: 'PREVIOUS_VERSION'
};
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
branchList: [],
}
fetchLeakPeriodSetting() {
- const { branchLike, branchesEnabled, component } = this.props;
+ const { branchLike, appState, component } = this.props;
this.setState({ loading: true });
Promise.all([
getNewCodePeriod(),
getNewCodePeriod({
- branch: branchesEnabled ? undefined : branchLike.name,
+ branch: appState.branchesEnabled ? undefined : branchLike.name,
project: component.key
})
]).then(
};
render() {
- const { branchesEnabled, canAdmin, component, branchLike } = this.props;
+ const { appState, component, branchLike } = this.props;
const {
analysis,
branchList,
<>
<Suggestions suggestions="project_baseline" />
<div className="page page-limited">
- <AppHeader canAdmin={!!canAdmin} />
+ <AppHeader canAdmin={!!appState.canAdmin} />
{loading ? (
<DeferredSpinner />
) : (
<div className="panel-white project-baseline">
- {branchesEnabled && <h2>{translate('project_baseline.default_setting')}</h2>}
+ {appState.branchesEnabled && <h2>{translate('project_baseline.default_setting')}</h2>}
{generalSetting && overrideGeneralSetting !== undefined && (
<ProjectBaselineSelector
analysis={analysis}
branch={branchLike}
branchList={branchList}
- branchesEnabled={branchesEnabled}
+ branchesEnabled={appState.branchesEnabled}
component={component.key}
currentSetting={currentSetting}
currentSettingValue={currentSettingValue}
{translate('settings.state.saved')}
</span>
</div>
- {generalSetting && branchesEnabled && (
+ {generalSetting && appState.branchesEnabled && (
<div className="huge-spacer-top branch-baseline-selector">
<hr />
<h2>{translate('project_baseline.configure_branches')}</h2>
);
}
}
+
+export default withAppStateContext(App);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { getAppState, Store } from '../../../store/rootReducer';
-import App from './App';
-
-const mapStateToProps = (state: Store) => ({
- branchesEnabled: getAppState(state).branchesEnabled,
- canAdmin: getAppState(state).canAdmin
-});
-
-export default connect(mapStateToProps)(App);
} from '../../../../api/newCodePeriod';
import { mockBranch, mockMainBranch, mockPullRequest } from '../../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockEvent } from '../../../../helpers/testMocks';
+import { mockAppState, mockEvent } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
-import App from '../App';
+import { App } from '../App';
jest.mock('../../../../api/newCodePeriod', () => ({
getNewCodePeriod: jest.fn().mockResolvedValue({}),
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
- wrapper = shallowRender({ branchesEnabled: false });
+ wrapper = shallowRender({ appState: mockAppState({ branchesEnabled: false, canAdmin: true }) });
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot('without branch support');
});
<App
branchLike={mockBranch()}
branchLikes={[mockMainBranch()]}
- branchesEnabled={true}
- canAdmin={true}
+ appState={mockAppState({ branchesEnabled: true, canAdmin: true })}
component={mockComponent()}
{...props}
/>
const routes = [
{
- indexRoute: { component: lazyLoadComponent(() => import('./components/AppContainer')) }
+ indexRoute: { component: lazyLoadComponent(() => import('./components/App')) }
}
];
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
import { getValues } from '../../../api/settings';
-import { getAppState, Store } from '../../../store/rootReducer';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { SettingsKey } from '../../../types/settings';
+import { AppState } from '../../../types/types';
import LifetimeInformationRenderer from './LifetimeInformationRenderer';
interface Props {
- canAdmin?: boolean;
+ appState: AppState;
}
interface State {
}
render() {
- const { canAdmin } = this.props;
+ const {
+ appState: { canAdmin }
+ } = this.props;
const { branchAndPullRequestLifeTimeInDays, loading } = this.state;
return (
}
}
-const mapStoreToProps = (state: Store) => ({
- canAdmin: getAppState(state).canAdmin
-});
-
-export default connect(mapStoreToProps)(LifetimeInformation);
+export default withAppStateContext(LifetimeInformation);
import { shallow } from 'enzyme';
import * as React from 'react';
import { getValues } from '../../../../api/settings';
+import { mockAppState } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { SettingsKey } from '../../../../types/settings';
import { LifetimeInformation } from '../LifetimeInformation';
});
function shallowRender(props: Partial<LifetimeInformation['props']> = {}) {
- return shallow<LifetimeInformation>(<LifetimeInformation canAdmin={true} {...props} />);
+ return shallow<LifetimeInformation>(
+ <LifetimeInformation appState={mockAppState({ canAdmin: true })} {...props} />
+ );
}
<h1>
project_branch_pull_request.page
</h1>
- <Connect(LifetimeInformation) />
+ <withAppStateContext(LifetimeInformation) />
</header>
<BranchLikeTabs
branchLikes={
import * as React from 'react';
import { getActivity } from '../../api/ce';
import { getStatus } from '../../api/project-dump';
+import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import throwGlobalError from '../../app/utils/throwGlobalError';
-import { withAppState } from '../../components/hoc/withAppState';
import { translate } from '../../helpers/l10n';
import { DumpStatus, DumpTask } from '../../types/project-dump';
import { TaskStatuses, TaskTypes } from '../../types/tasks';
const POLL_INTERNAL = 5000;
interface Props {
- appState: Pick<AppState, 'projectImportFeatureEnabled'>;
+ appState: AppState;
component: Component;
}
}
}
-export default withAppState(ProjectDumpApp);
+export default withAppStateContext(ProjectDumpApp);
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
import ListFooter from '../../../components/controls/ListFooter';
import { get, save } from '../../../helpers/storage';
import { isLoggedIn } from '../../../helpers/users';
import { ComponentQualifier } from '../../../types/component';
-import { CurrentUser, RawQuery } from '../../../types/types';
+import { AppState, CurrentUser, RawQuery } from '../../../types/types';
import { hasFilterParams, hasViewParams, parseUrlQuery, Query } from '../query';
import '../styles.css';
import { Facets, Project } from '../types';
currentUser: CurrentUser;
isFavorite: boolean;
location: Pick<Location, 'pathname' | 'query'>;
- qualifiers: ComponentQualifier[];
+ appState: AppState;
router: Pick<Router, 'push' | 'replace'>;
}
/>
<PageSidebar
- applicationsEnabled={this.props.qualifiers.includes(ComponentQualifier.Application)}
+ applicationsEnabled={this.props.appState.qualifiers.includes(
+ ComponentQualifier.Application
+ )}
facets={this.state.facets}
onClearAll={this.handleClearAll}
onQueryChange={this.updateLocationQuery}
}
}
-export default withRouter(AllProjects);
+export default withRouter(withAppStateContext(AllProjects));
*/
import { connect } from 'react-redux';
import { lazyLoadComponent } from '../../../components/lazyLoadComponent';
-import { getAppState, getCurrentUser, Store } from '../../../store/rootReducer';
-import { ComponentQualifier } from '../../../types/component';
+import { getCurrentUser, Store } from '../../../store/rootReducer';
const stateToProps = (state: Store) => ({
- currentUser: getCurrentUser(state),
- qualifiers: getAppState(state).qualifiers as ComponentQualifier[]
+ currentUser: getCurrentUser(state)
});
export default connect(stateToProps)(lazyLoadComponent(() => import('./AllProjects')));
*/
import * as React from 'react';
import { getComponentNavigation } from '../../../api/nav';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import CreateApplicationForm from '../../../app/components/extensions/CreateApplicationForm';
import { Button } from '../../../components/controls/buttons';
-import { withAppState } from '../../../components/hoc/withAppState';
import { withCurrentUser } from '../../../components/hoc/withCurrentUser';
import { Router, withRouter } from '../../../components/hoc/withRouter';
import { translate } from '../../../helpers/l10n';
import { AppState, LoggedInUser } from '../../../types/types';
export interface ApplicationCreationProps {
- appState: Pick<AppState, 'qualifiers'>;
+ appState: AppState;
className?: string;
currentUser: LoggedInUser;
router: Router;
);
}
-export default withAppState(withCurrentUser(withRouter(ApplicationCreation)));
+export default withCurrentUser(withRouter(withAppStateContext(ApplicationCreation)));
import { shallow } from 'enzyme';
import * as React from 'react';
import { get, save } from '../../../../helpers/storage';
+import { mockAppState } from '../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../types/component';
import { Dict } from '../../../../types/types';
import { AllProjects, LS_PROJECTS_SORT, LS_PROJECTS_VIEW } from '../AllProjects';
currentUser={{ isLoggedIn: true }}
isFavorite={false}
location={{ pathname: '/projects', query: {} }}
- qualifiers={[ComponentQualifier.Project, ComponentQualifier.Application]}
+ appState={mockAppState({
+ qualifiers: [ComponentQualifier.Project, ComponentQualifier.Application]
+ })}
router={{ push, replace }}
{...props}
/>
<Connect(withCurrentUser(ProjectCreationMenu))
className="little-spacer-right"
/>
- <Connect(withAppState(Connect(withCurrentUser(withRouter(ApplicationCreation)))))
+ <Connect(withCurrentUser(withRouter(withAppStateContext(ApplicationCreation))))
className="little-spacer-right"
/>
<Connect(HomePageSelect)
<Connect(withCurrentUser(ProjectCreationMenu))
className="little-spacer-right"
/>
- <Connect(withAppState(Connect(withCurrentUser(withRouter(ApplicationCreation)))))
+ <Connect(withCurrentUser(withRouter(withAppStateContext(ApplicationCreation))))
className="little-spacer-right"
/>
<Connect(HomePageSelect)
import { toShortNotSoISOString } from '../../helpers/dates';
import { translate } from '../../helpers/l10n';
import { hasGlobalPermission } from '../../helpers/users';
-import { getAppState, getCurrentUser, Store } from '../../store/rootReducer';
+import { getCurrentUser, Store } from '../../store/rootReducer';
import { Permissions } from '../../types/permissions';
import { SettingsKey } from '../../types/settings';
-import { AppState, LoggedInUser, Visibility } from '../../types/types';
+import { LoggedInUser, Visibility } from '../../types/types';
import CreateProjectForm from './CreateProjectForm';
import Header from './Header';
import Projects from './Projects';
export interface Props {
currentUser: LoggedInUser;
- appState: Pick<AppState, 'qualifiers'>;
}
interface State {
};
render() {
- const { appState, currentUser } = this.props;
+ const { currentUser } = this.props;
const { defaultProjectVisibility } = this.state;
return (
<div className="page page-limited" id="projects-management-page">
query={this.state.query}
ready={this.state.ready}
selection={this.state.selection}
- topLevelQualifiers={appState.qualifiers}
total={this.state.total}
visibility={this.state.visibility}
/>
}
const mapStateToProps = (state: Store) => ({
- appState: getAppState(state),
currentUser: getCurrentUser(state) as LoggedInUser
});
import { sortBy } from 'lodash';
import * as React from 'react';
import { Project } from '../../api/components';
+import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import { Button } from '../../components/controls/buttons';
import Checkbox from '../../components/controls/Checkbox';
import DateInput from '../../components/controls/DateInput';
import SelectLegacy from '../../components/controls/SelectLegacy';
import QualifierIcon from '../../components/icons/QualifierIcon';
import { translate } from '../../helpers/l10n';
-import { Visibility } from '../../types/types';
+import { AppState, Visibility } from '../../types/types';
import BulkApplyTemplateModal from './BulkApplyTemplateModal';
import DeleteModal from './DeleteModal';
query: string;
ready: boolean;
selection: any[];
- topLevelQualifiers: string[];
+ appState: AppState;
total: number;
visibility?: Visibility;
}
const QUALIFIERS_ORDER = ['TRK', 'VW', 'APP'];
-export default class Search extends React.PureComponent<Props, State> {
+export class Search extends React.PureComponent<Props, State> {
mounted = false;
state: State = { bulkApplyTemplateModal: false, deleteModal: false };
getQualifierOptions = () => {
- const options = this.props.topLevelQualifiers.map(q => ({
+ const options = this.props.appState.qualifiers.map(q => ({
label: translate('qualifiers', q),
value: q
}));
);
}
}
+
+export default withAppStateContext(Search);
import { getComponents } from '../../../api/components';
import { changeProjectDefaultVisibility } from '../../../api/permissions';
import { getValues } from '../../../api/settings';
-import { mockAppState, mockLoggedInUser } from '../../../helpers/testMocks';
+import { mockLoggedInUser } from '../../../helpers/testMocks';
import { waitAndUpdate } from '../../../helpers/testUtils';
import { App, Props } from '../App';
-import Search from '../Search';
jest.mock('lodash', () => {
const lodash = jest.requireActual('lodash');
it('selects provisioned', () => {
const wrapper = shallowRender();
- wrapper.find('Search').prop<Function>('onProvisionedChanged')(true);
+ wrapper.find('withAppStateContext(Search)').prop<Function>('onProvisionedChanged')(true);
expect(getComponents).lastCalledWith({
...defaultSearchParameters,
onProvisionedOnly: true,
it('changes qualifier and resets provisioned', () => {
const wrapper = shallowRender();
wrapper.setState({ provisioned: true });
- wrapper.find('Search').prop<Function>('onQualifierChanged')('VW');
+ wrapper.find('withAppStateContext(Search)').prop<Function>('onQualifierChanged')('VW');
expect(getComponents).lastCalledWith({ ...defaultSearchParameters, qualifiers: 'VW' });
});
it('searches', () => {
const wrapper = shallowRender();
- wrapper.find('Search').prop<Function>('onSearch')('foo');
+ wrapper.find('withAppStateContext(Search)').prop<Function>('onSearch')('foo');
expect(getComponents).lastCalledWith({ ...defaultSearchParameters, q: 'foo', qualifiers: 'TRK' });
});
it('should handle date filtering', () => {
const wrapper = shallowRender();
- wrapper
- .find(Search)
- .props()
- .onDateChanged(new Date('2019-11-14T06:55:02.663Z'));
+ wrapper.find('withAppStateContext(Search)').prop<Function>('onDateChanged')(
+ '2019-11-14T06:55:02.663Z'
+ );
expect(getComponents).toHaveBeenCalledWith({
...defaultSearchParameters,
qualifiers: 'TRK',
wrapper.find('Projects').prop<Function>('onProjectDeselected')('foo');
expect(wrapper.state('selection')).toEqual(['bar']);
- wrapper.find('Search').prop<Function>('onAllDeselected')();
+ wrapper.find('withAppStateContext(Search)').prop<Function>('onAllDeselected')();
expect(wrapper.state('selection')).toEqual([]);
- wrapper.find('Search').prop<Function>('onAllSelected')();
+ wrapper.find('withAppStateContext(Search)').prop<Function>('onAllSelected')();
expect(wrapper.state('selection')).toEqual(['foo', 'bar']);
});
function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
return shallow<App>(
<App
- appState={mockAppState({ qualifiers: ['TRK', 'VW', 'APP'] })}
currentUser={mockLoggedInUser({ login: 'foo', permissions: { global: ['provisioning'] } })}
{...props}
/>
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockAppState } from '../../../helpers/testMocks';
import { click } from '../../../helpers/testUtils';
-import Search, { Props } from '../Search';
+import { Props, Search } from '../Search';
it('renders', () => {
expect(shallowRender()).toMatchSnapshot();
});
it('render qualifiers filter', () => {
- expect(shallowRender({ topLevelQualifiers: ['TRK', 'VW', 'APP'] })).toMatchSnapshot();
+ expect(
+ shallowRender({ appState: mockAppState({ qualifiers: ['TRK', 'VW', 'APP'] }) })
+ ).toMatchSnapshot();
});
it('updates qualifier', () => {
const onQualifierChanged = jest.fn();
- const wrapper = shallowRender({ onQualifierChanged, topLevelQualifiers: ['TRK', 'VW', 'APP'] });
+ const wrapper = shallowRender({
+ onQualifierChanged,
+ appState: mockAppState({ qualifiers: ['TRK', 'VW', 'APP'] })
+ });
wrapper.find('SelectLegacy[name="projects-qualifier"]').prop<Function>('onChange')({
value: 'VW'
});
query=""
ready={true}
selection={[]}
- topLevelQualifiers={['TRK']}
+ appState={mockAppState({ qualifiers: ['TRK'] })}
total={17}
{...props}
/>
*/
import { differenceWith, map, sortBy, uniqBy } from 'lodash';
import * as React from 'react';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
import DocumentationTooltip from '../../../components/common/DocumentationTooltip';
import { Button } from '../../../components/controls/buttons';
import ModalButton from '../../../components/controls/ModalButton';
-import { withAppState } from '../../../components/hoc/withAppState';
import { Alert } from '../../../components/ui/Alert';
import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
import { isDiffMetric } from '../../../helpers/measures';
import ConditionModal from './ConditionModal';
interface Props {
- appState: Pick<AppState, 'branchesEnabled'>;
+ appState: AppState;
canEdit: boolean;
conditions: ConditionType[];
metrics: Dict<Metric>;
}
}
-export default withAppState(withMetricsContext(Conditions));
+export default withMetricsContext(withAppStateContext(Conditions));
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockQualityGate } from '../../../../helpers/mocks/quality-gates';
-import { mockCondition, mockMetric } from '../../../../helpers/testMocks';
+import { mockAppState, mockCondition, mockMetric } from '../../../../helpers/testMocks';
import { MetricKey } from '../../../../types/metrics';
import { Conditions } from '../Conditions';
function shallowRender(props: Partial<Conditions['props']> = {}) {
return shallow<Conditions>(
<Conditions
- appState={{ branchesEnabled: true }}
+ appState={mockAppState({ branchesEnabled: true })}
canEdit={false}
conditions={[mockCondition(), mockCondition({ id: 2, metric: MetricKey.duplicated_lines })]}
metrics={{
<div
className="layout-page-main-inner"
>
- <Connect(withAppState(withMetricsContext(Conditions)))
+ <withMetricsContext(withAppStateContext(Conditions))
canEdit={false}
conditions={Array []}
onAddCondition={[MockFunction]}
<div
className="layout-page-main-inner"
>
- <Connect(withAppState(withMetricsContext(Conditions)))
+ <withMetricsContext(withAppStateContext(Conditions))
canEdit={false}
conditions={
Array [
>
quality_gates.is_default_no_conditions
</Alert>
- <Connect(withAppState(withMetricsContext(Conditions)))
+ <withMetricsContext(withAppStateContext(Conditions))
canEdit={false}
conditions={Array []}
onAddCondition={[MockFunction]}
<div
className="layout-page-main-inner"
>
- <Connect(withAppState(withMetricsContext(Conditions)))
+ <withMetricsContext(withAppStateContext(Conditions))
canEdit={false}
conditions={
Array [
import classNames from 'classnames';
import { sortBy } from 'lodash';
import * as React from 'react';
-import { connect } from 'react-redux';
import { IndexLink } from 'react-router';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { getGlobalSettingsUrl, getProjectSettingsUrl } from '../../../helpers/urls';
-import { getAppState, Store } from '../../../store/rootReducer';
-import { Component } from '../../../types/types';
+import { AppState, Component } from '../../../types/types';
import { getCategoryName } from '../utils';
import { ADDITIONAL_CATEGORIES } from './AdditionalCategories';
import CATEGORY_OVERRIDES from './CategoryOverrides';
export interface CategoriesListProps {
- branchesEnabled?: boolean;
+ appState: AppState;
categories: string[];
component?: Component;
defaultCategory: string;
}
export function CategoriesList(props: CategoriesListProps) {
- const { branchesEnabled, categories, component, defaultCategory, selectedCategory } = props;
+ const { appState, categories, component, defaultCategory, selectedCategory } = props;
const categoriesWithName = categories
.filter(key => !CATEGORY_OVERRIDES[key.toLowerCase()])
: // Global settings
c.availableGlobally
)
- .filter(c => branchesEnabled || !c.requiresBranchesEnabled)
+ .filter(c => appState.branchesEnabled || !c.requiresBranchesEnabled)
);
const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase());
);
}
-const mapStateToProps = (state: Store) => ({
- branchesEnabled: getAppState(state).branchesEnabled
-});
-
-export default connect(mapStateToProps)(CategoriesList);
+export default withAppStateContext(CategoriesList);
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockComponent } from '../../../../helpers/mocks/component';
+import { mockAppState } from '../../../../helpers/testMocks';
import { AdditionalCategory } from '../AdditionalCategories';
import { CategoriesList, CategoriesListProps } from '../AllCategoriesList';
expect(shallowRender()).toMatchSnapshot('global mode');
expect(shallowRender({ selectedCategory: 'CAT_2' })).toMatchSnapshot('selected category');
expect(shallowRender({ component: mockComponent() })).toMatchSnapshot('project mode');
- expect(shallowRender({ branchesEnabled: false })).toMatchSnapshot('branches disabled');
+ expect(shallowRender({ appState: mockAppState({ branchesEnabled: false }) })).toMatchSnapshot(
+ 'branches disabled'
+ );
});
function shallowRender(props?: Partial<CategoriesListProps>) {
return shallow<CategoriesListProps>(
<CategoriesList
- branchesEnabled={true}
+ appState={mockAppState({ branchesEnabled: true })}
categories={['general']}
defaultCategory="general"
selectedCategory=""
`;
exports[`should render additional categories component correctly 4`] = `
-<withRouter(Connect(withAppState(AlmIntegration)))
+<withRouter(withAppStateContext(AlmIntegration))
categories={Array []}
component={
Object {
`;
exports[`should render additional categories component correctly 5`] = `
-<Connect(Connect(withCurrentUser(PRDecorationBinding)))
+<Connect(withCurrentUser(PRDecorationBinding))
component={
Object {
"breadcrumbs": Array [],
<div
className="big-padded"
>
- <withRouter(Connect(withAppState(AlmIntegration)))
+ <withRouter(withAppStateContext(AlmIntegration))
categories={
Array [
"foo category",
<div
className="layout-page-side-inner"
>
- <Connect(CategoriesList)
+ <withAppStateContext(CategoriesList)
categories={
Array [
"foo category",
getAlmDefinitions,
validateAlmSettings
} from '../../../../api/alm-settings';
-import { withAppState } from '../../../../components/hoc/withAppState';
+import withAppStateContext from '../../../../app/components/app-state/withAppStateContext';
import { withRouter } from '../../../../components/hoc/withRouter';
import {
AlmBindingDefinitionBase,
import AlmIntegrationRenderer from './AlmIntegrationRenderer';
interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
- appState: Pick<AppState, 'branchesEnabled' | 'multipleAlmEnabled'>;
+ appState: AppState;
definitions: ExtendedSettingDefinition[];
}
}
}
-export default withRouter(withAppState(AlmIntegration));
+export default withRouter(withAppStateContext(AlmIntegration));
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
+import withAppStateContext from '../../../../app/components/app-state/withAppStateContext';
import Tooltip from '../../../../components/controls/Tooltip';
-import { withAppState } from '../../../../components/hoc/withAppState';
import { getEdition, getEditionUrl } from '../../../../helpers/editions';
import { translate } from '../../../../helpers/l10n';
import { AlmKeys } from '../../../../types/alm-settings';
);
}
-export default withAppState(CreationTooltip);
+export default withAppStateContext(CreationTooltip);
getAlmDefinitions,
validateAlmSettings
} from '../../../../../api/alm-settings';
-import { mockLocation, mockRouter } from '../../../../../helpers/testMocks';
+import { mockAppState, mockLocation, mockRouter } from '../../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../../helpers/testUtils';
import { AlmKeys, AlmSettingsBindingStatusType } from '../../../../../types/alm-settings';
import { AlmIntegration } from '../AlmIntegration';
function shallowRender(props: Partial<AlmIntegration['props']> = {}) {
return shallow<AlmIntegration>(
<AlmIntegration
- appState={{ branchesEnabled: true }}
+ appState={mockAppState({ branchesEnabled: true })}
definitions={[]}
location={mockLocation()}
router={mockRouter()}
<div
className="spacer-bottom text-right"
>
- <Connect(withAppState(CreationTooltip))
+ <withAppStateContext(CreationTooltip)
alm="azure"
preventCreation={false}
>
>
settings.almintegration.create
</Button>
- </Connect(withAppState(CreationTooltip))>
+ </withAppStateContext(CreationTooltip)>
</div>
<AlmBindingDefinitionBox
alm="azure"
<div
className="spacer-bottom text-right"
>
- <Connect(withAppState(CreationTooltip))
+ <withAppStateContext(CreationTooltip)
alm="azure"
preventCreation={false}
>
>
settings.almintegration.create
</Button>
- </Connect(withAppState(CreationTooltip))>
+ </withAppStateContext(CreationTooltip)>
</div>
<AlmBindingDefinitionBox
alm="azure"
<div
className="spacer-bottom text-right"
>
- <Connect(withAppState(CreationTooltip))
+ <withAppStateContext(CreationTooltip)
alm="azure"
preventCreation={false}
>
>
settings.almintegration.create
</Button>
- </Connect(withAppState(CreationTooltip))>
+ </withAppStateContext(CreationTooltip)>
</div>
<AlmBindingDefinitionBox
alm="azure"
<div
className="spacer-bottom text-right"
>
- <Connect(withAppState(CreationTooltip))
+ <withAppStateContext(CreationTooltip)
alm="azure"
preventCreation={true}
>
>
settings.almintegration.create
</Button>
- </Connect(withAppState(CreationTooltip))>
+ </withAppStateContext(CreationTooltip)>
</div>
<AlmBindingDefinitionBox
alm="azure"
<div
className="spacer-bottom text-right"
>
- <Connect(withAppState(CreationTooltip))
+ <withAppStateContext(CreationTooltip)
alm="azure"
preventCreation={true}
>
>
settings.almintegration.create
</Button>
- </Connect(withAppState(CreationTooltip))>
+ </withAppStateContext(CreationTooltip)>
</div>
<AlmBindingDefinitionBox
alm="azure"
<div
className="spacer-bottom text-right"
>
- <Connect(withAppState(CreationTooltip))
+ <withAppStateContext(CreationTooltip)
alm="azure"
preventCreation={true}
>
>
settings.almintegration.create
</Button>
- </Connect(withAppState(CreationTooltip))>
+ </withAppStateContext(CreationTooltip)>
</div>
<AlmBindingDefinitionBox
alm="azure"
<div
className="spacer-bottom text-right"
>
- <Connect(withAppState(CreationTooltip))
+ <withAppStateContext(CreationTooltip)
alm="azure"
preventCreation={true}
>
>
settings.almintegration.create
</Button>
- </Connect(withAppState(CreationTooltip))>
+ </withAppStateContext(CreationTooltip)>
</div>
<AlmBindingDefinitionBox
alm="azure"
<div
className="big-spacer-top"
>
- <Connect(withAppState(CreationTooltip))
+ <withAppStateContext(CreationTooltip)
alm="azure"
preventCreation={false}
>
>
settings.almintegration.create
</Button>
- </Connect(withAppState(CreationTooltip))>
+ </withAppStateContext(CreationTooltip)>
</div>
</DeferredSpinner>
</div>
<div
className="spacer-bottom text-right"
>
- <Connect(withAppState(CreationTooltip))
+ <withAppStateContext(CreationTooltip)
alm="azure"
preventCreation={false}
>
>
settings.almintegration.create
</Button>
- </Connect(withAppState(CreationTooltip))>
+ </withAppStateContext(CreationTooltip)>
</div>
<AlmBindingDefinitionBox
alm="azure"
<div
className="spacer-bottom text-right"
>
- <Connect(withAppState(CreationTooltip))
+ <withAppStateContext(CreationTooltip)
alm="azure"
preventCreation={false}
>
>
settings.almintegration.create
</Button>
- </Connect(withAppState(CreationTooltip))>
+ </withAppStateContext(CreationTooltip)>
</div>
<AlmBindingDefinitionBox
alm="azure"
<div
className="big-spacer-top"
>
- <Connect(withAppState(CreationTooltip))
+ <withAppStateContext(CreationTooltip)
alm="azure"
preventCreation={false}
>
>
settings.almintegration.create
</Button>
- </Connect(withAppState(CreationTooltip))>
+ </withAppStateContext(CreationTooltip)>
</div>
</DeferredSpinner>
</div>
<div
className="spacer-bottom text-right"
>
- <Connect(withAppState(CreationTooltip))
+ <withAppStateContext(CreationTooltip)
alm="bitbucket"
preventCreation={false}
>
>
settings.almintegration.create
</Button>
- </Connect(withAppState(CreationTooltip))>
+ </withAppStateContext(CreationTooltip)>
</div>
<AlmBindingDefinitionBox
alm="bitbucketcloud"
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
+import withAppStateContext from '../../../../app/components/app-state/withAppStateContext';
import Toggle from '../../../../components/controls/Toggle';
import { Alert } from '../../../../components/ui/Alert';
import MandatoryFieldMarker from '../../../../components/ui/MandatoryFieldMarker';
AlmSettingsInstance,
ProjectAlmBindingResponse
} from '../../../../types/alm-settings';
-import { Dict } from '../../../../types/types';
+import { EditionKey } from '../../../../types/editions';
+import { AppState, Dict } from '../../../../types/types';
export interface AlmSpecificFormProps {
alm: AlmKeys;
instances: AlmSettingsInstance[];
formData: Omit<ProjectAlmBindingResponse, 'alm'>;
onFieldChange: (id: keyof ProjectAlmBindingResponse, value: string | boolean) => void;
- monorepoEnabled: boolean;
+ appState: AppState;
}
interface LabelProps {
);
}
-export default function AlmSpecificForm(props: AlmSpecificFormProps) {
+export function AlmSpecificForm(props: AlmSpecificFormProps) {
const {
alm,
instances,
formData: { repository, slug, summaryCommentEnabled, monorepo },
- monorepoEnabled
+ appState
} = props;
let formFields: JSX.Element;
break;
}
+ // This feature trigger will be replaced when SONAR-14349 is implemented
+ const monorepoEnabled = [EditionKey.enterprise, EditionKey.datacenter].includes(
+ appState.edition as EditionKey
+ );
+
return (
<>
{formFields}
</>
);
}
+
+export default withAppStateContext(AlmSpecificForm);
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
import {
deleteProjectAlmBinding,
getAlmSettings,
import { withCurrentUser } from '../../../../components/hoc/withCurrentUser';
import { HttpStatus } from '../../../../helpers/request';
import { hasGlobalPermission } from '../../../../helpers/users';
-import { getAppState, Store } from '../../../../store/rootReducer';
import {
AlmKeys,
AlmSettingsInstance,
ProjectAlmBindingConfigurationErrors,
ProjectAlmBindingResponse
} from '../../../../types/alm-settings';
-import { EditionKey } from '../../../../types/editions';
import { Permissions } from '../../../../types/permissions';
import { Component, CurrentUser } from '../../../../types/types';
import PRDecorationBindingRenderer from './PRDecorationBindingRenderer';
type FormData = Omit<ProjectAlmBindingResponse, 'alm'>;
-interface StateProps {
- monorepoEnabled: boolean;
-}
-
interface Props {
component: Component;
currentUser: CurrentUser;
[AlmKeys.GitLab]: ['repository']
};
-export class PRDecorationBinding extends React.PureComponent<Props & StateProps, State> {
+export class PRDecorationBinding extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
formData: { key: '', monorepo: false },
};
render() {
- const { currentUser, monorepoEnabled } = this.props;
+ const { currentUser } = this.props;
return (
<PRDecorationBindingRenderer
onReset={this.handleReset}
onSubmit={this.handleSubmit}
onCheckConfiguration={this.handleCheckConfiguration}
- monorepoEnabled={monorepoEnabled}
isSysAdmin={hasGlobalPermission(currentUser, Permissions.Admin)}
{...this.state}
/>
}
}
-const mapStateToProps = (state: Store): StateProps => ({
- // This feature trigger will be replaced when SONAR-14349 is implemented
- monorepoEnabled: [EditionKey.enterprise, EditionKey.datacenter].includes(
- getAppState(state).edition as EditionKey
- )
-});
-
-export default connect(mapStateToProps)(withCurrentUser(PRDecorationBinding));
+export default withCurrentUser(PRDecorationBinding);
onSubmit: () => void;
updating: boolean;
successfullyUpdated: boolean;
- monorepoEnabled: boolean;
onCheckConfiguration: () => void;
checkingConfiguration: boolean;
configurationErrors?: ProjectAlmBindingConfigurationErrors;
loading,
updating,
successfullyUpdated,
- monorepoEnabled,
checkingConfiguration,
configurationErrors,
isSysAdmin
instances={instances}
formData={formData}
onFieldChange={props.onFieldChange}
- monorepoEnabled={monorepoEnabled}
/>
)}
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockAlmSettingsInstance } from '../../../../../helpers/mocks/alm-settings';
+import { mockAppState } from '../../../../../helpers/testMocks';
import { AlmKeys, AlmSettingsInstance } from '../../../../../types/alm-settings';
-import AlmSpecificForm, { AlmSpecificFormProps } from '../AlmSpecificForm';
+import { EditionKey } from '../../../../../types/editions';
+import { AlmSpecificForm, AlmSpecificFormProps } from '../AlmSpecificForm';
it.each([
[AlmKeys.Azure],
);
it('should render the monorepo field when the feature is supported', () => {
- expect(shallowRender(AlmKeys.Azure, { monorepoEnabled: true })).toMatchSnapshot();
+ expect(
+ shallowRender(AlmKeys.Azure, { appState: mockAppState({ edition: EditionKey.enterprise }) })
+ ).toMatchSnapshot();
});
function shallowRender(alm: AlmKeys, props: Partial<AlmSpecificFormProps> = {}) {
monorepo: false
}}
onFieldChange={jest.fn()}
- monorepoEnabled={false}
+ appState={mockAppState({ edition: EditionKey.developer })}
{...props}
/>
);
<PRDecorationBinding
currentUser={mockCurrentUser()}
component={mockComponent({ key: PROJECT_KEY })}
- monorepoEnabled={false}
{...props}
/>
);
onSubmit={jest.fn()}
updating={false}
successfullyUpdated={false}
- monorepoEnabled={false}
checkingConfiguration={false}
onCheckConfiguration={jest.fn()}
isSysAdmin={false}
isSysAdmin={false}
isValid={false}
loading={true}
- monorepoEnabled={false}
onCheckConfiguration={[Function]}
onFieldChange={[Function]}
onReset={[Function]}
/>
</div>
</div>
- <AlmSpecificForm
+ <withAppStateContext(AlmSpecificForm)
alm="github"
formData={
Object {
},
]
}
- monorepoEnabled={false}
onFieldChange={[MockFunction]}
/>
<div
/>
</div>
</div>
- <AlmSpecificForm
+ <withAppStateContext(AlmSpecificForm)
alm="github"
formData={
Object {
},
]
}
- monorepoEnabled={false}
onFieldChange={[MockFunction]}
/>
<div
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { ClipboardButton } from '../../../components/controls/clipboard';
import { Alert } from '../../../components/ui/Alert';
import { toShortNotSoISOString } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
-import { getAppState, Store } from '../../../store/rootReducer';
+import { AppState } from '../../../types/types';
import PageActions from './PageActions';
export interface Props {
loading: boolean;
logLevel: string;
onLogLevelChange: () => void;
- productionDatabase: boolean;
+ appState: AppState;
serverId?: string;
showActions: boolean;
version?: string;
}
export function PageHeader(props: Props) {
- const {
- isCluster,
- loading,
- logLevel,
- serverId,
- showActions,
- version,
- productionDatabase
- } = props;
+ const { isCluster, loading, logLevel, serverId, showActions, version, appState } = props;
return (
<header className="page-header">
<h1 className="page-title">{translate('system_info.page')}</h1>
)}
{serverId && version && (
<div className="system-info-copy-paste-id-info boxed-group ">
- {!productionDatabase && (
+ {!appState.productionDatabase && (
<Alert className="width-100" variant="warning">
{translate('system.not_production_database_warning')}
</Alert>
);
}
-const mapStateToProps = (store: Store) => ({
- productionDatabase: getAppState(store).productionDatabase
-});
-
-export default connect(mapStateToProps)(PageHeader);
+export default withAppStateContext(PageHeader);
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockAppState } from '../../../../helpers/testMocks';
import { PageHeader, Props } from '../PageHeader';
jest.mock('../../../../helpers/dates', () => ({
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
expect(
- shallowRender({ productionDatabase: false, serverId: 'foo-bar', version: '7.7.0.1234' })
+ shallowRender({
+ appState: mockAppState({ productionDatabase: false }),
+ serverId: 'foo-bar',
+ version: '7.7.0.1234'
+ })
).toMatchSnapshot('on embedded database');
expect(shallowRender({ loading: true, showActions: false })).toMatchSnapshot();
expect(shallowRender({ serverId: 'foo-bar', version: '7.7.0.1234' })).toMatchSnapshot();
loading={false}
logLevel="INFO"
onLogLevelChange={jest.fn()}
- productionDatabase={true}
+ appState={mockAppState({ productionDatabase: true })}
showActions={true}
{...props}
/>
<div
className="page-notifs"
>
- <Connect(withCurrentUser(Connect(withAppState(UpdateNotification))))
+ <Connect(withCurrentUser(withAppStateContext(UpdateNotification)))
dismissable={false}
/>
</div>
- <Connect(PageHeader)
+ <withAppStateContext(PageHeader)
isCluster={true}
loading={false}
logLevel="DEBUG"
<div
className="page-notifs"
>
- <Connect(withCurrentUser(Connect(withAppState(UpdateNotification))))
+ <Connect(withCurrentUser(withAppStateContext(UpdateNotification)))
dismissable={false}
/>
</div>
<div
className="page-notifs"
>
- <Connect(withCurrentUser(Connect(withAppState(UpdateNotification))))
+ <Connect(withCurrentUser(withAppStateContext(UpdateNotification)))
dismissable={false}
/>
</div>
- <Connect(PageHeader)
+ <withAppStateContext(PageHeader)
isCluster={false}
loading={false}
logLevel="DEBUG"
*/
import * as React from 'react';
import { Link } from 'react-router';
+import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import DetachIcon from '../../components/icons/DetachIcon';
import { isSonarCloud } from '../../helpers/system';
import { AppState } from '../../types/types';
-import { withAppState } from '../hoc/withAppState';
interface OwnProps {
- appState: Pick<AppState, 'canAdmin'>;
+ appState: AppState;
customProps?: {
[k: string]: any;
};
{children}
</SonarQubeAdminLink>
);
- } else {
- const url = '/documentation' + href;
- return (
- <Link to={url} {...other}>
- {children}
- </Link>
- );
}
+ const url = '/documentation' + href;
+ return (
+ <Link to={url} {...other}>
+ {children}
+ </Link>
+ );
}
return (
}
}
-export default withAppState(DocLink);
+export default withAppStateContext(DocLink);
interface SonarCloudLinkProps {
children: React.ReactNode;
function SonarCloudLink({ children, url }: SonarCloudLinkProps) {
if (!isSonarCloud()) {
return <>{children}</>;
- } else {
- const to = `/${url.substr(SONARCLOUD_LINK.length)}`;
- return <Link to={to}>{children}</Link>;
}
+ const to = `/${url.substr(SONARCLOUD_LINK.length)}`;
+ return <Link to={to}>{children}</Link>;
}
interface SonarQubeLinkProps {
function SonarQubeLink({ children, url }: SonarQubeLinkProps) {
if (isSonarCloud()) {
return <>{children}</>;
- } else {
- const to = `/${url.substr(SONARQUBE_LINK.length)}`;
- return (
- <Link target="_blank" to={to}>
- {children}
- </Link>
- );
}
+ const to = `/${url.substr(SONARQUBE_LINK.length)}`;
+ return (
+ <Link target="_blank" to={to}>
+ {children}
+ </Link>
+ );
}
interface SonarQubeAdminLinkProps {
function SonarQubeAdminLink({ canAdmin, children, url }: SonarQubeAdminLinkProps) {
if (isSonarCloud() || !canAdmin) {
return <>{children}</>;
- } else {
- const to = `/${url.substr(SONARQUBE_ADMIN_LINK.length)}`;
- return (
- <Link target="_blank" to={to}>
- {children}
- </Link>
- );
}
+ const to = `/${url.substr(SONARQUBE_ADMIN_LINK.length)}`;
+ return (
+ <Link target="_blank" to={to}>
+ {children}
+ </Link>
+ );
}
import { shallow } from 'enzyme';
import * as React from 'react';
import { isSonarCloud } from '../../../helpers/system';
+import { mockAppState } from '../../../helpers/testMocks';
import { DocLink } from '../DocLink';
jest.mock('../../../helpers/system', () => ({
it('should render simple link', () => {
expect(
shallow(
- <DocLink appState={{ canAdmin: false }} href="http://sample.com">
+ <DocLink appState={mockAppState({ canAdmin: false })} href="http://sample.com">
link text
</DocLink>
)
it('should render documentation link', () => {
expect(
shallow(
- <DocLink appState={{ canAdmin: false }} href="/foo/bar">
+ <DocLink appState={mockAppState({ canAdmin: false })} href="/foo/bar">
link text
</DocLink>
)
it('should render sonarcloud link on sonarcloud', () => {
(isSonarCloud as jest.Mock).mockImplementationOnce(() => true);
const wrapper = shallow(
- <DocLink appState={{ canAdmin: false }} href="/#sonarcloud#/foo/bar">
+ <DocLink appState={mockAppState({ canAdmin: false })} href="/#sonarcloud#/foo/bar">
link text
</DocLink>
);
it('should not render sonarcloud link on sonarcloud', () => {
(isSonarCloud as jest.Mock).mockImplementationOnce(() => false);
const wrapper = shallow(
- <DocLink appState={{ canAdmin: false }} href="/#sonarcloud#/foo/bar">
+ <DocLink appState={mockAppState({ canAdmin: false })} href="/#sonarcloud#/foo/bar">
link text
</DocLink>
);
it('should render sonarqube link on sonarqube', () => {
const wrapper = shallow(
- <DocLink appState={{ canAdmin: false }} href="/#sonarqube#/foo/bar">
+ <DocLink appState={mockAppState({ canAdmin: false })} href="/#sonarqube#/foo/bar">
link text
</DocLink>
);
it('should not render sonarqube link on sonarcloud', () => {
(isSonarCloud as jest.Mock).mockImplementationOnce(() => true);
const wrapper = shallow(
- <DocLink appState={{ canAdmin: false }} href="/#sonarqube#/foo/bar">
+ <DocLink appState={mockAppState({ canAdmin: false })} href="/#sonarqube#/foo/bar">
link text
</DocLink>
);
it('should render sonarqube admin link on sonarqube for admin', () => {
const wrapper = shallow(
- <DocLink appState={{ canAdmin: true }} href="/#sonarqube-admin#/foo/bar">
+ <DocLink appState={mockAppState({ canAdmin: true })} href="/#sonarqube-admin#/foo/bar">
link text
</DocLink>
);
it('should not render sonarqube admin link on sonarqube for non-admin', () => {
const wrapper = shallow(
- <DocLink appState={{ canAdmin: false }} href="/#sonarqube-admin#/foo/bar">
+ <DocLink appState={mockAppState({ canAdmin: false })} href="/#sonarqube-admin#/foo/bar">
link text
</DocLink>
);
it('should not render sonarqube admin link on sonarcloud', () => {
(isSonarCloud as jest.Mock).mockImplementationOnce(() => true);
const wrapper = shallow(
- <DocLink appState={{ canAdmin: true }} href="/#sonarqube-admin#/foo/bar">
+ <DocLink appState={mockAppState({ canAdmin: true })} href="/#sonarqube-admin#/foo/bar">
link text
</DocLink>
);
it('should render documentation anchor', () => {
expect(
shallow(
- <DocLink appState={{ canAdmin: false }} href="#quality-profiles">
+ <DocLink appState={mockAppState({ canAdmin: false })} href="#quality-profiles">
link text
</DocLink>
)
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import * as React from 'react';
-import { connect } from 'react-redux';
-import { getAppState, Store } from '../../store/rootReducer';
-import { AppState } from '../../types/types';
-import { getWrappedDisplayName } from './utils';
-
-export function withAppState<P>(
- WrappedComponent: React.ComponentType<P & { appState: Partial<AppState> }>
-) {
- class Wrapper extends React.Component<P & { appState: AppState }> {
- static displayName = getWrappedDisplayName(WrappedComponent, 'withAppState');
-
- render() {
- return <WrappedComponent {...this.props} />;
- }
- }
-
- function mapStateToProps(state: Store) {
- return { appState: getAppState(state) };
- }
-
- return connect(mapStateToProps)(Wrapper);
-}
exports[`should render correctly: jenkins tutorial 1`] = `
<Fragment>
- <Connect(JenkinsTutorial)
+ <Connect(withAppStateContext(JenkinsTutorial))
almBinding={
Object {
"alm": "github",
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
+import withAppStateContext from '../../../../app/components/app-state/withAppStateContext';
import { Alert } from '../../../../components/ui/Alert';
import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
import { translate } from '../../../../helpers/l10n';
import { AlmKeys } from '../../../../types/alm-settings';
import { AppState } from '../../../../types/types';
-import { withAppState } from '../../../hoc/withAppState';
import SentenceWithHighlights from '../../components/SentenceWithHighlights';
export interface PublishStepsProps {
);
}
-export default withAppState(PublishSteps);
+export default withAppStateContext(PublishSteps);
translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run.ccpp"
/>
</li>
- <Connect(withAppState(PublishSteps)) />
+ <withAppStateContext(PublishSteps) />
</ol>
</Fragment>
`;
translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run.ccpp"
/>
</li>
- <Connect(withAppState(PublishSteps)) />
+ <withAppStateContext(PublishSteps) />
</ol>
</Fragment>
`;
translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run.ccpp"
/>
</li>
- <Connect(withAppState(PublishSteps)) />
+ <withAppStateContext(PublishSteps) />
</ol>
</Fragment>
`;
translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run"
/>
</li>
- <Connect(withAppState(PublishSteps)) />
+ <withAppStateContext(PublishSteps) />
</ol>
</Fragment>
`;
/>
</li>
</ul>
- <Connect(withAppState(PublishSteps)) />
+ <withAppStateContext(PublishSteps) />
</ol>
</Fragment>
`;
/>
</li>
</ul>
- <Connect(withAppState(PublishSteps)) />
+ <withAppStateContext(PublishSteps) />
</ol>
</Fragment>
`;
translationKey="onboarding.tutorial.with.azure_pipelines.BranchAnalysis.run"
/>
</li>
- <Connect(withAppState(PublishSteps)) />
+ <withAppStateContext(PublishSteps) />
</ol>
</Fragment>
`;
*/
import { Dictionary } from 'lodash';
import * as React from 'react';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { AppState, Component } from '../../../types/types';
-import { withAppState } from '../../hoc/withAppState';
import { CompilationInfo } from '../components/CompilationInfo';
import CreateYmlFile from '../components/CreateYmlFile';
import { BuildTools } from '../types';
);
}
-export default withAppState(AnalysisCommand);
+export default withAppStateContext(AnalysisCommand);
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
import { AlmKeys } from '../../../types/alm-settings';
import { AppState } from '../../../types/types';
-import { withAppState } from '../../hoc/withAppState';
import SentenceWithHighlights from './SentenceWithHighlights';
export interface AllSetProps {
);
}
-export default withAppState(AllSet);
+export default withAppStateContext(AllSet);
<div
className="boxed-group-inner"
>
- <Connect(withAppState(AllSet))
+ <withAppStateContext(AllSet)
alm="azure"
willRefreshAutomatically={true}
/>
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { AppState, Component } from '../../../types/types';
-import { withAppState } from '../../hoc/withAppState';
import { BuildTools } from '../types';
import CFamily from './commands/CFamily';
import DotNet from './commands/DotNet';
return null;
}
-export default withAppState(AnalysisCommand);
+export default withAppStateContext(AnalysisCommand);
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { ClipboardIconButton } from '../../../components/controls/clipboard';
import { translate } from '../../../helpers/l10n';
import { AppState } from '../../../types/types';
-import { withAppState } from '../../hoc/withAppState';
import FinishButton from '../components/FinishButton';
import GithubCFamilyExampleRepositories from '../components/GithubCFamilyExampleRepositories';
import Step from '../components/Step';
);
}
-export default withAppState(YmlFileStep);
+export default withAppStateContext(YmlFileStep);
onOpen={[Function]}
open={false}
/>
- <Connect(withAppState(YmlFileStep))
+ <withAppStateContext(YmlFileStep)
finished={false}
onDone={[Function]}
onOpen={[Function]}
*/
import * as React from 'react';
import { connect } from 'react-redux';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { translate } from '../../../helpers/l10n';
-import { getAppState, getCurrentUserSetting, Store } from '../../../store/rootReducer';
+import { getCurrentUserSetting, Store } from '../../../store/rootReducer';
import { setCurrentUserSetting } from '../../../store/users';
import {
AlmKeys,
AlmSettingsInstance,
ProjectAlmBindingResponse
} from '../../../types/alm-settings';
-import { Component, CurrentUserSetting } from '../../../types/types';
+import { AppState, Component, CurrentUserSetting } from '../../../types/types';
import AllSetStep from '../components/AllSetStep';
import JenkinsfileStep from './JenkinsfileStep';
import MultiBranchPipelineStep from './MultiBranchPipelineStep';
export interface JenkinsTutorialProps {
almBinding?: AlmSettingsInstance;
baseUrl: string;
- branchesEnabled: boolean;
+ appState: AppState;
component: Component;
projectBinding?: ProjectAlmBindingResponse;
setCurrentUserSetting: (setting: CurrentUserSetting) => void;
const {
almBinding,
baseUrl,
- branchesEnabled,
+ appState,
component,
projectBinding,
skipPreReqs,
<>
<PreRequisitesStep
alm={alm}
- branchesEnabled={branchesEnabled}
+ branchesEnabled={!!appState.branchesEnabled}
finished={step > Steps.PreRequisites}
onDone={() => setStep(Steps.MultiBranchPipeline)}
onOpen={() => setStep(Steps.PreRequisites)}
skipNextTime={skipPreReqs}
/>
- {branchesEnabled ? (
+ {appState.branchesEnabled ? (
<MultiBranchPipelineStep
alm={alm}
almBinding={almBinding}
<WebhookStep
alm={alm}
almBinding={almBinding}
- branchesEnabled={branchesEnabled}
+ branchesEnabled={!!appState.branchesEnabled}
finished={step > Steps.Webhook}
onDone={() => setStep(Steps.Jenkinsfile)}
onOpen={() => setStep(Steps.Webhook)}
);
}
-const mapStateToProps = (
- state: Store
-): Pick<JenkinsTutorialProps, 'branchesEnabled' | 'skipPreReqs'> => {
+const mapStateToProps = (state: Store): Pick<JenkinsTutorialProps, 'skipPreReqs'> => {
return {
- branchesEnabled: Boolean(getAppState(state).branchesEnabled),
skipPreReqs: getCurrentUserSetting(state, USER_SETTING_SKIP_BITBUCKET_PREREQS) === 'true'
};
};
const mapDispatchToProps = { setCurrentUserSetting };
-export default connect(mapStateToProps, mapDispatchToProps)(JenkinsTutorial);
+export default connect(mapStateToProps, mapDispatchToProps)(withAppStateContext(JenkinsTutorial));
import * as React from 'react';
import { mockProjectBitbucketBindingResponse } from '../../../../helpers/mocks/alm-settings';
import { mockComponent } from '../../../../helpers/mocks/component';
+import { mockAppState } from '../../../../helpers/testMocks';
import { AlmKeys } from '../../../../types/alm-settings';
import JenkinsfileStep from '../JenkinsfileStep';
import { JenkinsTutorial, JenkinsTutorialProps } from '../JenkinsTutorial';
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
- expect(shallowRender({ branchesEnabled: false })).toMatchSnapshot('branches not enabled');
+ expect(shallowRender({ appState: mockAppState({ branchesEnabled: false }) })).toMatchSnapshot(
+ 'branches not enabled'
+ );
expect(shallowRender({ projectBinding: undefined })).toMatchSnapshot('no project binding');
});
return shallow<JenkinsTutorialProps>(
<JenkinsTutorial
baseUrl=""
- branchesEnabled={true}
+ appState={mockAppState({ branchesEnabled: true })}
component={mockComponent()}
projectBinding={mockProjectBitbucketBindingResponse()}
setCurrentUserSetting={jest.fn()}
*/
import { filter, flatMap, isEmpty, negate } from 'lodash';
import * as React from 'react';
+import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import { translate } from '../../helpers/l10n';
import { EditionKey } from '../../types/editions';
import { SystemUpgrade } from '../../types/system';
import { AppState } from '../../types/types';
import { ResetButtonLink } from '../controls/buttons';
import Modal from '../controls/Modal';
-import { withAppState } from '../hoc/withAppState';
import { Alert, AlertVariant } from '../ui/Alert';
import SystemUpgradeItem from './SystemUpgradeItem';
import { UpdateUseCase } from './utils';
interface Props {
- appState: Pick<AppState, 'edition' | 'version'>;
+ appState: AppState;
onClose: () => void;
systemUpgrades: SystemUpgrade[][];
latestLTS: string;
}
}
-export default withAppState(SystemUpgradeForm);
+export default withAppStateContext(SystemUpgradeForm);
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockAppState } from '../../../helpers/testMocks';
import { EditionKey } from '../../../types/editions';
import { SystemUpgradeForm } from '../SystemUpgradeForm';
import { UpdateUseCase } from '../utils';
expect(
shallow(
<SystemUpgradeForm
- appState={{ edition: EditionKey.community, version: '5.6.3' }}
+ appState={mockAppState({ edition: EditionKey.community, version: '5.6.3' })}
onClose={jest.fn()}
systemUpgrades={UPGRADES}
latestLTS="9.1"
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { AppState, Extension } from '../types/types';
+import { AppState } from '../types/types';
import { ActionType } from './utils/actions';
export const enum Actions {
SetAppState = 'SET_APP_STATE',
- SetAdminPages = 'SET_ADMIN_PAGES',
RequireAuthorization = 'REQUIRE_AUTHORIZATION'
}
export type Action =
| ActionType<typeof setAppState, Actions.SetAppState>
- | ActionType<typeof setAdminPages, Actions.SetAdminPages>
| ActionType<typeof requireAuthorization, Actions.RequireAuthorization>;
export function setAppState(appState: AppState) {
return { type: Actions.SetAppState, appState };
}
-export function setAdminPages(adminPages: Extension[]) {
- return { type: Actions.SetAdminPages, adminPages };
-}
-
export function requireAuthorization() {
return { type: Actions.RequireAuthorization };
}
if (action.type === Actions.SetAppState) {
return { ...state, ...action.appState };
}
- if (action.type === Actions.SetAdminPages) {
- return { ...state, adminPages: action.adminPages };
- }
if (action.type === Actions.RequireAuthorization) {
return { ...state, authorizationError: true };
}
}
export interface AppState {
- adminPages?: Extension[];
authenticationError?: boolean;
authorizationError?: boolean;
branchesEnabled?: boolean;