PromucFlow_constructor/app/client/src/selectors/debuggerSelectors.tsx
Hetu Nandu 1d4198048c
chore: Debugger Split states (#31043)
## Description

Creates local states for the debugger for Query Pane, Api Pane and JS
Pane and separates it from the main Canvas Debugger state. This is done
so that in Split pane, the states of Action Pane debugger can be
different from the Canvas Debugger state. To keep handling the
Fullscreen Debugger experience, a new hook `useDebuggerTriggerClick` is
introduced which opens the correct debugger based on the IDE state.

Also removes the Error and Logs from the Query / Api / JS Debuggers when
in split screen mode for a cleaner debugging experience

##### This change removes the expectation of having a common debugger
state that follows around as the user navigates in the IDE. Instead it
create a new debugger state per entity item. The tests have been updated
to reflect this



#### PR fixes following issue(s)
Fixes #30836
Fixes #30342


#### Media


#### Type of change
- Breaking change (fix or feature that would cause existing
functionality to not work as expected)

## Testing

#### How Has This Been Tested?

- [ ] Manual
- [ ] JUnit
- [ ] Jest
- [ ] Cypress
>
>
#### Test Plan
> Add Testsmith test cases links that relate to this PR
>
>
#### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)
>
>
>
## Checklist:
#### Dev activity
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


#### QA activity:
- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-)
have been covered
- [ ] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-)
- [ ] Test plan has been peer reviewed by project stakeholders and other
QA members
- [ ] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [ ] Cypress test cases have been added and approved by SDET/manual QA
- [ ] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
2024-02-29 11:53:57 +05:30

171 lines
5.7 KiB
TypeScript

import type { Log } from "entities/AppsmithConsole";
import type { WidgetEntity } from "@appsmith/entities/DataTree/types";
import type { DataTree } from "entities/DataTree/dataTreeTypes";
import { isEmpty } from "lodash";
import type { AppState } from "@appsmith/reducers";
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
import { createSelector } from "reselect";
import { getWidgets } from "sagas/selectors";
import {
shouldSuppressDebuggerError,
isWidget,
} from "@appsmith/workers/Evaluation/evaluationUtils";
import { getDataTree } from "./dataTreeSelectors";
import { combinedPreviewModeSelector } from "./editorSelectors";
import type { CanvasDebuggerState } from "reducers/uiReducers/debuggerReducer";
interface ErrorObejct {
[k: string]: Log;
}
export const getDebuggerErrors = (state: AppState) => state.ui.debugger.errors;
export const hideErrors = (state: AppState) => state.ui.debugger.hideErrors;
const emptyErrorObejct: ErrorObejct = {};
export const getFilteredErrors = createSelector(
getDebuggerErrors,
hideErrors,
getWidgets,
getDataTree,
(errors, hideErrors, canvasWidgets, dataTree: DataTree) => {
if (hideErrors) return emptyErrorObejct;
if (isEmpty(errors)) return emptyErrorObejct;
const alwaysShowEntities: Record<string, boolean> = {};
Object.entries(errors).forEach(([, error]) => {
const entity = error?.source?.name && dataTree[error.source.name];
if (
entity &&
isWidget(entity) &&
error.source?.propertyPath === "isVisible"
) {
alwaysShowEntities[error.source.id] = true;
}
});
const filteredErrors = Object.fromEntries(
Object.entries(errors).filter(([, error]) => {
const entity = error?.source?.name && dataTree[error.source.name];
// filter error - when widget or parent widget is hidden
// parent widgets e.g. modal, tab, container
if (entity && isWidget(entity)) {
const widgetEntity = entity as WidgetEntity;
if (shouldSuppressDebuggerError(widgetEntity)) {
return false;
}
if (!hasParentWidget(widgetEntity)) {
return widgetEntity.isVisible
? true
: alwaysShowEntities[widgetEntity.widgetId];
} else {
const isParentWidgetVisible = isParentVisible(
widgetEntity,
canvasWidgets,
dataTree,
);
return widgetEntity.isVisible
? isParentWidgetVisible
: isParentWidgetVisible &&
alwaysShowEntities[widgetEntity.widgetId];
}
}
return true;
}),
);
return filteredErrors;
},
);
export const isParentVisible = (
currentWidgetData: WidgetEntity,
canvasWidgets: CanvasWidgetsReduxState,
dataTree: DataTree,
): boolean => {
const isWidgetVisible = !!currentWidgetData.isVisible;
if (!hasParentWidget(currentWidgetData)) {
return isWidgetVisible;
}
const parentWidget = canvasWidgets[currentWidgetData.parentId as string];
if (!parentWidget) return isWidgetVisible;
const parentWidgetData = dataTree[parentWidget.widgetName] as WidgetEntity;
if (!parentWidgetData) return isWidgetVisible;
switch (parentWidgetData.type) {
// check for widget types instead of harcoded string
case "TABS_WIDGET":
// need type for selectedTab and tabName
const isTabContentVisible =
!!parentWidgetData.isVisible &&
parentWidgetData.selectedTab === currentWidgetData.tabName;
return isTabContentVisible
? isParentVisible(parentWidgetData, canvasWidgets, dataTree)
: false;
case "MODAL_WIDGET":
return !!parentWidgetData.isVisible;
default:
return parentWidgetData.isVisible
? isParentVisible(parentWidgetData, canvasWidgets, dataTree)
: false;
}
};
export const hasParentWidget = (widget: WidgetEntity) =>
widget.parentId && widget.parentId !== "0";
export const getMessageCount = createSelector(getFilteredErrors, (errors) => {
let errorsCount = 0;
// count number of messages in each error.
// This logic is required because each messages in error is rendered separately.
Object.values(errors).forEach((error) => {
if (error.messages) {
errorsCount += error.messages.length;
}
});
// count number of warnings.
const warningsCount = Object.keys(errors).filter((key: string) =>
key.includes("warning"),
).length;
errorsCount = errorsCount - warningsCount;
return { errors: errorsCount, warnings: warningsCount };
});
// get selected tab in debugger.
export const getDebuggerSelectedTab = (state: AppState) =>
state.ui.debugger.context.selectedDebuggerTab;
export const getDebuggerSelectedFilter = (state: AppState) =>
state.ui.debugger.context.selectedDebuggerFilter;
export const getResponsePaneHeight = (state: AppState) =>
state.ui.debugger.context.responseTabHeight;
export const getErrorCount = (state: AppState) =>
state.ui.debugger.context.errorCount;
export const getScrollPosition = (state: AppState) =>
state.ui.debugger.context.scrollPosition;
export const getDebuggerContext = (state: AppState) =>
state.ui.debugger.context;
export const getDebuggerOpen = (state: AppState) => state.ui.debugger.isOpen;
export const showDebuggerFlag = createSelector(
getDebuggerOpen,
combinedPreviewModeSelector,
(isOpen, isPreview) => isOpen && !isPreview,
);
export const getCanvasDebuggerState = createSelector(
showDebuggerFlag,
getDebuggerContext,
(openState, context): CanvasDebuggerState => {
return {
open: openState,
selectedTab: context.selectedDebuggerTab,
responseTabHeight: context.responseTabHeight,
};
},
);