diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index 4ef1651202..87f5b7ea7e 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -7,18 +7,19 @@ import { DashboardHeader } from "./common/headerComponents/DashboardHeader"; import { dashboardVersion, DashboardVersion } from "./common/DashboardVersion"; import { Route, Routes } from "react-router"; import { useEffect, useMemo, useState } from "react"; -import { NewClient, schemaMapping, enhanceHealthMetrics } from "./utils"; +import { NewClient, schemaMapping, enhanceHealthMetrics, processDeploymentData } from "./utils"; import { useQuery } from "@apollo/client"; import { decentralizedNetworkSubgraphsQuery } from "./queries/decentralizedNetworkSubgraphsQuery"; function App() { console.log("RUNNING VERSION " + dashboardVersion); const [loading, setLoading] = useState(false); - const [protocolsToQuery, setProtocolsToQuery] = useState<{ - [type: string]: { [proto: string]: { [network: string]: string } }; - }>({}); - + const [protocolsToQuery, setProtocolsToQuery] = useState({}); const [issuesMapping, setIssuesMapping] = useState({}); + // Add cache timestamp state + const [lastFetchTime, setLastFetchTime] = useState(0); + // Cache expiry time - 15 minutes (in milliseconds) + const CACHE_EXPIRY_TIME = 15 * 60 * 1000; const getGithubRepoIssues = () => { try { @@ -52,56 +53,56 @@ function App() { const getDeployments = () => { if (Object.keys(protocolsToQuery).length === 0) { setLoading(true); + + // Check if cache is still valid (less than 15 minutes old) + const currentTime = Date.now(); + const shouldUseCachedData = currentTime - lastFetchTime < CACHE_EXPIRY_TIME; + + // If we have cached data that's still valid, use it from local storage + if (shouldUseCachedData) { + try { + const cachedData = localStorage.getItem("deploymentData"); + if (cachedData) { + const parsedData = JSON.parse(cachedData); + setProtocolsToQuery(parsedData); + setLoading(false); + console.log("Using cached deployment data"); + return; + } + } catch (error) { + console.error("Error retrieving cached data:", error); + // Continue with fetch if cache retrieval fails + } + } + try { - fetch(process.env.REACT_APP_MESSARI_STATUS_URL!, { - method: "GET", - headers: { - Accept: "application/json", - "x-messari-api-key": process.env.REACT_APP_MESSARI_API_KEY!, - }, - }) - .then(function (res) { - return res.json(); - }) - .then(function (json) { + // Fetch the raw deployment data directly from GitHub + fetch("https://raw.githubusercontent.com/messari/subgraphs/master/deployment/deployment.json") + .then((response) => response.json()) + .then((deploymentData) => { + // Process the deployment data on the fly + const processedData = processDeploymentData(deploymentData); setLoading(false); - // Hacky fix for the subgraph status API returning an error response, setting protocols to empty object. - // Status Page Will Not Load With This Condition, but the charting will work. - if (JSON.stringify(Object.keys(json)) == JSON.stringify(['error', 'data'])) { - console.log("Error Response from Messari Subgraph Status API. Setting protocols to empty object."); - json = {}; - } - - // Process the data to ensure all health metrics are properly populated - if (json && typeof json === 'object') { - try { - Object.keys(json).forEach(protocolName => { - const protocol = json[protocolName]; - if (protocol.deployments) { - Object.keys(protocol.deployments).forEach(deploymentKey => { - // Enhance the health metrics of each deployment - protocol.deployments[deploymentKey] = enhanceHealthMetrics(protocol.deployments[deploymentKey]); - }); - - // Debug the first protocol's health metrics - if (Object.keys(json).length > 0) { - debugHealthMetrics(protocol); - } - } - }); - } catch (err) { - console.error("Error enhancing health metrics:", err); - } + setProtocolsToQuery(processedData); + + // Update cache timestamp and store the processed data in localStorage + setLastFetchTime(currentTime); + try { + localStorage.setItem("deploymentData", JSON.stringify(processedData)); + } catch (cacheError) { + console.error("Error caching deployment data:", cacheError); } - - setProtocolsToQuery(json); }) .catch((err) => { - console.log(err); + setLoading(false); + console.error("Error loading deployment data from GitHub:", err); + // Show empty state instead of trying to fallback to the deleted file + setProtocolsToQuery({}); }); } catch (error) { setLoading(false); - console.error(error); + console.error("Error in getDeployments:", error); + setProtocolsToQuery({}); } } }; @@ -127,7 +128,7 @@ function App() { subgraphEndpoints[schemaType][protocolName] = {}; } } - if (protocol.deployments && typeof protocol.deployments === 'object') { + if (protocol.deployments && typeof protocol.deployments === "object") { Object.values(protocol.deployments).forEach((depoData: any) => { if (!depoData?.services) { return; @@ -219,35 +220,12 @@ function App() { }); setDecentralizedDeployments(decenDepos); } - }, [decentralized]); - - const debugHealthMetrics = (protocol: any) => { - if (!protocol || !protocol.deployments) { - console.warn("No protocol or deployments data found for debugging"); - return; - } - - try { - // Log a sample of health metrics for the first deployment - const firstDeploymentKey = Object.keys(protocol.deployments)[0]; - if (!firstDeploymentKey) { - console.warn("No deployments found for debugging"); - return; - } - - const deployment = protocol.deployments[firstDeploymentKey]; - if (!deployment || !deployment.services) { - console.warn("Invalid deployment data for debugging"); - return; - } - - const hostedService = deployment.services["hosted-service"]; - const decentralizedNetwork = deployment.services["decentralized-network"]; + }, [decentralized, decentralizedDeployments]); - } catch (err) { - console.error("Error in debug health metrics:", err); - } - }; + // Remove this useEffect when the API service is back online + useEffect(() => { + getDeployments(); + }, []); // eslint-disable-line react-hooks/exhaustive-deps return (
diff --git a/dashboard/src/deployments/DeploymentsPage.tsx b/dashboard/src/deployments/DeploymentsPage.tsx index dbbb3a0408..dd6ca8ef8e 100644 --- a/dashboard/src/deployments/DeploymentsPage.tsx +++ b/dashboard/src/deployments/DeploymentsPage.tsx @@ -106,7 +106,7 @@ function DeploymentsPage({ navigate(`subgraph?endpoint=${val}&tab=protocol`); } }} - placeholder="Subgraph query name ie. messari/balancer-v2-ethereum" + placeholder="Enter subgraph QM hash e.g. QmXMJ2Hnhhoz6bGFNtTBjnf7kAk9CNCQG7r4R5b7fyVjD7" > Load Subgraph @@ -128,27 +128,6 @@ function DeploymentsPage({ > {showSubgraphCountTable ? "Hide" : "Show"} Subgraph Count Table - navigate("protocols-list")} - > - Protocols To Develop - - navigate("version-comparison")} - > - Version Comparison - { // Sections to move to the bottom - const bottomSections = ['bridge', 'erc20', 'erc721']; - + const bottomSections = ["bridge", "erc20", "erc721"]; + // If a is a bottom section and b is not, move a down - if (bottomSections.includes(schemaMapping[a[0]] || a[0].toLowerCase()) && - !bottomSections.includes(schemaMapping[b[0]] || b[0].toLowerCase())) + if ( + bottomSections.includes(schemaMapping[a[0]] || a[0].toLowerCase()) && + !bottomSections.includes(schemaMapping[b[0]] || b[0].toLowerCase()) + ) return 1; - + // If b is a bottom section and a is not, move b down - if (!bottomSections.includes(schemaMapping[a[0]] || a[0].toLowerCase()) && - bottomSections.includes(schemaMapping[b[0]] || b[0].toLowerCase())) + if ( + !bottomSections.includes(schemaMapping[a[0]] || a[0].toLowerCase()) && + bottomSections.includes(schemaMapping[b[0]] || b[0].toLowerCase()) + ) return -1; - + // If both are bottom sections, sort them alphabetically among themselves - if (bottomSections.includes(schemaMapping[a[0]] || a[0].toLowerCase()) && - bottomSections.includes(schemaMapping[b[0]] || b[0].toLowerCase())) { + if ( + bottomSections.includes(schemaMapping[a[0]] || a[0].toLowerCase()) && + bottomSections.includes(schemaMapping[b[0]] || b[0].toLowerCase()) + ) { // Order within bottom sections: erc20, erc721, bridge const aIndex = bottomSections.indexOf(schemaMapping[a[0]] || a[0].toLowerCase()); const bIndex = bottomSections.indexOf(schemaMapping[b[0]] || b[0].toLowerCase()); return aIndex - bIndex; } - + // For all other schemas, keep alphabetical order return a[0].localeCompare(b[0]); }) @@ -404,14 +420,14 @@ function DeploymentsTable({ protocolsToQuery, issuesMapping, getData, decenDepos additionalStyles = { minHeight: "510px", overflow: "hidden" }; } return ( - @@ -442,19 +458,7 @@ function DeploymentsTable({ protocolsToQuery, issuesMapping, getData, decenDepos
- {schemaMapping[schemaType] ? ( - <> - {executeDownloadCSV} - - setDeposSelected({ ...deposSelected, [schemaMapping[schemaType]]: x }) - } - label="Deployment Selection" - /> - - ) : null} + {schemaMapping[schemaType] ? <>{executeDownloadCSV} : null} {tableHead} {tableRows} diff --git a/dashboard/src/deployments/ProtocolSection.tsx b/dashboard/src/deployments/ProtocolSection.tsx index d26b7539a2..13619f3916 100644 --- a/dashboard/src/deployments/ProtocolSection.tsx +++ b/dashboard/src/deployments/ProtocolSection.tsx @@ -17,47 +17,62 @@ interface ProtocolSection { } // Utility function to format large numbers with K, M, B notation -export const formatLargeNumber = (num: number | string | undefined): { formattedValue: string, fullValue: string } => { - if (num === undefined || num === null) { +export const formatLargeNumber = (num: number | string | undefined): { formattedValue: string; fullValue: string } => { + if (num === undefined || num === null || num === "N/A") { return { formattedValue: "N/A", fullValue: "N/A" }; } - + + // Handle value of 0 to display as "N/A" + if (num === 0 || num === "0" || num === "0.00" || num === "0.00%" || num === 0.0) { + return { formattedValue: "N/A", fullValue: "N/A" }; + } + // Convert string to number if needed - const numValue = typeof num === 'string' ? parseFloat(num) : num; - + const numValue = typeof num === "string" ? parseFloat(num) : num; + + // Handle NaN or very small numbers as "N/A" + if (isNaN(numValue) || numValue === 0) { + return { formattedValue: "N/A", fullValue: "N/A" }; + } + // Format the full value with commas for tooltip - const fullValue = typeof numValue === 'number' ? numValue.toLocaleString() : String(num); - + const fullValue = typeof numValue === "number" ? numValue.toLocaleString() : String(num); + // Early return for small numbers or non-numbers - if (typeof numValue !== 'number' || isNaN(numValue)) { + if (typeof numValue !== "number" || isNaN(numValue)) { return { formattedValue: String(num), fullValue }; } - + // Format with abbreviations based on magnitude if (numValue >= 1_000_000_000) { - return { - formattedValue: (numValue / 1_000_000_000).toFixed(1) + 'B', - fullValue + return { + formattedValue: (numValue / 1_000_000_000).toFixed(1) + "B", + fullValue, }; } else if (numValue >= 1_000_000) { - return { - formattedValue: (numValue / 1_000_000).toFixed(1) + 'M', - fullValue + return { + formattedValue: (numValue / 1_000_000).toFixed(1) + "M", + fullValue, }; } else if (numValue >= 1_000) { - return { - formattedValue: (numValue / 1_000).toFixed(1) + 'K', - fullValue + return { + formattedValue: (numValue / 1_000).toFixed(1) + "K", + fullValue, }; } - + return { formattedValue: fullValue, fullValue }; }; // Component to display a number with formatting and tooltip export const FormattedNumber = ({ value }: { value: number | string | undefined }) => { + // If value is "N/A" or undefined, display "N/A" + if (value === "N/A" || value === undefined) { + return N/A; + } + const { formattedValue, fullValue } = formatLargeNumber(value); - + return ( {formattedValue} @@ -70,11 +85,11 @@ const safeDisplayMetric = (value: any, defaultValue: string = "N/A"): string => if (value === undefined || value === null) { return defaultValue; } - - if (typeof value === 'number') { + + if (typeof value === "number") { return value.toLocaleString(); } - + return String(value); }; @@ -83,11 +98,11 @@ const getSafeMetric = (depoObject: any, property: string, defaultValue: string = if (!depoObject || depoObject[property] === undefined || depoObject[property] === null) { return defaultValue; } - - if (typeof depoObject[property] === 'number') { + + if (typeof depoObject[property] === "number") { return depoObject[property].toLocaleString(); } - + return String(depoObject[property]); }; @@ -101,7 +116,7 @@ function ProtocolSection({ validationSupported, }: ProtocolSection) { // NOTE: We now ONLY show decentralized network deployments as the hosted service is being deprecated - + const navigate = useNavigate(); const [showDeposDropDown, toggleShowDeposDropDown] = useState(false); @@ -113,19 +128,17 @@ function ProtocolSection({ const subNameUpper = subgraphName.toUpperCase(); // Filter to only show networks with decentralizedNetworkId - const decentralizedNetworks = protocol.networks.filter((depo: any) => - !!depo?.decentralizedNetworkId - ); - + const decentralizedNetworks = protocol.networks.filter((depo: any) => !!depo?.decentralizedNetworkId); + // If no networks have decentralizedNetworkId, don't render this protocol if (decentralizedNetworks.length === 0) { return null; } - + let hasDecentralizedDepo = decentralizedNetworks.length > 0; let prodStatusIcon = "https://images.emojiterra.com/twitter/v13.1/512px/2705.png"; let prodStatusHover = "Subgraph is frozen"; - + decentralizedNetworks.forEach((depo: any) => { if (Array.isArray(issuesTitles)) { const openRepoIssue = issuesTitles.find((x: any) => { @@ -157,24 +170,31 @@ function ProtocolSection({ } return x; }); - + schemaCell = ( -
+
{schemaVersOnProtocol.map((version: string, index: number) => ( - + {version} ))} @@ -184,7 +204,7 @@ function ProtocolSection({ } catch (err: any) { console.error(err.message); } - + const depoRowsOnProtocol = decentralizedNetworks.map((depo: any) => { let chainLabel = depo.chain; if (decentralizedNetworks.filter((x: any) => x.chain === depo.chain).length > 1) { @@ -200,9 +220,10 @@ function ProtocolSection({ let highlightColor = "#3f51b5"; const depoObject = depo.decentralizedIndexStatus; let synced = depoObject ? depoObject["synced"] : false; - let indexedPercentage = depoObject && typeof depoObject["indexed-percentage"] === "number" - ? formatIntToFixed2(depoObject["indexed-percentage"]) - : "0.00"; + let indexedPercentage = + depoObject && typeof depoObject["indexed-percentage"] === "number" + ? formatIntToFixed2(depoObject["indexed-percentage"]) + : "0.00"; if (synced && Number(indexedPercentage) > 99) { highlightColor = "#58BC82"; @@ -216,17 +237,17 @@ function ProtocolSection({ if (decenSubgraphKey) { decenSubgraphId = decenDeposToSubgraphIds[decenSubgraphKey]?.id; } - + // Get the deployment ID from the decentralized network service // This retrieves the actual deployment ID from the API response - const deploymentId = depo.decentralizedIndexStatus && - depo.decentralizedIndexStatus["deployment-id"] ? - depo.decentralizedIndexStatus["deployment-id"] : - depo.decentralizedNetworkId || ""; - + const deploymentId = + depo.decentralizedIndexStatus && depo.decentralizedIndexStatus["deployment-id"] + ? depo.decentralizedIndexStatus["deployment-id"] + : depo.decentralizedNetworkId || ""; + // Use the deployment ID directly as the endpoint URL let endpointURL = deploymentId; - + // Keep a reference to the old URL construction for logging/debugging purposes /* let oldEndpointURL = @@ -276,41 +297,19 @@ function ProtocolSection({ decenRow = ( { - if (event.ctrlKey) { - if (!validationSupported) { - window.open( - process.env.REACT_APP_GRAPH_EXPLORER_URL! + "/subgraphs/" + depo.decentralizedNetworkId, - "_blank" - ); - return; - } - if (depoObject["is-healthy"]) { - window.open(`${window.location.href}subgraph?endpoint=${endpointURL}&tab=protocol`, "_blank"); - } else { - window.open( - process.env.REACT_APP_OKGRAPH_BASE_URL! + "/?q=" + deploymentId, - "_blank", - ); - } - } else { - if (!validationSupported) { - window.location.href = process.env.REACT_APP_GRAPH_EXPLORER_URL! + "/subgraphs/" + depo.decentralizedNetworkId; - return; - } - if (depoObject["is-healthy"]) { - navigate(`/subgraph?endpoint=${endpointURL}&tab=protocol`); - } else { - window.location.href = - process.env.REACT_APP_OKGRAPH_BASE_URL! + "/?q=" + deploymentId; - } + // Only redirect to The Graph Explorer, not to internal pages + if (!validationSupported || event.ctrlKey) { + window.open( + process.env.REACT_APP_GRAPH_EXPLORER_URL! + "/subgraphs/" + depo.decentralizedNetworkId, + "_blank", + ); } - return; }} key={subgraphName + depo.decentralizedNetworkId + "DepInDevRow-DECEN"} - sx={{ - height: "10px", - width: "100%", - backgroundColor: "rgba(22,24,29,0.9)", + sx={{ + height: "10px", + width: "100%", + backgroundColor: "rgba(22,24,29,0.9)", cursor: "pointer", position: "relative", "&::before": { @@ -320,14 +319,16 @@ function ProtocolSection({ top: 0, bottom: 0, width: "4px", - backgroundColor: depoObject && !depoObject["is-healthy"] - ? "#B8301C" // Red for unhealthy - : depoObject && depoObject["synced"] && - typeof depoObject["indexed-percentage"] === "number" && - Number(formatIntToFixed2(depoObject["indexed-percentage"])) > 99 - ? "#58BC82" // Green for synced - : "#EFCB68", // Yellow/orange for in-progress - } + backgroundColor: + depoObject && !depoObject["is-healthy"] + ? "#B8301C" // Red for unhealthy + : depoObject && + depoObject["synced"] && + typeof depoObject["indexed-percentage"] === "number" && + Number(formatIntToFixed2(depoObject["indexed-percentage"])) > 99 + ? "#58BC82" // Green for synced + : "#EFCB68", // Yellow/orange for in-progress + }, }} > @@ -424,8 +425,13 @@ function ProtocolSection({ textAlign: "right", }} > - {depo.decentralizedIndexStatus && typeof depo.decentralizedIndexStatus["indexed-percentage"] === "number" - ? formatIntToFixed2(depo.decentralizedIndexStatus["indexed-percentage"]) + "%" + {depo.decentralizedIndexStatus && + typeof depo.decentralizedIndexStatus["indexed-percentage"] !== "undefined" && + depo.decentralizedIndexStatus["indexed-percentage"] !== "N/A" && + depo.decentralizedIndexStatus["indexed-percentage"] !== 0 && + depo.decentralizedIndexStatus["indexed-percentage"] !== "0" && + parseFloat(depo.decentralizedIndexStatus["indexed-percentage"]) !== 0 + ? formatIntToFixed2(depo.decentralizedIndexStatus["indexed-percentage"]) + "%" : "N/A"} - {depo.decentralizedIndexStatus ? - : "N/A"} + - {depo.decentralizedIndexStatus ? - : "N/A"} + - {depo.decentralizedIndexStatus ? - : "N/A"} + {depo?.versions?.subgraph ? ( - + {depo?.versions?.subgraph} - ) : "N/A"} + ) : ( + "N/A" + )} @@ -512,8 +525,16 @@ function ProtocolSection({ }} > - {depo.decentralizedIndexStatus ? - : "N/A"} + @@ -533,14 +554,22 @@ function ProtocolSection({ toggleShowDeposDropDown(!showDeposDropDown); }} key={subgraphName + "DepInDevRow"} - sx={{ - cursor: "pointer", - height: "10px", - width: "100%", - backgroundColor: "rgba(22,24,29,0.9)" + sx={{ + cursor: "pointer", + height: "10px", + width: "100%", + backgroundColor: "rgba(22,24,29,0.9)", }} > - + @@ -561,37 +590,19 @@ function ProtocolSection({ {decentralizedNetworks.map((x: { [x: string]: any }) => { - let borderColor = "#EFCB68"; - let indexedPercentage = formatIntToFixed2(0); - - const depoObject = x.decentralizedIndexStatus; - if (depoObject) { - let synced = depoObject["synced"]; - indexedPercentage = typeof depoObject["indexed-percentage"] === "number" - ? formatIntToFixed2(depoObject["indexed-percentage"]) - : "0.00"; - - if (!depoObject["is-healthy"]) { - borderColor = "#B8301C"; - } else { - if (synced && Number(indexedPercentage) > 99) { - borderColor = "#58BC82"; - indexedPercentage = formatIntToFixed2(100); - } - } - } - return ( - + ); })} @@ -626,26 +637,33 @@ function ProtocolSection({ {protocol?.subgraphVersions?.length > 0 ? ( -
+
{protocol?.subgraphVersions.map((version: string, index: number) => ( - + {version} ))}
- ) : "N/A"} + ) : ( + "N/A" + )} @@ -669,24 +687,31 @@ function ProtocolSection({ } return x; }); - + schemaCell = ( -
+
{schemaVersOnProtocol.map((version: string, index: number) => ( - + {version} ))} @@ -726,14 +751,16 @@ function ProtocolSection({ toggleShowDeposDropDown(!showDeposDropDown); }} key={subgraphName + "DepInDevRow"} - sx={{ - cursor: "pointer", - height: "10px", - width: "100%", - backgroundColor: "rgba(22,24,29,0.9)" + sx={{ + cursor: "pointer", + height: "10px", + width: "100%", + backgroundColor: "rgba(22,24,29,0.9)", }} > - + @@ -756,37 +783,19 @@ function ProtocolSection({ <> {decentralizedNetworks.map((x: { [x: string]: any }) => { - let borderColor = "#EFCB68"; - let indexedPercentage = formatIntToFixed2(0); - - const depoObject = x.decentralizedIndexStatus; - if (depoObject) { - let synced = depoObject["synced"]; - indexedPercentage = typeof depoObject["indexed-percentage"] === "number" - ? formatIntToFixed2(depoObject["indexed-percentage"]) - : "0.00"; - - if (!depoObject["is-healthy"]) { - borderColor = "#B8301C"; - } else { - if (synced && Number(indexedPercentage) > 99) { - borderColor = "#58BC82"; - indexedPercentage = formatIntToFixed2(100); - } - } - } - return ( - + ); })} @@ -823,26 +832,33 @@ function ProtocolSection({ {protocol?.subgraphVersions?.length > 0 ? ( -
+
{protocol?.subgraphVersions.map((version: string, index: number) => ( - + {version} ))}
- ) : "N/A"} + ) : ( + "N/A" + )} @@ -853,4 +869,3 @@ function ProtocolSection({ } export default ProtocolSection; - diff --git a/dashboard/src/deployments/VersionComparison.tsx b/dashboard/src/deployments/VersionComparison.tsx index cdec6d2b07..98198f735c 100644 --- a/dashboard/src/deployments/VersionComparison.tsx +++ b/dashboard/src/deployments/VersionComparison.tsx @@ -190,7 +190,7 @@ function VersionComparison({ protocolsToQuery, getData }: VersionComparisonProps return ( {depo} @@ -199,11 +199,6 @@ function VersionComparison({ protocolsToQuery, getData }: VersionComparisonProps {type} - (window.location.href = versionPending - ? "/subgraph?endpoint=" + slugToQueryString[depo] + "&tab=protocol&version=pending" - : "#") - } sx={{ padding: "0", paddingRight: "6px", @@ -214,11 +209,6 @@ function VersionComparison({ protocolsToQuery, getData }: VersionComparisonProps {versionPending} - (window.location.href = versionDecen - ? "/subgraph?endpoint=" + slugToQueryString[depo + " (Decentralized)"] + "&tab=protocol" - : "#") - } sx={{ padding: "0", paddingRight: "6px", @@ -229,11 +219,6 @@ function VersionComparison({ protocolsToQuery, getData }: VersionComparisonProps {versionDecen} - (window.location.href = versionHostedService - ? "/subgraph?endpoint=" + slugToQueryString[depo] + "&tab=protocol" - : "#") - } sx={{ padding: "0", paddingRight: "6px", @@ -251,11 +236,8 @@ function VersionComparison({ protocolsToQuery, getData }: VersionComparisonProps } else if (subgraphVersionMapping[depo]) { failedQueryRows.push( - (window.location.href = process.env.REACT_APP_OKGRAPH_BASE_URL! + "/?q=" + slugToQueryString[depo]) - } key={depo + "RowComp"} - sx={{ height: "10px", width: "100%", backgroundColor: "rgba(22,24,29,0.9)", cursor: "pointer" }} + sx={{ height: "10px", width: "100%", backgroundColor: "rgba(22,24,29,0.9)" }} > {depo} @@ -319,4 +301,3 @@ function VersionComparison({ protocolsToQuery, getData }: VersionComparisonProps } export default VersionComparison; - diff --git a/dashboard/src/utils/index.ts b/dashboard/src/utils/index.ts index 213193fbad..80d9c44edd 100644 --- a/dashboard/src/utils/index.ts +++ b/dashboard/src/utils/index.ts @@ -406,7 +406,7 @@ export function upperCaseFirstOfString(str: string) { } /** - * Enhances deployment health metrics by properly extracting data from the + * Enhances deployment health metrics by properly extracting data from the * deployment object. This ensures the health metrics like "Indexed %", * "Start Block", "Current Block", and "Chain Head" are correctly displayed. * @param deploymentData Deployment data from the Messari Status API @@ -419,81 +419,111 @@ export function enhanceHealthMetrics(deploymentData: any) { const hostedService = deploymentData.services["hosted-service"]; const decentralizedNetwork = deploymentData.services["decentralized-network"]; - + // Process hosted service health data - if (hostedService && Array.isArray(hostedService.health) && hostedService.health.length > 0) { - for (let i = 0; i < hostedService.health.length; i++) { - const healthData = hostedService.health[i]; - - if (!healthData) { - continue; - } - - // Ensure these fields exist with default values if they don't - // Convert to numbers where appropriate - healthData["start-block"] = safeNumberConversion(healthData["start-block"], 0); - healthData["latest-block"] = safeNumberConversion(healthData["latest-block"], 0); - healthData["chain-head-block"] = safeNumberConversion(healthData["chain-head-block"], 0); - healthData["entity-count"] = safeNumberConversion(healthData["entity-count"], 0); - - if (healthData["synced"] === undefined || healthData["synced"] === null) { - healthData["synced"] = false; - } - - // Calculate indexed percentage - const startBlock = healthData["start-block"]; - const latestBlock = healthData["latest-block"]; - const chainHeadBlock = healthData["chain-head-block"]; - - if (chainHeadBlock > startBlock) { - const indexedPercentage = ((latestBlock - startBlock) / (chainHeadBlock - startBlock)) * 100; - healthData["indexed-percentage"] = indexedPercentage > 99.5 ? 100 : indexedPercentage; - } else { - healthData["indexed-percentage"] = 0; + if (hostedService) { + // If health is null, create an empty array for compatibility + if (hostedService.health === null) { + hostedService.health = []; + // Add a single mock health entry with default values + hostedService.health.push({ + "start-block": 0, + "latest-block": 0, + "chain-head-block": 0, + "entity-count": 0, + synced: false, + "indexed-percentage": 0, + "has-been-enhanced": true, + }); + } else if (Array.isArray(hostedService.health) && hostedService.health.length > 0) { + for (let i = 0; i < hostedService.health.length; i++) { + const healthData = hostedService.health[i]; + + if (!healthData) { + continue; + } + + // Ensure these fields exist with default values if they don't + // Convert to numbers where appropriate + healthData["start-block"] = safeNumberConversion(healthData["start-block"], 0); + healthData["latest-block"] = safeNumberConversion(healthData["latest-block"], 0); + healthData["chain-head-block"] = safeNumberConversion(healthData["chain-head-block"], 0); + healthData["entity-count"] = safeNumberConversion(healthData["entity-count"], 0); + + if (healthData["synced"] === undefined || healthData["synced"] === null) { + healthData["synced"] = false; + } + + // Calculate indexed percentage + const startBlock = healthData["start-block"]; + const latestBlock = healthData["latest-block"]; + const chainHeadBlock = healthData["chain-head-block"]; + + if (chainHeadBlock > startBlock) { + const indexedPercentage = ((latestBlock - startBlock) / (chainHeadBlock - startBlock)) * 100; + healthData["indexed-percentage"] = indexedPercentage > 99.5 ? 100 : indexedPercentage; + } else { + healthData["indexed-percentage"] = 0; + } + + // Set a flag to indicate that this health data has been enhanced + healthData["has-been-enhanced"] = true; } - - // Set a flag to indicate that this health data has been enhanced - healthData["has-been-enhanced"] = true; } } - + // Process decentralized network health data - if (decentralizedNetwork && Array.isArray(decentralizedNetwork.health) && decentralizedNetwork.health.length > 0) { - for (let i = 0; i < decentralizedNetwork.health.length; i++) { - const healthData = decentralizedNetwork.health[i]; - - if (!healthData) { - continue; - } - - // Ensure these fields exist with default values if they don't - // Convert to numbers where appropriate - healthData["start-block"] = safeNumberConversion(healthData["start-block"], 0); - healthData["latest-block"] = safeNumberConversion(healthData["latest-block"], 0); - healthData["chain-head-block"] = safeNumberConversion(healthData["chain-head-block"], 0); - healthData["entity-count"] = safeNumberConversion(healthData["entity-count"], 0); - - if (healthData["synced"] === undefined || healthData["synced"] === null) { - healthData["synced"] = false; - } - - // Calculate indexed percentage - const startBlock = healthData["start-block"]; - const latestBlock = healthData["latest-block"]; - const chainHeadBlock = healthData["chain-head-block"]; - - if (chainHeadBlock > startBlock) { - const indexedPercentage = ((latestBlock - startBlock) / (chainHeadBlock - startBlock)) * 100; - healthData["indexed-percentage"] = indexedPercentage > 99.5 ? 100 : indexedPercentage; - } else { - healthData["indexed-percentage"] = 0; + if (decentralizedNetwork) { + // If health is null, create an empty array for compatibility + if (decentralizedNetwork.health === null) { + decentralizedNetwork.health = []; + // Add a single mock health entry with default values + decentralizedNetwork.health.push({ + "start-block": 0, + "latest-block": 0, + "chain-head-block": 0, + "entity-count": 0, + synced: false, + "indexed-percentage": 0, + "has-been-enhanced": true, + }); + } else if (Array.isArray(decentralizedNetwork.health) && decentralizedNetwork.health.length > 0) { + for (let i = 0; i < decentralizedNetwork.health.length; i++) { + const healthData = decentralizedNetwork.health[i]; + + if (!healthData) { + continue; + } + + // Ensure these fields exist with default values if they don't + // Convert to numbers where appropriate + healthData["start-block"] = safeNumberConversion(healthData["start-block"], 0); + healthData["latest-block"] = safeNumberConversion(healthData["latest-block"], 0); + healthData["chain-head-block"] = safeNumberConversion(healthData["chain-head-block"], 0); + healthData["entity-count"] = safeNumberConversion(healthData["entity-count"], 0); + + if (healthData["synced"] === undefined || healthData["synced"] === null) { + healthData["synced"] = false; + } + + // Calculate indexed percentage + const startBlock = healthData["start-block"]; + const latestBlock = healthData["latest-block"]; + const chainHeadBlock = healthData["chain-head-block"]; + + if (chainHeadBlock > startBlock) { + const indexedPercentage = ((latestBlock - startBlock) / (chainHeadBlock - startBlock)) * 100; + healthData["indexed-percentage"] = indexedPercentage > 99.5 ? 100 : indexedPercentage; + } else { + healthData["indexed-percentage"] = 0; + } + + // Set a flag to indicate that this health data has been enhanced + healthData["has-been-enhanced"] = true; } - - // Set a flag to indicate that this health data has been enhanced - healthData["has-been-enhanced"] = true; } } - + return deploymentData; } @@ -507,11 +537,68 @@ function safeNumberConversion(value: any, defaultValue: number): number { if (value === undefined || value === null) { return defaultValue; } - - if (typeof value === 'number') { + + if (typeof value === "number") { return value; } - + const parsed = Number(value); return !isNaN(parsed) ? parsed : defaultValue; } + +/** + * Processes raw deployment data to ensure all deployments have the necessary health metrics structure. + * Similar to what the prepare-deployment-data.js script does, but performed at runtime. + * @param deploymentData Raw deployment data from GitHub + * @returns Processed deployment data with proper health metrics structure + */ +export function processDeploymentData(deploymentData: Record): Record { + // Create a deep copy to avoid modifying the original data + const processedData = JSON.parse(JSON.stringify(deploymentData)); + + // Process each protocol + Object.keys(processedData).forEach((protocolName) => { + const protocol = processedData[protocolName]; + + if (protocol.deployments) { + Object.keys(protocol.deployments).forEach((deploymentKey) => { + const deployment = protocol.deployments[deploymentKey]; + + // Ensure services object exists + if (!deployment.services) { + deployment.services = {}; + } + + // Ensure hosted-service exists if needed + if (!deployment.services["hosted-service"]) { + deployment.services["hosted-service"] = { + slug: deploymentKey, + "query-id": deploymentKey, + health: null, // Will be enhanced by enhanceHealthMetrics + }; + } else if (deployment.services["hosted-service"] && !deployment.services["hosted-service"].health) { + deployment.services["hosted-service"].health = null; + } + + // Ensure decentralized-network exists if needed + if (!deployment.services["decentralized-network"]) { + deployment.services["decentralized-network"] = { + slug: deploymentKey, + "query-id": "todo", + health: null, // Will be enhanced by enhanceHealthMetrics + }; + } else if ( + deployment.services["decentralized-network"] && + !deployment.services["decentralized-network"].health + ) { + deployment.services["decentralized-network"].health = null; + } + + // Apply the enhanceHealthMetrics function to add health metrics + protocol.deployments[deploymentKey] = enhanceHealthMetrics(deployment); + }); + } + }); + + return processedData; +}