Skip to content

Commit 99327dd

Browse files
Merge pull request #4 from harche/cluster-update-ui
OTA-1973: Add cluster update console plugin with Lightspeed proposal integration
2 parents e491897 + a5eaf01 commit 99327dd

53 files changed

Lines changed: 5875 additions & 420 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ install-state.gz
1313
!.yarn/releases
1414
!.yarn/sdks
1515
!.yarn/versions
16+
.playwright-cli/

AGENTS.md

Lines changed: 127 additions & 128 deletions
Large diffs are not rendered by default.

console-extensions.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
"type": "console.page/route",
44
"properties": {
55
"exact": true,
6-
"path": "/example",
7-
"component": { "$codeRef": "ExamplePage" }
6+
"path": "/administration/cluster-update",
7+
"component": { "$codeRef": "ClusterUpdatePage" }
88
}
99
},
1010
{
1111
"type": "console.navigation/href",
1212
"properties": {
13-
"id": "example",
14-
"name": "%plugin__console-plugin-template~Plugin example%",
15-
"href": "/example",
13+
"id": "cluster-update",
14+
"name": "%plugin__cluster-update-console-plugin~Cluster Update%",
15+
"href": "/administration/cluster-update",
1616
"perspective": "admin",
17-
"section": "home"
17+
"section": "administration"
1818
}
1919
}
2020
]

i18next-parser.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module.exports = {
99
locales: ['en'],
1010
namespaceSeparator: '~',
1111
reactNamespace: false,
12-
defaultNamespace: 'plugin__console-plugin-template',
12+
defaultNamespace: 'plugin__cluster-update-console-plugin',
1313
useKeysAsDefaultValue: true,
1414

1515
// see below for more details

jest.config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { Config } from 'jest';
2+
3+
const config: Config = {
4+
preset: 'ts-jest',
5+
testEnvironment: 'node',
6+
roots: ['<rootDir>/src'],
7+
testMatch: ['**/__tests__/**/*.test.ts'],
8+
moduleFileExtensions: ['ts', 'tsx', 'js'],
9+
moduleNameMapper: {
10+
'@openshift-console/dynamic-plugin-sdk':
11+
'<rootDir>/src/__mocks__/@openshift-console/dynamic-plugin-sdk.ts',
12+
},
13+
};
14+
15+
export default config;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
{
2+
"{{count}} incompatible with target_one": "{{count}} incompatible with target_one",
3+
"{{count}} incompatible with target_other": "{{count}} incompatible with target_other",
4+
"{{count}} manual approval_one": "{{count}} manual approval_one",
5+
"{{count}} manual approval_other": "{{count}} manual approval_other",
6+
"{{count}} pending upgrade_one": "{{count}} pending upgrade_one",
7+
"{{count}} pending upgrade_other": "{{count}} pending upgrade_other",
8+
"{{passed}}/{{total}} checks passed": "{{passed}}/{{total}} checks passed",
9+
"{{passed}}/{{total}} checks passed, {{failures}} failed": "{{passed}}/{{total}} checks passed, {{failures}} failed",
10+
"{{passed}}/{{total}} checks passed, {{warnings}} warnings": "{{passed}}/{{total}} checks passed, {{warnings}} warnings",
11+
"{{total}} operators managed by OLM": "{{total}} operators managed by OLM",
12+
"Action failed": "Action failed",
13+
"Active update plans": "Active update plans",
14+
"Activity summary and the current proposed plan - {{channel}} channel": "Activity summary and the current proposed plan - {{channel}} channel",
15+
"Affected: {{resources}}": "Affected: {{resources}}",
16+
"Age": "Age",
17+
"Agent target version": "Agent target version",
18+
"AI Assessment": "AI Assessment",
19+
"AI is analyzing your cluster for upgrade readiness...": "AI is analyzing your cluster for upgrade readiness...",
20+
"AI Update Agent": "AI Update Agent",
21+
"All Clear": "All Clear",
22+
"Analyzing": "Analyzing",
23+
"API Deprecations": "API Deprecations",
24+
"Approval": "Approval",
25+
"Approve & upgrade": "Approve & upgrade",
26+
"Awaiting Decision": "Awaiting Decision",
27+
"Channel": "Channel",
28+
"Check": "Check",
29+
"Close": "Close",
30+
"Cluster Metrics": "Cluster Metrics",
31+
"Cluster Update": "Cluster Update",
32+
"Completed": "Completed",
33+
"Confirm approve & upgrade": "Confirm approve & upgrade",
34+
"Confirm reject": "Confirm reject",
35+
"CPU utilization": "CPU utilization",
36+
"Detail": "Detail",
37+
"Duration": "Duration",
38+
"ends {{date}}": "ends {{date}}",
39+
"Error loading cluster version": "Error loading cluster version",
40+
"Estimated Duration": "Estimated Duration",
41+
"Estimated Impact": "Estimated Impact",
42+
"etcd fsync p99": "etcd fsync p99",
43+
"Failed to create plan": "Failed to create plan",
44+
"Findings": "Findings",
45+
"Findings table": "Findings table",
46+
"Generate plan": "Generate plan",
47+
"Generated": "Generated",
48+
"In the OCP 5.0 agent led experience, the update agent proposes plans and drives execution. OpenShift Lightspeed can still run a pre-check on cluster and operator readiness before you approve, so risks and prerequisites stay visible alongside automated planning.": "In the OCP 5.0 agent led experience, the update agent proposes plans and drives execution. OpenShift Lightspeed can still run a pre-check on cluster and operator readiness before you approve, so risks and prerequisites stay visible alongside automated planning.",
49+
"Lightspeed proposals unavailable": "Lightspeed proposals unavailable",
50+
"Loading": "Loading",
51+
"Maintenance Details": "Maintenance Details",
52+
"Memory utilization": "Memory utilization",
53+
"Minor": "Minor",
54+
"Monitor Command": "Monitor Command",
55+
"Name": "Name",
56+
"No active update plans": "No active update plans",
57+
"No deprecated APIs in use": "No deprecated APIs in use",
58+
"No issues found — all readiness checks passed.": "No issues found — all readiness checks passed.",
59+
"No maintenance details available.": "No maintenance details available.",
60+
"No update history available": "No update history available",
61+
"Not reversible": "Not reversible",
62+
"OCP Compat": "OCP Compat",
63+
"OLM Operator Lifecycle": "OLM Operator Lifecycle",
64+
"OLM operators table": "OLM operators table",
65+
"Operator": "Operator",
66+
"Pending": "Pending",
67+
"Phase": "Phase",
68+
"Pick a target from your channel (updates AI Assessment), then click Generate plan to build or refresh the proposed update below.": "Pick a target from your channel (updates AI Assessment), then click Generate plan to build or refresh the proposed update below.",
69+
"Plan ready — approving will start the cluster upgrade. You will be redirected to the cluster settings page to monitor progress.": "Plan ready — approving will start the cluster upgrade. You will be redirected to the cluster settings page to monitor progress.",
70+
"Plans Created": "Plans Created",
71+
"Pre-check with AI": "Pre-check with AI",
72+
"Prerequisites": "Prerequisites",
73+
"Proposed Update: {{current}}": "Proposed Update: {{current}}",
74+
"Readiness Checks": "Readiness Checks",
75+
"Readiness data not yet available.": "Readiness data not yet available.",
76+
"Ready to proceed": "Ready to proceed",
77+
"Reject plan": "Reject plan",
78+
"Reversibility": "Reversibility",
79+
"Reversible": "Reversible",
80+
"Review available versions, assess operator compatibility, and plan how this cluster version is newer OpenShift releases. Use Updates plan to prepare or start an update, Active update plans for in-flight work, and Update history for completed ones.": "Review available versions, assess operator compatibility, and plan how this cluster version is newer OpenShift releases. Use Updates plan to prepare or start an update, Active update plans for in-flight work, and Update history for completed ones.",
81+
"Risk assessment not yet available.": "Risk assessment not yet available.",
82+
"Risk Level": "Risk Level",
83+
"Schedule for later": "Schedule for later",
84+
"Select a version": "Select a version",
85+
"Severity": "Severity",
86+
"Started": "Started",
87+
"Status": "Status",
88+
"Support": "Support",
89+
"Target version": "Target version",
90+
"Target Version": "Target Version",
91+
"Target version {{version}} not found in available updates": "Target version {{version}} not found in available updates",
92+
"The Lightspeed Proposal CRD is not installed on this cluster. AI-driven update planning features are disabled.": "The Lightspeed Proposal CRD is not installed on this cluster. AI-driven update planning features are disabled.",
93+
"There are no update plans currently in progress.": "There are no update plans currently in progress.",
94+
"This cluster has no recorded update history.": "This cluster has no recorded update history.",
95+
"unknown": "unknown",
96+
"Update": "Update",
97+
"Update history": "Update history",
98+
"Update Type": "Update Type",
99+
"Updates Executed": "Updates Executed",
100+
"Updates plan": "Updates plan",
101+
"Upgrade Command": "Upgrade Command",
102+
"upgrade pending": "upgrade pending",
103+
"upgrade to {{version}}": "upgrade to {{version}}",
104+
"Verified": "Verified",
105+
"Version": "Version",
106+
"Version {{version}}, {{count}} available_one": "Version {{version}}, {{count}} available_one",
107+
"Version {{version}}, {{count}} available_other": "Version {{version}}, {{count}} available_other",
108+
"View etcd pods": "View etcd pods",
109+
"View monitoring": "View monitoring",
110+
"z-stream": "z-stream"
111+
}

locales/en/plugin__console-plugin-template.json

Lines changed: 0 additions & 8 deletions
This file was deleted.

package.json

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"name": "console-plugin-template",
2+
"name": "cluster-update-console-plugin",
33
"version": "0.0.1",
4-
"description": "Template project for OpenShift Console plugins",
4+
"description": "AI-driven cluster update experience for OpenShift",
55
"private": true,
66
"license": "Apache-2.0",
77
"repository": {
@@ -15,6 +15,7 @@
1515
"start": "yarn webpack serve --progress",
1616
"start-console": "./start-console.sh",
1717
"i18n": "./i18n-scripts/build-i18n.sh && node ./i18n-scripts/set-english-defaults.js",
18+
"test": "jest",
1819
"lint": "yarn eslint src integration-tests --fix && stylelint 'src/**/*.css' --allow-empty-input --fix",
1920
"test-cypress": "cd integration-tests && cypress open",
2021
"test-cypress-headless": "cd integration-tests && node --max-old-space-size=4096 ../node_modules/.bin/cypress run --browser ${BRIDGE_E2E_BROWSER_NAME:-electron}",
@@ -32,6 +33,7 @@
3233
"@patternfly/react-core": "^6.2.2",
3334
"@patternfly/react-icons": "^6.2.2",
3435
"@patternfly/react-table": "^6.2.2",
36+
"@types/jest": "^30.0.0",
3537
"@types/node": "^22.0.0",
3638
"@types/react": "^17.0.37",
3739
"@types/react-router-dom": "^5.3.3",
@@ -49,6 +51,7 @@
4951
"globals": "^17.4.0",
5052
"i18next": "^23.11.5",
5153
"i18next-parser": "^9.4.0",
54+
"jest": "^30.3.0",
5255
"mocha": "^11.7.5",
5356
"mocha-junit-reporter": "^2.2.1",
5457
"mochawesome": "^7.1.4",
@@ -65,6 +68,7 @@
6568
"style-loader": "^4.0.0",
6669
"stylelint": "^17.4.0",
6770
"stylelint-config-standard": "^40.0.0",
71+
"ts-jest": "^29.4.9",
6872
"ts-loader": "^9.5.4",
6973
"ts-node": "^10.9.2",
7074
"typescript": "^5.9.3",
@@ -74,12 +78,12 @@
7478
"webpack-dev-server": "^5.2.3"
7579
},
7680
"consolePlugin": {
77-
"name": "console-plugin-template",
81+
"name": "cluster-update-console-plugin",
7882
"version": "0.0.1",
79-
"displayName": "OpenShift Console Plugin Template",
80-
"description": "Template project for OpenShift Console plugins. Edit package.json to change this message and the plugin name.",
83+
"displayName": "Cluster Update Console Plugin",
84+
"description": "AI-driven cluster update experience for OpenShift",
8185
"exposedModules": {
82-
"ExamplePage": "./components/ExamplePage"
86+
"ClusterUpdatePage": "./components/ClusterUpdatePage"
8387
},
8488
"dependencies": {
8589
"@console/pluginAPI": "^4.21.0"
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
export const getGroupVersionKindForModel = (model: {
2+
apiGroup?: string;
3+
apiVersion: string;
4+
kind: string;
5+
}) => ({
6+
group: model.apiGroup || '',
7+
version: model.apiVersion,
8+
kind: model.kind,
9+
});
10+
11+
export const getAPIVersionForModel = (model: { apiGroup?: string; apiVersion: string }) =>
12+
model.apiGroup ? `${model.apiGroup}/${model.apiVersion}` : model.apiVersion;
13+
14+
export const k8sCreate = jest.fn();
15+
export const k8sPatch = jest.fn();
16+
17+
export type K8sModel = {
18+
apiGroup?: string;
19+
apiVersion: string;
20+
kind: string;
21+
plural: string;
22+
abbr: string;
23+
namespaced: boolean;
24+
label: string;
25+
labelPlural: string;
26+
};
27+
28+
export type K8sResourceCommon = {
29+
apiVersion?: string;
30+
kind?: string;
31+
metadata?: {
32+
name?: string;
33+
namespace?: string;
34+
labels?: Record<string, string>;
35+
annotations?: Record<string, string>;
36+
uid?: string;
37+
creationTimestamp?: string;
38+
};
39+
};
40+
41+
export type K8sResourceCondition = {
42+
type: string;
43+
status: string;
44+
lastTransitionTime?: string;
45+
reason?: string;
46+
message?: string;
47+
};

src/__tests__/constants.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { isTerminalPhase, TERMINAL_PHASES } from '../utils/constants';
2+
3+
describe('isTerminalPhase', () => {
4+
it('returns true for terminal phases', () => {
5+
for (const phase of TERMINAL_PHASES) {
6+
expect(isTerminalPhase(phase)).toBe(true);
7+
}
8+
});
9+
10+
it('returns false for non-terminal phases', () => {
11+
expect(isTerminalPhase('Pending')).toBe(false);
12+
expect(isTerminalPhase('Analyzing')).toBe(false);
13+
expect(isTerminalPhase('Executing')).toBe(false);
14+
});
15+
16+
it('returns true for undefined (no active phase)', () => {
17+
expect(isTerminalPhase(undefined)).toBe(true);
18+
});
19+
20+
it('returns true for empty string', () => {
21+
expect(isTerminalPhase('')).toBe(true);
22+
});
23+
});

0 commit comments

Comments
 (0)