Skip to content

Commit ecab3d8

Browse files
committed
refactor(LeasesPage): simplify annotation handling and remove unused types
Signed-off-by: Magnus Ullberg <magnus@ullberg.us>
1 parent a4b1364 commit ecab3d8

2 files changed

Lines changed: 41 additions & 147 deletions

File tree

object-lease-console-plugin/src/components/LeasesPage.tsx

Lines changed: 24 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -2,82 +2,13 @@ import * as React from 'react';
22
import { PageSection, Title } from '@patternfly/react-core';
33
import { ResourceLink, Timestamp, useK8sWatchResource, useK8sWatchResources, ResourceIcon } from '@openshift-console/dynamic-plugin-sdk';
44

5-
// Annotation keys - use short keys for safe static access
6-
type AnnotationKey = 'ttl' | 'expireAt' | 'status';
7-
8-
const ANNOTATIONS = {
9-
ttl: 'object-lease-controller.ullberg.io/ttl',
10-
expireAt: 'object-lease-controller.ullberg.io/expire-at',
11-
status: 'object-lease-controller.ullberg.io/lease-status',
12-
} as const;
13-
14-
// Helper functions for safe annotation access - avoids dynamic property access security issues
15-
const getAnnotation = (annotations: Record<string, string> | undefined, key: AnnotationKey): string | undefined => {
16-
if (!annotations) return undefined;
17-
const annotationKey = ANNOTATIONS[key];
18-
return Object.prototype.hasOwnProperty.call(annotations, annotationKey) ? annotations[annotationKey] : undefined;
19-
};
20-
21-
const hasLeaseAnnotations = (annotations: Record<string, string> | undefined): boolean => {
22-
if (!annotations) return false;
23-
return (
24-
Object.prototype.hasOwnProperty.call(annotations, ANNOTATIONS.ttl) ||
25-
Object.prototype.hasOwnProperty.call(annotations, ANNOTATIONS.expireAt) ||
26-
Object.prototype.hasOwnProperty.call(annotations, ANNOTATIONS.status)
27-
);
28-
};
5+
const ANN_TTL = 'object-lease-controller.ullberg.io/ttl';
6+
const ANN_EXPIRE_AT = 'object-lease-controller.ullberg.io/expire-at';
7+
const ANN_STATUS = 'object-lease-controller.ullberg.io/lease-status';
298

309
type GVK = { group: string; version: string; kind: string };
3110
type WatchCfg = { groupVersionKind: GVK; namespaced: boolean; isList: true; namespace?: string };
3211

33-
// Kubernetes resource metadata type
34-
type K8sMetadata = {
35-
uid?: string;
36-
namespace?: string;
37-
name?: string;
38-
annotations?: Record<string, string>;
39-
};
40-
41-
// Generic Kubernetes resource type
42-
type K8sResource = {
43-
apiVersion?: string;
44-
kind?: string;
45-
metadata?: K8sMetadata;
46-
};
47-
48-
// Watch result type from useK8sWatchResources
49-
type WatchResult = {
50-
data?: K8sResource[];
51-
loaded: boolean;
52-
loadError?: Error;
53-
};
54-
55-
type KindSpec = {
56-
singular?: string;
57-
kind?: string;
58-
name?: string;
59-
plural?: string;
60-
};
61-
62-
type LeaseControllerSpec = {
63-
group?: string;
64-
version?: string;
65-
kind?: string | KindSpec;
66-
};
67-
68-
type LeaseController = K8sResource & {
69-
spec?: LeaseControllerSpec;
70-
};
71-
72-
// Item with lease annotations
73-
type LeaseItem = {
74-
obj: K8sResource & { metadata: K8sMetadata & { name: string } };
75-
gvk: GVK;
76-
};
77-
78-
// Monitored GVK type
79-
type Monitored = { gvk: GVK; plural?: string };
80-
8112
const LeasesPage = () => {
8213
// Try to use LeaseController CRs if present to determine which Kinds to scan
8314
const leaseControllerGVK: GVK = { group: 'object-lease-controller.ullberg.io', version: 'v1', kind: 'LeaseController' };
@@ -92,7 +23,7 @@ const LeasesPage = () => {
9223
const cfgs: Record<string, WatchCfg> = {};
9324
if (lcLoaded && !lcError && Array.isArray(leaseControllers) && leaseControllers.length > 0) {
9425
const set = new Set<string>();
95-
leaseControllers.forEach((lc: LeaseController) => {
26+
leaseControllers.forEach((lc: any) => {
9627
const g = lc?.spec?.group || '';
9728
const v = lc?.spec?.version as string | undefined;
9829
const rawKind = lc?.spec?.kind;
@@ -131,36 +62,36 @@ const LeasesPage = () => {
13162
return cfgs;
13263
}, [leaseControllers, lcLoaded, lcError]);
13364

134-
const resources = useK8sWatchResources<Record<string, WatchResult>>(watches);
65+
const resources = useK8sWatchResources(watches as any);
13566

136-
const loaded = React.useMemo(() => Object.values(resources).every((r: WatchResult) => r.loaded || r.loadError), [resources]);
137-
const loadError = React.useMemo(() => Object.values(resources).find((r: WatchResult) => r.loadError)?.loadError, [resources]);
67+
const loaded = React.useMemo(() => Object.values(resources).every((r: any) => r.loaded || r.loadError), [resources]);
68+
const loadError = React.useMemo(() => (Object.values(resources).find((r: any) => r.loadError) as any)?.loadError, [resources]);
13869

139-
const items: LeaseItem[] = React.useMemo(() => {
140-
const map = new Map<string, LeaseItem>();
141-
Object.entries(resources).forEach(([key, res]: [string, WatchResult]) => {
70+
const items: Array<{ obj: any; gvk: GVK }> = React.useMemo(() => {
71+
const map = new Map<string, { obj: any; gvk: GVK }>();
72+
Object.entries(resources).forEach(([key, res]: any) => {
14273
if (!res?.data) return;
14374
const [groupPart, version, kind] = key.split('~');
14475
const group = groupPart === 'core' ? '' : groupPart;
14576
const gvk: GVK = { group, version, kind };
146-
res.data.forEach((obj: K8sResource) => {
147-
const anns = obj?.metadata?.annotations;
148-
if (hasLeaseAnnotations(anns)) {
77+
(res.data as any[]).forEach((obj) => {
78+
const anns = obj?.metadata?.annotations || {};
79+
if (anns[ANN_TTL] || anns[ANN_EXPIRE_AT] || anns[ANN_STATUS]) {
14980
const uid = obj?.metadata?.uid;
150-
const name = obj?.metadata?.name;
151-
if (!name) return;
152-
const dedupKey = uid ?? `${obj?.metadata?.namespace ?? 'cluster'}-${name}`;
81+
const dedupKey = uid
82+
? `${gvk.group}|${gvk.version}|${gvk.kind}|${uid}`
83+
: `${gvk.group}|${gvk.version}|${gvk.kind}|${obj?.metadata?.namespace || ''}|${obj?.metadata?.name}`;
15384
if (!map.has(dedupKey)) {
154-
map.set(dedupKey, { obj: obj as LeaseItem['obj'], gvk });
85+
map.set(dedupKey, { obj, gvk });
15586
}
15687
}
15788
});
15889
});
15990
const out = Array.from(map.values());
16091
// Sort by namespace, kind, name
16192
out.sort((a, b) => {
162-
const nsA = a.obj.metadata.namespace ?? '';
163-
const nsB = b.obj.metadata.namespace ?? '';
93+
const nsA = a.obj.metadata.namespace || '';
94+
const nsB = b.obj.metadata.namespace || '';
16495
if (nsA !== nsB) return nsA.localeCompare(nsB);
16596
if (a.gvk.kind !== b.gvk.kind) return a.gvk.kind.localeCompare(b.gvk.kind);
16697
return a.obj.metadata.name.localeCompare(b.obj.metadata.name);
@@ -169,10 +100,11 @@ const LeasesPage = () => {
169100
}, [resources]);
170101

171102
// Derive the distinct set of monitored GVKs for display
103+
type Monitored = { gvk: GVK; plural?: string };
172104
const monitored: Monitored[] = React.useMemo(() => {
173105
if (!lcLoaded || lcError || !Array.isArray(leaseControllers)) return [];
174106
const map = new Map<string, Monitored>();
175-
leaseControllers.forEach((lc: LeaseController) => {
107+
leaseControllers.forEach((lc: any) => {
176108
const g = lc?.spec?.group || '';
177109
const v = lc?.spec?.version as string | undefined;
178110
const rawKind = lc?.spec?.kind;
@@ -248,9 +180,9 @@ const LeasesPage = () => {
248180
<td>
249181
<ResourceLink groupVersionKind={gvk} name={obj.metadata.name} namespace={obj.metadata.namespace} />
250182
</td>
251-
<td>{getAnnotation(obj?.metadata?.annotations, 'ttl') ?? '-'}</td>
252-
<td><Timestamp timestamp={getAnnotation(obj?.metadata?.annotations, 'expireAt')} /></td>
253-
<td>{getAnnotation(obj?.metadata?.annotations, 'status') ?? '-'}</td>
183+
<td>{obj?.metadata?.annotations?.[ANN_TTL] ?? '-'}</td>
184+
<td><Timestamp timestamp={obj?.metadata?.annotations?.[ANN_EXPIRE_AT]} /></td>
185+
<td>{obj?.metadata?.annotations?.[ANN_STATUS] ?? '-'}</td>
254186
</tr>
255187
);
256188
})
Lines changed: 17 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,33 @@
11
// Minimal shims so the editor doesn't complain when building outside the container.
2-
3-
// React types - defined first to avoid circular imports
4-
declare module 'react' {
5-
export type ReactNode = string | number | boolean | null | undefined | ReactElement | ReactNode[];
6-
export type ReactElement = { type: unknown; props: unknown; key: string | number | null };
7-
export type ComponentType<P = Record<string, unknown>> = (props: P) => ReactElement | null;
8-
export type FC<P = Record<string, unknown>> = ComponentType<P>;
9-
10-
export function useMemo<T>(factory: () => T, deps: readonly unknown[]): T;
11-
export function useState<T>(initialState: T | (() => T)): [T, (value: T | ((prev: T) => T)) => void];
12-
export function useEffect(effect: () => void | (() => void), deps?: readonly unknown[]): void;
13-
export function useCallback<T extends (...args: unknown[]) => unknown>(callback: T, deps: readonly unknown[]): T;
14-
15-
const React: {
16-
useMemo: typeof useMemo;
17-
useState: typeof useState;
18-
useEffect: typeof useEffect;
19-
useCallback: typeof useCallback;
20-
};
21-
export default React;
22-
}
23-
242
declare module '@openshift-console/dynamic-plugin-sdk' {
25-
import type { ComponentType } from 'react';
26-
27-
export const ResourceLink: ComponentType<{
28-
groupVersionKind: { group: string; version: string; kind: string };
29-
name: string;
30-
namespace?: string;
31-
}>;
32-
export const ResourceIcon: ComponentType<{
33-
groupVersionKind: { group: string; version: string; kind: string };
34-
}>;
35-
export const Timestamp: ComponentType<{ timestamp?: string }>;
36-
export const useK8sModels: () => [Record<string, unknown>, boolean];
37-
export function useK8sWatchResources<T>(watches: Record<string, unknown>): T;
38-
export function useK8sWatchResource<T>(config: {
39-
groupVersionKind: { group: string; version: string; kind: string };
40-
isList: boolean;
41-
namespaced: boolean;
42-
namespace?: string;
43-
}): [T, boolean, Error | undefined];
3+
export const ResourceLink: any;
4+
export const ResourceIcon: any;
5+
export const Timestamp: any;
6+
export const useK8sModels: any;
7+
export const useK8sWatchResources: any;
8+
export const useK8sWatchResource: any;
449
}
4510

4611
declare module '@patternfly/react-core' {
47-
import type { ComponentType, ReactNode } from 'react';
12+
export const PageSection: any;
13+
export const Title: any;
14+
}
4815

49-
export const PageSection: ComponentType<{
50-
variant?: string;
51-
className?: string;
52-
children?: ReactNode;
53-
}>;
54-
export const Title: ComponentType<{
55-
headingLevel: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
56-
children?: ReactNode;
57-
}>;
16+
declare module 'react' {
17+
const React: any;
18+
export default React;
19+
export = React;
5820
}
5921

6022
declare module 'react/jsx-runtime' {
61-
export const jsx: (type: unknown, props: Record<string, unknown>, key?: string) => unknown;
62-
export const jsxs: (type: unknown, props: Record<string, unknown>, key?: string) => unknown;
63-
export const Fragment: symbol;
23+
export const jsx: any;
24+
export const jsxs: any;
25+
export const Fragment: any;
6426
}
6527

6628
// Ensure JSX types exist
6729
declare namespace JSX {
6830
interface IntrinsicElements {
69-
[elemName: string]: Record<string, unknown>;
31+
[elemName: string]: any;
7032
}
7133
}

0 commit comments

Comments
 (0)