Skip to content

Commit 9127a7b

Browse files
committed
chore: merge main into release for new releases
2 parents 028cffa + 78beb4e commit 9127a7b

13 files changed

Lines changed: 141 additions & 25 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,5 @@ packages/*/dist
8686
# Release script
8787
scripts/sync-release-branch.sh
8888
/.vscode
89+
90+
.claude/projects/-Users-marfuen-code-comp/

apps/api/src/integration-platform/controllers/connections.controller.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,13 @@ export class ConnectionsController {
384384
if (typeof credentials.externalId === 'string') {
385385
metadata.externalId = credentials.externalId;
386386
}
387+
// Store Azure tenant/subscription IDs in metadata for display and pre-filling
388+
if (typeof credentials.tenantId === 'string') {
389+
metadata.tenantId = credentials.tenantId;
390+
}
391+
if (typeof credentials.subscriptionId === 'string') {
392+
metadata.subscriptionId = credentials.subscriptionId;
393+
}
387394
}
388395

389396
// Create connection (only after validation passes)

apps/app/src/app/(app)/[orgId]/cloud-tests/actions/connect-cloud.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,27 @@ export const connectCloudAction = authActionClient
9292
? [credentials.region]
9393
: [];
9494

95+
const tenantId =
96+
typeof credentials.tenantId === 'string' ? credentials.tenantId.trim() : undefined;
97+
const subscriptionId =
98+
typeof credentials.subscriptionId === 'string'
99+
? credentials.subscriptionId.trim()
100+
: undefined;
101+
95102
const settings =
96103
cloudProvider === 'aws'
97104
? {
98105
accountId,
99106
connectionName,
100107
regions: regionValues,
101108
}
102-
: {};
109+
: cloudProvider === 'azure'
110+
? {
111+
connectionName,
112+
tenantId,
113+
subscriptionId,
114+
}
115+
: {};
103116

104117
// Create new integration (allow multiple per provider)
105118
const newIntegration = await db.integration.create({

apps/app/src/app/(app)/[orgId]/cloud-tests/components/EmptyState.tsx

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -142,26 +142,33 @@ export function EmptyState({
142142
onConnected,
143143
initialProvider = null,
144144
}: EmptyStateProps) {
145-
const initialIsAws = initialProvider === 'aws';
146-
const [step, setStep] = useState<Step>(initialProvider && !initialIsAws ? 'connect' : 'choose');
145+
const initialUsesDialog = initialProvider === 'aws' || initialProvider === 'azure';
146+
const [step, setStep] = useState<Step>(
147+
initialProvider && !initialUsesDialog ? 'connect' : 'choose',
148+
);
147149
const [selectedProvider, setSelectedProvider] = useState<CloudProvider>(
148-
initialProvider && !initialIsAws ? initialProvider : null,
150+
initialProvider && !initialUsesDialog ? initialProvider : null,
151+
);
152+
const [showConnectDialog, setShowConnectDialog] = useState(initialUsesDialog);
153+
const [connectDialogProvider, setConnectDialogProvider] = useState<'aws' | 'azure'>(
154+
initialProvider === 'azure' ? 'azure' : 'aws',
149155
);
150-
const [showConnectDialog, setShowConnectDialog] = useState(initialIsAws);
151156
const [credentials, setCredentials] = useState<Record<string, string | string[]>>({});
152157
const [errors, setErrors] = useState<Record<string, string>>({});
153158
const [isConnecting, setIsConnecting] = useState(false);
154159
const [awsRegions, setAwsRegions] = useState<{ value: string; label: string }[]>([]);
155160
const [awsAccountId, setAwsAccountId] = useState<string>('');
156161

157162
useEffect(() => {
158-
if (initialProvider === 'aws') {
163+
if (initialProvider === 'aws' || initialProvider === 'azure') {
164+
setConnectDialogProvider(initialProvider);
159165
setShowConnectDialog(true);
160166
}
161167
}, [initialProvider]);
162168

163169
const handleProviderSelect = (providerId: CloudProvider) => {
164-
if (providerId === 'aws') {
170+
if (providerId === 'aws' || providerId === 'azure') {
171+
setConnectDialogProvider(providerId);
165172
setShowConnectDialog(true);
166173
return;
167174
}
@@ -414,9 +421,15 @@ export function EmptyState({
414421
<ConnectIntegrationDialog
415422
open={showConnectDialog}
416423
onOpenChange={(open) => setShowConnectDialog(open)}
417-
integrationId="aws"
418-
integrationName="Amazon Web Services"
419-
integrationLogoUrl="https://img.logo.dev/aws.amazon.com?token=pk_AZatYxV5QDSfWpRDaBxzRQ"
424+
integrationId={connectDialogProvider}
425+
integrationName={
426+
connectDialogProvider === 'azure' ? 'Microsoft Azure' : 'Amazon Web Services'
427+
}
428+
integrationLogoUrl={
429+
connectDialogProvider === 'azure'
430+
? 'https://img.logo.dev/azure.microsoft.com?token=pk_AZatYxV5QDSfWpRDaBxzRQ'
431+
: 'https://img.logo.dev/aws.amazon.com?token=pk_AZatYxV5QDSfWpRDaBxzRQ'
432+
}
420433
onConnected={() => {
421434
setShowConnectDialog(false);
422435
onConnected?.();
@@ -426,7 +439,8 @@ export function EmptyState({
426439

427440
<div className="grid w-full gap-4 md:grid-cols-3">
428441
{CLOUD_PROVIDERS.filter(
429-
(cp) => cp.id === 'aws' || !connectedProviders.includes(cp.id),
442+
(cp) =>
443+
cp.id === 'aws' || cp.id === 'azure' || !connectedProviders.includes(cp.id),
430444
).map((cloudProvider) => (
431445
<Card
432446
key={cloudProvider.id}

apps/app/src/app/(app)/[orgId]/cloud-tests/components/ProviderTabs.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ function ConnectionDetails({ connection }: { connection: Provider }) {
9494
details.push(`Account: ${connection.accountId}`);
9595
}
9696

97+
if (connection.tenantId) {
98+
details.push(`Tenant: ${connection.tenantId}`);
99+
}
100+
101+
if (connection.subscriptionId) {
102+
details.push(`Subscription: ${connection.subscriptionId}`);
103+
}
104+
97105
if (connection.regions?.length) {
98106
details.push(
99107
`${connection.regions.length} region${connection.regions.length !== 1 ? 's' : ''}`,

apps/app/src/app/(app)/[orgId]/cloud-tests/components/TestsLayout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,13 +287,13 @@ export function TestsLayout({ initialFindings, initialProviders, orgId }: TestsL
287287
needsConfiguration={needsVariableConfiguration}
288288
/>
289289

290-
{/* CloudSettingsModal only for providers that do NOT support multiple connections */}
291-
{/* AWS is managed via ConnectIntegrationDialog since it supports multiple connections */}
290+
{/* CloudSettingsModal for single-connection providers AND legacy connections */}
291+
{/* Legacy connections need this modal for disconnect since ConnectIntegrationDialog can't see them */}
292292
<CloudSettingsModal
293293
open={showSettings}
294294
onOpenChange={setShowSettings}
295295
connectedProviders={connectedProviders
296-
.filter((p) => !p.supportsMultipleConnections)
296+
.filter((p) => !p.supportsMultipleConnections || p.isLegacy)
297297
.map((p) => ({
298298
id: p.integrationId,
299299
connectionId: p.id,

apps/app/src/app/(app)/[orgId]/cloud-tests/page.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ export default async function CloudTestsPage({ params }: { params: Promise<{ org
107107
requiredVariables: string[];
108108
accountId?: string;
109109
regions?: string[];
110+
tenantId?: string;
111+
subscriptionId?: string;
110112
supportsMultipleConnections?: boolean;
111113
};
112114

@@ -118,6 +120,9 @@ export default async function CloudTestsPage({ params }: { params: Promise<{ org
118120
const regions = Array.isArray(metadata.regions)
119121
? metadata.regions.filter((region): region is string => typeof region === 'string')
120122
: undefined;
123+
const tenantId = typeof metadata.tenantId === 'string' ? metadata.tenantId : undefined;
124+
const subscriptionId =
125+
typeof metadata.subscriptionId === 'string' ? metadata.subscriptionId : undefined;
121126
const manifest = getManifest(conn.provider.slug);
122127

123128
return {
@@ -135,6 +140,8 @@ export default async function CloudTestsPage({ params }: { params: Promise<{ org
135140
requiredVariables: getRequiredVariables(conn.provider.slug),
136141
accountId,
137142
regions,
143+
tenantId,
144+
subscriptionId,
138145
supportsMultipleConnections: manifest?.supportsMultipleConnections ?? false,
139146
};
140147
});
@@ -147,6 +154,9 @@ export default async function CloudTestsPage({ params }: { params: Promise<{ org
147154
const regions = Array.isArray(settings.regions)
148155
? settings.regions.filter((region): region is string => typeof region === 'string')
149156
: undefined;
157+
const tenantId = typeof settings.tenantId === 'string' ? settings.tenantId : undefined;
158+
const subscriptionId =
159+
typeof settings.subscriptionId === 'string' ? settings.subscriptionId : undefined;
150160
const manifest = getManifest(integration.integrationId);
151161

152162
return {
@@ -164,6 +174,8 @@ export default async function CloudTestsPage({ params }: { params: Promise<{ org
164174
requiredVariables: getRequiredVariables(integration.integrationId),
165175
accountId,
166176
regions,
177+
tenantId,
178+
subscriptionId,
167179
supportsMultipleConnections: manifest?.supportsMultipleConnections ?? false,
168180
};
169181
});

apps/app/src/app/(app)/[orgId]/cloud-tests/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export interface Provider {
2828
requiredVariables?: string[];
2929
accountId?: string;
3030
regions?: string[];
31+
tenantId?: string;
32+
subscriptionId?: string;
3133
supportsMultipleConnections?: boolean;
3234
}
3335
export type FailedIntegration = {

apps/app/src/app/(app)/[orgId]/people/all/components/PendingInvitationRow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export function PendingInvitationRow({
119119
<OverflowMenuVertical />
120120
</DropdownMenuTrigger>
121121
<DropdownMenuContent align="end">
122-
<DropdownMenuItem variant="destructive" onSelect={handleOpenCancelDialog}>
122+
<DropdownMenuItem variant="destructive" onClick={handleOpenCancelDialog}>
123123
<TrashCan size={16} />
124124
Cancel Invitation
125125
</DropdownMenuItem>

apps/app/src/app/(app)/[orgId]/tasks/components/TasksByCategory.tsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import type { Member, Task, User } from '@db';
44
import { ArrowLeft, Folder } from 'lucide-react';
55
import Image from 'next/image';
6-
import { usePathname, useRouter } from 'next/navigation';
7-
import { useMemo, useState } from 'react';
6+
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
7+
import { useMemo } from 'react';
88
import { AutomationIndicator } from './AutomationIndicator';
99
import { TaskStatusSelector } from './TaskStatusSelector';
1010

@@ -62,7 +62,7 @@ const statusPalette = {
6262
export function TasksByCategory({ tasks, members, statusFilter }: TasksByCategoryProps) {
6363
const router = useRouter();
6464
const pathname = usePathname();
65-
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
65+
const searchParams = useSearchParams();
6666

6767
// Group tasks by their first control, or "Uncategorized"
6868
const categories = useMemo(() => {
@@ -110,6 +110,13 @@ export function TasksByCategory({ tasks, members, statusFilter }: TasksByCategor
110110
return sortedCategories;
111111
}, [tasks]);
112112

113+
// Selected category from URL so it survives back navigation
114+
const categoryFromUrl = searchParams.get('category');
115+
const selectedCategory = useMemo(() => {
116+
if (!categoryFromUrl) return null;
117+
return categories.some((c) => c.id === categoryFromUrl) ? categoryFromUrl : null;
118+
}, [categoryFromUrl, categories]);
119+
113120
// Calculate stats for a category
114121
const getCategoryStats = (categoryTasks: Task[]) => {
115122
const total = categoryTasks.length;
@@ -129,7 +136,19 @@ export function TasksByCategory({ tasks, members, statusFilter }: TasksByCategor
129136
};
130137

131138
const handleCategoryClick = (categoryId: string) => {
132-
setSelectedCategory(selectedCategory === categoryId ? null : categoryId);
139+
const next = selectedCategory === categoryId ? null : categoryId;
140+
const params = new URLSearchParams(searchParams.toString());
141+
if (next) params.set('category', next);
142+
else params.delete('category');
143+
const query = params.toString();
144+
router.replace(query ? `${pathname}?${query}` : pathname);
145+
};
146+
147+
const handleBackToCategories = () => {
148+
const params = new URLSearchParams(searchParams.toString());
149+
params.delete('category');
150+
const query = params.toString();
151+
router.replace(query ? `${pathname}?${query}` : pathname);
133152
};
134153

135154
// If a category is selected, show only that category's tasks
@@ -212,7 +231,7 @@ export function TasksByCategory({ tasks, members, statusFilter }: TasksByCategor
212231
{/* Back button and category header */}
213232
<div className="flex items-center justify-between">
214233
<button
215-
onClick={() => setSelectedCategory(null)}
234+
onClick={handleBackToCategories}
216235
className="inline-flex items-center gap-2 rounded-sm border border-border/60 px-3 py-1.5 text-[11px] font-medium uppercase tracking-[0.18em] text-muted-foreground transition-colors hover:bg-muted/20 hover:text-foreground"
217236
>
218237
<ArrowLeft className="h-3 w-3" />

0 commit comments

Comments
 (0)