Skip to content

Commit 9fb71eb

Browse files
committed
Visualize the repository queue (#148)
1 parent aebadb5 commit 9fb71eb

File tree

5 files changed

+212
-1
lines changed

5 files changed

+212
-1
lines changed

src/app/api/queue/route.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import InsertQueue from "@/service/insert-queue";
2+
import { NextRequest, NextResponse } from "next/server";
3+
4+
export async function GET(request: NextRequest) {
5+
const insertQueue = InsertQueue.getInstance();
6+
const processing = insertQueue.processingItem;
7+
const processingTime = insertQueue.processingTime;
8+
const queue = insertQueue.queue;
9+
if (processing || queue.length > 0) {
10+
return NextResponse.json({
11+
status: 200,
12+
body: {
13+
processing,
14+
queue,
15+
processingTime,
16+
},
17+
});
18+
}
19+
if (processing || queue.length == 0) {
20+
return NextResponse.json({
21+
status: 200,
22+
body: {
23+
processing,
24+
processingTime,
25+
},
26+
});
27+
}
28+
return NextResponse.json({ error: "No repositories found" }, { status: 404 });
29+
}

src/app/get/queue/ProcessingItem.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use client';
2+
3+
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
4+
import React, { useState, useEffect } from "react";
5+
6+
interface ProcessingItemProps {
7+
owner: string;
8+
repo: string;
9+
createdAt: string;
10+
}
11+
12+
export default function ProcessingItem({
13+
owner,
14+
repo,
15+
createdAt,
16+
}: ProcessingItemProps) {
17+
const [elapsedTime, setElapsedTime] = useState(
18+
calculateElapsedTime(createdAt),
19+
);
20+
21+
useEffect(() => {
22+
const interval = setInterval(() => {
23+
setElapsedTime(calculateElapsedTime(createdAt));
24+
}, 1000);
25+
26+
return () => clearInterval(interval);
27+
}, [createdAt]);
28+
29+
return (
30+
<div>
31+
<Card>
32+
<CardHeader>
33+
<CardTitle>
34+
{owner}/{repo}
35+
</CardTitle>
36+
</CardHeader>
37+
<CardContent>
38+
<p className="text-sm text-muted-foreground mb-4">
39+
Processing this item for: {elapsedTime}
40+
</p>
41+
</CardContent>
42+
</Card>
43+
</div>
44+
);
45+
}
46+
47+
function calculateElapsedTime(createdAt: string): string {
48+
const now = Date.now();
49+
const createdTime = new Date(createdAt).getTime();
50+
const elapsedTime = now - createdTime;
51+
52+
const seconds = Math.floor(elapsedTime / 1000);
53+
const minutes = Math.floor(seconds / 60);
54+
const hours = Math.floor(minutes / 60);
55+
const days = Math.floor(hours / 24);
56+
57+
if (days > 0) return `${days} days`;
58+
if (hours > 0) return `${hours} hours`;
59+
if (minutes > 0) return `${minutes} minutes`;
60+
return `${seconds} seconds`;
61+
}

src/app/get/queue/page.tsx

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
'use client';
2+
3+
import { useEffect, useState } from "react";
4+
import { Card, CardContent, CardHeader } from "@/components/ui/card";
5+
import { Skeleton } from "@/components/ui/skeleton";
6+
import { AlertCircle } from 'lucide-react';
7+
import ProcessingItem from "./ProcessingItem";
8+
import React from "react";
9+
10+
export default function Page() {
11+
const [queue, setQueue] = useState([]);
12+
const [processing, setProcessing] = useState(null);
13+
const [processingTime, setProcessingTime] = useState("");
14+
const [isLoading, setIsLoading] = useState(true);
15+
16+
useEffect(() => {
17+
const fetchData = async () => {
18+
setIsLoading(true);
19+
const response = await fetch("/api/queue");
20+
if (response.ok) {
21+
const { body } = await response.json();
22+
setQueue(body.queue || []);
23+
setProcessing(body.processing || null);
24+
setProcessingTime(body.processingTime || "");
25+
}
26+
setIsLoading(false);
27+
};
28+
fetchData();
29+
}, []);
30+
31+
if (isLoading) {
32+
return <LoadingSkeleton />;
33+
}
34+
35+
return (
36+
<div className="container mx-auto p-4 h-[calc(80vh-2rem)] flex flex-col space-y-4">
37+
<h1>Currently Processing</h1>
38+
{processing ? (
39+
<ProcessingItem owner={processing.owner} repo={processing.repo} createdAt={processingTime} />
40+
) : (
41+
<div className="flex items-center justify-center h-full text-muted-foreground">
42+
<AlertCircle className="mr-2 h-4 w-4" />
43+
No repository currently being processed
44+
</div>
45+
)}
46+
47+
<h1>Queue</h1>
48+
{queue.length > 0 ? (
49+
<ol className="space-y-2 list-decimal list-inside list-numbered">
50+
{queue.map((item, index) => (
51+
<li key={item.repo} className={`p-0 sm:p-2 rounded-md ${index % 2 === 0 ? 'bg-muted' : ''}`}>
52+
<div className="flex items-center gap-2">
53+
<span className="font-medium">{item.owner}/{item.repo}</span>
54+
</div>
55+
</li>
56+
))}
57+
</ol>
58+
) : (
59+
<div className="flex items-center justify-center h-full text-muted-foreground">
60+
<AlertCircle className="mr-2 h-4 w-4" />
61+
No repositories in the queue
62+
</div>
63+
)}
64+
</div>
65+
);
66+
}
67+
68+
function LoadingSkeleton() {
69+
return (
70+
<div className="container mx-auto p-4 h-[calc(100vh-2rem)] flex flex-col space-y-4">
71+
<Card className="h-1/4 min-h-[200px]">
72+
<CardHeader>
73+
<Skeleton className="h-8 w-64" />
74+
</CardHeader>
75+
<CardContent>
76+
<Skeleton className="h-4 w-full" />
77+
<Skeleton className="h-4 w-full mt-2" />
78+
</CardContent>
79+
</Card>
80+
<Card className="h-3/4">
81+
<CardHeader>
82+
<Skeleton className="h-8 w-64" />
83+
</CardHeader>
84+
<CardContent>
85+
<Skeleton className="h-4 w-full" />
86+
<Skeleton className="h-4 w-full mt-2" />
87+
<Skeleton className="h-4 w-full mt-2" />
88+
</CardContent>
89+
</Card>
90+
</div>
91+
);
92+
}
93+

src/components/NavBar.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ export default function Navbar() {
8080
</NavigationMenuLink>
8181
</Link>
8282
</NavigationMenuItem>
83+
<NavigationMenuItem>
84+
<Link href="/get/queue" legacyBehavior passHref>
85+
<NavigationMenuLink
86+
className={cn(navigationMenuTriggerStyle(), "w-28")}
87+
>
88+
Queue
89+
</NavigationMenuLink>
90+
</Link>
91+
</NavigationMenuItem>
8392
</NavigationMenuList>
8493
</NavigationMenu>
8594

@@ -128,6 +137,11 @@ export default function Navbar() {
128137
Add Repo
129138
</Link>
130139
</SheetClose>
140+
<SheetClose asChild>
141+
<Link href="/get/queue" className="text-lg font-medium hover:text-primary transition-colors">
142+
Queue
143+
</Link>
144+
</SheetClose>
131145
</nav>
132146
</SheetContent>
133147
</Sheet>

src/service/insert-queue.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ export default class InsertQueue {
2222
private _isProcessing: boolean = false
2323
private _repoService: InsertRepoService
2424
private _processingItem: InsertItem | null = null
25+
private _processingTime: string | null = null
2526
private static _instance?: InsertQueue
2627
private _rateLimitBeforeStop: number = 500
27-
private _maxQueueSize: number = 10
28+
private _maxQueueSize: number = 25
2829
private _repository: Repository
2930

3031
private constructor() {
@@ -45,6 +46,10 @@ export default class InsertQueue {
4546
return this._instance;
4647
}
4748

49+
get queue(): InsertItem[] {
50+
return this._queue;
51+
}
52+
4853
/**
4954
* Adds a new item to the queue
5055
*/
@@ -102,6 +107,13 @@ export default class InsertQueue {
102107
return this._processingItem
103108
}
104109

110+
/**
111+
* Returns the current processing time in UTC timezone
112+
*/
113+
get processingTime(): string | null {
114+
return this._processingTime
115+
}
116+
105117
/**
106118
* Processes the queue, if rate limit is not reached
107119
* if rate limit is reached, it will wait for the reset time
@@ -132,6 +144,7 @@ export default class InsertQueue {
132144
item: InsertItem
133145
): Promise<RepositoryData | null> {
134146
console.log(`Processing item: ${item.owner}/${item.repo}`)
147+
this._processingTime = new Date().toISOString()
135148
this._processingItem = item
136149
let result: RepositoryData | null = null
137150
try {
@@ -145,6 +158,7 @@ export default class InsertQueue {
145158
}
146159

147160
this._processingItem = null
161+
this._processingTime = null
148162
return result
149163
}
150164
}

0 commit comments

Comments
 (0)