Skip to content

Commit 2347497

Browse files
authored
feat: Add joule feature flag (#4394)
* add joule feature flag * only add joule component if enabled in config * improve JouleChat component slightly * feat: use new joule deployment on dev * feat: remove comment
1 parent 8059626 commit 2347497

File tree

16 files changed

+203
-25
lines changed

16 files changed

+203
-25
lines changed

index.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
<meta
2424
http-equiv="Content-Security-Policy"
2525
content="
26-
font-src 'self' https://sdk.openui5.org/ https://cdn.jsdelivr.net data:;
27-
script-src 'self' 'unsafe-eval' blob:;
28-
object-src 'self';
29-
"
26+
font-src 'self' https://sdk.openui5.org/ https://cdn.jsdelivr.net data:;
27+
script-src 'self' 'unsafe-eval' blob:;
28+
object-src 'self';
29+
"
3030
/>
3131
<title>Busola</title>
3232
</head>

kyma/environments/dev/config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ config:
6565
documentationLink: https://help.sap.com/docs/btp/sap-business-technology-platform-internal/kyma-companion?locale=en-US&version=Internal&state=DRAFT
6666
model: 'gpt-4.1'
6767
queryMaxTokens: 8000
68+
useJoule: false
69+
jouleConfig:
70+
url: 'https://kcp-dev-joule-vfglrzqg.eu12.sapdas.cloud.sap/resources/public/webclient/bootstrap.js'
71+
botname: 'kyma_companion'
6872
GARDENER_LOGIN:
6973
isEnabled: false
7074
kubeconfig: null

kyma/environments/stage/config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ config:
6464
documentationLink: https://help.sap.com/docs/btp/sap-business-technology-platform-internal/kyma-companion?locale=en-US&version=Internal&state=DRAFT
6565
model: 'gpt-4.1'
6666
queryMaxTokens: 8000
67+
useJoule: false
68+
jouleConfig:
69+
url: ''
70+
botname: ''
6771
GARDENER_LOGIN:
6872
isEnabled: false
6973
kubeconfig: null

public/defaultConfig.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ config:
5151
documentationLink: https://help.sap.com/docs/btp/sap-business-technology-platform-internal/kyma-companion?locale=en-US&version=Internal&state=DRAFT
5252
model: 'gpt-4.1'
5353
queryMaxTokens: 8000
54+
useJoule: false
55+
jouleConfig:
56+
url: ''
57+
botname: ''
5458
GARDENER_LOGIN:
5559
isEnabled: false
5660
kubeconfig: null

src/command-pallette/CommandPalletteUI/CommandPaletteSearchBar.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,16 @@ export function CommandPaletteSearchBar({
7373

7474
timer = setTimeout(() => {
7575
setShellbarWidth(
76-
showCompanion.show
76+
showCompanion.show && !showCompanion.useJoule
7777
? shellbarRef?.current?.getBoundingClientRect().width || 0
7878
: window.innerWidth,
7979
);
8080
}, 0);
8181
}
8282

8383
useEffect(() => {
84+
if (showCompanion.useJoule) return;
85+
8486
const elementObserver = new ResizeObserver(() => {
8587
handleChangedWidth();
8688
});

src/command-pallette/CommandPalletteUI/CommandPaletteUI.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,9 @@ export function CommandPaletteUI({
229229
<Background hide={hide}>
230230
<div
231231
className={`command-palette-ui__wrapper ${
232-
showCompanion.show && shellbarWidth < SCREEN_SIZE_BREAKPOINT_M
232+
showCompanion.show &&
233+
!showCompanion.useJoule &&
234+
shellbarWidth < SCREEN_SIZE_BREAKPOINT_M
233235
? 'full-size'
234236
: ''
235237
}`}

src/components/App/App.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,9 @@
3131
display: flex;
3232
justify-content: flex-end;
3333
}
34+
35+
// Joule/SAP DAS Web Client z-index override
36+
// Ensures the external Joule chat overlay appears above Busola UI components
37+
#cai-webclient-main {
38+
z-index: 10000 !important;
39+
}

src/components/App/App.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import { manualKubeConfigIdAtom } from 'state/manualKubeConfigIdAtom';
5757
import { AuthForm } from 'components/Clusters/components/AuthForm';
5858
import { ResourceForm } from 'shared/ResourceForm';
5959
import { checkAuthRequiredInputs } from 'components/Clusters/helper';
60+
import JouleChat from 'components/KymaCompanion/JouleChat';
6061

6162
export default function App() {
6263
const theme = useAtomValue(themeAtom);
@@ -132,16 +133,17 @@ export default function App() {
132133
return (
133134
<SplitterLayout id="splitter-layout">
134135
<SplitterElement
135-
resizable={showCompanion.show}
136+
resizable={showCompanion.show && !showCompanion.useJoule}
136137
size={
137-
showCompanion.show
138+
showCompanion.show && !showCompanion.useJoule
138139
? showCompanion.fullScreen
139140
? '0%'
140141
: '70%'
141142
: '100%'
142143
}
143144
>
144145
<div id="html-wrap">
146+
{showCompanion.useJoule && <JouleChat />}
145147
<Header />
146148
<div id="page-wrap">
147149
<Sidebar key={cluster?.name} />
@@ -220,7 +222,7 @@ export default function App() {
220222
</div>
221223
</div>
222224
</SplitterElement>
223-
{showCompanion.show ? (
225+
{showCompanion.show && !showCompanion.useJoule ? (
224226
<SplitterElement
225227
resizable={!showCompanion.fullScreen}
226228
size={showCompanion.fullScreen ? '100%' : '30%'}

src/components/Clusters/views/ClusterList.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,11 @@ function ClusterList() {
5353
const setShowCompanion = useSetAtom(showKymaCompanionAtom);
5454

5555
useEffect(() => {
56-
setShowCompanion({
56+
setShowCompanion((prevState) => ({
57+
...prevState,
5758
show: false,
5859
fullScreen: false,
59-
});
60+
}));
6061
}, []); // eslint-disable-line react-hooks/exhaustive-deps,
6162

6263
useEffect(() => {
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { useEffect, useRef } from 'react';
2+
import { useAtom } from 'jotai';
3+
import { showKymaCompanionAtom } from 'state/companion/showKymaCompanionAtom';
4+
5+
import { useCurrentResource } from 'components/KymaCompanion/utils/useResource';
6+
import { useFeature } from 'hooks/useFeature';
7+
import { configFeaturesNames } from 'state/types';
8+
9+
export default function JouleChat() {
10+
const [showKymaCompanion, setShowKymaCompanion] = useAtom(
11+
showKymaCompanionAtom,
12+
);
13+
14+
const { isEnabled, jouleConfig } = useFeature(
15+
configFeaturesNames.KYMA_COMPANION,
16+
);
17+
18+
const currentResource = useCurrentResource();
19+
const resourceRef = useRef(currentResource);
20+
useEffect(() => {
21+
resourceRef.current = currentResource;
22+
}, [currentResource]);
23+
24+
useEffect(() => {
25+
if (!isEnabled || !jouleConfig?.url || !jouleConfig?.botname) {
26+
return;
27+
}
28+
29+
const dasProps = {
30+
url: jouleConfig.url,
31+
botname: jouleConfig.botname,
32+
};
33+
34+
window.sapdas = window.sapdas || {};
35+
window.sapdas.webclientBridge = window.sapdas.webclientBridge || {};
36+
37+
window.sapdas.webclientBridge.onClose = () => {
38+
setShowKymaCompanion((prevState) => ({
39+
...prevState,
40+
show: false,
41+
}));
42+
};
43+
44+
const myBridgeImpl = {
45+
getApplicationContext: () => {
46+
return resourceRef.current;
47+
},
48+
};
49+
50+
window.sapdas.webclientPreregistration =
51+
window.sapdas.webclientPreregistration || {};
52+
window.sapdas.webclientPreregistration.myAppId = {
53+
callbacks: myBridgeImpl,
54+
priority: 3, // default prio, higher values are higher prio
55+
};
56+
57+
// Check if script already exists to avoid duplicates
58+
const existingScript = document.querySelector(
59+
`script[src="${dasProps.url}"]`,
60+
);
61+
if (existingScript) return;
62+
63+
const script = document.createElement('script');
64+
script.src = dasProps.url;
65+
script.setAttribute('data-bot-name', dasProps.botname);
66+
script.setAttribute('data-expander-type', 'CUSTOM');
67+
68+
script.onload = () => {
69+
console.log('Joule Web Client loaded successfully');
70+
};
71+
72+
script.onerror = () => {
73+
console.error('Failed to load Joule Web Client');
74+
};
75+
76+
document.head.appendChild(script);
77+
78+
return () => {
79+
if (script.parentNode) {
80+
script.parentNode.removeChild(script);
81+
}
82+
if (window.sapdas?.webclientBridge) {
83+
delete window.sapdas.webclientBridge.onClose;
84+
}
85+
};
86+
}, [isEnabled, jouleConfig, setShowKymaCompanion]);
87+
88+
// Control visibility based on Jotai state
89+
useEffect(() => {
90+
if (!isEnabled) return;
91+
92+
const webclient = window.sap?.das?.webclient;
93+
if (!webclient) return;
94+
95+
if (
96+
showKymaCompanion.show &&
97+
showKymaCompanion.useJoule &&
98+
!webclient.isOpen()
99+
) {
100+
webclient.show();
101+
} else if (!showKymaCompanion.show && webclient.isOpen()) {
102+
webclient.hide();
103+
}
104+
}, [isEnabled, showKymaCompanion]);
105+
106+
return null;
107+
}

0 commit comments

Comments
 (0)