chore: remove xml parser v3 as a default library (#28012)

## Description
Contains the changes to remove fast-xml-parserV3.17.5 as a default
library and migrate all existing apps to install it as a custom JS
library on page load. Installations no longer fail when there is a
naming collision, we determine a unique accessor that can work inside
the application both for UMD & ESM builds.

#### PR fixes following issue(s)
Fixes https://github.com/appsmithorg/appsmith-ee/issues/2562
Fixes https://github.com/appsmithorg/appsmith-ee/issues/2563
Fixes https://github.com/appsmithorg/appsmith-ee/issues/2073
Fixes https://github.com/appsmithorg/appsmith-ee/issues/2403

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

## Testing
>
#### How Has This Been Tested?
- [x] Manual
- [x] JUnit
- [x] Jest
- [x] Cypress
>
>
#### Test Plan
https://github.com/appsmithorg/TestSmith/issues/2536
Scenarios for existing apps will be tested post-merge since DP's are
created with fresh DB that don't have release data
>
>
#### Issues raised during DP testing

https://github.com/appsmithorg/appsmith/pull/28012#issuecomment-1767711382
response:
https://github.com/appsmithorg/appsmith/pull/28012#issuecomment-1767781029
>
>
>
## Checklist:
#### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


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

---------

Co-authored-by: manish kumar <manish@appsmith.com>
Co-authored-by: Manish Kumar <107841575+sondermanish@users.noreply.github.com>
This commit is contained in:
arunvjn 2023-10-20 11:08:47 +05:30 committed by GitHub
parent 17eae14dfc
commit af9e89d2a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 748 additions and 333 deletions

View File

@ -86,15 +86,6 @@ describe("Autocomplete bug fixes", function () {
); );
}); });
it("6. feat #16426 Autocomplete for fast-xml-parser", function () {
entityExplorer.SelectEntityByName("Text1");
propPane.TypeTextIntoField("Text", "{{xmlParser.j");
agHelper.GetNAssertElementText(locators._hints, "j2xParser");
propPane.TypeTextIntoField("Text", "{{new xmlParser.j2xParser().p");
agHelper.GetNAssertElementText(locators._hints, "parse");
});
it( it(
"excludeForAirgap", "excludeForAirgap",
"7. Installed library should show up in autocomplete", "7. Installed library should show up in autocomplete",

View File

@ -3,9 +3,21 @@ import * as _ from "../../../../support/Objects/ObjectsCore";
describe("xml2json text", function () { describe("xml2json text", function () {
before(() => { before(() => {
_.agHelper.AddDsl("xmlParser"); _.homePage.NavigateToHome();
_.homePage.ImportApp("xmlParser.json");
_.homePage.AssertImportToast();
}); });
it("1. Publish widget and validate the data displayed in text widget from xmlParser function", function () {
it("1. Check if XMLparser v3 autocomplete works", function () {
_.entityExplorer.SelectEntityByName("Text2", "Widgets");
_.propPane.TypeTextIntoField("Text", "{{xmlParser.j", true);
_.agHelper.GetNAssertElementText(_.locators._hints, "j2xParser");
_.propPane.TypeTextIntoField("Text", "{{new xmlParser.j2xParser().p", true);
_.agHelper.GetNAssertElementText(_.locators._hints, "parse");
});
it("2. Publish widget and validate the data displayed in text widget from xmlParser function", function () {
_.deployMode.DeployApp(); _.deployMode.DeployApp();
cy.get(publish.textWidget) cy.get(publish.textWidget)
.first() .first()

View File

@ -53,11 +53,13 @@ describe("Import and validate older app (app created in older versions of Appsmi
gitSync._gitStatusChanges, gitSync._gitStatusChanges,
/[0-9] page(|s) modified/, /[0-9] page(|s) modified/,
); );
agHelper.GetNAssertElementText(
gitSync._gitStatusChanges, // Commenting it as part of #28012 - to be added back later
"Application settings modified", // agHelper.GetNAssertElementText(
"not.contain.text", // gitSync._gitStatusChanges,
); // "Application settings modified",
// "not.contain.text",
// );
agHelper.GetNAssertElementText( agHelper.GetNAssertElementText(
gitSync._gitStatusChanges, gitSync._gitStatusChanges,
"Theme modified", "Theme modified",
@ -73,7 +75,10 @@ describe("Import and validate older app (app created in older versions of Appsmi
// ); // );
agHelper.AssertContains(/[0-9] JS Object(|s) modified/, "not.exist"); agHelper.AssertContains(/[0-9] JS Object(|s) modified/, "not.exist");
agHelper.AssertContains(/[0-9] librar(y|ies) modified/, "not.exist");
// Commenting it as part of #28012 - to be added back later
// agHelper.AssertContains(/[0-9] librar(y|ies) modified/, "not.exist");
agHelper.GetNAssertElementText( agHelper.GetNAssertElementText(
gitSync._gitStatusChanges, gitSync._gitStatusChanges,
"Some of the changes above are due to an improved file structure designed to reduce merge conflicts. You can safely commit them to your repository.", "Some of the changes above are due to an improved file structure designed to reduce merge conflicts. You can safely commit them to your repository.",

View File

@ -10,14 +10,13 @@ describe("excludeForAirgap", "Tests JS Libraries", () => {
_.installer.assertUnInstall("uuidjs"); _.installer.assertUnInstall("uuidjs");
}); });
it("2. Checks for naming collision", () => { it("2. Installs the library against a unique namespace when there is a collision with the existing entity", () => {
_.entityExplorer.DragDropWidgetNVerify(_.draggableWidgets.TABLE, 200, 200); _.entityExplorer.DragDropWidgetNVerify(_.draggableWidgets.TABLE, 200, 200);
_.entityExplorer.NavigateToSwitcher("Explorer"); _.entityExplorer.NavigateToSwitcher("Explorer");
_.entityExplorer.RenameEntityFromExplorer("Table1", "jsonwebtoken"); _.entityExplorer.RenameEntityFromExplorer("Table1", "jsonwebtoken");
_.entityExplorer.ExpandCollapseEntity("Libraries"); _.entityExplorer.ExpandCollapseEntity("Libraries");
_.installer.OpenInstaller(); _.installer.OpenInstaller();
_.installer.installLibrary("jsonwebtoken", "jsonwebtoken", false); _.installer.installLibrary("jsonwebtoken", "jsonwebtoken_1", true);
_.agHelper.AssertContains("Name collision detected: jsonwebtoken");
}); });
it("3. Checks jspdf library", () => { it("3. Checks jspdf library", () => {

View File

@ -1,64 +1,342 @@
{ {
"dsl": { "clientSchemaVersion": 1.0,
"widgetName": "MainContainer", "serverSchemaVersion": 6.0,
"backgroundColor": "none", "exportedApplication": {
"rightColumn": 1224, "name": "xml_paser_test",
"snapColumns": 16, "isPublic": false,
"detachFromLayout": true, "pages": [
"widgetId": "0",
"topRow": 0,
"bottomRow": 1280,
"containerStyle": "none",
"snapRows": 33,
"parentRowSpace": 1,
"type": "CANVAS_WIDGET",
"canExtend": true,
"version": 7,
"minHeight": 1292,
"parentColumnSpace": 1,
"leftColumn": 0,
"dynamicBindingPathList": [],
"children": [
{ {
"isVisible": true, "id": "Page1",
"text": "{{xmlParser.parse(Input1.text) }}", "isDefault": true
"textStyle": "LABEL",
"textAlign": "LEFT",
"widgetName": "Text1",
"type": "TEXT_WIDGET",
"isLoading": false,
"parentColumnSpace": 74,
"parentRowSpace": 40,
"leftColumn": 3,
"rightColumn": 8,
"topRow": 3,
"bottomRow": 10,
"parentId": "0",
"widgetId": "axlcnmjk4t",
"dynamicBindingPathList": [
{
"key": "text"
}
]
},
{
"isVisible": true,
"inputType": "TEXT",
"label": "",
"widgetName": "Input1",
"type": "INPUT_WIDGET_V2",
"isLoading": false,
"parentColumnSpace": 74,
"parentRowSpace": 40,
"leftColumn": 3,
"rightColumn": 8,
"topRow": 2,
"bottomRow": 3,
"parentId": "0",
"widgetId": "8n5urob9mz",
"dynamicBindingPathList": [],
"defaultText": "<note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend!</body> </note>"
} }
] ],
"publishedPages": [
{
"id": "Page1",
"isDefault": true
}
],
"viewMode": false,
"appIsExample": false,
"unreadCommentThreads": 0.0,
"color": "#EAEDFB",
"icon": "yen",
"slug": "xml-paser-test",
"unpublishedCustomJSLibs": [],
"publishedCustomJSLibs": [],
"evaluationVersion": 2.0,
"applicationVersion": 2.0,
"collapseInvisibleWidgets": true,
"isManualUpdate": false,
"deleted": false
},
"datasourceList": [],
"customJSLibList": [],
"pageList": [
{
"unpublishedPage": {
"name": "Page1",
"slug": "page1",
"layouts": [
{
"viewMode": false,
"dsl": {
"widgetName": "MainContainer",
"backgroundColor": "none",
"rightColumn": 4896.0,
"snapColumns": 64.0,
"detachFromLayout": true,
"widgetId": "0",
"topRow": 0.0,
"bottomRow": 380.0,
"containerStyle": "none",
"snapRows": 124.0,
"parentRowSpace": 1.0,
"type": "CANVAS_WIDGET",
"canExtend": true,
"version": 87.0,
"minHeight": 1292.0,
"dynamicTriggerPathList": [],
"parentColumnSpace": 1.0,
"dynamicBindingPathList": [],
"leftColumn": 0.0,
"children": [
{
"mobileBottomRow": 14.0,
"widgetName": "Text1",
"displayName": "Text",
"iconSVG": "https://release-appcdn.appsmith.com/static/media/icon.a47d6d5dbbb718c4dc4b2eb4f218c1b7.svg",
"searchTags": [
"typography",
"paragraph",
"label"
],
"topRow": 0.0,
"bottomRow": 16.0,
"parentRowSpace": 10.0,
"type": "TEXT_WIDGET",
"hideCard": false,
"mobileRightColumn": 23.0,
"animateLoading": true,
"overflow": "NONE",
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"parentColumnSpace": 14.953125,
"dynamicTriggerPathList": [],
"leftColumn": 0.0,
"dynamicBindingPathList": [
{
"key": "truncateButtonColor"
},
{
"key": "fontFamily"
},
{
"key": "borderRadius"
},
{
"key": "text"
}
],
"shouldTruncate": false,
"truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}",
"text": "{{xmlParser.parse(Input1.text)}}",
"key": "bdg0tfrf6x",
"isDeprecated": false,
"rightColumn": 13.0,
"textAlign": "LEFT",
"dynamicHeight": "AUTO_HEIGHT",
"widgetId": "b8blvt9b2z",
"minWidth": 450.0,
"isVisible": true,
"fontStyle": "BOLD",
"textColor": "#231F20",
"version": 1.0,
"parentId": "0",
"tags": [
"Suggested",
"Content"
],
"renderMode": "CANVAS",
"isLoading": false,
"mobileTopRow": 10.0,
"responsiveBehavior": "fill",
"originalTopRow": 0.0,
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"mobileLeftColumn": 7.0,
"maxDynamicHeight": 9000.0,
"originalBottomRow": 4.0,
"fontSize": "1rem",
"minDynamicHeight": 4.0
},
{
"boxShadow": "none",
"iconSVG": "https://release-appcdn.appsmith.com/static/media/icon.f2c34197dbcf03595098986de898928f.svg",
"topRow": 17.0,
"labelWidth": 5.0,
"type": "INPUT_WIDGET_V2",
"animateLoading": true,
"resetOnSubmit": true,
"leftColumn": 0.0,
"dynamicBindingPathList": [
{
"key": "accentColor"
},
{
"key": "borderRadius"
}
],
"labelStyle": "",
"inputType": "TEXT",
"isDisabled": false,
"isRequired": false,
"dynamicHeight": "FIXED",
"accentColor": "{{appsmith.theme.colors.primaryColor}}",
"showStepArrows": false,
"isVisible": true,
"version": 2.0,
"tags": [
"Suggested",
"Inputs"
],
"isLoading": false,
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"originalBottomRow": 21.0,
"mobileBottomRow": 21.0,
"widgetName": "Input1",
"displayName": "Input",
"searchTags": [
"form",
"text input",
"number",
"textarea"
],
"bottomRow": 24.0,
"parentRowSpace": 10.0,
"autoFocus": false,
"hideCard": false,
"mobileRightColumn": 19.0,
"parentColumnSpace": 14.34375,
"dynamicTriggerPathList": [],
"labelPosition": "Top",
"key": "mx0emg6xlv",
"labelTextSize": "0.875rem",
"isDeprecated": false,
"rightColumn": 19.0,
"widgetId": "ni16fc0xys",
"minWidth": 450.0,
"label": "Label",
"parentId": "0",
"labelAlignment": "left",
"renderMode": "CANVAS",
"mobileTopRow": 14.0,
"responsiveBehavior": "fill",
"originalTopRow": 14.0,
"mobileLeftColumn": 0.0,
"maxDynamicHeight": 9000.0,
"iconAlign": "left",
"defaultText": "<note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend!</body> </note>",
"minDynamicHeight": 4.0
},
{
"mobileBottomRow": 35.0,
"widgetName": "Text2",
"displayName": "Text",
"iconSVG": "https://release-appcdn.appsmith.com/static/media/icon.a47d6d5dbbb718c4dc4b2eb4f218c1b7.svg",
"searchTags": [
"typography",
"paragraph",
"label"
],
"topRow": 31.0,
"bottomRow": 35.0,
"parentRowSpace": 10.0,
"type": "TEXT_WIDGET",
"hideCard": false,
"mobileRightColumn": 16.0,
"animateLoading": true,
"overflow": "NONE",
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"parentColumnSpace": 14.34375,
"leftColumn": 0.0,
"dynamicBindingPathList": [
{
"key": "truncateButtonColor"
},
{
"key": "fontFamily"
},
{
"key": "borderRadius"
},
{
"key": "text"
}
],
"shouldTruncate": false,
"truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}",
"text": "Hello {{appsmith.user.name || appsmith.user.email}}",
"key": "bdg0tfrf6x",
"isDeprecated": false,
"rightColumn": 20.0,
"textAlign": "LEFT",
"dynamicHeight": "AUTO_HEIGHT",
"widgetId": "3m7c3st35v",
"minWidth": 450.0,
"isVisible": true,
"fontStyle": "BOLD",
"textColor": "#231F20",
"version": 1.0,
"parentId": "0",
"tags": [
"Suggested",
"Content"
],
"renderMode": "CANVAS",
"isLoading": false,
"mobileTopRow": 31.0,
"responsiveBehavior": "fill",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"mobileLeftColumn": 0.0,
"maxDynamicHeight": 9000.0,
"fontSize": "1rem",
"minDynamicHeight": 4.0
}
]
},
"layoutOnLoadActions": [],
"layoutOnLoadActionErrors": [],
"validOnPageLoadActions": true,
"id": "Page1",
"deleted": false,
"policies": [],
"userPermissions": []
}
],
"userPermissions": [],
"policies": []
},
"publishedPage": {
"name": "Page1",
"slug": "page1",
"layouts": [
{
"viewMode": false,
"dsl": {
"widgetName": "MainContainer",
"backgroundColor": "none",
"rightColumn": 1224.0,
"snapColumns": 16.0,
"detachFromLayout": true,
"widgetId": "0",
"topRow": 0.0,
"bottomRow": 1250.0,
"containerStyle": "none",
"snapRows": 33.0,
"parentRowSpace": 1.0,
"type": "CANVAS_WIDGET",
"canExtend": true,
"version": 4.0,
"minHeight": 1292.0,
"dynamicTriggerPathList": [],
"parentColumnSpace": 1.0,
"dynamicBindingPathList": [],
"leftColumn": 0.0,
"children": []
},
"validOnPageLoadActions": true,
"id": "Page1",
"deleted": false,
"policies": [],
"userPermissions": []
}
],
"userPermissions": [],
"policies": []
},
"deleted": false,
"gitSyncId": "6529174c97a7581320fb6a4f_6529174c97a7581320fb6a51"
}
],
"actionList": [],
"actionCollectionList": [],
"updatedResources": {
"customJSLibList": [],
"actionList": [],
"pageList": [
"Page1"
],
"actionCollectionList": []
},
"editModeTheme": {
"name": "Default-New",
"displayName": "Modern",
"isSystemTheme": true,
"deleted": false
},
"publishedTheme": {
"name": "Default-New",
"displayName": "Modern",
"isSystemTheme": true,
"deleted": false
} }
} }

View File

@ -104,7 +104,6 @@
"echarts-gl": "^2.0.9", "echarts-gl": "^2.0.9",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"fast-sort": "^3.4.0", "fast-sort": "^3.4.0",
"fast-xml-parser": "^3.17.5",
"fastdom": "^1.0.11", "fastdom": "^1.0.11",
"focus-trap-react": "^8.9.2", "focus-trap-react": "^8.9.2",
"fuse.js": "^3.4.5", "fuse.js": "^3.4.5",

View File

@ -1,5 +1,5 @@
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import type { TJSLibrary } from "workers/common/JSLibrary"; import type { JSLibrary } from "workers/common/JSLibrary";
export function fetchJSLibraries(applicationId: string) { export function fetchJSLibraries(applicationId: string) {
return { return {
@ -8,7 +8,7 @@ export function fetchJSLibraries(applicationId: string) {
}; };
} }
export function installLibraryInit(payload: Partial<TJSLibrary>) { export function installLibraryInit(payload: Partial<JSLibrary>) {
return { return {
type: ReduxActionTypes.INSTALL_LIBRARY_INIT, type: ReduxActionTypes.INSTALL_LIBRARY_INIT,
payload, payload,
@ -22,7 +22,7 @@ export function toggleInstaller(payload: boolean) {
}; };
} }
export function uninstallLibraryInit(payload: TJSLibrary) { export function uninstallLibraryInit(payload: JSLibrary) {
return { return {
type: ReduxActionTypes.UNINSTALL_LIBRARY_INIT, type: ReduxActionTypes.UNINSTALL_LIBRARY_INIT,
payload, payload,

View File

@ -1,5 +1,5 @@
import { APP_MODE } from "entities/App"; import { APP_MODE } from "entities/App";
import type { TJSLibrary } from "workers/common/JSLibrary"; import type { JSLibrary } from "workers/common/JSLibrary";
import Api from "./Api"; import Api from "./Api";
export default class LibraryApi extends Api { export default class LibraryApi extends Api {
@ -10,7 +10,7 @@ export default class LibraryApi extends Api {
static async addLibrary( static async addLibrary(
applicationId: string, applicationId: string,
library: Partial<TJSLibrary> & { defs: string }, library: Partial<JSLibrary> & { defs: string },
) { ) {
const url = LibraryApi.getUpdateLibraryBaseURL(applicationId) + "/add"; const url = LibraryApi.getUpdateLibraryBaseURL(applicationId) + "/add";
return Api.patch(url, library); return Api.patch(url, library);
@ -18,7 +18,7 @@ export default class LibraryApi extends Api {
static async removeLibrary( static async removeLibrary(
applicationId: string, applicationId: string,
library: Partial<TJSLibrary>, library: Partial<JSLibrary>,
) { ) {
const url = LibraryApi.getUpdateLibraryBaseURL(applicationId) + "/remove"; const url = LibraryApi.getUpdateLibraryBaseURL(applicationId) + "/remove";
return Api.patch(url, library); return Api.patch(url, library);

View File

@ -39,7 +39,7 @@ import {
import { InstallState } from "reducers/uiReducers/libraryReducer"; import { InstallState } from "reducers/uiReducers/libraryReducer";
import recommendedLibraries from "pages/Editor/Explorer/Libraries/recommendedLibraries"; import recommendedLibraries from "pages/Editor/Explorer/Libraries/recommendedLibraries";
import type { TJSLibrary } from "workers/common/JSLibrary"; import type { JSLibrary } from "workers/common/JSLibrary";
import { getEntityNameAndPropertyPath } from "@appsmith/workers/Evaluation/evaluationUtils"; import { getEntityNameAndPropertyPath } from "@appsmith/workers/Evaluation/evaluationUtils";
import { getFormValues } from "redux-form"; import { getFormValues } from "redux-form";
import { TEMP_DATASOURCE_ID } from "constants/Datasource"; import { TEMP_DATASOURCE_ID } from "constants/Datasource";
@ -1099,7 +1099,7 @@ export const selectLibrariesForExplorer = createSelector(
version: recommendedLibrary?.version || "", version: recommendedLibrary?.version || "",
url: recommendedLibrary?.url || url, url: recommendedLibrary?.url || url,
accessor: [], accessor: [],
} as TJSLibrary; } as JSLibrary;
}); });
return [...queuedInstalls, ...libs]; return [...queuedInstalls, ...libs];
}, },

View File

@ -1,39 +0,0 @@
{
"!name": "LIB/xmlParser",
"xmlParser": {
"parse": {
"!doc": "converts xml string to json object",
"!type": "fn(xml: string, options?: object, validationOption?: object) -> object"
},
"validate": {
"!doc": "validate xml data",
"!type": "fn(xml: string) -> bool"
},
"convertToJson": {
"!type": "fn(node: ?, options: object) -> ?"
},
"convertToJsonString": {
"!type": "fn(node: ?, options: object) -> string"
},
"convertTonimn": {
"!type": "fn(node: ?, e_schema: ?, options: object) -> ?"
},
"getTraversalObj": {
"!type": "fn(xmlData: ?, options: object) -> ?"
},
"j2xParser": {
"!type": "fn(options: object) -> object",
"prototype": {
"parse": {
"!type": "fn(jObj: ?)"
},
"j2x": {
"!type": "fn(jObj: ?, level: ?)"
}
}
},
"parseToNimn": {
"!type": "fn(xmlData: ?, schema: ?, options: ?) -> ?"
}
}
}

View File

@ -36,7 +36,7 @@ import recommendedLibraries from "pages/Editor/Explorer/Libraries/recommendedLib
import type { AppState } from "@appsmith/reducers"; import type { AppState } from "@appsmith/reducers";
import { installLibraryInit } from "actions/JSLibraryActions"; import { installLibraryInit } from "actions/JSLibraryActions";
import classNames from "classnames"; import classNames from "classnames";
import type { TJSLibrary } from "workers/common/JSLibrary"; import type { JSLibrary } from "workers/common/JSLibrary";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import { EntityClassNames } from "pages/Editor/Explorer/Entity"; import { EntityClassNames } from "pages/Editor/Explorer/Entity";
@ -317,7 +317,7 @@ export function Installer() {
}, [URL, isValid]); }, [URL, isValid]);
const installLibrary = useCallback( const installLibrary = useCallback(
(lib?: Partial<TJSLibrary>) => { (lib?: Partial<JSLibrary>) => {
const url = lib?.url || URL; const url = lib?.url || URL;
const isQueued = queuedLibraries.find((libURL) => libURL === url); const isQueued = queuedLibraries.find((libURL) => libURL === url);
if (isQueued) return; if (isQueued) return;

View File

@ -33,7 +33,7 @@ import {
uninstallLibraryInit, uninstallLibraryInit,
} from "actions/JSLibraryActions"; } from "actions/JSLibraryActions";
import EntityAddButton from "../Entity/AddButton"; import EntityAddButton from "../Entity/AddButton";
import type { TJSLibrary } from "workers/common/JSLibrary"; import type { JSLibrary } from "workers/common/JSLibrary";
import { import {
getCurrentPageId, getCurrentPageId,
getPagePermissions, getPagePermissions,
@ -177,7 +177,7 @@ const Version = styled.div<{ version?: string }>`
margin: ${(props) => (props.version ? "0 8px" : "0")}; margin: ${(props) => (props.version ? "0 8px" : "0")};
`; `;
const PrimaryCTA = function ({ lib }: { lib: TJSLibrary }) { const PrimaryCTA = function ({ lib }: { lib: JSLibrary }) {
const installationStatus = useSelector(selectInstallationStatus); const installationStatus = useSelector(selectInstallationStatus);
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -215,7 +215,7 @@ const PrimaryCTA = function ({ lib }: { lib: TJSLibrary }) {
return null; return null;
}; };
function LibraryEntity({ lib }: { lib: TJSLibrary }) { function LibraryEntity({ lib }: { lib: JSLibrary }) {
const openDocs = useCallback( const openDocs = useCallback(
(url?: string) => (e: React.MouseEvent) => { (url?: string) => (e: React.MouseEvent) => {
e?.stopPropagation(); e?.stopPropagation();

View File

@ -5,9 +5,19 @@ export default [
author: "auth0", author: "auth0",
docsURL: "https://github.com/auth0/node-jsonwebtoken#readme", docsURL: "https://github.com/auth0/node-jsonwebtoken#readme",
version: "8.5.1", version: "8.5.1",
url: `/libraries/jsonwebtoken@8.5.1.js`, url: "/libraries/jsonwebtoken@8.5.1.js",
icon: "https://github.com/auth0.png?s=20", icon: "https://github.com/auth0.png?s=20",
}, },
{
name: "fast-xml-parser",
description:
"Validate XML, Parse XML to JS Object, or Build XML from JS Object without C/C++ based libraries and no callback.",
author: "NaturalIntelligence",
docsURL: "https://github.com/NaturalIntelligence/fast-xml-parser",
url: "https://cdnjs.cloudflare.com/ajax/libs/fast-xml-parser/4.3.2/fxparser.min.js",
version: "4.3.2",
icon: "https://img.jsdelivr.com/github.com/NaturalIntelligence.png",
},
{ {
name: "jspdf", name: "jspdf",
description: "PDF Document creation from JavaScript", description: "PDF Document creation from JavaScript",

View File

@ -10,7 +10,7 @@ import type {
} from "workers/Evaluation/evaluate"; } from "workers/Evaluation/evaluate";
import type { DependencyMap } from "utils/DynamicBindingUtils"; import type { DependencyMap } from "utils/DynamicBindingUtils";
import type { TJSPropertiesState } from "workers/Evaluation/JSObject/jsPropertiesState"; import type { TJSPropertiesState } from "workers/Evaluation/JSObject/jsPropertiesState";
import type { TJSLibrary } from "workers/common/JSLibrary"; import type { JSLibrary } from "workers/common/JSLibrary";
export enum LINT_WORKER_ACTIONS { export enum LINT_WORKER_ACTIONS {
LINT_TREE = "LINT_TREE", LINT_TREE = "LINT_TREE",
@ -78,5 +78,5 @@ export interface getLintErrorsFromTreeResponse {
export interface updateJSLibraryProps { export interface updateJSLibraryProps {
add?: boolean; add?: boolean;
libs: TJSLibrary[]; libs: JSLibrary[];
} }

View File

@ -5,7 +5,7 @@ import {
ReduxActionTypes, ReduxActionTypes,
} from "@appsmith/constants/ReduxActionConstants"; } from "@appsmith/constants/ReduxActionConstants";
import recommendedLibraries from "pages/Editor/Explorer/Libraries/recommendedLibraries"; import recommendedLibraries from "pages/Editor/Explorer/Libraries/recommendedLibraries";
import type { TJSLibrary } from "workers/common/JSLibrary"; import type { JSLibrary } from "workers/common/JSLibrary";
import { defaultLibraries } from "workers/common/JSLibrary"; import { defaultLibraries } from "workers/common/JSLibrary";
export enum InstallState { export enum InstallState {
@ -17,14 +17,14 @@ export enum InstallState {
export interface LibraryState { export interface LibraryState {
installationStatus: Record<string, InstallState>; installationStatus: Record<string, InstallState>;
installedLibraries: TJSLibrary[]; installedLibraries: JSLibrary[];
isInstallerOpen: boolean; isInstallerOpen: boolean;
} }
const initialState = { const initialState = {
isInstallerOpen: false, isInstallerOpen: false,
installationStatus: {}, installationStatus: {},
installedLibraries: defaultLibraries.map((lib: TJSLibrary) => { installedLibraries: defaultLibraries.map((lib: JSLibrary) => {
return { return {
name: lib.name, name: lib.name,
docsURL: lib.docsURL, docsURL: lib.docsURL,
@ -38,7 +38,7 @@ const initialState = {
const jsLibraryReducer = createImmerReducer(initialState, { const jsLibraryReducer = createImmerReducer(initialState, {
[ReduxActionTypes.INSTALL_LIBRARY_INIT]: ( [ReduxActionTypes.INSTALL_LIBRARY_INIT]: (
state: LibraryState, state: LibraryState,
action: ReduxAction<Partial<TJSLibrary>>, action: ReduxAction<Partial<JSLibrary>>,
) => { ) => {
const { url } = action.payload; const { url } = action.payload;
state.installationStatus[url as string] = state.installationStatus[url as string] =
@ -91,7 +91,7 @@ const jsLibraryReducer = createImmerReducer(initialState, {
}, },
[ReduxActionTypes.FETCH_JS_LIBRARIES_SUCCESS]: ( [ReduxActionTypes.FETCH_JS_LIBRARIES_SUCCESS]: (
state: LibraryState, state: LibraryState,
action: ReduxAction<TJSLibrary[]>, action: ReduxAction<JSLibrary[]>,
) => { ) => {
state.installedLibraries = action.payload.concat( state.installedLibraries = action.payload.concat(
initialState.installedLibraries, initialState.installedLibraries,
@ -99,7 +99,7 @@ const jsLibraryReducer = createImmerReducer(initialState, {
}, },
[ReduxActionTypes.UNINSTALL_LIBRARY_SUCCESS]: ( [ReduxActionTypes.UNINSTALL_LIBRARY_SUCCESS]: (
state: LibraryState, state: LibraryState,
action: ReduxAction<TJSLibrary>, action: ReduxAction<JSLibrary>,
) => { ) => {
const uLib = action.payload; const uLib = action.payload;
state.installedLibraries = state.installedLibraries.filter( state.installedLibraries = state.installedLibraries.filter(

View File

@ -29,7 +29,7 @@ import log from "loglevel";
import { APP_MODE } from "entities/App"; import { APP_MODE } from "entities/App";
import { getAppMode } from "@appsmith/selectors/applicationSelectors"; import { getAppMode } from "@appsmith/selectors/applicationSelectors";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import type { TJSLibrary } from "workers/common/JSLibrary"; import type { JSLibrary } from "workers/common/JSLibrary";
import { getUsedActionNames } from "selectors/actionSelectors"; import { getUsedActionNames } from "selectors/actionSelectors";
import AppsmithConsole from "utils/AppsmithConsole"; import AppsmithConsole from "utils/AppsmithConsole";
import { selectInstalledLibraries } from "@appsmith/selectors/entitiesSelector"; import { selectInstalledLibraries } from "@appsmith/selectors/entitiesSelector";
@ -73,7 +73,7 @@ function* handleInstallationFailure(
log.error(message); log.error(message);
} }
export function* installLibrarySaga(lib: Partial<TJSLibrary>) { export function* installLibrarySaga(lib: Partial<JSLibrary>) {
const { url } = lib; const { url } = lib;
const takenNamesMap: Record<string, true> = yield select( const takenNamesMap: Record<string, true> = yield select(
@ -81,7 +81,7 @@ export function* installLibrarySaga(lib: Partial<TJSLibrary>) {
"", "",
); );
const installedLibraries: TJSLibrary[] = yield select( const installedLibraries: JSLibrary[] = yield select(
selectInstalledLibraries, selectInstalledLibraries,
); );
@ -232,7 +232,7 @@ export function* installLibrarySaga(lib: Partial<TJSLibrary>) {
}); });
} }
function* uninstallLibrarySaga(action: ReduxAction<TJSLibrary>) { function* uninstallLibrarySaga(action: ReduxAction<JSLibrary>) {
const { accessor, name } = action.payload; const { accessor, name } = action.payload;
const applicationId: string = yield select(getCurrentApplicationId); const applicationId: string = yield select(getCurrentApplicationId);
@ -320,7 +320,7 @@ function* fetchJSLibraries(action: ReduxAction<string>) {
const isValidResponse: boolean = yield validateResponse(response); const isValidResponse: boolean = yield validateResponse(response);
if (!isValidResponse) return; if (!isValidResponse) return;
const libraries = response.data as Array<TJSLibrary & { defs: string }>; const libraries = response.data as Array<JSLibrary & { defs: string }>;
const { message, success }: { success: boolean; message: string } = const { message, success }: { success: boolean; message: string } =
yield call( yield call(
@ -404,7 +404,7 @@ function* startInstallationRequestChannel() {
ReduxActionTypes.INSTALL_LIBRARY_INIT, ReduxActionTypes.INSTALL_LIBRARY_INIT,
]); ]);
while (true) { while (true) {
const action: ReduxAction<Partial<TJSLibrary>> = const action: ReduxAction<Partial<JSLibrary>> =
yield take(queueInstallChannel); yield take(queueInstallChannel);
yield put({ yield put({
type: ReduxActionTypes.INSTALL_LIBRARY_START, type: ReduxActionTypes.INSTALL_LIBRARY_START,

View File

@ -4,7 +4,7 @@ import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import { APP_MODE } from "entities/App"; import { APP_MODE } from "entities/App";
import { call, put, select, takeEvery } from "redux-saga/effects"; import { call, put, select, takeEvery } from "redux-saga/effects";
import { getAppMode } from "@appsmith/selectors/entitiesSelector"; import { getAppMode } from "@appsmith/selectors/entitiesSelector";
import type { TJSLibrary } from "workers/common/JSLibrary"; import type { JSLibrary } from "workers/common/JSLibrary";
import { logLatestLintPropertyErrors } from "./PostLintingSagas"; import { logLatestLintPropertyErrors } from "./PostLintingSagas";
import { getAppsmithConfigs } from "@appsmith/configs"; import { getAppsmithConfigs } from "@appsmith/configs";
import type { AppState } from "@appsmith/reducers"; import type { AppState } from "@appsmith/reducers";
@ -29,7 +29,7 @@ const APPSMITH_CONFIGS = getAppsmithConfigs();
export const lintWorker = new Linter(); export const lintWorker = new Linter();
function* updateLintGlobals( function* updateLintGlobals(
action: ReduxAction<{ add?: boolean; libs: TJSLibrary[] }>, action: ReduxAction<{ add?: boolean; libs: JSLibrary[] }>,
) { ) {
const appMode: APP_MODE = yield select(getAppMode); const appMode: APP_MODE = yield select(getAppMode);
const isEditorMode = appMode === APP_MODE.EDIT; const isEditorMode = appMode === APP_MODE.EDIT;

View File

@ -2,7 +2,7 @@ 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 type { FlattenedWidgetProps } from "WidgetProvider/constants"; import type { FlattenedWidgetProps } from "WidgetProvider/constants";
import type { TJSLibrary } from "workers/common/JSLibrary"; import type { JSLibrary } from "workers/common/JSLibrary";
import { getDataTree } from "./dataTreeSelectors"; import { getDataTree } from "./dataTreeSelectors";
import { import {
getExistingPageNames, getExistingPageNames,
@ -28,7 +28,7 @@ export const getUsedActionNames = createSelector(
pageNames: Record<string, any>, pageNames: Record<string, any>,
dataTree: DataTree, dataTree: DataTree,
parentWidget: FlattenedWidgetProps | undefined, parentWidget: FlattenedWidgetProps | undefined,
installedLibraries: TJSLibrary[], installedLibraries: JSLibrary[],
) => { ) => {
const map: Record<string, boolean> = {}; const map: Record<string, boolean> = {};
// The logic has been copied from Explorer/Entity/Name.tsx Component. // The logic has been copied from Explorer/Entity/Name.tsx Component.

View File

@ -7,14 +7,14 @@ import * as mod from "../../../common/JSLibrary/ternDefinitionGenerator";
jest.mock("../../../common/JSLibrary/ternDefinitionGenerator"); jest.mock("../../../common/JSLibrary/ternDefinitionGenerator");
declare const self: WorkerGlobalScope;
describe("Tests to assert install/uninstall flows", function () { describe("Tests to assert install/uninstall flows", function () {
beforeAll(() => { beforeAll(() => {
self.importScripts = jest.fn(() => { self.importScripts = jest.fn(() => {
//@ts-expect-error importScripts is not defined in the test environment
self.lodash = {}; self.lodash = {};
}); });
//@ts-expect-error importScripts is not defined in the test environment
self.import = jest.fn(); self.import = jest.fn();
const mockTernDefsGenerator = jest.fn(() => ({})); const mockTernDefsGenerator = jest.fn(() => ({}));
@ -49,7 +49,7 @@ describe("Tests to assert install/uninstall flows", function () {
}); });
}); });
it("Reinstalling a different version of the same installed library should fail", async function () { it("Reinstalling a different version of the same installed library should create a new accessor", async function () {
const res = await installLibrary({ const res = await installLibrary({
data: { data: {
url: "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.0/lodash.min.js", url: "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.0/lodash.min.js",
@ -59,39 +59,43 @@ describe("Tests to assert install/uninstall flows", function () {
method: EVAL_WORKER_ASYNC_ACTION.INSTALL_LIBRARY, method: EVAL_WORKER_ASYNC_ACTION.INSTALL_LIBRARY,
}); });
expect(res).toEqual({ expect(res).toEqual({
success: false, success: true,
defs: {}, defs: {
error: expect.any(Error), "!name": "LIB/lodash_1",
lodash_1: undefined,
},
accessor: ["lodash_1"],
}); });
}); });
it("Detects name space collision where there is another entity(api, widget or query) with the same name", async function () { it("Detects name space collision where there is another entity(api, widget or query) with the same name and creates a unique accessor", async function () {
//@ts-expect-error ignore delete self["lodash"];
delete self.lodash;
const res = await installLibrary({ const res = await installLibrary({
data: { data: {
url: "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.0/lodash.min.js", url: "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.0/lodash.min.js",
takenAccessors: [], takenAccessors: ["lodash_1"],
takenNamesMap: { lodash: true }, takenNamesMap: { lodash: true },
}, },
method: EVAL_WORKER_ASYNC_ACTION.INSTALL_LIBRARY, method: EVAL_WORKER_ASYNC_ACTION.INSTALL_LIBRARY,
}); });
expect(res).toEqual({ expect(res).toEqual({
success: false, success: true,
defs: {}, defs: {
error: expect.any(Error), "!name": "LIB/lodash_2",
lodash_2: undefined,
},
accessor: ["lodash_2"],
}); });
delete self["lodash_2"];
}); });
it("Removes or set the accessors to undefined on the global object on uninstallation", async function () { it("Removes or set the accessors to undefined on the global object on un-installation", async function () {
//@ts-expect-error ignore
self.lodash = {}; self.lodash = {};
const res = await uninstallLibrary({ const res = await uninstallLibrary({
data: ["lodash"], data: ["lodash"],
method: EVAL_WORKER_SYNC_ACTION.UNINSTALL_LIBRARY, method: EVAL_WORKER_SYNC_ACTION.UNINSTALL_LIBRARY,
}); });
expect(res).toEqual({ success: true }); expect(res).toEqual({ success: true });
//@ts-expect-error ignore
expect(self.lodash).toBeUndefined(); expect(self.lodash).toBeUndefined();
}); });
}); });

View File

@ -9,10 +9,20 @@ import {
JSLibraries, JSLibraries,
libraryReservedIdentifiers, libraryReservedIdentifiers,
} from "../../common/JSLibrary"; } from "../../common/JSLibrary";
import type { JSLibrary } from "../../common/JSLibrary";
import { resetJSLibraries } from "../../common/JSLibrary/resetJSLibraries"; import { resetJSLibraries } from "../../common/JSLibrary/resetJSLibraries";
import { makeTernDefs } from "../../common/JSLibrary/ternDefinitionGenerator"; import { makeTernDefs } from "../../common/JSLibrary/ternDefinitionGenerator";
import type { EvalWorkerASyncRequest, EvalWorkerSyncRequest } from "../types"; import type { EvalWorkerASyncRequest, EvalWorkerSyncRequest } from "../types";
import { dataTreeEvaluator } from "./evalTree"; import { dataTreeEvaluator } from "./evalTree";
import log from "loglevel";
declare global {
interface WorkerGlobalScope {
[x: string]: any;
}
}
declare const self: WorkerGlobalScope;
enum LibraryInstallError { enum LibraryInstallError {
NameCollisionError, NameCollisionError,
@ -21,16 +31,6 @@ enum LibraryInstallError {
LibraryOverrideError, LibraryOverrideError,
} }
class NameCollisionError extends Error {
code = LibraryInstallError.NameCollisionError;
constructor(accessors: string) {
super(
createMessage(customJSLibraryMessages.NAME_COLLISION_ERROR, accessors),
);
this.name = "NameCollisionError";
}
}
class ImportError extends Error { class ImportError extends Error {
code = LibraryInstallError.ImportError; code = LibraryInstallError.ImportError;
constructor(url: string) { constructor(url: string) {
@ -47,25 +47,13 @@ class TernDefinitionError extends Error {
} }
} }
class LibraryOverrideError extends Error {
code = LibraryInstallError.LibraryOverrideError;
data: any;
constructor(name: string, data: any) {
super(createMessage(customJSLibraryMessages.LIB_OVERRIDE_ERROR, name));
this.name = "LibraryOverrideError";
this.data = data;
}
}
const removeDataTreeFromContext = () => { const removeDataTreeFromContext = () => {
if (!dataTreeEvaluator) return {}; if (!dataTreeEvaluator) return {};
const evalTree = dataTreeEvaluator?.getEvalTree(); const evalTree = dataTreeEvaluator?.getEvalTree();
const dataTreeEntityNames = Object.keys(evalTree); const dataTreeEntityNames = Object.keys(evalTree);
const tempDataTreeStore: Record<string, any> = {}; const tempDataTreeStore: Record<string, any> = {};
for (const entityName of dataTreeEntityNames) { for (const entityName of dataTreeEntityNames) {
// @ts-expect-error: self is a global variable
tempDataTreeStore[entityName] = self[entityName]; tempDataTreeStore[entityName] = self[entityName];
// @ts-expect-error: self is a global variable
delete self[entityName]; delete self[entityName];
} }
return tempDataTreeStore; return tempDataTreeStore;
@ -76,12 +64,17 @@ function addTempStoredDataTreeToContext(
) { ) {
const dataTreeEntityNames = Object.keys(tempDataTreeStore); const dataTreeEntityNames = Object.keys(tempDataTreeStore);
for (const entityName of dataTreeEntityNames) { for (const entityName of dataTreeEntityNames) {
// @ts-expect-error: self is a global variable
self[entityName] = tempDataTreeStore[entityName]; self[entityName] = tempDataTreeStore[entityName];
} }
} }
export async function installLibrary(request: EvalWorkerASyncRequest) { export async function installLibrary(
request: EvalWorkerASyncRequest<{
url: string;
takenNamesMap: Record<string, true>;
takenAccessors: Array<string>;
}>,
) {
const { data } = request; const { data } = request;
const { takenAccessors, takenNamesMap, url } = data; const { takenAccessors, takenNamesMap, url } = data;
const defs: Def = {}; const defs: Def = {};
@ -91,73 +84,105 @@ export async function installLibrary(request: EvalWorkerASyncRequest) {
* We store the data tree in a temporary variable and add it back to the global scope after the library is imported. * We store the data tree in a temporary variable and add it back to the global scope after the library is imported.
*/ */
const tempDataTreeStore = removeDataTreeFromContext(); const tempDataTreeStore = removeDataTreeFromContext();
// Map of all the currently installed libraries
const libStore = takenAccessors.reduce(
(acc: Record<string, unknown>, a: string) => {
acc[a] = self[a];
return acc;
},
{},
);
try { try {
const currentEnvKeys = Object.keys(self); const envKeysBeforeInstallation = Object.keys(self);
//@ts-expect-error Find libraries that were uninstalled. /** Holds keys of uninstalled libraries that cannot be removed from worker global.
const unsetKeys = currentEnvKeys.filter((key) => self[key] === undefined); * Certain libraries are added to the global scope with { configurable: false }
*/
const unsetLibraryKeys = envKeysBeforeInstallation.filter(
(k) => self[k] === undefined,
);
const accessors: string[] = [];
const existingLibraries: Record<string, any> = {};
for (const acc of takenAccessors) {
existingLibraries[acc] = self[acc];
}
let module = null; let module = null;
try { try {
self.importScripts(url); self.importScripts(url);
// Find keys add that were installed to the global scope.
const keysAfterInstallation = Object.keys(self);
accessors.push(
...difference(keysAfterInstallation, envKeysBeforeInstallation),
);
// Check the list of installed library to see if their values have changed.
// This is to check if the newly installed library overwrites an already existing
accessors.push(
...Object.keys(libStore).filter((k) => {
return libStore[k] !== self[k];
}),
);
accessors.push(...unsetLibraryKeys.filter((k) => self[k] !== undefined));
for (let i = 0; i < accessors.length; i++) {
if (
takenNamesMap.hasOwnProperty(accessors[i]) ||
takenAccessors.includes(accessors[i])
) {
const uniqueName = generateUniqueAccessor(
accessors[i],
takenAccessors,
takenNamesMap,
);
self[uniqueName] = self[accessors[i]];
accessors[i] = uniqueName;
}
}
} catch (e) { } catch (e) {
log.debug(e, `importScripts failed for ${url}`);
try { try {
module = await import(/* webpackIgnore: true */ url); module = await import(/* webpackIgnore: true */ url);
if (module && typeof module === "object") {
const uniqAccessor = generateUniqueAccessor(
url,
takenAccessors,
takenNamesMap,
);
self[uniqAccessor] = flattenModule(module);
accessors.push(uniqAccessor);
}
} catch (e) { } catch (e) {
log.debug(e, `dynamic import failed for ${url}`);
throw new ImportError(url); throw new ImportError(url);
} }
} }
// Find keys add that were installed to the global scope. // If no accessors at this point, installation likely failed.
const accessors = difference(
Object.keys(self),
currentEnvKeys,
) as Array<string>;
// If no keys were added to the global scope, check if the module is a ESM module.
if (accessors.length === 0) { if (accessors.length === 0) {
if (module && typeof module === "object") { throw new Error("Unable to determine a unique accessor");
const uniqAccessor = generateUniqueAccessor(
url,
takenAccessors,
takenNamesMap,
);
// @ts-expect-error no types
self[uniqAccessor] = flattenModule(module);
accessors.push(uniqAccessor);
}
} }
addTempStoredDataTreeToContext(tempDataTreeStore);
checkForNameCollision(accessors, takenNamesMap);
checkIfUninstalledEarlier(accessors, unsetKeys);
checkForOverrides(url, accessors, takenAccessors, existingLibraries);
if (accessors.length === 0)
return { status: false, defs, accessor: accessors };
//Reserves accessor names.
const name = accessors[accessors.length - 1]; const name = accessors[accessors.length - 1];
defs["!name"] = `LIB/${name}`; defs["!name"] = `LIB/${name}`;
try { try {
for (const key of accessors) { for (const key of accessors) {
//@ts-expect-error no types
defs[key] = makeTernDefs(self[key]); defs[key] = makeTernDefs(self[key]);
} }
} catch (e) { } catch (e) {
for (const acc of accessors) { for (const acc of accessors) {
//@ts-expect-error no types
self[acc] = undefined; self[acc] = undefined;
} }
log.debug(e, `ternDefinitions failed for ${url}`);
throw new TernDefinitionError( throw new TernDefinitionError(
`Failed to generate autocomplete definitions: ${name}`, `Failed to generate autocomplete definitions: ${name}`,
); );
} }
// All the existing library and the newly installed one is added to global.
Object.keys(libStore).forEach((k) => (self[k] = libStore[k]));
//Reserve accessor names. //Reserve accessor names.
for (const acc of accessors) { for (const acc of accessors) {
//we have to update invalidEntityIdentifiers as well //we have to update invalidEntityIdentifiers as well
@ -167,21 +192,20 @@ export async function installLibrary(request: EvalWorkerASyncRequest) {
return { success: true, defs, accessor: accessors }; return { success: true, defs, accessor: accessors };
} catch (error) { } catch (error) {
addTempStoredDataTreeToContext(tempDataTreeStore);
takenAccessors.forEach((k) => (self[k] = libStore[k]));
return { success: false, defs, error }; return { success: false, defs, error };
} }
} }
export function uninstallLibrary(request: EvalWorkerSyncRequest) { export function uninstallLibrary(
request: EvalWorkerSyncRequest<Array<string>>,
) {
const { data } = request; const { data } = request;
const accessor = data; const accessor = data;
try { try {
for (const key of accessor) { for (const key of accessor) {
try { self[key] = undefined;
delete self[key];
} catch (e) {
//@ts-expect-error ignore
self[key] = undefined;
}
//we have to update invalidEntityIdentifiers as well //we have to update invalidEntityIdentifiers as well
delete libraryReservedIdentifiers[key]; delete libraryReservedIdentifiers[key];
delete invalidEntityIdentifiers[key]; delete invalidEntityIdentifiers[key];
@ -192,39 +216,60 @@ export function uninstallLibrary(request: EvalWorkerSyncRequest) {
} }
} }
export async function loadLibraries(request: EvalWorkerASyncRequest) { export async function loadLibraries(
request: EvalWorkerASyncRequest<JSLibrary[]>,
) {
resetJSLibraries(); resetJSLibraries();
//Add types
const { data: libs } = request; const { data: libs } = request;
let message = ""; let message = "";
const libStore: Record<string, unknown> = {};
try { try {
for (const lib of libs) { for (const lib of libs) {
const url = lib.url; const url = lib.url as string;
const accessor = lib.accessor; const accessors = lib.accessor;
const keysBefore = Object.keys(self); const keysBefore = Object.keys(self);
let module = null; let module = null;
try { try {
self.importScripts(url); self.importScripts(url);
const keysAfter = Object.keys(self);
const defaultAccessors = difference(keysAfter, keysBefore);
/**
* Installing 2 different version of lodash tries to add the same accessor on the self object. Let take version a & b for example.
* Installation of version a, will add _ to the self object and can be detected by looking at the differences in the previous step.
* Now when version b is installed, differences will be [], since _ already exists in the self object.
* We add all the installations to the libStore and see if the reference it points to in the self object changes.
* If the references changes it means that it a valid accessor.
*/
defaultAccessors.push(
...Object.keys(libStore).filter((k) => libStore[k] !== self[k]),
);
/** Sort the accessor list from backend and installed accessor list using the same rule to apply all modifications.
* This is required only for UMD builds, since we always generate unique names for ESM.
*/
accessors.sort();
defaultAccessors.sort();
for (let i = 0; i < defaultAccessors.length; i++) {
self[accessors[i]] = self[defaultAccessors[i]];
libStore[defaultAccessors[i]] = self[defaultAccessors[i]];
libraryReservedIdentifiers[accessors[i]] = true;
invalidEntityIdentifiers[accessors[i]] = true;
}
} catch (e) { } catch (e) {
message = (e as Error).message;
try { try {
module = await import(/* webpackIgnore: true */ url); module = await import(/* webpackIgnore: true */ url);
if (!module || typeof module !== "object") throw "Not an ESM module";
const key = accessors[0];
libStore[key] = module;
libraryReservedIdentifiers[key] = true;
invalidEntityIdentifiers[key] = true;
} catch (e) { } catch (e) {
message = (e as Error).message; throw new ImportError(url);
} }
} }
const keysAfter = Object.keys(self);
const newKeys = difference(keysAfter, keysBefore);
if (newKeys.length === 0 && module && typeof module === "object") {
self[accessor[0]] = flattenModule(module);
newKeys.push(accessor[0]);
}
for (const key of newKeys) {
//we have to update invalidEntityIdentifiers as well
libraryReservedIdentifiers[key] = true;
invalidEntityIdentifiers[key] = true;
}
} }
JSLibraries.push(...libs); JSLibraries.push(...libs);
return { success: true, message }; return { success: true, message };
@ -234,48 +279,6 @@ export async function loadLibraries(request: EvalWorkerASyncRequest) {
} }
} }
function checkForNameCollision(
accessor: string[],
takenNamesMap: Record<string, any>,
) {
const collidingNames = accessor.filter((key: string) => takenNamesMap[key]);
if (collidingNames.length) {
for (const acc of accessor) {
//@ts-expect-error no types
self[acc] = undefined;
}
throw new NameCollisionError(collidingNames.join(", "));
}
}
function checkIfUninstalledEarlier(accessor: string[], unsetKeys: string[]) {
if (accessor.length > 0) return;
for (const key of unsetKeys) {
//@ts-expect-error no types
if (!self[key]) continue;
accessor.push(key);
}
}
function checkForOverrides(
url: string,
accessor: string[],
takenAccessors: string[],
existingLibraries: Record<string, any>,
) {
if (accessor.length > 0) return;
const overriddenAccessors: Array<string> = [];
for (const acc of takenAccessors) {
//@ts-expect-error no types
if (existingLibraries[acc] === self[acc]) continue;
//@ts-expect-error no types
self[acc] = existingLibraries[acc];
overriddenAccessors.push(acc);
}
if (overriddenAccessors.length === 0) return;
throw new LibraryOverrideError(url, overriddenAccessors);
}
/** /**
* This function is called only for ESM modules and generates a unique namespace for the module. * This function is called only for ESM modules and generates a unique namespace for the module.
* @param url * @param url
@ -284,22 +287,24 @@ function checkForOverrides(
* @returns * @returns
*/ */
function generateUniqueAccessor( function generateUniqueAccessor(
url: string, urlOrName: string,
takenAccessors: Array<string>, takenAccessors: Array<string>,
takenNamesMap: Record<string, true>, takenNamesMap: Record<string, true>,
) { ) {
let name = urlOrName;
// extract file name from url // extract file name from url
const urlObject = new URL(url); try {
// URL pattern for ESM modules from jsDelivr - https://cdn.jsdelivr.net/npm/stripe@13.3.0/+esm // Checks to see if a URL was passed
// Assuming the file name is the last part of the path const urlObject = new URL(urlOrName);
const urlPathParts = urlObject.pathname.split("/"); // URL pattern for ESM modules from jsDelivr - https://cdn.jsdelivr.net/npm/stripe@13.3.0/+esm
let fileName = urlPathParts.pop(); // Assuming the file name is the last part of the path
fileName = fileName?.includes("esm") ? urlPathParts.pop() : fileName; const urlPathParts = urlObject.pathname.split("/");
name = urlPathParts.pop() as string;
name = name?.includes("+esm") ? (urlPathParts.pop() as string) : name;
} catch (e) {}
// This should never happen. This is just to avoid the typescript error.
if (!fileName) throw new Error("Unable to generate a unique accessor");
// Replace all non-alphabetic characters with underscores and remove trailing underscores // Replace all non-alphabetic characters with underscores and remove trailing underscores
const validVar = fileName.replace(/[^a-zA-Z]/g, "_").replace(/_+$/, ""); const validVar = name.replace(/[^a-zA-Z]/g, "_").replace(/_+$/, "");
if ( if (
!takenAccessors.includes(validVar) && !takenAccessors.includes(validVar) &&
!takenNamesMap.hasOwnProperty(validVar) !takenNamesMap.hasOwnProperty(validVar)
@ -308,7 +313,7 @@ function generateUniqueAccessor(
} }
let index = 0; let index = 0;
while (index++ < 100) { while (index++ < 100) {
const name = `Library_${index}`; const name = `${validVar}_${index}`;
if (!takenAccessors.includes(name) && !takenNamesMap.hasOwnProperty(name)) { if (!takenAccessors.includes(name) && !takenNamesMap.hasOwnProperty(name)) {
return name; return name;
} }

View File

@ -19,9 +19,12 @@ import type { WorkerRequest } from "@appsmith/workers/common/types";
import type { DataTreeDiff } from "@appsmith/workers/Evaluation/evaluationUtils"; import type { DataTreeDiff } from "@appsmith/workers/Evaluation/evaluationUtils";
import type { APP_MODE } from "entities/App"; import type { APP_MODE } from "entities/App";
export type EvalWorkerSyncRequest = WorkerRequest<any, EVAL_WORKER_SYNC_ACTION>; export type EvalWorkerSyncRequest<T = any> = WorkerRequest<
export type EvalWorkerASyncRequest = WorkerRequest< T,
any, EVAL_WORKER_SYNC_ACTION
>;
export type EvalWorkerASyncRequest<T = any> = WorkerRequest<
T,
EVAL_WORKER_ASYNC_ACTION EVAL_WORKER_ASYNC_ACTION
>; >;
export type EvalWorkerResponse = EvalTreeResponseData | boolean | unknown; export type EvalWorkerResponse = EvalTreeResponseData | boolean | unknown;

View File

@ -6,7 +6,6 @@ import ecma from "constants/defs/ecmascript.json";
import lodash from "constants/defs/lodash.json"; import lodash from "constants/defs/lodash.json";
import base64 from "constants/defs/base64-js.json"; import base64 from "constants/defs/base64-js.json";
import moment from "constants/defs/moment.json"; import moment from "constants/defs/moment.json";
import xmlJs from "constants/defs/xmlParser.json";
import forge from "constants/defs/forge.json"; import forge from "constants/defs/forge.json";
import browser from "constants/defs/browser.json"; import browser from "constants/defs/browser.json";
import { import {
@ -64,7 +63,6 @@ function startServer(plugins = {}, scripts?: string[]) {
lodash, lodash,
base64, base64,
moment, moment,
xmlJs,
forge, forge,
] as Def[], ] as Def[],
plugins: plugins, plugins: plugins,

View File

@ -1,7 +1,7 @@
import lodashPackageJson from "lodash/package.json"; import lodashPackageJson from "lodash/package.json";
import momentPackageJson from "moment-timezone/package.json"; import momentPackageJson from "moment-timezone/package.json";
export interface TJSLibrary { export interface JSLibrary {
version?: string; version?: string;
docsURL: string; docsURL: string;
name: string; name: string;
@ -9,7 +9,7 @@ export interface TJSLibrary {
url?: string; url?: string;
} }
export const defaultLibraries: TJSLibrary[] = [ export const defaultLibraries: JSLibrary[] = [
{ {
accessor: ["_"], accessor: ["_"],
version: lodashPackageJson.version, version: lodashPackageJson.version,
@ -22,12 +22,6 @@ export const defaultLibraries: TJSLibrary[] = [
docsURL: `https://momentjs.com/docs/`, docsURL: `https://momentjs.com/docs/`,
name: "moment", name: "moment",
}, },
{
accessor: ["xmlParser"],
version: "3.17.5",
docsURL: "https://github.com/NaturalIntelligence/fast-xml-parser",
name: "xmlParser",
},
{ {
accessor: ["forge"], accessor: ["forge"],
version: "1.3.0", version: "1.3.0",

View File

@ -1,6 +1,5 @@
import _ from "./lodash-wrapper"; import _ from "./lodash-wrapper";
import moment from "moment-timezone"; import moment from "moment-timezone";
import parser from "fast-xml-parser";
import forge from "node-forge"; import forge from "node-forge";
import { defaultLibraries } from "./index"; import { defaultLibraries } from "./index";
import { JSLibraries, libraryReservedIdentifiers } from "./index"; import { JSLibraries, libraryReservedIdentifiers } from "./index";
@ -8,7 +7,6 @@ import { invalidEntityIdentifiers } from "../DependencyMap/utils";
const defaultLibImplementations = { const defaultLibImplementations = {
lodash: _, lodash: _,
moment: moment, moment: moment,
xmlParser: parser,
// We are removing some functionalities of node-forge because they wont // We are removing some functionalities of node-forge because they wont
// work in the worker thread // work in the worker thread
forge: /*#__PURE*/ _.omit(forge, ["tls", "http", "xhr", "socket", "task"]), forge: /*#__PURE*/ _.omit(forge, ["tls", "http", "xhr", "socket", "task"]),

View File

@ -10511,7 +10511,6 @@ __metadata:
factory.ts: ^0.5.1 factory.ts: ^0.5.1
fast-deep-equal: ^3.1.3 fast-deep-equal: ^3.1.3
fast-sort: ^3.4.0 fast-sort: ^3.4.0
fast-xml-parser: ^3.17.5
fastdom: ^1.0.11 fastdom: ^1.0.11
focus-trap-react: ^8.9.2 focus-trap-react: ^8.9.2
fuse.js: ^3.4.5 fuse.js: ^3.4.5
@ -16679,15 +16678,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"fast-xml-parser@npm:^3.17.5":
version: 3.17.5
resolution: "fast-xml-parser@npm:3.17.5"
bin:
xml2js: cli.js
checksum: 6c436f540a4dcab67d395c0f6e8dc70090e6ced2ff73ed5fec181c1b25df755ed9fd9ba8271d6f70096fbe3add8b4eac130a5f4daeb12970427f72403a56934e
languageName: node
linkType: hard
"fastdom@npm:^1.0.11": "fastdom@npm:^1.0.11":
version: 1.0.11 version: 1.0.11
resolution: "fastdom@npm:1.0.11" resolution: "fastdom@npm:1.0.11"

View File

@ -1,8 +1,34 @@
package com.appsmith.server.constants; package com.appsmith.server.constants;
import com.appsmith.server.domains.CustomJSLib;
import java.util.Set;
public class ApplicationConstants { public class ApplicationConstants {
public static final String[] APP_CARD_COLORS = { public static final String[] APP_CARD_COLORS = {
"#FFDEDE", "#FFEFDB", "#F3F1C7", "#F4FFDE", "#C7F3F0", "#D9E7FF", "#E3DEFF", "#F1DEFF", "#C7F3E3", "#F5D1D1", "#FFDEDE", "#FFEFDB", "#F3F1C7", "#F4FFDE", "#C7F3F0", "#D9E7FF", "#E3DEFF", "#F1DEFF", "#C7F3E3", "#F5D1D1",
"#ECECEC", "#FBF4ED", "#D6D1F2", "#FFEBFB", "#EAEDFB" "#ECECEC", "#FBF4ED", "#D6D1F2", "#FFEBFB", "#EAEDFB"
}; };
public static CustomJSLib getDefaultParserCustomJsLibCompatibilityDTO() {
CustomJSLib customJSLib = new CustomJSLib();
customJSLib.setName("xmlParser");
customJSLib.setVersion("3.17.5");
customJSLib.setAccessor(Set.of("xmlParser"));
customJSLib.setUrl("https://cdnjs.cloudflare.com/ajax/libs/fast-xml-parser/3.17.5/parser.min.js");
customJSLib.setDefs(
"{\"!name\":\"LIB/xmlParser\",\"xmlParser\":{\"parse\":{\"!type\":\"fn()\",\"prototype\":{}},\"convertTonimn\":{\"!type\":\"fn()\",\"prototype\":{}},\"getTraversalObj\":{\"!type\":\"fn()\",\"prototype\":{}},\"convertToJson\":{\"!type\":\"fn()\",\"prototype\":{}},\"convertToJsonString\":{\"!type\":\"fn()\",\"prototype\":{}},\"validate\":{\"!type\":\"fn()\",\"prototype\":{}},\"j2xParser\":{\"!type\":\"fn()\",\"prototype\":{\"parse\":{\"!type\":\"fn()\",\"prototype\":{}},\"j2x\":{\"!type\":\"fn()\",\"prototype\":{}}}},\"parseToNimn\":{\"!type\":\"fn()\",\"prototype\":{}}}}");
customJSLib.setUidString(XML_PARSER_LIBRARY_UID);
return customJSLib;
}
/**
* Appsmith provides xmlParser v 3.17.5 and few other customJSLibraries by default, xmlParser has been
* flagged because it has some vulnerabilities. Appsmith is stopping natively providing support for xmlParser.
* This however, would break existing applications which are using xmlParser. In order to prevent this,
* we are adding xmlParser as an add-onn to all existing applications and applications which will be imported
* This CustomJSLib UID needs to be added to all the imported applications where we don't have any later versions of xmlParser present.
*/
public static final String XML_PARSER_LIBRARY_UID =
"xmlParser_https://cdnjs.cloudflare.com/ajax/libs/fast-xml-parser/3.17.5/parser.min.js";
} }

View File

@ -16,6 +16,7 @@ import com.appsmith.external.models.DefaultResources;
import com.appsmith.external.models.OAuth2; import com.appsmith.external.models.OAuth2;
import com.appsmith.external.models.Policy; import com.appsmith.external.models.Policy;
import com.appsmith.server.actioncollections.base.ActionCollectionService; import com.appsmith.server.actioncollections.base.ActionCollectionService;
import com.appsmith.server.constants.ApplicationConstants;
import com.appsmith.server.constants.FieldName; import com.appsmith.server.constants.FieldName;
import com.appsmith.server.constants.ResourceModes; import com.appsmith.server.constants.ResourceModes;
import com.appsmith.server.datasources.base.DatasourceService; import com.appsmith.server.datasources.base.DatasourceService;
@ -1135,6 +1136,8 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC
customJSLibs = new ArrayList<>(); customJSLibs = new ArrayList<>();
} }
ensureXmlParserPresenceInCustomJsLibList(customJSLibs);
return Flux.fromIterable(customJSLibs) return Flux.fromIterable(customJSLibs)
.flatMap(customJSLib -> { .flatMap(customJSLib -> {
customJSLib.setId(null); customJSLib.setId(null);
@ -1154,6 +1157,32 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC
}); });
} }
/**
* This method takes customJSLibList from application JSON, checks if an entry for XML parser exists,
* otherwise adds the entry.
* This has been done to add the xmlParser entry in imported application as appsmith is stopping native support
* for xml parser.
* Read More: https://github.com/appsmithorg/appsmith/pull/28012
*
* @param customJSLibList
*/
public void ensureXmlParserPresenceInCustomJsLibList(List<CustomJSLib> customJSLibList) {
boolean isXmlParserLibFound = false;
for (CustomJSLib customJSLib : customJSLibList) {
if (!customJSLib.getUidString().equals(ApplicationConstants.XML_PARSER_LIBRARY_UID)) {
continue;
}
isXmlParserLibFound = true;
break;
}
if (!isXmlParserLibFound) {
CustomJSLib xmlParserJsLib = ApplicationConstants.getDefaultParserCustomJsLibCompatibilityDTO();
customJSLibList.add(xmlParserJsLib);
}
}
private Mono<List<Datasource>> getExistingDatasourceMono(String applicationId, Flux<Datasource> datasourceFlux) { private Mono<List<Datasource>> getExistingDatasourceMono(String applicationId, Flux<Datasource> datasourceFlux) {
Mono<List<Datasource>> existingDatasourceMono; Mono<List<Datasource>> existingDatasourceMono;
// Check if the request is to hydrate the application to DB for particular branch // Check if the request is to hydrate the application to DB for particular branch

View File

@ -3,6 +3,7 @@ package com.appsmith.server.migrations;
import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.ActionDTO;
import com.appsmith.external.models.BaseDomain; import com.appsmith.external.models.BaseDomain;
import com.appsmith.external.models.InvisibleActionFields; import com.appsmith.external.models.InvisibleActionFields;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.constants.ResourceModes; import com.appsmith.server.constants.ResourceModes;
import com.appsmith.server.domains.ApplicationPage; import com.appsmith.server.domains.ApplicationPage;
import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewAction;
@ -236,4 +237,23 @@ public class MigrationHelperMethods {
mongoTemplate.find(query(where(fieldName(path)).is(id)), type); mongoTemplate.find(query(where(fieldName(path)).is(id)), type);
return domainObject; return domainObject;
} }
/**
* The method provides the criteria for any document to qualify as not deleted
* @return Criteria
*/
public static Criteria notDeleted() {
return new Criteria()
.andOperator(
// Older check for deleted
new Criteria()
.orOperator(
where(FieldName.DELETED).exists(false),
where(FieldName.DELETED).is(false)),
// New check for deleted
new Criteria()
.orOperator(
where(FieldName.DELETED_AT).exists(false),
where(FieldName.DELETED_AT).is(null)));
}
} }

View File

@ -0,0 +1,90 @@
package com.appsmith.server.migrations.db.ce;
import com.appsmith.server.constants.ApplicationConstants;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.CustomJSLib;
import com.appsmith.server.domains.QApplication;
import com.appsmith.server.dtos.CustomJSLibApplicationDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import io.mongock.api.annotations.ChangeUnit;
import io.mongock.api.annotations.Execution;
import io.mongock.api.annotations.RollbackExecution;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import static com.appsmith.server.constants.ApplicationConstants.XML_PARSER_LIBRARY_UID;
import static com.appsmith.server.migrations.MigrationHelperMethods.notDeleted;
import static com.appsmith.server.repositories.ce.BaseAppsmithRepositoryCEImpl.fieldName;
/**
* Appsmith provides xmlParser v 3.17.5 and few other customJSLibraries by default, xmlParser has been
* flagged because it has some vulnerabilities. Appsmith is stopping natively providing support for xmlParser.
* This however, would break existing applications which are using xmlParser. In order to prevent this,
* applications require to have xmlParser as added library.
*
* This migration takes care of adding a document in customJsLib for xml-parser and adding corresponding entry
* in application collection
*
*/
@Slf4j
@ChangeUnit(order = "032", id = "add-xml-parser-to-application-jslibs", author = " ")
public class Migration032AddingXmlParserToApplicationLibraries {
private final MongoTemplate mongoTemplate;
private static final String UNPUBLISHED_CUSTOM_JS_LIBS =
fieldName(QApplication.application.unpublishedCustomJSLibs);
private static final String PUBLISHED_CUSTOM_JS_LIBS = fieldName(QApplication.application.publishedCustomJSLibs);
public Migration032AddingXmlParserToApplicationLibraries(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
@RollbackExecution
public void rollbackExecution() {}
@Execution
public void addXmlParserEntryToEachApplication() {
// add the xml-parser to customJSLib if it's not already present
CustomJSLib customJSLib = generateXmlParserJSLibObject();
try {
mongoTemplate.save(customJSLib);
} catch (DuplicateKeyException duplicateKeyException) {
log.debug(
"Addition of xmlParser object in customJSLib failed, because object with similar UID already exists");
} catch (Exception exception) {
throw new AppsmithException(
AppsmithError.MIGRATION_FAILED,
"Migration032AddingXmlParserToApplicationLibraries",
exception.getMessage(),
"Unable to insert xml parser library in CustomJSLib collection");
}
// add uid entry in all these custom js libs
Update pushXmlParserUpdate = new Update()
.addToSet(PUBLISHED_CUSTOM_JS_LIBS, getXmlParserCustomJSLibApplicationDTO())
.addToSet(UNPUBLISHED_CUSTOM_JS_LIBS, getXmlParserCustomJSLibApplicationDTO());
log.debug("Going to add Xml Parser uid in all application DTOs");
mongoTemplate.updateMulti(
new Query().addCriteria(getMigrationCriteria()), pushXmlParserUpdate, Application.class);
}
private CustomJSLibApplicationDTO getXmlParserCustomJSLibApplicationDTO() {
CustomJSLibApplicationDTO xmlParserApplicationDTO = new CustomJSLibApplicationDTO();
xmlParserApplicationDTO.setUidString(XML_PARSER_LIBRARY_UID);
return xmlParserApplicationDTO;
}
private static CustomJSLib generateXmlParserJSLibObject() {
return ApplicationConstants.getDefaultParserCustomJsLibCompatibilityDTO();
}
private static Criteria getMigrationCriteria() {
return notDeleted();
}
}

View File

@ -968,7 +968,10 @@ public class ImportApplicationServiceTests {
final List<ActionCollection> actionCollectionList = tuple.getT5(); final List<ActionCollection> actionCollectionList = tuple.getT5();
final List<CustomJSLib> importedJSLibList = tuple.getT6(); final List<CustomJSLib> importedJSLibList = tuple.getT6();
assertEquals(1, importedJSLibList.size()); // although the imported list had only one jsLib entry, the other entry comes from ensuring an xml
// parser entry
// for backward compatibility
assertEquals(2, importedJSLibList.size());
CustomJSLib importedJSLib = (CustomJSLib) importedJSLibList.toArray()[0]; CustomJSLib importedJSLib = (CustomJSLib) importedJSLibList.toArray()[0];
CustomJSLib expectedJSLib = new CustomJSLib( CustomJSLib expectedJSLib = new CustomJSLib(
"TestLib", Set.of("accessor1"), "url", "docsUrl", "1" + ".0", "defs_string"); "TestLib", Set.of("accessor1"), "url", "docsUrl", "1" + ".0", "defs_string");
@ -978,10 +981,10 @@ public class ImportApplicationServiceTests {
assertEquals(expectedJSLib.getDocsUrl(), importedJSLib.getDocsUrl()); assertEquals(expectedJSLib.getDocsUrl(), importedJSLib.getDocsUrl());
assertEquals(expectedJSLib.getVersion(), importedJSLib.getVersion()); assertEquals(expectedJSLib.getVersion(), importedJSLib.getVersion());
assertEquals(expectedJSLib.getDefs(), importedJSLib.getDefs()); assertEquals(expectedJSLib.getDefs(), importedJSLib.getDefs());
assertEquals(1, application.getUnpublishedCustomJSLibs().size()); // although the imported list had only one jsLib entry, the other entry comes from ensuring an xml
assertEquals( // parser entry
getDTOFromCustomJSLib(expectedJSLib), // for backward compatibility
application.getUnpublishedCustomJSLibs().toArray()[0]); assertEquals(2, application.getUnpublishedCustomJSLibs().size());
assertThat(application.getName()).isEqualTo("valid_application"); assertThat(application.getName()).isEqualTo("valid_application");
assertThat(application.getWorkspaceId()).isNotNull(); assertThat(application.getWorkspaceId()).isNotNull();