]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22559 Remove GitLab auto-provisioning status + Gitlab Binding Banner for non...
authorguillaume-peoch-sonarsource <guillaume.peoch@sonarsource.com>
Fri, 16 Aug 2024 14:48:38 +0000 (16:48 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 19 Aug 2024 20:02:45 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx
server/sonar-web/src/main/js/apps/permissions/project/components/PermissionsProjectApp.tsx
server/sonar-web/src/main/js/apps/permissions/project/components/PermissionsProjectVisibility.tsx
server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/PermissionsProject-it.tsx
server/sonar-web/src/main/js/apps/permissions/test-utils.ts
server/sonar-web/src/main/js/components/permissions/AllHoldersList.tsx
server/sonar-web/src/main/js/components/permissions/HoldersList.tsx

index 431674c5dec5395908e8559687fb6daf8bb5bc59..80aa2ec2b756fcb6f571137cd3373d132f288b71 100644 (file)
@@ -24,7 +24,6 @@ import * as React from 'react';
 import { Image } from '~sonar-aligned/components/common/Image';
 import { isPortfolioLike } from '~sonar-aligned/helpers/component';
 import GitHubSynchronisationWarning from '../../../../app/components/GitHubSynchronisationWarning';
-import GitLabSynchronisationWarning from '../../../../app/components/GitLabSynchronisationWarning';
 import { translate, translateWithParameters } from '../../../../helpers/l10n';
 import { isDefined } from '../../../../helpers/types';
 import {
@@ -32,27 +31,27 @@ import {
   useIsGitLabProjectQuery,
 } from '../../../../queries/devops-integration';
 import { useGithubProvisioningEnabledQuery } from '../../../../queries/identity-provider/github';
-import { useGilabProvisioningEnabledQuery } from '../../../../queries/identity-provider/gitlab';
 import { isApplication, isProject } from '../../../../types/component';
 import { Component } from '../../../../types/types';
 import ApplyTemplate from './ApplyTemplate';
 
 interface Props {
   component: Component;
+  isProjectManaged: boolean;
   loadHolders: () => void;
 }
 
 export default function PageHeader(props: Readonly<Props>) {
-  const { component, loadHolders } = props;
+  const { component, loadHolders, isProjectManaged } = props;
   const { configuration, key, qualifier, visibility } = component;
   const [applyTemplateModal, setApplyTemplateModal] = React.useState(false);
   const { data: isGitHubProject } = useIsGitHubProjectQuery(key);
   const { data: isGitLabProject } = useIsGitLabProjectQuery(key);
   const { data: githubProvisioningStatus } = useGithubProvisioningEnabledQuery();
-  const { data: gitlabProvisioningStatus } = useGilabProvisioningEnabledQuery();
+  // to know if we are provisioning with GitLab: managed + GitLab project
 
   const provisionedByGitHub = isGitHubProject && !!githubProvisioningStatus;
-  const provisionedByGitLab = isGitLabProject && !!gitlabProvisioningStatus;
+  const provisionedByGitLab = isGitLabProject && isProjectManaged;
   const provisioned = provisionedByGitHub || provisionedByGitLab;
   const canApplyPermissionTemplate = configuration?.canApplyPermissionTemplate && !provisioned;
 
@@ -107,7 +106,6 @@ export default function PageHeader(props: Readonly<Props>) {
               </p>
               <div className="sw-mt-2">
                 {provisionedByGitHub && <GitHubSynchronisationWarning short />}
-                {provisionedByGitLab && <GitLabSynchronisationWarning short />}
               </div>
             </>
           )}
@@ -116,11 +114,6 @@ export default function PageHeader(props: Readonly<Props>) {
               {translate('project_permission.local_project_with_github_provisioning')}
             </FlagMessage>
           )}
-          {gitlabProvisioningStatus && !isGitLabProject && (
-            <FlagMessage variant="warning" className="sw-mt-2">
-              {translate('project_permission.local_project_with_gitlab_provisioning')}
-            </FlagMessage>
-          )}
         </div>
       </div>
       {canApplyPermissionTemplate && (
index c1e5708966aca829a86821a66c67478454873899..c0d22a92660c2273851138286a1c9177cdc5d7ff 100644 (file)
@@ -21,8 +21,9 @@ import { LargeCenteredLayout, PageContentFontWrapper } from 'design-system';
 import { noop, without } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
-import { Visibility } from '~sonar-aligned/types/component';
+import { ComponentQualifier, Visibility } from '~sonar-aligned/types/component';
 import * as api from '../../../../api/permissions';
+import { getComponents } from '../../../../api/project-management';
 import withComponentContext from '../../../../app/components/componentContext/withComponentContext';
 import AllHoldersList from '../../../../components/permissions/AllHoldersList';
 import { FilterOption } from '../../../../components/permissions/SearchForm';
@@ -48,6 +49,7 @@ interface State {
   filter: FilterOption;
   groups: PermissionGroup[];
   groupsPaging?: Paging;
+  isProjectManaged: boolean;
   loading: boolean;
   query: string;
   selectedPermission?: string;
@@ -67,12 +69,14 @@ class PermissionsProjectApp extends React.PureComponent<Props, State> {
       loading: true,
       query: '',
       users: [],
+      isProjectManaged: false,
     };
   }
 
   componentDidMount() {
     this.mounted = true;
     this.loadHolders();
+    this.getIsProjectManaged();
   }
 
   componentWillUnmount() {
@@ -122,6 +126,21 @@ class PermissionsProjectApp extends React.PureComponent<Props, State> {
     }, this.stopLoading);
   };
 
+  getIsProjectManaged = () => {
+    if (this.props.component.qualifier === ComponentQualifier.Project) {
+      getComponents({ projects: this.props.component.key })
+        .then((response) => {
+          if (this.mounted) {
+            const { managed } = response.components[0];
+            this.setState({
+              isProjectManaged: !!managed,
+            });
+          }
+        })
+        .catch(noop);
+    }
+  };
+
   handleLoadMore = () => {
     const { usersPaging, groupsPaging } = this.state;
     this.setState({ loading: true });
@@ -332,6 +351,7 @@ class PermissionsProjectApp extends React.PureComponent<Props, State> {
       users,
       usersPaging,
       groupsPaging,
+      isProjectManaged,
     } = this.state;
 
     let order = PERMISSIONS_ORDER_BY_QUALIFIER[component.qualifier];
@@ -345,9 +365,14 @@ class PermissionsProjectApp extends React.PureComponent<Props, State> {
         <PageContentFontWrapper className="sw-my-8 sw-body-sm">
           <Helmet defer={false} title={translate('permissions.page')} />
 
-          <PageHeader component={component} loadHolders={this.loadHolders} />
+          <PageHeader
+            isProjectManaged={isProjectManaged}
+            component={component}
+            loadHolders={this.loadHolders}
+          />
           <div>
             <PermissionsProjectVisibility
+              isProjectManaged={isProjectManaged}
               component={component}
               handleVisibilityChange={this.handleVisibilityChange}
               isLoading={loading}
@@ -380,6 +405,7 @@ class PermissionsProjectApp extends React.PureComponent<Props, State> {
             users={users}
             usersPaging={usersPaging}
             permissions={permissions}
+            isProjectManaged={isProjectManaged}
           />
         </PageContentFontWrapper>
       </LargeCenteredLayout>
index 8cde3d106768279fbdb66c558ba097d08879bf1d..abb7a4706f0aef827673ee3303a417f599b59033 100644 (file)
@@ -25,29 +25,26 @@ import {
   useIsGitLabProjectQuery,
 } from '../../../../queries/devops-integration';
 import { useGithubProvisioningEnabledQuery } from '../../../../queries/identity-provider/github';
-import { useGilabProvisioningEnabledQuery } from '../../../../queries/identity-provider/gitlab';
 import { Component } from '../../../../types/types';
 
 interface Props {
   component: Component;
   handleVisibilityChange: (visibility: string) => void;
   isLoading: boolean;
+  isProjectManaged: boolean;
 }
 
 export default function PermissionsProjectVisibility(props: Readonly<Props>) {
-  const { component, handleVisibilityChange, isLoading } = props;
+  const { component, handleVisibilityChange, isLoading, isProjectManaged } = props;
   const canTurnToPrivate = component.configuration?.canUpdateProjectVisibilityToPrivate;
 
   const { data: isGitHubProject } = useIsGitHubProjectQuery(component.key);
   const { data: isGitLabProject } = useIsGitLabProjectQuery(component.key);
   const { data: gitHubProvisioningStatus, isFetching: isFetchingGitHubProvisioningStatus } =
     useGithubProvisioningEnabledQuery();
-  const { data: gitLabProvisioningStatus, isFetching: isFetchingGitLabProvisioningStatus } =
-    useGilabProvisioningEnabledQuery();
-  const isFetching = isFetchingGitHubProvisioningStatus || isFetchingGitLabProvisioningStatus;
+  const isFetching = isFetchingGitHubProvisioningStatus;
   const isDisabled =
-    (isGitHubProject && !!gitHubProvisioningStatus) ||
-    (isGitLabProject && !!gitLabProvisioningStatus);
+    (isGitHubProject && !!gitHubProvisioningStatus) || (isGitLabProject && isProjectManaged);
 
   return (
     <VisibilitySelector
index e011d4c9c86d8439faba47ac2437e6ce63a051b8..e7e2585b60d0f0c46b8cc20631230ca491c7a47d 100644 (file)
@@ -21,16 +21,17 @@ import { screen, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import { ComponentQualifier, Visibility } from '~sonar-aligned/types/component';
 import AlmSettingsServiceMock from '../../../../../api/mocks/AlmSettingsServiceMock';
-import ComputeEngineServiceMock from '../../../../../api/mocks/ComputeEngineServiceMock';
 import DopTranslationServiceMock from '../../../../../api/mocks/DopTranslationServiceMock';
 import GithubProvisioningServiceMock from '../../../../../api/mocks/GithubProvisioningServiceMock';
 import GitlabProvisioningServiceMock from '../../../../../api/mocks/GitlabProvisioningServiceMock';
 import PermissionsServiceMock from '../../../../../api/mocks/PermissionsServiceMock';
+import ProjectManagementServiceMock from '../../../../../api/mocks/ProjectsManagementServiceMock';
+import SettingsServiceMock from '../../../../../api/mocks/SettingsServiceMock';
 import SystemServiceMock from '../../../../../api/mocks/SystemServiceMock';
-import { mockGitlabConfiguration } from '../../../../../helpers/mocks/alm-integrations';
 import { mockComponent } from '../../../../../helpers/mocks/component';
 import { mockGitHubConfiguration } from '../../../../../helpers/mocks/dop-translation';
 import { mockPermissionGroup, mockPermissionUser } from '../../../../../helpers/mocks/permissions';
+import { mockProject } from '../../../../../helpers/mocks/projects';
 import {
   PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE,
   PERMISSIONS_ORDER_FOR_VIEW,
@@ -44,7 +45,6 @@ import { ComponentContextShape } from '../../../../../types/component';
 import { Feature } from '../../../../../types/features';
 import { Permissions } from '../../../../../types/permissions';
 import { ProvisioningType } from '../../../../../types/provisioning';
-import { TaskStatuses, TaskTypes } from '../../../../../types/tasks';
 import { Component, PermissionGroup, PermissionUser, Provider } from '../../../../../types/types';
 import { projectPermissionsRoutes } from '../../../routes';
 import { getPageObject } from '../../../test-utils';
@@ -54,8 +54,9 @@ let dopTranslationHandler: DopTranslationServiceMock;
 let githubHandler: GithubProvisioningServiceMock;
 let gitlabHandler: GitlabProvisioningServiceMock;
 let almHandler: AlmSettingsServiceMock;
+let settingsHandler: SettingsServiceMock;
+let projectHandler: ProjectManagementServiceMock;
 let systemHandler: SystemServiceMock;
-let computeEngineHandler: ComputeEngineServiceMock;
 
 beforeAll(() => {
   serviceMock = new PermissionsServiceMock();
@@ -63,8 +64,9 @@ beforeAll(() => {
   githubHandler = new GithubProvisioningServiceMock(dopTranslationHandler);
   gitlabHandler = new GitlabProvisioningServiceMock();
   almHandler = new AlmSettingsServiceMock();
+  settingsHandler = new SettingsServiceMock();
+  projectHandler = new ProjectManagementServiceMock(settingsHandler);
   systemHandler = new SystemServiceMock();
-  computeEngineHandler = new ComputeEngineServiceMock();
 });
 
 afterEach(() => {
@@ -73,7 +75,8 @@ afterEach(() => {
   githubHandler.reset();
   gitlabHandler.reset();
   almHandler.reset();
-  computeEngineHandler.reset();
+  settingsHandler.reset();
+  projectHandler.reset();
 });
 
 describe('rendering', () => {
@@ -451,17 +454,13 @@ describe('GitHub provisioning', () => {
 describe('GitLab provisioning', () => {
   beforeEach(() => {
     systemHandler.setProvider(Provider.Gitlab);
-    computeEngineHandler.addTask({
-      status: TaskStatuses.InProgress,
-      executedAt: '2022-02-03T11:55:35+0200',
-      type: TaskTypes.GitlabProvisioning,
-    });
-    computeEngineHandler.addTask({
-      status: TaskStatuses.Failed,
-      executedAt: '2022-02-03T11:45:35+0200',
-      errorMessage: "T'es mauvais Jacques",
-      type: TaskTypes.GitlabProvisioning,
+    almHandler.handleSetProjectBinding(AlmKeys.GitLab, {
+      almSetting: 'test',
+      repository: 'test',
+      monorepo: false,
+      project: 'my-project',
     });
+    projectHandler.setProjects([mockProject({ key: 'my-project', managed: true })]);
   });
 
   it('should not allow to change visibility for GitLab Project with auto-provisioning', async () => {
@@ -470,9 +469,6 @@ describe('GitLab provisioning', () => {
     dopTranslationHandler.gitHubConfigurations.push(
       mockGitHubConfiguration({ provisioningType: ProvisioningType.jit }),
     );
-    gitlabHandler.setGitlabConfigurations([
-      mockGitlabConfiguration({ id: '1', enabled: true, provisioningType: ProvisioningType.auto }),
-    ]);
     almHandler.handleSetProjectBinding(AlmKeys.GitLab, {
       almSetting: 'test',
       repository: 'test',
@@ -493,9 +489,6 @@ describe('GitLab provisioning', () => {
   it('should allow to change visibility for non-GitLab Project', async () => {
     const user = userEvent.setup();
     const ui = getPageObject(user);
-    gitlabHandler.setGitlabConfigurations([
-      mockGitlabConfiguration({ id: '1', enabled: true, provisioningType: ProvisioningType.auto }),
-    ]);
     almHandler.handleSetProjectBinding(AlmKeys.GitHub, {
       almSetting: 'test',
       repository: 'test',
@@ -515,15 +508,13 @@ describe('GitLab provisioning', () => {
   it('should allow to change visibility for GitLab Project with disabled auto-provisioning', async () => {
     const user = userEvent.setup();
     const ui = getPageObject(user);
-    gitlabHandler.setGitlabConfigurations([
-      mockGitlabConfiguration({ id: '1', enabled: true, provisioningType: ProvisioningType.jit }),
-    ]);
     almHandler.handleSetProjectBinding(AlmKeys.GitLab, {
       almSetting: 'test',
       repository: 'test',
       monorepo: false,
       project: 'my-project',
     });
+    projectHandler.setProjects([mockProject({ key: 'my-project', managed: false })]);
     renderPermissionsProjectApp({}, { featureList: [Feature.GitlabProvisioning] });
     await ui.appLoaded();
 
@@ -537,9 +528,6 @@ describe('GitLab provisioning', () => {
   it('should have disabled permissions for GitLab Project', async () => {
     const user = userEvent.setup();
     const ui = getPageObject(user);
-    gitlabHandler.setGitlabConfigurations([
-      mockGitlabConfiguration({ id: '1', enabled: true, provisioningType: ProvisioningType.auto }),
-    ]);
     almHandler.handleSetProjectBinding(AlmKeys.GitLab, {
       almSetting: 'test',
       repository: 'test',
@@ -614,9 +602,8 @@ describe('GitLab provisioning', () => {
   it('should allow to change permissions for GitLab Project without auto-provisioning', async () => {
     const user = userEvent.setup();
     const ui = getPageObject(user);
-    gitlabHandler.setGitlabConfigurations([
-      mockGitlabConfiguration({ id: '1', enabled: true, provisioningType: ProvisioningType.jit }),
-    ]);
+
+    projectHandler.setProjects([mockProject({ key: 'my-project', managed: false })]);
     almHandler.handleSetProjectBinding(AlmKeys.GitLab, {
       almSetting: 'test',
       repository: 'test',
@@ -644,15 +631,22 @@ describe('GitLab provisioning', () => {
 
   it('should allow to change permissions for non-GitLab Project', async () => {
     const user = userEvent.setup();
+    projectHandler.reset();
+    projectHandler.setProjects([mockProject({ key: 'my-project', managed: false })]);
+    almHandler.reset();
+    almHandler.handleSetProjectBinding(AlmKeys.BitbucketServer, {
+      almSetting: 'test',
+      repository: 'test',
+      monorepo: false,
+      project: 'my-project',
+    });
+
     const ui = getPageObject(user);
-    gitlabHandler.setGitlabConfigurations([
-      mockGitlabConfiguration({ id: '1', enabled: true, provisioningType: ProvisioningType.auto }),
-    ]);
+
     renderPermissionsProjectApp({}, { featureList: [Feature.GitlabProvisioning] });
     await ui.appLoaded();
 
     expect(ui.pageTitle.get()).toBeInTheDocument();
-    expect(ui.nonGitLabProjectWarning.get()).toBeInTheDocument();
     expect(ui.pageTitle.byRole('img').query()).not.toBeInTheDocument();
 
     expect(ui.applyTemplateBtn.get()).toBeInTheDocument();
index 80b95d13e2c15134233a293866694d1ac6dead83..3c0f93d2bec742a63892704d01be1ce805361cd2 100644 (file)
@@ -48,7 +48,6 @@ export function getPageObject(user: UserEvent) {
       name: 'project_permission.remove_only_confirmation_title',
     }),
     nonGHProjectWarning: byText('project_permission.local_project_with_github_provisioning'),
-    nonGitLabProjectWarning: byText('project_permission.local_project_with_gitlab_provisioning'),
     makePublicDisclaimer: byText(
       'projects_role.are_you_sure_to_turn_project_to_public.warning.TRK',
     ),
index 9ee04386a93393c13f6f3d1b5c134de753bc651e..3828b10a77bb53e64b9c8803b562b97ddcbe2b35 100644 (file)
@@ -34,6 +34,7 @@ interface Props {
   filter: FilterOption;
   groups: PermissionGroup[];
   groupsPaging?: Paging;
+  isProjectManaged?: boolean;
   loading?: boolean;
   onFilter: (filter: string) => void;
   onGrantPermissionToGroup: (group: string, permission: string) => Promise<void>;
@@ -96,6 +97,7 @@ export default class AllHoldersList extends React.PureComponent<Props> {
       permissions,
       selectedPermission,
       loading = false,
+      isProjectManaged,
     } = this.props;
     const { count, total } = this.getPaging();
 
@@ -114,6 +116,7 @@ export default class AllHoldersList extends React.PureComponent<Props> {
           <BasicSeparator className="sw-mt-4" />
         </div>
         <HoldersList
+          isProjectManaged={!!isProjectManaged}
           loading={loading}
           filter={filter}
           groups={groups}
index 82482841c0223914a0c19bd1103d05c458164354..9f109eb7db8e31a7e3227b18681a224f9b2f7854 100644 (file)
@@ -25,7 +25,6 @@ import { translate } from '../../helpers/l10n';
 import { isPermissionDefinitionGroup } from '../../helpers/permissions';
 import { useIsGitHubProjectQuery, useIsGitLabProjectQuery } from '../../queries/devops-integration';
 import { useGithubProvisioningEnabledQuery } from '../../queries/identity-provider/github';
-import { useGilabProvisioningEnabledQuery } from '../../queries/identity-provider/gitlab';
 import { Dict, PermissionDefinitions, PermissionGroup, PermissionUser } from '../../types/types';
 import GroupHolder from './GroupHolder';
 import PermissionHeader from './PermissionHeader';
@@ -35,6 +34,7 @@ interface Props {
   filter?: string;
   groups: PermissionGroup[];
   isComponentPrivate?: boolean;
+  isProjectManaged: boolean;
   loading?: boolean;
   onSelectPermission?: (permission: string) => void;
   onToggleGroup: (group: PermissionGroup, permission: string) => Promise<void>;
@@ -108,58 +108,47 @@ export default class HoldersList extends React.PureComponent<
   }
 
   renderItem(item: PermissionUser | PermissionGroup, permissions: PermissionDefinitions) {
-    const { selectedPermission, isComponentPrivate } = this.props;
+    const { selectedPermission, isComponentPrivate, isProjectManaged } = this.props;
+
     return (
       <UseQuery key={this.getKey(item)} query={useIsGitHubProjectQuery}>
         {({ data: isGitHubProject }) => (
           <UseQuery key={this.getKey(item)} query={useIsGitLabProjectQuery}>
             {({ data: isGitLabProject }) => (
-              <UseQuery query={useGilabProvisioningEnabledQuery}>
-                {({ data: gitlabProvisioningStatus }) => (
-                  <UseQuery query={useGithubProvisioningEnabledQuery}>
-                    {({ data: githubProvisioningStatus }) => (
-                      <>
-                        {this.isPermissionUser(item) ? (
-                          <UserHolder
-                            key={`user-${item.login}`}
-                            onToggle={this.handleUserToggle}
-                            permissions={permissions}
-                            selectedPermission={selectedPermission}
-                            user={item}
-                            isGitHubUser={
-                              isGitHubProject && !!githubProvisioningStatus && item.managed
-                            }
-                            isGitLabUser={
-                              isGitLabProject && !!gitlabProvisioningStatus && item.managed
-                            }
-                            removeOnly={
-                              (isGitHubProject && !!githubProvisioningStatus && !item.managed) ||
-                              (isGitLabProject && !!gitlabProvisioningStatus && !item.managed)
-                            }
-                          />
-                        ) : (
-                          <GroupHolder
-                            group={item}
-                            isComponentPrivate={isComponentPrivate}
-                            key={`group-${item.id || item.name}`}
-                            onToggle={this.handleGroupToggle}
-                            permissions={permissions}
-                            selectedPermission={selectedPermission}
-                            isGitHubUser={
-                              isGitHubProject && !!githubProvisioningStatus && item.managed
-                            }
-                            isGitLabUser={
-                              isGitLabProject && !!gitlabProvisioningStatus && item.managed
-                            }
-                            removeOnly={
-                              (isGitHubProject && !!githubProvisioningStatus && !item.managed) ||
-                              (isGitLabProject && !!gitlabProvisioningStatus && !item.managed)
-                            }
-                          />
-                        )}
-                      </>
+              <UseQuery query={useGithubProvisioningEnabledQuery}>
+                {({ data: githubProvisioningStatus }) => (
+                  <>
+                    {this.isPermissionUser(item) ? (
+                      <UserHolder
+                        key={`user-${item.login}`}
+                        onToggle={this.handleUserToggle}
+                        permissions={permissions}
+                        selectedPermission={selectedPermission}
+                        user={item}
+                        isGitHubUser={isGitHubProject && !!githubProvisioningStatus && item.managed}
+                        isGitLabUser={isGitLabProject && item.managed}
+                        removeOnly={
+                          (isGitHubProject && !!githubProvisioningStatus && !item.managed) ||
+                          (isGitLabProject && isProjectManaged && !item.managed)
+                        }
+                      />
+                    ) : (
+                      <GroupHolder
+                        group={item}
+                        isComponentPrivate={isComponentPrivate}
+                        key={`group-${item.id || item.name}`}
+                        onToggle={this.handleGroupToggle}
+                        permissions={permissions}
+                        selectedPermission={selectedPermission}
+                        isGitHubUser={isGitHubProject && !!githubProvisioningStatus && item.managed}
+                        isGitLabUser={isGitLabProject && item.managed}
+                        removeOnly={
+                          (isGitHubProject && !!githubProvisioningStatus && !item.managed) ||
+                          (isGitLabProject && isProjectManaged && !item.managed)
+                        }
+                      />
                     )}
-                  </UseQuery>
+                  </>
                 )}
               </UseQuery>
             )}