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
10 changes: 9 additions & 1 deletion packages/das/src/api/api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { FETCH_QUEUE } from "../queue/constants";
import { AdminController } from "./admin.controller";
import { RequireApiKeyGuard } from "./require-api-key.guard";
import { HealthController } from "./health.controller";
import { DashboardController } from "./dashboard/dashboard.controller";
import { DashboardService } from "./dashboard/dashboard.service";
import { MinersController } from "./miners/miners.controller";
import { MinersService } from "./miners/miners.service";
import { PullsController } from "./pulls/pulls.controller";
Expand All @@ -31,11 +33,17 @@ import { PullsService } from "./pulls/pulls.service";
BullModule.registerQueue({ name: FETCH_QUEUE }),
],
controllers: [
DashboardController,
MinersController,
PullsController,
AdminController,
HealthController,
],
providers: [MinersService, PullsService, RequireApiKeyGuard],
providers: [
DashboardService,
MinersService,
PullsService,
RequireApiKeyGuard,
],
})
export class ApiModule {}
37 changes: 37 additions & 0 deletions packages/das/src/api/dashboard/dashboard.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { BadRequestException, Controller, Get, Query } from "@nestjs/common";
import { ApiOperation, ApiQuery, ApiTags } from "@nestjs/swagger";
import { DashboardService } from "./dashboard.service";

@ApiTags("Dashboard")
@Controller("api/v1/dashboard")
export class DashboardController {
constructor(private readonly dashboard: DashboardService) {}

@Get("issues")
@ApiOperation({
summary: "Slim issue rows for dashboard trend aggregation",
description:
"Returns every issue with `created_at` on or after `since`, plus " +
"every CLOSED issue whose `closed_at` is on or after `since`. " +
"The mirror is intentionally roster-blind: every issue is returned " +
"regardless of author. The dashboard blends with the gittensor API " +
"miner roster client-side to filter to subnet authors. " +
"Designed as a single bulk replacement for the dashboard's per-miner " +
"fan-out against `/miners/<id>/issues` (one call instead of N).",
})
@ApiQuery({
name: "since",
required: true,
description: "ISO timestamp — earliest creation/close date to include.",
})
async getIssues(@Query("since") since?: string): Promise<unknown> {
if (!since) {
throw new BadRequestException("`since` query parameter is required");
}
const parsed = new Date(since);
if (Number.isNaN(parsed.getTime())) {
throw new BadRequestException("`since` must be a valid ISO timestamp");
}
return this.dashboard.getIssues(parsed.toISOString());
}
}
58 changes: 58 additions & 0 deletions packages/das/src/api/dashboard/dashboard.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-assignment */
import { Injectable } from "@nestjs/common";
import { DataSource } from "typeorm";

interface DashboardIssueRow {
repo_full_name: string;
issue_number: number;
author_github_id: string | null;
created_at: string;
closed_at: string | null;
state: string;
state_reason: string | null;
solving_pr: { merged_at: string } | null;
}

@Injectable()
export class DashboardService {
constructor(private readonly dataSource: DataSource) {}

async getIssues(since: string): Promise<{
since: string;
generated_at: string;
issues: DashboardIssueRow[];
}> {
const rows = await this.dataSource.query(
`
SELECT
LOWER(i.repo_full_name) AS repo_full_name,
i.issue_number,
i.author_github_id,
i.created_at,
i.closed_at,
i.state,
i.state_reason,
(
SELECT json_build_object('merged_at', sp.merged_at)
FROM pull_requests sp
WHERE sp.repo_full_name = i.repo_full_name
AND sp.pr_number = i.solved_by_pr
AND sp.merged_at IS NOT NULL
LIMIT 1
) AS solving_pr
FROM issues i
WHERE
i.created_at >= $1
OR (i.state = 'CLOSED' AND i.closed_at >= $1)
ORDER BY i.created_at DESC
`,
[since],
);

return {
since,
generated_at: new Date().toISOString(),
issues: rows as DashboardIssueRow[],
};
}
}
Loading