import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { CustomFieldType, IssueLinkType, IssuePriority, NotificationKind, ProjectRole, ReleaseStatus, SprintStatus, SETTING_COLORS, StatusCategory, TYPE_ICONS, } from '@task-stream/api/tracker/domain'; /* * REST response bodies. Same shapes as the GraphQL types (both are views of * the read models); here they only document the contract for OpenAPI. */ export class UserRefDto { @ApiProperty({ description: 'Pireon user id' }) id!: string; @ApiProperty() name!: string; } export class ProjectMemberDto { @ApiProperty({ type: UserRefDto }) user!: UserRefDto; @ApiProperty({ enum: ProjectRole, enumName: 'ProjectRole' }) role!: ProjectRole; } export class WorkflowStatusDto { @ApiProperty({ example: 'IN_REVIEW', description: 'Stable id on tickets' }) id!: string; @ApiProperty({ example: 'En revisión' }) name!: string; @ApiProperty({ enum: StatusCategory, enumName: 'StatusCategory' }) category!: StatusCategory; @ApiProperty({ enum: SETTING_COLORS, enumName: 'SettingColor' }) color!: string; @ApiProperty({ description: 'Entering it needs a reason' }) requiresReason!: boolean; @ApiProperty({ description: 'No new tickets or transitions into it' }) archived!: boolean; @ApiProperty({ type: Number, nullable: true, description: 'Work-in-progress limit (a warning, not a lock)', }) wipLimit!: number | null; } export class StatusTransitionsDto { @ApiProperty({ example: 'IN_QA' }) from!: string; @ApiProperty({ type: [String], example: ['DONE', 'QA_FAILED'] }) to!: string[]; } export class IssueTemplateDto { @ApiProperty({ type: String, nullable: true }) description!: string | null; @ApiProperty({ type: [String] }) checklist!: string[]; } export class IssueTypeOptionDto { @ApiProperty({ example: 'BUG' }) id!: string; @ApiProperty({ example: 'Bug' }) name!: string; @ApiProperty({ enum: TYPE_ICONS, enumName: 'TypeIcon' }) icon!: string; @ApiProperty({ enum: SETTING_COLORS, enumName: 'SettingColor' }) color!: string; @ApiProperty() archived!: boolean; @ApiPropertyOptional({ type: IssueTemplateDto, description: 'What a new ticket of the type starts with', }) template?: IssueTemplateDto; } export class AreaOptionDto { @ApiProperty({ example: 'FRONTEND' }) id!: string; @ApiProperty({ example: 'Frontend' }) name!: string; @ApiProperty() archived!: boolean; } export class TeamDto { @ApiProperty({ example: 'FRONTEND' }) id!: string; @ApiProperty({ example: 'Frontend' }) name!: string; @ApiProperty({ type: [String] }) memberIds!: string[]; @ApiProperty() archived!: boolean; } export class CustomFieldDefinitionDto { @ApiProperty({ example: 'BROWSER' }) id!: string; @ApiProperty({ example: 'Navegador' }) name!: string; @ApiProperty({ enum: CustomFieldType, enumName: 'CustomFieldType', description: 'DATE values are YYYY-MM-DD', }) type!: CustomFieldType; @ApiProperty({ type: [String], description: 'The choices of a SELECT field' }) options!: string[]; @ApiProperty() archived!: boolean; } export class ProjectSettingsDto { @ApiProperty({ type: [WorkflowStatusDto], description: 'In board order' }) statuses!: WorkflowStatusDto[]; @ApiProperty({ type: [StatusTransitionsDto] }) transitions!: StatusTransitionsDto[]; @ApiProperty({ type: [IssueTypeOptionDto] }) issueTypes!: IssueTypeOptionDto[]; @ApiProperty({ type: [AreaOptionDto] }) areas!: AreaOptionDto[]; @ApiProperty({ type: [CustomFieldDefinitionDto] }) customFields!: CustomFieldDefinitionDto[]; @ApiProperty({ description: 'Definition of done: tickets reach DONE only with their checklist ticked', }) checklistBeforeDone!: boolean; @ApiProperty({ type: [TeamDto] }) teams!: TeamDto[]; } export class ProjectDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ example: 'WEB', description: 'Prefix of the ticket keys' }) key!: string; @ApiProperty({ example: 'Checkout revamp' }) name!: string; @ApiProperty({ type: String, nullable: true }) description!: string | null; @ApiProperty({ description: 'Readable by anyone, also without a token' }) isPublic!: boolean; @ApiProperty({ description: 'Takes bug reports and ideas from anyone (POST /api/explore/projects/{project}/proposals) while public', }) acceptsProposals!: boolean; @ApiProperty({ type: UserRefDto }) owner!: UserRefDto; @ApiProperty({ type: [ProjectMemberDto] }) members!: ProjectMemberDto[]; @ApiProperty({ type: ProjectSettingsDto }) settings!: ProjectSettingsDto; @ApiProperty({ format: 'date-time' }) createdAt!: string; @ApiProperty({ type: String, nullable: true, format: 'date-time', description: 'Archived since then (read-only); null while active', }) archivedAt!: string | null; @ApiProperty({ format: 'date-time' }) updatedAt!: string; @ApiProperty({ description: 'Version of the aggregate (event count)' }) version!: number; } export class IssueLinkDto { @ApiProperty({ enum: IssueLinkType, enumName: 'IssueLinkType' }) type!: IssueLinkType; @ApiProperty({ enum: ['OUTWARD', 'INWARD'], enumName: 'LinkDirection', description: 'OUTWARD: this issue → other. INWARD: other → this issue.', }) direction!: 'OUTWARD' | 'INWARD'; @ApiProperty({ format: 'uuid' }) issueId!: string; @ApiProperty({ example: 'WEB-3' }) issueKey!: string; } export class ReleaseRefDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ example: 'v1.4.0' }) name!: string; } export class AttachmentDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ example: 'captura-login.png' }) fileName!: string; @ApiProperty({ example: 'image/png' }) mimeType!: string; @ApiProperty({ description: 'Bytes', example: 48213 }) size!: number; @ApiProperty({ type: UserRefDto }) uploadedBy!: UserRefDto; @ApiProperty({ format: 'date-time' }) uploadedAt!: string; @ApiProperty({ type: String, nullable: true, format: 'uuid', description: 'The comment it was attached to, if any', }) commentId!: string | null; } export class ChecklistItemDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ example: 'Actualizar el changelog' }) text!: string; @ApiProperty() done!: boolean; } export class DevLinkDto { @ApiProperty({ enum: ['COMMIT', 'BRANCH', 'PULL_REQUEST'], enumName: 'DevLinkKind', }) kind!: string; @ApiProperty() url!: string; @ApiProperty() title!: string; @ApiProperty({ description: 'Short SHA, branch or PR number' }) ref!: string; @ApiProperty({ enum: ['OPEN', 'MERGED', 'CLOSED'], enumName: 'DevLinkState', nullable: true, }) state!: string | null; @ApiProperty({ example: 'gitea' }) provider!: string; @ApiProperty({ type: String, nullable: true, example: 'api', description: "The project's name for the repository it came from", }) repository!: string | null; @ApiProperty({ type: String, nullable: true }) author!: string | null; @ApiProperty({ format: 'date-time' }) at!: string; } export class GitRepositoryDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ example: 'api' }) name!: string; @ApiProperty({ type: String, nullable: true, description: 'Webhook secret of this repository; only owners and admins see it', }) secret!: string | null; @ApiProperty({ format: 'date-time' }) createdAt!: string; } export class GitIntegrationDto { @ApiProperty({ description: 'On while there is at least one repository' }) enabled!: boolean; @ApiProperty({ example: '/api/webhooks/git/3f2c…', description: 'The same for every repository of the project', }) webhookPath!: string; @ApiProperty({ type: [GitRepositoryDto] }) repositories!: GitRepositoryDto[]; } export class WebhookDeliveryDto { @ApiProperty({ format: 'date-time' }) at!: string; @ApiProperty({ example: 'issue.created' }) event!: string; @ApiProperty({ type: Number, nullable: true, description: 'HTTP status' }) status!: number | null; @ApiProperty() ok!: boolean; @ApiProperty({ type: String, nullable: true }) error!: string | null; } export class WebhookDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ format: 'uuid' }) projectId!: string; @ApiProperty() url!: string; @ApiProperty({ type: [String] }) events!: string[]; @ApiProperty() active!: boolean; @ApiProperty({ type: UserRefDto }) createdBy!: UserRefDto; @ApiProperty({ format: 'date-time' }) createdAt!: string; @ApiProperty({ type: WebhookDeliveryDto, nullable: true }) lastDelivery!: WebhookDeliveryDto | null; } export class WebhookCreatedDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ description: 'Signs every delivery. Shown only now.' }) secret!: string; } export class ImportLineDto { @ApiProperty({ description: 'Line of the file (the header is 1)' }) line!: number; @ApiPropertyOptional() reason?: string; @ApiPropertyOptional() message?: string; } export class ImportReportDto { @ApiProperty({ enum: ['taskstream', 'jira'] }) format!: string; @ApiProperty({ description: 'Nothing was created: a preview' }) dryRun!: boolean; @ApiProperty({ description: 'Rows that become tickets' }) rows!: number; @ApiProperty() created!: number; @ApiProperty({ type: [String], example: ['WEB-41', 'WEB-42'] }) keys!: string[]; @ApiProperty({ type: [ImportLineDto] }) skipped!: ImportLineDto[]; @ApiProperty({ type: [ImportLineDto] }) warnings!: ImportLineDto[]; } export class BulkIssueResultDto { @ApiProperty({ format: 'uuid' }) issueId!: string; @ApiProperty() ok!: boolean; @ApiProperty({ type: String, nullable: true }) error!: string | null; } export class BulkResultDto { @ApiProperty() updated!: number; @ApiProperty({ type: [BulkIssueResultDto] }) results!: BulkIssueResultDto[]; } class ShowcaseProjectDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ example: 'WEB' }) key!: string; @ApiProperty() name!: string; @ApiProperty({ type: String, nullable: true }) description!: string | null; } class ShowcaseSprintDto { @ApiProperty() name!: string; @ApiProperty({ type: String, nullable: true }) goal!: string | null; @ApiProperty({ format: 'date' }) startDate!: string; @ApiProperty({ format: 'date' }) endDate!: string; @ApiProperty() percent!: number; } class ShowcaseEpicDto { @ApiProperty() key!: string; @ApiProperty() title!: string; @ApiProperty({ type: String, nullable: true, format: 'date' }) startDate!: string | null; @ApiProperty({ type: String, nullable: true, format: 'date' }) dueDate!: string | null; @ApiProperty() percent!: number; @ApiProperty() done!: boolean; @ApiProperty({ description: 'How many want it' }) votes!: number; @ApiProperty({ example: '/p/WEB/issues/WEB-3', description: 'In the web app', }) path!: string; } class ShowcaseReleaseDto { @ApiProperty() name!: string; @ApiProperty({ enum: ReleaseStatus, enumName: 'ReleaseStatus' }) status!: ReleaseStatus; @ApiProperty({ type: String, nullable: true }) targetDate!: string | null; @ApiProperty({ type: String, nullable: true }) releasedAt!: string | null; @ApiProperty() percent!: number; @ApiProperty({ type: String, nullable: true, description: 'Shipped ones only', }) notes!: string | null; } class ShowcaseIssueDto { @ApiProperty() key!: string; @ApiProperty() title!: string; @ApiProperty({ description: 'A ticket type id' }) type!: string; @ApiProperty({ example: '/p/WEB/issues/WEB-9' }) path!: string; } class ShowcaseProposalDto extends ShowcaseIssueDto { @ApiProperty({ description: 'How many want it' }) votes!: number; } class ShowcaseProposalsDto { @ApiProperty({ description: 'Whether it takes bug reports and ideas now' }) open!: boolean; @ApiProperty({ type: String, nullable: true, example: '/p/WEB/proponer', description: 'The form in the web app, while it takes them', }) path!: string | null; @ApiProperty({ type: [ShowcaseProposalDto], description: 'Open proposals, most voted first', }) top!: ShowcaseProposalDto[]; } export class ShowcaseDto { @ApiProperty({ type: ShowcaseProjectDto }) project!: ShowcaseProjectDto; @ApiProperty({ type: ShowcaseSprintDto, nullable: true }) activeSprint!: ShowcaseSprintDto | null; @ApiProperty({ type: [ShowcaseEpicDto] }) roadmap!: ShowcaseEpicDto[]; @ApiProperty({ type: [ShowcaseReleaseDto] }) releases!: ShowcaseReleaseDto[]; @ApiProperty({ type: [ShowcaseIssueDto], description: 'Open tickets labelled good-first-issue', }) goodFirst!: ShowcaseIssueDto[]; @ApiProperty({ type: ShowcaseProposalsDto }) proposals!: ShowcaseProposalsDto; } export class ProposalResultDto { @ApiProperty({ type: String, nullable: true, example: 'WEB-42', description: 'The ticket it became', }) key!: string | null; @ApiProperty({ type: String, nullable: true, example: '/p/WEB/issues/WEB-42', description: 'Where to see it in the web app', }) path!: string | null; } export class GitDeliveryResultDto { @ApiProperty({ description: 'Tickets that got something new' }) recorded!: number; } export class IssueParentDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ example: 'WEB-3' }) key!: string; @ApiProperty({ example: 'Pago con tarjeta' }) title!: string; } export class SprintRefDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ example: 'Sprint 14' }) name!: string; } export class IssueDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ format: 'uuid' }) projectId!: string; @ApiProperty({ example: 'WEB' }) projectKey!: string; @ApiProperty({ example: 12 }) number!: number; @ApiProperty({ example: 'WEB-12', description: 'Human key' }) key!: string; @ApiProperty({ example: 'BUG', description: 'A ticket type id' }) type!: string; @ApiProperty({ type: String, nullable: true, example: 'FRONTEND' }) area!: string | null; @ApiProperty({ type: String, nullable: true, description: 'A team id' }) team!: string | null; @ApiProperty() title!: string; @ApiProperty({ type: String, nullable: true }) description!: string | null; @ApiProperty({ example: 'IN_PROGRESS', description: 'A status id' }) status!: string; @ApiProperty({ enum: StatusCategory, enumName: 'StatusCategory' }) statusCategory!: StatusCategory; @ApiProperty({ type: [String], description: "Statuses this ticket can move to next (project's workflow)", }) allowedTransitions!: string[]; @ApiProperty({ enum: IssuePriority, enumName: 'IssuePriority' }) priority!: IssuePriority; @ApiProperty({ type: UserRefDto }) reporter!: UserRefDto; @ApiProperty({ type: UserRefDto, nullable: true }) assignee!: UserRefDto | null; @ApiProperty({ type: ReleaseRefDto, nullable: true }) release!: ReleaseRefDto | null; @ApiProperty({ type: SprintRefDto, nullable: true }) sprint!: SprintRefDto | null; @ApiProperty({ type: [String] }) labels!: string[]; @ApiProperty({ type: String, nullable: true, description: 'Figma frame' }) figmaUrl!: string | null; @ApiProperty({ type: Number, nullable: true }) storyPoints!: number | null; @ApiProperty({ type: String, nullable: true, format: 'date', description: 'When work should start (roadmap)', }) startDate!: string | null; @ApiProperty({ type: String, nullable: true, format: 'date' }) dueDate!: string | null; @ApiProperty({ type: 'object', additionalProperties: { type: 'string' }, example: { BROWSER: 'Safari' }, description: 'Custom field id → value (only those set)', }) customFields!: Record; @ApiProperty({ type: [IssueLinkDto] }) links!: IssueLinkDto[]; @ApiProperty({ type: [AttachmentDto] }) attachments!: AttachmentDto[]; @ApiProperty({ type: [UserRefDto], description: 'People following it' }) watchers!: UserRefDto[]; @ApiProperty({ type: IssueParentDto, nullable: true, description: 'The epic or ticket this one breaks down', }) parent!: IssueParentDto | null; @ApiProperty({ type: [IssueParentDto], description: 'Open tickets it waits for: the ones that block it and the ones it depends on', }) blockedBy!: IssueParentDto[]; @ApiProperty({ description: 'How many want it done' }) votes!: number; @ApiProperty({ description: 'Whether I voted for it (false without a token)', }) voted!: boolean; @ApiProperty({ description: 'Place in the backlog and its board column (ascending, as text)', }) rank!: string; @ApiProperty({ type: [ChecklistItemDto] }) checklist!: ChecklistItemDto[]; @ApiProperty({ type: String, nullable: true, format: 'date-time', description: 'In the trash since then; null when it is not', }) trashedAt!: string | null; @ApiProperty({ type: [DevLinkDto], description: 'Commits, branches and pull requests that mention it', }) devLinks!: DevLinkDto[]; @ApiProperty() commentCount!: number; @ApiProperty({ format: 'date-time' }) createdAt!: string; @ApiProperty({ format: 'date-time' }) updatedAt!: string; @ApiProperty() version!: number; } export class SavedFilterCriteriaResponseDto { @ApiPropertyOptional({ type: [String] }) types?: string[]; @ApiPropertyOptional({ type: [String] }) areas?: string[]; @ApiPropertyOptional({ enum: IssuePriority, enumName: 'IssuePriority', isArray: true, }) priorities?: IssuePriority[]; @ApiPropertyOptional({ type: String, nullable: true }) assigneeId?: string | null; @ApiPropertyOptional() unassigned?: boolean; @ApiPropertyOptional({ type: String, nullable: true, format: 'uuid' }) releaseId?: string | null; @ApiPropertyOptional({ type: [String] }) labels?: string[]; @ApiPropertyOptional({ type: String, nullable: true }) search?: string | null; } export class SavedFilterDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ format: 'uuid' }) projectId!: string; @ApiProperty({ type: UserRefDto }) owner!: UserRefDto; @ApiProperty({ example: 'Bugs críticos de frontend' }) name!: string; @ApiProperty({ description: 'Visible to the whole project' }) shared!: boolean; @ApiProperty({ type: SavedFilterCriteriaResponseDto }) criteria!: SavedFilterCriteriaResponseDto; @ApiProperty({ format: 'date-time' }) createdAt!: string; } export class MyViewCriteriaResponseDto { @ApiPropertyOptional({ type: [String], format: 'uuid' }) projectIds?: string[]; @ApiPropertyOptional() assignedToMe?: boolean; @ApiPropertyOptional() watching?: boolean; @ApiPropertyOptional({ enum: StatusCategory, enumName: 'StatusCategory', isArray: true, }) categories?: StatusCategory[]; @ApiPropertyOptional({ enum: IssuePriority, enumName: 'IssuePriority', isArray: true, }) priorities?: IssuePriority[]; @ApiPropertyOptional() bugsOnly?: boolean; @ApiPropertyOptional() overdue?: boolean; @ApiPropertyOptional() search?: string; } export class MyViewDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ example: 'Bugs críticos de todos mis proyectos' }) name!: string; @ApiProperty({ type: MyViewCriteriaResponseDto }) criteria!: MyViewCriteriaResponseDto; @ApiProperty({ format: 'date-time' }) createdAt!: string; } export class SavedFilterIdDto { @ApiProperty({ format: 'uuid' }) id!: string; } export class IssuePageDto { @ApiProperty({ type: [IssueDto] }) items!: IssueDto[]; @ApiProperty({ description: 'Total matches (for pagination)' }) total!: number; } export class BoardColumnDto { @ApiProperty({ example: 'IN_PROGRESS' }) status!: string; @ApiProperty({ type: [IssueDto] }) issues!: IssueDto[]; @ApiProperty({ description: 'Tickets in the column (may exceed `issues`)' }) total!: number; } export class CommentReactionDto { @ApiProperty({ example: '👍' }) emoji!: string; @ApiProperty({ type: [UserRefDto] }) users!: UserRefDto[]; } export class CommentDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ format: 'uuid' }) issueId!: string; @ApiProperty({ type: UserRefDto }) author!: UserRefDto; @ApiProperty({ description: 'Empty on a deleted comment kept for its replies', }) body!: string; @ApiProperty({ type: String, nullable: true, format: 'uuid', description: 'The top-level comment it answers; null for a top-level one', }) replyTo!: string | null; @ApiProperty({ description: 'Deleted, but kept (without its text) while it has replies', }) deleted!: boolean; @ApiProperty({ type: [CommentReactionDto], description: 'In the order they are offered; only the ones somebody chose', }) reactions!: CommentReactionDto[]; @ApiProperty({ format: 'date-time' }) createdAt!: string; @ApiProperty({ type: String, nullable: true, format: 'date-time' }) editedAt!: string | null; @ApiProperty() version!: number; } export class CommentPageDto { @ApiProperty({ type: [CommentDto] }) items!: CommentDto[]; @ApiProperty() total!: number; } export class SprintDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ format: 'uuid' }) projectId!: string; @ApiProperty({ example: 'Sprint 14' }) name!: string; @ApiProperty({ type: String, nullable: true }) goal!: string | null; @ApiProperty({ format: 'date' }) startDate!: string; @ApiProperty({ format: 'date', description: 'Last day, included' }) endDate!: string; @ApiProperty({ enum: SprintStatus, enumName: 'SprintStatus' }) status!: SprintStatus; @ApiProperty({ type: String, nullable: true, format: 'date-time' }) startedAt!: string | null; @ApiProperty({ type: String, nullable: true, format: 'date-time' }) closedAt!: string | null; @ApiProperty({ format: 'date-time' }) createdAt!: string; @ApiProperty() version!: number; } export class SprintProgressDto { @ApiProperty() total!: number; @ApiProperty({ description: 'Done or cancelled' }) finished!: number; @ApiProperty({ minimum: 0, maximum: 100 }) percent!: number; @ApiProperty() storyPoints!: number; @ApiProperty() finishedStoryPoints!: number; } export class SprintBurndownDto { @ApiProperty({ format: 'uuid' }) sprintId!: string; @ApiProperty({ type: [String], format: 'date' }) dates!: string[]; @ApiProperty({ type: [Number], nullable: true, description: 'Tickets in the sprint (null: days to come)', }) scope!: (number | null)[]; @ApiProperty({ type: [Number], nullable: true }) remaining!: ( | number | null )[]; @ApiProperty({ type: [Number], nullable: true }) remainingPoints!: (number | null)[]; @ApiProperty({ type: [Number] }) ideal!: number[]; } export class ReleaseDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ format: 'uuid' }) projectId!: string; @ApiProperty({ example: 'v1.4.0' }) name!: string; @ApiProperty({ type: String, nullable: true }) description!: string | null; @ApiProperty({ type: String, nullable: true, format: 'date' }) targetDate!: string | null; @ApiProperty({ enum: ReleaseStatus, enumName: 'ReleaseStatus' }) status!: ReleaseStatus; @ApiProperty({ type: String, nullable: true, format: 'date-time' }) releasedAt!: string | null; @ApiProperty({ type: String, nullable: true, description: 'What shipped, told for people (Markdown)', }) notes!: string | null; @ApiProperty({ format: 'date-time' }) createdAt!: string; @ApiProperty() version!: number; } export class CountByStatusDto { @ApiProperty({ example: 'IN_PROGRESS', description: 'Status id' }) key!: string; @ApiProperty() count!: number; } export class CountByKeyDto { @ApiProperty({ description: 'Type, priority or area ("NONE" = unset)' }) key!: string; @ApiProperty() count!: number; } export class CountByAssigneeDto { @ApiProperty({ type: UserRefDto, nullable: true, description: 'Null = unassigned', }) user!: UserRefDto | null; @ApiProperty() count!: number; } export class ChildProgressDto { @ApiProperty() total!: number; @ApiProperty({ description: 'Done or cancelled' }) finished!: number; @ApiProperty({ minimum: 0, maximum: 100 }) percent!: number; @ApiProperty() storyPoints!: number; @ApiProperty() finishedStoryPoints!: number; } export class EpicDto { @ApiProperty({ type: IssueDto }) issue!: IssueDto; @ApiProperty({ type: ChildProgressDto }) progress!: ChildProgressDto; } export class ReleaseNotesDraftDto { @ApiProperty({ example: '## v1.4.0\n\n### Novedades\n\n- Pago con tarjeta (WEB-1)\n', }) markdown!: string; } export class ReleaseProgressDto { @ApiProperty() total!: number; @ApiProperty() finished!: number; @ApiProperty({ minimum: 0, maximum: 100 }) percent!: number; @ApiProperty({ type: [CountByStatusDto] }) byStatus!: CountByStatusDto[]; } export class ProjectStatsDto { @ApiProperty() total!: number; @ApiProperty({ description: 'Not done nor cancelled' }) open!: number; @ApiProperty({ type: [CountByStatusDto] }) byStatus!: CountByStatusDto[]; @ApiProperty({ type: [CountByKeyDto] }) byType!: CountByKeyDto[]; @ApiProperty({ type: [CountByKeyDto] }) byPriority!: CountByKeyDto[]; @ApiProperty({ type: [CountByKeyDto] }) byArea!: CountByKeyDto[]; @ApiProperty({ type: [CountByAssigneeDto], description: 'Open tickets per person', }) byAssignee!: CountByAssigneeDto[]; } export class PresenceDto { @ApiProperty({ type: UserRefDto }) user!: UserRefDto; @ApiProperty({ description: 'Editing the ticket or writing a comment' }) editing!: boolean; @ApiProperty({ format: 'date-time', description: 'Last heartbeat' }) at!: string; } export class ActivityDto { @ApiProperty() id!: string; @ApiProperty({ example: 'IssueStatusChanged' }) type!: string; @ApiProperty({ format: 'uuid' }) projectId!: string; @ApiProperty({ type: String, nullable: true }) issueId!: string | null; @ApiProperty({ type: String, nullable: true, example: 'WEB-12' }) issueKey!: string | null; @ApiProperty({ type: UserRefDto }) actor!: UserRefDto; @ApiProperty({ example: 'Ana movió WEB-12 de En curso a En revisión' }) summary!: string; @ApiProperty({ format: 'date-time' }) occurredAt!: string; } export class CommandResultDto { @ApiProperty({ description: 'Id of the aggregate written' }) id!: string; @ApiPropertyOptional({ description: 'Human key when one was assigned (project or issue)', example: 'WEB-13', }) key?: string; @ApiProperty({ description: 'Version after the write. Writes are acknowledged at once and the read ' + 'model catches up asynchronously: a GET may briefly return the previous ' + 'version (or 404 right after a create).', }) version!: number; } export class AttachmentResultDto extends CommandResultDto { @ApiProperty({ format: 'uuid', description: 'The new file; `![name](attachment:)` shows an image inside a description or comment', }) attachmentId!: string; } // ---- Statistics --------------------------------------------------------------- export class CreatedResolvedDto { @ApiProperty({ format: 'date', description: 'Monday of the week' }) week!: string; @ApiProperty() created!: number; @ApiProperty() resolved!: number; } export class FlowSeriesDto { @ApiProperty({ example: 'IN_PROGRESS' }) status!: string; @ApiProperty({ type: [Number], description: 'One value per date' }) counts!: number[]; } export class CumulativeFlowDto { @ApiProperty({ type: [String], format: 'date' }) dates!: string[]; @ApiProperty({ type: [FlowSeriesDto] }) series!: FlowSeriesDto[]; } export class DurationStatsDto { @ApiProperty() count!: number; @ApiProperty({ type: Number, nullable: true }) averageDays!: number | null; @ApiProperty({ type: Number, nullable: true, description: '85% of tickets take at most this long', }) p85Days!: number | null; } export class ThroughputDto { @ApiProperty({ format: 'date' }) week!: string; @ApiProperty({ type: UserRefDto, nullable: true }) user!: UserRefDto | null; @ApiProperty() count!: number; } export class QaStatsDto { @ApiProperty({ description: 'Left QA to a done status' }) passed!: number; @ApiProperty({ description: 'Left QA back to work' }) failed!: number; @ApiProperty({ type: Number, nullable: true, minimum: 0, maximum: 1 }) failureRate!: number | null; } export class ProjectAnalyticsDto { @ApiProperty({ format: 'date-time' }) from!: string; @ApiProperty({ format: 'date-time' }) to!: string; @ApiProperty({ type: [CreatedResolvedDto] }) createdVsResolved!: CreatedResolvedDto[]; @ApiProperty({ type: CumulativeFlowDto, description: 'Tickets in each status at the end of every day', }) cumulativeFlow!: CumulativeFlowDto; @ApiProperty({ type: DurationStatsDto, description: 'Start of work → done' }) cycleTime!: DurationStatsDto; @ApiProperty({ type: DurationStatsDto, description: 'Creation → done' }) leadTime!: DurationStatsDto; @ApiProperty({ type: [ThroughputDto] }) throughput!: ThroughputDto[]; @ApiProperty({ type: QaStatsDto }) qa!: QaStatsDto; } export class ReleaseBurnupDto { @ApiProperty({ format: 'uuid' }) releaseId!: string; @ApiProperty({ type: String, nullable: true, format: 'date' }) targetDate!: string | null; @ApiProperty({ type: [String], format: 'date' }) dates!: string[]; @ApiProperty({ type: [Number], description: 'Tickets in the release (not cancelled)', }) scope!: number[]; @ApiProperty({ type: [Number] }) done!: number[]; } // ---- Notifications ---------------------------------------------------------- export class NotificationDto { @ApiProperty() id!: string; @ApiProperty({ enum: NotificationKind, enumName: 'NotificationKind' }) kind!: NotificationKind; @ApiProperty({ format: 'uuid' }) projectId!: string; @ApiProperty({ example: 'WEB' }) projectKey!: string; @ApiProperty({ type: String, nullable: true, format: 'uuid' }) issueId!: string | null; @ApiProperty({ type: String, nullable: true, example: 'WEB-12' }) issueKey!: string | null; @ApiProperty({ type: UserRefDto }) actor!: UserRefDto; @ApiProperty({ example: 'Ana te asignó WEB-12: Login roto en Safari' }) summary!: string; @ApiProperty({ type: String, nullable: true, format: 'date-time', description: 'Null: unread', }) readAt!: string | null; @ApiProperty({ format: 'date-time' }) createdAt!: string; } export class MyNotificationsDto { @ApiProperty({ type: [NotificationDto] }) items!: NotificationDto[]; @ApiProperty({ description: 'All my unread ones, not just these' }) unread!: number; } export class CalendarFeedDto { @ApiProperty({ description: 'Secret of the calendar URL: GET /api/calendar/{token}.ics (iCalendar, no session)', }) token!: string; @ApiProperty() createdAt!: string; } export class NotificationSettingsResponseDto { @ApiProperty({ description: 'Copies by e-mail' }) email!: boolean; @ApiProperty({ description: 'Whether this installation can send e-mail' }) emailAvailable!: boolean; @ApiProperty({ type: String, nullable: true, description: 'VAPID public key to subscribe a device to push with; null: no push here', }) pushPublicKey!: string | null; } export class InsightIssueDto { @ApiProperty({ format: 'uuid' }) id!: string; @ApiProperty({ example: 'WEB-12' }) key!: string; @ApiProperty() title!: string; @ApiProperty({ description: 'Status id' }) status!: string; @ApiProperty({ type: UserRefDto, nullable: true }) assignee!: UserRefDto | null; @ApiProperty({ description: 'Days in its status (aging, stuck) or past / to its due date', }) days!: number; @ApiProperty({ type: String, nullable: true, format: 'date' }) dueDate!: string | null; } export class SprintPulseDto { @ApiProperty({ format: 'uuid' }) sprintId!: string; @ApiProperty() name!: string; @ApiProperty({ format: 'date' }) startDate!: string; @ApiProperty({ format: 'date' }) endDate!: string; @ApiProperty() totalDays!: number; @ApiProperty({ description: 'Days left after today' }) daysLeft!: number; @ApiProperty() tickets!: number; @ApiProperty() doneTickets!: number; @ApiProperty() points!: number; @ApiProperty() donePoints!: number; @ApiProperty({ type: Number, nullable: true }) pacePerDay!: number | null; @ApiProperty({ type: Number, nullable: true }) neededPerDay!: number | null; @ApiProperty({ description: 'At this pace, what is open would not be closed', }) behind!: boolean; } export class RisksDto { @ApiProperty({ type: [InsightIssueDto] }) overdue!: InsightIssueDto[]; @ApiProperty() overdueCount!: number; @ApiProperty({ type: [InsightIssueDto], description: 'Due within 7 days' }) dueSoon!: InsightIssueDto[]; @ApiProperty() dueSoonCount!: number; @ApiProperty({ type: [InsightIssueDto] }) blocked!: InsightIssueDto[]; @ApiProperty() blockedCount!: number; @ApiProperty({ type: [InsightIssueDto] }) stuck!: InsightIssueDto[]; @ApiProperty() stuckCount!: number; @ApiProperty() stuckDays!: number; } export class AgingDto { @ApiProperty({ type: [InsightIssueDto] }) issues!: InsightIssueDto[]; @ApiProperty({ type: Number, nullable: true, description: 'p85 cycle time of the last 90 days', }) thresholdDays!: number | null; } export class StatusTimeDto { @ApiProperty() status!: string; @ApiProperty({ type: Number, nullable: true }) averageDays!: number | null; @ApiProperty() samples!: number; @ApiProperty() open!: number; @ApiProperty({ type: Number, nullable: true }) wipLimit!: number | null; @ApiProperty() overLimit!: boolean; } export class ReleaseForecastDto { @ApiProperty({ format: 'uuid' }) releaseId!: string; @ApiProperty() name!: string; @ApiProperty({ type: String, nullable: true, format: 'date' }) targetDate!: string | null; @ApiProperty() total!: number; @ApiProperty() finished!: number; @ApiProperty({ type: String, nullable: true, format: 'date', description: 'Half of the simulated futures finish by then', }) likelyDate!: string | null; @ApiProperty({ type: String, nullable: true, format: 'date', description: '85% finish by then', }) safeDate!: string | null; @ApiProperty({ enum: ['DONE', 'NO_DATE', 'UNKNOWN', 'ON_TRACK', 'AT_RISK', 'LATE'], }) verdict!: string; } export class ChangesSinceDto { @ApiProperty() since!: string; @ApiProperty({ type: [InsightIssueDto] }) created!: InsightIssueDto[]; @ApiProperty() createdCount!: number; @ApiProperty({ type: [InsightIssueDto] }) finished!: InsightIssueDto[]; @ApiProperty() finishedCount!: number; @ApiProperty({ type: [InsightIssueDto], description: 'Sent back from QA' }) returned!: InsightIssueDto[]; @ApiProperty() returnedCount!: number; @ApiProperty({ description: 'Times you were mentioned here' }) mentions!: number; } export class TeamLoadDto { @ApiProperty({ type: UserRefDto }) user!: UserRefDto; @ApiProperty() openTickets!: number; @ApiProperty() openPoints!: number; @ApiProperty() blocked!: number; @ApiProperty({ type: Number, nullable: true, description: 'Points closed per sprint, last three closed sprints', }) pointsPerSprint!: number | null; @ApiProperty() overloaded!: boolean; } export class BacklogHealthDto { @ApiProperty() open!: number; @ApiProperty() unestimated!: number; @ApiProperty() noArea!: number; @ApiProperty() unassigned!: number; @ApiProperty() stale!: number; @ApiProperty() staleDays!: number; } export class PriorityCountDto { @ApiProperty({ enum: IssuePriority, enumName: 'IssuePriority' }) priority!: IssuePriority; @ApiProperty() count!: number; } export class BugWeekDto { @ApiProperty({ format: 'date', description: 'Monday (UTC)' }) week!: string; @ApiProperty() created!: number; @ApiProperty() closed!: number; } export class QaByAreaDto { @ApiProperty({ type: String, nullable: true, description: 'Null: no area' }) area!: string | null; @ApiProperty() returned!: number; @ApiProperty() passed!: number; } export class EscapedBugsDto { @ApiProperty({ format: 'uuid' }) releaseId!: string; @ApiProperty() name!: string; @ApiProperty() releasedAt!: string; @ApiProperty({ description: 'Bugs reported in the 14 days after' }) bugs!: number; } export class QualityDto { @ApiProperty({ type: [PriorityCountDto] }) openBugsByPriority!: PriorityCountDto[]; @ApiProperty({ type: [BugWeekDto], description: 'Last 8 weeks, oldest first', }) bugWeeks!: BugWeekDto[]; @ApiProperty({ type: [QaByAreaDto] }) qaByArea!: QaByAreaDto[]; @ApiProperty({ type: [EscapedBugsDto] }) escaped!: EscapedBugsDto[]; } export class OptionCountDto { @ApiProperty() value!: string; @ApiProperty() count!: number; } export class FieldBreakdownDto { @ApiProperty() fieldId!: string; @ApiProperty({ type: [OptionCountDto] }) counts!: OptionCountDto[]; @ApiProperty({ description: 'Open tickets with no value' }) none!: number; } export class ProjectInsightsDto { @ApiProperty() generatedAt!: string; @ApiProperty({ type: SprintPulseDto, nullable: true }) sprint!: SprintPulseDto | null; @ApiProperty({ type: RisksDto }) risks!: RisksDto; @ApiProperty({ type: AgingDto }) aging!: AgingDto; @ApiProperty({ type: [StatusTimeDto] }) bottlenecks!: StatusTimeDto[]; @ApiProperty({ type: [ReleaseForecastDto] }) releases!: ReleaseForecastDto[]; @ApiProperty({ type: ChangesSinceDto, nullable: true, description: 'Only when asked with `since`', }) changes!: ChangesSinceDto | null; @ApiProperty({ type: [TeamLoadDto] }) team!: TeamLoadDto[]; @ApiProperty({ type: BacklogHealthDto }) backlog!: BacklogHealthDto; @ApiProperty({ type: QualityDto }) quality!: QualityDto; @ApiProperty({ type: [FieldBreakdownDto], description: 'One per active list custom field', }) fields!: FieldBreakdownDto[]; } export class PortfolioProjectDto { @ApiProperty({ format: 'uuid' }) projectId!: string; @ApiProperty({ example: 'WEB' }) key!: string; @ApiProperty() name!: string; @ApiProperty() isPublic!: boolean; @ApiProperty() total!: number; @ApiProperty() open!: number; @ApiProperty() overdue!: number; @ApiProperty() blocked!: number; @ApiProperty() stuck!: number; @ApiProperty({ type: SprintPulseDto, nullable: true }) sprint!: SprintPulseDto | null; @ApiProperty({ type: [ReleaseForecastDto] }) releases!: ReleaseForecastDto[]; @ApiProperty({ type: [EpicDto], description: 'Open epics, nearest due date first (six at most)', }) epics!: EpicDto[]; @ApiProperty() updatedAt!: string; }