Stack Headers: Use new PR button

The new PR headers use the new PR details modal
This commit is contained in:
estib 2024-10-04 14:22:25 +02:00
parent 5cc83a4fdb
commit 718fa8d2ff
4 changed files with 62 additions and 141 deletions

View File

@ -2,26 +2,19 @@
import BranchLabel from './BranchLabel.svelte';
import StackingStatusIcon from './StackingStatusIcon.svelte';
import { getColorFromBranchType } from './stackingUtils';
import { Project } from '$lib/backend/projects';
import { BaseBranch } from '$lib/baseBranch/baseBranch';
import { BaseBranchService } from '$lib/baseBranch/baseBranchService';
import StackingBranchHeaderContextMenu from '$lib/branch/StackingBranchHeaderContextMenu.svelte';
import ContextMenu from '$lib/components/contextmenu/ContextMenu.svelte';
import { mapErrorToToast } from '$lib/gitHost/github/errorMap';
import { getGitHost } from '$lib/gitHost/interface/gitHost';
import { getGitHostListingService } from '$lib/gitHost/interface/gitHostListingService';
import { getGitHostPrService } from '$lib/gitHost/interface/gitHostPrService';
import { showError, showToast } from '$lib/notifications/toasts';
import PullRequestButton from '$lib/pr/PullRequestButton.svelte';
import PrDetailsModal from '$lib/pr/PrDetailsModal.svelte';
import StackingPullRequestCard from '$lib/pr/StackingPullRequestCard.svelte';
import { getContext, getContextStore } from '$lib/utils/context';
import { sleep } from '$lib/utils/sleep';
import { error } from '$lib/utils/toasts';
import { openExternalUrl } from '$lib/utils/url';
import { BranchController } from '$lib/vbranches/branchController';
import { DetailedCommit, VirtualBranch, type CommitStatus } from '$lib/vbranches/types';
import Button from '@gitbutler/ui/Button.svelte';
import type { PullRequest } from '$lib/gitHost/interface/types';
interface Props {
name: string;
@ -31,7 +24,6 @@
const { name, upstreamName, commits }: Props = $props();
let isLoading = $state(false);
let descriptionVisible = $state(false);
const branchStore = getContextStore(VirtualBranch);
@ -39,16 +31,12 @@
const branchController = getContext(BranchController);
const baseBranch = getContextStore(BaseBranch);
const baseBranchService = getContext(BaseBranchService);
const prService = getGitHostPrService();
const gitListService = getGitHostListingService();
const gitHost = getGitHost();
const gitHostBranch = $derived(upstreamName ? $gitHost?.branch(upstreamName) : undefined);
const project = getContext(Project);
const baseBranchName = $derived($baseBranch.shortName);
let contextMenu = $state<ReturnType<typeof ContextMenu>>();
let prDetailsModal = $state<ReturnType<typeof PrDetailsModal>>();
let meatballButtonEl = $state<HTMLDivElement>();
const branchColorType = $derived<CommitStatus>(branch.commits?.[0]?.status ?? 'local');
@ -66,95 +54,8 @@
const prMonitor = $derived(prNumber ? $prService?.prMonitor(prNumber) : undefined);
const pr = $derived(prMonitor?.pr);
interface CreatePrOpts {
draft: boolean;
}
const defaultPrOpts: CreatePrOpts = {
draft: true
};
async function createPr(createPrOpts: CreatePrOpts): Promise<PullRequest | undefined> {
const opts = { ...defaultPrOpts, ...createPrOpts };
if (!$gitHost) {
error('Pull request service not available');
return;
}
let title: string;
let body: string;
let pullRequestTemplateBody: string | undefined;
const prTemplatePath = project.git_host.pullRequestTemplatePath;
if (prTemplatePath) {
pullRequestTemplateBody = await $prService?.pullRequestTemplateContent(
prTemplatePath,
project.id
);
}
if (pullRequestTemplateBody) {
title = name;
body = pullRequestTemplateBody;
} else {
// In case of a single commit, use the commit summary and description for the title and
// description of the PR.
if (commits.length === 1) {
const commit = commits[0];
title = commit?.descriptionTitle ?? '';
body = commit?.descriptionBody ?? '';
} else {
title = name;
body = '';
}
}
isLoading = true;
try {
let upstreamBranchName: string | undefined = upstreamName;
if (commits.some((c) => !c.isRemote)) {
const firstPush = !branch.upstream;
await branchController.pushBranch(branch.id, branch.requiresForce, true);
if (firstPush) {
// TODO: fix this hack for reactively available prService.
await sleep(500);
}
}
if (!baseBranchName) {
error('No base branch name determined');
return;
}
if (!upstreamBranchName) {
error('No upstream branch name determined');
return;
}
if (!$prService) {
error('Pull request service not available');
return;
}
await $prService.createPr({
title,
body,
draft: opts.draft,
baseBranchName,
upstreamName: upstreamBranchName
});
} catch (err: any) {
console.error(err);
const toast = mapErrorToToast(err);
if (toast) showToast(toast);
else showError('Error while creating pull request', err);
} finally {
isLoading = false;
}
await $gitListService?.refresh();
baseBranchService.fetchFromRemotes();
function handleOpenPR() {
prDetailsModal?.show();
}
function editTitle(title: string) {
@ -221,19 +122,19 @@
{#if $pr}
<StackingPullRequestCard pr={$pr} {prMonitor} sourceBranch={$pr.sourceBranch} />
{:else}
<PullRequestButton
click={async ({ draft }) => await createPr({ draft })}
<Button
style="ghost"
outline
disabled={commits.length === 0 || !$gitHost || !$prService}
tooltip={!$gitHost || !$prService
? 'You can enable git host integration in the settings'
: ''}
loading={isLoading}
/>
onclick={handleOpenPR}>Start a PR</Button
>
{/if}
</div>
</div>
</div>
<PrDetailsModal bind:this={prDetailsModal} type="preview-series" {upstreamName} {name} {commits} />
<style lang="postcss">
.branch-header {
display: flex;

View File

@ -30,7 +30,7 @@
import { sleep } from '$lib/utils/sleep';
import { error } from '$lib/utils/toasts';
import { BranchController } from '$lib/vbranches/branchController';
import { VirtualBranch } from '$lib/vbranches/types';
import { DetailedCommit, VirtualBranch } from '$lib/vbranches/types';
import Button from '@gitbutler/ui/Button.svelte';
import Checkbox from '@gitbutler/ui/Checkbox.svelte';
import Modal from '@gitbutler/ui/Modal.svelte';
@ -38,7 +38,7 @@
import type { DetailedPullRequest, PullRequest } from '$lib/gitHost/interface/types';
interface BaseProps {
type: 'display' | 'preview';
type: 'display' | 'preview' | 'preview-series';
}
interface DisplayProps extends BaseProps {
@ -50,7 +50,14 @@
type: 'preview';
}
type Props = DisplayProps | PreviewProps;
interface PreviewSeriesProps {
type: 'preview-series';
name: string;
upstreamName?: string;
commits: DetailedCommit[];
}
type Props = DisplayProps | PreviewProps | PreviewSeriesProps;
let props: Props = $props();
@ -68,6 +75,11 @@
const preferredPRAction = getPreferredPRAction();
const branch = $derived($branchStore);
const branchName = $derived(props.type === 'preview-series' ? props.name : branch.name);
const commits = $derived(props.type === 'preview-series' ? props.commits : branch.commits);
const upstreamName = $derived(
props.type === 'preview-series' ? props.upstreamName : branch.upstreamName
);
const baseBranchName = $derived($baseBranch.shortName);
const prTemplatePath = $derived(project.git_host.pullRequestTemplatePath);
const isDraft = $derived<boolean>($preferredPRAction === PRAction.CreateDraft);
@ -88,11 +100,11 @@
const defaultTitle: string = $derived.by(() => {
if (props.type === 'display') return props.pr.title;
// In case of a single commit, use the commit summary for the title
if (branch.commits.length === 1) {
const commit = branch.commits[0];
if (commits.length === 1) {
const commit = commits[0];
return commit?.descriptionTitle ?? '';
} else {
return branch.name;
return branchName;
}
});
@ -100,8 +112,8 @@
if (props.type === 'display') return props.pr.body ?? '';
if (pullRequestTemplateBody) return pullRequestTemplateBody;
// In case of a single commit, use the commit description for the body
if (branch.commits.length === 1) {
const commit = branch.commits[0];
if (commits.length === 1) {
const commit = commits[0];
return commit?.descriptionBody ?? '';
} else {
return '';
@ -142,15 +154,19 @@
isLoading = true;
try {
let upstreamBranchName = branch.upstreamName;
let upstreamBranchName = upstreamName;
if (branch.commits.some((c) => !c.isRemote)) {
if (commits.some((c) => !c.isRemote)) {
const firstPush = !branch.upstream;
const { refname, remote } = await branchController.pushBranch(
const pushResult = await branchController.pushBranch(
branch.id,
branch.requiresForce
branch.requiresForce,
props.type === 'preview-series'
);
upstreamBranchName = getBranchNameFromRef(refname, remote);
if (pushResult) {
upstreamBranchName = getBranchNameFromRef(pushResult.refname, pushResult.remote);
}
if (firstPush) {
// TODO: fix this hack for reactively available prService.
@ -192,9 +208,9 @@
baseBranchService.fetchFromRemotes();
}
function handleCreatePR(close: () => void) {
if (props.type !== 'preview') return;
createPr({
async function handleCreatePR(close: () => void) {
if (props.type === 'display') return;
await createPr({
title: actualTitle,
body: actualBody,
draft: isDraft
@ -212,7 +228,7 @@
}
function toggleEdit() {
if (props.type !== 'preview') return;
if (props.type === 'display') return;
isEditing = !isEditing;
}
@ -228,7 +244,7 @@
title: actualTitle,
body: actualBody,
directive: aiDescriptionDirective,
commitMessages: branch.commits.map((c) => c.description),
commitMessages: commits.map((c) => c.description),
prBodyTemplate: pullRequestTemplateBody,
userToken: $user.access_token,
onToken: (t) => {
@ -387,7 +403,7 @@
</div>
{/if}
<div class="pr-modal__button-wrapper">
{#if props.type === 'preview'}
{#if props.type === 'preview' || props.type === 'preview-series'}
<div class="pr-modal__checkbox-wrapper">
<Checkbox name="is-draft" small checked={isDraft} onchange={handleCheckDraft} />
<label class="text-13" for="is-draft">Draft</label>
@ -402,7 +418,7 @@
kind="solid"
disabled={isEditing || isLoading || aiIsLoading}
{isLoading}
onclick={() => handleCreatePR(close)}
onclick={async () => await handleCreatePR(close)}
>{isDraft ? 'Create Draft PR' : 'Create PR'}</Button
>
{:else if props.type === 'display'}

View File

@ -1,5 +1,6 @@
<script lang="ts">
import MergeButton from './MergeButton.svelte';
import PrDetailsModal from './PrDetailsModal.svelte';
import ViewPrButton from './ViewPrButton.svelte';
import InfoMessage from '../shared/InfoMessage.svelte';
import { Project } from '$lib/backend/projects';
@ -57,6 +58,7 @@
// });
let isMerging = $state(false);
let prDetailsModal = $state<ReturnType<typeof PrDetailsModal>>();
const lastFetch = $derived(prMonitor?.lastFetch);
const timeAgo = $derived($lastFetch ? createTimeAgoStore($lastFetch) : undefined);
@ -64,17 +66,9 @@
const mrLoading = $derived(prMonitor?.loading);
const checksLoading = $derived(checksMonitor?.loading);
$effect(() => {
console.log($checksLoading);
});
const checksError = $derived(checksMonitor?.error);
const detailsError = $derived(prMonitor?.error);
$effect(() => {
console.log($checksError);
});
function getChecksCount(status: ChecksStatus): string {
if (!status) return 'Running checks';
@ -198,7 +192,15 @@
</div>
<div class="text-13 text-semibold pr-header-title">
<span style="color: var(--clr-scale-ntrl-50)">PR #{pr?.number}:</span>
{pr.title}
<Button
style="ghost"
outline
onclick={() => {
prDetailsModal?.show();
}}
>
{pr.title}</Button
>
</div>
<div class="pr-header-tags">
<Button
@ -275,6 +277,8 @@
</div>
{/if}
</div>
<PrDetailsModal bind:this={prDetailsModal} type="display" {pr} />
{/if}
<style lang="postcss">

View File

@ -311,10 +311,10 @@ export class BranchController {
branchId: string,
withForce: boolean,
stack: boolean = false
): Promise<BranchPushResult> {
): Promise<BranchPushResult | undefined> {
try {
const command = stack ? 'push_stack' : 'push_virtual_branch';
const pushResult = await invoke<BranchPushResult>(command, {
const pushResult = await invoke<BranchPushResult | undefined>(command, {
projectId: this.projectId,
branchId,
withForce