Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions apps/server/src/pullRequest/GiteaPullRequestApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import {
PullRequestChecksState as PullRequestChecksStateSchema,
PullRequestReviewDecision as PullRequestReviewDecisionSchema,
} from "@t3tools/contracts";
import type {
PullRequestAction,
PullRequestActor,
Expand Down Expand Up @@ -93,10 +97,8 @@ const RawPullRequest = Schema.Struct({
merged: Schema.optional(Schema.Boolean),
mergeable: Schema.optional(Schema.NullOr(Schema.Boolean)),
draft: Schema.optional(Schema.Boolean),
review_decision: Schema.optional(
Schema.NullOr(Schema.Literals(["approved", "changes-requested", "review-required"])),
),
checks_state: Schema.optional(Schema.NullOr(Schema.Literals(["passing", "failing", "pending"]))),
review_decision: Schema.optional(Schema.NullOr(PullRequestReviewDecisionSchema)),
checks_state: Schema.optional(Schema.NullOr(PullRequestChecksStateSchema)),
auto_merge_enabled: Schema.optional(Schema.NullOr(Schema.Boolean)),
auto_merge_method: Schema.optional(Schema.NullOr(Schema.String)),
html_url: Schema.String,
Expand Down
47 changes: 47 additions & 0 deletions apps/server/src/pullRequest/PullRequestService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3840,6 +3840,53 @@ it.effect("judges the review filter only on a host that summarises its reviews",
}),
);

it.effect("judges checks from provider rows while preserving rows without a check summary", () =>
Effect.gen(function* () {
const service = yield* makeService({
projects: [
project({
id: "p1",
title: "web",
workspaceRoot: "/a",
repository: "acme/web",
provider: "gitea",
host: "forge.example.test",
}),
],
providers: [
// Gitea can carry its check rollup on a row, but older or unconfigured hosts may leave it
// undefined. A null rollup is different: it says the host checked and found no checks.
fakeProvider("gitea", {
listChangeRequests: () =>
Effect.succeed({
items: [
{ ...changeRequest(1, "2026-07-04T00:00:00Z"), checksState: "passing" },
{ ...changeRequest(2, "2026-07-03T00:00:00Z"), checksState: "failing" },
{ ...changeRequest(3, "2026-07-02T00:00:00Z"), checksState: "pending" },
{ ...changeRequest(4, "2026-07-01T00:00:00Z"), checksState: null },
changeRequest(5, "2026-06-30T00:00:00Z"),
],
truncated: false,
continues: false,
}),
}),
],
});

const passing = yield* service.list({ state: "open", filters: { checks: "passing" } });
assert.deepStrictEqual(
passing.entries.map((entry) => entry.number),
[1, 5],
);

const failing = yield* service.list({ state: "open", filters: { checks: "failing" } });
assert.deepStrictEqual(
failing.entries.map((entry) => entry.number),
[2, 5],
);
}),
);

it.effect("sends only the words a rewrite carries", () =>
Effect.gen(function* () {
const received: Array<{ title?: string | undefined; body?: string | undefined }> = [];
Expand Down
8 changes: 6 additions & 2 deletions apps/server/src/pullRequest/PullRequestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -833,8 +833,9 @@ export const make = Effect.gen(function* () {
* answers unnarrowed, and without this pass a draft filter or a label filter would be sent,
* accepted and quietly ignored. Idempotent for the hosts that did narrow.
*
* `checks` is absent because no listed row carries its check state: that one filter is the
* host's alone, and a row nobody narrowed stays rather than being guessed at.
* `checks` is judged when a row carries its check state. A host that leaves the field undefined
* cannot be judged, so that row stays rather than being guessed at; null means the host answered
* that no checks are present and therefore matches neither passing nor failing.
*/
const matchesRowFilters = (
item: ProviderChangeRequest,
Expand All @@ -856,6 +857,9 @@ export const make = Effect.gen(function* () {
(filters.review === "none"
? item.reviewDecision === null
: item.reviewDecision === filters.review)) &&
(filters.checks === undefined ||
item.checksState === undefined ||
item.checksState === filters.checks) &&
(filters.labels === undefined || filters.labels.every((group) => group.some(holds))) &&
(filters.excludedLabels === undefined || !filters.excludedLabels.some(holds)) &&
(filters.author === undefined ||
Expand Down
5 changes: 3 additions & 2 deletions packages/contracts/src/pullRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ const PullRequestQualifierValues = Schema.Array(PullRequestQualifierValue).check
* nothing, which is what every listing did before there were any. Optional as a whole so a page
* and a server of different ages still speak to each other.
*
* `checks` is host-side only: no row carries its own check state, so a host that cannot match it
* answers unnarrowed rather than the page pretending to know.
* `checks` is host-side where supported, and is also judged from a row's own state when the
* provider carries one. A host that cannot match it answers unnarrowed rather than the page
* pretending to know.
*/
export const PullRequestListFilters = Schema.Struct({
draft: Schema.optional(Schema.Literals(["only", "hide"])),
Expand Down