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 1de1669b52 AMBARI-26659: Installation wizards persist not working and
change tab icon to ambari (#4221)
1de1669b52 is described below
commit 1de1669b5265ce32206119ba6cc1be8b1a8ccdf1
Author: Sandeep Kumar <[email protected]>
AuthorDate: Wed Sep 16 09:16:09 2026 +0530
AMBARI-26659: Installation wizards persist not working and change tab icon
to ambari (#4221)
---
ambari-web/latest/index.html | 4 ++-
ambari-web/latest/public/ambari-logo.png | Bin 0 -> 2779 bytes
ambari-web/latest/public/vite.svg | 1 -
.../latest/src/Utils/persistedSettings.test.ts | 37 ++++++++++++++++++++-
ambari-web/latest/src/Utils/persistedSettings.ts | 30 +++++++++++++++++
ambari-web/latest/src/api/clusterApi.test.ts | 21 ++++++++++++
ambari-web/latest/src/api/clusterApi.ts | 3 +-
7 files changed, 92 insertions(+), 4 deletions(-)
diff --git a/ambari-web/latest/index.html b/ambari-web/latest/index.html
index 292a977e49..c786d74ad7 100755
--- a/ambari-web/latest/index.html
+++ b/ambari-web/latest/index.html
@@ -20,7 +20,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
- <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+ <!-- Relative so the icon still resolves when the app is served from a
+ sub-path rather than the web root (vite base is "./"). -->
+ <link rel="icon" type="image/png" href="./ambari-logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ambari</title>
</head>
diff --git a/ambari-web/latest/public/ambari-logo.png
b/ambari-web/latest/public/ambari-logo.png
new file mode 100644
index 0000000000..07d31ee480
Binary files /dev/null and b/ambari-web/latest/public/ambari-logo.png differ
diff --git a/ambari-web/latest/public/vite.svg
b/ambari-web/latest/public/vite.svg
deleted file mode 100755
index e7b8dfb1b2..0000000000
--- a/ambari-web/latest/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
class="iconify iconify--logos" width="31.88" height="32"
preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient
id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%"
y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%"
stop-color="#BD34FE"></stop></linearGradient><linearGradient
id="IconifyId1813088fe1fbc01fb [...]
\ No newline at end of file
diff --git a/ambari-web/latest/src/Utils/persistedSettings.test.ts
b/ambari-web/latest/src/Utils/persistedSettings.test.ts
index b3924ecc7c..424a8cc37b 100644
--- a/ambari-web/latest/src/Utils/persistedSettings.test.ts
+++ b/ambari-web/latest/src/Utils/persistedSettings.test.ts
@@ -17,7 +17,12 @@
*/
import { describe, expect, it } from "vitest";
-import { parsePersistedValue, persistedPayload } from "./persistedSettings";
+import {
+ decodePersistedMap,
+ decodePersistedValue,
+ parsePersistedValue,
+ persistedPayload,
+} from "./persistedSettings";
describe("persisted settings", () => {
it("round trips booleans, strings, and objects as JSON values", () => {
@@ -39,4 +44,34 @@ describe("persisted settings", () => {
expect(parsePersistedValue("null", { userName: "" })).toEqual({ userName:
"" });
expect(parsePersistedValue("not-json", "Browser")).toBe("Browser");
});
+
+ it("decodes the JSON encoded values of an aggregate GET /persist response",
() => {
+ const decoded = decodePersistedMap({
+ CLUSTER_CURRENT: '{"clusterCreationSteps":{"NAME":{"step":"NAME"}}}',
+ CLUSTER_STATE:
'{"progressStatus":"PROVISIONING","stepName":"CONFIGURATION"}',
+ "wizard-data": '{"userName":"admin","controllerName":"clusterCreation"}',
+ USER_REDIRECTION_URL: "/main/admin/kerberos",
+ });
+
+ expect(decoded).toEqual({
+ CLUSTER_CURRENT: { clusterCreationSteps: { NAME: { step: "NAME" } } },
+ CLUSTER_STATE: { progressStatus: "PROVISIONING", stepName:
"CONFIGURATION" },
+ "wizard-data": { userName: "admin", controllerName: "clusterCreation" },
+ USER_REDIRECTION_URL: "/main/admin/kerberos",
+ });
+ });
+
+ it("leaves already decoded values and non-map responses alone", () => {
+ const state = { stepName: "CONFIGURATION" };
+ expect(decodePersistedValue(state)).toBe(state);
+ expect(decodePersistedValue(undefined)).toBeUndefined();
+ expect(decodePersistedMap(undefined)).toBeUndefined();
+ expect(decodePersistedMap("")).toBe("");
+ expect(decodePersistedMap({ CLUSTER_STATE: state })).toEqual({
CLUSTER_STATE: state });
+ });
+
+ it("keeps decoded values usable by parsePersistedValue", () => {
+ const decoded = decodePersistedValue(persistedPayload({ enabled: false
}).enabled);
+ expect(parsePersistedValue(decoded, true)).toBe(false);
+ });
});
diff --git a/ambari-web/latest/src/Utils/persistedSettings.ts
b/ambari-web/latest/src/Utils/persistedSettings.ts
index f66487225e..36e6223b97 100644
--- a/ambari-web/latest/src/Utils/persistedSettings.ts
+++ b/ambari-web/latest/src/Utils/persistedSettings.ts
@@ -31,6 +31,36 @@ export function parsePersistedValue<T>(value: unknown,
fallback: T): T {
}
}
+/**
+ * GET /persist serializes Map<String, String>, so every value comes back as
the
+ * raw string it was stored as - anything written with JSON.stringify stays
+ * encoded. (GET /persist/<key> returns that lone value as the whole body,
which
+ * axios parses for us, which is why the per-key reads never needed this.)
Decode
+ * a single value, leaving anything that was never JSON - USER_REDIRECTION_URL,
+ * for instance - as the plain string it is.
+ */
+export function decodePersistedValue(value: unknown): unknown {
+ if (typeof value !== "string") {
+ return value;
+ }
+ try {
+ return JSON.parse(value);
+ } catch {
+ return value;
+ }
+}
+
+/** Decode every value of the aggregate GET /persist response. */
+export function decodePersistedMap(data: unknown): unknown {
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
+ return data;
+ }
+ return Object.fromEntries(
+ Object.entries(data as Record<string, unknown>)
+ .map(([key, value]) => [key, decodePersistedValue(value)]),
+ );
+}
+
export function persistedPayload(values: Record<string, unknown>):
Record<string, string> {
return Object.fromEntries(
Object.entries(values).map(([key, value]) => [key, JSON.stringify(value)]),
diff --git a/ambari-web/latest/src/api/clusterApi.test.ts
b/ambari-web/latest/src/api/clusterApi.test.ts
index 37da1a3da1..6a9389c6f6 100644
--- a/ambari-web/latest/src/api/clusterApi.test.ts
+++ b/ambari-web/latest/src/api/clusterApi.test.ts
@@ -57,6 +57,27 @@ describe("cluster persisted data API", () => {
});
});
+ it("decodes the JSON encoded values the aggregate /persist response stores",
async () => {
+ mocks.suppressedRequest.mockResolvedValue({
+ data: {
+ CLUSTER_CURRENT: '{"clusterCreationSteps":{"NAME":{"step":"NAME"}}}',
+ CLUSTER_STATE:
'{"progressStatus":"PROVISIONING","stepName":"CONFIGURATION"}',
+ USER_REDIRECTION_URL: "/main/admin/kerberos",
+ },
+ });
+
+ await
expect(ClusterApi.getPersistData("CLUSTER_CURRENT")).resolves.toEqual({
+ clusterCreationSteps: { NAME: { step: "NAME" } },
+ });
+ await expect(ClusterApi.getPersistData("CLUSTER_STATE")).resolves.toEqual({
+ progressStatus: "PROVISIONING",
+ stepName: "CONFIGURATION",
+ });
+ await
expect(ClusterApi.getPersistData("USER_REDIRECTION_URL")).resolves.toBe(
+ "/main/admin/kerberos",
+ );
+ });
+
it("deduplicates concurrent aggregate requests and treats missing keys as
optional", async () => {
let resolveRequest: (value: unknown) => void = () => undefined;
mocks.suppressedRequest.mockReturnValue(new Promise((resolve) => {
diff --git a/ambari-web/latest/src/api/clusterApi.ts
b/ambari-web/latest/src/api/clusterApi.ts
index 9fde7c0059..23244ed763 100644
--- a/ambari-web/latest/src/api/clusterApi.ts
+++ b/ambari-web/latest/src/api/clusterApi.ts
@@ -19,6 +19,7 @@
import { apiPathSegment } from "./apiPath";
import { set } from "lodash";
import { ambariApi, supressErrorAmbariApi } from "./config/axiosConfig";
+import { decodePersistedMap } from "../Utils/persistedSettings";
let pendingPersistDataRequest: Promise<any> | null = null;
@@ -27,7 +28,7 @@ const loadPersistData = async () => {
pendingPersistDataRequest = supressErrorAmbariApi.request({
url: "/persist",
method: "GET",
- }).then((response) => response.data).finally(() => {
+ }).then((response) => decodePersistedMap(response.data)).finally(() => {
pendingPersistDataRequest = null;
});
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]