From 3d04b0b04948e76e0225be87df2eabda16f249d7 Mon Sep 17 00:00:00 2001 From: Rahman Adianto Date: Wed, 24 Dec 2025 17:34:52 +0700 Subject: [PATCH 1/3] add new component for Labels section --- .../src/components/LabelSection.svelte | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 apps/desktop/src/components/LabelSection.svelte diff --git a/apps/desktop/src/components/LabelSection.svelte b/apps/desktop/src/components/LabelSection.svelte new file mode 100644 index 0000000000..3397cf7a1a --- /dev/null +++ b/apps/desktop/src/components/LabelSection.svelte @@ -0,0 +1,130 @@ + + +
+
+ Labels + +
+ +
+ {#if selectedLabels.length > 0} +
+ {#each selectedLabels as label} + (selectedLabels = selectedLabels.filter((l) => l !== label))} + > + {label} + + {/each} +
+ {:else} + None + {/if} +
+ + {#if labelsQuery?.result?.error} + + {/if} +
+ + From 05320ccf1e66191bd3b27c9705baa022df7a1744 Mon Sep 17 00:00:00 2001 From: Rahman Adianto Date: Wed, 24 Dec 2025 18:00:42 +0700 Subject: [PATCH 2/3] add labels to forge service --- .../forge/github/githubPrService.svelte.ts | 46 ++++++++++++++++--- .../forge/gitlab/gitlabPrService.svelte.ts | 45 +++++++++++++++--- .../src/lib/forge/interface/forgePrService.ts | 4 +- apps/desktop/src/lib/forge/interface/types.ts | 1 + 4 files changed, 83 insertions(+), 13 deletions(-) diff --git a/apps/desktop/src/lib/forge/github/githubPrService.svelte.ts b/apps/desktop/src/lib/forge/github/githubPrService.svelte.ts index ae90ca8860..673c349fd7 100644 --- a/apps/desktop/src/lib/forge/github/githubPrService.svelte.ts +++ b/apps/desktop/src/lib/forge/github/githubPrService.svelte.ts @@ -10,7 +10,8 @@ import { MergeMethod, type CreatePullRequestArgs, type DetailedPullRequest, - type PullRequest + type PullRequest, + type Label } from '$lib/forge/interface/types'; import { eventualConsistencyCheck } from '$lib/forge/shared/progressivePolling'; import { providesItem, invalidatesItem, ReduxTag, invalidatesList } from '$lib/state/tags'; @@ -39,7 +40,8 @@ export class GitHubPrService implements ForgePrService { body, draft, baseBranchName, - upstreamName + upstreamName, + labels }: CreatePullRequestArgs): Promise { this.loading.set(true); const request = async () => { @@ -49,7 +51,8 @@ export class GitHubPrService implements ForgePrService { base: baseBranchName, title, body, - draft + draft, + labels }) ); }; @@ -102,6 +105,9 @@ export class GitHubPrService implements ForgePrService { ) { await this.api.endpoints.updatePr.mutate({ number, update }); } + labels(options?: StartQueryActionCreatorOptions) { + return this.api.endpoints.getLabels.useQuery(undefined, options); + } } async function fetchRepoPermissions( @@ -183,17 +189,45 @@ function injectEndpoints(api: GitHubApi) { }), createPr: build.mutation< CreatePrResult, - { head: string; base: string; title: string; body: string; draft: boolean } + { + head: string; + base: string; + title: string; + body: string; + draft: boolean; + labels: string[]; + } >({ - queryFn: async ({ head, base, title, body, draft }, api) => + queryFn: async ({ head, base, title, body, draft, labels }, api) => await ghQuery({ domain: 'pulls', action: 'create', - parameters: { head, base, title, body, draft }, + parameters: { head, base, title, body, draft, labels }, extra: api.extra }), invalidatesTags: (result) => [invalidatesItem(ReduxTag.PullRequests, result?.number)] }), + getLabels: build.query({ + queryFn: async (_args, api) => { + const result = await ghQuery({ + domain: 'issues', + action: 'listLabelsForRepo', + parameters: {}, + extra: api.extra + }); + if (result.error) { + return { error: result.error }; + } + return { + data: result.data.map((l: any) => ({ + name: l.name, + description: l.description || undefined, + color: l.color + })) + }; + }, + providesTags: [ReduxTag.PullRequests] + }), mergePr: build.mutation({ queryFn: async ({ number, method: method }, api) => { const result = await ghQuery({ diff --git a/apps/desktop/src/lib/forge/gitlab/gitlabPrService.svelte.ts b/apps/desktop/src/lib/forge/gitlab/gitlabPrService.svelte.ts index 70b54fa379..0de575c322 100644 --- a/apps/desktop/src/lib/forge/gitlab/gitlabPrService.svelte.ts +++ b/apps/desktop/src/lib/forge/gitlab/gitlabPrService.svelte.ts @@ -10,7 +10,8 @@ import type { CreatePullRequestArgs, DetailedPullRequest, MergeMethod, - PullRequest + PullRequest, + Label } from '$lib/forge/interface/types'; import type { QueryOptions } from '$lib/state/butlerModule'; import type { GitLabApi } from '$lib/state/clientState.svelte'; @@ -33,7 +34,8 @@ export class GitLabPrService implements ForgePrService { body, draft, baseBranchName, - upstreamName + upstreamName, + labels }: CreatePullRequestArgs): Promise { this.loading.set(true); @@ -43,7 +45,8 @@ export class GitLabPrService implements ForgePrService { base: baseBranchName, title, body, - draft + draft, + labels }); }; @@ -95,6 +98,10 @@ export class GitLabPrService implements ForgePrService { ) { await this.api.endpoints.updatePr.mutate({ number, update }); } + + labels(options?: StartQueryActionCreatorOptions) { + return this.api.endpoints.getLabels.useQuery(undefined, options); + } } function injectEndpoints(api: GitLabApi) { @@ -123,9 +130,16 @@ function injectEndpoints(api: GitLabApi) { }), createPr: build.mutation< PullRequest, - { head: string; base: string; title: string; body: string; draft: boolean } + { + head: string; + base: string; + title: string; + body: string; + draft: boolean; + labels: string[]; + } >({ - queryFn: async ({ head, base, title, body, draft }, query) => { + queryFn: async ({ head, base, title, body, draft, labels }, query) => { try { const { api, upstreamProjectId, forkProjectId } = gitlab(query.extra); const upstreamProject = await api.Projects.show(upstreamProjectId); @@ -136,7 +150,8 @@ function injectEndpoints(api: GitLabApi) { const mr = await api.MergeRequests.create(forkProjectId, head, base, finalTitle, { description: body, targetProjectId: upstreamProject.id, - removeSourceBranch: true + removeSourceBranch: true, + labels: labels.join(',') }); return { data: mrToInstance(mr) }; } catch (e: unknown) { @@ -145,6 +160,24 @@ function injectEndpoints(api: GitLabApi) { }, invalidatesTags: (result) => [invalidatesItem(ReduxTag.GitLabPullRequests, result?.number)] }), + getLabels: build.query({ + queryFn: async (_args, query) => { + try { + const { api, upstreamProjectId } = gitlab(query.extra); + const labels = await api.ProjectLabels.all(upstreamProjectId); + return { + data: labels.map((l: any) => ({ + name: l.name, + description: l.description || undefined, + color: l.color + })) + }; + } catch (e: unknown) { + return { error: toSerializable(e) }; + } + }, + providesTags: [ReduxTag.GitLabPullRequests] + }), mergePr: build.mutation({ queryFn: async ({ number }, query) => { try { diff --git a/apps/desktop/src/lib/forge/interface/forgePrService.ts b/apps/desktop/src/lib/forge/interface/forgePrService.ts index fb75240fdf..c5f59d1cb6 100644 --- a/apps/desktop/src/lib/forge/interface/forgePrService.ts +++ b/apps/desktop/src/lib/forge/interface/forgePrService.ts @@ -2,7 +2,8 @@ import type { CreatePullRequestArgs, DetailedPullRequest, MergeMethod, - PullRequest + PullRequest, + Label } from '$lib/forge/interface/types'; import type { ReactiveQuery } from '$lib/state/butlerModule'; import type { StartQueryActionCreatorOptions } from '@reduxjs/toolkit/query'; @@ -38,4 +39,5 @@ export interface ForgePrService { prNumber: number, details: { description?: string; state?: 'open' | 'closed'; targetBase?: string } ): Promise; + labels(options?: StartQueryActionCreatorOptions): ReactiveQuery; } diff --git a/apps/desktop/src/lib/forge/interface/types.ts b/apps/desktop/src/lib/forge/interface/types.ts index ec1966fe3f..d6e0c63343 100644 --- a/apps/desktop/src/lib/forge/interface/types.ts +++ b/apps/desktop/src/lib/forge/interface/types.ts @@ -166,4 +166,5 @@ export type CreatePullRequestArgs = { draft: boolean; baseBranchName: string; upstreamName: string; + labels: string[]; }; From 5d36f20ed516e5dc3feb3839f542fc02b89cfc0b Mon Sep 17 00:00:00 2001 From: Rahman Adianto Date: Wed, 24 Dec 2025 18:00:55 +0700 Subject: [PATCH 3/3] integrate label section into review creation component --- apps/desktop/src/components/ReviewCreation.svelte | 11 +++++++++-- apps/desktop/src/lib/state/clientState.svelte.ts | 2 +- packages/ui/src/lib/components/select/Select.svelte | 11 +++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/components/ReviewCreation.svelte b/apps/desktop/src/components/ReviewCreation.svelte index 830c4ec3fe..c7c7a97159 100644 --- a/apps/desktop/src/components/ReviewCreation.svelte +++ b/apps/desktop/src/components/ReviewCreation.svelte @@ -6,10 +6,12 @@ body: string; draft: boolean; upstreamBranchName: string | undefined; + labels: string[]; }
+ ({}) diff --git a/packages/ui/src/lib/components/select/Select.svelte b/packages/ui/src/lib/components/select/Select.svelte index 3ceb624d1b..3dd2deb2f5 100644 --- a/packages/ui/src/lib/components/select/Select.svelte +++ b/packages/ui/src/lib/components/select/Select.svelte @@ -37,6 +37,7 @@ children?: Snippet; icon?: keyof typeof iconsJson; autofocus?: boolean; + closeOnSelect?: boolean; onselect?: (value: T, modifiers?: Modifiers) => void; ontoggle?: (isOpen: boolean) => void; } @@ -78,6 +79,7 @@ children, icon, autofocus, + closeOnSelect = true, onselect, ontoggle }: Props = $props(); @@ -223,10 +225,11 @@ } : undefined; onselect?.(value, modifiers); - - // Maintain focus if selection was made via keyboard - const isKeyboardSelection = event instanceof KeyboardEvent; - closeList(isKeyboardSelection); + if (closeOnSelect) { + // Maintain focus if selection was made via keyboard + const isKeyboardSelection = event instanceof KeyboardEvent; + closeList(isKeyboardSelection); + } } function handleEnter(event: KeyboardEvent) {