🔨 chore: update PrService to support injecting and retrieving additional Pull Requests

This commit is contained in:
Mattias Granlund 2023-11-28 22:07:45 +01:00
parent 2c08ae499a
commit cc9a6a5b70
7 changed files with 127 additions and 107 deletions

View File

@ -1,6 +1,14 @@
import lscache from 'lscache'; import lscache from 'lscache';
import { Observable, EMPTY, BehaviorSubject, of } from 'rxjs'; import { Observable, EMPTY, BehaviorSubject, of } from 'rxjs';
import { catchError, combineLatestWith, shareReplay, switchMap, tap } from 'rxjs/operators'; import {
catchError,
combineLatestWith,
find,
map,
shareReplay,
switchMap,
tap
} from 'rxjs/operators';
import { import {
type PullRequest, type PullRequest,
@ -13,6 +21,7 @@ export class PrService {
prs$: Observable<PullRequest[]>; prs$: Observable<PullRequest[]>;
error$ = new BehaviorSubject<string | undefined>(undefined); error$ = new BehaviorSubject<string | undefined>(undefined);
private reload$ = new BehaviorSubject<void>(undefined); private reload$ = new BehaviorSubject<void>(undefined);
private inject$ = new BehaviorSubject<PullRequest | undefined>(undefined);
constructor(ghContext$: Observable<GitHubIntegrationContext | undefined>) { constructor(ghContext$: Observable<GitHubIntegrationContext | undefined>) {
this.prs$ = ghContext$.pipe( this.prs$ = ghContext$.pipe(
@ -23,6 +32,11 @@ export class PrService {
return loadPrs(ctx); return loadPrs(ctx);
}), }),
shareReplay(1), shareReplay(1),
combineLatestWith(this.inject$),
map(([prs, inject]) => {
if (inject) return prs.concat(inject);
return prs;
}),
catchError((err) => { catchError((err) => {
console.log(err); console.log(err);
this.error$.next(err); this.error$.next(err);
@ -34,6 +48,13 @@ export class PrService {
reload(): void { reload(): void {
this.reload$.next(); this.reload$.next();
} }
add(pr: PullRequest) {
this.inject$.next(pr);
}
get(branch: string | undefined): Observable<PullRequest | undefined> | undefined {
if (!branch) return;
return this.prs$.pipe(map((prs) => prs.find((pr) => pr.sourceBranch == branch)));
}
} }
function loadPrs(ctx: GitHubIntegrationContext): Observable<PullRequest[]> { function loadPrs(ctx: GitHubIntegrationContext): Observable<PullRequest[]> {

View File

@ -14,6 +14,7 @@
$: projectId = data.projectId; $: projectId = data.projectId;
$: base$ = baseBranchService.base$; $: base$ = baseBranchService.base$;
$: user$ = data.user$; $: user$ = data.user$;
$: prService = data.prService;
$: project$ = data.project$; $: project$ = data.project$;
$: branches$ = vbranchService.branches$; $: branches$ = vbranchService.branches$;
@ -59,6 +60,7 @@
githubContext={$githubContext$} githubContext={$githubContext$}
branchesError={$error$} branchesError={$error$}
user={$user$} user={$user$}
{prService}
/> />
</div> </div>
</div> </div>

View File

@ -7,6 +7,7 @@
import { open } from '@tauri-apps/api/shell'; import { open } from '@tauri-apps/api/shell';
import { IconFile, IconTerminal, IconExternalLink } from '$lib/icons'; import { IconFile, IconTerminal, IconExternalLink } from '$lib/icons';
import type { GitHubIntegrationContext } from '$lib/github/types'; import type { GitHubIntegrationContext } from '$lib/github/types';
import type { PrService } from '$lib/github/pullrequest';
export let projectId: string; export let projectId: string;
export let projectPath: string; export let projectPath: string;
@ -19,6 +20,7 @@
export let cloudEnabled: boolean; export let cloudEnabled: boolean;
export let cloud: ReturnType<typeof getCloudApiClient>; export let cloud: ReturnType<typeof getCloudApiClient>;
export let branchController: BranchController; export let branchController: BranchController;
export let prService: PrService;
export let githubContext: GitHubIntegrationContext | undefined; export let githubContext: GitHubIntegrationContext | undefined;
export let user: User | undefined; export let user: User | undefined;
@ -101,6 +103,7 @@
{githubContext} {githubContext}
{projectPath} {projectPath}
{user} {user}
{prService}
/> />
</div> </div>
{/each} {/each}

View File

@ -22,15 +22,13 @@
import CommitDialog from './CommitDialog.svelte'; import CommitDialog from './CommitDialog.svelte';
import { writable, type Writable } from 'svelte/store'; import { writable, type Writable } from 'svelte/store';
import { computedAddedRemoved } from '$lib/vbranches/fileStatus'; import { computedAddedRemoved } from '$lib/vbranches/fileStatus';
import { getPullRequestByBranch, createPullRequest } from '$lib/github/pullrequest'; import type { PrService } from '$lib/github/pullrequest';
import type { GitHubIntegrationContext } from '$lib/github/types'; import type { GitHubIntegrationContext } from '$lib/github/types';
import { isDraggableRemoteCommit, type DraggableRemoteCommit } from '$lib/draggables'; import { isDraggableRemoteCommit, type DraggableRemoteCommit } from '$lib/draggables';
import BranchHeader from './BranchHeader.svelte'; import BranchHeader from './BranchHeader.svelte';
import UpstreamCommits from './UpstreamCommits.svelte'; import UpstreamCommits from './UpstreamCommits.svelte';
import BranchFiles from './BranchFiles.svelte'; import BranchFiles from './BranchFiles.svelte';
import LocalCommits from './LocalCommits.svelte'; import LocalCommits from './LocalCommits.svelte';
import RemoteCommits from './RemoteCommits.svelte';
import IntegratedCommits from './IntegratedCommits.svelte';
const [send, receive] = crossfade({ const [send, receive] = crossfade({
duration: (d) => Math.sqrt(d * 200), duration: (d) => Math.sqrt(d * 200),
@ -62,6 +60,7 @@
export let githubContext: GitHubIntegrationContext | undefined; export let githubContext: GitHubIntegrationContext | undefined;
export let user: User | undefined; export let user: User | undefined;
export let selectedFileId: Writable<string | undefined>; export let selectedFileId: Writable<string | undefined>;
export let prService: PrService;
const userSettings = getContext<SettingsStore>(SETTINGS_CONTEXT); const userSettings = getContext<SettingsStore>(SETTINGS_CONTEXT);
@ -74,29 +73,6 @@
const laneWidthKey = 'laneWidth:'; const laneWidthKey = 'laneWidth:';
let laneWidth: number; let laneWidth: number;
$: prPromise =
githubContext && branch.upstream
? getPullRequestByBranch(githubContext, branch.upstream?.name.split('/').slice(-1)[0])
: undefined;
$: branchName = branch.upstream?.name.split('/').slice(-1)[0];
async function createPr() {
if (githubContext && base?.branchName && branchName) {
console.log('creating pr');
const pr = await createPullRequest(
githubContext,
branchName,
base.branchName.split('/').slice(-1)[0],
branch.name,
branch.notes
);
console.log(pr);
prPromise = Promise.resolve(pr);
return pr;
}
}
$: { $: {
// On refresh we need to check expansion status from localStorage // On refresh we need to check expansion status from localStorage
branch.files && expandFromCache(); branch.files && expandFromCache();
@ -355,7 +331,6 @@
{base} {base}
{send} {send}
{receive} {receive}
{prPromise}
{githubContext} {githubContext}
{projectId} {projectId}
{branchController} {branchController}
@ -364,7 +339,7 @@
{onAmend} {onAmend}
{onSquash} {onSquash}
{resetHeadCommit} {resetHeadCommit}
{createPr} {prService}
type="local" type="local"
/> />
<LocalCommits <LocalCommits
@ -372,7 +347,6 @@
{base} {base}
{send} {send}
{receive} {receive}
{prPromise}
{githubContext} {githubContext}
{projectId} {projectId}
{branchController} {branchController}
@ -381,7 +355,7 @@
{onAmend} {onAmend}
{onSquash} {onSquash}
{resetHeadCommit} {resetHeadCommit}
{createPr} {prService}
type="remote" type="remote"
/> />
<LocalCommits <LocalCommits
@ -389,7 +363,6 @@
{base} {base}
{send} {send}
{receive} {receive}
{prPromise}
{githubContext} {githubContext}
{projectId} {projectId}
{branchController} {branchController}
@ -398,7 +371,7 @@
{onAmend} {onAmend}
{onSquash} {onSquash}
{resetHeadCommit} {resetHeadCommit}
{createPr} {prService}
type="integrated" type="integrated"
/> />
{/if} {/if}

View File

@ -7,6 +7,7 @@
import FileCard from './FileCard.svelte'; import FileCard from './FileCard.svelte';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { Ownership } from '$lib/vbranches/ownership'; import { Ownership } from '$lib/vbranches/ownership';
import type { PrService } from '$lib/github/pullrequest';
export let branch: Branch; export let branch: Branch;
export let readonly = false; export let readonly = false;
@ -20,6 +21,7 @@
export let githubContext: GitHubIntegrationContext | undefined; export let githubContext: GitHubIntegrationContext | undefined;
export let user: User | undefined; export let user: User | undefined;
export let projectPath: string; export let projectPath: string;
export let prService: PrService;
const selectedOwnership = writable(Ownership.fromBranch(branch)); const selectedOwnership = writable(Ownership.fromBranch(branch));
const selectedFileId = writable<string | undefined>(undefined); const selectedFileId = writable<string | undefined>(undefined);
@ -48,6 +50,7 @@
{githubContext} {githubContext}
{user} {user}
{selectedFileId} {selectedFileId}
{prService}
/> />
{#if selected} {#if selected}

View File

@ -18,21 +18,21 @@
import { open } from '@tauri-apps/api/shell'; import { open } from '@tauri-apps/api/shell';
import toast from 'svelte-french-toast'; import toast from 'svelte-french-toast';
import { sleep } from '$lib/utils/sleep'; import { sleep } from '$lib/utils/sleep';
import { createPullRequest, type PrService } from '$lib/github/pullrequest';
export let branch: Branch; export let branch: Branch;
export let githubContext: GitHubIntegrationContext | undefined; export let githubContext: GitHubIntegrationContext | undefined;
export let base: BaseBranch | undefined | null; export let base: BaseBranch | undefined | null;
export let prPromise: Promise<PullRequest | undefined> | undefined;
export let projectId: string; export let projectId: string;
export let branchController: BranchController; export let branchController: BranchController;
export let type: CommitType; export let type: CommitType;
export let prService: PrService;
export let acceptAmend: (commit: Commit) => (data: any) => boolean; export let acceptAmend: (commit: Commit) => (data: any) => boolean;
export let acceptSquash: (commit: Commit) => (data: any) => boolean; export let acceptSquash: (commit: Commit) => (data: any) => boolean;
export let onAmend: (data: DraggableFile | DraggableHunk) => void; export let onAmend: (data: DraggableFile | DraggableHunk) => void;
export let onSquash: (commit: Commit) => (data: DraggableCommit) => void; export let onSquash: (commit: Commit) => (data: DraggableCommit) => void;
export let resetHeadCommit: () => void; export let resetHeadCommit: () => void;
export let createPr: () => Promise<PullRequest | undefined>;
export let receive: ( export let receive: (
node: any, node: any,
@ -50,6 +50,7 @@
let isPushing: boolean; let isPushing: boolean;
$: branchName = branch.upstream?.name.split('/').slice(-1)[0];
$: headCommit = branch.commits[0]; $: headCommit = branch.commits[0];
$: commits = branch.commits.filter((c) => { $: commits = branch.commits.filter((c) => {
switch (type) { switch (type) {
@ -61,6 +62,7 @@
return c.isIntegrated; return c.isIntegrated;
} }
}); });
$: pr$ = prService.get(branchName);
async function push(opts?: { createPr: boolean }) { async function push(opts?: { createPr: boolean }) {
isPushing = true; isPushing = true;
@ -80,6 +82,23 @@
return `${target.repoBaseUrl}/compare/${baseBranchName}...${branchName}`; return `${target.repoBaseUrl}/compare/${baseBranchName}...${branchName}`;
} }
async function createPr() {
if (githubContext && base?.branchName && branchName) {
const pr = await createPullRequest(
githubContext,
branchName,
base.branchName.split('/').slice(-1)[0],
branch.name,
branch.notes
);
if (pr) {
prService.add(pr);
prService.reload();
}
return pr;
}
}
let expanded = true; let expanded = true;
</script> </script>
@ -99,22 +118,21 @@
> >
{branch.upstream.name.split('refs/remotes/')[1]} {branch.upstream.name.split('refs/remotes/')[1]}
</Link> </Link>
{#await prPromise then pr} {#if $pr$?.htmlUrl}
{#if pr?.htmlUrl} <Tag
<Tag icon="pr-small"
icon="pr-small" color="neutral-light"
color="neutral-light" clickable
clickable on:click={(e) => {
on:click={(e) => { const url = $pr$?.htmlUrl;
open(pr?.htmlUrl); if (url) open(url);
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
}} }}
> >
PR PR
</Tag> </Tag>
{/if} {/if}
{/await}
{/if} {/if}
{:else if type == 'integrated'} {:else if type == 'integrated'}
Integrated Integrated
@ -138,73 +156,71 @@
{commit} {commit}
{base} {base}
{projectId} {projectId}
isChained={idx != commits.length - 1}
isHeadCommit={commit.id === headCommit?.id}
{acceptAmend} {acceptAmend}
{acceptSquash} {acceptSquash}
{onAmend} {onAmend}
{onSquash} {onSquash}
{resetHeadCommit} {resetHeadCommit}
isChained={idx != commits.length - 1}
isHeadCommit={commit.id === headCommit?.id}
/> />
</div> </div>
{/each} {/each}
</div> </div>
{#if type != 'integrated'} {#if type != 'integrated'}
<div class="actions"> <div class="actions">
{#await prPromise then pr} {#if githubContext && !$pr$ && type == 'local'}
{#if githubContext && !pr && type == 'local'} <PushButton
<PushButton wide
wide isLoading={isPushing}
isLoading={isPushing} {projectId}
{projectId} {githubContext}
{githubContext} on:trigger={async (e) => {
on:trigger={async (e) => { try {
try { await push({ createPr: e.detail.with_pr });
await push({ createPr: e.detail.with_pr }); } catch {
} catch { toast.error('Failed to create pull qequest');
toast.error('Failed to create pull qequest'); }
} }}
}} />
/> {:else if githubContext && !$pr$ && type == 'remote'}
{:else if githubContext && !pr && type == 'remote'} <Button
<Button wide
wide kind="outlined"
kind="outlined" color="primary"
color="primary" id="push-commits"
id="push-commits" loading={isPushing}
loading={isPushing} on:click={async () => {
on:click={async () => { try {
try { await push({ createPr: true });
await push({ createPr: true }); } catch (e) {
} catch (e) { toast.error('Failed to create pull qequest');
toast.error('Failed to create pull qequest'); }
} }}
}} >
> Create Pull Request
Create Pull Request </Button>
</Button> {:else if type == 'local'}
{:else if type == 'local'} <Button
<Button kind="outlined"
kind="outlined" color="primary"
color="primary" id="push-commits"
id="push-commits" loading={isPushing}
loading={isPushing} on:click={async () => {
on:click={async () => { try {
try { await push();
await push(); } catch {
} catch { toast.error('Failed to push');
toast.error('Failed to push'); }
} }}
}} >
> {#if branch.requiresForce}
{#if branch.requiresForce} Force Push
Force Push {:else}
{:else} Push
Push {/if}
{/if} </Button>
</Button> {/if}
{/if}
{/await}
</div> </div>
{/if} {/if}
</div> </div>

View File

@ -27,6 +27,7 @@
$: branches$ = vbranchService.branches$; $: branches$ = vbranchService.branches$;
$: error$ = vbranchService.branchesError$; $: error$ = vbranchService.branchesError$;
$: branch = $branches$?.find((b) => b.id == $page.params.branchId); $: branch = $branches$?.find((b) => b.id == $page.params.branchId);
$: prService = data.prService;
function applyBranch(branch: Branch) { function applyBranch(branch: Branch) {
if (!branch.isMergeable) { if (!branch.isMergeable) {
@ -80,6 +81,7 @@
githubContext={$githubContext$} githubContext={$githubContext$}
user={$user$} user={$user$}
projectPath={$project$.path} projectPath={$project$.path}
{prService}
/> />
</div> </div>
{:else} {:else}