Skip to content

Commit 32ed960

Browse files
committed
feat: gate Teams Integrations tab behind enableTeamsBridge feature flag
Off by default. Cloud-ui pipes the LaunchDarkly value through useConsoleFeatureFlags; without it the tab and its content are not rendered. Also: disable Save when enabled with incomplete fields, consolidate isEditing into editedState, fix dark-mode classes on read-only fields, document proto fields including bare-key divergence on bot_app_secret_ref.
1 parent c09a752 commit 32ed960

9 files changed

Lines changed: 77 additions & 24 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ requests.txt
2020
build
2121

2222
.cursor
23+
.claude/worktrees/

backend/pkg/protogen/redpanda/api/dataplane/v1alpha3/ai_agent.pb.go

Lines changed: 14 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/components/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const FEATURE_FLAGS = {
1616
enableNewPipelineLogs: false,
1717
enablePipelineDiagrams: false,
1818
enableConnectSlashMenu: false,
19+
enableTeamsBridge: false,
1920
};
2021

2122
// Cloud-managed tag keys for service account integration

frontend/src/components/pages/agents/details/ai-agent-details-page.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { getRouteApi, useNavigate } from '@tanstack/react-router';
1414
const routeApi = getRouteApi('/agents/$id/');
1515

1616
import { Tabs, TabsContent, TabsList, TabsTrigger } from 'components/redpanda-ui/components/tabs';
17+
import { isFeatureFlagEnabled } from 'config';
1718
import { AlertCircle, FileText, Loader2, Network, Plug, Search, Settings } from 'lucide-react';
1819
import { useEffect } from 'react';
1920
import { useGetAIAgentQuery } from 'react-query/api/ai-agent';
@@ -80,6 +81,8 @@ export const AIAgentDetailsPage = () => {
8081
return null;
8182
}
8283

84+
const showTeamsBridge = isFeatureFlagEnabled('enableTeamsBridge');
85+
8386
return (
8487
<div className="flex flex-col gap-4 pb-1">
8588
<AIAgentDetailsHeader />
@@ -92,12 +95,14 @@ export const AIAgentDetailsPage = () => {
9295
Configuration
9396
</div>
9497
</TabsTrigger>
95-
<TabsTrigger className="gap-2" value="integrations">
96-
<div className="flex items-center gap-2">
97-
<Plug className="h-4 w-4" />
98-
Integrations
99-
</div>
100-
</TabsTrigger>
98+
{showTeamsBridge && (
99+
<TabsTrigger className="gap-2" value="integrations">
100+
<div className="flex items-center gap-2">
101+
<Plug className="h-4 w-4" />
102+
Integrations
103+
</div>
104+
</TabsTrigger>
105+
)}
101106
<TabsTrigger className="gap-2" value="agent-card">
102107
<div className="flex items-center gap-2">
103108
<Network className="h-4 w-4" />
@@ -119,9 +124,11 @@ export const AIAgentDetailsPage = () => {
119124
<TabsContent value="configuration">
120125
<AIAgentConfigurationTab />
121126
</TabsContent>
122-
<TabsContent value="integrations">
123-
<AIAgentIntegrationsTab />
124-
</TabsContent>
127+
{showTeamsBridge && (
128+
<TabsContent value="integrations">
129+
<AIAgentIntegrationsTab />
130+
</TabsContent>
131+
)}
125132
<TabsContent value="agent-card">
126133
<AIAgentCardTab />
127134
</TabsContent>

frontend/src/components/pages/agents/details/ai-agent-integrations-tab.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ export const AIAgentIntegrationsTab = () => {
5757
const { mutateAsync: updateAIAgent, isPending: isUpdatePending } = useUpdateAIAgentMutation();
5858
const { data: secretsData } = useListSecretsQuery();
5959

60-
const [isEditing, setIsEditing] = useState(false);
6160
const [editedState, setEditedState] = useState<TeamsBridgeState | null>(null);
61+
const isEditing = editedState !== null;
6262

6363
const agent = aiAgentData?.aiAgent;
6464

@@ -86,6 +86,10 @@ export const AIAgentIntegrationsTab = () => {
8686
setEditedState({ ...displayState, ...updates });
8787
};
8888

89+
const isSaveDisabled =
90+
isUpdatePending ||
91+
(displayState.enabled && !(displayState.botAppId && displayState.botTenantId && displayState.botAppSecretName));
92+
8993
const handleSave = async () => {
9094
if (!id) {
9195
return;
@@ -117,7 +121,6 @@ export const AIAgentIntegrationsTab = () => {
117121
{
118122
onSuccess: () => {
119123
toast.success('Teams integration updated');
120-
setIsEditing(false);
121124
setEditedState(null);
122125
},
123126
onError: (error) => {
@@ -131,7 +134,6 @@ export const AIAgentIntegrationsTab = () => {
131134
};
132135

133136
const handleCancel = () => {
134-
setIsEditing(false);
135137
setEditedState(null);
136138
};
137139

@@ -150,7 +152,7 @@ export const AIAgentIntegrationsTab = () => {
150152
<div className="flex gap-2">
151153
{isEditing ? (
152154
<>
153-
<Button disabled={isUpdatePending} onClick={handleSave} variant="primary">
155+
<Button disabled={isSaveDisabled} onClick={handleSave} variant="primary">
154156
<Save className="h-4 w-4" />
155157
{isUpdatePending ? 'Saving...' : 'Save Changes'}
156158
</Button>
@@ -159,7 +161,7 @@ export const AIAgentIntegrationsTab = () => {
159161
</Button>
160162
</>
161163
) : (
162-
<Button onClick={() => setIsEditing(true)} variant="primary">
164+
<Button onClick={() => setEditedState(displayState)} variant="primary">
163165
<Edit className="h-4 w-4" />
164166
Edit Configuration
165167
</Button>
@@ -196,7 +198,7 @@ export const AIAgentIntegrationsTab = () => {
196198
value={displayState.botAppId}
197199
/>
198200
) : (
199-
<div className="flex h-10 items-center rounded-md border border-gray-200 bg-gray-50 px-3 py-2">
201+
<div className="flex h-10 items-center rounded-md border border-border bg-muted px-3 py-2">
200202
<Text className="truncate">{displayState.botAppId || '-'}</Text>
201203
</div>
202204
)}
@@ -212,7 +214,7 @@ export const AIAgentIntegrationsTab = () => {
212214
value={displayState.botTenantId}
213215
/>
214216
) : (
215-
<div className="flex h-10 items-center rounded-md border border-gray-200 bg-gray-50 px-3 py-2">
217+
<div className="flex h-10 items-center rounded-md border border-border bg-muted px-3 py-2">
216218
<Text className="truncate">{displayState.botTenantId || '-'}</Text>
217219
</div>
218220
)}
@@ -234,7 +236,7 @@ export const AIAgentIntegrationsTab = () => {
234236
/>
235237
</div>
236238
) : (
237-
<div className="flex h-10 items-center rounded-md border border-gray-200 bg-gray-50 px-3 py-2">
239+
<div className="flex h-10 items-center rounded-md border border-border bg-muted px-3 py-2">
238240
<Text className="truncate">{displayState.botAppSecretName || '-'}</Text>
239241
</div>
240242
)}

frontend/src/protogen/redpanda/api/dataplane/v1alpha3/ai_agent_pb.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,26 +639,39 @@ export const AIAgent_StateSchema: GenEnum<AIAgent_State> = /*@__PURE__*/
639639

640640
/**
641641
* Microsoft Teams bridge configuration for connecting an AI agent to Teams.
642+
* Each agent gets its own Azure Bot registration; the bridge validates
643+
* inbound Bot Framework JWTs against the per-agent bot_app_id as audience.
642644
*
643645
* @generated from message redpanda.api.dataplane.v1alpha3.AIAgentTeamsBridge
644646
*/
645647
export type AIAgentTeamsBridge = Message<"redpanda.api.dataplane.v1alpha3.AIAgentTeamsBridge"> & {
646648
/**
649+
* Whether the Teams bridge is active for this agent.
650+
*
647651
* @generated from field: bool enabled = 1;
648652
*/
649653
enabled: boolean;
650654

651655
/**
656+
* Azure Bot registration Application (client) ID.
657+
*
652658
* @generated from field: string bot_app_id = 2;
653659
*/
654660
botAppId: string;
655661

656662
/**
663+
* AAD tenant ID the bot registration lives in.
664+
*
657665
* @generated from field: string bot_tenant_id = 3;
658666
*/
659667
botTenantId: string;
660668

661669
/**
670+
* Bare secret key in the Redpanda secret store (e.g. "TEAMS_BOT_SECRET").
671+
* Unlike provider api_key fields which use ${secrets.NAME} template syntax,
672+
* this is a plain identifier — the bridge resolves it directly via the
673+
* tenant-scoped secret store prefix.
674+
*
662675
* @generated from field: string bot_app_secret_ref = 4;
663676
*/
664677
botAppSecretRef: string;

proto/gen/openapi/openapi.v1alpha3.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

proto/gen/openapi/openapi.v1alpha3.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,15 +181,26 @@ components:
181181
- service_account
182182
type: object
183183
AIAgentTeamsBridge:
184-
description: Microsoft Teams bridge configuration for connecting an AI agent to Teams.
184+
description: |-
185+
Microsoft Teams bridge configuration for connecting an AI agent to Teams.
186+
Each agent gets its own Azure Bot registration; the bridge validates
187+
inbound Bot Framework JWTs against the per-agent bot_app_id as audience.
185188
properties:
186189
bot_app_id:
190+
description: Azure Bot registration Application (client) ID.
187191
type: string
188192
bot_app_secret_ref:
193+
description: |-
194+
Bare secret key in the Redpanda secret store (e.g. "TEAMS_BOT_SECRET").
195+
Unlike provider api_key fields which use ${secrets.NAME} template syntax,
196+
this is a plain identifier — the bridge resolves it directly via the
197+
tenant-scoped secret store prefix.
189198
type: string
190199
bot_tenant_id:
200+
description: AAD tenant ID the bot registration lives in.
191201
type: string
192202
enabled:
203+
description: Whether the Teams bridge is active for this agent.
193204
type: boolean
194205
type: object
195206
AIAgentUpdate:

proto/redpanda/api/dataplane/v1alpha3/ai_agent.proto

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,19 @@ message AIAgent {
303303
}
304304

305305
// Microsoft Teams bridge configuration for connecting an AI agent to Teams.
306+
// Each agent gets its own Azure Bot registration; the bridge validates
307+
// inbound Bot Framework JWTs against the per-agent bot_app_id as audience.
306308
message AIAgentTeamsBridge {
309+
// Whether the Teams bridge is active for this agent.
307310
bool enabled = 1;
311+
// Azure Bot registration Application (client) ID.
308312
string bot_app_id = 2;
313+
// AAD tenant ID the bot registration lives in.
309314
string bot_tenant_id = 3;
315+
// Bare secret key in the Redpanda secret store (e.g. "TEAMS_BOT_SECRET").
316+
// Unlike provider api_key fields which use ${secrets.NAME} template syntax,
317+
// this is a plain identifier — the bridge resolves it directly via the
318+
// tenant-scoped secret store prefix.
310319
string bot_app_secret_ref = 4 [
311320
(buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE,
312321
(buf.validate.field).string.pattern = "^[A-Za-z_][A-Za-z0-9_]*$"

0 commit comments

Comments
 (0)