Skip to content
Merged
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
9 changes: 1 addition & 8 deletions apps/extension/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Reply, Request } from "./messages.ts";
import { policyFor } from "./policy-source.ts";
import { prKey } from "./pr.ts";
import { browserSession, sessionGithub, sessionLogin, type SessionReads } from "./session-reads.ts";
import { settle } from "./settle.ts";
import { openPanelOnActionClick, openSidePanel } from "./side-panel.ts";
import { clearRunning, clearSaved, githubToken, readAs, saveView, setRunState, tokenSaved, views } from "./storage.ts";
import { FRESH_MS, type TriageView } from "./view.ts";
Expand All @@ -13,14 +14,6 @@ type Triage = Extract<Request, { type: "triage" }>;

const running = new Map<string, Promise<null>>();

async function settle(work: Promise<unknown>): Promise<void> {
try {
await work;
} catch (cause) {
console.warn(errorText(cause));
}
}

/** Opened in the same turn as the click's message: the side panel opens only inside a user gesture. */
function openPanel(windowId: number | undefined): void {
if (windowId !== undefined) settle(openSidePanel(windowId));
Expand Down
10 changes: 1 addition & 9 deletions apps/extension/src/content/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { ContentScriptContext } from "wxt/utils/content-script-context";
import { createShadowRootUi, type ShadowRootContentScriptUi } from "wxt/utils/content-script-ui/shadow-root";
import { errorText } from "@corbits/common";
import { send } from "../messages.ts";
import type { FileNote } from "../notes.ts";
import { prFromUrl, prKey, type PrRef } from "../pr.ts";
import { settle } from "../settle.ts";
import { runStates, views, type RunState } from "../storage.ts";
import type { TriageView } from "../view.ts";
import { scoreChip } from "./chip.ts";
Expand All @@ -18,14 +18,6 @@ const SIDEBAR_ANCHOR = "#partial-discussion-sidebar";

type Marker = { marker: FileMarker; ui?: ShadowRootContentScriptUi<void>; shown?: FileNote[] };

async function settle(work: Promise<unknown>): Promise<void> {
try {
await work;
} catch (cause) {
console.warn(errorText(cause));
}
}

function notesByPath(view: TriageView | undefined): Map<string, FileNote[]> {
const notes = new Map<string, FileNote[]>();
for (const note of view?.notes ?? []) notes.set(note.path, [...(notes.get(note.path) ?? []), note]);
Expand Down
9 changes: 9 additions & 0 deletions apps/extension/src/settle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { errorText } from "@corbits/common";

export async function settle(work: Promise<unknown>): Promise<void> {
try {
await work;
} catch (cause) {
console.warn(errorText(cause));
}
}
7 changes: 3 additions & 4 deletions apps/hub/src/github/tenant-open-heads.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// Pull request heads for a tenant's repositories, read as the tenant's GitHub
// App or Bitbucket Data Center token by each record's source, for hub-side work.
import type { DB } from "@intx/db";
import { asRecord } from "@corbits/common";
import * as bitbucket from "@corbits/bitbucket-datacenter-reads";
import { tokenUser } from "@corbits/bitbucket-datacenter-tool/bitbucket";
import { getChecks, listOpenPrs, readPr, type GithubFetch } from "@corbits/github-reads";
import type { TenantBitbucket } from "../bitbucket-datacenter/tenant-bitbucket.js";
import { GITHUB_CREDENTIAL, githubAppKey, type AppKeyDeps } from "./app-key.js";
import { GITHUB_CREDENTIAL, githubAppKey, githubAppLogin, type AppKeyDeps } from "./app-key.js";
import { createGithubAppCredentialFetch } from "./github-app-credential-adapter.js";
import { forInstallation, type OpenPrsLister } from "./open-pulls.js";
import { appGithubFetch, heldCredential } from "./portal-credential.js";
Expand Down Expand Up @@ -74,8 +73,8 @@ export function createTenantGithub(deps: TenantGithubDeps) {
/** The login the workspace's GitHub App writes as, read from its credential without asking GitHub; undefined when it has no slug or can't be read, since the listing must not fail over it. */
async function appBotLogin(db: DB["db"], tenantId: string): Promise<string | undefined> {
try {
const slug = asRecord((await heldCredential(db, tenantId, GITHUB_CREDENTIAL))?.metadata)?.["appSlug"];
return typeof slug === "string" && slug !== "" ? `${slug}[bot]` : undefined;
const held = await heldCredential(db, tenantId, GITHUB_CREDENTIAL);
return held ? githubAppLogin(held) : undefined;
} catch {
return undefined;
}
Expand Down
5 changes: 1 addition & 4 deletions apps/hub/src/github/verdict-row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { type } from "arktype";
import { asRecord } from "@corbits/common";
import { VerdictDetail, type RepoSource } from "@corbits/triage-contracts";
import { mirrorCallId } from "@corbits/triage-workflows/actions/mirror";
import { NOT_ASKED_REASON } from "@corbits/triage-workflows/logic/render";
import { unsettledBy } from "@corbits/triage-workflows/logic/settled";
import type { RecordedDo, RecordedVerdict } from "./verdict-record-store.js";
Expand Down Expand Up @@ -74,10 +75,6 @@ function notPostedOf(outcome: Record<string, unknown> | undefined): string | und
return outcome?.["skipped"] === true && typeof outcome["reason"] === "string" ? outcome["reason"] : undefined;
}

function mirrorCallId(repo: string, number: number): string {
return `mirror:${repo.replace("/", "--")}:${number}`;
}

const SummaryFields = type({
state: "string",
actor: "string",
Expand Down
3 changes: 2 additions & 1 deletion apps/hub/src/manager-routes.postgres.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { createApp, createRequireGrant, HUB_CONDITION_REGISTRY, type GetSession
import { createEventCollectorRegistry, createSidecarRouter, type SessionService } from "@intx/hub-sessions";
import { matchesRunTokenRoute } from "../../../vendor/interchange/packages/hub-api/src/middleware/run-token-auth.js";
import { MANAGER_DEPLOYED_ACTION, MANAGER_RESULT_ACTION, MANAGER_RESULT_RESOURCE, MANAGER_STATE_ACTION, MANAGER_STATE_RESOURCE, TRIAGE_MANAGERS, type RepoSource } from "@corbits/triage-contracts";
import { mirrorCallId } from "@corbits/triage-workflows/actions/mirror";
import { createCodeReviewRuns } from "./code-review-runs.js";
import { createOverdueWake, createSupersede } from "./manager-intake.js";
import { createManagerRoutes, MANAGER_RUN_TOKEN_ROUTES } from "./manager-routes.js";
Expand Down Expand Up @@ -356,7 +357,7 @@ function postedOn(pr: PullRequestRef, head: string) {
report: {
head,
verdict: { repo: pr.repo, number: pr.number, headSha: head, verdict: "pass", state: "ready-monitoring", actor: "author", humanGated: false, degraded: null, checks: [{ check: "title", kind: "machine", result: "pass" }], actions, request },
mirror: { call: `mirror:${pr.repo.replace("/", "--")}:${pr.number}`, ok: true },
mirror: { call: mirrorCallId(pr.repo, pr.number), ok: true },
executed: { dos: [{ effectId: `auto-${head}`, status: "done" }] },
rules: { items: [{ facts: { repo: pr.repo, number: pr.number, title: "Fix the widget", author: "octocat" }, det: { findings: [{ reason: "The title says what changed." }] } }] },
},
Expand Down
3 changes: 2 additions & 1 deletion apps/hub/src/server.postgres.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { findArtifactByTitle, getArtifact } from "@corbits/artifacts";
import { DEPLOYER_ROLE, emptyPack, exportPolicyFile, parseTriageState, TRIAGE_LISTENERS, triageStateName, TriageStats } from "@corbits/triage-contracts";
import type { ResolvedTarget, VerdictWord } from "@corbits/triage-contracts";
import { GITHUB_HOOK_HEADERS, xHubHookMetadata } from "@corbits/common/hook-metadata";
import { mirrorCallId } from "@corbits/triage-workflows/actions/mirror";
import { doEffectId } from "@corbits/triage-workflows/logic/actions";
import { replaceVerdict } from "./github/verdict-record-store.js";
import { mirrorOutcomesOf, summaryOf } from "./github/verdict-row.js";
Expand Down Expand Up @@ -561,7 +562,7 @@ async function seedVerdict(db: Db, tenantId: string, runId: string, started: Dat
const number = Number(verdict["number"]);
const headSha = String(verdict["headSha"]);
const trigger = { repo: REPO, source: "github" as const, number, headSha, startedAt: started.toISOString() };
const mirror = mirrorOutcomesOf(mirrored ? { call: `mirror:${REPO.replace("/", "--")}:${number}`, ok: true } : undefined);
const mirror = mirrorOutcomesOf(mirrored ? { call: mirrorCallId(REPO, number), ok: true } : undefined);
const summary = summaryOf(verdict, new Date(started.getTime() + tookMs).toISOString(), trigger, mirror, undefined);
if (!summary) throw new Error(`no summary for ${runId}`);
await replaceVerdict(db, tenantId, { runId, repo: REPO, number, headSha, startedAt: started, ...summary, runStatus: "completed" });
Expand Down
18 changes: 3 additions & 15 deletions apps/hub/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
import { E2B_PLACE } from "@corbits/provisioner-e2b";
import { RAILWAY_PLACE } from "@corbits/provisioner-railway";
import { policyKinds, REPO_SOURCES, repoSourceOf, timingsFrom, TRIAGE_LISTENERS, TRIAGE_MAIL_SENDER, TRIAGE_MANAGERS, type ActionKind, type RepoSource } from "@corbits/triage-contracts";
import { asRecord, errorText } from "@corbits/common";
import { asRecord, decodedSegment, errorText } from "@corbits/common";
import { createInterchangeHub } from "./interchange-hub.js";
import { createManagerIntake, createManagerWake, createOverdueWake, createSupersede } from "./manager-intake.js";
import { MANAGER_RUN_TOKEN_ROUTES } from "./manager-routes.js";
Expand Down Expand Up @@ -995,14 +995,6 @@ const servePortal = env.PORTAL_DIR === undefined ? undefined : createPortalHandl
const CREDENTIAL_WRITE = /^\/api\/tenants\/([^/]+)\/credentials(?:\/([^/]+))?$/;
const PROVIDER_WRITE = /^\/api\/tenants\/([^/]+)\/providers(?:\/([^/]+))?$/;

function decodedSegment(segment: string): string | undefined {
try {
return decodeURIComponent(segment);
} catch {
return undefined;
}
}

/** Rolls the workspace after a release write succeeds, starting again the listeners of the hosts the write reaches. */
async function forwardReleaseWrite(req: Request, path: string, forward: () => Promise<Response> | Response): Promise<Response> {
const release = workflowsBundled ? await releaseWriteOf(composition.db, req, path) : undefined;
Expand Down Expand Up @@ -1046,12 +1038,8 @@ async function routeRequest(req: Request, server: Parameters<typeof stock.fetch>
if (req.method === "PATCH") {
const tenantMatch = /^\/api\/tenants\/([^/]+)$/.exec(path);
if (tenantMatch) {
let tenantId = tenantMatch[1]!;
try {
tenantId = decodeURIComponent(tenantId);
} catch {
return Response.json({ error: "not_found" }, { status: 404 });
}
const tenantId = decodedSegment(tenantMatch[1]!);
if (tenantId === undefined) return Response.json({ error: "not_found" }, { status: 404 });
const principalId = await tenantManager({ db: composition.db, getSession: composition.getSession, authorize: authorizePortal }, tenantId, req.headers);
if (principalId instanceof Response) return principalId;
let switchedOn = false;
Expand Down
14 changes: 3 additions & 11 deletions apps/hub/src/workflow-release/triggers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { and, eq } from "drizzle-orm";
import { schema, type DB } from "@intx/db";
import { SidecarIdentityValidationError, type AllocatedSidecarTarget } from "@intx/hub-sessions";
import { asRecord, errorText } from "@corbits/common";
import { asRecord, decodedSegment, errorText } from "@corbits/common";
import { REPO_SOURCES, type RepoSource } from "@corbits/triage-contracts";

/** A write that can change a workspace's release: its catalog decides which workflows publish and the offerings they run on. */
Expand All @@ -19,14 +19,6 @@ export function credentialHosts(name: string): readonly RepoSource[] {
return host ? [host] : REPO_SOURCES;
}

function decoded(segment: string): string | undefined {
try {
return decodeURIComponent(segment);
} catch {
return undefined;
}
}

async function credentialName(db: DB["db"], req: Request, tenantId: string, credentialId: string | undefined): Promise<string | undefined> {
if (credentialId === undefined) {
let body: unknown;
Expand All @@ -47,10 +39,10 @@ async function credentialName(db: DB["db"], req: Request, tenantId: string, cred
export async function releaseWriteOf(db: DB["db"], req: Request, path: string): Promise<ReleaseWrite | undefined> {
if (!WRITE_METHODS.has(req.method)) return undefined;
const written = RELEASE_WRITE.exec(path);
const tenantId = written ? decoded(written[1]!) : undefined;
const tenantId = written ? decodedSegment(written[1]!) : undefined;
if (!written || tenantId === undefined) return undefined;
if (written[2] !== "credentials") return { tenantId, redeploy: REPO_SOURCES };
const credentialId = written[3] === undefined ? undefined : decoded(written[3]);
const credentialId = written[3] === undefined ? undefined : decodedSegment(written[3]);
const name = await credentialName(db, req, tenantId, credentialId);
return { tenantId, redeploy: name === undefined ? REPO_SOURCES : credentialHosts(name) };
}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/CodeReviewThreads.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { errorText } from "@corbits/common";
import { counted, errorText } from "@corbits/common";
import { BUDGET_EXHAUSTED_NOTE, INCOMPLETE_NOTE, UNVERIFIED_FINDING } from "@corbits/triage-contracts";
import { useCodeReview, useReviewAgain, useSendCodeReview } from "../lib/code-review.ts";
import { useCodeReviewOn } from "../lib/check-packs.ts";
Expand All @@ -21,7 +21,7 @@ function hint(review: CodeReviewRun, completed: CodeReviewRun | null): string {
if (review.status === "failed") return "The last review did not finish";
const count = completed?.comments?.length ?? 0;
if (count === 0) return "No findings";
return count === 1 ? "1 finding" : `${count} findings`;
return counted(count, "finding", "findings");
}

const MAX_NAMED_UNREAD = 5;
Expand Down
10 changes: 3 additions & 7 deletions apps/web/src/components/PaneReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Button, Skeleton, Table, TableBody, TableCell, TableRow } from "@corbit
import { BUILT_IN_CHECK_NAMES, isCatalogId, type ActionKind, type CheckPack } from "@corbits/triage-contracts";
import type { CheckResult, GithubPullDetail, PrItem } from "../lib/hub-api.ts";
import { checkLimitText } from "../lib/check-limit.ts";
import { errorText } from "@corbits/common";
import { counted, errorText } from "@corbits/common";
import { linearIssues } from "../lib/linked-issue.ts";
import { checkLabel, DO_NAMES } from "../lib/pack-prose.ts";
import { heldText, lineText, passedChecks, type HeldChoice, type PaneStatus, type Problem, type Severity } from "../lib/pane-report.ts";
Expand Down Expand Up @@ -37,10 +37,6 @@ function problemText(name: string, reason: string | null): string {
return opensWithName ? sentence(reason) : `${name}: ${sentence(reason)}`;
}

function plural(count: number, one: string, many: string): string {
return `${count} ${count === 1 ? one : many}`;
}

export function StatusLine({ status }: { status: PaneStatus }) {
return (
<p className={`st3 t-${status.tone}`} role="status">
Expand Down Expand Up @@ -271,10 +267,10 @@ export function ReportRows({ item, pull, pack }: { item: PrItem; pull: PullState
{detail === undefined ? <PullStatus pull={pull} /> : (
<>
<Disclosure label={issueLabel(detail)}><LinkedIssues detail={detail} /></Disclosure>
<Disclosure label={plural(detail.pr.changedFiles, "file", "files")}><Files files={detail.files} changed={detail.pr.changedFiles} /></Disclosure>
<Disclosure label={counted(detail.pr.changedFiles, "file", "files")}><Files files={detail.files} changed={detail.pr.changedFiles} /></Disclosure>
</>
)}
{passed.length > 0 ? <Disclosure label={`Checked and fine: ${plural(passed.length, "rule", "rules")}`}><Passed checks={passed} pack={pack} /></Disclosure> : null}
{passed.length > 0 ? <Disclosure label={`Checked and fine: ${counted(passed.length, "rule", "rules")}`}><Passed checks={passed} pack={pack} /></Disclosure> : null}
<Link className="more" to={prHref(item)}>Commits and conversation</Link>
</div>
);
Expand Down
3 changes: 1 addition & 2 deletions apps/web/src/components/settings/GithubSource.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { useState } from "react";
import { Button } from "@corbits/react-ui";
import { Link } from "react-router-dom";
import { errorText } from "@corbits/common";
import { counted, errorText } from "@corbits/common";
import { githubAppHeld, githubSecrets, namedSecrets, otherSecrets } from "../../lib/credential-actions.ts";
import { DeniedNotice } from "../../lib/denied.tsx";
import { githubAppPickerUrl, githubWebhookUrl, GITHUB_APP_PICKER_UNAVAILABLE, openGithubInstallation } from "../../lib/github-manifest.ts";
import { useGithubReturnSync } from "../../lib/github-return-sync.ts";
import { useGithubManageAccess } from "../../lib/github-sync.ts";
import { GITHUB_CREDENTIAL_NAME, GITHUB_READ_CREDENTIAL_NAME, githubAppSlugFromCredentials, hasActiveGithubCredential, type PortalSnapshot } from "../../lib/hub-api.ts";
import { counted } from "../../lib/plural.ts";
import { usePortal } from "../../lib/portal.tsx";
import { CopyField } from "../CopyField.tsx";
import { CredentialRow } from "./CredentialRow.tsx";
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/settings/TriageSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useCheckPacks } from "../../lib/check-packs.ts";
import { modelSecrets } from "../../lib/credential-actions.ts";
import { DeniedNotice } from "../../lib/denied.tsx";
import { connectedSources, currentDeployment, isActiveCredential, lacksDecisionModel, type DeployedWorkflow, type PortalSnapshot } from "../../lib/hub-api.ts";
import { counted } from "../../lib/plural.ts";
import { counted } from "@corbits/common";
import { usePortal } from "../../lib/portal.tsx";
import { hostFullName } from "../../lib/pull-link.ts";
import { enabledRepos, packsPending, triageReadyRepos } from "../../lib/repo-rows.ts";
Expand Down
3 changes: 0 additions & 3 deletions apps/web/src/lib/plural.ts

This file was deleted.

3 changes: 1 addition & 2 deletions apps/web/src/pages/Connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState, type ChangeEvent, type ReactNode } from "
import { Badge, Button, StatusDot } from "@corbits/react-ui";
import { Link, useNavigate } from "react-router-dom";
import { Check, CircleCheck, Sparkles, Upload } from "lucide-react";
import { errorText } from "@corbits/common";
import { counted, errorText } from "@corbits/common";
import { catchingUpRepos } from "../lib/backlog-status.ts";
import { useRepositories } from "../lib/bitbucket-datacenter.ts";
import { connectedSources, githubAppSlugFromCredentials, hasActiveGithubCredential, portalConnected, type PortalSnapshot, type RepoRecord } from "../lib/hub-api.ts";
Expand All @@ -19,7 +19,6 @@ import {
startGithubManifest,
} from "../lib/github-manifest.ts";
import { useGithubSync } from "../lib/github-sync.ts";
import { counted } from "../lib/plural.ts";
import { usePortal } from "../lib/portal.tsx";
import { sameRepo } from "../lib/repo-id.ts";
import { hostedRepos } from "../lib/repo-rows.ts";
Expand Down
Loading
Loading