PromucFlow_constructor/app/client/src/ce/sagas/NavigationSagas.ts
Hetu Nandu 2bde0d11c4
chore: Show Tabs on recent access order (#30450)
## Description

Update IDE tabs order and limits

- Show only recently accessed tabs
- If recent tab is was already on screen, do not update order
- Limit to just 5 active tabs


#### PR fixes following issue(s)
Fixes #30365

#### Media



https://github.com/appsmithorg/appsmith/assets/12022471/a53a93cd-1b5e-4341-ba4f-289c6bd82b6d


>
#### Type of change
- Chore (housekeeping or task changes that don't impact user perception)

## Testing
>
#### How Has This Been Tested?
> Please describe the tests that you ran to verify your changes. Also
list any relevant details for your test configuration.
> Delete anything that is not relevant
- [ ] 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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced new actions to manage JavaScript and query tabs within the
IDE.
	- Enhanced IDE to update tabs based on route changes.

- **Enhancements**
	- Improved tab management for JavaScript and queries in the IDE.

- **Bug Fixes**
	- Ensured consistent IDE tab states during navigation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-01-23 10:22:50 +05:30

141 lines
4.9 KiB
TypeScript

import { fork, put, select, call } from "redux-saga/effects";
import type { RouteChangeActionPayload } from "actions/focusHistoryActions";
import { FocusEntity, identifyEntityFromPath } from "navigation/FocusEntity";
import log from "loglevel";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { getRecentEntityIds } from "selectors/globalSearchSelectors";
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
import { getCurrentThemeDetails } from "selectors/themeSelectors";
import type { BackgroundTheme } from "sagas/ThemeSaga";
import { changeAppBackground } from "sagas/ThemeSaga";
import { updateRecentEntitySaga } from "sagas/GlobalSearchSagas";
import {
setLastSelectedWidget,
setSelectedWidgets,
} from "actions/widgetSelectionActions";
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
import FocusRetention from "sagas/FocusRetentionSaga";
import { getSafeCrash } from "selectors/errorSelectors";
import { flushErrors } from "actions/errorActions";
import type { NavigationMethod } from "utils/history";
import UsagePulse from "usagePulse";
import { getIDETypeByUrl } from "@appsmith/entities/IDE/utils";
import { IDE_TYPE } from "@appsmith/entities/IDE/constants";
import { updateIDETabsOnRouteChangeSaga } from "sagas/IDESaga";
let previousPath: string;
export function* handleRouteChange(
action: ReduxAction<RouteChangeActionPayload>,
) {
const { pathname, state } = action.payload.location;
try {
yield fork(clearErrors);
yield fork(watchForTrackableUrl, action.payload);
const ideType = getIDETypeByUrl(pathname);
const isAnEditorPath = ideType !== IDE_TYPE.None;
// handled only on edit mode
if (isAnEditorPath) {
yield fork(
FocusRetention.onRouteChange.bind(FocusRetention),
pathname,
previousPath,
state,
);
if (ideType === IDE_TYPE.App) {
yield fork(logNavigationAnalytics, action.payload);
yield fork(appBackgroundHandler);
const entityInfo = identifyEntityFromPath(pathname);
yield fork(updateRecentEntitySaga, entityInfo);
yield fork(updateIDETabsOnRouteChangeSaga, entityInfo);
yield fork(setSelectedWidgetsSaga, state?.invokedBy);
}
}
} catch (e) {
log.error("Error in focus change", e);
} finally {
previousPath = pathname;
}
}
function* appBackgroundHandler() {
const currentTheme: BackgroundTheme = yield select(getCurrentThemeDetails);
changeAppBackground(currentTheme);
}
/**
* When an error occurs, we take over the whole router and keep it the error
* state till the errors are flushed. By default, we will flush out the
* error state when a CTA on the page is clicked but in case the
* user navigates via the browser buttons, this will ensure
* the errors are flushed
* */
function* clearErrors() {
const isCrashed: boolean = yield select(getSafeCrash);
if (isCrashed) {
yield put(flushErrors());
}
}
function* watchForTrackableUrl(payload: RouteChangeActionPayload) {
const oldPathname = payload.prevLocation.pathname;
const newPathname = payload.location.pathname;
const isOldPathTrackable: boolean = yield call(
UsagePulse.isTrackableUrl,
oldPathname,
);
const isNewPathTrackable: boolean = yield call(
UsagePulse.isTrackableUrl,
newPathname,
);
// Trackable to Trackable URL -> No pulse
// Non-Trackable to Non-Trackable URL -> No pulse
// Trackable to Non-Trackable -> No Pulse
// Non-Trackable to Trackable URL -> Send Pulse
if (!isOldPathTrackable && isNewPathTrackable) {
yield call(UsagePulse.sendPulseAndScheduleNext);
}
}
function* logNavigationAnalytics(payload: RouteChangeActionPayload) {
const {
location: { pathname, state },
} = payload;
const recentEntityIds: Array<string> = yield select(getRecentEntityIds);
const currentEntity = identifyEntityFromPath(pathname);
const previousEntity = identifyEntityFromPath(previousPath);
const isRecent = recentEntityIds.some(
(entityId) => entityId === currentEntity.id,
);
const { height, width } = window.screen;
AnalyticsUtil.logEvent("ROUTE_CHANGE", {
toPath: pathname,
fromPath: previousPath || undefined,
navigationMethod: state?.invokedBy,
isRecent,
recentLength: recentEntityIds.length,
toType: currentEntity.entity,
fromType: previousEntity.entity,
screenHeight: height,
screenWidth: width,
});
}
function* setSelectedWidgetsSaga(invokedBy?: NavigationMethod) {
const pathname = window.location.pathname;
const entityInfo = identifyEntityFromPath(pathname);
let widgets: string[] = [];
let lastSelectedWidget = MAIN_CONTAINER_WIDGET_ID;
if (entityInfo.entity === FocusEntity.PROPERTY_PANE) {
widgets = entityInfo.id.split(",");
if (widgets.length) {
lastSelectedWidget = widgets[widgets.length - 1];
}
}
yield put(setSelectedWidgets(widgets, invokedBy));
yield put(setLastSelectedWidget(lastSelectedWidget));
}