This is an automated email from the ASF dual-hosted git repository.
JiaLiangC 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 474f6711c1 AMBARI-26648: Service Configs Bug: all properties render
read-only, and JVM/command-line values incorrectly split into multi-line
display (#4205)
474f6711c1 is described below
commit 474f6711c18663c29c0fc674053d21e7adcc2295
Author: Sandeep Kumar <[email protected]>
AuthorDate: Mon Sep 7 12:23:16 2026 +0530
AMBARI-26648: Service Configs Bug: all properties render read-only, and
JVM/command-line values incorrectly split into multi-line display (#4205)
---
ambari-web/latest/src/Utils/configHistory.ts | 6 +-
ambari-web/latest/src/Utils/jvmFormatUtils.test.ts | 39 +++++---
ambari-web/latest/src/Utils/jvmFormatUtils.ts | 104 ++++++++++-----------
.../latest/src/screens/ServiceConfigs/index.tsx | 3 +-
4 files changed, 81 insertions(+), 71 deletions(-)
diff --git a/ambari-web/latest/src/Utils/configHistory.ts
b/ambari-web/latest/src/Utils/configHistory.ts
index 69ba38ba3f..1616402cb4 100644
--- a/ambari-web/latest/src/Utils/configHistory.ts
+++ b/ambari-web/latest/src/Utils/configHistory.ts
@@ -113,7 +113,11 @@ export function resolveConfigHistorySelection(
currentDefaultVersion: string,
navigationState?: ConfigHistoryNavigationState | null,
) {
- const selectedVersion = navigationState?.serviceConfigVersion ||
currentDefaultVersion;
+ // Normalize to string: navigationState may carry a raw numeric version
+ // (e.g. from router state constructed elsewhere), and comparing that
+ // against the string-typed currentDefaultVersion with === would otherwise
+ // never match, making every property look read-only.
+ const selectedVersion = String(navigationState?.serviceConfigVersion ||
currentDefaultVersion);
const configGroup = navigationState?.configGroup || "Default";
const versionsToLoad = selectedVersion === currentDefaultVersion &&
configGroup === "Default"
? null
diff --git a/ambari-web/latest/src/Utils/jvmFormatUtils.test.ts
b/ambari-web/latest/src/Utils/jvmFormatUtils.test.ts
index 27e1230ec1..6cf4c0e330 100644
--- a/ambari-web/latest/src/Utils/jvmFormatUtils.test.ts
+++ b/ambari-web/latest/src/Utils/jvmFormatUtils.test.ts
@@ -24,24 +24,33 @@ import {
} from "./jvmFormatUtils";
describe("JVM parameter formatting", () => {
- it("formats JVM options without splitting quoted whitespace", () => {
- const value = '-Xmx1g -Dname="value with spaces" -XX:+UseG1GC';
- const displayed = [
- "-Xmx1g",
- '-Dname="value with spaces"',
- "-XX:+UseG1GC",
- ].join("\n");
+ it("does not treat single-line JVM/command-line argument strings as
multiline", () => {
+ // e.g. mapreduce.admin.map.child.java.opts — must stay single-line, or the
+ // injected display newlines get persisted and corrupt the config.
+ const value =
+ "-server -XX:NewRatio=8 -Djava.net.preferIPv4Stack=true
-Dhdp.version=${hdp.version}";
- expect(shouldUseMultilineFormatting(value, "string")).toBe(true);
- expect(formatParamsForDisplay(value, "string")).toBe(displayed);
- expect(formatParamsForSave(displayed)).toBe(value);
+ expect(shouldUseMultilineFormatting(value)).toBe(false);
+ expect(formatParamsForDisplay(value)).toBe(value);
+ expect(formatParamsForSave(value)).toBe(value);
});
- it("does not rewrite ordinary or explicitly multiline content", () => {
+ it("honors an explicit multiLine displayType regardless of content", () => {
+ expect(shouldUseMultilineFormatting("-Xmx1g -Xms1g",
"multiLine")).toBe(true);
+ });
+
+ it("does not treat content as multiline when displayType says otherwise", ()
=> {
expect(shouldUseMultilineFormatting("first\nsecond",
"string")).toBe(false);
- expect(shouldUseMultilineFormatting("-Xmx1g -Xms1g", "multiLine")).toBe(
- false,
- );
- expect(shouldUseMultilineFormatting("plain words", "string")).toBe(false);
+ });
+
+ it("falls back to content-based detection when no displayType is given", ()
=> {
+ expect(shouldUseMultilineFormatting("first\nsecond")).toBe(true);
+ expect(shouldUseMultilineFormatting("first\\nsecond")).toBe(true);
+ expect(shouldUseMultilineFormatting("plain words")).toBe(false);
+ });
+
+ it("converts escaped newlines for display and leaves real newlines for
save", () => {
+ expect(formatParamsForDisplay("first\\nsecond")).toBe("first\nsecond");
+ expect(formatParamsForSave("first\nsecond")).toBe("first\nsecond");
});
});
diff --git a/ambari-web/latest/src/Utils/jvmFormatUtils.ts
b/ambari-web/latest/src/Utils/jvmFormatUtils.ts
index 2cfdb7d75c..dd1b999bb0 100644
--- a/ambari-web/latest/src/Utils/jvmFormatUtils.ts
+++ b/ambari-web/latest/src/Utils/jvmFormatUtils.ts
@@ -16,66 +16,62 @@
* limitations under the License.
*/
-const splitQuotedParameters = (value: string): string[] => {
- const parameters: string[] = [];
- let current = "";
- let quote = "";
- let escaped = false;
-
- for (const character of value) {
- if (escaped) {
- current += character;
- escaped = false;
- continue;
- }
- if (character === "\\") {
- current += character;
- escaped = true;
- continue;
- }
- if (quote) {
- current += character;
- if (character === quote) quote = "";
- continue;
- }
- if (character === '"' || character === "'") {
- current += character;
- quote = character;
- continue;
- }
- if (/\s/.test(character)) {
- if (current) parameters.push(current);
- current = "";
- continue;
- }
- current += character;
- }
- if (current) parameters.push(current);
- return parameters;
+// Checks if a string contains only a single line. Also treats an escaped
+// newline sequence from the backend as a newline.
+const isSingleLine = (value: string): boolean => {
+ const stringValue = String(value).trim();
+ return stringValue.indexOf("\n") === -1 && stringValue.indexOf("\\n") === -1;
};
-const startsLikeJvmOption = (value: string) =>
- /^(?:-X|-D|-XX:|-server$|-client$|--add-(?:opens|exports)=)/.test(value);
-
-export const formatParamsForDisplay = (
- value: string,
- _displayType?: string,
-): string => splitQuotedParameters(String(value ?? "")).join("\n");
-
-export const formatParamsForSave = (value: string): string =>
- splitQuotedParameters(String(value ?? "")).join(" ");
-
+// Ember's logic: an explicit displayType from the backend takes priority;
+// otherwise fall back to content-based detection. Values that merely look
+// like command-line/JVM arguments (e.g. mapreduce.admin.map.child.java.opts:
+// "-server -XX:NewRatio=8 -Djava.net.preferIPv4Stack=true
-Dhdp.version=${hdp.version}")
+// must NOT be treated as multiline just because they contain multiple
"-X"-style
+// tokens — only genuine (real or escaped) newlines should.
export const shouldUseMultilineFormatting = (
value: string,
displayType?: string,
): boolean => {
- if (
- ["content", "directories", "directory", "multiLine"].includes(
- String(displayType),
- )
- ) {
+ if (!value || typeof value !== "string") {
return false;
}
- const parameters = splitQuotedParameters(String(value ?? ""));
- return parameters.length > 1 && parameters.every(startsLikeJvmOption);
+ if (displayType) {
+ return displayType === "multiLine";
+ }
+ return !isSingleLine(value);
+};
+
+export const formatParamsForDisplay = (
+ value: string,
+ displayType?: string,
+): string => {
+ if (!value || typeof value !== "string") {
+ return value;
+ }
+ if (!shouldUseMultilineFormatting(value, displayType)) {
+ return value;
+ }
+ // Already has real newlines; let the textarea render it naturally.
+ if (value.includes("\n")) {
+ return value;
+ }
+ // Convert escaped newline sequences to actual newlines for display.
+ if (value.includes("\\n")) {
+ return value.replace(/\\n/g, "\n");
+ }
+ return value;
+};
+
+// Leaves real newlines as real newlines — JSON.stringify() will escape them
+// when the save payload is serialized. Escaping here too would double-escape
+// (\n -> \\n -> \\\\n).
+export const formatParamsForSave = (value: string): string => {
+ if (!value || typeof value !== "string") {
+ return value;
+ }
+ if (value.includes("\\n")) {
+ return value;
+ }
+ return value;
};
diff --git a/ambari-web/latest/src/screens/ServiceConfigs/index.tsx
b/ambari-web/latest/src/screens/ServiceConfigs/index.tsx
index e94e556b7b..c71236680a 100644
--- a/ambari-web/latest/src/screens/ServiceConfigs/index.tsx
+++ b/ambari-web/latest/src/screens/ServiceConfigs/index.tsx
@@ -231,7 +231,8 @@ export default function ServiceConfigs({
async function onVersionChange(versionNumber: any) {
const requestId = ++versionRequestId.current;
try {
- setSelectedVersion(versionNumber);
+ // Normalize to string so it compares correctly against
defaultVersionNumber.
+ setSelectedVersion(String(versionNumber));
let apiVersionNumber = versionNumber;
if (configGroup !== "Default") {
apiVersionNumber = defaultVersionNumber + "," + versionNumber;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]