diff options
author | Guillaume Peoc'h <guillaume.peoch@sonarsource.com> | 2022-02-02 15:36:37 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-02-09 20:02:55 +0000 |
commit | 2b13f016dee62cc813d0374e9f835dee5f4cda28 (patch) | |
tree | a14a39e129bb993ab041c8b29af4f9963bdf0002 /server/sonar-web/src/main/js/apps | |
parent | 2d48cb3c1eb4f6cd68bd091c4eb9d62fa71deff8 (diff) | |
download | sonarqube-2b13f016dee62cc813d0374e9f835dee5f4cda28.tar.gz sonarqube-2b13f016dee62cc813d0374e9f835dee5f4cda28.zip |
SONAR-15909 Extract AppState Redux
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
63 files changed, 324 insertions, 368 deletions
diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx b/server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx index 4854062f011..84c09c6ec85 100644 --- a/server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx +++ b/server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx @@ -19,8 +19,9 @@ */ 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'; @@ -29,23 +30,32 @@ import AuditAppRenderer from './AuditAppRenderer'; 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']); } @@ -64,17 +74,22 @@ export class AuditApp extends React.PureComponent<Props, State> { }; 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; + ); } } @@ -82,13 +97,8 @@ const mapDispatchToProps = { fetchValues }; 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 }; }; diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-test.tsx b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-test.tsx index b4d6a18eca0..1b9740af760 100644 --- a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-test.tsx @@ -21,6 +21,7 @@ import { subDays } from 'date-fns'; 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'; @@ -31,7 +32,7 @@ it('should render correctly', () => { 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(); @@ -80,7 +81,7 @@ function shallowRender(props: Partial<AuditApp['props']> = {}) { <AuditApp auditHousekeepingPolicy={HousekeepingPolicy.Monthly} fetchValues={jest.fn()} - hasGovernanceExtension={true} + adminPages={[{ key: AdminPageExtension.GovernanceConsole, name: 'name' }]} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingCount.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingCount.tsx index 6c2ef497549..d1cc1e097c7 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingCount.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingCount.tsx @@ -18,21 +18,21 @@ * 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; } @@ -42,7 +42,7 @@ export function StatPendingCount({ isSystemAdmin, onCancelAllPending, pendingCou <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')} @@ -62,8 +62,4 @@ export function StatPendingCount({ isSystemAdmin, onCancelAllPending, pendingCou ); } -const mapStateToProps = (state: Store) => ({ - isSystemAdmin: getAppState(state).canAdmin -}); - -export default connect(mapStateToProps)(StatPendingCount); +export default withAppStateContext(StatPendingCount); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/StatPendingCount-test.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/StatPendingCount-test.tsx index f0714f5a1be..4a4086955a6 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/StatPendingCount-test.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/StatPendingCount-test.tsx @@ -19,6 +19,7 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; +import { mockAppState } from '../../../../helpers/testMocks'; import { Props, StatPendingCount } from '../StatPendingCount'; it('should render correctly', () => { @@ -36,7 +37,7 @@ it('should not show cancel pending button', () => { .exists() ).toBe(false); expect( - shallowRender({ isSystemAdmin: false }) + shallowRender({ appState: mockAppState({ canAdmin: false }) }) .find('ConfirmButton') .exists() ).toBe(false); @@ -53,7 +54,7 @@ it('should trigger cancelling pending', () => { function shallowRender(props: Partial<Props> = {}) { return shallow( <StatPendingCount - isSystemAdmin={true} + appState={mockAppState({ canAdmin: true })} onCancelAllPending={jest.fn()} pendingCount={5} {...props} diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Stats-test.tsx.snap b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Stats-test.tsx.snap index c352f81491e..1253b96bb1f 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Stats-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Stats-test.tsx.snap @@ -4,7 +4,7 @@ exports[`should render correctly 1`] = ` <section className="big-spacer-top big-spacer-bottom" > - <Connect(StatPendingCount) + <withAppStateContext(StatPendingCount) onCancelAllPending={[MockFunction]} pendingCount={2} /> @@ -25,7 +25,7 @@ exports[`should render correctly for a component 1`] = ` <section className="big-spacer-top big-spacer-bottom" > - <Connect(StatPendingCount) + <withAppStateContext(StatPendingCount) onCancelAllPending={[MockFunction]} pendingCount={2} /> diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordApp.tsx b/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordApp.tsx index 80c821e4c79..5bb5d8b601b 100644 --- a/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordApp.tsx +++ b/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordApp.tsx @@ -18,16 +18,15 @@ * 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; } @@ -49,7 +48,7 @@ export class ChangeAdminPasswordApp extends React.PureComponent<Props, State> { passwordValue: '', confirmPasswordValue: '', submitting: false, - success: !props.instanceUsesDefaultAdminCredentials + success: !props.appState.instanceUsesDefaultAdminCredentials }; } @@ -93,7 +92,10 @@ export class ChangeAdminPasswordApp extends React.PureComponent<Props, State> { }; render() { - const { canAdmin, location } = this.props; + const { + appState: { canAdmin }, + location + } = this.props; const { canSubmit, confirmPasswordValue, passwordValue, submitting, success } = this.state; return ( <ChangeAdminPasswordAppRenderer @@ -112,9 +114,4 @@ export class ChangeAdminPasswordApp extends React.PureComponent<Props, State> { } } -export const mapStateToProps = (state: Store) => { - const { canAdmin, instanceUsesDefaultAdminCredentials } = getAppState(state); - return { canAdmin, instanceUsesDefaultAdminCredentials }; -}; - -export default connect(mapStateToProps)(withRouter(ChangeAdminPasswordApp)); +export default withRouter(withAppStateContext(ChangeAdminPasswordApp)); diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordApp-test.tsx b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordApp-test.tsx index cf173422826..fe8e3e54412 100644 --- a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordApp-test.tsx @@ -20,21 +20,15 @@ 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) })); @@ -43,9 +37,9 @@ beforeEach(jest.clearAllMocks); 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', () => { @@ -99,29 +93,10 @@ it('should correctly update the password', async () => { 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} /> diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordApp-test.tsx.snap index 5371a8f4386..e7e4585e6ae 100644 --- a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordApp-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordApp-test.tsx.snap @@ -2,7 +2,6 @@ exports[`should render correctly: admin is not using the default password 1`] = ` <ChangeAdminPasswordAppRenderer - canAdmin={true} confirmPasswordValue="" location={ Object { diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx index ef4601199f1..fa64d3cee56 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx @@ -20,8 +20,8 @@ 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'; @@ -29,7 +29,7 @@ import { getIssuesUrl } from '../../../helpers/urls'; import { AppState, RuleDetails } from '../../../types/types'; interface Props { - appState: Pick<AppState, 'branchesEnabled'>; + appState: AppState; ruleDetails: Pick<RuleDetails, 'key' | 'type'>; } @@ -173,4 +173,4 @@ export class RuleDetailsIssues extends React.PureComponent<Props, State> { } } -export default withAppState(RuleDetailsIssues); +export default withAppStateContext(RuleDetailsIssues); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx index 207b08648f7..7aeb0542100 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx @@ -20,6 +20,7 @@ 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'; @@ -59,7 +60,7 @@ it('should fetch issues and render', async () => { function shallowRender(props: Partial<RuleDetailsIssues['props']> = {}) { return shallow( <RuleDetailsIssues - appState={{ branchesEnabled: false }} + appState={mockAppState({ branchesEnabled: false })} ruleDetails={{ key: 'foo', type: 'BUG' }} {...props} /> diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetails-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetails-test.tsx.snap index e7230ad913a..5de95675322 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetails-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetails-test.tsx.snap @@ -156,7 +156,7 @@ exports[`should render correctly: loaded 1`] = ` } } /> - <Connect(withAppState(RuleDetailsIssues)) + <withAppStateContext(RuleDetailsIssues) ruleDetails={ Object { "createdAt": "2014-12-16T17:26:54+0100", diff --git a/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx b/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx index 7e4819ac484..3cd2701916e 100644 --- a/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx @@ -19,7 +19,7 @@ */ 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'; @@ -31,7 +31,7 @@ export interface CreateProjectModeSelectionProps { almCounts: { [k in AlmKeys]: number; }; - appState: Pick<AppState, 'canAdmin'>; + appState: AppState; loadingBindings: boolean; onSelectMode: (mode: CreateProjectModes) => void; onConfigMode: (mode: AlmKeys) => void; @@ -166,4 +166,4 @@ export function CreateProjectModeSelection(props: CreateProjectModeSelectionProp ); } -export default withAppState(CreateProjectModeSelection); +export default withAppStateContext(CreateProjectModeSelection); diff --git a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx index a97188e9969..6f250980373 100644 --- a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx @@ -22,8 +22,8 @@ import { Helmet } from 'react-helmet-async'; 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'; @@ -40,7 +40,7 @@ import './style.css'; import { CreateProjectModes } from './types'; interface Props extends Pick<WithRouterProps, 'router' | 'location'> { - appState: Pick<AppState, 'canAdmin' | 'branchesEnabled'>; + appState: AppState; currentUser: LoggedInUser; } @@ -272,4 +272,4 @@ export class CreateProjectPage extends React.PureComponent<Props, State> { } } -export default whenLoggedIn(withAppState(CreateProjectPage)); +export default whenLoggedIn(withAppStateContext(CreateProjectPage)); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectModeSelection-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectModeSelection-test.tsx index f9cee16a180..4be1b325d6a 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectModeSelection-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectModeSelection-test.tsx @@ -19,6 +19,7 @@ */ 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 { @@ -35,19 +36,19 @@ it('should render correctly', () => { ); 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, @@ -118,7 +119,7 @@ it('should call the proper click handler', () => { onSelectMode.mockClear(); wrapper = shallowRender( - { onSelectMode, onConfigMode, appState: { canAdmin: true } }, + { onSelectMode, onConfigMode, appState: mockAppState({ canAdmin: true }) }, { [AlmKeys.Azure]: 0 } ); @@ -144,7 +145,7 @@ function shallowRender( return shallow<CreateProjectModeSelectionProps>( <CreateProjectModeSelection almCounts={almCounts} - appState={{ canAdmin: false }} + appState={mockAppState({ canAdmin: false })} loadingBindings={false} onSelectMode={jest.fn()} onConfigMode={jest.fn()} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx index 59c21d71170..541f478f55c 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx @@ -20,7 +20,12 @@ 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'; @@ -124,7 +129,7 @@ it('should submit alm configuration creation properly for BBC', async () => { function shallowRender(props: Partial<CreateProjectPage['props']> = {}) { return shallow<CreateProjectPage>( <CreateProjectPage - appState={{}} + appState={mockAppState()} currentUser={mockLoggedInUser()} location={mockLocation()} router={mockRouter()} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap index 4b824a163f9..70474361ec7 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap @@ -16,7 +16,7 @@ exports[`should render alm configuration creation correctly 1`] = ` className="page page-limited huge-spacer-bottom position-relative" id="create-project" > - <Connect(withAppState(CreateProjectModeSelection)) + <withAppStateContext(CreateProjectModeSelection) almCounts={ Object { "azure": 0, @@ -57,7 +57,7 @@ exports[`should render correctly 1`] = ` className="page page-limited huge-spacer-bottom position-relative" id="create-project" > - <Connect(withAppState(CreateProjectModeSelection)) + <withAppStateContext(CreateProjectModeSelection) almCounts={ Object { "azure": 0, diff --git a/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx b/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx index 7ba8b943083..379c082bfe8 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx @@ -20,38 +20,47 @@ 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)); diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/AppContainer-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/__tests__/AppContainer-test.tsx index fbafb1047d8..d306400526f 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/AppContainer-test.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/__tests__/AppContainer-test.tsx @@ -19,8 +19,7 @@ */ 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', () => ({ @@ -29,7 +28,6 @@ jest.mock('react-redux', () => ({ jest.mock('../../../store/rootReducer', () => { return { - getAppState: jest.fn(), getGlobalSettingValue: jest.fn() }; }); @@ -37,10 +35,7 @@ jest.mock('../../../store/rootReducer', () => { 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}` }); @@ -49,8 +44,6 @@ describe('redux', () => { const props = mapStateToProps(store); expect(props).toEqual({ - currentEdition: edition, - standaloneMode: standalone, updateCenterActive }); diff --git a/server/sonar-web/src/main/js/apps/overview/components/App.tsx b/server/sonar-web/src/main/js/apps/overview/components/App.tsx index bfa2e17b359..665b63e6ddc 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/App.tsx @@ -18,8 +18,8 @@ * 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'; @@ -33,7 +33,7 @@ const EmptyOverview = lazyLoadComponent(() => import('./EmptyOverview')); const PullRequestOverview = lazyLoadComponent(() => import('../pullRequests/PullRequestOverview')); interface Props { - appState: Pick<AppState, 'branchesEnabled'>; + appState: AppState; branchLike?: BranchLike; branchLikes: BranchLike[]; component: Component; @@ -93,4 +93,4 @@ export class App extends React.PureComponent<Props> { } } -export default withRouter(withAppState(App)); +export default withRouter(withAppStateContext(App)); diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx index 8f1f508aca0..f5fea8b899a 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx @@ -20,12 +20,11 @@ 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'; @@ -33,7 +32,7 @@ import Template from './Template'; interface Props { location: Location; - topQualifiers: string[]; + appState: AppState; } interface State { @@ -90,7 +89,7 @@ export class App extends React.PureComponent<Props, State> { <Template refresh={this.requestPermissions} template={template} - topQualifiers={this.props.topQualifiers} + topQualifiers={this.props.appState.qualifiers} /> ); } @@ -102,7 +101,7 @@ export class App extends React.PureComponent<Props, State> { permissions={this.state.permissions} ready={this.state.ready} refresh={this.requestPermissions} - topQualifiers={this.props.topQualifiers} + topQualifiers={this.props.appState.qualifiers} /> ); } @@ -121,6 +120,4 @@ export class App extends React.PureComponent<Props, State> { } } -const mapStateToProps = (state: Store) => ({ topQualifiers: getAppState(state).qualifiers }); - -export default connect(mapStateToProps)(App); +export default withAppStateContext(App); diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/App-test.tsx index fa5469f91b5..7dbbdfa0f9f 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/App-test.tsx @@ -19,7 +19,7 @@ */ 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'; @@ -54,5 +54,7 @@ it('should render correctly', async () => { }); function shallowRender(props: Partial<App['props']> = {}) { - return shallow(<App location={mockLocation()} topQualifiers={['TRK']} {...props} />); + return shallow( + <App location={mockLocation()} appState={mockAppState({ qualifiers: ['TRK'] })} {...props} /> + ); } diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx index def9d1e4ae7..0f2d5f98748 100644 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx @@ -18,9 +18,8 @@ * 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'; @@ -32,7 +31,7 @@ import { } from '../../utils'; interface StateProps { - appState: Pick<AppState, 'qualifiers'>; + appState: AppState; } interface OwnProps { @@ -60,9 +59,8 @@ export class AllHoldersList extends React.PureComponent<Props> { 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) => { @@ -70,13 +68,21 @@ export class AllHoldersList extends React.PureComponent<Props> { 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); @@ -100,19 +106,19 @@ export class AllHoldersList extends React.PureComponent<Props> { 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} /> @@ -121,8 +127,4 @@ export class AllHoldersList extends React.PureComponent<Props> { } } -const mapStateToProps = (state: Store): StateProps => ({ - appState: getAppState(state) -}); - -export default connect(mapStateToProps)(AllHoldersList); +export default withAppStateContext(AllHoldersList); diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/AllHoldersList-test.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/AllHoldersList-test.tsx index 354bbf20c36..7ca580ec0db 100644 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/AllHoldersList-test.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/AllHoldersList-test.tsx @@ -20,6 +20,7 @@ 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'; @@ -29,12 +30,16 @@ it('should render correctly', () => { 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'); }); @@ -74,7 +79,7 @@ it('should correctly toggle group permissions', () => { 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()} diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/App-test.tsx.snap index 9ccdf81dfce..bf85f340bc3 100644 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/permissions/global/components/__tests__/__snapshots__/App-test.tsx.snap @@ -16,7 +16,7 @@ exports[`should render correctly 1`] = ` <PageHeader loading={true} /> - <Connect(AllHoldersList) + <withAppStateContext(AllHoldersList) filter="all" grantPermissionToGroup={[Function]} grantPermissionToUser={[Function]} @@ -50,7 +50,7 @@ exports[`should render correctly 2`] = ` <PageHeader loading={false} /> - <Connect(AllHoldersList) + <withAppStateContext(AllHoldersList) filter="all" grantPermissionToGroup={[Function]} grantPermissionToUser={[Function]} diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx index eb380cbc7b1..d515f2294e7 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx @@ -21,6 +21,7 @@ import classNames from 'classnames'; 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'; @@ -28,6 +29,7 @@ import { isBranch, sortBranches } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; import { Branch, BranchLike } from '../../../types/branch-like'; import { + AppState, Component, NewCodePeriod, NewCodePeriodSettingType, @@ -42,9 +44,8 @@ import ProjectBaselineSelector from './ProjectBaselineSelector'; interface Props { branchLike: Branch; branchLikes: BranchLike[]; - branchesEnabled?: boolean; - canAdmin?: boolean; component: Component; + appState: AppState; } interface State { @@ -68,7 +69,7 @@ const DEFAULT_GENERAL_SETTING: { type: NewCodePeriodSettingType } = { 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: [], @@ -127,14 +128,14 @@ export default class App extends React.PureComponent<Props, State> { } 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( @@ -235,7 +236,7 @@ export default class App extends React.PureComponent<Props, State> { }; render() { - const { branchesEnabled, canAdmin, component, branchLike } = this.props; + const { appState, component, branchLike } = this.props; const { analysis, branchList, @@ -255,19 +256,19 @@ export default class App extends React.PureComponent<Props, State> { <> <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} @@ -293,7 +294,7 @@ export default class App extends React.PureComponent<Props, State> { {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> @@ -318,3 +319,5 @@ export default class App extends React.PureComponent<Props, State> { ); } } + +export default withAppStateContext(App); diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/AppContainer.ts b/server/sonar-web/src/main/js/apps/projectBaseline/components/AppContainer.ts deleted file mode 100644 index 17120732063..00000000000 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/AppContainer.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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); diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/App-test.tsx index f05c9404271..e99eb125f1d 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/App-test.tsx @@ -26,9 +26,9 @@ import { } 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({}), @@ -41,7 +41,7 @@ it('should render correctly', async () => { 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'); }); @@ -109,8 +109,7 @@ function shallowRender(props: Partial<App['props']> = {}) { <App branchLike={mockBranch()} branchLikes={[mockMainBranch()]} - branchesEnabled={true} - canAdmin={true} + appState={mockAppState({ branchesEnabled: true, canAdmin: true })} component={mockComponent()} {...props} /> diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/routes.ts b/server/sonar-web/src/main/js/apps/projectBaseline/routes.ts index 16e3be4e88a..9d8f2bd42e4 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/routes.ts +++ b/server/sonar-web/src/main/js/apps/projectBaseline/routes.ts @@ -21,7 +21,7 @@ import { lazyLoadComponent } from '../../components/lazyLoadComponent'; const routes = [ { - indexRoute: { component: lazyLoadComponent(() => import('./components/AppContainer')) } + indexRoute: { component: lazyLoadComponent(() => import('./components/App')) } } ]; diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformation.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformation.tsx index 05c1e3b2f71..3e855acdfa6 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformation.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformation.tsx @@ -18,14 +18,14 @@ * 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 { @@ -65,7 +65,9 @@ export class LifetimeInformation extends React.PureComponent<Props, State> { } render() { - const { canAdmin } = this.props; + const { + appState: { canAdmin } + } = this.props; const { branchAndPullRequestLifeTimeInDays, loading } = this.state; return ( @@ -78,8 +80,4 @@ export class LifetimeInformation extends React.PureComponent<Props, State> { } } -const mapStoreToProps = (state: Store) => ({ - canAdmin: getAppState(state).canAdmin -}); - -export default connect(mapStoreToProps)(LifetimeInformation); +export default withAppStateContext(LifetimeInformation); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LifetimeInformation-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LifetimeInformation-test.tsx index 65326bee132..f34d9e24983 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LifetimeInformation-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LifetimeInformation-test.tsx @@ -20,6 +20,7 @@ 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'; @@ -41,5 +42,7 @@ it('should render correctly', async () => { }); function shallowRender(props: Partial<LifetimeInformation['props']> = {}) { - return shallow<LifetimeInformation>(<LifetimeInformation canAdmin={true} {...props} />); + return shallow<LifetimeInformation>( + <LifetimeInformation appState={mockAppState({ canAdmin: true })} {...props} /> + ); } diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap index 0bd2000c616..e673ebd9ff5 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap @@ -11,7 +11,7 @@ exports[`should render correctly 1`] = ` <h1> project_branch_pull_request.page </h1> - <Connect(LifetimeInformation) /> + <withAppStateContext(LifetimeInformation) /> </header> <BranchLikeTabs branchLikes={ diff --git a/server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx b/server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx index 0ad4fcc38de..cd067423adf 100644 --- a/server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx +++ b/server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx @@ -20,8 +20,8 @@ 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'; @@ -33,7 +33,7 @@ import './styles.css'; const POLL_INTERNAL = 5000; interface Props { - appState: Pick<AppState, 'projectImportFeatureEnabled'>; + appState: AppState; component: Component; } @@ -198,4 +198,4 @@ export class ProjectDumpApp extends React.Component<Props, State> { } } -export default withAppState(ProjectDumpApp); +export default withAppStateContext(ProjectDumpApp); diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx index be94169551e..38fa2a56eaf 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx @@ -21,6 +21,7 @@ import { omitBy } from 'lodash'; 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'; @@ -33,7 +34,7 @@ import { addSideBarClass, removeSideBarClass } from '../../../helpers/pages'; 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'; @@ -46,7 +47,7 @@ interface Props { currentUser: CurrentUser; isFavorite: boolean; location: Pick<Location, 'pathname' | 'query'>; - qualifiers: ComponentQualifier[]; + appState: AppState; router: Pick<Router, 'push' | 'replace'>; } @@ -226,7 +227,9 @@ export class AllProjects extends React.PureComponent<Props, State> { /> <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} @@ -313,4 +316,4 @@ export class AllProjects extends React.PureComponent<Props, State> { } } -export default withRouter(AllProjects); +export default withRouter(withAppStateContext(AllProjects)); diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx index 01165132238..c1d5e5491ca 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx @@ -19,12 +19,10 @@ */ 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'))); diff --git a/server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx b/server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx index 268fbd19754..4fe00dc2785 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx @@ -19,9 +19,9 @@ */ 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'; @@ -32,7 +32,7 @@ import { Permissions } from '../../../types/permissions'; import { AppState, LoggedInUser } from '../../../types/types'; export interface ApplicationCreationProps { - appState: Pick<AppState, 'qualifiers'>; + appState: AppState; className?: string; currentUser: LoggedInUser; router: Router; @@ -84,4 +84,4 @@ export function ApplicationCreation(props: ApplicationCreationProps) { ); } -export default withAppState(withCurrentUser(withRouter(ApplicationCreation))); +export default withCurrentUser(withRouter(withAppStateContext(ApplicationCreation))); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx index 819e956a14a..63b97a12434 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx @@ -20,6 +20,7 @@ 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'; @@ -174,7 +175,9 @@ function shallowRender( 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} /> diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap index 6f89a384375..8dff4f4dd70 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap @@ -21,7 +21,7 @@ exports[`should render correctly 1`] = ` <Connect(withCurrentUser(ProjectCreationMenu)) className="little-spacer-right" /> - <Connect(withAppState(Connect(withCurrentUser(withRouter(ApplicationCreation))))) + <Connect(withCurrentUser(withRouter(withAppStateContext(ApplicationCreation)))) className="little-spacer-right" /> <Connect(HomePageSelect) @@ -97,7 +97,7 @@ exports[`should render correctly while loading 1`] = ` <Connect(withCurrentUser(ProjectCreationMenu)) className="little-spacer-right" /> - <Connect(withAppState(Connect(withCurrentUser(withRouter(ApplicationCreation))))) + <Connect(withCurrentUser(withRouter(withAppStateContext(ApplicationCreation)))) className="little-spacer-right" /> <Connect(HomePageSelect) diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx index 7b090bbc3ee..be9c7f03620 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx @@ -29,10 +29,10 @@ import ListFooter from '../../components/controls/ListFooter'; 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'; @@ -40,7 +40,6 @@ import Search from './Search'; export interface Props { currentUser: LoggedInUser; - appState: Pick<AppState, 'qualifiers'>; } interface State { @@ -198,7 +197,7 @@ export class App extends React.PureComponent<Props, 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"> @@ -228,7 +227,6 @@ export class App extends React.PureComponent<Props, State> { query={this.state.query} ready={this.state.ready} selection={this.state.selection} - topLevelQualifiers={appState.qualifiers} total={this.state.total} visibility={this.state.visibility} /> @@ -262,7 +260,6 @@ export class App extends React.PureComponent<Props, State> { } const mapStateToProps = (state: Store) => ({ - appState: getAppState(state), currentUser: getCurrentUser(state) as LoggedInUser }); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx index 8f47af0fdf4..4d19a37e27a 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx @@ -20,6 +20,7 @@ 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'; @@ -28,7 +29,7 @@ import SearchBox from '../../components/controls/SearchBox'; 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'; @@ -48,7 +49,7 @@ export interface Props { query: string; ready: boolean; selection: any[]; - topLevelQualifiers: string[]; + appState: AppState; total: number; visibility?: Visibility; } @@ -60,12 +61,12 @@ interface State { 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 })); @@ -281,3 +282,5 @@ export default class Search extends React.PureComponent<Props, State> { ); } } + +export default withAppStateContext(Search); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx index cc02fe3436f..653e059a155 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/App-test.tsx @@ -22,10 +22,9 @@ import * as React from 'react'; 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'); @@ -65,7 +64,7 @@ it('fetches all projects on mount', async () => { 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, @@ -76,22 +75,21 @@ it('selects provisioned', () => { 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', @@ -138,10 +136,10 @@ it('selects and deselects projects', async () => { 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']); }); @@ -165,7 +163,6 @@ it('creates project', () => { 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} /> diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx index 11dc6ea01f0..b4e3f13b637 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx @@ -19,8 +19,9 @@ */ 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(); @@ -37,12 +38,17 @@ it('disables the delete and bulk apply buttons unless a project is selected', () }); 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' }); @@ -129,7 +135,7 @@ function shallowRender(props?: { [P in keyof Props]?: Props[P] }) { query="" ready={true} selection={[]} - topLevelQualifiers={['TRK']} + appState={mockAppState({ qualifiers: ['TRK'] })} total={17} {...props} /> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx index d7ae7b87b7d..81dbb739289 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx @@ -19,11 +19,11 @@ */ 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'; @@ -39,7 +39,7 @@ import Condition from './Condition'; import ConditionModal from './ConditionModal'; interface Props { - appState: Pick<AppState, 'branchesEnabled'>; + appState: AppState; canEdit: boolean; conditions: ConditionType[]; metrics: Dict<Metric>; @@ -228,4 +228,4 @@ export class Conditions extends React.PureComponent<Props> { } } -export default withAppState(withMetricsContext(Conditions)); +export default withMetricsContext(withAppStateContext(Conditions)); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Conditions-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Conditions-test.tsx index c63143a38da..2963e8c21f2 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Conditions-test.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Conditions-test.tsx @@ -20,7 +20,7 @@ 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'; @@ -57,7 +57,7 @@ it('should render the add conditions button and modal', () => { 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={{ diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap index 456f401e581..7354a115ce8 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap @@ -4,7 +4,7 @@ exports[`should render correctly: Admin 1`] = ` <div className="layout-page-main-inner" > - <Connect(withAppState(withMetricsContext(Conditions))) + <withMetricsContext(withAppStateContext(Conditions)) canEdit={false} conditions={Array []} onAddCondition={[MockFunction]} @@ -80,7 +80,7 @@ exports[`should render correctly: is default 1`] = ` <div className="layout-page-main-inner" > - <Connect(withAppState(withMetricsContext(Conditions))) + <withMetricsContext(withAppStateContext(Conditions)) canEdit={false} conditions={ Array [ @@ -150,7 +150,7 @@ exports[`should render correctly: is default, no conditions 1`] = ` > quality_gates.is_default_no_conditions </Alert> - <Connect(withAppState(withMetricsContext(Conditions))) + <withMetricsContext(withAppStateContext(Conditions)) canEdit={false} conditions={Array []} onAddCondition={[MockFunction]} @@ -198,7 +198,7 @@ exports[`should render correctly: is not default 1`] = ` <div className="layout-page-main-inner" > - <Connect(withAppState(withMetricsContext(Conditions))) + <withMetricsContext(withAppStateContext(Conditions)) canEdit={false} conditions={ Array [ diff --git a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx index c05f1fb30fc..69722c98a05 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx @@ -20,17 +20,16 @@ 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; @@ -38,7 +37,7 @@ export interface CategoriesListProps { } 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()]) @@ -55,7 +54,7 @@ export function CategoriesList(props: CategoriesListProps) { : // Global settings c.availableGlobally ) - .filter(c => branchesEnabled || !c.requiresBranchesEnabled) + .filter(c => appState.branchesEnabled || !c.requiresBranchesEnabled) ); const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase()); @@ -84,8 +83,4 @@ export function CategoriesList(props: CategoriesListProps) { ); } -const mapStateToProps = (state: Store) => ({ - branchesEnabled: getAppState(state).branchesEnabled -}); - -export default connect(mapStateToProps)(CategoriesList); +export default withAppStateContext(CategoriesList); diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx index 35e919e9f31..3d7a1401872 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx @@ -20,6 +20,7 @@ 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'; @@ -65,13 +66,15 @@ it('should render correctly', () => { 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="" diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap index 7d3b5667d2b..8787399d313 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap @@ -63,7 +63,7 @@ exports[`should render additional categories component correctly 3`] = ` `; exports[`should render additional categories component correctly 4`] = ` -<withRouter(Connect(withAppState(AlmIntegration))) +<withRouter(withAppStateContext(AlmIntegration)) categories={Array []} component={ Object { @@ -93,7 +93,7 @@ exports[`should render additional categories component correctly 4`] = ` `; exports[`should render additional categories component correctly 5`] = ` -<Connect(Connect(withCurrentUser(PRDecorationBinding))) +<Connect(withCurrentUser(PRDecorationBinding)) component={ Object { "breadcrumbs": Array [], diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsAppRenderer-test.tsx.snap index 1b1ea74df0c..a40cc74b2c1 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsAppRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsAppRenderer-test.tsx.snap @@ -50,7 +50,7 @@ exports[`should render almintegration correctly 1`] = ` <div className="big-padded" > - <withRouter(Connect(withAppState(AlmIntegration))) + <withRouter(withAppStateContext(AlmIntegration)) categories={ Array [ "foo category", @@ -177,7 +177,7 @@ exports[`should render default view correctly: All Categories List 1`] = ` <div className="layout-page-side-inner" > - <Connect(CategoriesList) + <withAppStateContext(CategoriesList) categories={ Array [ "foo category", diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx index f26b1f7ffc2..7a54c0bc12b 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx @@ -25,7 +25,7 @@ import { 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, @@ -39,7 +39,7 @@ import { AppState, Dict } from '../../../../types/types'; import AlmIntegrationRenderer from './AlmIntegrationRenderer'; interface Props extends Pick<WithRouterProps, 'location' | 'router'> { - appState: Pick<AppState, 'branchesEnabled' | 'multipleAlmEnabled'>; + appState: AppState; definitions: ExtendedSettingDefinition[]; } @@ -246,4 +246,4 @@ export class AlmIntegration extends React.PureComponent<Props, State> { } } -export default withRouter(withAppState(AlmIntegration)); +export default withRouter(withAppStateContext(AlmIntegration)); diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/CreationTooltip.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/CreationTooltip.tsx index 147f90d8c46..9f53a41396f 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/CreationTooltip.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/CreationTooltip.tsx @@ -19,8 +19,8 @@ */ 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'; @@ -73,4 +73,4 @@ export function CreationTooltip(props: CreationTooltipProps) { ); } -export default withAppState(CreationTooltip); +export default withAppStateContext(CreationTooltip); diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx index c6f66f233bb..aa701feea92 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx @@ -25,7 +25,7 @@ import { 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'; @@ -189,7 +189,7 @@ it('should detect the current ALM from the query', () => { function shallowRender(props: Partial<AlmIntegration['props']> = {}) { return shallow<AlmIntegration>( <AlmIntegration - appState={{ branchesEnabled: true }} + appState={mockAppState({ branchesEnabled: true })} definitions={[]} location={mockLocation()} router={mockRouter()} diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap index ba36a1e7f3f..b987913a570 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap @@ -13,7 +13,7 @@ exports[`should render correctly for multi-ALM binding: editing a definition 1`] <div className="spacer-bottom text-right" > - <Connect(withAppState(CreationTooltip)) + <withAppStateContext(CreationTooltip) alm="azure" preventCreation={false} > @@ -24,7 +24,7 @@ exports[`should render correctly for multi-ALM binding: editing a definition 1`] > settings.almintegration.create </Button> - </Connect(withAppState(CreationTooltip))> + </withAppStateContext(CreationTooltip)> </div> <AlmBindingDefinitionBox alm="azure" @@ -71,7 +71,7 @@ exports[`should render correctly for multi-ALM binding: loaded 1`] = ` <div className="spacer-bottom text-right" > - <Connect(withAppState(CreationTooltip)) + <withAppStateContext(CreationTooltip) alm="azure" preventCreation={false} > @@ -82,7 +82,7 @@ exports[`should render correctly for multi-ALM binding: loaded 1`] = ` > settings.almintegration.create </Button> - </Connect(withAppState(CreationTooltip))> + </withAppStateContext(CreationTooltip)> </div> <AlmBindingDefinitionBox alm="azure" @@ -129,7 +129,7 @@ exports[`should render correctly for multi-ALM binding: loading ALM definitions <div className="spacer-bottom text-right" > - <Connect(withAppState(CreationTooltip)) + <withAppStateContext(CreationTooltip) alm="azure" preventCreation={false} > @@ -140,7 +140,7 @@ exports[`should render correctly for multi-ALM binding: loading ALM definitions > settings.almintegration.create </Button> - </Connect(withAppState(CreationTooltip))> + </withAppStateContext(CreationTooltip)> </div> <AlmBindingDefinitionBox alm="azure" @@ -187,7 +187,7 @@ exports[`should render correctly for multi-ALM binding: loading project count 1` <div className="spacer-bottom text-right" > - <Connect(withAppState(CreationTooltip)) + <withAppStateContext(CreationTooltip) alm="azure" preventCreation={true} > @@ -198,7 +198,7 @@ exports[`should render correctly for multi-ALM binding: loading project count 1` > settings.almintegration.create </Button> - </Connect(withAppState(CreationTooltip))> + </withAppStateContext(CreationTooltip)> </div> <AlmBindingDefinitionBox alm="azure" @@ -245,7 +245,7 @@ exports[`should render correctly for single-ALM binding 1`] = ` <div className="spacer-bottom text-right" > - <Connect(withAppState(CreationTooltip)) + <withAppStateContext(CreationTooltip) alm="azure" preventCreation={true} > @@ -256,7 +256,7 @@ exports[`should render correctly for single-ALM binding 1`] = ` > settings.almintegration.create </Button> - </Connect(withAppState(CreationTooltip))> + </withAppStateContext(CreationTooltip)> </div> <AlmBindingDefinitionBox alm="azure" @@ -303,7 +303,7 @@ exports[`should render correctly for single-ALM binding 2`] = ` <div className="spacer-bottom text-right" > - <Connect(withAppState(CreationTooltip)) + <withAppStateContext(CreationTooltip) alm="azure" preventCreation={true} > @@ -314,7 +314,7 @@ exports[`should render correctly for single-ALM binding 2`] = ` > settings.almintegration.create </Button> - </Connect(withAppState(CreationTooltip))> + </withAppStateContext(CreationTooltip)> </div> <AlmBindingDefinitionBox alm="azure" @@ -361,7 +361,7 @@ exports[`should render correctly for single-ALM binding 3`] = ` <div className="spacer-bottom text-right" > - <Connect(withAppState(CreationTooltip)) + <withAppStateContext(CreationTooltip) alm="azure" preventCreation={true} > @@ -372,7 +372,7 @@ exports[`should render correctly for single-ALM binding 3`] = ` > settings.almintegration.create </Button> - </Connect(withAppState(CreationTooltip))> + </withAppStateContext(CreationTooltip)> </div> <AlmBindingDefinitionBox alm="azure" @@ -424,7 +424,7 @@ exports[`should render correctly with validation: create a first 1`] = ` <div className="big-spacer-top" > - <Connect(withAppState(CreationTooltip)) + <withAppStateContext(CreationTooltip) alm="azure" preventCreation={false} > @@ -435,7 +435,7 @@ exports[`should render correctly with validation: create a first 1`] = ` > settings.almintegration.create </Button> - </Connect(withAppState(CreationTooltip))> + </withAppStateContext(CreationTooltip)> </div> </DeferredSpinner> </div> @@ -467,7 +467,7 @@ exports[`should render correctly with validation: create a second 1`] = ` <div className="spacer-bottom text-right" > - <Connect(withAppState(CreationTooltip)) + <withAppStateContext(CreationTooltip) alm="azure" preventCreation={false} > @@ -478,7 +478,7 @@ exports[`should render correctly with validation: create a second 1`] = ` > settings.almintegration.create </Button> - </Connect(withAppState(CreationTooltip))> + </withAppStateContext(CreationTooltip)> </div> <AlmBindingDefinitionBox alm="azure" @@ -529,7 +529,7 @@ exports[`should render correctly with validation: default 1`] = ` <div className="spacer-bottom text-right" > - <Connect(withAppState(CreationTooltip)) + <withAppStateContext(CreationTooltip) alm="azure" preventCreation={false} > @@ -540,7 +540,7 @@ exports[`should render correctly with validation: default 1`] = ` > settings.almintegration.create </Button> - </Connect(withAppState(CreationTooltip))> + </withAppStateContext(CreationTooltip)> </div> <AlmBindingDefinitionBox alm="azure" @@ -596,7 +596,7 @@ exports[`should render correctly with validation: empty 1`] = ` <div className="big-spacer-top" > - <Connect(withAppState(CreationTooltip)) + <withAppStateContext(CreationTooltip) alm="azure" preventCreation={false} > @@ -607,7 +607,7 @@ exports[`should render correctly with validation: empty 1`] = ` > settings.almintegration.create </Button> - </Connect(withAppState(CreationTooltip))> + </withAppStateContext(CreationTooltip)> </div> </DeferredSpinner> </div> @@ -639,7 +639,7 @@ exports[`should render correctly with validation: pass the correct key for bitbu <div className="spacer-bottom text-right" > - <Connect(withAppState(CreationTooltip)) + <withAppStateContext(CreationTooltip) alm="bitbucket" preventCreation={false} > @@ -650,7 +650,7 @@ exports[`should render correctly with validation: pass the correct key for bitbu > settings.almintegration.create </Button> - </Connect(withAppState(CreationTooltip))> + </withAppStateContext(CreationTooltip)> </div> <AlmBindingDefinitionBox alm="bitbucketcloud" diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx index 6020027303c..cd60e263b9d 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx @@ -20,6 +20,7 @@ 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'; @@ -31,14 +32,15 @@ import { 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 { @@ -140,12 +142,12 @@ function renderField( ); } -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; @@ -275,6 +277,11 @@ export default function AlmSpecificForm(props: AlmSpecificFormProps) { 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} @@ -301,3 +308,5 @@ export default function AlmSpecificForm(props: AlmSpecificFormProps) { </> ); } + +export default withAppStateContext(AlmSpecificForm); diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx index 6c1369995f4..885689f50c9 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { connect } from 'react-redux'; import { deleteProjectAlmBinding, getAlmSettings, @@ -34,24 +33,18 @@ import throwGlobalError from '../../../../app/utils/throwGlobalError'; 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; @@ -81,7 +74,7 @@ const REQUIRED_FIELDS_BY_ALM: { [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 }, @@ -343,7 +336,7 @@ export class PRDecorationBinding extends React.PureComponent<Props & StateProps, }; render() { - const { currentUser, monorepoEnabled } = this.props; + const { currentUser } = this.props; return ( <PRDecorationBindingRenderer @@ -351,7 +344,6 @@ export class PRDecorationBinding extends React.PureComponent<Props & StateProps, onReset={this.handleReset} onSubmit={this.handleSubmit} onCheckConfiguration={this.handleCheckConfiguration} - monorepoEnabled={monorepoEnabled} isSysAdmin={hasGlobalPermission(currentUser, Permissions.Admin)} {...this.state} /> @@ -359,11 +351,4 @@ export class PRDecorationBinding extends React.PureComponent<Props & StateProps, } } -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); diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx index 2b113aef557..29162ee65e5 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx @@ -50,7 +50,6 @@ export interface PRDecorationBindingRendererProps { onSubmit: () => void; updating: boolean; successfullyUpdated: boolean; - monorepoEnabled: boolean; onCheckConfiguration: () => void; checkingConfiguration: boolean; configurationErrors?: ProjectAlmBindingConfigurationErrors; @@ -78,7 +77,6 @@ export default function PRDecorationBindingRenderer(props: PRDecorationBindingRe loading, updating, successfullyUpdated, - monorepoEnabled, checkingConfiguration, configurationErrors, isSysAdmin @@ -169,7 +167,6 @@ export default function PRDecorationBindingRenderer(props: PRDecorationBindingRe instances={instances} formData={formData} onFieldChange={props.onFieldChange} - monorepoEnabled={monorepoEnabled} /> )} diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx index 4a7151f0ada..cdc9538d83a 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx @@ -20,8 +20,10 @@ 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], @@ -48,7 +50,9 @@ it.each([ ); 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> = {}) { @@ -63,7 +67,7 @@ function shallowRender(alm: AlmKeys, props: Partial<AlmSpecificFormProps> = {}) monorepo: false }} onFieldChange={jest.fn()} - monorepoEnabled={false} + appState={mockAppState({ edition: EditionKey.developer })} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx index eeb1756458f..793ee25b46b 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx @@ -420,7 +420,6 @@ function shallowRender(props: Partial<PRDecorationBinding['props']> = {}) { <PRDecorationBinding currentUser={mockCurrentUser()} component={mockComponent({ key: PROJECT_KEY })} - monorepoEnabled={false} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx index 96c6dc4d450..a4bf1454e6d 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx @@ -154,7 +154,6 @@ function shallowRender(props: Partial<PRDecorationBindingRendererProps> = {}) { onSubmit={jest.fn()} updating={false} successfullyUpdated={false} - monorepoEnabled={false} checkingConfiguration={false} onCheckConfiguration={jest.fn()} isSysAdmin={false} diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBinding-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBinding-test.tsx.snap index eb3d7ad054b..aa67734fdaf 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBinding-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBinding-test.tsx.snap @@ -15,7 +15,6 @@ exports[`should render correctly 1`] = ` isSysAdmin={false} isValid={false} loading={true} - monorepoEnabled={false} onCheckConfiguration={[Function]} onFieldChange={[Function]} onReset={[Function]} diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap index 1ebb0bf76f4..f1bf70fdb61 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap @@ -172,7 +172,7 @@ exports[`should render correctly: when there are configuration errors (admin use /> </div> </div> - <AlmSpecificForm + <withAppStateContext(AlmSpecificForm) alm="github" formData={ Object { @@ -204,7 +204,6 @@ exports[`should render correctly: when there are configuration errors (admin use }, ] } - monorepoEnabled={false} onFieldChange={[MockFunction]} /> <div @@ -743,7 +742,7 @@ exports[`should render correctly: with a valid and saved form 1`] = ` /> </div> </div> - <AlmSpecificForm + <withAppStateContext(AlmSpecificForm) alm="github" formData={ Object { @@ -775,7 +774,6 @@ exports[`should render correctly: with a valid and saved form 1`] = ` }, ] } - monorepoEnabled={false} onFieldChange={[MockFunction]} /> <div diff --git a/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx index 53cf6c5c629..486c80de4c3 100644 --- a/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx @@ -18,12 +18,12 @@ * 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 { @@ -31,22 +31,14 @@ 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> @@ -67,7 +59,7 @@ export function PageHeader(props: Props) { )} {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> @@ -109,8 +101,4 @@ Date: ${toShortNotSoISOString(Date.now())} ); } -const mapStateToProps = (store: Store) => ({ - productionDatabase: getAppState(store).productionDatabase -}); - -export default connect(mapStateToProps)(PageHeader); +export default withAppStateContext(PageHeader); diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx index b79bdf364e1..e8725a325af 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx @@ -19,6 +19,7 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; +import { mockAppState } from '../../../../helpers/testMocks'; import { PageHeader, Props } from '../PageHeader'; jest.mock('../../../../helpers/dates', () => ({ @@ -28,7 +29,11 @@ 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(); @@ -41,7 +46,7 @@ function shallowRender(props: Partial<Props> = {}) { loading={false} logLevel="INFO" onLogLevelChange={jest.fn()} - productionDatabase={true} + appState={mockAppState({ productionDatabase: true })} showActions={true} {...props} /> diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/App-test.tsx.snap index 9e6b4ffab23..274ea824bba 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/App-test.tsx.snap @@ -16,11 +16,11 @@ exports[`should render correctly: cluster sysinfo 1`] = ` <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" @@ -209,7 +209,7 @@ exports[`should render correctly: loading 1`] = ` <div className="page-notifs" > - <Connect(withCurrentUser(Connect(withAppState(UpdateNotification)))) + <Connect(withCurrentUser(withAppStateContext(UpdateNotification))) dismissable={false} /> </div> @@ -232,11 +232,11 @@ exports[`should render correctly: stand-alone sysinfo 1`] = ` <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" |