feat: import and export tickets as CSV

Every ticket of a project can be downloaded as CSV, and a CSV exported
from TaskStream or from Jira can be imported: types, statuses, priorities
and people are matched by id or name, the rest falls back with a warning,
and parents are rebuilt. The settings page previews an import before it
creates anything.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 15:43:02 +02:00
parent d43fc620e9
commit 1076205acc
24 changed files with 1666 additions and 31 deletions

View File

@@ -0,0 +1,58 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import {
AccessTokenSource,
API_CONFIG,
} from '@task-stream/web/shared/infrastructure';
import { ImportReport, TransferGateway } from '@task-stream/web/tracker/domain';
import { from, Observable, switchMap } from 'rxjs';
/** CSV files travel over REST, like attachments. */
@Injectable()
export class HttpTransferGateway extends TransferGateway {
private readonly http = inject(HttpClient);
private readonly api = inject(API_CONFIG);
private readonly tokens = inject(AccessTokenSource);
exportCsv(projectId: string): Observable<Blob> {
return this.authorized((headers) =>
this.http.get(`${this.base(projectId)}/export.csv`, {
headers,
responseType: 'blob',
}),
);
}
importCsv(
projectId: string,
file: File,
dryRun: boolean,
): Observable<ImportReport> {
const body = new FormData();
body.append('file', file, file.name);
return this.authorized((headers) =>
this.http.post<ImportReport>(`${this.base(projectId)}/import`, body, {
headers,
params: { dryRun: String(dryRun) },
}),
);
}
private base(projectId: string): string {
return `${this.api.restUrl}/projects/${encodeURIComponent(projectId)}`;
}
private authorized<T>(
request: (headers: HttpHeaders) => Observable<T>,
): Observable<T> {
return from(this.tokens.accessToken()).pipe(
switchMap((token) =>
request(
token
? new HttpHeaders({ Authorization: `Bearer ${token}` })
: new HttpHeaders(),
),
),
);
}
}