diff --git a/backend/internal/api/handlers/pagerduty_migration.go b/backend/internal/api/handlers/pagerduty_migration.go index c705927..7db633c 100644 --- a/backend/internal/api/handlers/pagerduty_migration.go +++ b/backend/internal/api/handlers/pagerduty_migration.go @@ -13,12 +13,22 @@ import ( // pdClientFactory is a package-level variable so tests can override it to point // the real PagerDuty client at a mock HTTP server. -var pdClientFactory = func(apiKey string) *pagerduty.Client { - return pagerduty.NewClient(apiKey) +var pdClientFactory = func(apiKey, baseURL string) *pagerduty.Client { + return pagerduty.NewClientWithBaseURL(apiKey, baseURL) +} + +// pdBaseURL maps a region string ("us" or "eu") to the PagerDuty API base URL. +// Defaults to the US endpoint for empty or unrecognised values. +func pdBaseURL(region string) string { + if region == "eu" { + return "https://api.eu.pagerduty.com" + } + return "https://api.pagerduty.com" } type pdMigrationRequest struct { APIKey string `json:"api_key" binding:"required"` + Region string `json:"region"` Force bool `json:"force"` } @@ -57,7 +67,7 @@ func PreviewPagerDutyMigration( return } - client := pdClientFactory(req.APIKey) + client := pdClientFactory(req.APIKey, pdBaseURL(req.Region)) if err := client.ValidateAPIKey(); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{"message": err.Error()}}) return @@ -87,7 +97,7 @@ func ImportPagerDutyMigration( return } - client := pdClientFactory(req.APIKey) + client := pdClientFactory(req.APIKey, pdBaseURL(req.Region)) if err := client.ValidateAPIKey(); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{"message": err.Error()}}) return diff --git a/backend/internal/api/handlers/pagerduty_migration_test.go b/backend/internal/api/handlers/pagerduty_migration_test.go index a20967d..797af53 100644 --- a/backend/internal/api/handlers/pagerduty_migration_test.go +++ b/backend/internal/api/handlers/pagerduty_migration_test.go @@ -106,7 +106,7 @@ func newPDMockServer(t *testing.T, cfg pdMockConfig) *httptest.Server { func overridePDFactory(t *testing.T, baseURL string) { t.Helper() original := pdClientFactory - pdClientFactory = func(apiKey string) *pagerduty.Client { + pdClientFactory = func(apiKey, _ string) *pagerduty.Client { return pagerduty.NewClientWithBaseURL(apiKey, baseURL) } t.Cleanup(func() { pdClientFactory = original }) @@ -278,3 +278,36 @@ func TestImportPagerDutyMigration_InvalidAPIKey(t *testing.T) { w := postJSON(r, "/api/v1/migrations/pagerduty/import", map[string]string{"api_key": "bad"}) assert.Equal(t, http.StatusBadRequest, w.Code) } + +// ── Region tests ────────────────────────────────────────────────────────────── + +func TestPDBaseURL_DefaultsToUS(t *testing.T) { + assert.Equal(t, "https://api.pagerduty.com", pdBaseURL("")) + assert.Equal(t, "https://api.pagerduty.com", pdBaseURL("us")) +} + +func TestPDBaseURL_EU(t *testing.T) { + assert.Equal(t, "https://api.eu.pagerduty.com", pdBaseURL("eu")) +} + +func TestPreviewPagerDutyMigration_EURegion(t *testing.T) { + srv := newPDMockServer(t, pdMockConfig{}) + overridePDFactory(t, srv.URL) + + db := setupMigrationTestDB(t) + r := buildPDRouter(repository.NewScheduleRepository(db), repository.NewEscalationPolicyRepository(db)) + + w := postJSON(r, "/api/v1/migrations/pagerduty/preview", map[string]string{"api_key": "tok", "region": "eu"}) + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestImportPagerDutyMigration_EURegion(t *testing.T) { + srv := newPDMockServer(t, pdMockConfig{}) + overridePDFactory(t, srv.URL) + + db := setupMigrationTestDB(t) + r := buildPDRouter(repository.NewScheduleRepository(db), repository.NewEscalationPolicyRepository(db)) + + w := postJSON(r, "/api/v1/migrations/pagerduty/import", map[string]string{"api_key": "tok", "region": "eu"}) + assert.Equal(t, http.StatusOK, w.Code) +} diff --git a/frontend/src/api/migrations.ts b/frontend/src/api/migrations.ts index 53ce797..4cd39f1 100644 --- a/frontend/src/api/migrations.ts +++ b/frontend/src/api/migrations.ts @@ -99,6 +99,7 @@ export async function importOnCallMigration( export interface PDMigrationRequest { api_key: string + region?: 'us' | 'eu' force?: boolean } diff --git a/frontend/src/components/migrations/PagerDutyImportPanel.tsx b/frontend/src/components/migrations/PagerDutyImportPanel.tsx index 471d545..bca02b5 100644 --- a/frontend/src/components/migrations/PagerDutyImportPanel.tsx +++ b/frontend/src/components/migrations/PagerDutyImportPanel.tsx @@ -9,6 +9,7 @@ import { } from '../../api/migrations' type Step = 'idle' | 'previewing' | 'preview' | 'importing' | 'done' +type Region = 'us' | 'eu' interface PagerDutyImportPanelProps { onComplete?: () => void @@ -22,6 +23,7 @@ function extractError(e: unknown): string { export function PagerDutyImportPanel({ onComplete }: PagerDutyImportPanelProps) { const [step, setStep] = useState('idle') const [apiKey, setApiKey] = useState('') + const [region, setRegion] = useState('us') const [showKey, setShowKey] = useState(false) const [force, setForce] = useState(false) const [error, setError] = useState('') @@ -39,7 +41,7 @@ export function PagerDutyImportPanel({ onComplete }: PagerDutyImportPanelProps) } setStep('previewing') try { - const data = await previewPagerDutyMigration({ api_key: apiKey.trim() }) + const data = await previewPagerDutyMigration({ api_key: apiKey.trim(), region }) setPreview(data) setStep('preview') } catch (e) { @@ -53,7 +55,7 @@ export function PagerDutyImportPanel({ onComplete }: PagerDutyImportPanelProps) setError('') setStep('importing') try { - const data = await importPagerDutyMigration({ api_key: apiKey.trim(), force }) + const data = await importPagerDutyMigration({ api_key: apiKey.trim(), region, force }) setResult(data) setStep('done') } catch (e) { @@ -72,9 +74,35 @@ export function PagerDutyImportPanel({ onComplete }: PagerDutyImportPanelProps) )} - {/* ── Step 1: API key input ─────────────────────────────────────────── */} + {/* ── Step 1: API key + region input ───────────────────────────────── */} {(step === 'idle' || step === 'previewing') && (
+ {/* Region selector */} +
+ +
+ {(['us', 'eu'] as Region[]).map((r) => ( + + ))} +
+
+ + {/* API key */}