Skill update round trip / roundtrip (false, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, latest) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
170 lines
5.5 KiB
TypeScript
170 lines
5.5 KiB
TypeScript
import type { LinearIssue } from '../../shared/linear/issue-types'
|
|
import type {
|
|
LinearCustomViewModel,
|
|
LinearCustomViewSummary,
|
|
LinearProjectSummary
|
|
} from '../../shared/linear/project-types'
|
|
import type {
|
|
LinearCollectionResult,
|
|
LinearConcreteWorkspaceId,
|
|
LinearWorkspaceSelection
|
|
} from '../../shared/linear/workspace-types'
|
|
import { clampLinearIssueListLimit } from '../../shared/linear/issue-read-limits'
|
|
import { acquire, release } from './linear-request-concurrency'
|
|
import { clearToken } from './linear-token-store'
|
|
import { getClients, isAuthError } from './client'
|
|
import {
|
|
CUSTOM_VIEW_ISSUES_QUERY,
|
|
CUSTOM_VIEW_PROJECTS_QUERY,
|
|
CUSTOM_VIEW_QUERY,
|
|
CUSTOM_VIEWS_QUERY
|
|
} from './linear-custom-view-graphql'
|
|
import type { CustomViewConnectionResponse, LinearRawVariables } from './linear-project-nodes'
|
|
import {
|
|
clampLimit,
|
|
coalesce,
|
|
mapCustomViewForWorkspace,
|
|
mapCustomViewModel,
|
|
mapProjectForWorkspace,
|
|
normalizeConcreteWorkspaceId
|
|
} from './linear-project-models'
|
|
import { readCollection, readConcreteCollection } from './linear-project-collection-read'
|
|
import { readIssueConnectionPages } from './linear-project-issue-queries'
|
|
|
|
export async function listCustomViews(
|
|
model: LinearCustomViewModel,
|
|
limit = 20,
|
|
workspaceId?: LinearWorkspaceSelection | null,
|
|
force = false
|
|
): Promise<LinearCollectionResult<LinearCustomViewSummary>> {
|
|
const first = clampLimit(limit)
|
|
const key = `listCustomViews:${workspaceId ?? 'default'}:${model}:${first}`
|
|
const filter = { modelName: { eq: model === 'project' ? 'Project' : 'Issue' } }
|
|
return readCollection(
|
|
key,
|
|
workspaceId,
|
|
async (entry) => {
|
|
const result = await entry.client.client.rawRequest<
|
|
CustomViewConnectionResponse,
|
|
LinearRawVariables
|
|
>(CUSTOM_VIEWS_QUERY, { first, filter, orderBy: 'updatedAt' })
|
|
const connection = result.data?.customViews
|
|
return {
|
|
items: (connection?.nodes ?? [])
|
|
.map((view) => mapCustomViewForWorkspace(entry, view))
|
|
.filter((view): view is LinearCustomViewSummary => !!view && view.model === model),
|
|
hasMore: !!connection?.pageInfo?.hasNextPage
|
|
}
|
|
},
|
|
force
|
|
)
|
|
}
|
|
|
|
export async function getCustomView(
|
|
viewId: string,
|
|
model: LinearCustomViewModel,
|
|
workspaceId: LinearConcreteWorkspaceId,
|
|
force = false
|
|
): Promise<LinearCustomViewSummary | null> {
|
|
const id = viewId.trim()
|
|
const concreteWorkspaceId = normalizeConcreteWorkspaceId(workspaceId)
|
|
if (!id) {
|
|
throw new Error('Custom view ID is required')
|
|
}
|
|
const key = `getCustomView:${concreteWorkspaceId}:${model}:${id}`
|
|
return coalesce(
|
|
key,
|
|
async () => {
|
|
const entries = getClients(concreteWorkspaceId)
|
|
const entry = entries[0]
|
|
if (!entry) {
|
|
return null
|
|
}
|
|
await acquire()
|
|
try {
|
|
const result = await entry.client.client.rawRequest<
|
|
CustomViewConnectionResponse,
|
|
LinearRawVariables
|
|
>(CUSTOM_VIEW_QUERY, { id })
|
|
const view = result.data?.customView
|
|
const mapped = view ? mapCustomViewForWorkspace(entry, view) : null
|
|
return mapped?.model === model ? mapped : null
|
|
} catch (error) {
|
|
if (isAuthError(error)) {
|
|
clearToken(entry.workspace.id)
|
|
}
|
|
throw error
|
|
} finally {
|
|
release()
|
|
}
|
|
},
|
|
force
|
|
)
|
|
}
|
|
|
|
export async function listCustomViewIssues(
|
|
viewId: string,
|
|
limit = 20,
|
|
workspaceId: LinearConcreteWorkspaceId,
|
|
force = false
|
|
): Promise<LinearCollectionResult<LinearIssue>> {
|
|
const id = viewId.trim()
|
|
if (!id) {
|
|
throw new Error('Custom view ID is required')
|
|
}
|
|
const first = clampLinearIssueListLimit(limit)
|
|
const concreteWorkspaceId = normalizeConcreteWorkspaceId(workspaceId)
|
|
return readConcreteCollection(
|
|
`listCustomViewIssues:${concreteWorkspaceId}:${id}:${first}`,
|
|
concreteWorkspaceId,
|
|
async (entry) => {
|
|
return readIssueConnectionPages(entry, first, async (page) => {
|
|
const result = await entry.client.client.rawRequest<
|
|
CustomViewConnectionResponse,
|
|
LinearRawVariables
|
|
>(CUSTOM_VIEW_ISSUES_QUERY, { id, ...page, orderBy: 'updatedAt' })
|
|
const view = result.data?.customView
|
|
if (mapCustomViewModel(view?.modelName) !== 'issue') {
|
|
throw new Error('Custom view does not contain issues')
|
|
}
|
|
return view?.issues
|
|
})
|
|
},
|
|
force
|
|
)
|
|
}
|
|
|
|
export async function listCustomViewProjects(
|
|
viewId: string,
|
|
limit = 20,
|
|
workspaceId: LinearConcreteWorkspaceId,
|
|
force = false
|
|
): Promise<LinearCollectionResult<LinearProjectSummary>> {
|
|
const id = viewId.trim()
|
|
if (!id) {
|
|
throw new Error('Custom view ID is required')
|
|
}
|
|
const first = clampLimit(limit)
|
|
const concreteWorkspaceId = normalizeConcreteWorkspaceId(workspaceId)
|
|
return readConcreteCollection(
|
|
`listCustomViewProjects:${concreteWorkspaceId}:${id}:${first}`,
|
|
concreteWorkspaceId,
|
|
async (entry) => {
|
|
const result = await entry.client.client.rawRequest<
|
|
CustomViewConnectionResponse,
|
|
LinearRawVariables
|
|
>(CUSTOM_VIEW_PROJECTS_QUERY, { id, first, orderBy: 'updatedAt' })
|
|
const view = result.data?.customView
|
|
if (mapCustomViewModel(view?.modelName) !== 'project') {
|
|
throw new Error('Custom view does not contain projects')
|
|
}
|
|
const connection = view?.projects
|
|
return {
|
|
items: (connection?.nodes ?? []).map((project) => mapProjectForWorkspace(entry, project)),
|
|
hasMore: !!connection?.pageInfo?.hasNextPage
|
|
}
|
|
},
|
|
force
|
|
)
|
|
}
|