feat(integrations): add issue creation and Azure work item support

Add creation flow for integration issues and Azure work items.
Expose Tauri commands to list Azure projects and types.
Also add a command to create integration issues and return the created item.
Preserve the target repository when provider responses omit it and return
the created issue information to the UI.

- Implement Azure-specific URL and payload handling and creation logic
- Add a Svelte CreateIssueDialog and integrate it into IssueCenter
- Add tests to validate creation requests and created-issue parsing
This commit is contained in:
2026-09-11 09:53:59 +02:00
parent 088cbc5e18
commit 5c39fa3e30
8 changed files with 298 additions and 2 deletions
+30 -2
View File
@@ -6,8 +6,9 @@
import { onMount, onDestroy, tick } from "svelte";
import { fly } from "svelte/transition";
import { cubicOut } from "svelte/easing";
import { ChevronRight, CircleDot, FolderGit2, ExternalLink, RefreshCw, Search, Settings2, X, XCircle } from "@lucide/svelte";
import { ChevronRight, CircleDot, Plus, FolderGit2, ExternalLink, RefreshCw, Search, Settings2, X, XCircle } from "@lucide/svelte";
import "../issueWorkspace.css";
import CreateIssueDialog from "./CreateIssueDialog.svelte";
import IssueComments from "./IssueComments.svelte";
import IssueLabels from "./IssueLabels.svelte";
import IssueAssignees from "./IssueAssignees.svelte";
@@ -17,13 +18,16 @@
import { readWorkspacePreferences, writeWorkspacePreferences } from "../workspacePreferences";
import { listIntegrationIssues, closeIntegrationIssue, listAzureIssueStates, setAzureIssueState, openInBrowser } from "../git";
import { configuredIntegrationSources, integrationCredentialKey } from "../integrations";
import type { GitIntegrationSettings, IntegrationIssue, StoredCredential } from "../types";
import type { GitIntegrationSettings, GitIntegrationSource, IntegrationIssue, StoredCredential } from "../types";
export let language: "de" | "en" = "en";
export let integrations: GitIntegrationSettings;
export let loadCredential: (key: string) => Promise<StoredCredential | null>;
export let onOpenSettings: () => void;
let createSource: GitIntegrationSource | null = null;
let createKey = "";
let createRepository = "";
let viewMode: "list" | "board" = "list";
let sourceId = "";
let issues: IntegrationIssue[] = [];
@@ -216,6 +220,25 @@
} finally { closingId = ""; }
}
function issueCreated(issue: IntegrationIssue) {
const key = createKey;
const saved = cache.get(key);
const next = [issue, ...(saved?.issues ?? []).filter(item => item.id !== issue.id)];
cache.set(key, { issues: next, nextCursor: saved?.nextCursor ?? null });
createSource = null;
if (sourceKey !== key) return;
generation++; // Ignore any list response started before creation.
loading = false;
issues = next;
viewMode = "list";
repositoryFilter = ""; stateFilter = ""; query = "";
const collapsed = new Set(collapsedRepositories);
collapsed.delete(JSON.stringify([key, issue.repositoryName]));
collapsedRepositories = collapsed;
selectedId = issue.id;
error = ""; actionError = "";
}
async function openIssue(url: string) {
try { await openInBrowser(url); }
catch (cause) { error = String(cause); }
@@ -249,6 +272,7 @@
<SelectMenu class="repository-select" value={repositoryFilter} options={repositoryOptions} ariaLabel={de ? "Repository filtern" : "Filter repository"} onChange={value => { repositoryFilter = value; selectedId = ""; }} />
<SelectMenu class="state-select" value={stateFilter} options={stateOptions} ariaLabel={de ? "Status filtern" : "Filter state"} onChange={value => { stateFilter = value; }} />
<button class="workspace-button icon-button" disabled={loading} onclick={() => loadIssues()} title={de ? "Aktualisieren" : "Refresh"} aria-label={de ? "Aktualisieren" : "Refresh"}><RefreshCw size={15} /></button>
{#if source}<button class="workspace-button issue-create-button" onclick={() => { createKey = sourceKey; createRepository = repositoryFilter; createSource = source; }}><Plus size={14}/>{de ? "Neues Issue" : "New issue"}</button>{/if}
</div>
{#if error}<div class="workspace-notice error" role="alert">{error}<p>{de ? "Bitte Token und Leserechte für Issues bzw. Azure Boards prüfen." : "Check the token and read permissions for issues or Azure Boards."}</p></div>{/if}
<div class="issue-list">
@@ -317,3 +341,7 @@
{/if}
{/if}
</section>
{#if createSource}
<CreateIssueDialog source={createSource} {de} initialRepository={createRepository} {loadCredential} onClose={() => { createSource = null; }} onCreated={issueCreated}/>
{/if}