@stolostron/multicluster-sdk
Version:
Provides extensions and APIs that dynamic plugins can use to leverage multicluster capabilities provided by Red Hat Advanced Cluster Management.
190 lines • 9.21 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.useFleetSearchPoll = void 0;
/* Copyright Contributors to the Open Cluster Management project */
const search_sdk_1 = require("../internal/search/search-sdk");
const react_1 = require("react");
const search_client_1 = require("../internal/search/search-client");
// Constants for polling interval configuration
const DEFAULT_POLL_INTERVAL_SECONDS = 30;
/**
* A React hook that provides fleet-wide search functionality using the ACM search API.
*
* @template T - The type of Kubernetes resource(s) to search for, extending K8sResourceCommon
*
* @param watchOptions - Configuration options for the resource watch
* @param watchOptions.groupVersionKind - The group, version, and kind of the resource to search for
* @param watchOptions.limit - Maximum number of results to return (defaults to -1 for no limit)
* @param watchOptions.namespace - Namespace to search in (only used if namespaced is true)
* @param watchOptions.namespaced - Whether the resource is namespaced
* @param watchOptions.name - Specific resource name to search for (exact match)
* @param watchOptions.isList - Whether to return results as a list or single item
*
* @param advancedSearch - Optional array of additional search filters
* @param advancedSearch[].property - The property name to filter on
* @param advancedSearch[].values - Array of values to match for the property
*
* @param pollInterval - Optional polling interval in seconds. Defaults to 30 seconds (polling enabled).
* - Not specified: polls every 30 seconds
* - 0-30 inclusive: polls every 30 seconds (minimum interval)
* - >30: polls at the given interval in seconds
* - false or negative: disables polling
*
* @returns A tuple containing:
* - `data`: The search results formatted as Kubernetes resources, or undefined if no results
* - `loaded`: Boolean indicating if the search has completed (opposite of loading)
* - `error`: Any error that occurred during the search, or undefined if successful
* - `refetch`: A callback that enables you to re-execute the query
*
* @example
* ```typescript
* // Search for all Pods in a specific namespace with default 30-second polling
* const [pods, loaded, error] = useFleetSearchPoll({
* groupVersionKind: { group: '', version: 'v1', kind: 'Pod' },
* namespace: 'default',
* namespaced: true,
* isList: true
* });
*
* // Search for a specific Deployment with polling every 60 seconds
* const [deployment, loaded, error] = useFleetSearchPoll({
* groupVersionKind: { group: 'apps', version: 'v1', kind: 'Deployment' },
* name: 'my-deployment',
* namespace: 'default',
* namespaced: true,
* isList: false
* }, [
* { property: 'label', values: ['app=my-app'] }
* ], 60);
*
* // Search without polling (one-time query)
* const [services, loaded, error] = useFleetSearchPoll({
* groupVersionKind: { group: '', version: 'v1', kind: 'Service' },
* namespaced: true,
* isList: true
* }, undefined, false);
* ```
*
* @remarks
* - The hook automatically handles the transformation of flattened search results back into
* properly structured Kubernetes resources
* - Special handling is provided for VirtualMachine and VirtualMachineInstance resources
* - Watch options filters take precedence over advanced search filters
* - The search is skipped if no `kind` is specified in the groupVersionKind
* - Results include cluster information for multi-cluster environments
* - Polling is enabled by default with a 30-second interval; use false to disable
* - Minimum polling interval is 30 seconds for performance reasons
*/
const useFleetSearchPoll = (watchOptions, advancedSearch, pollInterval) => {
var _a;
const { groupVersionKind, limit, namespace, namespaced, name, isList } = watchOptions;
const { group, version, kind } = groupVersionKind !== null && groupVersionKind !== void 0 ? groupVersionKind : {};
// Calculate the actual polling interval in milliseconds
const actualPollInterval = (0, react_1.useMemo)(() => {
// Disable polling for false or negative values
if (pollInterval === false || (typeof pollInterval === 'number' && pollInterval < 0)) {
return undefined;
}
// Default to 30 seconds if not specified, or use minimum of 30 seconds for specified values
const intervalInSeconds = pollInterval === undefined || pollInterval <= DEFAULT_POLL_INTERVAL_SECONDS
? DEFAULT_POLL_INTERVAL_SECONDS
: pollInterval;
return intervalInSeconds * 1000;
}, [pollInterval]);
const searchInput = (0, react_1.useMemo)(() => {
const filters = [];
// Add filters from watchOptions (these take precedence)
const watchOptionsProperties = new Set();
if (group) {
filters.push({ property: 'apigroup', values: [group] });
watchOptionsProperties.add('apigroup');
}
if (version) {
filters.push({ property: 'apiversion', values: [version] });
watchOptionsProperties.add('apiversion');
}
if (kind) {
filters.push({ property: 'kind', values: [kind] });
watchOptionsProperties.add('kind');
}
if (namespaced && namespace) {
filters.push({ property: 'namespace', values: [namespace] });
watchOptionsProperties.add('namespace');
}
if (name && name.trim()) {
// Use exact match instead of wildcard
filters.push({ property: 'name', values: [name] });
watchOptionsProperties.add('name');
}
// Add filters from advancedSearch, excluding properties already specified in watchOptions
if (advancedSearch) {
for (const { property, values } of advancedSearch) {
if (property && values !== undefined && !watchOptionsProperties.has(property)) {
filters.push({ property, values });
}
}
}
return {
filters,
limit: limit !== null && limit !== void 0 ? limit : -1,
};
}, [group, version, kind, namespaced, namespace, name, advancedSearch, limit]);
const { data: result, loading, error, refetch, } = (0, search_sdk_1.useSearchResultItemsQuery)({
client: search_client_1.searchClient,
skip: kind === undefined,
pollInterval: actualPollInterval,
variables: {
input: [searchInput],
},
});
const triggerRefetch = (0, react_1.useCallback)(() => {
refetch();
}, [refetch]);
const data = (0, react_1.useMemo)(() => {
var _a, _b, _c;
return (_c = (_b = (_a = result === null || result === void 0 ? void 0 : result.searchResult) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.items) === null || _c === void 0 ? void 0 : _c.map((item) => {
let label = {};
if (item === null || item === void 0 ? void 0 : item.label) {
label = Object.fromEntries(item.label.split(';').map((pair) => pair.trimStart().split('=')));
}
const resource = {
cluster: item.cluster,
apiVersion: `${item.apigroup ? `${item.apigroup}/` : ''}${item.apiversion}`,
kind: item.kind,
metadata: {
creationTimestamp: item.created,
name: item.name,
namespace: item.namespace,
labels: label,
},
};
// Reverse the flattening of specific resources by the search-collector
// See https://github.com/stolostron/search-collector/blob/main/pkg/transforms/genericResourceConfig.go
switch (kind) {
case 'VirtualMachine':
resource.spec = {
running: item._specRunning,
runStrategy: item._specRunStrategy,
template: { spec: { domain: { cpu: { cores: item.cpu }, memory: { guest: item.memory } } } },
};
resource.status = { conditions: [{ type: 'Ready', status: item.ready }], printableStatus: item.status };
break;
case 'VirtualMachineInstance':
resource.status = {
conditions: [
{ type: 'LiveMigratable', status: item.liveMigratable },
{ type: 'Ready', status: item.ready },
],
interfaces: [{ ipAddress: item.ipaddress, name: 'default' }],
nodeName: item.node,
phase: item.phase,
};
}
return resource;
});
}, [kind, result]);
const nullResponse = (0, react_1.useMemo)(() => (isList ? [] : undefined), [isList]);
return [(_a = data) !== null && _a !== void 0 ? _a : nullResponse, !loading, error, triggerRefetch];
};
exports.useFleetSearchPoll = useFleetSearchPoll;
//# sourceMappingURL=useFleetSearchPoll.js.map