Repository: ceph/ceph
Pull request #61772: mgr/dashboard: fix multisite wizard form not showing 
Author: login: nizamial09
Labels: name: pybind
name: dashboard
name: ui-tested
Created: 2025-02-12T14:16:45Z | Merged: 2025-03-14T14:26:51Z

**BEFORE**
![Screenshot From 2025-02-12 19-39-46](https://github.com/user-attachments/assets/4c770f85-6d70-4c63-b114-cbeb040372c4)

**AFTER**
![Screenshot From 2025-02-12 19-39-07](https://github.com/user-attachments/assets/702c92ff-90be-47a2-86be-eb3b1486836c)


Fixes: https://tracker.ceph.com/issues/69925
<!--
  - Please give your pull request a title like

      [component]: [short description]

  - Please use this format for each git commit message:

      [component]: [short description]

      [A longer multiline description]

      Fixes: [ticket URL on tracker.ceph.com, create one if necessary]
      Signed-off-by: [Your Name] <[your email]>

    For examples, use "git log".
-->

## Contribution Guidelines
- To sign and title your commits, please refer to [Submitting Patches to Ceph](https://github.com/ceph/ceph/blob/main/SubmittingPatches.rst).

- If you are submitting a fix for a stable branch (e.g. "quincy"), please refer to [Submitting Patches to Ceph - Backports](https://github.com/ceph/ceph/blob/master/SubmittingPatches-backports.rst) for the proper workflow.

- When filling out the below checklist, you may click boxes directly in the GitHub web UI.  When entering or editing the entire PR message in the GitHub web UI editor, you may also select a checklist item by adding an `x` between the brackets: `[x]`.  Spaces and capitalization matter when checking off items this way.

## Checklist
- Tracker (select at least one)
  - [x] References tracker ticket
  - [ ] Very recent bug; references commit where it was introduced
  - [ ] New feature (ticket optional)
  - [ ] Doc update (no ticket needed)
  - [ ] Code cleanup (no ticket needed)
- Component impact
  - [ ] Affects [Dashboard](https://tracker.ceph.com/projects/dashboard/issues/new), opened tracker ticket
  - [ ] Affects [Orchestrator](https://tracker.ceph.com/projects/orchestrator/issues/new), opened tracker ticket
  - [x] No impact that needs to be tracked
- Documentation (select at least one)
  - [ ] Updates relevant documentation
  - [x] No doc update is appropriate
- Tests (select at least one)
  - [ ] Includes [unit test(s)](https://docs.ceph.com/en/latest/dev/developer_guide/tests-unit-tests/)
  - [ ] Includes [integration test(s)](https://docs.ceph.com/en/latest/dev/developer_guide/testing_integration_tests/)
  - [ ] Includes bug reproducer
  - [x] No tests

<details>
<summary>Show available Jenkins commands</summary>

- `jenkins retest this please`
- `jenkins test classic perf`
- `jenkins test crimson perf`
- `jenkins test signed`
- `jenkins test make check`
- `jenkins test make check arm64`
- `jenkins test submodules`
- `jenkins test dashboard`
- `jenkins test dashboard cephadm`
- `jenkins test api`
- `jenkins test docs`
- `jenkins render docs`
- `jenkins test ceph-volume all`
- `jenkins test ceph-volume tox`
- `jenkins test windows`
- `jenkins test rook e2e`
</details>



Diff:
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.e2e-spec.ts
index 8d8ed22da0b7f..5ec787f2e33a5 100644
--- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.e2e-spec.ts
+++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.e2e-spec.ts
@@ -102,4 +102,31 @@ describe('Multisite page', () => {
       multisite.deletePipe('new-pipe');
     });
   });
+
+  describe('Multi-site topology viewer', () => {
+    it('should show topology viewer', () => {
+      multisite.navigateTo('topology');
+      multisite.topologyViewerExist();
+    });
+
+    describe('Multisite replication wizard', () => {
+      beforeEach(() => {
+        multisite.navigateTo('wizard');
+      });
+
+      it('should show replication wizard', () => {
+        multisite.replicationWizardExist();
+      });
+
+      it('should verify the wizard is properly loaded', () => {
+        multisite.replicationWizardExist();
+        // // Verify first step
+        multisite.verifyWizardContents('CreateRealmZonegroup');
+        // Verify second step
+        multisite.verifyWizardContents('CreateZone');
+        // Verify the review
+        multisite.verifyWizardContents('Review');
+      });
+    });
+  });
 });
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.po.ts
index 0f2078f20bba4..5240c8364244a 100644
--- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.po.ts
+++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.po.ts
@@ -7,8 +7,22 @@ const pages = {
     url: '#/rgw/multisite/sync-policy/(modal:create)',
     id: 'cd-rgw-multisite-sync-policy-form'
   },
-  edit: { url: '#/rgw/multisite/sync-policy/(modal:edit', id: 'cd-rgw-multisite-sync-policy-form' }
+  edit: { url: '#/rgw/multisite/sync-policy/(modal:edit', id: 'cd-rgw-multisite-sync-policy-form' },
+  topology: { url: '#/rgw/multisite/configuration', id: 'cd-rgw-multisite-details' },
+  wizard: {
+    url: '#/rgw/multisite/configuration/(modal:setup-multisite-replication)',
+    id: 'cd-rgw-multisite-wizard'
+  }
 };
+
+enum WizardSteps {
+  CreateRealmZonegroup = 'Create Realm & Zonegroup',
+  CreateZone = 'Create Zone',
+  Review = 'Review'
+}
+
+type Step = keyof typeof WizardSteps;
+
 export class MultisitePageHelper extends PageHelper {
   pages = pages;
 
@@ -289,4 +303,51 @@ export class MultisitePageHelper extends PageHelper {
     // Waits for item to be removed from table
     getRow(pipe_id).should('not.exist');
   }
+
+  @PageHelper.restrictTo(pages.topology.url)
+  topologyViewerExist() {
+    cy.get(pages.topology.id).should('be.visible');
+    cy.get('[data-testid=rgw-multisite-details-header]').should('have.text', 'Topology Viewer');
+  }
+
+  @PageHelper.restrictTo(pages.wizard.url)
+  replicationWizardExist() {
+    cy.get('cds-modal').then(() => {
+      cy.get('[data-testid=rgw-multisite-wizard-header]').should(
+        'have.text',
+        'Set up Multi-site Replication'
+      );
+    });
+  }
+
+  @PageHelper.restrictTo(pages.index.url)
+  verifyWizardContents(step: Step) {
+    cy.get('cds-modal').then(() => {
+      this.gotoStep(step);
+      if (step === 'CreateRealmZonegroup') {
+        this.typeValueToField('realmName', 'test-realm');
+        this.typeValueToField('zonegroupName', 'test-zg');
+      } else if (step === 'CreateZone') {
+        this.typeValueToField('zoneName', 'test-zone');
+      } else {
+        this.gotoStep('Review');
+        cy.get('.form-group.row').then(() => {
+          cy.get('#realmName').invoke('text').should('eq', 'test-realm');
+          cy.get('#zonegroupName').invoke('text').should('eq', 'test-zg');
+          cy.get('#zoneName').invoke('text').should('eq', 'test-zone');
+        });
+      }
+    });
+  }
+
+  typeValueToField(fieldID: string, value: string) {
+    cy.get(`#${fieldID}`).clear().type(value).should('have.value', value);
+  }
+
+  gotoStep(step: Step) {
+    cy.get('cd-wizard').then(() => {
+      cy.get('form').should('be.visible');
+      cy.get('button').contains(WizardSteps[step]).click();
+    });
+  }
 }
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/support/e2e.ts b/src/pybind/mgr/dashboard/frontend/cypress/support/e2e.ts
index 4db2c6a4926a9..8b55b576589d0 100644
--- a/src/pybind/mgr/dashboard/frontend/cypress/support/e2e.ts
+++ b/src/pybind/mgr/dashboard/frontend/cypress/support/e2e.ts
@@ -17,3 +17,10 @@ Cypress.on('uncaught:exception', (err: Error) => {
   }
   return true;
 });
+
+Cypress.on('fail', (err: Error) => {
+  if (err.message.includes('xhr') && err.message.includes('canceled')) {
+    return false; // Ignore canceled XHR requests
+  }
+  return true;
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html
index c3b740ec7c682..48fc0eeb5f2f7 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html
@@ -51,8 +51,8 @@
 </div>
 <div class="card">
   <div class="card-header"
-       i18n>Topology Viewer
-  </div>
+       data-testid="rgw-multisite-details-header"
+       i18n>Topology Viewer</div>
   <div class="row">
     <div class="col-sm-6 col-lg-6 tree-container">
       <i *ngIf="loadingIndicator"
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.html
index 9a34ce63bacbd..d431cd3aa6caf 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.html
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.html
@@ -5,6 +5,7 @@
            (overlaySelected)="closeModal()">
   <cds-modal-header (closeSelect)="closeModal()">
     <h3 cdsModalHeaderHeading
+        data-testid="rgw-multisite-wizard-header"
         i18n>Set up Multi-site Replication</h3>
   </cds-modal-header>
 
@@ -22,7 +23,7 @@
               #formDir="ngForm"
               novalidate>
           <ng-container [ngSwitch]="currentStep?.stepIndex">
-            <div *ngSwitchCase="'0'"
+            <div *ngSwitchCase="0"
                  class="ms-5">
               <cd-alert-panel type="info"
                               spacingClass="mb-3">
@@ -95,7 +96,7 @@
                 </div>
               </div>
             </div>
-            <div *ngSwitchCase="'1'"
+            <div *ngSwitchCase="1"
                  class="ms-5">
               <div class="form-group row">
                 <label class="cd-col-form-label required"
@@ -165,7 +166,7 @@
               </div>
             </div>
             <div cass="ms-5"
-                 *ngSwitchCase="'2'">
+                 *ngSwitchCase="2">
               <div *ngIf="isMultiClusterConfigured; else nonMultiClusterTemplate">
                 <div class="form-group row">
                   <label class="cd-col-form-label required"
@@ -212,7 +213,7 @@
                 </div>
               </div>
             </div>
-            <div *ngSwitchCase="'3'"
+            <div *ngSwitchCase="3"
                  class="ms-5">
               <div *ngIf="isMultiClusterConfigured">
                 <ng-container *ngIf="!loading; else loadingTemplate">
@@ -323,14 +324,16 @@
   <div class="form-group row">
     <label class="cd-col-form-label"
            i18n>Realm Name:</label>
-    <div class="cd-col-form-input mt-2 text-muted">
+    <div class="cd-col-form-input mt-2 text-muted"
+         id="realmName">
       <b>{{ multisiteSetupForm.get('realmName').value }}</b>
     </div>
   </div>
   <div class="form-group row">
     <label class="cd-col-form-label"
            i18n>Zonegroup Name:</label>
-    <div class="cd-col-form-input mt-2 text-muted">
+    <div class="cd-col-form-input mt-2 text-muted"
+         id="zonegroupName">
       <b>{{ multisiteSetupForm.get('zonegroupName').value }}</b>
     </div>
   </div>
@@ -344,7 +347,8 @@
   <div class="form-group row">
     <label class="cd-col-form-label"
            i18n>Zone Name:</label>
-    <div class="cd-col-form-input mt-2 text-muted">
+    <div class="cd-col-form-input mt-2 text-muted"
+         id="zoneName">
       <b>{{ multisiteSetupForm.get('zoneName').value }}</b>
     </div>
   </div>


Reviews:
user: login: aaSharma14
body: Tested locally, looks good to me! Thanks @nizamial09 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-02-13T06:18:51Z
user: login: afreen23
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T09:27:25Z
user: login: afreen23
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T09:28:08Z
user: login: nizamial09
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-11T17:22:32Z
user: login: nizamial09
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-11T17:24:35Z

Review comments:
diff_hunk: @@ -7,8 +7,22 @@ const pages = {
     url: '#/rgw/multisite/sync-policy/(modal:create)',
     id: 'cd-rgw-multisite-sync-policy-form'
   },
-  edit: { url: '#/rgw/multisite/sync-policy/(modal:edit', id: 'cd-rgw-multisite-sync-policy-form' }
+  edit: { url: '#/rgw/multisite/sync-policy/(modal:edit', id: 'cd-rgw-multisite-sync-policy-form' },
+  topology: { url: '#/rgw/multisite/configuration', id: 'cd-rgw-multisite-details' },
+  wizard: {
+    url: '#/rgw/multisite/configuration/(modal:setup-multisite-replication)',
+    id: 'cd-rgw-multisite-wizard'
+  }
 };
+
+enum WizardSteps {
+  CreateRealmZonegroup = 'Create Realm & Zonegroup',
+  CreateZone = 'Create Zone',
+  Review = 'Review'
path: src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.po.ts
user: login: afreen23
body: localize
created_at: 2025-03-11T09:26:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -289,4 +303,50 @@ export class MultisitePageHelper extends PageHelper {
     // Waits for item to be removed from table
     getRow(pipe_id).should('not.exist');
   }
+
+  @PageHelper.restrictTo(pages.topology.url)
+  topologyViewerExist() {
+    cy.get(pages.topology.id).should('be.visible');
+    cy.get('[data-testid=rgw-multisite-details-header]').should('have.text', 'Topology Viewer');
+  }
+
+  @PageHelper.restrictTo(pages.wizard.url)
+  replicationWizardExist() {
+    cy.get('cds-modal').then(() => {
+      cy.get('[data-testid=rgw-multisite-wizard-header]').should(
+        'have.text',
+        'Set up Multi-site Replication'
+      );
+    });
+  }
+
+  @PageHelper.restrictTo(pages.index.url)
+  verifyWizardContents(step: Step) {
+    cy.get('cds-modal').then(() => {
+      this.gotoStep(step);
+      if (step === 'CreateRealmZonegroup') {
path: src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.po.ts
user: login: afreen23
body: (non-blocking): we should have an enum for steps.
created_at: 2025-03-11T09:27:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,8 +7,22 @@ const pages = {
     url: '#/rgw/multisite/sync-policy/(modal:create)',
     id: 'cd-rgw-multisite-sync-policy-form'
   },
-  edit: { url: '#/rgw/multisite/sync-policy/(modal:edit', id: 'cd-rgw-multisite-sync-policy-form' }
+  edit: { url: '#/rgw/multisite/sync-policy/(modal:edit', id: 'cd-rgw-multisite-sync-policy-form' },
+  topology: { url: '#/rgw/multisite/configuration', id: 'cd-rgw-multisite-details' },
+  wizard: {
+    url: '#/rgw/multisite/configuration/(modal:setup-multisite-replication)',
+    id: 'cd-rgw-multisite-wizard'
+  }
 };
+
+enum WizardSteps {
+  CreateRealmZonegroup = 'Create Realm & Zonegroup',
+  CreateZone = 'Create Zone',
+  Review = 'Review'
path: src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.po.ts
user: login: nizamial09
body: we don't need to localize the tests. It isn't exposed to any end users.
created_at: 2025-03-11T17:22:32Z
author_association: MEMBER
diff_hunk: @@ -289,4 +303,50 @@ export class MultisitePageHelper extends PageHelper {
     // Waits for item to be removed from table
     getRow(pipe_id).should('not.exist');
   }
+
+  @PageHelper.restrictTo(pages.topology.url)
+  topologyViewerExist() {
+    cy.get(pages.topology.id).should('be.visible');
+    cy.get('[data-testid=rgw-multisite-details-header]').should('have.text', 'Topology Viewer');
+  }
+
+  @PageHelper.restrictTo(pages.wizard.url)
+  replicationWizardExist() {
+    cy.get('cds-modal').then(() => {
+      cy.get('[data-testid=rgw-multisite-wizard-header]').should(
+        'have.text',
+        'Set up Multi-site Replication'
+      );
+    });
+  }
+
+  @PageHelper.restrictTo(pages.index.url)
+  verifyWizardContents(step: Step) {
+    cy.get('cds-modal').then(() => {
+      this.gotoStep(step);
+      if (step === 'CreateRealmZonegroup') {
path: src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.po.ts
user: login: nizamial09
body: I created an enum for the steps `enum WizardSteps` above. And I also have the type defined above for `Step` as `type Step = keyof typeof WizardSteps;` so essentially the `step` will only have these strings..
created_at: 2025-03-11T17:24:31Z
author_association: MEMBER

PR comments:
user: login: cloudbehl
created_at: 2025-02-17T12:43:22Z
body: jenkins test make check
author_association: CONTRIBUTOR
user: login: nizamial09
created_at: 2025-02-24T07:47:11Z
body: jenkins test make check
author_association: MEMBER
user: login: nizamial09
created_at: 2025-02-24T07:47:39Z
body: jenkins test make check arm64
author_association: MEMBER
user: login: aaSharma14
created_at: 2025-03-03T05:06:05Z
body: jenkins test make check
author_association: CONTRIBUTOR
user: login: nizamial09
created_at: 2025-03-05T07:34:53Z
body: jenkins test make check
author_association: MEMBER
user: login: aaSharma14
created_at: 2025-03-10T05:29:30Z
body: jenkins test make check
author_association: CONTRIBUTOR
user: login: nizamial09
created_at: 2025-03-14T05:39:28Z
body: jenkins test dashboard
author_association: MEMBER
user: login: nizamial09
created_at: 2025-03-14T07:17:21Z
body: looks like there is a related failure, will test it and update the PR
[Test Result](https://jenkins.ceph.com/job/ceph-dashboard-pull-requests/18168/testReport/) (8 failures / +4)
[should verify the wizard is properly loaded.Multisite page Multi-site topology viewer Multisite replication wizard should verify the wizard is properly loaded](https://jenkins.ceph.com/job/ceph-dashboard-pull-requests/18168/testReport/junit/(root)/should%20verify%20the%20wizard%20is%20properly%20loaded/Multisite_page_Multi_site_topology_viewer_Multisite_replication_wizard_should_verify_the_wizard_is_properly_loaded/)
author_association: MEMBER
user: login: nizamial09
created_at: 2025-03-14T07:59:05Z
body: while running this e2e multiple times locally, I came across this weird e2e failure where even if all the tests passes cypress gives you an error at the end
```
after all (failed)
(xhr)GET /api/rgw/zone/get_all_zones_info
(uncaught exception)TypeError: Cannot read properties of undefined (reading 'focus')
```

while checking further I saw that some of the API calls like `get_all_zones_info` are getting cancelled while exiting the tests and this registers as a failure. These API calls are timed and polled frequently but when you switch component it gets cancelled. Hence I went ahead and added 
```
Cypress.on('fail', (err: Error) => {
  if (err.message.includes('xhr') && err.message.includes('canceled')) {
    return false; // Ignore canceled XHR requests
  }
  return true;
});
```

to ignore it.
author_association: MEMBER
user: login: nizamial09
created_at: 2025-03-14T10:30:30Z
body: > while checking further I saw that some of the API calls that like `get_all_zones_info` are getting cancelled while exiting the tests and this registers as a failure. These API calls are timed and polled frequently. Hence I went ahead and added

maybe a better way is to have `finalizers` or `complete` when destroying component?
author_association: MEMBER
user: login: nizamial09
created_at: 2025-03-14T10:56:25Z
body: jenkins test make check arm64
author_association: MEMBER