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";
|
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(
|
export function memoize(
|
||||||
target: unknown,
|
target: unknown,
|
||||||
methodName: unknown,
|
methodName: unknown,
|
||||||
descriptor: PropertyDescriptor,
|
descriptor: PropertyDescriptor,
|
||||||
) {
|
) {
|
||||||
descriptor.value = memo(descriptor.value, {
|
descriptor.value = memoizeWithClear(descriptor.value);
|
||||||
maxSize: 100,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function freeze(
|
export function freeze(
|
||||||
|
|
@ -25,3 +59,8 @@ export function freeze(
|
||||||
return Object.freeze(result);
|
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 type BaseWidget from "widgets/BaseWidget";
|
||||||
import WidgetFactory from ".";
|
import WidgetFactory from ".";
|
||||||
import { withBaseWidgetHOC } from "widgets/BaseWidgetHOC/withBaseWidgetHOC";
|
import { withBaseWidgetHOC } from "widgets/BaseWidgetHOC/withBaseWidgetHOC";
|
||||||
|
import { incrementWidgetConfigsVersion } from "./widgetConfigVersion";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Function to create builder for the widgets and register them in widget factory
|
* 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.
|
* extracted this into a seperate file to break the circular reference.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const registerWidgets = (widgets: (typeof BaseWidget)[]) => {
|
export const registerWidgets = (widgets: (typeof BaseWidget)[]) => {
|
||||||
const widgetAndBuilders = widgets.map((widget) => {
|
widgets.forEach((widget) => {
|
||||||
const { eagerRender = false, needsMeta = false } = widget.getConfig();
|
registerWidget(widget);
|
||||||
|
|
||||||
// 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,
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
|
// Increment version to trigger selectors that depend on widget configs
|
||||||
WidgetFactory.initialize(widgetAndBuilders);
|
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>) {
|
export function installLibraryInit(payload: Partial<JSLibrary>) {
|
||||||
return {
|
return {
|
||||||
type: ReduxActionTypes.INSTALL_LIBRARY_INIT,
|
type: ReduxActionTypes.INSTALL_LIBRARY_INIT,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import type {
|
||||||
ConditionalOutput,
|
ConditionalOutput,
|
||||||
DynamicValues,
|
DynamicValues,
|
||||||
} from "reducers/evaluationReducers/formEvaluationReducer";
|
} from "reducers/evaluationReducers/formEvaluationReducer";
|
||||||
|
import type { ReduxActionWithoutPayload } from "./ReduxActionTypes";
|
||||||
|
|
||||||
export const shouldTriggerEvaluation = (action: ReduxAction<unknown>) => {
|
export const shouldTriggerEvaluation = (action: ReduxAction<unknown>) => {
|
||||||
return (
|
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
|
// These actions require the entire tree to be re-evaluated
|
||||||
const FORCE_EVAL_ACTIONS = {
|
const FORCE_EVAL_ACTIONS = {
|
||||||
[ReduxActionTypes.INSTALL_LIBRARY_SUCCESS]: true,
|
[ReduxActionTypes.INSTALL_LIBRARY_SUCCESS]: true,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const JSLibraryActionTypes = {
|
||||||
TOGGLE_INSTALLER: "TOGGLE_INSTALLER",
|
TOGGLE_INSTALLER: "TOGGLE_INSTALLER",
|
||||||
FETCH_JS_LIBRARIES_INIT: "FETCH_JS_LIBRARIES_INIT",
|
FETCH_JS_LIBRARIES_INIT: "FETCH_JS_LIBRARIES_INIT",
|
||||||
FETCH_JS_LIBRARIES_SUCCESS: "FETCH_JS_LIBRARIES_SUCCESS",
|
FETCH_JS_LIBRARIES_SUCCESS: "FETCH_JS_LIBRARIES_SUCCESS",
|
||||||
|
DEFER_LOADING_JS_LIBRARIES: "DEFER_LOADING_JS_LIBRARIES",
|
||||||
CLEAR_PROCESSED_INSTALLS: "CLEAR_PROCESSED_INSTALLS",
|
CLEAR_PROCESSED_INSTALLS: "CLEAR_PROCESSED_INSTALLS",
|
||||||
INSTALL_LIBRARY_INIT: "INSTALL_LIBRARY_INIT",
|
INSTALL_LIBRARY_INIT: "INSTALL_LIBRARY_INIT",
|
||||||
INSTALL_LIBRARY_START: "INSTALL_LIBRARY_START",
|
INSTALL_LIBRARY_START: "INSTALL_LIBRARY_START",
|
||||||
|
|
@ -1288,7 +1289,15 @@ const PlatformActionErrorTypes = {
|
||||||
API_ERROR: "API_ERROR",
|
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 = {
|
export const ReduxActionTypes = {
|
||||||
|
...DeferRenderingAppViewerActionTypes,
|
||||||
...ActionActionTypes,
|
...ActionActionTypes,
|
||||||
...AdminSettingsActionTypes,
|
...AdminSettingsActionTypes,
|
||||||
...AnalyticsActionTypes,
|
...AnalyticsActionTypes,
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ import type { layoutConversionReduxState } from "reducers/uiReducers/layoutConve
|
||||||
import type { OneClickBindingState } from "reducers/uiReducers/oneClickBindingReducer";
|
import type { OneClickBindingState } from "reducers/uiReducers/oneClickBindingReducer";
|
||||||
import type { IDEState } from "reducers/uiReducers/ideReducer";
|
import type { IDEState } from "reducers/uiReducers/ideReducer";
|
||||||
import type { PluginActionEditorState } from "PluginActionEditor";
|
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
|
/* 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 */
|
or done so by a module that is designed to be eventually pluggable */
|
||||||
|
|
@ -171,6 +172,7 @@ export interface AppState {
|
||||||
loadingEntities: LoadingEntitiesState;
|
loadingEntities: LoadingEntitiesState;
|
||||||
formEvaluation: FormEvaluationState;
|
formEvaluation: FormEvaluationState;
|
||||||
triggers: TriggerValuesEvaluationState;
|
triggers: TriggerValuesEvaluationState;
|
||||||
|
firstEvaluation: FirstEvaluationState;
|
||||||
};
|
};
|
||||||
linting: {
|
linting: {
|
||||||
errors: LintErrorsStore;
|
errors: LintErrorsStore;
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,7 @@ import { apiFailureResponseInterceptor } from "api/interceptors/response";
|
||||||
import type { AxiosError } from "axios";
|
import type { AxiosError } from "axios";
|
||||||
import { handleFetchApplicationError } from "./ApplicationSagas";
|
import { handleFetchApplicationError } from "./ApplicationSagas";
|
||||||
import { getCurrentUser } from "actions/authActions";
|
import { getCurrentUser } from "actions/authActions";
|
||||||
|
import { getIsFirstPageLoad } from "selectors/evaluationSelectors";
|
||||||
|
|
||||||
export interface HandleWidgetNameUpdatePayload {
|
export interface HandleWidgetNameUpdatePayload {
|
||||||
newName: string;
|
newName: string;
|
||||||
|
|
@ -370,8 +371,14 @@ export function* postFetchedPublishedPage(
|
||||||
response.data.userPermissions,
|
response.data.userPermissions,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
// Clear any existing caches
|
const isFirstLoad: boolean = yield select(getIsFirstPageLoad);
|
||||||
yield call(clearEvalCache);
|
|
||||||
|
// Only the first page load we defer the clearing of caches
|
||||||
|
if (!isFirstLoad) {
|
||||||
|
// Clear any existing caches
|
||||||
|
yield call(clearEvalCache);
|
||||||
|
}
|
||||||
|
|
||||||
// Set url params
|
// Set url params
|
||||||
yield call(setDataUrl);
|
yield call(setDataUrl);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,6 @@ export const getModuleInstanceJSCollectionById = (
|
||||||
): JSCollection | undefined => {
|
): JSCollection | undefined => {
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
export const getAllUniqueWidgetTypesInUiModules = (state: DefaultRootState) => {
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ function ToggleComponentToJsonHandler(props: HandlerProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToggleComponentToJson(props: Props) {
|
function ToggleComponentToJson(props: Props) {
|
||||||
return props.viewType === ViewTypes.JSON
|
return props.viewType === ViewTypes.JSON && props.renderCompFunction
|
||||||
? props.renderCompFunction({
|
? props.renderCompFunction({
|
||||||
...alternateViewTypeInputConfig(),
|
...alternateViewTypeInputConfig(),
|
||||||
configProperty: props.configProperty,
|
configProperty: props.configProperty,
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import type { SwitchControlProps } from "components/propertyControls/SwitchContr
|
||||||
import SwitchControl from "components/propertyControls/SwitchControl";
|
import SwitchControl from "components/propertyControls/SwitchControl";
|
||||||
import OptionControl from "components/propertyControls/OptionControl";
|
import OptionControl from "components/propertyControls/OptionControl";
|
||||||
import type { ControlProps } from "components/propertyControls/BaseControl";
|
import type { ControlProps } from "components/propertyControls/BaseControl";
|
||||||
import type BaseControl from "components/propertyControls/BaseControl";
|
|
||||||
import CodeEditorControl from "components/propertyControls/CodeEditorControl";
|
import CodeEditorControl from "components/propertyControls/CodeEditorControl";
|
||||||
|
import type BaseControl from "components/propertyControls/BaseControl";
|
||||||
import type { DatePickerControlProps } from "components/propertyControls/DatePickerControl";
|
import type { DatePickerControlProps } from "components/propertyControls/DatePickerControl";
|
||||||
import DatePickerControl from "components/propertyControls/DatePickerControl";
|
import DatePickerControl from "components/propertyControls/DatePickerControl";
|
||||||
import ChartDataControl from "components/propertyControls/ChartDataControl";
|
import ChartDataControl from "components/propertyControls/ChartDataControl";
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
ReduxActionTypes,
|
ReduxActionTypes,
|
||||||
} from "ee/constants/ReduxActionConstants";
|
} from "ee/constants/ReduxActionConstants";
|
||||||
import type { APP_MODE } from "entities/App";
|
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 type { DeployConsolidatedApi } from "sagas/InitSagas";
|
||||||
import {
|
import {
|
||||||
failFastApiCalls,
|
failFastApiCalls,
|
||||||
|
|
@ -20,7 +20,10 @@ import {
|
||||||
} from "sagas/InitSagas";
|
} from "sagas/InitSagas";
|
||||||
import type { AppEnginePayload } from ".";
|
import type { AppEnginePayload } from ".";
|
||||||
import AppEngine, { ActionsNotFoundError } from ".";
|
import AppEngine, { ActionsNotFoundError } from ".";
|
||||||
import { fetchJSLibraries } from "actions/JSLibraryActions";
|
import {
|
||||||
|
fetchJSLibraries,
|
||||||
|
deferLoadingJSLibraries,
|
||||||
|
} from "actions/JSLibraryActions";
|
||||||
import { waitForFetchUserSuccess } from "ee/sagas/userSagas";
|
import { waitForFetchUserSuccess } from "ee/sagas/userSagas";
|
||||||
import { fetchJSCollectionsForView } from "actions/jsActionActions";
|
import { fetchJSCollectionsForView } from "actions/jsActionActions";
|
||||||
import {
|
import {
|
||||||
|
|
@ -29,6 +32,7 @@ import {
|
||||||
} from "actions/appThemingActions";
|
} from "actions/appThemingActions";
|
||||||
import type { Span } from "instrumentation/types";
|
import type { Span } from "instrumentation/types";
|
||||||
import { endSpan, startNestedSpan } from "instrumentation/generateTraces";
|
import { endSpan, startNestedSpan } from "instrumentation/generateTraces";
|
||||||
|
import { getIsFirstPageLoad } from "selectors/evaluationSelectors";
|
||||||
|
|
||||||
export default class AppViewerEngine extends AppEngine {
|
export default class AppViewerEngine extends AppEngine {
|
||||||
constructor(mode: APP_MODE) {
|
constructor(mode: APP_MODE) {
|
||||||
|
|
@ -120,9 +124,18 @@ export default class AppViewerEngine extends AppEngine {
|
||||||
ReduxActionErrorTypes.SETUP_PUBLISHED_PAGE_ERROR,
|
ReduxActionErrorTypes.SETUP_PUBLISHED_PAGE_ERROR,
|
||||||
];
|
];
|
||||||
|
|
||||||
initActionsCalls.push(fetchJSLibraries(applicationId, customJSLibraries));
|
const isFirstPageLoad = yield select(getIsFirstPageLoad);
|
||||||
successActionEffects.push(ReduxActionTypes.FETCH_JS_LIBRARIES_SUCCESS);
|
|
||||||
failureActionEffects.push(ReduxActionErrorTypes.FETCH_JS_LIBRARIES_FAILED);
|
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(
|
const resultOfPrimaryCalls: boolean = yield failFastApiCalls(
|
||||||
initActionsCalls,
|
initActionsCalls,
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ import { getWidgetHierarchy } from "layoutSystems/anvil/utils/paste/utils";
|
||||||
import type { AnvilGlobalDnDStates } from "../../canvas/hooks/useAnvilGlobalDnDStates";
|
import type { AnvilGlobalDnDStates } from "../../canvas/hooks/useAnvilGlobalDnDStates";
|
||||||
import { getWidgets } from "sagas/selectors";
|
import { getWidgets } from "sagas/selectors";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { WDSZoneWidget } from "widgets/wds/WDSZoneWidget";
|
|
||||||
import { useAnvilWidgetElevation } from "../../canvas/providers/AnvilWidgetElevationProvider";
|
import { useAnvilWidgetElevation } from "../../canvas/providers/AnvilWidgetElevationProvider";
|
||||||
|
import { anvilWidgets } from "widgets/wds/constants";
|
||||||
|
|
||||||
interface AnvilDnDListenerStatesProps {
|
interface AnvilDnDListenerStatesProps {
|
||||||
anvilGlobalDragStates: AnvilGlobalDnDStates;
|
anvilGlobalDragStates: AnvilGlobalDnDStates;
|
||||||
|
|
@ -148,7 +148,7 @@ export const useAnvilDnDListenerStates = ({
|
||||||
}, [widgetProps, allWidgets]);
|
}, [widgetProps, allWidgets]);
|
||||||
|
|
||||||
const isElevatedWidget = useMemo(() => {
|
const isElevatedWidget = useMemo(() => {
|
||||||
if (widgetProps.type === WDSZoneWidget.type) {
|
if (widgetProps.type === anvilWidgets.ZONE_WIDGET) {
|
||||||
const isAnyZoneElevated = allSiblingsWidgetIds.some(
|
const isAnyZoneElevated = allSiblingsWidgetIds.some(
|
||||||
(each) => !!elevatedWidgets[each],
|
(each) => !!elevatedWidgets[each],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -56,14 +56,13 @@ export const FixedLayoutViewerCanvas = (props: BaseWidgetProps) => {
|
||||||
!!props.noPad,
|
!!props.noPad,
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
props.children,
|
props?.children,
|
||||||
|
props?.metaWidgetChildrenStructure,
|
||||||
props.positioning,
|
props.positioning,
|
||||||
props.shouldScrollContents,
|
|
||||||
props.widgetId,
|
props.widgetId,
|
||||||
props.componentHeight,
|
props.noPad,
|
||||||
props.componentWidth,
|
defaultWidgetProps,
|
||||||
snapColumnSpace,
|
layoutSystemProps,
|
||||||
props.metaWidgetChildrenStructure,
|
|
||||||
]);
|
]);
|
||||||
const snapRows = getCanvasSnapRows(props.bottomRow);
|
const snapRows = getCanvasSnapRows(props.bottomRow);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import { useIsMobileDevice } from "utils/hooks/useDeviceDetect";
|
||||||
import HtmlTitle from "../AppViewerHtmlTitle";
|
import HtmlTitle from "../AppViewerHtmlTitle";
|
||||||
import Sidebar from "./Sidebar";
|
import Sidebar from "./Sidebar";
|
||||||
import TopHeader from "./components/TopHeader";
|
import TopHeader from "./components/TopHeader";
|
||||||
|
import { getRenderPage } from "selectors/evaluationSelectors";
|
||||||
|
|
||||||
export function Navigation() {
|
export function Navigation() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
@ -50,7 +51,7 @@ export function Navigation() {
|
||||||
getCurrentApplication,
|
getCurrentApplication,
|
||||||
);
|
);
|
||||||
const pages = useSelector(getViewModePageList);
|
const pages = useSelector(getViewModePageList);
|
||||||
|
const shouldShowHeader = useSelector(getRenderPage);
|
||||||
const queryParams = new URLSearchParams(search);
|
const queryParams = new URLSearchParams(search);
|
||||||
const isEmbed = queryParams.get("embed") === "true";
|
const isEmbed = queryParams.get("embed") === "true";
|
||||||
const forceShowNavBar = queryParams.get("navbar") === "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
|
// TODO: refactor this to not directly reference a DOM element by class defined elsewhere
|
||||||
useEffect(
|
useEffect(
|
||||||
function adjustHeaderHeightEffect() {
|
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 () => {
|
return () => {
|
||||||
dispatch(setAppViewHeaderHeight(0));
|
dispatch(setAppViewHeaderHeight(0));
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[navStyle, orientation, dispatch],
|
[navStyle, orientation, dispatch, shouldShowHeader],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
|
|
@ -122,6 +125,8 @@ export function Navigation() {
|
||||||
pages,
|
pages,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (!shouldShowHeader) return null;
|
||||||
|
|
||||||
if (hideHeader) return <HtmlTitle />;
|
if (hideHeader) return <HtmlTitle />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,6 @@ import {
|
||||||
getAppThemeSettings,
|
getAppThemeSettings,
|
||||||
getCurrentApplication,
|
getCurrentApplication,
|
||||||
} from "ee/selectors/applicationSelectors";
|
} from "ee/selectors/applicationSelectors";
|
||||||
import { editorInitializer } from "../../utils/editor/EditorUtils";
|
|
||||||
import { widgetInitialisationSuccess } from "../../actions/widgetActions";
|
|
||||||
import {
|
import {
|
||||||
ThemeProvider as WDSThemeProvider,
|
ThemeProvider as WDSThemeProvider,
|
||||||
useTheme,
|
useTheme,
|
||||||
|
|
@ -49,6 +47,10 @@ import {
|
||||||
import urlBuilder from "ee/entities/URLRedirect/URLAssembly";
|
import urlBuilder from "ee/entities/URLRedirect/URLAssembly";
|
||||||
import { getHideWatermark } from "ee/selectors/organizationSelectors";
|
import { getHideWatermark } from "ee/selectors/organizationSelectors";
|
||||||
import { getIsAnvilLayout } from "layoutSystems/anvil/integrations/selectors";
|
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<{
|
const AppViewerBody = styled.section<{
|
||||||
hasPages: boolean;
|
hasPages: boolean;
|
||||||
|
|
@ -80,6 +82,21 @@ type Props = AppViewerProps & RouteComponentProps<AppViewerRouteParams>;
|
||||||
|
|
||||||
const DEFAULT_FONT_NAME = "System Default";
|
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) {
|
function AppViewer(props: Props) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { pathname, search } = props.location;
|
const { pathname, search } = props.location;
|
||||||
|
|
@ -103,15 +120,7 @@ function AppViewer(props: Props) {
|
||||||
getCurrentApplication,
|
getCurrentApplication,
|
||||||
);
|
);
|
||||||
const isAnvilLayout = useSelector(getIsAnvilLayout);
|
const isAnvilLayout = useSelector(getIsAnvilLayout);
|
||||||
const themeSetting = useSelector(getAppThemeSettings);
|
const renderPage = useSelector(getRenderPage);
|
||||||
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 focusRef = useWidgetFocus();
|
const focusRef = useWidgetFocus();
|
||||||
const isAutoLayout = useSelector(getIsAutoLayout);
|
const isAutoLayout = useSelector(getIsAutoLayout);
|
||||||
|
|
@ -120,9 +129,9 @@ function AppViewer(props: Props) {
|
||||||
* initializes the widgets factory and registers all widgets
|
* initializes the widgets factory and registers all widgets
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editorInitializer().then(() => {
|
registerLayoutComponents();
|
||||||
dispatch(widgetInitialisationSuccess());
|
// 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
|
* initialize the app if branch, pageId or application is changed
|
||||||
|
|
@ -205,6 +214,8 @@ function AppViewer(props: Props) {
|
||||||
};
|
};
|
||||||
}, [selectedTheme.properties.fontFamily.appFont]);
|
}, [selectedTheme.properties.fontFamily.appFont]);
|
||||||
|
|
||||||
|
if (!renderPage) return null;
|
||||||
|
|
||||||
const renderChildren = () => {
|
const renderChildren = () => {
|
||||||
return (
|
return (
|
||||||
<EditorContextProvider renderMode="PAGE">
|
<EditorContextProvider renderMode="PAGE">
|
||||||
|
|
@ -251,7 +262,7 @@ function AppViewer(props: Props) {
|
||||||
|
|
||||||
if (isAnvilLayout) {
|
if (isAnvilLayout) {
|
||||||
return (
|
return (
|
||||||
<WDSThemeProvider theme={theme}>{renderChildren()}</WDSThemeProvider>
|
<WDSThemeProviderWithTheme>{renderChildren()}</WDSThemeProviderWithTheme>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import TemplateDescription from "./Template/TemplateDescription";
|
||||||
import SimilarTemplates from "./Template/SimilarTemplates";
|
import SimilarTemplates from "./Template/SimilarTemplates";
|
||||||
import { templateIdUrl } from "ee/RouteBuilder";
|
import { templateIdUrl } from "ee/RouteBuilder";
|
||||||
import TemplateViewHeader from "./TemplateViewHeader";
|
import TemplateViewHeader from "./TemplateViewHeader";
|
||||||
import { registerEditorWidgets } from "utils/editor/EditorUtils";
|
import { registerAllWidgets } from "utils/editor/EditorUtils";
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
@ -154,7 +154,7 @@ export function TemplateView({
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
registerEditorWidgets();
|
registerAllWidgets();
|
||||||
}, []);
|
}, []);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getTemplateInformation(templateId));
|
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 loadingEntitiesReducer from "./loadingEntitiesReducer";
|
||||||
import formEvaluationReducer from "./formEvaluationReducer";
|
import formEvaluationReducer from "./formEvaluationReducer";
|
||||||
import triggerReducer from "./triggerReducer";
|
import triggerReducer from "./triggerReducer";
|
||||||
|
import firstEvaluationReducer from "./firstEvaluationReducer";
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
tree: evaluatedTreeReducer,
|
tree: evaluatedTreeReducer,
|
||||||
|
|
@ -11,4 +12,5 @@ export default combineReducers({
|
||||||
loadingEntities: loadingEntitiesReducer,
|
loadingEntities: loadingEntitiesReducer,
|
||||||
formEvaluation: formEvaluationReducer,
|
formEvaluation: formEvaluationReducer,
|
||||||
triggers: triggerReducer,
|
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 { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
|
||||||
import { MAIN_THREAD_ACTION } from "ee/workers/Evaluation/evalWorkerActions";
|
import { MAIN_THREAD_ACTION } from "ee/workers/Evaluation/evalWorkerActions";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
|
|
@ -13,6 +13,7 @@ import { MessageType } from "utils/MessageUtil";
|
||||||
import type { ResponsePayload } from "../sagas/EvaluationsSaga";
|
import type { ResponsePayload } from "../sagas/EvaluationsSaga";
|
||||||
import {
|
import {
|
||||||
executeTriggerRequestSaga,
|
executeTriggerRequestSaga,
|
||||||
|
getUnevalTreeWithWidgetsRegistered,
|
||||||
updateDataTreeHandler,
|
updateDataTreeHandler,
|
||||||
} from "../sagas/EvaluationsSaga";
|
} from "../sagas/EvaluationsSaga";
|
||||||
import { evalWorker } from "utils/workerInstances";
|
import { evalWorker } from "utils/workerInstances";
|
||||||
|
|
@ -22,7 +23,7 @@ import isEmpty from "lodash/isEmpty";
|
||||||
import { sortJSExecutionDataByCollectionId } from "workers/Evaluation/JSObject/utils";
|
import { sortJSExecutionDataByCollectionId } from "workers/Evaluation/JSObject/utils";
|
||||||
import type { LintTreeSagaRequestData } from "plugins/Linting/types";
|
import type { LintTreeSagaRequestData } from "plugins/Linting/types";
|
||||||
import { evalErrorHandler } from "./EvalErrorHandler";
|
import { evalErrorHandler } from "./EvalErrorHandler";
|
||||||
import { getUnevaluatedDataTree } from "selectors/dataTreeSelectors";
|
import type { getUnevaluatedDataTree } from "selectors/dataTreeSelectors";
|
||||||
import { endSpan, startRootSpan } from "instrumentation/generateTraces";
|
import { endSpan, startRootSpan } from "instrumentation/generateTraces";
|
||||||
import type { UpdateDataTreeMessageData } from "./types";
|
import type { UpdateDataTreeMessageData } from "./types";
|
||||||
|
|
||||||
|
|
@ -165,9 +166,8 @@ export function* handleEvalWorkerMessage(message: TMessage<any>) {
|
||||||
case MAIN_THREAD_ACTION.UPDATE_DATATREE: {
|
case MAIN_THREAD_ACTION.UPDATE_DATATREE: {
|
||||||
const { workerResponse } = data as UpdateDataTreeMessageData;
|
const { workerResponse } = data as UpdateDataTreeMessageData;
|
||||||
const rootSpan = startRootSpan("DataTreeFactory.create");
|
const rootSpan = startRootSpan("DataTreeFactory.create");
|
||||||
|
|
||||||
const unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree> =
|
const unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree> =
|
||||||
yield select(getUnevaluatedDataTree);
|
yield call(getUnevalTreeWithWidgetsRegistered);
|
||||||
|
|
||||||
endSpan(rootSpan);
|
endSpan(rootSpan);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,17 @@ import {
|
||||||
getCurrentPageId,
|
getCurrentPageId,
|
||||||
} from "selectors/editorSelectors";
|
} from "selectors/editorSelectors";
|
||||||
import { updateActionData } from "actions/pluginActionActions";
|
import { updateActionData } from "actions/pluginActionActions";
|
||||||
|
import watchInitSagas from "./InitSagas";
|
||||||
|
|
||||||
|
import { clearAllWidgetFactoryCache } from "WidgetProvider/factory/decorators";
|
||||||
|
|
||||||
jest.mock("loglevel");
|
jest.mock("loglevel");
|
||||||
|
jest.mock("utils/editor/EditorUtils", () => ({
|
||||||
|
registerAllWidgets: jest.fn(),
|
||||||
|
}));
|
||||||
|
jest.mock("WidgetProvider/factory/decorators", () => ({
|
||||||
|
clearAllWidgetFactoryCache: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe("evaluateTreeSaga", () => {
|
describe("evaluateTreeSaga", () => {
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
|
|
@ -64,29 +73,34 @@ describe("evaluateTreeSaga", () => {
|
||||||
],
|
],
|
||||||
[select(getCurrentPageDSLVersion), 1],
|
[select(getCurrentPageDSLVersion), 1],
|
||||||
])
|
])
|
||||||
.call(evalWorker.request, EVAL_WORKER_ACTIONS.EVAL_TREE, {
|
.call(
|
||||||
cacheProps: {
|
evalWorker.request,
|
||||||
instanceId: "instanceId",
|
EVAL_WORKER_ACTIONS.EVAL_TREE,
|
||||||
appId: "applicationId",
|
{
|
||||||
pageId: "pageId",
|
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,
|
appMode: false,
|
||||||
timestamp: new Date("11 September 2024").toISOString(),
|
widgetsMeta: {},
|
||||||
dslVersion: 1,
|
shouldRespondWithLogs: true,
|
||||||
|
affectedJSObjects: { ids: [], isAllAffected: false },
|
||||||
|
actionDataPayloadConsolidated: undefined,
|
||||||
},
|
},
|
||||||
unevalTree: unEvalAndConfigTree,
|
false,
|
||||||
widgetTypeConfigMap: undefined,
|
)
|
||||||
widgets: {},
|
|
||||||
theme: {},
|
|
||||||
shouldReplay: true,
|
|
||||||
allActionValidationConfig: {},
|
|
||||||
forceEvaluation: false,
|
|
||||||
metaWidgets: {},
|
|
||||||
appMode: false,
|
|
||||||
widgetsMeta: {},
|
|
||||||
shouldRespondWithLogs: true,
|
|
||||||
affectedJSObjects: { ids: [], isAllAffected: false },
|
|
||||||
actionDataPayloadConsolidated: undefined,
|
|
||||||
})
|
|
||||||
.run();
|
.run();
|
||||||
});
|
});
|
||||||
test("should set 'shouldRespondWithLogs' to false when the log level is not debug", async () => {
|
test("should set 'shouldRespondWithLogs' to false when the log level is not debug", async () => {
|
||||||
|
|
@ -112,29 +126,34 @@ describe("evaluateTreeSaga", () => {
|
||||||
],
|
],
|
||||||
[select(getCurrentPageDSLVersion), 1],
|
[select(getCurrentPageDSLVersion), 1],
|
||||||
])
|
])
|
||||||
.call(evalWorker.request, EVAL_WORKER_ACTIONS.EVAL_TREE, {
|
.call(
|
||||||
cacheProps: {
|
evalWorker.request,
|
||||||
instanceId: "instanceId",
|
EVAL_WORKER_ACTIONS.EVAL_TREE,
|
||||||
appId: "applicationId",
|
{
|
||||||
pageId: "pageId",
|
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,
|
appMode: false,
|
||||||
timestamp: new Date("11 September 2024").toISOString(),
|
widgetsMeta: {},
|
||||||
dslVersion: 1,
|
shouldRespondWithLogs: false,
|
||||||
|
affectedJSObjects: { ids: [], isAllAffected: false },
|
||||||
|
actionDataPayloadConsolidated: undefined,
|
||||||
},
|
},
|
||||||
unevalTree: unEvalAndConfigTree,
|
false,
|
||||||
widgetTypeConfigMap: undefined,
|
)
|
||||||
widgets: {},
|
|
||||||
theme: {},
|
|
||||||
shouldReplay: true,
|
|
||||||
allActionValidationConfig: {},
|
|
||||||
forceEvaluation: false,
|
|
||||||
metaWidgets: {},
|
|
||||||
appMode: false,
|
|
||||||
widgetsMeta: {},
|
|
||||||
shouldRespondWithLogs: false,
|
|
||||||
affectedJSObjects: { ids: [], isAllAffected: false },
|
|
||||||
actionDataPayloadConsolidated: undefined,
|
|
||||||
})
|
|
||||||
.run();
|
.run();
|
||||||
});
|
});
|
||||||
test("should propagate affectedJSObjects property to evaluation action", async () => {
|
test("should propagate affectedJSObjects property to evaluation action", async () => {
|
||||||
|
|
@ -169,29 +188,95 @@ describe("evaluateTreeSaga", () => {
|
||||||
],
|
],
|
||||||
[select(getCurrentPageDSLVersion), 1],
|
[select(getCurrentPageDSLVersion), 1],
|
||||||
])
|
])
|
||||||
.call(evalWorker.request, EVAL_WORKER_ACTIONS.EVAL_TREE, {
|
.call(
|
||||||
cacheProps: {
|
evalWorker.request,
|
||||||
instanceId: "instanceId",
|
EVAL_WORKER_ACTIONS.EVAL_TREE,
|
||||||
appId: "applicationId",
|
{
|
||||||
pageId: "pageId",
|
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,
|
appMode: false,
|
||||||
timestamp: new Date("11 September 2024").toISOString(),
|
widgetsMeta: {},
|
||||||
dslVersion: 1,
|
shouldRespondWithLogs: false,
|
||||||
|
affectedJSObjects,
|
||||||
|
actionDataPayloadConsolidated: undefined,
|
||||||
},
|
},
|
||||||
unevalTree: unEvalAndConfigTree,
|
false,
|
||||||
widgetTypeConfigMap: undefined,
|
)
|
||||||
widgets: {},
|
.run();
|
||||||
theme: {},
|
});
|
||||||
shouldReplay: true,
|
test("should call evalWorker.request with isFirstEvaluation as true when isFirstEvaluation is set as true in evaluateTreeSaga", async () => {
|
||||||
allActionValidationConfig: {},
|
const unEvalAndConfigTree = { unEvalTree: {}, configTree: {} };
|
||||||
forceEvaluation: false,
|
const isFirstEvaluation = true;
|
||||||
metaWidgets: {},
|
|
||||||
appMode: false,
|
return expectSaga(
|
||||||
widgetsMeta: {},
|
evaluateTreeSaga,
|
||||||
shouldRespondWithLogs: false,
|
unEvalAndConfigTree,
|
||||||
affectedJSObjects,
|
[],
|
||||||
actionDataPayloadConsolidated: undefined,
|
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();
|
.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 {
|
import {
|
||||||
actionChannel,
|
actionChannel,
|
||||||
all,
|
all,
|
||||||
|
|
@ -9,6 +14,7 @@ import {
|
||||||
select,
|
select,
|
||||||
spawn,
|
spawn,
|
||||||
take,
|
take,
|
||||||
|
join,
|
||||||
} from "redux-saga/effects";
|
} from "redux-saga/effects";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -16,7 +22,10 @@ import type {
|
||||||
ReduxActionType,
|
ReduxActionType,
|
||||||
AnyReduxAction,
|
AnyReduxAction,
|
||||||
} from "actions/ReduxActionTypes";
|
} from "actions/ReduxActionTypes";
|
||||||
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
|
import {
|
||||||
|
ReduxActionTypes,
|
||||||
|
ReduxActionErrorTypes,
|
||||||
|
} from "ee/constants/ReduxActionConstants";
|
||||||
import {
|
import {
|
||||||
getDataTree,
|
getDataTree,
|
||||||
getUnevaluatedDataTree,
|
getUnevaluatedDataTree,
|
||||||
|
|
@ -39,6 +48,7 @@ import {
|
||||||
import {
|
import {
|
||||||
setDependencyMap,
|
setDependencyMap,
|
||||||
setEvaluatedTree,
|
setEvaluatedTree,
|
||||||
|
setIsFirstPageLoad,
|
||||||
shouldForceEval,
|
shouldForceEval,
|
||||||
shouldLog,
|
shouldLog,
|
||||||
shouldProcessAction,
|
shouldProcessAction,
|
||||||
|
|
@ -99,7 +109,7 @@ import {
|
||||||
} from "actions/pluginActionActions";
|
} from "actions/pluginActionActions";
|
||||||
import { executeJSUpdates } from "actions/jsPaneActions";
|
import { executeJSUpdates } from "actions/jsPaneActions";
|
||||||
import { setEvaluatedActionSelectorField } from "actions/actionSelectorActions";
|
import { setEvaluatedActionSelectorField } from "actions/actionSelectorActions";
|
||||||
import { waitForWidgetConfigBuild } from "./InitSagas";
|
|
||||||
import { logDynamicTriggerExecution } from "ee/sagas/analyticsSaga";
|
import { logDynamicTriggerExecution } from "ee/sagas/analyticsSaga";
|
||||||
import { selectFeatureFlags } from "ee/selectors/featureFlagsSelectors";
|
import { selectFeatureFlags } from "ee/selectors/featureFlagsSelectors";
|
||||||
import { fetchFeatureFlagsInit } from "actions/userActions";
|
import { fetchFeatureFlagsInit } from "actions/userActions";
|
||||||
|
|
@ -108,7 +118,6 @@ import {
|
||||||
parseUpdatesAndDeleteUndefinedUpdates,
|
parseUpdatesAndDeleteUndefinedUpdates,
|
||||||
} from "./EvaluationsSagaUtils";
|
} from "./EvaluationsSagaUtils";
|
||||||
import { getFeatureFlagsFetched } from "selectors/usersSelectors";
|
import { getFeatureFlagsFetched } from "selectors/usersSelectors";
|
||||||
import { getIsCurrentEditorWorkflowType } from "ee/selectors/workflowSelectors";
|
|
||||||
import { evalErrorHandler } from "./EvalErrorHandler";
|
import { evalErrorHandler } from "./EvalErrorHandler";
|
||||||
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
|
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
|
||||||
import { endSpan, startRootSpan } from "instrumentation/generateTraces";
|
import { endSpan, startRootSpan } from "instrumentation/generateTraces";
|
||||||
|
|
@ -124,11 +133,89 @@ import type {
|
||||||
EvaluationReduxAction,
|
EvaluationReduxAction,
|
||||||
} from "actions/EvaluationReduxActionTypes";
|
} from "actions/EvaluationReduxActionTypes";
|
||||||
import { appsmithTelemetry } from "instrumentation";
|
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();
|
const APPSMITH_CONFIGS = getAppsmithConfigs();
|
||||||
|
|
||||||
let widgetTypeConfigMap: WidgetTypeConfigMap;
|
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(
|
export function* updateDataTreeHandler(
|
||||||
data: {
|
data: {
|
||||||
evalTreeResponse: EvalTreeResponseData;
|
evalTreeResponse: EvalTreeResponseData;
|
||||||
|
|
@ -271,6 +358,7 @@ export function* evaluateTreeSaga(
|
||||||
requiresLogging = false,
|
requiresLogging = false,
|
||||||
affectedJSObjects: AffectedJSObjects = defaultAffectedJSObjects,
|
affectedJSObjects: AffectedJSObjects = defaultAffectedJSObjects,
|
||||||
actionDataPayloadConsolidated?: actionDataPayload,
|
actionDataPayloadConsolidated?: actionDataPayload,
|
||||||
|
isFirstEvaluation = false,
|
||||||
) {
|
) {
|
||||||
const allActionValidationConfig: ReturnType<
|
const allActionValidationConfig: ReturnType<
|
||||||
typeof getAllActionValidationConfig
|
typeof getAllActionValidationConfig
|
||||||
|
|
@ -322,6 +410,7 @@ export function* evaluateTreeSaga(
|
||||||
evalWorker.request,
|
evalWorker.request,
|
||||||
EVAL_WORKER_ACTIONS.EVAL_TREE,
|
EVAL_WORKER_ACTIONS.EVAL_TREE,
|
||||||
evalTreeRequestData,
|
evalTreeRequestData,
|
||||||
|
isFirstEvaluation,
|
||||||
);
|
);
|
||||||
|
|
||||||
yield call(
|
yield call(
|
||||||
|
|
@ -369,8 +458,8 @@ export function* evaluateAndExecuteDynamicTrigger(
|
||||||
) {
|
) {
|
||||||
const rootSpan = startRootSpan("DataTreeFactory.create");
|
const rootSpan = startRootSpan("DataTreeFactory.create");
|
||||||
|
|
||||||
const unEvalTree: ReturnType<typeof getUnevaluatedDataTree> = yield select(
|
const unEvalTree: ReturnType<typeof getUnevaluatedDataTree> = yield call(
|
||||||
getUnevaluatedDataTree,
|
getUnevalTreeWithWidgetsRegistered,
|
||||||
);
|
);
|
||||||
|
|
||||||
endSpan(rootSpan);
|
endSpan(rootSpan);
|
||||||
|
|
@ -521,7 +610,7 @@ function* validateProperty(property: string, value: any, props: WidgetProps) {
|
||||||
const rootSpan = startRootSpan("DataTreeFactory.create");
|
const rootSpan = startRootSpan("DataTreeFactory.create");
|
||||||
|
|
||||||
const unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree> =
|
const unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree> =
|
||||||
yield select(getUnevaluatedDataTree);
|
yield call(getUnevalTreeWithWidgetsRegistered);
|
||||||
|
|
||||||
endSpan(rootSpan);
|
endSpan(rootSpan);
|
||||||
const configTree = unEvalAndConfigTree.configTree;
|
const configTree = unEvalAndConfigTree.configTree;
|
||||||
|
|
@ -541,6 +630,15 @@ function* validateProperty(property: string, value: any, props: WidgetProps) {
|
||||||
return response;
|
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
|
// 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
|
// So that during that evaluation cycle all affected JS objects are correctly diffed
|
||||||
function mergeJSBufferedActions(
|
function mergeJSBufferedActions(
|
||||||
|
|
@ -706,6 +804,8 @@ export function* evalAndLintingHandler(
|
||||||
requiresLogging: boolean;
|
requiresLogging: boolean;
|
||||||
affectedJSObjects: AffectedJSObjects;
|
affectedJSObjects: AffectedJSObjects;
|
||||||
actionDataPayloadConsolidated: actionDataPayload[];
|
actionDataPayloadConsolidated: actionDataPayload[];
|
||||||
|
isFirstEvaluation?: boolean;
|
||||||
|
jsLibrariesTask?: Task;
|
||||||
}>,
|
}>,
|
||||||
) {
|
) {
|
||||||
const span = startRootSpan("evalAndLintingHandler");
|
const span = startRootSpan("evalAndLintingHandler");
|
||||||
|
|
@ -713,6 +813,9 @@ export function* evalAndLintingHandler(
|
||||||
actionDataPayloadConsolidated,
|
actionDataPayloadConsolidated,
|
||||||
affectedJSObjects,
|
affectedJSObjects,
|
||||||
forceEvaluation,
|
forceEvaluation,
|
||||||
|
|
||||||
|
isFirstEvaluation = false,
|
||||||
|
jsLibrariesTask,
|
||||||
requiresLogging,
|
requiresLogging,
|
||||||
shouldReplay,
|
shouldReplay,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
@ -737,10 +840,17 @@ export function* evalAndLintingHandler(
|
||||||
|
|
||||||
// Generate all the data needed for both eval and linting
|
// Generate all the data needed for both eval and linting
|
||||||
const unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree> =
|
const unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree> =
|
||||||
yield select(getUnevaluatedDataTree);
|
yield call(getUnevalTreeWithWidgetsRegistered);
|
||||||
|
|
||||||
|
widgetTypeConfigMap = WidgetFactory.getWidgetTypeConfigMap();
|
||||||
|
|
||||||
endSpan(rootSpan);
|
endSpan(rootSpan);
|
||||||
|
|
||||||
|
// wait for the webworker to complete its setup before starting the evaluation
|
||||||
|
if (jsLibrariesTask) {
|
||||||
|
yield join(jsLibrariesTask);
|
||||||
|
}
|
||||||
|
|
||||||
const postEvalActions = getPostEvalActions(action);
|
const postEvalActions = getPostEvalActions(action);
|
||||||
const fn: (...args: unknown[]) => CallEffect<unknown> | ForkEffect<unknown> =
|
const fn: (...args: unknown[]) => CallEffect<unknown> | ForkEffect<unknown> =
|
||||||
isBlockingCall ? call : fork;
|
isBlockingCall ? call : fork;
|
||||||
|
|
@ -758,6 +868,7 @@ export function* evalAndLintingHandler(
|
||||||
requiresLogging,
|
requiresLogging,
|
||||||
affectedJSObjects,
|
affectedJSObjects,
|
||||||
actionDataPayloadConsolidated,
|
actionDataPayloadConsolidated,
|
||||||
|
isFirstEvaluation,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -769,51 +880,80 @@ export function* evalAndLintingHandler(
|
||||||
yield all(effects);
|
yield all(effects);
|
||||||
endSpan(span);
|
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
|
// TODO: Fix this the next time the file is edited
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
function* evaluationChangeListenerSaga(): any {
|
function* evaluationChangeListenerSaga(): any {
|
||||||
const firstEvalActionChannel = yield actionChannel(FIRST_EVAL_REDUX_ACTIONS);
|
const firstEvalActionChannel = yield actionChannel(FIRST_EVAL_REDUX_ACTIONS);
|
||||||
|
|
||||||
// Explicitly shutdown old worker if present
|
const initializeJSLibrariesChannel = yield actionChannel(
|
||||||
yield all([call(evalWorker.shutdown), call(lintWorker.shutdown)]);
|
ReduxActionTypes.DEFER_LOADING_JS_LIBRARIES,
|
||||||
const [evalWorkerListenerChannel] = yield all([
|
);
|
||||||
call(evalWorker.start),
|
const appMode = yield select(getAppMode);
|
||||||
call(lintWorker.start),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const isFFFetched = yield select(getFeatureFlagsFetched);
|
let jsLibrariesTask: Task | undefined;
|
||||||
|
|
||||||
if (!isFFFetched) {
|
// for all published apps, we need to reset the data tree and setup the worker as an independent process
|
||||||
yield call(fetchFeatureFlagsInit);
|
// after the process is forked we can allow the main thread to continue its execution since the main thread's tasks would be independent
|
||||||
yield take(ReduxActionTypes.FETCH_FEATURE_FLAGS_SUCCESS);
|
// 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(
|
const initAction: EvaluationReduxAction<unknown> = yield take(
|
||||||
firstEvalActionChannel,
|
firstEvalActionChannel,
|
||||||
);
|
);
|
||||||
|
|
||||||
firstEvalActionChannel.close();
|
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, {
|
yield fork(evalAndLintingHandler, false, initAction, {
|
||||||
shouldReplay: false,
|
shouldReplay: false,
|
||||||
forceEvaluation: false,
|
forceEvaluation: false,
|
||||||
|
|
@ -822,6 +962,8 @@ function* evaluationChangeListenerSaga(): any {
|
||||||
ids: [],
|
ids: [],
|
||||||
isAllAffected: true,
|
isAllAffected: true,
|
||||||
},
|
},
|
||||||
|
isFirstEvaluation: true,
|
||||||
|
jsLibrariesTask: jsLibrariesTask,
|
||||||
});
|
});
|
||||||
// TODO: Fix this the next time the file is edited
|
// TODO: Fix this the next time the file is edited
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// 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 type { PACKAGE_PULL_STATUS } from "ee/constants/ModuleConstants";
|
||||||
import { validateSessionToken } from "utils/SessionUtils";
|
import { validateSessionToken } from "utils/SessionUtils";
|
||||||
import { appsmithTelemetry } from "instrumentation";
|
import { appsmithTelemetry } from "instrumentation";
|
||||||
|
import { clearAllWidgetFactoryCache } from "WidgetProvider/factory/decorators";
|
||||||
|
|
||||||
export const URL_CHANGE_ACTIONS = [
|
export const URL_CHANGE_ACTIONS = [
|
||||||
ReduxActionTypes.CURRENT_APPLICATION_NAME_UPDATE,
|
ReduxActionTypes.CURRENT_APPLICATION_NAME_UPDATE,
|
||||||
|
|
@ -535,6 +536,11 @@ function* eagerPageInitSaga() {
|
||||||
} catch (e) {}
|
} 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() {
|
export default function* watchInitSagas() {
|
||||||
yield all([
|
yield all([
|
||||||
takeLeading(
|
takeLeading(
|
||||||
|
|
@ -547,5 +553,7 @@ export default function* watchInitSagas() {
|
||||||
takeLatest(ReduxActionTypes.RESET_EDITOR_REQUEST, resetEditorSaga),
|
takeLatest(ReduxActionTypes.RESET_EDITOR_REQUEST, resetEditorSaga),
|
||||||
takeEvery(URL_CHANGE_ACTIONS, updateURLSaga),
|
takeEvery(URL_CHANGE_ACTIONS, updateURLSaga),
|
||||||
takeEvery(ReduxActionTypes.INITIALIZE_CURRENT_PAGE, eagerPageInitSaga),
|
takeEvery(ReduxActionTypes.INITIALIZE_CURRENT_PAGE, eagerPageInitSaga),
|
||||||
|
|
||||||
|
takeLeading(ReduxActionTypes.WIDGET_INIT_SUCCESS, handleWidgetInitSuccess),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { DataTree } from "entities/DataTree/dataTreeTypes";
|
import type { DataTree } from "entities/DataTree/dataTreeTypes";
|
||||||
import { createSelector } from "reselect";
|
import { createSelector } from "reselect";
|
||||||
import WidgetFactory from "WidgetProvider/factory";
|
import WidgetFactory from "WidgetProvider/factory";
|
||||||
|
import { getWidgetConfigsVersion } from "WidgetProvider/factory/widgetConfigVersion";
|
||||||
import type { FlattenedWidgetProps } from "WidgetProvider/types";
|
import type { FlattenedWidgetProps } from "WidgetProvider/types";
|
||||||
import type { JSLibrary } from "workers/common/JSLibrary";
|
import type { JSLibrary } from "workers/common/JSLibrary";
|
||||||
import { getDataTree } from "./dataTreeSelectors";
|
import { getDataTree } from "./dataTreeSelectors";
|
||||||
|
|
@ -24,6 +25,7 @@ export const getUsedActionNames = createSelector(
|
||||||
getDataTree,
|
getDataTree,
|
||||||
getParentWidget,
|
getParentWidget,
|
||||||
selectInstalledLibraries,
|
selectInstalledLibraries,
|
||||||
|
getWidgetConfigsVersion, // Add dependency on widget configs version
|
||||||
(
|
(
|
||||||
// TODO: Fix this the next time the file is edited
|
// TODO: Fix this the next time the file is edited
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// 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 { objectKeys } from "@appsmith/utils";
|
||||||
import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsReducer";
|
import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsReducer";
|
||||||
import { ActionRunBehaviour } from "PluginActionEditor/types/PluginActionTypes";
|
import { ActionRunBehaviour } from "PluginActionEditor/types/PluginActionTypes";
|
||||||
|
import { getWidgetConfigsVersion } from "WidgetProvider/factory/widgetConfigVersion";
|
||||||
|
|
||||||
const getIsDraggingOrResizing = (state: DefaultRootState) =>
|
const getIsDraggingOrResizing = (state: DefaultRootState) =>
|
||||||
state.ui.widgetDragResize.isResizing || state.ui.widgetDragResize.isDragging;
|
state.ui.widgetDragResize.isResizing || state.ui.widgetDragResize.isDragging;
|
||||||
|
|
@ -398,6 +399,7 @@ const isModuleWidget = (
|
||||||
export const getWidgetCards = createSelector(
|
export const getWidgetCards = createSelector(
|
||||||
getIsAutoLayout,
|
getIsAutoLayout,
|
||||||
getIsAnvilLayout,
|
getIsAnvilLayout,
|
||||||
|
getWidgetConfigsVersion, // Add dependency on widget configs version
|
||||||
(isAutoLayout, isAnvilLayout) => {
|
(isAutoLayout, isAnvilLayout) => {
|
||||||
const widgetConfigs = WidgetFactory.getConfigs();
|
const widgetConfigs = WidgetFactory.getConfigs();
|
||||||
const widgetConfigsArray = Object.values(widgetConfigs);
|
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 { getNextEntityName } from "utils/AppsmithUtils";
|
||||||
|
|
||||||
import WidgetFactory from "WidgetProvider/factory";
|
import WidgetFactory from "WidgetProvider/factory";
|
||||||
|
import { getWidgetConfigsVersion } from "WidgetProvider/factory/widgetConfigVersion";
|
||||||
import {
|
import {
|
||||||
getAltBlockWidgetSelection,
|
getAltBlockWidgetSelection,
|
||||||
getFocusedWidget,
|
getFocusedWidget,
|
||||||
|
|
@ -78,6 +79,7 @@ export const getModalDropdownList = createSelector(
|
||||||
export const getNextModalName = createSelector(
|
export const getNextModalName = createSelector(
|
||||||
getExistingWidgetNames,
|
getExistingWidgetNames,
|
||||||
getModalWidgetType,
|
getModalWidgetType,
|
||||||
|
getWidgetConfigsVersion, // Add dependency on widget configs version
|
||||||
(names, modalWidgetType) => {
|
(names, modalWidgetType) => {
|
||||||
const prefix =
|
const prefix =
|
||||||
WidgetFactory.widgetConfigMap.get(modalWidgetType)?.widgetName || "";
|
WidgetFactory.widgetConfigMap.get(modalWidgetType)?.widgetName || "";
|
||||||
|
|
@ -267,3 +269,19 @@ export const isResizingOrDragging = createSelector(
|
||||||
(state: DefaultRootState) => state.ui.widgetDragResize.isDragging,
|
(state: DefaultRootState) => state.ui.widgetDragResize.isDragging,
|
||||||
(isResizing, isDragging) => !!isResizing || !!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,
|
props: WidgetProps,
|
||||||
) => {
|
) => {
|
||||||
const { getCanvasHeightOffset } = WidgetFactory.getWidgetMethods(widgetType);
|
const { getCanvasHeightOffset } = WidgetFactory.getWidgetMethods(widgetType);
|
||||||
|
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
|
||||||
if (getCanvasHeightOffset) {
|
if (getCanvasHeightOffset) {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import {
|
||||||
filterSpanData,
|
filterSpanData,
|
||||||
newWebWorkerSpanData,
|
newWebWorkerSpanData,
|
||||||
} from "instrumentation/generateWebWorkerTraces";
|
} from "instrumentation/generateWebWorkerTraces";
|
||||||
|
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap a webworker to provide a synchronous request-response semantic.
|
* 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 method identifier for a rpc method
|
||||||
* @param requestData data that we want to send over to the worker
|
* @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
|
* @returns response from the worker
|
||||||
*/
|
*/
|
||||||
// TODO: Fix this the next time the file is edited
|
// TODO: Fix this the next time the file is edited
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// 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);
|
yield this.ready(true);
|
||||||
|
|
||||||
// Impossible case, but helps avoid `?` later in code and makes it clearer.
|
// Impossible case, but helps avoid `?` later in code and makes it clearer.
|
||||||
|
|
@ -292,6 +294,12 @@ export class GracefulWorkerService {
|
||||||
messageId,
|
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.
|
// The `this._broker` method is listening to events and will pass response to us over this channel.
|
||||||
const response = yield take(ch);
|
const response = yield take(ch);
|
||||||
const { data, endTime, startTime } = response;
|
const { data, endTime, startTime } = response;
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,20 @@
|
||||||
// import Widgets from "widgets";
|
// import Widgets from "widgets";
|
||||||
import { registerWidgets } from "WidgetProvider/factory/registrationHelper";
|
import { registerWidgets } from "WidgetProvider/factory/registrationHelper";
|
||||||
import { registerLayoutComponents } from "layoutSystems/anvil/utils/layouts/layoutUtils";
|
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(Array.from(loadedWidgets.values()));
|
||||||
registerWidgets(widgets);
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error("Error loading widgets", error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const editorInitializer = async () => {
|
export const editorInitializer = async () => {
|
||||||
registerEditorWidgets();
|
await registerAllWidgets();
|
||||||
// TODO: do this only for anvil.
|
// TODO: do this only for anvil.
|
||||||
registerLayoutComponents();
|
registerLayoutComponents();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@ import type {
|
||||||
} from "constants/PropertyControlConstants";
|
} from "constants/PropertyControlConstants";
|
||||||
import { ValidationTypes } from "constants/WidgetValidation";
|
import { ValidationTypes } from "constants/WidgetValidation";
|
||||||
import { isFunction } from "lodash";
|
import { isFunction } from "lodash";
|
||||||
import widgets from "widgets";
|
import { loadAllWidgets } from "widgets";
|
||||||
import WidgetFactory from "WidgetProvider/factory";
|
import WidgetFactory from "WidgetProvider/factory";
|
||||||
import { registerWidgets } from "WidgetProvider/factory/registrationHelper";
|
import { registerWidgets } from "WidgetProvider/factory/registrationHelper";
|
||||||
|
import type BaseWidget from "widgets/BaseWidget";
|
||||||
|
|
||||||
function validatePropertyPaneConfig(
|
function validatePropertyPaneConfig(
|
||||||
config: PropertyPaneConfig[],
|
config: PropertyPaneConfig[],
|
||||||
|
|
@ -143,96 +144,112 @@ const isNotFloat = (n: any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("Tests all widget's propertyPane config", () => {
|
describe("Tests all widget's propertyPane config", () => {
|
||||||
beforeAll(() => {
|
let widgetsArray: (typeof BaseWidget)[] = [];
|
||||||
registerWidgets(widgets);
|
|
||||||
|
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
|
it("should have loaded widgets", () => {
|
||||||
// Exclude WDS widgets from the tests, since they work differently
|
expect(widgetsArray.length).toBeGreaterThan(0);
|
||||||
.filter((widget) => !widget.type.includes("WDS"))
|
});
|
||||||
.forEach((widget) => {
|
|
||||||
const config = widget.getConfig();
|
|
||||||
|
|
||||||
it(`Checks ${widget.type}'s propertyPaneConfig`, () => {
|
describe("Property Pane Config Tests", () => {
|
||||||
const propertyPaneConfig = widget.getPropertyPaneConfig();
|
//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(
|
it(`Checks ${widget.type}'s propertyPaneConfig`, () => {
|
||||||
validatePropertyPaneConfig(propertyPaneConfig, !!config.hideCard),
|
const propertyPaneConfig = widget.getPropertyPaneConfig();
|
||||||
).toStrictEqual(true);
|
|
||||||
const propertyPaneContentConfig = widget.getPropertyPaneContentConfig();
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
validatePropertyPaneConfig(
|
validatePropertyPaneConfig(propertyPaneConfig, !!config.hideCard),
|
||||||
propertyPaneContentConfig,
|
).toStrictEqual(true);
|
||||||
!!config.isDeprecated,
|
const propertyPaneContentConfig =
|
||||||
),
|
widget.getPropertyPaneContentConfig();
|
||||||
).toStrictEqual(true);
|
|
||||||
const propertyPaneStyleConfig = widget.getPropertyPaneStyleConfig();
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
validatePropertyPaneConfig(
|
validatePropertyPaneConfig(
|
||||||
propertyPaneStyleConfig,
|
propertyPaneContentConfig,
|
||||||
!!config.isDeprecated,
|
!!config.isDeprecated,
|
||||||
),
|
),
|
||||||
).toStrictEqual(true);
|
).toStrictEqual(true);
|
||||||
});
|
const propertyPaneStyleConfig = widget.getPropertyPaneStyleConfig();
|
||||||
it(`Check if ${widget.type}'s dimensions are always integers`, () => {
|
|
||||||
const defaults = widget.getDefaults();
|
|
||||||
|
|
||||||
expect(isNotFloat(defaults.rows)).toBe(true);
|
expect(
|
||||||
expect(isNotFloat(defaults.columns)).toBe(true);
|
validatePropertyPaneConfig(
|
||||||
});
|
propertyPaneStyleConfig,
|
||||||
|
!!config.isDeprecated,
|
||||||
|
),
|
||||||
|
).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
it(`Check if ${widget.type}'s dimensions are always integers`, () => {
|
||||||
|
const defaults = widget.getDefaults();
|
||||||
|
|
||||||
if (config.isDeprecated) {
|
expect(isNotFloat(defaults.rows)).toBe(true);
|
||||||
it(`Check if ${widget.type}'s deprecation config has a proper replacement Widget`, () => {
|
expect(isNotFloat(defaults.columns)).toBe(true);
|
||||||
const widgetType = widget.type;
|
});
|
||||||
|
|
||||||
if (config.replacement === undefined) {
|
if (config.isDeprecated) {
|
||||||
fail(`${widgetType}'s replacement widget is not defined`);
|
it(`Check if ${widget.type}'s deprecation config has a proper replacement Widget`, () => {
|
||||||
}
|
const widgetType = widget.type;
|
||||||
|
|
||||||
const replacementWidgetType = config.replacement;
|
if (config.replacement === undefined) {
|
||||||
const replacementWidget = WidgetFactory.get(replacementWidgetType);
|
fail(`${widgetType}'s replacement widget is not defined`);
|
||||||
const replacementWidgetConfig = replacementWidget?.getConfig();
|
}
|
||||||
|
|
||||||
if (replacementWidgetConfig === undefined) {
|
const replacementWidgetType = config.replacement;
|
||||||
fail(
|
const replacementWidget = WidgetFactory.get(replacementWidgetType);
|
||||||
`${widgetType}'s replacement widget ${replacementWidgetType} does not resolve to an actual widget Config`,
|
const replacementWidgetConfig = replacementWidget?.getConfig();
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (replacementWidgetConfig?.isDeprecated) {
|
if (replacementWidgetConfig === undefined) {
|
||||||
fail(
|
fail(
|
||||||
`${widgetType}'s replacement widget ${replacementWidgetType} itself is deprecated. Cannot have a deprecated widget as a replacement for another deprecated widget`,
|
`${widgetType}'s replacement widget ${replacementWidgetType} does not resolve to an actual widget Config`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (replacementWidgetConfig?.hideCard) {
|
if (replacementWidgetConfig?.isDeprecated) {
|
||||||
fail(
|
fail(
|
||||||
`${widgetType}'s replacement widget ${replacementWidgetType} should be available in the entity Explorer`,
|
`${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 type BaseWidget from "./BaseWidget";
|
||||||
import ExternalWidget from "./ExternalWidget";
|
import { retryPromise } from "utils/AppsmithUtils";
|
||||||
import { WDSTableWidget } from "widgets/wds/WDSTableWidget";
|
import { anvilWidgets } from "./wds/constants";
|
||||||
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 { EEWDSWidgets } from "ee/widgets/wds";
|
import { EEWDSWidgets } from "ee/widgets/wds";
|
||||||
import { WDSDatePickerWidget } from "widgets/wds/WDSDatePickerWidget";
|
|
||||||
import { WDSMultiSelectWidget } from "widgets/wds/WDSMultiSelectWidget";
|
|
||||||
import { EEWidgets } from "ee/widgets";
|
import { EEWidgets } from "ee/widgets";
|
||||||
|
|
||||||
const LegacyWidgets = [
|
// Create widget loader map
|
||||||
CanvasWidget,
|
const WidgetLoaders = new Map<string, () => Promise<typeof BaseWidget>>([
|
||||||
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,
|
|
||||||
...EEWDSWidgets,
|
...EEWDSWidgets,
|
||||||
...EEWidgets,
|
...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 { EVAL_WORKER_ACTIONS } from "ee/workers/Evaluation/evalWorkerActions";
|
||||||
import { runSaga } from "redux-saga";
|
import { runSaga } from "redux-saga";
|
||||||
import { TriggerKind } from "constants/AppsmithActionConstants/ActionConstants";
|
import { TriggerKind } from "constants/AppsmithActionConstants/ActionConstants";
|
||||||
|
import { registerAllWidgets } from "utils/editor/EditorUtils";
|
||||||
|
|
||||||
export async function UNSTABLE_executeDynamicTrigger(dynamicTrigger: string) {
|
export async function UNSTABLE_executeDynamicTrigger(dynamicTrigger: string) {
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
|
|
||||||
|
await registerAllWidgets();
|
||||||
|
|
||||||
const unEvalTree = getUnevaluatedDataTree(state);
|
const unEvalTree = getUnevaluatedDataTree(state);
|
||||||
|
|
||||||
const result = runSaga(
|
const result = runSaga(
|
||||||
|
|
|
||||||
|
|
@ -290,75 +290,64 @@ export async function loadLibraries(
|
||||||
const libStore: Record<string, unknown> = {};
|
const libStore: Record<string, unknown> = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const lib of libs) {
|
await Promise.all(
|
||||||
const url = lib.url as string;
|
libs.map(async (lib) => {
|
||||||
const accessors = lib.accessor;
|
const url = lib.url as string;
|
||||||
const keysBefore = Object.keys(self);
|
const accessors = lib.accessor;
|
||||||
let module = null;
|
const keysBefore = Object.keys(self);
|
||||||
|
let module = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
self.importScripts(url);
|
self.importScripts(url);
|
||||||
const keysAfter = Object.keys(self);
|
const keysAfter = Object.keys(self);
|
||||||
let defaultAccessors = difference(keysAfter, keysBefore);
|
let defaultAccessors = difference(keysAfter, keysBefore);
|
||||||
|
|
||||||
// Changing default export to library accessors name which was saved when it was installed, if default export present
|
movetheDefaultExportedLibraryToAccessorKey(
|
||||||
movetheDefaultExportedLibraryToAccessorKey(
|
defaultAccessors,
|
||||||
defaultAccessors,
|
accessors[0],
|
||||||
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);
|
||||||
|
|
||||||
/**
|
defaultAccessors.push(
|
||||||
* Installing 2 different version of lodash tries to add the same accessor on the self object. Let take version a & b for example.
|
...Object.keys(libStore).filter((k) => libStore[k] !== self[k]),
|
||||||
* 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]),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
accessors.sort();
|
||||||
* Sort the accessor list from backend and installed accessor list using the same rule to apply all modifications.
|
defaultAccessors.sort();
|
||||||
* This is required only for UMD builds, since we always generate unique names for ESM.
|
|
||||||
*/
|
|
||||||
accessors.sort();
|
|
||||||
defaultAccessors.sort();
|
|
||||||
|
|
||||||
for (let i = 0; i < defaultAccessors.length; i++) {
|
for (let i = 0; i < defaultAccessors.length; i++) {
|
||||||
self[accessors[i]] = self[defaultAccessors[i]];
|
self[accessors[i]] = self[defaultAccessors[i]];
|
||||||
libStore[defaultAccessors[i]] = self[defaultAccessors[i]];
|
libStore[defaultAccessors[i]] = self[defaultAccessors[i]];
|
||||||
libraryReservedIdentifiers[accessors[i]] = true;
|
libraryReservedIdentifiers[accessors[i]] = true;
|
||||||
invalidEntityIdentifiers[accessors[i]] = true;
|
invalidEntityIdentifiers[accessors[i]] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
log.debug(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
try {
|
||||||
} catch (e) {
|
module = await import(/* webpackIgnore: true */ url);
|
||||||
log.debug(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
if (!module || typeof module !== "object") throw "Not an ESM module";
|
||||||
module = await import(/* webpackIgnore: true */ url);
|
|
||||||
|
|
||||||
if (!module || typeof module !== "object") throw "Not an ESM module";
|
const key = accessors[0];
|
||||||
|
const flattenedModule = flattenModule(module);
|
||||||
|
|
||||||
const key = accessors[0];
|
libStore[key] = flattenedModule;
|
||||||
const flattenedModule = flattenModule(module);
|
self[key] = flattenedModule;
|
||||||
|
libraryReservedIdentifiers[key] = true;
|
||||||
libStore[key] = flattenedModule;
|
invalidEntityIdentifiers[key] = true;
|
||||||
self[key] = flattenedModule;
|
} catch (e) {
|
||||||
libraryReservedIdentifiers[key] = true;
|
log.debug(e);
|
||||||
invalidEntityIdentifiers[key] = true;
|
throw new ImportError(url);
|
||||||
} catch (e) {
|
}
|
||||||
log.debug(e);
|
}),
|
||||||
throw new ImportError(url);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JSLibraries.push(...libs);
|
JSLibraries.push(...libs);
|
||||||
JSLibraryAccessor.regenerateSet();
|
JSLibraryAccessor.regenerateSet();
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import {
|
||||||
import { updateDependencyMap } from "workers/common/DependencyMap";
|
import { updateDependencyMap } from "workers/common/DependencyMap";
|
||||||
import { replaceThisDotParams } from "./utils";
|
import { replaceThisDotParams } from "./utils";
|
||||||
import { isDataField } from "./utils";
|
import { isDataField } from "./utils";
|
||||||
import widgets from "widgets";
|
import { loadAllWidgets } from "widgets";
|
||||||
import type { WidgetConfiguration } from "WidgetProvider/types";
|
import type { WidgetConfiguration } from "WidgetProvider/types";
|
||||||
import { type WidgetEntity } from "ee/entities/DataTree/types";
|
import { type WidgetEntity } from "ee/entities/DataTree/types";
|
||||||
import {
|
import {
|
||||||
|
|
@ -35,14 +35,18 @@ const widgetConfigMap: Record<
|
||||||
}
|
}
|
||||||
> = {};
|
> = {};
|
||||||
|
|
||||||
widgets.map((widget) => {
|
beforeAll(async () => {
|
||||||
if (widget.type) {
|
const loadedWidgets = await loadAllWidgets();
|
||||||
widgetConfigMap[widget.type] = {
|
|
||||||
defaultProperties: widget.getDefaultPropertiesMap(),
|
loadedWidgets.forEach((widget) => {
|
||||||
derivedProperties: widget.getDerivedPropertiesMap(),
|
if (widget.type) {
|
||||||
metaProperties: widget.getMetaPropertiesMap(),
|
widgetConfigMap[widget.type] = {
|
||||||
};
|
defaultProperties: widget.getDefaultPropertiesMap(),
|
||||||
}
|
derivedProperties: widget.getDerivedPropertiesMap(),
|
||||||
|
metaProperties: widget.getMetaPropertiesMap(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock("ee/workers/Evaluation/generateOverrideContext"); // mock the generateOverrideContext function
|
jest.mock("ee/workers/Evaluation/generateOverrideContext"); // mock the generateOverrideContext function
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user