feat: custom fields per project
Text, number, date or option-list fields defined in the project settings (archived, never removed; their type is fixed once saved). Tickets hold them, edited in the side panel, listed under the title and filtered by option in the list view; also over GraphQL, REST (?field=ID:value) and MCP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,16 @@ export enum StatusCategory {
|
||||
CANCELLED = 'CANCELLED',
|
||||
}
|
||||
|
||||
/** What a custom field holds. */
|
||||
export enum CustomFieldType {
|
||||
TEXT = 'TEXT',
|
||||
NUMBER = 'NUMBER',
|
||||
/** `YYYY-MM-DD`. */
|
||||
DATE = 'DATE',
|
||||
/** One of its options. */
|
||||
SELECT = 'SELECT',
|
||||
}
|
||||
|
||||
export enum IssuePriority {
|
||||
LOWEST = 'LOWEST',
|
||||
LOW = 'LOW',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CustomFieldValue } from './workflow';
|
||||
import {
|
||||
IssueLinkType,
|
||||
IssuePriority,
|
||||
@@ -49,6 +50,8 @@ export interface CreateIssueInput {
|
||||
storyPoints?: number | null;
|
||||
startDate?: string | null;
|
||||
dueDate?: string | null;
|
||||
/** Only the fields to change; null or '' clears one. */
|
||||
customFields?: { fieldId: string; value: string | null }[];
|
||||
assigneeId?: string | null;
|
||||
releaseId?: string | null;
|
||||
/** Epic or ticket the new one breaks down. */
|
||||
@@ -97,6 +100,8 @@ export interface IssueListFilter {
|
||||
assigneeId?: string;
|
||||
unassigned?: boolean;
|
||||
releaseId?: string;
|
||||
/** Only tickets holding these custom values. */
|
||||
customFields?: CustomFieldValue[];
|
||||
limit: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
SprintStatus,
|
||||
StatusCategory,
|
||||
} from './enums';
|
||||
import { ProjectSettings } from './workflow';
|
||||
import { CustomFieldValue, ProjectSettings } from './workflow';
|
||||
|
||||
export interface UserRef {
|
||||
id: string;
|
||||
@@ -126,6 +126,8 @@ export interface IssueSummary {
|
||||
/** Planned start and end (`YYYY-MM-DD`), for the roadmap. */
|
||||
startDate: string | null;
|
||||
dueDate: string | null;
|
||||
/** Values of the project's custom fields (only those set). */
|
||||
customFields: CustomFieldValue[];
|
||||
/** In the trash since then (null: not in it). */
|
||||
trashedAt: string | null;
|
||||
commentCount: number;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IssuePriority, StatusCategory } from './enums';
|
||||
import { CustomFieldType, IssuePriority, StatusCategory } from './enums';
|
||||
|
||||
/** Colour tokens a status or ticket type can use (`--ts-c-*`). */
|
||||
export const SETTING_COLORS = [
|
||||
@@ -74,12 +74,29 @@ export interface AreaOption {
|
||||
archived: boolean;
|
||||
}
|
||||
|
||||
/** Per-project configuration: statuses (board order), transitions, types and areas. */
|
||||
/** A field the project adds to its tickets; its type never changes once saved. */
|
||||
export interface CustomFieldDefinition {
|
||||
id: string;
|
||||
name: string;
|
||||
type: CustomFieldType;
|
||||
/** The choices of a SELECT field. */
|
||||
options: string[];
|
||||
archived: boolean;
|
||||
}
|
||||
|
||||
/** A ticket's value of a custom field. */
|
||||
export interface CustomFieldValue {
|
||||
fieldId: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/** Per-project configuration: statuses (board order), transitions, types, areas and custom fields. */
|
||||
export interface ProjectSettings {
|
||||
statuses: WorkflowStatus[];
|
||||
transitions: StatusTransitions[];
|
||||
issueTypes: IssueTypeOption[];
|
||||
areas: AreaOption[];
|
||||
customFields: CustomFieldDefinition[];
|
||||
}
|
||||
|
||||
export const FINISHED_CATEGORIES: readonly StatusCategory[] = [
|
||||
@@ -114,6 +131,7 @@ const EMPTY: ProjectSettings = {
|
||||
transitions: [],
|
||||
issueTypes: [],
|
||||
areas: [],
|
||||
customFields: [],
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -167,6 +185,15 @@ export class Workflow {
|
||||
return this.settings.areas.filter((a) => !a.archived);
|
||||
}
|
||||
|
||||
/** Custom fields to fill in (archived ones only show where they have a value). */
|
||||
activeCustomFields(): CustomFieldDefinition[] {
|
||||
return (this.settings.customFields ?? []).filter((f) => !f.archived);
|
||||
}
|
||||
|
||||
customField(id: string): CustomFieldDefinition | undefined {
|
||||
return this.settings.customFields?.find((f) => f.id === id);
|
||||
}
|
||||
|
||||
/** Most tickets the status should hold (null: no limit). */
|
||||
wipLimit(id: string): number | null {
|
||||
return this.status(id).wipLimit ?? null;
|
||||
|
||||
Reference in New Issue
Block a user