diff --git a/apps/desktop/src/components/LabelSection.svelte b/apps/desktop/src/components/LabelSection.svelte new file mode 100644 index 00000000000..3397cf7a1a0 --- /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} +
+ + diff --git a/apps/desktop/src/components/ReviewCreation.svelte b/apps/desktop/src/components/ReviewCreation.svelte index 830c4ec3fe8..c7c7a97159c 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[]; }
+ { 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 70b54fa379f..0de575c3229 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 fb75240fdf1..c5f59d1cb6a 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 ec1966fe3f0..d6e0c633430 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[]; }; diff --git a/apps/desktop/src/lib/state/clientState.svelte.ts b/apps/desktop/src/lib/state/clientState.svelte.ts index 614a5b1bff2..84f5aadb13e 100644 --- a/apps/desktop/src/lib/state/clientState.svelte.ts +++ b/apps/desktop/src/lib/state/clientState.svelte.ts @@ -234,7 +234,7 @@ const FORGE_API_CONFIG = { tagTypes: Object.values(ReduxTag), invalidationBehavior: 'immediately' as const, baseQuery: fakeBaseQuery, - refetchOnFocus: true, + refetchOnFocus: false, refetchOnReconnect: true, keepUnusedDataFor: FORGE_CACHE_TTL_SECONDS, endpoints: () => ({}) diff --git a/packages/ui/src/lib/components/select/Select.svelte b/packages/ui/src/lib/components/select/Select.svelte index 3ceb624d1ba..3dd2deb2f5f 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) {