Refactor credential check

- show tests while running (with indicator)
- incremental progress update
This commit is contained in:
Mattias Granlund 2024-03-09 21:25:11 +01:00
parent 653c831471
commit 6137d6b63a
3 changed files with 120 additions and 77 deletions

View File

@ -2,40 +2,33 @@ import { invoke } from './ipc';
export type GitCredentialCheck = {
error?: string;
name?: string;
ok: boolean;
};
export type CredentialCheckError = {
check: string;
message: string;
};
export class AuthService {
async checkGitFetch(projectId: string, remoteName: string | null | undefined) {
if (!remoteName) return { ok: false, error: 'No remote specified' };
try {
const resp = await invoke<string>('git_test_fetch', {
projectId: projectId,
remoteName
});
return { ok: !resp };
} catch (err: any) {
return { ok: false, error: err.message };
}
async checkGitFetch(projectId: string, remoteName: string) {
const resp = await invoke<string>('git_test_fetch', {
projectId: projectId,
remoteName
});
if (resp) throw new Error(resp);
return;
}
async checkGitPush(
projectId: string,
remoteName: string | null | undefined,
branchName: string | null | undefined
) {
if (!remoteName) return { ok: false, error: 'No remote specified' };
if (!branchName) return { ok: false, error: 'No branchspecified' };
try {
const resp = await invoke<string>('git_test_push', {
projectId: projectId,
remoteName,
branchName
});
return { ok: !resp };
} catch (err: any) {
return { ok: false, error: err.message };
}
async checkGitPush(projectId: string, remoteName: string, branchName: string) {
const resp = await invoke<string>('git_test_push', {
projectId: projectId,
remoteName,
branchName
});
if (resp) throw new Error(resp);
return { name: 'push', ok: true };
}
async getPublicKey() {

View File

@ -1,91 +1,122 @@
<script lang="ts">
import Button from './Button.svelte';
import Icon from './Icon.svelte';
import InfoMessage, { type MessageStyle } from './InfoMessage.svelte';
import Link from './Link.svelte';
import { slide } from 'svelte/transition';
import type { AuthService, GitCredentialCheck } from '$lib/backend/auth';
import type { AuthService } from '$lib/backend/auth';
export let authService: AuthService;
export let projectId: string;
export let remoteName: string | null | undefined;
export let branchName: string | null | undefined;
let credentialsCheck: any | undefined;
let loading = false;
$: success = true;
$: loading = false;
$: errors = 0;
$: passes = 0;
$: style = checkToStyle(loading, success, errors);
let fetchError: string | undefined;
let pushError: string | undefined;
let success = false;
type Check = { name: string; promise: Promise<any> };
$: checks = [] as Check[];
$: style = checkToStyle(credentialsCheck);
function checkToStyle(check: GitCredentialCheck | undefined): MessageStyle {
if (check?.ok) return 'success';
if (check?.error) return 'warn';
function checkToStyle(loading: boolean, success: boolean, errors: number): MessageStyle {
if (success) return 'success';
if (errors > 0) return 'warn';
if (loading) return 'neutral';
return 'neutral';
}
const isRejected = (input: PromiseSettledResult<unknown>): input is PromiseRejectedResult =>
input.status === 'rejected';
const isFulfilled = <T,>(
input: PromiseSettledResult<unknown>
): input is PromiseFulfilledResult<T> => input.status === 'fulfilled';
async function checkCredentials() {
loading = true;
success = false;
await checkPush();
await checkFetch();
loading = false;
success = true;
}
passes = 0;
errors = 0;
async function checkFetch() {
credentialsCheck = undefined;
loading = true;
credentialsCheck = await authService.checkGitFetch(projectId, remoteName);
if (credentialsCheck.error) {
fetchError = credentialsCheck.error;
if (!remoteName || !branchName) {
return;
}
loading = false;
}
async function checkPush() {
credentialsCheck = undefined;
loading = true;
credentialsCheck = await authService.checkGitPush(projectId, remoteName, branchName);
if (credentialsCheck.error) {
pushError = credentialsCheck.error;
try {
checks = [
{
name: 'Fetch',
promise: authService.checkGitFetch(projectId, remoteName).catch((reason) => {
++errors;
throw reason;
})
},
{ name: 'Push', promise: authService.checkGitPush(projectId, remoteName, branchName) }
];
const results = await Promise.allSettled(checks.map((c) => c.promise));
errors = results.filter(isRejected).map((r) => `${r.reason}`).length;
passes = results.filter(isFulfilled).map((r) => `${r.value}`).length;
setTimeout(() => (success = errors == 0), 1250);
} finally {
loading = false;
}
loading = false;
}
</script>
<div class="credential-check">
{#if success || fetchError || pushError}
<div transition:slide>
{#if checks.length > 0}
<div transition:slide={{ duration: 250 }}>
<InfoMessage {style} filled outlined={false}>
<svelte:fragment slot="title">
{#if loading}
Checking git credentials …
{:else if fetchError}
Unable to fetch
{:else if pushError}
Unable to push
{:else if errors > 0}
There was a problem with your credentials
{:else}
All checks passed successfully
{/if}
</svelte:fragment>
<svelte:fragment>
{#if fetchError}
We were unable to fetch from the remote, please check your authentication settings.
<Link href="https://docs.gitbutler.com/troubleshooting/fetch-push">Learn more</Link>.
{fetchError}
{:else if pushError}
We were unable to push to the remote, please check your authentication settings.
<Link href="https://docs.gitbutler.com/troubleshooting/fetch-push">Learn more</Link>.
{pushError}
<svelte:fragment slot="content">
{#if loading || (checks.length > 0 && (errors > 0 || (errors == 0 && passes == 0)))}
<div class="checks-list" transition:slide={{ duration: 250, delay: 1000 }}>
{#each checks as check}
<div class="check-result">
{#await check.promise}
<Icon name="spinner" spinnerRadius={3.5} />
{:then}
<Icon name="success-small" color="success" />
{:catch}
<Icon name="error-small" color="error" />
{/await}
{check.name}
{#await check.promise catch err}
- {err}
{/await}
</div>
{/each}
</div>
{/if}
{#if errors > 0}
<div class="help-text" transition:slide>
Try another setting and test again? You can also refer to our
<Link href="https://docs.gitbutler.com/troubleshooting/fetch-push">
fetch & pull documentation
</Link>
for help fixing this problem.
</div>
{/if}
</svelte:fragment>
</InfoMessage>
</div>
{/if}
<Button wide icon="test" {loading} disabled={loading} on:click={checkCredentials}>
Test credentials
<Button wide icon="test" disabled={loading} on:click={checkCredentials}>
{#if loading || checks.length == 0}
Test credentials
{:else}
Re-test credentials
{/if}
</Button>
<div class="disclaimer">
To test the push command, we create an empty branch and promptly remove it after the check. <Link
@ -101,6 +132,21 @@
gap: var(--space-16);
}
.checks-list {
display: flex;
flex-direction: column;
gap: var(--space-6);
padding-left: var(--space-4);
margin-top: var(--space-12);
}
.check-result {
display: flex;
gap: var(--space-6);
}
.help-text {
margin-top: var(--space-6);
}
.disclaimer {
color: var(--clr-theme-scale-ntrl-50);
background: var(--clr-theme-container-pale);

View File

@ -67,7 +67,11 @@
{/if}
</div>
{/if}
<div class="info-message__text text-base-body-12"><slot /></div>
{#if SLOTS.content}
<slot name="content" />
{:else}
<div class="info-message__text text-base-body-12"><slot /></div>
{/if}
</div>
{#if primary || secondary}
<div class="info-message__actions">