You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@texera.apache.org by "mengw15 (via GitHub)" <gi...@apache.org> on 2026/07/19 07:58:27 UTC

[PR] test(frontend): extend ComputingUnitSelectionComponent coverage with resource getters [texera]

mengw15 opened a new pull request, #6555:
URL: https://github.com/apache/texera/pull/6555

   ### What changes were proposed in this PR?
   
   Extends the existing `ComputingUnitSelectionComponent` spec
   (`frontend/src/app/workspace/component/power-button/`, codecov ~59%) to cover the
   previously-untested resource-display getters and a few small handlers. Follows
   `frontend/TESTING.md` and reuses the existing `makeComputingUnit` fixture.
   
   Adds 13 tests:
   
   - **resource accessors** — `getCurrentComputingUnit{Cpu,Memory}Usage`,
     `getCurrentComputingUnit{Cpu,Memory,Gpu}Limit`, `getCurrentComputingUnitJvmMemorySize`,
     `getCurrentSharedMemorySize`: read the selected unit's raw values, and return
     `"NaN"` for every accessor when nothing is selected.
   - **derived getters** — `getCpuLimit`/`getMemoryLimit` (numbers), `getGpuLimit`/
     `getJvmMemorySize`/`getSharedMemorySize`, `getCpuLimitUnit`/`getMemoryLimitUnit`/
     `getCpuUnit`/`getMemoryUnit`; `getCpuValue`/`getMemoryValue`/`getCpuPercentage`/
     `getMemoryPercentage` return 0 when metrics are unavailable; `getCpuStatus`/
     `getMemoryStatus` map to `"success"`; `getBadgeColor` and `getUnitStatusTooltip`
     map status → color/tooltip.
   - **misc handlers** — `trackByCuid`/`trackByIndex` identity, `cancelEditingUnitName`
     state reset, `openComputingUnitMetadataModal` opens the metadata modal with the
     unit as `nzData`, and `scrollToBottomOfPipModal` scrolls the deferred pip-log
     element (and is a safe no-op when it is absent).
   
   No production code was changed.
   
   ### Any related issues, documentation, discussions?
   
   Closes #6551
   
   ### How was this PR tested?
   
   Extended Vitest spec, run locally in `frontend/` (whole file green, run 4× to
   confirm determinism; failure path verified by breaking an assertion). To stay
   deterministic, `scrollToBottomOfPipModal` is driven with fake timers and a
   stubbed `document.getElementById` (a plain stand-in object, restored after each
   test) rather than relying on jsdom layout.
   
   ```
   ng test --watch=false --include src/app/workspace/component/power-button/computing-unit-selection.component.spec.ts
   # Test Files 1 passed (1) / Tests 100 passed (100)
   eslint <spec>      # clean
   prettier --check   # clean
   ```
   
   ### Was this PR authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Opus 4.8 [1M context])
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@texera.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] test(frontend): extend ComputingUnitSelectionComponent coverage with resource getters [texera]

Posted by "codecov-commenter (via GitHub)" <gi...@apache.org>.
codecov-commenter commented on PR #6555:
URL: https://github.com/apache/texera/pull/6555#issuecomment-5014967578

   ## [Codecov](https://app.codecov.io/gh/apache/texera/pull/6555?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) Report
   :white_check_mark: All modified and coverable lines are covered by tests.
   :white_check_mark: Project coverage is 72.58%. Comparing base ([`80c45be`](https://app.codecov.io/gh/apache/texera/commit/80c45be89dbbc64abafe0f70a1c05ef7117fbaa8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)) to head ([`90df8bb`](https://app.codecov.io/gh/apache/texera/commit/90df8bba8b7ecddb92fd06e1e2f8c2f58a04e6bd?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)).
   
   <details><summary>Additional details and impacted files</summary>
   
   
   
   ```diff
   @@             Coverage Diff              @@
   ##               main    #6555      +/-   ##
   ============================================
   + Coverage     72.54%   72.58%   +0.04%     
     Complexity     3436     3436              
   ============================================
     Files          1145     1145              
     Lines         45129    45129              
     Branches       4975     4975              
   ============================================
   + Hits          32738    32757      +19     
   + Misses        10709    10685      -24     
   - Partials       1682     1687       +5     
   ```
   
   | [Flag](https://app.codecov.io/gh/apache/texera/pull/6555/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Coverage Δ | | *Carryforward flag |
   |---|---|---|---|
   | [access-control-service](https://app.codecov.io/gh/apache/texera/pull/6555/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `70.00% <ø> (ø)` | | Carriedforward from [80c45be](https://app.codecov.io/gh/apache/texera/commit/80c45be89dbbc64abafe0f70a1c05ef7117fbaa8?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [agent-service](https://app.codecov.io/gh/apache/texera/pull/6555/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `76.76% <ø> (ø)` | | Carriedforward from [80c45be](https://app.codecov.io/gh/apache/texera/commit/80c45be89dbbc64abafe0f70a1c05ef7117fbaa8?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [amber](https://app.codecov.io/gh/apache/texera/pull/6555/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `66.81% <ø> (ø)` | | Carriedforward from [80c45be](https://app.codecov.io/gh/apache/texera/commit/80c45be89dbbc64abafe0f70a1c05ef7117fbaa8?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [computing-unit-managing-service](https://app.codecov.io/gh/apache/texera/pull/6555/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `17.72% <ø> (ø)` | | Carriedforward from [80c45be](https://app.codecov.io/gh/apache/texera/commit/80c45be89dbbc64abafe0f70a1c05ef7117fbaa8?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [config-service](https://app.codecov.io/gh/apache/texera/pull/6555/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `66.66% <ø> (ø)` | | Carriedforward from [80c45be](https://app.codecov.io/gh/apache/texera/commit/80c45be89dbbc64abafe0f70a1c05ef7117fbaa8?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [file-service](https://app.codecov.io/gh/apache/texera/pull/6555/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `66.80% <ø> (ø)` | | Carriedforward from [80c45be](https://app.codecov.io/gh/apache/texera/commit/80c45be89dbbc64abafe0f70a1c05ef7117fbaa8?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [frontend](https://app.codecov.io/gh/apache/texera/pull/6555/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `73.91% <ø> (+0.10%)` | :arrow_up: | |
   | [notebook-migration-service](https://app.codecov.io/gh/apache/texera/pull/6555/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `78.94% <ø> (ø)` | | Carriedforward from [80c45be](https://app.codecov.io/gh/apache/texera/commit/80c45be89dbbc64abafe0f70a1c05ef7117fbaa8?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [pyamber](https://app.codecov.io/gh/apache/texera/pull/6555/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `91.79% <ø> (ø)` | | Carriedforward from [80c45be](https://app.codecov.io/gh/apache/texera/commit/80c45be89dbbc64abafe0f70a1c05ef7117fbaa8?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [workflow-compiling-service](https://app.codecov.io/gh/apache/texera/pull/6555/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `55.14% <ø> (ø)` | | Carriedforward from [80c45be](https://app.codecov.io/gh/apache/texera/commit/80c45be89dbbc64abafe0f70a1c05ef7117fbaa8?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   
   *This pull request uses carry forward flags. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) to find out more.
   </details>
   
   [:umbrella: View full report in Codecov by Harness](https://app.codecov.io/gh/apache/texera/pull/6555?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache).   
   :loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache).
   <details><summary> :rocket: New features to boost your workflow: </summary>
   
   - :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
   </details>


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@texera.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] test(frontend): extend ComputingUnitSelectionComponent coverage with resource getters [texera]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #6555:
URL: https://github.com/apache/texera/pull/6555#issuecomment-5014947736

   <!-- texera-reviewer-suggestion -->
   ### Automated Reviewer Suggestions
   
   Based on the `git blame` history of the changed files, we recommend the following reviewers:
   
   - **Contributors with relevant context:** `@aglinxinyuan`
     You can notify them by mentioning `@aglinxinyuan` in a comment.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@texera.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] test(frontend): extend ComputingUnitSelectionComponent coverage with resource getters [texera]

Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #6555:
URL: https://github.com/apache/texera/pull/6555#discussion_r3610168733


##########
frontend/src/app/workspace/component/power-button/computing-unit-selection.component.spec.ts:
##########
@@ -1263,4 +1264,148 @@ describe("PowerButtonComponent", () => {
       expect(host.querySelector(".unit-name-text")).toBeNull();
     });
   });
+
+  describe("resource display accessors", () => {
+    beforeEach(() => {
+      component.selectedComputingUnit = makeComputingUnit();
+    });
+
+    it("reads the selected unit's raw resource limits and usage", () => {
+      expect(component.getCurrentComputingUnitCpuLimit()).toBe("1");
+      expect(component.getCurrentComputingUnitMemoryLimit()).toBe("1Gi");
+      expect(component.getCurrentComputingUnitGpuLimit()).toBe("0");
+      expect(component.getCurrentComputingUnitJvmMemorySize()).toBe("1Gi");
+      expect(component.getCurrentSharedMemorySize()).toBe("64Mi");
+      expect(component.getCurrentComputingUnitCpuUsage()).toBe("N/A");
+      expect(component.getCurrentComputingUnitMemoryUsage()).toBe("N/A");
+    });
+
+    it("returns 'NaN' for every accessor when no unit is selected", () => {
+      component.selectedComputingUnit = null;
+      expect(component.getCurrentComputingUnitCpuLimit()).toBe("NaN");
+      expect(component.getCurrentComputingUnitMemoryLimit()).toBe("NaN");
+      expect(component.getCurrentComputingUnitGpuLimit()).toBe("NaN");
+      expect(component.getCurrentComputingUnitJvmMemorySize()).toBe("NaN");
+      expect(component.getCurrentSharedMemorySize()).toBe("NaN");
+      expect(component.getCurrentComputingUnitCpuUsage()).toBe("NaN");
+      expect(component.getCurrentComputingUnitMemoryUsage()).toBe("NaN");
+    });
+  });
+
+  describe("resource display getters", () => {
+    beforeEach(() => {
+      component.selectedComputingUnit = makeComputingUnit();
+    });
+
+    it("derives the display limits and units from the raw values", () => {
+      expect(component.getCpuLimit()).toBe(1);
+      expect(component.getMemoryLimit()).toBe(1);
+      expect(component.getGpuLimit()).toBe("0");
+      expect(component.getJvmMemorySize()).toBe("1Gi");
+      expect(component.getSharedMemorySize()).toBe("64Mi");
+      expect(component.getCpuLimitUnit()).toBe("CPU");
+      expect(component.getMemoryLimitUnit()).toBe("Gi");
+      expect(component.getCpuUnit()).toBe("Cores");
+      expect(component.getMemoryUnit()).toBe("Gi");
+    });
+
+    it("returns zero usage values and percentages when metrics are unavailable", () => {
+      expect(component.getCpuValue()).toBe(0);
+      expect(component.getMemoryValue()).toBe(0);
+      expect(component.getCpuPercentage()).toBe(0);
+      expect(component.getMemoryPercentage()).toBe(0);
+    });
+
+    it("maps a zero percentage to the 'success' progress status", () => {
+      expect(component.getCpuStatus()).toBe("success");
+      expect(component.getMemoryStatus()).toBe("success");
+    });
+
+    it("maps a status to a badge color", () => {
+      expect(component.getBadgeColor("Running")).toBe("green");
+      expect(component.getBadgeColor("Pending")).toBe("gold");
+      expect(component.getBadgeColor("Failed")).toBe("red");
+    });
+
+    it("describes a unit's status as a tooltip", () => {
+      expect(component.getUnitStatusTooltip(makeComputingUnit({ status: "Running" }))).toBe("Ready to use");
+      expect(component.getUnitStatusTooltip(makeComputingUnit({ status: "Pending" }))).toBe(
+        "Computing unit is starting up"
+      );
+      expect(component.getUnitStatusTooltip(makeComputingUnit({ status: "Failed" }))).toBe("Failed");
+    });
+  });
+
+  describe("trackBy helpers", () => {
+    it("trackByCuid keys a unit by its cuid", () => {
+      expect(component.trackByCuid(0, makeComputingUnit({ cuid: 77 }))).toBe(77);
+    });
+
+    it("trackByIndex returns the index", () => {
+      expect(component.trackByIndex(4)).toBe(4);
+    });
+  });
+
+  describe("cancelEditingUnitName", () => {
+    it("clears the editing state", () => {
+      component.editingNameOfUnit = 3;
+      component.editingUnitName = "half-typed";
+      component.cancelEditingUnitName();
+      expect(component.editingNameOfUnit).toBeNull();
+      expect(component.editingUnitName).toBe("");
+    });
+  });
+
+  describe("openComputingUnitMetadataModal", () => {
+    it("opens the metadata modal with the unit as nzData", () => {
+      const unit = makeComputingUnit({ cuid: 9 });
+      const createSpy = vi.spyOn(TestBed.inject(NzModalService), "create").mockReturnValue({} as any);
+
+      component.openComputingUnitMetadataModal(unit);
+
+      expect(createSpy).toHaveBeenCalledWith(
+        expect.objectContaining({
+          nzTitle: "Computing Unit Information",
+          nzContent: ComputingUnitMetadataComponent,
+          nzData: unit,
+          nzFooter: null,
+        })
+      );

Review Comment:
   The `NzModalService.create` spy isn’t restored, which can leak a mocked method into later tests (in this file or future additions) and make the suite order-dependent. Please restore the spy in a `finally` (consistent with the fake-timer tests below) or add an `afterEach` restore.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@texera.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] test(frontend): extend ComputingUnitSelectionComponent coverage with resource getters [texera]

Posted by "aglinxinyuan (via GitHub)" <gi...@apache.org>.
aglinxinyuan merged PR #6555:
URL: https://github.com/apache/texera/pull/6555


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@texera.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] test(frontend): extend ComputingUnitSelectionComponent coverage with resource getters [texera]

Posted by "mengw15 (via GitHub)" <gi...@apache.org>.
mengw15 commented on code in PR #6555:
URL: https://github.com/apache/texera/pull/6555#discussion_r3610176627


##########
frontend/src/app/workspace/component/power-button/computing-unit-selection.component.spec.ts:
##########
@@ -1263,4 +1264,148 @@ describe("PowerButtonComponent", () => {
       expect(host.querySelector(".unit-name-text")).toBeNull();
     });
   });
+
+  describe("resource display accessors", () => {
+    beforeEach(() => {
+      component.selectedComputingUnit = makeComputingUnit();
+    });
+
+    it("reads the selected unit's raw resource limits and usage", () => {
+      expect(component.getCurrentComputingUnitCpuLimit()).toBe("1");
+      expect(component.getCurrentComputingUnitMemoryLimit()).toBe("1Gi");
+      expect(component.getCurrentComputingUnitGpuLimit()).toBe("0");
+      expect(component.getCurrentComputingUnitJvmMemorySize()).toBe("1Gi");
+      expect(component.getCurrentSharedMemorySize()).toBe("64Mi");
+      expect(component.getCurrentComputingUnitCpuUsage()).toBe("N/A");
+      expect(component.getCurrentComputingUnitMemoryUsage()).toBe("N/A");
+    });
+
+    it("returns 'NaN' for every accessor when no unit is selected", () => {
+      component.selectedComputingUnit = null;
+      expect(component.getCurrentComputingUnitCpuLimit()).toBe("NaN");
+      expect(component.getCurrentComputingUnitMemoryLimit()).toBe("NaN");
+      expect(component.getCurrentComputingUnitGpuLimit()).toBe("NaN");
+      expect(component.getCurrentComputingUnitJvmMemorySize()).toBe("NaN");
+      expect(component.getCurrentSharedMemorySize()).toBe("NaN");
+      expect(component.getCurrentComputingUnitCpuUsage()).toBe("NaN");
+      expect(component.getCurrentComputingUnitMemoryUsage()).toBe("NaN");
+    });
+  });
+
+  describe("resource display getters", () => {
+    beforeEach(() => {
+      component.selectedComputingUnit = makeComputingUnit();
+    });
+
+    it("derives the display limits and units from the raw values", () => {
+      expect(component.getCpuLimit()).toBe(1);
+      expect(component.getMemoryLimit()).toBe(1);
+      expect(component.getGpuLimit()).toBe("0");
+      expect(component.getJvmMemorySize()).toBe("1Gi");
+      expect(component.getSharedMemorySize()).toBe("64Mi");
+      expect(component.getCpuLimitUnit()).toBe("CPU");
+      expect(component.getMemoryLimitUnit()).toBe("Gi");
+      expect(component.getCpuUnit()).toBe("Cores");
+      expect(component.getMemoryUnit()).toBe("Gi");
+    });
+
+    it("returns zero usage values and percentages when metrics are unavailable", () => {
+      expect(component.getCpuValue()).toBe(0);
+      expect(component.getMemoryValue()).toBe(0);
+      expect(component.getCpuPercentage()).toBe(0);
+      expect(component.getMemoryPercentage()).toBe(0);
+    });
+
+    it("maps a zero percentage to the 'success' progress status", () => {
+      expect(component.getCpuStatus()).toBe("success");
+      expect(component.getMemoryStatus()).toBe("success");
+    });
+
+    it("maps a status to a badge color", () => {
+      expect(component.getBadgeColor("Running")).toBe("green");
+      expect(component.getBadgeColor("Pending")).toBe("gold");
+      expect(component.getBadgeColor("Failed")).toBe("red");
+    });
+
+    it("describes a unit's status as a tooltip", () => {
+      expect(component.getUnitStatusTooltip(makeComputingUnit({ status: "Running" }))).toBe("Ready to use");
+      expect(component.getUnitStatusTooltip(makeComputingUnit({ status: "Pending" }))).toBe(
+        "Computing unit is starting up"
+      );
+      expect(component.getUnitStatusTooltip(makeComputingUnit({ status: "Failed" }))).toBe("Failed");
+    });
+  });
+
+  describe("trackBy helpers", () => {
+    it("trackByCuid keys a unit by its cuid", () => {
+      expect(component.trackByCuid(0, makeComputingUnit({ cuid: 77 }))).toBe(77);
+    });
+
+    it("trackByIndex returns the index", () => {
+      expect(component.trackByIndex(4)).toBe(4);
+    });
+  });
+
+  describe("cancelEditingUnitName", () => {
+    it("clears the editing state", () => {
+      component.editingNameOfUnit = 3;
+      component.editingUnitName = "half-typed";
+      component.cancelEditingUnitName();
+      expect(component.editingNameOfUnit).toBeNull();
+      expect(component.editingUnitName).toBe("");
+    });
+  });
+
+  describe("openComputingUnitMetadataModal", () => {
+    it("opens the metadata modal with the unit as nzData", () => {
+      const unit = makeComputingUnit({ cuid: 9 });
+      const createSpy = vi.spyOn(TestBed.inject(NzModalService), "create").mockReturnValue({} as any);
+
+      component.openComputingUnitMetadataModal(unit);
+
+      expect(createSpy).toHaveBeenCalledWith(
+        expect.objectContaining({
+          nzTitle: "Computing Unit Information",
+          nzContent: ComputingUnitMetadataComponent,
+          nzData: unit,
+          nzFooter: null,
+        })
+      );

Review Comment:
   Done — restored the `create` spy in a `finally` to match the fake-timer tests below. In practice `NzModalService` is re-created per `TestBed` module so the spy was already test-isolated, but the explicit restore is cleaner and consistent.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@texera.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org