From 59cc68d1c3d994ec533aab5f191ad487c0f86110 Mon Sep 17 00:00:00 2001 From: Roshaan Siddiqui Date: Wed, 15 Nov 2023 21:43:37 -0600 Subject: [PATCH] QueryAPI Logging Features + UI/UX changes (#388) This PR addresses a few painpoints of logging and QueryAPI Indexer development. 1. Logs can now be seen through the QueryAPI editor window via a `Show Logs` button. - No need to switch between `Indexer Status` & `Editor` window anymore saving time and frustration with the editor taking a few seconds to load. - Enables more powerful integrations with the editor for future PRs (e.g: adding blocks to debug directly from the indexer Logs table) 2. Logs are easier to digest now and search now. - Logs are grouped by `block_height` so now the user sees all the messages belonging to a block's execution together. - Additionally the user can use the `search` functionality to search for any block_height where the function was executed to view its log in entirety. - Logs have relative dates now e.g (4 mins ago...) - BlockHeights can be clicked on to take you to the explorer link 3. Simplifying QueryAPI links. - There is no need for `&view=status/editor-window` to be passed in anymore. - As long as `selectedIndexerPath` is defined, the user is sent to the editor window. If `&view=status` is passed the user is redirected to the indexer logs page within the editor window. - Note: I have a PR on `queryapi/docs` to reflect these changes: https://github.com/near/docs/pull/1584 Notes: Instead of using GraphQL to fetch logs, we are using a feature of Hasura which `restifies` your graphql queries. You can find details here: https://near-queryapi.api.pagoda.co/console/api/rest/list - The Old Indexer Status Widget should still be avalaible if needed via the following link: https://near.org/dataplatform.near/widget/QueryApi.IndexerStatus?accountId=dataplatform.near&indexer_name=social_feed Other small updates - If a invalid indexer path is chosen, the editor will show an error - QueryAPI is avalaible for users in read only mode even if they are not logged in on near.org ### This PR does not add WS support. This will be done in a future PR. ![image](https://github.com/near/queryapi/assets/25015977/0e94ede2-6cca-41ae-9dd4-827ff388ba48) ![image](https://github.com/near/queryapi/assets/25015977/01e655cb-786d-4913-9b09-e00014037863) ![queryapi logs testing](https://github.com/near/queryapi/assets/25015977/c66d980c-0988-492e-b225-46371ff7572e) --- frontend/package.json | 4 +- frontend/src/components/Editor/Editor.js | 7 + .../src/components/Editor/EditorButtons.jsx | 110 ++++---- frontend/src/components/Logs/IndexerLogs.jsx | 236 ++++++++++++++++++ frontend/src/components/Logs/LogButtons.jsx | 136 ++++++++++ frontend/src/components/Logs/Status.jsx | 88 +++++++ .../src/components/Playground/graphiql.jsx | 2 +- .../src/contexts/IndexerDetailsContext.js | 14 +- frontend/src/pages/_app.tsx | 13 +- frontend/src/pages/indexer-logs/index.js | 35 +++ frontend/src/pages/query-api-editor/index.js | 14 +- frontend/widgets/src/QueryApi.App.jsx | 2 + frontend/widgets/src/QueryApi.Dashboard.jsx | 88 +++---- frontend/widgets/src/QueryApi.Editor.jsx | 5 +- frontend/widgets/src/QueryApi.IndexerCard.jsx | 6 +- 15 files changed, 646 insertions(+), 114 deletions(-) create mode 100644 frontend/src/components/Logs/IndexerLogs.jsx create mode 100644 frontend/src/components/Logs/LogButtons.jsx create mode 100644 frontend/src/components/Logs/Status.jsx create mode 100644 frontend/src/pages/indexer-logs/index.js diff --git a/frontend/package.json b/frontend/package.json index 7ce4a1c14..5a83207d9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "lint": "next lint" }, "dependencies": { + "@apollo/client": "^3.8.7", "@graphiql/plugin-code-exporter": "0.3.5", "@graphiql/plugin-explorer": "0.3.5", "@monaco-editor/react": "^4.1.3", @@ -25,7 +26,8 @@ "eslint": "8.34.0", "eslint-config-next": "13.1.6", "graphiql": "3.0.6", - "graphql": "^16.6.0", + "graphql": "^16.8.1", + "gridjs": "6.0.6", "near-api-js": "1.1.0", "near-social-bridge": "^1.4.1", "next": "13.1.6", diff --git a/frontend/src/components/Editor/Editor.js b/frontend/src/components/Editor/Editor.js index 0da8175c4..f8a7fc2e3 100644 --- a/frontend/src/components/Editor/Editor.js +++ b/frontend/src/components/Editor/Editor.js @@ -381,6 +381,12 @@ const Editor = ({ height: "85vh", }} > + {!indexerDetails.code && ( + + Indexer Function could not be found. Are you sure this indexer exists? + + )} + {indexerDetails.code && <> + } ); }; diff --git a/frontend/src/components/Editor/EditorButtons.jsx b/frontend/src/components/Editor/EditorButtons.jsx index 212390f6e..83ac5dba8 100644 --- a/frontend/src/components/Editor/EditorButtons.jsx +++ b/frontend/src/components/Editor/EditorButtons.jsx @@ -19,7 +19,8 @@ import { TrashFill, XCircle, NodePlus, - Code + Code, + FileText, } from "react-bootstrap-icons"; import { BlockPicker } from "./BlockPicker"; import { IndexerDetailsContext } from '../../contexts/IndexerDetailsContext'; @@ -49,7 +50,9 @@ const EditorButtons = ({ debugMode, isCreateNewIndexer, indexerNameField, - setIndexerNameField + setIndexerNameField, + setShowLogsView, + showLogsView, } = useContext(IndexerDetailsContext); const removeHeight = (index) => { @@ -69,7 +72,6 @@ const EditorButtons = ({ > - {accountId} @@ -107,7 +109,7 @@ const EditorButtons = ({ )} )} - Reset Changes To Code} - > - - - - Format Code} - > - - - - Generate Types} - > - - + + + )} {(!isUserIndexer && !isCreateNewIndexer) ? ( )} + + + Reset Changes To Code} + > + + + Format Code} + > + + + Generate Types} + > + + + + {debugMode && heights.length > 0 && ( diff --git a/frontend/src/components/Logs/IndexerLogs.jsx b/frontend/src/components/Logs/IndexerLogs.jsx new file mode 100644 index 000000000..6b13b9ad8 --- /dev/null +++ b/frontend/src/components/Logs/IndexerLogs.jsx @@ -0,0 +1,236 @@ +import React, { useContext, useRef, useEffect, useState } from "react"; +import { Grid, html } from "gridjs"; +import "gridjs/dist/theme/mermaid.css"; +import { IndexerDetailsContext } from "../../contexts/IndexerDetailsContext"; +import LogButtons from "./LogButtons"; +import { useInitialPayload } from "near-social-bridge"; +import Status from "./Status"; + +const LIMIT = 100; + +const IndexerLogsComponent = () => { + const { indexerDetails, debugMode, setLogsView, latestHeight } = useContext( + IndexerDetailsContext + ); + const functionName = `${indexerDetails.accountId}/${indexerDetails.indexerName}`; + + const DEBUG_LIST_STORAGE_KEY = `QueryAPI:debugList:${indexerDetails.accountId}#${indexerDetails.indexerName} `; + + const { height, selectedTab, currentUserAccountId } = useInitialPayload(); + const [heights, setHeights] = useState( + localStorage.getItem(DEBUG_LIST_STORAGE_KEY) || [] + ); + useEffect(() => { + localStorage.setItem(DEBUG_LIST_STORAGE_KEY, heights); + }, [heights]); + + const indexerLogsRef = useRef(null); + const indexerStateRef = useRef(null); + + function formatTimestamp(timestamp) { + const date = new Date(timestamp); + + const options = { + year: "numeric", + month: "long", + day: "numeric", + hour: "numeric", + minute: "2-digit", + hour12: true, + }; + const formattedDate = date.toLocaleDateString(undefined, options); + + const now = new Date(); + const diffInSeconds = Math.round((now - date) / 1000); + let relativeTime = undefined; + if (diffInSeconds < 60) { + relativeTime = "(just now)"; + } else if (diffInSeconds < 3600) { + relativeTime = `(${Math.floor(diffInSeconds / 60)} minutes ago)`; + } else if (diffInSeconds < 86400) { + relativeTime = `(${Math.floor(diffInSeconds / 3600)} hours ago)`; + } + + return `${formattedDate} ${relativeTime ?? ""}`; + } + + function formatTimestampToReadableLocal(timestamp) { + const date = new Date(timestamp); + + const options = { + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit", + hour12: true, + }; + const formattedDate = date.toLocaleString(undefined, options); + + return formattedDate; + } + + const processLogs = (data) => { + const logEntries = data.indexer_log_entries.map((row) => ({ + block_height: row.block_height, + timestamp: row.timestamp, + message: row.message, + })); + + const groupedEntries = new Map(); + + logEntries.forEach(({ block_height, timestamp, message }) => { + if (!groupedEntries.has(block_height)) { + groupedEntries.set(block_height, []); + } + groupedEntries.get(block_height).push({ timestamp, message }); + }); + + const mergedEntries = Array.from(groupedEntries).map( + ([block_height, entries]) => { + const messages = entries + .map( + (e) => + `Timestamp: ${e.timestamp + }(${formatTimestampToReadableLocal( + e.timestamp + )}): \n

${e.message}

` + ) + .join("
"); + + const minTimestamp = entries.reduce( + (min, e) => (e.timestamp < min ? e.timestamp : min), + entries[0].timestamp + ); + + const formattedMinTimstamp = formatTimestamp(minTimestamp); + const humanReadableStamp = formatTimestampToReadableLocal(minTimestamp); + + return { + block_height, + timestamp: { humanReadableStamp, formattedMinTimstamp }, + messages, + }; + } + ); + + return mergedEntries; + }; + + useEffect(() => { + const grid = new Grid({ + columns: [ + { + name: "Block Height", + formatter: (cell) => + html( + `
${cell}
` + ), + }, + "Timestamp", + { + name: "Message", + formatter: (cell) => html(`
${cell}
`), + sort: false, + }, + ], + search: { + server: { + url: (prev, keyword) => + `${process.env.NEXT_PUBLIC_HASURA_ENDPOINT}/api/rest/queryapi/logsByBlock/?_functionName=${functionName}&_blockHeight=${keyword}`, + then: (data) => { + const logs = processLogs(data).map((log) => [ + log.block_height, + log.timestamp.formattedMinTimstamp, + log.messages, + ]); + return logs; + }, + debounceTimeout: 2000, + }, + }, + sort: true, + resizable: true, + fixedHeader: true, + pagination: { + limit: 30, + server: { + url: (prev, page, limit) => { + return prev + "&limit=" + limit + "&offset=" + page * limit; + }, + }, + }, + server: { + url: `${process.env.NEXT_PUBLIC_HASURA_ENDPOINT}/api/rest/queryapi/logs/?_functionName=${functionName}`, + headers: { + "x-hasura-role": "append", + }, + then: (data) => { + const logs = processLogs(data).map((log) => [ + log.block_height, + log.timestamp.formattedMinTimstamp, + log.messages, + ]); + return logs; + }, + total: (data) => data.indexer_log_entries_aggregate.aggregate.count, + }, + style: { + container: { + "font-family": '"Roboto Mono", monospace', + }, + table: {}, + th: { + "text-align": "center", + "max-width": "950px", + width: "800px", + }, + td: { + "text-align": "left", + "font-size": "11px", + "vertical-align": "text-top", + "background-color": "rgb(255, 255, 255)", + "max-height": "400px", + padding: "5px", + }, + }, + language: { + search: { + placeholder: "🔍 Search by Block Height...", + }, + pagination: { + results: () => "Indexer Logs", + }, + }, + }); + + grid.render(indexerLogsRef.current); + }, []); + + return ( + <> +
+ + +
+
+ + ); +}; + +export default IndexerLogsComponent; diff --git a/frontend/src/components/Logs/LogButtons.jsx b/frontend/src/components/Logs/LogButtons.jsx new file mode 100644 index 000000000..c831e118e --- /dev/null +++ b/frontend/src/components/Logs/LogButtons.jsx @@ -0,0 +1,136 @@ +import React, { useContext } from "react"; +import { + Breadcrumb, + Button, + Form, + InputGroup, + Navbar, + Container, + Col, + Row, + ButtonGroup, + OverlayTrigger, + Tooltip, + Badge, +} from "react-bootstrap"; +import { + ArrowCounterclockwise, + Justify, + TrashFill, + XCircle, + NodePlus, + Code, +} from "react-bootstrap-icons"; +import { IndexerDetailsContext } from "../../contexts/IndexerDetailsContext"; + +const LogButtons = ({ + currentUserAccountId, + heights, + setHeights, + latestHeight, + isUserIndexer, +}) => { + const { + indexerName, + accountId, + indexerDetails, + debugMode, + setShowLogsView, + showLogsView, + } = useContext(IndexerDetailsContext); + + const removeHeight = (index) => { + setHeights(heights.filter((_, i) => i !== index)); + }; + + return ( + <> + + + + + + + {accountId} + + {indexerName && ( + + {indexerName} + + )} + + { + + Contract Filter + + + } + + + + <> + Open Editor} + > + + + + + + + {debugMode && heights.length > 0 && ( + +
+ {heights.map((height, index) => ( + + {height} + removeHeight(index)} + /> + + ))} +
+
+ )} +
+
+ + ); +}; + +export default LogButtons; diff --git a/frontend/src/components/Logs/Status.jsx b/frontend/src/components/Logs/Status.jsx new file mode 100644 index 000000000..6f32f1b78 --- /dev/null +++ b/frontend/src/components/Logs/Status.jsx @@ -0,0 +1,88 @@ +import React from "react"; +import { + Card, + Badge, + ListGroup, + OverlayTrigger, + Tooltip, +} from "react-bootstrap"; +import { useQuery, gql } from "@apollo/client"; + +const Status = ({ functionName, latestHeight }) => { + const GET_STATUS = gql` + query GetState($_functionName: String!) { + indexer_state(where: { function_name: { _eq: $_functionName } }) { + status + function_name + current_block_height + current_historical_block_height + } + } + `; + const { loading, error, data } = useQuery(GET_STATUS, { + variables: { + _functionName: functionName, + }, + }); + + if (loading) return

Loading...

; + if (error) return

Error : {error.message}

; + return ( +
+ {data && + data.indexer_state.map((item, index) => ( + + + Indexer Status: {item.function_name} + + + Near's Current Block Height is {latestHeight}. Your indexer has a gap of {latestHeight - item.current_block_height} Blocks} + > + + Current Block Height:{" "} + {item.current_block_height} + + + + Historical Block Height:{" "} + {item.current_historical_block_height} + + + {item.status === "RUNNING" ? "Indexer is operating normally" : "Indexer stopped due to errors. Check Logs for more details."}} + > + <> + Status:{" "} + + {item.status} + + + + + + + ))} +
+ ); +}; + +export default Status; diff --git a/frontend/src/components/Playground/graphiql.jsx b/frontend/src/components/Playground/graphiql.jsx index c8955f6f3..bf22f29cc 100644 --- a/frontend/src/components/Playground/graphiql.jsx +++ b/frontend/src/components/Playground/graphiql.jsx @@ -9,7 +9,7 @@ import '@graphiql/plugin-code-exporter/dist/style.css'; import '@graphiql/plugin-explorer/dist/style.css'; const HASURA_ENDPOINT = - process.env.NEXT_PUBLIC_HASURA_ENDPOINT || + `${process.env.NEXT_PUBLIC_HASURA_ENDPOINT}/v1/graphql` || "https://near-queryapi.dev.api.pagoda.co/v1/graphql"; const graphQLFetcher = async (graphQLParams, accountId) => { diff --git a/frontend/src/contexts/IndexerDetailsContext.js b/frontend/src/contexts/IndexerDetailsContext.js index 10358857b..29ab66ecc 100644 --- a/frontend/src/contexts/IndexerDetailsContext.js +++ b/frontend/src/contexts/IndexerDetailsContext.js @@ -6,6 +6,7 @@ import { wrapCode, } from "../utils/formatters"; +import { useInitialPayload } from "near-social-bridge"; import { getLatestBlockHeight } from "../utils/getLatestBlockHeight"; // interface IndexerDetails { // accountId: String, @@ -39,6 +40,8 @@ export const IndexerDetailsContext = React.createContext({ indexerName: undefined, setIndexerName: () => { }, setIndexerDetails: () => { }, + showLogsView: false, + setShowLogsView: () => { }, }); export const IndexerDetailsProvider = ({ children }) => { @@ -49,9 +52,16 @@ export const IndexerDetailsProvider = ({ children }) => { const [showPublishModal, setShowPublishModal] = useState(false); const [showForkIndexerModal, setShowForkIndexerModal] = useState(false); const [debugMode, setDebugMode] = useState(false); + const [showLogsView, setShowLogsView] = useState(false); const [latestHeight, setLatestHeight] = useState(0); const [isCreateNewIndexer, setIsCreateNewIndexer] = useState(false); + const { activeView } = useInitialPayload(); + + useEffect(() => { + if (activeView == 'status') setShowLogsView(true) + }, []) + const requestIndexerDetails = async () => { const data = await queryIndexerFunctionDetails(accountId, indexerName); if (data) { @@ -117,7 +127,9 @@ export const IndexerDetailsProvider = ({ children }) => { setDebugMode, latestHeight, isCreateNewIndexer, - setIsCreateNewIndexer + setIsCreateNewIndexer, + showLogsView, + setShowLogsView, }} > {children} diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index eecf66fe2..4033f8daa 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -8,15 +8,26 @@ import { } from "near-social-bridge"; import { IndexerDetailsProvider } from '../contexts/IndexerDetailsContext'; import 'regenerator-runtime/runtime'; +import { ApolloClient, InMemoryCache, ApolloProvider, gql } from '@apollo/client'; overrideLocalStorage(); export default function App({ Component, pageProps }: AppProps) { + const client = new ApolloClient({ + uri: `${process.env.NEXT_PUBLIC_HASURA_ENDPOINT}/v1/graphql`, + cache: new InMemoryCache(), + options: { + headers: { + "x-hasura-role": "append" + } + } + }); return ( }> + + ); } - diff --git a/frontend/src/pages/indexer-logs/index.js b/frontend/src/pages/indexer-logs/index.js new file mode 100644 index 000000000..fd3cbe107 --- /dev/null +++ b/frontend/src/pages/indexer-logs/index.js @@ -0,0 +1,35 @@ +import React, { useEffect, useContext } from "react"; + +import IndexerLogs from "../../components/Logs/IndexerLogs"; +import { withRouter } from 'next/router' +import { Alert } from 'react-bootstrap'; +// import { EditorContext } from '../../contexts/EditorContext'; +import { IndexerDetailsContext } from '../../contexts/IndexerDetailsContext'; + +const IndexerLogsPage = ({ router }) => { + const { accountId, indexerName } = router.query + // const { setAccountId, setIndexerName } = useContext(EditorContext); + const { setAccountId, setIndexerName } = useContext(IndexerDetailsContext); + useEffect(() => { + if (accountId == undefined || indexerName == undefined) { + return; + } + setAccountId(accountId); + setIndexerName(indexerName); + }, [accountId, indexerName, setAccountId, setIndexerName]); + + if (accountId == undefined || indexerName == undefined) { + return ( + <> + + Both accountId and IndexerName need to be specified in the URL. + + + ) + } + return ( + + ); +}; + +export default withRouter(QueryApiEditorPage); diff --git a/frontend/src/pages/query-api-editor/index.js b/frontend/src/pages/query-api-editor/index.js index d9d844719..36356f337 100644 --- a/frontend/src/pages/query-api-editor/index.js +++ b/frontend/src/pages/query-api-editor/index.js @@ -3,13 +3,13 @@ import React, { useEffect, useContext } from "react"; import Editor from "../../components/Editor"; import { withRouter } from 'next/router' import { Alert } from 'react-bootstrap'; -// import { EditorContext } from '../../contexts/EditorContext'; import { IndexerDetailsContext } from '../../contexts/IndexerDetailsContext'; +import IndexerLogs from "../../components/Logs/IndexerLogs"; const QueryApiEditorPage = ({ router }) => { const { accountId, indexerName } = router.query - // const { setAccountId, setIndexerName } = useContext(EditorContext); - const { setAccountId, setIndexerName } = useContext(IndexerDetailsContext); + const { setAccountId, setIndexerName, showLogsView } = useContext(IndexerDetailsContext); + useEffect(() => { if (accountId == undefined || indexerName == undefined) { return; @@ -28,7 +28,13 @@ const QueryApiEditorPage = ({ router }) => { ) } return ( - + <> + {showLogsView ? ( + + ) : ( + + )} + ); }; diff --git a/frontend/widgets/src/QueryApi.App.jsx b/frontend/widgets/src/QueryApi.App.jsx index 033ce17bf..63313b733 100644 --- a/frontend/widgets/src/QueryApi.App.jsx +++ b/frontend/widgets/src/QueryApi.App.jsx @@ -1,6 +1,7 @@ const view = props.view; const path = props.path; const tab = props.tab; +const activeIndexerView = props.activeIndexerView; const selectedIndexerPath = props.selectedIndexerPath; return ( @@ -11,6 +12,7 @@ return ( path, tab, selectedIndexerPath, + activeIndexerView }} /> ); diff --git a/frontend/widgets/src/QueryApi.Dashboard.jsx b/frontend/widgets/src/QueryApi.Dashboard.jsx index 23a937c91..2771a3f62 100644 --- a/frontend/widgets/src/QueryApi.Dashboard.jsx +++ b/frontend/widgets/src/QueryApi.Dashboard.jsx @@ -3,12 +3,14 @@ const [selected_accountId, selected_indexerName] = props.selectedIndexerPath ? props.selectedIndexerPath.split("/") : [undefined, undefined]; -const activeTab = props.view ?? "indexers"; +const activeTab = props.view == "create-new-indexer" ? "create-new-indexer" : props.selectedIndexerPath ? "indexer" : "explore" +const activeIndexerView = props.activeIndexerView ?? "editor"; const limit = 7; let totalIndexers = 0; State.init({ activeTab: activeTab, + activeIndexerView: activeIndexerView, my_indexers: [], all_indexers: [], selected_indexer: undefined, @@ -53,6 +55,11 @@ const Subheading = styled.h2` outline: none; `; +const Editor = styled.div` +`; +const Status = styled.div` +`; + const Wrapper = styled.div` margin-top: calc(var(--body-top-padding) * -1); `; @@ -107,7 +114,7 @@ const Title = styled.h1` const TabsButton = styled.button` font-weight: 600; - font-size: 12px; + font-size: 14px; line-height: 16px; padding: 0 12px; position: relative; @@ -310,9 +317,15 @@ const selectTab = (tabName) => { }); }; +const selectIndexerPage = (viewName) => { + Storage.privateSet("queryapi:activeIndexerTabView", viewName); + State.update({ + activeIndexerView: viewName, + }); +}; const indexerView = (accountId, indexerName) => { - const editUrl = `https://near.org/#/${REPL_ACCOUNT_ID}/widget/QueryApi.App?selectedIndexerPath=${accountId}/${indexerName}&view=editor-window`; - const statusUrl = `https://near.org/#/${REPL_ACCOUNT_ID}/widget/QueryApi.App?selectedIndexerPath=${accountId}/${indexerName}&view=indexer-status`; + const editUrl = `https://near.org/#/${REPL_ACCOUNT_ID}/widget/QueryApi.App?selectedIndexerPath=${accountId}/${indexerName}`; + const statusUrl = `https://near.org/#/${REPL_ACCOUNT_ID}/widget/QueryApi.App?selectedIndexerPath=${accountId}/${indexerName}&view=indexer&activeIndexerView=status`; const playgroundLink = `https://cloud.hasura.io/public/graphiql?endpoint=${REPL_GRAPHQL_ENDPOINT}/v1/graphql&header=x-hasura-role%3A${accountId.replaceAll( ".", "_" @@ -344,10 +357,10 @@ const indexerView = (accountId, indexerName) => { - selectTab("indexer-status")}> + selectIndexerPage("status")}> View Status - selectTab("editor-window")}> + selectIndexerPage("editor")}> {accountId === context.accountId ? "Edit Indexer" : "View Indexer"} @@ -359,14 +372,14 @@ const indexerView = (accountId, indexerName) => { }; return ( - + selectTab("indexers")} - selected={state.activeTab === "indexers"} + onClick={() => selectTab("explore")} + selected={state.activeTab === "explore"} > - Indexers + Explore Indexers {state.activeTab == "create-new-indexer" && ( selectTab("editor-window")} - selected={state.activeTab === "editor-window"} + onClick={() => selectTab("indexer")} + selected={state.activeTab === "indexer"} > - Indexer Editor - - - selectTab("indexer-status")} - selected={state.activeTab === "indexer-status"} - > - Indexer Status + Indexer ({props.selectedIndexerPath}) )}
-
+
selectTab("indexers")} + onClick={() => selectTab("explore")} > )}
-
- {state.activeTab === "indexer-status" && ( -
- {state.indexers.length > 0 && - (state.selected_indexer != "" ? ( -

{state.selected_indexer}

- ) : ( -

{state.indexers[0].indexerName}

- ))} - {indexerView( - selected_accountId ?? state.indexers[0].accountId, - selected_indexerName ?? state.indexers[0].indexerName - )} - -
- )} -
-
- {state.activeTab === "editor-window" && ( -
+
+ {state.indexers.length > 0 && (state.selected_indexer != undefined ? (

{state.selected_indexer}

@@ -511,10 +487,10 @@ return ( accountId: selected_accountId ?? state.indexers[0].accountId, path: "query-api-editor", tab: props.tab, + activeView: state.activeIndexerView }} /> -
- )} + {state.activeTab === "create-new-indexer" && (
{state.indexers.length > 0 && diff --git a/frontend/widgets/src/QueryApi.Editor.jsx b/frontend/widgets/src/QueryApi.Editor.jsx index 8deadca58..8e37a34f6 100644 --- a/frontend/widgets/src/QueryApi.Editor.jsx +++ b/frontend/widgets/src/QueryApi.Editor.jsx @@ -1,5 +1,6 @@ const path = props.path || "query-api-editor"; const tab = props.tab || ""; +const activeView = props.activeView || "editor"; let accountId = props.accountId || context.accountId; let externalAppUrl = `${REPL_EXTERNAL_APP_URL}/${path}?accountId=${accountId}`; @@ -7,13 +8,11 @@ if (props.indexerName) { externalAppUrl += `&indexerName=${props.indexerName}`; } const initialViewHeight = 1000; -if (!context.accountId) { - return "Please sign in to use this widget."; -} const initialPayload = { height: Near.block("optimistic").header.height, selectedTab: tab, + activeView, currentUserAccountId: context.accountId, }; diff --git a/frontend/widgets/src/QueryApi.IndexerCard.jsx b/frontend/widgets/src/QueryApi.IndexerCard.jsx index f64545ba3..191f688a1 100644 --- a/frontend/widgets/src/QueryApi.IndexerCard.jsx +++ b/frontend/widgets/src/QueryApi.IndexerCard.jsx @@ -1,7 +1,7 @@ const accountId = props.accountId || context.accountId; const indexerName = props.indexerName; -const editUrl = `https://near.org/#/${REPL_ACCOUNT_ID}/widget/QueryApi.App?selectedIndexerPath=${accountId}/${indexerName}&view=editor-window`; -const statusUrl = `https://near.org/#/${REPL_ACCOUNT_ID}/widget/QueryApi.App?selectedIndexerPath=${accountId}/${indexerName}&view=indexer-status`; +const editUrl = `https://near.org/#/${REPL_ACCOUNT_ID}/widget/QueryApi.App?selectedIndexerPath=${accountId}/${indexerName}`; +const statusUrl = `https://near.org/#/${REPL_ACCOUNT_ID}/widget/QueryApi.App?selectedIndexerPath=${accountId}/${indexerName}&view=indexer&activeIndexerView=status`; const playgroundLink = `https://cloud.hasura.io/public/graphiql?endpoint=${REPL_GRAPHQL_ENDPOINT}/v1/graphql&header=x-hasura-role%3A${accountId.replaceAll( ".", "_" @@ -178,7 +178,7 @@ return ( href={editUrl} onClick={() => State.update({ - activeTab: "editor-window", + activeTab: "editor", }) } >