Improve autocomplete sorting (#5798)
This commit is contained in:
parent
6e20e2b659
commit
ba06d797de
|
|
@ -31,7 +31,7 @@ describe("Dynamic input autocomplete", () => {
|
||||||
// Tests if data tree entities are sorted
|
// Tests if data tree entities are sorted
|
||||||
cy.get(`${dynamicInputLocators.hints} li`)
|
cy.get(`${dynamicInputLocators.hints} li`)
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.should("have.text", "Aditya.backgroundColor");
|
.should("have.text", "input.text");
|
||||||
|
|
||||||
// Tests if "No suggestions" message will pop if you type any garbage
|
// Tests if "No suggestions" message will pop if you type any garbage
|
||||||
cy.get(dynamicInputLocators.input)
|
cy.get(dynamicInputLocators.input)
|
||||||
|
|
|
||||||
82
app/client/src/actions/evaluationActions.ts
Normal file
82
app/client/src/actions/evaluationActions.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import {
|
||||||
|
ReduxAction,
|
||||||
|
ReduxActionErrorTypes,
|
||||||
|
ReduxActionTypes,
|
||||||
|
} from "../constants/ReduxActionConstants";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { DataTree } from "../entities/DataTree/dataTreeFactory";
|
||||||
|
import { DependencyMap } from "../utils/DynamicBindingUtils";
|
||||||
|
import { Diff } from "deep-diff";
|
||||||
|
|
||||||
|
export const FIRST_EVAL_REDUX_ACTIONS = [
|
||||||
|
// Pages
|
||||||
|
ReduxActionTypes.FETCH_PAGE_SUCCESS,
|
||||||
|
ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS,
|
||||||
|
];
|
||||||
|
export const EVALUATE_REDUX_ACTIONS = [
|
||||||
|
...FIRST_EVAL_REDUX_ACTIONS,
|
||||||
|
// Actions
|
||||||
|
ReduxActionTypes.FETCH_ACTIONS_SUCCESS,
|
||||||
|
ReduxActionTypes.FETCH_PLUGIN_FORM_CONFIGS_SUCCESS,
|
||||||
|
ReduxActionTypes.FETCH_ACTIONS_VIEW_MODE_SUCCESS,
|
||||||
|
ReduxActionErrorTypes.FETCH_ACTIONS_ERROR,
|
||||||
|
ReduxActionErrorTypes.FETCH_ACTIONS_VIEW_MODE_ERROR,
|
||||||
|
ReduxActionTypes.FETCH_ACTIONS_FOR_PAGE_SUCCESS,
|
||||||
|
ReduxActionTypes.SUBMIT_CURL_FORM_SUCCESS,
|
||||||
|
ReduxActionTypes.CREATE_ACTION_SUCCESS,
|
||||||
|
ReduxActionTypes.UPDATE_ACTION_PROPERTY,
|
||||||
|
ReduxActionTypes.DELETE_ACTION_SUCCESS,
|
||||||
|
ReduxActionTypes.COPY_ACTION_SUCCESS,
|
||||||
|
ReduxActionTypes.MOVE_ACTION_SUCCESS,
|
||||||
|
ReduxActionTypes.RUN_ACTION_SUCCESS,
|
||||||
|
ReduxActionErrorTypes.RUN_ACTION_ERROR,
|
||||||
|
ReduxActionTypes.EXECUTE_API_ACTION_SUCCESS,
|
||||||
|
ReduxActionErrorTypes.EXECUTE_ACTION_ERROR,
|
||||||
|
// App Data
|
||||||
|
ReduxActionTypes.SET_APP_MODE,
|
||||||
|
ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS,
|
||||||
|
ReduxActionTypes.UPDATE_APP_PERSISTENT_STORE,
|
||||||
|
ReduxActionTypes.UPDATE_APP_TRANSIENT_STORE,
|
||||||
|
// Widgets
|
||||||
|
ReduxActionTypes.UPDATE_LAYOUT,
|
||||||
|
ReduxActionTypes.UPDATE_WIDGET_PROPERTY,
|
||||||
|
ReduxActionTypes.UPDATE_WIDGET_NAME_SUCCESS,
|
||||||
|
// Widget Meta
|
||||||
|
ReduxActionTypes.SET_META_PROP,
|
||||||
|
ReduxActionTypes.RESET_WIDGET_META,
|
||||||
|
// Batches
|
||||||
|
ReduxActionTypes.BATCH_UPDATES_SUCCESS,
|
||||||
|
];
|
||||||
|
export const shouldProcessBatchedAction = (action: ReduxAction<unknown>) => {
|
||||||
|
if (
|
||||||
|
action.type === ReduxActionTypes.BATCH_UPDATES_SUCCESS &&
|
||||||
|
Array.isArray(action.payload)
|
||||||
|
) {
|
||||||
|
const batchedActionTypes = action.payload.map(
|
||||||
|
(batchedAction) => batchedAction.type,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
_.intersection(EVALUATE_REDUX_ACTIONS, batchedActionTypes).length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setEvaluatedTree = (
|
||||||
|
dataTree: DataTree,
|
||||||
|
updates: Diff<DataTree, DataTree>[],
|
||||||
|
): ReduxAction<{ dataTree: DataTree; updates: Diff<DataTree, DataTree>[] }> => {
|
||||||
|
return {
|
||||||
|
type: ReduxActionTypes.SET_EVALUATED_TREE,
|
||||||
|
payload: { dataTree, updates },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setDependencyMap = (
|
||||||
|
inverseDependencyMap: DependencyMap,
|
||||||
|
): ReduxAction<{ inverseDependencyMap: DependencyMap }> => {
|
||||||
|
return {
|
||||||
|
type: ReduxActionTypes.SET_EVALUATION_INVERSE_DEPENDENCY_MAP,
|
||||||
|
payload: { inverseDependencyMap },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import CodeMirror from "codemirror";
|
import CodeMirror from "codemirror";
|
||||||
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||||
|
|
||||||
export enum EditorModes {
|
export enum EditorModes {
|
||||||
TEXT = "text/plain",
|
TEXT = "text/plain",
|
||||||
|
|
@ -40,18 +40,23 @@ export const EditorThemes: Record<EditorTheme, string> = {
|
||||||
[EditorTheme.DARK]: "duotone-dark",
|
[EditorTheme.DARK]: "duotone-dark",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type HintEntityInformation = {
|
||||||
|
entityName?: string;
|
||||||
|
expectedType?: string;
|
||||||
|
entityType?: ENTITY_TYPE.ACTION | ENTITY_TYPE.WIDGET;
|
||||||
|
};
|
||||||
|
|
||||||
export type HintHelper = (
|
export type HintHelper = (
|
||||||
editor: CodeMirror.Editor,
|
editor: CodeMirror.Editor,
|
||||||
data: DataTree,
|
data: DataTree,
|
||||||
additionalData?: Record<string, Record<string, unknown>>,
|
customDataTree?: Record<string, Record<string, unknown>>,
|
||||||
) => Hinter;
|
) => Hinter;
|
||||||
export type Hinter = {
|
export type Hinter = {
|
||||||
showHint: (
|
showHint: (
|
||||||
editor: CodeMirror.Editor,
|
editor: CodeMirror.Editor,
|
||||||
expected: string,
|
entityInformation: HintEntityInformation,
|
||||||
entityName: string,
|
|
||||||
additionalData?: any,
|
additionalData?: any,
|
||||||
) => any;
|
) => boolean;
|
||||||
update?: (data: DataTree) => void;
|
update?: (data: DataTree) => void;
|
||||||
trigger?: (editor: CodeMirror.Editor) => void;
|
trigger?: (editor: CodeMirror.Editor) => void;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import CodeMirror from "codemirror";
|
||||||
|
import { getDynamicStringSegments } from "utils/DynamicBindingUtils";
|
||||||
|
|
||||||
export const removeNewLineChars = (inputValue: any) => {
|
export const removeNewLineChars = (inputValue: any) => {
|
||||||
return inputValue && inputValue.replace(/(\r\n|\n|\r)/gm, "");
|
return inputValue && inputValue.replace(/(\r\n|\n|\r)/gm, "");
|
||||||
};
|
};
|
||||||
|
|
@ -10,3 +13,43 @@ export const getInputValue = (inputValue: any) => {
|
||||||
}
|
}
|
||||||
return inputValue;
|
return inputValue;
|
||||||
};
|
};
|
||||||
|
const computeCursorIndex = (editor: CodeMirror.Editor) => {
|
||||||
|
const cursor = editor.getCursor();
|
||||||
|
let cursorIndex = cursor.ch;
|
||||||
|
if (cursor.line > 0) {
|
||||||
|
for (let lineIndex = 0; lineIndex < cursor.line; lineIndex++) {
|
||||||
|
const line = editor.getLine(lineIndex);
|
||||||
|
cursorIndex = cursorIndex + line.length + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cursorIndex;
|
||||||
|
};
|
||||||
|
export const checkIfCursorInsideBinding = (
|
||||||
|
editor: CodeMirror.Editor,
|
||||||
|
): boolean => {
|
||||||
|
let cursorBetweenBinding = false;
|
||||||
|
const value = editor.getValue();
|
||||||
|
const cursorIndex = computeCursorIndex(editor);
|
||||||
|
const stringSegments = getDynamicStringSegments(value);
|
||||||
|
// count of chars processed
|
||||||
|
let cumulativeCharCount = 0;
|
||||||
|
stringSegments.forEach((segment: string) => {
|
||||||
|
const start = cumulativeCharCount;
|
||||||
|
const dynamicStart = segment.indexOf("{{");
|
||||||
|
const dynamicDoesStart = dynamicStart > -1;
|
||||||
|
const dynamicEnd = segment.indexOf("}}");
|
||||||
|
const dynamicDoesEnd = dynamicEnd > -1;
|
||||||
|
const dynamicStartIndex = dynamicStart + start + 2;
|
||||||
|
const dynamicEndIndex = dynamicEnd + start;
|
||||||
|
if (
|
||||||
|
dynamicDoesStart &&
|
||||||
|
cursorIndex >= dynamicStartIndex &&
|
||||||
|
((dynamicDoesEnd && cursorIndex <= dynamicEndIndex) ||
|
||||||
|
(!dynamicDoesEnd && cursorIndex >= dynamicStartIndex))
|
||||||
|
) {
|
||||||
|
cursorBetweenBinding = true;
|
||||||
|
}
|
||||||
|
cumulativeCharCount = start + segment.length;
|
||||||
|
});
|
||||||
|
return cursorBetweenBinding;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
import CodeMirror from "codemirror";
|
import CodeMirror from "codemirror";
|
||||||
import { HintHelper } from "components/editorComponents/CodeEditor/EditorConfig";
|
import { HintHelper } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||||
import { CommandsCompletion } from "utils/autocomplete/TernServer";
|
import { CommandsCompletion } from "utils/autocomplete/TernServer";
|
||||||
import { checkIfCursorInsideBinding } from "./hintHelpers";
|
|
||||||
import { generateQuickCommands } from "./generateQuickCommands";
|
import { generateQuickCommands } from "./generateQuickCommands";
|
||||||
import { Datasource } from "entities/Datasource";
|
import { Datasource } from "entities/Datasource";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { ENTITY_TYPE } from "entities/AppsmithConsole";
|
import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||||
|
import { checkIfCursorInsideBinding } from "components/editorComponents/CodeEditor/codeEditorUtils";
|
||||||
|
|
||||||
export const commandsHelper: HintHelper = (editor, data: any) => {
|
export const commandsHelper: HintHelper = (editor, data: DataTree) => {
|
||||||
let entitiesForSuggestions = Object.values(data).filter(
|
let entitiesForSuggestions = Object.values(data).filter(
|
||||||
(entity: any) => entity.ENTITY_TYPE && entity.ENTITY_TYPE !== "APPSMITH",
|
(entity: any) =>
|
||||||
|
entity.ENTITY_TYPE && entity.ENTITY_TYPE !== ENTITY_TYPE.APPSMITH,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
showHint: (
|
showHint: (
|
||||||
editor: CodeMirror.Editor,
|
editor: CodeMirror.Editor,
|
||||||
_: string,
|
{ entityType },
|
||||||
entityName: string,
|
|
||||||
{
|
{
|
||||||
datasources,
|
datasources,
|
||||||
executeCommand,
|
executeCommand,
|
||||||
|
|
@ -31,11 +31,11 @@ export const commandsHelper: HintHelper = (editor, data: any) => {
|
||||||
update: (value: string) => void;
|
update: (value: string) => void;
|
||||||
},
|
},
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const currentEntityType = data[entityName]?.ENTITY_TYPE || "ACTION";
|
const currentEntityType = entityType || ENTITY_TYPE.ACTION;
|
||||||
entitiesForSuggestions = entitiesForSuggestions.filter((entity: any) => {
|
entitiesForSuggestions = entitiesForSuggestions.filter((entity: any) => {
|
||||||
return currentEntityType === "WIDGET"
|
return currentEntityType === ENTITY_TYPE.WIDGET
|
||||||
? entity.ENTITY_TYPE !== "WIDGET"
|
? entity.ENTITY_TYPE !== ENTITY_TYPE.WIDGET
|
||||||
: entity.ENTITY_TYPE !== "ACTION";
|
: entity.ENTITY_TYPE !== ENTITY_TYPE.ACTION;
|
||||||
});
|
});
|
||||||
const cursorBetweenBinding = checkIfCursorInsideBinding(editor);
|
const cursorBetweenBinding = checkIfCursorInsideBinding(editor);
|
||||||
const value = editor.getValue();
|
const value = editor.getValue();
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import CodeMirror from "codemirror";
|
import CodeMirror from "codemirror";
|
||||||
import TernServer from "utils/autocomplete/TernServer";
|
import TernServer from "utils/autocomplete/TernServer";
|
||||||
import KeyboardShortcuts from "constants/KeyboardShortcuts";
|
import KeyboardShortcuts from "constants/KeyboardShortcuts";
|
||||||
import { getDynamicStringSegments } from "utils/DynamicBindingUtils";
|
|
||||||
import { HintHelper } from "components/editorComponents/CodeEditor/EditorConfig";
|
import { HintHelper } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import { customTreeTypeDefCreator } from "../../../utils/autocomplete/customTreeTypeDefCreator";
|
import { customTreeTypeDefCreator } from "utils/autocomplete/customTreeTypeDefCreator";
|
||||||
|
import { checkIfCursorInsideBinding } from "components/editorComponents/CodeEditor/codeEditorUtils";
|
||||||
|
|
||||||
export const bindingHint: HintHelper = (editor, dataTree, additionalData) => {
|
export const bindingHint: HintHelper = (editor, dataTree, customDataTree) => {
|
||||||
if (additionalData) {
|
if (customDataTree) {
|
||||||
const customTreeDef = customTreeTypeDefCreator(additionalData);
|
const customTreeDef = customTreeTypeDefCreator(customDataTree);
|
||||||
TernServer.updateDef("customDataTree", customTreeDef);
|
TernServer.updateDef("customDataTree", customTreeDef);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -16,11 +16,8 @@ export const bindingHint: HintHelper = (editor, dataTree, additionalData) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: No types available
|
// @ts-ignore: No types available
|
||||||
...editor.options.extraKeys,
|
...editor.options.extraKeys,
|
||||||
[KeyboardShortcuts.CodeEditor.OpenAutocomplete]: (
|
[KeyboardShortcuts.CodeEditor.OpenAutocomplete]: (cm: CodeMirror.Editor) =>
|
||||||
cm: CodeMirror.Editor,
|
TernServer.complete(cm),
|
||||||
expected: string,
|
|
||||||
entity: string,
|
|
||||||
) => TernServer.complete(cm, expected, entity),
|
|
||||||
[KeyboardShortcuts.CodeEditor.ShowTypeAndInfo]: (cm: CodeMirror.Editor) => {
|
[KeyboardShortcuts.CodeEditor.ShowTypeAndInfo]: (cm: CodeMirror.Editor) => {
|
||||||
TernServer.showType(cm);
|
TernServer.showType(cm);
|
||||||
},
|
},
|
||||||
|
|
@ -29,15 +26,12 @@ export const bindingHint: HintHelper = (editor, dataTree, additionalData) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
showHint: (
|
showHint: (editor: CodeMirror.Editor, entityInformation): boolean => {
|
||||||
editor: CodeMirror.Editor,
|
TernServer.setEntityInformation(entityInformation);
|
||||||
expected: string,
|
|
||||||
entityName: string,
|
|
||||||
): boolean => {
|
|
||||||
const shouldShow = checkIfCursorInsideBinding(editor);
|
const shouldShow = checkIfCursorInsideBinding(editor);
|
||||||
if (shouldShow) {
|
if (shouldShow) {
|
||||||
AnalyticsUtil.logEvent("AUTO_COMPLETE_SHOW", {});
|
AnalyticsUtil.logEvent("AUTO_COMPLETE_SHOW", {});
|
||||||
TernServer.complete(editor, expected, entityName);
|
TernServer.complete(editor);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
|
@ -47,45 +41,3 @@ export const bindingHint: HintHelper = (editor, dataTree, additionalData) => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const computeCursorIndex = (editor: CodeMirror.Editor) => {
|
|
||||||
const cursor = editor.getCursor();
|
|
||||||
let cursorIndex = cursor.ch;
|
|
||||||
if (cursor.line > 0) {
|
|
||||||
for (let lineIndex = 0; lineIndex < cursor.line; lineIndex++) {
|
|
||||||
const line = editor.getLine(lineIndex);
|
|
||||||
cursorIndex = cursorIndex + line.length + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cursorIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const checkIfCursorInsideBinding = (
|
|
||||||
editor: CodeMirror.Editor,
|
|
||||||
): boolean => {
|
|
||||||
let cursorBetweenBinding = false;
|
|
||||||
const value = editor.getValue();
|
|
||||||
const cursorIndex = computeCursorIndex(editor);
|
|
||||||
const stringSegments = getDynamicStringSegments(value);
|
|
||||||
// count of chars processed
|
|
||||||
let cumulativeCharCount = 0;
|
|
||||||
stringSegments.forEach((segment: string) => {
|
|
||||||
const start = cumulativeCharCount;
|
|
||||||
const dynamicStart = segment.indexOf("{{");
|
|
||||||
const dynamicDoesStart = dynamicStart > -1;
|
|
||||||
const dynamicEnd = segment.indexOf("}}");
|
|
||||||
const dynamicDoesEnd = dynamicEnd > -1;
|
|
||||||
const dynamicStartIndex = dynamicStart + start + 2;
|
|
||||||
const dynamicEndIndex = dynamicEnd + start;
|
|
||||||
if (
|
|
||||||
dynamicDoesStart &&
|
|
||||||
cursorIndex >= dynamicStartIndex &&
|
|
||||||
((dynamicDoesEnd && cursorIndex <= dynamicEndIndex) ||
|
|
||||||
(!dynamicDoesEnd && cursorIndex >= dynamicStartIndex))
|
|
||||||
) {
|
|
||||||
cursorBetweenBinding = true;
|
|
||||||
}
|
|
||||||
cumulativeCharCount = start + segment.length;
|
|
||||||
});
|
|
||||||
return cursorBetweenBinding;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import { WrappedFieldInputProps } from "redux-form";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import {
|
import {
|
||||||
DataTree,
|
DataTree,
|
||||||
|
ENTITY_TYPE,
|
||||||
EvaluationSubstitutionType,
|
EvaluationSubstitutionType,
|
||||||
} from "entities/DataTree/dataTreeFactory";
|
} from "entities/DataTree/dataTreeFactory";
|
||||||
import { Skin } from "constants/DefaultTheme";
|
import { Skin } from "constants/DefaultTheme";
|
||||||
|
|
@ -30,6 +31,7 @@ import {
|
||||||
EditorSize,
|
EditorSize,
|
||||||
EditorTheme,
|
EditorTheme,
|
||||||
EditorThemes,
|
EditorThemes,
|
||||||
|
HintEntityInformation,
|
||||||
Hinter,
|
Hinter,
|
||||||
HintHelper,
|
HintHelper,
|
||||||
MarkHelper,
|
MarkHelper,
|
||||||
|
|
@ -55,7 +57,7 @@ import {
|
||||||
getEvalValuePath,
|
getEvalValuePath,
|
||||||
PropertyEvaluationErrorType,
|
PropertyEvaluationErrorType,
|
||||||
} from "utils/DynamicBindingUtils";
|
} from "utils/DynamicBindingUtils";
|
||||||
import { removeNewLineChars, getInputValue } from "./codeEditorUtils";
|
import { getInputValue, removeNewLineChars } from "./codeEditorUtils";
|
||||||
import { commandsHelper } from "./commandsHelper";
|
import { commandsHelper } from "./commandsHelper";
|
||||||
import { getEntityNameAndPropertyPath } from "workers/evaluationUtils";
|
import { getEntityNameAndPropertyPath } from "workers/evaluationUtils";
|
||||||
import Button from "components/ads/Button";
|
import Button from "components/ads/Button";
|
||||||
|
|
@ -373,13 +375,27 @@ class CodeEditor extends Component<Props, State> {
|
||||||
|
|
||||||
handleAutocompleteVisibility = (cm: CodeMirror.Editor) => {
|
handleAutocompleteVisibility = (cm: CodeMirror.Editor) => {
|
||||||
if (!this.state.isFocused) return;
|
if (!this.state.isFocused) return;
|
||||||
const expected = this.props.expected ? this.props.expected : "";
|
const { dataTreePath, dynamicData, expected } = this.props;
|
||||||
const { entityName } = getEntityNameAndPropertyPath(
|
const entityInformation: HintEntityInformation = {
|
||||||
this.props.dataTreePath || "",
|
expectedType: expected,
|
||||||
);
|
};
|
||||||
|
if (dataTreePath) {
|
||||||
|
const { entityName } = getEntityNameAndPropertyPath(dataTreePath);
|
||||||
|
entityInformation.entityName = entityName;
|
||||||
|
const entity = dynamicData[entityName];
|
||||||
|
if (entity && "ENTITY_TYPE" in entity) {
|
||||||
|
const entityType = entity.ENTITY_TYPE;
|
||||||
|
if (
|
||||||
|
entityType === ENTITY_TYPE.WIDGET ||
|
||||||
|
entityType === ENTITY_TYPE.ACTION
|
||||||
|
) {
|
||||||
|
entityInformation.entityType = entityType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let hinterOpen = false;
|
let hinterOpen = false;
|
||||||
for (let i = 0; i < this.hinters.length; i++) {
|
for (let i = 0; i < this.hinters.length; i++) {
|
||||||
hinterOpen = this.hinters[i].showHint(cm, expected, entityName, {
|
hinterOpen = this.hinters[i].showHint(cm, entityInformation, {
|
||||||
datasources: this.props.datasources.list,
|
datasources: this.props.datasources.list,
|
||||||
pluginIdToImageLocation: this.props.pluginIdToImageLocation,
|
pluginIdToImageLocation: this.props.pluginIdToImageLocation,
|
||||||
recentEntities: this.props.recentEntities,
|
recentEntities: this.props.recentEntities,
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
EditorTheme,
|
EditorTheme,
|
||||||
TabBehaviour,
|
TabBehaviour,
|
||||||
EditorSize,
|
EditorSize,
|
||||||
|
HintHelper,
|
||||||
} from "components/editorComponents/CodeEditor/EditorConfig";
|
} from "components/editorComponents/CodeEditor/EditorConfig";
|
||||||
import { bindingMarker } from "components/editorComponents/CodeEditor/markHelpers";
|
import { bindingMarker } from "components/editorComponents/CodeEditor/markHelpers";
|
||||||
import { bindingHint } from "components/editorComponents/CodeEditor/hintHelpers";
|
import { bindingHint } from "components/editorComponents/CodeEditor/hintHelpers";
|
||||||
|
|
@ -218,7 +219,7 @@ class EmbeddedDatasourcePathComponent extends React.Component<Props> {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
handleDatasourceHint = () => {
|
handleDatasourceHint = (): HintHelper => {
|
||||||
const { datasourceList } = this.props;
|
const { datasourceList } = this.props;
|
||||||
return () => {
|
return () => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -270,7 +271,7 @@ class EmbeddedDatasourcePathComponent extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showHint: () => {
|
showHint: () => {
|
||||||
return;
|
return false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,7 @@ function KeyValueRow(props: Props & WrappedFieldArrayProps) {
|
||||||
border={CodeEditorBorder.BOTTOM_SIDE}
|
border={CodeEditorBorder.BOTTOM_SIDE}
|
||||||
className={`t--${field}.key.${index}`}
|
className={`t--${field}.key.${index}`}
|
||||||
dataTreePath={`${props.dataTreePath}[${index}].key`}
|
dataTreePath={`${props.dataTreePath}[${index}].key`}
|
||||||
|
expected={FIELD_VALUES.API_ACTION.params}
|
||||||
hoverInteraction
|
hoverInteraction
|
||||||
name={`${field}.key`}
|
name={`${field}.key`}
|
||||||
placeholder={`Key ${index + 1}`}
|
placeholder={`Key ${index + 1}`}
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,14 @@ const FIELD_VALUES: Record<
|
||||||
isRequired: "boolean",
|
isRequired: "boolean",
|
||||||
isVisible: "boolean",
|
isVisible: "boolean",
|
||||||
isDisabled: "boolean",
|
isDisabled: "boolean",
|
||||||
// onDateSelected: "Function Call",
|
onDateSelected: "Function Call",
|
||||||
},
|
},
|
||||||
DATE_PICKER_WIDGET2: {
|
DATE_PICKER_WIDGET2: {
|
||||||
defaultDate: "string | null", //TODO:Vicky validate this property
|
defaultDate: "string", //TODO:Vicky validate this property
|
||||||
isRequired: "boolean",
|
isRequired: "boolean",
|
||||||
isVisible: "boolean",
|
isVisible: "boolean",
|
||||||
isDisabled: "boolean",
|
isDisabled: "boolean",
|
||||||
// onDateSelected: "Function Call",
|
onDateSelected: "Function Call",
|
||||||
},
|
},
|
||||||
TABLE_WIDGET: {
|
TABLE_WIDGET: {
|
||||||
tableData: "Array<Object>",
|
tableData: "Array<Object>",
|
||||||
|
|
@ -40,8 +40,8 @@ const FIELD_VALUES: Record<
|
||||||
exportExcel: "boolean",
|
exportExcel: "boolean",
|
||||||
exportCsv: "boolean",
|
exportCsv: "boolean",
|
||||||
defaultSelectedRow: "string",
|
defaultSelectedRow: "string",
|
||||||
// onRowSelected: "Function Call",
|
onRowSelected: "Function Call",
|
||||||
// onPageChange: "Function Call",
|
onPageChange: "Function Call",
|
||||||
},
|
},
|
||||||
VIDEO_WIDGET: {
|
VIDEO_WIDGET: {
|
||||||
url: "string",
|
url: "string",
|
||||||
|
|
@ -59,7 +59,7 @@ const FIELD_VALUES: Record<
|
||||||
defaultOptionValue: "string",
|
defaultOptionValue: "string",
|
||||||
isRequired: "boolean",
|
isRequired: "boolean",
|
||||||
isVisible: "boolean",
|
isVisible: "boolean",
|
||||||
// onSelectionChange: "Function Call",
|
onSelectionChange: "Function Call",
|
||||||
},
|
},
|
||||||
TABS_WIDGET: {
|
TABS_WIDGET: {
|
||||||
selectedTab: "string",
|
selectedTab: "string",
|
||||||
|
|
@ -86,7 +86,7 @@ const FIELD_VALUES: Record<
|
||||||
isRequired: "boolean",
|
isRequired: "boolean",
|
||||||
isVisible: "boolean",
|
isVisible: "boolean",
|
||||||
isDisabled: "boolean",
|
isDisabled: "boolean",
|
||||||
// onTextChanged: "Function Call",
|
onTextChanged: "Function Call",
|
||||||
},
|
},
|
||||||
DROP_DOWN_WIDGET: {
|
DROP_DOWN_WIDGET: {
|
||||||
label: "string",
|
label: "string",
|
||||||
|
|
@ -95,7 +95,7 @@ const FIELD_VALUES: Record<
|
||||||
defaultOptionValue: "string",
|
defaultOptionValue: "string",
|
||||||
isRequired: "boolean",
|
isRequired: "boolean",
|
||||||
isVisible: "boolean",
|
isVisible: "boolean",
|
||||||
// onOptionChange: "Function Call",
|
onOptionChange: "Function Call",
|
||||||
},
|
},
|
||||||
FORM_BUTTON_WIDGET: {
|
FORM_BUTTON_WIDGET: {
|
||||||
text: "string",
|
text: "string",
|
||||||
|
|
@ -103,7 +103,7 @@ const FIELD_VALUES: Record<
|
||||||
disabledWhenInvalid: "boolean",
|
disabledWhenInvalid: "boolean",
|
||||||
resetFormOnClick: "boolean",
|
resetFormOnClick: "boolean",
|
||||||
isVisible: "boolean",
|
isVisible: "boolean",
|
||||||
// onClick: "Function Call",
|
onClick: "Function Call",
|
||||||
},
|
},
|
||||||
MAP_WIDGET: {
|
MAP_WIDGET: {
|
||||||
mapCenter: "{ lat: number, long: number }",
|
mapCenter: "{ lat: number, long: number }",
|
||||||
|
|
@ -112,30 +112,29 @@ const FIELD_VALUES: Record<
|
||||||
enablePickLocation: "boolean",
|
enablePickLocation: "boolean",
|
||||||
enableCreateMarker: "boolean",
|
enableCreateMarker: "boolean",
|
||||||
isVisible: "boolean",
|
isVisible: "boolean",
|
||||||
// onMarkerClick: "Function Call",
|
onMarkerClick: "Function Call",
|
||||||
// onCreateMarker: "Function Call",
|
onCreateMarker: "Function Call",
|
||||||
},
|
},
|
||||||
BUTTON_WIDGET: {
|
BUTTON_WIDGET: {
|
||||||
text: "string",
|
text: "string",
|
||||||
buttonStyle: "PRIMARY_BUTTON | SECONDARY_BUTTON | DANGER_BUTTON",
|
buttonStyle: "PRIMARY_BUTTON | SECONDARY_BUTTON | DANGER_BUTTON",
|
||||||
isVisible: "boolean",
|
isVisible: "boolean",
|
||||||
// onClick: "Function Call",
|
onClick: "Function Call",
|
||||||
},
|
},
|
||||||
RICH_TEXT_EDITOR_WIDGET: {
|
RICH_TEXT_EDITOR_WIDGET: {
|
||||||
defaultText: "string",
|
defaultText: "string",
|
||||||
isVisible: "boolean",
|
isVisible: "boolean",
|
||||||
isDisabled: "boolean",
|
isDisabled: "boolean",
|
||||||
// onTextChange: "Function Call",
|
onTextChange: "Function Call",
|
||||||
},
|
},
|
||||||
FILE_PICKER_WIDGET: {
|
FILE_PICKER_WIDGET: {
|
||||||
label: "string",
|
label: "string",
|
||||||
maxNumFiles: "number",
|
maxNumFiles: "number",
|
||||||
maxFileSize: "number",
|
maxFileSize: "number",
|
||||||
|
|
||||||
allowedFileTypes: "Array<string>",
|
allowedFileTypes: "Array<string>",
|
||||||
isRequired: "boolean",
|
isRequired: "boolean",
|
||||||
isVisible: "boolean",
|
isVisible: "boolean",
|
||||||
// onFilesSelected: "Function Call",
|
onFilesSelected: "Function Call",
|
||||||
},
|
},
|
||||||
CHECKBOX_WIDGET: {
|
CHECKBOX_WIDGET: {
|
||||||
label: "string",
|
label: "string",
|
||||||
|
|
@ -143,7 +142,7 @@ const FIELD_VALUES: Record<
|
||||||
isRequired: "boolean",
|
isRequired: "boolean",
|
||||||
isDisabled: "boolean",
|
isDisabled: "boolean",
|
||||||
isVisible: "boolean",
|
isVisible: "boolean",
|
||||||
// onCheckChange: "Function Call",
|
onCheckChange: "Function Call",
|
||||||
},
|
},
|
||||||
SWITCH_WIDGET: {
|
SWITCH_WIDGET: {
|
||||||
label: "string",
|
label: "string",
|
||||||
|
|
@ -151,7 +150,7 @@ const FIELD_VALUES: Record<
|
||||||
isDisabled: "boolean",
|
isDisabled: "boolean",
|
||||||
isVisible: "boolean",
|
isVisible: "boolean",
|
||||||
alignWidget: "LEFT | RIGHT",
|
alignWidget: "LEFT | RIGHT",
|
||||||
// onChange: "Function Call",
|
onChange: "Function Call",
|
||||||
},
|
},
|
||||||
FORM_WIDGET: {
|
FORM_WIDGET: {
|
||||||
backgroundColor: "string",
|
backgroundColor: "string",
|
||||||
|
|
|
||||||
1360
app/client/src/constants/defs/ecmascript.json
Normal file
1360
app/client/src/constants/defs/ecmascript.json
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"!name": "../../node-forge/lib/aes.js",
|
"!name": "LIB/node-forge",
|
||||||
"!define": {
|
"!define": {
|
||||||
"forge.aes.Algorithm.prototype.initialize.!0": {
|
"forge.aes.Algorithm.prototype.initialize.!0": {
|
||||||
"key": {
|
"key": {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"!name": "lodash",
|
"!name": "LIB/lodash",
|
||||||
"_": {
|
"_": {
|
||||||
"chunk": {
|
"chunk": {
|
||||||
"!url": "https://lodash.com/docs/4.17.15#chunk",
|
"!url": "https://lodash.com/docs/4.17.15#chunk",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"!name": "moment",
|
"!name": "LIB/moment",
|
||||||
"moment": {
|
"moment": {
|
||||||
"!type": "fn(inp?: MomentInput, format?: MomentFormatSpecification, strict?: boolean) -> Moment",
|
"!type": "fn(inp?: MomentInput, format?: MomentFormatSpecification, strict?: boolean) -> Moment",
|
||||||
"!url": "https://momentjs.com/docs/#/parsing/",
|
"!url": "https://momentjs.com/docs/#/parsing/",
|
||||||
|
|
@ -469,7 +469,7 @@
|
||||||
"!doc": "A global locale configuration can be problematic when passing around moments that may need to be formatted into different locale"
|
"!doc": "A global locale configuration can be problematic when passing around moments that may need to be formatted into different locale"
|
||||||
},
|
},
|
||||||
"localeData": {
|
"localeData": {
|
||||||
"!type": "fn() -> Locale"
|
"!type": "fn() -> Locale"
|
||||||
},
|
},
|
||||||
"toObject": {
|
"toObject": {
|
||||||
"!type": "fn() -> MomentObjectOutput",
|
"!type": "fn() -> MomentObjectOutput",
|
||||||
|
|
@ -727,4 +727,4 @@
|
||||||
"!doc": "Returns am/pm string for particular time-of-day in upper/lower case"
|
"!doc": "Returns am/pm string for particular time-of-day in upper/lower case"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"!name": "xmlParser",
|
"!name": "LIB/xmlParser",
|
||||||
"xmlParser": {
|
"xmlParser": {
|
||||||
"parse": {
|
"parse": {
|
||||||
"!doc": "converts xml string to json object",
|
"!doc": "converts xml string to json object",
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,11 @@ export const CodemirrorHintStyles = createGlobalStyle<{
|
||||||
letter-spacing: -0.24px;
|
letter-spacing: -0.24px;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: ${(props) =>
|
background: ${(props) =>
|
||||||
props.editorTheme === EditorTheme.LIGHT ? "#6A86CE" : "#157A96"};
|
props.editorTheme === EditorTheme.LIGHT ? "#E8E8E8" : "#157A96"};
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
color: #fff;
|
color: #090707;
|
||||||
&:after {
|
&:after {
|
||||||
color: #fff;
|
color: #090707;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -139,7 +139,11 @@ export const CodemirrorHintStyles = createGlobalStyle<{
|
||||||
padding-left: ${(props) => props.theme.spaces[11]}px !important;
|
padding-left: ${(props) => props.theme.spaces[11]}px !important;
|
||||||
&:hover{
|
&:hover{
|
||||||
background: ${(props) =>
|
background: ${(props) =>
|
||||||
props.editorTheme === EditorTheme.LIGHT ? "#6A86CE" : "#157A96"};
|
props.editorTheme === EditorTheme.LIGHT ? "#E8E8E8" : "#157A96"};
|
||||||
|
color: #090707;
|
||||||
|
&:after {
|
||||||
|
color: #090707;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.CodeMirror-Tern-completion:before {
|
.CodeMirror-Tern-completion:before {
|
||||||
|
|
@ -214,6 +218,13 @@ export const CodemirrorHintStyles = createGlobalStyle<{
|
||||||
&:after {
|
&:after {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
&:hover {
|
||||||
|
background: #6A86CE;
|
||||||
|
color: #fff;
|
||||||
|
&:after {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.CodeMirror-Tern-hint-doc {
|
.CodeMirror-Tern-hint-doc {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,6 @@ const PropertyControl = memo((props: Props) => {
|
||||||
config.validationMessage = "";
|
config.validationMessage = "";
|
||||||
delete config.dataTreePath;
|
delete config.dataTreePath;
|
||||||
delete config.evaluatedValue;
|
delete config.evaluatedValue;
|
||||||
delete config.expected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDynamic: boolean = isPathADynamicProperty(
|
const isDynamic: boolean = isPathADynamicProperty(
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import {
|
||||||
import {
|
import {
|
||||||
EvaluationReduxAction,
|
EvaluationReduxAction,
|
||||||
ReduxAction,
|
ReduxAction,
|
||||||
ReduxActionErrorTypes,
|
|
||||||
ReduxActionTypes,
|
ReduxActionTypes,
|
||||||
ReduxActionWithoutPayload,
|
ReduxActionWithoutPayload,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
|
|
@ -21,16 +20,7 @@ import {
|
||||||
import WidgetFactory, { WidgetTypeConfigMap } from "../utils/WidgetFactory";
|
import WidgetFactory, { WidgetTypeConfigMap } from "../utils/WidgetFactory";
|
||||||
import { GracefulWorkerService } from "utils/WorkerUtil";
|
import { GracefulWorkerService } from "utils/WorkerUtil";
|
||||||
import Worker from "worker-loader!../workers/evaluation.worker";
|
import Worker from "worker-loader!../workers/evaluation.worker";
|
||||||
import {
|
import { EVAL_WORKER_ACTIONS } from "utils/DynamicBindingUtils";
|
||||||
EVAL_WORKER_ACTIONS,
|
|
||||||
EvalError,
|
|
||||||
EvalErrorTypes,
|
|
||||||
EvaluationError,
|
|
||||||
getEvalErrorPath,
|
|
||||||
getEvalValuePath,
|
|
||||||
PropertyEvalErrorTypeDebugMessage,
|
|
||||||
PropertyEvaluationErrorType,
|
|
||||||
} from "utils/DynamicBindingUtils";
|
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { WidgetProps } from "widgets/BaseWidget";
|
import { WidgetProps } from "widgets/BaseWidget";
|
||||||
import PerformanceTracker, {
|
import PerformanceTracker, {
|
||||||
|
|
@ -38,319 +28,24 @@ import PerformanceTracker, {
|
||||||
} from "../utils/PerformanceTracker";
|
} from "../utils/PerformanceTracker";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import { Action } from "redux";
|
import { Action } from "redux";
|
||||||
import _ from "lodash";
|
|
||||||
import { ENTITY_TYPE, Message } from "entities/AppsmithConsole";
|
|
||||||
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
|
||||||
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
|
||||||
import { AppState } from "reducers";
|
|
||||||
import {
|
import {
|
||||||
getEntityNameAndPropertyPath,
|
EVALUATE_REDUX_ACTIONS,
|
||||||
isAction,
|
FIRST_EVAL_REDUX_ACTIONS,
|
||||||
isWidget,
|
setDependencyMap,
|
||||||
} from "workers/evaluationUtils";
|
setEvaluatedTree,
|
||||||
import moment from "moment/moment";
|
shouldProcessBatchedAction,
|
||||||
import { Toaster } from "components/ads/Toast";
|
} from "actions/evaluationActions";
|
||||||
import { Variant } from "components/ads/common";
|
|
||||||
import AppsmithConsole from "utils/AppsmithConsole";
|
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
|
||||||
import {
|
import {
|
||||||
createMessage,
|
evalErrorHandler,
|
||||||
ERROR_EVAL_ERROR_GENERIC,
|
logSuccessfulBindings,
|
||||||
ERROR_EVAL_TRIGGER,
|
postEvalActionDispatcher,
|
||||||
} from "constants/messages";
|
updateTernDefinitions,
|
||||||
import { getAppMode } from "selectors/applicationSelectors";
|
} from "./PostEvaluationSagas";
|
||||||
import { APP_MODE } from "reducers/entityReducers/appReducer";
|
|
||||||
import { dataTreeTypeDefCreator } from "utils/autocomplete/dataTreeTypeDefCreator";
|
|
||||||
import TernServer from "utils/autocomplete/TernServer";
|
|
||||||
import store from "store";
|
|
||||||
import { logDebuggerErrorAnalytics } from "actions/debuggerActions";
|
|
||||||
|
|
||||||
let widgetTypeConfigMap: WidgetTypeConfigMap;
|
let widgetTypeConfigMap: WidgetTypeConfigMap;
|
||||||
|
|
||||||
const worker = new GracefulWorkerService(Worker);
|
const worker = new GracefulWorkerService(Worker);
|
||||||
|
|
||||||
const getDebuggerErrors = (state: AppState) => state.ui.debugger.errors;
|
|
||||||
|
|
||||||
function getLatestEvalPropertyErrors(
|
|
||||||
currentDebuggerErrors: Record<string, Message>,
|
|
||||||
dataTree: DataTree,
|
|
||||||
evaluationOrder: Array<string>,
|
|
||||||
) {
|
|
||||||
const updatedDebuggerErrors: Record<string, Message> = {
|
|
||||||
...currentDebuggerErrors,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const evaluatedPath of evaluationOrder) {
|
|
||||||
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
|
|
||||||
evaluatedPath,
|
|
||||||
);
|
|
||||||
const entity = dataTree[entityName];
|
|
||||||
if (isWidget(entity) || isAction(entity)) {
|
|
||||||
if (propertyPath in entity.logBlackList) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const allEvalErrors: EvaluationError[] = _.get(
|
|
||||||
entity,
|
|
||||||
getEvalErrorPath(evaluatedPath, false),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
const evaluatedValue = _.get(
|
|
||||||
entity,
|
|
||||||
getEvalValuePath(evaluatedPath, false),
|
|
||||||
);
|
|
||||||
const evalErrors = allEvalErrors.filter(
|
|
||||||
(error) => error.errorType !== PropertyEvaluationErrorType.LINT,
|
|
||||||
);
|
|
||||||
const idField = isWidget(entity) ? entity.widgetId : entity.actionId;
|
|
||||||
const nameField = isWidget(entity) ? entity.widgetName : entity.name;
|
|
||||||
const entityType = isWidget(entity)
|
|
||||||
? ENTITY_TYPE.WIDGET
|
|
||||||
: ENTITY_TYPE.ACTION;
|
|
||||||
const debuggerKey = idField + "-" + propertyPath;
|
|
||||||
// if dataTree has error but debugger does not -> add
|
|
||||||
// if debugger has error and data tree has error -> update error
|
|
||||||
// if debugger has error but data tree does not -> remove
|
|
||||||
// if debugger or data tree does not have an error -> no change
|
|
||||||
|
|
||||||
if (evalErrors.length) {
|
|
||||||
// TODO Rank and set the most critical error
|
|
||||||
const error = evalErrors[0];
|
|
||||||
const errorMessages = evalErrors.map((e) => ({
|
|
||||||
message: e.errorMessage,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!(debuggerKey in updatedDebuggerErrors)) {
|
|
||||||
store.dispatch(
|
|
||||||
logDebuggerErrorAnalytics({
|
|
||||||
eventName: "DEBUGGER_NEW_ERROR",
|
|
||||||
entityId: idField,
|
|
||||||
entityName: nameField,
|
|
||||||
entityType,
|
|
||||||
propertyPath,
|
|
||||||
errorMessages,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const analyticsData = isWidget(entity)
|
|
||||||
? {
|
|
||||||
widgetType: entity.type,
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
|
|
||||||
// Add or update
|
|
||||||
updatedDebuggerErrors[debuggerKey] = {
|
|
||||||
logType: LOG_TYPE.EVAL_ERROR,
|
|
||||||
text: PropertyEvalErrorTypeDebugMessage[error.errorType](
|
|
||||||
propertyPath,
|
|
||||||
),
|
|
||||||
messages: errorMessages,
|
|
||||||
severity: error.severity,
|
|
||||||
timestamp: moment().format("hh:mm:ss"),
|
|
||||||
source: {
|
|
||||||
id: idField,
|
|
||||||
name: nameField,
|
|
||||||
type: entityType,
|
|
||||||
propertyPath: propertyPath,
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
[propertyPath]: evaluatedValue,
|
|
||||||
},
|
|
||||||
analytics: analyticsData,
|
|
||||||
};
|
|
||||||
} else if (debuggerKey in updatedDebuggerErrors) {
|
|
||||||
store.dispatch(
|
|
||||||
logDebuggerErrorAnalytics({
|
|
||||||
eventName: "DEBUGGER_RESOLVED_ERROR",
|
|
||||||
entityId: idField,
|
|
||||||
entityName: nameField,
|
|
||||||
entityType,
|
|
||||||
propertyPath:
|
|
||||||
updatedDebuggerErrors[debuggerKey].source?.propertyPath ?? "",
|
|
||||||
errorMessages: updatedDebuggerErrors[debuggerKey].messages ?? [],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
// Remove
|
|
||||||
delete updatedDebuggerErrors[debuggerKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return updatedDebuggerErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
function* evalErrorHandler(
|
|
||||||
errors: EvalError[],
|
|
||||||
dataTree?: DataTree,
|
|
||||||
evaluationOrder?: Array<string>,
|
|
||||||
): any {
|
|
||||||
if (dataTree && evaluationOrder) {
|
|
||||||
const currentDebuggerErrors: Record<string, Message> = yield select(
|
|
||||||
getDebuggerErrors,
|
|
||||||
);
|
|
||||||
const evalPropertyErrors = getLatestEvalPropertyErrors(
|
|
||||||
currentDebuggerErrors,
|
|
||||||
dataTree,
|
|
||||||
evaluationOrder,
|
|
||||||
);
|
|
||||||
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.DEBUGGER_UPDATE_ERROR_LOGS,
|
|
||||||
payload: evalPropertyErrors,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
errors.forEach((error) => {
|
|
||||||
switch (error.type) {
|
|
||||||
case EvalErrorTypes.CYCLICAL_DEPENDENCY_ERROR: {
|
|
||||||
if (error.context) {
|
|
||||||
// Add more info about node for the toast
|
|
||||||
const { entityType, node } = error.context;
|
|
||||||
Toaster.show({
|
|
||||||
text: `${error.message} Node was: ${node}`,
|
|
||||||
variant: Variant.danger,
|
|
||||||
});
|
|
||||||
AppsmithConsole.error({
|
|
||||||
text: `${error.message} Node was: ${node}`,
|
|
||||||
});
|
|
||||||
// Send the generic error message to sentry for better grouping
|
|
||||||
Sentry.captureException(new Error(error.message), {
|
|
||||||
tags: {
|
|
||||||
node,
|
|
||||||
entityType,
|
|
||||||
},
|
|
||||||
// Level is warning because it could be a user error
|
|
||||||
level: Sentry.Severity.Warning,
|
|
||||||
});
|
|
||||||
// Log an analytics event for cyclical dep errors
|
|
||||||
AnalyticsUtil.logEvent("CYCLICAL_DEPENDENCY_ERROR", {
|
|
||||||
node,
|
|
||||||
entityType,
|
|
||||||
// Level is warning because it could be a user error
|
|
||||||
level: Sentry.Severity.Warning,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EvalErrorTypes.EVAL_TREE_ERROR: {
|
|
||||||
Toaster.show({
|
|
||||||
text: createMessage(ERROR_EVAL_ERROR_GENERIC),
|
|
||||||
variant: Variant.danger,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EvalErrorTypes.BAD_UNEVAL_TREE_ERROR: {
|
|
||||||
Sentry.captureException(error);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EvalErrorTypes.EVAL_TRIGGER_ERROR: {
|
|
||||||
log.debug(error);
|
|
||||||
Toaster.show({
|
|
||||||
text: createMessage(ERROR_EVAL_TRIGGER, error.message),
|
|
||||||
variant: Variant.danger,
|
|
||||||
showDebugButton: true,
|
|
||||||
});
|
|
||||||
AppsmithConsole.error({
|
|
||||||
text: createMessage(ERROR_EVAL_TRIGGER, error.message),
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EvalErrorTypes.EVAL_PROPERTY_ERROR: {
|
|
||||||
log.debug(error);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
Sentry.captureException(error);
|
|
||||||
log.debug(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function* logSuccessfulBindings(
|
|
||||||
unEvalTree: DataTree,
|
|
||||||
dataTree: DataTree,
|
|
||||||
evaluationOrder: string[],
|
|
||||||
) {
|
|
||||||
const appMode = yield select(getAppMode);
|
|
||||||
if (appMode === APP_MODE.PUBLISHED) return;
|
|
||||||
if (!evaluationOrder) return;
|
|
||||||
evaluationOrder.forEach((evaluatedPath) => {
|
|
||||||
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
|
|
||||||
evaluatedPath,
|
|
||||||
);
|
|
||||||
const entity = dataTree[entityName];
|
|
||||||
if (isAction(entity) || isWidget(entity)) {
|
|
||||||
const unevalValue = _.get(unEvalTree, evaluatedPath);
|
|
||||||
const entityType = isAction(entity) ? entity.pluginType : entity.type;
|
|
||||||
const isABinding = _.find(entity.dynamicBindingPathList, {
|
|
||||||
key: propertyPath,
|
|
||||||
});
|
|
||||||
const logBlackList = entity.logBlackList;
|
|
||||||
const errors: EvaluationError[] = _.get(
|
|
||||||
dataTree,
|
|
||||||
getEvalErrorPath(evaluatedPath),
|
|
||||||
[],
|
|
||||||
) as EvaluationError[];
|
|
||||||
const criticalErrors = errors.filter(
|
|
||||||
(error) => error.errorType !== PropertyEvaluationErrorType.LINT,
|
|
||||||
);
|
|
||||||
const hasErrors = criticalErrors.length > 0;
|
|
||||||
|
|
||||||
if (isABinding && !hasErrors && !(propertyPath in logBlackList)) {
|
|
||||||
AnalyticsUtil.logEvent("BINDING_SUCCESS", {
|
|
||||||
unevalValue,
|
|
||||||
entityType,
|
|
||||||
propertyPath,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update only the changed entities on tern. We will pick up the updated
|
|
||||||
// entities from the evaluation order and create a new def from them.
|
|
||||||
// When there is a top level entity removed in removedPaths,
|
|
||||||
// we will remove its def
|
|
||||||
function* updateTernDefinitions(
|
|
||||||
dataTree: DataTree,
|
|
||||||
evaluationOrder: string[],
|
|
||||||
isFirstEvaluation: boolean,
|
|
||||||
) {
|
|
||||||
const updatedEntities: Set<string> = new Set();
|
|
||||||
// If it is the first evaluation, we want to add everything in the data tree
|
|
||||||
if (isFirstEvaluation) {
|
|
||||||
Object.keys(dataTree).forEach((key) => updatedEntities.add(key));
|
|
||||||
} else {
|
|
||||||
evaluationOrder.forEach((path) => {
|
|
||||||
const { entityName } = getEntityNameAndPropertyPath(path);
|
|
||||||
updatedEntities.add(entityName);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedEntities.forEach((entityName) => {
|
|
||||||
const entity = dataTree[entityName];
|
|
||||||
if (entity) {
|
|
||||||
const { def, name } = dataTreeTypeDefCreator(entity, entityName);
|
|
||||||
TernServer.updateDef(name, def);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// removedPaths.forEach((path) => {
|
|
||||||
// // No '.' means that the path is an entity name
|
|
||||||
// if (path.split(".").length === 1) {
|
|
||||||
// TernServer.removeDef(path);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
function* postEvalActionDispatcher(
|
|
||||||
actions: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
|
||||||
) {
|
|
||||||
for (const action of actions) {
|
|
||||||
yield put(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* evaluateTreeSaga(
|
function* evaluateTreeSaga(
|
||||||
postEvalActions?: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
postEvalActions?: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
||||||
isFirstEvaluation = false,
|
isFirstEvaluation = false,
|
||||||
|
|
@ -382,10 +77,7 @@ function* evaluateTreeSaga(
|
||||||
PerformanceTracker.startAsyncTracking(
|
PerformanceTracker.startAsyncTracking(
|
||||||
PerformanceTransactionName.SET_EVALUATED_TREE,
|
PerformanceTransactionName.SET_EVALUATED_TREE,
|
||||||
);
|
);
|
||||||
yield put({
|
yield put(setEvaluatedTree(dataTree, updates));
|
||||||
type: ReduxActionTypes.SET_EVALUATED_TREE,
|
|
||||||
payload: { dataTree, updates },
|
|
||||||
});
|
|
||||||
PerformanceTracker.stopAsyncTracking(
|
PerformanceTracker.stopAsyncTracking(
|
||||||
PerformanceTransactionName.SET_EVALUATED_TREE,
|
PerformanceTransactionName.SET_EVALUATED_TREE,
|
||||||
);
|
);
|
||||||
|
|
@ -408,10 +100,7 @@ function* evaluateTreeSaga(
|
||||||
isFirstEvaluation,
|
isFirstEvaluation,
|
||||||
);
|
);
|
||||||
|
|
||||||
yield put({
|
yield put(setDependencyMap(dependencies));
|
||||||
type: ReduxActionTypes.SET_EVALUATION_INVERSE_DEPENDENCY_MAP,
|
|
||||||
payload: { inverseDependencyMap: dependencies },
|
|
||||||
});
|
|
||||||
if (postEvalActions && postEvalActions.length) {
|
if (postEvalActions && postEvalActions.length) {
|
||||||
yield call(postEvalActionDispatcher, postEvalActions);
|
yield call(postEvalActionDispatcher, postEvalActions);
|
||||||
}
|
}
|
||||||
|
|
@ -495,62 +184,6 @@ export function* validateProperty(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const FIRST_EVAL_REDUX_ACTIONS = [
|
|
||||||
// Pages
|
|
||||||
ReduxActionTypes.FETCH_PAGE_SUCCESS,
|
|
||||||
ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS,
|
|
||||||
];
|
|
||||||
|
|
||||||
const EVALUATE_REDUX_ACTIONS = [
|
|
||||||
...FIRST_EVAL_REDUX_ACTIONS,
|
|
||||||
// Actions
|
|
||||||
ReduxActionTypes.FETCH_ACTIONS_SUCCESS,
|
|
||||||
ReduxActionTypes.FETCH_PLUGIN_FORM_CONFIGS_SUCCESS,
|
|
||||||
ReduxActionTypes.FETCH_ACTIONS_VIEW_MODE_SUCCESS,
|
|
||||||
ReduxActionErrorTypes.FETCH_ACTIONS_ERROR,
|
|
||||||
ReduxActionErrorTypes.FETCH_ACTIONS_VIEW_MODE_ERROR,
|
|
||||||
ReduxActionTypes.FETCH_ACTIONS_FOR_PAGE_SUCCESS,
|
|
||||||
ReduxActionTypes.SUBMIT_CURL_FORM_SUCCESS,
|
|
||||||
ReduxActionTypes.CREATE_ACTION_SUCCESS,
|
|
||||||
ReduxActionTypes.UPDATE_ACTION_PROPERTY,
|
|
||||||
ReduxActionTypes.DELETE_ACTION_SUCCESS,
|
|
||||||
ReduxActionTypes.COPY_ACTION_SUCCESS,
|
|
||||||
ReduxActionTypes.MOVE_ACTION_SUCCESS,
|
|
||||||
ReduxActionTypes.RUN_ACTION_SUCCESS,
|
|
||||||
ReduxActionErrorTypes.RUN_ACTION_ERROR,
|
|
||||||
ReduxActionTypes.EXECUTE_API_ACTION_SUCCESS,
|
|
||||||
ReduxActionErrorTypes.EXECUTE_ACTION_ERROR,
|
|
||||||
// App Data
|
|
||||||
ReduxActionTypes.SET_APP_MODE,
|
|
||||||
ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS,
|
|
||||||
ReduxActionTypes.UPDATE_APP_PERSISTENT_STORE,
|
|
||||||
ReduxActionTypes.UPDATE_APP_TRANSIENT_STORE,
|
|
||||||
// Widgets
|
|
||||||
ReduxActionTypes.UPDATE_LAYOUT,
|
|
||||||
ReduxActionTypes.UPDATE_WIDGET_PROPERTY,
|
|
||||||
ReduxActionTypes.UPDATE_WIDGET_NAME_SUCCESS,
|
|
||||||
// Widget Meta
|
|
||||||
ReduxActionTypes.SET_META_PROP,
|
|
||||||
ReduxActionTypes.RESET_WIDGET_META,
|
|
||||||
// Batches
|
|
||||||
ReduxActionTypes.BATCH_UPDATES_SUCCESS,
|
|
||||||
];
|
|
||||||
|
|
||||||
const shouldProcessAction = (action: ReduxAction<unknown>) => {
|
|
||||||
if (
|
|
||||||
action.type === ReduxActionTypes.BATCH_UPDATES_SUCCESS &&
|
|
||||||
Array.isArray(action.payload)
|
|
||||||
) {
|
|
||||||
const batchedActionTypes = action.payload.map(
|
|
||||||
(batchedAction) => batchedAction.type,
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
_.intersection(EVALUATE_REDUX_ACTIONS, batchedActionTypes).length > 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
function evalQueueBuffer() {
|
function evalQueueBuffer() {
|
||||||
let canTake = false;
|
let canTake = false;
|
||||||
let postEvalActions: any = [];
|
let postEvalActions: any = [];
|
||||||
|
|
@ -571,7 +204,7 @@ function evalQueueBuffer() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const put = (action: EvaluationReduxAction<unknown | unknown[]>) => {
|
const put = (action: EvaluationReduxAction<unknown | unknown[]>) => {
|
||||||
if (!shouldProcessAction(action)) {
|
if (!shouldProcessBatchedAction(action)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
canTake = true;
|
canTake = true;
|
||||||
|
|
@ -607,11 +240,14 @@ function* evaluationChangeListenerSaga() {
|
||||||
const action: EvaluationReduxAction<unknown | unknown[]> = yield take(
|
const action: EvaluationReduxAction<unknown | unknown[]> = yield take(
|
||||||
evtActionChannel,
|
evtActionChannel,
|
||||||
);
|
);
|
||||||
if (shouldProcessAction(action)) {
|
if (FIRST_EVAL_REDUX_ACTIONS.includes(action.type)) {
|
||||||
yield call(evaluateTreeSaga, action.postEvalActions);
|
yield call(evaluateTreeSaga, initAction.postEvalActions, true);
|
||||||
|
} else {
|
||||||
|
if (shouldProcessBatchedAction(action)) {
|
||||||
|
yield call(evaluateTreeSaga, action.postEvalActions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO(hetu) need an action to stop listening and evaluate (exit app)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function* evaluationSagaListeners() {
|
export default function* evaluationSagaListeners() {
|
||||||
|
|
|
||||||
326
app/client/src/sagas/PostEvaluationSagas.ts
Normal file
326
app/client/src/sagas/PostEvaluationSagas.ts
Normal file
|
|
@ -0,0 +1,326 @@
|
||||||
|
import { ENTITY_TYPE, Message } from "entities/AppsmithConsole";
|
||||||
|
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||||
|
import {
|
||||||
|
getEntityNameAndPropertyPath,
|
||||||
|
isAction,
|
||||||
|
isWidget,
|
||||||
|
} from "workers/evaluationUtils";
|
||||||
|
import {
|
||||||
|
EvalError,
|
||||||
|
EvalErrorTypes,
|
||||||
|
EvaluationError,
|
||||||
|
getEvalErrorPath,
|
||||||
|
getEvalValuePath,
|
||||||
|
PropertyEvalErrorTypeDebugMessage,
|
||||||
|
PropertyEvaluationErrorType,
|
||||||
|
} from "utils/DynamicBindingUtils";
|
||||||
|
import _ from "lodash";
|
||||||
|
import LOG_TYPE from "../entities/AppsmithConsole/logtype";
|
||||||
|
import moment from "moment/moment";
|
||||||
|
import { put, select } from "redux-saga/effects";
|
||||||
|
import {
|
||||||
|
ReduxAction,
|
||||||
|
ReduxActionTypes,
|
||||||
|
ReduxActionWithoutPayload,
|
||||||
|
} from "constants/ReduxActionConstants";
|
||||||
|
import { Toaster } from "components/ads/Toast";
|
||||||
|
import { Variant } from "components/ads/common";
|
||||||
|
import AppsmithConsole from "../utils/AppsmithConsole";
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
|
import AnalyticsUtil from "../utils/AnalyticsUtil";
|
||||||
|
import {
|
||||||
|
createMessage,
|
||||||
|
ERROR_EVAL_ERROR_GENERIC,
|
||||||
|
ERROR_EVAL_TRIGGER,
|
||||||
|
} from "constants/messages";
|
||||||
|
import log from "loglevel";
|
||||||
|
import { AppState } from "reducers";
|
||||||
|
import { getAppMode } from "selectors/applicationSelectors";
|
||||||
|
import { APP_MODE } from "reducers/entityReducers/appReducer";
|
||||||
|
import { dataTreeTypeDefCreator } from "utils/autocomplete/dataTreeTypeDefCreator";
|
||||||
|
import TernServer from "utils/autocomplete/TernServer";
|
||||||
|
import { logDebuggerErrorAnalytics } from "actions/debuggerActions";
|
||||||
|
import store from "../store";
|
||||||
|
|
||||||
|
const getDebuggerErrors = (state: AppState) => state.ui.debugger.errors;
|
||||||
|
|
||||||
|
function getLatestEvalPropertyErrors(
|
||||||
|
currentDebuggerErrors: Record<string, Message>,
|
||||||
|
dataTree: DataTree,
|
||||||
|
evaluationOrder: Array<string>,
|
||||||
|
) {
|
||||||
|
const updatedDebuggerErrors: Record<string, Message> = {
|
||||||
|
...currentDebuggerErrors,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const evaluatedPath of evaluationOrder) {
|
||||||
|
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
|
||||||
|
evaluatedPath,
|
||||||
|
);
|
||||||
|
const entity = dataTree[entityName];
|
||||||
|
if (isWidget(entity) || isAction(entity)) {
|
||||||
|
if (propertyPath in entity.logBlackList) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const allEvalErrors: EvaluationError[] = _.get(
|
||||||
|
entity,
|
||||||
|
getEvalErrorPath(evaluatedPath, false),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const evaluatedValue = _.get(
|
||||||
|
entity,
|
||||||
|
getEvalValuePath(evaluatedPath, false),
|
||||||
|
);
|
||||||
|
const evalErrors = allEvalErrors.filter(
|
||||||
|
(error) => error.errorType !== PropertyEvaluationErrorType.LINT,
|
||||||
|
);
|
||||||
|
const idField = isWidget(entity) ? entity.widgetId : entity.actionId;
|
||||||
|
const nameField = isWidget(entity) ? entity.widgetName : entity.name;
|
||||||
|
const entityType = isWidget(entity)
|
||||||
|
? ENTITY_TYPE.WIDGET
|
||||||
|
: ENTITY_TYPE.ACTION;
|
||||||
|
const debuggerKey = idField + "-" + propertyPath;
|
||||||
|
// if dataTree has error but debugger does not -> add
|
||||||
|
// if debugger has error and data tree has error -> update error
|
||||||
|
// if debugger has error but data tree does not -> remove
|
||||||
|
// if debugger or data tree does not have an error -> no change
|
||||||
|
|
||||||
|
if (evalErrors.length) {
|
||||||
|
// TODO Rank and set the most critical error
|
||||||
|
const error = evalErrors[0];
|
||||||
|
const errorMessages = evalErrors.map((e) => ({
|
||||||
|
message: e.errorMessage,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!(debuggerKey in updatedDebuggerErrors)) {
|
||||||
|
store.dispatch(
|
||||||
|
logDebuggerErrorAnalytics({
|
||||||
|
eventName: "DEBUGGER_NEW_ERROR",
|
||||||
|
entityId: idField,
|
||||||
|
entityName: nameField,
|
||||||
|
entityType,
|
||||||
|
propertyPath,
|
||||||
|
errorMessages,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const analyticsData = isWidget(entity)
|
||||||
|
? {
|
||||||
|
widgetType: entity.type,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
// Add or update
|
||||||
|
updatedDebuggerErrors[debuggerKey] = {
|
||||||
|
logType: LOG_TYPE.EVAL_ERROR,
|
||||||
|
text: PropertyEvalErrorTypeDebugMessage[error.errorType](
|
||||||
|
propertyPath,
|
||||||
|
),
|
||||||
|
messages: errorMessages,
|
||||||
|
severity: error.severity,
|
||||||
|
timestamp: moment().format("hh:mm:ss"),
|
||||||
|
source: {
|
||||||
|
id: idField,
|
||||||
|
name: nameField,
|
||||||
|
type: entityType,
|
||||||
|
propertyPath: propertyPath,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
[propertyPath]: evaluatedValue,
|
||||||
|
},
|
||||||
|
analytics: analyticsData,
|
||||||
|
};
|
||||||
|
} else if (debuggerKey in updatedDebuggerErrors) {
|
||||||
|
store.dispatch(
|
||||||
|
logDebuggerErrorAnalytics({
|
||||||
|
eventName: "DEBUGGER_RESOLVED_ERROR",
|
||||||
|
entityId: idField,
|
||||||
|
entityName: nameField,
|
||||||
|
entityType,
|
||||||
|
propertyPath:
|
||||||
|
updatedDebuggerErrors[debuggerKey].source?.propertyPath ?? "",
|
||||||
|
errorMessages: updatedDebuggerErrors[debuggerKey].messages ?? [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// Remove
|
||||||
|
delete updatedDebuggerErrors[debuggerKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updatedDebuggerErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* evalErrorHandler(
|
||||||
|
errors: EvalError[],
|
||||||
|
dataTree?: DataTree,
|
||||||
|
evaluationOrder?: Array<string>,
|
||||||
|
): any {
|
||||||
|
if (dataTree && evaluationOrder) {
|
||||||
|
const currentDebuggerErrors: Record<string, Message> = yield select(
|
||||||
|
getDebuggerErrors,
|
||||||
|
);
|
||||||
|
const evalPropertyErrors = getLatestEvalPropertyErrors(
|
||||||
|
currentDebuggerErrors,
|
||||||
|
dataTree,
|
||||||
|
evaluationOrder,
|
||||||
|
);
|
||||||
|
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionTypes.DEBUGGER_UPDATE_ERROR_LOGS,
|
||||||
|
payload: evalPropertyErrors,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.forEach((error) => {
|
||||||
|
switch (error.type) {
|
||||||
|
case EvalErrorTypes.CYCLICAL_DEPENDENCY_ERROR: {
|
||||||
|
if (error.context) {
|
||||||
|
// Add more info about node for the toast
|
||||||
|
const { entityType, node } = error.context;
|
||||||
|
Toaster.show({
|
||||||
|
text: `${error.message} Node was: ${node}`,
|
||||||
|
variant: Variant.danger,
|
||||||
|
});
|
||||||
|
AppsmithConsole.error({
|
||||||
|
text: `${error.message} Node was: ${node}`,
|
||||||
|
});
|
||||||
|
// Send the generic error message to sentry for better grouping
|
||||||
|
Sentry.captureException(new Error(error.message), {
|
||||||
|
tags: {
|
||||||
|
node,
|
||||||
|
entityType,
|
||||||
|
},
|
||||||
|
// Level is warning because it could be a user error
|
||||||
|
level: Sentry.Severity.Warning,
|
||||||
|
});
|
||||||
|
// Log an analytics event for cyclical dep errors
|
||||||
|
AnalyticsUtil.logEvent("CYCLICAL_DEPENDENCY_ERROR", {
|
||||||
|
node,
|
||||||
|
entityType,
|
||||||
|
// Level is warning because it could be a user error
|
||||||
|
level: Sentry.Severity.Warning,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EvalErrorTypes.EVAL_TREE_ERROR: {
|
||||||
|
Toaster.show({
|
||||||
|
text: createMessage(ERROR_EVAL_ERROR_GENERIC),
|
||||||
|
variant: Variant.danger,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EvalErrorTypes.BAD_UNEVAL_TREE_ERROR: {
|
||||||
|
Sentry.captureException(error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EvalErrorTypes.EVAL_TRIGGER_ERROR: {
|
||||||
|
log.debug(error);
|
||||||
|
Toaster.show({
|
||||||
|
text: createMessage(ERROR_EVAL_TRIGGER, error.message),
|
||||||
|
variant: Variant.danger,
|
||||||
|
showDebugButton: true,
|
||||||
|
});
|
||||||
|
AppsmithConsole.error({
|
||||||
|
text: createMessage(ERROR_EVAL_TRIGGER, error.message),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EvalErrorTypes.EVAL_PROPERTY_ERROR: {
|
||||||
|
log.debug(error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
Sentry.captureException(error);
|
||||||
|
log.debug(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* logSuccessfulBindings(
|
||||||
|
unEvalTree: DataTree,
|
||||||
|
dataTree: DataTree,
|
||||||
|
evaluationOrder: string[],
|
||||||
|
) {
|
||||||
|
const appMode = yield select(getAppMode);
|
||||||
|
if (appMode === APP_MODE.PUBLISHED) return;
|
||||||
|
if (!evaluationOrder) return;
|
||||||
|
evaluationOrder.forEach((evaluatedPath) => {
|
||||||
|
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
|
||||||
|
evaluatedPath,
|
||||||
|
);
|
||||||
|
const entity = dataTree[entityName];
|
||||||
|
if (isAction(entity) || isWidget(entity)) {
|
||||||
|
const unevalValue = _.get(unEvalTree, evaluatedPath);
|
||||||
|
const entityType = isAction(entity) ? entity.pluginType : entity.type;
|
||||||
|
const isABinding = _.find(entity.dynamicBindingPathList, {
|
||||||
|
key: propertyPath,
|
||||||
|
});
|
||||||
|
const logBlackList = entity.logBlackList;
|
||||||
|
const errors: EvaluationError[] = _.get(
|
||||||
|
dataTree,
|
||||||
|
getEvalErrorPath(evaluatedPath),
|
||||||
|
[],
|
||||||
|
) as EvaluationError[];
|
||||||
|
const criticalErrors = errors.filter(
|
||||||
|
(error) => error.errorType !== PropertyEvaluationErrorType.LINT,
|
||||||
|
);
|
||||||
|
const hasErrors = criticalErrors.length > 0;
|
||||||
|
|
||||||
|
if (isABinding && !hasErrors && !(propertyPath in logBlackList)) {
|
||||||
|
AnalyticsUtil.logEvent("BINDING_SUCCESS", {
|
||||||
|
unevalValue,
|
||||||
|
entityType,
|
||||||
|
propertyPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* postEvalActionDispatcher(
|
||||||
|
actions: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
||||||
|
) {
|
||||||
|
for (const action of actions) {
|
||||||
|
yield put(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update only the changed entities on tern. We will pick up the updated
|
||||||
|
// entities from the evaluation order and create a new def from them.
|
||||||
|
// When there is a top level entity removed in removedPaths,
|
||||||
|
// we will remove its def
|
||||||
|
export function* updateTernDefinitions(
|
||||||
|
dataTree: DataTree,
|
||||||
|
evaluationOrder: string[],
|
||||||
|
isFirstEvaluation: boolean,
|
||||||
|
) {
|
||||||
|
const updatedEntities: Set<string> = new Set();
|
||||||
|
// If it is the first evaluation, we want to add everything in the data tree
|
||||||
|
if (isFirstEvaluation) {
|
||||||
|
TernServer.resetServer();
|
||||||
|
Object.keys(dataTree).forEach((key) => updatedEntities.add(key));
|
||||||
|
} else {
|
||||||
|
evaluationOrder.forEach((path) => {
|
||||||
|
const { entityName } = getEntityNameAndPropertyPath(path);
|
||||||
|
updatedEntities.add(entityName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedEntities.forEach((entityName) => {
|
||||||
|
const entity = dataTree[entityName];
|
||||||
|
if (entity) {
|
||||||
|
const { def, name } = dataTreeTypeDefCreator(entity, entityName);
|
||||||
|
TernServer.updateDef(name, def);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// removedPaths.forEach((path) => {
|
||||||
|
// // No '.' means that the path is an entity name
|
||||||
|
// if (path.split(".").length === 1) {
|
||||||
|
// TernServer.removeDef(path);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,6 @@ import { generateTypeDef } from "utils/autocomplete/dataTreeTypeDefCreator";
|
||||||
import { DataTreeAction } from "entities/DataTree/dataTreeFactory";
|
import { DataTreeAction } from "entities/DataTree/dataTreeFactory";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
// const isLoading = {
|
|
||||||
// "!type": "bool",
|
|
||||||
// "!doc": "Boolean value indicating if the entity is in loading state",
|
|
||||||
// };
|
|
||||||
const isVisible = {
|
const isVisible = {
|
||||||
"!type": "bool",
|
"!type": "bool",
|
||||||
"!doc": "Boolean value indicating if the widget is in visible state",
|
"!doc": "Boolean value indicating if the widget is in visible state",
|
||||||
|
|
@ -14,7 +10,6 @@ const isVisible = {
|
||||||
export const entityDefinitions = {
|
export const entityDefinitions = {
|
||||||
ACTION: (entity: DataTreeAction) => {
|
ACTION: (entity: DataTreeAction) => {
|
||||||
const dataDef = generateTypeDef(entity.data);
|
const dataDef = generateTypeDef(entity.data);
|
||||||
const responseMetaDef = generateTypeDef(entity.responseMeta);
|
|
||||||
let data: Record<string, any> = {
|
let data: Record<string, any> = {
|
||||||
"!doc": "The response of the action",
|
"!doc": "The response of the action",
|
||||||
};
|
};
|
||||||
|
|
@ -23,21 +18,16 @@ export const entityDefinitions = {
|
||||||
} else {
|
} else {
|
||||||
data = { ...data, ...dataDef };
|
data = { ...data, ...dataDef };
|
||||||
}
|
}
|
||||||
let responseMeta: Record<string, any> = {
|
|
||||||
"!doc": "The response meta of the action",
|
|
||||||
};
|
|
||||||
if (_.isString(responseMetaDef)) {
|
|
||||||
responseMeta["!type"] = responseMetaDef;
|
|
||||||
} else {
|
|
||||||
responseMeta = { ...responseMeta, ...responseMetaDef };
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
"!doc":
|
"!doc":
|
||||||
"Actions allow you to connect your widgets to your backend data in a secure manner.",
|
"Actions allow you to connect your widgets to your backend data in a secure manner.",
|
||||||
"!url": "https://docs.appsmith.com/v/v1.2.1/framework-reference/run",
|
"!url": "https://docs.appsmith.com/v/v1.2.1/framework-reference/run",
|
||||||
isLoading: "bool",
|
isLoading: "bool",
|
||||||
data,
|
data,
|
||||||
responseMeta,
|
responseMeta: {
|
||||||
|
"!doc": "The response meta of the action",
|
||||||
|
"!type": "?",
|
||||||
|
},
|
||||||
run: "fn(onSuccess: fn() -> void, onError: fn() -> void) -> void",
|
run: "fn(onSuccess: fn() -> void, onError: fn() -> void) -> void",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -137,8 +127,6 @@ export const entityDefinitions = {
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
text: "string",
|
text: "string",
|
||||||
isDisabled: "bool",
|
isDisabled: "bool",
|
||||||
recaptchaToken: "string",
|
|
||||||
googleRecaptchaKey: "string",
|
|
||||||
},
|
},
|
||||||
DATE_PICKER_WIDGET: {
|
DATE_PICKER_WIDGET: {
|
||||||
"!doc":
|
"!doc":
|
||||||
|
|
@ -219,8 +207,6 @@ export const entityDefinitions = {
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
text: "string",
|
text: "string",
|
||||||
isDisabled: "bool",
|
isDisabled: "bool",
|
||||||
recaptchaToken: "string",
|
|
||||||
googleRecaptchaKey: "string",
|
|
||||||
},
|
},
|
||||||
MAP_WIDGET: {
|
MAP_WIDGET: {
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
|
|
@ -319,6 +305,7 @@ export const GLOBAL_DEFS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GLOBAL_FUNCTIONS = {
|
export const GLOBAL_FUNCTIONS = {
|
||||||
|
"!name": "DATA_TREE.APPSMITH.FUNCTIONS",
|
||||||
navigateTo: {
|
navigateTo: {
|
||||||
"!doc": "Action to navigate the user to another page or url",
|
"!doc": "Action to navigate the user to another page or url",
|
||||||
"!type": "fn(pageNameOrUrl: string, params: {}, target?: string) -> void",
|
"!type": "fn(pageNameOrUrl: string, params: {}, target?: string) -> void",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import TernServer from "./TernServer";
|
import TernServer, { Completion, createCompletionHeader } from "./TernServer";
|
||||||
import { MockCodemirrorEditor } from "../../../test/__mocks__/CodeMirrorEditorMock";
|
import { MockCodemirrorEditor } from "../../../test/__mocks__/CodeMirrorEditorMock";
|
||||||
|
import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
describe("Tern server", () => {
|
describe("Tern server", () => {
|
||||||
it("Check whether the correct value is being sent to tern", () => {
|
it("Check whether the correct value is being sent to tern", () => {
|
||||||
|
|
@ -157,3 +159,133 @@ describe("Tern server", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Tern server sorting", () => {
|
||||||
|
const contextCompletion: Completion = {
|
||||||
|
text: "context",
|
||||||
|
type: "STRING",
|
||||||
|
origin: "[doc]",
|
||||||
|
data: {
|
||||||
|
doc: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const sameEntityCompletion: Completion = {
|
||||||
|
text: "sameEntity.tableData",
|
||||||
|
type: "ARRAY",
|
||||||
|
origin: "DATA_TREE.WIDGET.TABLE_WIDGET.sameEntity",
|
||||||
|
data: {
|
||||||
|
doc: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const sameTypeCompletion: Completion = {
|
||||||
|
text: "sameType.selectedRow",
|
||||||
|
type: "OBJECT",
|
||||||
|
origin: "DATA_TREE.WIDGET.TABLE_WIDGET.sameType",
|
||||||
|
data: {
|
||||||
|
doc: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const diffTypeCompletion: Completion = {
|
||||||
|
text: "diffType.tableData",
|
||||||
|
type: "ARRAY",
|
||||||
|
origin: "DATA_TREE.WIDGET.TABLE_WIDGET.diffType",
|
||||||
|
data: {
|
||||||
|
doc: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const sameTypeDiffEntityTypeCompletion: Completion = {
|
||||||
|
text: "diffEntity.data",
|
||||||
|
type: "OBJECT",
|
||||||
|
origin: "DATA_TREE.ACTION.ACTION.diffEntity",
|
||||||
|
data: {
|
||||||
|
doc: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataTreeCompletion: Completion = {
|
||||||
|
text: "otherDataTree",
|
||||||
|
type: "STRING",
|
||||||
|
origin: "DATA_TREE.WIDGET.TEXT_WIDGET.otherDataTree",
|
||||||
|
data: {
|
||||||
|
doc: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const functionCompletion: Completion = {
|
||||||
|
text: "otherDataFunction",
|
||||||
|
type: "FUNCTION",
|
||||||
|
origin: "DATA_TREE.APPSMITH.FUNCTIONS",
|
||||||
|
data: {
|
||||||
|
doc: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const ecmascriptCompletion: Completion = {
|
||||||
|
text: "otherJS",
|
||||||
|
type: "OBJECT",
|
||||||
|
origin: "ecmascript",
|
||||||
|
data: {
|
||||||
|
doc: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const libCompletion: Completion = {
|
||||||
|
text: "libValue",
|
||||||
|
type: "OBJECT",
|
||||||
|
origin: "LIB/lodash",
|
||||||
|
data: {
|
||||||
|
doc: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const unknownCompletion: Completion = {
|
||||||
|
text: "unknownSuggestion",
|
||||||
|
type: "UNKNOWN",
|
||||||
|
origin: "unknown",
|
||||||
|
data: {
|
||||||
|
doc: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const completions = [
|
||||||
|
sameEntityCompletion,
|
||||||
|
sameTypeCompletion,
|
||||||
|
contextCompletion,
|
||||||
|
libCompletion,
|
||||||
|
unknownCompletion,
|
||||||
|
diffTypeCompletion,
|
||||||
|
sameTypeDiffEntityTypeCompletion,
|
||||||
|
ecmascriptCompletion,
|
||||||
|
functionCompletion,
|
||||||
|
dataTreeCompletion,
|
||||||
|
];
|
||||||
|
|
||||||
|
it("shows best match results", () => {
|
||||||
|
TernServer.setEntityInformation({
|
||||||
|
entityName: "sameEntity",
|
||||||
|
entityType: ENTITY_TYPE.WIDGET,
|
||||||
|
expectedType: "object",
|
||||||
|
});
|
||||||
|
const sortedCompletions = TernServer.sortCompletions(
|
||||||
|
_.shuffle(completions),
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
expect(sortedCompletions[0]).toStrictEqual(contextCompletion);
|
||||||
|
expect(sortedCompletions).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
createCompletionHeader("Best Match"),
|
||||||
|
sameTypeDiffEntityTypeCompletion,
|
||||||
|
createCompletionHeader("Search Results"),
|
||||||
|
dataTreeCompletion,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(sortedCompletions).toEqual(
|
||||||
|
expect.not.arrayContaining([diffTypeCompletion]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
// Heavily inspired from https://github.com/codemirror/CodeMirror/blob/master/addon/tern/tern.js
|
// Heavily inspired from https://github.com/codemirror/CodeMirror/blob/master/addon/tern/tern.js
|
||||||
import tern, { Server, Def } from "tern";
|
import tern, { Server, Def } from "tern";
|
||||||
import ecma from "tern/defs/ecmascript.json";
|
import ecma from "constants/defs/ecmascript.json";
|
||||||
import lodash from "constants/defs/lodash.json";
|
import lodash from "constants/defs/lodash.json";
|
||||||
import base64 from "constants/defs/base64-js.json";
|
import base64 from "constants/defs/base64-js.json";
|
||||||
import moment from "constants/defs/moment.json";
|
import moment from "constants/defs/moment.json";
|
||||||
|
|
@ -9,6 +9,7 @@ import xmlJs from "constants/defs/xmlParser.json";
|
||||||
import forge from "constants/defs/forge.json";
|
import forge from "constants/defs/forge.json";
|
||||||
import CodeMirror, { Hint, Pos, cmpPos } from "codemirror";
|
import CodeMirror, { Hint, Pos, cmpPos } from "codemirror";
|
||||||
import {
|
import {
|
||||||
|
getDynamicBindings,
|
||||||
getDynamicStringSegments,
|
getDynamicStringSegments,
|
||||||
isDynamicValue,
|
isDynamicValue,
|
||||||
} from "utils/DynamicBindingUtils";
|
} from "utils/DynamicBindingUtils";
|
||||||
|
|
@ -16,6 +17,10 @@ import {
|
||||||
GLOBAL_DEFS,
|
GLOBAL_DEFS,
|
||||||
GLOBAL_FUNCTIONS,
|
GLOBAL_FUNCTIONS,
|
||||||
} from "utils/autocomplete/EntityDefinitions";
|
} from "utils/autocomplete/EntityDefinitions";
|
||||||
|
import { HintEntityInformation } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||||
|
import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||||
|
import SortRules from "./dataTypeSortRules";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
const DEFS: Def[] = [
|
const DEFS: Def[] = [
|
||||||
GLOBAL_FUNCTIONS,
|
GLOBAL_FUNCTIONS,
|
||||||
|
|
@ -77,8 +82,7 @@ class TernServer {
|
||||||
docs: TernDocs = Object.create(null);
|
docs: TernDocs = Object.create(null);
|
||||||
cachedArgHints: ArgHints | null = null;
|
cachedArgHints: ArgHints | null = null;
|
||||||
active: any;
|
active: any;
|
||||||
expected?: string;
|
entityInformation: HintEntityInformation = {};
|
||||||
entityName?: string;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.server = new tern.Server({
|
this.server = new tern.Server({
|
||||||
|
|
@ -87,9 +91,15 @@ class TernServer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
complete(cm: CodeMirror.Editor, expected: string, entityName: string) {
|
resetServer() {
|
||||||
this.expected = expected;
|
this.server = new tern.Server({
|
||||||
this.entityName = entityName;
|
async: true,
|
||||||
|
defs: DEFS,
|
||||||
|
});
|
||||||
|
this.docs = Object.create(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
complete(cm: CodeMirror.Editor) {
|
||||||
cm.showHint({
|
cm.showHint({
|
||||||
hint: this.getHint.bind(this),
|
hint: this.getHint.bind(this),
|
||||||
completeSingle: false,
|
completeSingle: false,
|
||||||
|
|
@ -161,25 +171,36 @@ class TernServer {
|
||||||
) {
|
) {
|
||||||
after = '"]';
|
after = '"]';
|
||||||
}
|
}
|
||||||
|
const bindings = getDynamicBindings(cm.getValue());
|
||||||
|
const onlySingleBinding = bindings.stringSegments.length === 1;
|
||||||
|
const searchText = bindings.jsSnippets[0].trim();
|
||||||
for (let i = 0; i < data.completions.length; ++i) {
|
for (let i = 0; i < data.completions.length; ++i) {
|
||||||
const completion = data.completions[i];
|
const completion = data.completions[i];
|
||||||
let className = this.typeToIcon(completion.type);
|
let className = this.typeToIcon(completion.type);
|
||||||
const dataType = this.getDataType(completion.type);
|
const dataType = this.getDataType(completion.type);
|
||||||
const entityName = this.entityName;
|
|
||||||
if (data.guess) className += " " + cls + "guess";
|
if (data.guess) className += " " + cls + "guess";
|
||||||
if (!entityName || !completion.name.includes(entityName)) {
|
let completionText = completion.name + after;
|
||||||
completions.push({
|
if (dataType === "FUNCTION") {
|
||||||
text: completion.name + after,
|
completionText = completionText + "()";
|
||||||
displayText: completion.displayName || completion.name,
|
|
||||||
className: className,
|
|
||||||
data: completion,
|
|
||||||
origin: completion.origin,
|
|
||||||
type: dataType,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
completions.push({
|
||||||
|
text: completionText,
|
||||||
|
displayText: completionText,
|
||||||
|
className: className,
|
||||||
|
data: completion,
|
||||||
|
origin: completion.origin,
|
||||||
|
type: dataType,
|
||||||
|
isHeader: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
completions = this.sortCompletions(completions);
|
|
||||||
const indexToBeSelected = completions.length > 1 ? 1 : 0;
|
completions = this.sortCompletions(
|
||||||
|
completions,
|
||||||
|
onlySingleBinding,
|
||||||
|
searchText,
|
||||||
|
);
|
||||||
|
const indexToBeSelected =
|
||||||
|
completions.length && completions[0].isHeader ? 1 : 0;
|
||||||
const obj = {
|
const obj = {
|
||||||
from: from,
|
from: from,
|
||||||
to: to,
|
to: to,
|
||||||
|
|
@ -242,56 +263,135 @@ class TernServer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sortCompletions(completions: Completion[]) {
|
sortCompletions(
|
||||||
// Add data tree completions before others
|
completions: Completion[],
|
||||||
|
findBestMatch: boolean,
|
||||||
|
bestMatchSearch: string,
|
||||||
|
) {
|
||||||
const expectedDataType = this.getExpectedDataType();
|
const expectedDataType = this.getExpectedDataType();
|
||||||
const dataTreeCompletions = completions
|
const { entityName, entityType } = this.entityInformation;
|
||||||
.filter((c) => c.origin && c.origin.startsWith("DATA_TREE_"))
|
type CompletionType =
|
||||||
.sort((a: Completion, b: Completion) => {
|
| "DATA_TREE"
|
||||||
|
| "MATCHING_TYPE"
|
||||||
|
| "OTHER"
|
||||||
|
| "CONTEXT"
|
||||||
|
| "JS"
|
||||||
|
| "LIBRARY";
|
||||||
|
const completionType: Record<CompletionType, Completion[]> = {
|
||||||
|
MATCHING_TYPE: [],
|
||||||
|
DATA_TREE: [],
|
||||||
|
CONTEXT: [],
|
||||||
|
JS: [],
|
||||||
|
LIBRARY: [],
|
||||||
|
OTHER: [],
|
||||||
|
};
|
||||||
|
completions.forEach((completion) => {
|
||||||
|
if (entityName && completion.text.includes(entityName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (completion.origin) {
|
||||||
|
if (completion.origin && completion.origin.startsWith("DATA_TREE")) {
|
||||||
|
if (completion.text.includes(".")) {
|
||||||
|
// nested paths (with ".") should only be used for best match
|
||||||
|
if (completion.type === expectedDataType) {
|
||||||
|
completionType.MATCHING_TYPE.push(completion);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
completion.origin === "DATA_TREE.APPSMITH.FUNCTIONS" &&
|
||||||
|
completion.type === expectedDataType
|
||||||
|
) {
|
||||||
|
// Global functions should be in best match as well as DataTree
|
||||||
|
completionType.MATCHING_TYPE.push(completion);
|
||||||
|
completionType.DATA_TREE.push(completion);
|
||||||
|
} else {
|
||||||
|
// All top level entities are set in data tree
|
||||||
|
completionType.DATA_TREE.push(completion);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
completion.origin === "[doc]" ||
|
||||||
|
completion.origin === "customDataTree"
|
||||||
|
) {
|
||||||
|
// [doc] are variables defined in the current context
|
||||||
|
// customDataTree are implicit context defined by platform
|
||||||
|
completionType.CONTEXT.push(completion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
completion.origin === "ecmascript" ||
|
||||||
|
completion.origin === "base64-js"
|
||||||
|
) {
|
||||||
|
completionType.JS.push(completion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (completion.origin.startsWith("LIB/")) {
|
||||||
|
completionType.LIBRARY.push(completion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generally keywords or other unCategorised completions
|
||||||
|
completionType.OTHER.push(completion);
|
||||||
|
});
|
||||||
|
completionType.DATA_TREE = completionType.DATA_TREE.sort(
|
||||||
|
(a: Completion, b: Completion) => {
|
||||||
if (a.type === "FUNCTION" && b.type !== "FUNCTION") {
|
if (a.type === "FUNCTION" && b.type !== "FUNCTION") {
|
||||||
return 1;
|
return 1;
|
||||||
} else if (a.type !== "FUNCTION" && b.type === "FUNCTION") {
|
} else if (a.type !== "FUNCTION" && b.type === "FUNCTION") {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return a.text.toLowerCase().localeCompare(b.text.toLowerCase());
|
return a.text.toLowerCase().localeCompare(b.text.toLowerCase());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
completionType.MATCHING_TYPE = completionType.MATCHING_TYPE.filter((c) =>
|
||||||
|
c.text.toLowerCase().startsWith(bestMatchSearch.toLowerCase()),
|
||||||
|
);
|
||||||
|
if (findBestMatch && completionType.MATCHING_TYPE.length) {
|
||||||
|
const sortedMatches: Completion[] = [];
|
||||||
|
const groupedMatches = _.groupBy(completionType.MATCHING_TYPE, (c) => {
|
||||||
|
const [, , subType, name] = c.origin.split(".");
|
||||||
|
return c.text.replace(name, subType);
|
||||||
});
|
});
|
||||||
const sameDataType = dataTreeCompletions.filter(
|
SortRules[expectedDataType].forEach((rule) => {
|
||||||
(c) => c.type === expectedDataType,
|
if (Array.isArray(groupedMatches[rule])) {
|
||||||
);
|
sortedMatches.push(...groupedMatches[rule]);
|
||||||
const otherDataType = dataTreeCompletions.filter(
|
}
|
||||||
(c) => c.type !== expectedDataType,
|
});
|
||||||
);
|
|
||||||
if (otherDataType.length && sameDataType.length) {
|
sortedMatches.sort((a, b) => {
|
||||||
const otherDataTitle: Completion = {
|
let aRank = 0;
|
||||||
text: "Search results",
|
let bRank = 0;
|
||||||
displayText: "Search results",
|
const entityTypeA: ENTITY_TYPE = a.origin.split(".")[1] as ENTITY_TYPE;
|
||||||
className: "CodeMirror-hint-header",
|
const entityTypeB: ENTITY_TYPE = b.origin.split(".")[1] as ENTITY_TYPE;
|
||||||
data: { doc: "" },
|
if (entityTypeA === entityType) {
|
||||||
origin: "",
|
aRank = aRank + 1;
|
||||||
type: "UNKNOWN",
|
}
|
||||||
isHeader: true,
|
if (entityTypeB === entityType) {
|
||||||
};
|
bRank = bRank + 1;
|
||||||
const sameDataTitle: Completion = {
|
}
|
||||||
text: "Best Match",
|
return aRank - bRank;
|
||||||
displayText: "Best Match",
|
});
|
||||||
className: "CodeMirror-hint-header",
|
completionType.MATCHING_TYPE = _.take(sortedMatches, 3);
|
||||||
data: { doc: "" },
|
if (completionType.MATCHING_TYPE.length) {
|
||||||
origin: "",
|
completionType.MATCHING_TYPE.unshift(
|
||||||
type: "UNKNOWN",
|
createCompletionHeader("Best Match"),
|
||||||
isHeader: true,
|
);
|
||||||
};
|
completionType.DATA_TREE.unshift(
|
||||||
sameDataType.unshift(sameDataTitle);
|
createCompletionHeader("Search Results"),
|
||||||
otherDataType.unshift(otherDataTitle);
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Clear any matching type because we dont want to find best match
|
||||||
|
completionType.MATCHING_TYPE = [];
|
||||||
}
|
}
|
||||||
const docCompletetions = completions.filter((c) => c.origin === "[doc]");
|
|
||||||
const otherCompletions = completions.filter(
|
|
||||||
(c) => c.origin !== "dataTree" && c.origin !== "[doc]",
|
|
||||||
);
|
|
||||||
return [
|
return [
|
||||||
...docCompletetions,
|
...completionType.CONTEXT,
|
||||||
...sameDataType,
|
...completionType.MATCHING_TYPE,
|
||||||
...otherDataType,
|
...completionType.DATA_TREE,
|
||||||
...otherCompletions,
|
...completionType.LIBRARY,
|
||||||
|
...completionType.JS,
|
||||||
|
...completionType.OTHER,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -306,21 +406,23 @@ class TernServer {
|
||||||
else return "OBJECT";
|
else return "OBJECT";
|
||||||
}
|
}
|
||||||
|
|
||||||
getExpectedDataType() {
|
getExpectedDataType(): DataType {
|
||||||
const type = this.expected;
|
const type = this.entityInformation.expectedType;
|
||||||
|
if (type === undefined) return "UNKNOWN";
|
||||||
if (
|
if (
|
||||||
type === "Array<Object>" ||
|
type === "Array<Object>" ||
|
||||||
type === "Array" ||
|
type === "Array" ||
|
||||||
type === "Array<{ label: string, value: string }>" ||
|
type === "Array<{ label: string, value: string }>" ||
|
||||||
type === "Array<x:string, y:number>"
|
type === "Array<x:string, y:number>"
|
||||||
)
|
) {
|
||||||
return "ARRAY";
|
return "ARRAY";
|
||||||
|
}
|
||||||
if (type === "boolean") return "BOOLEAN";
|
if (type === "boolean") return "BOOLEAN";
|
||||||
if (type === "string") return "STRING";
|
if (type === "string") return "STRING";
|
||||||
if (type === "number") return "NUMBER";
|
if (type === "number") return "NUMBER";
|
||||||
if (type === "object" || type === "JSON") return "OBJECT";
|
if (type === "object" || type === "JSON") return "OBJECT";
|
||||||
if (type === undefined) return "UNKNOWN";
|
if (type === "Function Call") return "FUNCTION";
|
||||||
return undefined;
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
||||||
typeToIcon(type: string) {
|
typeToIcon(type: string) {
|
||||||
|
|
@ -417,6 +519,7 @@ class TernServer {
|
||||||
end?: any;
|
end?: any;
|
||||||
start?: any;
|
start?: any;
|
||||||
file?: any;
|
file?: any;
|
||||||
|
includeKeywords?: boolean;
|
||||||
},
|
},
|
||||||
pos?: CodeMirror.Position,
|
pos?: CodeMirror.Position,
|
||||||
) {
|
) {
|
||||||
|
|
@ -425,6 +528,7 @@ class TernServer {
|
||||||
const allowFragments = !query.fullDocs;
|
const allowFragments = !query.fullDocs;
|
||||||
if (!allowFragments) delete query.fullDocs;
|
if (!allowFragments) delete query.fullDocs;
|
||||||
query.lineCharPositions = true;
|
query.lineCharPositions = true;
|
||||||
|
query.includeKeywords = true;
|
||||||
if (!query.end) {
|
if (!query.end) {
|
||||||
const lineValue = this.lineValue(doc);
|
const lineValue = this.lineValue(doc);
|
||||||
const focusedValue = this.getFocusedDynamicValue(doc);
|
const focusedValue = this.getFocusedDynamicValue(doc);
|
||||||
|
|
@ -712,6 +816,20 @@ class TernServer {
|
||||||
fadeOut(tooltip: HTMLElement) {
|
fadeOut(tooltip: HTMLElement) {
|
||||||
this.remove(tooltip);
|
this.remove(tooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setEntityInformation(entityInformation: HintEntityInformation) {
|
||||||
|
this.entityInformation = entityInformation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const createCompletionHeader = (name: string): Completion => ({
|
||||||
|
text: name,
|
||||||
|
displayText: name,
|
||||||
|
className: "CodeMirror-hint-header",
|
||||||
|
data: { doc: "" },
|
||||||
|
origin: "",
|
||||||
|
type: "UNKNOWN",
|
||||||
|
isHeader: true,
|
||||||
|
});
|
||||||
|
|
||||||
export default new TernServer();
|
export default new TernServer();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { generateReactKey } from "utils/generators";
|
import { generateTypeDef } from "./dataTreeTypeDefCreator";
|
||||||
import { getType, Types } from "utils/TypeHelpers";
|
|
||||||
|
|
||||||
let extraDefs: any = {};
|
let extraDefs: any = {};
|
||||||
|
|
||||||
|
|
@ -17,35 +16,3 @@ export const customTreeTypeDefCreator = (
|
||||||
extraDefs = {};
|
extraDefs = {};
|
||||||
return { ...def };
|
return { ...def };
|
||||||
};
|
};
|
||||||
|
|
||||||
export function generateTypeDef(
|
|
||||||
obj: any,
|
|
||||||
): string | Record<string, string | Record<string, unknown>> {
|
|
||||||
const type = getType(obj);
|
|
||||||
switch (type) {
|
|
||||||
case Types.ARRAY: {
|
|
||||||
const arrayType = generateTypeDef(obj[0]);
|
|
||||||
const name = generateReactKey();
|
|
||||||
extraDefs[name] = arrayType;
|
|
||||||
return `[${name}]`;
|
|
||||||
}
|
|
||||||
case Types.OBJECT: {
|
|
||||||
const objType: Record<string, string | Record<string, unknown>> = {};
|
|
||||||
Object.keys(obj).forEach((k) => {
|
|
||||||
objType[k] = generateTypeDef(obj[k]);
|
|
||||||
});
|
|
||||||
return objType;
|
|
||||||
}
|
|
||||||
case Types.STRING:
|
|
||||||
return "string";
|
|
||||||
case Types.NUMBER:
|
|
||||||
return "number";
|
|
||||||
case Types.BOOLEAN:
|
|
||||||
return "bool";
|
|
||||||
case Types.NULL:
|
|
||||||
case Types.UNDEFINED:
|
|
||||||
return "?";
|
|
||||||
default:
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
generateTypeDef,
|
generateTypeDef,
|
||||||
dataTreeTypeDefCreator,
|
dataTreeTypeDefCreator,
|
||||||
flattenObjKeys,
|
flattenDef,
|
||||||
} from "utils/autocomplete/dataTreeTypeDefCreator";
|
} from "utils/autocomplete/dataTreeTypeDefCreator";
|
||||||
import {
|
import {
|
||||||
DataTreeWidget,
|
DataTreeWidget,
|
||||||
|
|
@ -72,26 +72,37 @@ describe("dataTreeTypeDefCreator", () => {
|
||||||
expect(objType).toStrictEqual(expected);
|
expect(objType).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("flatten object", () => {
|
it("flatten def", () => {
|
||||||
const options = {
|
const def = {
|
||||||
someNumber: "number",
|
entity1: {
|
||||||
someString: "string",
|
someNumber: "number",
|
||||||
someBool: "bool",
|
someString: "string",
|
||||||
nested: {
|
someBool: "bool",
|
||||||
someExtraNested: "string",
|
nested: {
|
||||||
|
someExtraNested: "string",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
|
entity1: {
|
||||||
|
someNumber: "number",
|
||||||
|
someString: "string",
|
||||||
|
someBool: "bool",
|
||||||
|
nested: {
|
||||||
|
someExtraNested: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
"entity1.someNumber": "number",
|
"entity1.someNumber": "number",
|
||||||
"entity1.someString": "string",
|
"entity1.someString": "string",
|
||||||
"entity1.someBool": "bool",
|
"entity1.someBool": "bool",
|
||||||
"entity1.nested": {
|
"entity1.nested": {
|
||||||
someExtraNested: "string",
|
someExtraNested: "string",
|
||||||
},
|
},
|
||||||
|
"entity1.nested.someExtraNested": "string",
|
||||||
};
|
};
|
||||||
|
|
||||||
const value = flattenObjKeys(options, "entity1");
|
const value = flattenDef(def, "entity1");
|
||||||
expect(value).toStrictEqual(expected);
|
expect(value).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,58 +1,55 @@
|
||||||
import { DataTreeEntity, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
import { DataTreeEntity } from "entities/DataTree/dataTreeFactory";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { generateReactKey } from "utils/generators";
|
|
||||||
import { entityDefinitions } from "utils/autocomplete/EntityDefinitions";
|
import { entityDefinitions } from "utils/autocomplete/EntityDefinitions";
|
||||||
import { getType, Types } from "utils/TypeHelpers";
|
import { getType, Types } from "utils/TypeHelpers";
|
||||||
import { Def } from "tern";
|
import { Def } from "tern";
|
||||||
|
import {
|
||||||
|
isAction,
|
||||||
|
isAppsmithEntity,
|
||||||
|
isTrueObject,
|
||||||
|
isWidget,
|
||||||
|
} from "workers/evaluationUtils";
|
||||||
|
|
||||||
|
// When there is a complex data type, we store it in extra def and refer to it
|
||||||
|
// in the def
|
||||||
let extraDefs: any = {};
|
let extraDefs: any = {};
|
||||||
const skipProperties = ["!doc", "!url", "!type"];
|
|
||||||
|
|
||||||
|
// Def names are encoded with information about the entity
|
||||||
|
// This so that we have more info about them
|
||||||
|
// when sorting results in autocomplete
|
||||||
|
// DATA_TREE.{entityType}.{entitySubType}.{entityName}
|
||||||
|
// eg DATA_TREE.WIDGET.TABLE_WIDGET.Table1
|
||||||
|
// or DATA_TREE.ACTION.ACTION.Api1
|
||||||
export const dataTreeTypeDefCreator = (
|
export const dataTreeTypeDefCreator = (
|
||||||
entity: DataTreeEntity,
|
entity: DataTreeEntity,
|
||||||
entityName: string,
|
entityName: string,
|
||||||
): { def: Def; name: string } => {
|
): { def: Def; name: string } => {
|
||||||
const defName = `DATA_TREE_${entityName}`;
|
const def: any = {};
|
||||||
const def: any = {
|
if (isWidget(entity)) {
|
||||||
"!name": defName,
|
const widgetType = entity.type;
|
||||||
};
|
if (widgetType in entityDefinitions) {
|
||||||
if (entity && "ENTITY_TYPE" in entity) {
|
const definition = _.get(entityDefinitions, widgetType);
|
||||||
if (entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET) {
|
if (_.isFunction(definition)) {
|
||||||
const widgetType = entity.type;
|
def[entityName] = definition(entity);
|
||||||
if (widgetType in entityDefinitions) {
|
} else {
|
||||||
const definition = _.get(entityDefinitions, widgetType);
|
def[entityName] = definition;
|
||||||
if (_.isFunction(definition)) {
|
|
||||||
const data = definition(entity);
|
|
||||||
const allData = flattenObjKeys(data, entityName);
|
|
||||||
for (const [key, value] of Object.entries(allData)) {
|
|
||||||
def[key] = value;
|
|
||||||
}
|
|
||||||
def[entityName] = definition(entity);
|
|
||||||
} else {
|
|
||||||
def[entityName] = definition;
|
|
||||||
const allFlattenData = flattenObjKeys(definition, entityName);
|
|
||||||
for (const [key, value] of Object.entries(allFlattenData)) {
|
|
||||||
def[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
flattenDef(def, entityName);
|
||||||
|
def["!name"] = `DATA_TREE.WIDGET.${widgetType}.${entityName}`;
|
||||||
}
|
}
|
||||||
if (entity.ENTITY_TYPE === ENTITY_TYPE.ACTION) {
|
} else if (isAction(entity)) {
|
||||||
const actionDefs = entityDefinitions.ACTION(entity);
|
def[entityName] = entityDefinitions.ACTION(entity);
|
||||||
def[entityName] = actionDefs;
|
flattenDef(def, entityName);
|
||||||
const finalData = flattenObjKeys(actionDefs, entityName);
|
def["!name"] = `DATA_TREE.ACTION.ACTION.${entityName}`;
|
||||||
for (const [key, value] of Object.entries(finalData)) {
|
} else if (isAppsmithEntity(entity)) {
|
||||||
def[key] = value;
|
def["!name"] = "DATA_TREE.APPSMITH.APPSMITH";
|
||||||
}
|
def.appsmith = generateTypeDef(_.omit(entity, "ENTITY_TYPE"));
|
||||||
}
|
|
||||||
if (entity.ENTITY_TYPE === ENTITY_TYPE.APPSMITH) {
|
|
||||||
const options: any = generateTypeDef(_.omit(entity, "ENTITY_TYPE"));
|
|
||||||
def.appsmith = options;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
def["!define"] = { ...extraDefs };
|
if (Object.keys(extraDefs)) {
|
||||||
extraDefs = {};
|
def["!define"] = { ...extraDefs };
|
||||||
return { def, name: defName };
|
extraDefs = {};
|
||||||
|
}
|
||||||
|
return { def, name: def["!name"] };
|
||||||
};
|
};
|
||||||
|
|
||||||
export function generateTypeDef(
|
export function generateTypeDef(
|
||||||
|
|
@ -61,10 +58,8 @@ export function generateTypeDef(
|
||||||
const type = getType(obj);
|
const type = getType(obj);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Types.ARRAY: {
|
case Types.ARRAY: {
|
||||||
const arrayType = generateTypeDef(obj[0]);
|
const arrayType = getType(obj[0]);
|
||||||
const name = generateReactKey();
|
return `[${arrayType}]`;
|
||||||
extraDefs[name] = arrayType;
|
|
||||||
return `[${name}]`;
|
|
||||||
}
|
}
|
||||||
case Types.OBJECT: {
|
case Types.OBJECT: {
|
||||||
const objType: Record<string, string | Record<string, unknown>> = {};
|
const objType: Record<string, string | Record<string, unknown>> = {};
|
||||||
|
|
@ -87,16 +82,21 @@ export function generateTypeDef(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const flattenObjKeys = (
|
export const flattenDef = (def: Def, entityName: string): Def => {
|
||||||
options: any,
|
const flattenedDef = def;
|
||||||
parentKey: string,
|
if (isTrueObject(def[entityName])) {
|
||||||
results: any = {},
|
Object.entries(def[entityName]).forEach(([key, value]) => {
|
||||||
): any => {
|
if (!key.startsWith("!")) {
|
||||||
const r: any = results;
|
flattenedDef[`${entityName}.${key}`] = value;
|
||||||
for (const [key, value] of Object.entries(options)) {
|
if (isTrueObject(value)) {
|
||||||
if (!skipProperties.includes(key)) {
|
Object.entries(value).forEach(([subKey, subValue]) => {
|
||||||
r[parentKey + "." + key] = value;
|
if (!subKey.startsWith("!")) {
|
||||||
}
|
flattenedDef[`${entityName}.${key}.${subKey}`] = subValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return r;
|
return flattenedDef;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
100
app/client/src/utils/autocomplete/dataTypeSortRules.ts
Normal file
100
app/client/src/utils/autocomplete/dataTypeSortRules.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { DataType } from "utils/autocomplete/TernServer";
|
||||||
|
|
||||||
|
const RULES: Record<DataType, Array<string>> = {
|
||||||
|
STRING: [
|
||||||
|
"INPUT_WIDGET.text",
|
||||||
|
"RICH_TEXT_EDITOR_WIDGET.text",
|
||||||
|
"DROP_DOWN_WIDGET.selectedOptionValue",
|
||||||
|
"DATE_PICKER_WIDGET_2.selectedDate",
|
||||||
|
"DATE_PICKER_WIDGET_2.formattedDate",
|
||||||
|
"TABLE_WIDGET.pageNo",
|
||||||
|
"TABLE_WIDGET.searchText",
|
||||||
|
"TABLE_WIDGET.pageSize",
|
||||||
|
"TABS_WIDGET.selectedTab",
|
||||||
|
"TABLE_WIDGET.selectedRowIndex",
|
||||||
|
"IFRAME_WIDGET.source",
|
||||||
|
"IFRAME_WIDGET.title",
|
||||||
|
"DROP_DOWN_WIDGET.selectedOptionLabel",
|
||||||
|
"BUTTON_WIDGET.recaptchaToken",
|
||||||
|
"IMAGE_WIDGET.image",
|
||||||
|
"TEXT_WIDGET.text",
|
||||||
|
"BUTTON_WIDGET.text",
|
||||||
|
"FORM_BUTTON_WIDGET.text",
|
||||||
|
"CHART_WIDGET.xAxisName",
|
||||||
|
"CHART_WIDGET.yAxisName",
|
||||||
|
"CONTAINER_WIDGET.backgroundColor",
|
||||||
|
"BUTTON_WIDGET.googleRecaptchaKey",
|
||||||
|
],
|
||||||
|
NUMBER: [
|
||||||
|
"TABLE_WIDGET.pageNo",
|
||||||
|
"TABLE_WIDGET.pageSize",
|
||||||
|
"INPUT_WIDGET.text",
|
||||||
|
"TABLE_WIDGET.selectedRowIndex",
|
||||||
|
"RICH_TEXT_EDITOR_WIDGET.text",
|
||||||
|
"DROP_DOWN_WIDGET.selectedOptionValue",
|
||||||
|
"DATE_PICKER_WIDGET_2.selectedDate",
|
||||||
|
"DATE_PICKER_WIDGET_2.formattedDate",
|
||||||
|
"TABLE_WIDGET.searchText",
|
||||||
|
"TABS_WIDGET.selectedTab",
|
||||||
|
"IFRAME_WIDGET.source",
|
||||||
|
"IFRAME_WIDGET.title",
|
||||||
|
"DROP_DOWN_WIDGET.selectedOptionLabel",
|
||||||
|
"IMAGE_WIDGET.image",
|
||||||
|
"TEXT_WIDGET.text",
|
||||||
|
"BUTTON_WIDGET.text",
|
||||||
|
"FORM_BUTTON_WIDGET.text",
|
||||||
|
"CHART_WIDGET.xAxisName",
|
||||||
|
"CHART_WIDGET.yAxisName",
|
||||||
|
"CONTAINER_WIDGET.backgroundColor",
|
||||||
|
],
|
||||||
|
OBJECT: ["ACTION.data"],
|
||||||
|
ARRAY: ["ACTION.data"],
|
||||||
|
BOOLEAN: [
|
||||||
|
"CHECKBOX_WIDGET.isChecked",
|
||||||
|
"SWITCH_WIDGET.isSwitchedOn",
|
||||||
|
"CONTAINER_WIDGET.isVisible",
|
||||||
|
"INPUT_WIDGET.isVisible",
|
||||||
|
"TABLE_WIDGET.isVisible",
|
||||||
|
"DROP_DOWN_WIDGET.isVisible",
|
||||||
|
"IMAGE_WIDGET.isVisible",
|
||||||
|
"TEXT_WIDGET.isVisible",
|
||||||
|
"BUTTON_WIDGET.isVisible",
|
||||||
|
"DATE_PICKER_WIDGET2.isVisible",
|
||||||
|
"CHECKBOX_WIDGET.isVisible",
|
||||||
|
"SWITCH_WIDGET.isVisible",
|
||||||
|
"RADIO_GROUP_WIDGET.isVisible",
|
||||||
|
"TABS_WIDGET.isVisible",
|
||||||
|
"MODAL_WIDGET.isVisible",
|
||||||
|
"RICH_TEXT_EDITOR_WIDGET.isVisible",
|
||||||
|
"FORM_WIDGET.isVisible",
|
||||||
|
"FORM_BUTTON_WIDGET.isVisible",
|
||||||
|
"FILE_PICKER_WIDGET.isVisible",
|
||||||
|
"LIST_WIDGET.isVisible",
|
||||||
|
"RATE_WIDGET.isVisible",
|
||||||
|
"IFRAME_WIDGET.isVisible",
|
||||||
|
"DIVIDER_WIDGET.isVisible",
|
||||||
|
"INPUT_WIDGET.isValid",
|
||||||
|
"INPUT_WIDGET.isDisabled",
|
||||||
|
"DROP_DOWN_WIDGET.isDisabled",
|
||||||
|
"BUTTON_WIDGET.isDisabled",
|
||||||
|
"DATE_PICKER_WIDGET2.isDisabled",
|
||||||
|
"CHECKBOX_WIDGET.isDisabled",
|
||||||
|
"SWITCH_WIDGET.isDisabled",
|
||||||
|
"RICH_TEXT_EDITOR_WIDGET.isDisabled",
|
||||||
|
"FORM_BUTTON_WIDGET.isDisabled",
|
||||||
|
"FILE_PICKER_WIDGET.isRequired",
|
||||||
|
"MODAL_WIDGET.isOpen",
|
||||||
|
],
|
||||||
|
FUNCTION: [
|
||||||
|
"ACTION.run()",
|
||||||
|
"storeValue()",
|
||||||
|
"showAlert()",
|
||||||
|
"navigateTo()",
|
||||||
|
"resetWidget()",
|
||||||
|
"download()",
|
||||||
|
"showModal()",
|
||||||
|
],
|
||||||
|
UNKNOWN: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RULES;
|
||||||
|
|
@ -306,7 +306,6 @@ describe("substituteDynamicBindingWithValues", () => {
|
||||||
"wrongBinding": undefined,
|
"wrongBinding": undefined,
|
||||||
"emptyBinding": null,
|
"emptyBinding": null,
|
||||||
}`;
|
}`;
|
||||||
debugger;
|
|
||||||
const result = substituteDynamicBindingWithValues(
|
const result = substituteDynamicBindingWithValues(
|
||||||
binding,
|
binding,
|
||||||
subBindings,
|
subBindings,
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ describe("Add functions", () => {
|
||||||
responseMeta: { isExecutionSuccess: false },
|
responseMeta: { isExecutionSuccess: false },
|
||||||
ENTITY_TYPE: ENTITY_TYPE.ACTION,
|
ENTITY_TYPE: ENTITY_TYPE.ACTION,
|
||||||
dependencyMap: {},
|
dependencyMap: {},
|
||||||
|
logBlackList: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const dataTreeWithFunctions = addFunctions(dataTree);
|
const dataTreeWithFunctions = addFunctions(dataTree);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { Diff } from "deep-diff";
|
||||||
import {
|
import {
|
||||||
DataTree,
|
DataTree,
|
||||||
DataTreeAction,
|
DataTreeAction,
|
||||||
|
DataTreeAppsmith,
|
||||||
DataTreeEntity,
|
DataTreeEntity,
|
||||||
DataTreeWidget,
|
DataTreeWidget,
|
||||||
ENTITY_TYPE,
|
ENTITY_TYPE,
|
||||||
|
|
@ -230,6 +231,16 @@ export function isAction(entity: DataTreeEntity): entity is DataTreeAction {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isAppsmithEntity(
|
||||||
|
entity: DataTreeEntity,
|
||||||
|
): entity is DataTreeAppsmith {
|
||||||
|
return (
|
||||||
|
typeof entity === "object" &&
|
||||||
|
"ENTITY_TYPE" in entity &&
|
||||||
|
entity.ENTITY_TYPE === ENTITY_TYPE.APPSMITH
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// We need to remove functions from data tree to avoid any unexpected identifier while JSON parsing
|
// We need to remove functions from data tree to avoid any unexpected identifier while JSON parsing
|
||||||
// Check issue https://github.com/appsmithorg/appsmith/issues/719
|
// Check issue https://github.com/appsmithorg/appsmith/issues/719
|
||||||
export const removeFunctions = (value: any) => {
|
export const removeFunctions = (value: any) => {
|
||||||
|
|
@ -577,7 +588,9 @@ export const addErrorToEntityProperty = (
|
||||||
|
|
||||||
// For the times when you need to know if something truly an object like { a: 1, b: 2}
|
// For the times when you need to know if something truly an object like { a: 1, b: 2}
|
||||||
// typeof, lodash.isObject and others will return false positives for things like array, null, etc
|
// typeof, lodash.isObject and others will return false positives for things like array, null, etc
|
||||||
export const isTrueObject = (item: unknown): boolean => {
|
export const isTrueObject = (
|
||||||
|
item: unknown,
|
||||||
|
): item is Record<string, unknown> => {
|
||||||
return Object.prototype.toString.call(item) === "[object Object]";
|
return Object.prototype.toString.call(item) === "[object Object]";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user