Skip to content

Commit

Permalink
add page for running queries
Browse files Browse the repository at this point in the history
  • Loading branch information
eriktaubeneck committed Jul 17, 2024
1 parent 5f28cca commit 6ed48ef
Show file tree
Hide file tree
Showing 8 changed files with 277 additions and 31 deletions.
2 changes: 1 addition & 1 deletion server/app/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Bars3Icon, BellIcon, XMarkIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import beerTap from "@/public/beer-tap.png";

const navigation = [{ name: "Dashboard", href: "/query", current: true }];
const navigation = [{ name: "Queries", href: "/query", current: true }];
const userNavigation = [
{ name: "Your Profile", href: "#" },
{ name: "Settings", href: "#" },
Expand Down
2 changes: 1 addition & 1 deletion server/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default async function Example() {
<div className="mt-10 flex items-center justify-center gap-x-6">
<div className="rounded-md bg-indigo-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-400">
{isLoggedIn ? (
<Link href="/query">Dashboard</Link>
<Link href="/query">Queries</Link>
) : (
<Link href="/login">Log in</Link>
)}
Expand Down
202 changes: 202 additions & 0 deletions server/app/query/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,213 @@
"use client";

import { useEffect, useState } from "react";
import Link from "next/link";

import { StatusPill, RunTimePill } from "@/app/query/view/[id]/components";
import {
Status,
RemoteServer,
RemoteServerNames,
IPARemoteServers, //hack until the queryId is stored in a DB
StatusByRemoteServer,
StartTimeByRemoteServer,
EndTimeByRemoteServer,
initialStatusByRemoteServer,
initialStartTimeByRemoteServer,
initialEndTimeByRemoteServer,
} from "@/app/query/servers";
import { getQueryByUUID, Query } from "@/data/query";

type QueryData = {
status: StatusByRemoteServer;
startTime: StartTimeByRemoteServer;
endTime: EndTimeByRemoteServer;
query: Query;
};
type DataByQuery = {
[queryID: string]: QueryData;
};

export default function Page() {
const [queryIDs, setQueryIDs] = useState<string[]>([]);
const [dataByQuery, setDataByQuery] = useState<DataByQuery>({});

const updateData = (
query: Query,
remoteServer: RemoteServer,
key: keyof QueryData,
value: Status | number,
) => {
setDataByQuery((prev) => {
let _prev = prev;
if (!prev.hasOwnProperty(query.uuid)) {
// if queryID not in dataByQuery yet,
// add initial status before updating value
_prev = {
..._prev,
[query.uuid]: {
status: initialStatusByRemoteServer,
startTime: initialStartTimeByRemoteServer,
endTime: initialEndTimeByRemoteServer,
query: query,
},
};
}

return {
..._prev,
[query.uuid]: {
..._prev[query.uuid],
[key]: {
..._prev[query.uuid][key],
[remoteServer.remoteServerName]: value,
},
},
};
});
};

useEffect(() => {
// poll runningQueries every second
(async () => {
const interval = setInterval(async () => {
const _queryIDs: string[] =
await IPARemoteServers[RemoteServerNames.Helper1].runningQueries();

setQueryIDs(_queryIDs);
}, 1000); // 1000 milliseconds = 1 second
return () => clearInterval(interval);
})();
}, []);

useEffect(() => {
(async () => {
let webSockets: WebSocket[] = [];

// remove queries when no longer running
const filteredDataByQuery = Object.fromEntries(
Object.keys(dataByQuery)
.filter((queryID) => queryIDs.includes(queryID))
.map((queryID) => [queryID, dataByQuery[queryID]]),
);
setDataByQuery(filteredDataByQuery);

for (const queryID of queryIDs) {
const query: Query = await getQueryByUUID(queryID);

for (const remoteServer of Object.values(IPARemoteServers)) {
const statusWs = remoteServer.openStatusSocket(
queryID,
(status) => updateData(query, remoteServer, "status", status),
(startTime) =>
updateData(query, remoteServer, "startTime", startTime),
(endTime) => updateData(query, remoteServer, "endTime", endTime),
);
webSockets = [...webSockets, statusWs];
}
}
return () => {
for (const ws of webSockets) {
ws.close();
}
};
})();
}, [queryIDs, dataByQuery]);

return (
<>
<div className="md:flex md:items-center md:justify-between">
<div className="min-w-0 flex-1">
<h2 className="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
Current Queries
</h2>

{Object.entries(dataByQuery).map(([queryID, queryData]) => {
const statusByRemoteServer = queryData.status;
const startTimeByRemoteServer = queryData.startTime;
const endTimeByRemoteServer = queryData.endTime;
const query = queryData.query;

return (
<div
className="mx-auto mt-10 w-full max-w-7xl overflow-hidden rounded-lg bg-slate-50 text-left shadow hover:bg-slate-200 dark:bg-slate-950 dark:hover:bg-slate-800"
key={queryID}
>
<Link href={`/query/view/${query.displayId}`}>
<div className="size-full border-b border-gray-300 px-4 py-2 font-bold text-slate-900 sm:p-2 dark:border-gray-700 dark:text-slate-100">
<h3 className="py-2 pl-2 text-base font-semibold leading-6 text-gray-900 dark:text-gray-100">
Query: {query.displayId}
</h3>
<div className="my-2 flex justify-end text-center">
<dl className="mb-2 grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-4">
{Object.values(IPARemoteServers).map(
(remoteServer: RemoteServer) => {
const startTime =
startTimeByRemoteServer[
remoteServer.remoteServerName
];
const endTime =
endTimeByRemoteServer[
remoteServer.remoteServerName
];

const status =
statusByRemoteServer[
remoteServer.remoteServerName
] ?? Status.UNKNOWN;

return (
<div
key={remoteServer.remoteServerName}
className="w-48 overflow-hidden rounded-lg bg-white px-4 py-2 shadow dark:bg-slate-900"
>
<dt className="truncate text-sm font-medium text-gray-500 dark:text-gray-300">
{remoteServer.toString()} Run Time
</dt>
<dd>
<RunTimePill
status={status}
startTime={startTime}
endTime={endTime}
/>
</dd>
</div>
);
},
)}
</dl>
</div>
<div className="my-2 flex justify-end text-center">
<dl className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-4">
{Object.values(IPARemoteServers).map(
(remoteServer: RemoteServer) => {
const status =
statusByRemoteServer[
remoteServer.remoteServerName
] ?? Status.UNKNOWN;

return (
<div
key={remoteServer.remoteServerName}
className="w-48 overflow-hidden rounded-lg bg-white px-4 py-2 shadow dark:bg-slate-900"
>
<dt className="truncate text-sm font-medium text-gray-500 dark:text-gray-300">
{remoteServer.remoteServerNameStr} Status
</dt>
<dd>
<StatusPill status={status} />
</dd>
</div>
);
},
)}
</dl>
</div>
</div>
</Link>
</div>
);
})}
</div>
</div>
</>
Expand Down
39 changes: 18 additions & 21 deletions server/app/query/servers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ export class RemoteServer {
throw new Error("Not Implemented");
}

runningQueriesURL(): URL {
return new URL(`/start/running-queries`, this.baseURL);
}

async runningQueries(): Promise<string[]> {
const queries_response = await fetch(this.runningQueriesURL());
const queriesJSON = await queries_response.json();
return queriesJSON["running_queries"];
}

logURL(id: string): URL {
return new URL(`/start/${id}/log-file`, this.baseURL);
}
Expand Down Expand Up @@ -187,35 +197,22 @@ export class RemoteServer {

openStatusSocket(
id: string,
setStatus: React.Dispatch<React.SetStateAction<StatusByRemoteServer>>,
setStartTime: React.Dispatch<React.SetStateAction<StartTimeByRemoteServer>>,
setEndTime: React.Dispatch<React.SetStateAction<EndTimeByRemoteServer>>,
setStatus: (status: Status) => void,
setStartTime: (startTime: number) => void,
setEndTime: (endTime: number) => void,
): WebSocket {
const ws = this.statusSocket(id);

const updateStatus = (status: Status) => {
setStatus((prevStatus) => ({
...prevStatus,
[this.remoteServerName]: status,
}));
setStatus(status);
};

const updateStartTime = (runTime: number) => {
setStartTime((prevStartTime) => {
return {
...prevStartTime,
[this.remoteServerName]: runTime,
};
});
const updateStartTime = (startTime: number) => {
setStartTime(startTime);
};

const updateEndTime = (runTime: number) => {
setEndTime((prevEndTime) => {
return {
...prevEndTime,
[this.remoteServerName]: runTime,
};
});
const updateEndTime = (endTime: number) => {
setEndTime(endTime);
};

ws.onmessage = (event) => {
Expand Down
31 changes: 28 additions & 3 deletions server/app/query/view/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,31 @@ export default function QueryPage({ params }: { params: { id: string } }) {
const [endTimeByRemoteServer, setEndTimeByRemoteServer] =
useState<EndTimeByRemoteServer>(initialEndTimeByRemoteServer);

const updateStatus = (remoteServer: RemoteServer, status: Status) => {
setStatusByRemoteServer((prevStatus) => ({
...prevStatus,
[remoteServer.remoteServerName]: status,
}));
};

const updateStartTime = (remoteServer: RemoteServer, runTime: number) => {
setStartTimeByRemoteServer((prevStartTime) => {
return {
...prevStartTime,
[remoteServer.remoteServerName]: runTime,
};
});
};

const updateEndTime = (remoteServer: RemoteServer, runTime: number) => {
setEndTimeByRemoteServer((prevEndTime) => {
return {
...prevEndTime,
[remoteServer.remoteServerName]: runTime,
};
});
};

function flipLogsHidden() {
setLogsHidden(!logsHidden);
}
Expand Down Expand Up @@ -111,9 +136,9 @@ export default function QueryPage({ params }: { params: { id: string } }) {
const loggingWs = remoteServer.openLogSocket(query.uuid, setLogs);
const statusWs = remoteServer.openStatusSocket(
query.uuid,
setStatusByRemoteServer,
setStartTimeByRemoteServer,
setEndTimeByRemoteServer,
(status) => updateStatus(remoteServer, status),
(startTime) => updateStartTime(remoteServer, startTime),
(endTime) => updateEndTime(remoteServer, endTime),
);
const statsWs = remoteServer.openStatsSocket(
query.uuid,
Expand Down
22 changes: 22 additions & 0 deletions server/data/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,28 @@ export async function getQuery(displayId: string): Promise<Query> {
throw new Error(`${displayId} not found.`);
}

export async function getQueryByUUID(uuid: string): Promise<Query> {
const supabase = await buildSupabaseServerClient();

const { status, data, error } = await supabase
.from("queries")
.select("*")
.eq("uuid", uuid)
.limit(1)
.maybeSingle();

if (error) {
console.error(error);
} else if (status === 200) {
if (data) {
return processQueryData(data);
} else {
notFound();
}
}
throw new Error(`${uuid} not found.`);
}

export async function createNewQuery(
params: FormData,
queryType: QueryType,
Expand Down
4 changes: 2 additions & 2 deletions sidecar/app/routes/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ class IncorrectRoleError(Exception):
pass


@router.get("/capacity_available")
@router.get("/capacity-available")
def capacity_available(
request: Request,
):
query_manager = request.app.state.QUERY_MANAGER
return {"capacity_available": query_manager.capacity_available}


@router.get("/running_queries")
@router.get("/running-queries")
def running_queries(
request: Request,
):
Expand Down
Loading

0 comments on commit 6ed48ef

Please sign in to comment.