chore: ce changes related to decoupling webworker (#41033)
## Description We are improving the LCP by reducing the time to reach the first evaluation, aiming for a 1.8 to 2.2 second reduction. To achieve this, we’ve implemented the following changes: Code Splitting of Widgets: During page load, only the widgets required for an evaluation are loaded and registered. For every evaluation cycle we keep discovering widget types and load them as required. Web Worker Offloading: Macro tasks such as clearCache and JavaScript library installation have been moved to the web worker setup. These are now executed in a separate thread, allowing the firstUnevaluatedTree to be computed in parallel with JS library installation. Parallel JS Library Loading: All JavaScript libraries are now loaded in parallel within the web worker, instead of sequentially, improving efficiency. Deferred Rendering of AppViewer: We now render the AppViewer and Header component only after registering the remaining widgets. This ensures that heavy rendering tasks—such as expensive selector computations and loading additional chunks related to the AppViewer—can execute in parallel with the first evaluation, further enhancing performance. ## Automation /ok-to-test tags="@tag.All" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/16202622510> > Commit: b648036bd7b74ae742f5c5d7f6cfd770867a2828 > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=16202622510&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.All` > Spec: > <hr>Thu, 10 Jul 2025 19:22:25 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit * **New Features** * Widgets are now loaded and registered asynchronously, improving app startup and modularity. * Widget registration and configuration changes are now versioned, ensuring selectors and UI update appropriately. * Widget initialization and factory cache management are more robust, with explicit cache clearing after widget registration. * Added new Redux actions and selectors to manage first page load, deferred JS library loading, and page rendering state. * Theme handling and widget initialization in AppViewer are streamlined for faster evaluation and rendering. * Deferred loading of JavaScript libraries on first page load improves performance. * Conditional rendering gates added to AppViewer and Navigation components based on evaluation state. * **Bug Fixes** * Prevented errors when conditionally rendering widgets and navigation components before evaluation is complete. * Improved widget property pane and configuration tests to ensure all widgets are properly loaded and validated. * **Refactor** * Widget import and registration logic was refactored to support dynamic, on-demand loading. * Evaluation and initialization sagas were modularized for better maintainability and performance. * Widget factory and memoization logic were enhanced to allow explicit cache clearing and version tracking. * JavaScript library loading logic was parallelized for faster startup. * Theme application extracted into a dedicated component for clarity and reuse. * **Tests** * Expanded and updated widget and evaluation saga test suites to cover asynchronous widget loading, cache management, and first evaluation scenarios. * Added tests verifying widget factory cache behavior and first evaluation integration. * **Chores** * Updated internal dependencies and selectors to track widget configuration version changes, ensuring UI consistency. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
7282f64dcf
commit
e5b2a26c65
|
|
@ -0,0 +1,39 @@
|
|||
import WidgetFactory from "../index";
|
||||
import { clearAllWidgetFactoryCache } from "../decorators";
|
||||
import type BaseWidget from "widgets/BaseWidget";
|
||||
|
||||
describe("WidgetFactory Cache Tests", () => {
|
||||
beforeAll(() => {
|
||||
// Clear the widget factory state before each test
|
||||
WidgetFactory.widgetsMap.clear();
|
||||
clearAllWidgetFactoryCache();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// Clean up after each test
|
||||
WidgetFactory.widgetsMap.clear();
|
||||
clearAllWidgetFactoryCache();
|
||||
});
|
||||
|
||||
it("should return stale data after widget registration until cache is cleared", () => {
|
||||
// Initial state - no widgets
|
||||
let widgetTypes = WidgetFactory.getWidgetTypes();
|
||||
|
||||
expect(widgetTypes).toEqual([]);
|
||||
|
||||
// Add a widget to the map
|
||||
WidgetFactory.widgetsMap.set("TEST_WIDGET", {} as typeof BaseWidget);
|
||||
|
||||
// getWidgetTypes should still return empty array (stale cache)
|
||||
widgetTypes = WidgetFactory.getWidgetTypes();
|
||||
expect(widgetTypes).toEqual([]);
|
||||
|
||||
// Clear the cache
|
||||
clearAllWidgetFactoryCache();
|
||||
|
||||
// Now getWidgetTypes should return the updated widget type
|
||||
widgetTypes = WidgetFactory.getWidgetTypes();
|
||||
expect(widgetTypes).toContain("TEST_WIDGET");
|
||||
expect(widgetTypes).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,13 +1,47 @@
|
|||
import memo from "micro-memoize";
|
||||
|
||||
type AnyFn = (...args: unknown[]) => unknown;
|
||||
|
||||
interface MemoizedWithClear {
|
||||
(...args: unknown[]): unknown;
|
||||
clearCache: () => void;
|
||||
}
|
||||
|
||||
// Track all memoized functions
|
||||
const memoizedFunctions = new Set<MemoizedWithClear>();
|
||||
|
||||
// Function to clear memoized cache
|
||||
function clearMemoizedCache(fn: {
|
||||
cache: { keys: unknown[]; values: unknown[] };
|
||||
}) {
|
||||
fn.cache.keys.length = fn.cache.values.length = 0;
|
||||
}
|
||||
|
||||
// Create a memoize wrapper that adds cache clearing capability
|
||||
function memoizeWithClear(fn: AnyFn): MemoizedWithClear {
|
||||
const memoized = memo(fn, {
|
||||
maxSize: 100,
|
||||
}) as unknown as MemoizedWithClear;
|
||||
|
||||
// Add clearCache method to the memoized function
|
||||
memoized.clearCache = () => {
|
||||
clearMemoizedCache(
|
||||
memoized as unknown as { cache: { keys: unknown[]; values: unknown[] } },
|
||||
);
|
||||
};
|
||||
|
||||
// Add to tracked functions
|
||||
memoizedFunctions.add(memoized);
|
||||
|
||||
return memoized;
|
||||
}
|
||||
|
||||
export function memoize(
|
||||
target: unknown,
|
||||
methodName: unknown,
|
||||
descriptor: PropertyDescriptor,
|
||||
) {
|
||||
descriptor.value = memo(descriptor.value, {
|
||||
maxSize: 100,
|
||||
});
|
||||
descriptor.value = memoizeWithClear(descriptor.value);
|
||||
}
|
||||
|
||||
export function freeze(
|
||||
|
|
@ -25,3 +59,8 @@ export function freeze(
|
|||
return Object.freeze(result);
|
||||
};
|
||||
}
|
||||
|
||||
// Function to clear all memoized caches
|
||||
export function clearAllWidgetFactoryCache() {
|
||||
memoizedFunctions.forEach((fn) => fn.clearCache());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type { CanvasWidgetStructure } from "WidgetProvider/types";
|
|||
import type BaseWidget from "widgets/BaseWidget";
|
||||
import WidgetFactory from ".";
|
||||
import { withBaseWidgetHOC } from "widgets/BaseWidgetHOC/withBaseWidgetHOC";
|
||||
import { incrementWidgetConfigsVersion } from "./widgetConfigVersion";
|
||||
|
||||
/*
|
||||
* Function to create builder for the widgets and register them in widget factory
|
||||
|
|
@ -11,28 +12,31 @@ import { withBaseWidgetHOC } from "widgets/BaseWidgetHOC/withBaseWidgetHOC";
|
|||
* extracted this into a seperate file to break the circular reference.
|
||||
*
|
||||
*/
|
||||
|
||||
export const registerWidgets = (widgets: (typeof BaseWidget)[]) => {
|
||||
const widgetAndBuilders = widgets.map((widget) => {
|
||||
const { eagerRender = false, needsMeta = false } = widget.getConfig();
|
||||
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const ProfiledWidget: any = withBaseWidgetHOC(
|
||||
widget,
|
||||
needsMeta,
|
||||
eagerRender,
|
||||
);
|
||||
|
||||
return [
|
||||
widget,
|
||||
(widgetProps: CanvasWidgetStructure) => (
|
||||
<ProfiledWidget {...widgetProps} key={widgetProps.widgetId} />
|
||||
),
|
||||
] as [
|
||||
typeof BaseWidget,
|
||||
(widgetProps: CanvasWidgetStructure) => React.ReactNode,
|
||||
];
|
||||
widgets.forEach((widget) => {
|
||||
registerWidget(widget);
|
||||
});
|
||||
|
||||
WidgetFactory.initialize(widgetAndBuilders);
|
||||
// Increment version to trigger selectors that depend on widget configs
|
||||
incrementWidgetConfigsVersion();
|
||||
};
|
||||
|
||||
export const registerWidget = (widget: typeof BaseWidget) => {
|
||||
const { eagerRender = false, needsMeta = false } = widget.getConfig();
|
||||
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const ProfiledWidget: any = withBaseWidgetHOC(widget, needsMeta, eagerRender);
|
||||
|
||||
const widgetAndBuilder: [
|
||||
typeof BaseWidget,
|
||||
(widgetProps: CanvasWidgetStructure) => React.ReactNode,
|
||||
] = [
|
||||
widget,
|
||||
(widgetProps: CanvasWidgetStructure) => (
|
||||
<ProfiledWidget {...widgetProps} key={widgetProps.widgetId} />
|
||||
),
|
||||
];
|
||||
|
||||
WidgetFactory.initialize([widgetAndBuilder]);
|
||||
};
|
||||
|
|
|
|||
10
app/client/src/WidgetProvider/factory/widgetConfigVersion.ts
Normal file
10
app/client/src/WidgetProvider/factory/widgetConfigVersion.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Global version counter that increments when widgets are registered
|
||||
let widgetConfigsVersion = 0;
|
||||
|
||||
// Export getter for selectors to depend on
|
||||
export const getWidgetConfigsVersion = () => widgetConfigsVersion;
|
||||
|
||||
// Export incrementer for registration helper to use
|
||||
export const incrementWidgetConfigsVersion = () => {
|
||||
widgetConfigsVersion++;
|
||||
};
|
||||
|
|
@ -12,6 +12,16 @@ export function fetchJSLibraries(
|
|||
};
|
||||
}
|
||||
|
||||
export function deferLoadingJSLibraries(
|
||||
applicationId: string,
|
||||
customJSLibraries?: ApiResponse,
|
||||
) {
|
||||
return {
|
||||
type: ReduxActionTypes.DEFER_LOADING_JS_LIBRARIES,
|
||||
payload: { applicationId, customJSLibraries },
|
||||
};
|
||||
}
|
||||
|
||||
export function installLibraryInit(payload: Partial<JSLibrary>) {
|
||||
return {
|
||||
type: ReduxActionTypes.INSTALL_LIBRARY_INIT,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import type {
|
|||
ConditionalOutput,
|
||||
DynamicValues,
|
||||
} from "reducers/evaluationReducers/formEvaluationReducer";
|
||||
import type { ReduxActionWithoutPayload } from "./ReduxActionTypes";
|
||||
|
||||
export const shouldTriggerEvaluation = (action: ReduxAction<unknown>) => {
|
||||
return (
|
||||
|
|
@ -79,6 +80,12 @@ export const setDependencyMap = (
|
|||
};
|
||||
};
|
||||
|
||||
export const setIsFirstPageLoad = (): ReduxActionWithoutPayload => {
|
||||
return {
|
||||
type: ReduxActionTypes.IS_FIRST_PAGE_LOAD,
|
||||
};
|
||||
};
|
||||
|
||||
// These actions require the entire tree to be re-evaluated
|
||||
const FORCE_EVAL_ACTIONS = {
|
||||
[ReduxActionTypes.INSTALL_LIBRARY_SUCCESS]: true,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const JSLibraryActionTypes = {
|
|||
TOGGLE_INSTALLER: "TOGGLE_INSTALLER",
|
||||
FETCH_JS_LIBRARIES_INIT: "FETCH_JS_LIBRARIES_INIT",
|
||||
FETCH_JS_LIBRARIES_SUCCESS: "FETCH_JS_LIBRARIES_SUCCESS",
|
||||
DEFER_LOADING_JS_LIBRARIES: "DEFER_LOADING_JS_LIBRARIES",
|
||||
CLEAR_PROCESSED_INSTALLS: "CLEAR_PROCESSED_INSTALLS",
|
||||
INSTALL_LIBRARY_INIT: "INSTALL_LIBRARY_INIT",
|
||||
INSTALL_LIBRARY_START: "INSTALL_LIBRARY_START",
|
||||
|
|
@ -1288,7 +1289,15 @@ const PlatformActionErrorTypes = {
|
|||
API_ERROR: "API_ERROR",
|
||||
};
|
||||
|
||||
const DeferRenderingAppViewerActionTypes = {
|
||||
HAS_DISPATCHED_FIRST_EVALUATION_MESSAGE:
|
||||
"HAS_DISPATCHED_FIRST_EVALUATION_MESSAGE",
|
||||
RENDER_PAGE: "RENDER_PAGE",
|
||||
IS_FIRST_PAGE_LOAD: "IS_FIRST_PAGE_LOAD",
|
||||
};
|
||||
|
||||
export const ReduxActionTypes = {
|
||||
...DeferRenderingAppViewerActionTypes,
|
||||
...ActionActionTypes,
|
||||
...AdminSettingsActionTypes,
|
||||
...AnalyticsActionTypes,
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ import type { layoutConversionReduxState } from "reducers/uiReducers/layoutConve
|
|||
import type { OneClickBindingState } from "reducers/uiReducers/oneClickBindingReducer";
|
||||
import type { IDEState } from "reducers/uiReducers/ideReducer";
|
||||
import type { PluginActionEditorState } from "PluginActionEditor";
|
||||
import type { FirstEvaluationState } from "reducers/evaluationReducers/firstEvaluationReducer";
|
||||
|
||||
/* Reducers which are integrated into the core system when registering a pluggable module
|
||||
or done so by a module that is designed to be eventually pluggable */
|
||||
|
|
@ -171,6 +172,7 @@ export interface AppState {
|
|||
loadingEntities: LoadingEntitiesState;
|
||||
formEvaluation: FormEvaluationState;
|
||||
triggers: TriggerValuesEvaluationState;
|
||||
firstEvaluation: FirstEvaluationState;
|
||||
};
|
||||
linting: {
|
||||
errors: LintErrorsStore;
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ import { apiFailureResponseInterceptor } from "api/interceptors/response";
|
|||
import type { AxiosError } from "axios";
|
||||
import { handleFetchApplicationError } from "./ApplicationSagas";
|
||||
import { getCurrentUser } from "actions/authActions";
|
||||
import { getIsFirstPageLoad } from "selectors/evaluationSelectors";
|
||||
|
||||
export interface HandleWidgetNameUpdatePayload {
|
||||
newName: string;
|
||||
|
|
@ -370,8 +371,14 @@ export function* postFetchedPublishedPage(
|
|||
response.data.userPermissions,
|
||||
),
|
||||
);
|
||||
// Clear any existing caches
|
||||
yield call(clearEvalCache);
|
||||
const isFirstLoad: boolean = yield select(getIsFirstPageLoad);
|
||||
|
||||
// Only the first page load we defer the clearing of caches
|
||||
if (!isFirstLoad) {
|
||||
// Clear any existing caches
|
||||
yield call(clearEvalCache);
|
||||
}
|
||||
|
||||
// Set url params
|
||||
yield call(setDataUrl);
|
||||
|
||||
|
|
|
|||
|
|
@ -14,3 +14,6 @@ export const getModuleInstanceJSCollectionById = (
|
|||
): JSCollection | undefined => {
|
||||
return undefined;
|
||||
};
|
||||
export const getAllUniqueWidgetTypesInUiModules = (state: DefaultRootState) => {
|
||||
return [];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ function ToggleComponentToJsonHandler(props: HandlerProps) {
|
|||
}
|
||||
|
||||
function ToggleComponentToJson(props: Props) {
|
||||
return props.viewType === ViewTypes.JSON
|
||||
return props.viewType === ViewTypes.JSON && props.renderCompFunction
|
||||
? props.renderCompFunction({
|
||||
...alternateViewTypeInputConfig(),
|
||||
configProperty: props.configProperty,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import type { SwitchControlProps } from "components/propertyControls/SwitchContr
|
|||
import SwitchControl from "components/propertyControls/SwitchControl";
|
||||
import OptionControl from "components/propertyControls/OptionControl";
|
||||
import type { ControlProps } from "components/propertyControls/BaseControl";
|
||||
import type BaseControl from "components/propertyControls/BaseControl";
|
||||
import CodeEditorControl from "components/propertyControls/CodeEditorControl";
|
||||
import type BaseControl from "components/propertyControls/BaseControl";
|
||||
import type { DatePickerControlProps } from "components/propertyControls/DatePickerControl";
|
||||
import DatePickerControl from "components/propertyControls/DatePickerControl";
|
||||
import ChartDataControl from "components/propertyControls/ChartDataControl";
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
ReduxActionTypes,
|
||||
} from "ee/constants/ReduxActionConstants";
|
||||
import type { APP_MODE } from "entities/App";
|
||||
import { call, put, spawn } from "redux-saga/effects";
|
||||
import { call, put, select, spawn } from "redux-saga/effects";
|
||||
import type { DeployConsolidatedApi } from "sagas/InitSagas";
|
||||
import {
|
||||
failFastApiCalls,
|
||||
|
|
@ -20,7 +20,10 @@ import {
|
|||
} from "sagas/InitSagas";
|
||||
import type { AppEnginePayload } from ".";
|
||||
import AppEngine, { ActionsNotFoundError } from ".";
|
||||
import { fetchJSLibraries } from "actions/JSLibraryActions";
|
||||
import {
|
||||
fetchJSLibraries,
|
||||
deferLoadingJSLibraries,
|
||||
} from "actions/JSLibraryActions";
|
||||
import { waitForFetchUserSuccess } from "ee/sagas/userSagas";
|
||||
import { fetchJSCollectionsForView } from "actions/jsActionActions";
|
||||
import {
|
||||
|
|
@ -29,6 +32,7 @@ import {
|
|||
} from "actions/appThemingActions";
|
||||
import type { Span } from "instrumentation/types";
|
||||
import { endSpan, startNestedSpan } from "instrumentation/generateTraces";
|
||||
import { getIsFirstPageLoad } from "selectors/evaluationSelectors";
|
||||
|
||||
export default class AppViewerEngine extends AppEngine {
|
||||
constructor(mode: APP_MODE) {
|
||||
|
|
@ -120,9 +124,18 @@ export default class AppViewerEngine extends AppEngine {
|
|||
ReduxActionErrorTypes.SETUP_PUBLISHED_PAGE_ERROR,
|
||||
];
|
||||
|
||||
initActionsCalls.push(fetchJSLibraries(applicationId, customJSLibraries));
|
||||
successActionEffects.push(ReduxActionTypes.FETCH_JS_LIBRARIES_SUCCESS);
|
||||
failureActionEffects.push(ReduxActionErrorTypes.FETCH_JS_LIBRARIES_FAILED);
|
||||
const isFirstPageLoad = yield select(getIsFirstPageLoad);
|
||||
|
||||
if (isFirstPageLoad) {
|
||||
// we are deferring the loading of JS libraries
|
||||
yield put(deferLoadingJSLibraries(applicationId, customJSLibraries));
|
||||
} else {
|
||||
initActionsCalls.push(fetchJSLibraries(applicationId, customJSLibraries));
|
||||
successActionEffects.push(ReduxActionTypes.FETCH_JS_LIBRARIES_SUCCESS);
|
||||
failureActionEffects.push(
|
||||
ReduxActionErrorTypes.FETCH_JS_LIBRARIES_FAILED,
|
||||
);
|
||||
}
|
||||
|
||||
const resultOfPrimaryCalls: boolean = yield failFastApiCalls(
|
||||
initActionsCalls,
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ import { getWidgetHierarchy } from "layoutSystems/anvil/utils/paste/utils";
|
|||
import type { AnvilGlobalDnDStates } from "../../canvas/hooks/useAnvilGlobalDnDStates";
|
||||
import { getWidgets } from "sagas/selectors";
|
||||
import { useMemo } from "react";
|
||||
import { WDSZoneWidget } from "widgets/wds/WDSZoneWidget";
|
||||
import { useAnvilWidgetElevation } from "../../canvas/providers/AnvilWidgetElevationProvider";
|
||||
import { anvilWidgets } from "widgets/wds/constants";
|
||||
|
||||
interface AnvilDnDListenerStatesProps {
|
||||
anvilGlobalDragStates: AnvilGlobalDnDStates;
|
||||
|
|
@ -148,7 +148,7 @@ export const useAnvilDnDListenerStates = ({
|
|||
}, [widgetProps, allWidgets]);
|
||||
|
||||
const isElevatedWidget = useMemo(() => {
|
||||
if (widgetProps.type === WDSZoneWidget.type) {
|
||||
if (widgetProps.type === anvilWidgets.ZONE_WIDGET) {
|
||||
const isAnyZoneElevated = allSiblingsWidgetIds.some(
|
||||
(each) => !!elevatedWidgets[each],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -56,14 +56,13 @@ export const FixedLayoutViewerCanvas = (props: BaseWidgetProps) => {
|
|||
!!props.noPad,
|
||||
);
|
||||
}, [
|
||||
props.children,
|
||||
props?.children,
|
||||
props?.metaWidgetChildrenStructure,
|
||||
props.positioning,
|
||||
props.shouldScrollContents,
|
||||
props.widgetId,
|
||||
props.componentHeight,
|
||||
props.componentWidth,
|
||||
snapColumnSpace,
|
||||
props.metaWidgetChildrenStructure,
|
||||
props.noPad,
|
||||
defaultWidgetProps,
|
||||
layoutSystemProps,
|
||||
]);
|
||||
const snapRows = getCanvasSnapRows(props.bottomRow);
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { useIsMobileDevice } from "utils/hooks/useDeviceDetect";
|
|||
import HtmlTitle from "../AppViewerHtmlTitle";
|
||||
import Sidebar from "./Sidebar";
|
||||
import TopHeader from "./components/TopHeader";
|
||||
import { getRenderPage } from "selectors/evaluationSelectors";
|
||||
|
||||
export function Navigation() {
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -50,7 +51,7 @@ export function Navigation() {
|
|||
getCurrentApplication,
|
||||
);
|
||||
const pages = useSelector(getViewModePageList);
|
||||
|
||||
const shouldShowHeader = useSelector(getRenderPage);
|
||||
const queryParams = new URLSearchParams(search);
|
||||
const isEmbed = queryParams.get("embed") === "true";
|
||||
const forceShowNavBar = queryParams.get("navbar") === "true";
|
||||
|
|
@ -69,15 +70,17 @@ export function Navigation() {
|
|||
// TODO: refactor this to not directly reference a DOM element by class defined elsewhere
|
||||
useEffect(
|
||||
function adjustHeaderHeightEffect() {
|
||||
const header = document.querySelector(".js-appviewer-header");
|
||||
if (shouldShowHeader) {
|
||||
const header = document.querySelector(".js-appviewer-header");
|
||||
|
||||
dispatch(setAppViewHeaderHeight(header?.clientHeight || 0));
|
||||
dispatch(setAppViewHeaderHeight(header?.clientHeight || 0));
|
||||
}
|
||||
|
||||
return () => {
|
||||
dispatch(setAppViewHeaderHeight(0));
|
||||
};
|
||||
},
|
||||
[navStyle, orientation, dispatch],
|
||||
[navStyle, orientation, dispatch, shouldShowHeader],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
|
|
@ -122,6 +125,8 @@ export function Navigation() {
|
|||
pages,
|
||||
]);
|
||||
|
||||
if (!shouldShowHeader) return null;
|
||||
|
||||
if (hideHeader) return <HtmlTitle />;
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@ import {
|
|||
getAppThemeSettings,
|
||||
getCurrentApplication,
|
||||
} from "ee/selectors/applicationSelectors";
|
||||
import { editorInitializer } from "../../utils/editor/EditorUtils";
|
||||
import { widgetInitialisationSuccess } from "../../actions/widgetActions";
|
||||
import {
|
||||
ThemeProvider as WDSThemeProvider,
|
||||
useTheme,
|
||||
|
|
@ -49,6 +47,10 @@ import {
|
|||
import urlBuilder from "ee/entities/URLRedirect/URLAssembly";
|
||||
import { getHideWatermark } from "ee/selectors/organizationSelectors";
|
||||
import { getIsAnvilLayout } from "layoutSystems/anvil/integrations/selectors";
|
||||
import { getRenderPage } from "selectors/evaluationSelectors";
|
||||
import type { ReactNode } from "react";
|
||||
import { registerLayoutComponents } from "layoutSystems/anvil/utils/layouts/layoutUtils";
|
||||
import { widgetInitialisationSuccess } from "actions/widgetActions";
|
||||
|
||||
const AppViewerBody = styled.section<{
|
||||
hasPages: boolean;
|
||||
|
|
@ -80,6 +82,21 @@ type Props = AppViewerProps & RouteComponentProps<AppViewerRouteParams>;
|
|||
|
||||
const DEFAULT_FONT_NAME = "System Default";
|
||||
|
||||
function WDSThemeProviderWithTheme({ children }: { children: ReactNode }) {
|
||||
const isAnvilLayout = useSelector(getIsAnvilLayout);
|
||||
const themeSetting = useSelector(getAppThemeSettings);
|
||||
const wdsThemeProps = {
|
||||
borderRadius: themeSetting.borderRadius,
|
||||
seedColor: themeSetting.accentColor,
|
||||
colorMode: themeSetting.colorMode.toLowerCase(),
|
||||
userSizing: themeSetting.sizing,
|
||||
userDensity: themeSetting.density,
|
||||
} as Parameters<typeof useTheme>[0];
|
||||
const { theme } = useTheme(isAnvilLayout ? wdsThemeProps : {});
|
||||
|
||||
return <WDSThemeProvider theme={theme}>{children}</WDSThemeProvider>;
|
||||
}
|
||||
|
||||
function AppViewer(props: Props) {
|
||||
const dispatch = useDispatch();
|
||||
const { pathname, search } = props.location;
|
||||
|
|
@ -103,15 +120,7 @@ function AppViewer(props: Props) {
|
|||
getCurrentApplication,
|
||||
);
|
||||
const isAnvilLayout = useSelector(getIsAnvilLayout);
|
||||
const themeSetting = useSelector(getAppThemeSettings);
|
||||
const wdsThemeProps = {
|
||||
borderRadius: themeSetting.borderRadius,
|
||||
seedColor: themeSetting.accentColor,
|
||||
colorMode: themeSetting.colorMode.toLowerCase(),
|
||||
userSizing: themeSetting.sizing,
|
||||
userDensity: themeSetting.density,
|
||||
} as Parameters<typeof useTheme>[0];
|
||||
const { theme } = useTheme(isAnvilLayout ? wdsThemeProps : {});
|
||||
const renderPage = useSelector(getRenderPage);
|
||||
|
||||
const focusRef = useWidgetFocus();
|
||||
const isAutoLayout = useSelector(getIsAutoLayout);
|
||||
|
|
@ -120,9 +129,9 @@ function AppViewer(props: Props) {
|
|||
* initializes the widgets factory and registers all widgets
|
||||
*/
|
||||
useEffect(() => {
|
||||
editorInitializer().then(() => {
|
||||
dispatch(widgetInitialisationSuccess());
|
||||
});
|
||||
registerLayoutComponents();
|
||||
// we want to intialise only the widgets relevant to the tab within the appViewer page first so that first evaluation is faster
|
||||
dispatch(widgetInitialisationSuccess());
|
||||
}, []);
|
||||
/**
|
||||
* initialize the app if branch, pageId or application is changed
|
||||
|
|
@ -205,6 +214,8 @@ function AppViewer(props: Props) {
|
|||
};
|
||||
}, [selectedTheme.properties.fontFamily.appFont]);
|
||||
|
||||
if (!renderPage) return null;
|
||||
|
||||
const renderChildren = () => {
|
||||
return (
|
||||
<EditorContextProvider renderMode="PAGE">
|
||||
|
|
@ -251,7 +262,7 @@ function AppViewer(props: Props) {
|
|||
|
||||
if (isAnvilLayout) {
|
||||
return (
|
||||
<WDSThemeProvider theme={theme}>{renderChildren()}</WDSThemeProvider>
|
||||
<WDSThemeProviderWithTheme>{renderChildren()}</WDSThemeProviderWithTheme>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import TemplateDescription from "./Template/TemplateDescription";
|
|||
import SimilarTemplates from "./Template/SimilarTemplates";
|
||||
import { templateIdUrl } from "ee/RouteBuilder";
|
||||
import TemplateViewHeader from "./TemplateViewHeader";
|
||||
import { registerEditorWidgets } from "utils/editor/EditorUtils";
|
||||
import { registerAllWidgets } from "utils/editor/EditorUtils";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
overflow: auto;
|
||||
|
|
@ -154,7 +154,7 @@ export function TemplateView({
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
registerEditorWidgets();
|
||||
registerAllWidgets();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
dispatch(getTemplateInformation(templateId));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import type { ReduxAction } from "actions/ReduxActionTypes";
|
||||
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
|
||||
|
||||
export interface FirstEvaluationState {
|
||||
renderPage: boolean;
|
||||
isFirstPageLoad: boolean;
|
||||
}
|
||||
|
||||
const initialState: FirstEvaluationState = {
|
||||
renderPage: false,
|
||||
isFirstPageLoad: true,
|
||||
};
|
||||
|
||||
export default function firstEvaluationReducer(
|
||||
state = initialState,
|
||||
action: ReduxAction<unknown>,
|
||||
): FirstEvaluationState {
|
||||
switch (action.type) {
|
||||
case ReduxActionTypes.RENDER_PAGE:
|
||||
return { ...state, renderPage: true };
|
||||
case ReduxActionTypes.IS_FIRST_PAGE_LOAD:
|
||||
return { ...state, isFirstPageLoad: false };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import evaluationDependencyReducer from "./dependencyReducer";
|
|||
import loadingEntitiesReducer from "./loadingEntitiesReducer";
|
||||
import formEvaluationReducer from "./formEvaluationReducer";
|
||||
import triggerReducer from "./triggerReducer";
|
||||
import firstEvaluationReducer from "./firstEvaluationReducer";
|
||||
|
||||
export default combineReducers({
|
||||
tree: evaluatedTreeReducer,
|
||||
|
|
@ -11,4 +12,5 @@ export default combineReducers({
|
|||
loadingEntities: loadingEntitiesReducer,
|
||||
formEvaluation: formEvaluationReducer,
|
||||
triggers: triggerReducer,
|
||||
firstEvaluation: firstEvaluationReducer,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { all, call, put, select, spawn, take } from "redux-saga/effects";
|
||||
import { all, call, put, spawn, take } from "redux-saga/effects";
|
||||
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
|
||||
import { MAIN_THREAD_ACTION } from "ee/workers/Evaluation/evalWorkerActions";
|
||||
import log from "loglevel";
|
||||
|
|
@ -13,6 +13,7 @@ import { MessageType } from "utils/MessageUtil";
|
|||
import type { ResponsePayload } from "../sagas/EvaluationsSaga";
|
||||
import {
|
||||
executeTriggerRequestSaga,
|
||||
getUnevalTreeWithWidgetsRegistered,
|
||||
updateDataTreeHandler,
|
||||
} from "../sagas/EvaluationsSaga";
|
||||
import { evalWorker } from "utils/workerInstances";
|
||||
|
|
@ -22,7 +23,7 @@ import isEmpty from "lodash/isEmpty";
|
|||
import { sortJSExecutionDataByCollectionId } from "workers/Evaluation/JSObject/utils";
|
||||
import type { LintTreeSagaRequestData } from "plugins/Linting/types";
|
||||
import { evalErrorHandler } from "./EvalErrorHandler";
|
||||
import { getUnevaluatedDataTree } from "selectors/dataTreeSelectors";
|
||||
import type { getUnevaluatedDataTree } from "selectors/dataTreeSelectors";
|
||||
import { endSpan, startRootSpan } from "instrumentation/generateTraces";
|
||||
import type { UpdateDataTreeMessageData } from "./types";
|
||||
|
||||
|
|
@ -165,9 +166,8 @@ export function* handleEvalWorkerMessage(message: TMessage<any>) {
|
|||
case MAIN_THREAD_ACTION.UPDATE_DATATREE: {
|
||||
const { workerResponse } = data as UpdateDataTreeMessageData;
|
||||
const rootSpan = startRootSpan("DataTreeFactory.create");
|
||||
|
||||
const unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree> =
|
||||
yield select(getUnevaluatedDataTree);
|
||||
yield call(getUnevalTreeWithWidgetsRegistered);
|
||||
|
||||
endSpan(rootSpan);
|
||||
|
||||
|
|
|
|||
|
|
@ -34,8 +34,17 @@ import {
|
|||
getCurrentPageId,
|
||||
} from "selectors/editorSelectors";
|
||||
import { updateActionData } from "actions/pluginActionActions";
|
||||
import watchInitSagas from "./InitSagas";
|
||||
|
||||
import { clearAllWidgetFactoryCache } from "WidgetProvider/factory/decorators";
|
||||
|
||||
jest.mock("loglevel");
|
||||
jest.mock("utils/editor/EditorUtils", () => ({
|
||||
registerAllWidgets: jest.fn(),
|
||||
}));
|
||||
jest.mock("WidgetProvider/factory/decorators", () => ({
|
||||
clearAllWidgetFactoryCache: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("evaluateTreeSaga", () => {
|
||||
afterAll(() => {
|
||||
|
|
@ -64,29 +73,34 @@ describe("evaluateTreeSaga", () => {
|
|||
],
|
||||
[select(getCurrentPageDSLVersion), 1],
|
||||
])
|
||||
.call(evalWorker.request, EVAL_WORKER_ACTIONS.EVAL_TREE, {
|
||||
cacheProps: {
|
||||
instanceId: "instanceId",
|
||||
appId: "applicationId",
|
||||
pageId: "pageId",
|
||||
.call(
|
||||
evalWorker.request,
|
||||
EVAL_WORKER_ACTIONS.EVAL_TREE,
|
||||
{
|
||||
cacheProps: {
|
||||
instanceId: "instanceId",
|
||||
appId: "applicationId",
|
||||
pageId: "pageId",
|
||||
appMode: false,
|
||||
timestamp: new Date("11 September 2024").toISOString(),
|
||||
dslVersion: 1,
|
||||
},
|
||||
unevalTree: unEvalAndConfigTree,
|
||||
widgetTypeConfigMap: undefined,
|
||||
widgets: {},
|
||||
theme: {},
|
||||
shouldReplay: true,
|
||||
allActionValidationConfig: {},
|
||||
forceEvaluation: false,
|
||||
metaWidgets: {},
|
||||
appMode: false,
|
||||
timestamp: new Date("11 September 2024").toISOString(),
|
||||
dslVersion: 1,
|
||||
widgetsMeta: {},
|
||||
shouldRespondWithLogs: true,
|
||||
affectedJSObjects: { ids: [], isAllAffected: false },
|
||||
actionDataPayloadConsolidated: undefined,
|
||||
},
|
||||
unevalTree: unEvalAndConfigTree,
|
||||
widgetTypeConfigMap: undefined,
|
||||
widgets: {},
|
||||
theme: {},
|
||||
shouldReplay: true,
|
||||
allActionValidationConfig: {},
|
||||
forceEvaluation: false,
|
||||
metaWidgets: {},
|
||||
appMode: false,
|
||||
widgetsMeta: {},
|
||||
shouldRespondWithLogs: true,
|
||||
affectedJSObjects: { ids: [], isAllAffected: false },
|
||||
actionDataPayloadConsolidated: undefined,
|
||||
})
|
||||
false,
|
||||
)
|
||||
.run();
|
||||
});
|
||||
test("should set 'shouldRespondWithLogs' to false when the log level is not debug", async () => {
|
||||
|
|
@ -112,29 +126,34 @@ describe("evaluateTreeSaga", () => {
|
|||
],
|
||||
[select(getCurrentPageDSLVersion), 1],
|
||||
])
|
||||
.call(evalWorker.request, EVAL_WORKER_ACTIONS.EVAL_TREE, {
|
||||
cacheProps: {
|
||||
instanceId: "instanceId",
|
||||
appId: "applicationId",
|
||||
pageId: "pageId",
|
||||
.call(
|
||||
evalWorker.request,
|
||||
EVAL_WORKER_ACTIONS.EVAL_TREE,
|
||||
{
|
||||
cacheProps: {
|
||||
instanceId: "instanceId",
|
||||
appId: "applicationId",
|
||||
pageId: "pageId",
|
||||
appMode: false,
|
||||
timestamp: new Date("11 September 2024").toISOString(),
|
||||
dslVersion: 1,
|
||||
},
|
||||
unevalTree: unEvalAndConfigTree,
|
||||
widgetTypeConfigMap: undefined,
|
||||
widgets: {},
|
||||
theme: {},
|
||||
shouldReplay: true,
|
||||
allActionValidationConfig: {},
|
||||
forceEvaluation: false,
|
||||
metaWidgets: {},
|
||||
appMode: false,
|
||||
timestamp: new Date("11 September 2024").toISOString(),
|
||||
dslVersion: 1,
|
||||
widgetsMeta: {},
|
||||
shouldRespondWithLogs: false,
|
||||
affectedJSObjects: { ids: [], isAllAffected: false },
|
||||
actionDataPayloadConsolidated: undefined,
|
||||
},
|
||||
unevalTree: unEvalAndConfigTree,
|
||||
widgetTypeConfigMap: undefined,
|
||||
widgets: {},
|
||||
theme: {},
|
||||
shouldReplay: true,
|
||||
allActionValidationConfig: {},
|
||||
forceEvaluation: false,
|
||||
metaWidgets: {},
|
||||
appMode: false,
|
||||
widgetsMeta: {},
|
||||
shouldRespondWithLogs: false,
|
||||
affectedJSObjects: { ids: [], isAllAffected: false },
|
||||
actionDataPayloadConsolidated: undefined,
|
||||
})
|
||||
false,
|
||||
)
|
||||
.run();
|
||||
});
|
||||
test("should propagate affectedJSObjects property to evaluation action", async () => {
|
||||
|
|
@ -169,29 +188,95 @@ describe("evaluateTreeSaga", () => {
|
|||
],
|
||||
[select(getCurrentPageDSLVersion), 1],
|
||||
])
|
||||
.call(evalWorker.request, EVAL_WORKER_ACTIONS.EVAL_TREE, {
|
||||
cacheProps: {
|
||||
instanceId: "instanceId",
|
||||
appId: "applicationId",
|
||||
pageId: "pageId",
|
||||
.call(
|
||||
evalWorker.request,
|
||||
EVAL_WORKER_ACTIONS.EVAL_TREE,
|
||||
{
|
||||
cacheProps: {
|
||||
instanceId: "instanceId",
|
||||
appId: "applicationId",
|
||||
pageId: "pageId",
|
||||
appMode: false,
|
||||
timestamp: new Date("11 September 2024").toISOString(),
|
||||
dslVersion: 1,
|
||||
},
|
||||
unevalTree: unEvalAndConfigTree,
|
||||
widgetTypeConfigMap: undefined,
|
||||
widgets: {},
|
||||
theme: {},
|
||||
shouldReplay: true,
|
||||
allActionValidationConfig: {},
|
||||
forceEvaluation: false,
|
||||
metaWidgets: {},
|
||||
appMode: false,
|
||||
timestamp: new Date("11 September 2024").toISOString(),
|
||||
dslVersion: 1,
|
||||
widgetsMeta: {},
|
||||
shouldRespondWithLogs: false,
|
||||
affectedJSObjects,
|
||||
actionDataPayloadConsolidated: undefined,
|
||||
},
|
||||
unevalTree: unEvalAndConfigTree,
|
||||
widgetTypeConfigMap: undefined,
|
||||
widgets: {},
|
||||
theme: {},
|
||||
shouldReplay: true,
|
||||
allActionValidationConfig: {},
|
||||
forceEvaluation: false,
|
||||
metaWidgets: {},
|
||||
appMode: false,
|
||||
widgetsMeta: {},
|
||||
shouldRespondWithLogs: false,
|
||||
affectedJSObjects,
|
||||
actionDataPayloadConsolidated: undefined,
|
||||
})
|
||||
false,
|
||||
)
|
||||
.run();
|
||||
});
|
||||
test("should call evalWorker.request with isFirstEvaluation as true when isFirstEvaluation is set as true in evaluateTreeSaga", async () => {
|
||||
const unEvalAndConfigTree = { unEvalTree: {}, configTree: {} };
|
||||
const isFirstEvaluation = true;
|
||||
|
||||
return expectSaga(
|
||||
evaluateTreeSaga,
|
||||
unEvalAndConfigTree,
|
||||
[],
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
isFirstEvaluation,
|
||||
)
|
||||
.provide([
|
||||
[select(getAllActionValidationConfig), {}],
|
||||
[select(getWidgets), {}],
|
||||
[select(getMetaWidgets), {}],
|
||||
[select(getSelectedAppTheme), {}],
|
||||
[select(getAppMode), false],
|
||||
[select(getWidgetsMeta), {}],
|
||||
[select(getInstanceId), "instanceId"],
|
||||
[select(getCurrentApplicationId), "applicationId"],
|
||||
[select(getCurrentPageId), "pageId"],
|
||||
[
|
||||
select(getApplicationLastDeployedAt),
|
||||
new Date("11 September 2024").toISOString(),
|
||||
],
|
||||
[select(getCurrentPageDSLVersion), 1],
|
||||
])
|
||||
.call(
|
||||
evalWorker.request,
|
||||
EVAL_WORKER_ACTIONS.EVAL_TREE,
|
||||
{
|
||||
cacheProps: {
|
||||
instanceId: "instanceId",
|
||||
appId: "applicationId",
|
||||
pageId: "pageId",
|
||||
appMode: false,
|
||||
timestamp: new Date("11 September 2024").toISOString(),
|
||||
dslVersion: 1,
|
||||
},
|
||||
unevalTree: unEvalAndConfigTree,
|
||||
widgetTypeConfigMap: undefined,
|
||||
widgets: {},
|
||||
theme: {},
|
||||
shouldReplay: true,
|
||||
allActionValidationConfig: {},
|
||||
forceEvaluation: false,
|
||||
metaWidgets: {},
|
||||
appMode: false,
|
||||
widgetsMeta: {},
|
||||
shouldRespondWithLogs: false,
|
||||
affectedJSObjects: { ids: [], isAllAffected: false },
|
||||
actionDataPayloadConsolidated: undefined,
|
||||
},
|
||||
true,
|
||||
)
|
||||
.run();
|
||||
});
|
||||
});
|
||||
|
|
@ -534,3 +619,15 @@ describe("evaluationLoopWithDebounce", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("first evaluation integration", () => {
|
||||
it("should call clearAllWidgetFactoryCache when WIDGET_INIT_SUCCESS is dispatched", async () => {
|
||||
await expectSaga(watchInitSagas)
|
||||
.dispatch({
|
||||
type: ReduxActionTypes.WIDGET_INIT_SUCCESS,
|
||||
})
|
||||
.silentRun();
|
||||
|
||||
expect(clearAllWidgetFactoryCache).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import type { ActionPattern, CallEffect, ForkEffect } from "redux-saga/effects";
|
||||
import type {
|
||||
ActionPattern,
|
||||
CallEffect,
|
||||
Effect,
|
||||
ForkEffect,
|
||||
} from "redux-saga/effects";
|
||||
import {
|
||||
actionChannel,
|
||||
all,
|
||||
|
|
@ -9,6 +14,7 @@ import {
|
|||
select,
|
||||
spawn,
|
||||
take,
|
||||
join,
|
||||
} from "redux-saga/effects";
|
||||
|
||||
import type {
|
||||
|
|
@ -16,7 +22,10 @@ import type {
|
|||
ReduxActionType,
|
||||
AnyReduxAction,
|
||||
} from "actions/ReduxActionTypes";
|
||||
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
|
||||
import {
|
||||
ReduxActionTypes,
|
||||
ReduxActionErrorTypes,
|
||||
} from "ee/constants/ReduxActionConstants";
|
||||
import {
|
||||
getDataTree,
|
||||
getUnevaluatedDataTree,
|
||||
|
|
@ -39,6 +48,7 @@ import {
|
|||
import {
|
||||
setDependencyMap,
|
||||
setEvaluatedTree,
|
||||
setIsFirstPageLoad,
|
||||
shouldForceEval,
|
||||
shouldLog,
|
||||
shouldProcessAction,
|
||||
|
|
@ -99,7 +109,7 @@ import {
|
|||
} from "actions/pluginActionActions";
|
||||
import { executeJSUpdates } from "actions/jsPaneActions";
|
||||
import { setEvaluatedActionSelectorField } from "actions/actionSelectorActions";
|
||||
import { waitForWidgetConfigBuild } from "./InitSagas";
|
||||
|
||||
import { logDynamicTriggerExecution } from "ee/sagas/analyticsSaga";
|
||||
import { selectFeatureFlags } from "ee/selectors/featureFlagsSelectors";
|
||||
import { fetchFeatureFlagsInit } from "actions/userActions";
|
||||
|
|
@ -108,7 +118,6 @@ import {
|
|||
parseUpdatesAndDeleteUndefinedUpdates,
|
||||
} from "./EvaluationsSagaUtils";
|
||||
import { getFeatureFlagsFetched } from "selectors/usersSelectors";
|
||||
import { getIsCurrentEditorWorkflowType } from "ee/selectors/workflowSelectors";
|
||||
import { evalErrorHandler } from "./EvalErrorHandler";
|
||||
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
|
||||
import { endSpan, startRootSpan } from "instrumentation/generateTraces";
|
||||
|
|
@ -124,11 +133,89 @@ import type {
|
|||
EvaluationReduxAction,
|
||||
} from "actions/EvaluationReduxActionTypes";
|
||||
import { appsmithTelemetry } from "instrumentation";
|
||||
import { getUsedWidgetTypes } from "selectors/widgetSelectors";
|
||||
import type BaseWidget from "widgets/BaseWidget";
|
||||
import { loadWidget } from "widgets";
|
||||
import { registerWidgets } from "WidgetProvider/factory/registrationHelper";
|
||||
import { failFastApiCalls } from "./InitSagas";
|
||||
import { fetchJSLibraries } from "actions/JSLibraryActions";
|
||||
import type { Task } from "redux-saga";
|
||||
import { getAllUniqueWidgetTypesInUiModules } from "ee/selectors/moduleInstanceSelectors";
|
||||
import { clearAllWidgetFactoryCache } from "WidgetProvider/factory/decorators";
|
||||
|
||||
const APPSMITH_CONFIGS = getAppsmithConfigs();
|
||||
|
||||
let widgetTypeConfigMap: WidgetTypeConfigMap;
|
||||
|
||||
// Common worker setup logic
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function* setupWorkers(clearCache = false): any {
|
||||
// Explicitly shutdown old worker if present
|
||||
yield all([call(evalWorker.shutdown), call(lintWorker.shutdown)]);
|
||||
const [evalWorkerListenerChannel] = yield all([
|
||||
call(evalWorker.start),
|
||||
call(lintWorker.start),
|
||||
]);
|
||||
|
||||
if (clearCache) {
|
||||
yield call(evalWorker.request, EVAL_WORKER_ACTIONS.CLEAR_CACHE);
|
||||
}
|
||||
|
||||
const isFFFetched = yield select(getFeatureFlagsFetched);
|
||||
|
||||
if (!isFFFetched) {
|
||||
yield call(fetchFeatureFlagsInit);
|
||||
yield take(ReduxActionTypes.FETCH_FEATURE_FLAGS_SUCCESS);
|
||||
}
|
||||
|
||||
const featureFlags: Record<string, boolean> =
|
||||
yield select(selectFeatureFlags);
|
||||
|
||||
yield call(evalWorker.request, EVAL_WORKER_ACTIONS.SETUP, {
|
||||
cloudHosting: !!APPSMITH_CONFIGS.cloudHosting,
|
||||
featureFlags: featureFlags,
|
||||
});
|
||||
|
||||
return evalWorkerListenerChannel;
|
||||
}
|
||||
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function* webWorkerSetupSaga(): any {
|
||||
const evalWorkerListenerChannel = yield call(setupWorkers);
|
||||
|
||||
yield spawn(handleEvalWorkerRequestSaga, evalWorkerListenerChannel);
|
||||
}
|
||||
|
||||
function* webWorkerSetupSagaWithJSLibraries(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
initializeJSLibrariesChannel: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): any {
|
||||
const evalWorkerListenerChannel = yield call(setupWorkers, true);
|
||||
|
||||
// Take the action from the appVi
|
||||
const jsLibrariesAction = yield take(initializeJSLibrariesChannel);
|
||||
const { applicationId, customJSLibraries } = jsLibrariesAction.payload;
|
||||
|
||||
yield put(setIsFirstPageLoad());
|
||||
|
||||
// Use failFastApiCalls to execute fetchJSLibraries
|
||||
const resultOfJSLibrariesCall: boolean = yield call(
|
||||
failFastApiCalls,
|
||||
[fetchJSLibraries(applicationId, customJSLibraries)],
|
||||
[ReduxActionTypes.FETCH_JS_LIBRARIES_SUCCESS],
|
||||
[ReduxActionErrorTypes.FETCH_JS_LIBRARIES_FAILED],
|
||||
);
|
||||
|
||||
if (!resultOfJSLibrariesCall) {
|
||||
throw new Error("Failed to load JS libraries");
|
||||
}
|
||||
|
||||
yield spawn(handleEvalWorkerRequestSaga, evalWorkerListenerChannel);
|
||||
}
|
||||
|
||||
export function* updateDataTreeHandler(
|
||||
data: {
|
||||
evalTreeResponse: EvalTreeResponseData;
|
||||
|
|
@ -271,6 +358,7 @@ export function* evaluateTreeSaga(
|
|||
requiresLogging = false,
|
||||
affectedJSObjects: AffectedJSObjects = defaultAffectedJSObjects,
|
||||
actionDataPayloadConsolidated?: actionDataPayload,
|
||||
isFirstEvaluation = false,
|
||||
) {
|
||||
const allActionValidationConfig: ReturnType<
|
||||
typeof getAllActionValidationConfig
|
||||
|
|
@ -322,6 +410,7 @@ export function* evaluateTreeSaga(
|
|||
evalWorker.request,
|
||||
EVAL_WORKER_ACTIONS.EVAL_TREE,
|
||||
evalTreeRequestData,
|
||||
isFirstEvaluation,
|
||||
);
|
||||
|
||||
yield call(
|
||||
|
|
@ -369,8 +458,8 @@ export function* evaluateAndExecuteDynamicTrigger(
|
|||
) {
|
||||
const rootSpan = startRootSpan("DataTreeFactory.create");
|
||||
|
||||
const unEvalTree: ReturnType<typeof getUnevaluatedDataTree> = yield select(
|
||||
getUnevaluatedDataTree,
|
||||
const unEvalTree: ReturnType<typeof getUnevaluatedDataTree> = yield call(
|
||||
getUnevalTreeWithWidgetsRegistered,
|
||||
);
|
||||
|
||||
endSpan(rootSpan);
|
||||
|
|
@ -521,7 +610,7 @@ function* validateProperty(property: string, value: any, props: WidgetProps) {
|
|||
const rootSpan = startRootSpan("DataTreeFactory.create");
|
||||
|
||||
const unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree> =
|
||||
yield select(getUnevaluatedDataTree);
|
||||
yield call(getUnevalTreeWithWidgetsRegistered);
|
||||
|
||||
endSpan(rootSpan);
|
||||
const configTree = unEvalAndConfigTree.configTree;
|
||||
|
|
@ -541,6 +630,15 @@ function* validateProperty(property: string, value: any, props: WidgetProps) {
|
|||
return response;
|
||||
}
|
||||
|
||||
export function* getUnevalTreeWithWidgetsRegistered() {
|
||||
yield call(loadAndRegisterOnlyCanvasWidgets);
|
||||
|
||||
const unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree> =
|
||||
yield select(getUnevaluatedDataTree);
|
||||
|
||||
return unEvalAndConfigTree;
|
||||
}
|
||||
|
||||
// We are clubbing all pending action's affected JS objects into the buffered action
|
||||
// So that during that evaluation cycle all affected JS objects are correctly diffed
|
||||
function mergeJSBufferedActions(
|
||||
|
|
@ -706,6 +804,8 @@ export function* evalAndLintingHandler(
|
|||
requiresLogging: boolean;
|
||||
affectedJSObjects: AffectedJSObjects;
|
||||
actionDataPayloadConsolidated: actionDataPayload[];
|
||||
isFirstEvaluation?: boolean;
|
||||
jsLibrariesTask?: Task;
|
||||
}>,
|
||||
) {
|
||||
const span = startRootSpan("evalAndLintingHandler");
|
||||
|
|
@ -713,6 +813,9 @@ export function* evalAndLintingHandler(
|
|||
actionDataPayloadConsolidated,
|
||||
affectedJSObjects,
|
||||
forceEvaluation,
|
||||
|
||||
isFirstEvaluation = false,
|
||||
jsLibrariesTask,
|
||||
requiresLogging,
|
||||
shouldReplay,
|
||||
} = options;
|
||||
|
|
@ -737,10 +840,17 @@ export function* evalAndLintingHandler(
|
|||
|
||||
// Generate all the data needed for both eval and linting
|
||||
const unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree> =
|
||||
yield select(getUnevaluatedDataTree);
|
||||
yield call(getUnevalTreeWithWidgetsRegistered);
|
||||
|
||||
widgetTypeConfigMap = WidgetFactory.getWidgetTypeConfigMap();
|
||||
|
||||
endSpan(rootSpan);
|
||||
|
||||
// wait for the webworker to complete its setup before starting the evaluation
|
||||
if (jsLibrariesTask) {
|
||||
yield join(jsLibrariesTask);
|
||||
}
|
||||
|
||||
const postEvalActions = getPostEvalActions(action);
|
||||
const fn: (...args: unknown[]) => CallEffect<unknown> | ForkEffect<unknown> =
|
||||
isBlockingCall ? call : fork;
|
||||
|
|
@ -758,6 +868,7 @@ export function* evalAndLintingHandler(
|
|||
requiresLogging,
|
||||
affectedJSObjects,
|
||||
actionDataPayloadConsolidated,
|
||||
isFirstEvaluation,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -769,51 +880,80 @@ export function* evalAndLintingHandler(
|
|||
yield all(effects);
|
||||
endSpan(span);
|
||||
}
|
||||
export function* loadAndRegisterOnlyCanvasWidgets(): Generator<
|
||||
Effect,
|
||||
(typeof BaseWidget)[],
|
||||
unknown
|
||||
> {
|
||||
try {
|
||||
const widgetTypes = (yield select(getUsedWidgetTypes)) as string[];
|
||||
|
||||
const uiModuleTypes = (yield select(
|
||||
getAllUniqueWidgetTypesInUiModules,
|
||||
)) as string[];
|
||||
|
||||
const uniqueWidgetTypes = Array.from(
|
||||
new Set([...uiModuleTypes, ...widgetTypes, "SKELETON_WIDGET"]),
|
||||
);
|
||||
|
||||
// Filter out already registered widget types
|
||||
const unregisteredWidgetTypes = uniqueWidgetTypes.filter(
|
||||
(type: string) => !WidgetFactory.widgetsMap.has(type),
|
||||
);
|
||||
|
||||
if (!unregisteredWidgetTypes.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Load only unregistered widgets in parallel
|
||||
const loadedWidgets = (yield all(
|
||||
unregisteredWidgetTypes.map((type: string) => call(loadWidget, type)),
|
||||
)) as (typeof BaseWidget)[];
|
||||
|
||||
// Register only the newly loaded widgets
|
||||
registerWidgets(loadedWidgets);
|
||||
|
||||
clearAllWidgetFactoryCache();
|
||||
|
||||
return loadedWidgets;
|
||||
} catch (error) {
|
||||
log.error("Error loading and registering widgets:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function* evaluationChangeListenerSaga(): any {
|
||||
const firstEvalActionChannel = yield actionChannel(FIRST_EVAL_REDUX_ACTIONS);
|
||||
|
||||
// Explicitly shutdown old worker if present
|
||||
yield all([call(evalWorker.shutdown), call(lintWorker.shutdown)]);
|
||||
const [evalWorkerListenerChannel] = yield all([
|
||||
call(evalWorker.start),
|
||||
call(lintWorker.start),
|
||||
]);
|
||||
const initializeJSLibrariesChannel = yield actionChannel(
|
||||
ReduxActionTypes.DEFER_LOADING_JS_LIBRARIES,
|
||||
);
|
||||
const appMode = yield select(getAppMode);
|
||||
|
||||
const isFFFetched = yield select(getFeatureFlagsFetched);
|
||||
let jsLibrariesTask: Task | undefined;
|
||||
|
||||
if (!isFFFetched) {
|
||||
yield call(fetchFeatureFlagsInit);
|
||||
yield take(ReduxActionTypes.FETCH_FEATURE_FLAGS_SUCCESS);
|
||||
// for all published apps, we need to reset the data tree and setup the worker as an independent process
|
||||
// after the process is forked we can allow the main thread to continue its execution since the main thread's tasks would be independent
|
||||
// we just need to ensure that the webworker setup is completed before the first evaluation is triggered
|
||||
if (appMode === APP_MODE.PUBLISHED) {
|
||||
yield put({ type: ReduxActionTypes.RESET_DATA_TREE });
|
||||
jsLibrariesTask = yield fork(
|
||||
webWorkerSetupSagaWithJSLibraries,
|
||||
initializeJSLibrariesChannel,
|
||||
);
|
||||
} else {
|
||||
// for all other modes, just call the webworker
|
||||
yield call(webWorkerSetupSaga);
|
||||
}
|
||||
|
||||
const featureFlags: Record<string, boolean> =
|
||||
yield select(selectFeatureFlags);
|
||||
|
||||
yield call(evalWorker.request, EVAL_WORKER_ACTIONS.SETUP, {
|
||||
cloudHosting: !!APPSMITH_CONFIGS.cloudHosting,
|
||||
featureFlags: featureFlags,
|
||||
});
|
||||
yield spawn(handleEvalWorkerRequestSaga, evalWorkerListenerChannel);
|
||||
|
||||
const initAction: EvaluationReduxAction<unknown> = yield take(
|
||||
firstEvalActionChannel,
|
||||
);
|
||||
|
||||
firstEvalActionChannel.close();
|
||||
|
||||
// Wait for widget config build to complete before starting evaluation only if the current editor is not a workflow
|
||||
const isCurrentEditorWorkflowType = yield select(
|
||||
getIsCurrentEditorWorkflowType,
|
||||
);
|
||||
|
||||
if (!isCurrentEditorWorkflowType) {
|
||||
yield call(waitForWidgetConfigBuild);
|
||||
}
|
||||
|
||||
widgetTypeConfigMap = WidgetFactory.getWidgetTypeConfigMap();
|
||||
yield fork(evalAndLintingHandler, false, initAction, {
|
||||
shouldReplay: false,
|
||||
forceEvaluation: false,
|
||||
|
|
@ -822,6 +962,8 @@ function* evaluationChangeListenerSaga(): any {
|
|||
ids: [],
|
||||
isAllAffected: true,
|
||||
},
|
||||
isFirstEvaluation: true,
|
||||
jsLibrariesTask: jsLibrariesTask,
|
||||
});
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ import type { Page } from "entities/Page";
|
|||
import type { PACKAGE_PULL_STATUS } from "ee/constants/ModuleConstants";
|
||||
import { validateSessionToken } from "utils/SessionUtils";
|
||||
import { appsmithTelemetry } from "instrumentation";
|
||||
import { clearAllWidgetFactoryCache } from "WidgetProvider/factory/decorators";
|
||||
|
||||
export const URL_CHANGE_ACTIONS = [
|
||||
ReduxActionTypes.CURRENT_APPLICATION_NAME_UPDATE,
|
||||
|
|
@ -535,6 +536,11 @@ function* eagerPageInitSaga() {
|
|||
} catch (e) {}
|
||||
}
|
||||
|
||||
function handleWidgetInitSuccess() {
|
||||
//every time a widget is initialized, we clear the cache so that all widgetFactory values are recomputed
|
||||
clearAllWidgetFactoryCache();
|
||||
}
|
||||
|
||||
export default function* watchInitSagas() {
|
||||
yield all([
|
||||
takeLeading(
|
||||
|
|
@ -547,5 +553,7 @@ export default function* watchInitSagas() {
|
|||
takeLatest(ReduxActionTypes.RESET_EDITOR_REQUEST, resetEditorSaga),
|
||||
takeEvery(URL_CHANGE_ACTIONS, updateURLSaga),
|
||||
takeEvery(ReduxActionTypes.INITIALIZE_CURRENT_PAGE, eagerPageInitSaga),
|
||||
|
||||
takeLeading(ReduxActionTypes.WIDGET_INIT_SUCCESS, handleWidgetInitSuccess),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { DataTree } from "entities/DataTree/dataTreeTypes";
|
||||
import { createSelector } from "reselect";
|
||||
import WidgetFactory from "WidgetProvider/factory";
|
||||
import { getWidgetConfigsVersion } from "WidgetProvider/factory/widgetConfigVersion";
|
||||
import type { FlattenedWidgetProps } from "WidgetProvider/types";
|
||||
import type { JSLibrary } from "workers/common/JSLibrary";
|
||||
import { getDataTree } from "./dataTreeSelectors";
|
||||
|
|
@ -24,6 +25,7 @@ export const getUsedActionNames = createSelector(
|
|||
getDataTree,
|
||||
getParentWidget,
|
||||
selectInstalledLibraries,
|
||||
getWidgetConfigsVersion, // Add dependency on widget configs version
|
||||
(
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import type { Page } from "entities/Page";
|
|||
import { objectKeys } from "@appsmith/utils";
|
||||
import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsReducer";
|
||||
import { ActionRunBehaviour } from "PluginActionEditor/types/PluginActionTypes";
|
||||
import { getWidgetConfigsVersion } from "WidgetProvider/factory/widgetConfigVersion";
|
||||
|
||||
const getIsDraggingOrResizing = (state: DefaultRootState) =>
|
||||
state.ui.widgetDragResize.isResizing || state.ui.widgetDragResize.isDragging;
|
||||
|
|
@ -398,6 +399,7 @@ const isModuleWidget = (
|
|||
export const getWidgetCards = createSelector(
|
||||
getIsAutoLayout,
|
||||
getIsAnvilLayout,
|
||||
getWidgetConfigsVersion, // Add dependency on widget configs version
|
||||
(isAutoLayout, isAnvilLayout) => {
|
||||
const widgetConfigs = WidgetFactory.getConfigs();
|
||||
const widgetConfigsArray = Object.values(widgetConfigs);
|
||||
|
|
|
|||
7
app/client/src/selectors/evaluationSelectors.ts
Normal file
7
app/client/src/selectors/evaluationSelectors.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import type { DefaultRootState } from "react-redux";
|
||||
|
||||
export const getRenderPage = (state: DefaultRootState): boolean =>
|
||||
state.evaluations?.firstEvaluation?.renderPage ?? false;
|
||||
|
||||
export const getIsFirstPageLoad = (state: DefaultRootState): boolean =>
|
||||
state.evaluations?.firstEvaluation?.isFirstPageLoad ?? false;
|
||||
|
|
@ -8,6 +8,7 @@ import { getExistingWidgetNames } from "sagas/selectors";
|
|||
import { getNextEntityName } from "utils/AppsmithUtils";
|
||||
|
||||
import WidgetFactory from "WidgetProvider/factory";
|
||||
import { getWidgetConfigsVersion } from "WidgetProvider/factory/widgetConfigVersion";
|
||||
import {
|
||||
getAltBlockWidgetSelection,
|
||||
getFocusedWidget,
|
||||
|
|
@ -78,6 +79,7 @@ export const getModalDropdownList = createSelector(
|
|||
export const getNextModalName = createSelector(
|
||||
getExistingWidgetNames,
|
||||
getModalWidgetType,
|
||||
getWidgetConfigsVersion, // Add dependency on widget configs version
|
||||
(names, modalWidgetType) => {
|
||||
const prefix =
|
||||
WidgetFactory.widgetConfigMap.get(modalWidgetType)?.widgetName || "";
|
||||
|
|
@ -267,3 +269,19 @@ export const isResizingOrDragging = createSelector(
|
|||
(state: DefaultRootState) => state.ui.widgetDragResize.isDragging,
|
||||
(isResizing, isDragging) => !!isResizing || !!isDragging,
|
||||
);
|
||||
// get widgets types associated to a tab
|
||||
export const getUsedWidgetTypes = createSelector(
|
||||
getCanvasWidgets,
|
||||
(canvasWidgets) => {
|
||||
const widgetTypes = new Set<string>();
|
||||
|
||||
// Iterate through all widgets in the state
|
||||
Object.values(canvasWidgets).forEach((widget) => {
|
||||
if (widget.type && !widget.type.startsWith("MODULE_WIDGET_")) {
|
||||
widgetTypes.add(widget.type);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(widgetTypes);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export const getCanvasHeightOffset = (
|
|||
props: WidgetProps,
|
||||
) => {
|
||||
const { getCanvasHeightOffset } = WidgetFactory.getWidgetMethods(widgetType);
|
||||
|
||||
let offset = 0;
|
||||
|
||||
if (getCanvasHeightOffset) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
filterSpanData,
|
||||
newWebWorkerSpanData,
|
||||
} from "instrumentation/generateWebWorkerTraces";
|
||||
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
|
||||
|
||||
/**
|
||||
* Wrap a webworker to provide a synchronous request-response semantic.
|
||||
|
|
@ -241,12 +242,13 @@ export class GracefulWorkerService {
|
|||
*
|
||||
* @param method identifier for a rpc method
|
||||
* @param requestData data that we want to send over to the worker
|
||||
* @param isFirstEvaluation whether this is the first evaluation of the request
|
||||
*
|
||||
* @returns response from the worker
|
||||
*/
|
||||
// TODO: Fix this the next time the file is edited
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
*request(method: string, data = {}): any {
|
||||
*request(method: string, data = {}, isFirstEvaluation = false): any {
|
||||
yield this.ready(true);
|
||||
|
||||
// Impossible case, but helps avoid `?` later in code and makes it clearer.
|
||||
|
|
@ -292,6 +294,12 @@ export class GracefulWorkerService {
|
|||
messageId,
|
||||
});
|
||||
|
||||
// Use delay to ensure RENDER_PAGE is dispatched after the sendMessage macro task
|
||||
if (isFirstEvaluation) {
|
||||
yield delay(0); // This ensures the macro task completes
|
||||
yield put({ type: ReduxActionTypes.RENDER_PAGE });
|
||||
}
|
||||
|
||||
// The `this._broker` method is listening to events and will pass response to us over this channel.
|
||||
const response = yield take(ch);
|
||||
const { data, endTime, startTime } = response;
|
||||
|
|
|
|||
|
|
@ -2,14 +2,20 @@
|
|||
// import Widgets from "widgets";
|
||||
import { registerWidgets } from "WidgetProvider/factory/registrationHelper";
|
||||
import { registerLayoutComponents } from "layoutSystems/anvil/utils/layouts/layoutUtils";
|
||||
import widgets from "widgets";
|
||||
import { loadAllWidgets } from "widgets";
|
||||
export const registerAllWidgets = async () => {
|
||||
try {
|
||||
const loadedWidgets = await loadAllWidgets();
|
||||
|
||||
export const registerEditorWidgets = () => {
|
||||
registerWidgets(widgets);
|
||||
registerWidgets(Array.from(loadedWidgets.values()));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error loading widgets", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const editorInitializer = async () => {
|
||||
registerEditorWidgets();
|
||||
await registerAllWidgets();
|
||||
// TODO: do this only for anvil.
|
||||
registerLayoutComponents();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import type {
|
|||
} from "constants/PropertyControlConstants";
|
||||
import { ValidationTypes } from "constants/WidgetValidation";
|
||||
import { isFunction } from "lodash";
|
||||
import widgets from "widgets";
|
||||
import { loadAllWidgets } from "widgets";
|
||||
import WidgetFactory from "WidgetProvider/factory";
|
||||
import { registerWidgets } from "WidgetProvider/factory/registrationHelper";
|
||||
import type BaseWidget from "widgets/BaseWidget";
|
||||
|
||||
function validatePropertyPaneConfig(
|
||||
config: PropertyPaneConfig[],
|
||||
|
|
@ -143,96 +144,112 @@ const isNotFloat = (n: any) => {
|
|||
};
|
||||
|
||||
describe("Tests all widget's propertyPane config", () => {
|
||||
beforeAll(() => {
|
||||
registerWidgets(widgets);
|
||||
let widgetsArray: (typeof BaseWidget)[] = [];
|
||||
|
||||
beforeAll(async () => {
|
||||
// Load all widgets and convert Map to array
|
||||
const widgetsMap = await loadAllWidgets();
|
||||
|
||||
widgetsArray = Array.from(widgetsMap.values());
|
||||
|
||||
// Register all widgets
|
||||
registerWidgets(widgetsArray);
|
||||
});
|
||||
|
||||
widgets
|
||||
// Exclude WDS widgets from the tests, since they work differently
|
||||
.filter((widget) => !widget.type.includes("WDS"))
|
||||
.forEach((widget) => {
|
||||
const config = widget.getConfig();
|
||||
it("should have loaded widgets", () => {
|
||||
expect(widgetsArray.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it(`Checks ${widget.type}'s propertyPaneConfig`, () => {
|
||||
const propertyPaneConfig = widget.getPropertyPaneConfig();
|
||||
describe("Property Pane Config Tests", () => {
|
||||
//widgets are loaded in the beforeAll and ready now
|
||||
widgetsArray
|
||||
// Exclude WDS widgets from the tests, since they work differently
|
||||
.filter((widget) => !widget.type.includes("WDS"))
|
||||
.forEach((widget) => {
|
||||
const config = widget.getConfig();
|
||||
|
||||
expect(
|
||||
validatePropertyPaneConfig(propertyPaneConfig, !!config.hideCard),
|
||||
).toStrictEqual(true);
|
||||
const propertyPaneContentConfig = widget.getPropertyPaneContentConfig();
|
||||
it(`Checks ${widget.type}'s propertyPaneConfig`, () => {
|
||||
const propertyPaneConfig = widget.getPropertyPaneConfig();
|
||||
|
||||
expect(
|
||||
validatePropertyPaneConfig(
|
||||
propertyPaneContentConfig,
|
||||
!!config.isDeprecated,
|
||||
),
|
||||
).toStrictEqual(true);
|
||||
const propertyPaneStyleConfig = widget.getPropertyPaneStyleConfig();
|
||||
expect(
|
||||
validatePropertyPaneConfig(propertyPaneConfig, !!config.hideCard),
|
||||
).toStrictEqual(true);
|
||||
const propertyPaneContentConfig =
|
||||
widget.getPropertyPaneContentConfig();
|
||||
|
||||
expect(
|
||||
validatePropertyPaneConfig(
|
||||
propertyPaneStyleConfig,
|
||||
!!config.isDeprecated,
|
||||
),
|
||||
).toStrictEqual(true);
|
||||
});
|
||||
it(`Check if ${widget.type}'s dimensions are always integers`, () => {
|
||||
const defaults = widget.getDefaults();
|
||||
expect(
|
||||
validatePropertyPaneConfig(
|
||||
propertyPaneContentConfig,
|
||||
!!config.isDeprecated,
|
||||
),
|
||||
).toStrictEqual(true);
|
||||
const propertyPaneStyleConfig = widget.getPropertyPaneStyleConfig();
|
||||
|
||||
expect(isNotFloat(defaults.rows)).toBe(true);
|
||||
expect(isNotFloat(defaults.columns)).toBe(true);
|
||||
});
|
||||
expect(
|
||||
validatePropertyPaneConfig(
|
||||
propertyPaneStyleConfig,
|
||||
!!config.isDeprecated,
|
||||
),
|
||||
).toStrictEqual(true);
|
||||
});
|
||||
it(`Check if ${widget.type}'s dimensions are always integers`, () => {
|
||||
const defaults = widget.getDefaults();
|
||||
|
||||
if (config.isDeprecated) {
|
||||
it(`Check if ${widget.type}'s deprecation config has a proper replacement Widget`, () => {
|
||||
const widgetType = widget.type;
|
||||
expect(isNotFloat(defaults.rows)).toBe(true);
|
||||
expect(isNotFloat(defaults.columns)).toBe(true);
|
||||
});
|
||||
|
||||
if (config.replacement === undefined) {
|
||||
fail(`${widgetType}'s replacement widget is not defined`);
|
||||
}
|
||||
if (config.isDeprecated) {
|
||||
it(`Check if ${widget.type}'s deprecation config has a proper replacement Widget`, () => {
|
||||
const widgetType = widget.type;
|
||||
|
||||
const replacementWidgetType = config.replacement;
|
||||
const replacementWidget = WidgetFactory.get(replacementWidgetType);
|
||||
const replacementWidgetConfig = replacementWidget?.getConfig();
|
||||
if (config.replacement === undefined) {
|
||||
fail(`${widgetType}'s replacement widget is not defined`);
|
||||
}
|
||||
|
||||
if (replacementWidgetConfig === undefined) {
|
||||
fail(
|
||||
`${widgetType}'s replacement widget ${replacementWidgetType} does not resolve to an actual widget Config`,
|
||||
);
|
||||
}
|
||||
const replacementWidgetType = config.replacement;
|
||||
const replacementWidget = WidgetFactory.get(replacementWidgetType);
|
||||
const replacementWidgetConfig = replacementWidget?.getConfig();
|
||||
|
||||
if (replacementWidgetConfig?.isDeprecated) {
|
||||
fail(
|
||||
`${widgetType}'s replacement widget ${replacementWidgetType} itself is deprecated. Cannot have a deprecated widget as a replacement for another deprecated widget`,
|
||||
);
|
||||
}
|
||||
if (replacementWidgetConfig === undefined) {
|
||||
fail(
|
||||
`${widgetType}'s replacement widget ${replacementWidgetType} does not resolve to an actual widget Config`,
|
||||
);
|
||||
}
|
||||
|
||||
if (replacementWidgetConfig?.hideCard) {
|
||||
fail(
|
||||
`${widgetType}'s replacement widget ${replacementWidgetType} should be available in the entity Explorer`,
|
||||
);
|
||||
if (replacementWidgetConfig?.isDeprecated) {
|
||||
fail(
|
||||
`${widgetType}'s replacement widget ${replacementWidgetType} itself is deprecated. Cannot have a deprecated widget as a replacement for another deprecated widget`,
|
||||
);
|
||||
}
|
||||
|
||||
if (replacementWidgetConfig?.hideCard) {
|
||||
fail(
|
||||
`${widgetType}'s replacement widget ${replacementWidgetType} should be available in the entity Explorer`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it(`Check if ${widget.type}'s setter method are configured correctly`, () => {
|
||||
const setterConfig = widget.getSetterConfig();
|
||||
|
||||
if (setterConfig) {
|
||||
expect(setterConfig).toHaveProperty("__setters");
|
||||
const setters = setterConfig.__setters;
|
||||
|
||||
for (const [setterName, config] of Object.entries(setters)) {
|
||||
expect(config).toHaveProperty("type");
|
||||
expect(config).toHaveProperty("path");
|
||||
expect(setterName).toContain("set");
|
||||
const type = config.type;
|
||||
const path = config.path;
|
||||
|
||||
expect(typeof type).toBe("string");
|
||||
expect(typeof path).toBe("string");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it(`Check if ${widget.type}'s setter method are configured correctly`, () => {
|
||||
const setterConfig = widget.getSetterConfig();
|
||||
|
||||
if (setterConfig) {
|
||||
expect(setterConfig).toHaveProperty("__setters");
|
||||
const setters = setterConfig.__setters;
|
||||
|
||||
for (const [setterName, config] of Object.entries(setters)) {
|
||||
expect(config).toHaveProperty("type");
|
||||
expect(config).toHaveProperty("path");
|
||||
expect(setterName).toContain("set");
|
||||
const type = config.type;
|
||||
const path = config.path;
|
||||
|
||||
expect(typeof type).toBe("string");
|
||||
expect(typeof path).toBe("string");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,205 +1,463 @@
|
|||
import AudioRecorderWidget from "./AudioRecorderWidget";
|
||||
import AudioWidget from "./AudioWidget";
|
||||
import ButtonGroupWidget from "./ButtonGroupWidget";
|
||||
import ButtonWidget from "./ButtonWidget";
|
||||
import SelectWidget from "./SelectWidget";
|
||||
import CameraWidget from "./CameraWidget";
|
||||
import CanvasWidget from "./CanvasWidget";
|
||||
import ChartWidget from "./ChartWidget";
|
||||
import CheckboxGroupWidget from "./CheckboxGroupWidget";
|
||||
import CheckboxWidget from "./CheckboxWidget";
|
||||
import CircularProgressWidget from "./CircularProgressWidget";
|
||||
import ContainerWidget from "./ContainerWidget";
|
||||
import CurrencyInputWidget from "./CurrencyInputWidget";
|
||||
import DatePickerWidget from "./DatePickerWidget";
|
||||
import DatePickerWidget2 from "./DatePickerWidget2";
|
||||
import DividerWidget from "./DividerWidget";
|
||||
import MultiSelectWidgetV2 from "./MultiSelectWidgetV2";
|
||||
import DocumentViewerWidget from "./DocumentViewerWidget";
|
||||
import DropdownWidget from "./DropdownWidget";
|
||||
import FilePickerWidget from "./FilepickerWidget";
|
||||
import FilePickerWidgetV2 from "./FilePickerWidgetV2";
|
||||
import FormButtonWidget from "./FormButtonWidget";
|
||||
import FormWidget from "./FormWidget";
|
||||
import IconButtonWidget from "./IconButtonWidget";
|
||||
import IconWidget from "./IconWidget";
|
||||
import IframeWidget from "./IframeWidget";
|
||||
import ImageWidget from "./ImageWidget";
|
||||
import InputWidget from "./InputWidget";
|
||||
import InputWidgetV2 from "./InputWidgetV2";
|
||||
import ListWidget from "./ListWidget";
|
||||
import MapChartWidget from "./MapChartWidget";
|
||||
import MapWidget from "./MapWidget";
|
||||
import MenuButtonWidget from "./MenuButtonWidget";
|
||||
import ModalWidget from "./ModalWidget";
|
||||
import MultiSelectTreeWidget from "./MultiSelectTreeWidget";
|
||||
import MultiSelectWidget from "./MultiSelectWidget";
|
||||
import PhoneInputWidget from "./PhoneInputWidget";
|
||||
import ProgressBarWidget from "./ProgressBarWidget";
|
||||
import RadioGroupWidget from "./RadioGroupWidget";
|
||||
import RateWidget from "./RateWidget";
|
||||
import RichTextEditorWidget from "./RichTextEditorWidget";
|
||||
import SingleSelectTreeWidget from "./SingleSelectTreeWidget";
|
||||
import SkeletonWidget from "./SkeletonWidget";
|
||||
import StatboxWidget from "./StatboxWidget";
|
||||
import JSONFormWidget from "./JSONFormWidget";
|
||||
import SwitchGroupWidget from "./SwitchGroupWidget";
|
||||
import SwitchWidget from "./SwitchWidget";
|
||||
import TableWidget from "./TableWidget";
|
||||
import TabsMigratorWidget from "./TabsMigrator";
|
||||
import TabsWidget from "./TabsWidget";
|
||||
import TextWidget from "./TextWidget";
|
||||
import VideoWidget from "./VideoWidget";
|
||||
import ProgressWidget from "./ProgressWidget";
|
||||
import TableWidgetV2 from "./TableWidgetV2";
|
||||
import NumberSliderWidget from "./NumberSliderWidget";
|
||||
import RangeSliderWidget from "./RangeSliderWidget";
|
||||
import CategorySliderWidget from "./CategorySliderWidget";
|
||||
import CodeScannerWidget from "./CodeScannerWidget";
|
||||
import ListWidgetV2 from "./ListWidgetV2";
|
||||
import { WDSButtonWidget } from "widgets/wds/WDSButtonWidget";
|
||||
import { WDSInputWidget } from "widgets/wds/WDSInputWidget";
|
||||
import { WDSCheckboxWidget } from "widgets/wds/WDSCheckboxWidget";
|
||||
import { WDSIconButtonWidget } from "widgets/wds/WDSIconButtonWidget";
|
||||
import type BaseWidget from "./BaseWidget";
|
||||
import ExternalWidget from "./ExternalWidget";
|
||||
import { WDSTableWidget } from "widgets/wds/WDSTableWidget";
|
||||
import { WDSCurrencyInputWidget } from "widgets/wds/WDSCurrencyInputWidget";
|
||||
import { WDSToolbarButtonsWidget } from "widgets/wds/WDSToolbarButtonsWidget";
|
||||
import { WDSPhoneInputWidget } from "widgets/wds/WDSPhoneInputWidget";
|
||||
import { WDSCheckboxGroupWidget } from "widgets/wds/WDSCheckboxGroupWidget";
|
||||
import { WDSComboBoxWidget } from "widgets/wds/WDSComboBoxWidget";
|
||||
import { WDSSwitchWidget } from "widgets/wds/WDSSwitchWidget";
|
||||
import { WDSSwitchGroupWidget } from "widgets/wds/WDSSwitchGroupWidget";
|
||||
import { WDSRadioGroupWidget } from "widgets/wds/WDSRadioGroupWidget";
|
||||
import { WDSMenuButtonWidget } from "widgets/wds/WDSMenuButtonWidget";
|
||||
import CustomWidget from "./CustomWidget";
|
||||
import { WDSSectionWidget } from "widgets/wds/WDSSectionWidget";
|
||||
import { WDSZoneWidget } from "widgets/wds/WDSZoneWidget";
|
||||
import { WDSHeadingWidget } from "widgets/wds/WDSHeadingWidget";
|
||||
import { WDSParagraphWidget } from "widgets/wds/WDSParagraphWidget";
|
||||
import { WDSModalWidget } from "widgets/wds/WDSModalWidget";
|
||||
import { WDSStatsWidget } from "widgets/wds/WDSStatsWidget";
|
||||
import { WDSKeyValueWidget } from "widgets/wds/WDSKeyValueWidget";
|
||||
import { WDSInlineButtonsWidget } from "widgets/wds/WDSInlineButtonsWidget";
|
||||
import { WDSEmailInputWidget } from "widgets/wds/WDSEmailInputWidget";
|
||||
import { WDSPasswordInputWidget } from "widgets/wds/WDSPasswordInputWidget";
|
||||
import { WDSNumberInputWidget } from "widgets/wds/WDSNumberInputWidget";
|
||||
import { WDSMultilineInputWidget } from "widgets/wds/WDSMultilineInputWidget";
|
||||
import { WDSSelectWidget } from "widgets/wds/WDSSelectWidget";
|
||||
import { WDSCustomWidget } from "widgets/wds/WDSCustomWidget";
|
||||
import { retryPromise } from "utils/AppsmithUtils";
|
||||
import { anvilWidgets } from "./wds/constants";
|
||||
import { EEWDSWidgets } from "ee/widgets/wds";
|
||||
import { WDSDatePickerWidget } from "widgets/wds/WDSDatePickerWidget";
|
||||
import { WDSMultiSelectWidget } from "widgets/wds/WDSMultiSelectWidget";
|
||||
import { EEWidgets } from "ee/widgets";
|
||||
|
||||
const LegacyWidgets = [
|
||||
CanvasWidget,
|
||||
SkeletonWidget,
|
||||
ContainerWidget,
|
||||
TextWidget,
|
||||
TableWidget,
|
||||
CheckboxWidget,
|
||||
RadioGroupWidget,
|
||||
ButtonWidget,
|
||||
ImageWidget,
|
||||
VideoWidget,
|
||||
TabsWidget,
|
||||
ModalWidget,
|
||||
ChartWidget,
|
||||
MapWidget,
|
||||
RichTextEditorWidget,
|
||||
DatePickerWidget2,
|
||||
SwitchWidget,
|
||||
FormWidget,
|
||||
RateWidget,
|
||||
IframeWidget,
|
||||
TabsMigratorWidget,
|
||||
DividerWidget,
|
||||
MenuButtonWidget,
|
||||
IconButtonWidget,
|
||||
CheckboxGroupWidget,
|
||||
FilePickerWidgetV2,
|
||||
StatboxWidget,
|
||||
AudioRecorderWidget,
|
||||
DocumentViewerWidget,
|
||||
ButtonGroupWidget,
|
||||
MultiSelectTreeWidget,
|
||||
SingleSelectTreeWidget,
|
||||
SwitchGroupWidget,
|
||||
AudioWidget,
|
||||
ProgressBarWidget,
|
||||
CameraWidget,
|
||||
MapChartWidget,
|
||||
SelectWidget,
|
||||
MultiSelectWidgetV2,
|
||||
InputWidgetV2,
|
||||
PhoneInputWidget,
|
||||
CurrencyInputWidget,
|
||||
JSONFormWidget,
|
||||
TableWidgetV2,
|
||||
NumberSliderWidget,
|
||||
RangeSliderWidget,
|
||||
CategorySliderWidget,
|
||||
CodeScannerWidget,
|
||||
ListWidgetV2,
|
||||
ExternalWidget,
|
||||
];
|
||||
|
||||
const DeprecatedWidgets = [
|
||||
//Deprecated Widgets
|
||||
InputWidget,
|
||||
DropdownWidget,
|
||||
DatePickerWidget,
|
||||
IconWidget,
|
||||
FilePickerWidget,
|
||||
MultiSelectWidget,
|
||||
FormButtonWidget,
|
||||
ProgressWidget,
|
||||
CircularProgressWidget,
|
||||
ListWidget,
|
||||
];
|
||||
|
||||
const WDSWidgets = [
|
||||
WDSButtonWidget,
|
||||
WDSInputWidget,
|
||||
WDSCheckboxWidget,
|
||||
WDSIconButtonWidget,
|
||||
WDSTableWidget,
|
||||
WDSCurrencyInputWidget,
|
||||
WDSToolbarButtonsWidget,
|
||||
WDSPhoneInputWidget,
|
||||
WDSCheckboxGroupWidget,
|
||||
WDSComboBoxWidget,
|
||||
WDSSwitchWidget,
|
||||
WDSSwitchGroupWidget,
|
||||
WDSRadioGroupWidget,
|
||||
WDSMenuButtonWidget,
|
||||
CustomWidget,
|
||||
WDSSectionWidget,
|
||||
WDSZoneWidget,
|
||||
WDSParagraphWidget,
|
||||
WDSHeadingWidget,
|
||||
WDSModalWidget,
|
||||
WDSStatsWidget,
|
||||
WDSKeyValueWidget,
|
||||
WDSInlineButtonsWidget,
|
||||
WDSEmailInputWidget,
|
||||
WDSPasswordInputWidget,
|
||||
WDSNumberInputWidget,
|
||||
WDSMultilineInputWidget,
|
||||
WDSSelectWidget,
|
||||
WDSDatePickerWidget,
|
||||
WDSCustomWidget,
|
||||
WDSMultiSelectWidget,
|
||||
];
|
||||
|
||||
const Widgets = [
|
||||
...WDSWidgets,
|
||||
...DeprecatedWidgets,
|
||||
...LegacyWidgets,
|
||||
// Create widget loader map
|
||||
const WidgetLoaders = new Map<string, () => Promise<typeof BaseWidget>>([
|
||||
...EEWDSWidgets,
|
||||
...EEWidgets,
|
||||
] as (typeof BaseWidget)[];
|
||||
// WDS Widgets
|
||||
[
|
||||
"WDS_BUTTON_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSButtonWidget").then((m) => m.WDSButtonWidget),
|
||||
],
|
||||
[
|
||||
"WDS_INPUT_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSInputWidget").then((m) => m.WDSInputWidget),
|
||||
],
|
||||
[
|
||||
"WDS_CHECKBOX_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSCheckboxWidget").then((m) => m.WDSCheckboxWidget),
|
||||
],
|
||||
[
|
||||
"WDS_ICON_BUTTON_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSIconButtonWidget").then(
|
||||
(m) => m.WDSIconButtonWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_TABLE_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSTableWidget").then((m) => m.WDSTableWidget),
|
||||
],
|
||||
[
|
||||
"WDS_CURRENCY_INPUT_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSCurrencyInputWidget").then(
|
||||
(m) => m.WDSCurrencyInputWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_TOOLBAR_BUTTONS_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSToolbarButtonsWidget").then(
|
||||
(m) => m.WDSToolbarButtonsWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_PHONE_INPUT_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSPhoneInputWidget").then(
|
||||
(m) => m.WDSPhoneInputWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_CHECKBOX_GROUP_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSCheckboxGroupWidget").then(
|
||||
(m) => m.WDSCheckboxGroupWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_COMBO_BOX_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSComboBoxWidget").then((m) => m.WDSComboBoxWidget),
|
||||
],
|
||||
[
|
||||
"WDS_SWITCH_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSSwitchWidget").then((m) => m.WDSSwitchWidget),
|
||||
],
|
||||
[
|
||||
"WDS_SWITCH_GROUP_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSSwitchGroupWidget").then(
|
||||
(m) => m.WDSSwitchGroupWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_RADIO_GROUP_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSRadioGroupWidget").then(
|
||||
(m) => m.WDSRadioGroupWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_MENU_BUTTON_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSMenuButtonWidget").then(
|
||||
(m) => m.WDSMenuButtonWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"CUSTOM_WIDGET",
|
||||
async () => import("./CustomWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
anvilWidgets.SECTION_WIDGET,
|
||||
async () =>
|
||||
import("widgets/wds/WDSSectionWidget").then((m) => m.WDSSectionWidget),
|
||||
],
|
||||
[
|
||||
anvilWidgets.ZONE_WIDGET,
|
||||
async () =>
|
||||
import("widgets/wds/WDSZoneWidget").then((m) => m.WDSZoneWidget),
|
||||
],
|
||||
[
|
||||
"WDS_PARAGRAPH_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSParagraphWidget").then(
|
||||
(m) => m.WDSParagraphWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_HEADING_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSHeadingWidget").then((m) => m.WDSHeadingWidget),
|
||||
],
|
||||
[
|
||||
"WDS_MODAL_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSModalWidget").then((m) => m.WDSModalWidget),
|
||||
],
|
||||
[
|
||||
"WDS_STATS_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSStatsWidget").then((m) => m.WDSStatsWidget),
|
||||
],
|
||||
[
|
||||
"WDS_KEY_VALUE_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSKeyValueWidget").then((m) => m.WDSKeyValueWidget),
|
||||
],
|
||||
[
|
||||
"WDS_INLINE_BUTTONS_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSInlineButtonsWidget").then(
|
||||
(m) => m.WDSInlineButtonsWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_EMAIL_INPUT_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSEmailInputWidget").then(
|
||||
(m) => m.WDSEmailInputWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_PASSWORD_INPUT_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSPasswordInputWidget").then(
|
||||
(m) => m.WDSPasswordInputWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_NUMBER_INPUT_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSNumberInputWidget").then(
|
||||
(m) => m.WDSNumberInputWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_MULTILINE_INPUT_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSMultilineInputWidget").then(
|
||||
(m) => m.WDSMultilineInputWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_SELECT_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSSelectWidget").then((m) => m.WDSSelectWidget),
|
||||
],
|
||||
[
|
||||
"WDS_DATEPICKER_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSDatePickerWidget").then(
|
||||
(m) => m.WDSDatePickerWidget,
|
||||
),
|
||||
],
|
||||
[
|
||||
"WDS_MULTI_SELECT_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSMultiSelectWidget").then(
|
||||
(m) => m.WDSMultiSelectWidget,
|
||||
),
|
||||
],
|
||||
|
||||
export default Widgets;
|
||||
// Legacy Widgets
|
||||
[
|
||||
"CANVAS_WIDGET",
|
||||
async () => import("./CanvasWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"SKELETON_WIDGET",
|
||||
async () => import("./SkeletonWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"CONTAINER_WIDGET",
|
||||
async () => import("./ContainerWidget").then((m) => m.default),
|
||||
],
|
||||
["TEXT_WIDGET", async () => import("./TextWidget").then((m) => m.default)],
|
||||
["TABLE_WIDGET", async () => import("./TableWidget").then((m) => m.default)],
|
||||
[
|
||||
"CHECKBOX_WIDGET",
|
||||
async () => import("./CheckboxWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"RADIO_GROUP_WIDGET",
|
||||
async () => import("./RadioGroupWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"BUTTON_WIDGET",
|
||||
async () => import("./ButtonWidget").then((m) => m.default),
|
||||
],
|
||||
["IMAGE_WIDGET", async () => import("./ImageWidget").then((m) => m.default)],
|
||||
["VIDEO_WIDGET", async () => import("./VideoWidget").then((m) => m.default)],
|
||||
["TABS_WIDGET", async () => import("./TabsWidget").then((m) => m.default)],
|
||||
["MODAL_WIDGET", async () => import("./ModalWidget").then((m) => m.default)],
|
||||
["CHART_WIDGET", async () => import("./ChartWidget").then((m) => m.default)],
|
||||
["MAP_WIDGET", async () => import("./MapWidget").then((m) => m.default)],
|
||||
[
|
||||
"RICH_TEXT_EDITOR_WIDGET",
|
||||
async () => import("./RichTextEditorWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"DATE_PICKER_WIDGET2",
|
||||
async () => import("./DatePickerWidget2").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"SWITCH_WIDGET",
|
||||
async () => import("./SwitchWidget").then((m) => m.default),
|
||||
],
|
||||
["FORM_WIDGET", async () => import("./FormWidget").then((m) => m.default)],
|
||||
["RATE_WIDGET", async () => import("./RateWidget").then((m) => m.default)],
|
||||
[
|
||||
"IFRAME_WIDGET",
|
||||
async () => import("./IframeWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"TABS_MIGRATOR_WIDGET",
|
||||
async () => import("./TabsMigrator").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"DIVIDER_WIDGET",
|
||||
async () => import("./DividerWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"MENU_BUTTON_WIDGET",
|
||||
async () => import("./MenuButtonWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"ICON_BUTTON_WIDGET",
|
||||
async () => import("./IconButtonWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"CHECKBOX_GROUP_WIDGET",
|
||||
async () => import("./CheckboxGroupWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"FILE_PICKER_WIDGET_V2",
|
||||
async () => import("./FilePickerWidgetV2").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"STATBOX_WIDGET",
|
||||
async () => import("./StatboxWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"AUDIO_RECORDER_WIDGET",
|
||||
async () => import("./AudioRecorderWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"DOCUMENT_VIEWER_WIDGET",
|
||||
async () => import("./DocumentViewerWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"BUTTON_GROUP_WIDGET",
|
||||
async () => import("./ButtonGroupWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"WDS_CUSTOM_WIDGET",
|
||||
async () =>
|
||||
import("widgets/wds/WDSCustomWidget").then((m) => m.WDSCustomWidget),
|
||||
],
|
||||
[
|
||||
"MULTI_SELECT_TREE_WIDGET",
|
||||
async () => import("./MultiSelectTreeWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"SINGLE_SELECT_TREE_WIDGET",
|
||||
async () => import("./SingleSelectTreeWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"SWITCH_GROUP_WIDGET",
|
||||
async () => import("./SwitchGroupWidget").then((m) => m.default),
|
||||
],
|
||||
["AUDIO_WIDGET", async () => import("./AudioWidget").then((m) => m.default)],
|
||||
[
|
||||
"PROGRESSBAR_WIDGET",
|
||||
async () => import("./ProgressBarWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"CAMERA_WIDGET",
|
||||
async () => import("./CameraWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"MAP_CHART_WIDGET",
|
||||
async () => import("./MapChartWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"SELECT_WIDGET",
|
||||
async () => import("./SelectWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"MULTI_SELECT_WIDGET_V2",
|
||||
async () => import("./MultiSelectWidgetV2").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"MULTI_SELECT_WIDGET",
|
||||
async () => import("./MultiSelectWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"INPUT_WIDGET_V2",
|
||||
async () => import("./InputWidgetV2").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"PHONE_INPUT_WIDGET",
|
||||
async () => import("./PhoneInputWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"CURRENCY_INPUT_WIDGET",
|
||||
async () => import("./CurrencyInputWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"JSON_FORM_WIDGET",
|
||||
async () => import("./JSONFormWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"TABLE_WIDGET_V2",
|
||||
async () => import("./TableWidgetV2").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"NUMBER_SLIDER_WIDGET",
|
||||
async () => import("./NumberSliderWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"RANGE_SLIDER_WIDGET",
|
||||
async () => import("./RangeSliderWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"CATEGORY_SLIDER_WIDGET",
|
||||
async () => import("./CategorySliderWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"CODE_SCANNER_WIDGET",
|
||||
async () => import("./CodeScannerWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"LIST_WIDGET_V2",
|
||||
async () => import("./ListWidgetV2").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"EXTERNAL_WIDGET",
|
||||
async () => import("./ExternalWidget").then((m) => m.default),
|
||||
],
|
||||
|
||||
// Deprecated Widgets
|
||||
[
|
||||
"DROP_DOWN_WIDGET",
|
||||
async () => import("./DropdownWidget").then((m) => m.default),
|
||||
],
|
||||
["ICON_WIDGET", async () => import("./IconWidget").then((m) => m.default)],
|
||||
[
|
||||
"FILE_PICKER_WIDGET",
|
||||
async () => import("./FilepickerWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"FORM_BUTTON_WIDGET",
|
||||
async () => import("./FormButtonWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"PROGRESS_WIDGET",
|
||||
async () => import("./ProgressWidget").then((m) => m.default),
|
||||
],
|
||||
[
|
||||
"CIRCULAR_PROGRESS_WIDGET",
|
||||
async () => import("./CircularProgressWidget").then((m) => m.default),
|
||||
],
|
||||
["LIST_WIDGET", async () => import("./ListWidget").then((m) => m.default)],
|
||||
[
|
||||
"DATE_PICKER_WIDGET",
|
||||
async () => import("./DatePickerWidget").then((m) => m.default),
|
||||
],
|
||||
["INPUT_WIDGET", async () => import("./InputWidget").then((m) => m.default)],
|
||||
]);
|
||||
|
||||
// Cache for loaded widgets
|
||||
const loadedWidgets = new Map<string, typeof BaseWidget>();
|
||||
|
||||
// Function to load a specific widget by type
|
||||
export const loadWidget = async (type: string): Promise<typeof BaseWidget> => {
|
||||
if (loadedWidgets.has(type)) {
|
||||
return loadedWidgets.get(type)!;
|
||||
}
|
||||
|
||||
const loader = WidgetLoaders.get(type);
|
||||
|
||||
if (!loader) {
|
||||
throw new Error(`Widget type ${type} not found`);
|
||||
}
|
||||
|
||||
try {
|
||||
const widget = await retryPromise(async () => loader());
|
||||
|
||||
loadedWidgets.set(type, widget);
|
||||
|
||||
return widget;
|
||||
} catch (error) {
|
||||
throw new Error(`Error loading widget ${type}:` + error);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to load all widgets
|
||||
// Function to load all widgets
|
||||
export const loadAllWidgets = async (): Promise<
|
||||
Map<string, typeof BaseWidget>
|
||||
> => {
|
||||
const allWidgets = new Map<string, typeof BaseWidget>();
|
||||
|
||||
const widgetPromises = Array.from(WidgetLoaders.entries()).map(
|
||||
async ([type, loader]) => {
|
||||
if (loadedWidgets.has(type)) {
|
||||
return [type, loadedWidgets.get(type)!] as [string, typeof BaseWidget];
|
||||
}
|
||||
|
||||
try {
|
||||
const widget = await retryPromise(async () => loader());
|
||||
|
||||
loadedWidgets.set(type, widget);
|
||||
|
||||
return [type, widget] as [string, typeof BaseWidget];
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to load widget type ${type}: ${error instanceof Error ? error.message : error}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const loadedWidgetEntries = await Promise.all(widgetPromises);
|
||||
|
||||
for (const [type, widget] of loadedWidgetEntries) {
|
||||
allWidgets.set(type, widget);
|
||||
}
|
||||
|
||||
return allWidgets;
|
||||
};
|
||||
export default WidgetLoaders;
|
||||
|
|
|
|||
|
|
@ -4,9 +4,13 @@ import { evalWorker } from "utils/workerInstances";
|
|||
import { EVAL_WORKER_ACTIONS } from "ee/workers/Evaluation/evalWorkerActions";
|
||||
import { runSaga } from "redux-saga";
|
||||
import { TriggerKind } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import { registerAllWidgets } from "utils/editor/EditorUtils";
|
||||
|
||||
export async function UNSTABLE_executeDynamicTrigger(dynamicTrigger: string) {
|
||||
const state = store.getState();
|
||||
|
||||
await registerAllWidgets();
|
||||
|
||||
const unEvalTree = getUnevaluatedDataTree(state);
|
||||
|
||||
const result = runSaga(
|
||||
|
|
|
|||
|
|
@ -290,75 +290,64 @@ export async function loadLibraries(
|
|||
const libStore: Record<string, unknown> = {};
|
||||
|
||||
try {
|
||||
for (const lib of libs) {
|
||||
const url = lib.url as string;
|
||||
const accessors = lib.accessor;
|
||||
const keysBefore = Object.keys(self);
|
||||
let module = null;
|
||||
await Promise.all(
|
||||
libs.map(async (lib) => {
|
||||
const url = lib.url as string;
|
||||
const accessors = lib.accessor;
|
||||
const keysBefore = Object.keys(self);
|
||||
let module = null;
|
||||
|
||||
try {
|
||||
self.importScripts(url);
|
||||
const keysAfter = Object.keys(self);
|
||||
let defaultAccessors = difference(keysAfter, keysBefore);
|
||||
try {
|
||||
self.importScripts(url);
|
||||
const keysAfter = Object.keys(self);
|
||||
let defaultAccessors = difference(keysAfter, keysBefore);
|
||||
|
||||
// Changing default export to library accessors name which was saved when it was installed, if default export present
|
||||
movetheDefaultExportedLibraryToAccessorKey(
|
||||
defaultAccessors,
|
||||
accessors[0],
|
||||
);
|
||||
movetheDefaultExportedLibraryToAccessorKey(
|
||||
defaultAccessors,
|
||||
accessors[0],
|
||||
);
|
||||
|
||||
// Following the same process which was happening earlier
|
||||
const keysAfterDefaultOperation = Object.keys(self);
|
||||
const keysAfterDefaultOperation = Object.keys(self);
|
||||
|
||||
defaultAccessors = difference(keysAfterDefaultOperation, keysBefore);
|
||||
defaultAccessors = difference(keysAfterDefaultOperation, keysBefore);
|
||||
|
||||
/**
|
||||
* Installing 2 different version of lodash tries to add the same accessor on the self object. Let take version a & b for example.
|
||||
* Installation of version a, will add _ to the self object and can be detected by looking at the differences in the previous step.
|
||||
* Now when version b is installed, differences will be [], since _ already exists in the self object.
|
||||
* We add all the installations to the libStore and see if the reference it points to in the self object changes.
|
||||
* If the references changes it means that it a valid accessor.
|
||||
*/
|
||||
defaultAccessors.push(
|
||||
...Object.keys(libStore).filter((k) => libStore[k] !== self[k]),
|
||||
);
|
||||
defaultAccessors.push(
|
||||
...Object.keys(libStore).filter((k) => libStore[k] !== self[k]),
|
||||
);
|
||||
|
||||
/**
|
||||
* Sort the accessor list from backend and installed accessor list using the same rule to apply all modifications.
|
||||
* This is required only for UMD builds, since we always generate unique names for ESM.
|
||||
*/
|
||||
accessors.sort();
|
||||
defaultAccessors.sort();
|
||||
accessors.sort();
|
||||
defaultAccessors.sort();
|
||||
|
||||
for (let i = 0; i < defaultAccessors.length; i++) {
|
||||
self[accessors[i]] = self[defaultAccessors[i]];
|
||||
libStore[defaultAccessors[i]] = self[defaultAccessors[i]];
|
||||
libraryReservedIdentifiers[accessors[i]] = true;
|
||||
invalidEntityIdentifiers[accessors[i]] = true;
|
||||
for (let i = 0; i < defaultAccessors.length; i++) {
|
||||
self[accessors[i]] = self[defaultAccessors[i]];
|
||||
libStore[defaultAccessors[i]] = self[defaultAccessors[i]];
|
||||
libraryReservedIdentifiers[accessors[i]] = true;
|
||||
invalidEntityIdentifiers[accessors[i]] = true;
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (e) {
|
||||
log.debug(e);
|
||||
}
|
||||
|
||||
continue;
|
||||
} catch (e) {
|
||||
log.debug(e);
|
||||
}
|
||||
try {
|
||||
module = await import(/* webpackIgnore: true */ url);
|
||||
|
||||
try {
|
||||
module = await import(/* webpackIgnore: true */ url);
|
||||
if (!module || typeof module !== "object") throw "Not an ESM module";
|
||||
|
||||
if (!module || typeof module !== "object") throw "Not an ESM module";
|
||||
const key = accessors[0];
|
||||
const flattenedModule = flattenModule(module);
|
||||
|
||||
const key = accessors[0];
|
||||
const flattenedModule = flattenModule(module);
|
||||
|
||||
libStore[key] = flattenedModule;
|
||||
self[key] = flattenedModule;
|
||||
libraryReservedIdentifiers[key] = true;
|
||||
invalidEntityIdentifiers[key] = true;
|
||||
} catch (e) {
|
||||
log.debug(e);
|
||||
throw new ImportError(url);
|
||||
}
|
||||
}
|
||||
libStore[key] = flattenedModule;
|
||||
self[key] = flattenedModule;
|
||||
libraryReservedIdentifiers[key] = true;
|
||||
invalidEntityIdentifiers[key] = true;
|
||||
} catch (e) {
|
||||
log.debug(e);
|
||||
throw new ImportError(url);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
JSLibraries.push(...libs);
|
||||
JSLibraryAccessor.regenerateSet();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
import { updateDependencyMap } from "workers/common/DependencyMap";
|
||||
import { replaceThisDotParams } from "./utils";
|
||||
import { isDataField } from "./utils";
|
||||
import widgets from "widgets";
|
||||
import { loadAllWidgets } from "widgets";
|
||||
import type { WidgetConfiguration } from "WidgetProvider/types";
|
||||
import { type WidgetEntity } from "ee/entities/DataTree/types";
|
||||
import {
|
||||
|
|
@ -35,14 +35,18 @@ const widgetConfigMap: Record<
|
|||
}
|
||||
> = {};
|
||||
|
||||
widgets.map((widget) => {
|
||||
if (widget.type) {
|
||||
widgetConfigMap[widget.type] = {
|
||||
defaultProperties: widget.getDefaultPropertiesMap(),
|
||||
derivedProperties: widget.getDerivedPropertiesMap(),
|
||||
metaProperties: widget.getMetaPropertiesMap(),
|
||||
};
|
||||
}
|
||||
beforeAll(async () => {
|
||||
const loadedWidgets = await loadAllWidgets();
|
||||
|
||||
loadedWidgets.forEach((widget) => {
|
||||
if (widget.type) {
|
||||
widgetConfigMap[widget.type] = {
|
||||
defaultProperties: widget.getDefaultPropertiesMap(),
|
||||
derivedProperties: widget.getDerivedPropertiesMap(),
|
||||
metaProperties: widget.getMetaPropertiesMap(),
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
jest.mock("ee/workers/Evaluation/generateOverrideContext"); // mock the generateOverrideContext function
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user