mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 02:57:36 +00:00
* [comment audit] Shorten daemon, hook, and emulator comments [skip ci] * [comment audit] Shorten VM, conversation, SQLite, and theme-import comments [skip ci] * [comment audit] Tighten Git execution and source-control comment contracts [skip ci] * [comment audit] Clarify repository probes and maintenance comments [skip ci] * [comment audit] Shorten status, diff-cache, and checkout comments [skip ci] * [comment audit] Clarify worktree lifecycle and WSL routing comments [skip ci] * [comment audit] Shorten forge identity and review-refresh comments [skip ci] * [comment audit] Tighten hosted-review client comment contracts [skip ci] * [comment audit] Clarify GitLab compatibility and review comments [skip ci] * [comment audit] Tighten hook ownership and watchdog rationale [skip ci] * [comment audit] Condense host and integration compatibility comments [skip ci] * [comment audit] Clarify IPC authority and remote-routing rationale [skip ci] * [comment audit] Finalize file-import and containment comment cleanup * [comment audit] Fix stale Jira type imports blocking full CI --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
169 lines
5.6 KiB
TypeScript
169 lines
5.6 KiB
TypeScript
import type { GetProjectViewTableArgs } from '../../../shared/github/project-request-types'
|
|
import type { GetProjectViewTableResult } from '../../../shared/github/project-result-types'
|
|
import type { GitHubProjectTable } from '../../../shared/github/project-types'
|
|
import { githubProjectHost } from '../../../shared/github/project-identity'
|
|
import { isRenderableProjectViewLayout } from '../../../shared/github/project-types'
|
|
import { assertPositiveInt, assertSlug } from './internals'
|
|
import {
|
|
fetchProjectViewsPage,
|
|
fetchViewFieldsContinuation,
|
|
finalizeView,
|
|
matchesSelector,
|
|
type RawProjectView
|
|
} from './project-view-config'
|
|
import type { RawProjectV2Field } from './project-view-field-normalization'
|
|
import { fetchAllItems, fetchItemsCountOnly } from './project-view-items'
|
|
|
|
export async function getProjectViewTable(
|
|
args: GetProjectViewTableArgs
|
|
): Promise<GetProjectViewTableResult> {
|
|
const ownerCheck = assertSlug(args.owner, 'owner')
|
|
if (!ownerCheck.ok) {
|
|
return { ok: false, error: ownerCheck.error }
|
|
}
|
|
const numCheck = assertPositiveInt(args.projectNumber, 'projectNumber')
|
|
if (!numCheck.ok) {
|
|
return { ok: false, error: numCheck.error }
|
|
}
|
|
if (args.ownerType !== 'organization' && args.ownerType !== 'user') {
|
|
return {
|
|
ok: false,
|
|
error: { type: 'validation_error', message: 'Invalid ownerType.' }
|
|
}
|
|
}
|
|
|
|
let cursor: string | null = null
|
|
let project: { id: string; title: string; url: string } | null = null
|
|
let selectedRaw: RawProjectView | null = null
|
|
let matchStrength: 'id' | 'number' | 'name' | 'default' | null = null
|
|
const viewsSeen: RawProjectView[] = []
|
|
while (true) {
|
|
const page = await fetchProjectViewsPage({
|
|
owner: args.owner,
|
|
ownerType: args.ownerType,
|
|
projectNumber: args.projectNumber,
|
|
host: args.host,
|
|
after: cursor
|
|
})
|
|
if (!page.ok) {
|
|
return { ok: false, error: page.error }
|
|
}
|
|
project = page.project
|
|
for (const v of page.views) {
|
|
viewsSeen.push(v)
|
|
const m = matchesSelector(v, {
|
|
viewId: args.viewId,
|
|
viewNumber: args.viewNumber,
|
|
viewName: args.viewName
|
|
})
|
|
if (m === 'none') {
|
|
continue
|
|
}
|
|
const rank: Record<typeof m, number> = { id: 4, number: 3, name: 2, default: 1 }
|
|
const currentRank = matchStrength ? rank[matchStrength] : 0
|
|
if (!selectedRaw || rank[m] > currentRank) {
|
|
selectedRaw = v
|
|
matchStrength = m
|
|
}
|
|
}
|
|
// Stop on the first match; further pagination spends quota without reranking it.
|
|
if (selectedRaw) {
|
|
break
|
|
}
|
|
if (!page.hasNextPage) {
|
|
break
|
|
}
|
|
cursor = page.endCursor
|
|
if (typeof cursor !== 'string') {
|
|
break
|
|
}
|
|
}
|
|
if (!project) {
|
|
return { ok: false, error: { type: 'not_found', message: 'Project not found.' } }
|
|
}
|
|
const noSelector =
|
|
args.viewId === undefined && args.viewNumber === undefined && args.viewName === undefined
|
|
if (!selectedRaw && noSelector) {
|
|
// Prefer tables, but allow roadmap/board defaults when no table view matched.
|
|
selectedRaw =
|
|
viewsSeen.find((v) => v.layout === 'ROADMAP_LAYOUT') ??
|
|
viewsSeen.find((v) => v.layout === 'BOARD_LAYOUT') ??
|
|
null
|
|
}
|
|
if (!selectedRaw) {
|
|
return { ok: false, error: { type: 'not_found', message: 'Could not find the selected view.' } }
|
|
}
|
|
|
|
let extraFields: RawProjectV2Field[] = []
|
|
const fieldsPi = selectedRaw.fields?.pageInfo
|
|
if (fieldsPi?.hasNextPage === true && typeof fieldsPi.endCursor === 'string' && selectedRaw.id) {
|
|
const cont = await fetchViewFieldsContinuation(selectedRaw.id, fieldsPi.endCursor, args.host)
|
|
if (!cont.ok) {
|
|
return { ok: false, error: cont.error }
|
|
}
|
|
extraFields = cont.fields
|
|
}
|
|
|
|
const finalized = finalizeView(selectedRaw, extraFields)
|
|
if (!finalized.ok) {
|
|
return { ok: false, error: finalized.drift }
|
|
}
|
|
const selectedView = finalized.view
|
|
|
|
// Empty override clears the filter; undefined retains the stored view filter. Neither persists.
|
|
const effectiveQuery =
|
|
typeof args.queryOverride === 'string' ? args.queryOverride : selectedView.filter
|
|
|
|
// Layouts share item data, but future unsupported layouts need explicit rejection.
|
|
if (!isRenderableProjectViewLayout(selectedView.layout)) {
|
|
const count = await fetchItemsCountOnly({
|
|
owner: args.owner,
|
|
ownerType: args.ownerType,
|
|
projectNumber: args.projectNumber,
|
|
query: effectiveQuery,
|
|
host: args.host
|
|
})
|
|
return {
|
|
ok: false,
|
|
error: {
|
|
type: 'unsupported_layout',
|
|
// Unchecked runtime layout values can reach this branch despite the closed type union.
|
|
message: `Orca renders table, board, and roadmap views. This is a ${String(selectedView.layout).replace('_LAYOUT', '').toLowerCase()} view.`
|
|
},
|
|
...(typeof count === 'number' ? { totalCount: count } : {})
|
|
}
|
|
}
|
|
|
|
const items = await fetchAllItems({
|
|
owner: args.owner,
|
|
ownerType: args.ownerType,
|
|
projectNumber: args.projectNumber,
|
|
query: effectiveQuery,
|
|
host: args.host
|
|
})
|
|
if (!items.ok) {
|
|
return {
|
|
ok: false,
|
|
error: items.error,
|
|
...(typeof items.totalCount === 'number' ? { totalCount: items.totalCount } : {})
|
|
}
|
|
}
|
|
|
|
const table: GitHubProjectTable = {
|
|
project: {
|
|
id: project.id,
|
|
host: githubProjectHost(args.host),
|
|
owner: args.owner,
|
|
ownerType: args.ownerType,
|
|
number: args.projectNumber,
|
|
title: project.title,
|
|
url: project.url
|
|
},
|
|
selectedView,
|
|
rows: items.rows,
|
|
totalCount: items.totalCount,
|
|
parentFieldDropped: items.parentFieldDropped
|
|
}
|
|
return { ok: true, data: table }
|
|
}
|