feat: State Inspector (#38368)

This commit is contained in:
Hetu Nandu 2025-01-03 18:05:09 +05:30 committed by GitHub
parent 28d35ad903
commit c2e4e11eb3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 825 additions and 182 deletions

View File

@ -137,8 +137,8 @@ const GitPullRequest = importRemixIcon(
const GitRepository = importRemixIcon( const GitRepository = importRemixIcon(
async () => import("remixicon-react/GitRepositoryLineIcon"), async () => import("remixicon-react/GitRepositoryLineIcon"),
); );
const GlobalLineIcon = importRemixIcon( const GlobalLineIcon = importSvg(
async () => import("remixicon-react/GlobalLineIcon"), async () => import("../__assets__/icons/ads/globe-simple.svg"),
); );
const GuideIcon = importRemixIcon( const GuideIcon = importRemixIcon(
async () => import("remixicon-react/GuideFillIcon"), async () => import("remixicon-react/GuideFillIcon"),

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 2.5C4.96243 2.5 2.5 4.96243 2.5 8C2.5 11.0376 4.96243 13.5 8 13.5C11.0376 13.5 13.5 11.0376 13.5 8C13.5 4.96243 11.0376 2.5 8 2.5ZM1.5 8C1.5 4.41015 4.41015 1.5 8 1.5C11.5899 1.5 14.5 4.41015 14.5 8C14.5 11.5899 11.5899 14.5 8 14.5C4.41015 14.5 1.5 11.5899 1.5 8Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 8C1.5 7.72386 1.72386 7.5 2 7.5H14C14.2761 7.5 14.5 7.72386 14.5 8C14.5 8.27614 14.2761 8.5 14 8.5H2C1.72386 8.5 1.5 8.27614 1.5 8Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.69186 4.0692C6.27193 5.04973 6 6.43917 6 8.0001C6 9.56103 6.27193 10.9505 6.69186 11.931C6.9022 12.4222 7.13983 12.7877 7.37823 13.0231C7.61449 13.2564 7.82403 13.3376 8 13.3376C8.17597 13.3376 8.38551 13.2564 8.62177 13.0231C8.86017 12.7877 9.0978 12.4222 9.30814 11.931C9.72807 10.9505 10 9.56103 10 8.0001C10 6.43917 9.72807 5.04973 9.30814 4.0692C9.0978 3.57804 8.86017 3.21253 8.62177 2.97709C8.38551 2.74375 8.17597 2.6626 8 2.6626C7.82403 2.6626 7.61449 2.74375 7.37823 2.97709C7.13983 3.21253 6.9022 3.57804 6.69186 4.0692ZM6.67554 2.26559C7.03748 1.90814 7.48561 1.6626 8 1.6626C8.51439 1.6626 8.96253 1.90814 9.32446 2.26559C9.68425 2.62093 9.98533 3.1103 10.2274 3.67552C10.7123 4.80775 11 6.33707 11 8.0001C11 9.66313 10.7123 11.1924 10.2274 12.3247C9.98533 12.8899 9.68425 13.3793 9.32446 13.7346C8.96252 14.0921 8.51439 14.3376 8 14.3376C7.48561 14.3376 7.03748 14.0921 6.67554 13.7346C6.31575 13.3793 6.01467 12.8899 5.77261 12.3247C5.28771 11.1924 5 9.66313 5 8.0001C5 6.33707 5.28771 4.80775 5.77261 3.67552C6.01467 3.1103 6.31575 2.62093 6.67554 2.26559Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -28,6 +28,7 @@ const Container = styled.div<{ displayMode: ViewDisplayMode }>`
const ViewWrapper = styled.div` const ViewWrapper = styled.div`
height: 100%; height: 100%;
&&& { &&& {
ul.ads-v2-tabs__list { ul.ads-v2-tabs__list {
margin: 0 var(--ads-v2-spaces-8); margin: 0 var(--ads-v2-spaces-8);
@ -39,6 +40,7 @@ const ViewWrapper = styled.div`
.ads-v2-tabs__list { .ads-v2-tabs__list {
padding: var(--ads-v2-spaces-1) var(--ads-v2-spaces-7); padding: var(--ads-v2-spaces-1) var(--ads-v2-spaces-7);
padding-left: var(--ads-v2-spaces-3); padding-left: var(--ads-v2-spaces-3);
user-select: none;
} }
} }

View File

@ -7,6 +7,7 @@ import type {
} from "reducers/uiReducers/debuggerReducer"; } from "reducers/uiReducers/debuggerReducer";
import type { EventName } from "ee/utils/analyticsUtilTypes"; import type { EventName } from "ee/utils/analyticsUtilTypes";
import type { APP_MODE } from "entities/App"; import type { APP_MODE } from "entities/App";
import type { GenericEntityItem } from "ee/entities/IDE/constants";
export interface LogDebuggerErrorAnalyticsPayload { export interface LogDebuggerErrorAnalyticsPayload {
entityName: string; entityName: string;
@ -147,3 +148,12 @@ export const showDebuggerLogs = () => {
type: ReduxActionTypes.SHOW_DEBUGGER_LOGS, type: ReduxActionTypes.SHOW_DEBUGGER_LOGS,
}; };
}; };
export const setDebuggerStateInspectorSelectedItem = (
payload: GenericEntityItem,
) => {
return {
type: ReduxActionTypes.SET_DEBUGGER_STATE_INSPECTOR_SELECTED_ITEM,
payload,
};
};

View File

@ -1,6 +1,8 @@
import type { ReduxAction } from "ee/constants/ReduxActionConstants"; import {
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants"; type ReduxAction,
import type { JSCollection, JSAction } from "entities/JSCollection"; ReduxActionTypes,
} from "ee/constants/ReduxActionConstants";
import type { JSAction, JSCollection } from "entities/JSCollection";
import type { import type {
RefactorAction, RefactorAction,
SetFunctionPropertyPayload, SetFunctionPropertyPayload,
@ -10,6 +12,7 @@ import type {
JSEditorTab, JSEditorTab,
JSPaneDebuggerState, JSPaneDebuggerState,
} from "reducers/uiReducers/jsPaneReducer"; } from "reducers/uiReducers/jsPaneReducer";
import type { JSUpdate } from "../utils/JSPaneUtils";
export const createNewJSCollection = ( export const createNewJSCollection = (
pageId: string, pageId: string,
@ -132,3 +135,10 @@ export const setJsPaneDebuggerState = (
type: ReduxActionTypes.SET_JS_PANE_DEBUGGER_STATE, type: ReduxActionTypes.SET_JS_PANE_DEBUGGER_STATE,
payload, payload,
}); });
export const executeJSUpdates = (
payload: Record<string, JSUpdate>,
): ReduxAction<unknown> => ({
type: ReduxActionTypes.EXECUTE_JS_UPDATES,
payload,
});

View File

@ -6,7 +6,6 @@ import {
ReduxActionErrorTypes, ReduxActionErrorTypes,
ReduxActionTypes, ReduxActionTypes,
} from "ee/constants/ReduxActionConstants"; } from "ee/constants/ReduxActionConstants";
import type { JSUpdate } from "utils/JSPaneUtils";
import type { import type {
Action, Action,
ActionViewMode, ActionViewMode,
@ -343,13 +342,6 @@ export const executePageLoadActions = (
}; };
}; };
export const executeJSUpdates = (
payload: Record<string, JSUpdate>,
): ReduxAction<unknown> => ({
type: ReduxActionTypes.EXECUTE_JS_UPDATES,
payload,
});
export const setActionsToExecuteOnPageLoad = ( export const setActionsToExecuteOnPageLoad = (
actions: Array<{ actions: Array<{
executeOnLoad: boolean; executeOnLoad: boolean;
@ -399,6 +391,7 @@ export interface updateActionDataPayloadType {
actionDataPayload: actionDataPayload; actionDataPayload: actionDataPayload;
parentSpan?: Span; parentSpan?: Span;
} }
export const updateActionData = ( export const updateActionData = (
payload: actionDataPayload, payload: actionDataPayload,
parentSpan?: Span, parentSpan?: Span,

View File

@ -3,7 +3,7 @@ import { usePluginActionContext } from "PluginActionEditor/PluginActionContext";
import type { BottomTab } from "components/editorComponents/EntityBottomTabs"; import type { BottomTab } from "components/editorComponents/EntityBottomTabs";
import { getIDEViewMode } from "selectors/ideSelectors"; import { getIDEViewMode } from "selectors/ideSelectors";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { EditorViewMode } from "ee/entities/IDE/constants"; import { EditorViewMode, IDE_TYPE } from "ee/entities/IDE/constants";
import { DEBUGGER_TAB_KEYS } from "components/editorComponents/Debugger/constants"; import { DEBUGGER_TAB_KEYS } from "components/editorComponents/Debugger/constants";
import { import {
createMessage, createMessage,
@ -11,6 +11,7 @@ import {
DEBUGGER_HEADERS, DEBUGGER_HEADERS,
DEBUGGER_LOGS, DEBUGGER_LOGS,
DEBUGGER_RESPONSE, DEBUGGER_RESPONSE,
DEBUGGER_STATE,
} from "ee/constants/messages"; } from "ee/constants/messages";
import ErrorLogs from "components/editorComponents/Debugger/Errors"; import ErrorLogs from "components/editorComponents/Debugger/Errors";
import DebuggerLogs from "components/editorComponents/Debugger/DebuggerLogs"; import DebuggerLogs from "components/editorComponents/Debugger/DebuggerLogs";
@ -32,6 +33,9 @@ import {
} from "PluginActionEditor/hooks"; } from "PluginActionEditor/hooks";
import useDebuggerTriggerClick from "components/editorComponents/Debugger/hooks/useDebuggerTriggerClick"; import useDebuggerTriggerClick from "components/editorComponents/Debugger/hooks/useDebuggerTriggerClick";
import { Response } from "PluginActionEditor/components/PluginActionResponse/components/Response"; import { Response } from "PluginActionEditor/components/PluginActionResponse/components/Response";
import { StateInspector } from "components/editorComponents/Debugger/StateInspector";
import { useLocation } from "react-router";
import { getIDETypeByUrl } from "ee/entities/IDE/utils";
function usePluginActionResponseTabs() { function usePluginActionResponseTabs() {
const { action, actionResponse, datasource, plugin } = const { action, actionResponse, datasource, plugin } =
@ -108,7 +112,6 @@ function usePluginActionResponseTabs() {
if ( if (
[ [
PluginType.DB, PluginType.DB,
PluginType.AI,
PluginType.REMOTE, PluginType.REMOTE,
PluginType.SAAS, PluginType.SAAS,
PluginType.INTERNAL, PluginType.INTERNAL,
@ -145,6 +148,10 @@ function usePluginActionResponseTabs() {
}); });
} }
const location = useLocation();
const ideType = getIDETypeByUrl(location.pathname);
if (IDEViewMode === EditorViewMode.FullScreen) { if (IDEViewMode === EditorViewMode.FullScreen) {
tabs.push( tabs.push(
{ {
@ -159,6 +166,14 @@ function usePluginActionResponseTabs() {
panelComponent: <ErrorLogs />, panelComponent: <ErrorLogs />,
}, },
); );
if (ideType === IDE_TYPE.App) {
tabs.push({
key: DEBUGGER_TAB_KEYS.STATE_TAB,
title: createMessage(DEBUGGER_STATE),
panelComponent: <StateInspector />,
});
}
} }
return tabs; return tabs;

View File

@ -697,6 +697,8 @@ const IDEDebuggerActionTypes = {
SET_JS_PANE_DEBUGGER_STATE: "SET_JS_PANE_DEBUGGER_STATE", SET_JS_PANE_DEBUGGER_STATE: "SET_JS_PANE_DEBUGGER_STATE",
SET_CANVAS_DEBUGGER_STATE: "SET_CANVAS_DEBUGGER_STATE", SET_CANVAS_DEBUGGER_STATE: "SET_CANVAS_DEBUGGER_STATE",
SHOW_DEBUGGER_LOGS: "SHOW_DEBUGGER_LOGS", SHOW_DEBUGGER_LOGS: "SHOW_DEBUGGER_LOGS",
SET_DEBUGGER_STATE_INSPECTOR_SELECTED_ITEM:
"SET_DEBUGGER_STATE_INSPECTOR_SELECTED_ITEM",
}; };
const ThemeActionTypes = { const ThemeActionTypes = {

View File

@ -564,6 +564,7 @@ export const DEBUGGER_ERRORS = () => "Linter";
export const DEBUGGER_RESPONSE = () => "Response"; export const DEBUGGER_RESPONSE = () => "Response";
export const DEBUGGER_HEADERS = () => "Headers"; export const DEBUGGER_HEADERS = () => "Headers";
export const DEBUGGER_LOGS = () => "Logs"; export const DEBUGGER_LOGS = () => "Logs";
export const DEBUGGER_STATE = () => "State";
export const INSPECT_ENTITY = () => "Inspect entity"; export const INSPECT_ENTITY = () => "Inspect entity";
export const INSPECT_ENTITY_BLANK_STATE = () => "Select an entity to inspect"; export const INSPECT_ENTITY_BLANK_STATE = () => "Select an entity to inspect";

View File

@ -129,6 +129,8 @@ export interface EntityItem {
userPermissions?: string[]; userPermissions?: string[];
} }
export interface GenericEntityItem extends Omit<EntityItem, "type"> {}
export type UseRoutes = Array<{ export type UseRoutes = Array<{
key: string; key: string;
// TODO: Fix this the next time the file is edited // TODO: Fix this the next time the file is edited

View File

@ -24,7 +24,6 @@ export const JSListItem = (props: JSListItemProps) => {
parentEntityType={parentEntityType} parentEntityType={parentEntityType}
searchKeyword={""} searchKeyword={""}
step={1} step={1}
type={item.type}
/> />
</Flex> </Flex>
); );

View File

@ -23,7 +23,10 @@ import {
import { countBy, find, get, groupBy, keyBy, sortBy } from "lodash"; import { countBy, find, get, groupBy, keyBy, sortBy } from "lodash";
import ImageAlt from "assets/images/placeholder-image.svg"; import ImageAlt from "assets/images/placeholder-image.svg";
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer"; import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants"; import {
MAIN_CONTAINER_WIDGET_ID,
MAIN_CONTAINER_WIDGET_NAME,
} from "constants/WidgetConstants";
import type { AppStoreState } from "reducers/entityReducers/appReducer"; import type { AppStoreState } from "reducers/entityReducers/appReducer";
import type { import type {
JSCollectionData, JSCollectionData,
@ -59,10 +62,15 @@ import {
import { MAX_DATASOURCE_SUGGESTIONS } from "constants/DatasourceEditorConstants"; import { MAX_DATASOURCE_SUGGESTIONS } from "constants/DatasourceEditorConstants";
import type { CreateNewActionKeyInterface } from "ee/entities/Engine/actionHelpers"; import type { CreateNewActionKeyInterface } from "ee/entities/Engine/actionHelpers";
import { getNextEntityName } from "utils/AppsmithUtils"; import { getNextEntityName } from "utils/AppsmithUtils";
import { EditorEntityTab, type EntityItem } from "ee/entities/IDE/constants"; import {
EditorEntityTab,
type EntityItem,
type GenericEntityItem,
} from "ee/entities/IDE/constants";
import { import {
ActionUrlIcon, ActionUrlIcon,
JsFileIconV2, JsFileIconV2,
WidgetIconByType,
} from "pages/Editor/Explorer/ExplorerIcons"; } from "pages/Editor/Explorer/ExplorerIcons";
import { getAssetUrl } from "ee/utils/airgapHelpers"; import { getAssetUrl } from "ee/utils/airgapHelpers";
import { import {
@ -979,6 +987,18 @@ export const getAllPageWidgets = createSelector(
}, },
); );
export const getUISegmentItems = createSelector(getCanvasWidgets, (widgets) => {
const items: GenericEntityItem[] = Object.values(widgets)
.filter((widget) => widget.widgetName !== MAIN_CONTAINER_WIDGET_NAME)
.map((widget) => ({
icon: WidgetIconByType(widget.type),
title: widget.widgetName,
key: widget.widgetId,
}));
return items;
});
export const getPageList = createSelector( export const getPageList = createSelector(
(state: AppState) => state.entities.pageList.pages, (state: AppState) => state.entities.pageList.pages,
(pages) => pages, (pages) => pages,

View File

@ -1,4 +1,4 @@
import React, { useCallback } from "react"; import React, { useCallback, useMemo } from "react";
import DebuggerLogs from "./DebuggerLogs"; import DebuggerLogs from "./DebuggerLogs";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { import {
@ -17,11 +17,16 @@ import {
createMessage, createMessage,
DEBUGGER_ERRORS, DEBUGGER_ERRORS,
DEBUGGER_LOGS, DEBUGGER_LOGS,
DEBUGGER_STATE,
} from "ee/constants/messages"; } from "ee/constants/messages";
import { DEBUGGER_TAB_KEYS } from "./constants"; import { DEBUGGER_TAB_KEYS } from "./constants";
import EntityBottomTabs from "../EntityBottomTabs"; import EntityBottomTabs from "../EntityBottomTabs";
import { ActionExecutionResizerHeight } from "PluginActionEditor/components/PluginActionResponse/constants"; import { ActionExecutionResizerHeight } from "PluginActionEditor/components/PluginActionResponse/constants";
import { IDEBottomView, ViewHideBehaviour, ViewDisplayMode } from "IDE"; import { IDEBottomView, ViewHideBehaviour, ViewDisplayMode } from "IDE";
import { StateInspector } from "./StateInspector";
import { getIDETypeByUrl } from "ee/entities/IDE/utils";
import { useLocation } from "react-router";
import { IDE_TYPE } from "ee/entities/IDE/constants";
function DebuggerTabs() { function DebuggerTabs() {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -31,10 +36,15 @@ function DebuggerTabs() {
// get the height of the response pane. // get the height of the response pane.
const responsePaneHeight = useSelector(getResponsePaneHeight); const responsePaneHeight = useSelector(getResponsePaneHeight);
// set the height of the response pane. // set the height of the response pane.
const updateResponsePaneHeight = useCallback((height: number) => { const updateResponsePaneHeight = useCallback(
(height: number) => {
dispatch(setResponsePaneHeight(height)); dispatch(setResponsePaneHeight(height));
}, []); },
const setSelectedTab = (tabKey: string) => { [dispatch],
);
const setSelectedTab = useCallback(
(tabKey: string) => {
if (tabKey === DEBUGGER_TAB_KEYS.ERROR_TAB) { if (tabKey === DEBUGGER_TAB_KEYS.ERROR_TAB) {
AnalyticsUtil.logEvent("OPEN_DEBUGGER", { AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
source: "WIDGET_EDITOR", source: "WIDGET_EDITOR",
@ -42,10 +52,20 @@ function DebuggerTabs() {
} }
dispatch(setDebuggerSelectedTab(tabKey)); dispatch(setDebuggerSelectedTab(tabKey));
}; },
const onClose = () => dispatch(showDebugger(false)); [dispatch],
);
const DEBUGGER_TABS = [ const onClose = useCallback(() => {
dispatch(showDebugger(false));
}, [dispatch]);
const location = useLocation();
const ideType = getIDETypeByUrl(location.pathname);
const DEBUGGER_TABS = useMemo(() => {
const tabs = [
{ {
key: DEBUGGER_TAB_KEYS.LOGS_TAB, key: DEBUGGER_TAB_KEYS.LOGS_TAB,
title: createMessage(DEBUGGER_LOGS), title: createMessage(DEBUGGER_LOGS),
@ -59,6 +79,17 @@ function DebuggerTabs() {
}, },
]; ];
if (ideType === IDE_TYPE.App) {
tabs.push({
key: DEBUGGER_TAB_KEYS.STATE_TAB,
title: createMessage(DEBUGGER_STATE),
panelComponent: <StateInspector />,
});
}
return tabs;
}, [errorCount, ideType]);
// Do not render if response, header or schema tab is selected in the bottom bar. // Do not render if response, header or schema tab is selected in the bottom bar.
const shouldRender = !( const shouldRender = !(
selectedTab === DEBUGGER_TAB_KEYS.RESPONSE_TAB || selectedTab === DEBUGGER_TAB_KEYS.RESPONSE_TAB ||

View File

@ -0,0 +1,143 @@
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom";
import { StateInspector } from "./StateInspector";
import { useStateInspectorItems } from "./hooks";
import { filterEntityGroupsBySearchTerm } from "IDE/utils";
jest.mock("./hooks");
jest.mock("IDE/utils");
const mockedUseStateInspectorItems = useStateInspectorItems as jest.Mock;
const mockedFilterEntityGroupsBySearchTerm =
filterEntityGroupsBySearchTerm as jest.Mock;
describe("StateInspector", () => {
beforeEach(() => {
mockedFilterEntityGroupsBySearchTerm.mockImplementation(
(searchTerm, items) =>
items.filter((item: { group: string }) =>
item.group.toLowerCase().includes(searchTerm.toLowerCase()),
),
);
});
it("renders search input and filters items based on search term", () => {
mockedUseStateInspectorItems.mockReturnValue([
{ title: "Item 1", icon: "icon1", code: { key: "value1" } },
[
{ group: "Group 1", items: [{ title: "Item 1" }] },
{ group: "Group 2", items: [{ title: "Item 2" }] },
],
{ key: "value1" },
]);
render(<StateInspector />);
const searchInput = screen.getByPlaceholderText("Search entities");
fireEvent.change(searchInput, { target: { value: "Group 1" } });
expect(screen.getByText("Group 1")).toBeInTheDocument();
expect(screen.queryByText("Group 2")).not.toBeInTheDocument();
});
it("Calls the onClick of the item", () => {
const mockOnClick = jest.fn();
mockedUseStateInspectorItems.mockReturnValue([
{ title: "Item 1", icon: "icon1", code: { key: "value1" } },
[
{ group: "Group 1", items: [{ title: "Item 1" }] },
{
group: "Group 2",
items: [{ title: "Item 2", onClick: mockOnClick }],
},
],
{ key: "value1" },
]);
render(<StateInspector />);
fireEvent.click(screen.getByText("Item 2"));
expect(mockOnClick).toHaveBeenCalled();
});
it("Renders the selected item details", () => {
mockedUseStateInspectorItems.mockReturnValue([
{ title: "Item 1", icon: "icon1", code: { key: "value1" } },
[
{ group: "Group 1", items: [{ title: "Item 1" }] },
{
group: "Group 2",
items: [{ title: "Item 2" }],
},
],
{ key: "Value1" },
]);
render(<StateInspector />);
expect(
screen.getByTestId("t--selected-entity-details").textContent,
).toContain("Item 1");
expect(
screen.getByTestId("t--selected-entity-details").textContent,
).toContain("Value1");
});
it("does not render selected item details when no item is selected", () => {
mockedUseStateInspectorItems.mockReturnValue([null, [], null]);
render(<StateInspector />);
expect(screen.queryByText("Item 1")).not.toBeInTheDocument();
});
it("renders all items when search term is empty", () => {
mockedUseStateInspectorItems.mockReturnValue([
{ title: "Item 1", icon: "icon1", code: { key: "value1" } },
[
{ group: "Group 1", items: [{ title: "Item 1" }] },
{
group: "Group 2",
items: [{ title: "Item 2" }],
},
],
{ key: "value1" },
]);
render(<StateInspector />);
expect(screen.getByText("Group 1")).toBeInTheDocument();
expect(screen.getByText("Group 2")).toBeInTheDocument();
});
it("renders no items when search term does not match any group", () => {
render(<StateInspector />);
const searchInput = screen.getByPlaceholderText("Search entities");
fireEvent.change(searchInput, { target: { value: "Nonexistent Group" } });
expect(screen.queryByText("Group 1")).not.toBeInTheDocument();
expect(screen.queryByText("Group 2")).not.toBeInTheDocument();
});
it("renders no items when items list is empty", () => {
mockedUseStateInspectorItems.mockReturnValue([null, [], null]);
render(<StateInspector />);
expect(screen.queryByText("Group 1")).not.toBeInTheDocument();
expect(screen.queryByText("Group 2")).not.toBeInTheDocument();
});
it("renders correctly when selected item has no code", () => {
mockedUseStateInspectorItems.mockReturnValue([
{ title: "Item 1", icon: "icon1", code: null },
[
{ group: "Group 1", items: [{ title: "Item 1" }] },
{ group: "Group 2", items: [{ title: "Item 2" }] },
],
{},
]);
render(<StateInspector />);
expect(
screen.getByTestId("t--selected-entity-details").textContent,
).toContain("Item 1");
expect(
screen.getByTestId("t--selected-entity-details").textContent,
).toContain("0 items");
});
});

View File

@ -0,0 +1,101 @@
import React, { useState } from "react";
import ReactJson from "react-json-view";
import {
Flex,
List,
type ListItemProps,
SearchInput,
Text,
} from "@appsmith/ads";
import { filterEntityGroupsBySearchTerm } from "IDE/utils";
import { useStateInspectorItems } from "./hooks";
import * as Styled from "./styles";
export const reactJsonProps = {
name: null,
enableClipboard: false,
displayDataTypes: false,
displayArrayKey: true,
quotesOnKeys: false,
style: {
fontSize: "12px",
},
collapsed: 1,
indentWidth: 2,
collapseStringsAfterLength: 30,
};
export const StateInspector = () => {
const [selectedItem, items, selectedItemCode] = useStateInspectorItems();
const [searchTerm, setSearchTerm] = useState("");
const filteredItemGroups = filterEntityGroupsBySearchTerm<
{ group: string },
ListItemProps
>(searchTerm, items);
return (
<Flex h="calc(100% - 40px)" overflow="hidden" w="100%">
<Flex
borderRight="1px solid var(--ads-v2-color-border)"
flexDirection="column"
h="100%"
overflowY="hidden"
w="400px"
>
<Flex p="spaces-3">
<SearchInput
onChange={setSearchTerm}
placeholder="Search entities"
value={searchTerm}
/>
</Flex>
<Flex
flexDirection="column"
gap="spaces-3"
overflowY="auto"
pl="spaces-3"
pr="spaces-3"
>
{filteredItemGroups.map((item) => (
<Styled.Group
flexDirection="column"
gap="spaces-2"
key={item.group}
>
<Styled.GroupName
className="overflow-hidden overflow-ellipsis whitespace-nowrap flex-shrink-0"
kind="body-s"
>
{item.group}
</Styled.GroupName>
<List items={item.items} />
</Styled.Group>
))}
</Flex>
</Flex>
{selectedItem ? (
<Flex
className="mp-mask"
data-testid="t--selected-entity-details"
flex="1"
flexDirection="column"
overflowY="hidden"
>
<Styled.SelectedItem
alignItems="center"
flexDirection="row"
gap="spaces-2"
p="spaces-3"
>
{selectedItem.icon}
<Text kind="body-m">{selectedItem.title}</Text>
</Styled.SelectedItem>
<Flex overflowY="auto" px="spaces-3">
<ReactJson src={selectedItemCode} {...reactJsonProps} />
</Flex>
</Flex>
) : null}
</Flex>
);
};

View File

@ -0,0 +1 @@
export { useStateInspectorItems } from "./useStateInspectorItems";

View File

@ -0,0 +1,20 @@
import { GlobeIcon } from "pages/Editor/Explorer/ExplorerIcons";
import type { GetGroupHookType } from "../types";
export const useGetGlobalItemsForStateInspector: GetGroupHookType = () => {
const appsmithObj = {
key: "appsmith",
title: "appsmith",
icon: GlobeIcon(),
};
const appsmithItems = [
{
id: appsmithObj.key,
title: appsmithObj.title,
startIcon: appsmithObj.icon,
},
];
return { group: "Globals", items: appsmithItems };
};

View File

@ -0,0 +1,15 @@
import { useSelector } from "react-redux";
import { getJSSegmentItems } from "ee/selectors/entitiesSelector";
import type { GetGroupHookType } from "../types";
export const useGetJSItemsForStateInspector: GetGroupHookType = () => {
const jsObjects = useSelector(getJSSegmentItems);
const jsItems = jsObjects.map((jsObject) => ({
id: jsObject.key,
title: jsObject.title,
startIcon: jsObject.icon,
}));
return { group: "JS objects", items: jsItems };
};

View File

@ -0,0 +1,16 @@
import { useSelector } from "react-redux";
import { getQuerySegmentItems } from "ee/selectors/entitiesSelector";
import type { GetGroupHookType } from "../types";
export const useGetQueryItemsForStateInspector: GetGroupHookType = () => {
const queries = useSelector(getQuerySegmentItems);
const queryItems = queries.map((query) => ({
id: query.key,
title: query.title,
startIcon: query.icon,
className: "query-item",
}));
return { group: "Queries", items: queryItems };
};

View File

@ -0,0 +1,15 @@
import { useSelector } from "react-redux";
import { getUISegmentItems } from "ee/selectors/entitiesSelector";
import type { GetGroupHookType } from "../types";
export const useGetUIItemsForStateInspector: GetGroupHookType = () => {
const widgets = useSelector(getUISegmentItems);
const widgetItems = widgets.map((widget) => ({
id: widget.key,
title: widget.title,
startIcon: widget.icon,
}));
return { group: "UI elements", items: widgetItems };
};

View File

@ -0,0 +1,100 @@
import { useEffect, useMemo } from "react";
import { useStateInspectorState } from "./useStateInspectorState";
import { useGetGlobalItemsForStateInspector } from "./useGetGlobalItemsForStateInspector";
import { useGetQueryItemsForStateInspector } from "./useGetQueryItemsForStateInspector";
import { useGetJSItemsForStateInspector } from "./useGetJSItemsForStateInspector";
import { useGetUIItemsForStateInspector } from "./useGetUIItemsForStateInspector";
import type { GroupedItems } from "../types";
import { enhanceItemForListItem } from "../utils";
import type { GenericEntityItem } from "ee/entities/IDE/constants";
import { filterInternalProperties } from "utils/FilterInternalProperties";
import { getConfigTree, getDataTree } from "selectors/dataTreeSelectors";
import { getJSCollections } from "ee/selectors/entitiesSelector";
import { useSelector } from "react-redux";
export const useStateInspectorItems: () => [
GenericEntityItem | undefined,
GroupedItems[],
unknown,
] = () => {
const [selectedItem, setSelectedItem] = useStateInspectorState();
const queries = useGetQueryItemsForStateInspector();
const jsItems = useGetJSItemsForStateInspector();
const uiItems = useGetUIItemsForStateInspector();
const globalItems = useGetGlobalItemsForStateInspector();
const groups = useMemo(() => {
const returnValue: GroupedItems[] = [];
if (queries.items.length) {
returnValue.push({
...queries,
items: queries.items.map((query) =>
enhanceItemForListItem(query, selectedItem, setSelectedItem),
),
});
}
if (jsItems.items.length) {
returnValue.push({
...jsItems,
items: jsItems.items.map((jsItem) =>
enhanceItemForListItem(jsItem, selectedItem, setSelectedItem),
),
});
}
if (uiItems.items.length) {
returnValue.push({
...uiItems,
items: uiItems.items.map((uiItem) =>
enhanceItemForListItem(uiItem, selectedItem, setSelectedItem),
),
});
}
if (globalItems.items.length) {
returnValue.push({
...globalItems,
items: globalItems.items.map((globalItem) =>
enhanceItemForListItem(globalItem, selectedItem, setSelectedItem),
),
});
}
return returnValue;
}, [globalItems, jsItems, queries, selectedItem, setSelectedItem, uiItems]);
const dataTree = useSelector(getDataTree);
const configTree = useSelector(getConfigTree);
const jsActions = useSelector(getJSCollections);
let filteredData: unknown = "";
if (selectedItem && selectedItem.title in dataTree) {
filteredData = filterInternalProperties(
selectedItem.title,
dataTree[selectedItem.title],
jsActions,
dataTree,
configTree,
);
}
useEffect(
function handleNoItemSelected() {
if (!selectedItem || !(selectedItem.title in dataTree)) {
const firstItem = groups[0].items[0];
setSelectedItem({
key: firstItem.id as string,
icon: firstItem.startIcon,
title: firstItem.title,
});
}
},
[dataTree, groups, selectedItem, setSelectedItem],
);
return [selectedItem, groups, filteredData];
};

View File

@ -0,0 +1,19 @@
import { useDispatch, useSelector } from "react-redux";
import type { GenericEntityItem } from "ee/entities/IDE/constants";
import { setDebuggerStateInspectorSelectedItem } from "actions/debuggerActions";
import { getDebuggerStateInspectorSelectedItem } from "selectors/debuggerSelectors";
export const useStateInspectorState: () => [
GenericEntityItem | undefined,
(item: GenericEntityItem) => void,
] = () => {
const dispatch = useDispatch();
const setSelectedItem = (item: GenericEntityItem) => {
dispatch(setDebuggerStateInspectorSelectedItem(item));
};
const selectedItem = useSelector(getDebuggerStateInspectorSelectedItem);
return [selectedItem, setSelectedItem];
};

View File

@ -0,0 +1 @@
export { StateInspector } from "./StateInspector";

View File

@ -0,0 +1,23 @@
import styled, { css } from "styled-components";
import { Flex, Text } from "@appsmith/ads";
const imgSizer = css`
img {
height: 16px;
width: 16px;
}
`;
export const Group = styled(Flex)`
.query-item {
${imgSizer}
}
`;
export const GroupName = styled(Text)`
padding: var(--ads-v2-spaces-1) var(--ads-v2-spaces-3);
`;
export const SelectedItem = styled(Flex)`
${imgSizer}
`;

View File

@ -0,0 +1,15 @@
import type { ListItemProps } from "@appsmith/ads";
export interface GroupedItems {
group: string;
items: ListItemProps[];
}
export interface ListItemWithoutOnClick extends Omit<ListItemProps, "onClick"> {
id: string;
}
export type GetGroupHookType = () => {
group: string;
items: ListItemWithoutOnClick[];
};

View File

@ -0,0 +1,21 @@
import type { ListItemWithoutOnClick } from "./types";
import type { ListItemProps } from "@appsmith/ads";
import type { GenericEntityItem } from "ee/entities/IDE/constants";
export const enhanceItemForListItem = (
item: ListItemWithoutOnClick,
selectedItem: GenericEntityItem | undefined,
setSelectedItem: (item: GenericEntityItem) => void,
): ListItemProps => {
return {
...item,
isSelected: selectedItem ? selectedItem.key === item.id : false,
onClick: () =>
setSelectedItem({
key: item.id,
title: item.title,
icon: item.startIcon,
}),
size: "md",
};
};

View File

@ -5,4 +5,5 @@ export enum DEBUGGER_TAB_KEYS {
HEADER_TAB = "HEADERS_TAB", HEADER_TAB = "HEADERS_TAB",
ERROR_TAB = "ERROR_TAB", ERROR_TAB = "ERROR_TAB",
LOGS_TAB = "LOGS_TAB", LOGS_TAB = "LOGS_TAB",
STATE_TAB = "STATE_TAB",
} }

View File

@ -1,15 +1,12 @@
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
import { connect, useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import type { RouteComponentProps } from "react-router";
import { withRouter } from "react-router";
import styled from "styled-components"; import styled from "styled-components";
import type { AppState } from "ee/reducers";
import type { JSEditorRouteParams } from "constants/routes";
import { import {
createMessage, createMessage,
DEBUGGER_ERRORS, DEBUGGER_ERRORS,
DEBUGGER_LOGS, DEBUGGER_LOGS,
DEBUGGER_RESPONSE, DEBUGGER_RESPONSE,
DEBUGGER_STATE,
EXECUTING_FUNCTION, EXECUTING_FUNCTION,
NO_JS_FUNCTION_RETURN_VALUE, NO_JS_FUNCTION_RETURN_VALUE,
UPDATING_JS_COLLECTION, UPDATING_JS_COLLECTION,
@ -35,11 +32,15 @@ import {
import { getJsPaneDebuggerState } from "selectors/jsPaneSelectors"; import { getJsPaneDebuggerState } from "selectors/jsPaneSelectors";
import { setJsPaneDebuggerState } from "actions/jsPaneActions"; import { setJsPaneDebuggerState } from "actions/jsPaneActions";
import { getIDEViewMode } from "selectors/ideSelectors"; import { getIDEViewMode } from "selectors/ideSelectors";
import { EditorViewMode } from "ee/entities/IDE/constants"; import { EditorViewMode, IDE_TYPE } from "ee/entities/IDE/constants";
import ErrorLogs from "./Debugger/Errors"; import ErrorLogs from "./Debugger/Errors";
import { isBrowserExecutionAllowed } from "ee/utils/actionExecutionUtils"; import { isBrowserExecutionAllowed } from "ee/utils/actionExecutionUtils";
import JSRemoteExecutionView from "ee/components/JSRemoteExecutionView"; import JSRemoteExecutionView from "ee/components/JSRemoteExecutionView";
import { IDEBottomView, ViewHideBehaviour } from "IDE"; import { IDEBottomView, ViewHideBehaviour } from "IDE";
import { StateInspector } from "./Debugger/StateInspector";
import { getErrorCount } from "selectors/debuggerSelectors";
import { getIDETypeByUrl } from "ee/entities/IDE/utils";
import { useLocation } from "react-router";
const ResponseTabWrapper = styled.div` const ResponseTabWrapper = styled.div`
display: flex; display: flex;
@ -61,12 +62,7 @@ const NoReturnValueWrapper = styled.div`
padding-top: ${(props) => props.theme.spaces[6]}px; padding-top: ${(props) => props.theme.spaces[6]}px;
`; `;
interface ReduxStateProps { interface Props {
errorCount: number;
}
type Props = ReduxStateProps &
RouteComponentProps<JSEditorRouteParams> & {
currentFunction: JSAction | null; currentFunction: JSAction | null;
theme?: EditorTheme; theme?: EditorTheme;
errors: Array<EvaluationError>; errors: Array<EvaluationError>;
@ -75,29 +71,45 @@ type Props = ReduxStateProps &
onButtonClick: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void; onButtonClick: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
jsCollectionData: JSCollectionData | undefined; jsCollectionData: JSCollectionData | undefined;
debuggerLogsDefaultName?: string; debuggerLogsDefaultName?: string;
}; }
function JSResponseView(props: Props) { function JSResponseView(props: Props) {
const { const {
currentFunction, currentFunction,
disabled, disabled,
errorCount,
errors, errors,
isLoading, isLoading,
jsCollectionData, jsCollectionData,
onButtonClick, onButtonClick,
theme,
} = props; } = props;
const [responseStatus, setResponseStatus] = useState<JSResponseState>( const [responseStatus, setResponseStatus] = useState<JSResponseState>(
JSResponseState.NoResponse, JSResponseState.NoResponse,
); );
const responses = (jsCollectionData && jsCollectionData.data) || {}; const errorCount = useSelector(getErrorCount);
const isDirty = (jsCollectionData && jsCollectionData.isDirty) || {};
const isExecuting = (jsCollectionData && jsCollectionData.isExecuting) || {}; const { isDirty, isExecuting, responses } = useMemo(() => {
return {
responses: (jsCollectionData && jsCollectionData.data) || {},
isDirty: (jsCollectionData && jsCollectionData.isDirty) || {},
isExecuting: (jsCollectionData && jsCollectionData.isExecuting) || {},
};
}, [jsCollectionData]);
const dispatch = useDispatch(); const dispatch = useDispatch();
const response =
currentFunction && currentFunction.id && currentFunction.id in responses const response = useMemo(() => {
? responses[currentFunction.id] if (
: ""; !currentFunction ||
!currentFunction.id ||
!(currentFunction.id in responses)
) {
return { value: "" };
}
return { value: responses[currentFunction.id] as string };
}, [currentFunction, responses]);
// parse error found while trying to execute function // parse error found while trying to execute function
const hasExecutionParseErrors = responseStatus === JSResponseState.IsDirty; const hasExecutionParseErrors = responseStatus === JSResponseState.IsDirty;
// error found while trying to parse JS Object // error found while trying to parse JS Object
@ -122,13 +134,8 @@ function JSResponseView(props: Props) {
); );
}, [jsCollectionData?.config, currentFunction]); }, [jsCollectionData?.config, currentFunction]);
const ideViewMode = useSelector(getIDEViewMode); const JSResponseTab = useMemo(() => {
return (
const tabs: BottomTab[] = [
{
key: DEBUGGER_TAB_KEYS.RESPONSE_TAB,
title: createMessage(DEBUGGER_RESPONSE),
panelComponent: (
<> <>
{localExecutionAllowed && hasExecutionParseErrors && ( {localExecutionAllowed && hasExecutionParseErrors && (
<ResponseErrorContainer> <ResponseErrorContainer>
@ -156,7 +163,7 @@ function JSResponseView(props: Props) {
/> />
)} )}
{responseStatus === JSResponseState.IsExecuting && ( {responseStatus === JSResponseState.IsExecuting && (
<LoadingOverlayScreen theme={props.theme}> <LoadingOverlayScreen theme={theme}>
{createMessage(EXECUTING_FUNCTION)} {createMessage(EXECUTING_FUNCTION)}
</LoadingOverlayScreen> </LoadingOverlayScreen>
)} )}
@ -171,13 +178,7 @@ function JSResponseView(props: Props) {
</NoReturnValueWrapper> </NoReturnValueWrapper>
)} )}
{responseStatus === JSResponseState.ShowResponse && ( {responseStatus === JSResponseState.ShowResponse && (
<ReadOnlyEditor <ReadOnlyEditor folding height="100%" input={response} />
folding
height={"100%"}
input={{
value: response as string,
}}
/>
)} )}
</> </>
)} )}
@ -185,7 +186,7 @@ function JSResponseView(props: Props) {
<JSRemoteExecutionView collectionData={jsCollectionData} /> <JSRemoteExecutionView collectionData={jsCollectionData} />
)} )}
{responseStatus === JSResponseState.IsUpdating && ( {responseStatus === JSResponseState.IsUpdating && (
<LoadingOverlayScreen theme={props.theme}> <LoadingOverlayScreen theme={theme}>
{createMessage(UPDATING_JS_COLLECTION)} {createMessage(UPDATING_JS_COLLECTION)}
</LoadingOverlayScreen> </LoadingOverlayScreen>
)} )}
@ -193,7 +194,32 @@ function JSResponseView(props: Props) {
</Flex> </Flex>
</ResponseTabWrapper> </ResponseTabWrapper>
</> </>
), );
}, [
currentFunction?.name,
disabled,
errors.length,
hasExecutionParseErrors,
isLoading,
jsCollectionData,
localExecutionAllowed,
onButtonClick,
theme,
response,
responseStatus,
]);
const ideViewMode = useSelector(getIDEViewMode);
const location = useLocation();
const ideType = getIDETypeByUrl(location.pathname);
const tabs = useMemo(() => {
const jsTabs: BottomTab[] = [
{
key: DEBUGGER_TAB_KEYS.RESPONSE_TAB,
title: createMessage(DEBUGGER_RESPONSE),
panelComponent: JSResponseTab,
}, },
{ {
key: DEBUGGER_TAB_KEYS.LOGS_TAB, key: DEBUGGER_TAB_KEYS.LOGS_TAB,
@ -203,13 +229,24 @@ function JSResponseView(props: Props) {
]; ];
if (ideViewMode === EditorViewMode.FullScreen) { if (ideViewMode === EditorViewMode.FullScreen) {
tabs.push({ jsTabs.push({
key: DEBUGGER_TAB_KEYS.ERROR_TAB, key: DEBUGGER_TAB_KEYS.ERROR_TAB,
title: createMessage(DEBUGGER_ERRORS), title: createMessage(DEBUGGER_ERRORS),
count: errorCount, count: errorCount,
panelComponent: <ErrorLogs />, panelComponent: <ErrorLogs />,
}); });
if (ideType === IDE_TYPE.App) {
jsTabs.push({
key: DEBUGGER_TAB_KEYS.STATE_TAB,
title: createMessage(DEBUGGER_STATE),
panelComponent: <StateInspector />,
});
} }
}
return jsTabs;
}, [JSResponseTab, errorCount, ideType, ideViewMode]);
// get the selected tab from the store. // get the selected tab from the store.
const { open, responseTabHeight, selectedTab } = useSelector( const { open, responseTabHeight, selectedTab } = useSelector(
@ -217,18 +254,24 @@ function JSResponseView(props: Props) {
); );
// set the selected tab in the store. // set the selected tab in the store.
const setSelectedResponseTab = useCallback((selectedTab: string) => { const setSelectedResponseTab = useCallback(
(selectedTab: string) => {
dispatch(setJsPaneDebuggerState({ open: true, selectedTab })); dispatch(setJsPaneDebuggerState({ open: true, selectedTab }));
}, []); },
[dispatch],
);
// set the height of the response pane on resize. // set the height of the response pane on resize.
const setResponseHeight = useCallback((height: number) => { const setResponseHeight = useCallback(
(height: number) => {
dispatch(setJsPaneDebuggerState({ responseTabHeight: height })); dispatch(setJsPaneDebuggerState({ responseTabHeight: height }));
}, []); },
[dispatch],
);
// close the debugger // close the debugger
const onToggle = useCallback( const onToggle = useCallback(
() => dispatch(setJsPaneDebuggerState({ open: !open })), () => dispatch(setJsPaneDebuggerState({ open: !open })),
[open], [dispatch, open],
); );
// Do not render if header tab is selected in the bottom bar. // Do not render if header tab is selected in the bottom bar.
@ -251,12 +294,4 @@ function JSResponseView(props: Props) {
); );
} }
const mapStateToProps = (state: AppState) => { export default JSResponseView;
const errorCount = state.ui.debugger.context.errorCount;
return {
errorCount,
};
};
export default connect(mapStateToProps)(withRouter(JSResponseView));

View File

@ -12,6 +12,9 @@ import { PRIMARY_KEY, FOREIGN_KEY } from "constants/DatasourceEditorConstants";
import { Icon } from "@appsmith/ads"; import { Icon } from "@appsmith/ads";
import { getAssetUrl } from "ee/utils/airgapHelpers"; import { getAssetUrl } from "ee/utils/airgapHelpers";
import { importSvg } from "@appsmith/ads-old"; import { importSvg } from "@appsmith/ads-old";
import WidgetFactory from "WidgetProvider/factory";
import WidgetTypeIcon from "pages/Editor/Explorer/Widgets/WidgetIcon";
import type { WidgetType } from "constants/WidgetConstants";
const ApiIcon = importSvg( const ApiIcon = importSvg(
async () => import("assets/icons/menu/api-colored.svg"), async () => import("assets/icons/menu/api-colored.svg"),
@ -225,6 +228,7 @@ const EntityIconWrapper = styled.div<{
justify-content: center; justify-content: center;
text-align: center; text-align: center;
border-radius: var(--ads-v2-border-radius); border-radius: var(--ads-v2-border-radius);
svg, svg,
img { img {
height: 100% !important; height: 100% !important;
@ -357,3 +361,13 @@ export function DefaultModuleIcon() {
</EntityIcon> </EntityIcon>
); );
} }
export function WidgetIconByType(widgetType: WidgetType) {
const { IconCmp } = WidgetFactory.getWidgetMethods(widgetType);
return IconCmp ? <IconCmp /> : <WidgetTypeIcon type={widgetType} />;
}
export function GlobeIcon() {
return <Icon name="global-line" size="md" />;
}

View File

@ -125,7 +125,6 @@ function Files() {
parentEntityType={parentEntityType} parentEntityType={parentEntityType}
searchKeyword={""} searchKeyword={""}
step={2} step={2}
type={type}
/> />
); );
} else { } else {

View File

@ -7,7 +7,6 @@ import { getJsCollectionByBaseId } from "ee/selectors/entitiesSelector";
import type { AppState } from "ee/reducers"; import type { AppState } from "ee/reducers";
import type { JSCollection } from "entities/JSCollection"; import type { JSCollection } from "entities/JSCollection";
import { JsFileIconV2 } from "../ExplorerIcons"; import { JsFileIconV2 } from "../ExplorerIcons";
import type { PluginType } from "entities/Action";
import { jsCollectionIdURL } from "ee/RouteBuilder"; import { jsCollectionIdURL } from "ee/RouteBuilder";
import AnalyticsUtil from "ee/utils/AnalyticsUtil"; import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import { useLocation } from "react-router"; import { useLocation } from "react-router";
@ -26,7 +25,6 @@ interface ExplorerJSCollectionEntityProps {
searchKeyword?: string; searchKeyword?: string;
baseCollectionId: string; baseCollectionId: string;
isActive: boolean; isActive: boolean;
type: PluginType;
parentEntityId: string; parentEntityId: string;
parentEntityType: ActionParentEntityTypeInterface; parentEntityType: ActionParentEntityTypeInterface;
} }

View File

@ -6,6 +6,7 @@ import { omit, isUndefined, isEmpty } from "lodash";
import equal from "fast-deep-equal"; import equal from "fast-deep-equal";
import { ActionExecutionResizerHeight } from "PluginActionEditor/components/PluginActionResponse/constants"; import { ActionExecutionResizerHeight } from "PluginActionEditor/components/PluginActionResponse/constants";
import { klona } from "klona"; import { klona } from "klona";
import type { GenericEntityItem } from "ee/entities/IDE/constants";
export const DefaultDebuggerContext = { export const DefaultDebuggerContext = {
scrollPosition: 0, scrollPosition: 0,
@ -22,6 +23,7 @@ const initialState: DebuggerReduxState = {
expandId: "", expandId: "",
hideErrors: true, hideErrors: true,
context: DefaultDebuggerContext, context: DefaultDebuggerContext,
stateInspector: {},
}; };
// check the last message from the current log and update the occurrence count // check the last message from the current log and update the occurrence count
@ -185,6 +187,17 @@ const debuggerReducer = createImmerReducer(initialState, {
}, },
}; };
}, },
[ReduxActionTypes.SET_DEBUGGER_STATE_INSPECTOR_SELECTED_ITEM]: (
state: DebuggerReduxState,
action: ReduxAction<GenericEntityItem>,
): DebuggerReduxState => {
return {
...state,
stateInspector: {
selectedItem: action.payload,
},
};
},
// Resetting debugger state after env switch // Resetting debugger state after env switch
[ReduxActionTypes.SWITCH_ENVIRONMENT_SUCCESS]: () => { [ReduxActionTypes.SWITCH_ENVIRONMENT_SUCCESS]: () => {
return klona(initialState); return klona(initialState);
@ -198,6 +211,9 @@ export interface DebuggerReduxState {
expandId: string; expandId: string;
hideErrors: boolean; hideErrors: boolean;
context: DebuggerContext; context: DebuggerContext;
stateInspector: {
selectedItem?: GenericEntityItem;
};
} }
export interface DebuggerContext { export interface DebuggerContext {

View File

@ -5,7 +5,6 @@ import {
put, put,
select, select,
take, take,
takeEvery,
takeLatest, takeLatest,
} from "redux-saga/effects"; } from "redux-saga/effects";
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
@ -19,10 +18,7 @@ import {
updateAction, updateAction,
updateActionData, updateActionData,
} from "actions/pluginActionActions"; } from "actions/pluginActionActions";
import { import { handleExecuteJSFunctionSaga } from "sagas/JSPaneSagas";
handleExecuteJSFunctionSaga,
makeUpdateJSCollection,
} from "sagas/JSPaneSagas";
import type { ApplicationPayload } from "entities/Application"; import type { ApplicationPayload } from "entities/Application";
import type { ReduxAction } from "ee/constants/ReduxActionConstants"; import type { ReduxAction } from "ee/constants/ReduxActionConstants";
@ -1665,6 +1661,5 @@ export function* watchPluginActionExecutionSagas() {
executePageLoadActionsSaga, executePageLoadActionsSaga,
), ),
takeLatest(ReduxActionTypes.PLUGIN_SOFT_REFRESH, softRefreshActionsSaga), takeLatest(ReduxActionTypes.PLUGIN_SOFT_REFRESH, softRefreshActionsSaga),
takeEvery(ReduxActionTypes.EXECUTE_JS_UPDATES, makeUpdateJSCollection),
]); ]);
} }

View File

@ -95,10 +95,10 @@ import type { ActionDescription } from "ee/workers/Evaluation/fns";
import { handleEvalWorkerRequestSaga } from "./EvalWorkerActionSagas"; import { handleEvalWorkerRequestSaga } from "./EvalWorkerActionSagas";
import { getAppsmithConfigs } from "ee/configs"; import { getAppsmithConfigs } from "ee/configs";
import { import {
executeJSUpdates,
type actionDataPayload, type actionDataPayload,
type updateActionDataPayloadType, type updateActionDataPayloadType,
} from "actions/pluginActionActions"; } from "actions/pluginActionActions";
import { executeJSUpdates } from "actions/jsPaneActions";
import { setEvaluatedActionSelectorField } from "actions/actionSelectorActions"; import { setEvaluatedActionSelectorField } from "actions/actionSelectorActions";
import { waitForWidgetConfigBuild } from "./InitSagas"; import { waitForWidgetConfigBuild } from "./InitSagas";
import { logDynamicTriggerExecution } from "ee/sagas/analyticsSaga"; import { logDynamicTriggerExecution } from "ee/sagas/analyticsSaga";
@ -545,6 +545,7 @@ interface BUFFERED_ACTION {
hasBufferedAction: boolean; hasBufferedAction: boolean;
actionDataPayloadConsolidated: actionDataPayload[]; actionDataPayloadConsolidated: actionDataPayload[];
} }
export function evalQueueBuffer() { export function evalQueueBuffer() {
let canTake = false; let canTake = false;
let hasDebouncedHandleUpdate = false; let hasDebouncedHandleUpdate = false;

View File

@ -928,5 +928,6 @@ export default function* root() {
ReduxActionTypes.CREATE_NEW_JS_FROM_ACTION_CREATOR, ReduxActionTypes.CREATE_NEW_JS_FROM_ACTION_CREATOR,
handleCreateNewJSFromActionCreator, handleCreateNewJSFromActionCreator,
), ),
takeEvery(ReduxActionTypes.EXECUTE_JS_UPDATES, makeUpdateJSCollection),
]); ]);
} }

View File

@ -184,3 +184,6 @@ export const getCanvasDebuggerState = createSelector(
}; };
}, },
); );
export const getDebuggerStateInspectorSelectedItem = (state: AppState) =>
state.ui.debugger.stateInspector.selectedItem;