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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,12 @@ const project = await client.projects.create("workspace-slug", {
- **Releases**: Release management with tags, labels, item labels, changelog, comments, links, and work items
- **Collections**: Folders that group workspace pages, with member and page management
- **AgentRuns**: AI agent run orchestration and activity tracking
- **Workflows**: Project workflow management with state attachments and transitions
- **Workflows**: Project workflow management with state attachments, transitions, transition hooks, activities, and work item approvals
- **ProjectTemplates**: Work item and page template management per project
- **Features**: Workspace and project features management
- **WorkspaceStates**: Workspace-level (catalog) work-item states under workspace governance — dual-mode reads, governed-only writes
- **WorkspaceWorkflows**: Workspace-level workflow catalog under workspace governance, with chain (states), transitions, usage, activities, and transition hooks
- **WorkItemTypeGovernance**: Governs which workflows a workspace-level work item type may use (any/constrained/required modes), with per-project pins and the project-side pick/fallback-preview endpoints

## Development

Expand Down
48 changes: 48 additions & 0 deletions src/api/WorkItemTypeGovernance/Pins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { BaseResource } from "../BaseResource";
import { Configuration } from "../../Configuration";
import { CreateWorkItemTypeWorkflowPins, WorkItemTypeWorkflowPin } from "../../models/WorkItemTypeGovernance";
Comment thread
akhil-vamshi-konam marked this conversation as resolved.

/**
* WorkItemTypeGovernance.pins sub-resource
*
* A pin forces one project to resolve a type to a specific workflow,
* overriding the workspace default and the constrained allowlist.
*/
export class Pins extends BaseResource {
constructor(config: Configuration) {
super(config);
}

/**
* List a type's project-to-workflow pins
*/
async list(workspaceSlug: string, typeId: string): Promise<WorkItemTypeWorkflowPin[]> {
const data = await this.get<WorkItemTypeWorkflowPin[] | { results: WorkItemTypeWorkflowPin[] }>(
`/workspaces/${workspaceSlug}/work-item-types/${typeId}/governance/pins/`
);
return Array.isArray(data) ? data : data.results;
}

/**
* Pin a workflow for this type across one or more projects.
* Returns the type's pins after the change.
*/
async create(
workspaceSlug: string,
typeId: string,
data: CreateWorkItemTypeWorkflowPins
): Promise<WorkItemTypeWorkflowPin[]> {
const response = await this.post<WorkItemTypeWorkflowPin[] | { results: WorkItemTypeWorkflowPin[] }>(
`/workspaces/${workspaceSlug}/work-item-types/${typeId}/governance/pins/`,
data
);
return Array.isArray(response) ? response : response.results;
}

/**
* Remove a pin
*/
async delete(workspaceSlug: string, typeId: string, pinId: string): Promise<void> {
return this.httpDelete(`/workspaces/${workspaceSlug}/work-item-types/${typeId}/governance/pins/${pinId}/`);
Comment thread
akhil-vamshi-konam marked this conversation as resolved.
}
}
89 changes: 89 additions & 0 deletions src/api/WorkItemTypeGovernance/ProjectWorkflows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { BaseResource } from "../BaseResource";
import { Configuration } from "../../Configuration";
import {
GovernancePreview,
ProjectTypeWorkflow,
ProjectWorkflowPickResult,
SetProjectWorkflowPick,
WorkflowFallbackPreviewRequest,
} from "../../models/WorkItemTypeGovernance";

type GovernancePreviewResponse = { preview?: GovernancePreview } | GovernancePreview;

function unwrapPreview(response: GovernancePreviewResponse): GovernancePreview {
return "preview" in response && response.preview ? response.preview : (response as GovernancePreview);
}

/**
* WorkItemTypeGovernance.projectWorkflows sub-resource
*
* Reports each active type's governance mode and the workflow it effectively
* resolves to within a project, and manages the project's own workflow pick
* for a type.
*/
export class ProjectWorkflows extends BaseResource {
constructor(config: Configuration) {
super(config);
}

/**
* List every active type's governance mode, effective workflow, and
* pickable options for a project
*/
async list(workspaceSlug: string, projectId: string): Promise<ProjectTypeWorkflow[]> {
const data = await this.get<ProjectTypeWorkflow[] | { results: ProjectTypeWorkflow[] }>(
`/workspaces/${workspaceSlug}/projects/${projectId}/work-item-types/workflows/`
);
return Array.isArray(data) ? data : data.results;
}

/**
* Retrieve one type's governance mode and effective workflow in a project
*/
async retrieve(workspaceSlug: string, projectId: string, typeId: string): Promise<ProjectTypeWorkflow> {
return this.get<ProjectTypeWorkflow>(
`/workspaces/${workspaceSlug}/projects/${projectId}/work-item-types/${typeId}/workflows/`
);
}

/**
* Retrieve the project's current workflow pick context for a type
*/
async retrievePick(workspaceSlug: string, projectId: string, typeId: string): Promise<ProjectTypeWorkflow> {
return this.get<ProjectTypeWorkflow>(
`/workspaces/${workspaceSlug}/projects/${projectId}/work-item-types/${typeId}/workflow/`
);
}

/**
* Set the project's workflow pick for a type.
* Runs the workflow fallback for stranded work items; every orphan must be
* covered by `data.state_mapping` (400 with an orphan report otherwise).
*/
async updatePick(
workspaceSlug: string,
projectId: string,
typeId: string,
data: SetProjectWorkflowPick
): Promise<ProjectWorkflowPickResult> {
return this.put<ProjectWorkflowPickResult>(
`/workspaces/${workspaceSlug}/projects/${projectId}/work-item-types/${typeId}/workflow/`,
data
);
}

/**
* Dry-run the project's workflow fallback (re-type / switch dialogs)
*/
async previewFallback(
workspaceSlug: string,
projectId: string,
data: WorkflowFallbackPreviewRequest
): Promise<GovernancePreview> {
const response = await this.post<GovernancePreviewResponse>(
`/workspaces/${workspaceSlug}/projects/${projectId}/workflow-fallback-preview/`,
data
);
return unwrapPreview(response);
}
}
65 changes: 65 additions & 0 deletions src/api/WorkItemTypeGovernance/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { BaseResource } from "../BaseResource";
import { Configuration } from "../../Configuration";
import {
GovernancePreview,
TypeGovernance,
TypeGovernancePreviewRequest,
UpdateTypeGovernance,
} from "../../models/WorkItemTypeGovernance";
import { Pins } from "./Pins";
import { ProjectWorkflows } from "./ProjectWorkflows";

type GovernancePreviewResponse = { preview?: GovernancePreview } | GovernancePreview;

function unwrapPreview(response: GovernancePreviewResponse): GovernancePreview {
return "preview" in response && response.preview ? response.preview : (response as GovernancePreview);
}

/**
* WorkItemTypeGovernance API resource (workspace governance only)
*
* Governs which workflows a workspace-level work item type may use
* (`any` / `constrained` / `required` modes and allowlists). Per-project pins
* live on `.pins`; the project-side view of effective workflows and picks
* lives on `.projectWorkflows`. Every endpoint requires the workspace to own
* states and workflows — otherwise the API responds 400 with code
* `workspace_not_managed`.
*/
export class WorkItemTypeGovernance extends BaseResource {
public pins: Pins;
public projectWorkflows: ProjectWorkflows;

constructor(config: Configuration) {
super(config);
this.pins = new Pins(config);
this.projectWorkflows = new ProjectWorkflows(config);
}

/**
* Retrieve a type's governance settings (mode, required workflow, allowlist)
*/
async retrieve(workspaceSlug: string, typeId: string): Promise<TypeGovernance> {
return this.get<TypeGovernance>(`/workspaces/${workspaceSlug}/work-item-types/${typeId}/governance/`);
}

/**
* Update a type's governance mode / allowlist / required workflow.
* Destructive changes (dropping in-use workflows, mandating one) require
* `data.acknowledge` and may need a `data.state_mapping` for orphaned work
* items.
*/
async update(workspaceSlug: string, typeId: string, data: UpdateTypeGovernance): Promise<TypeGovernance> {
return this.patch<TypeGovernance>(`/workspaces/${workspaceSlug}/work-item-types/${typeId}/governance/`, data);
}

/**
* Dry-run a governance change and report affected work items (no writes)
*/
async preview(workspaceSlug: string, typeId: string, data: TypeGovernancePreviewRequest): Promise<GovernancePreview> {
const response = await this.post<GovernancePreviewResponse>(
`/workspaces/${workspaceSlug}/work-item-types/${typeId}/governance/preview/`,
data
);
return unwrapPreview(response);
}
}
102 changes: 102 additions & 0 deletions src/api/Workflows/Hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { BaseResource } from "../BaseResource";
Comment thread
akhil-vamshi-konam marked this conversation as resolved.
import { Configuration } from "../../Configuration";
import {
CreateWorkflowTransitionHook,
UpdateWorkflowTransitionHook,
WorkflowTransitionHook,
} from "../../models/Workflow";

/**
* WorkflowTransitionHooks sub-resource
* Manages hooks attached to project workflow transitions
*/
export class Hooks extends BaseResource {
constructor(config: Configuration) {
super(config);
}

private basePath(workspaceSlug: string, projectId: string, workflowId: string, transitionId: string): string {
return (
`/workspaces/${workspaceSlug}/projects/${projectId}/workflows/${workflowId}` +
`/state-transitions/${transitionId}/hooks`
);
}

/**
* List hooks on a workflow transition
*/
async list(
workspaceSlug: string,
projectId: string,
workflowId: string,
transitionId: string
): Promise<WorkflowTransitionHook[]> {
const data = await this.get<WorkflowTransitionHook[] | { results: WorkflowTransitionHook[] }>(
`${this.basePath(workspaceSlug, projectId, workflowId, transitionId)}/`
);
return Array.isArray(data) ? data : data.results;
}

/**
* Create a hook on a workflow transition.
* For send_webhook handlers the one-shot `secret_plaintext` is included in
* the response of this call only.
*/
async create(
workspaceSlug: string,
projectId: string,
workflowId: string,
transitionId: string,
data: CreateWorkflowTransitionHook
): Promise<WorkflowTransitionHook> {
return this.post<WorkflowTransitionHook>(
`${this.basePath(workspaceSlug, projectId, workflowId, transitionId)}/`,
data
);
}

/**
* Retrieve a hook by ID
*/
async retrieve(
workspaceSlug: string,
projectId: string,
workflowId: string,
transitionId: string,
hookId: string
): Promise<WorkflowTransitionHook> {
return this.get<WorkflowTransitionHook>(
`${this.basePath(workspaceSlug, projectId, workflowId, transitionId)}/${hookId}/`
);
}

/**
* Update a hook (`phase` and `handler_name` are immutable)
*/
async update(
workspaceSlug: string,
projectId: string,
workflowId: string,
transitionId: string,
hookId: string,
data: UpdateWorkflowTransitionHook
): Promise<WorkflowTransitionHook> {
return this.patch<WorkflowTransitionHook>(
`${this.basePath(workspaceSlug, projectId, workflowId, transitionId)}/${hookId}/`,
data
);
}

/**
* Delete a hook
*/
async del(
workspaceSlug: string,
projectId: string,
workflowId: string,
transitionId: string,
hookId: string
): Promise<void> {
return this.httpDelete(`${this.basePath(workspaceSlug, projectId, workflowId, transitionId)}/${hookId}/`);
}
}
45 changes: 44 additions & 1 deletion src/api/Workflows/States.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BaseResource } from "../BaseResource";
import { Configuration } from "../../Configuration";
import { AttachWorkflowStates } from "../../models/Workflow";
import { AttachWorkflowStates, UpdateWorkflowState, WorkflowState } from "../../models/Workflow";

/**
* WorkflowStates sub-resource
Expand All @@ -11,6 +11,16 @@ export class States extends BaseResource {
super(config);
}

/**
* List the states attached to a workflow
*/
async list(workspaceSlug: string, projectId: string, workflowId: string): Promise<WorkflowState[]> {
const data = await this.get<WorkflowState[] | { results: WorkflowState[] }>(
`/workspaces/${workspaceSlug}/projects/${projectId}/workflows/${workflowId}/states/`
);
return Array.isArray(data) ? data : data.results;
}

/**
* Attach states to a workflow
*/
Expand All @@ -23,6 +33,23 @@ export class States extends BaseResource {
return this.post<void>(`/workspaces/${workspaceSlug}/projects/${projectId}/workflows/${workflowId}/states/`, data);
}

/**
* Update a state's membership row (type, allow_issue_creation, is_default)
*/
async update(
workspaceSlug: string,
projectId: string,
workflowId: string,
stateId: string,
data: UpdateWorkflowState
): Promise<WorkflowState | null> {
const response = await this.patch<WorkflowState | null>(
`/workspaces/${workspaceSlug}/projects/${projectId}/workflows/${workflowId}/states/${stateId}/`,
data
);
return response ?? null;
}

/**
* Detach a state from a workflow
*/
Expand All @@ -31,4 +58,20 @@ export class States extends BaseResource {
`/workspaces/${workspaceSlug}/projects/${projectId}/workflows/${workflowId}/states/${stateId}/`
);
}

/**
* Transfer work items off a state and remove it from the workflow
*/
async transfer(
workspaceSlug: string,
projectId: string,
workflowId: string,
stateId: string,
newStateId: string
): Promise<void> {
return this.post<void>(
`/workspaces/${workspaceSlug}/projects/${projectId}/workflows/${workflowId}/states/${stateId}/transfer/`,
{ new_state_id: newStateId }
);
}
}
Loading
Loading