feat: dashboard maps the activity, follows a ticket's way and the proposals
Three more blocks on the project dashboard, also by REST, GraphQL and MCP: - Activity: what moved each day of the last 12 weeks, in a heatmap, with the busiest weekday and the longest lull. - A ticket's way: for the tickets done in the last 90 days, where the time from created to done went, status by status (waiting included), and how much of it was work (flow efficiency). - The public's proposals: received, waiting, under way, done and discarded, and the most voted still unanswered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1038,6 +1038,67 @@ export class FieldBreakdownGql {
|
||||
none!: number;
|
||||
}
|
||||
|
||||
@ObjectType('ActivityDay', { description: 'How much happened on one day' })
|
||||
export class ActivityDayGql {
|
||||
@Field({ description: 'YYYY-MM-DD' }) day!: string;
|
||||
@Field(() => Int) count!: number;
|
||||
}
|
||||
|
||||
@ObjectType('JourneyStep')
|
||||
export class JourneyStepGql {
|
||||
@Field({ description: 'Status id of the project settings' }) status!: string;
|
||||
@Field(() => Float, {
|
||||
description: 'Average days a closed ticket spent in it',
|
||||
})
|
||||
days!: number;
|
||||
@Field(() => Int, { description: 'Its part of the whole lead time, 0-100' })
|
||||
share!: number;
|
||||
}
|
||||
|
||||
@ObjectType('Journey', {
|
||||
description:
|
||||
'Where the time of the tickets done in the last 90 days went, created to done',
|
||||
})
|
||||
export class JourneyGql {
|
||||
@Field(() => Int) samples!: number;
|
||||
@Field(() => Float, { nullable: true }) leadDays!: number | null;
|
||||
@Field(() => Int, {
|
||||
nullable: true,
|
||||
description: 'Part of the lead time in an in-progress status, 0-100',
|
||||
})
|
||||
flowEfficiency!: number | null;
|
||||
@Field(() => [JourneyStepGql]) steps!: JourneyStepGql[];
|
||||
}
|
||||
|
||||
@ObjectType('WaitingProposal')
|
||||
export class WaitingProposalGql {
|
||||
@Field(() => ID) id!: string;
|
||||
@Field() key!: string;
|
||||
@Field() title!: string;
|
||||
@Field(() => Int) votes!: number;
|
||||
@Field(() => Int, { description: 'Days since it came in' }) days!: number;
|
||||
}
|
||||
|
||||
@ObjectType('ProposalsFunnel', {
|
||||
description: "The public's proposals, from received to done or discarded",
|
||||
})
|
||||
export class ProposalsFunnelGql {
|
||||
@Field(() => Int) received!: number;
|
||||
@Field(() => Int, { description: 'In the last 30 days' })
|
||||
receivedLately!: number;
|
||||
@Field(() => Int, {
|
||||
description: 'Open, not started, in no sprint nor release',
|
||||
})
|
||||
waiting!: number;
|
||||
@Field(() => Int) underWay!: number;
|
||||
@Field(() => Int) done!: number;
|
||||
@Field(() => Int) discarded!: number;
|
||||
@Field(() => [WaitingProposalGql], {
|
||||
description: 'Most voted waiting, at most 5',
|
||||
})
|
||||
topWaiting!: WaitingProposalGql[];
|
||||
}
|
||||
|
||||
@ObjectType('ProjectInsights', {
|
||||
description:
|
||||
'The dashboard: sprint under way, risks, aging, bottlenecks, release forecasts, team load, backlog health and changes',
|
||||
@@ -1062,6 +1123,16 @@ export class ProjectInsightsGql {
|
||||
description: 'One per active list custom field',
|
||||
})
|
||||
fields!: FieldBreakdownGql[];
|
||||
@Field(() => [ActivityDayGql], {
|
||||
description: 'The last 12 weeks from a Monday, oldest first (84 days)',
|
||||
})
|
||||
heatmap!: ActivityDayGql[];
|
||||
@Field(() => JourneyGql) journey!: JourneyGql;
|
||||
@Field(() => ProposalsFunnelGql, {
|
||||
nullable: true,
|
||||
description: 'Null while the project has never had a proposal',
|
||||
})
|
||||
proposals!: ProposalsFunnelGql | null;
|
||||
}
|
||||
|
||||
@ObjectType('Presence', {
|
||||
|
||||
@@ -1117,6 +1117,53 @@ export class FieldBreakdownDto {
|
||||
@ApiProperty({ description: 'Open tickets with no value' }) none!: number;
|
||||
}
|
||||
|
||||
export class ActivityDayDto {
|
||||
@ApiProperty({ example: '2026-10-15' }) day!: string;
|
||||
@ApiProperty() count!: number;
|
||||
}
|
||||
|
||||
export class JourneyStepDto {
|
||||
@ApiProperty({ description: 'Status id of the project settings' })
|
||||
status!: string;
|
||||
@ApiProperty({ description: 'Average days a closed ticket spent in it' })
|
||||
days!: number;
|
||||
@ApiProperty({ description: 'Its part of the whole lead time, 0-100' })
|
||||
share!: number;
|
||||
}
|
||||
|
||||
export class JourneyDto {
|
||||
@ApiProperty({ description: 'Tickets done in the last 90 days' })
|
||||
samples!: number;
|
||||
@ApiProperty({ type: Number, nullable: true }) leadDays!: number | null;
|
||||
@ApiProperty({
|
||||
type: Number,
|
||||
nullable: true,
|
||||
description: 'Part of the lead time in an in-progress status, 0-100',
|
||||
})
|
||||
flowEfficiency!: number | null;
|
||||
@ApiProperty({ type: [JourneyStepDto] }) steps!: JourneyStepDto[];
|
||||
}
|
||||
|
||||
export class WaitingProposalDto {
|
||||
@ApiProperty() id!: string;
|
||||
@ApiProperty() key!: string;
|
||||
@ApiProperty() title!: string;
|
||||
@ApiProperty() votes!: number;
|
||||
@ApiProperty({ description: 'Days since it came in' }) days!: number;
|
||||
}
|
||||
|
||||
export class ProposalsFunnelDto {
|
||||
@ApiProperty() received!: number;
|
||||
@ApiProperty({ description: 'In the last 30 days' }) receivedLately!: number;
|
||||
@ApiProperty({ description: 'Open, not started, in no sprint nor release' })
|
||||
waiting!: number;
|
||||
@ApiProperty() underWay!: number;
|
||||
@ApiProperty() done!: number;
|
||||
@ApiProperty() discarded!: number;
|
||||
@ApiProperty({ type: [WaitingProposalDto] })
|
||||
topWaiting!: WaitingProposalDto[];
|
||||
}
|
||||
|
||||
export class ProjectInsightsDto {
|
||||
@ApiProperty() generatedAt!: string;
|
||||
@ApiProperty({ type: SprintPulseDto, nullable: true })
|
||||
@@ -1139,6 +1186,18 @@ export class ProjectInsightsDto {
|
||||
description: 'One per active list custom field',
|
||||
})
|
||||
fields!: FieldBreakdownDto[];
|
||||
@ApiProperty({
|
||||
type: [ActivityDayDto],
|
||||
description: 'The last 12 weeks from a Monday, oldest first (84 days)',
|
||||
})
|
||||
heatmap!: ActivityDayDto[];
|
||||
@ApiProperty({ type: JourneyDto }) journey!: JourneyDto;
|
||||
@ApiProperty({
|
||||
type: ProposalsFunnelDto,
|
||||
nullable: true,
|
||||
description: 'Null while the project has never had a proposal',
|
||||
})
|
||||
proposals!: ProposalsFunnelDto | null;
|
||||
}
|
||||
|
||||
export class PortfolioProjectDto {
|
||||
|
||||
Reference in New Issue
Block a user