From c3439c34f045b5641a93fa72b102381d5c7291e2 Mon Sep 17 00:00:00 2001
From: Kevin Zhang <42101107+Kevin101Zhang@users.noreply.github.com>
Date: Tue, 23 Jul 2024 16:28:17 -0400
Subject: [PATCH] feat: use QueryAPI Indexer for Explorers and added pagination
(#907)
QueryAPI Indexer's explore page now fetches from Indexers and will
default to NEAR RPC request if something goes wrong. Added
Pagination/Loading more for All Indexers
---
frontend/src/pages/query-api-editor/index.js | 2 +-
.../widgets/src/QueryApi.IndexerExplorer.jsx | 301 +++++++++++++-----
2 files changed, 227 insertions(+), 76 deletions(-)
diff --git a/frontend/src/pages/query-api-editor/index.js b/frontend/src/pages/query-api-editor/index.js
index 87750c60..ec622a31 100644
--- a/frontend/src/pages/query-api-editor/index.js
+++ b/frontend/src/pages/query-api-editor/index.js
@@ -25,7 +25,7 @@ const QueryApiEditorPage = ({ router }) => {
);
}
- if (accountId == 'test' || indexerName == 'test') {
+ if (accountId == 'test' && indexerName == 'test') {
return ;
}
diff --git a/frontend/widgets/src/QueryApi.IndexerExplorer.jsx b/frontend/widgets/src/QueryApi.IndexerExplorer.jsx
index abb9ca61..1562f8f4 100644
--- a/frontend/widgets/src/QueryApi.IndexerExplorer.jsx
+++ b/frontend/widgets/src/QueryApi.IndexerExplorer.jsx
@@ -1,33 +1,135 @@
const myAccountId = context.accountId;
+const PAGE_SIZE = 50;
+const TABLE_NAME = "dataplatform_near_queryapi_indexer_indexers";
+const GET_ALL_ACTIVE_INDEXERS = `
+query getAllActiveIndexers($limit: Int!, $offset: Int!) {
+ ${TABLE_NAME}(
+ where: {is_removed: {_eq: false}}
+ limit: $limit
+ offset: $offset
+ ) {
+ author_account_id
+ indexer_name
+ }
+ ${TABLE_NAME}_aggregate(
+ where: {is_removed: {_eq: false}}
+ ) {
+ aggregate {
+ count
+ }
+ }
+}
+`;
+
+const GET_MY_ACTIVE_INDEXERS = `
+query getMyActiveIndexers($authorAccountId: String!) {
+ ${TABLE_NAME}(
+ where: {
+ is_removed: { _eq: false }
+ author_account_id: { _eq: $authorAccountId }
+ }
+ ) {
+ author_account_id
+ indexer_name
+ }
+}
+`;
+
const [selectedTab, setSelectedTab] = useState(props.tab && props.tab !== "all" ? props.tab : "all");
-const [myIndexers, setMyIndexers] = useState([]);
-const [allIndexers, setAllIndexers] = useState([]);
-const [error, setError] = useState(null);
const [indexerMetadata, setIndexerMetaData] = useState(new Map());
+
+const [indexers, setIndexers] = useState([]);
+const [total, setTotal] = useState(0);
+const [myIndexers, setMyIndexers] = useState([]);
+const [page, setPage] = useState(0);
+
const [loading, setLoading] = useState(false);
+const [isLoadingMore, setIsLoadingMore] = useState(false);
+const [isFetching, setIsFetching] = useState(false);
+const [error, setError] = useState(null);
+
+const fetchGraphQL = (operationsDoc, operationName, variables) => {
+ const graphQLEndpoint = `${REPL_GRAPHQL_ENDPOINT}`;
+ return asyncFetch(`${graphQLEndpoint}/v1/graphql`, {
+ method: "POST",
+ headers: {
+ "x-hasura-role": "dataplatform_near",
+ },
+ body: JSON.stringify({
+ query: operationsDoc,
+ variables: variables,
+ operationName: operationName,
+ }),
+ });
+}
-const fetchIndexerData = () => {
+const fetchMyIndexerData = () => {
setLoading(true);
- Near.asyncView(`${REPL_REGISTRY_CONTRACT_ID}`, "list_all").then((data) => {
- const allIndexers = [];
- const myIndexers = [];
- Object.keys(data).forEach((accId) => {
- Object.keys(data[accId]).forEach((functionName) => {
- const indexer = {
- accountId: accId,
- indexerName: functionName,
- };
- if (accId === myAccountId) myIndexers.push(indexer);
- allIndexers.push(indexer);
- });
+
+ fetchGraphQL(GET_MY_ACTIVE_INDEXERS, 'getMyActiveIndexers', {
+ authorAccountId: myAccountId,
+ })
+ .then((result) => {
+ if (result.status === 200) {
+ const data = result?.body?.data?.[TABLE_NAME];
+ if (Array.isArray(data)) {
+ const newIndexers = data.map(({ author_account_id, indexer_name }) => ({
+ accountId: author_account_id,
+ indexerName: indexer_name,
+ }));
+ setMyIndexers(newIndexers);
+ } else {
+ throw new Error('Data is not an array:', data);
+ }
+ } else {
+ throw new Error('Failed to fetch data:', result);
+ }
+ setLoading(false);
+ })
+ .catch((error) => {
+ setError('An error occurred while retrieving indexer data. Attempting to fetch from NEAR RPC...', error);
+ backupNearRPCRequest();
});
- setMyIndexers(myIndexers);
- setAllIndexers(allIndexers);
- setLoading(false);
- });
}
+const fetchIndexerData = (page, append) => {
+ if (isFetching) return;
+ setIsFetching(true);
+ append ? setIsLoadingMore(true) : setLoading(true);
+
+ fetchGraphQL(GET_ALL_ACTIVE_INDEXERS, 'getAllActiveIndexers', {
+ limit: PAGE_SIZE,
+ offset: page * PAGE_SIZE,
+ })
+ .then((result) => {
+ if (result.status === 200) {
+ const data = result?.body?.data?.[TABLE_NAME];
+ const totalCount = result?.body?.data?.[`${TABLE_NAME}_aggregate`]?.aggregate?.count;
+ if (Array.isArray(data)) {
+ const newIndexers = data.map(({ author_account_id, indexer_name }) => ({
+ accountId: author_account_id,
+ indexerName: indexer_name,
+ }));
+ setTotal(totalCount);
+ setIndexers((prevIndexers) => append ? [...prevIndexers, ...newIndexers] : newIndexers);
+ } else {
+ throw new Error('Data is not an array:', data);
+ }
+ } else {
+ throw new Error('Failed to fetch data:', result);
+ }
+ append ? setIsLoadingMore(false) : setLoading(false);
+ setIsFetching(false);
+ })
+ .catch((error) => {
+ setError('An error occurred while retrieving indexer data. Attempting to fetch from NEAR RPC...', error);
+ append ? setIsLoadingMore(false) : setLoading(false);
+ setIsFetching(false);
+ backupNearRPCRequest();
+ });
+};
+
const storeIndexerMetaData = () => {
const url = `${REPL_QUERY_API_USAGE_URL}`;
@@ -35,7 +137,7 @@ const storeIndexerMetaData = () => {
.then(response => {
if (!response.ok) {
setError('There was an error fetching the data');
- throw new Error(`HTTP error! status: ${response.status}`);
+ return;
}
const { data } = JSON.parse(response.body);
const map = new Map();
@@ -55,15 +157,55 @@ const storeIndexerMetaData = () => {
});
});
setIndexerMetaData(map);
- setError(null);
})
}
useEffect(() => {
- fetchIndexerData();
storeIndexerMetaData();
}, []);
+useEffect(() => {
+ fetchIndexerData(page, page > 0);
+}, [page]);
+
+useEffect(() => {
+ if (selectedTab === "my-indexers") {
+ if (myIndexers.length <= 0) fetchMyIndexerData();
+ }
+ if (selectedTab === "all") {
+ if (indexers.length <= 0) fetchIndexerData(page, page > 0);
+ }
+}, [selectedTab]);
+
+const handleLoadMore = () => {
+ if (!isLoadingMore) setPage((prevPage) => prevPage + 1);
+};
+
+const backupNearRPCRequest = () => {
+ console.log('Retrieving data from Near RPC..');
+ setLoading(true);
+ Near.asyncView(`${REPL_REGISTRY_CONTRACT_ID}`, "list_all").then((data) => {
+ const allIndexers = [];
+ const myIndexers = [];
+
+ Object.keys(data).forEach((accId) => {
+ Object.keys(data[accId]).forEach((functionName) => {
+ const indexer = {
+ accountId: accId,
+ indexerName: functionName,
+ };
+ if (accId === myAccountId) myIndexers.push(indexer);
+ allIndexers.push(indexer);
+ });
+ });
+ setMyIndexers(myIndexers);
+ setIndexers(allIndexers);
+ setTotal(0);
+ setLoading(false);
+ setError(null);
+ });
+}
+
const Container = styled.div`
display: flex;
justify-content: center;
@@ -176,6 +318,13 @@ const Button = styled.button`
color: #11181c !important;
margin: 0;
+ &:disabled {
+ background-color: #cccccc;
+ color: #666666;
+ cursor: not-allowed;
+ opacity: 0.6;
+ }
+
&:hover,
&:focus {
background: #ecedee;
@@ -186,6 +335,8 @@ const Button = styled.button`
span {
color: #687076 !important;
}
+
+
`;
const Tabs = styled.div`
@@ -290,7 +441,7 @@ const SignUpLink = styled.a`
color: #0070f3;
text-decoration: none;
font-size: 0.75rem;
- margin-right: 1rem;
+ margin-left: 1rem;
`;
const ButtonWrapper = styled.div`
@@ -356,6 +507,14 @@ const ToggleButton = styled.button`
}
`;
+const LoadMoreContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: flex-end;
+ margin-top: 16px;
+ padding: 16px;
+`;
+
const LoadingSpinner = () => {
const spinnerStyle = {
width: '40px',
@@ -391,56 +550,40 @@ const LoadingSpinner = () => {
return (
-
-
-
- QueryApi
-
+
+ setSelectedTab("my-indexers")}
+ selected={selectedTab === "my-indexers"}
+ >
+ My Indexers
+
+ setSelectedTab("all")}
+ selected={selectedTab === "all"}
+ >
+ All Indexers
+
+
(Documentation)
-
-
- {
- setActiveTab("create-new-indexer");
- setSelectedIndexerName("");
- selectTab("create-new-indexer");
- }}
- >
- Create New Indexer
-
-
-
- setSelectedTab("my-indexers")}
- selected={selectedTab === "my-indexers"}
- >
- My Indexers
-
- setSelectedTab("all")}
- selected={selectedTab === "all"}
+
+ {
+ setActiveTab("create-new-indexer");
+ setSelectedIndexerName("");
+ selectTab("create-new-indexer");
+ }}
>
- All Indexers
-
-
+ Create New Indexer
+
+
{error && {error}}
@@ -452,16 +595,24 @@ return (
) : (
-
- {allIndexers.map((indexer, i) => (
- -
-
-
- ))}
-
+ <>
+
+ {indexers.map((indexer, i) => (
+ -
+
+
+ ))}
+
+ {isLoadingMore && }
+
+
+
+ >
)}
>
)}