Add error marker when invalid main function (#9489)
## Before  ## After 
This commit is contained in:
@ -8,6 +8,7 @@ import { WorkflowCodeAction } from '@/workflow/types/Workflow';
|
|||||||
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||||
import { setNestedValue } from '@/workflow/workflow-steps/workflow-actions/utils/setNestedValue';
|
import { setNestedValue } from '@/workflow/workflow-steps/workflow-actions/utils/setNestedValue';
|
||||||
|
|
||||||
|
import { Monaco } from '@monaco-editor/react';
|
||||||
import { CmdEnterActionButton } from '@/action-menu/components/CmdEnterActionButton';
|
import { CmdEnterActionButton } from '@/action-menu/components/CmdEnterActionButton';
|
||||||
import { ServerlessFunctionExecutionResult } from '@/serverless-functions/components/ServerlessFunctionExecutionResult';
|
import { ServerlessFunctionExecutionResult } from '@/serverless-functions/components/ServerlessFunctionExecutionResult';
|
||||||
import { INDEX_FILE_PATH } from '@/serverless-functions/constants/IndexFilePath';
|
import { INDEX_FILE_PATH } from '@/serverless-functions/constants/IndexFilePath';
|
||||||
@ -26,13 +27,13 @@ import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/w
|
|||||||
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Monaco } from '@monaco-editor/react';
|
|
||||||
import { editor } from 'monaco-editor';
|
import { editor } from 'monaco-editor';
|
||||||
import { AutoTypings } from 'monaco-editor-auto-typings';
|
import { AutoTypings } from 'monaco-editor-auto-typings';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import { CodeEditor, IconCode, IconPlayerPlay, isDefined } from 'twenty-ui';
|
import { CodeEditor, IconCode, IconPlayerPlay, isDefined } from 'twenty-ui';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -299,6 +300,7 @@ export const WorkflowEditActionFormServerlessFunction = ({
|
|||||||
language={'typescript'}
|
language={'typescript'}
|
||||||
onChange={handleCodeChange}
|
onChange={handleCodeChange}
|
||||||
onMount={handleEditorDidMount}
|
onMount={handleEditorDidMount}
|
||||||
|
setMarkers={getWrongExportedFunctionMarkers}
|
||||||
options={{
|
options={{
|
||||||
readOnly: actionOptions.readonly,
|
readOnly: actionOptions.readonly,
|
||||||
domReadOnly: actionOptions.readonly,
|
domReadOnly: actionOptions.readonly,
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers';
|
||||||
|
|
||||||
|
describe('getWrongExportedFunctionMarkers', () => {
|
||||||
|
it('should return marker when no exported function', () => {
|
||||||
|
const value = 'const main = async () => {}';
|
||||||
|
const result = getWrongExportedFunctionMarkers(value);
|
||||||
|
expect(result.length).toEqual(1);
|
||||||
|
expect(result[0].message).toEqual(
|
||||||
|
'An exported "main" arrow function is required.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return marker when no wrong exported function', () => {
|
||||||
|
const value = 'export const wrongMain = async () => {}';
|
||||||
|
const result = getWrongExportedFunctionMarkers(value);
|
||||||
|
expect(result.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return no marker when valid exported function', () => {
|
||||||
|
const value = 'export const main = async () => {}';
|
||||||
|
const result = getWrongExportedFunctionMarkers(value);
|
||||||
|
expect(result.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return handle multiple spaces', () => {
|
||||||
|
const value = 'export const main = async () => {}';
|
||||||
|
const result = getWrongExportedFunctionMarkers(value);
|
||||||
|
expect(result.length).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
import { isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
|
const getSubstringCoordinate = (
|
||||||
|
text: string,
|
||||||
|
substring: string,
|
||||||
|
): { line: number; column: number } | null => {
|
||||||
|
const lines = text.split('\n');
|
||||||
|
|
||||||
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
||||||
|
const columnIndex = lines[lineIndex].indexOf(substring);
|
||||||
|
if (columnIndex !== -1) {
|
||||||
|
return {
|
||||||
|
line: lineIndex + 1, // 1-based line number
|
||||||
|
column: columnIndex + 1, // 1-based column number
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getWrongExportedFunctionMarkers = (value: string) => {
|
||||||
|
const validRegex = /export\s+const\s+main\s*=/g;
|
||||||
|
const invalidRegex = /export\s+const\s+\S*/g;
|
||||||
|
const exportRegex = /export\s+const/g;
|
||||||
|
const validMatch = value.match(validRegex);
|
||||||
|
const invalidMatch = value.match(invalidRegex);
|
||||||
|
const exportMatch = value.match(exportRegex);
|
||||||
|
const markers = [];
|
||||||
|
|
||||||
|
if (!validMatch && !!invalidMatch) {
|
||||||
|
const coordinates = getSubstringCoordinate(value, invalidMatch[0]);
|
||||||
|
if (isDefined(coordinates)) {
|
||||||
|
const endColumn = invalidMatch[0].length + coordinates.column;
|
||||||
|
markers.push({
|
||||||
|
severity: 8, //MarkerSeverity.Error,
|
||||||
|
message: 'Exported arrow function should be named "main"',
|
||||||
|
code: 'export const main',
|
||||||
|
startLineNumber: coordinates.line,
|
||||||
|
startColumn: coordinates.column,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exportMatch) {
|
||||||
|
markers.push({
|
||||||
|
severity: 8, //MarkerSeverity.Error,
|
||||||
|
message: 'An exported "main" arrow function is required.',
|
||||||
|
code: 'export const main',
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return markers;
|
||||||
|
};
|
||||||
@ -1,11 +1,14 @@
|
|||||||
import { useTheme, css } from '@emotion/react';
|
import { useTheme, css } from '@emotion/react';
|
||||||
import Editor, { EditorProps } from '@monaco-editor/react';
|
import Editor, { EditorProps, Monaco } from '@monaco-editor/react';
|
||||||
import { codeEditorTheme } from '@ui/input';
|
import { codeEditorTheme } from '@ui/input';
|
||||||
import { isDefined } from '@ui/utilities';
|
import { isDefined } from '@ui/utilities';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { editor } from 'monaco-editor';
|
||||||
|
|
||||||
type CodeEditorProps = Omit<EditorProps, 'onChange'> & {
|
type CodeEditorProps = Omit<EditorProps, 'onChange'> & {
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
|
setMarkers?: (value: string) => editor.IMarkerData[];
|
||||||
withHeader?: boolean;
|
withHeader?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,12 +38,31 @@ export const CodeEditor = ({
|
|||||||
language,
|
language,
|
||||||
onMount,
|
onMount,
|
||||||
onChange,
|
onChange,
|
||||||
|
setMarkers,
|
||||||
onValidate,
|
onValidate,
|
||||||
height = 450,
|
height = 450,
|
||||||
withHeader = false,
|
withHeader = false,
|
||||||
options,
|
options,
|
||||||
}: CodeEditorProps) => {
|
}: CodeEditorProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const [monaco, setMonaco] = useState<Monaco | undefined>(undefined);
|
||||||
|
const [editor, setEditor] = useState<
|
||||||
|
editor.IStandaloneCodeEditor | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
|
const setModelMarkers = (
|
||||||
|
editor: editor.IStandaloneCodeEditor | undefined,
|
||||||
|
monaco: Monaco | undefined,
|
||||||
|
) => {
|
||||||
|
const model = editor?.getModel();
|
||||||
|
if (!isDefined(model)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const customMarkers = setMarkers?.(model.getValue());
|
||||||
|
if (isDefined(customMarkers)) {
|
||||||
|
monaco?.editor.setModelMarkers(model, 'customMarker', customMarkers);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledEditor
|
<StyledEditor
|
||||||
@ -49,17 +71,22 @@ export const CodeEditor = ({
|
|||||||
value={value}
|
value={value}
|
||||||
language={language}
|
language={language}
|
||||||
onMount={(editor, monaco) => {
|
onMount={(editor, monaco) => {
|
||||||
|
setMonaco(monaco);
|
||||||
|
setEditor(editor);
|
||||||
monaco.editor.defineTheme('codeEditorTheme', codeEditorTheme(theme));
|
monaco.editor.defineTheme('codeEditorTheme', codeEditorTheme(theme));
|
||||||
monaco.editor.setTheme('codeEditorTheme');
|
monaco.editor.setTheme('codeEditorTheme');
|
||||||
|
|
||||||
onMount?.(editor, monaco);
|
onMount?.(editor, monaco);
|
||||||
|
setModelMarkers(editor, monaco);
|
||||||
}}
|
}}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (isDefined(value)) {
|
if (isDefined(value)) {
|
||||||
onChange?.(value);
|
onChange?.(value);
|
||||||
|
setModelMarkers(editor, monaco);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onValidate={onValidate}
|
onValidate={(markers) => {
|
||||||
|
onValidate?.(markers);
|
||||||
|
}}
|
||||||
options={{
|
options={{
|
||||||
overviewRulerLanes: 0,
|
overviewRulerLanes: 0,
|
||||||
scrollbar: {
|
scrollbar: {
|
||||||
|
|||||||
Reference in New Issue
Block a user