Skip to content

Commit

Permalink
feat: Update frontend to handle new registry types (#566)
Browse files Browse the repository at this point in the history
This PR updates the frontend to work with the updated registry contract
types/methods. There are two parts to this change:
- Consuming the new types, mainly `rule` and `startBlock`, and writing
these types back during registration
- Adding the new `StartBlock::Continue` option, which allows developers
to resume the existing block stream.

I've attached images of the updated UI for reference. Note that updating
the contract filter is disabled with the new "Continue" option.


![image](https://github.com/near/queryapi/assets/11974624/d4069cc8-d7bf-4c66-9d33-ec9d51bfcc83)

![image](https://github.com/near/queryapi/assets/11974624/0b3ef7e4-6542-4b5a-86ad-14c8028565ed)
  • Loading branch information
morgsmccauley authored Feb 21, 2024
1 parent 2a5d923 commit 345e15a
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 66 deletions.
13 changes: 12 additions & 1 deletion frontend/src/components/Editor/Editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,22 @@ const Editor = ({ actionButtonText }) => {
return;
}

let startBlock = null;
if (indexerConfig.startBlock === "startBlockHeight") {
startBlock = {
HEIGHT: indexerConfig.height
};
} else if (indexerConfig.startBlock === "startBlockLatest") {
startBlock = "LATEST";
} else {
startBlock = "CONTINUE"
}

request("register-function", {
indexerName: indexerName,
code: innerCode,
schema: validatedSchema,
blockHeight: indexerConfig.startBlockHeight,
startBlock,
contractFilter: indexerConfig.filter,
});

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Editor/EditorButtons.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const EditorButtons = ({
<InputGroup.Text> Contract Filter</InputGroup.Text>
<Form.Control
disabled={!isCreateNewIndexer}
value={indexerDetails.config.filter}
value={indexerDetails.rule.affected_account_id}
type="text"
placeholder="social.near"
required={true}
Expand Down
109 changes: 75 additions & 34 deletions frontend/src/components/Form/IndexerConfigOptionsInputGroup.jsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,73 @@
import React, { useContext, useState, useEffect } from "react";
import { InputGroup, Alert } from "react-bootstrap";
import Form from "react-bootstrap/Form";

import { IndexerDetailsContext } from '../../contexts/IndexerDetailsContext';
import { validateContractIds } from "../../utils/validators";

const GENESIS_BLOCK_HEIGHT = 9820210;

const START_BLOCK = {
CONTINUE: "startBlockContinue",
LATEST: "startBlockLatest",
HEIGHT: "startBlockHeight",
}

const IndexerConfigOptions = ({ updateConfig }) => {
const { indexerDetails, showPublishModal, isCreateNewIndexer, latestHeight } = useContext(IndexerDetailsContext);
const [blockHeight, setBlockHeight] = useState("0");
const [contractFilter, setContractFilter] = useState("social.near");
const [selectedOption, setSelectedOption] = useState("latestBlockHeight");
const [startBlock, setStartBlock] = useState(START_BLOCK.LATEST);
const [isContractFilterValid, setIsContractFilterValid] = useState(true);
const [indexerNameField, setIndexerNameField] = useState(indexerDetails.indexerName || "");
const [blockHeightError, setBlockHeightError] = useState(null)

const handleOptionChange = (event) => {
setSelectedOption(event.target.value);
// setBlockHeightError(null);
};

useEffect(() => {
if (indexerDetails.config?.startBlockHeight) {
setSelectedOption("specificBlockHeight")
setBlockHeight(indexerDetails.config.startBlockHeight)
if (indexerDetails.rule?.affected_account_id) {
setContractFilter(indexerDetails.rule.affected_account_id)
}
if (indexerDetails.config?.filter) {
setContractFilter(indexerDetails.config.filter)

if (indexerDetails.startBlock?.HEIGHT) {
setStartBlock(START_BLOCK.HEIGHT)
setBlockHeight(indexerDetails.startBlock.HEIGHT)
return;
}

if (indexerDetails.startBlock == "LATEST") {
setStartBlock(START_BLOCK.LATEST)
return;
}

if (indexerDetails.startBlock == "CONTINUE") {
setStartBlock(START_BLOCK.CONTINUE)
return;
}
}, [indexerDetails])

function handleSetContractFilter(e) {
const contractFilter = e.target.value;
const onChangeStartBlock = (e) => {
setStartBlock(e.target.value)

if (e.target.value === START_BLOCK.CONTINUE) {
handleSetContractFilter(indexerDetails.rule.affected_account_id)
}
}

function handleSetContractFilter(contractFilter) {
setContractFilter(contractFilter);
const isContractFilterValid = validateContractIds(contractFilter);
setIsContractFilterValid(isContractFilterValid);
}

useEffect(() => {
if (selectedOption == "specificBlockHeight" && blockHeight <= GENESIS_BLOCK_HEIGHT) {
setBlockHeightError(() => `Choose a block height greater than the Genesis BlockHeight ${GENESIS_BLOCK_HEIGHT}. Latest Block Height is ${latestHeight}`)
return
}
setBlockHeightError(() => null)
updateConfig(indexerNameField, contractFilter, blockHeight, selectedOption)
},
[indexerNameField, contractFilter, selectedOption, blockHeight])
if (startBlock == START_BLOCK.HEIGHT && blockHeight <= GENESIS_BLOCK_HEIGHT) {
setBlockHeightError(() => `Choose a block height greater than the Genesis BlockHeight ${GENESIS_BLOCK_HEIGHT}. Latest Block Height is ${latestHeight}`)
return
}
setBlockHeightError(() => null)
updateConfig(indexerNameField, contractFilter, blockHeight, startBlock)
},
[indexerNameField, contractFilter, startBlock, blockHeight]
)

return (
<>
Expand All @@ -58,23 +82,34 @@ const IndexerConfigOptions = ({ updateConfig }) => {
onChange={(e) => setIndexerNameField(e.target.value.toLowerCase().trim())}
/>
</InputGroup>
<InputGroup size="sm" className="pt-3">
<InputGroup.Checkbox
value={START_BLOCK.LATEST}
checked={startBlock === START_BLOCK.LATEST}
onChange={onChangeStartBlock}
aria-label="Checkbox for following text input"
/>
<InputGroup.Text>Start from latest block</InputGroup.Text>
</InputGroup>
{!isCreateNewIndexer && (
<InputGroup size="sm" className="pt-3">
<InputGroup.Checkbox
value="latestBlockHeight"
checked={selectedOption === "latestBlockHeight"}
onChange={handleOptionChange}
value={START_BLOCK.CONTINUE}
checked={startBlock === START_BLOCK.CONTINUE}
onChange={onChangeStartBlock}
aria-label="Checkbox for following text input"
/>
<InputGroup.Text>From Latest Block Height</InputGroup.Text>
<InputGroup.Text>Continue from last processed block</InputGroup.Text>
</InputGroup>
<InputGroup size="sm" className="px-1 pt-3">
)}
<InputGroup size="sm" className="pt-3">
<InputGroup.Checkbox
value="specificBlockHeight"
checked={selectedOption === "specificBlockHeight"}
onChange={handleOptionChange}
value={START_BLOCK.HEIGHT}
checked={startBlock === START_BLOCK.HEIGHT}
onChange={onChangeStartBlock}
aria-label="Checkbox for following text input"
/>
<InputGroup.Text>Specific Block Height</InputGroup.Text>
<InputGroup.Text>Start from block height</InputGroup.Text>
<Form.Control
value={blockHeight}
onChange={(e) => setBlockHeight(parseInt(e.target.value))}
Expand All @@ -86,19 +121,25 @@ const IndexerConfigOptions = ({ updateConfig }) => {
</Alert>
)}
</InputGroup>
<InputGroup size="sm" hasValidation={true} className="px-1 pt-3">
<InputGroup.Text> Contract Filter</InputGroup.Text>
<InputGroup size="sm" hasValidation={true} className="pt-3">
<InputGroup.Text>Contract Filter</InputGroup.Text>
<Form.Control
value={contractFilter}
onChange={handleSetContractFilter}
value={startBlock === START_BLOCK.CONTINUE ? indexerDetails.rule.affected_account_id : contractFilter}
onChange={(e) => handleSetContractFilter(e.target.value)}
isValid={isContractFilterValid}
type="text"
placeholder="social.near"
required={true}
disabled={startBlock === START_BLOCK.CONTINUE}
/>
<Form.Control.Feedback type="invalid">
Please provide a valid contract name.
</Form.Control.Feedback>
{startBlock === START_BLOCK.CONTINUE && (
<Alert className="px-3 mt-3" variant="warning">
Contract filter cannot be changed for &quot;Continue&quot; option.
</Alert>
)}
</InputGroup>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Logs/LogButtons.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const LogButtons = ({
<InputGroup size="sm" style={{ width: "fit-content" }}>
<InputGroup.Text> Contract Filter</InputGroup.Text>
<Form.Control
value={indexerDetails.config.filter}
value={indexerDetails.rule.affected_account_id}
disabled={true}
type="text"
placeholder="social.near"
Expand Down
7 changes: 2 additions & 5 deletions frontend/src/components/Modals/PublishModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@ export const PublishModal = ({
const [indexerName, setIndexerName] = useState("")
const [error, setError] = useState(null)

const updateConfig = (indexerName, filter, startBlockHeight, option) => {
if (option === "latestBlockHeight") {
startBlockHeight = null
}
setIndexerConfig({ filter, startBlockHeight })
const updateConfig = (indexerName, filter, height, startBlock) => {
setIndexerConfig({ filter, startBlock, height })
setIndexerName(indexerName)
}

Expand Down
14 changes: 6 additions & 8 deletions frontend/src/contexts/IndexerDetailsContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { getLatestBlockHeight } from "../utils/getLatestBlockHeight";
// }

export const IndexerDetailsContext = React.createContext({
indexerDetails: { code: undefined, schema: undefined, config: { filter: "social.near", startBlockHeight: null }, accountId: "", indexerName: "" },
indexerDetails: { code: undefined, schema: undefined, rule: { affected_account_id: "social.near" }, startBlock: "LATEST", accountId: "", indexerName: "" },
showResetCodeModel: false,
setShowResetCodeModel: () => { },
showPublishModal: false,
Expand All @@ -47,7 +47,7 @@ export const IndexerDetailsContext = React.createContext({
export const IndexerDetailsProvider = ({ children }) => {
const [accountId, setAccountId] = useState(undefined);
const [indexerName, setIndexerName] = useState(undefined);
const [indexerDetails, setIndexerDetails] = useState({ code: undefined, schema: undefined, config: { filter: "social.near", startBlockHeight: null }, accountId: accountId, indexerName: indexerName })
const [indexerDetails, setIndexerDetails] = useState({ code: undefined, schema: undefined, rule: { affected_account_id: "social.near" }, startBlock: "LATEST", accountId: accountId, indexerName: indexerName })
const [showResetCodeModel, setShowResetCodeModel] = useState(false);
const [showPublishModal, setShowPublishModal] = useState(false);
const [showForkIndexerModal, setShowForkIndexerModal] = useState(false);
Expand All @@ -65,16 +65,13 @@ export const IndexerDetailsProvider = ({ children }) => {
const requestIndexerDetails = async () => {
const data = await queryIndexerFunctionDetails(accountId, indexerName);
if (data) {
const indexerConfig = {
startBlockHeight: data.start_block_height,
filter: data.filter.matching_rule.affected_account_id,
}
const details = {
accountId: accountId,
indexerName: indexerName,
code: wrapCode(data.code),
schema: data.schema,
config: indexerConfig
startBlock: data.start_block,
rule: data.rule
}
return details
}
Expand Down Expand Up @@ -102,7 +99,8 @@ export const IndexerDetailsProvider = ({ children }) => {
indexerName: indexer.indexerName,
code: indexer.code,
schema: indexer.schema,
config: indexer.config
startBlock: indexer.startBlock,
rule: indexer.rule
}
setIndexerDetails(details);
})();
Expand Down
18 changes: 12 additions & 6 deletions frontend/src/utils/queryIndexerFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,27 @@ const provider = new providers.JsonRpcProvider(
);

export const queryIndexerFunctionDetails = async (accountId, functionName) => {
let args = { account_id: accountId, function_name: functionName };
let args = { account_id: accountId };

try {
const result = await provider.query({
request_type: "call_function",
account_id: REGISTRY_CONTRACT,
method_name: "read_indexer_function",
// TODO Create method to query single indexer
method_name: "list_by_account",
args_base64: Buffer.from(JSON.stringify(args)).toString("base64"),
finality: "optimistic",
});
return (
result.result &&

const indexers = result.result &&
result.result.length > 0 &&
JSON.parse(Buffer.from(result.result).toString())
);
JSON.parse(Buffer.from(result.result).toString());

if (!indexers) {
return null;
}

return indexers[functionName];
} catch (error) {
console.log(`Could not query indexer function details from registry ${REGISTRY_CONTRACT}, for ${accountId}/${functionName}`)
console.log(error, "error");
Expand Down
6 changes: 3 additions & 3 deletions frontend/widgets/src/QueryApi.Dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ State.init({
selected_account: undefined,
});

Near.asyncView(`${REPL_REGISTRY_CONTRACT_ID}`, "list_indexer_functions").then((data) => {
Near.asyncView(`${REPL_REGISTRY_CONTRACT_ID}`, "list_all").then((data) => {
const indexers = [];
const total_indexers = 0;
Object.keys(data.All).forEach((accountId) => {
Object.keys(data.All[accountId]).forEach((functionName) => {
Object.keys(data).forEach((accountId) => {
Object.keys(data[accountId]).forEach((functionName) => {
indexers.push({
accountId: accountId,
indexerName: functionName,
Expand Down
12 changes: 8 additions & 4 deletions frontend/widgets/src/QueryApi.Editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,24 @@ const initialPayload = {

const registerFunctionHandler = (request, response) => {
const gas = 200000000000000;
const { indexerName, code, schema, blockHeight, contractFilter } =
const { indexerName, code, schema, startBlock, contractFilter } =
request.payload;

const jsonFilter = `{"indexer_rule_kind":"Action","matching_rule":{"rule":"ACTION_ANY","affected_account_id":"${contractFilter || "social.near"}","status":"SUCCESS"}}`

Near.call(
`${REPL_REGISTRY_CONTRACT_ID}`,
"register_indexer_function",
"register",
{
function_name: indexerName,
code,
schema,
start_block_height: blockHeight,
filter_json: jsonFilter
start_block: startBlock,
rule: {
kind: "ACTION_ANY",
affected_account_id: contractFilter,
status: "SUCCESS"
}
},
gas
);
Expand Down
6 changes: 3 additions & 3 deletions frontend/widgets/src/QueryApi.IndexerExplorer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ if (props.tab && props.tab !== state.selectedTab) {
});
}

Near.asyncView(`${REPL_REGISTRY_CONTRACT_ID}`, "list_indexer_functions").then((data) => {
Near.asyncView(`${REPL_REGISTRY_CONTRACT_ID}`, "list_all").then((data) => {
const indexers = [];
const total_indexers = 0;
Object.keys(data.All).forEach((accountId) => {
Object.keys(data.All[accountId]).forEach((functionName) => {
Object.keys(data).forEach((accountId) => {
Object.keys(data[accountId]).forEach((functionName) => {
indexers.push({
accountId: accountId,
indexerName: functionName,
Expand Down

0 comments on commit 345e15a

Please sign in to comment.