Skip to content

Commit

Permalink
Improve code/schema validation before registering (#495)
Browse files Browse the repository at this point in the history
More info about this can be found on this
[issue](#483)

Changes on this PR:
- Validate code & schema before registering the indexer
Logic: If formatting either the code or the schema fails, the `Publish`
button is disabled. Only if type generation errors or no errors are
detected, the `Publish` button will be enabled. More about this on this
[discussion](#480 (comment))


https://github.com/near/queryapi/assets/15988846/68f89cb4-f561-4e8a-9fa7-81c1a38e548c

Additionally 
- Created a reusable Modal with a global context to manage it, so we can
trigger it from any component to display some info
- Updated schema with granular error types for improved clarity
- Created a custom error class to filter by type Error. We can add more
fields on it if needed

---------

Co-authored-by: Juan Luis Santana <juanluis@near.org>
Co-authored-by: Roshaan Siddiqui <siddiqui.roshaan@gmail.com>
  • Loading branch information
3 people authored Jan 11, 2024
1 parent b76fac0 commit 81edb7e
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 168 deletions.
6 changes: 6 additions & 0 deletions frontend/src/classes/ValidationError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class ValidationError extends Error {
constructor(message, type) {
super(message);
this.type = type;
}
}
264 changes: 153 additions & 111 deletions frontend/src/components/Editor/Editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ import { IndexerDetailsContext } from '../../contexts/IndexerDetailsContext';
import { PgSchemaTypeGen } from "../../utils/pgSchemaTypeGen";
import { validateJSCode, validateSQLSchema } from "@/utils/validators";
import { useDebouncedCallback } from "use-debounce";
import { SCHEMA_GENERAL_ERROR, CODE_GENERAL_ERROR, CODE_FORMATTING_ERROR, SCHEMA_FORMATTING_ERROR } from '../../constants/Strings';

const BLOCKHEIGHT_LIMIT = 3600;
import { CODE_GENERAL_ERROR_MESSAGE, CODE_FORMATTING_ERROR_MESSAGE, SCHEMA_TYPE_GENERATION_ERROR_MESSAGE, SCHEMA_FORMATTING_ERROR_MESSAGE, FORMATTING_ERROR_TYPE, TYPE_GENERATION_ERROR_TYPE, INDEXER_REGISTER_TYPE_GENERATION_ERROR } from '../../constants/Strings';
import { InfoModal } from '@/core/InfoModal';
import { useModal } from "@/contexts/ModalContext";

const Editor = ({
onLoadErrorText,
actionButtonText,
}) => {
const {
Expand All @@ -38,7 +37,6 @@ const Editor = ({
setShowPublishModal,
debugMode,
isCreateNewIndexer,
indexerNameField,
setAccountId,
} = useContext(IndexerDetailsContext);

Expand Down Expand Up @@ -68,11 +66,11 @@ const Editor = ({
const [debugModeInfoDisabled, setDebugModeInfoDisabled] = useState(false);
const [diffView, setDiffView] = useState(false);
const [blockView, setBlockView] = useState(false);

const { openModal, showModal, data, message, hideModal } = useModal();

const [isExecutingIndexerFunction, setIsExecutingIndexerFunction] = useState(false);

const { height, selectedTab, currentUserAccountId } = useInitialPayload();
const { height, currentUserAccountId } = useInitialPayload();

const handleLog = (_, log, callback) => {
if (log) console.log(log);
Expand All @@ -86,14 +84,18 @@ const Editor = ({
const disposableRef = useRef(null);
const debouncedValidateSQLSchema = useDebouncedCallback((_schema) => {
const { error: schemaError } = validateSQLSchema(_schema);
if (!schemaError) {
if (schemaError?.type === FORMATTING_ERROR_TYPE) {
setError(SCHEMA_FORMATTING_ERROR_MESSAGE);
} else {
setError();
}
}, 500);

const debouncedValidateCode = useDebouncedCallback((_code) => {
const { error: codeError } = validateJSCode(_code);
if (!codeError) {
if (codeError) {
setError(CODE_FORMATTING_ERROR_MESSAGE)
} else {
setError();
}
}, 500);
Expand All @@ -103,7 +105,7 @@ const Editor = ({
const { data: formattedCode, error: codeError } = validateJSCode(indexerDetails.code)

if (codeError) {
setError(CODE_FORMATTING_ERROR)
setError(CODE_FORMATTING_ERROR_MESSAGE)
}

setOriginalIndexingCode(formattedCode)
Expand All @@ -115,8 +117,11 @@ const Editor = ({
useEffect(() => {
if (indexerDetails.schema != null) {
const { data: formattedSchema, error: schemaError } = validateSQLSchema(indexerDetails.schema);
if (schemaError) {
setError(SCHEMA_GENERAL_ERROR)

if (schemaError?.type === FORMATTING_ERROR_TYPE) {
setError(SCHEMA_FORMATTING_ERROR_MESSAGE);
} else if (schemaError?.type === TYPE_GENERATION_ERROR_TYPE) {
setError(SCHEMA_TYPE_GENERATION_ERROR_MESSAGE);
}

setSchema(formattedSchema)
Expand All @@ -128,9 +133,14 @@ const Editor = ({
const { error: schemaError } = validateSQLSchema(schema);
const { error: codeError } = validateJSCode(indexingCode);

if (schemaError) setError(SCHEMA_GENERAL_ERROR)
else if (codeError) setError(CODE_GENERAL_ERROR)
else setError();
if (schemaError?.type === FORMATTING_ERROR_TYPE) {
setError(SCHEMA_FORMATTING_ERROR_MESSAGE);
} else if (schemaError?.type === TYPE_GENERATION_ERROR_TYPE) {
setError(SCHEMA_TYPE_GENERATION_ERROR_MESSAGE);
} else if (codeError) setError(CODE_GENERAL_ERROR_MESSAGE)
else {
setError()
};

}, [fileName])

Expand Down Expand Up @@ -188,23 +198,39 @@ const Editor = ({
}

const registerFunction = async (indexerName, indexerConfig) => {
const { data: formattedSchema, error: schemaError } = validateSQLSchema(schema);
const { data: validatedSchema, error: schemaValidationError } = validateSQLSchema(schema);
const { data: validatedCode, error: codeValidationError } = validateJSCode(indexingCode);

if (schemaError) {
setError(SCHEMA_GENERAL_ERROR);
if (codeValidationError) {
setError(CODE_FORMATTING_ERROR_MESSAGE);
return;
}

let innerCode = indexingCode.match(/getBlock\s*\([^)]*\)\s*{([\s\S]*)}/)[1];
let innerCode = validatedCode.match(/getBlock\s*\([^)]*\)\s*{([\s\S]*)}/)[1];
indexerName = indexerName.replaceAll(" ", "_");

if (schemaValidationError?.type === FORMATTING_ERROR_TYPE) {
setError(SCHEMA_FORMATTING_ERROR_MESSAGE);
return;
} else if (schemaValidationError?.type === TYPE_GENERATION_ERROR_TYPE) {
showModal(INDEXER_REGISTER_TYPE_GENERATION_ERROR, {
indexerName,
code: innerCode,
schema: validatedSchema,
blockHeight: indexerConfig.startBlockHeight,
contractFilter: indexerConfig.filter
});
return;
}

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

setShowPublishModal(false);
};

Expand Down Expand Up @@ -264,10 +290,13 @@ const Editor = ({

if (codeError) {
formattedCode = indexingCode
setError(CODE_FORMATTING_ERROR);
} else if (schemaError) {
setError(CODE_FORMATTING_ERROR_MESSAGE);
} else if (schemaError?.type === FORMATTING_ERROR_TYPE) {
formattedSchema = schema;
setError(SCHEMA_FORMATTING_ERROR_MESSAGE);
} else if (schemaError?.type === TYPE_GENERATION_ERROR_TYPE) {
formattedSchema = schema;
setError(SCHEMA_GENERAL_ERROR)
setError(SCHEMA_TYPE_GENERATION_ERROR_MESSAGE)
} else {
setError()
}
Expand All @@ -281,7 +310,7 @@ const Editor = ({
attachTypesToMonaco(); // Just in case schema types have been updated but weren't added to monaco
} catch (_error) {
console.error("Error generating types for saved schema.\n", _error);
setError(SCHEMA_FORMATTING_ERROR);
setError(SCHEMA_TYPE_GENERATION_ERROR_MESSAGE);
}
}

Expand Down Expand Up @@ -333,96 +362,109 @@ const Editor = ({
debouncedValidateCode(_code);
}

function handleRegisterIndexerWithErrors(args) {
request("register-function", args);
}

return (
<div
style={{
display: "flex",
flexDirection: "column",
width: "100%",
height: "85vh",
}}
>
{!indexerDetails.code && !isCreateNewIndexer && (
<Alert className="px-3 pt-3" variant="danger">
Indexer Function could not be found. Are you sure this indexer exists?
</Alert>
)}
{(indexerDetails.code || isCreateNewIndexer) && <>
<EditorButtons
handleFormating={handleFormating}
handleCodeGen={handleCodeGen}
executeIndexerFunction={executeIndexerFunction}
currentUserAccountId={currentUserAccountId}
getActionButtonText={getActionButtonText}
heights={heights}
setHeights={setHeights}
isCreateNewIndexer={isCreateNewIndexer}
isExecuting={isExecutingIndexerFunction}
stopExecution={() => indexerRunner.stop()}
latestHeight={height}
isUserIndexer={indexerDetails.accountId === currentUserAccountId}
handleDeleteIndexer={handleDeleteIndexer}
/>
<ResetChangesModal
handleReload={handleReload}
/>
<PublishModal
registerFunction={registerFunction}
actionButtonText={getActionButtonText()}
blockHeightError={blockHeightError}
/>
<ForkIndexerModal
forkIndexer={forkIndexer}
/>

<div
className="px-3 pt-3"
style={{
flex: "display",
justifyContent: "space-around",
width: "100%",
height: "100%",
}}
>
{error && (
<Alert dismissible="true" onClose={() => setError()} className="px-3 pt-3" variant="danger">
{error}
</Alert>
)}
{debugMode && !debugModeInfoDisabled && (
<Alert
className="px-3 pt-3"
dismissible="true"
onClose={() => setDebugModeInfoDisabled(true)}
variant="info"
>
To debug, you will need to open your browser console window in
order to see the logs.
</Alert>
)}
<FileSwitcher
fileName={fileName}
setFileName={setFileName}
diffView={diffView}
setDiffView={setDiffView}
/>
<ResizableLayoutEditor
fileName={fileName}
indexingCode={indexingCode}
blockView={blockView}
diffView={diffView}
onChangeCode={handleOnChangeCode}
onChangeSchema={handleOnChangeSchema}
block_details={block_details}
originalSQLCode={originalSQLCode}
originalIndexingCode={originalIndexingCode}
schema={schema}
<>
<div
style={{
display: "flex",
flexDirection: "column",
width: "100%",
height: "85vh",
}}
>
{!indexerDetails.code && !isCreateNewIndexer && (
<Alert className="px-3 pt-3" variant="danger">
Indexer Function could not be found. Are you sure this indexer exists?
</Alert>
)}
{(indexerDetails.code || isCreateNewIndexer) && <>
<EditorButtons
handleFormating={handleFormating}
handleCodeGen={handleCodeGen}
error={error}
executeIndexerFunction={executeIndexerFunction}
heights={heights}
setHeights={setHeights}
isCreateNewIndexer={isCreateNewIndexer}
handleEditorWillMount={handleEditorWillMount}
isExecuting={isExecutingIndexerFunction}
stopExecution={() => indexerRunner.stop()}
latestHeight={height}
isUserIndexer={indexerDetails.accountId === currentUserAccountId}
handleDeleteIndexer={handleDeleteIndexer}
/>
<ResetChangesModal
handleReload={handleReload}
/>
<PublishModal
registerFunction={registerFunction}
actionButtonText={getActionButtonText()}
blockHeightError={blockHeightError}
/>
</div>
</>}
</div>
<ForkIndexerModal
forkIndexer={forkIndexer}
/>

<div
className="px-3 pt-3"
style={{
flex: "display",
justifyContent: "space-around",
width: "100%",
height: "100%",
}}
>
{error && (
<Alert dismissible="true" onClose={() => setError()} className="px-3 pt-3" variant="danger">
{error}
</Alert>
)}
{debugMode && !debugModeInfoDisabled && (
<Alert
className="px-3 pt-3"
dismissible="true"
onClose={() => setDebugModeInfoDisabled(true)}
variant="info"
>
To debug, you will need to open your browser console window in
order to see the logs.
</Alert>
)}
<FileSwitcher
fileName={fileName}
setFileName={setFileName}
diffView={diffView}
setDiffView={setDiffView}
/>
<ResizableLayoutEditor
fileName={fileName}
indexingCode={indexingCode}
blockView={blockView}
diffView={diffView}
onChangeCode={handleOnChangeCode}
onChangeSchema={handleOnChangeSchema}
block_details={block_details}
originalSQLCode={originalSQLCode}
originalIndexingCode={originalIndexingCode}
schema={schema}
isCreateNewIndexer={isCreateNewIndexer}
handleEditorWillMount={handleEditorWillMount}
/>
</div>
</>}
</div>
<InfoModal
open={openModal}
title="Validation Error"
message={message}
okButtonText="Proceed"
onOkButtonPressed={() => handleRegisterIndexerWithErrors(data)}
onCancelButtonPressed={hideModal}
onClose={hideModal} />
</>
);
};

Expand Down
Loading

0 comments on commit 81edb7e

Please sign in to comment.