Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update frontend to handle new registry types #566

Merged
merged 11 commits into from
Feb 21, 2024
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";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come startBlockHeight sets startBlock to an object whereas latest and continue set it to a string? Is this a quirk of the way the new register function expects it?

} 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}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to change the filter on LATEST, then check CONTINUE and it publishes new filter IDs anyway?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, when we change to CONTINUE, the filter gets reset back to the original state :)

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