From 293691beede957bc8831491368bc5dcc058ce88a Mon Sep 17 00:00:00 2001 From: Andy Pickering Date: Thu, 13 Aug 2026 16:58:42 +0900 Subject: [PATCH] Fix agentic capabilities toggle when config is absent When the AgenticOLSConfig is missing, treat the agentic capabilities as enabled and create the AgenticOLSConfig if the user toggles the agentic capabilities off. Co-Authored-By: Claude Opus 4.8 --- ...in__lightspeed-agentic-console-plugin.json | 1 + .../runs/AgenticCapabilitiesToggle.test.ts | 37 ++++++++++++- .../runs/AgenticCapabilitiesToggle.tsx | 52 +++++++++++++++---- .../runs/agenticCapabilitiesUtils.ts | 14 +++++ 4 files changed, 93 insertions(+), 11 deletions(-) diff --git a/locales/en/plugin__lightspeed-agentic-console-plugin.json b/locales/en/plugin__lightspeed-agentic-console-plugin.json index 1d651513..54da3881 100644 --- a/locales/en/plugin__lightspeed-agentic-console-plugin.json +++ b/locales/en/plugin__lightspeed-agentic-console-plugin.json @@ -86,6 +86,7 @@ "Failed to approve execution.": "Failed to approve execution.", "Failed to copy to clipboard": "Failed to copy to clipboard", "Failed to deny {{stage}}.": "Failed to deny {{stage}}.", + "Failed to load agentic capabilities configuration": "Failed to load agentic capabilities configuration", "Failed to load logs.": "Failed to load logs.", "Failed to update agentic capabilities": "Failed to update agentic capabilities", "Failure reason": "Failure reason", diff --git a/src/components/runs/AgenticCapabilitiesToggle.test.ts b/src/components/runs/AgenticCapabilitiesToggle.test.ts index f500969d..697a134d 100644 --- a/src/components/runs/AgenticCapabilitiesToggle.test.ts +++ b/src/components/runs/AgenticCapabilitiesToggle.test.ts @@ -1,5 +1,9 @@ import { describe, expect, test } from 'vitest'; -import { buildSuspendedPatch } from './agenticCapabilitiesUtils'; +import { + buildAgenticOLSConfig, + buildSuspendedPatch, + isNotFoundError, +} from './agenticCapabilitiesUtils'; describe('buildSuspendedPatch', () => { test('returns replace op setting suspended to true', () => { @@ -14,3 +18,34 @@ describe('buildSuspendedPatch', () => { ]); }); }); + +describe('buildAgenticOLSConfig', () => { + test('builds a cluster config with the given suspended value', () => { + expect(buildAgenticOLSConfig(true)).toEqual({ + apiVersion: 'agentic.openshift.io/v1alpha1', + kind: 'AgenticOLSConfig', + metadata: { name: 'cluster' }, + spec: { suspended: true }, + }); + }); +}); + +describe('isNotFoundError', () => { + test('is true for a 404 error', () => { + expect(isNotFoundError({ code: 404 })).toBe(true); + }); + + test('is false for other error codes', () => { + expect(isNotFoundError({ code: 403 })).toBe(false); + expect(isNotFoundError({ code: 500 })).toBe(false); + }); + + test('is false for errors without a code', () => { + expect(isNotFoundError(new Error('network down'))).toBe(false); + }); + + test('is false when there is no error', () => { + expect(isNotFoundError(null)).toBe(false); + expect(isNotFoundError(undefined)).toBe(false); + }); +}); diff --git a/src/components/runs/AgenticCapabilitiesToggle.tsx b/src/components/runs/AgenticCapabilitiesToggle.tsx index 6de55c0b..90367f23 100644 --- a/src/components/runs/AgenticCapabilitiesToggle.tsx +++ b/src/components/runs/AgenticCapabilitiesToggle.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { + k8sCreate, k8sPatch, useAccessReview, useK8sWatchResource, @@ -24,7 +25,12 @@ import { AgenticOLSConfigGVK, AgenticOLSConfigModel, } from '../../models/agenticrun'; -import { buildSuspendedPatch } from './agenticCapabilitiesUtils'; +import { + AGENTIC_OLS_CONFIG_NAME, + buildAgenticOLSConfig, + buildSuspendedPatch, + isNotFoundError, +} from './agenticCapabilitiesUtils'; import './AgenticCapabilitiesToggle.css'; @@ -34,9 +40,9 @@ const AgenticCapabilitiesToggle: React.FC = () => { const [error, setError] = React.useState(''); const [confirmOpen, setConfirmOpen] = React.useState(false); - const [config, loaded] = useK8sWatchResource({ + const [config, loaded, loadError] = useK8sWatchResource({ groupVersionKind: AgenticOLSConfigGVK, - name: 'cluster', + name: AGENTIC_OLS_CONFIG_NAME, }); const [canPatch] = useAccessReview({ @@ -45,6 +51,18 @@ const AgenticCapabilitiesToggle: React.FC = () => { verb: 'patch', }); + const [canCreate] = useAccessReview({ + group: AgenticOLSConfigModel.apiGroup, + resource: AgenticOLSConfigModel.plural, + verb: 'create', + }); + + const configExists = !!config?.metadata?.name; + const configAbsent = isNotFoundError(loadError); + const unknownLoadError = !!loadError && !configAbsent; + const ready = loaded || configAbsent; + const canModify = configExists ? canPatch : canCreate; + const isEnabled = !config?.spec?.suspended; const setSuspended = React.useCallback( @@ -52,11 +70,16 @@ const AgenticCapabilitiesToggle: React.FC = () => { setSaving(true); setError(''); try { - await k8sPatch({ - model: AgenticOLSConfigModel, - resource: config, - data: buildSuspendedPatch(suspended), - }); + await (configExists + ? k8sPatch({ + model: AgenticOLSConfigModel, + resource: config, + data: buildSuspendedPatch(suspended), + }) + : k8sCreate({ + model: AgenticOLSConfigModel, + data: buildAgenticOLSConfig(suspended), + })); return true; } catch (err) { setError(err instanceof Error ? err.message : String(err)); @@ -65,7 +88,7 @@ const AgenticCapabilitiesToggle: React.FC = () => { setSaving(false); } }, - [config], + [config, configExists], ); const handleToggle = React.useCallback( @@ -88,6 +111,15 @@ const AgenticCapabilitiesToggle: React.FC = () => { return ( <> + {unknownLoadError && ( + + {loadError instanceof Error ? loadError.message : String(loadError)} + + )} {error && !confirmOpen && ( setError('')} />} @@ -135,7 +167,7 @@ const AgenticCapabilitiesToggle: React.FC = () => { aria-label={t('Agentic capabilities')} id="agentic-capabilities-toggle" isChecked={isEnabled} - isDisabled={!loaded || saving || !canPatch} + isDisabled={!ready || saving || !canModify} onChange={handleToggle} /> diff --git a/src/components/runs/agenticCapabilitiesUtils.ts b/src/components/runs/agenticCapabilitiesUtils.ts index c7e26347..80e15bc5 100644 --- a/src/components/runs/agenticCapabilitiesUtils.ts +++ b/src/components/runs/agenticCapabilitiesUtils.ts @@ -1,3 +1,17 @@ +import { AgenticOLSConfig, AgenticOLSConfigModel } from '../../models/agenticrun'; + +export const AGENTIC_OLS_CONFIG_NAME = 'cluster'; + +export const isNotFoundError = (loadError: unknown): boolean => + (loadError as { code?: number } | null)?.code === 404; + export const buildSuspendedPatch = (suspended: boolean) => [ { op: 'add' as const, path: '/spec/suspended', value: suspended }, ]; + +export const buildAgenticOLSConfig = (suspended: boolean): AgenticOLSConfig => ({ + apiVersion: `${AgenticOLSConfigModel.apiGroup}/${AgenticOLSConfigModel.apiVersion}`, + kind: AgenticOLSConfigModel.kind, + metadata: { name: AGENTIC_OLS_CONFIG_NAME }, + spec: { suspended }, +});