@stolostron/multicluster-sdk
Version:
Provides extensions and APIs that dynamic plugins can use to leverage multicluster capabilities provided by Red Hat Advanced Cluster Management.
234 lines • 11.1 kB
JavaScript
;
/* Copyright Contributors to the Open Cluster Management project */
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleWebsocketEvent = exports.stopWatch = exports.startWatch = exports.subscribe = exports.getRequestPathFromResource = void 0;
exports.useGetInitialResult = useGetInitialResult;
const fleetK8sWatchResourceStore_1 = require("./fleetK8sWatchResourceStore");
// Type imports
const dynamic_plugin_sdk_1 = require("@openshift-console/dynamic-plugin-sdk");
const requirements_1 = require("./requirements");
const apiRequests_1 = require("./apiRequests");
const constants_1 = require("./constants");
const api_1 = require("../api");
const react_1 = require("react");
const getRequestPathFromResource = (resource, model, basePath) => {
const { cluster, name, namespace, fieldSelector, selector } = resource;
return (0, apiRequests_1.buildResourceURL)({
model,
ns: namespace,
name,
cluster,
queryParams: {
...(fieldSelector ? { fieldSelector: fieldSelector } : {}),
labelSelector: (0, requirements_1.selectorToString)(selector || {}),
},
basePath,
});
};
exports.getRequestPathFromResource = getRequestPathFromResource;
const getDefaultData = (resource) => {
const { isList } = resource ?? {};
return isList ? [] : undefined;
};
const handleError = (err, requestPath, resource) => {
const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState();
store.setResult(requestPath, getDefaultData(resource), true, err);
};
const openFleetWatchSocket = (requestPath, resource, model, basePath) => {
const { cluster, name, namespace, selector, isList } = resource;
const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState();
const cachedResult = store.getResult(requestPath);
const resourceVersion = store.getResourceVersion(requestPath);
try {
const socket = (0, apiRequests_1.fleetWatch)(model, {
ns: namespace,
cluster,
fieldSelector: name ? `metadata.name=${name}` : undefined,
labelSelector: selector || undefined,
resourceVersion: isList ? resourceVersion : undefined,
allowWatchBookmarks: isList,
}, basePath);
store.setSocket(requestPath, socket);
socket.onmessage = (event) => {
try {
// Handle WebSocket event - this will update the store and notify all subscribers
(0, exports.handleWebsocketEvent)(event, requestPath, isList, cluster);
}
catch (e) {
console.error('Failed to parse WebSocket message', e);
}
};
socket.onclose = (event) => {
if (event.wasClean) {
// assume data is fresh up to this point
store.touchEntry(requestPath);
}
else {
console.error('WebSocket did not close cleanly:', event);
}
};
socket.onerror = (err) => {
console.error('WebSocket error:', err);
store.setResult(requestPath, cachedResult?.data, true, err);
};
}
catch (err) {
handleError(err, requestPath, resource);
}
};
const loadInitialData = async (requestPath, resource) => {
const { cluster, isList } = resource;
const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState();
try {
// load initial data into the zustand store
const data = await (0, dynamic_plugin_sdk_1.consoleFetchJSON)(requestPath, 'GET');
const processedData = isList
? data.items.map((i) => ({ cluster, ...i }))
: { cluster, ...data };
const resourceVersion = isList ? data?.metadata?.resourceVersion : undefined;
store.setResult(requestPath, processedData, true, undefined, resourceVersion);
}
catch (err) {
handleError(err, requestPath, resource);
return false;
}
return true;
};
const monitorFleetWatchSocket = async (requestPath, resource, model, basePath) => {
async function checkFleetWatchSocket(requestPath, resource, model, basePath) {
const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState();
const entry = store.cache[requestPath];
if (entry && entry.refCount > 0) {
if ((0, fleetK8sWatchResourceStore_1.isCacheEntryFresh)(entry)) {
// check again when data is due to expire
setTimeout(() => checkFleetWatchSocket(requestPath, resource, model, basePath), (0, fleetK8sWatchResourceStore_1.getSocketMonitoringInterval)() - (0, fleetK8sWatchResourceStore_1.getCacheEntryAge)(entry));
}
else {
// Socket may have disconnected; reconnect
entry.socket?.close();
if (await loadInitialData(requestPath, resource)) {
// only start watch if initial load succeeds
openFleetWatchSocket(requestPath, resource, model, basePath);
}
// check again after default interval
setTimeout(() => checkFleetWatchSocket(requestPath, resource, model, basePath), (0, fleetK8sWatchResourceStore_1.getSocketMonitoringInterval)());
}
}
}
setTimeout(() => checkFleetWatchSocket(requestPath, resource, model, basePath), (0, fleetK8sWatchResourceStore_1.getSocketMonitoringInterval)());
};
function useGetInitialResult() {
const isFleetAvailable = (0, api_1.useIsFleetAvailable)();
const [hubClusterName, hubClusterNameLoaded, hubClusterNameLoadedError] = (0, api_1.useHubClusterName)();
return (0, react_1.useCallback)((resource, model, basePath) => {
if (resource && model && basePath) {
const requestPath = (0, exports.getRequestPathFromResource)(resource, model, basePath);
const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState();
const entry = store.cache[requestPath];
if (entry && (0, fleetK8sWatchResourceStore_1.isCacheEntryValid)(entry)) {
return store.getResult(requestPath);
}
}
// Return default data and error, if any
const waitingForHubClusterName = !!resource?.cluster && !hubClusterNameLoaded;
const isProbablyFleetQuery = !!resource?.cluster && resource?.cluster !== hubClusterName;
let loadError = undefined;
if (waitingForHubClusterName) {
// if we are still waiting for hub name to load, we should return any error fetching the hub name
loadError = hubClusterNameLoadedError;
}
else if (isProbablyFleetQuery && !isFleetAvailable) {
// if we need to use fleet support but it it not available, we return an error
loadError = constants_1.NO_FLEET_AVAILABLE_ERROR;
}
return { data: getDefaultData(resource), loaded: false, loadError };
}, [isFleetAvailable, hubClusterName, hubClusterNameLoaded, hubClusterNameLoadedError]);
}
const subscribe = (resource, requestPath, setResult) => {
return fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.subscribe((state) => state.cache[requestPath]?.result, (result) => {
if (result) {
setResult(result);
}
else {
setResult({ data: getDefaultData(resource), loaded: false });
}
});
};
exports.subscribe = subscribe;
const startWatch = async (resource, model, basePath) => {
const requestPath = (0, exports.getRequestPathFromResource)(resource, model, basePath);
const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState();
store.incrementRefCount(requestPath);
// if we are the first subscriber, we are responsible for getting the initial data and watching for updates
if (store.getRefCount(requestPath) === 1) {
// if there is a cached value that is not expired, we can skip the initial fetch
const entry = store.cache[requestPath];
if ((entry && (0, fleetK8sWatchResourceStore_1.isCacheEntryValid)(entry)) || (await loadInitialData(requestPath, resource))) {
// only start watch if we have valid cached data or an initial load succeeds
openFleetWatchSocket(requestPath, resource, model, basePath);
}
monitorFleetWatchSocket(requestPath, resource, model, basePath);
}
};
exports.startWatch = startWatch;
const stopWatch = (resource, model, basePath) => {
const requestPath = (0, exports.getRequestPathFromResource)(resource, model, basePath);
const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState();
store.decrementRefCount(requestPath);
};
exports.stopWatch = stopWatch;
const handleWebsocketEvent = (event, requestPath, isList, cluster) => {
if (!event) {
console.warn('Received undefined event', event);
return;
}
const eventDataParsed = JSON.parse(event.data);
const eventType = eventDataParsed?.type;
const object = eventDataParsed?.object;
if (!object)
return;
const store = fleetK8sWatchResourceStore_1.useFleetK8sWatchResourceStore.getState();
if (!isList) {
const currentEntry = store.getResult(requestPath);
if (eventType === 'ADDED' && currentEntry?.data)
return;
const processedEventData = { cluster, ...object };
if (processedEventData) {
// Update the store with the new data - this will notify all subscribers
store.setResult(requestPath, processedEventData, true);
}
return;
}
const currentEntry = store.getResult(requestPath);
const storedData = currentEntry?.data;
if (!storedData) {
return;
}
if (eventType === 'DELETED') {
const updatedData = storedData.filter((i) => i.metadata?.uid !== object?.metadata?.uid);
store.setResult(requestPath, updatedData, true);
return;
}
if (eventType === 'BOOKMARK') {
store.setResult(requestPath, storedData, true, undefined, object?.metadata?.resourceVersion);
}
if (eventType !== 'ADDED' && eventType !== 'MODIFIED') {
return;
}
if (!object?.metadata?.uid) {
console.warn('Event object does not have a metadata.uid', eventDataParsed);
return;
}
const objectExists = storedData.some((i) => i.metadata?.uid === object?.metadata?.uid);
if (objectExists && eventType === 'MODIFIED') {
const updatedData = storedData.map((i) => (i.metadata?.uid === object?.metadata?.uid ? { cluster, ...object } : i));
store.setResult(requestPath, updatedData, true);
return;
}
if (!objectExists) {
const updatedData = [...storedData, { cluster, ...object }];
store.setResult(requestPath, updatedData, true);
}
};
exports.handleWebsocketEvent = handleWebsocketEvent;
//# sourceMappingURL=fleetK8sWatchResource.js.map