Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
afa016c
feat: add incident.io connector backend
beng360 Apr 20, 2026
ea966b1
feat: add incident.io connector frontend
beng360 Apr 20, 2026
ab5f81d
feat: add incident.io skill definition for RCA agent
beng360 Apr 20, 2026
aab5c6e
fix: harden incident.io connector end-to-end
beng360 Apr 20, 2026
6776ec9
fix: resolve CodeQL and code quality issues on incident.io connector
beng360 Apr 20, 2026
1dbaa28
fix: remaining CodeRabbit issues — fuzzy match and API filter params
beng360 Apr 20, 2026
d0d501c
remove unsued logos
OlivierTrudeau Apr 21, 2026
ac5c660
Fix damian wrong ordering of creation of tables
OlivierTrudeau Apr 21, 2026
1f81ad4
fix: resolve remaining CodeRabbit review comments on incident.io conn…
beng360 Apr 21, 2026
7d3161c
fix: address Olivier's review — remove Next.js webhook proxy, add mis…
beng360 Apr 21, 2026
afc897d
refactor: use ConnectorRegistry for source display names instead of h…
beng360 Apr 21, 2026
8a419bc
Merge remote-tracking branch 'origin/main' into feat/incidentio-conne…
beng360 Apr 21, 2026
dc7b43e
fix logo
OlivierTrudeau Apr 21, 2026
a96cc94
Remove prefix matching from load_skill fuzzy match
beng360 Apr 21, 2026
751297c
Address PR #287 review: webhook signing, pagination, UX fixes
beng360 Apr 21, 2026
9dd0e5a
Fix CodeQL and SonarCloud security findings
beng360 Apr 21, 2026
7405379
Harden incidentio routes against log injection and info exposure
beng360 Apr 21, 2026
51cb51c
Fix SonarCloud CSRF hotspots and reduce code duplication
beng360 Apr 21, 2026
ffa7e3b
Extract useConnectorAuth hook to reduce auth page duplication
beng360 Apr 21, 2026
7c59994
Fix infinite re-render loop in useConnectorAuth
beng360 Apr 21, 2026
0f2dd90
Fix incident.io field extraction and org-scoped preference storage
beng360 Apr 21, 2026
e116c5d
Remove user-controlled data from log statements in store_user_preference
beng360 Apr 22, 2026
e786673
Merge remote-tracking branch 'origin/main' into feat/incidentio-conne…
beng360 Apr 22, 2026
242a086
Fix incident.io webhook: add to open prefixes, match v2 event types, …
beng360 Apr 22, 2026
4f9b1d1
Clarify webhook signing secret description in incident.io setup
beng360 Apr 22, 2026
9a11b7f
Show webhook signing secret saved state in incident.io setup UI
beng360 Apr 22, 2026
3bbbadf
Fix all 23 SonarCloud quality gate issues on PR #287
beng360 Apr 23, 2026
779d74e
Fix 2 SonarCloud reliability bugs (S7727, S7502) to pass quality gate
beng360 Apr 23, 2026
b16a15a
Merge remote-tracking branch 'origin/main' into feat/incidentio-conne…
beng360 Apr 23, 2026
f803698
Merge commit '0bdf89b09245ba2ead91b8c69616a85d3226105c' into feat/inc…
OlivierTrudeau Apr 23, 2026
909f7ac
Fix stale cache showing false "connected" state and use set_rls_conte…
beng360 Apr 23, 2026
ff7d167
Merge remote-tracking branch 'origin/main' into feat/incidentio-conne…
beng360 Apr 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions client/public/incidentio.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 21 additions & 1 deletion client/src/app/api/connected-accounts/[provider]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export async function DELETE(
const { provider } = await context.params

// Validate provider
if (!['gcp', 'azure', 'aws', 'github', 'grafana', 'datadog', 'netdata', 'ovh', 'scaleway', 'tailscale', 'slack', 'google_chat', 'splunk', 'dynatrace', 'confluence', 'jira', 'sharepoint', 'coroot', 'thousandeyes', 'jenkins', 'cloudbees', 'bigpanda', 'spinnaker', 'newrelic', 'opsgenie'].includes(provider)) {
if (!['gcp', 'azure', 'aws', 'github', 'grafana', 'datadog', 'netdata', 'ovh', 'scaleway', 'tailscale', 'slack', 'google_chat', 'splunk', 'dynatrace', 'confluence', 'jira', 'sharepoint', 'coroot', 'thousandeyes', 'jenkins', 'cloudbees', 'bigpanda', 'spinnaker', 'newrelic', 'opsgenie', 'incidentio'].includes(provider)) {
return NextResponse.json(
{ error: 'Invalid provider' },
{ status: 400 }
Expand Down Expand Up @@ -442,6 +442,26 @@ export async function DELETE(
return NextResponse.json({ success: true })
}

// Special handling for incident.io
if (provider === 'incidentio') {
const response = await fetch(`${API_BASE_URL}/incidentio/disconnect`, {
method: 'DELETE',
headers: authHeaders,
})

if (!response.ok) {
const errorText = await response.text()
console.error('Backend error disconnecting incident.io:', errorText)
return NextResponse.json(
{ error: 'Failed to disconnect incident.io' },
{ status: response.status }
)
}

const data = await response.json()
return NextResponse.json(data)
}

// Special handling for BigPanda
if (provider === 'bigpanda') {
const response = await fetch(`${API_BASE_URL}/bigpanda/disconnect`, {
Expand Down
6 changes: 6 additions & 0 deletions client/src/app/api/incident-io/alerts/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { NextRequest } from 'next/server';
import { forwardRequest } from '@/lib/backend-proxy';

export async function GET(request: NextRequest) {
return forwardRequest(request, 'GET', '/incidentio/alerts', 'incident-io/alerts');
}
6 changes: 6 additions & 0 deletions client/src/app/api/incident-io/alerts/webhook-url/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { NextRequest } from 'next/server';
import { forwardRequest } from '@/lib/backend-proxy';

export async function GET(request: NextRequest) {
return forwardRequest(request, 'GET', '/incidentio/alerts/webhook-url', 'incident-io/webhook-url');
}
6 changes: 6 additions & 0 deletions client/src/app/api/incident-io/connect/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { NextRequest } from 'next/server';
import { forwardRequest } from '@/lib/backend-proxy';

export async function POST(request: NextRequest) {
return forwardRequest(request, 'POST', '/incidentio/connect', 'incident-io/connect');
}
10 changes: 10 additions & 0 deletions client/src/app/api/incident-io/rca-settings/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NextRequest } from 'next/server';
import { forwardRequest } from '@/lib/backend-proxy';

export async function GET(request: NextRequest) {
return forwardRequest(request, 'GET', '/incidentio/rca-settings', 'incident-io/rca-settings');
}

export async function PUT(request: NextRequest) {
return forwardRequest(request, 'PUT', '/incidentio/rca-settings', 'incident-io/rca-settings');
}
6 changes: 6 additions & 0 deletions client/src/app/api/incident-io/status/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { NextRequest } from 'next/server';
import { forwardRequest } from '@/lib/backend-proxy';

export async function GET(request: NextRequest) {
return forwardRequest(request, 'GET', '/incidentio/status', 'incident-io/status');
}
170 changes: 170 additions & 0 deletions client/src/app/incident-io/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
"use client";

import { useState } from "react";
import { useToast } from "@/hooks/use-toast";
import { useConnectorAuth } from "@/hooks/use-connector-auth";
import { incidentIoService } from "@/lib/services/incident-io";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Loader2 } from "lucide-react";
import { getUserFriendlyError } from "@/lib/utils";
import { IncidentIoWebhookStep } from "@/components/incident-io/IncidentIoWebhookStep";
import ConnectorAuthGuard from "@/components/connectors/ConnectorAuthGuard";
import Image from "next/image";

export default function IncidentIoAuthPage() {
const { toast } = useToast();
const [apiKey, setApiKey] = useState("");
const [loading, setLoading] = useState(false);

const {
isConnected,
isCheckingStatus,
updateLocalState,
disconnect,
} = useConnectorAuth({
cacheKey: "incident_io_connection_status",
storageKey: "isIncidentIoConnected",
fetchStatus: () => incidentIoService.getStatus(),
disconnectPath: "/api/connected-accounts/incidentio",
});

const handleConnect = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setLoading(true);

try {
const result = await incidentIoService.connect({ apiKey });
updateLocalState(result);
toast({ title: "Success", description: "incident.io connected successfully!" });
} catch (err: any) {
console.error("incident.io connection failed", err);
toast({
title: "Failed to connect to incident.io",
description: getUserFriendlyError(err),
variant: "destructive",
});
} finally {
setLoading(false);
setApiKey("");
}
};

const handleDisconnect = async () => {
setLoading(true);
try {
await disconnect();
toast({ title: "Success", description: "incident.io disconnected successfully" });
} catch (err: any) {
console.error("incident.io disconnect failed", err);
toast({
title: "Failed to disconnect incident.io",
description: getUserFriendlyError(err),
variant: "destructive",
});
} finally {
setLoading(false);
}
};

if (isCheckingStatus) {
return (
<ConnectorAuthGuard connectorName="incident.io">
<div className="container mx-auto py-8 px-4 max-w-2xl">
<Card>
<CardContent className="flex items-center justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</CardContent>
</Card>
</div>
</ConnectorAuthGuard>
);
}

return (
<ConnectorAuthGuard connectorName="incident.io">
<div className="container mx-auto py-8 px-4 max-w-2xl">
<div className="flex items-center gap-4 mb-6">
<div className="flex items-center justify-center h-12 w-12 rounded-lg bg-white dark:bg-white p-1.5">
<Image src="/incidentio.svg" alt="incident.io" width={40} height={40} />
</div>
<div>
<h1 className="text-3xl font-bold">incident.io</h1>
<p className="text-muted-foreground mt-0.5">
Incident lifecycle tracking and automated RCA
</p>
</div>
</div>

<div className="flex items-center justify-center mb-8">
<div className="flex items-center">
<div className={`flex items-center justify-center w-10 h-10 rounded-full font-bold ${!isConnected ? 'text-white' : 'bg-gray-200 text-gray-600'}`} style={!isConnected ? { backgroundColor: '#F04438' } : undefined}>

Check warning on line 103 in client/src/app/incident-io/auth/page.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=Arvo-AI_aurora&issues=AZ2xVkrvEKMbOcCSnoVZ&open=AZ2xVkrvEKMbOcCSnoVZ&pullRequest=287

Check warning on line 103 in client/src/app/incident-io/auth/page.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=Arvo-AI_aurora&issues=AZ2xVkrvEKMbOcCSnoVa&open=AZ2xVkrvEKMbOcCSnoVa&pullRequest=287
1
</div>
<div className="w-24 h-1" style={{ backgroundColor: isConnected ? '#F04438' : '#e5e7eb' }}></div>
<div className={`flex items-center justify-center w-10 h-10 rounded-full font-bold ${isConnected ? 'text-white' : 'bg-gray-200 text-gray-600'}`} style={isConnected ? { backgroundColor: '#F04438' } : undefined}>
2
</div>
</div>
</div>

<div className="flex items-center justify-center mb-6 text-sm font-medium">
<span className={!isConnected ? 'text-foreground' : 'text-muted-foreground'} style={!isConnected ? { color: '#F04438' } : undefined}>

Check warning on line 114 in client/src/app/incident-io/auth/page.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=Arvo-AI_aurora&issues=AZ2xVkrvEKMbOcCSnoVc&open=AZ2xVkrvEKMbOcCSnoVc&pullRequest=287

Check warning on line 114 in client/src/app/incident-io/auth/page.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=Arvo-AI_aurora&issues=AZ2xVkrvEKMbOcCSnoVb&open=AZ2xVkrvEKMbOcCSnoVb&pullRequest=287
Connect
</span>
<span className="mx-4 text-muted-foreground">&rarr;</span>
<span className={isConnected ? 'text-foreground' : 'text-muted-foreground'} style={isConnected ? { color: '#F04438' } : undefined}>
Configure Webhook
</span>
</div>

{!isConnected ? (

Check warning on line 123 in client/src/app/incident-io/auth/page.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=Arvo-AI_aurora&issues=AZ2xVkrvEKMbOcCSnoVd&open=AZ2xVkrvEKMbOcCSnoVd&pullRequest=287
<Card>
<CardHeader>
<CardTitle>Connect to incident.io</CardTitle>
<CardDescription>
Create an API key at <strong>Settings &rarr; API keys</strong> in incident.io, then paste it below.
</CardDescription>
</CardHeader>
<CardContent>
<form className="space-y-4" onSubmit={handleConnect}>
<div className="space-y-2">
<Label htmlFor="apiKey">API Key</Label>
<Input
id="apiKey"
type="password"
placeholder="Paste your incident.io API key"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
required
/>
<p className="text-xs text-muted-foreground">
The API key needs these permissions: <strong>View all incident data (including private incidents)</strong> and <strong>Create incidents</strong>. Keys are stored securely in Vault.
</p>
Comment thread
OlivierTrudeau marked this conversation as resolved.
</div>

<Button type="submit" className="w-full" disabled={loading || !apiKey}>
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Connecting...
</>
) : (
"Connect incident.io"
)}
</Button>
</form>
</CardContent>
</Card>
) : (
<IncidentIoWebhookStep
onDisconnect={handleDisconnect}
loading={loading}
/>
)}
</div>
</ConnectorAuthGuard>
);
}
Loading
Loading