From 9fb71eba130da99b5c1883d6fc1533368f4d5239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dae=E2=9D=A4=EF=B8=8F?= <74119677+daeisbae@users.noreply.github.com> Date: Sun, 5 Jan 2025 02:24:59 -0800 Subject: [PATCH] Visualize the repository queue (#148) --- src/app/api/queue/route.ts | 29 +++++++++ src/app/get/queue/ProcessingItem.tsx | 61 ++++++++++++++++++ src/app/get/queue/page.tsx | 93 ++++++++++++++++++++++++++++ src/components/NavBar.tsx | 14 +++++ src/service/insert-queue.ts | 16 ++++- 5 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 src/app/api/queue/route.ts create mode 100644 src/app/get/queue/ProcessingItem.tsx create mode 100644 src/app/get/queue/page.tsx diff --git a/src/app/api/queue/route.ts b/src/app/api/queue/route.ts new file mode 100644 index 0000000..3e47b48 --- /dev/null +++ b/src/app/api/queue/route.ts @@ -0,0 +1,29 @@ +import InsertQueue from "@/service/insert-queue"; +import { NextRequest, NextResponse } from "next/server"; + +export async function GET(request: NextRequest) { + const insertQueue = InsertQueue.getInstance(); + const processing = insertQueue.processingItem; + const processingTime = insertQueue.processingTime; + const queue = insertQueue.queue; + if (processing || queue.length > 0) { + return NextResponse.json({ + status: 200, + body: { + processing, + queue, + processingTime, + }, + }); + } + if (processing || queue.length == 0) { + return NextResponse.json({ + status: 200, + body: { + processing, + processingTime, + }, + }); + } + return NextResponse.json({ error: "No repositories found" }, { status: 404 }); +} diff --git a/src/app/get/queue/ProcessingItem.tsx b/src/app/get/queue/ProcessingItem.tsx new file mode 100644 index 0000000..d8b000e --- /dev/null +++ b/src/app/get/queue/ProcessingItem.tsx @@ -0,0 +1,61 @@ +'use client'; + +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; +import React, { useState, useEffect } from "react"; + +interface ProcessingItemProps { + owner: string; + repo: string; + createdAt: string; +} + +export default function ProcessingItem({ + owner, + repo, + createdAt, +}: ProcessingItemProps) { + const [elapsedTime, setElapsedTime] = useState( + calculateElapsedTime(createdAt), + ); + + useEffect(() => { + const interval = setInterval(() => { + setElapsedTime(calculateElapsedTime(createdAt)); + }, 1000); + + return () => clearInterval(interval); + }, [createdAt]); + + return ( +
+ + + + {owner}/{repo} + + + +

+ Processing this item for: {elapsedTime} +

+
+
+
+ ); +} + +function calculateElapsedTime(createdAt: string): string { + const now = Date.now(); + const createdTime = new Date(createdAt).getTime(); + const elapsedTime = now - createdTime; + + const seconds = Math.floor(elapsedTime / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (days > 0) return `${days} days`; + if (hours > 0) return `${hours} hours`; + if (minutes > 0) return `${minutes} minutes`; + return `${seconds} seconds`; +} diff --git a/src/app/get/queue/page.tsx b/src/app/get/queue/page.tsx new file mode 100644 index 0000000..bd9d196 --- /dev/null +++ b/src/app/get/queue/page.tsx @@ -0,0 +1,93 @@ +'use client'; + +import { useEffect, useState } from "react"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; +import { Skeleton } from "@/components/ui/skeleton"; +import { AlertCircle } from 'lucide-react'; +import ProcessingItem from "./ProcessingItem"; +import React from "react"; + +export default function Page() { + const [queue, setQueue] = useState([]); + const [processing, setProcessing] = useState(null); + const [processingTime, setProcessingTime] = useState(""); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchData = async () => { + setIsLoading(true); + const response = await fetch("/api/queue"); + if (response.ok) { + const { body } = await response.json(); + setQueue(body.queue || []); + setProcessing(body.processing || null); + setProcessingTime(body.processingTime || ""); + } + setIsLoading(false); + }; + fetchData(); + }, []); + + if (isLoading) { + return ; + } + + return ( +
+

Currently Processing

+ {processing ? ( + + ) : ( +
+ + No repository currently being processed +
+ )} + +

Queue

+ {queue.length > 0 ? ( +
    + {queue.map((item, index) => ( +
  1. +
    + {item.owner}/{item.repo} +
    +
  2. + ))} +
+ ) : ( +
+ + No repositories in the queue +
+ )} +
+ ); +} + +function LoadingSkeleton() { + return ( +
+ + + + + + + + + + + + + + + + + + + +
+ ); +} + diff --git a/src/components/NavBar.tsx b/src/components/NavBar.tsx index 7abe376..9d861a4 100644 --- a/src/components/NavBar.tsx +++ b/src/components/NavBar.tsx @@ -80,6 +80,15 @@ export default function Navbar() { + + + + Queue + + + @@ -128,6 +137,11 @@ export default function Navbar() { Add Repo + + + Queue + + diff --git a/src/service/insert-queue.ts b/src/service/insert-queue.ts index 1d092ab..c297a26 100644 --- a/src/service/insert-queue.ts +++ b/src/service/insert-queue.ts @@ -22,9 +22,10 @@ export default class InsertQueue { private _isProcessing: boolean = false private _repoService: InsertRepoService private _processingItem: InsertItem | null = null + private _processingTime: string | null = null private static _instance?: InsertQueue private _rateLimitBeforeStop: number = 500 - private _maxQueueSize: number = 10 + private _maxQueueSize: number = 25 private _repository: Repository private constructor() { @@ -45,6 +46,10 @@ export default class InsertQueue { return this._instance; } + get queue(): InsertItem[] { + return this._queue; + } + /** * Adds a new item to the queue */ @@ -102,6 +107,13 @@ export default class InsertQueue { return this._processingItem } + /** + * Returns the current processing time in UTC timezone + */ + get processingTime(): string | null { + return this._processingTime + } + /** * Processes the queue, if rate limit is not reached * if rate limit is reached, it will wait for the reset time @@ -132,6 +144,7 @@ export default class InsertQueue { item: InsertItem ): Promise { console.log(`Processing item: ${item.owner}/${item.repo}`) + this._processingTime = new Date().toISOString() this._processingItem = item let result: RepositoryData | null = null try { @@ -145,6 +158,7 @@ export default class InsertQueue { } this._processingItem = null + this._processingTime = null return result } }