mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Rename /runs/{id}/compare to /runs/{id}/files with pagination
Replace the RunCompare envelope (checkpoints + files + stats) with a standard PaginatedRunFileList response containing FileDiff items, matching the existing pagination pattern used by other endpoints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e73f2ab00e
commit
5a720ddb26
8 changed files with 55 additions and 106 deletions
|
|
@ -27,7 +27,7 @@ export default [
|
|||
route("stages/:stageId", "routes/run-stages.tsx"),
|
||||
route("configuration", "routes/run-configuration.tsx"),
|
||||
route("graph", "routes/run-graph.tsx"),
|
||||
route("compare", "routes/run-compare.tsx"),
|
||||
route("files", "routes/run-files.tsx"),
|
||||
route("verification", "routes/run-verification.tsx"),
|
||||
route("usage", "routes/run-usage.tsx"),
|
||||
route("retro", "routes/run-retro.tsx"),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import type { Route } from "./+types/run-detail";
|
|||
const tabs = [
|
||||
{ name: "Overview", path: "", count: null },
|
||||
{ name: "Stages", path: "/stages/detect-drift", count: null },
|
||||
{ name: "Files Changed", path: "/compare", count: null },
|
||||
{ name: "Files Changed", path: "/files", count: null },
|
||||
{ name: "Verification", path: "/verification", count: null },
|
||||
{ name: "Retro", path: "/retro", count: null },
|
||||
{ name: "Usage", path: "/usage", count: null },
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useParams } from "react-router";
|
||||
import { ChevronDownIcon, Cog6ToothIcon } from "@heroicons/react/24/outline";
|
||||
import {
|
||||
MultiFileDiff,
|
||||
type AnnotationSide,
|
||||
|
|
@ -8,13 +6,13 @@ import {
|
|||
} from "@pierre/diffs/react";
|
||||
import { useTheme } from "../lib/theme";
|
||||
import { apiJson } from "../api-client";
|
||||
import type { RunCompare } from "@qltysh/arc-api-client";
|
||||
import type { Route } from "./+types/run-compare";
|
||||
import type { PaginatedRunFileList } from "@qltysh/arc-api-client";
|
||||
import type { Route } from "./+types/run-files";
|
||||
|
||||
export const handle = { wide: true };
|
||||
|
||||
export async function loader({ request, params }: Route.LoaderArgs) {
|
||||
const data = await apiJson<RunCompare>(`/runs/${params.id}/compare`, { request });
|
||||
const data = await apiJson<PaginatedRunFileList>(`/runs/${params.id}/files`, { request });
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
@ -217,29 +215,6 @@ export async function execute(
|
|||
},
|
||||
];
|
||||
|
||||
const BLOCK_COUNT = 5;
|
||||
|
||||
function DiffStat({ additions, deletions }: { additions: number; deletions: number }) {
|
||||
const total = additions + deletions;
|
||||
const addBlocks = total === 0 ? 0 : Math.round((additions / total) * BLOCK_COUNT);
|
||||
const delBlocks = BLOCK_COUNT - addBlocks;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 font-mono text-xs">
|
||||
<span className="font-semibold text-mint">+{additions.toLocaleString()}</span>
|
||||
<span className="font-semibold text-coral">-{deletions.toLocaleString()}</span>
|
||||
<div className="flex gap-0.5">
|
||||
{Array.from({ length: BLOCK_COUNT }, (_, i) => (
|
||||
<span
|
||||
key={`block-${i}`}
|
||||
className={`inline-block size-2.5 rounded-sm ${i < addBlocks ? "bg-mint" : "bg-coral"}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface SteerAnnotation {
|
||||
fileName: string;
|
||||
lineNumber: number;
|
||||
|
|
@ -508,21 +483,14 @@ function buildAnnotationsForFile(
|
|||
return annotations;
|
||||
}
|
||||
|
||||
export default function RunCompare({ loaderData }: Route.ComponentProps) {
|
||||
export default function RunFiles({ loaderData }: Route.ComponentProps) {
|
||||
const runFiles = loaderData;
|
||||
const checkpoints = [
|
||||
{ id: "all", label: "All changes" },
|
||||
...runFiles.checkpoints.map((cp) => ({ id: cp.id, label: cp.label })),
|
||||
];
|
||||
const files = runFiles.files.length > 0
|
||||
? runFiles.files.map((f) => ({
|
||||
const files = runFiles.data.length > 0
|
||||
? runFiles.data.map((f) => ({
|
||||
oldFile: { name: f.old_file.name, contents: f.old_file.contents },
|
||||
newFile: { name: f.new_file.name, contents: f.new_file.contents },
|
||||
}))
|
||||
: fallbackFiles;
|
||||
const diffStats = runFiles.stats;
|
||||
|
||||
const [checkpoint, setCheckpoint] = useState(checkpoints[0].id);
|
||||
const [openSteers, setOpenSteers] = useState(
|
||||
() => new Map<string, SteerAnnotation>(),
|
||||
);
|
||||
|
|
@ -560,31 +528,6 @@ export default function RunCompare({ loaderData }: Route.ComponentProps) {
|
|||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative">
|
||||
<select
|
||||
value={checkpoint}
|
||||
onChange={(e) => setCheckpoint(e.target.value)}
|
||||
className="appearance-none rounded-md border border-line bg-panel/80 py-2 pl-3 pr-8 text-sm text-fg-2 outline-none transition-colors focus:border-focus focus:ring-0"
|
||||
>
|
||||
{checkpoints.map((cp) => (
|
||||
<option key={cp.id} value={cp.id}>{cp.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDownIcon className="pointer-events-none absolute right-2 top-1/2 size-4 -translate-y-1/2 text-fg-muted" />
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-3">
|
||||
<DiffStat additions={diffStats.additions} deletions={diffStats.deletions} />
|
||||
<button
|
||||
type="button"
|
||||
title="Settings"
|
||||
className="flex size-8 items-center justify-center rounded-md border border-line bg-panel/80 text-fg-3 transition-colors hover:bg-overlay hover:text-fg"
|
||||
>
|
||||
<Cog6ToothIcon className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{files.map(({ oldFile, newFile }) => (
|
||||
<DiffWithSteer
|
||||
key={newFile.name}
|
||||
|
|
@ -74,7 +74,7 @@ To migrate: Update any DOT workflow prompts referencing `{{script.output}}` or `
|
|||
</Warning>
|
||||
|
||||
<Warning>
|
||||
**`GET /runs/{id}/files` renamed to `GET /runs/{id}/compare`.** The endpoint returns the same data but the path now reflects its purpose of comparing file changes between checkpoints.
|
||||
**`GET /runs/{id}/compare` renamed to `GET /runs/{id}/files`.** The endpoint now returns a standard paginated list of `FileDiff` items instead of the `RunCompare` envelope.
|
||||
|
||||
To migrate: Update API integrations using `/files` to use `/compare`.
|
||||
To migrate: Update API integrations using `/compare` to use `/files`, and adjust response parsing from `{ checkpoints, files, stats }` to `{ data, meta }`.
|
||||
</Warning>
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ models/paginated-api-question-list.ts
|
|||
models/paginated-history-entry-list.ts
|
||||
models/paginated-model-list.ts
|
||||
models/paginated-retro-list.ts
|
||||
models/paginated-run-file-list.ts
|
||||
models/paginated-run-list.ts
|
||||
models/paginated-run-stage-list.ts
|
||||
models/paginated-run-verification-list.ts
|
||||
|
|
@ -95,7 +96,6 @@ models/retro-stats.ts
|
|||
models/root-response-urls.ts
|
||||
models/root-response.ts
|
||||
models/run-checkpoint.ts
|
||||
models/run-compare.ts
|
||||
models/run-configuration.ts
|
||||
models/run-error.ts
|
||||
models/run-list-item.ts
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError
|
|||
// @ts-ignore
|
||||
import type { ErrorResponse } from '../models';
|
||||
// @ts-ignore
|
||||
import type { PaginatedRunVerificationList } from '../models';
|
||||
import type { PaginatedRunFileList } from '../models';
|
||||
// @ts-ignore
|
||||
import type { RunCompare } from '../models';
|
||||
import type { PaginatedRunVerificationList } from '../models';
|
||||
// @ts-ignore
|
||||
import type { RunUsage } from '../models';
|
||||
/**
|
||||
|
|
@ -35,17 +35,19 @@ import type { RunUsage } from '../models';
|
|||
export const RunOutputsApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||
return {
|
||||
/**
|
||||
* Returns file-level diffs produced by the run, optionally filtered to a specific checkpoint.
|
||||
* @summary Retrieve Run Compare
|
||||
* Returns a paginated list of file-level diffs produced by the run, optionally filtered to a specific checkpoint.
|
||||
* @summary Retrieve Run Files
|
||||
* @param {string} id Unique run identifier (ULID).
|
||||
* @param {string} [checkpoint] Filter to a specific checkpoint ID. Omit to include all changes.
|
||||
* @param {number} [pageLimit] Maximum number of items to return per page.
|
||||
* @param {number} [pageOffset] Number of items to skip before returning results.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
retrieveRunCompare: async (id: string, checkpoint?: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
retrieveRunFiles: async (id: string, checkpoint?: string, pageLimit?: number, pageOffset?: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('retrieveRunCompare', 'id', id)
|
||||
const localVarPath = `/runs/{id}/compare`
|
||||
assertParamExists('retrieveRunFiles', 'id', id)
|
||||
const localVarPath = `/runs/{id}/files`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
|
|
@ -69,6 +71,14 @@ export const RunOutputsApiAxiosParamCreator = function (configuration?: Configur
|
|||
localVarQueryParameter['checkpoint'] = checkpoint;
|
||||
}
|
||||
|
||||
if (pageLimit !== undefined) {
|
||||
localVarQueryParameter['page[limit]'] = pageLimit;
|
||||
}
|
||||
|
||||
if (pageOffset !== undefined) {
|
||||
localVarQueryParameter['page[offset]'] = pageOffset;
|
||||
}
|
||||
|
||||
localVarHeaderParameter['Accept'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
|
|
@ -182,17 +192,19 @@ export const RunOutputsApiFp = function(configuration?: Configuration) {
|
|||
const localVarAxiosParamCreator = RunOutputsApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
* Returns file-level diffs produced by the run, optionally filtered to a specific checkpoint.
|
||||
* @summary Retrieve Run Compare
|
||||
* Returns a paginated list of file-level diffs produced by the run, optionally filtered to a specific checkpoint.
|
||||
* @summary Retrieve Run Files
|
||||
* @param {string} id Unique run identifier (ULID).
|
||||
* @param {string} [checkpoint] Filter to a specific checkpoint ID. Omit to include all changes.
|
||||
* @param {number} [pageLimit] Maximum number of items to return per page.
|
||||
* @param {number} [pageOffset] Number of items to skip before returning results.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async retrieveRunCompare(id: string, checkpoint?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<RunCompare>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveRunCompare(id, checkpoint, options);
|
||||
async retrieveRunFiles(id: string, checkpoint?: string, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PaginatedRunFileList>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveRunFiles(id, checkpoint, pageLimit, pageOffset, options);
|
||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||
const localVarOperationServerBasePath = operationServerMap['RunOutputsApi.retrieveRunCompare']?.[localVarOperationServerIndex]?.url;
|
||||
const localVarOperationServerBasePath = operationServerMap['RunOutputsApi.retrieveRunFiles']?.[localVarOperationServerIndex]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
|
|
@ -233,15 +245,17 @@ export const RunOutputsApiFactory = function (configuration?: Configuration, bas
|
|||
const localVarFp = RunOutputsApiFp(configuration)
|
||||
return {
|
||||
/**
|
||||
* Returns file-level diffs produced by the run, optionally filtered to a specific checkpoint.
|
||||
* @summary Retrieve Run Compare
|
||||
* Returns a paginated list of file-level diffs produced by the run, optionally filtered to a specific checkpoint.
|
||||
* @summary Retrieve Run Files
|
||||
* @param {string} id Unique run identifier (ULID).
|
||||
* @param {string} [checkpoint] Filter to a specific checkpoint ID. Omit to include all changes.
|
||||
* @param {number} [pageLimit] Maximum number of items to return per page.
|
||||
* @param {number} [pageOffset] Number of items to skip before returning results.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
retrieveRunCompare(id: string, checkpoint?: string, options?: RawAxiosRequestConfig): AxiosPromise<RunCompare> {
|
||||
return localVarFp.retrieveRunCompare(id, checkpoint, options).then((request) => request(axios, basePath));
|
||||
retrieveRunFiles(id: string, checkpoint?: string, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): AxiosPromise<PaginatedRunFileList> {
|
||||
return localVarFp.retrieveRunFiles(id, checkpoint, pageLimit, pageOffset, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* Returns token and cost usage broken down by stage and model for a specific run.
|
||||
|
|
@ -273,15 +287,17 @@ export const RunOutputsApiFactory = function (configuration?: Configuration, bas
|
|||
*/
|
||||
export class RunOutputsApi extends BaseAPI {
|
||||
/**
|
||||
* Returns file-level diffs produced by the run, optionally filtered to a specific checkpoint.
|
||||
* @summary Retrieve Run Compare
|
||||
* Returns a paginated list of file-level diffs produced by the run, optionally filtered to a specific checkpoint.
|
||||
* @summary Retrieve Run Files
|
||||
* @param {string} id Unique run identifier (ULID).
|
||||
* @param {string} [checkpoint] Filter to a specific checkpoint ID. Omit to include all changes.
|
||||
* @param {number} [pageLimit] Maximum number of items to return per page.
|
||||
* @param {number} [pageOffset] Number of items to skip before returning results.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
public retrieveRunCompare(id: string, checkpoint?: string, options?: RawAxiosRequestConfig) {
|
||||
return RunOutputsApiFp(this.configuration).retrieveRunCompare(id, checkpoint, options).then((request) => request(this.axios, this.basePath));
|
||||
public retrieveRunFiles(id: string, checkpoint?: string, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig) {
|
||||
return RunOutputsApiFp(this.configuration).retrieveRunFiles(id, checkpoint, pageLimit, pageOffset, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export * from './paginated-api-question-list';
|
|||
export * from './paginated-history-entry-list';
|
||||
export * from './paginated-model-list';
|
||||
export * from './paginated-retro-list';
|
||||
export * from './paginated-run-file-list';
|
||||
export * from './paginated-run-list';
|
||||
export * from './paginated-run-stage-list';
|
||||
export * from './paginated-run-verification-list';
|
||||
|
|
@ -75,7 +76,6 @@ export * from './retro-stats';
|
|||
export * from './root-response';
|
||||
export * from './root-response-urls';
|
||||
export * from './run-checkpoint';
|
||||
export * from './run-compare';
|
||||
export * from './run-configuration';
|
||||
export * from './run-error';
|
||||
export * from './run-list-item';
|
||||
|
|
|
|||
|
|
@ -13,28 +13,18 @@
|
|||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { DiffStats } from './diff-stats';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { FileCheckpoint } from './file-checkpoint';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { FileDiff } from './file-diff';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { PaginationMeta } from './pagination-meta';
|
||||
|
||||
/**
|
||||
* File-level diff output for a run, with checkpoint filtering support.
|
||||
* Paginated list of file diffs produced by a run.
|
||||
*/
|
||||
export interface RunCompare {
|
||||
/**
|
||||
* Available checkpoints for filtering.
|
||||
*/
|
||||
'checkpoints': Array<FileCheckpoint>;
|
||||
/**
|
||||
* File diffs, optionally filtered by checkpoint.
|
||||
*/
|
||||
'files': Array<FileDiff>;
|
||||
'stats': DiffStats;
|
||||
export interface PaginatedRunFileList {
|
||||
'data': Array<FileDiff>;
|
||||
'meta': PaginationMeta;
|
||||
}
|
||||
|
||||
Loading…
Add table
Reference in a new issue