Provisioning: Replace manual GitHub app refresh with real-time watch (#117312)

* Provisioning: Replace manual GitHub app connection refresh with real-time watch

- Use useConnectionStatus hook with k8s watch for real-time connection status updates
- Remove manual sync/refresh button from connection dropdown
- Add creating state feedback to create connection button
- Only fetch repos for ready connections to avoid API errors

* Use RTK Query loading state instead of manual useState

* i18n
This commit is contained in:
Alex Khomenko 2026-02-03 17:26:22 +02:00 committed by GitHub
parent 37efd9fc8f
commit 23d6e3dba2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 31 additions and 30 deletions

View file

@ -1,12 +1,10 @@
import { skipToken } from '@reduxjs/toolkit/query';
import { useMemo } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { t } from '@grafana/i18n';
import { Field, RadioButtonGroup, Stack } from '@grafana/ui';
import { useConnectionList } from '../hooks/useConnectionList';
import { isConnectionReady } from '../utils/connectionStatus';
import { useConnectionStatus } from '../hooks/useConnectionStatus';
import { GitHubAppFields } from './GitHubAppFields';
import { RepositoryField } from './components/RepositoryField';
@ -55,13 +53,9 @@ export function AuthTypeStep({ onGitHubAppSubmit }: AuthTypeStepProps) {
const authTypeOptions = useMemo(() => getAuthTypeOptions(), []);
const shouldShowRepositories = githubAuthType !== 'github-app' || githubAppMode !== 'new';
const shouldFetchConnections = githubAuthType === 'github-app';
const [connections] = useConnectionList(shouldFetchConnections ? {} : skipToken);
const isSelectedConnectionReady = useMemo(() => {
const selectedConnection = connections?.find((c) => c.metadata?.name === githubAppConnectionName);
return isConnectionReady(selectedConnection?.status);
}, [connections, githubAppConnectionName]);
const { isConnected: isSelectedConnectionReady } = useConnectionStatus(
githubAuthType === 'github-app' ? githubAppConnectionName : undefined
);
return (
<Stack direction="column" gap={2}>

View file

@ -2,16 +2,16 @@ import { Controller, FormProvider, useForm, useFormContext } from 'react-hook-fo
import { Trans, t } from '@grafana/i18n';
import { isFetchError } from '@grafana/runtime';
import { Alert, Combobox, Field, IconButton, RadioButtonGroup, Stack } from '@grafana/ui';
import { Alert, Combobox, Field, RadioButtonGroup, Stack } from '@grafana/ui';
import { ConnectionSpec } from 'app/api/clients/provisioning/v0alpha1';
import { extractErrorMessage } from 'app/api/utils';
import { ConnectionStatusBadge } from '../Connection/ConnectionStatusBadge';
import { GitHubConnectionFields } from '../components/Shared/GitHubConnectionFields';
import { useConnectionOptions } from '../hooks/useConnectionOptions';
import { useConnectionStatus } from '../hooks/useConnectionStatus';
import { useCreateOrUpdateConnection } from '../hooks/useCreateOrUpdateConnection';
import { ConnectionFormData } from '../types';
import { isConnectionReady } from '../utils/connectionStatus';
import { getConnectionFormErrors } from '../utils/getFormErrors';
import { useStepStatus } from './StepStatusContext';
@ -43,17 +43,17 @@ export function GitHubAppFields({ onGitHubAppSubmit }: GitHubAppFieldsProps) {
},
});
const [createConnection] = useCreateOrUpdateConnection();
const [createConnection, connectionRequest] = useCreateOrUpdateConnection();
const {
options: connectionOptions,
isLoading,
connections: githubConnections,
error: connectionListError,
refetch: refetchConnections,
} = useConnectionOptions(true);
const [githubAppMode, githubAppConnectionName] = watch(['githubAppMode', 'githubApp.connectionName']);
const selectedConnection = githubConnections.find((c) => c.metadata?.name === githubAppConnectionName);
const { connection: selectedConnection } = useConnectionStatus(githubAppConnectionName);
const handleCreateConnection = async () => {
// Reset any existing step errors
setStepStatusInfo({ status: 'idle' });
@ -191,15 +191,6 @@ export function GitHubAppFields({ onGitHubAppSubmit }: GitHubAppFieldsProps) {
<Stack>
<Trans i18nKey="provisioning.wizard.github-app-connection-status">Connection status:</Trans>
<ConnectionStatusBadge status={selectedConnection.status} />
{!isConnectionReady(selectedConnection?.status) && (
<IconButton
aria-label={t('provisioning.wizard.github-app-sync-connection', 'Sync Connection')}
key="syncConnection"
name="sync"
onClick={refetchConnections}
disabled={isLoading}
/>
)}
</Stack>
)}
</Stack>
@ -211,7 +202,11 @@ export function GitHubAppFields({ onGitHubAppSubmit }: GitHubAppFieldsProps) {
{githubAppMode === 'new' && (
<FormProvider {...credentialForm}>
<GitHubConnectionFields required onNewConnectionCreation={handleCreateConnection} />
<GitHubConnectionFields
required
onNewConnectionCreation={handleCreateConnection}
isCreating={connectionRequest.isLoading}
/>
</FormProvider>
)}
</Stack>

View file

@ -12,10 +12,12 @@ export interface GitHubConnectionFieldsProps {
/** Initial value for whether private key is configured (edit mode) */
privateKeyConfigured?: boolean;
onNewConnectionCreation?: () => void;
/** Whether the connection is currently being created */
isCreating?: boolean;
}
export const GitHubConnectionFields = memo<GitHubConnectionFieldsProps>(
({ required = true, privateKeyConfigured = false, onNewConnectionCreation }) => {
({ required = true, privateKeyConfigured = false, onNewConnectionCreation, isCreating = false }) => {
const [isPrivateKeyConfigured, setIsPrivateKeyConfigured] = useState(privateKeyConfigured);
const {
register,
@ -140,8 +142,12 @@ export const GitHubConnectionFields = memo<GitHubConnectionFieldsProps>(
{onNewConnectionCreation && (
<Stack>
<Button onClick={onNewConnectionCreation}>
<Trans i18nKey="provisioning.connection-form.create-new-connection-button">Create connection</Trans>
<Button onClick={onNewConnectionCreation} disabled={isCreating}>
{isCreating ? (
<Trans i18nKey="provisioning.connection-form.creating-connection-button">Creating connection...</Trans>
) : (
<Trans i18nKey="provisioning.connection-form.create-new-connection-button">Create connection</Trans>
)}
</Button>
</Stack>
)}

View file

@ -6,6 +6,7 @@ import { t } from '@grafana/i18n';
import { useLazyGetConnectionRepositoriesQuery } from 'app/api/clients/provisioning/v0alpha1';
import { ExternalRepository } from '../types';
import { isConnectionReady } from '../utils/connectionStatus';
import { formatRepoUrl } from '../utils/git';
import { useConnectionList } from './useConnectionList';
@ -14,8 +15,13 @@ export function useConnectionOptions(enabled: boolean) {
const [connections, connectionsLoading, error, refetch] = useConnectionList(enabled ? {} : skipToken);
const githubConnections = useMemo(() => connections?.filter((c) => c.spec?.type === 'github') ?? [], [connections]);
// Only fetch repos for ready connections
const connectionNames = useMemo(
() => githubConnections.map((conn) => conn.metadata?.name).filter((name): name is string => Boolean(name)),
() =>
githubConnections
.filter((c) => isConnectionReady(c.status))
.map((conn) => conn.metadata?.name)
.filter((name): name is string => Boolean(name)),
[githubConnections]
);

View file

@ -11939,6 +11939,7 @@
"button-save": "Save",
"button-saving": "Saving...",
"create-new-connection-button": "Create connection",
"creating-connection-button": "Creating connection...",
"description-app-id": "The ID of your GitHub App",
"description-description": "Optional description for this connection",
"description-installation-id": "The installation ID of your GitHub App",
@ -12474,7 +12475,6 @@
"github-app-no-connections": "No GitHub connections found",
"github-app-no-connections-message": "You don't have any existing GitHub app connections. Please select \"Connect to a new app\" to create one.",
"github-app-select-connection": "Select a GitHub App connection",
"github-app-sync-connection": "Sync Connection",
"step-bootstrap": "Choose what to synchronize",
"step-configure-repo": "Configure repository",
"step-connect": "Connect",