diff --git a/src/api/query-hooks/health.tsx b/src/api/query-hooks/health.tsx index 4c2e72116c..418d611de5 100644 --- a/src/api/query-hooks/health.tsx +++ b/src/api/query-hooks/health.tsx @@ -1,7 +1,7 @@ import { useQuery } from "@tanstack/react-query"; -import { HealthCheck } from "../types/health"; import { getStartValue } from "../../utils/common"; -import { getCanaryGraph } from "../services/topology"; +import { getCanaryGraph, getHealthCheckDetails } from "../services/topology"; +import { HealthCheck } from "../types/health"; export function useCanaryGraphQuery( timeRange: string, @@ -17,3 +17,14 @@ export function useCanaryGraphQuery( return res.data ?? undefined; }); } + +export function useGetCheckDetails(checkId?: string) { + return useQuery({ + queryKey: ["check", "details", checkId], + queryFn: async () => { + const res = await getHealthCheckDetails(checkId!); + return res; + }, + enabled: checkId !== undefined + }); +} diff --git a/src/api/services/topology.ts b/src/api/services/topology.ts index 52f883c027..ff5b2447ef 100644 --- a/src/api/services/topology.ts +++ b/src/api/services/topology.ts @@ -8,15 +8,19 @@ import { Snapshot } from "../axios"; import { resolve } from "../resolve"; +import { SchemaResourceI } from "../schemaResources"; import { PaginationInfo } from "../types/common"; import { HealthCheck, HealthCheckStatus, HealthCheckSummary } from "../types/health"; -import { ComponentHealthCheckView, Topology } from "../types/topology"; -import { ComponentTeamItem } from "../types/topology"; -import { ComponentTemplateItem } from "../types/topology"; +import { + ComponentHealthCheckView, + ComponentTeamItem, + ComponentTemplateItem, + Topology +} from "../types/topology"; interface IParam { id?: string; @@ -224,8 +228,17 @@ export const getHealthCheckSummary = async (id: string) => { return null; }; +export const getHealthCheckDetails = async (id: string) => { + const res = await resolve[] | null>( + IncidentCommander.get( + `/checks?id=eq.${id}&select=*,canaries(id,name,source),agents(id, name)` + ) + ); + return res.data?.[0]; +}; + export const getHealthCheckItem = async (id: string) => { - const res = await IncidentCommander.get( + const res = await IncidentCommander.get( `/canaries?id=eq.${id}` ); return res.data?.[0]; diff --git a/src/api/types/health.ts b/src/api/types/health.ts index d8929c2c85..436a14e050 100644 --- a/src/api/types/health.ts +++ b/src/api/types/health.ts @@ -1,4 +1,6 @@ +import { SchemaResourceI } from "../schemaResources"; import { Agent, Avatar, Timestamped } from "../traits"; +import { AgentItem } from "./common"; export interface HealthChecksResponse { duration: number; @@ -21,7 +23,6 @@ export interface HealthCheck extends Timestamped, Avatar, Agent { checkStatuses: HealthCheckStatus[]; lastRuntime: string; description?: string; - source?: string; spec?: any; icon?: string; endpoint?: string; @@ -32,11 +33,14 @@ export interface HealthCheck extends Timestamped, Avatar, Agent { owner?: any; loading?: boolean; severity?: string; + next_runtime?: string; + agents?: AgentItem; + canaries?: SchemaResourceI; } export type HealthCheckSummary = Pick< HealthCheck, - "id" | "name" | "icon" | "type" | "status" | "severity" + "id" | "name" | "icon" | "type" | "status" | "severity" | "next_runtime" >; export interface HealthCheckStatus { diff --git a/src/components/Canary/CanaryPopup/CheckDetails.tsx b/src/components/Canary/CanaryPopup/CheckDetails.tsx index c202cccb2d..f8f14998c2 100644 --- a/src/components/Canary/CanaryPopup/CheckDetails.tsx +++ b/src/components/Canary/CanaryPopup/CheckDetails.tsx @@ -1,22 +1,25 @@ import React, { Suspense, useMemo, useRef } from "react"; import { useCanaryGraphQuery } from "../../../api/query-hooks/health"; import { HealthCheck } from "../../../api/types/health"; +import { isCanaryUI } from "../../../context/Environment"; import { capitalizeFirstLetter, toFixedIfNecessary } from "../../../utils/common"; -import { usePrevious } from "../../../utils/hooks"; +import { relativeDateTime } from "../../../utils/date"; import mixins from "../../../utils/mixins.module.css"; import { DropdownStandaloneWrapper } from "../../Dropdown/StandaloneWrapper"; import { TimeRange, timeRanges } from "../../Dropdown/TimeRange"; +import { Head } from "../../Head/Head"; import { Duration } from "../renderers"; import { CanaryCheckDetailsSpecTab } from "./CanaryCheckDetailsSpec"; import CheckLabels from "./CheckLabels"; import { CheckStat } from "./CheckStat"; import { StatusHistory } from "./StatusHistory/StatusHistory"; +import { calculateDefaultTimeRangeValue } from "./Utils/calculateDefaultTimeRangeValue"; import { PopupTabs } from "./tabs"; import { getUptimePercentage } from "./utils"; -import dayjs from "dayjs"; + const CanaryStatusChart = React.lazy(() => import("../CanaryStatusChart").then(({ CanaryStatusChart }) => ({ default: CanaryStatusChart @@ -32,105 +35,18 @@ export function CheckDetails({ check, ...rest }: CheckDetailsProps) { const containerRef = useRef(null); const statsRef = useRef(null); - const prevCheck = usePrevious(check); - const validCheck = check || prevCheck; - - const timeRange = useMemo(() => { - // get time passed since last runtime - const lapsedTime = dayjs(validCheck?.lastRuntime!).diff(dayjs(), "minute"); - // if passed time is less than 1 hour, use 1 hour time range - if (lapsedTime < 60) { - return "1h"; - } - // if passed time is less than 3 hours, use 3 hours time range - if (lapsedTime < 180) { - return "3h"; - } - - // if passed time is less than 6 hours, use 6 hours time range - if (lapsedTime < 360) { - return "6h"; - } - - // if passed time is less than 12 hours, use 12 hours time range - if (lapsedTime < 720) { - return "12h"; - } - - // if passed time is less than 1 day, use 1 day time range - if (lapsedTime < 1440) { - return "1d"; - } - - // if passed time is less than 2 days, use 2 days time range - if (lapsedTime < 2880) { - return "2d"; - } - - // if passed time is less than 3 days, use 3 days time range - if (lapsedTime < 4320) { - return "3d"; - } - - // if passed time is less than 1 week, use 1 week time range - if (lapsedTime < 10080) { - return "1w"; - } - - // if passed time is less than 2 weeks, use 2 weeks time range - if (lapsedTime < 20160) { - return "2w"; - } - - // if passed time is less than 3 weeks, use 3 weeks time range - if (lapsedTime < 30240) { - return "3w"; - } - - // if passed time is less than 1 month, use 1 month time range - if (lapsedTime < 43200) { - return "1mo"; - } - - // if passed time is less than 2 months, use 2 months time range - if (lapsedTime < 86400) { - return "2mo"; - } - - // if passed time is less than 3 months, use 3 months time range - if (lapsedTime < 129600) { - return "3mo"; - } - - // if passed time is less than 6 months, use 6 months time range - if (lapsedTime < 259200) { - return "6mo"; - } - - // if passed time is less than 1 year, use 1 year time range - if (lapsedTime < 525600) { - return "1y"; - } - - // if passed time is less than 2 years, use 2 years time range - if (lapsedTime < 1051200) { - return "2y"; - } - - // if passed time is less than 3 years, use 3 years time range - if (lapsedTime < 1576800) { - return "3y"; - } + const timeRange = useMemo( + () => calculateDefaultTimeRangeValue(check), + [check] + ); - // if passed time is more than 3 years, use 3 years time range - if (lapsedTime > 1576800) { - return "5y"; - } + const { data } = useCanaryGraphQuery(timeRange, check); - return "1h"; - }, [validCheck?.lastRuntime]); + const lastRun = relativeDateTime(check?.lastRuntime!).replace(" ago", ""); - const { data } = useCanaryGraphQuery(timeRange, validCheck); + const nextRun = check?.next_runtime + ? relativeDateTime(check?.next_runtime!).replace(" ago", "") + : "-"; const uptimeValue = toFixedIfNecessary( getUptimePercentage(data)?.toString()!, @@ -138,118 +54,139 @@ export function CheckDetails({ check, ...rest }: CheckDetailsProps) { ); const validUptime = - !Number.isNaN(validCheck?.uptime?.passed) && - !Number.isNaN(validCheck?.uptime?.failed); + !Number.isNaN(check?.uptime?.passed) && + !Number.isNaN(check?.uptime?.failed); - const severityValue = - validCheck?.severity ?? validCheck?.spec?.severity ?? "-"; + const severityValue = check?.severity ?? check?.spec?.severity ?? "-"; - if (validCheck == null) { + if (!check) { return null; } + const suffix = isCanaryUI ? "" : " | Mission Control"; + return ( -
-
- -
- + {check.name && } +
+
+ +
+ + + {data?.uptime?.passed} passed + + + {data?.uptime?.failed} failed + +
+ ) + } + /> + } + /> + } + /> + } + /> + + - - {data?.uptime?.passed} passed + + {lastRun}{" "} + ago - - {data?.uptime?.failed} failed + + {nextRun}{" "} + (next run)
- ) - } - /> - } - /> - } - /> - } - /> - -
-
-
- Health overview - } - defaultValue={timeRange ?? timeRanges[1].value} - name="timeRange" + } />
-
- Loading..
}> - - +
+
+ Health overview + } + defaultValue={timeRange ?? timeRanges[1].value} + name="timeRange" + /> +
+
+ Loading..
}> + + +
+ + +
+ ), + class: + "flex-1 flex flex-col overflow-y-hidden border-b h-full border-gray-300" + }, + specs: { + label: "Spec", + content: , + class: `flex-1 flex flex-col overflow-y-auto border border-gray-300 ${mixins.appleScrollbar}` + } + }} + />
- - - - ), - class: - "flex-1 flex flex-col overflow-y-hidden border-b h-full border-gray-300" - }, - specs: { - label: "Spec", - content: , - class: `flex-1 flex flex-col overflow-y-auto border border-gray-300 ${mixins.appleScrollbar}` - } - }} - /> - + ); } diff --git a/src/components/Canary/CanaryPopup/CheckLabels.tsx b/src/components/Canary/CanaryPopup/CheckLabels.tsx index cc9b62cf92..abf3233bc4 100644 --- a/src/components/Canary/CanaryPopup/CheckLabels.tsx +++ b/src/components/Canary/CanaryPopup/CheckLabels.tsx @@ -64,6 +64,8 @@ export default function CheckLabels({ check }: Props) { className="bg-blue-100 text-blue-800" key={item.key} tag={item} + keyClassName="text-gray-500" + valueClassName="text-gray-800" /> ))} @@ -83,6 +85,8 @@ export default function CheckLabels({ check }: Props) { tags={checkLabels} minimumItemsToShow={checkLabels.length} childClassName="bg-blue-100 text-blue-800" + keyClassName="text-gray-500" + valueClassName="text-gray-800" /> diff --git a/src/components/Canary/CanaryPopup/CheckStat.tsx b/src/components/Canary/CanaryPopup/CheckStat.tsx index eabf4338d3..41fbe5057d 100644 --- a/src/components/Canary/CanaryPopup/CheckStat.tsx +++ b/src/components/Canary/CanaryPopup/CheckStat.tsx @@ -26,7 +26,9 @@ export function CheckStat({ className={`flex flex-col ${containerClassName} ${className}`} {...rest} > -
{title}
+ {title && ( +
{title}
+ )}
{value} {append} diff --git a/src/components/Canary/CanaryPopup/CheckTitle.tsx b/src/components/Canary/CanaryPopup/CheckTitle.tsx index e87baf28fa..a5160633a7 100644 --- a/src/components/Canary/CanaryPopup/CheckTitle.tsx +++ b/src/components/Canary/CanaryPopup/CheckTitle.tsx @@ -1,10 +1,9 @@ import clsx from "clsx"; -import React, { useMemo } from "react"; +import React from "react"; import { HealthCheck } from "../../../api/types/health"; -import { usePrevious } from "../../../utils/hooks"; +import AgentName from "../../Agents/AgentName"; import { Badge } from "../../Badge"; import { Icon } from "../../Icon"; -import AgentName from "../../Agents/AgentName"; type CheckTitleProps = Omit, "size"> & { check?: Partial; @@ -17,13 +16,10 @@ export function CheckTitle({ size = "large", ...rest }: CheckTitleProps) { - const prevCheck = usePrevious(check); - const validCheck = check || prevCheck; - // todo: this is a hack to get the agent id from the check, before it gets // update to a check without the agent id - // eslint-disable-next-line react-hooks/exhaustive-deps - const agentID = useMemo(() => check?.agent_id, []); + const agentID = check?.agent_id; + const namespace = check?.namespace; return (
@@ -34,49 +30,56 @@ export function CheckTitle({ )} >
- {validCheck?.name} + {check?.name} {" "} {size === "large" && ( - + )}
- {validCheck?.endpoint} + {check?.endpoint}
- - - - + +
+
); diff --git a/src/components/Canary/CanaryPopup/Utils/calculateDefaultTimeRangeValue.tsx b/src/components/Canary/CanaryPopup/Utils/calculateDefaultTimeRangeValue.tsx new file mode 100644 index 0000000000..fe785979cb --- /dev/null +++ b/src/components/Canary/CanaryPopup/Utils/calculateDefaultTimeRangeValue.tsx @@ -0,0 +1,100 @@ +import dayjs from "dayjs"; +import { HealthCheck } from "../../../../api/types/health"; + +export function calculateDefaultTimeRangeValue( + validCheck: Pick, "lastRuntime"> | undefined +) { + // get time passed since last runtime + const lapsedTime = dayjs(validCheck?.lastRuntime!).diff(dayjs(), "minute"); + console.log("lapsedTime", lapsedTime); + // if passed time is less than 1 hour, use 1 hour time range + if (lapsedTime < 60) { + return "1h"; + } + // if passed time is less than 3 hours, use 3 hours time range + if (lapsedTime < 180) { + return "3h"; + } + + // if passed time is less than 6 hours, use 6 hours time range + if (lapsedTime < 360) { + return "6h"; + } + + // if passed time is less than 12 hours, use 12 hours time range + if (lapsedTime < 720) { + return "12h"; + } + + // if passed time is less than 1 day, use 1 day time range + if (lapsedTime < 1440) { + return "1d"; + } + + // if passed time is less than 2 days, use 2 days time range + if (lapsedTime < 2880) { + return "2d"; + } + + // if passed time is less than 3 days, use 3 days time range + if (lapsedTime < 4320) { + return "3d"; + } + + // if passed time is less than 1 week, use 1 week time range + if (lapsedTime < 10080) { + return "1w"; + } + + // if passed time is less than 2 weeks, use 2 weeks time range + if (lapsedTime < 20160) { + return "2w"; + } + + // if passed time is less than 3 weeks, use 3 weeks time range + if (lapsedTime < 30240) { + return "3w"; + } + + // if passed time is less than 1 month, use 1 month time range + if (lapsedTime < 43200) { + return "1mo"; + } + + // if passed time is less than 2 months, use 2 months time range + if (lapsedTime < 86400) { + return "2mo"; + } + + // if passed time is less than 3 months, use 3 months time range + if (lapsedTime < 129600) { + return "3mo"; + } + + // if passed time is less than 6 months, use 6 months time range + if (lapsedTime < 259200) { + return "6mo"; + } + + // if passed time is less than 1 year, use 1 year time range + if (lapsedTime < 525600) { + return "1y"; + } + + // if passed time is less than 2 years, use 2 years time range + if (lapsedTime < 1051200) { + return "2y"; + } + + // if passed time is less than 3 years, use 3 years time range + if (lapsedTime < 1576800) { + return "3y"; + } + + // if passed time is more than 3 years, use 3 years time range + if (lapsedTime > 1576800) { + return "5y"; + } + + return "1h"; +} diff --git a/src/components/Canary/HealthCheckEdit/index.tsx b/src/components/Canary/HealthCheckEdit/index.tsx index 45a58b8eb7..b7d5f8cbcb 100644 --- a/src/components/Canary/HealthCheckEdit/index.tsx +++ b/src/components/Canary/HealthCheckEdit/index.tsx @@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query"; import { FaEdit } from "react-icons/fa"; import { Link } from "react-router-dom"; import { useCanaryCheckItemQuery } from "../../../api/query-hooks"; +import { SchemaResourceI } from "../../../api/schemaResources"; import { getComponentTemplate, getTopology @@ -11,7 +12,7 @@ import { Icon } from "../../Icon"; import TextSkeletonLoader from "../../SkeletonLoader/TextSkeletonLoader"; type HealthCheckEditComponentProps = { - check: Pick; + check: Pick; }; function HealthCheckEditComponent({ check }: HealthCheckEditComponentProps) { diff --git a/src/components/Canary/minimal.tsx b/src/components/Canary/minimal.tsx index 05851b2d33..792b77faee 100644 --- a/src/components/Canary/minimal.tsx +++ b/src/components/Canary/minimal.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; +import { useGetCheckDetails } from "../../api/query-hooks/health"; import { getCanaries } from "../../api/services/topology"; import { EvidenceType } from "../../api/types/evidence"; import { HealthCheck } from "../../api/types/health"; @@ -42,6 +43,8 @@ const MinimalCanaryFC = ({ const [selectedCheck, setSelectedCheck] = useState>(); const [openChecksModal, setOpenChecksModal] = useState(false); + const { data: check } = useGetCheckDetails(selectedCheck?.id); + const handleCheckSelect = useCallback( (check: Pick) => { setSelectedCheck({ @@ -108,16 +111,16 @@ const MinimalCanaryFC = ({ /> )} clearCheck()} - title={} + title={} size="full" containerClassName="flex flex-col h-full overflow-y-auto" bodyClass="flex flex-col flex-1 overflow-y-auto" >
diff --git a/src/components/TagList/TagList.tsx b/src/components/TagList/TagList.tsx index ba319bd119..fd72e5c6f8 100644 --- a/src/components/TagList/TagList.tsx +++ b/src/components/TagList/TagList.tsx @@ -40,6 +40,8 @@ type TagListProps = React.HTMLProps & { tags: TagEntry[]; minimumItemsToShow?: number; childClassName?: string; + keyClassName?: string; + valueClassName?: string; }; type TagItemProps = { @@ -49,12 +51,16 @@ type TagItemProps = { key: string; value: string; }; + keyClassName?: string; + valueClassName?: string; }; export function TagItem({ tag: { key, value }, containerWidth, - className = "bg-gray-200 text-gray-600 mx-1" + className = "bg-gray-200 text-gray-600 mx-1", + keyClassName = "", + valueClassName = "font-light" }: TagItemProps) { return (
- {key}: - {value} + {key}: + {value}
); @@ -77,6 +83,8 @@ export function TagList({ minimumItemsToShow = 1, className = `flex flex-row text-left items-start flex-1`, childClassName = "bg-gray-200 text-gray-600 mx-1", + keyClassName = "", + valueClassName = "font-light", ...rest }: TagListProps) { const [showAll, setShowAll] = useState(false); @@ -142,6 +150,8 @@ export function TagList({ tag={{ key, value }} className={childClassName} containerWidth={containerWidth} + keyClassName={keyClassName} + valueClassName={valueClassName} /> ))}