Skip to content

Commit 231628e

Browse files
authored
fix: ambiguity in multi github connector (#618)
1 parent 72aaaad commit 231628e

File tree

5 files changed

+197
-75
lines changed

5 files changed

+197
-75
lines changed

api/internal/features/github-connector/service/update_connector.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
//
1313
// If ConnectorID is provided, it updates that specific connector.
1414
// Otherwise, it finds the connector without an installation_id and updates that one.
15+
// If multiple connectors exist and ConnectorID is not provided, it returns an error to prevent ambiguity.
1516
// If no connector without installation_id is found, it updates the first connector (backward compatibility).
1617
//
1718
// If any errors occur during the update process, the method returns the error.
@@ -24,7 +25,47 @@ func (c *GithubConnectorService) UpdateGithubConnectorRequest(InstallationID str
2425

2526
if len(connectors) == 0 {
2627
fmt.Println("no connector found")
27-
return nil
28+
return fmt.Errorf("no connectors found for user")
29+
}
30+
31+
var connectorToUpdate *shared_types.GithubConnector
32+
33+
// If ConnectorID is provided, find and update that specific connector
34+
if ConnectorID != "" {
35+
// Validate UUID format
36+
if _, err := uuid.Parse(ConnectorID); err != nil {
37+
return fmt.Errorf("invalid connector_id format: %v", err)
38+
}
39+
40+
// Find the connector with matching ID
41+
for i := range connectors {
42+
if connectors[i].ID.String() == ConnectorID {
43+
connectorToUpdate = &connectors[i]
44+
break
45+
}
46+
}
47+
48+
if connectorToUpdate == nil {
49+
return fmt.Errorf("connector with id %s not found", ConnectorID)
50+
}
51+
} else {
52+
// If multiple connectors exist, connector_id is required to avoid ambiguity
53+
if len(connectors) > 1 {
54+
return fmt.Errorf("connector_id is required when multiple connectors exist")
55+
}
56+
57+
// Find connector without installation_id (newly created connector)
58+
for i := range connectors {
59+
if connectors[i].InstallationID == "" || strings.TrimSpace(connectors[i].InstallationID) == "" {
60+
connectorToUpdate = &connectors[i]
61+
break
62+
}
63+
}
64+
65+
// If no connector without installation_id found, use first connector (backward compatibility for single connector)
66+
if connectorToUpdate == nil {
67+
connectorToUpdate = &connectors[0]
68+
}
2869
}
2970

3071
var connectorToUpdate *shared_types.GithubConnector

view/app/self-host/components/github-connector/github-connector-settings-modal.tsx

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,6 @@ const ConnectorItem: React.FC<ConnectorItemProps> = ({
6969
? 'border-primary bg-primary/5 cursor-default'
7070
: 'border-border bg-card cursor-pointer hover:border-primary/50 hover:bg-muted/50'
7171
} ${isDeleting || isUpdating ? 'opacity-50 cursor-not-allowed' : ''}`}
72-
return (
73-
<>
74-
<div
75-
className={`flex items-center justify-between p-4 rounded-lg border transition-colors ${
76-
isActive ? 'border-primary bg-primary/5' : 'border-border bg-card'
77-
}`}
7872
>
7973
<div className="flex items-center gap-3 flex-1 min-w-0">
8074
<div className="rounded-full bg-primary/10 p-2 shrink-0">
@@ -243,45 +237,63 @@ const GitHubConnectorSettingsModal: React.FC<GitHubConnectorSettingsModalProps>
243237
isActive={connector.id === activeConnectorId}
244238
onSetActive={handleSetActiveConnector}
245239
onDelete={handleDeleteConnector}
246-
onReset={handleResetConnector}
247240
isDeleting={isDeleting === connector.id}
248241
isUpdating={isUpdating === connector.id}
249242
/>
250243
))}
251244
</div>
252-
{activeConnector && (
253-
<div className="pt-4 border-t">
254-
<div className="flex items-start gap-3">
255-
<Settings size={18} className="text-muted-foreground mt-0.5 shrink-0" />
256-
<div className="flex-1 space-y-2">
257-
<div>
258-
<p className="text-sm font-medium mb-1">
259-
{t('selfHost.connectorSettings.currentConnector.title' as any)}
260-
</p>
261-
<div className="flex items-center gap-2 mb-2">
262-
<Badge variant="secondary" className="text-xs">
263-
{activeConnector.name || activeConnector.slug}
264-
</Badge>
245+
{(() => {
246+
const currentActiveConnector = connectors.find((c) => c.id === activeConnectorId);
247+
248+
if (!currentActiveConnector) {
249+
return null;
250+
}
251+
252+
const installationUrl = `https://github.com/settings/installations/${currentActiveConnector.installation_id}`;
253+
254+
return (
255+
<div className="pt-4 border-t">
256+
<div className="flex items-start gap-3">
257+
<Settings size={18} className="text-muted-foreground mt-0.5 shrink-0" />
258+
<div className="flex-1 space-y-2">
259+
<div>
260+
<p className="text-sm font-medium mb-1">
261+
{t('selfHost.connectorSettings.currentConnector.title' as any)}
262+
</p>
263+
<div className="flex items-center gap-2 mb-2">
264+
<Badge variant="secondary" className="text-xs">
265+
{currentActiveConnector.name || currentActiveConnector.slug}
266+
</Badge>
267+
</div>
265268
</div>
269+
<p className="text-xs text-muted-foreground leading-relaxed">
270+
{t('selfHost.connectorSettings.currentConnector.description' as any)}
271+
</p>
272+
{currentActiveConnector.installation_id && (
273+
<a
274+
href={installationUrl}
275+
target="_blank"
276+
rel="noopener noreferrer"
277+
onClick={(e) => {
278+
console.log('[GitHubConnectorSettingsModal] Link clicked:', {
279+
href: installationUrl,
280+
connectorId: currentActiveConnector.id,
281+
installationId: currentActiveConnector.installation_id,
282+
activeConnectorId,
283+
timestamp: new Date().toISOString()
284+
});
285+
}}
286+
className="inline-flex items-center gap-1.5 text-xs text-primary hover:underline font-medium"
287+
>
288+
{t('selfHost.connectorSettings.currentConnector.viewOnGithub' as any)}
289+
<ExternalLink size={12} />
290+
</a>
291+
)}
266292
</div>
267-
<p className="text-xs text-muted-foreground leading-relaxed">
268-
{t('selfHost.connectorSettings.currentConnector.description' as any)}
269-
</p>
270-
{activeConnector.installation_id && (
271-
<a
272-
href={`https://github.com/settings/installations/${activeConnector.installation_id}`}
273-
target="_blank"
274-
rel="noopener noreferrer"
275-
className="inline-flex items-center gap-1.5 text-xs text-primary hover:underline font-medium"
276-
>
277-
{t('selfHost.connectorSettings.currentConnector.viewOnGithub' as any)}
278-
<ExternalLink size={12} />
279-
</a>
280-
)}
281293
</div>
282294
</div>
283-
</div>
284-
)}
295+
);
296+
})()}
285297
</>
286298
)}
287299
</div>

view/app/self-host/hooks/use-github-connector-settings.ts

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ import {
1111
import { GithubConnectorApi } from '@/redux/services/connector/githubConnectorApi';
1212
import { useAppDispatch, useAppSelector } from '@/redux/hooks';
1313
import { setActiveConnectorId } from '@/redux/features/github-connector/githubConnectorSlice';
14-
import { useAppDispatch } from '@/redux/hooks';
15-
16-
const ACTIVE_CONNECTOR_KEY = 'active_github_connector';
17-
1814

1915
function useGithubConnectorSettings() {
2016
const { t } = useTranslation();
@@ -27,10 +23,6 @@ function useGithubConnectorSettings() {
2723
(state) => state.githubConnector.activeConnectorId
2824
);
2925

30-
const [activeConnectorId, setActiveConnectorId] = React.useState<string | null>(null);
31-
const [isDeleting, setIsDeleting] = React.useState<string | null>(null);
32-
const [isUpdating, setIsUpdating] = React.useState<string | null>(null);
33-
3426
const {
3527
data: connectors,
3628
isLoading: isLoadingConnectors,
@@ -46,35 +38,17 @@ function useGithubConnectorSettings() {
4638
dispatch(setActiveConnectorId(connectors[0].id));
4739
}
4840
}, [activeConnectorId, connectors, dispatch]);
49-
// Load active connector from localStorage on mount
50-
React.useEffect(() => {
51-
const storedConnectorId = localStorage.getItem(ACTIVE_CONNECTOR_KEY);
52-
if (storedConnectorId && connectors) {
53-
const connectorExists = connectors.some((c) => c.id === storedConnectorId);
54-
if (connectorExists) {
55-
setActiveConnectorId(storedConnectorId);
56-
} else if (connectors.length > 0) {
57-
// If stored connector doesn't exist, use first available
58-
setActiveConnectorId(connectors[0].id);
59-
localStorage.setItem(ACTIVE_CONNECTOR_KEY, connectors[0].id);
60-
}
61-
} else if (connectors && connectors.length > 0 && !storedConnectorId) {
62-
// If no stored connector, use first available
63-
setActiveConnectorId(connectors[0].id);
64-
localStorage.setItem(ACTIVE_CONNECTOR_KEY, connectors[0].id);
65-
}
66-
}, [connectors]);
6741

6842
const activeConnector = React.useMemo(() => {
69-
if (!activeConnectorId || !connectors) return null;
43+
if (!activeConnectorId || !connectors) {
44+
return null;
45+
}
7046
return connectors.find((c) => c.id === activeConnectorId) || null;
7147
}, [activeConnectorId, connectors]);
7248

7349
const handleSetActiveConnector = React.useCallback(
7450
(connectorId: string) => {
7551
dispatch(setActiveConnectorId(connectorId));
76-
setActiveConnectorId(connectorId);
77-
localStorage.setItem(ACTIVE_CONNECTOR_KEY, connectorId);
7852
// Invalidate cache to refetch repositories with new connector
7953
dispatch(GithubConnectorApi.util.invalidateTags([{ type: 'GithubConnector', id: 'LIST' }]));
8054
toast.success(t('selfHost.connectorSettings.actions.switch.success' as any));
@@ -117,7 +91,10 @@ function useGithubConnectorSettings() {
11791
async (connectorId: string, installationId: string) => {
11892
setIsUpdating(connectorId);
11993
try {
120-
await updateConnector({ installation_id: installationId }).unwrap();
94+
await updateConnector({
95+
installation_id: installationId,
96+
connector_id: connectorId
97+
}).unwrap();
12198
toast.success(t('selfHost.connectorSettings.actions.update.success' as any));
12299
await refetchConnectors();
123100
// Invalidate cache to refetch repositories

view/app/self-host/hooks/use_get_deployed_applications.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,48 @@ function useGetDeployedApplications() {
162162
if (installationId) {
163163
const githubConnector = async () => {
164164
try {
165-
await updateGithubConnector({
166-
installation_id: installationId,
167-
connector_id: pendingConnectorId || undefined
168-
});
165+
// Determine which connector to update
166+
let connectorIdToUpdate = pendingConnectorId;
167+
168+
// If no pending connector ID, try to find one without installation_id
169+
if (!connectorIdToUpdate && connectors && connectors.length > 0) {
170+
const newConnector = connectors.find(
171+
(c) => !c.installation_id || c.installation_id.trim() === ''
172+
);
173+
if (newConnector) {
174+
connectorIdToUpdate = newConnector.id;
175+
} else if (connectors.length === 1) {
176+
connectorIdToUpdate = connectors[0].id;
177+
} else {
178+
// Multiple connectors exist and we can't determine which one
179+
// Use active connector as fallback
180+
connectorIdToUpdate = activeConnectorId || connectors[0].id;
181+
}
182+
}
183+
184+
// When multiple connectors exist, connector_id is required
185+
const updatePayload: { installation_id: string; connector_id?: string } = {
186+
installation_id: installationId
187+
};
188+
189+
if (connectors && connectors.length > 1) {
190+
if (!connectorIdToUpdate) {
191+
throw new Error('connector_id is required when multiple connectors exist');
192+
}
193+
updatePayload.connector_id = connectorIdToUpdate;
194+
} else if (connectorIdToUpdate) {
195+
// Include connector_id even for single connector for clarity
196+
updatePayload.connector_id = connectorIdToUpdate;
197+
}
198+
199+
await updateGithubConnector(updatePayload);
169200
await GetGithubConnectors();
170-
// Set the updated connector as active if it was the pending one
171-
if (pendingConnectorId) {
172-
dispatch(setActiveConnectorId(pendingConnectorId));
201+
202+
// Set the updated connector as active
203+
if (connectorIdToUpdate) {
204+
dispatch(setActiveConnectorId(connectorIdToUpdate));
173205
}
206+
174207
setInGitHubFlow(false);
175208
setPendingConnectorId(null);
176209
// Clean up URL parameters
@@ -187,7 +220,7 @@ function useGetDeployedApplications() {
187220
};
188221
githubConnector();
189222
}
190-
}, [installationId, pendingConnectorId, router, GetGithubConnectors, updateGithubConnector]);
223+
}, [installationId, pendingConnectorId, router, GetGithubConnectors, updateGithubConnector, connectors, activeConnectorId, dispatch]);
191224

192225
return {
193226
connectors,

view/lib/i18n/locales/fr.json

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,65 @@
12951295
"back": "Retour"
12961296
}
12971297
},
1298+
"repositories": {
1299+
"title": "Dépôts",
1300+
"noRepositories": "Aucun dépôt trouvé",
1301+
"search": {
1302+
"placeholder": "Rechercher des dépôts..."
1303+
},
1304+
"sort": {
1305+
"name": "Nom",
1306+
"created": "Créé",
1307+
"updated": "Mis à jour",
1308+
"stars": "Étoiles"
1309+
}
1310+
},
1311+
"connectorSettings": {
1312+
"title": "Paramètres du Connecteur GitHub",
1313+
"description": "Gérez vos connecteurs GitHub, basculez entre eux ou ajoutez-en de nouveaux",
1314+
"active": "Actif",
1315+
"noConnectors": "Aucun connecteur GitHub trouvé. Veuillez en créer un pour commencer.",
1316+
"connector": {
1317+
"slug": "Slug",
1318+
"installationId": "ID d'Installation"
1319+
},
1320+
"currentConnector": {
1321+
"title": "Connecteur Actuel",
1322+
"description": "Si un dépôt manque ou si vous souhaitez ajouter plus de dépôts, cliquez sur le lien ci-dessous pour gérer les paramètres de votre installation GitHub.",
1323+
"viewOnGithub": "Gérer l'Installation GitHub"
1324+
},
1325+
"actions": {
1326+
"addNew": "Ajouter un Nouveau Connecteur",
1327+
"switch": {
1328+
"label": "Changer",
1329+
"success": "Connecteur changé avec succès"
1330+
},
1331+
"delete": {
1332+
"label": "Supprimer",
1333+
"success": "Connecteur supprimé avec succès",
1334+
"error": {
1335+
"generic": "Échec de la suppression du connecteur",
1336+
"lastConnector": "Impossible de supprimer le dernier connecteur. Veuillez d'abord ajouter un autre connecteur."
1337+
},
1338+
"dialog": {
1339+
"title": "Supprimer le Connecteur",
1340+
"description": "Êtes-vous sûr de vouloir supprimer {name} ? Cette action est irréversible.",
1341+
"confirm": "Supprimer",
1342+
"cancel": "Annuler",
1343+
"deleting": "Suppression..."
1344+
}
1345+
},
1346+
"update": {
1347+
"success": "Connecteur mis à jour avec succès",
1348+
"error": "Échec de la mise à jour du connecteur"
1349+
},
1350+
"reset": {
1351+
"label": "Réinitialiser",
1352+
"info": "Ouverture des paramètres d'installation GitHub. Veuillez reconfigurer l'installation là-bas.",
1353+
"error": "Impossible d'ouvrir les paramètres GitHub. ID d'installation introuvable."
1354+
}
1355+
}
1356+
},
12981357
"repositoryCard": {
12991358
"viewOnGithub": "Voir sur GitHub",
13001359
"unnamed": "Dépôt Sans Nom",

0 commit comments

Comments
 (0)