Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/build-tools/explorer/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ function App() {
prevStaticTablePage,
firstStaticTablePage,
setStaticTableLimit,
contractsData,
eventsData,
eventsPagination,
} = useTableData();

// Error handling for uncaught promises
Expand Down Expand Up @@ -80,6 +83,11 @@ function App() {
newBlockIndices={newBlockIndices}
/>

<TableSection
title="Tracked Contracts"
tables={contractsData}
/>

<TableSection
title="Primitive Data"
tables={primitiveData}
Expand All @@ -90,6 +98,12 @@ function App() {
onLimitChange={(name, limit) => setPrimitiveLimit(name, limit)}
/>

<TableSection
title="Events"
tables={eventsData}
pagination={eventsPagination}
/>

<TableSection
title="State Machine Tables"
tables={staticTableData}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ function formatCellValue(value: any, fieldName: string): string {

// Check if this looks like an Ethereum address
if (
typeof value === "string" && value.startsWith("0x") && value.length === 42
typeof value === "string" &&
(fieldName === 'contract_address' || (value.startsWith("0x") && value.length === 42))
) {
return `<span class="address-cell" style="overflow: hidden; text-overflow: ellipsis;" title="${value}">${value}</span>`;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/build-tools/explorer/client/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ export const PRIMITIVES_SCHEMA_ENDPOINT =
`http://127.0.0.1:${ENV.PAIMA_API_PORT}/primitives-schema`;
export const TABLE_SCHEMA_ENDPOINT =
`http://127.0.0.1:${ENV.PAIMA_API_PORT}/table-schema`;
export const EVENTS_ENDPOINT = `http://127.0.0.1:${ENV.PAIMA_API_PORT}/events`;
export const EVENTS_SCHEMA_ENDPOINT =
`http://127.0.0.1:${ENV.PAIMA_API_PORT}/events-schema`;
export const BATCHER_ENDPOINT =
`http://localhost:${ENV.BATCHER_PORT}/send-input`;
export const BATCHER_OPENAPI_URL =
Expand Down
225 changes: 225 additions & 0 deletions packages/build-tools/explorer/client/src/hooks/useTableData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import {
CONFIG_ENDPOINT,
EVENTS_ENDPOINT,
PRIMITIVES_ENDPOINT,
PRIMITIVES_SCHEMA_ENDPOINT,
SCHEDULED_DATA_ENDPOINT,
Expand Down Expand Up @@ -60,12 +61,21 @@ export function useTableData() {
const [scheduledData, setScheduledData] = useState<
Record<string, TableData | null>
>({});
const [contractsData, setContractsData] = useState<
Record<string, TableData | null>
>({});
const [eventsData, setEventsData] = useState<
Record<string, TableData | null>
>({});
const [primitivePagination, setPrimitivePagination] = useState<
Record<string, PaginationMeta>
>({});
const [staticTablePagination, setStaticTablePagination] = useState<
Record<string, PaginationMeta>
>({});
const [eventsPagination, setEventsPagination] = useState<
Record<string, PaginationMeta>
>({});
const [primitiveSchemas, setPrimitiveSchemas] = useState<
Record<string, SchemaColumn[]>
>({});
Expand All @@ -82,6 +92,7 @@ export function useTableData() {
const staticTableSchemasRef = useRef<Record<string, SchemaColumn[]>>({});
const primitivePaginationRef = useRef<Record<string, PaginationMeta>>({});
const staticTablePaginationRef = useRef<Record<string, PaginationMeta>>({});
const eventsPaginationRef = useRef<Record<string, PaginationMeta>>({});

// Update refs whenever state changes
useEffect(() => {
Expand All @@ -104,6 +115,10 @@ export function useTableData() {
staticTablePaginationRef.current = staticTablePagination;
}, [staticTablePagination]);

useEffect(() => {
eventsPaginationRef.current = eventsPagination;
}, [eventsPagination]);

// Convert schema columns to Field format
const convertSchemaToFields = useCallback(
(schema: SchemaColumn[]): Field[] => {
Expand Down Expand Up @@ -179,6 +194,59 @@ export function useTableData() {
[convertSchemaToFields],
);

// Convert contract data (from config) to TableData format
const convertContractsDataToTableFormat = useCallback(
(config: any[]): TableData | null => {
if (!config || !Array.isArray(config)) {
return null;
}

const rows: any[] = [];
config.forEach((syncProtocolConfig) => {
if (
!syncProtocolConfig.primitives ||
!Array.isArray(syncProtocolConfig.primitives)
) {
return;
}
syncProtocolConfig.primitives.forEach((primitiveConfig: any) => {
if (!primitiveConfig.primitive || !primitiveConfig.primitive.name) {
console.warn(
"Primitive config is missing primitive or primitive.name",
primitiveConfig,
);
return;
}
rows.push({
network_type: syncProtocolConfig.networkType || "N/A",
primitive_name: primitiveConfig.primitive.name,
primitive_type: primitiveConfig.primitive.type,
contract_address: primitiveConfig.primitive.contractAddress ||
"N/A",
start_block: primitiveConfig.primitive.startBlockHeight ||
"N/A",
});
});
});

const fields: Field[] = [
{ name: "network_type", dataTypeID: 25 },
{ name: "primitive_name", dataTypeID: 25 },
{ name: "primitive_type", dataTypeID: 25 },
{ name: "contract_address", dataTypeID: 25 },
{ name: "start_block", dataTypeID: 23 }, // number
];

return {
command: "SELECT",
rowCount: rows.length,
rows: rows,
fields: fields,
};
},
[],
);

// Fetch schema for primitive
const fetchPrimitiveSchema = useCallback(
async (primitiveName: string): Promise<SchemaColumn[] | null> => {
Expand Down Expand Up @@ -404,6 +472,82 @@ export function useTableData() {
[convertTableDataToTableFormat],
);

// Fetch events data
const fetchEventsData = useCallback(
async (pagination?: PaginationMeta) => {
try {
const current = pagination ?? {
limit: DEFAULT_LIMIT,
cursors: [undefined],
currentPage: 0,
hasMore: false,
};
const url = new URL(EVENTS_ENDPOINT);
url.searchParams.set("limit", String(current.limit));

const cursor = current.cursors[current.currentPage];
if (cursor) {
url.searchParams.set("after", cursor);
}

const response = await fetch(url.toString());
if (!response.ok) {
if (response.status === 404) {
console.log(`🚫 Events not found (404)`);
return null;
}
throw new Error(`HTTP error! status: ${response.status}`);
}
const jsonResponse = await response.json();
console.log(`📊 Fetched events data:`, jsonResponse);
const data = jsonResponse.data ?? [];
const paginationMeta = jsonResponse.pagination;
if (paginationMeta) {
setEventsPagination((prev) => {
const newCursors = [...prev["events"].cursors];
if (
paginationMeta.nextCursor &&
newCursors.length === prev["events"].currentPage + 1
) {
newCursors.push(paginationMeta.nextCursor);
}
return {
...prev,
["events"]: {
...prev["events"],
hasMore: paginationMeta.hasMore,
cursors: newCursors,
},
};
});
}

// Manually define fields for events data
const fields: Field[] = [
{ name: "id", dataTypeID: 23 },
{ name: "event_name", dataTypeID: 25 },
{ name: "topic", dataTypeID: 25 },
{ name: "address", dataTypeID: 25 },
{ name: "data", dataTypeID: 25 },
{ name: "block_height", dataTypeID: 23 },
{ name: "tx_index", dataTypeID: 23 },
{ name: "log_index", dataTypeID: 23 },
];

return {
command: "SELECT",
rowCount: data.length,
rows: data,
fields: fields,
};
} catch (error) {
console.error(`Error fetching events data:`, error);
return null;
}
},
[],
);

// Fetch scheduled data
const fetchScheduledData = useCallback(
async (): Promise<TableData | null> => {
Expand Down Expand Up @@ -528,6 +672,23 @@ export function useTableData() {
}
}, [fetchScheduledData]);

// Refresh events data
const refreshEventsData = useCallback(async () => {
if (!isInitialLoadComplete.current) {
return;
}

try {
const pagination = eventsPaginationRef.current["events"];
const data = await fetchEventsData(pagination);
if (data !== null) {
setEventsData({ "events": data });
}
} catch (error) {
console.error("Error refreshing events data:", error);
}
}, [fetchEventsData]);

// Initialize primitive tables
const initializePrimitiveTables = useCallback(async () => {
console.log("📋 Initializing primitive tables...");
Expand Down Expand Up @@ -674,18 +835,68 @@ export function useTableData() {
}
}, [fetchScheduledData]);

const initializeEventsData = useCallback(async () => {
console.log("📋 Initializing events data...");

try {
setEventsPagination((prev) => ({
...prev,
["events"]: prev["events"] ?? {
limit: DEFAULT_LIMIT,
cursors: [undefined],
currentPage: 0,
hasMore: false,
},
}));

const pagination = eventsPaginationRef.current["events"] ?? {
limit: DEFAULT_LIMIT,
cursors: [undefined],
currentPage: 0,
hasMore: false,
};
const data = await fetchEventsData(pagination);
setEventsData({ "events": data });
console.log("✅ Events data initialized");
} catch (error) {
console.error("Error initializing events data:", error);
}
}, [fetchEventsData]);

// Initialize contracts data
const initializeContractsData = useCallback(async () => {
console.log("📋 Initializing contracts data...");

try {
const config = await fetchConfig();
if (!config) {
console.error("Failed to fetch config for contracts data");
return;
}

const tableData = convertContractsDataToTableFormat(config);
setContractsData({ "contracts": tableData });
console.log("✅ Contracts data initialized");
} catch (error) {
console.error("Error initializing contracts data:", error);
}
}, [fetchConfig, convertContractsDataToTableFormat]);

// Initialize and setup refresh intervals
useEffect(() => {
let primitiveRefreshInterval: number;
let staticTableRefreshInterval: number;
let scheduledDataRefreshInterval: number;
let eventsRefreshInterval: number;

const initialize = async () => {
// Initialize tables
await Promise.all([
initializePrimitiveTables(),
initializeStaticTables(),
initializeScheduledData(),
initializeContractsData(),
initializeEventsData(),
]);

// Mark initial load as complete
Expand All @@ -712,6 +923,14 @@ export function useTableData() {
refreshScheduledData();
}, 5000);
}, 3000);

// Refresh events data after 4.5 seconds, then every 5 seconds
setTimeout(() => {
refreshEventsData();
eventsRefreshInterval = setInterval(() => {
refreshEventsData();
}, 5000);
}, 4500);
};

initialize();
Expand All @@ -726,18 +945,24 @@ export function useTableData() {
if (scheduledDataRefreshInterval) {
clearInterval(scheduledDataRefreshInterval);
}
if (eventsRefreshInterval) {
clearInterval(eventsRefreshInterval);
}
};
}, []); // Empty dependency array to prevent re-runs

return {
primitiveData,
staticTableData,
scheduledData,
contractsData,
eventsData,
refreshPrimitiveData,
refreshStaticTableData,
refreshScheduledData,
primitivePagination,
staticTablePagination,
eventsPagination,
// Pagination controls for primitives
setPrimitiveLimit: async (primitiveName: string, limit: number) => {
const newPagination = {
Expand Down
Loading