diff --git a/server/app/query/page.tsx b/server/app/query/page.tsx
index fca22de..3262b94 100644
--- a/server/app/query/page.tsx
+++ b/server/app/query/page.tsx
@@ -5,23 +5,17 @@ import Link from "next/link";
import { StatusPill, RunTimePill } from "@/app/query/view/[id]/components";
import {
- Status,
+ StatusEvent,
RemoteServer,
RemoteServerNames,
IPARemoteServers, //hack until the queryId is stored in a DB
- StatusByRemoteServer,
- StartTimeByRemoteServer,
- EndTimeByRemoteServer,
- initialStatusByRemoteServer,
- initialStartTimeByRemoteServer,
- initialEndTimeByRemoteServer,
+ StatusEventByRemoteServer,
+ initialStatusEventByRemoteServer,
} from "@/app/query/servers";
import { getQueryByUUID, Query } from "@/data/query";
type QueryData = {
- status: StatusByRemoteServer;
- startTime: StartTimeByRemoteServer;
- endTime: EndTimeByRemoteServer;
+ statusEvent: StatusEventByRemoteServer;
query: Query;
};
type DataByQuery = {
@@ -35,8 +29,7 @@ export default function Page() {
const updateData = (
query: Query,
remoteServer: RemoteServer,
- key: keyof QueryData,
- value: Status | number,
+ statusEvent: StatusEvent,
) => {
setDataByQuery((prev) => {
let _prev = prev;
@@ -46,9 +39,7 @@ export default function Page() {
_prev = {
..._prev,
[query.uuid]: {
- status: initialStatusByRemoteServer,
- startTime: initialStartTimeByRemoteServer,
- endTime: initialEndTimeByRemoteServer,
+ statusEvent: initialStatusEventByRemoteServer,
query: query,
},
};
@@ -58,9 +49,9 @@ export default function Page() {
..._prev,
[query.uuid]: {
..._prev[query.uuid],
- [key]: {
- ..._prev[query.uuid][key],
- [remoteServer.remoteServerName]: value,
+ statusEvent: {
+ ..._prev[query.uuid].statusEvent,
+ [remoteServer.remoteServerName]: statusEvent,
},
},
};
@@ -82,37 +73,25 @@ export default function Page() {
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);
+ setDataByQuery((prev) => {
+ return Object.fromEntries(
+ Object.keys(prev)
+ .filter((queryID) => queryIDs.includes(queryID))
+ .map((queryID) => [queryID, prev[queryID]]),
+ );
+ });
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];
+ const statusEvent: StatusEvent =
+ await remoteServer.queryStatus(queryID);
+ updateData(query, remoteServer, statusEvent);
}
}
- return () => {
- for (const ws of webSockets) {
- ws.close();
- }
- };
})();
- }, [queryIDs, dataByQuery]);
+ }, [queryIDs]);
return (
<>
@@ -122,10 +101,14 @@ export default function Page() {
Current Queries
+ {Object.keys(dataByQuery).length == 0 && (
+
+ None currently running.
+
+ )}
+
{Object.entries(dataByQuery).map(([queryID, queryData]) => {
- const statusByRemoteServer = queryData.status;
- const startTimeByRemoteServer = queryData.startTime;
- const endTimeByRemoteServer = queryData.endTime;
+ const statusEventByRemoteServer = queryData.statusEvent;
const query = queryData.query;
return (
@@ -142,19 +125,16 @@ export default function Page() {
{Object.values(IPARemoteServers).map(
(remoteServer: RemoteServer) => {
- const startTime =
- startTimeByRemoteServer[
+ const statusEvent: StatusEvent | null =
+ statusEventByRemoteServer[
remoteServer.remoteServerName
];
- const endTime =
- endTimeByRemoteServer[
- remoteServer.remoteServerName
- ];
-
- const status =
- statusByRemoteServer[
- remoteServer.remoteServerName
- ] ?? Status.UNKNOWN;
+ if (statusEvent === null) {
+ return <>>;
+ }
+ const status = statusEvent.status;
+ const startTime = statusEvent.startTime;
+ const endTime = statusEvent.endTime;
return (
-
-
+
);
@@ -181,10 +157,14 @@ export default function Page() {
{Object.values(IPARemoteServers).map(
(remoteServer: RemoteServer) => {
- const status =
- statusByRemoteServer[
+ const statusEvent: StatusEvent | null =
+ statusEventByRemoteServer[
remoteServer.remoteServerName
- ] ?? Status.UNKNOWN;
+ ];
+ if (statusEvent === null) {
+ return <>>;
+ }
+ const status = statusEvent.status;
return (
[[serverName], null]),
);
-export type StatsByRemoteServer = {
- [key in RemoteServerNames]: StatsDataPoint[];
-};
-
-export const initialStatsByRemoteServer: StatsByRemoteServer =
- Object.fromEntries(
- Object.values(RemoteServerNames).map((serverName) => [[serverName], []]),
- );
-
export type StartTimeByRemoteServer = {
[key in RemoteServerNames]: number | null;
};
@@ -67,6 +66,10 @@ export type EndTimeByRemoteServer = {
[key in RemoteServerNames]: number | null;
};
+export type StatusEventByRemoteServer = {
+ [key in RemoteServerNames]: StatusEvent | null;
+};
+
export const initialStartTimeByRemoteServer: StartTimeByRemoteServer =
Object.fromEntries(
Object.values(RemoteServerNames).map((serverName) => [[serverName], null]),
@@ -77,6 +80,26 @@ export const initialEndTimeByRemoteServer: StartTimeByRemoteServer =
Object.values(RemoteServerNames).map((serverName) => [[serverName], null]),
);
+export const initialStatusEventByRemoteServer: StatusEventByRemoteServer =
+ Object.fromEntries(
+ Object.values(RemoteServerNames).map((serverName) => [[serverName], null]),
+ );
+
+export interface StatsDataPoint {
+ timestamp: string;
+ memoryRSSUsage: number;
+ cpuUsage: number;
+}
+
+export type StatsByRemoteServer = {
+ [key in RemoteServerNames]: StatsDataPoint[];
+};
+
+export const initialStatsByRemoteServer: StatsByRemoteServer =
+ Object.fromEntries(
+ Object.values(RemoteServerNames).map((serverName) => [[serverName], []]),
+ );
+
export class RemoteServer {
protected baseURL: URL;
remoteServerName: RemoteServerNames;
@@ -96,6 +119,16 @@ export class RemoteServer {
throw new Error("Not Implemented");
}
+ queryStatusURL(id: string): URL {
+ return new URL(`/start/${id}/status`, this.baseURL);
+ }
+
+ async queryStatus(id: string): Promise
{
+ const status_response = await fetch(this.queryStatusURL(id));
+ const statusJSON = await status_response.json();
+ return buildStatusEventFromJSON(statusJSON);
+ }
+
runningQueriesURL(): URL {
return new URL(`/start/running-queries`, this.baseURL);
}
@@ -103,7 +136,7 @@ export class RemoteServer {
async runningQueries(): Promise {
const queries_response = await fetch(this.runningQueriesURL());
const queriesJSON = await queries_response.json();
- return queriesJSON["running_queries"];
+ return queriesJSON.running_queries;
}
logURL(id: string): URL {
@@ -197,31 +230,13 @@ export class RemoteServer {
openStatusSocket(
id: string,
- setStatus: (status: Status) => void,
- setStartTime: (startTime: number) => void,
- setEndTime: (endTime: number) => void,
+ setStatusEvent: (statusEvent: StatusEvent) => void,
): WebSocket {
const ws = this.statusSocket(id);
- const updateStatus = (status: Status) => {
- setStatus(status);
- };
-
- const updateStartTime = (startTime: number) => {
- setStartTime(startTime);
- };
-
- const updateEndTime = (endTime: number) => {
- setEndTime(endTime);
- };
-
ws.onmessage = (event) => {
- const eventData = JSON.parse(event.data);
- updateStartTime(eventData.start_time);
- updateEndTime(eventData.end_time ?? null);
- const statusString: string = eventData.status;
- const status = getStatusFromString(statusString);
- updateStatus(status);
+ const statusEvent = buildStatusEventFromJSON(JSON.parse(event.data));
+ setStatusEvent(statusEvent);
};
ws.onclose = (event) => {
diff --git a/server/app/query/view/[id]/components.tsx b/server/app/query/view/[id]/components.tsx
index 5aaf35a..2411ddd 100644
--- a/server/app/query/view/[id]/components.tsx
+++ b/server/app/query/view/[id]/components.tsx
@@ -2,7 +2,7 @@ import { useEffect, useState, useRef } from "react";
import { Source_Code_Pro } from "next/font/google";
import clsx from "clsx";
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/24/solid";
-import { Status, ServerLog } from "@/app/query/servers";
+import { Status, StatusEvent, ServerLog } from "@/app/query/servers";
const sourceCodePro = Source_Code_Pro({ subsets: ["latin"] });
@@ -64,15 +64,7 @@ function secondsToTime(e: number) {
return h + ":" + m + ":" + s;
}
-export function RunTimePill({
- status,
- startTime,
- endTime,
-}: {
- status: Status;
- startTime: number | null;
- endTime: number | null;
-}) {
+export function RunTimePill({ statusEvent }: { statusEvent: StatusEvent }) {
const [runTime, setRunTime] = useState(null);
const runTimeStr = runTime ? secondsToTime(runTime) : "N/A";
const intervalId = useRef | null>(null);
@@ -83,22 +75,27 @@ export function RunTimePill({
// which runs the timer. if a new one is needed, it's created.
clearTimeout(intervalId.current);
}
- if (startTime === null) {
+ if (statusEvent?.startTime === null) {
setRunTime(null);
} else {
- if (endTime !== null) {
- setRunTime(endTime - startTime);
+ if (statusEvent?.endTime !== null) {
+ setRunTime(statusEvent.endTime - statusEvent.startTime);
} else {
let newIntervalId = setInterval(() => {
- setRunTime(Date.now() / 1000 - startTime);
+ setRunTime(Date.now() / 1000 - statusEvent.startTime);
}, 1000);
intervalId.current = newIntervalId;
}
}
- }, [startTime, endTime]);
+ }, [statusEvent]);
return (
-
+
{runTimeStr}
);
diff --git a/server/app/query/view/[id]/page.tsx b/server/app/query/view/[id]/page.tsx
index 29cbdf7..02cfbc0 100644
--- a/server/app/query/view/[id]/page.tsx
+++ b/server/app/query/view/[id]/page.tsx
@@ -9,6 +9,7 @@ import {
} from "@/app/query/view/[id]/components";
import {
Status,
+ StatusEvent,
ServerLog,
RemoteServer,
RemoteServerNames,
@@ -18,10 +19,12 @@ import {
StatsByRemoteServer,
StartTimeByRemoteServer,
EndTimeByRemoteServer,
+ StatusEventByRemoteServer,
initialStatusByRemoteServer,
initialStatsByRemoteServer,
initialStartTimeByRemoteServer,
initialEndTimeByRemoteServer,
+ initialStatusEventByRemoteServer,
} from "@/app/query/servers";
import { StatsComponent } from "@/app/query/view/[id]/charts";
import { JSONSafeParse } from "@/app/utils";
@@ -46,40 +49,21 @@ export default function QueryPage({ params }: { params: { id: string } }) {
selectedRemoteServerLogs.includes(item.remoteServer.remoteServerNameStr),
);
- const [statusByRemoteServer, setStatusByRemoteServer] =
- useState
(initialStatusByRemoteServer);
const [statsByRemoteServer, setStatsByRemoteServer] =
useState(initialStatsByRemoteServer);
- const [startTimeByRemoteServer, setStartTimeByRemoteServer] =
- useState(initialStartTimeByRemoteServer);
- const [endTimeByRemoteServer, setEndTimeByRemoteServer] =
- useState(initialEndTimeByRemoteServer);
+ const [statusEventByRemoteServer, setStatusEventByRemoteServer] =
+ useState(initialStatusEventByRemoteServer);
- const updateStatus = (remoteServer: RemoteServer, status: Status) => {
- setStatusByRemoteServer((prevStatus) => ({
+ const updateStatusEvent = (
+ remoteServer: RemoteServer,
+ statusEvent: StatusEvent,
+ ) => {
+ setStatusEventByRemoteServer((prevStatus) => ({
...prevStatus,
- [remoteServer.remoteServerName]: status,
+ [remoteServer.remoteServerName]: statusEvent,
}));
};
- 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);
}
@@ -136,9 +120,7 @@ export default function QueryPage({ params }: { params: { id: string } }) {
const loggingWs = remoteServer.openLogSocket(query.uuid, setLogs);
const statusWs = remoteServer.openStatusSocket(
query.uuid,
- (status) => updateStatus(remoteServer, status),
- (startTime) => updateStartTime(remoteServer, startTime),
- (endTime) => updateEndTime(remoteServer, endTime),
+ (statusEvent) => updateStatusEvent(remoteServer, statusEvent),
);
const statsWs = remoteServer.openStatsSocket(
query.uuid,
@@ -242,14 +224,11 @@ export default function QueryPage({ params }: { params: { id: string } }) {
{Object.values(IPARemoteServers).map(
(remoteServer: RemoteServer) => {
- const startTime =
- startTimeByRemoteServer[remoteServer.remoteServerName];
- const endTime =
- endTimeByRemoteServer[remoteServer.remoteServerName];
-
- const status =
- statusByRemoteServer[remoteServer.remoteServerName] ??
- Status.UNKNOWN;
+ const statusEvent: StatusEvent | null =
+ statusEventByRemoteServer[remoteServer.remoteServerName];
+ if (statusEvent === null) {
+ return <>>;
+ }
return (
-
-
+
);
@@ -298,9 +273,11 @@ export default function QueryPage({ params }: { params: { id: string } }) {
{Object.values(IPARemoteServers).map(
(remoteServer: RemoteServer) => {
- const status =
- statusByRemoteServer[remoteServer.remoteServerName] ??
- Status.UNKNOWN;
+ const statusEvent: StatusEvent | null =
+ statusEventByRemoteServer[remoteServer.remoteServerName];
+ if (statusEvent === null) {
+ return <>>;
+ }
return (
-
-
+
);
diff --git a/sidecar/app/query/ipa.py b/sidecar/app/query/ipa.py
index eb980a5..044c410 100644
--- a/sidecar/app/query/ipa.py
+++ b/sidecar/app/query/ipa.py
@@ -251,7 +251,7 @@ def run(self):
for sidecar_url in sidecar_urls:
url = urlunparse(
sidecar_url._replace(
- scheme="https", path=f"/start/ipa-helper/{self.query_id}/status"
+ scheme="https", path=f"/start/{self.query_id}/status"
),
)
while True:
diff --git a/sidecar/app/routes/start.py b/sidecar/app/routes/start.py
index 6d3b169..e65f093 100644
--- a/sidecar/app/routes/start.py
+++ b/sidecar/app/routes/start.py
@@ -109,13 +109,13 @@ def start_ipa_helper(
return {"message": "Process started successfully", "query_id": query_id}
-@router.get("/ipa-helper/{query_id}/status")
-def get_ipa_helper_status(
+@router.get("/{query_id}/status")
+def get_query_status(
query_id: str,
request: Request,
):
query = get_query_from_query_id(request.app.state.QUERY_MANAGER, Query, query_id)
- return {"status": query.status.name}
+ return query.status_event_json
@router.get("/{query_id}/log-file")
diff --git a/sidecar/tests/app/routes/test_start.py b/sidecar/tests/app/routes/test_start.py
index 8ee1aae..e7e0478 100644
--- a/sidecar/tests/app/routes/test_start.py
+++ b/sidecar/tests/app/routes/test_start.py
@@ -130,16 +130,29 @@ def test_start_ipa_query_as_helper(mock_role):
)
-def test_get_ipa_helper_status_not_found():
+def test_get_status_not_found():
query_id = str(uuid4())
- response = client.get(f"/start/ipa-helper/{query_id}/status")
+ response = client.get(f"/start/{query_id}/status")
assert response.status_code == 404
-def test_get_ipa_helper_status(running_query):
- response = client.get(f"/start/ipa-helper/{running_query.query_id}/status")
+def test_get_status_running(running_query):
+ response = client.get(f"/start/{running_query.query_id}/status")
assert response.status_code == 200
- assert response.json() == {"status": str(Status.STARTING.name)}
+ status_event_json = response.json()
+ assert status_event_json["status"] == str(Status.STARTING.name)
+ assert "start_time" in status_event_json
+ assert "end_time" not in status_event_json
+
+
+def test_get_status_complete(running_query):
+ running_query.status = Status.COMPLETE
+ response = client.get(f"/start/{running_query.query_id}/status")
+ assert response.status_code == 200
+ status_event_json = response.json()
+ assert status_event_json["status"] == str(Status.COMPLETE.name)
+ assert "start_time" in status_event_json
+ assert "end_time" in status_event_json
def test_get_ipa_helper_log_file_not_found():