]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-17118 migrate branch support flag to features/list API
authorMatteo Mara <matteo.mara@sonarsource.com>
Mon, 10 Oct 2022 14:07:02 +0000 (16:07 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 12 Oct 2022 20:03:43 +0000 (20:03 +0000)
48 files changed:
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/Menu-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Header-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/BranchLikeNavigation-test.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsIssues-test.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetails-test.tsx.snap
server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx
server/sonar-web/src/main/js/apps/overview/components/App.tsx
server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/App-test.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/App-it.tsx
server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx
server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsAppRenderer-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegration-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/__tests__/__snapshots__/TutorialSelectionRenderer-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/PublishSteps.tsx
server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/AnalysisCommand.tsx
server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/AnalysisCommand-test.tsx
server/sonar-web/src/main/js/components/tutorials/components/AllSet.tsx
server/sonar-web/src/main/js/components/tutorials/components/__tests__/AllSet-test.tsx
server/sonar-web/src/main/js/components/tutorials/github-action/AnalysisCommand.tsx
server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/AnalysisCommand-test.tsx
server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/__snapshots__/AnalysisCommand-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/gitlabci/YmlFileStep.tsx
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/YmlFileStep-test.tsx
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/GitLabCITutorial-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/jenkins/JenkinsTutorial.tsx
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-test.tsx
server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
server/sonar-web/src/main/js/types/appstate.ts
server/sonar-web/src/main/js/types/features.ts
server/sonar-webserver-api/src/main/java/org/sonar/server/branch/BranchFeatureExtension.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/ui/ws/GlobalAction.java
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/ui/ws/global-example.json
server/sonar-webserver-webapi/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java

index 1e788618770c3f975587e21c1671b881cbe969c6..f4dfc260e659ff0c3af90c37b25f167dc5a3ee33 100644 (file)
@@ -38,21 +38,22 @@ import {
   ProjectAlmBindingConfigurationErrors,
   ProjectAlmBindingResponse
 } from '../../types/alm-settings';
-import { AppState } from '../../types/appstate';
 import { BranchLike } from '../../types/branch-like';
 import { ComponentQualifier, isPortfolioLike } from '../../types/component';
+import { Feature } from '../../types/features';
 import { Task, TaskStatuses, TaskTypes, TaskWarning } from '../../types/tasks';
 import { Component, Status } from '../../types/types';
 import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
-import withAppStateContext from './app-state/withAppStateContext';
+import withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from './available-features/withAvailableFeatures';
 import withBranchStatusActions from './branch-status/withBranchStatusActions';
 import ComponentContainerNotFound from './ComponentContainerNotFound';
 import { ComponentContext } from './componentContext/ComponentContext';
 import PageUnavailableDueToIndexation from './indexation/PageUnavailableDueToIndexation';
 import ComponentNav from './nav/component/ComponentNav';
 
-interface Props {
-  appState: AppState;
+interface Props extends WithAvailableFeaturesProps {
   location: Location;
   updateBranchStatus: (branchLike: BranchLike, component: string, status: Status) => void;
   router: Router;
@@ -155,9 +156,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
   };
 
   fetchBranches = async (componentWithQualifier: Component) => {
-    const {
-      appState: { branchesEnabled }
-    } = this.props;
+    const { hasFeature } = this.props;
 
     const breadcrumb = componentWithQualifier.breadcrumbs.find(({ qualifier }) => {
       return ([ComponentQualifier.Application, ComponentQualifier.Project] as string[]).includes(
@@ -172,7 +171,8 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
       const { key } = breadcrumb;
       const [branches, pullRequests] = await Promise.all([
         getBranches(key),
-        !branchesEnabled || breadcrumb.qualifier === ComponentQualifier.Application
+        !hasFeature(Feature.BranchSupport) ||
+        breadcrumb.qualifier === ComponentQualifier.Application
           ? Promise.resolve([])
           : getPullRequests(key)
       ]);
@@ -251,7 +251,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
     if (
       component.qualifier === ComponentQualifier.Project &&
       component.analysisDate === undefined &&
-      this.props.appState.branchesEnabled
+      this.props.hasFeature(Feature.BranchSupport)
     ) {
       const projectBindingErrors = await validateProjectAlmBinding(component.key).catch(
         () => undefined
@@ -471,4 +471,4 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
   }
 }
 
-export default withRouter(withAppStateContext(withBranchStatusActions(ComponentContainer)));
+export default withRouter(withAvailableFeatures(withBranchStatusActions(ComponentContainer)));
index 45c4bfe9c6e695085641a53929b1a1f69b00676e..9e571d079a1caab03388e9a67fade5b709dc3f51 100644 (file)
@@ -29,7 +29,7 @@ import { mockBranch, mockMainBranch, mockPullRequest } from '../../../helpers/mo
 import { mockComponent } from '../../../helpers/mocks/component';
 import { mockTask } from '../../../helpers/mocks/tasks';
 import { HttpStatus } from '../../../helpers/request';
-import { mockAppState, mockLocation, mockRouter } from '../../../helpers/testMocks';
+import { mockLocation, mockRouter } from '../../../helpers/testMocks';
 import { waitAndUpdate } from '../../../helpers/testUtils';
 import { AlmKeys } from '../../../types/alm-settings';
 import { ComponentQualifier } from '../../../types/component';
@@ -141,7 +141,7 @@ it("doesn't load branches portfolio", async () => {
 it('updates branches on change', async () => {
   const updateBranchStatus = jest.fn();
   const wrapper = shallowRender({
-    appState: mockAppState({ branchesEnabled: true }),
+    hasFeature: () => true,
     location: mockLocation({ query: { id: 'portfolioKey' } }),
     updateBranchStatus
   });
@@ -403,7 +403,7 @@ describe('should correctly validate the project binding depending on the context
       (validateProjectAlmBinding as jest.Mock).mockResolvedValueOnce(projectBindingErrors);
     }
 
-    const wrapper = shallowRender({ appState: mockAppState({ branchesEnabled: true }) });
+    const wrapper = shallowRender({ hasFeature: () => true });
     await waitAndUpdate(wrapper);
     expect(wrapper.state().projectBindingErrors).toBe(projectBindingErrors);
 
@@ -435,7 +435,7 @@ it.each([
 function shallowRender(props: Partial<ComponentContainer['props']> = {}) {
   return shallow<ComponentContainer>(
     <ComponentContainer
-      appState={mockAppState()}
+      hasFeature={jest.fn().mockReturnValue(false)}
       location={mockLocation({ query: { id: 'foo' } })}
       updateBranchStatus={jest.fn()}
       router={mockRouter()}
index 7c76a14f8104dc2b4d7e2cdcfc4f023119d283f6..825823e47634f002136d2c565aeaec1d5ea8808c 100644 (file)
@@ -29,11 +29,13 @@ import NavBarTabs from '../../../../components/ui/NavBarTabs';
 import { getBranchLikeQuery, isPullRequest } from '../../../../helpers/branch-like';
 import { hasMessage, translate, translateWithParameters } from '../../../../helpers/l10n';
 import { getPortfolioUrl, getProjectQueryUrl } from '../../../../helpers/urls';
-import { AppState } from '../../../../types/appstate';
 import { BranchLike, BranchParameters } from '../../../../types/branch-like';
 import { ComponentQualifier, isPortfolioLike } from '../../../../types/component';
+import { Feature } from '../../../../types/features';
 import { Component, Dict, Extension } from '../../../../types/types';
-import withAppStateContext from '../../app-state/withAppStateContext';
+import withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../available-features/withAvailableFeatures';
 import './Menu.css';
 
 const SETTINGS_URLS = [
@@ -52,8 +54,7 @@ const SETTINGS_URLS = [
   '/project/webhooks'
 ];
 
-interface Props {
-  appState: AppState;
+interface Props extends WithAvailableFeaturesProps {
   branchLike: BranchLike | undefined;
   branchLikes: BranchLike[] | undefined;
   component: Component;
@@ -389,7 +390,7 @@ export class Menu extends React.PureComponent<Props> {
 
   renderBranchesLink = (query: Query, isProject: boolean) => {
     if (
-      !this.props.appState.branchesEnabled ||
+      !this.props.hasFeature(Feature.BranchSupport) ||
       !isProject ||
       !this.getConfiguration().showSettings
     ) {
@@ -641,4 +642,4 @@ export class Menu extends React.PureComponent<Props> {
   }
 }
 
-export default withAppStateContext(Menu);
+export default withAvailableFeatures(Menu);
index 1ffc075f483f1d0caf2a90d8951c03534211b3a6..fe446883e96b760009b8834edc8ada811a1be1dd 100644 (file)
@@ -25,7 +25,6 @@ import {
   mockPullRequest
 } from '../../../../../helpers/mocks/branch-like';
 import { mockComponent } from '../../../../../helpers/mocks/component';
-import { mockAppState } from '../../../../../helpers/testMocks';
 import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
 import { ComponentQualifier } from '../../../../../types/component';
 import { Menu } from '../Menu';
@@ -139,7 +138,7 @@ function renderMenu(props: Partial<Menu['props']> = {}) {
   const mainBranch = mockMainBranch();
   return renderComponent(
     <Menu
-      appState={mockAppState()}
+      hasFeature={jest.fn().mockReturnValue(false)}
       branchLike={mainBranch}
       branchLikes={[mainBranch]}
       component={BASE_COMPONENT}
index b64a5de49641445d9939cac2d044ec73f081a84f..d6d6c538a0b0c7c4e2ac4a47e5cffc9d57190577 100644 (file)
@@ -49,7 +49,7 @@ exports[`should render correctly 1`] = `
       favorite={false}
       qualifier="TRK"
     />
-    <withAppStateContext(Component)
+    <withAvailableFeaturesContext(Component)
       branchLikes={
         Array [
           Object {
index d0bee5e91f1b938250b4a0b257f2b42908309344..8a7dff79217c122ca881a65e98aabbebbeb81f3d 100644 (file)
@@ -22,16 +22,17 @@ import * as React from 'react';
 import { ButtonPlain } from '../../../../../components/controls/buttons';
 import Toggler from '../../../../../components/controls/Toggler';
 import { ProjectAlmBindingResponse } from '../../../../../types/alm-settings';
-import { AppState } from '../../../../../types/appstate';
 import { BranchLike } from '../../../../../types/branch-like';
+import { Feature } from '../../../../../types/features';
 import { Component } from '../../../../../types/types';
-import withAppStateContext from '../../../app-state/withAppStateContext';
+import withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../available-features/withAvailableFeatures';
 import './BranchLikeNavigation.css';
 import CurrentBranchLike from './CurrentBranchLike';
 import Menu from './Menu';
 
-export interface BranchLikeNavigationProps {
-  appState: AppState;
+export interface BranchLikeNavigationProps extends WithAvailableFeaturesProps {
   branchLikes: BranchLike[];
   component: Component;
   currentBranchLike: BranchLike;
@@ -40,7 +41,6 @@ export interface BranchLikeNavigationProps {
 
 export function BranchLikeNavigation(props: BranchLikeNavigationProps) {
   const {
-    appState: { branchesEnabled },
     branchLikes,
     component,
     component: { configuration },
@@ -49,14 +49,15 @@ export function BranchLikeNavigation(props: BranchLikeNavigationProps) {
   } = props;
 
   const [isMenuOpen, setIsMenuOpen] = React.useState(false);
+  const branchSupportEnabled = props.hasFeature(Feature.BranchSupport);
 
   const canAdminComponent = configuration && configuration.showSettings;
   const hasManyBranches = branchLikes.length >= 2;
-  const isMenuEnabled = branchesEnabled && hasManyBranches;
+  const isMenuEnabled = branchSupportEnabled && hasManyBranches;
 
   const currentBranchLikeElement = (
     <CurrentBranchLike
-      branchesEnabled={Boolean(branchesEnabled)}
+      branchesEnabled={branchSupportEnabled}
       component={component}
       currentBranchLike={currentBranchLike}
       hasManyBranches={hasManyBranches}
@@ -100,4 +101,4 @@ export function BranchLikeNavigation(props: BranchLikeNavigationProps) {
   );
 }
 
-export default withAppStateContext(React.memo(BranchLikeNavigation));
+export default withAvailableFeatures(React.memo(BranchLikeNavigation));
index 4be90c8c544f2b81e9215d0b2f95ed2c264d7703..3278a79b8c4cc182334aa5ba1a5b9b960a7e7025 100644 (file)
@@ -23,7 +23,6 @@ import { ButtonPlain } from '../../../../../../components/controls/buttons';
 import Toggler from '../../../../../../components/controls/Toggler';
 import { mockSetOfBranchAndPullRequest } from '../../../../../../helpers/mocks/branch-like';
 import { mockComponent } from '../../../../../../helpers/mocks/component';
-import { mockAppState } from '../../../../../../helpers/testMocks';
 import { click } from '../../../../../../helpers/testUtils';
 import { BranchLikeNavigation, BranchLikeNavigationProps } from '../BranchLikeNavigation';
 
@@ -33,12 +32,12 @@ it('should render correctly', () => {
 });
 
 it('should render the menu trigger if branches are enabled', () => {
-  const wrapper = shallowRender({ appState: mockAppState({ branchesEnabled: true }) });
+  const wrapper = shallowRender({ hasFeature: () => true });
   expect(wrapper).toMatchSnapshot();
 });
 
 it('should properly toggle menu opening when clicking the anchor', () => {
-  const wrapper = shallowRender({ appState: mockAppState({ branchesEnabled: true }) });
+  const wrapper = shallowRender({ hasFeature: () => true });
   expect(wrapper.find(Toggler).props().open).toBe(false);
 
   click(wrapper.find(ButtonPlain));
@@ -49,7 +48,7 @@ it('should properly toggle menu opening when clicking the anchor', () => {
 });
 
 it('should properly close menu when toggler asks for', () => {
-  const wrapper = shallowRender({ appState: mockAppState({ branchesEnabled: true }) });
+  const wrapper = shallowRender({ hasFeature: () => true });
   expect(wrapper.find(Toggler).props().open).toBe(false);
 
   click(wrapper.find(ButtonPlain));
@@ -67,7 +66,7 @@ function shallowRender(props?: Partial<BranchLikeNavigationProps>) {
 
   return shallow(
     <BranchLikeNavigation
-      appState={mockAppState()}
+      hasFeature={jest.fn().mockReturnValue(false)}
       branchLikes={branchLikes}
       component={mockComponent()}
       currentBranchLike={branchLikes[0]}
index 1a6a3ebd3630a528a0dbd87be07ae31c1b7a1630..8ca75699c7b46ee04bcd9848eb07a6514d2b0ec8 100644 (file)
  */
 import * as React from 'react';
 import { getFacet } from '../../../api/issues';
-import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
+import withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../app/components/available-features/withAvailableFeatures';
 import Link from '../../../components/common/Link';
 import Tooltip from '../../../components/controls/Tooltip';
 import DeferredSpinner from '../../../components/ui/DeferredSpinner';
 import { translate } from '../../../helpers/l10n';
 import { formatMeasure } from '../../../helpers/measures';
 import { getIssuesUrl } from '../../../helpers/urls';
-import { AppState } from '../../../types/appstate';
+import { Feature } from '../../../types/features';
 import { RuleDetails } from '../../../types/types';
 
-interface Props {
-  appState: AppState;
+interface Props extends WithAvailableFeaturesProps {
   ruleDetails: Pick<RuleDetails, 'key' | 'type'>;
 }
 
@@ -118,7 +119,7 @@ export class RuleDetailsIssues extends React.PureComponent<Props, State> {
       </span>
     );
 
-    if (!this.props.appState.branchesEnabled) {
+    if (!this.props.hasFeature(Feature.BranchSupport)) {
       return totalItem;
     }
 
@@ -176,4 +177,4 @@ export class RuleDetailsIssues extends React.PureComponent<Props, State> {
   }
 }
 
-export default withAppStateContext(RuleDetailsIssues);
+export default withAvailableFeatures(RuleDetailsIssues);
index 7aeb0542100d57e821dddd49e233543040cfba43..16865c5ea2ef146c153bb0ad2b0def6bb06f4798 100644 (file)
@@ -20,7 +20,6 @@
 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';
 
@@ -60,7 +59,7 @@ it('should fetch issues and render', async () => {
 function shallowRender(props: Partial<RuleDetailsIssues['props']> = {}) {
   return shallow(
     <RuleDetailsIssues
-      appState={mockAppState({ branchesEnabled: false })}
+      hasFeature={jest.fn().mockReturnValue(false)}
       ruleDetails={{ key: 'foo', type: 'BUG' }}
       {...props}
     />
index 39780c67157ed7e3782b7ad4a17cbe72f6d72b89..450d9d3ecc0eff6801361614b080cf4df6ea55fa 100644 (file)
@@ -174,7 +174,7 @@ exports[`should render correctly: loaded 1`] = `
         }
       }
     />
-    <withAppStateContext(RuleDetailsIssues)
+    <withAvailableFeaturesContext(RuleDetailsIssues)
       ruleDetails={
         Object {
           "createdAt": "2014-12-16T17:26:54+0100",
index 384f7906a6673216cfa46b2d146fd6f1152ffbb7..7ae78c00b105fbc7e4cccf1a09c95497f8940603 100644 (file)
@@ -21,12 +21,16 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { getAlmSettings } from '../../../api/alm-settings';
 import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
+import withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../app/components/available-features/withAvailableFeatures';
 import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
 import { translate } from '../../../helpers/l10n';
 import { getProjectUrl } from '../../../helpers/urls';
 import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
 import { AppState } from '../../../types/appstate';
+import { Feature } from '../../../types/features';
 import AlmBindingDefinitionForm from '../../settings/components/almIntegration/AlmBindingDefinitionForm';
 import AzureProjectCreate from './AzureProjectCreate';
 import BitbucketCloudProjectCreate from './BitbucketCloudProjectCreate';
@@ -38,7 +42,7 @@ import ManualProjectCreate from './ManualProjectCreate';
 import './style.css';
 import { CreateProjectModes } from './types';
 
-interface Props {
+interface Props extends WithAvailableFeaturesProps {
   appState: AppState;
   location: Location;
   router: Router;
@@ -144,7 +148,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> {
 
   renderProjectCreation(mode?: CreateProjectModes) {
     const {
-      appState: { canAdmin, branchesEnabled },
+      appState: { canAdmin },
       location,
       router
     } = this.props;
@@ -156,6 +160,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> {
       gitlabSettings,
       loading
     } = this.state;
+    const branchSupportEnabled = this.props.hasFeature(Feature.BranchSupport);
 
     switch (mode) {
       case CreateProjectModes.AzureDevOps: {
@@ -221,7 +226,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> {
       case CreateProjectModes.Manual: {
         return (
           <ManualProjectCreate
-            branchesEnabled={!!branchesEnabled}
+            branchesEnabled={branchSupportEnabled}
             onProjectCreate={this.handleProjectCreate}
           />
         );
@@ -272,4 +277,4 @@ export class CreateProjectPage extends React.PureComponent<Props, State> {
   }
 }
 
-export default withRouter(withAppStateContext(CreateProjectPage));
+export default withRouter(withAvailableFeatures(withAppStateContext(CreateProjectPage)));
index 31dd8c7ca2f5ddc7cee8bc198211179ee02bc9dd..d508cda2e7acc9b6cf186761224401568e9ed331 100644 (file)
@@ -128,6 +128,7 @@ function shallowRender(props: Partial<CreateProjectPage['props']> = {}) {
   return shallow<CreateProjectPage>(
     <CreateProjectPage
       appState={mockAppState()}
+      hasFeature={jest.fn().mockReturnValue(false)}
       location={mockLocation()}
       router={mockRouter()}
       {...props}
index b80ca62eabc1595e3301296a6d208d4f5fecbe0e..26771be6dcb3c4e21b68ceea3a18992639040b74 100644 (file)
  * 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 withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../app/components/available-features/withAvailableFeatures';
 import withComponentContext from '../../../app/components/componentContext/withComponentContext';
 import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { isPullRequest } from '../../../helpers/branch-like';
 import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
-import { AppState } from '../../../types/appstate';
 import { BranchLike } from '../../../types/branch-like';
 import { isPortfolioLike } from '../../../types/component';
+import { Feature } from '../../../types/features';
 import { Component } from '../../../types/types';
 import BranchOverview from '../branches/BranchOverview';
 import PullRequestOverview from '../pullRequests/PullRequestOverview';
 import EmptyOverview from './EmptyOverview';
 
-interface Props {
-  appState: AppState;
+interface Props extends WithAvailableFeaturesProps {
   branchLike?: BranchLike;
   branchLikes: BranchLike[];
   component: Component;
@@ -47,13 +48,8 @@ export class App extends React.PureComponent<Props> {
   };
 
   render() {
-    const {
-      appState: { branchesEnabled },
-      branchLike,
-      branchLikes,
-      component,
-      projectBinding
-    } = this.props;
+    const { branchLike, branchLikes, component, projectBinding } = this.props;
+    const branchSupportEnabled = this.props.hasFeature(Feature.BranchSupport);
 
     if (this.isPortfolio()) {
       return null;
@@ -81,7 +77,7 @@ export class App extends React.PureComponent<Props> {
         {component.analysisDate && (
           <BranchOverview
             branch={branchLike}
-            branchesEnabled={branchesEnabled}
+            branchesEnabled={branchSupportEnabled}
             component={component}
             projectBinding={projectBinding}
           />
@@ -91,4 +87,4 @@ export class App extends React.PureComponent<Props> {
   }
 }
 
-export default withComponentContext(withAppStateContext(App));
+export default withComponentContext(withAvailableFeatures(App));
index 505ed021c241cf5aab6ab737768a036cb64a52ad..6d9959e0bf514128c6c6ccd921943ca2ed0ee83d 100644 (file)
@@ -19,7 +19,6 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import { mockAppState } from '../../../../helpers/testMocks';
 import BranchOverview from '../../branches/BranchOverview';
 import { App } from '../App';
 
@@ -42,6 +41,11 @@ it('should render BranchOverview', () => {
 
 function getWrapper(props = {}) {
   return shallow(
-    <App appState={mockAppState()} branchLikes={[]} component={component} {...props} />
+    <App
+      hasFeature={jest.fn().mockReturnValue(false)}
+      branchLikes={[]}
+      component={component}
+      {...props}
+    />
   );
 }
index a9021768e71e45dbd01cf62c6c54c0e8b35dd22d..a68bf11ed8f43d99c91b7b8f2f401acdff945cf0 100644 (file)
@@ -22,6 +22,9 @@ 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 withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../app/components/available-features/withAvailableFeatures';
 import withComponentContext from '../../../app/components/componentContext/withComponentContext';
 import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
@@ -30,6 +33,7 @@ import { isBranch, sortBranches } from '../../../helpers/branch-like';
 import { translate } from '../../../helpers/l10n';
 import { AppState } from '../../../types/appstate';
 import { Branch, BranchLike } from '../../../types/branch-like';
+import { Feature } from '../../../types/features';
 import {
   Component,
   NewCodePeriod,
@@ -42,7 +46,7 @@ import AppHeader from './AppHeader';
 import BranchList from './BranchList';
 import ProjectBaselineSelector from './ProjectBaselineSelector';
 
-interface Props {
+interface Props extends WithAvailableFeaturesProps {
   branchLike: Branch;
   branchLikes: BranchLike[];
   component: Component;
@@ -129,14 +133,14 @@ export class App extends React.PureComponent<Props, State> {
   }
 
   fetchLeakPeriodSetting() {
-    const { branchLike, appState, component } = this.props;
+    const { branchLike, component } = this.props;
 
     this.setState({ loading: true });
 
     Promise.all([
       getNewCodePeriod(),
       getNewCodePeriod({
-        branch: appState.branchesEnabled ? undefined : branchLike.name,
+        branch: this.props.hasFeature(Feature.BranchSupport) ? undefined : branchLike.name,
         project: component.key
       })
     ]).then(
@@ -252,6 +256,7 @@ export class App extends React.PureComponent<Props, State> {
       selected,
       success
     } = this.state;
+    const branchSupportEnabled = this.props.hasFeature(Feature.BranchSupport);
 
     return (
       <>
@@ -262,14 +267,14 @@ export class App extends React.PureComponent<Props, State> {
             <DeferredSpinner />
           ) : (
             <div className="panel-white project-baseline">
-              {appState.branchesEnabled && <h2>{translate('project_baseline.default_setting')}</h2>}
+              {branchSupportEnabled && <h2>{translate('project_baseline.default_setting')}</h2>}
 
               {generalSetting && overrideGeneralSetting !== undefined && (
                 <ProjectBaselineSelector
                   analysis={analysis}
                   branch={branchLike}
                   branchList={branchList}
-                  branchesEnabled={appState.branchesEnabled}
+                  branchesEnabled={branchSupportEnabled}
                   component={component.key}
                   currentSetting={currentSetting}
                   currentSettingValue={currentSettingValue}
@@ -295,7 +300,7 @@ export class App extends React.PureComponent<Props, State> {
                   {translate('settings.state.saved')}
                 </span>
               </div>
-              {generalSetting && appState.branchesEnabled && (
+              {generalSetting && branchSupportEnabled && (
                 <div className="huge-spacer-top branch-baseline-selector">
                   <hr />
                   <h2>{translate('project_baseline.configure_branches')}</h2>
@@ -321,4 +326,4 @@ export class App extends React.PureComponent<Props, State> {
   }
 }
 
-export default withComponentContext(withAppStateContext(App));
+export default withComponentContext(withAvailableFeatures(withAppStateContext(App)));
index 9ceb6e3b6e333d4b4525ef44a4aaddfea48d8f9c..cbf4f74058f7702f8ce42942aaf7ffff7653e0c1 100644 (file)
@@ -41,7 +41,7 @@ it('should render correctly', async () => {
   await waitAndUpdate(wrapper);
   expect(wrapper).toMatchSnapshot();
 
-  wrapper = shallowRender({ appState: mockAppState({ branchesEnabled: false, canAdmin: true }) });
+  wrapper = shallowRender({ appState: mockAppState({ canAdmin: true }), hasFeature: () => false });
   await waitAndUpdate(wrapper);
   expect(wrapper).toMatchSnapshot('without branch support');
 });
@@ -109,7 +109,8 @@ function shallowRender(props: Partial<App['props']> = {}) {
     <App
       branchLike={mockBranch()}
       branchLikes={[mockMainBranch()]}
-      appState={mockAppState({ branchesEnabled: true, canAdmin: true })}
+      appState={mockAppState({ canAdmin: true })}
+      hasFeature={jest.fn().mockReturnValue(true)}
       component={mockComponent()}
       {...props}
     />
index 14f98297bf7b5b4e4b82d96340ea2ca4d16235e2..1df8b307482f8b34309f14ebebaa74b63a6726c1 100644 (file)
@@ -19,7 +19,9 @@
  */
 import { differenceWith, map, sortBy, uniqBy } from 'lodash';
 import * as React from 'react';
-import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
+import withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../app/components/available-features/withAvailableFeatures';
 import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
 import DocumentationTooltip from '../../../components/common/DocumentationTooltip';
 import { Button } from '../../../components/controls/buttons';
@@ -27,14 +29,13 @@ import ModalButton from '../../../components/controls/ModalButton';
 import { Alert } from '../../../components/ui/Alert';
 import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
 import { isDiffMetric } from '../../../helpers/measures';
-import { AppState } from '../../../types/appstate';
+import { Feature } from '../../../types/features';
 import { MetricKey } from '../../../types/metrics';
 import { Condition as ConditionType, Dict, Metric, QualityGate } from '../../../types/types';
 import Condition from './Condition';
 import ConditionModal from './ConditionModal';
 
-interface Props {
-  appState: AppState;
+interface Props extends WithAvailableFeaturesProps {
   canEdit: boolean;
   conditions: ConditionType[];
   metrics: Dict<Metric>;
@@ -56,7 +57,6 @@ const FORBIDDEN_METRICS: string[] = [
 export class Conditions extends React.PureComponent<Props> {
   renderConditionsTable = (conditions: ConditionType[], scope: 'new' | 'overall') => {
     const {
-      appState,
       qualityGate,
       metrics,
       canEdit,
@@ -74,7 +74,7 @@ export class Conditions extends React.PureComponent<Props> {
         <caption>
           <h4>{translate(captionTranslationId, 'long')}</h4>
 
-          {appState.branchesEnabled && (
+          {this.props.hasFeature(Feature.BranchSupport) && (
             <p className="spacer-top spacer-bottom">
               {translate(captionTranslationId, 'description')}
             </p>
@@ -222,4 +222,4 @@ export class Conditions extends React.PureComponent<Props> {
   }
 }
 
-export default withMetricsContext(withAppStateContext(Conditions));
+export default withMetricsContext(withAvailableFeatures(Conditions));
index be1520ef628776c8d24494729e7d75436573d053..a6aedcef3fb74f161e40fc9520b619c0560cbbe9 100644 (file)
@@ -22,9 +22,8 @@ import userEvent from '@testing-library/user-event';
 import selectEvent from 'react-select-event';
 import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock';
 import { searchProjects, searchUsers } from '../../../../api/quality-gates';
-import { mockAppState } from '../../../../helpers/testMocks';
-import { renderAppRoutes } from '../../../../helpers/testReactTestingUtils';
-import { AppState } from '../../../../types/appstate';
+import { renderAppRoutes, RenderContext } from '../../../../helpers/testReactTestingUtils';
+import { Feature } from '../../../../types/features';
 import routes from '../../routes';
 
 jest.mock('../../../../api/quality-gates');
@@ -266,7 +265,7 @@ it('should be able to handle delete condition', async () => {
 });
 
 it('should explain condition on branch', async () => {
-  renderQualityGateApp(mockAppState({ branchesEnabled: true }));
+  renderQualityGateApp({ featureList: [Feature.BranchSupport] });
 
   expect(
     await screen.findByText('quality_gates.conditions.new_code.description')
@@ -487,6 +486,6 @@ describe('The Permissions section', () => {
   });
 });
 
-function renderQualityGateApp(appState?: AppState) {
-  renderAppRoutes('quality_gates', routes, { appState });
+function renderQualityGateApp(context?: RenderContext) {
+  renderAppRoutes('quality_gates', routes, context);
 }
index 6476bb8f1bba5b1b3d63a495b5dbb55df7cfc129..b536a5006240e4fa3bd776d09aec0abf8027d067 100644 (file)
@@ -50,7 +50,7 @@ export interface AdditionalCategory {
   key: string;
   name: string;
   renderComponent: (props: AdditionalCategoryComponentProps) => React.ReactNode;
-  requiresBranchesEnabled?: boolean;
+  requiresBranchSupport?: boolean;
 }
 
 export const ADDITIONAL_CATEGORIES: AdditionalCategory[] = [
@@ -93,7 +93,7 @@ export const ADDITIONAL_CATEGORIES: AdditionalCategory[] = [
     availableGlobally: false,
     availableForProject: true,
     displayTab: true,
-    requiresBranchesEnabled: true
+    requiresBranchSupport: true
   },
   {
     key: AUTHENTICATION_CATEGORY,
index d0b0c01a8a76676527abe25fc9dff10da467c627..cb32f4830db8b9c29d43787db86fbee50a703287 100644 (file)
@@ -21,16 +21,17 @@ import classNames from 'classnames';
 import { sortBy } from 'lodash';
 import * as React from 'react';
 import { NavLink } from 'react-router-dom';
-import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
+import withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../app/components/available-features/withAvailableFeatures';
 import { getGlobalSettingsUrl, getProjectSettingsUrl } from '../../../helpers/urls';
-import { AppState } from '../../../types/appstate';
+import { Feature } from '../../../types/features';
 import { Component } from '../../../types/types';
 import { CATEGORY_OVERRIDES } from '../constants';
 import { getCategoryName } from '../utils';
 import { ADDITIONAL_CATEGORIES } from './AdditionalCategories';
 
-export interface CategoriesListProps {
-  appState: AppState;
+export interface CategoriesListProps extends WithAvailableFeaturesProps {
   categories: string[];
   component?: Component;
   defaultCategory: string;
@@ -38,7 +39,7 @@ export interface CategoriesListProps {
 }
 
 export function CategoriesList(props: CategoriesListProps) {
-  const { appState, categories, component, defaultCategory, selectedCategory } = props;
+  const { categories, component, defaultCategory, selectedCategory } = props;
 
   const categoriesWithName = categories
     .filter(key => !CATEGORY_OVERRIDES[key.toLowerCase()])
@@ -55,7 +56,7 @@ export function CategoriesList(props: CategoriesListProps) {
             : // Global settings
               c.availableGlobally
         )
-        .filter(c => appState.branchesEnabled || !c.requiresBranchesEnabled)
+        .filter(c => props.hasFeature(Feature.BranchSupport) || !c.requiresBranchSupport)
     );
   const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase());
 
@@ -87,4 +88,4 @@ export function CategoriesList(props: CategoriesListProps) {
   );
 }
 
-export default withAppStateContext(CategoriesList);
+export default withAvailableFeatures(CategoriesList);
index 94ff4c16fcc41e2de48974b447ee69f6d07d788a..a45a2368ecadcc974856a942c7fa3db5108731d7 100644 (file)
@@ -20,7 +20,6 @@
 import { screen } from '@testing-library/dom';
 import * as React from 'react';
 import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockAppState } from '../../../../helpers/testMocks';
 import { renderComponent } from '../../../../helpers/testReactTestingUtils';
 import { AdditionalCategory } from '../AdditionalCategories';
 import { CategoriesList, CategoriesListProps } from '../AllCategoriesList';
@@ -34,7 +33,7 @@ jest.mock('../AdditionalCategories', () => ({
       availableGlobally: true,
       availableForProject: true,
       displayTab: true,
-      requiresBranchesEnabled: true
+      requiresBranchSupport: true
     },
     {
       key: 'CAT_2',
@@ -83,7 +82,7 @@ it('should correctly for project', () => {
 });
 
 it('should render correctly when branches are disabled', () => {
-  renderCategoriesList({ appState: mockAppState({ branchesEnabled: false }) });
+  renderCategoriesList({ hasFeature: () => false });
 
   expect(screen.queryByText('CAT_1_NAME')).not.toBeInTheDocument();
   expect(screen.getByText('CAT_2_NAME')).toBeInTheDocument();
@@ -93,7 +92,7 @@ it('should render correctly when branches are disabled', () => {
 function renderCategoriesList(props?: Partial<CategoriesListProps>) {
   return renderComponent(
     <CategoriesList
-      appState={mockAppState({ branchesEnabled: true })}
+      hasFeature={jest.fn().mockReturnValue(true)}
       categories={['general']}
       defaultCategory="general"
       selectedCategory=""
index e9f366fc6fe99e7f779adba5c2525670ef289591..190caae86821ebbdba18cc6cc50f55ae042f3756 100644 (file)
@@ -63,7 +63,7 @@ exports[`should render additional categories component correctly 3`] = `
 `;
 
 exports[`should render additional categories component correctly 4`] = `
-<withRouter(withAppStateContext(withAvailableFeaturesContext(AlmIntegration)))
+<withRouter(withAvailableFeaturesContext(AlmIntegration))
   categories={Array []}
   component={
     Object {
index d251c34105f56beddfc0cd752511d29f63ca9b31..33c14061cbff7d6b4f1059fa8bee06b1e6491377 100644 (file)
@@ -51,7 +51,7 @@ exports[`should render almintegration correctly 1`] = `
           className="big-padded"
           key="almintegration"
         >
-          <withRouter(withAppStateContext(withAvailableFeaturesContext(AlmIntegration)))
+          <withRouter(withAvailableFeaturesContext(AlmIntegration))
             categories={
               Array [
                 "foo category",
@@ -179,7 +179,7 @@ exports[`should render default view correctly: All Categories List 1`] = `
     <div
       className="layout-page-side-inner"
     >
-      <withAppStateContext(CategoriesList)
+      <withAvailableFeaturesContext(CategoriesList)
         categories={
           Array [
             "foo category",
index f92b709abca67efe7f60c0614dfd2de07b887fc0..eac5572f4cafa2768cc93dc088b703ce0be472a4 100644 (file)
@@ -24,8 +24,9 @@ import {
   getAlmDefinitions,
   validateAlmSettings
 } from '../../../../api/alm-settings';
-import withAppStateContext from '../../../../app/components/app-state/withAppStateContext';
-import withAvailableFeatures from '../../../../app/components/available-features/withAvailableFeatures';
+import withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../../app/components/available-features/withAvailableFeatures';
 import { Location, Router, withRouter } from '../../../../components/hoc/withRouter';
 import {
   AlmBindingDefinitionBase,
@@ -34,13 +35,11 @@ import {
   AlmSettingsBindingStatus,
   AlmSettingsBindingStatusType
 } from '../../../../types/alm-settings';
-import { AppState } from '../../../../types/appstate';
 import { Feature } from '../../../../types/features';
 import { Dict } from '../../../../types/types';
 import AlmIntegrationRenderer from './AlmIntegrationRenderer';
 
-interface Props {
-  appState: AppState;
+interface Props extends WithAvailableFeaturesProps {
   hasFeature: (feature: Feature) => boolean;
   location: Location;
   router: Router;
@@ -212,10 +211,7 @@ export class AlmIntegration extends React.PureComponent<Props, State> {
   };
 
   render() {
-    const {
-      appState: { branchesEnabled },
-      hasFeature
-    } = this.props;
+    const { hasFeature } = this.props;
     const {
       currentAlmTab,
       definitionKeyForDeletion,
@@ -228,7 +224,7 @@ export class AlmIntegration extends React.PureComponent<Props, State> {
 
     return (
       <AlmIntegrationRenderer
-        branchesEnabled={Boolean(branchesEnabled)}
+        branchesEnabled={this.props.hasFeature(Feature.BranchSupport)}
         multipleAlmEnabled={hasFeature(Feature.MultipleAlm)}
         onCancelDelete={this.handleCancelDelete}
         onConfirmDelete={this.handleConfirmDelete}
@@ -248,4 +244,4 @@ export class AlmIntegration extends React.PureComponent<Props, State> {
   }
 }
 
-export default withRouter(withAppStateContext(withAvailableFeatures(AlmIntegration)));
+export default withRouter(withAvailableFeatures(AlmIntegration));
index e8ec58bb592509a38458dd26dbbdcf49254a0520..29f7f5b790d8a97fbfa32e06931113d11f88d0e5 100644 (file)
@@ -25,7 +25,7 @@ import {
   getAlmDefinitions,
   validateAlmSettings
 } from '../../../../../api/alm-settings';
-import { mockAppState, mockLocation, mockRouter } from '../../../../../helpers/testMocks';
+import { mockLocation, mockRouter } from '../../../../../helpers/testMocks';
 import { waitAndUpdate } from '../../../../../helpers/testUtils';
 import { AlmKeys, AlmSettingsBindingStatusType } from '../../../../../types/alm-settings';
 import { AlmIntegration } from '../AlmIntegration';
@@ -189,9 +189,8 @@ it('should detect the current ALM from the query', () => {
 function shallowRender(props: Partial<AlmIntegration['props']> = {}) {
   return shallow<AlmIntegration>(
     <AlmIntegration
-      appState={mockAppState({ branchesEnabled: true })}
+      hasFeature={jest.fn().mockReturnValue(true)}
       location={mockLocation()}
-      hasFeature={jest.fn()}
       router={mockRouter()}
       {...props}
     />
index c84b2fc9fc23ddec9edbc5b2e9582ffe2a612de9..116616c5808fd6ca83d866af69958d78530e9a7c 100644 (file)
@@ -16,6 +16,7 @@ exports[`should render correctly 1`] = `
   }
   loadingAlmDefinitions={true}
   loadingProjectCount={false}
+  multipleAlmEnabled={true}
   onCancelDelete={[Function]}
   onCheckConfiguration={[Function]}
   onConfirmDelete={[Function]}
index 0b023b704dfb9bc3c404f8ff50d31430d3bdf75f..93fcf70abe1a4408442fe69f2c101b31c1d5d004 100644 (file)
@@ -496,7 +496,7 @@ exports[`should render correctly: gitlab tutorial 1`] = `
 
 exports[`should render correctly: jenkins tutorial 1`] = `
 <Fragment>
-  <withAppStateContext(JenkinsTutorial)
+  <withAvailableFeaturesContext(JenkinsTutorial)
     almBinding={
       Object {
         "alm": "github",
index 40c421dd41a16b75236989a87f54132dff96c9c5..98fb021231f4cdfebbf1222e3b1102a8850e30a2 100644 (file)
@@ -23,7 +23,7 @@ import { UserEvent } from '@testing-library/user-event/dist/types/setup';
 import * as React from 'react';
 import UserTokensMock from '../../../../api/mocks/UserTokensMock';
 import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockAppState, mockLanguage, mockLoggedInUser } from '../../../../helpers/testMocks';
+import { mockLanguage, mockLoggedInUser } from '../../../../helpers/testMocks';
 import { renderApp, RenderContext } from '../../../../helpers/testReactTestingUtils';
 import { Permissions } from '../../../../types/permissions';
 import { TokenType } from '../../../../types/token';
@@ -220,10 +220,7 @@ function assertFinishStepIsCorrectlyRendered() {
 
 function renderAzurePipelinesTutorial(
   props: Partial<AzurePipelinesTutorialProps> = {},
-  {
-    appState = mockAppState({ branchesEnabled: true }),
-    languages = { c: mockLanguage({ key: 'c' }) }
-  }: RenderContext = {}
+  { languages = { c: mockLanguage({ key: 'c' }) } }: RenderContext = {}
 ) {
   return renderApp(
     '/',
@@ -234,7 +231,7 @@ function renderAzurePipelinesTutorial(
       willRefreshAutomatically={true}
       {...props}
     />,
-    { appState, languages }
+    { languages }
   );
 }
 
index a259ac50553f12a5435822e89987a8f04dec8798..ea1ed9c725f154eb46ff0897951366c557d508bf 100644 (file)
  */
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import withAppStateContext from '../../../../app/components/app-state/withAppStateContext';
+import withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../../app/components/available-features/withAvailableFeatures';
 import { Alert } from '../../../../components/ui/Alert';
 import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
 import { translate } from '../../../../helpers/l10n';
 import { AlmKeys } from '../../../../types/alm-settings';
-import { AppState } from '../../../../types/appstate';
+import { Feature } from '../../../../types/features';
 import Link from '../../../common/Link';
 import SentenceWithHighlights from '../../components/SentenceWithHighlights';
 
-export interface PublishStepsProps {
-  appState: AppState;
-}
+export interface PublishStepsProps extends WithAvailableFeaturesProps {}
 export function PublishSteps(props: PublishStepsProps) {
-  const {
-    appState: { branchesEnabled }
-  } = props;
+  const branchSupportEnabled = props.hasFeature(Feature.BranchSupport);
 
   return (
     <>
@@ -52,14 +50,14 @@ export function PublishSteps(props: PublishStepsProps) {
       <li>
         <SentenceWithHighlights
           translationKey={
-            branchesEnabled
+            branchSupportEnabled
               ? 'onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration'
               : 'onboarding.tutorial.with.azure_pipelines.BranchAnalysis.continous_integration.no_branches'
           }
           highlightKeys={['tab', 'continuous_integration']}
         />
       </li>
-      {branchesEnabled && (
+      {branchSupportEnabled && (
         <>
           <hr />
           <FormattedMessage
@@ -83,4 +81,4 @@ export function PublishSteps(props: PublishStepsProps) {
   );
 }
 
-export default withAppStateContext(PublishSteps);
+export default withAvailableFeatures(PublishSteps);
index b0e372c194fe5f9db76171699ad435415da2664b..cc8586626c76a8241abd0645dfc96c333c9950ca 100644 (file)
  */
 import { Dictionary } from 'lodash';
 import * as React from 'react';
-import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
-import { AppState } from '../../../types/appstate';
+import withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../app/components/available-features/withAvailableFeatures';
+import { Feature } from '../../../types/features';
 import { Component } from '../../../types/types';
 import { CompilationInfo } from '../components/CompilationInfo';
 import CreateYmlFile from '../components/CreateYmlFile';
@@ -32,8 +34,7 @@ import mavenExample from './commands/Maven';
 import othersExample from './commands/Others';
 import { PreambuleYaml } from './PreambuleYaml';
 
-export interface AnalysisCommandProps {
-  appState: AppState;
+export interface AnalysisCommandProps extends WithAvailableFeaturesProps {
   buildTool: BuildTools;
   component: Component;
 }
@@ -47,17 +48,14 @@ const YamlTemplate: Dictionary<(branchesEnabled?: boolean, projectKey?: string)
 };
 
 export function AnalysisCommand(props: AnalysisCommandProps) {
-  const {
-    buildTool,
-    component,
-    appState: { branchesEnabled }
-  } = props;
+  const { buildTool, component } = props;
+  const branchSupportEnabled = props.hasFeature(Feature.BranchSupport);
 
   if (!buildTool) {
     return null;
   }
 
-  const yamlTemplate = YamlTemplate[buildTool](branchesEnabled, component.key);
+  const yamlTemplate = YamlTemplate[buildTool](branchSupportEnabled, component.key);
 
   return (
     <>
@@ -68,4 +66,4 @@ export function AnalysisCommand(props: AnalysisCommandProps) {
   );
 }
 
-export default withAppStateContext(AnalysisCommand);
+export default withAvailableFeatures(AnalysisCommand);
index be1f33ab22744d546110cd8a36f7186061c59c2c..a3f9602d9ff03de8c5d6334851fa7bc1683c6bb8 100644 (file)
@@ -20,7 +20,6 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockAppState } from '../../../../helpers/testMocks';
 import { BuildTools } from '../../types';
 import { AnalysisCommand, AnalysisCommandProps } from '../AnalysisCommand';
 
@@ -32,15 +31,15 @@ it.each([
   [BuildTools.Other]
 ])('should render correctly for %s', buildTool => {
   expect(shallowRender({ buildTool })).toMatchSnapshot();
-  expect(
-    shallowRender({ appState: mockAppState({ branchesEnabled: true }), buildTool })
-  ).toMatchSnapshot('with branch enabled');
+  expect(shallowRender({ hasFeature: () => true, buildTool })).toMatchSnapshot(
+    'with branch enabled'
+  );
 });
 
 function shallowRender(props: Partial<AnalysisCommandProps> = {}) {
   return shallow<AnalysisCommandProps>(
     <AnalysisCommand
-      appState={mockAppState()}
+      hasFeature={jest.fn().mockReturnValue(false)}
       buildTool={BuildTools.DotNet}
       component={mockComponent()}
       {...props}
index 1a8b2bcf243716e6afa334b08a71a90825bd3f72..df12b20c962488c156d02d7934c15b3f84312820 100644 (file)
  * 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 withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../app/components/available-features/withAvailableFeatures';
 import { translate } from '../../../helpers/l10n';
 import { getBaseUrl } from '../../../helpers/system';
 import { AlmKeys } from '../../../types/alm-settings';
-import { AppState } from '../../../types/appstate';
+import { Feature } from '../../../types/features';
 import SentenceWithHighlights from './SentenceWithHighlights';
 
-export interface AllSetProps {
+export interface AllSetProps extends WithAvailableFeaturesProps {
   alm: AlmKeys;
-  appState: AppState;
   willRefreshAutomatically?: boolean;
 }
 
 export function AllSet(props: AllSetProps) {
-  const {
-    alm,
-    appState: { branchesEnabled },
-    willRefreshAutomatically
-  } = props;
+  const { alm, willRefreshAutomatically } = props;
+  const branchSupportEnabled = props.hasFeature(Feature.BranchSupport);
 
   return (
     <>
@@ -61,7 +59,7 @@ export function AllSet(props: AllSetProps) {
               <strong>{translate('onboarding.tutorial.ci_outro.commit')}</strong>
             </p>
             <p>
-              {branchesEnabled
+              {branchSupportEnabled
                 ? translate('onboarding.tutorial.ci_outro.commit.why', alm)
                 : translate('onboarding.tutorial.ci_outro.commit.why.no_branches')}
             </p>
@@ -96,4 +94,4 @@ export function AllSet(props: AllSetProps) {
   );
 }
 
-export default withAppStateContext(AllSet);
+export default withAvailableFeatures(AllSet);
index fde3b01442a532764f1aadf21c13ddb4e536ce5c..5c3720993ba42bc66970853061b4964f182d3415 100644 (file)
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import { mockAppState } from '../../../../helpers/testMocks';
 import { AlmKeys } from '../../../../types/alm-settings';
 import { AllSet, AllSetProps } from '../AllSet';
 
 it('should render correctly', () => {
   expect(shallowRender()).toMatchSnapshot();
-  expect(shallowRender({ appState: mockAppState({ branchesEnabled: false }) })).toMatchSnapshot(
-    'without branch'
-  );
+  expect(shallowRender({ hasFeature: () => false })).toMatchSnapshot('without branch');
   expect(shallowRender({ willRefreshAutomatically: true })).toMatchSnapshot('with auto refresh');
 });
 
 function shallowRender(props: Partial<AllSetProps> = {}) {
   return shallow<AllSetProps>(
-    <AllSet alm={AlmKeys.GitHub} appState={mockAppState({ branchesEnabled: true })} {...props} />
+    <AllSet alm={AlmKeys.GitHub} hasFeature={jest.fn().mockReturnValue(true)} {...props} />
   );
 }
index d0bd6b5c0b2c3528d65a5d3f1034e346266b18e6..256d7592b4689aa0c8e4fc817b6922268e8408e3 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
-import { AppState } from '../../../types/appstate';
+import withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../app/components/available-features/withAvailableFeatures';
+import { Feature } from '../../../types/features';
 import { Component } from '../../../types/types';
 import { BuildTools } from '../types';
 import CFamily from './commands/CFamily';
@@ -28,19 +30,15 @@ import Gradle from './commands/Gradle';
 import JavaMaven from './commands/JavaMaven';
 import Others from './commands/Others';
 
-export interface AnalysisCommandProps {
-  appState: AppState;
+export interface AnalysisCommandProps extends WithAvailableFeaturesProps {
   buildTool: BuildTools;
   component: Component;
   onDone: () => void;
 }
 
 export function AnalysisCommand(props: AnalysisCommandProps) {
-  const {
-    buildTool,
-    component,
-    appState: { branchesEnabled }
-  } = props;
+  const { buildTool, component } = props;
+  const branchSupportEnabled = props.hasFeature(Feature.BranchSupport);
 
   if (!buildTool) {
     return null;
@@ -49,26 +47,46 @@ export function AnalysisCommand(props: AnalysisCommandProps) {
   switch (buildTool) {
     case BuildTools.Maven:
       return (
-        <JavaMaven branchesEnabled={branchesEnabled} component={component} onDone={props.onDone} />
+        <JavaMaven
+          branchesEnabled={branchSupportEnabled}
+          component={component}
+          onDone={props.onDone}
+        />
       );
     case BuildTools.Gradle:
       return (
-        <Gradle branchesEnabled={branchesEnabled} component={component} onDone={props.onDone} />
+        <Gradle
+          branchesEnabled={branchSupportEnabled}
+          component={component}
+          onDone={props.onDone}
+        />
       );
     case BuildTools.DotNet:
       return (
-        <DotNet branchesEnabled={branchesEnabled} component={component} onDone={props.onDone} />
+        <DotNet
+          branchesEnabled={branchSupportEnabled}
+          component={component}
+          onDone={props.onDone}
+        />
       );
     case BuildTools.CFamily:
       return (
-        <CFamily branchesEnabled={branchesEnabled} component={component} onDone={props.onDone} />
+        <CFamily
+          branchesEnabled={branchSupportEnabled}
+          component={component}
+          onDone={props.onDone}
+        />
       );
     case BuildTools.Other:
       return (
-        <Others branchesEnabled={branchesEnabled} component={component} onDone={props.onDone} />
+        <Others
+          branchesEnabled={branchSupportEnabled}
+          component={component}
+          onDone={props.onDone}
+        />
       );
   }
   return null;
 }
 
-export default withAppStateContext(AnalysisCommand);
+export default withAvailableFeatures(AnalysisCommand);
index 16f42fd1fb3feef1c601aa337d896c45b7f5bb1f..eeb4667d1f8197dde5cebda72da655e0d8afc7f9 100644 (file)
@@ -20,7 +20,6 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockAppState } from '../../../../helpers/testMocks';
 import { BuildTools } from '../../types';
 import { AnalysisCommand, AnalysisCommandProps } from '../AnalysisCommand';
 
@@ -38,7 +37,7 @@ it.each([
 function shallowRender(props: Partial<AnalysisCommandProps> = {}) {
   return shallow<AnalysisCommandProps>(
     <AnalysisCommand
-      appState={mockAppState()}
+      hasFeature={jest.fn().mockReturnValue(false)}
       component={mockComponent()}
       buildTool={BuildTools.DotNet}
       onDone={jest.fn()}
index 9d88eb179df37d572000ceeeb14f2c5dd5823217..5543105efd6ed178dab68ad7baa842bb5d3fed9a 100644 (file)
@@ -2,6 +2,7 @@
 
 exports[`should render correctly for "cfamily" 1`] = `
 <CFamily
+  branchesEnabled={false}
   component={
     Object {
       "breadcrumbs": Array [],
@@ -30,6 +31,7 @@ exports[`should render correctly for "cfamily" 1`] = `
 
 exports[`should render correctly for "dotnet" 1`] = `
 <DotNet
+  branchesEnabled={false}
   component={
     Object {
       "breadcrumbs": Array [],
@@ -58,6 +60,7 @@ exports[`should render correctly for "dotnet" 1`] = `
 
 exports[`should render correctly for "gradle" 1`] = `
 <Gradle
+  branchesEnabled={false}
   component={
     Object {
       "breadcrumbs": Array [],
@@ -86,6 +89,7 @@ exports[`should render correctly for "gradle" 1`] = `
 
 exports[`should render correctly for "maven" 1`] = `
 <JavaMaven
+  branchesEnabled={false}
   component={
     Object {
       "breadcrumbs": Array [],
@@ -114,6 +118,7 @@ exports[`should render correctly for "maven" 1`] = `
 
 exports[`should render correctly for "other" 1`] = `
 <Others
+  branchesEnabled={false}
   component={
     Object {
       "breadcrumbs": Array [],
index 7b83e9de998e6d6623c4a2c821d20ca94d05fcad..af9f96dc8c40fcf771e8752479225c9791e00d6e 100644 (file)
  */
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
+import withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../app/components/available-features/withAvailableFeatures';
 import { ClipboardIconButton } from '../../../components/controls/clipboard';
 import { translate } from '../../../helpers/l10n';
-import { AppState } from '../../../types/appstate';
+import { Feature } from '../../../types/features';
 import FinishButton from '../components/FinishButton';
 import GithubCFamilyExampleRepositories from '../components/GithubCFamilyExampleRepositories';
 import Step from '../components/Step';
 import { BuildTools, TutorialModes } from '../types';
 import PipeCommand from './commands/PipeCommand';
 
-export interface YmlFileStepProps {
-  appState: AppState;
+export interface YmlFileStepProps extends WithAvailableFeaturesProps {
   buildTool?: BuildTools;
   finished: boolean;
   onDone: () => void;
@@ -40,13 +41,8 @@ export interface YmlFileStepProps {
 }
 
 export function YmlFileStep(props: YmlFileStepProps) {
-  const {
-    appState: { branchesEnabled },
-    buildTool,
-    open,
-    finished,
-    projectKey
-  } = props;
+  const { buildTool, open, finished, projectKey } = props;
+  const branchSupportEnabled = props.hasFeature(Feature.BranchSupport);
 
   const renderForm = () => (
     <div className="boxed-group-inner">
@@ -82,12 +78,12 @@ export function YmlFileStep(props: YmlFileStepProps) {
               <div className="big-spacer-bottom abs-width-600">
                 <PipeCommand
                   buildTool={buildTool}
-                  branchesEnabled={branchesEnabled}
+                  branchesEnabled={branchSupportEnabled}
                   projectKey={projectKey}
                 />
               </div>
               <p className="little-spacer-bottom">
-                {branchesEnabled
+                {branchSupportEnabled
                   ? translate('onboarding.tutorial.with.gitlab_ci.yml.baseconfig')
                   : translate('onboarding.tutorial.with.gitlab_ci.yml.baseconfig.no_branches')}
               </p>
@@ -112,4 +108,4 @@ export function YmlFileStep(props: YmlFileStepProps) {
   );
 }
 
-export default withAppStateContext(YmlFileStep);
+export default withAvailableFeatures(YmlFileStep);
index 0bdce5f9b99b38ef2798309491768e9993a01067..9c6b0614afa8a7405df57fdf58d27190da65bc61 100644 (file)
@@ -19,7 +19,6 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import { mockAppState } from '../../../../helpers/testMocks';
 import { renderStepContent } from '../../test-utils';
 import { BuildTools } from '../../types';
 import { YmlFileStep, YmlFileStepProps } from '../YmlFileStep';
@@ -38,17 +37,15 @@ it.each([
   [BuildTools.Other]
 ])('should render correctly for build tool %s', buildTool => {
   expect(renderStepContent(shallowRender({ buildTool }))).toMatchSnapshot('with branch support');
-  expect(
-    renderStepContent(
-      shallowRender({ appState: mockAppState({ branchesEnabled: false }), buildTool })
-    )
-  ).toMatchSnapshot('without branch support');
+  expect(renderStepContent(shallowRender({ hasFeature: () => false, buildTool }))).toMatchSnapshot(
+    'without branch support'
+  );
 });
 
 function shallowRender(props: Partial<YmlFileStepProps> = {}) {
   return shallow<YmlFileStepProps>(
     <YmlFileStep
-      appState={mockAppState({ branchesEnabled: true })}
+      hasFeature={jest.fn().mockReturnValue(true)}
       open={true}
       projectKey="test"
       finished={true}
index fdadfc1aa7e94c58dfbc0e4cb0d8ee8378000e36..d3fc71fa39947fd86b088721c170fab3c6108917 100644 (file)
@@ -81,7 +81,7 @@ exports[`should render correctly 1`] = `
     onOpen={[Function]}
     open={false}
   />
-  <withAppStateContext(YmlFileStep)
+  <withAvailableFeaturesContext(YmlFileStep)
     finished={false}
     onDone={[Function]}
     onOpen={[Function]}
index 7b667b24e2297d6d0b91d5801d96273d2dfd318f..7260a88b189f84b07daa898932710500a2822eee 100644 (file)
  * 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 withAvailableFeatures, {
+  WithAvailableFeaturesProps
+} from '../../../app/components/available-features/withAvailableFeatures';
 import { translate } from '../../../helpers/l10n';
 import {
   AlmKeys,
   AlmSettingsInstance,
   ProjectAlmBindingResponse
 } from '../../../types/alm-settings';
-import { AppState } from '../../../types/appstate';
+import { Feature } from '../../../types/features';
 import { Component } from '../../../types/types';
 import AllSetStep from '../components/AllSetStep';
 import JenkinsfileStep from './JenkinsfileStep';
@@ -35,10 +37,9 @@ import PreRequisitesStep from './PreRequisitesStep';
 import SelectAlmStep from './SelectAlmStep';
 import WebhookStep from './WebhookStep';
 
-export interface JenkinsTutorialProps {
+export interface JenkinsTutorialProps extends WithAvailableFeaturesProps {
   almBinding?: AlmSettingsInstance;
   baseUrl: string;
-  appState: AppState;
   component: Component;
   projectBinding?: ProjectAlmBindingResponse;
   willRefreshAutomatically?: boolean;
@@ -54,15 +55,9 @@ enum Steps {
 }
 
 export function JenkinsTutorial(props: JenkinsTutorialProps) {
-  const {
-    almBinding,
-    baseUrl,
-    appState,
-    component,
-    projectBinding,
-    willRefreshAutomatically
-  } = props;
+  const { almBinding, baseUrl, component, projectBinding, willRefreshAutomatically } = props;
   const hasSelectAlmStep = projectBinding?.alm === undefined;
+  const branchSupportEnabled = props.hasFeature(Feature.BranchSupport);
   const [alm, setAlm] = React.useState<AlmKeys | undefined>(projectBinding?.alm);
   const [step, setStep] = React.useState(alm ? Steps.PreRequisites : Steps.SelectAlm);
 
@@ -88,14 +83,14 @@ export function JenkinsTutorial(props: JenkinsTutorialProps) {
         <>
           <PreRequisitesStep
             alm={alm}
-            branchesEnabled={!!appState.branchesEnabled}
+            branchesEnabled={branchSupportEnabled}
             finished={step > Steps.PreRequisites}
             onDone={() => setStep(Steps.MultiBranchPipeline)}
             onOpen={() => setStep(Steps.PreRequisites)}
             open={step === Steps.PreRequisites}
           />
 
-          {appState.branchesEnabled ? (
+          {branchSupportEnabled ? (
             <MultiBranchPipelineStep
               alm={alm}
               almBinding={almBinding}
@@ -118,7 +113,7 @@ export function JenkinsTutorial(props: JenkinsTutorialProps) {
           <WebhookStep
             alm={alm}
             almBinding={almBinding}
-            branchesEnabled={!!appState.branchesEnabled}
+            branchesEnabled={branchSupportEnabled}
             finished={step > Steps.Webhook}
             onDone={() => setStep(Steps.Jenkinsfile)}
             onOpen={() => setStep(Steps.Webhook)}
@@ -147,4 +142,4 @@ export function JenkinsTutorial(props: JenkinsTutorialProps) {
   );
 }
 
-export default withAppStateContext(JenkinsTutorial);
+export default withAvailableFeatures(JenkinsTutorial);
index e4d121ac0fa6880cd7cad1132b79e12d36689ada..7075595e28f8d2b8db7505494c631fdd8af6dcb3 100644 (file)
@@ -21,7 +21,6 @@ import { shallow } from 'enzyme';
 import * as React from 'react';
 import { mockProjectBitbucketBindingResponse } from '../../../../helpers/mocks/alm-settings';
 import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockAppState } from '../../../../helpers/testMocks';
 import { AlmKeys } from '../../../../types/alm-settings';
 import JenkinsfileStep from '../JenkinsfileStep';
 import { JenkinsTutorial, JenkinsTutorialProps } from '../JenkinsTutorial';
@@ -32,9 +31,7 @@ import WebhookStep from '../WebhookStep';
 
 it('should render correctly', () => {
   expect(shallowRender()).toMatchSnapshot('default');
-  expect(shallowRender({ appState: mockAppState({ branchesEnabled: false }) })).toMatchSnapshot(
-    'branches not enabled'
-  );
+  expect(shallowRender({ hasFeature: () => false })).toMatchSnapshot('branches not enabled');
   expect(shallowRender({ projectBinding: undefined })).toMatchSnapshot('no project binding');
 });
 
@@ -113,7 +110,7 @@ function shallowRender(props: Partial<JenkinsTutorialProps> = {}) {
   return shallow<JenkinsTutorialProps>(
     <JenkinsTutorial
       baseUrl=""
-      appState={mockAppState({ branchesEnabled: true })}
+      hasFeature={jest.fn().mockReturnValue(true)}
       component={mockComponent()}
       projectBinding={mockProjectBitbucketBindingResponse()}
       willRefreshAutomatically={true}
index 0ba3c484d6d6d65445ad6de4880ec4ab7c397956..c92cb3f960c91c52b1e3ebf0e4f3ebcb9f724a78 100644 (file)
@@ -24,6 +24,7 @@ import { IntlProvider } from 'react-intl';
 import { MemoryRouter, Outlet, parsePath, Route, Routes } from 'react-router-dom';
 import AdminContext from '../app/components/AdminContext';
 import AppStateContextProvider from '../app/components/app-state/AppStateContextProvider';
+import { AvailableFeaturesContext } from '../app/components/available-features/AvailableFeaturesContext';
 import { ComponentContext } from '../app/components/componentContext/ComponentContext';
 import CurrentUserContextProvider from '../app/components/current-user/CurrentUserContextProvider';
 import GlobalMessagesContainer from '../app/components/GlobalMessagesContainer';
@@ -33,6 +34,7 @@ import { MetricsContext } from '../app/components/metrics/MetricsContext';
 import { useLocation } from '../components/hoc/withRouter';
 import { AppState } from '../types/appstate';
 import { ComponentContextShape } from '../types/component';
+import { Feature } from '../types/features';
 import { Dict, Extension, Languages, Metric, SysStatus } from '../types/types';
 import { CurrentUser } from '../types/users';
 import { DEFAULT_METRICS } from './mocks/metrics';
@@ -44,6 +46,7 @@ export interface RenderContext {
   languages?: Languages;
   currentUser?: CurrentUser;
   navigateTo?: string;
+  featureList?: Feature[];
 }
 
 export function renderAppWithAdminContext(
@@ -156,6 +159,7 @@ function renderRoutedApp(
     navigateTo = indexPath,
     metrics = DEFAULT_METRICS,
     appState = mockAppState(),
+    featureList = [],
     languages = {}
   }: RenderContext = {}
 ): RenderResult {
@@ -167,19 +171,21 @@ function renderRoutedApp(
       <IntlProvider defaultLocale="en" locale="en">
         <MetricsContext.Provider value={metrics}>
           <LanguagesContext.Provider value={languages}>
-            <CurrentUserContextProvider currentUser={currentUser}>
-              <AppStateContextProvider appState={appState}>
-                <IndexationContextProvider>
-                  <GlobalMessagesContainer />
-                  <MemoryRouter initialEntries={[path]}>
-                    <Routes>
-                      {children}
-                      <Route path="*" element={<CatchAll />} />
-                    </Routes>
-                  </MemoryRouter>
-                </IndexationContextProvider>
-              </AppStateContextProvider>
-            </CurrentUserContextProvider>
+            <AvailableFeaturesContext.Provider value={featureList}>
+              <CurrentUserContextProvider currentUser={currentUser}>
+                <AppStateContextProvider appState={appState}>
+                  <IndexationContextProvider>
+                    <GlobalMessagesContainer />
+                    <MemoryRouter initialEntries={[path]}>
+                      <Routes>
+                        {children}
+                        <Route path="*" element={<CatchAll />} />
+                      </Routes>
+                    </MemoryRouter>
+                  </IndexationContextProvider>
+                </AppStateContextProvider>
+              </CurrentUserContextProvider>
+            </AvailableFeaturesContext.Provider>
           </LanguagesContext.Provider>
         </MetricsContext.Provider>
       </IntlProvider>
index d90558d00210166d767737ec5287d785243c16f1..b424ad158ff4006e331a6ea22e7e9688c222c051 100644 (file)
@@ -25,7 +25,6 @@ import { Extension } from './types';
 export interface AppState {
   authenticationError?: boolean;
   authorizationError?: boolean;
-  branchesEnabled?: boolean;
   canAdmin?: boolean;
   edition?: EditionKey;
   globalPages?: Extension[];
index 10efdac37de27bac248af102f1dace088fdd46f4..3309f8f8214453e80a8b5d4693457fd8c1925949 100644 (file)
@@ -23,5 +23,6 @@ export enum Feature {
   RegulatoryReport = 'regulatory-reports',
   ProjectImport = 'project-import',
   MultipleAlm = 'multiple-alm',
-  Announcement = 'announcement'
+  Announcement = 'announcement',
+  BranchSupport = 'branch-support'
 }
index b59831be338fa81df13350c5ae51f1633023d702..305a806d80a873b1d255f9254fa30f26712a2a2d 100644 (file)
 package org.sonar.server.branch;
 
 import org.sonar.api.server.ServerSide;
+import org.sonar.server.feature.SonarQubeFeature;
 
 /**
  * The branch plugin needs to implement this in order to know that the branch feature is supported
  */
 @ServerSide
-public interface BranchFeatureExtension extends BranchFeature {
+public interface BranchFeatureExtension extends SonarQubeFeature {
 
 }
index fa9bbb336702baeb9a70d2c7016e0d7a43e04187..fd59388942dcaf392480e01a30337ebba70b42f5 100644 (file)
@@ -38,7 +38,6 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.dialect.H2;
 import org.sonar.server.authentication.DefaultAdminCredentialsVerifier;
-import org.sonar.server.branch.BranchFeatureProxy;
 import org.sonar.server.issue.index.IssueIndexSyncProgressChecker;
 import org.sonar.server.platform.WebServer;
 import org.sonar.server.ui.PageRepository;
@@ -72,7 +71,6 @@ public class GlobalAction implements NavigationWsAction, Startable {
   private final Server server;
   private final WebServer webServer;
   private final DbClient dbClient;
-  private final BranchFeatureProxy branchFeature;
   private final UserSession userSession;
   private final PlatformEditionProvider editionProvider;
   private final WebAnalyticsLoader webAnalyticsLoader;
@@ -80,7 +78,7 @@ public class GlobalAction implements NavigationWsAction, Startable {
   private final DefaultAdminCredentialsVerifier defaultAdminCredentialsVerifier;
 
   public GlobalAction(PageRepository pageRepository, Configuration config, ResourceTypes resourceTypes, Server server,
-    WebServer webServer, DbClient dbClient, BranchFeatureProxy branchFeature, UserSession userSession, PlatformEditionProvider editionProvider,
+    WebServer webServer, DbClient dbClient, UserSession userSession, PlatformEditionProvider editionProvider,
     WebAnalyticsLoader webAnalyticsLoader, IssueIndexSyncProgressChecker issueIndexSyncChecker,
     DefaultAdminCredentialsVerifier defaultAdminCredentialsVerifier) {
     this.pageRepository = pageRepository;
@@ -89,7 +87,6 @@ public class GlobalAction implements NavigationWsAction, Startable {
     this.server = server;
     this.webServer = webServer;
     this.dbClient = dbClient;
-    this.branchFeature = branchFeature;
     this.userSession = userSession;
     this.editionProvider = editionProvider;
     this.webAnalyticsLoader = webAnalyticsLoader;
@@ -129,7 +126,6 @@ public class GlobalAction implements NavigationWsAction, Startable {
       writeQualifiers(json);
       writeVersion(json);
       writeDatabaseProduction(json);
-      writeBranchSupport(json);
       writeInstanceUsesDefaultAdminCredentials(json);
       editionProvider.get().ifPresent(e -> json.prop("edition", e.name().toLowerCase(Locale.ENGLISH)));
       writeNeedIssueSync(json);
@@ -183,10 +179,6 @@ public class GlobalAction implements NavigationWsAction, Startable {
     json.prop("productionDatabase", !dbClient.getDatabase().getDialect().getId().equals(H2.ID));
   }
 
-  private void writeBranchSupport(JsonWriter json) {
-    json.prop("branchesEnabled", branchFeature.isEnabled());
-  }
-
   private void writeInstanceUsesDefaultAdminCredentials(JsonWriter json) {
     if (userSession.isSystemAdministrator()) {
       json.prop("instanceUsesDefaultAdminCredentials", defaultAdminCredentialsVerifier.hasDefaultCredentialUser());
index 700eee264bc63ef24358c7b8dffecdfb11092abf..a1619ee69fd3b9b7a966625869aeced2c5afea1e 100644 (file)
@@ -25,7 +25,6 @@
   ],
   "version": "6.2",
   "productionDatabase": true,
-  "branchesEnabled": false,
   "canAdmin": false,
   "standalone": true,
   "edition": "community"
index f23616f3b7648b2a74bb2d760617669a10738dda..bdcb1b773b97dd7402907c04a2b854c56321d50c 100644 (file)
@@ -214,16 +214,6 @@ public class GlobalActionTest {
       "}");
   }
 
-  @Test
-  public void branch_support() {
-    init();
-    branchFeature.setEnabled(true);
-    assertJson(call()).isSimilarTo("{\"branchesEnabled\":true}");
-
-    branchFeature.setEnabled(false);
-    assertJson(call()).isSimilarTo("{\"branchesEnabled\":false}");
-  }
-
   @Test
   public void return_need_issue_sync() {
     init();
@@ -353,7 +343,7 @@ public class GlobalActionTest {
     }});
     pageRepository.start();
     GlobalAction wsAction = new GlobalAction(pageRepository, settings.asConfig(), new ResourceTypes(resourceTypeTrees), server,
-      webServer, dbClient, branchFeature, userSession, editionProvider, webAnalyticsLoader,
+      webServer, dbClient, userSession, editionProvider, webAnalyticsLoader,
       indexSyncProgressChecker, defaultAdminCredentialsVerifier);
     ws = new WsActionTester(wsAction);
     wsAction.start();