Update debugger error message CTA's (#6416)

This commit is contained in:
akash-codemonk 2021-08-16 16:33:27 +05:30 committed by GitHub
parent f05e32a97e
commit 8e08e778d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 443 additions and 345 deletions

View File

@ -1,5 +1,5 @@
import { ReduxActionTypes } from "constants/ReduxActionConstants";
import { Message, ENTITY_TYPE } from "entities/AppsmithConsole";
import { ENTITY_TYPE, Log, Message } from "entities/AppsmithConsole";
import { EventName } from "utils/AnalyticsUtil";
export interface LogDebuggerErrorAnalyticsPayload {
@ -8,15 +8,18 @@ export interface LogDebuggerErrorAnalyticsPayload {
entityType: ENTITY_TYPE;
eventName: EventName;
propertyPath: string;
errorMessages: { message: string }[];
errorMessages?: Message[];
errorMessage?: Message["message"];
errorType?: Message["type"];
analytics?: Log["analytics"];
}
export const debuggerLogInit = (payload: Message) => ({
export const debuggerLogInit = (payload: Log) => ({
type: ReduxActionTypes.DEBUGGER_LOG_INIT,
payload,
});
export const debuggerLog = (payload: Message) => ({
export const debuggerLog = (payload: Log) => ({
type: ReduxActionTypes.DEBUGGER_LOG,
payload,
});
@ -30,16 +33,33 @@ export const showDebugger = (payload?: boolean) => ({
payload,
});
export const errorLog = (payload: Message) => ({
type: ReduxActionTypes.DEBUGGER_ERROR_LOG,
// Add an error
export const addErrorLogInit = (payload: Log) => ({
type: ReduxActionTypes.DEBUGGER_ADD_ERROR_LOG_INIT,
payload,
});
export const updateErrorLog = (payload: Message) => ({
type: ReduxActionTypes.DEBUGGER_UPDATE_ERROR_LOG,
export const addErrorLog = (payload: Log) => ({
type: ReduxActionTypes.DEBUGGER_ADD_ERROR_LOG,
payload,
});
export const deleteErrorLogInit = (
id: string,
analytics?: Log["analytics"],
) => ({
type: ReduxActionTypes.DEBUGGER_DELETE_ERROR_LOG_INIT,
payload: {
id,
analytics,
},
});
export const deleteErrorLog = (id: string) => ({
type: ReduxActionTypes.DEBUGGER_DELETE_ERROR_LOG,
payload: id,
});
// Only used for analytics
export const logDebuggerErrorAnalytics = (
payload: LogDebuggerErrorAnalyticsPayload,

View File

@ -341,17 +341,6 @@ if (intercomAppID) {
});
}
export function bootIntercom(intercomAppID: string, user?: User) {
if (intercomAppID && window.Intercom) {
window.Intercom("boot", {
app_id: intercomAppID,
user_id: user?.username,
name: user?.name,
email: user?.email,
});
}
}
class DocumentationSearch extends React.Component<Props, State> {
constructor(props: Props) {
super(props);

View File

@ -1,7 +1,5 @@
import React, { SyntheticEvent } from "react";
import DocumentationSearch, {
bootIntercom,
} from "components/designSystems/appsmith/help/DocumentationSearch";
import DocumentationSearch from "components/designSystems/appsmith/help/DocumentationSearch";
import { getHelpModalOpen } from "selectors/helpSelectors";
import {
setHelpDefaultRefinement,
@ -19,7 +17,7 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
import { HELP_MODAL_HEIGHT, HELP_MODAL_WIDTH } from "constants/HelpConstants";
import { getCurrentUser } from "selectors/usersSelectors";
import { User } from "constants/userConstants";
const { intercomAppID } = getAppsmithConfigs();
import { bootIntercom } from "utils/helpers";
const { algolia } = getAppsmithConfigs();
const HelpButton = styled.button<{
@ -70,12 +68,12 @@ class HelpModal extends React.Component<Props> {
static contextType = LayersContext;
componentDidMount() {
const { user } = this.props;
bootIntercom(intercomAppID, user);
bootIntercom(user);
}
componentDidUpdate(prevProps: Props) {
const { user } = this.props;
if (user?.email && prevProps.user?.email !== user?.email) {
bootIntercom(intercomAppID, user);
bootIntercom(user);
}
}
/**

View File

@ -7,6 +7,9 @@ import { BlankState } from "./helpers";
import LogItem, { getLogItemProps } from "./LogItem";
import { usePagination, useFilteredLogs } from "./hooks";
import { createMessage, NO_LOGS } from "constants/messages";
import { useSelector } from "react-redux";
import { getCurrentUser } from "selectors/usersSelectors";
import { bootIntercom } from "utils/helpers";
const LIST_HEADER_HEIGHT = "38px";
@ -45,6 +48,11 @@ function DebbuggerLogs(props: Props) {
() => LOGS_FILTER_OPTIONS.find((option) => option.value === filter),
[filter],
);
const currentUser = useSelector(getCurrentUser);
useEffect(() => {
bootIntercom(currentUser);
}, [currentUser?.email]);
const handleScroll = (e: Event) => {
if ((e.target as HTMLDivElement).scrollTop === 0) {

View File

@ -1,10 +1,13 @@
import React from "react";
import React, { useEffect } from "react";
import { useSelector } from "react-redux";
import styled from "styled-components";
import { getDebuggerErrors } from "selectors/debuggerSelectors";
import LogItem, { getLogItemProps } from "./LogItem";
import { BlankState } from "./helpers";
import { createMessage, NO_ERRORS } from "constants/messages";
import { getCurrentUser } from "selectors/usersSelectors";
import { AppState } from "reducers";
import { bootIntercom } from "utils/helpers";
const ContainerWrapper = styled.div`
overflow: hidden;
@ -18,7 +21,12 @@ const ListWrapper = styled.div`
function Errors(props: { hasShortCut?: boolean }) {
const errors = useSelector(getDebuggerErrors);
const expandId = useSelector((state: any) => state.ui.debugger.expandId);
const expandId = useSelector((state: AppState) => state.ui.debugger.expandId);
const currentUser = useSelector(getCurrentUser);
useEffect(() => {
bootIntercom(currentUser);
}, [currentUser?.email]);
return (
<ContainerWrapper>

View File

@ -1,7 +1,7 @@
import { Collapse, Position } from "@blueprintjs/core";
import { Classes } from "components/ads/common";
import Icon, { IconName, IconSize } from "components/ads/Icon";
import { Message, Severity, SourceEntity } from "entities/AppsmithConsole";
import { Log, Message, Severity, SourceEntity } from "entities/AppsmithConsole";
import React, { useCallback, useState } from "react";
import ReactJson from "react-json-view";
import styled from "styled-components";
@ -16,9 +16,16 @@ import Text, { TextType } from "components/ads/Text";
import { getTypographyByKey } from "constants/DefaultTheme";
import AnalyticsUtil from "utils/AnalyticsUtil";
import TooltipComponent from "components/ads/Tooltip";
import { createMessage, TROUBLESHOOT_ISSUE } from "constants/messages";
import {
createMessage,
DEBUGGER_INTERCOM_TEXT,
TROUBLESHOOT_ISSUE,
} from "constants/messages";
import { PropertyEvaluationErrorType } from "utils/DynamicBindingUtils";
import { getAppsmithConfigs } from "configs";
const { intercomAppID } = getAppsmithConfigs();
const Log = styled.div<{ collapsed: boolean }>`
const Wrapper = styled.div<{ collapsed: boolean }>`
padding: 9px 30px;
display: flex;
@ -142,7 +149,7 @@ const MessageWrapper = styled.div`
padding-top: ${(props) => props.theme.spaces[1]}px;
`;
export const getLogItemProps = (e: Message) => {
export const getLogItemProps = (e: Log) => {
return {
icon: SeverityIcon[e.severity] as IconName,
iconColor: SeverityIconColor[e.severity],
@ -170,7 +177,7 @@ type LogItemProps = {
id?: string;
source?: SourceEntity;
expand?: boolean;
messages: Message["messages"];
messages?: Message[];
};
function LogItem(props: LogItemProps) {
@ -188,21 +195,47 @@ function LogItem(props: LogItemProps) {
const showToggleIcon = props.state || props.messages;
const dispatch = useDispatch();
const openHelpModal = useCallback((e, message?: string) => {
const onLogClick = useCallback((e, error?: Message) => {
e.stopPropagation();
const text = message || props.text;
AnalyticsUtil.logEvent("OPEN_OMNIBAR", {
source: "DEBUGGER",
searchTerm: text,
});
dispatch(setGlobalSearchQuery(text || ""));
dispatch(toggleShowGlobalSearchModal());
// If the error message was clicked we use that, else if the wand icon is clicked
// we use the first error "Message" in the list
// This is of type Message { message: string; type?: ErrorType; }
const focusedError =
error ||
(props.messages && props.messages.length ? props.messages[0] : undefined);
const text = focusedError?.message || props.text;
switch (focusedError?.type) {
case PropertyEvaluationErrorType.PARSE:
case PropertyEvaluationErrorType.LINT:
// Search google for the error message
window.open("http://google.com/search?q=" + text);
break;
case PropertyEvaluationErrorType.VALIDATION:
// Search through the omnibar
AnalyticsUtil.logEvent("OPEN_OMNIBAR", {
source: "DEBUGGER",
searchTerm: text,
errorType: PropertyEvaluationErrorType.VALIDATION,
});
dispatch(setGlobalSearchQuery(text || ""));
dispatch(toggleShowGlobalSearchModal());
break;
default:
// Prefill the error in intercom
if (intercomAppID && window.Intercom) {
window.Intercom(
"showNewMessage",
createMessage(DEBUGGER_INTERCOM_TEXT, text),
);
}
}
}, []);
const messages = props.messages || [];
return (
<Log
<Wrapper
className={props.severity}
collapsed={!isOpen}
onClick={() => setIsOpen(!isOpen)}
@ -244,7 +277,7 @@ function LogItem(props: LogItemProps) {
className={Classes.ICON}
fillColor={props.iconColor}
name={"wand"}
onClick={openHelpModal}
onClick={onLogClick}
size={IconSize.MEDIUM}
/>
</TooltipComponent>
@ -257,7 +290,7 @@ function LogItem(props: LogItemProps) {
<MessageWrapper key={e.message}>
<span
className="debugger-message"
onClick={(event) => openHelpModal(event, e.message)}
onClick={(event) => onLogClick(event, e)}
>
{e.message}
</span>
@ -283,7 +316,7 @@ function LogItem(props: LogItemProps) {
uiComponent={DebuggerLinkUI.ENTITY_NAME}
/>
)}
</Log>
</Wrapper>
);
}

View File

@ -1,4 +1,4 @@
import { Message, Severity } from "entities/AppsmithConsole";
import { Log, Severity } from "entities/AppsmithConsole";
import React from "react";
import styled from "styled-components";
import { getTypographyByKey } from "constants/DefaultTheme";
@ -125,7 +125,7 @@ export function getDependencyChain(
export const doesEntityHaveErrors = (
entityId: string,
debuggerErrors: Record<string, Message>,
debuggerErrors: Record<string, Log>,
) => {
const ids = Object.keys(debuggerErrors);

View File

@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { useSelector } from "react-redux";
import { useParams } from "react-router";
import { ENTITY_TYPE, Message } from "entities/AppsmithConsole";
import { ENTITY_TYPE, Log } from "entities/AppsmithConsole";
import { AppState } from "reducers";
import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers";
import { useNavigateToWidget } from "pages/Editor/Explorer/Widgets/useNavigateToWidget";
@ -33,11 +33,11 @@ export const useFilteredLogs = (query: string, filter?: any) => {
let logs = useSelector((state: AppState) => state.ui.debugger.logs);
if (filter) {
logs = logs.filter((log: Message) => log.severity === filter);
logs = logs.filter((log) => log.severity === filter);
}
if (query) {
logs = logs.filter((log: Message) => {
logs = logs.filter((log) => {
if (log.source?.name)
return (
log.source?.name.toUpperCase().indexOf(query.toUpperCase()) !== -1
@ -48,9 +48,9 @@ export const useFilteredLogs = (query: string, filter?: any) => {
return logs;
};
export const usePagination = (data: Message[], itemsPerPage = 50) => {
export const usePagination = (data: Log[], itemsPerPage = 50) => {
const [currentPage, setCurrentPage] = useState(1);
const [paginatedData, setPaginatedData] = useState<Message[]>([]);
const [paginatedData, setPaginatedData] = useState<Log[]>([]);
const maxPage = Math.ceil(data.length / itemsPerPage);
useEffect(() => {
@ -158,9 +158,7 @@ export const useEntityLink = () => {
export const useGetEntityInfo = (name: string) => {
const entity = useSelector((state: AppState) => state.evaluations.tree[name]);
const debuggerErrors: Record<string, Message> = useSelector(
getDebuggerErrors,
);
const debuggerErrors = useSelector(getDebuggerErrors);
const action = useSelector((state: AppState) =>
isAction(entity) ? getAction(state, entity.actionId) : undefined,
);

View File

@ -133,10 +133,11 @@ export const ReduxActionTypes = {
PUBLISH: "PUBLISH",
DEBUGGER_LOG: "DEBUGGER_LOG",
DEBUGGER_LOG_INIT: "DEBUGGER_LOG_INIT",
DEBUGGER_ERROR_LOG: "DEBUGGER_ERROR_LOG",
DEBUGGER_UPDATE_ERROR_LOG: "DEBUGGER_UPDATE_ERROR_LOG",
DEBUGGER_UPDATE_ERROR_LOGS: "DEBUGGER_UPDATE_ERROR_LOGS",
DEBUGGER_ERROR_ANALYTICS: "DEBUGGER_ERROR_ANALYTICS",
DEBUGGER_ADD_ERROR_LOG: "DEBUGGER_ADD_ERROR_LOG",
DEBUGGER_DELETE_ERROR_LOG: "DEBUGGER_DELETE_ERROR_LOG",
DEBUGGER_ADD_ERROR_LOG_INIT: "DEBUGGER_ADD_ERROR_LOG_INIT",
DEBUGGER_DELETE_ERROR_LOG_INIT: "DEBUGGER_DELETE_ERROR_LOG_INIT",
CLEAR_DEBUGGER_LOGS: "CLEAR_DEBUGGER_LOGS",
SHOW_DEBUGGER: "SHOW_DEBUGGER",
SET_ACTION_TABS_INITIAL_INDEX: "SET_ACTION_TABS_INITIAL_INDEX",

View File

@ -349,11 +349,15 @@ export const DEBUGGER_ERRORS = () => "Errors";
export const DEBUGGER_LOGS = () => "Logs";
export const INSPECT_ENTITY = () => "Inspect Entity";
export const INSPECT_ENTITY_BLANK_STATE = () => "Select an entity to inspect";
export const VALUE_IS_INVALID = (propertyPath: string) =>
`The value at ${propertyPath} is invalid`;
export const ACTION_CONFIGURATION_UPDATED = () => "Configuration updated";
export const WIDGET_PROPERTIES_UPDATED = () => "Widget properties were updated";
export const EMPTY_RESPONSE_FIRST_HALF = () => "🙌 Click on";
export const EMPTY_RESPONSE_LAST_HALF = () => "to get a response";
export const INVALID_EMAIL = () => "Please enter a valid email";
export const DEBUGGER_INTERCOM_TEXT = (text: string) =>
`Hi, \nI'm facing the following error on appsmith, can you please help? \n\n${text}`;
export const TROUBLESHOOT_ISSUE = () => "Troubleshoot issue";

View File

@ -1,10 +1,3 @@
import { ActionableError } from "entities/AppsmithConsole";
export enum ActionError {
EXECUTION_TIMEOUT = "action:execution:timeout",
}
export interface TimeoutError extends ActionableError {
type: ActionError.EXECUTION_TIMEOUT;
timeoutMs: number;
}

View File

@ -1,26 +1,5 @@
import { ActionableError } from "entities/AppsmithConsole";
export enum BindingError {
SYNTAX = "binding:syntax",
UNKNOWN_VARIABLE = "binding:unknown_variable",
DISALLOWED_FUNCTION = "binding:disallowed_function",
}
interface BaseBindingError extends ActionableError {
lineNumber: number;
position: number;
}
export interface SyntaxError extends BaseBindingError {
type: BindingError.SYNTAX;
}
export interface UnknownVariableError extends BaseBindingError {
type: BindingError.UNKNOWN_VARIABLE;
variableName: string;
}
export interface DisallowedFunctionError extends BaseBindingError {
type: BindingError.DISALLOWED_FUNCTION;
functionName: string;
}

View File

@ -1,10 +1,3 @@
import { ActionableError, SourceEntity } from "entities/AppsmithConsole";
export enum EvalError {
CYCLIC = "eval:cyclic",
}
export interface CyclicDependencyError extends ActionableError {
type: EvalError.CYCLIC;
entities: SourceEntity[];
}

View File

@ -1,9 +1,6 @@
import { ReduxAction } from "constants/ReduxActionConstants";
import { BindingError } from "entities/AppsmithConsole/binding";
import { ActionError } from "entities/AppsmithConsole/action";
import { WidgetError } from "entities/AppsmithConsole/widget";
import { EvalError } from "entities/AppsmithConsole/eval";
import LOG_TYPE from "./logtype";
import { PropertyEvaluationErrorType } from "utils/DynamicBindingUtils";
export enum ENTITY_TYPE {
ACTION = "ACTION",
@ -11,7 +8,11 @@ export enum ENTITY_TYPE {
WIDGET = "WIDGET",
}
export type ErrorType = BindingError | ActionError | WidgetError | EvalError;
export enum PLATFORM_ERROR {
PLUGIN_EXECUTION = "PLUGIN_EXECUTION",
}
export type ErrorType = PropertyEvaluationErrorType | PLATFORM_ERROR;
export enum Severity {
// Everything, irrespective of what the user should see or not
@ -47,17 +48,13 @@ export interface SourceEntity {
}
export interface LogActionPayload {
// Log id, used for updating or deleting
id?: string;
// What is the log about. Is it a datasource update, widget update, eval error etc.
logType?: LOG_TYPE;
text: string;
messages?: Array<Message>;
// Time taken for the event to complete
messages?: Array<{
// More contextual message than `text`
message: string;
// The section of code being referred to
// codeSegment?: string;
}>;
timeTaken?: string;
// "where" source entity and propertyPsath.
source?: SourceEntity;
@ -67,7 +64,15 @@ export interface LogActionPayload {
analytics?: Record<string, any>;
}
export interface Message extends LogActionPayload {
export interface Message {
// More contextual message than `text`
message: string;
type?: ErrorType;
// The section of code being referred to
// codeSegment?: string;
}
export interface Log extends LogActionPayload {
severity: Severity;
// "when" did this event happen
timestamp: string;
@ -100,12 +105,3 @@ export interface Message extends LogActionPayload {
* ]
* }
*/
export interface ActionableError extends Message {
// Error type of the event.
type: ErrorType;
severity: Severity.ERROR;
// Actions a user can take to resolve this issue
userActions: Array<UserAction>;
}

View File

@ -2,17 +2,15 @@ import React, { useEffect } from "react";
import styled, { createGlobalStyle, withTheme } from "styled-components";
import { Popover, Position } from "@blueprintjs/core";
import DocumentationSearch, {
bootIntercom,
} from "components/designSystems/appsmith/help/DocumentationSearch";
import DocumentationSearch from "components/designSystems/appsmith/help/DocumentationSearch";
import Icon, { IconSize } from "components/ads/Icon";
import { HELP_MODAL_WIDTH } from "constants/HelpConstants";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { Theme } from "constants/DefaultTheme";
import { getAppsmithConfigs } from "../../configs";
import { getCurrentUser } from "../../selectors/usersSelectors";
import { useSelector } from "react-redux";
import { bootIntercom } from "utils/helpers";
const HelpPopoverStyle = createGlobalStyle`
.bp3-popover.bp3-minimal.navbar-help-popover {
@ -47,12 +45,11 @@ const onOpened = () => {
AnalyticsUtil.logEvent("OPEN_HELP", { page: "Editor" });
};
const { intercomAppID } = getAppsmithConfigs();
function HelpButton() {
const user = useSelector(getCurrentUser);
useEffect(() => {
bootIntercom(intercomAppID, user);
bootIntercom(user);
}, [user?.email]);
return (

View File

@ -21,7 +21,7 @@ import {
getDependenciesFromInverseDependencies,
} from "components/editorComponents/Debugger/helpers";
import { getDebuggerErrors } from "selectors/debuggerSelectors";
import { ENTITY_TYPE, Message } from "entities/AppsmithConsole";
import { ENTITY_TYPE, Log } from "entities/AppsmithConsole";
import { DebugButton } from "components/editorComponents/Debugger/DebugCTA";
import { showDebugger } from "actions/debuggerActions";
import { setActionTabsInitialIndex } from "actions/actionActions";
@ -179,7 +179,7 @@ type TriggerNodeProps = DefaultDropDownValueNodeProps & {
const doConnectionsHaveErrors = (
options: DropdownOption[],
debuggerErrors: Record<string, Message>,
debuggerErrors: Record<string, Log>,
) => {
return options.some((option) =>
doesEntityHaveErrors(option.value as string, debuggerErrors),

View File

@ -1,8 +1,7 @@
import { createReducer } from "utils/AppsmithUtils";
import { Message, Severity } from "entities/AppsmithConsole";
import { Log, Severity } from "entities/AppsmithConsole";
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
import { get, merge, isEmpty, omit, isUndefined } from "lodash";
import LOG_TYPE from "entities/AppsmithConsole/logtype";
import { omit, isUndefined } from "lodash";
const initialState: DebuggerReduxState = {
logs: [],
@ -15,7 +14,7 @@ const initialState: DebuggerReduxState = {
const debuggerReducer = createReducer(initialState, {
[ReduxActionTypes.DEBUGGER_LOG]: (
state: DebuggerReduxState,
action: ReduxAction<Message>,
action: ReduxAction<Log>,
) => {
const isError = action.payload.severity === Severity.ERROR;
@ -41,70 +40,28 @@ const debuggerReducer = createReducer(initialState, {
isOpen: isUndefined(action.payload) ? !state.isOpen : action.payload,
};
},
[ReduxActionTypes.DEBUGGER_ERROR_LOG]: (
[ReduxActionTypes.DEBUGGER_ADD_ERROR_LOG]: (
state: DebuggerReduxState,
action: ReduxAction<Message>,
action: ReduxAction<Log>,
) => {
if (!action.payload.source) return state;
const entityId = action.payload.source.id;
const id =
action.payload.logType === LOG_TYPE.WIDGET_PROPERTY_VALIDATION_ERROR ||
action.payload.logType === LOG_TYPE.EVAL_ERROR
? `${entityId}-${action.payload.source.propertyPath}`
: entityId;
const previousState = get(state.errors, id, {});
if (!action.payload.id) return state;
return {
...state,
errors: {
...state.errors,
[id]: {
...merge(previousState, action.payload),
},
[action.payload.id]: action.payload,
},
expandId: id,
expandId: action.payload.id,
};
},
[ReduxActionTypes.DEBUGGER_UPDATE_ERROR_LOG]: (
[ReduxActionTypes.DEBUGGER_DELETE_ERROR_LOG]: (
state: DebuggerReduxState,
action: ReduxAction<Message>,
) => {
if (!action.payload.source) return state;
const entityId = action.payload.source.id;
const isWidgetErrorLog =
action.payload.logType === LOG_TYPE.WIDGET_PROPERTY_VALIDATION_ERROR ||
action.payload.logType === LOG_TYPE.EVAL_ERROR;
const id = isWidgetErrorLog
? `${entityId}-${action.payload.source.propertyPath}`
: entityId;
if (isEmpty(action.payload.state)) {
return {
...state,
errors: omit(state.errors, id),
};
}
return {
...state,
errors: {
...state.errors,
[id]: {
...action.payload,
},
},
expandId: id,
};
},
[ReduxActionTypes.DEBUGGER_UPDATE_ERROR_LOGS]: (
state: DebuggerReduxState,
action: ReduxAction<Message>,
action: ReduxAction<string>,
) => {
return {
...state,
errors: { ...action.payload },
errors: omit(state.errors, action.payload),
};
},
[ReduxActionTypes.INIT_CANVAS_LAYOUT]: () => {
@ -115,10 +72,10 @@ const debuggerReducer = createReducer(initialState, {
});
export interface DebuggerReduxState {
logs: Message[];
logs: Log[];
errorCount: number;
isOpen: boolean;
errors: Record<string, Message>;
errors: Record<string, Log>;
expandId: string;
}

View File

@ -115,7 +115,7 @@ import {
resetWidgetMetaProperty,
} from "actions/metaActions";
import AppsmithConsole from "utils/AppsmithConsole";
import { ENTITY_TYPE } from "entities/AppsmithConsole";
import { ENTITY_TYPE, PLATFORM_ERROR } from "entities/AppsmithConsole";
import LOG_TYPE from "entities/AppsmithConsole/logtype";
import { matchPath } from "react-router";
import { setDataUrl } from "./PageSagas";
@ -565,7 +565,8 @@ export function* executeActionSaga(
}),
);
if (isErrorResponse(response)) {
AppsmithConsole.error({
AppsmithConsole.addError({
id: actionId,
logType: LOG_TYPE.ACTION_EXECUTION_ERROR,
text: `Execution failed with status ${response.data.statusCode}`,
source: {
@ -574,7 +575,12 @@ export function* executeActionSaga(
id: actionId,
},
state: response.data?.request ?? null,
messages: [{ message: payload.body as string }],
messages: [
{
message: payload.body as string,
type: PLATFORM_ERROR.PLUGIN_EXECUTION,
},
],
});
PerformanceTracker.stopAsyncTracking(
PerformanceTransactionName.EXECUTE_ACTION,
@ -902,7 +908,8 @@ function* runActionSaga(
},
});
} else {
AppsmithConsole.error({
AppsmithConsole.addError({
id: actionId,
logType: LOG_TYPE.ACTION_EXECUTION_ERROR,
text: `Execution failed with status ${response.data.statusCode}`,
source: {
@ -915,6 +922,7 @@ function* runActionSaga(
message: !isString(payload.body)
? JSON.stringify(payload.body)
: payload.body,
type: PLATFORM_ERROR.PLUGIN_EXECUTION,
},
],
state: response.data?.request ?? null,
@ -931,7 +939,8 @@ function* runActionSaga(
error = response.data.body.toString();
}
AppsmithConsole.error({
AppsmithConsole.addError({
id: actionId,
logType: LOG_TYPE.ACTION_EXECUTION_ERROR,
text: `Execution failed with status ${response.data.statusCode} `,
source: {
@ -1016,7 +1025,8 @@ function* executePageLoadAction(pageAction: PageAction) {
message += `\nERROR: "${body}"`;
}
AppsmithConsole.error({
AppsmithConsole.addError({
id: pageAction.id,
logType: LOG_TYPE.ACTION_EXECUTION_ERROR,
text: `Execution failed with status ${response.data.statusCode}`,
source: {
@ -1025,7 +1035,12 @@ function* executePageLoadAction(pageAction: PageAction) {
id: pageAction.id,
},
state: response.data?.request ?? null,
messages: [{ message: JSON.stringify(body) }],
messages: [
{
message: JSON.stringify(body),
type: PLATFORM_ERROR.PLUGIN_EXECUTION,
},
],
});
yield put(

View File

@ -1,16 +1,12 @@
import {
addErrorLog,
debuggerLog,
errorLog,
logDebuggerErrorAnalytics,
debuggerLogInit,
deleteErrorLog,
LogDebuggerErrorAnalyticsPayload,
updateErrorLog,
} from "actions/debuggerActions";
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
import {
ENTITY_TYPE,
LogActionPayload,
Message,
} from "entities/AppsmithConsole";
import { ENTITY_TYPE, Log, LogActionPayload } from "entities/AppsmithConsole";
import {
all,
call,
@ -20,13 +16,9 @@ import {
take,
takeEvery,
} from "redux-saga/effects";
import { get, set } from "lodash";
import { findIndex, get, isMatch, set } from "lodash";
import { getDebuggerErrors } from "selectors/debuggerSelectors";
import {
getAction,
getPlugin,
getPluginNameFromId,
} from "selectors/entitiesSelector";
import { getAction, getPlugin } from "selectors/entitiesSelector";
import { Action, PluginType } from "entities/Action";
import LOG_TYPE from "entities/AppsmithConsole/logtype";
import { DataTree } from "entities/DataTree/dataTreeFactory";
@ -50,6 +42,7 @@ import { Plugin } from "api/PluginApi";
import { getCurrentPageId } from "selectors/editorSelectors";
import { getWidget } from "./selectors";
import { WidgetProps } from "widgets/BaseWidget";
import AppsmithConsole from "utils/AppsmithConsole";
// Saga to format action request values to be shown in the debugger
function* formatActionRequestSaga(
@ -103,62 +96,29 @@ function* formatActionRequestSaga(
}
}
function* onEntityDeleteSaga(payload: Message) {
function* onEntityDeleteSaga(payload: Log) {
const source = payload.source;
if (!source) {
yield put(debuggerLog(payload));
return;
}
const currentPageId = yield select(getCurrentPageId);
let pluginName: string = yield select(
getPluginNameFromId,
payload?.analytics?.pluginId,
);
const errors: Record<string, Message> = yield select(getDebuggerErrors);
const errors: Record<string, Log> = yield select(getDebuggerErrors);
const errorIds = Object.keys(errors);
const updatedErrors: any = {};
errorIds.map((e) => {
const includes = e.includes(source.id);
if (!includes) {
updatedErrors[e] = errors[e];
} else {
// If the error is being removed here
// need to send an analytics event for the same
const error = errors[e];
pluginName = pluginName.replace(/ /g, "");
if (source.type === ENTITY_TYPE.ACTION) {
AnalyticsUtil.logEvent("DEBUGGER_RESOLVED_ERROR", {
entityType: pluginName,
propertyPath: `${pluginName}.${error.source?.propertyPath ?? ""}`,
errorMessages: error.messages,
pageId: currentPageId,
});
} else if (source.type === ENTITY_TYPE.WIDGET) {
const widgetType = error?.analytics?.widgetType;
AnalyticsUtil.logEvent("DEBUGGER_RESOLVED_ERROR", {
entityType: widgetType,
propertyPath: `${widgetType}.${error.source?.propertyPath ?? ""}`,
errorMessages: error.messages,
pageId: currentPageId,
});
}
if (includes) {
AppsmithConsole.deleteError(e, payload.analytics);
}
});
yield put({
type: ReduxActionTypes.DEBUGGER_UPDATE_ERROR_LOGS,
payload: updatedErrors,
});
yield put(debuggerLog(payload));
}
function* logDependentEntityProperties(payload: Message) {
function* logDependentEntityProperties(payload: Log) {
const { source, state } = payload;
if (!state || !source) return;
@ -207,11 +167,8 @@ function* logDependentEntityProperties(payload: Message) {
);
}
function* debuggerLogSaga(action: ReduxAction<Message>) {
function* debuggerLogSaga(action: ReduxAction<Log>) {
const { payload } = action;
const debuggerErrors: Record<string, Message> = yield select(
getDebuggerErrors,
);
switch (payload.logType) {
case LOG_TYPE.WIDGET_UPDATE:
@ -226,9 +183,7 @@ function* debuggerLogSaga(action: ReduxAction<Message>) {
case LOG_TYPE.WIDGET_PROPERTY_VALIDATION_ERROR:
if (payload.source && payload.source.propertyPath) {
if (payload.text) {
yield put(errorLog(payload));
yield put(debuggerLog(payload));
yield put(addErrorLog(payload));
}
}
break;
@ -239,20 +194,7 @@ function* debuggerLogSaga(action: ReduxAction<Message>) {
payload,
"state",
);
if (!((payload.source?.id as string) in debuggerErrors)) {
yield put(
logDebuggerErrorAnalytics({
eventName: "DEBUGGER_NEW_ERROR",
errorMessages: payload.messages ?? [],
entityType: ENTITY_TYPE.ACTION,
entityId: payload.source?.id ?? "",
entityName: payload.source?.name ?? "",
propertyPath: "",
}),
);
}
yield put(errorLog(formattedLog));
yield put(addErrorLog(formattedLog));
yield put(debuggerLog(formattedLog));
}
break;
@ -264,25 +206,7 @@ function* debuggerLogSaga(action: ReduxAction<Message>) {
"state.request",
);
if ((payload.source?.id as string) in debuggerErrors) {
yield put(
logDebuggerErrorAnalytics({
eventName: "DEBUGGER_RESOLVED_ERROR",
errorMessages:
debuggerErrors[payload.source?.id ?? ""].messages ?? [],
entityType: ENTITY_TYPE.ACTION,
entityId: payload.source?.id ?? "",
entityName: payload.source?.name ?? "",
propertyPath: "",
}),
);
}
yield put(
updateErrorLog({
...payload,
state: {},
}),
);
AppsmithConsole.deleteError(payload.source?.id ?? "");
yield put(debuggerLog(formattedLog));
}
@ -304,8 +228,11 @@ function* logDebuggerErrorAnalyticsSaga(
const currentPageId = yield select(getCurrentPageId);
if (payload.entityType === ENTITY_TYPE.WIDGET) {
const widget: WidgetProps = yield select(getWidget, payload.entityId);
const widgetType = widget.type;
const widget: WidgetProps | undefined = yield select(
getWidget,
payload.entityId,
);
const widgetType = widget?.type || payload?.analytics?.widgetType || "";
const propertyPath = `${widgetType}.${payload.propertyPath}`;
// Sending widget type for widgets
@ -314,10 +241,16 @@ function* logDebuggerErrorAnalyticsSaga(
propertyPath,
errorMessages: payload.errorMessages,
pageId: currentPageId,
errorMessage: payload.errorMessage,
errorType: payload.errorType,
});
} else if (payload.entityType === ENTITY_TYPE.ACTION) {
const action: Action = yield select(getAction, payload.entityId);
const plugin: Plugin = yield select(getPlugin, action.pluginId);
const action: Action | undefined = yield select(
getAction,
payload.entityId,
);
const pluginId = action?.pluginId || payload?.analytics?.pluginId || "";
const plugin: Plugin = yield select(getPlugin, pluginId);
const pluginName = plugin.name.replace(/ /g, "");
let propertyPath = `${pluginName}`;
@ -331,6 +264,8 @@ function* logDebuggerErrorAnalyticsSaga(
propertyPath,
errorMessages: payload.errorMessages,
pageId: currentPageId,
errorMessage: payload.errorMessage,
errorType: payload.errorType,
});
}
} catch (e) {
@ -338,6 +273,147 @@ function* logDebuggerErrorAnalyticsSaga(
}
}
function* addDebuggerErrorLogSaga(action: ReduxAction<Log>) {
const payload = action.payload;
const errors: Record<string, Log> = yield select(getDebuggerErrors);
yield put(debuggerLogInit(payload));
if (!payload.source || !payload.id) return;
const analyticsPayload = {
entityName: payload.source.name,
entityType: payload.source.type,
entityId: payload.source.id,
propertyPath: payload.source.propertyPath ?? "",
};
// If this is a new error
if (!(payload.id in errors)) {
const errorMessages = payload.messages ?? [];
yield put({
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
payload: {
...analyticsPayload,
eventName: "DEBUGGER_NEW_ERROR",
errorMessages: payload.messages,
},
});
// Log analytics for new error messages
if (errorMessages.length && payload) {
yield all(
errorMessages.map((errorMessage) =>
put({
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
payload: {
...analyticsPayload,
eventName: "DEBUGGER_NEW_ERROR_MESSAGE",
errorMessage: errorMessage.message,
errorType: errorMessage.type,
},
}),
),
);
}
} else {
const updatedErrorMessages = payload.messages ?? [];
const existingErrorMessages = errors[payload.id].messages ?? [];
// Log new error messages
yield all(
updatedErrorMessages.map((updatedErrorMessage) => {
const exists = findIndex(
existingErrorMessages,
(existingErrorMessage) => {
return isMatch(existingErrorMessage, updatedErrorMessage);
},
);
if (exists < 0) {
return put({
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
payload: {
...analyticsPayload,
eventName: "DEBUGGER_NEW_ERROR_MESSAGE",
errorMessage: updatedErrorMessage.message,
errorType: updatedErrorMessage.type,
},
});
}
}),
);
// Log resolved error messages
yield all(
existingErrorMessages.map((existingErrorMessage) => {
const exists = findIndex(
updatedErrorMessages,
(updatedErrorMessage) => {
return isMatch(updatedErrorMessage, existingErrorMessage);
},
);
if (exists < 0) {
return put({
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
payload: {
...analyticsPayload,
eventName: "DEBUGGER_RESOLVED_ERROR_MESSAGE",
errorMessage: existingErrorMessage.message,
errorType: existingErrorMessage.type,
},
});
}
}),
);
}
}
function* deleteDebuggerErrorLogSaga(
action: ReduxAction<{ id: string; analytics: Log["analytics"] }>,
) {
const errors: Record<string, Log> = yield select(getDebuggerErrors);
const error = errors[action.payload.id];
if (!error.source) return;
const analyticsPayload = {
entityName: error.source.name,
entityType: error.source.type,
entityId: error.source.id,
propertyPath: error.source.propertyPath ?? "",
analytics: action.payload.analytics,
};
const errorMessages = error.messages;
yield put({
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
payload: {
...analyticsPayload,
eventName: "DEBUGGER_RESOLVED_ERROR",
errorMessages,
},
});
if (errorMessages) {
yield all(
errorMessages.map((errorMessage) => {
return put({
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
payload: {
...analyticsPayload,
eventName: "DEBUGGER_RESOLVED_ERROR_MESSAGE",
errorMessage: errorMessage.message,
errorType: errorMessage.type,
},
});
}),
);
}
yield put(deleteErrorLog(action.payload.id));
}
export default function* debuggerSagasListeners() {
yield all([
takeEvery(ReduxActionTypes.DEBUGGER_LOG_INIT, debuggerLogSaga),
@ -345,5 +421,13 @@ export default function* debuggerSagasListeners() {
ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
logDebuggerErrorAnalyticsSaga,
),
takeEvery(
ReduxActionTypes.DEBUGGER_ADD_ERROR_LOG_INIT,
addDebuggerErrorLogSaga,
),
takeEvery(
ReduxActionTypes.DEBUGGER_DELETE_ERROR_LOG_INIT,
deleteDebuggerErrorLogSaga,
),
]);
}

View File

@ -1,4 +1,4 @@
import { ENTITY_TYPE, Message } from "entities/AppsmithConsole";
import { ENTITY_TYPE, Log } from "entities/AppsmithConsole";
import { DataTree } from "entities/DataTree/dataTreeFactory";
import {
DataTreeDiff,
@ -13,16 +13,13 @@ import {
EvaluationError,
getEvalErrorPath,
getEvalValuePath,
PropertyEvalErrorTypeDebugMessage,
PropertyEvaluationErrorType,
} from "utils/DynamicBindingUtils";
import _ from "lodash";
import { find, get, some } 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";
@ -34,6 +31,7 @@ import {
createMessage,
ERROR_EVAL_ERROR_GENERIC,
ERROR_EVAL_TRIGGER,
VALUE_IS_INVALID,
} from "constants/messages";
import log from "loglevel";
import { AppState } from "reducers";
@ -41,17 +39,15 @@ import { getAppMode } from "selectors/applicationSelectors";
import { APP_MODE } from "entities/App";
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>,
function logLatestEvalPropertyErrors(
currentDebuggerErrors: Record<string, Log>,
dataTree: DataTree,
evaluationOrder: Array<string>,
) {
const updatedDebuggerErrors: Record<string, Message> = {
const updatedDebuggerErrors: Record<string, Log> = {
...currentDebuggerErrors,
};
@ -64,12 +60,12 @@ function getLatestEvalPropertyErrors(
if (propertyPath in entity.logBlackList) {
continue;
}
const allEvalErrors: EvaluationError[] = _.get(
const allEvalErrors: EvaluationError[] = get(
entity,
getEvalErrorPath(evaluatedPath, false),
[],
);
const evaluatedValue = _.get(
const evaluatedValue = get(
entity,
getEvalValuePath(evaluatedPath, false),
);
@ -89,23 +85,17 @@ function getLatestEvalPropertyErrors(
if (evalErrors.length) {
// TODO Rank and set the most critical error
const error = evalErrors[0];
const errorMessages = evalErrors.map((e) => ({
message: e.errorMessage,
}));
// const error = evalErrors[0];
// Reformatting eval errors here to a format usable by the debugger
const errorMessages = evalErrors.map((e) => {
// Error format required for the debugger
const formattedError = {
message: e.errorMessage,
type: e.errorType,
};
if (!(debuggerKey in updatedDebuggerErrors)) {
store.dispatch(
logDebuggerErrorAnalytics({
eventName: "DEBUGGER_NEW_ERROR",
entityId: idField,
entityName: nameField,
entityType,
propertyPath,
errorMessages,
}),
);
}
return formattedError;
});
const analyticsData = isWidget(entity)
? {
@ -114,14 +104,13 @@ function getLatestEvalPropertyErrors(
: {};
// Add or update
updatedDebuggerErrors[debuggerKey] = {
AppsmithConsole.addError({
id: debuggerKey,
logType: LOG_TYPE.EVAL_ERROR,
text: PropertyEvalErrorTypeDebugMessage[error.errorType](
propertyPath,
),
// Unless the intention is to change the message shown in the debugger please do not
// change the text shown here
text: createMessage(VALUE_IS_INVALID, propertyPath),
messages: errorMessages,
severity: error.severity,
timestamp: moment().format("hh:mm:ss"),
source: {
id: idField,
name: nameField,
@ -132,25 +121,12 @@ function getLatestEvalPropertyErrors(
[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];
AppsmithConsole.deleteError(debuggerKey);
}
}
}
return updatedDebuggerErrors;
}
export function* evalErrorHandler(
@ -159,19 +135,15 @@ export function* evalErrorHandler(
evaluationOrder?: Array<string>,
): any {
if (dataTree && evaluationOrder) {
const currentDebuggerErrors: Record<string, Message> = yield select(
const currentDebuggerErrors: Record<string, Log> = yield select(
getDebuggerErrors,
);
const evalPropertyErrors = getLatestEvalPropertyErrors(
// Update latest errors to the debugger
logLatestEvalPropertyErrors(
currentDebuggerErrors,
dataTree,
evaluationOrder,
);
yield put({
type: ReduxActionTypes.DEBUGGER_UPDATE_ERROR_LOGS,
payload: evalPropertyErrors,
});
}
errors.forEach((error) => {
@ -260,13 +232,13 @@ export function* logSuccessfulBindings(
);
const entity = dataTree[entityName];
if (isAction(entity) || isWidget(entity)) {
const unevalValue = _.get(unEvalTree, evaluatedPath);
const unevalValue = get(unEvalTree, evaluatedPath);
const entityType = isAction(entity) ? entity.pluginType : entity.type;
const isABinding = _.find(entity.dynamicBindingPathList, {
const isABinding = find(entity.dynamicBindingPathList, {
key: propertyPath,
});
const logBlackList = entity.logBlackList;
const errors: EvaluationError[] = _.get(
const errors: EvaluationError[] = get(
dataTree,
getEvalErrorPath(evaluatedPath),
[],
@ -310,7 +282,7 @@ export function* updateTernDefinitions(
shouldUpdate = false;
} else {
// Only when new field is added or deleted, we want to re create the def
shouldUpdate = _.some(updates, (update) => {
shouldUpdate = some(updates, (update) => {
return (
update.event === DataTreeDiffEvent.NEW ||
update.event === DataTreeDiffEvent.DELETE

View File

@ -545,6 +545,9 @@ export function* deleteAllSelectedWidgetsSaga(
type: ENTITY_TYPE.WIDGET,
id: widget.widgetId,
},
analytics: {
widgetType: widget.type,
},
});
});
}
@ -666,6 +669,9 @@ export function* deleteSaga(deleteAction: ReduxAction<WidgetDelete>) {
type: ENTITY_TYPE.WIDGET,
id: widget.widgetId,
},
analytics: {
widgetType: widget.type,
},
});
});
}

View File

@ -129,6 +129,8 @@ export type EventName =
| "SLASH_COMMAND"
| "DEBUGGER_NEW_ERROR"
| "DEBUGGER_RESOLVED_ERROR"
| "DEBUGGER_NEW_ERROR_MESSAGE"
| "DEBUGGER_RESOLVED_ERROR_MESSAGE"
| "ADD_MOCK_DATASOURCE_CLICK"
| "CREATE_DATA_SOURCE_AUTH_API_CLICK"
| "GEN_CRUD_PAGE_CREATE_NEW_DATASOURCE"

View File

@ -1,10 +1,19 @@
import { debuggerLogInit } from "actions/debuggerActions";
import { Message, Severity, LogActionPayload } from "entities/AppsmithConsole";
import {
addErrorLogInit,
debuggerLogInit,
deleteErrorLogInit,
} from "actions/debuggerActions";
import { ReduxAction } from "constants/ReduxActionConstants";
import { Severity, LogActionPayload, Log } from "entities/AppsmithConsole";
import moment from "moment";
import store from "store";
function log(ev: Message) {
store.dispatch(debuggerLogInit(ev));
function dispatchAction(action: ReduxAction<unknown>) {
store.dispatch(action);
}
function log(ev: Log) {
dispatchAction(debuggerLogInit(ev));
}
function getTimeStamp() {
@ -27,6 +36,9 @@ function warning(ev: LogActionPayload) {
});
}
// This is used to show a log as an error
// NOTE: These logs won't appear in the errors tab
// To add errors to the errors tab use the addError method.
function error(ev: LogActionPayload) {
log({
...ev,
@ -35,8 +47,26 @@ function error(ev: LogActionPayload) {
});
}
// This is used to add an error to the errors tab
function addError(payload: LogActionPayload) {
dispatchAction(
addErrorLogInit({
...payload,
severity: Severity.ERROR,
timestamp: getTimeStamp(),
}),
);
}
// This is used to remove an error from the errors tab
function deleteError(id: string, analytics?: Log["analytics"]) {
dispatchAction(deleteErrorLogInit(id, analytics));
}
export default {
info,
warning,
error,
addError,
deleteError,
};

View File

@ -13,6 +13,10 @@ import {
isPermitted,
PERMISSION_TYPE,
} from "pages/Applications/permissionHelpers";
import { User } from "constants/userConstants";
import { getAppsmithConfigs } from "configs";
const { intercomAppID } = getAppsmithConfigs();
export const snapToGrid = (
columnWidth: number,
@ -397,3 +401,14 @@ export const getIsSafeRedirectURL = (redirectURL: string) => {
return false;
}
};
export function bootIntercom(user?: User) {
if (intercomAppID && window.Intercom) {
window.Intercom("boot", {
app_id: intercomAppID,
user_id: user?.username,
name: user?.name,
email: user?.email,
});
}
}