Files
orca/src/main/github/project-view/project-view-item-normalization.ts
T
Neil ad8e71e1a2 Split GitHub project view read path (#17266)
* Split speech session lifecycle

* Split terminal output scheduler pipeline

* Split mobile browser pane modules

* Prune resolved max-lines suppressions

* Split pane tree equalization logic

* Extract mobile troubleshoot screen styles

* Split external automation manager

* Split main window service attachments

* Split hosted review creation checks

* Split automation dispatch event handling

* Split settings navigation metadata

* Split daemon initialization lifecycle

* Split GitLab item dialog

* Split relay dispatcher layers

* Split mobile host screen

* Retarget mobile view settings source test

* Split runtime file client layers

* Split ports panel layers

* Split runtime environments pane layers

* Split local PTY provider responsibilities

* Split CDP bridge responsibilities

* Split relay Git handler responsibilities

* Track moved relay Git fetch audit

* Split Linear item drawer responsibilities

* Split telemetry event schema responsibilities

* Split resource usage status responsibilities

* Split remote terminal multiplexer responsibilities

* Split Git worktree responsibilities

* Split Codex hook service responsibilities

* Keep mirrored hook trust type private

* Split web runtime session responsibilities

* Split GitHub project view read path

* Fix F3-speech for #17123

* Fix F1-cycle for #17131

* Fix F4-navtest for #17157

* Fix F2-allowlist for #17161
2026-08-29 20:22:10 -07:00

154 lines
4.6 KiB
TypeScript

import type {
GitHubProjectFieldValue,
GitHubProjectLabel,
GitHubProjectRow,
GitHubProjectRowItemType,
GitHubProjectUser
} from '../../../shared/github/project-types'
import type { GitHubProjectViewError } from '../../../shared/github/project-result-types'
import { driftError } from './project-error-classification'
import {
normalizeFieldValue,
normalizeLabel,
normalizeUser,
type RawFieldValue,
type RawLabel,
type RawUser
} from './project-view-field-normalization'
type RawContent = {
__typename?: string
id?: string
number?: number
title?: string
body?: string
url?: string
state?: string
stateReason?: string | null
isDraft?: boolean
repository?: { nameWithOwner?: string }
assignees?: { nodes?: RawUser[] }
labels?: { nodes?: RawLabel[] }
parent?: { number?: number; title?: string; url?: string } | null
issueType?: {
id?: string
name?: string
color?: string | null
description?: string | null
} | null
}
export type RawItem = {
id?: string
type?: string
updatedAt?: string
content?: RawContent | null
fieldValues?: {
nodes?: RawFieldValue[]
pageInfo?: { hasNextPage?: boolean }
}
}
type NormalizedItemOutcome =
| { ok: true; row: GitHubProjectRow }
| { ok: false; drift: GitHubProjectViewError }
function mapItemType(raw: string | undefined, hasContent: boolean): GitHubProjectRowItemType {
if (raw === 'ISSUE') {
return 'ISSUE'
}
if (raw === 'PULL_REQUEST') {
return 'PULL_REQUEST'
}
if (raw === 'DRAFT_ISSUE') {
return 'DRAFT_ISSUE'
}
if (raw === 'REDACTED' || !hasContent) {
return 'REDACTED'
}
// Unknown item type with content — treat as redacted rather than dropping.
return 'REDACTED'
}
export function normalizeItem(raw: RawItem, position: number): NormalizedItemOutcome {
if (!raw || typeof raw.id !== 'string') {
return {
ok: false,
drift: driftError('item missing id', { path: ['items', 'nodes', position, 'id'] })
}
}
if (raw.fieldValues?.pageInfo?.hasNextPage === true) {
return {
ok: false,
drift: driftError('item field values exceeded single page', {
path: ['items', 'nodes', position, 'fieldValues', 'pageInfo', 'hasNextPage']
})
}
}
const itemType = mapItemType(raw.type, raw.content !== null && raw.content !== undefined)
const content = raw.content ?? null
const assignees = (content?.assignees?.nodes ?? [])
.map(normalizeUser)
.filter((u): u is GitHubProjectUser => u !== null)
const labels = (content?.labels?.nodes ?? [])
.map(normalizeLabel)
.filter((l): l is GitHubProjectLabel => l !== null)
const parentIssue =
content?.parent &&
typeof content.parent.number === 'number' &&
typeof content.parent.title === 'string' &&
typeof content.parent.url === 'string'
? { number: content.parent.number, title: content.parent.title, url: content.parent.url }
: null
const issueType =
content?.issueType &&
typeof content.issueType.id === 'string' &&
typeof content.issueType.name === 'string'
? {
id: content.issueType.id,
name: content.issueType.name,
color: typeof content.issueType.color === 'string' ? content.issueType.color : null,
description:
typeof content.issueType.description === 'string' ? content.issueType.description : null
}
: null
const fieldValuesByFieldId: Record<string, GitHubProjectFieldValue> = {}
for (const fv of raw.fieldValues?.nodes ?? []) {
const normalized = normalizeFieldValue(fv)
if (normalized) {
fieldValuesByFieldId[normalized.fieldId] = normalized
}
}
const title =
itemType === 'REDACTED'
? 'Restricted item'
: typeof content?.title === 'string'
? content.title
: ''
const row: GitHubProjectRow = {
id: raw.id,
itemType,
content: {
number: typeof content?.number === 'number' ? content.number : null,
title,
body: typeof content?.body === 'string' ? content.body : null,
url: typeof content?.url === 'string' ? content.url : null,
state: typeof content?.state === 'string' ? content.state : null,
stateReason: typeof content?.stateReason === 'string' ? content.stateReason : null,
isDraft: typeof content?.isDraft === 'boolean' ? content.isDraft : null,
repository:
typeof content?.repository?.nameWithOwner === 'string'
? content.repository.nameWithOwner
: null,
assignees,
labels,
parentIssue,
issueType
},
fieldValuesByFieldId,
updatedAt: typeof raw.updatedAt === 'string' ? raw.updatedAt : '',
position
}
return { ok: true, row }
}