- Implement `endpoint_mode` (manual/auto) and related configuration in i18n across multiple locales. - Add new utility methods for channel rollback and runtime confirmation workflows. - Update unit and E2E tests to cover runtime refresh behavior, channel switching mechanics, and release references. - Enhance `SessionUser` and related services to improve error handling during release runtime refresh. - Add data-test attributes for frontend components to support improved test coverage.
253 lines
9.1 KiB
JavaScript
253 lines
9.1 KiB
JavaScript
import axios from "axios";
|
|
import { API_URL, RELEASE_MANAGER_CONTROL_API_FALLBACK_URLS, RELEASE_MANAGER_CONTROL_API_URL } from "@/config.js";
|
|
import { enqueueRequest } from "@/services/requestQueue.js";
|
|
|
|
export const RELEASE_MANAGER_CONTROL_API_STORAGE_KEY = "release_manager_control_api_url";
|
|
const RELEASE_MANAGER_LAST_WORKING_CONTROL_API_STORAGE_KEY = "release_manager_last_working_control_api_url";
|
|
|
|
const normalizeApiUrl = (value) => {
|
|
const url = String(value || "")
|
|
.trim()
|
|
.replace(/\/+$/, "");
|
|
if (!/^https?:\/\//i.test(url)) {
|
|
return "";
|
|
}
|
|
return url;
|
|
};
|
|
|
|
const browserStorage = (name) => {
|
|
if (typeof window === "undefined") {
|
|
return null;
|
|
}
|
|
try {
|
|
return window[name] || null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const getSelectedCustomerNumber = () => {
|
|
const stored = browserStorage("localStorage")?.getItem("selected_customer_number");
|
|
return stored ? parseInt(stored, 10) : null;
|
|
};
|
|
|
|
const uniqueUrls = (urls) => {
|
|
const seen = new Set();
|
|
return urls
|
|
.map((url) => normalizeApiUrl(url))
|
|
.filter((url) => {
|
|
if (!url || seen.has(url)) {
|
|
return false;
|
|
}
|
|
seen.add(url);
|
|
return true;
|
|
});
|
|
};
|
|
|
|
const releaseManagerLocalStorage = () => browserStorage("localStorage");
|
|
const releaseManagerSessionStorage = () => browserStorage("sessionStorage");
|
|
|
|
export const getReleaseManagerControlApiUrl = () =>
|
|
uniqueUrls([
|
|
releaseManagerSessionStorage()?.getItem(RELEASE_MANAGER_LAST_WORKING_CONTROL_API_STORAGE_KEY),
|
|
releaseManagerLocalStorage()?.getItem(RELEASE_MANAGER_CONTROL_API_STORAGE_KEY),
|
|
RELEASE_MANAGER_CONTROL_API_URL,
|
|
API_URL,
|
|
])[0] || API_URL;
|
|
|
|
export const releaseManagerControlApiCandidates = () =>
|
|
uniqueUrls([
|
|
releaseManagerSessionStorage()?.getItem(RELEASE_MANAGER_LAST_WORKING_CONTROL_API_STORAGE_KEY),
|
|
releaseManagerLocalStorage()?.getItem(RELEASE_MANAGER_CONTROL_API_STORAGE_KEY),
|
|
RELEASE_MANAGER_CONTROL_API_URL,
|
|
...RELEASE_MANAGER_CONTROL_API_FALLBACK_URLS,
|
|
API_URL,
|
|
]);
|
|
|
|
export const setReleaseManagerControlApiUrl = (url) => {
|
|
const normalized = normalizeApiUrl(url);
|
|
if (!normalized) {
|
|
return "";
|
|
}
|
|
releaseManagerLocalStorage()?.setItem(RELEASE_MANAGER_CONTROL_API_STORAGE_KEY, normalized);
|
|
releaseManagerSessionStorage()?.setItem(RELEASE_MANAGER_LAST_WORKING_CONTROL_API_STORAGE_KEY, normalized);
|
|
return normalized;
|
|
};
|
|
|
|
export const clearReleaseManagerControlApiUrl = () => {
|
|
releaseManagerLocalStorage()?.removeItem(RELEASE_MANAGER_CONTROL_API_STORAGE_KEY);
|
|
releaseManagerSessionStorage()?.removeItem(RELEASE_MANAGER_LAST_WORKING_CONTROL_API_STORAGE_KEY);
|
|
};
|
|
|
|
const rememberWorkingControlApiUrl = (url) => {
|
|
const normalized = normalizeApiUrl(url);
|
|
if (normalized) {
|
|
releaseManagerSessionStorage()?.setItem(RELEASE_MANAGER_LAST_WORKING_CONTROL_API_STORAGE_KEY, normalized);
|
|
}
|
|
};
|
|
|
|
const shouldTryNextControlApi = (error, method) => {
|
|
const status = Number(error?.response?.status || 0);
|
|
return (
|
|
status === 0 ||
|
|
(method === "GET" && status === 404) ||
|
|
status === 405 ||
|
|
status === 501 ||
|
|
error?.code === "ERR_NETWORK"
|
|
);
|
|
};
|
|
|
|
const buildHeaders = () => {
|
|
const headers = {};
|
|
const token = releaseManagerLocalStorage()?.getItem("token");
|
|
if (token) {
|
|
headers.Authorization = `Bearer ${token}`;
|
|
}
|
|
|
|
const isSubuser = releaseManagerLocalStorage()?.getItem("is_subuser") === "true";
|
|
const selectedCustomerNumber = getSelectedCustomerNumber();
|
|
if (isSubuser && selectedCustomerNumber) {
|
|
headers["X-Customer-Number"] = selectedCustomerNumber;
|
|
}
|
|
|
|
return headers;
|
|
};
|
|
|
|
export const requestReleaseManager = (url, method, data = {}) => {
|
|
const candidates = releaseManagerControlApiCandidates();
|
|
const headers = buildHeaders();
|
|
|
|
return enqueueRequest(
|
|
async () => {
|
|
let lastError = null;
|
|
for (const baseUrl of candidates) {
|
|
try {
|
|
const response = await axios({
|
|
method,
|
|
url: `${baseUrl}${url}`,
|
|
...(method === "GET" ? { params: data } : { data }),
|
|
__skipRequestQueue: true,
|
|
__skipReleaseApiRewrite: true,
|
|
headers,
|
|
});
|
|
rememberWorkingControlApiUrl(baseUrl);
|
|
return response;
|
|
} catch (error) {
|
|
lastError = error;
|
|
if (!shouldTryNextControlApi(error, method)) {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
throw lastError;
|
|
},
|
|
{
|
|
method,
|
|
url: `${candidates[0] || API_URL}${url}`,
|
|
requestData: {
|
|
params: method === "GET" ? data : null,
|
|
data: method === "GET" ? null : data,
|
|
headers,
|
|
},
|
|
shouldRetry: () => false,
|
|
}
|
|
);
|
|
};
|
|
|
|
export const getReleaseSummary = () => requestReleaseManager("/superuser/releases", "GET", {});
|
|
|
|
export const getReleaseConfig = () => requestReleaseManager("/superuser/releases/config", "GET", {});
|
|
|
|
export const updateReleaseConfig = (payload) => requestReleaseManager("/superuser/releases/config", "POST", payload);
|
|
|
|
export const listReleaseChannels = () => requestReleaseManager("/superuser/releases/channels", "GET", {});
|
|
|
|
export const createReleaseChannel = (payload) => requestReleaseManager("/superuser/releases/channels", "POST", payload);
|
|
|
|
export const updateReleaseChannel = (id, payload) =>
|
|
requestReleaseManager(`/superuser/releases/channels/${id}`, "PATCH", payload);
|
|
|
|
export const rollbackReleaseChannel = (id) =>
|
|
requestReleaseManager(`/superuser/releases/channels/${id}/rollback`, "POST", {});
|
|
|
|
export const setReleaseChannelBundle = (id, payload) =>
|
|
requestReleaseManager(`/superuser/releases/channels/${id}/bundle`, "POST", payload);
|
|
|
|
export const listReleaseAssignments = () => requestReleaseManager("/superuser/releases/assignments", "GET", {});
|
|
|
|
export const searchReleaseAssignmentSubjects = (params = {}) =>
|
|
requestReleaseManager("/superuser/releases/assignment-subjects", "GET", params);
|
|
|
|
export const createReleaseAssignment = (payload) =>
|
|
requestReleaseManager("/superuser/releases/assignments", "POST", payload);
|
|
|
|
export const deleteReleaseAssignment = (id) =>
|
|
requestReleaseManager(`/superuser/releases/assignments/${id}`, "DELETE", {});
|
|
|
|
export const listReleaseDeploymentTargets = () => requestReleaseManager("/superuser/releases/targets", "GET", {});
|
|
|
|
export const saveReleaseDeploymentTarget = (payload) =>
|
|
requestReleaseManager("/superuser/releases/targets", "POST", payload);
|
|
|
|
export const deleteReleaseDeploymentTarget = (id) =>
|
|
requestReleaseManager(`/superuser/releases/targets/${id}`, "DELETE", {});
|
|
|
|
export const listReleaseServiceSets = () => requestReleaseManager("/superuser/releases/service-sets", "GET", {});
|
|
|
|
export const createReleaseServiceSet = (payload) =>
|
|
requestReleaseManager("/superuser/releases/service-sets", "POST", payload);
|
|
|
|
export const deleteReleaseServiceSet = (id) =>
|
|
requestReleaseManager(`/superuser/releases/service-sets/${id}`, "DELETE", {});
|
|
|
|
export const completeReleaseServiceSetIsolatedDataServices = (id, payload = {}) =>
|
|
requestReleaseManager(`/superuser/releases/service-sets/${id}/isolated-data-services`, "POST", payload);
|
|
|
|
export const listReleaseBundles = (limit = 50) =>
|
|
requestReleaseManager("/superuser/releases/bundles", "GET", { limit });
|
|
|
|
export const createReleaseBundle = (payload) =>
|
|
requestReleaseManager("/superuser/releases/bundles", "POST", payload);
|
|
|
|
export const deployReleaseBundle = (id) =>
|
|
requestReleaseManager(`/superuser/releases/bundles/${id}/deploy`, "POST", {});
|
|
|
|
export const promoteReleaseBundle = (id) =>
|
|
requestReleaseManager(`/superuser/releases/bundles/${id}/promote`, "POST", {});
|
|
|
|
export const listReleaseGithubRepositories = (params = {}) =>
|
|
requestReleaseManager("/superuser/releases/github/repositories", "GET", params);
|
|
|
|
export const listReleaseGithubBranches = (repository) =>
|
|
requestReleaseManager("/superuser/releases/github/branches", "GET", { repository });
|
|
|
|
export const listReleaseGithubCommits = ({ repository, branch, query = "", commit_sha = "" } = {}) =>
|
|
requestReleaseManager("/superuser/releases/github/commits", "GET", { repository, branch, query, commit_sha });
|
|
|
|
export const testReleaseGithubAccess = (payload) =>
|
|
requestReleaseManager("/superuser/releases/github/test", "POST", payload);
|
|
|
|
export const listReleaseDeployments = (limit = 50) =>
|
|
requestReleaseManager("/superuser/releases/deployments", "GET", { limit });
|
|
|
|
export const startReleaseDeployment = (payload) =>
|
|
requestReleaseManager("/superuser/releases/deployments", "POST", payload);
|
|
|
|
export const promoteReleaseDeployment = (id) =>
|
|
requestReleaseManager(`/superuser/releases/deployments/${id}/promote`, "POST", {});
|
|
|
|
export const runReleaseIssueAction = (payload) =>
|
|
requestReleaseManager("/superuser/releases/issues/actions", "POST", payload);
|
|
|
|
export const setReleaseReplayTarget = (payload) =>
|
|
requestReleaseManager("/superuser/releases/replay-targets", "POST", payload);
|
|
|
|
export const searchReleaseTimeline = (filters = {}) =>
|
|
requestReleaseManager("/superuser/releases/timeline", "GET", filters);
|
|
|
|
export const listReleaseTimelineSessions = (filters = {}) =>
|
|
requestReleaseManager("/superuser/releases/timeline/sessions", "GET", filters);
|
|
|
|
export const getReleaseTimelineSession = (traceId) =>
|
|
requestReleaseManager(`/superuser/releases/timeline/sessions/${encodeURIComponent(traceId)}`, "GET", {});
|