This is an automated email from the ASF dual-hosted git repository.

sandeepk318 pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 94c6389a96 AMBARI-26647: Fix Capacity Scheduler config editing format 
and add Refresh YARN Queues prompt (#4204)
94c6389a96 is described below

commit 94c6389a96b38bccef0b6a08269481a086b63ca1
Author: Himanshu Maurya <[email protected]>
AuthorDate: Fri Sep 4 20:13:25 2026 +0530

    AMBARI-26647: Fix Capacity Scheduler config editing format and add Refresh 
YARN Queues prompt (#4204)
---
 ambari-web/latest/src/hooks/useConfigSaver.tsx     | 103 +++++++++++++++-
 .../src/screens/CommonConfigs/AdvancedConfigs.tsx  | 135 ++++++++++++++++++++-
 2 files changed, 233 insertions(+), 5 deletions(-)

diff --git a/ambari-web/latest/src/hooks/useConfigSaver.tsx 
b/ambari-web/latest/src/hooks/useConfigSaver.tsx
index 574346b545..90ade6a94f 100644
--- a/ambari-web/latest/src/hooks/useConfigSaver.tsx
+++ b/ambari-web/latest/src/hooks/useConfigSaver.tsx
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { useState, useContext } from "react";
+import { useState, useContext, useRef } from "react";
 import { isEmpty, isArray, get } from "lodash";
 import {
   ConfigPropertiesType,
@@ -29,6 +29,8 @@ import { trimProperty } from 
"../screens/CommonConfigs/ConfigUtils";
 import { messages } from "../screens/messages";
 import ConfirmationModal from "../components/ConfirmationModal";
 import modalManager from "../store/ModalManager";
+import { HostsApi } from "../api/hostsApi";
+import { ActionsApi } from "../api/actionsApi";
 
 interface AttributesType {
   final: { [key: string]: string };
@@ -52,6 +54,9 @@ export const useConfigSaver = (
   onSaveComplete?: () => void
 ) => {
   const [saveInProgress, setSaveInProgress] = useState(false);
+  // Ember.js ComponentActionsByConfigs#showPopup: capacity-scheduler.xml
+  // changes should prompt a "Refresh YARN Queues" confirmation after save.
+  const yarnQueueRefreshNeededRef = useRef(false);
 
   const heapsizeException = [
     "hadoop_heapsize",
@@ -139,6 +144,12 @@ export const useConfigSaver = (
 
   const saveConfigsForDefaultGroup = async () => {
     let data: any = [];
+    yarnQueueRefreshNeededRef.current = Object.keys(configProperties).some(
+      (svcName) =>
+        getModifiedConfigs(configProperties, svcName).some(
+          (config) => config.fileName === "capacity-scheduler.xml"
+        )
+    );
     Object.keys(configProperties).map((serviceName: string) => {
       var serviceConfigs = getServiceConfigToSave(
         serviceName,
@@ -671,7 +682,7 @@ export const useConfigSaver = (
     _value: string | undefined,
     _status: string,
     _urlParams: string,
-    _doConfigActions: boolean | undefined
+    doConfigActions: boolean | undefined
   ) => {
     modalManager.show(
       <ConfirmationModal
@@ -679,7 +690,15 @@ export const useConfigSaver = (
         onClose={() => modalManager.hide()}
         modalTitle={header}
         modalBody={<div className={messageClass}>{message}</div>}
-        successCallback={() => modalManager.hide()}
+        successCallback={() => {
+          modalManager.hide();
+          // Ember.js ComponentActionsByConfigs#showPopup: prompt to refresh
+          // YARN queues after a successful capacity-scheduler.xml change.
+          if (isSuccess && doConfigActions && 
yarnQueueRefreshNeededRef.current) {
+            yarnQueueRefreshNeededRef.current = false;
+            showRefreshYarnQueuesPopup();
+          }
+        }}
         buttonVariant={isSuccess ? "success" : "danger"}
         cancellable={false}
         okButtonText="OK"
@@ -687,6 +706,84 @@ export const useConfigSaver = (
     );
   };
 
+  const showRefreshYarnQueuesPopup = () => {
+    modalManager.show(
+      <ConfirmationModal
+        isOpen={true}
+        onClose={() => modalManager.hide()}
+        modalTitle={get(messages, "popup.confirmation.commonHeader", 
"Confirmation")}
+        modalBody={
+          <div
+            dangerouslySetInnerHTML={{
+              __html: get(messages, 
"popup.confirmation.refreshYarnQueues.body", ""),
+            }}
+          />
+        }
+        successCallback={() => {
+          modalManager.hide();
+          refreshYarnQueues();
+        }}
+        cancellable={true}
+        okButtonText={get(
+          messages,
+          "popup.confirmation.refreshYarnQueues.buttonText",
+          "Refresh Yarn Queues"
+        )}
+      />
+    );
+  };
+
+  const refreshYarnQueues = async () => {
+    try {
+      const fields =
+        "fields=Hosts/host_name,host_components/HostRoles/component_name";
+      const hostDetailsResponse = await HostsApi.getHostComponentsDetails(
+        clusterName,
+        fields
+      );
+      type HostComponentDetails = {
+        Hosts: { host_name: string };
+        host_components?: { HostRoles: { component_name: string } }[];
+      };
+      const hostNames = (
+        (hostDetailsResponse?.items || []) as HostComponentDetails[]
+      ).flatMap((host) => {
+        const matches = (host.host_components || []).filter(
+          (component) =>
+            component.HostRoles.component_name === "RESOURCEMANAGER"
+        );
+        return matches.map(() => host.Hosts.host_name);
+      });
+
+      if (!hostNames.length) {
+        return;
+      }
+
+      const payloadData = {
+        RequestInfo: {
+          command: "REFRESHQUEUES",
+          context: get(
+            messages,
+            "services.service.actions.run.yarnRefreshQueues.context",
+            "Refresh YARN Capacity Scheduler"
+          ),
+          "parameters/forceRefreshConfigTags": "capacity-scheduler",
+        },
+        "Requests/resource_filters": [
+          {
+            service_name: "YARN",
+            component_name: "RESOURCEMANAGER",
+            hosts: hostNames.join(","),
+          },
+        ],
+      };
+
+      await ActionsApi.submitActionRequest(clusterName, payloadData);
+    } catch (error) {
+      console.error("Error refreshing YARN queues:", error);
+    }
+  };
+
   // const showSavePopup = () => {
   //   // To be implemented
   // };
diff --git a/ambari-web/latest/src/screens/CommonConfigs/AdvancedConfigs.tsx 
b/ambari-web/latest/src/screens/CommonConfigs/AdvancedConfigs.tsx
index da30e5c146..ae7d98033d 100644
--- a/ambari-web/latest/src/screens/CommonConfigs/AdvancedConfigs.tsx
+++ b/ambari-web/latest/src/screens/CommonConfigs/AdvancedConfigs.tsx
@@ -295,6 +295,77 @@ function AdvancedConfigs({
     );
   };
 
+  // capacity-scheduler.xml properties are edited as one combined "key=value"
+  // per-line textarea (matching the old Ember UI's App.CapacitySceduler 
widget)
+  // instead of one field per property. The underlying properties map is still
+  // individual key/value entries - only the presentation is combined here.
+  const CAPACITY_SCHEDULER_SECTION = "capacity-scheduler";
+  // Matches ui/app/models/stack_service.js:381 - the category that shows as 
"Scheduler".
+  const CAPACITY_SCHEDULER_CATEGORY = "CapacityScheduler";
+
+  const getCapacitySchedulerTextValue = (properties: {
+    [key: string]: PropertyType;
+  }) => {
+    return Object.keys(properties)
+      .sort()
+      .map((name) => `${name}=${properties[name]?.value ?? ""}`)
+      .join("\n");
+  };
+
+  const handleCapacitySchedulerChange = (section: string, rawValue: string) => 
{
+    let advancedDataCopy = cloneDeep(advancedConfigs);
+    const properties = advancedDataCopy[chosenService][section].properties;
+    const templateProperty = Object.values(properties)[0] as
+      | PropertyType
+      | undefined;
+
+    const seenNames = new Set<string>();
+    rawValue.split("\n").forEach((line) => {
+      if (!line.trim()) {
+        return;
+      }
+      const separatorIndex = line.indexOf("=");
+      const name =
+        separatorIndex === -1 ? line.trim() : line.slice(0, 
separatorIndex).trim();
+      if (!name) {
+        return;
+      }
+      const value = separatorIndex === -1 ? "" : line.slice(separatorIndex + 
1);
+      seenNames.add(name);
+
+      if (properties[name]) {
+        properties[name].value = value;
+      } else if (templateProperty) {
+        properties[name] = {
+          ...cloneDeep(templateProperty),
+          propertyName: name,
+          propertyDisplayname: name,
+          value,
+          previousValue: value,
+          overrideValues: [],
+        };
+      }
+    });
+
+    Object.keys(properties).forEach((name) => {
+      if (!seenNames.has(name)) {
+        delete properties[name];
+      }
+    });
+
+    advancedDataCopy = updateVisibilityByForeignKeys(advancedDataCopy);
+    advancedDataCopy = validateAllProperties(advancedDataCopy);
+
+    commitAdvancedConfigs(advancedDataCopy);
+
+    const changedProperty = Object.values(properties)[0] as
+      | PropertyType
+      | undefined;
+    if (changedProperty) {
+      onValueUpdate(changedProperty, advancedDataCopy);
+    }
+  };
+
    const handleChangeForOverridenValues = (
     section: string,
     property: string,
@@ -773,8 +844,27 @@ function AdvancedConfigs({
                 return null;
               }
 
+              // capacity-scheduler.xml properties live in their own catch-all
+              // "capacity-scheduler" bucket, but Ember shows them inside the
+              // "CapacityScheduler" category (displayName "Scheduler") next to
+              // yarn.resourcemanager.scheduler.class - see
+              // ui/app/models/stack_service.js:381. Fold that bucket's 
combined
+              // textarea into the CapacityScheduler section below instead of
+              // rendering it as its own accordion item.
+              if (config === CAPACITY_SCHEDULER_SECTION) {
+                return null;
+              }
+              const capacitySchedulerBucket =
+                config === CAPACITY_SCHEDULER_CATEGORY
+                  ? advancedConfigs[chosenService][CAPACITY_SCHEDULER_SECTION]
+                  : undefined;
+              const capacitySchedulerProperties =
+                capacitySchedulerBucket?.properties || {};
+              const hasCapacitySchedulerProperties =
+                Object.keys(capacitySchedulerProperties).length > 0;
+
               // Check if section has no visible properties
-              const hasNoVisibleProperties = 
isEmpty(currentConfigValue.properties) || 
+              const hasNoVisibleProperties = 
isEmpty(currentConfigValue.properties) ||
                 filteredPropertiesCount === 0;
 
               // For custom sections with search active, hide if no properties 
match
@@ -784,7 +874,13 @@ function AdvancedConfigs({
 
               // For NON-custom sections, skip if no visible properties
               // Custom sections should always be shown so users can add 
properties via "Add Property..."
-              if (hasNoVisibleProperties && !config.includes("Custom")) {
+              // Keep the CapacityScheduler section visible when it has 
nothing of its
+              // own but the folded-in capacity-scheduler.xml bucket does.
+              if (
+                hasNoVisibleProperties &&
+                !config.includes("Custom") &&
+                !hasCapacitySchedulerProperties
+              ) {
                 return null;
               }
 
@@ -794,6 +890,8 @@ function AdvancedConfigs({
                     <div className="d-flex align-items-center fs-18">
                       {advancedConfigs[chosenService][config].displayName
                         ? advancedConfigs[chosenService][config].displayName
+                        : config === CAPACITY_SCHEDULER_CATEGORY
+                        ? "Scheduler"
                         : config}{" "}
                       {errorCount > 0 && (
                         <span className="ms-2 badge rounded-pill bg-danger">
@@ -1243,6 +1341,39 @@ function AdvancedConfigs({
                         return null;
                       }
                     )}
+                    {config === CAPACITY_SCHEDULER_CATEGORY &&
+                      hasCapacitySchedulerProperties && (
+                        <Row className="mt-2 align-items-center">
+                          <Col md={4}>
+                            <Form.Label className="p-2">
+                              Capacity Scheduler
+                            </Form.Label>
+                          </Col>
+                          <Col>
+                            <Form.Control
+                              as="textarea"
+                              rows={16}
+                              value={getCapacitySchedulerTextValue(
+                                capacitySchedulerProperties
+                              )}
+                              onChange={(e) =>
+                                handleCapacitySchedulerChange(
+                                  CAPACITY_SCHEDULER_SECTION,
+                                  e.target.value
+                                )
+                              }
+                              disabled={
+                                !(
+                                  canEditProperties &&
+                                  
(Object.values(capacitySchedulerProperties)[0] as
+                                    | PropertyType
+                                    | undefined)?.isEditable !== false
+                                )
+                              }
+                            />
+                          </Col>
+                        </Row>
+                      )}
                     {!hostConfigs && canEditProperties && 
config.includes("Custom") ? (
                       <h4
                         className="text-info ms-2 mt-2"


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to