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:
@@ -43,6 +43,7 @@ import {
|
||||
Attachment,
|
||||
Comment,
|
||||
DevLink,
|
||||
Issue,
|
||||
IssueLink,
|
||||
IssueLinkType,
|
||||
IssuePriority,
|
||||
@@ -70,6 +71,7 @@ import {
|
||||
MarkdownComponent,
|
||||
} from '../ui/markdown';
|
||||
import { RichEditorComponent } from '../ui/rich-editor';
|
||||
import { CustomFieldComponent } from '../ui/custom-field';
|
||||
|
||||
const NONE = '';
|
||||
/** Same ceiling as the API's ATTACHMENTS_MAX_MB default. */
|
||||
@@ -80,6 +82,7 @@ const MAX_FILE_BYTES = 25 * 1024 * 1024;
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
BlockedComponent,
|
||||
CustomFieldComponent,
|
||||
ReactionsComponent,
|
||||
MarkdownComponent,
|
||||
RichEditorComponent,
|
||||
@@ -843,6 +846,16 @@ const MAX_FILE_BYTES = 25 * 1024 * 1024;
|
||||
>
|
||||
<pi-label>Área</pi-label>
|
||||
</pi-select-field>
|
||||
@for (field of customFields(); track field.id) {
|
||||
<ts-custom-field
|
||||
[field]="field"
|
||||
[value]="customValue(issue, field.id)"
|
||||
[disabled]="!project.canContribute() || field.archived"
|
||||
(valueChange)="
|
||||
update({ customFields: [{ fieldId: field.id, value: $event }] })
|
||||
"
|
||||
/>
|
||||
}
|
||||
|
||||
@if (!issue.trashedAt && canTrash(issue)) {
|
||||
<pi-action-button
|
||||
@@ -1314,10 +1327,26 @@ export class IssuePage {
|
||||
storyPoints?: number | null;
|
||||
startDate?: string | null;
|
||||
dueDate?: string | null;
|
||||
customFields?: { fieldId: string; value: string | null }[];
|
||||
}): void {
|
||||
this.facade.update(changes);
|
||||
}
|
||||
|
||||
/** The project's active fields, plus archived ones this ticket still has. */
|
||||
protected readonly customFields = computed(() => {
|
||||
const issue = this.facade.issue();
|
||||
return (this.project.workflow().settings.customFields ?? []).filter(
|
||||
(f) =>
|
||||
!f.archived || !!issue?.customFields?.some((v) => v.fieldId === f.id),
|
||||
);
|
||||
});
|
||||
|
||||
protected customValue(issue: Issue, fieldId: string): string | null {
|
||||
return (
|
||||
issue.customFields?.find((v) => v.fieldId === fieldId)?.value ?? null
|
||||
);
|
||||
}
|
||||
|
||||
protected link(): void {
|
||||
const targetKey = this.linkTarget();
|
||||
if (!targetKey) return;
|
||||
|
||||
Reference in New Issue
Block a user