feat(integrations): add create integration review request API and UI

Add a new Tauri command and front-end flow to create PRs/MRs.
The backend builds and validates provider-specific payloads and
endpoints, sends creation requests, and parses responses into the
app's review model. A Svelte dialog and a JS wrapper wire the UI to the
command so users can create requests from the Review Center.

- Implement provider payload and endpoint logic with unit tests
- Expose create_integration_review_request as a Tauri command
- Add CreateReviewDialog UI and integrate a creation button in Review Center
This commit is contained in:
2026-09-08 21:32:12 +02:00
parent 80c7cf7280
commit f7f85d387d
6 changed files with 206 additions and 2 deletions
+18
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { onMount } from "svelte";
import CreateReviewDialog from "./CreateReviewDialog.svelte";
import SelectMenu from "./SelectMenu.svelte";
import { cubicOut } from "svelte/easing";
import { fly } from "svelte/transition";
@@ -32,6 +33,7 @@
}
let { language = "en", integrations, initialQuery = "", initialSourceId = "", localResolutionRequestId = "", localResolutionPhase = "idle", localResolutionMessage = "", loadCredential, onOpenSettings = () => {}, onStartLocalResolution = () => {}, onOpenLocalResolver = () => {}, onContinueLocalResolution = () => {}, onAbortLocalResolution = () => {}, onPushLocalResolution = () => {} }: Props = $props();
let createOpen = $state(false);
let requests = $state<IntegrationReviewRequest[]>([]);
let loading = $state(false);
let errors = $state<Array<{ source: string; message: string }>>([]);
@@ -398,9 +400,24 @@
}
</script>
{#if createOpen && activeSource}
<CreateReviewDialog source={activeSource} {de} {loadCredential} onClose={() => createOpen = false} onCreated={(request) => {
++loadGeneration;
loading = false;
requests = [request, ...requests.filter(item => item.id !== request.id)];
stateFilter = request.state;
query = "";
collapsedRepositories = new Set();
errors = [];
createOpen = false;
selectRequest(request, true);
}}/>
{/if}
<section class:has-inspector={detailOpen && !!selected} class="review-center" aria-label="Review Center">
<header class="review-header">
<div class="review-heading"><GitPullRequest size={17} /><h1>Review Center</h1></div>
{#if activeSource}<button class="create-review" type="button" disabled={loading} onclick={() => { detailOpen = false; createOpen = true; }}><GitPullRequest size={14}/>{activeSource.provider.startsWith("gitlab") ? (de ? "MR erstellen" : "Create MR") : (de ? "PR erstellen" : "Create PR")}</button>{/if}
</header>
{#if sources.length === 0}
@@ -528,6 +545,7 @@
</section>
<style>
.create-review{margin-left:auto;display:flex;align-items:center;gap:7px;padding:7px 11px;border:1px solid var(--color-border-subtle);border-radius:7px;background:var(--color-accent);color:white;cursor:pointer}.create-review:disabled{opacity:.5;cursor:default}
.review-center{display:flex;min-height:0;flex:1;flex-direction:column;overflow:hidden;color:var(--color-ink);background:var(--app-bg);font-size:12px}.review-center button,.review-center input{font:inherit}.review-header{display:flex;min-height:48px;align-items:center;padding:0 18px;border-bottom:1px solid var(--color-border-subtle);background:var(--color-surface)}.review-heading{display:flex;align-items:center;gap:9px}.review-heading>:global(svg){color:var(--color-accent)}.review-heading h1{margin:0;font-size:16px;font-weight:650}
.state-tabs{display:flex;min-height:42px;align-items:stretch;gap:18px;padding:0 18px;border-bottom:1px solid var(--color-border-subtle);background:var(--color-surface)}.state-tabs button{position:relative;display:flex;align-items:center;gap:7px;padding:0 5px;border:0;color:var(--color-ink-muted);background:transparent;font-size:11px}.state-tabs button:hover{color:var(--color-ink)}.state-tabs button.active{color:var(--color-accent)}.state-tabs button.active:after{position:absolute;right:0;bottom:0;left:0;height:2px;background:var(--color-accent);content:""}.state-tabs span,.group-header>span{display:grid;min-width:18px;height:18px;place-items:center;padding:0 5px;border-radius:9px;color:var(--color-ink-muted);background:var(--color-surface-raised);font-size:9.5px}
.review-toolbar{display:flex;min-height:48px;align-items:center;gap:10px;padding:7px 18px;border-bottom:1px solid var(--color-border-subtle)}.group-actions{display:flex;align-items:center;gap:2px}.group-actions button,.icon-button{display:inline-flex;height:30px;align-items:center;gap:5px;padding:0 7px;border:1px solid transparent;color:var(--color-ink-muted);background:transparent}.group-actions button:hover,.icon-button:hover{border-color:var(--color-border);color:var(--color-ink);background:var(--color-surface-hover)}.group-actions .icon-button{width:30px;justify-content:center;padding:0;border-color:var(--color-border-subtle);margin-left:3px}.review-search{display:flex;min-width:180px;height:30px;align-items:center;gap:7px;flex:1;padding:0 9px;border:1px solid var(--color-border-input);color:var(--color-ink-faint);background:var(--app-input-bg)}.review-search:focus-within{border-color:var(--color-accent)}.review-search input{width:100%;min-width:0;height:100%;padding:0;border:0;outline:0;color:var(--color-ink);background:transparent}