From 5e0c4d6d2f8528827ba3af37a95c631cbf1ad0f8 Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Fri, 17 Jun 2022 15:01:52 +0530 Subject: [PATCH 01/54] fix: Fix migration for overwriting recently used workspaces (#14634) --- .../server/migrations/DatabaseChangelog2.java | 51 ++----------------- 1 file changed, 5 insertions(+), 46 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java index df5f2dbc3d..090483443f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java @@ -1,89 +1,50 @@ package com.appsmith.server.migrations; -import com.appsmith.external.models.ApiTemplate; -import com.appsmith.external.models.BaseDomain; -import com.appsmith.external.models.Category; import com.appsmith.external.models.Datasource; import com.appsmith.external.models.Property; -import com.appsmith.external.models.Provider; import com.appsmith.external.models.QBaseDomain; import com.appsmith.external.models.QDatasource; -import com.appsmith.server.acl.AppsmithRole; import com.appsmith.server.constants.FieldName; -import com.appsmith.server.domains.Action; import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.ApplicationPage; -import com.appsmith.server.domains.Asset; -import com.appsmith.server.domains.Collection; import com.appsmith.server.domains.Comment; -import com.appsmith.server.domains.CommentNotification; import com.appsmith.server.domains.CommentThread; -import com.appsmith.server.domains.CommentThreadNotification; -import com.appsmith.server.domains.Config; -import com.appsmith.server.domains.GitDeployKeys; -import com.appsmith.server.domains.Group; -import com.appsmith.server.domains.InviteUser; -import com.appsmith.server.domains.Layout; import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; -import com.appsmith.server.domains.Notification; import com.appsmith.server.domains.Organization; -import com.appsmith.server.domains.Page; -import com.appsmith.server.domains.PasswordResetToken; import com.appsmith.server.domains.Plugin; -import com.appsmith.server.domains.QAction; -import com.appsmith.server.domains.QActionCollection; import com.appsmith.server.domains.PricingPlan; +import com.appsmith.server.domains.QActionCollection; import com.appsmith.server.domains.QApplication; -import com.appsmith.server.domains.QCollection; import com.appsmith.server.domains.QComment; import com.appsmith.server.domains.QCommentThread; -import com.appsmith.server.domains.QConfig; -import com.appsmith.server.domains.QGroup; -import com.appsmith.server.domains.QInviteUser; import com.appsmith.server.domains.QNewAction; import com.appsmith.server.domains.QNewPage; import com.appsmith.server.domains.QOrganization; import com.appsmith.server.domains.QPlugin; +import com.appsmith.server.domains.QTenant; import com.appsmith.server.domains.QTheme; import com.appsmith.server.domains.QUser; import com.appsmith.server.domains.QUserData; import com.appsmith.server.domains.QWorkspace; -import com.appsmith.server.domains.Role; -import com.appsmith.server.domains.Sequence; -import com.appsmith.server.domains.Theme; -import com.appsmith.server.domains.UsagePulse; -import com.appsmith.server.domains.User; -import com.appsmith.server.domains.UserData; -import com.appsmith.server.domains.QTenant; import com.appsmith.server.domains.Sequence; import com.appsmith.server.domains.Tenant; +import com.appsmith.server.domains.Theme; import com.appsmith.server.domains.User; +import com.appsmith.server.domains.UserData; import com.appsmith.server.domains.Workspace; -import com.appsmith.server.domains.WorkspacePlugin; import com.appsmith.server.dtos.ActionDTO; -import com.appsmith.server.dtos.ApplicationTemplate; -import com.appsmith.server.dtos.ResetUserPasswordDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.TextUtils; -import com.appsmith.server.services.ce.ConfigServiceCE; -import com.appsmith.server.services.ce.ConfigServiceCEImpl; import com.github.cloudyrock.mongock.ChangeLog; import com.github.cloudyrock.mongock.ChangeSet; import com.github.cloudyrock.mongock.driver.mongodb.springdata.v3.decorator.impl.MongockTemplate; import com.google.gson.Gson; import lombok.extern.slf4j.Slf4j; -import reactor.core.publisher.Flux; - -import org.bson.BsonArray; -import org.bson.Document; -import org.springframework.data.mongodb.core.aggregation.AggregationOperation; -import org.springframework.data.mongodb.core.aggregation.AggregationPipeline; import org.springframework.data.mongodb.core.aggregation.AggregationUpdate; import org.springframework.data.mongodb.core.aggregation.Fields; -import org.springframework.data.mongodb.core.aggregation.SetOperation; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; @@ -94,14 +55,12 @@ import org.springframework.util.StringUtils; import reactor.core.publisher.Flux; import java.time.Instant; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -967,7 +926,7 @@ public class DatabaseChangelog2 { AggregationUpdate.update().set(fieldName(QTheme.theme.workspaceId)).toValueOf(Fields.field(fieldName(QTheme.theme.organizationId))), Theme.class); mongockTemplate.updateMulti(new Query(), - AggregationUpdate.update().set(fieldName(QUserData.userData.recentlyUsedOrgIds)).toValueOf(Fields.field(fieldName(QUserData.userData.recentlyUsedWorkspaceIds))), + AggregationUpdate.update().set(fieldName(QUserData.userData.recentlyUsedWorkspaceIds)).toValueOf(Fields.field(fieldName(QUserData.userData.recentlyUsedOrgIds))), UserData.class); mongockTemplate.updateMulti(new Query(), AggregationUpdate.update().set(fieldName(QWorkspace.workspace.isAutoGeneratedWorkspace)).toValueOf(Fields.field(fieldName(QWorkspace.workspace.isAutoGeneratedOrganization))), From 7ae511f402e8907d024f593da22453d4bed67534 Mon Sep 17 00:00:00 2001 From: Arpit Mohan Date: Wed, 22 Jun 2022 06:27:40 +0200 Subject: [PATCH 02/54] ci: Revert "ci: Use buildjet runners for Cypress tests, and fewer of them" Reverts #14636 Reverting this change for now because there is an issue when re-running the failed specs. The action is unable to find the correct directory. --- .github/workflows/test-build-docker-image.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-build-docker-image.yml b/.github/workflows/test-build-docker-image.yml index cfca4329ec..d2f1a66784 100644 --- a/.github/workflows/test-build-docker-image.yml +++ b/.github/workflows/test-build-docker-image.yml @@ -761,7 +761,7 @@ jobs: (github.event_name == 'pull_request_review' && github.event.review.state == 'approved' && github.event.pull_request.head.repo.full_name == github.repository)) - runs-on: buildjet-4vcpu-ubuntu-2004 + runs-on: ubuntu-latest defaults: run: working-directory: app/client @@ -778,6 +778,23 @@ jobs: 4, 5, 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, ] # Service containers to run with this job. Required for running tests From dfab0570452f80116de7816edd2b02a7c24a50fc Mon Sep 17 00:00:00 2001 From: Aishwarya-U-R <91450662+Aishwarya-U-R@users.noreply.github.com> Date: Wed, 6 Jul 2022 20:52:24 +0530 Subject: [PATCH 03/54] test: Script fixes for failing cases due to UQI css changes (#15037) * flaky fixes * Eval value pop up hinder fix * JS delete from EE fix * ActionContextMenuByEntityName() fix * Adding Escape() * Toasts handling --- .../ClientSideTests/BugTests/Bug14299_Spec.ts | 5 +++-- .../ExplorerTests/JSEditorContextMenu_Spec.ts | 2 +- .../ClientSideTests/Widgets/TableBugs_Spec.ts | 2 +- .../ClientSideTests/Widgets/TableFilter_Spec.ts | 2 +- .../ServerSideTests/GenerateCRUD/Postgres_Spec.ts | 2 ++ .../JsFunctionExecution/JSFunctionExecution_spec.ts | 2 ++ .../ServerSideTests/OnLoadTests/JSOnLoad_Spec.ts | 6 +++--- .../ServerSideTests/Params/PassingParams_Spec.ts | 3 +-- app/client/cypress/support/Pages/AggregateHelper.ts | 6 +++++- app/client/cypress/support/Pages/EntityExplorer.ts | 5 +++++ 10 files changed, 24 insertions(+), 11 deletions(-) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug14299_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug14299_Spec.ts index 6220978d54..a18161cdf1 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug14299_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug14299_Spec.ts @@ -119,13 +119,14 @@ describe("[Bug]: The data from the query does not show up on the widget #14299", }); it("4. Verify Deletion of the datasource after all created queries are Deleted", () => { - deployMode.NavigateBacktoEditor() + deployMode.NavigateBacktoEditor(); + agHelper.WaitUntilToastDisappear("ran successfully"); //runAstros triggered on PageLaoad of Edit page! ee.ExpandCollapseEntity("QUERIES/JS"); ee.ActionContextMenuByEntityName("getAstronauts", "Delete", "Are you sure?"); ee.ActionContextMenuByEntityName( "JSObject1", "Delete", - "Are you sure?", + "Are you sure?", true ); deployMode.DeployApp(locator._widgetInDeployed("tablewidget"), false); deployMode.NavigateBacktoEditor(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/JSEditorContextMenu_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/JSEditorContextMenu_Spec.ts index 136316756b..0639299d9d 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/JSEditorContextMenu_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/JSEditorContextMenu_Spec.ts @@ -62,7 +62,7 @@ describe("Validate basic operations on Entity explorer JSEditor structure", () = ee.ActionContextMenuByEntityName( "ExplorerRenamed", "Delete", - "Are you sure?", + "Are you sure?", true ); ee.AssertEntityAbsenceInExplorer("ExplorerRenamed"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableBugs_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableBugs_Spec.ts index 78925817b7..daee7c7eb9 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableBugs_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableBugs_Spec.ts @@ -18,7 +18,7 @@ describe("Verify various Table property bugs", function () { ee.DragDropWidgetNVerify("tablewidget", 250, 250); propPane.UpdatePropertyFieldValue("Table Data", JSON.stringify(dataSet.TableURLColumnType)); agHelper.ValidateNetworkStatus("@updateLayout", 200); - cy.get('body').type("{esc}"); + agHelper.Escape(); }); it("2. Bug 13299 - Verify Display Text does not contain garbage value for URL column type when empty", function () { diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableFilter_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableFilter_Spec.ts index 1dc81a44c1..8300f06391 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableFilter_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableFilter_Spec.ts @@ -19,7 +19,7 @@ describe("Verify various Table_Filter combinations", function () { ee.DragDropWidgetNVerify("tablewidget", 250, 250); propPane.UpdatePropertyFieldValue("Table Data", JSON.stringify(dataSet.TableInput)); agHelper.ValidateNetworkStatus("@updateLayout", 200); - cy.get('body').type("{esc}"); + agHelper.Escape(); deployMode.DeployApp() }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/GenerateCRUD/Postgres_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/GenerateCRUD/Postgres_Spec.ts index dde7414956..0c61408b13 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/GenerateCRUD/Postgres_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/GenerateCRUD/Postgres_Spec.ts @@ -276,6 +276,7 @@ describe("Validate Postgres Generate CRUD with JSON Form", () => { ee.SelectEntityByName("UpdateQuery", "QUERIES/JS"); dataSources.EnterQuery(updateQuery); + agHelper.Escape(); agHelper.AssertAutoSave(); ee.ExpandCollapseEntity("QUERIES/JS", false); }); @@ -518,6 +519,7 @@ describe("Validate Postgres Generate CRUD with JSON Form", () => { ee.SelectEntityByName("InsertQuery", "QUERIES/JS"); dataSources.EnterQuery(insertQuery); + agHelper.Escape(); agHelper.AssertAutoSave(); ee.ExpandCollapseEntity("QUERIES/JS", false); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/JSFunctionExecution_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/JSFunctionExecution_spec.ts index 604996c0b4..dd2fbf14dd 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/JSFunctionExecution_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/JSFunctionExecution_spec.ts @@ -244,6 +244,7 @@ describe("JS Function Execution", function() { // Re-introduce parse errors jsEditor.EditJSObj(JS_OBJECT_WITH_PARSE_ERROR); agHelper.GetNClick(jsEditor._runButton); + agHelper.WaitUntilToastDisappear("ran successfully"); //to not hinder with next toast msg in next case! // Assert that there is a function execution parse error jsEditor.AssertParseError(true, true); @@ -255,6 +256,7 @@ describe("JS Function Execution", function() { "TypeError: Cannot read properties of undefined (reading 'name')", ).should("not.exist"); }); + it("6. Supports the use of large JSON data (doesn't crash)", () => { const jsObjectWithLargeJSONData = `export default{ largeData: ${JSON.stringify(largeJSONData)}, diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad_Spec.ts index 1015cb68cb..ed10e9d16f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad_Spec.ts @@ -170,7 +170,7 @@ describe("JSObjects OnLoad Actions tests", function() { ee.ActionContextMenuByEntityName( jsName as string, "Delete", - "Are you sure?", + "Are you sure?", true ); ee.ActionContextMenuByEntityName("GetUser", "Delete", "Are you sure?"); @@ -220,7 +220,7 @@ describe("JSObjects OnLoad Actions tests", function() { ee.ActionContextMenuByEntityName( jsName as string, "Delete", - "Are you sure?", + "Are you sure?", true ); }); }); @@ -559,7 +559,7 @@ describe("JSObjects OnLoad Actions tests", function() { ee.ActionContextMenuByEntityName( jsName as string, "Delete", - "Are you sure?", + "Are you sure?", true ); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Params/PassingParams_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Params/PassingParams_Spec.ts index ef740ff871..696b9107d2 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Params/PassingParams_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Params/PassingParams_Spec.ts @@ -231,9 +231,8 @@ describe("[Bug] - 10784 - Passing params from JS to SQL query should not break", ee.ActionContextMenuByEntityName( jsName as string, "Delete", - "Are you sure?", + "Are you sure?", true ); - agHelper.ValidateNetworkStatus("@deleteJSCollection", 200); // //Bug 12532 // ee.expandCollapseEntity('DATASOURCES') // ee.ActionContextMenuByEntityName(guid, 'Delete', 'Are you sure?') diff --git a/app/client/cypress/support/Pages/AggregateHelper.ts b/app/client/cypress/support/Pages/AggregateHelper.ts index 8b52234717..9259968ce1 100644 --- a/app/client/cypress/support/Pages/AggregateHelper.ts +++ b/app/client/cypress/support/Pages/AggregateHelper.ts @@ -330,12 +330,16 @@ export class AggregateHelper { } // //closing multiselect dropdown - cy.get("body").type("{esc}"); + this.Escape(); // cy.get(this.locator._widgetInDeployed(endpoint)) // .eq(index) // .click() } + public Escape(){ + cy.get('body').type("{esc}"); + } + public RemoveMultiSelectItems(items: string[]) { items.forEach(($each) => { cy.xpath(this.locator._multiSelectItem($each)) diff --git a/app/client/cypress/support/Pages/EntityExplorer.ts b/app/client/cypress/support/Pages/EntityExplorer.ts index 6d8862f436..5f23c92ae9 100644 --- a/app/client/cypress/support/Pages/EntityExplorer.ts +++ b/app/client/cypress/support/Pages/EntityExplorer.ts @@ -104,6 +104,7 @@ export class EntityExplorer { entityNameinLeftSidebar: string, action = "Delete", subAction = "", + jsDelete = false, ) { this.agHelper.Sleep(); cy.xpath(this._contextMenu(entityNameinLeftSidebar)) @@ -115,6 +116,10 @@ export class EntityExplorer { cy.xpath(this._contextMenuItem(subAction)).click({ force: true }); this.agHelper.Sleep(500); } + if (action == "Delete") { + jsDelete && this.agHelper.ValidateNetworkStatus("@deleteJSCollection"); + jsDelete && this.agHelper.WaitUntilToastDisappear("deleted successfully"); + } } public ActionTemplateMenuByEntityName( From 887d57b86b80c9179a4a3094a008483cc2b14650 Mon Sep 17 00:00:00 2001 From: Arsalan Yaldram Date: Wed, 13 Jul 2022 13:04:23 +0530 Subject: [PATCH 04/54] fix: using old versions of marked (#15151) (cherry picked from commit 8428ae506a02ec477027b82936ff003c0c53cafb) --- app/client/package.json | 4 ++-- .../GlobalSearch/parseDocumentationContent.ts | 7 +++---- app/client/yarn.lock | 16 ++++++++-------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/app/client/package.json b/app/client/package.json index cc69c86560..80c9762b6a 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -78,7 +78,7 @@ "loglevel": "^1.7.1", "lottie-web": "^5.7.4", "mammoth": "^1.4.19", - "marked": "^4.0.17", + "marked": "^2.0.0", "memoize-one": "^5.2.1", "micro-memoize": "^4.0.10", "moment": "2.29.3", @@ -201,7 +201,7 @@ "@types/js-beautify": "^1.13.2", "@types/jshint": "^2.12.0", "@types/lodash": "^4.14.120", - "@types/marked": "^4.0.3", + "@types/marked": "^1.2.2", "@types/moment-timezone": "^0.5.10", "@types/nanoid": "^2.0.0", "@types/node": "^10.12.18", diff --git a/app/client/src/components/editorComponents/GlobalSearch/parseDocumentationContent.ts b/app/client/src/components/editorComponents/GlobalSearch/parseDocumentationContent.ts index b66a8db3cc..ca78fee185 100644 --- a/app/client/src/components/editorComponents/GlobalSearch/parseDocumentationContent.ts +++ b/app/client/src/components/editorComponents/GlobalSearch/parseDocumentationContent.ts @@ -1,8 +1,7 @@ import { HelpBaseURL } from "constants/HelpConstants"; import { algoliaHighlightTag } from "./utils"; import log from "loglevel"; - -const { marked } = require("marked"); +import marked, { Token } from "marked"; /** * @param {String} HTML representing a single element @@ -123,8 +122,8 @@ const parseMarkdown = (value: string) => { value = replaceHintTagsWithCode(stripDescriptionMarkdown(value)); marked.use({ - walkTokens(token: any) { - const currentToken = token; + walkTokens(token: unknown) { + const currentToken = token as Token; if ("type" in currentToken && currentToken.type === "link") { let href = currentToken.href; try { diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 2a38d1590e..ab64d4f6b0 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -3057,10 +3057,10 @@ version "4.14.169" resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.169.tgz" -"@types/marked@^4.0.3": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.0.3.tgz#2098f4a77adaba9ce881c9e0b6baf29116e5acc4" - integrity sha512-HnMWQkLJEf/PnxZIfbm0yGJRRZYYMhb++O9M36UCTA9z53uPvVoSlAwJr3XOpDEryb7Hwl1qAx/MV6YIW1RXxg== +"@types/marked@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/marked/-/marked-1.2.2.tgz#1f858a0e690247ecf3b2eef576f98f86e8d960d4" + integrity sha512-wLfw1hnuuDYrFz97IzJja0pdVsC0oedtS4QsKH1/inyW9qkLQbXgMUqEQT0MVtUBx3twjWeInUfjQbhBVLECXw== "@types/mime@^1": version "1.3.2" @@ -10476,10 +10476,10 @@ map-obj@^4.0.0: version "4.2.1" resolved "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz" -marked@^4.0.17: - version "4.0.17" - resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.17.tgz#1186193d85bb7882159cdcfc57d1dfccaffb3fe9" - integrity sha512-Wfk0ATOK5iPxM4ptrORkFemqroz0ZDxp5MWfYA7H/F+wO17NRWV5Ypxi6p3g2Xmw2bKeiYOl6oVnLHKxBA0VhA== +marked@^2.0.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.3.tgz#bd017cef6431724fd4b27e0657f5ceb14bff3753" + integrity sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA== marker-clusterer-plus@^2.1.4: version "2.1.4" From fa82cead9883e2a420e787406f37d1bcc185131f Mon Sep 17 00:00:00 2001 From: Anand Srinivasan <66776129+eco-monk@users.noreply.github.com> Date: Wed, 13 Jul 2022 19:24:05 +0530 Subject: [PATCH 05/54] fix: curl import error (#15178) * send only pageId on curl import creation * remove variables * fix data source redirect (cherry picked from commit 59f91b618c79e3fb8931f9a4565215a72ae3f3e6) --- .../GlobalSearch/GlobalSearchHooks.tsx | 6 +----- .../src/pages/Editor/Explorer/Files/Submenu.tsx | 15 ++------------- app/client/src/utils/AnalyticsUtil.tsx | 3 ++- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/app/client/src/components/editorComponents/GlobalSearch/GlobalSearchHooks.tsx b/app/client/src/components/editorComponents/GlobalSearch/GlobalSearchHooks.tsx index db7fd883a8..f0591ed4d9 100644 --- a/app/client/src/components/editorComponents/GlobalSearch/GlobalSearchHooks.tsx +++ b/app/client/src/components/editorComponents/GlobalSearch/GlobalSearchHooks.tsx @@ -136,11 +136,7 @@ export const useFilteredFileOperations = (query = "") => { ), kind: SEARCH_ITEM_TYPES.actionOperation, - redirect: ( - applicationSlug: string, - pageSlug: string, - pageId: string, - ) => { + redirect: (pageId: string) => { history.push( integrationEditorURL({ pageId, diff --git a/app/client/src/pages/Editor/Explorer/Files/Submenu.tsx b/app/client/src/pages/Editor/Explorer/Files/Submenu.tsx index 10e79924f0..65622f75cb 100644 --- a/app/client/src/pages/Editor/Explorer/Files/Submenu.tsx +++ b/app/client/src/pages/Editor/Explorer/Files/Submenu.tsx @@ -8,11 +8,7 @@ import { import styled from "constants/DefaultTheme"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { - getCurrentPageId, - selectCurrentApplicationSlug, - selectPageSlugToIdMap, -} from "selectors/editorSelectors"; +import { getCurrentPageId } from "selectors/editorSelectors"; import EntityAddButton from "../Entity/AddButton"; import { ReactComponent as SearchIcon } from "assets/icons/ads/search.svg"; import { ReactComponent as CrossIcon } from "assets/icons/ads/cross.svg"; @@ -66,8 +62,6 @@ export default function ExplorerSubMenu({ const [show, setShow] = useState(openMenu); const fileOperations = useFilteredFileOperations(query); const pageId = useSelector(getCurrentPageId); - const applicationSlug = useSelector(selectCurrentApplicationSlug); - const pageIdToSlugMap = useSelector(selectPageSlugToIdMap); const dispatch = useDispatch(); const plugins = useSelector((state: AppState) => { return state.entities.plugins.list; @@ -128,12 +122,7 @@ export default function ExplorerSubMenu({ if (item.action) { dispatch(item.action(pageId, "SUBMENU")); } else if (item.redirect) { - item.redirect( - applicationSlug, - pageIdToSlugMap[pageId], - pageId, - "SUBMENU", - ); + item.redirect(pageId, "SUBMENU"); } setShow(false); }, diff --git a/app/client/src/utils/AnalyticsUtil.tsx b/app/client/src/utils/AnalyticsUtil.tsx index e0e16ea857..903d95afd4 100644 --- a/app/client/src/utils/AnalyticsUtil.tsx +++ b/app/client/src/utils/AnalyticsUtil.tsx @@ -22,7 +22,8 @@ export type EventLocation = | "QUERY_PANE" | "QUERY_TEMPLATE" | "QUICK_COMMANDS" - | "OMNIBAR"; + | "OMNIBAR" + | "SUBMENU"; export type EventName = | "APP_CRASH" From 64c693b9cdc52082cf7ba6f2215b427cf2dcd231 Mon Sep 17 00:00:00 2001 From: balajisoundar Date: Wed, 20 Jul 2022 11:42:59 +0530 Subject: [PATCH 06/54] fix: cell background for edit actions column in Table v2 (#15233) (cherry picked from commit 71e581d69b54db140e8cd47e3f41b1675ca97b99) --- .../cypress/fixtures/tableV2NewDsl.json | 1 + .../Widgets/TableV2/TableV2_Color_spec.js | 19 ++++++++- .../src/components/ads/DraggableList.tsx | 41 +++++++++++-------- .../PrimaryColumnsControlV2.tsx | 2 +- app/client/src/sagas/WidgetAdditionSagas.ts | 12 +++++- .../cellComponents/EditActionsCell.tsx | 2 + .../propertyConfig/PanelConfig/Styles.ts | 9 +--- 7 files changed, 57 insertions(+), 29 deletions(-) diff --git a/app/client/cypress/fixtures/tableV2NewDsl.json b/app/client/cypress/fixtures/tableV2NewDsl.json index d4e9e78e72..f19b88f4aa 100644 --- a/app/client/cypress/fixtures/tableV2NewDsl.json +++ b/app/client/cypress/fixtures/tableV2NewDsl.json @@ -21,6 +21,7 @@ "children": [ { "widgetName": "Table1", + "inlineEditingSaveOption": "ROW_LEVEL", "columnOrder": [ "id", "email", diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_Color_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_Color_spec.js index 2a61efeeb3..e36dd0db3f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_Color_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_Color_spec.js @@ -1,9 +1,12 @@ +const ObjectsRegistry = require("../../../../../support/Objects/Registry") + .ObjectsRegistry; +let propPane = ObjectsRegistry.PropertyPane; const widgetsPage = require("../../../../../locators/Widgets.json"); const dsl = require("../../../../../fixtures/tableV2NewDsl.json"); const publish = require("../../../../../locators/publishWidgetspage.json"); describe("Table Widget V2 property pane feature validation", function() { - before(() => { + beforeEach(() => { cy.addDsl(dsl); }); @@ -65,4 +68,18 @@ describe("Table Widget V2 property pane feature validation", function() { ); cy.get(publish.backToEditor).click(); }); + + it("2. check background of the edit action column", function() { + cy.openPropertyPane("tablewidgetv2"); + cy.makeColumnEditable("id"); + cy.readTableV2dataValidateCSS(0, 5, "background-color", "rgba(0, 0, 0, 0)"); + cy.get(".t--property-control-cellbackgroundcolor") + .find(".t--js-toggle") + .click(); + propPane.UpdatePropertyFieldValue( + "Cell Background Color", + "rgb(255, 0, 0)", + ); + cy.readTableV2dataValidateCSS(0, 5, "background-color", "rgb(255, 0, 0)"); + }); }); diff --git a/app/client/src/components/ads/DraggableList.tsx b/app/client/src/components/ads/DraggableList.tsx index 34a0ca3516..577a3075ac 100644 --- a/app/client/src/components/ads/DraggableList.tsx +++ b/app/client/src/components/ads/DraggableList.tsx @@ -102,26 +102,31 @@ function DraggableList(props: any) { }, [items]); useEffect(() => { - if (focusedIndex && listRef && listRef.current) { - const container = listRef.current; + /* + * we need to wait for the ui to get rendered before scrolling to the focusedColumn + */ + requestAnimationFrame(() => { + if (focusedIndex && listRef && listRef.current) { + const container = listRef.current; - if (focusedIndex * itemHeight < container.scrollTop) { - listRef.current.scrollTo({ - top: (focusedIndex - 1) * itemHeight, - left: 0, - behavior: "smooth", - }); - } else if ( - (focusedIndex + 1) * itemHeight > - listRef.current.scrollTop + listRef.current.clientHeight - ) { - listRef.current.scrollTo({ - top: (focusedIndex + 1) * itemHeight - listRef.current.clientHeight, - left: 0, - behavior: "smooth", - }); + if (focusedIndex * itemHeight < container.scrollTop) { + listRef.current.scrollTo({ + top: (focusedIndex - 1) * itemHeight, + left: 0, + behavior: "smooth", + }); + } else if ( + (focusedIndex + 1) * itemHeight > + listRef.current.scrollTop + listRef.current.clientHeight + ) { + listRef.current.scrollTo({ + top: (focusedIndex + 1) * itemHeight - listRef.current.clientHeight, + left: 0, + behavior: "smooth", + }); + } } - } + }); }, [focusedIndex]); const [springs, setSprings] = useSprings( diff --git a/app/client/src/components/propertyControls/PrimaryColumnsControlV2.tsx b/app/client/src/components/propertyControls/PrimaryColumnsControlV2.tsx index df60c0ce9b..6e43d08045 100644 --- a/app/client/src/components/propertyControls/PrimaryColumnsControlV2.tsx +++ b/app/client/src/components/propertyControls/PrimaryColumnsControlV2.tsx @@ -94,7 +94,7 @@ type State = { hasScrollableList: boolean; }; -const LIST_CLASSNAME = "tablewidget-primarycolumn-list"; +const LIST_CLASSNAME = "tablewidgetv2-primarycolumn-list"; class PrimaryColumnsControlV2 extends BaseControl { constructor(props: ControlProps) { super(props); diff --git a/app/client/src/sagas/WidgetAdditionSagas.ts b/app/client/src/sagas/WidgetAdditionSagas.ts index a455ba85a7..3db53eb9f1 100644 --- a/app/client/src/sagas/WidgetAdditionSagas.ts +++ b/app/client/src/sagas/WidgetAdditionSagas.ts @@ -69,11 +69,21 @@ function* getEntityNames() { * @returns */ function* getThemeDefaultConfig(type: string) { + const fallbackStylesheet: Record = { + TABLE_WIDGET_V2: "TABLE_WIDGET", + }; + const stylesheet: Record = yield select( getSelectedAppThemeStylesheet, ); - return stylesheet[type] || themePropertiesDefaults; + if (stylesheet[type]) { + return stylesheet[type]; + } else if (fallbackStylesheet[type] && stylesheet[fallbackStylesheet[type]]) { + return stylesheet[fallbackStylesheet[type]]; + } else { + return themePropertiesDefaults; + } } function* getChildWidgetProps( diff --git a/app/client/src/widgets/TableWidgetV2/component/cellComponents/EditActionsCell.tsx b/app/client/src/widgets/TableWidgetV2/component/cellComponents/EditActionsCell.tsx index d606a4a8dd..e9f0782c42 100644 --- a/app/client/src/widgets/TableWidgetV2/component/cellComponents/EditActionsCell.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/cellComponents/EditActionsCell.tsx @@ -58,6 +58,8 @@ export function EditActionCell(props: RenderEditActionsProps) { return ( Date: Wed, 20 Jul 2022 12:28:50 +0530 Subject: [PATCH 07/54] feat: Update the table widget to v2 in suggested widget list (#15277) Co-authored-by: balajisoundar (cherry picked from commit dbf0b94973a40103eacdf7e6867fc6bae164e750) --- .../cypress/fixtures/addWidgetTable-mock.json | 2 +- .../Bind_JSObject_Postgress_Table_spec.js | 4 ++-- .../QueryPane/AddWidgetTableAndBind_spec.js | 6 +++--- .../ServerSideTests/QueryPane/AddWidget_spec.js | 2 +- .../ServerSideTests/QueryPane/S3_spec.js | 2 +- app/client/cypress/locators/QueryEditor.json | 2 +- .../widget/propertyConfig/General.ts | 2 +- .../com/appsmith/external/models/WidgetType.java | 2 +- .../server/helpers/WidgetSuggestionHelper.java | 6 +++--- .../server/services/ce/ActionServiceCE_Test.java | 16 ++++++++-------- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app/client/cypress/fixtures/addWidgetTable-mock.json b/app/client/cypress/fixtures/addWidgetTable-mock.json index 7668852c78..38241194e1 100644 --- a/app/client/cypress/fixtures/addWidgetTable-mock.json +++ b/app/client/cypress/fixtures/addWidgetTable-mock.json @@ -45,7 +45,7 @@ ], "suggestedWidgets": [ { - "type": "TABLE_WIDGET", + "type": "TABLE_WIDGET_V2", "bindingQuery": "data" } ] diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_JSObject_Postgress_Table_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_JSObject_Postgress_Table_spec.js index 4ec54dfb01..a8ea2ace1b 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_JSObject_Postgress_Table_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_JSObject_Postgress_Table_spec.js @@ -39,7 +39,7 @@ describe("Addwidget from Query and bind with other widgets", function() { .click({ force: true }); cy.testJsontext("tabledata", "{{JSObject1.myFun1()}}"); cy.isSelectRow(1); - cy.readTabledataPublish("1", "0").then((tabData) => { + cy.readTableV2dataPublish("1", "0").then((tabData) => { let tabValue = tabData; cy.log("the value is" + tabValue); expect(tabValue).to.be.equal("5"); @@ -86,7 +86,7 @@ describe("Addwidget from Query and bind with other widgets", function() { ).then(() => cy.wait(500)); cy.isSelectRow(1); - cy.readTabledataPublish("1", "0").then((tabData) => { + cy.readTableV2dataPublish("1", "0").then((tabData) => { let tabValue = tabData; cy.log("Value in public viewing: " + tabValue); expect(tabValue).to.be.equal("5"); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidgetTableAndBind_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidgetTableAndBind_spec.js index a5b64a4ea6..fe8e8ec3a0 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidgetTableAndBind_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidgetTableAndBind_spec.js @@ -46,7 +46,7 @@ describe("Addwidget from Query and bind with other widgets", function() { cy.get(queryEditor.suggestedTableWidget).click(); cy.SearchEntityandOpen("Table1"); cy.isSelectRow(1); - cy.readTabledataPublish("1", "0").then((tabData) => { + cy.readTableV2dataPublish("1", "0").then((tabData) => { const tabValue = tabData; cy.log("the value is" + tabValue); expect(tabValue).to.be.equal("5"); @@ -70,7 +70,7 @@ describe("Addwidget from Query and bind with other widgets", function() { it("4. validation of data displayed in input widget based on row data selected", function() { cy.isSelectRow(1); - cy.readTabledataPublish("1", "0").then((tabData) => { + cy.readTableV2dataPublish("1", "0").then((tabData) => { const tabValue = tabData; cy.log("the value is" + tabValue); expect(tabValue).to.be.equal("5"); @@ -83,7 +83,7 @@ describe("Addwidget from Query and bind with other widgets", function() { }); it("5. Input widget test with default value from table widget[Bug#4136]", () => { - cy.openPropertyPane("tablewidget"); + cy.openPropertyPane("tablewidgetv2"); cy.get(".t--property-pane-title").click({ force: true }); cy.get(".t--property-pane-title") .type("TableUpdated", { delay: 300 }) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidget_spec.js index 7c82c45422..121135d114 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidget_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/AddWidget_spec.js @@ -26,7 +26,7 @@ describe("Add widget - Postgress DataSource", function() { cy.CheckAndUnfoldEntityItem("WIDGETS"); cy.selectEntityByName("Table1"); cy.isSelectRow(1); - cy.readTabledataPublish("1", "0").then((tabData) => { + cy.readTableV2dataPublish("1", "0").then((tabData) => { cy.log("the value is " + tabData); expect(tabData).to.be.equal("5"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/S3_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/S3_spec.js index 9ae09057ad..69f6bf961e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/S3_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/S3_spec.js @@ -679,7 +679,7 @@ describe("Validate CRUD queries for Amazon S3 along with UI flow verifications", cy.get(queryLocators.suggestedTableWidget) .click() .wait(1000); - cy.get(commonlocators.TableRow).validateWidgetExists(); + cy.get(commonlocators.TableV2Row).validateWidgetExists(); cy.get("@entity").then((entityN) => cy.selectEntityByName(entityN)); cy.xpath(queryLocators.suggestedWidgetText) diff --git a/app/client/cypress/locators/QueryEditor.json b/app/client/cypress/locators/QueryEditor.json index 8b44bc351d..43cd511a53 100644 --- a/app/client/cypress/locators/QueryEditor.json +++ b/app/client/cypress/locators/QueryEditor.json @@ -15,7 +15,7 @@ "settings": "li:contains('Settings')", "query": "li:contains('Query')", "switch": ".t--form-control-SWITCH input", - "suggestedTableWidget": ".t--suggested-widget-TABLE_WIDGET", + "suggestedTableWidget": ".t--suggested-widget-TABLE_WIDGET_V2", "queryResponse": "(//div[@class='table']//div[@class='tr'])[3]//div[@class='td']", "querySelect": "//div[contains(@class, 't--template-menu')]//div[text()='Select']", "queryCreate": "//div[contains(@class, 't--template-menu')]//div[text()='Create']", diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/General.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/General.ts index 44db00f120..7d7e42fe73 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/General.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/General.ts @@ -71,7 +71,7 @@ export default { }, { propertyName: "inlineEditingSaveOption", - helpText: "choose the save experience to save the edited cell", + helpText: "Choose the save experience to save the edited cell", label: "Update Mode", controlType: "DROP_DOWN", isBindProperty: true, diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/WidgetType.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/WidgetType.java index 3cdade7ff8..c33116aa44 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/WidgetType.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/WidgetType.java @@ -7,7 +7,7 @@ public enum WidgetType { TEXT_WIDGET("data"), SELECT_WIDGET("data.map( (obj) =>{ return {'label': obj.%s, 'value': obj.%s } })"), CHART_WIDGET("data.map( (obj) =>{ return {'x': obj.%s, 'y': obj.%s } })"), - TABLE_WIDGET("data"), + TABLE_WIDGET_V2("data"), INPUT_WIDGET("data"); public final String query; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/WidgetSuggestionHelper.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/WidgetSuggestionHelper.java index 0872ebfbaf..97acb907f8 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/WidgetSuggestionHelper.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/WidgetSuggestionHelper.java @@ -131,7 +131,7 @@ public class WidgetSuggestionHelper { } return getWidgetsForTypeArray(fields, numericFields); } - return List.of(getWidget(WidgetType.TABLE_WIDGET), getWidget(WidgetType.TEXT_WIDGET)); + return List.of(getWidget(WidgetType.TABLE_WIDGET_V2), getWidget(WidgetType.TEXT_WIDGET)); } /* @@ -186,7 +186,7 @@ public class WidgetSuggestionHelper { widgetTypeList.add(getWidget(WidgetType.CHART_WIDGET, fields.get(0), numericFields.get(0))); } } - widgetTypeList.add(getWidget(WidgetType.TABLE_WIDGET)); + widgetTypeList.add(getWidget(WidgetType.TABLE_WIDGET_V2)); widgetTypeList.add(getWidget(WidgetType.TEXT_WIDGET)); return widgetTypeList; } @@ -222,7 +222,7 @@ public class WidgetSuggestionHelper { widgetTypeList.add(getWidgetNestedData(WidgetType.CHART_WIDGET, nestedFieldName, fields.get(0), numericFields.get(0))); } } - widgetTypeList.add(getWidgetNestedData(WidgetType.TABLE_WIDGET, nestedFieldName)); + widgetTypeList.add(getWidgetNestedData(WidgetType.TABLE_WIDGET_V2, nestedFieldName)); widgetTypeList.add(getWidgetNestedData(WidgetType.TEXT_WIDGET, nestedFieldName)); return widgetTypeList; } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ActionServiceCE_Test.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ActionServiceCE_Test.java index 9871e9e6e0..b2a7f321a7 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ActionServiceCE_Test.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ActionServiceCE_Test.java @@ -1656,7 +1656,7 @@ public class ActionServiceCE_Test { List widgetTypeList = new ArrayList<>(); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.CHART_WIDGET, "x", "y")); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.SELECT_WIDGET, "x", "x")); - widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET)); + widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET_V2)); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TEXT_WIDGET)); mockResult.setSuggestedWidgets(widgetTypeList); @@ -1769,7 +1769,7 @@ public class ActionServiceCE_Test { List widgetTypeList = new ArrayList<>(); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.CHART_WIDGET, "id", "ppu")); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.SELECT_WIDGET, "id", "type")); - widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET)); + widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET_V2)); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TEXT_WIDGET)); mockResult.setSuggestedWidgets(widgetTypeList); @@ -1874,7 +1874,7 @@ public class ActionServiceCE_Test { List widgetTypeList = new ArrayList<>(); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.CHART_WIDGET, "url", "width")); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.SELECT_WIDGET, "url", "url")); - widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET)); + widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET_V2)); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TEXT_WIDGET)); mockResult.setSuggestedWidgets(widgetTypeList); @@ -1934,7 +1934,7 @@ public class ActionServiceCE_Test { List widgetTypeList = new ArrayList<>(); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.SELECT_WIDGET, "CarType", "carID")); - widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET)); + widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET_V2)); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TEXT_WIDGET)); mockResult.setSuggestedWidgets(widgetTypeList); @@ -2019,7 +2019,7 @@ public class ActionServiceCE_Test { mockResult.setDataTypes(List.of(new ParsedDataType(DisplayDataType.RAW))); List widgetTypeList = new ArrayList<>(); - widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET)); + widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET_V2)); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TEXT_WIDGET)); mockResult.setSuggestedWidgets(widgetTypeList); @@ -2172,7 +2172,7 @@ public class ActionServiceCE_Test { List widgetTypeList = new ArrayList<>(); widgetTypeList.add(WidgetSuggestionHelper.getWidgetNestedData(WidgetType.TEXT_WIDGET,"users")); widgetTypeList.add(WidgetSuggestionHelper.getWidgetNestedData(WidgetType.CHART_WIDGET,"users","name","id")); - widgetTypeList.add(WidgetSuggestionHelper.getWidgetNestedData(WidgetType.TABLE_WIDGET,"users")); + widgetTypeList.add(WidgetSuggestionHelper.getWidgetNestedData(WidgetType.TABLE_WIDGET_V2,"users")); widgetTypeList.add(WidgetSuggestionHelper.getWidgetNestedData(WidgetType.SELECT_WIDGET,"users","name", "status")); mockResult.setSuggestedWidgets(widgetTypeList); @@ -2379,7 +2379,7 @@ public class ActionServiceCE_Test { List widgetTypeList = new ArrayList<>(); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.CHART_WIDGET, "url", "width")); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.SELECT_WIDGET, "url", "url")); - widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET)); + widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET_V2)); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TEXT_WIDGET)); mockResult.setSuggestedWidgets(widgetTypeList); @@ -2427,7 +2427,7 @@ public class ActionServiceCE_Test { List widgetTypeList = new ArrayList<>(); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.SELECT_WIDGET, "url", "width")); - widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET)); + widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TABLE_WIDGET_V2)); widgetTypeList.add(WidgetSuggestionHelper.getWidget(WidgetType.TEXT_WIDGET)); mockResult.setSuggestedWidgets(widgetTypeList); From b20a025a368784dc378f36459e74fcb82914baef Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Wed, 20 Jul 2022 12:48:55 +0530 Subject: [PATCH 08/54] Fix method name for Redis cleaning migration (#15322) (cherry picked from commit e8be0936e651ee19c0bb054306be5cd63991628e) --- .../com/appsmith/server/migrations/DatabaseChangelog2.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java index 854f89874a..8d650aa748 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java @@ -1369,8 +1369,8 @@ public class DatabaseChangelog2 { return newWhereClause; } - @ChangeSet(order = "021", id = "flush-spring-redis-keys-2", author = "") - public void migrateGoogleSheetsToUqi(ReactiveRedisOperations reactiveRedisOperations) { + @ChangeSet(order = "021", id = "flush-spring-redis-keys-2a", author = "") + public void clearRedisCache2(ReactiveRedisOperations reactiveRedisOperations) { DatabaseChangelog.doClearRedisKeys(reactiveRedisOperations); } From 806d144b7a6d2b286ec42f7882be17f96cbddc56 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Wed, 20 Jul 2022 16:09:09 +0530 Subject: [PATCH 09/54] fix: Block the call to clear Redis sessions (#15330) (cherry picked from commit 5d166a46f1fc1c260e7956cd784ee3a52e92dbad) --- .../java/com/appsmith/server/migrations/DatabaseChangelog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java index 17eb214235..bcd72167bf 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java @@ -4469,7 +4469,7 @@ public class DatabaseChangelog { "end"; final Flux flushdb = reactiveRedisOperations.execute(RedisScript.of(script)); - flushdb.subscribe(); + flushdb.blockLast(); } /* Map values from pluginSpecifiedTemplates to formData (UQI) */ From 505d3a331dc345532d97fcdf48d4476d895be2bc Mon Sep 17 00:00:00 2001 From: Anagh Hegde Date: Wed, 27 Jul 2022 17:14:17 +0530 Subject: [PATCH 10/54] chore: NPE issue while copying in git-theming migration (#15480) ## Description > Fix the NPE issue in theming while copying the properties. ## Type of change - Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? > Locally ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes --- .../com/appsmith/server/migrations/DatabaseChangelog2.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java index cf5ed7b019..a694ab9e72 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java @@ -71,6 +71,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNestedNonNullProperties; import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNewFieldValuesIntoOldObject; import static com.appsmith.server.migrations.DatabaseChangelog.dropIndexIfExists; import static com.appsmith.server.migrations.DatabaseChangelog.ensureIndexes; @@ -1439,7 +1440,7 @@ public class DatabaseChangelog2 { Theme theme = mongockTemplate.findOne(themeQuery, Theme.class); for (Application application : applicationList) { Theme newTheme = new Theme(); - copyNewFieldValuesIntoOldObject(theme, newTheme); + copyNestedNonNullProperties(theme, newTheme); newTheme.setId(null); newTheme.setSystemTheme(false); newTheme = mongockTemplate.insert(newTheme); From fcaa6ae650342e8586055ef620789bb9f65df8dd Mon Sep 17 00:00:00 2001 From: Arpit Mohan Date: Sat, 6 Aug 2022 09:06:43 +0200 Subject: [PATCH 11/54] fix: Adding a check for invalid hosts on redirects as well (#15782) ## Description Fixes issue for checking for invalid hosts even when there are redirects in the Rest API plugin. ## Type of change - Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? - Junit test ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes (cherry picked from commit 0967b516b69a6e9438a19f8f6953cf1d7d2d9aa6) --- .../com/external/plugins/RestApiPlugin.java | 6 +++- .../external/plugins/RestApiPluginTest.java | 34 +++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java index 4f8bf13801..8b4a8de6d0 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java @@ -585,7 +585,11 @@ public class RestApiPlugin extends BasePlugin { URI redirectUri = null; try { redirectUri = new URI(redirectUrl); - } catch (URISyntaxException e) { + if (DISALLOWED_HOSTS.contains(redirectUri.getHost()) + || DISALLOWED_HOSTS.contains(InetAddress.getByName(redirectUri.getHost()).getHostAddress())) { + return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, "Host not allowed.")); + } + } catch (URISyntaxException | UnknownHostException e) { return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)); } return httpCall(webClient, httpMethod, redirectUri, finalRequestBody, iteration + 1, diff --git a/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java index cbf1801519..b7b30a0cd9 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java @@ -25,6 +25,9 @@ import io.jsonwebtoken.security.SignatureException; import net.minidev.json.JSONObject; import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.ParseException; +import okhttp3.HttpUrl; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpHeaders; @@ -34,6 +37,7 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuple2; import javax.crypto.SecretKey; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; @@ -641,7 +645,7 @@ public class RestApiPluginTest { Map> duplicateHeadersWithDsConfigOnly = getAllDuplicateHeaders(null, dsConfig); // Header duplicates - Set expectedDuplicateHeaders = new HashSet<>(); + Set expectedDuplicateHeaders = new HashSet<>(); expectedDuplicateHeaders.add("myHeader1"); expectedDuplicateHeaders.add("myHeader2"); assertTrue(expectedDuplicateHeaders.equals(duplicateHeadersWithDsConfigOnly.get(DATASOURCE_CONFIG_ONLY))); @@ -651,7 +655,7 @@ public class RestApiPluginTest { dsConfig); // Query param duplicates - Set expectedDuplicateParams = new HashSet<>(); + Set expectedDuplicateParams = new HashSet<>(); expectedDuplicateParams.add("myParam1"); expectedDuplicateParams.add("myParam2"); assertTrue(expectedDuplicateParams.equals(duplicateParamsWithDsConfigOnly.get(DATASOURCE_CONFIG_ONLY))); @@ -1004,6 +1008,32 @@ public class RestApiPluginTest { .verifyComplete(); } + @Test + public void testDenyInstanceMetadataAwsWithRedirect() throws IOException { + // Generate a mock response which redirects to the invalid host + MockWebServer mockWebServer = new MockWebServer(); + MockResponse mockRedirectResponse = new MockResponse() + .setResponseCode(301) + .addHeader("Location", "http://169.254.169.254.nip.io/latest/meta-data"); + mockWebServer.enqueue(mockRedirectResponse); + mockWebServer.start(); + + HttpUrl mockHttpUrl = mockWebServer.url("/mock/redirect"); + DatasourceConfiguration dsConfig = new DatasourceConfiguration(); + dsConfig.setUrl(mockHttpUrl.toString()); + + ActionConfiguration actionConfig = new ActionConfiguration(); + actionConfig.setHttpMethod(HttpMethod.GET); + + Mono resultMono = pluginExecutor.executeParameterized(null, new ExecuteActionDTO(), dsConfig, actionConfig); + StepVerifier.create(resultMono) + .assertNext(result -> { + assertFalse(result.getIsExecutionSuccess()); + assertEquals("Host not allowed.", result.getBody()); + }) + .verifyComplete(); + } + @Test public void testGetApiWithBody() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); From 0b347b0a5b300ec0a999f0003174a9000a8c1878 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Mon, 8 Aug 2022 21:07:15 +0530 Subject: [PATCH 12/54] fix: Adding checks to prevent disallowed hosts from connecting via Elasticsearch plugin (#15834) ## Description This PR fixes an issue where a potentially malicious user can connect to disallowed hosts from the Elasticsearch plugin within Appsmith. This is because Elasticsearch client SDK is a HTTP interface underneath the hood. ## Type of change - Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? - Junits for the following: - create datasource with disallowed host - validate datasource with disallowed host - test datasource with disallowed host ## Checklist: - [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 (cherry picked from commit c1dbca67796f635711ff84ea9f8b4365039efa39) Signed-off-by: Shrikant Sharat Kandula --- .../external/plugins/ElasticSearchPlugin.java | 234 +++++++++++------- .../plugins/ElasticSearchPluginTest.java | 188 ++++++++++++-- 2 files changed, 315 insertions(+), 107 deletions(-) diff --git a/app/server/appsmith-plugins/elasticSearchPlugin/src/main/java/com/external/plugins/ElasticSearchPlugin.java b/app/server/appsmith-plugins/elasticSearchPlugin/src/main/java/com/external/plugins/ElasticSearchPlugin.java index 06ccc82594..777ec85944 100644 --- a/app/server/appsmith-plugins/elasticSearchPlugin/src/main/java/com/external/plugins/ElasticSearchPlugin.java +++ b/app/server/appsmith-plugins/elasticSearchPlugin/src/main/java/com/external/plugins/ElasticSearchPlugin.java @@ -37,14 +37,17 @@ import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; import java.io.IOException; +import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY; import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_PATH; @@ -59,7 +62,20 @@ public class ElasticSearchPlugin extends BasePlugin { @Extension public static class ElasticSearchPluginExecutor implements PluginExecutor { - private final Scheduler scheduler = Schedulers.elastic(); + private final Scheduler scheduler = Schedulers.boundedElastic(); + + public static final String esDatasourceNotFoundMessage = "Either your host URL is invalid or the page you are trying to access does not exist"; + + public static final String esDatasourceUnauthorizedMessage = "Your username or password is not correct"; + + public static final String esDatasourceUnauthorizedPattern = ".*unauthorized.*"; + + public static final String esDatasourceNotFoundPattern = ".*(?:not.?found)|(?:refused)|(?:not.?known)|(?:timed?\\s?out).*"; + + private static final Set DISALLOWED_HOSTS = Set.of( + "169.254.169.254", + "metadata.google.internal" + ); @Override public Mono execute(RestClient client, @@ -72,64 +88,64 @@ public class ElasticSearchPlugin extends BasePlugin { List requestParams = new ArrayList<>(); return Mono.fromCallable(() -> { - final ActionExecutionResult result = new ActionExecutionResult(); + final ActionExecutionResult result = new ActionExecutionResult(); - String body = query; + String body = query; - final String path = actionConfiguration.getPath(); - requestData.put("path", path); + final String path = actionConfiguration.getPath(); + requestData.put("path", path); - HttpMethod httpMethod = actionConfiguration.getHttpMethod(); - requestData.put("method", httpMethod.name()); - requestParams.add(new RequestParamDTO("actionConfiguration.httpMethod", httpMethod.name(), null, - null, null)); - requestParams.add(new RequestParamDTO(ACTION_CONFIGURATION_PATH, path, null, null, null)); - requestParams.add(new RequestParamDTO(ACTION_CONFIGURATION_BODY, query, null, null, null)); + HttpMethod httpMethod = actionConfiguration.getHttpMethod(); + requestData.put("method", httpMethod.name()); + requestParams.add(new RequestParamDTO("actionConfiguration.httpMethod", httpMethod.name(), null, + null, null)); + requestParams.add(new RequestParamDTO(ACTION_CONFIGURATION_PATH, path, null, null, null)); + requestParams.add(new RequestParamDTO(ACTION_CONFIGURATION_BODY, query, null, null, null)); - final Request request = new Request(httpMethod.toString(), path); - ContentType contentType = ContentType.APPLICATION_JSON; + final Request request = new Request(httpMethod.toString(), path); + ContentType contentType = ContentType.APPLICATION_JSON; - if (isBulkQuery(path)) { - contentType = ContentType.create("application/x-ndjson"); + if (isBulkQuery(path)) { + contentType = ContentType.create("application/x-ndjson"); - // If body is a JSON Array, convert it to an ND-JSON string. - if (body != null && body.trim().startsWith("[")) { - final StringBuilder ndJsonBuilder = new StringBuilder(); - try { - List commands = objectMapper.readValue(body, ArrayList.class); - for (Object object : commands) { - ndJsonBuilder.append(objectMapper.writeValueAsString(object)).append("\n"); + // If body is a JSON Array, convert it to an ND-JSON string. + if (body != null && body.trim().startsWith("[")) { + final StringBuilder ndJsonBuilder = new StringBuilder(); + try { + List commands = objectMapper.readValue(body, ArrayList.class); + for (Object object : commands) { + ndJsonBuilder.append(objectMapper.writeValueAsString(object)).append("\n"); + } + } catch (IOException e) { + final String message = "Error converting array to ND-JSON: " + e.getMessage(); + log.warn(message, e); + return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, message)); + } + body = ndJsonBuilder.toString(); } - } catch (IOException e) { - final String message = "Error converting array to ND-JSON: " + e.getMessage(); - log.warn(message, e); - return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, message)); } - body = ndJsonBuilder.toString(); - } - } - if (body != null) { - request.setEntity(new NStringEntity(body, contentType)); - } + if (body != null) { + request.setEntity(new NStringEntity(body, contentType)); + } - try { - final String responseBody = new String( - client.performRequest(request).getEntity().getContent().readAllBytes()); - result.setBody(objectMapper.readValue(responseBody, HashMap.class)); - } catch (IOException e) { - final String message = "Error performing request: " + e.getMessage(); - log.warn(message, e); - return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, message)); - } + try { + final String responseBody = new String( + client.performRequest(request).getEntity().getContent().readAllBytes()); + result.setBody(objectMapper.readValue(responseBody, HashMap.class)); + } catch (IOException e) { + final String message = "Error performing request: " + e.getMessage(); + log.warn(message, e); + return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, message)); + } - result.setIsExecutionSuccess(true); - log.debug("In the Elastic Search Plugin, got action execution result"); - return Mono.just(result); - }) + result.setIsExecutionSuccess(true); + log.debug("In the Elastic Search Plugin, got action execution result"); + return Mono.just(result); + }) .flatMap(obj -> obj) .map(obj -> (ActionExecutionResult) obj) - .onErrorResume(error -> { + .onErrorResume(error -> { ActionExecutionResult result = new ActionExecutionResult(); result.setIsExecutionSuccess(false); result.setErrorInfo(error); @@ -155,55 +171,60 @@ public class ElasticSearchPlugin extends BasePlugin { @Override public Mono datasourceCreate(DatasourceConfiguration datasourceConfiguration) { - return (Mono) Mono.fromCallable(() -> { - final List hosts = new ArrayList<>(); + final List hosts = new ArrayList<>(); - for (Endpoint endpoint : datasourceConfiguration.getEndpoints()) { - URL url; - try { - url = new URL(endpoint.getHost()); - } catch (MalformedURLException e) { - return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, - "Invalid host provided. It should be of the form http(s)://your-es-url.com")); + for (Endpoint endpoint : datasourceConfiguration.getEndpoints()) { + URL url; + try { + url = new URL(endpoint.getHost()); + if (DISALLOWED_HOSTS.contains(url.getHost()) + || DISALLOWED_HOSTS.contains(InetAddress.getByName(url.getHost()).getHostAddress())) { + return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, "Invalid host provided.")); } - String scheme = "http"; - if (url.getProtocol() != null) { - scheme = url.getProtocol(); - } - - hosts.add(new HttpHost(url.getHost(), endpoint.getPort().intValue(), scheme)); + } catch (MalformedURLException e) { + return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, + "Invalid host provided. It should be of the form http(s)://your-es-url.com")); + } catch (UnknownHostException e) { + return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, + esDatasourceNotFoundMessage)); + } + String scheme = "http"; + if (url.getProtocol() != null) { + scheme = url.getProtocol(); } - final RestClientBuilder clientBuilder = RestClient.builder(hosts.toArray(new HttpHost[]{})); + hosts.add(new HttpHost(url.getHost(), endpoint.getPort().intValue(), scheme)); + } - final DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication(); - if (authentication != null - && !StringUtils.isEmpty(authentication.getUsername()) - && !StringUtils.isEmpty(authentication.getPassword())) { - final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - credentialsProvider.setCredentials( - AuthScope.ANY, - new UsernamePasswordCredentials(authentication.getUsername(), authentication.getPassword()) - ); + final RestClientBuilder clientBuilder = RestClient.builder(hosts.toArray(new HttpHost[]{})); - clientBuilder - .setHttpClientConfigCallback( - httpClientBuilder -> httpClientBuilder - .setDefaultCredentialsProvider(credentialsProvider) - ); - } + final DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication(); + if (authentication != null + && !StringUtils.isEmpty(authentication.getUsername()) + && !StringUtils.isEmpty(authentication.getPassword())) { + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + AuthScope.ANY, + new UsernamePasswordCredentials(authentication.getUsername(), authentication.getPassword()) + ); - if (!CollectionUtils.isEmpty(datasourceConfiguration.getHeaders())) { - clientBuilder.setDefaultHeaders( - (Header[]) datasourceConfiguration.getHeaders() - .stream() - .map(h -> new BasicHeader(h.getKey(), (String) h.getValue())) - .toArray() - ); - } + clientBuilder + .setHttpClientConfigCallback( + httpClientBuilder -> httpClientBuilder + .setDefaultCredentialsProvider(credentialsProvider) + ); + } - return Mono.just(clientBuilder.build()); - }) + if (!CollectionUtils.isEmpty(datasourceConfiguration.getHeaders())) { + clientBuilder.setDefaultHeaders( + (Header[]) datasourceConfiguration.getHeaders() + .stream() + .map(h -> new BasicHeader(h.getKey(), (String) h.getValue())) + .toArray() + ); + } + + return Mono.fromCallable(() -> Mono.just(clientBuilder.build())) .flatMap(obj -> obj) .subscribeOn(scheduler); } @@ -224,14 +245,21 @@ public class ElasticSearchPlugin extends BasePlugin { if (CollectionUtils.isEmpty(datasourceConfiguration.getEndpoints())) { invalids.add("No endpoint provided. Please provide a host:port where ElasticSearch is reachable."); } else { - for(Endpoint endpoint : datasourceConfiguration.getEndpoints()) { + for (Endpoint endpoint : datasourceConfiguration.getEndpoints()) { + if (endpoint.getHost() == null) { invalids.add("Missing host for endpoint"); } else { try { URL url = new URL(endpoint.getHost()); + if (DISALLOWED_HOSTS.contains(url.getHost()) + || DISALLOWED_HOSTS.contains(InetAddress.getByName(url.getHost()).getHostAddress())) { + invalids.add("Invalid host provided."); + } } catch (MalformedURLException e) { invalids.add("Invalid host provided. It should be of the form http(s)://your-es-url.com"); + } catch (UnknownHostException e) { + invalids.add(esDatasourceNotFoundMessage); } } @@ -239,6 +267,7 @@ public class ElasticSearchPlugin extends BasePlugin { invalids.add("Missing port for endpoint"); } } + } return invalids; @@ -251,16 +280,32 @@ public class ElasticSearchPlugin extends BasePlugin { if (client == null) { return new DatasourceTestResult("Null client object to ElasticSearch."); } - - // This HEAD request is to check if an index exists. It response with 200 if the index exists, + // This HEAD request is to check if the base of datasource exists. It responds with 200 if the index exists, // 404 if it doesn't. We just check for either of these two. // Ref: https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-exists.html - Request request = new Request("HEAD", "/potentially-missing-index?local=true"); + Request request = new Request("HEAD", "/"); final Response response; try { response = client.performRequest(request); } catch (IOException e) { + + /* since the 401, and 403 are registered as IOException, but for the given connection it + * in the current rest-client. We will figure out with matching patterns with regexes. + */ + + Pattern patternForUnauthorized = Pattern.compile(esDatasourceUnauthorizedPattern, Pattern.CASE_INSENSITIVE); + Pattern patternForNotFound = Pattern.compile(esDatasourceNotFoundPattern, Pattern.CASE_INSENSITIVE); + + if (patternForUnauthorized.matcher(e.getMessage()).find()) { + return new DatasourceTestResult(esDatasourceUnauthorizedMessage); + } + + if (patternForNotFound.matcher(e.getMessage()).find()) { + return new DatasourceTestResult(esDatasourceNotFoundMessage); + } + + return new DatasourceTestResult("Error running HEAD request: " + e.getMessage()); } @@ -271,8 +316,13 @@ public class ElasticSearchPlugin extends BasePlugin { } catch (IOException e) { log.warn("Error closing ElasticSearch client that was made for testing.", e); } + // earlier it was 404 and 200, now it has been changed to just expect 200 status code + // here it checks if it is anything else than 200, even 404 is not allowed! + if (statusLine.getStatusCode() == 404) { + return new DatasourceTestResult(esDatasourceNotFoundMessage); + } - if (statusLine.getStatusCode() != 404 && statusLine.getStatusCode() != 200) { + if (statusLine.getStatusCode() != 200) { return new DatasourceTestResult( "Unexpected response from ElasticSearch: " + statusLine); } diff --git a/app/server/appsmith-plugins/elasticSearchPlugin/src/test/java/com/external/plugins/ElasticSearchPluginTest.java b/app/server/appsmith-plugins/elasticSearchPlugin/src/test/java/com/external/plugins/ElasticSearchPluginTest.java index a2dadc3552..60bdda079e 100755 --- a/app/server/appsmith-plugins/elasticSearchPlugin/src/test/java/com/external/plugins/ElasticSearchPluginTest.java +++ b/app/server/appsmith-plugins/elasticSearchPlugin/src/test/java/com/external/plugins/ElasticSearchPluginTest.java @@ -1,21 +1,29 @@ package com.external.plugins; +import com.appsmith.external.constants.Authentication; +import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionResult; +import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.Endpoint; import com.appsmith.external.models.RequestParamDTO; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; import org.elasticsearch.client.Request; import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.springframework.http.HttpMethod; import org.testcontainers.elasticsearch.ElasticsearchContainer; -import org.testcontainers.utility.DockerImageName; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -39,20 +47,36 @@ public class ElasticSearchPluginTest { @ClassRule public static final ElasticsearchContainer container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:7.12.1") - .withEnv("discovery.type", "single-node"); - + .withEnv("discovery.type", "single-node") + .withPassword("esPassword"); + private static String username = "elastic"; + private static String password = "esPassword"; private static final DatasourceConfiguration dsConfig = new DatasourceConfiguration(); + private static DBAuth elasticInstanceCredentials = new DBAuth(DBAuth.Type.USERNAME_PASSWORD, username, password, null); private static String host; private static Integer port; + @BeforeClass public static void setUp() throws IOException { port = container.getMappedPort(9200); host = "http://" + container.getContainerIpAddress(); - final RestClient client = RestClient.builder( - new HttpHost(container.getContainerIpAddress(), port, "http") - ).build(); + final CredentialsProvider credentialsProvider = + new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, + new UsernamePasswordCredentials(username, password)); + + RestClient client = RestClient.builder( + new HttpHost(container.getContainerIpAddress(), port, "http")) + .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() { + @Override + public HttpAsyncClientBuilder customizeHttpClient( + HttpAsyncClientBuilder httpClientBuilder) { + return httpClientBuilder + .setDefaultCredentialsProvider(credentialsProvider); + } + }).build(); Request request; @@ -69,8 +93,11 @@ public class ElasticSearchPluginTest { client.performRequest(request); client.close(); - + elasticInstanceCredentials.setAuthenticationType(Authentication.BASIC); + elasticInstanceCredentials.setUsername(username); + elasticInstanceCredentials.setPassword(password); dsConfig.setEndpoints(List.of(new Endpoint(host, port.longValue()))); + dsConfig.setAuthentication(elasticInstanceCredentials); } private Mono execute(HttpMethod method, String path, String body) { @@ -130,7 +157,7 @@ public class ElasticSearchPluginTest { expectedRequestParams.add(new RequestParamDTO("actionConfiguration.httpMethod", HttpMethod.GET.toString(), null, null, null)); expectedRequestParams.add(new RequestParamDTO(ACTION_CONFIGURATION_PATH, "/planets/_mget", null, null, null)); - expectedRequestParams.add(new RequestParamDTO(ACTION_CONFIGURATION_BODY, contentJson, null, null, null)); + expectedRequestParams.add(new RequestParamDTO(ACTION_CONFIGURATION_BODY, contentJson, null, null, null)); assertEquals(result.getRequest().getRequestParams().toString(), expectedRequestParams.toString()); }) .verifyComplete(); @@ -208,12 +235,12 @@ public class ElasticSearchPluginTest { public void testBulkWithDirectBody() { final String contentJson = "{ \"index\" : { \"_index\" : \"test2\", \"_type\": \"doc\", \"_id\" : \"1\" } }\n" + - "{ \"field1\" : \"value1\" }\n" + - "{ \"delete\" : { \"_index\" : \"test2\", \"_type\": \"doc\", \"_id\" : \"2\" } }\n" + - "{ \"create\" : { \"_index\" : \"test2\", \"_type\": \"doc\", \"_id\" : \"3\" } }\n" + - "{ \"field1\" : \"value3\" }\n" + - "{ \"update\" : {\"_id\" : \"1\", \"_type\": \"doc\", \"_index\" : \"test2\"} }\n" + - "{ \"doc\" : {\"field2\" : \"value2\"} }\n"; + "{ \"field1\" : \"value1\" }\n" + + "{ \"delete\" : { \"_index\" : \"test2\", \"_type\": \"doc\", \"_id\" : \"2\" } }\n" + + "{ \"create\" : { \"_index\" : \"test2\", \"_type\": \"doc\", \"_id\" : \"3\" } }\n" + + "{ \"field1\" : \"value3\" }\n" + + "{ \"update\" : {\"_id\" : \"1\", \"_type\": \"doc\", \"_index\" : \"test2\"} }\n" + + "{ \"doc\" : {\"field2\" : \"value2\"} }\n"; StepVerifier.create(execute(HttpMethod.POST, "/_bulk", contentJson)) .assertNext(result -> { @@ -230,6 +257,7 @@ public class ElasticSearchPluginTest { @Test public void itShouldValidateDatasourceWithNoEndpoints() { DatasourceConfiguration invalidDatasourceConfiguration = new DatasourceConfiguration(); + invalidDatasourceConfiguration.setAuthentication(elasticInstanceCredentials); Assert.assertEquals(Set.of("No endpoint provided. Please provide a host:port where ElasticSearch is reachable."), pluginExecutor.validateDatasource(invalidDatasourceConfiguration)); @@ -238,6 +266,7 @@ public class ElasticSearchPluginTest { @Test public void itShouldValidateDatasourceWithEmptyPort() { DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setAuthentication(elasticInstanceCredentials); Endpoint endpoint = new Endpoint(); endpoint.setHost(host); datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); @@ -249,6 +278,7 @@ public class ElasticSearchPluginTest { @Test public void itShouldValidateDatasourceWithEmptyHost() { DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setAuthentication(elasticInstanceCredentials); Endpoint endpoint = new Endpoint(); endpoint.setPort(Long.valueOf(port)); datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); @@ -260,7 +290,7 @@ public class ElasticSearchPluginTest { @Test public void itShouldValidateDatasourceWithMissingEndpoint() { DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); - + datasourceConfiguration.setAuthentication(elasticInstanceCredentials); Endpoint endpoint = new Endpoint(); datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); @@ -272,6 +302,7 @@ public class ElasticSearchPluginTest { public void itShouldValidateDatasourceWithEndpointNoProtocol() { DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); Endpoint endpoint = new Endpoint(); + datasourceConfiguration.setAuthentication(elasticInstanceCredentials); endpoint.setHost("localhost"); endpoint.setPort(Long.valueOf(port)); datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); @@ -284,6 +315,7 @@ public class ElasticSearchPluginTest { @Test public void itShouldTestDatasourceWithInvalidEndpoint() { DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setAuthentication(elasticInstanceCredentials); Endpoint endpoint = new Endpoint(); endpoint.setHost("localhost"); endpoint.setPort(Long.valueOf(port)); @@ -304,4 +336,130 @@ public class ElasticSearchPluginTest { }) .verifyComplete(); } + + @Test + public void shouldVerifyUnauthorized() { + final Integer secureHostPort = container.getMappedPort(9200); + final String secureHostEndpoint = "http://" + container.getHttpHostAddress(); + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + Endpoint endpoint = new Endpoint(secureHostEndpoint, Long.valueOf(secureHostPort)); + datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); + + + StepVerifier.create(pluginExecutor.testDatasource(datasourceConfiguration) + .map(result -> { + return (Set) result.getInvalids(); + })) + .expectNext(Set.of(ElasticSearchPlugin.ElasticSearchPluginExecutor.esDatasourceUnauthorizedMessage)) + .verifyComplete(); + + } + + + @Test + public void shouldVerifyNotFound() { + final Integer secureHostPort = container.getMappedPort(9200); + final String secureHostEndpoint = "http://esdatabasenotfound.co"; + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + Endpoint endpoint = new Endpoint(secureHostEndpoint, Long.valueOf(secureHostPort)); + datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); + + StepVerifier.create(pluginExecutor.testDatasource(datasourceConfiguration) + .map(result -> { + return (Set) result.getInvalids(); + })) + .expectNext(Set.of(ElasticSearchPlugin.ElasticSearchPluginExecutor.esDatasourceNotFoundMessage)) + .verifyComplete(); + + } + + @Test + public void itShouldDenyTestDatasourceWithInstanceMetadataAws() { + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setAuthentication(elasticInstanceCredentials); + Endpoint endpoint = new Endpoint(); + endpoint.setHost("http://169.254.169.254"); + endpoint.setPort(Long.valueOf(port)); + datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); + + StepVerifier.create(pluginExecutor.testDatasource(datasourceConfiguration)) + .assertNext(result -> { + assertFalse(result.getInvalids().isEmpty()); + assertTrue(result.getInvalids().contains("Invalid host provided.")); + }) + .verifyComplete(); + } + + @Test + public void itShouldDenyTestDatasourceWithInstanceMetadataGcp() { + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setAuthentication(elasticInstanceCredentials); + Endpoint endpoint = new Endpoint(); + endpoint.setHost("http://metadata.google.internal"); + endpoint.setPort(Long.valueOf(port)); + datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); + + StepVerifier.create(pluginExecutor.testDatasource(datasourceConfiguration)) + .assertNext(result -> { + assertFalse(result.getInvalids().isEmpty()); + assertTrue(result.getInvalids().contains("Invalid host provided.")); + }) + .verifyComplete(); + } + + @Test + public void itShouldDenyCreateDatasourceWithInstanceMetadataAws() { + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setAuthentication(elasticInstanceCredentials); + Endpoint endpoint = new Endpoint(); + endpoint.setHost("http://169.254.169.254"); + endpoint.setPort(Long.valueOf(port)); + datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); + + StepVerifier.create(pluginExecutor.datasourceCreate(datasourceConfiguration)) + .verifyErrorSatisfies(e -> { + assertTrue(e instanceof AppsmithPluginException); + assertEquals("Invalid host provided.", e.getMessage()); + }); + } + + @Test + public void itShouldDenyCreateDatasourceWithInstanceMetadataGcp() { + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setAuthentication(elasticInstanceCredentials); + Endpoint endpoint = new Endpoint(); + endpoint.setHost("https://metadata.google.internal"); + endpoint.setPort(Long.valueOf(port)); + datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); + + StepVerifier.create(pluginExecutor.datasourceCreate(datasourceConfiguration)) + .verifyErrorSatisfies(e -> { + assertTrue(e instanceof AppsmithPluginException); + assertEquals("Invalid host provided.", e.getMessage()); + }); + } + + @Test + public void itShouldValidateDatasourceWithInstanceMetadataAws() { + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setAuthentication(elasticInstanceCredentials); + Endpoint endpoint = new Endpoint(); + endpoint.setHost("http://169.254.169.254"); + endpoint.setPort(Long.valueOf(port)); + datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); + + Assert.assertEquals(Set.of("Invalid host provided."), pluginExecutor.validateDatasource(datasourceConfiguration)); + } + + @Test + public void itShouldValidateDatasourceWithInstanceMetadataGcp() { + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setAuthentication(elasticInstanceCredentials); + Endpoint endpoint = new Endpoint(); + endpoint.setHost("https://metadata.google.internal"); + endpoint.setPort(Long.valueOf(port)); + datasourceConfiguration.setEndpoints(Collections.singletonList(endpoint)); + + Assert.assertEquals(Set.of("Invalid host provided."), pluginExecutor.validateDatasource(datasourceConfiguration)); + } } From 2ec11a4dee41936342a3249a48d99f6e0b9e3db5 Mon Sep 17 00:00:00 2001 From: Rishabh Rathod Date: Fri, 19 Aug 2022 19:09:07 +0530 Subject: [PATCH 13/54] fix: Autocomplete object value binding (#15999) ## Description Fixes #15950 Fixes #16141 ## Type of change - Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? **Test plan** - [ ] https://github.com/appsmithorg/TestSmith/issues/1982 - [ ] https://github.com/appsmithorg/TestSmith/issues/2049 ## Checklist: - [x] My code follows the style guidelines of this project - [ ] 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 - [ ] 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 --- .../src/utils/autocomplete/TernServer.test.ts | 53 +++++-- .../src/utils/autocomplete/TernServer.ts | 135 +++++++++++++++--- 2 files changed, 153 insertions(+), 35 deletions(-) diff --git a/app/client/src/utils/autocomplete/TernServer.test.ts b/app/client/src/utils/autocomplete/TernServer.test.ts index 08a20e62cc..0adbb04b0b 100644 --- a/app/client/src/utils/autocomplete/TernServer.test.ts +++ b/app/client/src/utils/autocomplete/TernServer.test.ts @@ -18,6 +18,7 @@ describe("Tern server", () => { doc: ({ getCursor: () => ({ ch: 0, line: 0 }), getLine: () => "{{Api.}}", + getValue: () => "{{Api.}}", } as unknown) as CodeMirror.Doc, changed: null, }, @@ -29,6 +30,7 @@ describe("Tern server", () => { doc: ({ getCursor: () => ({ ch: 0, line: 0 }), getLine: () => "a{{Api.}}", + getValue: () => "a{{Api.}}", } as unknown) as CodeMirror.Doc, changed: null, }, @@ -38,17 +40,30 @@ describe("Tern server", () => { input: { name: "test", doc: ({ - getCursor: () => ({ ch: 2, line: 0 }), - getLine: () => "a{{Api.}}", + getCursor: () => ({ ch: 10, line: 0 }), + getLine: () => "a{{Api.}}bc", + getValue: () => "a{{Api.}}bc", } as unknown) as CodeMirror.Doc, changed: null, }, - expectedOutput: "{{Api.}}", + expectedOutput: "a{{Api.}}bc", + }, + { + input: { + name: "test", + doc: ({ + getCursor: () => ({ ch: 4, line: 0 }), + getLine: () => "a{{Api.}}", + getValue: () => "a{{Api.}}", + } as unknown) as CodeMirror.Doc, + changed: null, + }, + expectedOutput: "Api.", }, ]; testCases.forEach((testCase) => { - const value = TernServer.getFocusedDynamicValue(testCase.input); + const { value } = TernServer.getFocusedDocValueAndPos(testCase.input); expect(value).toBe(testCase.expectedOutput); }); }); @@ -62,7 +77,7 @@ describe("Tern server", () => { getCursor: () => ({ ch: 0, line: 0 }), getLine: () => "{{Api.}}", somethingSelected: () => false, - getValue: () => "", + getValue: () => "{{Api.}}", } as unknown) as CodeMirror.Doc, changed: null, }, @@ -75,7 +90,7 @@ describe("Tern server", () => { getCursor: () => ({ ch: 0, line: 0 }), getLine: () => "{{Api.}}", somethingSelected: () => false, - getValue: () => "", + getValue: () => "{{Api.}}", } as unknown) as CodeMirror.Doc, changed: null, }, @@ -85,20 +100,32 @@ describe("Tern server", () => { input: { name: "test", doc: ({ - getCursor: () => ({ ch: 3, line: 0 }), + getCursor: () => ({ ch: 8, line: 0 }), getLine: () => "g {{Api.}}", somethingSelected: () => false, - getValue: () => "", + getValue: () => "g {{Api.}}", } as unknown) as CodeMirror.Doc, changed: null, }, - expectedOutput: { ch: 3, line: 0 }, + expectedOutput: { ch: 4, line: 0 }, + }, + { + input: { + name: "test", + doc: ({ + getCursor: () => ({ ch: 7, line: 1 }), + getLine: () => "c{{Api.}}", + somethingSelected: () => false, + getValue: () => "ab\nc{{Api.}}", + } as unknown) as CodeMirror.Doc, + changed: null, + }, + expectedOutput: { ch: 4, line: 0 }, }, ]; testCases.forEach((testCase) => { const request = TernServer.buildRequest(testCase.input, {}); - expect(request.query.end).toEqual(testCase.expectedOutput); }); }); @@ -115,6 +142,7 @@ describe("Tern server", () => { getCursor: () => ({ ch: 2, line: 0 }), getLine: () => "{{}}", somethingSelected: () => false, + getValue: () => "{{}}", } as unknown) as CodeMirror.Doc, }, requestCallbackData: { @@ -134,12 +162,13 @@ describe("Tern server", () => { getCursor: () => ({ ch: 3, line: 0 }), getLine: () => " {{}}", somethingSelected: () => false, + getValue: () => " {{}}", } as unknown) as CodeMirror.Doc, }, requestCallbackData: { completions: [{ name: "Api1" }], - start: { ch: 2, line: 0 }, - end: { ch: 6, line: 0 }, + start: { ch: 0, line: 0 }, + end: { ch: 4, line: 0 }, }, }, expectedOutput: { ch: 3, line: 0 }, diff --git a/app/client/src/utils/autocomplete/TernServer.ts b/app/client/src/utils/autocomplete/TernServer.ts index 810be7922e..2626bd3b54 100644 --- a/app/client/src/utils/autocomplete/TernServer.ts +++ b/app/client/src/utils/autocomplete/TernServer.ts @@ -207,20 +207,20 @@ class TernServer { } const doc = this.findDoc(cm.getDoc()); const cursor = cm.getCursor(); - const lineValue = this.lineValue(doc); - const focusedValue = this.getFocusedDynamicValue(doc); - const index = lineValue.indexOf(focusedValue); + const { extraChars } = this.getFocusedDocValueAndPos(doc); + let completions: Completion[] = []; let after = ""; const { end, start } = data; + const from = { ...start, - ch: start.ch + index, + ch: start.ch + extraChars, line: cursor.line, }; const to = { ...end, - ch: end.ch + index, + ch: end.ch + extraChars, line: cursor.line, }; if ( @@ -392,7 +392,7 @@ class TernServer { addDoc(name: string, doc: CodeMirror.Doc) { const data = { doc: doc, name: name, changed: null }; - this.server.addFile(name, this.getFocusedDynamicValue(data)); + this.server.addFile(name, this.getFocusedDocValueAndPos(data).value); CodeMirror.on(doc, "change", this.trackChange.bind(this)); return (this.docs[name] = data); } @@ -412,7 +412,13 @@ class TernServer { query.depth = 0; query.sort = true; if (query.end == null) { - query.end = pos || doc.doc.getCursor("end"); + const positions = pos || doc.doc.getCursor("end"); + const { end } = this.getFocusedDocValueAndPos(doc); + query.end = { + ...positions, + ...end, + }; + if (doc.doc.somethingSelected()) query.start = doc.doc.getCursor("start"); } const startPos = query.start || query.end; @@ -435,7 +441,7 @@ class TernServer { files.push({ type: "full", name: doc.name, - text: this.docValue(doc), + text: this.getFocusedDocValueAndPos(doc).value, }); query.file = doc.name; doc.changed = null; @@ -448,7 +454,7 @@ class TernServer { files.push({ type: "full", name: doc.name, - text: this.docValue(doc), + text: this.getFocusedDocValueAndPos(doc).value, }); } for (const name in this.docs) { @@ -457,7 +463,7 @@ class TernServer { files.push({ type: "full", name: cur.name, - text: this.docValue(cur), + text: this.getFocusedDocValueAndPos(doc).value, }); cur.changed = null; } @@ -508,7 +514,7 @@ class TernServer { { type: "full", name: doc.name, - text: this.getFocusedDynamicValue(doc), + text: this.docValue(doc), }, ], }, @@ -529,23 +535,106 @@ class TernServer { return doc.doc.getValue(); } - getFocusedDynamicValue(doc: TernDoc) { - const cursor = doc.doc.getCursor(); - const value = this.lineValue(doc); - const stringSegments = getDynamicStringSegments(value); - const dynamicStrings = stringSegments.filter((segment) => { - if (isDynamicValue(segment)) { - const index = value.indexOf(segment); + getFocusedDocValueAndPos( + doc: TernDoc, + ): { value: string; end: { line: number; ch: number }; extraChars: number } { + const cursor = doc.doc.getCursor("end"); + const value = this.docValue(doc); + const lineValue = this.lineValue(doc); + let extraChars = 0; - if (cursor.ch >= index && cursor.ch <= index + segment.length) { - return true; + const stringSegments = getDynamicStringSegments(value); + if (stringSegments.length === 1) { + return { + value, + end: { + line: cursor.line, + ch: cursor.ch, + }, + extraChars, + }; + } + + let dynamicString = value; + + let newCursorLine = cursor.line; + let newCursorPosition = cursor.ch; + + let currentLine = 0; + + for (let index = 0; index < stringSegments.length; index++) { + // segment is divided according to binding {{}} + + const segment = stringSegments[index]; + let currentSegment = segment; + if (segment.startsWith("{{")) { + currentSegment = segment.replace("{{", ""); + if (currentSegment.endsWith("}}")) { + currentSegment = currentSegment.slice(0, currentSegment.length - 2); } } - return false; - }); + // subSegment is segment further divided by EOD char (\n) + const subSegments = currentSegment.split("\n"); + const countEODCharInSegment = subSegments.length - 1; + const segmentEndLine = countEODCharInSegment + currentLine; - return dynamicStrings.length ? dynamicStrings[0] : value; + /** + * 3 case for cursor to point inside segment + * 1. cursor is before the {{ :- + * 2. cursor is inside segment :- + * - if cursor is after {{ on same line + * - if cursor is after {{ in different line + * - if cursor is before }} on same line + * 3. cursor is after the }} :- + * + */ + + const isCursorInBetweenSegmentStartAndEndLine = + cursor.line > currentLine && cursor.line < segmentEndLine; + + const isCursorAtSegmentStartLine = cursor.line === currentLine; + const isCursorAfterBindingOpenAtSegmentStart = + isCursorAtSegmentStartLine && cursor.ch > lineValue.indexOf("{{") + 1; + const isCursorAtSegmentEndLine = cursor.line === segmentEndLine; + const isCursorBeforeBindingCloseAtSegmentEnd = + isCursorAtSegmentEndLine && cursor.ch < lineValue.indexOf("}}") + 1; + + const isSegmentStartLineAndEndLineSame = currentLine === segmentEndLine; + const isCursorBetweenSingleLineSegmentBinding = + isSegmentStartLineAndEndLineSame && + isCursorBeforeBindingCloseAtSegmentEnd && + isCursorAfterBindingOpenAtSegmentStart; + + const isCursorPointingInsideSegment = + isCursorInBetweenSegmentStartAndEndLine || + (isSegmentStartLineAndEndLineSame && + isCursorBetweenSingleLineSegmentBinding); + (!isSegmentStartLineAndEndLineSame && + isCursorBeforeBindingCloseAtSegmentEnd) || + isCursorAfterBindingOpenAtSegmentStart; + + if (isDynamicValue(segment) && isCursorPointingInsideSegment) { + dynamicString = currentSegment; + newCursorLine = cursor.line - currentLine; + if (lineValue.includes("{{")) { + extraChars = lineValue.indexOf("{{") + 2; + } + newCursorPosition = cursor.ch - extraChars; + + break; + } + currentLine = segmentEndLine; + } + + return { + value: dynamicString, + end: { + line: newCursorLine, + ch: newCursorPosition, + }, + extraChars, + }; } getFragmentAround( From 1660092c7917d9b55edc4fbe250d4d91ade9badb Mon Sep 17 00:00:00 2001 From: Rishabh Rathod Date: Mon, 22 Aug 2022 13:37:03 +0530 Subject: [PATCH 14/54] fix: ternserver code (#16179) ## Description Hotfix to resolve `focusedValue` undefined error ## Type of change - Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes --- app/client/src/utils/autocomplete/TernServer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/client/src/utils/autocomplete/TernServer.ts b/app/client/src/utils/autocomplete/TernServer.ts index 2626bd3b54..4ab26a3b20 100644 --- a/app/client/src/utils/autocomplete/TernServer.ts +++ b/app/client/src/utils/autocomplete/TernServer.ts @@ -207,7 +207,9 @@ class TernServer { } const doc = this.findDoc(cm.getDoc()); const cursor = cm.getCursor(); - const { extraChars } = this.getFocusedDocValueAndPos(doc); + const { extraChars, value: focusedValue } = this.getFocusedDocValueAndPos( + doc, + ); let completions: Completion[] = []; let after = ""; From fd9719aa4e4ed947da310e5a7eb79deec9d59053 Mon Sep 17 00:00:00 2001 From: Rishabh Rathod Date: Mon, 22 Aug 2022 11:17:24 +0530 Subject: [PATCH 15/54] fix: autocomplete fixes & enhancement (#15857) --- .../Autocomplete/Autocomplete_JS_spec.ts | 179 ++++++++++--- .../Bind_MultiSelect_Button_Text_spec.js | 6 +- .../Widgets/AllInputWidgets_spec.js | 20 +- .../Widgets/AllWidgets_default_meta_spec.js | 6 +- app/client/cypress/locators/WidgetLocators.ts | 15 +- .../cypress/support/Pages/AggregateHelper.ts | 2 +- app/client/cypress/support/Pages/ApiPage.ts | 6 +- app/client/cypress/support/Pages/JSEditor.ts | 13 +- app/client/src/constants/defs/browser.json | 245 ++++++++++++++++++ app/client/src/pages/Editor/JSEditor/Form.tsx | 14 +- .../autocomplete/AutocompleteSortRules.ts | 19 ++ .../utils/autocomplete/EntityDefinitions.ts | 61 +++-- .../src/utils/autocomplete/TernServer.ts | 20 +- .../dataTreeTypeDefCreator.test.ts | 82 ++++++ .../autocomplete/dataTreeTypeDefCreator.ts | 69 +++-- .../utils/autocomplete/keywordCompletion.ts | 144 ++++++++++ 16 files changed, 789 insertions(+), 112 deletions(-) create mode 100644 app/client/src/constants/defs/browser.json create mode 100644 app/client/src/utils/autocomplete/keywordCompletion.ts diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Autocomplete/Autocomplete_JS_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Autocomplete/Autocomplete_JS_spec.ts index 8eaae35552..ba60e09412 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Autocomplete/Autocomplete_JS_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Autocomplete/Autocomplete_JS_spec.ts @@ -2,7 +2,13 @@ import { WIDGET } from "../../../../locators/WidgetLocators"; import { ObjectsRegistry } from "../../../../support/Objects/Registry"; const explorer = require("../../../../locators/explorerlocators.json"); -const { CommonLocators, EntityExplorer, JSEditor: jsEditor } = ObjectsRegistry; +const { + AggregateHelper: agHelper, + ApiPage, + CommonLocators, + EntityExplorer, + JSEditor: jsEditor, +} = ObjectsRegistry; const jsObjectBody = `export default { myVar1: [], @@ -16,12 +22,11 @@ const jsObjectBody = `export default { }`; describe("Autocomplete tests", () => { - before(() => { + it("1. Verify widgets autocomplete: ButtonGroup & Document viewer widget", () => { cy.get(explorer.addWidget).click(); - EntityExplorer.DragDropWidgetNVerify(WIDGET.BUTTON_GROUP_WIDGET, 300, 500); - }); + EntityExplorer.DragDropWidgetNVerify(WIDGET.BUTTON_GROUP, 200, 200); + EntityExplorer.DragDropWidgetNVerify(WIDGET.DOCUMENT_VIEWER, 200, 500); - it("1. ButtonGroup autocomplete & Eval shouldn't show up", () => { // create js object jsEditor.CreateJSObject(jsObjectBody, { paste: true, @@ -30,31 +35,120 @@ describe("Autocomplete tests", () => { shouldCreateNewJSObj: true, }); - const lineNumber = 5; - cy.get(`:nth-child(${lineNumber}) > .CodeMirror-line`).click(); + // focus on 5th line + cy.get(`:nth-child(5) > .CodeMirror-line`).click(); + // 1. Button group widget autocomplete verification cy.get(CommonLocators._codeMirrorTextArea) .focus() .type(`ButtonGroup1.`); - cy.get(`.CodeMirror-hints > :nth-child(1)`).contains("groupButtons"); + agHelper.AssertElementText(CommonLocators._hints, "groupButtons"); cy.get(CommonLocators._codeMirrorTextArea) .focus() .type(`groupButtons.`); - cy.get(`.CodeMirror-hints > :nth-child(1)`).contains("groupButton1"); + agHelper.AssertElementText(CommonLocators._hints, "groupButton1"); - cy.get(CommonLocators._codeMirrorTextArea).focus().type(` - eval`); + // 2. Document view widget autocomplete verification + cy.get(CommonLocators._codeMirrorTextArea) + .focus() + .type("{backspace}".repeat("ButtonGroup1.groupButtons.".length)) // remove "ButtonGroup1.groupButtons." + .wait(20) + .type(`DocumentViewer1.`); - cy.get(`.CodeMirror-hints > :nth-child(1)`).should( - "not.have.value", - "eval()", - ); + agHelper.AssertElementText(CommonLocators._hints, "docUrl"); }); - it("2. Local variables autocompletion support", () => { + it("2. Verify browser JavaScript APIs in autocomplete ", () => { + // create js object + jsEditor.CreateJSObject(jsObjectBody, { + paste: true, + completeReplace: true, + toRun: false, + shouldCreateNewJSObj: true, + prettify: false, + }); + + // focus on 5th line + cy.get(`:nth-child(5) > .CodeMirror-line`).click(); + + const JSAPIsToTest = [ + // console API verification + { + type: "console", + expected: "console", + shouldBePresent: true, + }, + // crypto API verification + { + type: "crypto", + expected: "crypto", + shouldBePresent: true, + }, + // eval function verification + { + type: "eval", + expected: "eval()", + shouldBePresent: false, + }, + { + type: "Blob", + expected: "Blob()", + shouldBePresent: true, + }, + { + type: "FormData", + expected: "FormData()", + shouldBePresent: true, + }, + { + type: "FileReader", + expected: "FileReader()", + shouldBePresent: true, + }, + ]; + + JSAPIsToTest.forEach((test, index) => { + const deleteCharCount = (JSAPIsToTest[index - 1]?.type || " ").length; + cy.get(CommonLocators._codeMirrorTextArea) + .focus() + // remove previously typed code + .type(deleteCharCount ? "{backspace}".repeat(deleteCharCount) : " ") + .wait(20) + .type(test.type); + + cy.get(CommonLocators._hints) + .eq(0) + .should( + test.shouldBePresent ? "have.text" : "not.have.text", + test.expected, + ); + }); + }); + + it("3. JSObject this. autocomplete", () => { + // create js object + jsEditor.CreateJSObject(jsObjectBody, { + paste: true, + completeReplace: true, + toRun: false, + shouldCreateNewJSObj: true, + }); + // focus on 5th line + cy.get(`:nth-child(5) > .CodeMirror-line`).click(); + + cy.get(CommonLocators._codeMirrorTextArea) + .focus() + .type("this."); + + ["myFun2()", "myVar1", "myVar2"].forEach((element, index) => { + cy.get(`.CodeMirror-hints > :nth-child(${index + 1})`).contains(element); + }); + }); + + it("4. Local variables & complex data autocompletion test", () => { // create js object jsEditor.CreateJSObject(jsObjectBody, { paste: true, @@ -65,15 +159,16 @@ describe("Autocomplete tests", () => { const lineNumber = 5; - const array = [ + const users = [ { label: "a", value: "b" }, { label: "a", value: "b" }, ]; const codeToType = ` - const arr = ${JSON.stringify(array)}; + const users = ${JSON.stringify(users)}; + const data = { userCollection: [{ users }, { users }] }; - arr.map(callBack) + users.map(callBack) `; // component re-render cause DOM element of cy.get to lost @@ -86,16 +181,36 @@ describe("Autocomplete tests", () => { .focus() .type(`${codeToType}`, { parseSpecialCharSequences: false }) .type(`{upArrow}{upArrow}`) - .type(`const callBack = (item) => item.l`); + .type(`const callBack = (user) => user.l`); - cy.get(`.CodeMirror-hints > :nth-child(1)`).contains("label"); + agHelper.AssertElementText(CommonLocators._hints, "label"); cy.get(CommonLocators._codeMirrorTextArea) .focus() - .type(`label`); + .type(`abel;`); + + cy.get(CommonLocators._codeMirrorTextArea) + .focus() + .type(`data.`); + + agHelper.AssertElementText(CommonLocators._hints, "userCollection"); + + cy.get(CommonLocators._codeMirrorTextArea) + .focus() + .type(`userCollection[0].`); + + agHelper.AssertElementText(CommonLocators._hints, "users"); + + cy.get(CommonLocators._codeMirrorTextArea) + .focus() + .type(`users[0].`); + + agHelper.AssertElementText(CommonLocators._hints, "label"); }); - it("3. JSObject this. autocomplete", () => { + it("5. Api data with array of object autocompletion test", () => { + ApiPage.CreateAndFillApi("https://mock-api.appsmith.com/users"); + ApiPage.RunAPI(); // create js object jsEditor.CreateJSObject(jsObjectBody, { paste: true, @@ -104,18 +219,18 @@ describe("Autocomplete tests", () => { shouldCreateNewJSObj: true, }); - const lineNumber = 5; - - const codeToType = "this."; - - cy.get(`:nth-child(${lineNumber}) > .CodeMirror-line`).click(); + cy.get(`:nth-child(${5}) > .CodeMirror-line`).click(); cy.get(CommonLocators._codeMirrorTextArea) .focus() - .type(`${codeToType}`); + .type("Api1.data.u"); - ["myFun2()", "myVar1", "myVar2"].forEach((element, index) => { - cy.get(`.CodeMirror-hints > :nth-child(${index + 1})`).contains(element); - }); + agHelper.AssertElementText(CommonLocators._hints, "users"); + + cy.get(CommonLocators._codeMirrorTextArea) + .focus() + .type("sers[0].e"); + + agHelper.AssertElementText(CommonLocators._hints, "email"); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_MultiSelect_Button_Text_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_MultiSelect_Button_Text_spec.js index 02660fff4d..79c97c0937 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_MultiSelect_Button_Text_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_MultiSelect_Button_Text_spec.js @@ -11,7 +11,7 @@ import { } from "../../../../locators/WidgetLocators"; const widgetsToTest = { - [WIDGET.MULTISELECT_WIDGET]: { + [WIDGET.MULTISELECT]: { testCases: [ { input: @@ -39,7 +39,7 @@ Object.entries(widgetsToTest).forEach(([widgetSelector, testConfig]) => { }); it("2. Bind Button on click and Text widget content", function() { - cy.openPropertyPane(WIDGET.BUTTON_WIDGET); + cy.openPropertyPane(WIDGET.BUTTON); cy.get(PROPERTY_SELECTOR.onClick) .find(".t--js-toggle") .click(); @@ -60,7 +60,7 @@ Object.entries(widgetsToTest).forEach(([widgetSelector, testConfig]) => { cy.wrap(item).should("contain.text", "BLUE"); }); const inputs = testConfig.testCases; - cy.get(getWidgetSelector(WIDGET.BUTTON_WIDGET)) + cy.get(getWidgetSelector(WIDGET.BUTTON)) .scrollIntoView() .click({ force: true }); cy.wait("@updateLayout"); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/AllInputWidgets_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/AllInputWidgets_spec.js index dedf4dded1..a108557947 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/AllInputWidgets_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/AllInputWidgets_spec.js @@ -9,7 +9,7 @@ import { } from "../../../../locators/WidgetLocators"; const widgetsToTest = { - [WIDGET.INPUT_WIDGET_V2]: { + [WIDGET.INPUT_V2]: { testCases: [ { input: "test", expected: "test", clearBeforeType: true }, { input: "12", expected: "test12", clearBeforeType: false }, @@ -29,7 +29,7 @@ const widgetsToTest = { widgetName: "Input widget", widgetPrefixName: "Input", }, - [WIDGET.PHONE_INPUT_WIDGET]: { + [WIDGET.PHONE_INPUT]: { testCases: [ { input: "9999999999", @@ -50,7 +50,7 @@ const widgetsToTest = { widgetName: "Phone Input widget", widgetPrefixName: "PhoneInput", }, - [WIDGET.CURRENCY_INPUT_WIDGET]: { + [WIDGET.CURRENCY_INPUT]: { testCases: [ { input: "1233", expected: "1,233", clearBeforeType: true }, { @@ -102,14 +102,14 @@ Object.entries(widgetsToTest).forEach(([widgetSelector, testConfig], index) => { cy.dragAndDropToCanvas(widgetSelector, { x: 300, y: 200 }); cy.get(getWidgetSelector(widgetSelector)).should("exist"); - cy.dragAndDropToCanvas(WIDGET.BUTTON_WIDGET, { x: 300, y: 400 }); + cy.dragAndDropToCanvas(WIDGET.BUTTON, { x: 300, y: 400 }); cy.dragAndDropToCanvas(WIDGET.TEXT, { x: 300, y: 600 }); }); it("2. StoreValue should have complete input value", () => { // if default input widget type is changed from text to any other type then uncomment below code. - // if (widgetSelector === WIDGET.INPUT_WIDGET_V2) { + // if (widgetSelector === WIDGET.INPUT_V2) { // cy.openPropertyPane(widgetSelector); // cy.selectDropdownValue(".t--property-control-datatype", "Text"); // cy.get(".t--property-control-required label") @@ -119,7 +119,7 @@ Object.entries(widgetsToTest).forEach(([widgetSelector, testConfig], index) => { // } // Set onClick action, storing value - cy.openPropertyPane(WIDGET.BUTTON_WIDGET); + cy.openPropertyPane(WIDGET.BUTTON); cy.get(PROPERTY_SELECTOR.onClick) .find(".t--js-toggle") .click(); @@ -147,7 +147,7 @@ Object.entries(widgetsToTest).forEach(([widgetSelector, testConfig], index) => { cy.get(getWidgetInputSelector(widgetSelector)).type(`${input}`); } - cy.get(getWidgetSelector(WIDGET.BUTTON_WIDGET)).click(); + cy.get(getWidgetSelector(WIDGET.BUTTON)).click(); // Assert if the Text widget contains the whole value, test cy.get(getWidgetSelector(WIDGET.TEXT)).should("have.text", expected); @@ -156,7 +156,7 @@ Object.entries(widgetsToTest).forEach(([widgetSelector, testConfig], index) => { it("3. Api params getting correct input values", () => { // Set onClick action, storing value - cy.openPropertyPane(WIDGET.BUTTON_WIDGET); + cy.openPropertyPane(WIDGET.BUTTON); // cy.get(PROPERTY_SELECTOR.onClick) // .find(".t--js-toggle") // .click(); @@ -177,7 +177,7 @@ Object.entries(widgetsToTest).forEach(([widgetSelector, testConfig], index) => { cy.get(getWidgetInputSelector(widgetSelector)).type(`${input}`); } - cy.get(getWidgetSelector(WIDGET.BUTTON_WIDGET)).click(); + cy.get(getWidgetSelector(WIDGET.BUTTON)).click(); // Assert if the Api request contains the expected value @@ -190,7 +190,7 @@ Object.entries(widgetsToTest).forEach(([widgetSelector, testConfig], index) => { }); it("4. Delete all the widgets on canvas", () => { - cy.get(getWidgetSelector(WIDGET.BUTTON_WIDGET)).click(); + cy.get(getWidgetSelector(WIDGET.BUTTON)).click(); cy.get("body").type(`{del}`, { force: true }); cy.get(getWidgetSelector(WIDGET.TEXT)).click(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/AllWidgets_default_meta_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/AllWidgets_default_meta_spec.js index 6e90d509fa..090859ec18 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/AllWidgets_default_meta_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/AllWidgets_default_meta_spec.js @@ -13,7 +13,7 @@ import { } from "../../../../locators/WidgetLocators"; const widgetsToTest = { - [WIDGET.MULTISELECT_WIDGET]: { + [WIDGET.MULTISELECT]: { widgetName: "MultiSelect", widgetPrefixName: "MultiSelect1", textBindingValue: "{{MultiSelect1.selectedOptionValues}}", @@ -61,7 +61,7 @@ const widgetsToTest = { selectAndReset(); }, }, - [WIDGET.CURRENCY_INPUT_WIDGET]: { + [WIDGET.CURRENCY_INPUT]: { widgetName: "CurrencyInput", widgetPrefixName: "CurrencyInput1", textBindingValue: testdata.currencyBindingValue, @@ -435,7 +435,7 @@ Object.entries(widgetsToTest).forEach(([widgetSelector, testConfig]) => { it("2. Bind Button on click and Text widget content", () => { // Set onClick assertWidgetReset, storing value - cy.openPropertyPane(WIDGET.BUTTON_WIDGET); + cy.openPropertyPane(WIDGET.BUTTON); cy.get(PROPERTY_SELECTOR.onClick) .find(".t--js-toggle") diff --git a/app/client/cypress/locators/WidgetLocators.ts b/app/client/cypress/locators/WidgetLocators.ts index 0b95f2cef3..ec4dd00e63 100644 --- a/app/client/cypress/locators/WidgetLocators.ts +++ b/app/client/cypress/locators/WidgetLocators.ts @@ -1,12 +1,12 @@ export const WIDGET = { - INPUT_WIDGET_V2: "inputwidgetv2", + INPUT_V2: "inputwidgetv2", TEXT: "textwidget", - PHONE_INPUT_WIDGET: "phoneinputwidget", - CURRENCY_INPUT_WIDGET: "currencyinputwidget", - BUTTON_WIDGET: "buttonwidget", - MULTISELECT_WIDGET: "multiselectwidgetv2", - BUTTON_GROUP_WIDGET: "buttongroupwidget", - TREESELECT_WIDGET: "singleselecttreewidget", + PHONE_INPUT: "phoneinputwidget", + CURRENCY_INPUT: "currencyinputwidget", + BUTTON: "buttonwidget", + MULTISELECT: "multiselectwidgetv2", + BUTTON_GROUP: "buttongroupwidget", + TREESELECT: "singleselecttreewidget", TAB: "tabswidget", TABLE: "tablewidgetv2", SWITCHGROUP: "switchgroupwidget", @@ -23,6 +23,7 @@ export const WIDGET = { PHONEINPUT: "phoneinputwidget", CAMERA: "camerawidget", FILEPICKER: "filepickerwidgetv2", + DOCUMENT_VIEWER: "documentviewerwidget", } as const; // property pane element selector are maintained here diff --git a/app/client/cypress/support/Pages/AggregateHelper.ts b/app/client/cypress/support/Pages/AggregateHelper.ts index 4e9668c5c3..f739c9a49b 100644 --- a/app/client/cypress/support/Pages/AggregateHelper.ts +++ b/app/client/cypress/support/Pages/AggregateHelper.ts @@ -110,7 +110,7 @@ export class AggregateHelper { }); } - public AssertElementText(selector: string, text: string, index: number = 0) { + public AssertElementText(selector: string, text: string, index = 0) { const locator = selector.startsWith("//") ? cy.xpath(selector) : cy.get(selector); diff --git a/app/client/cypress/support/Pages/ApiPage.ts b/app/client/cypress/support/Pages/ApiPage.ts index 2a7ac88ab0..5cd614daa5 100644 --- a/app/client/cypress/support/Pages/ApiPage.ts +++ b/app/client/cypress/support/Pages/ApiPage.ts @@ -42,7 +42,7 @@ export class ApiPage { _saveAsDS = ".t--store-as-datasource"; CreateApi( - apiName: string = "", + apiName = "", apiVerb: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" = "GET", ) { cy.get(this.locator._createNew).click({ force: true }); @@ -67,11 +67,11 @@ export class ApiPage { CreateAndFillApi( url: string, - apiname: string = "", + apiName = "", apiVerb: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" = "GET", queryTimeout = 30000, ) { - this.CreateApi(apiname, apiVerb); + this.CreateApi(apiName, apiVerb); this.EnterURL(url); this.agHelper.AssertAutoSave(); //this.agHelper.Sleep(2000);// Added because api name edit takes some time to reflect in api sidebar after the call passes. diff --git a/app/client/cypress/support/Pages/JSEditor.ts b/app/client/cypress/support/Pages/JSEditor.ts index 5c3cc56271..e7569f59b6 100644 --- a/app/client/cypress/support/Pages/JSEditor.ts +++ b/app/client/cypress/support/Pages/JSEditor.ts @@ -6,6 +6,7 @@ export interface ICreateJSObjectOptions { toRun: boolean; shouldCreateNewJSObj: boolean; lineNumber?: number; + prettify?: boolean; } const DEFAULT_CREATE_JS_OBJECT_OPTIONS = { paste: true, @@ -124,15 +125,14 @@ export class JSEditor { completeReplace, lineNumber = 4, paste, + prettify = true, shouldCreateNewJSObj, toRun, } = options; shouldCreateNewJSObj && this.NavigateToNewJSEditor(); if (!completeReplace) { - const downKeys = Array.from(new Array(lineNumber), () => "{downarrow}") - .toString() - .replaceAll(",", ""); + const downKeys = "{downarrow}".repeat(lineNumber); cy.get(this.locator._codeMirrorTextArea) .first() .focus() @@ -161,8 +161,11 @@ export class JSEditor { }); this.agHelper.AssertAutoSave(); - this.agHelper.ActionContextMenuWithInPane("Prettify Code"); - this.agHelper.AssertAutoSave(); //Ample wait due to open bug # 10284 + // Ample wait due to open bug # 10284 + if (prettify) { + this.agHelper.ActionContextMenuWithInPane("Prettify Code"); + this.agHelper.AssertAutoSave(); + } if (toRun) { //clicking 1 times & waits for 2 second for result to be populated! diff --git a/app/client/src/constants/defs/browser.json b/app/client/src/constants/defs/browser.json new file mode 100644 index 0000000000..d0a93d4764 --- /dev/null +++ b/app/client/src/constants/defs/browser.json @@ -0,0 +1,245 @@ +{ + "!name": "browser", + "console": { + "assert": { + "!type": "fn(assertion: bool, text: string)", + "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.assert", + "!doc": "Writes an error message to the console if the assertion is false." + }, + "clear": { + "!type": "fn()", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/Console/clear", + "!doc": " Clear the console." + }, + "count": { + "!type": "fn(label?: string)", + "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.count", + "!doc": "Logs the number of times that this particular call to count() has been called." + }, + "debug": "console.log", + "dir": { + "!type": "fn(object: ?)", + "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.dir", + "!doc": "Displays an interactive list of the properties of the specified JavaScript object." + }, + "error": { + "!type": "fn(...msg: ?)", + "!url": "https://developer.mozilla.org/en/docs/DOM/console.error", + "!doc": "Outputs an error message to the Web Console." + }, + "group": { + "!type": "fn(label?: string)", + "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.group", + "!doc": "Creates a new inline group in the Web Console log." + }, + "groupCollapsed": { + "!type": "fn(label?: string)", + "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.groupCollapsed", + "!doc": "Creates a new inline group in the Web Console log." + }, + "groupEnd": { + "!type": "fn()", + "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.groupEnd", + "!doc": "Exits the current inline group in the Web Console." + }, + "info": { + "!type": "fn(...msg: ?)", + "!url": "https://developer.mozilla.org/en/docs/DOM/console.info", + "!doc": "Outputs an informational message to the Web Console." + }, + "log": { + "!type": "fn(...msg: ?)", + "!url": "https://developer.mozilla.org/en/docs/DOM/console.log", + "!doc": "Outputs a message to the Web Console." + }, + "table": { + "!type": "fn(data: []|?, columns?: [])", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/Console/table", + "!doc": " Displays tabular data as a table." + }, + "time": { + "!type": "fn(label: string)", + "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.time", + "!doc": "Starts a timer you can use to track how long an operation takes." + }, + "timeEnd": { + "!type": "fn(label: string)", + "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.timeEnd", + "!doc": "Stops a timer that was previously started by calling console.time()." + }, + "trace": { + "!type": "fn()", + "!url": "https://developer.mozilla.org/en/docs/Web/API/Console.trace", + "!doc": "Outputs a stack trace to the Web Console." + }, + "warn": { + "!type": "fn(...msg: ?)", + "!url": "https://developer.mozilla.org/en/docs/DOM/console.warn", + "!doc": "Outputs a warning message to the Web Console." + }, + "!url": "https://developer.mozilla.org/en/docs/Web/API/Console", + "!doc": "The console object provides access to the browser's debugging console. The specifics of how it works vary from browser to browser, but there is a de facto set of features that are typically provided." + }, + "crypto": { + "getRandomValues": { + "!type": "fn([number])", + "!url": "https://developer.mozilla.org/en/docs/DOM/window.crypto.getRandomValues", + "!doc": "This methods lets you get cryptographically random values." + }, + "!url": "https://developer.mozilla.org/en/docs/DOM/window.crypto.getRandomValues", + "!doc": "This methods lets you get cryptographically random values." + }, + "Blob": { + "!type": "fn(parts: [?], options?: ?)", + "prototype": { + "size": { + "!type": "number", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/Blob/size", + "!doc": "The size, in bytes, of the data contained in the Blob object. Read only." + }, + "type": { + "!type": "string", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/Blob/type", + "!doc": "An ASCII-encoded string, in all lower case, indicating the MIME type of the data contained in the Blob. If the type is unknown, this string is empty. Read only." + }, + "slice": { + "!type": "fn(start: number, end?: number, type?: string) -> +Blob", + "!url": "https://developer.mozilla.org/en/docs/DOM/Blob", + "!doc": "Returns a new Blob object containing the data in the specified range of bytes of the source Blob." + } + }, + "!url": "https://developer.mozilla.org/en/docs/DOM/Blob", + "!doc": "A Blob object represents a file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system." + }, + "FileReader": { + "!type": "fn()", + "prototype": { + "abort": { + "!type": "fn()", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "Aborts the read operation. Upon return, the readyState will be DONE." + }, + "readAsArrayBuffer": { + "!type": "fn(blob: +Blob)", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "Starts reading the contents of the specified Blob, producing an ArrayBuffer." + }, + "readAsBinaryString": { + "!type": "fn(blob: +Blob)", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "Starts reading the contents of the specified Blob, producing raw binary data." + }, + "readAsDataURL": { + "!type": "fn(blob: +Blob)", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "Starts reading the contents of the specified Blob, producing a data: url." + }, + "readAsText": { + "!type": "fn(blob: +Blob, encoding?: string)", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "Starts reading the contents of the specified Blob, producing a string." + }, + "EMPTY": "number", + "LOADING": "number", + "DONE": "number", + "error": { + "!type": "?", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "The error that occurred while reading the file. Read only." + }, + "readyState": { + "!type": "number", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "Indicates the state of the FileReader. This will be one of the State constants. Read only." + }, + "result": { + "!type": "?", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "The file's contents. This property is only valid after the read operation is complete, and the format of the data depends on which of the methods was used to initiate the read operation. Read only." + }, + "onabort": { + "!type": "?", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "Called when the read operation is aborted." + }, + "onerror": { + "!type": "?", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "Called when an error occurs." + }, + "onload": { + "!type": "?", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "Called when the read operation is successfully completed." + }, + "onloadend": { + "!type": "?", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "Called when the read is completed, whether successful or not. This is called after either onload or onerror." + }, + "onloadstart": { + "!type": "?", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "Called when reading the data is about to begin." + }, + "onprogress": { + "!type": "?", + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "Called periodically while the data is being read." + } + }, + "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader", + "!doc": "The FileReader object lets web applications asynchronously read the contents of files (or raw data buffers) stored on the user's computer, using File or Blob objects to specify the file or data to read. File objects may be obtained from a FileList object returned as a result of a user selecting files using the element, from a drag and drop operation's DataTransfer object, or from the mozGetAsFile() API on an HTMLCanvasElement." + }, + "FormData": { + "!type": "fn()", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData", + "prototype": { + "append": { + "!type": "fn(name: string, value: string|+Blob, filename?: string)", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/append", + "!doc": "Appends a new value onto an existing key inside a FormData object, or adds the key if it does not already exist." + }, + "delete": { + "!type": "fn(name: string)", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/delete", + "!doc": "Deletes a key/value pair from a FormData object." + }, + "entries": { + "!type": "fn() -> +iter[:t=[number, string|+Blob]]", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/entries", + "!doc": "Returns an iterator allowing to go through all key/value pairs contained in this object." + }, + "get": { + "!type": "fn(name: string) -> string|+Blob", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/get", + "!doc": "Returns the first value associated with a given key from within a FormData object." + }, + "getAll": { + "!type": "fn(name: string) -> [string|+Blob]", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/getAll", + "!doc": "Returns an array of all the values associated with a given key from within a FormData." + }, + "has": { + "!type": "fn(name: string) -> bool", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/has", + "!doc": "Returns a boolean stating whether a FormData object contains a certain key/value pair." + }, + "set": { + "!type": "fn(name: string, value: string|+Blob, filename?: string)", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/set", + "!doc": "Sets a new value for an existing key inside a FormData object, or adds the key/value if it does not already exist." + }, + "keys": { + "!type": "fn() -> +iter[:t=number]", + "!doc": "Returns an iterator allowing to go through all keys of the key/value pairs contained in this object.", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/keys" + }, + "values": { + "!type": "fn() -> +iter[:t=string|blob]", + "!doc": "Returns an iterator allowing to go through all values of the key/value pairs contained in this object.", + "!url": "https://developer.mozilla.org/en-US/docs/Web/API/FormData/values" + } + } + } +} \ No newline at end of file diff --git a/app/client/src/pages/Editor/JSEditor/Form.tsx b/app/client/src/pages/Editor/JSEditor/Form.tsx index b4a307b557..cc312a06a5 100644 --- a/app/client/src/pages/Editor/JSEditor/Form.tsx +++ b/app/client/src/pages/Editor/JSEditor/Form.tsx @@ -170,7 +170,13 @@ function JSEditorForm({ jsCollection: currentJSCollection }: Props) { event: React.MouseEvent | KeyboardEvent, ) => { event.preventDefault(); - selectedJSActionOption.data && executeJSAction(selectedJSActionOption.data); + if ( + !disableRunFunctionality && + !isExecutingCurrentJSAction && + selectedJSActionOption.data + ) { + executeJSAction(selectedJSActionOption.data); + } }; useEffect(() => { @@ -179,9 +185,13 @@ function JSEditorForm({ jsCollection: currentJSCollection }: Props) { } else { setDisableRunFunctionality(false); } - setSelectedJSActionOption(getJSActionOption(activeJSAction, jsActions)); }, [parseErrors, jsActions, activeJSActionId]); + useEffect(() => { + // update the selectedJSActionOption when there is addition or removal of jsAction or function + setSelectedJSActionOption(getJSActionOption(activeJSAction, jsActions)); + }, [jsActions, activeJSActionId]); + const blockCompletions = useMemo(() => { if (selectedJSActionOption.label) { const funcName = `${selectedJSActionOption.label}()`; diff --git a/app/client/src/utils/autocomplete/AutocompleteSortRules.ts b/app/client/src/utils/autocomplete/AutocompleteSortRules.ts index 828bca5309..68a3ec55b8 100644 --- a/app/client/src/utils/autocomplete/AutocompleteSortRules.ts +++ b/app/client/src/utils/autocomplete/AutocompleteSortRules.ts @@ -43,6 +43,24 @@ export class AndRule implements AutocompleteRule { } } +/** + * Set score to -Infinity for internal defs to be hidden from autocompletion like $__dropdownOption__$ + * Max score - 0 + * Min score - -Infinity + */ +class HideInternalDefsRule implements AutocompleteRule { + static threshold = -Infinity; + + computeScore(completion: Completion): number { + let score = 0; + + if (completion.text.includes("$__") && completion.text.includes("__$")) { + score = HideInternalDefsRule.threshold; + } + return score; + } +} + /** * Set score to -Infinity for paths to be blocked from autocompletion * Max score - 0 @@ -280,6 +298,7 @@ export class ScoredCompletion { new JSLibraryRule(), new GlobalJSRule(), new BlockSuggestionsRule(), + new HideInternalDefsRule(), ]; completion: Completion; diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts index 7c054a6ebc..637a82cf03 100644 --- a/app/client/src/utils/autocomplete/EntityDefinitions.ts +++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts @@ -1,4 +1,7 @@ -import { generateTypeDef } from "utils/autocomplete/dataTreeTypeDefCreator"; +import { + ExtraDef, + generateTypeDef, +} from "utils/autocomplete/dataTreeTypeDefCreator"; import { DataTreeAction, DataTreeAppsmith, @@ -6,6 +9,7 @@ import { import _ from "lodash"; import { EVALUATION_PATH } from "utils/DynamicBindingUtils"; import { JSCollectionData } from "reducers/entityReducers/jsActionsReducer"; +import { Def } from "tern"; import { ButtonGroupWidgetProps } from "widgets/ButtonGroupWidget/widget"; const isVisible = { @@ -14,9 +18,10 @@ const isVisible = { }; export const entityDefinitions = { - APPSMITH: (entity: DataTreeAppsmith) => { + APPSMITH: (entity: DataTreeAppsmith, extraDefsToDefine: ExtraDef) => { const generatedTypeDef = generateTypeDef( _.omit(entity, "ENTITY_TYPE", EVALUATION_PATH), + extraDefsToDefine, ); if ( typeof generatedTypeDef === "object" && @@ -39,11 +44,13 @@ export const entityDefinitions = { } return generatedTypeDef; }, - ACTION: (entity: DataTreeAction) => { - const dataDef = generateTypeDef(entity.data); - let data: Record = { + ACTION: (entity: DataTreeAction, extraDefsToDefine: ExtraDef) => { + const dataDef = generateTypeDef(entity.data, extraDefsToDefine); + + let data: Def = { "!doc": "The response of the action", }; + if (_.isString(dataDef)) { data["!type"] = dataDef; } else { @@ -59,8 +66,7 @@ export const entityDefinitions = { "!doc": "The response meta of the action", "!type": "?", }, - run: - "fn(onSuccess: fn() -> void, onError: fn() -> void) -> +Promise[:t=[!0..:t]]", + run: "fn(params: ?) -> +Promise[:t=[!0..:t]]", clear: "fn() -> +Promise[:t=[!0..:t]]", }; }, @@ -102,17 +108,20 @@ export const entityDefinitions = { "!doc": "Selected country code for Currency type input", }, }, - TABLE_WIDGET: (widget: any) => ({ + TABLE_WIDGET: (widget: any, extraDefsToDefine?: ExtraDef) => ({ "!doc": "The Table is the hero widget of Appsmith. You can display data from an API in a table, trigger an action when a user selects a row and even work with large paginated data sets", "!url": "https://docs.appsmith.com/widget-reference/table", - selectedRow: generateTypeDef(widget.selectedRow), - selectedRows: generateTypeDef(widget.selectedRows), + selectedRow: generateTypeDef(widget.selectedRow, extraDefsToDefine), + selectedRows: generateTypeDef(widget.selectedRows, extraDefsToDefine), selectedRowIndices: generateTypeDef(widget.selectedRowIndices), triggeredRow: generateTypeDef(widget.triggeredRow), selectedRowIndex: "number", - tableData: generateTypeDef(widget.tableData), - filteredTableData: generateTypeDef(widget.filteredTableData), + tableData: generateTypeDef(widget.tableData, extraDefsToDefine), + filteredTableData: generateTypeDef( + widget.filteredTableData, + extraDefsToDefine, + ), pageNo: "number", pageSize: "number", isVisible: isVisible, @@ -123,17 +132,17 @@ export const entityDefinitions = { order: ["asc", "desc"], }, }), - TABLE_WIDGET_V2: (widget: any) => ({ + TABLE_WIDGET_V2: (widget: any, extraDefsToDefine?: ExtraDef) => ({ "!doc": "The Table is the hero widget of Appsmith. You can display data from an API in a table, trigger an action when a user selects a row and even work with large paginated data sets", "!url": "https://docs.appsmith.com/widget-reference/table", - selectedRow: generateTypeDef(widget.selectedRow), - selectedRows: generateTypeDef(widget.selectedRows), + selectedRow: generateTypeDef(widget.selectedRow, extraDefsToDefine), + selectedRows: generateTypeDef(widget.selectedRows, extraDefsToDefine), selectedRowIndices: generateTypeDef(widget.selectedRowIndices), triggeredRow: generateTypeDef(widget.triggeredRow), updatedRow: generateTypeDef(widget.updatedRow), selectedRowIndex: "number", - tableData: generateTypeDef(widget.tableData), + tableData: generateTypeDef(widget.tableData, extraDefsToDefine), pageNo: "number", pageSize: "number", isVisible: isVisible, @@ -143,7 +152,7 @@ export const entityDefinitions = { column: "string", order: ["asc", "desc"], }, - updatedRows: generateTypeDef(widget.updatedRows), + updatedRows: generateTypeDef(widget.updatedRows, extraDefsToDefine), updatedRowIndices: generateTypeDef(widget.updatedRowIndices), triggeredRowIndex: generateTypeDef(widget.triggeredRowIndex), }), @@ -341,12 +350,12 @@ export const entityDefinitions = { yAxisName: "string", selectedDataPoint: "$__chartDataPoint__$", }, - FORM_WIDGET: (widget: any) => ({ + FORM_WIDGET: (widget: any, extraDefsToDefine?: ExtraDef) => ({ "!doc": "Form is used to capture a set of data inputs from a user. Forms are used specifically because they reset the data inputs when a form is submitted and disable submission for invalid data inputs", "!url": "https://docs.appsmith.com/widget-reference/form", isVisible: isVisible, - data: generateTypeDef(widget.data), + data: generateTypeDef(widget.data, extraDefsToDefine), hasChanges: "bool", }), FORM_BUTTON_WIDGET: { @@ -389,7 +398,7 @@ export const entityDefinitions = { files: "[$__file__$]", isDisabled: "bool", }, - LIST_WIDGET: (widget: any) => ({ + LIST_WIDGET: (widget: any, extraDefsToDefine?: ExtraDef) => ({ "!doc": "Containers are used to group widgets together to form logical higher order widgets. Containers let you organize your page better and move all the widgets inside them together.", "!url": "https://docs.appsmith.com/widget-reference/list", @@ -399,9 +408,9 @@ export const entityDefinitions = { }, isVisible: isVisible, gridGap: "number", - selectedItem: generateTypeDef(widget.selectedItem), - items: generateTypeDef(widget.items), - listData: generateTypeDef(widget.listData), + selectedItem: generateTypeDef(widget.selectedItem, extraDefsToDefine), + items: generateTypeDef(widget.items, extraDefsToDefine), + listData: generateTypeDef(widget.listData, extraDefsToDefine), pageNo: generateTypeDef(widget.pageNo), pageSize: generateTypeDef(widget.pageSize), }), @@ -632,6 +641,12 @@ export const entityDefinitions = { isVisible: isVisible, progress: "number", }, + DOCUMENT_VIEWER_WIDGET: { + "!doc": "Document viewer widget is used to show documents on a page", + "!url": "https://docs.appsmith.com/reference/widgets/document-viewer", + isVisible: isVisible, + docUrl: "string", + }, }; /* diff --git a/app/client/src/utils/autocomplete/TernServer.ts b/app/client/src/utils/autocomplete/TernServer.ts index 4ab26a3b20..b915ed3e11 100644 --- a/app/client/src/utils/autocomplete/TernServer.ts +++ b/app/client/src/utils/autocomplete/TernServer.ts @@ -7,6 +7,7 @@ import base64 from "constants/defs/base64-js.json"; import moment from "constants/defs/moment.json"; import xmlJs from "constants/defs/xmlParser.json"; import forge from "constants/defs/forge.json"; +import browser from "constants/defs/browser.json"; import CodeMirror, { Hint, Pos, cmpPos } from "codemirror"; import { getDynamicStringSegments, @@ -19,10 +20,12 @@ import { import { FieldEntityInformation } from "components/editorComponents/CodeEditor/EditorConfig"; import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory"; import { AutocompleteSorter } from "./AutocompleteSortRules"; +import { getCompletionsForKeyword } from "./keywordCompletion"; const DEFS: Def[] = [ // @ts-expect-error: Types are not available ecma, + browser, GLOBAL_FUNCTIONS, GLOBAL_DEFS, lodash, @@ -38,7 +41,7 @@ const hintDelay = 1700; export type Completion = Hint & { origin: string; - type: AutocompleteDataType; + type: AutocompleteDataType | string; data: { doc: string; }; @@ -231,6 +234,15 @@ class TernServer { ) { after = '"]'; } + // Actual char space + const trimmedFocusedValueLength = focusedValue.trim().length; + // end.ch counts tab space as 1 instead of 2 space chars in string + // For eg: lets take string ` ab`. Here, end.ch = 3 & trimmedFocusedValueLength = 2 + // hence tabSpacesCount = end.ch - trimmedFocusedValueLength + const tabSpacesCount = end.ch - trimmedFocusedValueLength; + const cursorHorizontalPos = + tabSpacesCount * 2 + trimmedFocusedValueLength - 2; + for (let i = 0; i < data.completions.length; ++i) { const completion = data.completions[i]; let className = typeToIcon(completion.type, completion.isKeyword); @@ -258,6 +270,12 @@ class TernServer { element.setAttribute("keyword", data.displayText); element.innerHTML = data.displayText; }; + // Add relevant keyword completions + const keywordCompletions = getCompletionsForKeyword( + codeMirrorCompletion, + cursorHorizontalPos, + ); + completions = [...completions, ...keywordCompletions]; } completions.push(codeMirrorCompletion); } diff --git a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.test.ts b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.test.ts index d5cafa9047..09ce377cee 100644 --- a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.test.ts +++ b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.test.ts @@ -88,6 +88,88 @@ describe("dataTreeTypeDefCreator", () => { expect(objType).toStrictEqual(expected); }); + it("creates a correct def for a complex array of object", () => { + const data = [ + { + nested: [ + { + nested: [ + { + nested: [ + { + nested: [ + { + nested: [ + { + nested: [ + { + name: "", + email: "", + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ]; + const expected = "[def_6]"; + + const extraDef = {}; + const expectedExtraDef = { + def_1: { nested: "[?]" }, + def_2: { nested: "[def_1]" }, + def_3: { nested: "[def_2]" }, + def_4: { nested: "[def_3]" }, + def_5: { nested: "[def_4]" }, + def_6: { nested: "[def_5]" }, + }; + + const dataType = generateTypeDef(data, extraDef); + + expect(dataType).toStrictEqual(expected); + + expect(extraDef).toStrictEqual(expectedExtraDef); + + const extraDef2 = {}; + const expected2 = "[def_10]"; + const dataType2 = generateTypeDef( + data[0].nested[0].nested[0].nested, + extraDef2, + ); + + const expectedExtraDef2 = { + def_7: { name: "string", email: "string" }, + def_8: { nested: "[def_7]" }, + def_9: { nested: "[def_8]" }, + def_10: { nested: "[def_9]" }, + }; + + expect(dataType2).toStrictEqual(expected2); + expect(extraDef2).toStrictEqual(expectedExtraDef2); + }); + + it("creates a correct def for an array of array of object", () => { + const array = [[{ name: "", email: "" }]]; + const expected = "[[def_11]]"; + + const extraDefsToDefine = {}; + const expectedExtraDef = { + def_11: { name: "string", email: "string" }, + }; + + const objType = generateTypeDef(array, extraDefsToDefine); + + expect(objType).toStrictEqual(expected); + expect(extraDefsToDefine).toStrictEqual(expectedExtraDef); + }); + it("flatten def", () => { const def = { entity1: { diff --git a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts index b7f9a685bf..b59494285f 100644 --- a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts +++ b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts @@ -1,5 +1,5 @@ import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory"; -import { get, isFunction } from "lodash"; +import { uniqueId, get, isFunction, isObject } from "lodash"; import { entityDefinitions } from "utils/autocomplete/EntityDefinitions"; import { getType, Types } from "utils/TypeHelpers"; import { Def } from "tern"; @@ -11,11 +11,11 @@ import { isWidget, } from "workers/evaluationUtils"; import { DataTreeDefEntityInformation } from "utils/autocomplete/TernServer"; + +export type ExtraDef = Record; + import { Variable } from "entities/JSCollection"; -// When there is a complex data type, we store it in extra def and refer to it -// in the def -let extraDefs: Def = {}; // Def names are encoded with information about the entity // This so that we have more info about them // when sorting results in autocomplete @@ -26,6 +26,9 @@ export const dataTreeTypeDefCreator = ( dataTree: DataTree, isJSEditorEnabled: boolean, ): { def: Def; entityInfo: Map } => { + // When there is a complex data type, we store it in extra def and refer to it in the def + const extraDefsToDefine: Def = {}; + const def: Def = { "!name": "DATA_TREE", }; @@ -37,7 +40,7 @@ export const dataTreeTypeDefCreator = ( if (widgetType in entityDefinitions) { const definition = get(entityDefinitions, widgetType); if (isFunction(definition)) { - def[entityName] = definition(entity); + def[entityName] = definition(entity, extraDefsToDefine); } else { def[entityName] = definition; } @@ -48,21 +51,21 @@ export const dataTreeTypeDefCreator = ( }); } } else if (isAction(entity)) { - def[entityName] = entityDefinitions.ACTION(entity); + def[entityName] = entityDefinitions.ACTION(entity, extraDefsToDefine); flattenDef(def, entityName); entityMap.set(entityName, { type: ENTITY_TYPE.ACTION, subType: "ACTION", }); } else if (isAppsmithEntity(entity)) { - def.appsmith = (entityDefinitions.APPSMITH as any)(entity); + def.appsmith = entityDefinitions.APPSMITH(entity, extraDefsToDefine); entityMap.set("appsmith", { type: ENTITY_TYPE.APPSMITH, subType: ENTITY_TYPE.APPSMITH, }); } else if (isJSAction(entity) && isJSEditorEnabled) { const metaObj = entity.meta; - const jsProperty: Def = {}; + const jsPropertiesDef: Def = {}; for (const key in metaObj) { // const jsFunctionObj = metaObj[key]; @@ -72,42 +75,64 @@ export const dataTreeTypeDefCreator = ( // we will also need to check performance implications here const argsTypeString = getFunctionsArgsType([]); - jsProperty[key] = argsTypeString; + jsPropertiesDef[key] = argsTypeString; } for (let i = 0; i < entity.variables.length; i++) { const varKey = entity.variables[i]; const varValue = entity[varKey]; - jsProperty[varKey] = generateTypeDef(varValue); + jsPropertiesDef[varKey] = generateTypeDef(varValue, extraDefsToDefine); } - def[entityName] = jsProperty; + def[entityName] = jsPropertiesDef; flattenDef(def, entityName); entityMap.set(entityName, { type: ENTITY_TYPE.JSACTION, subType: "JSACTION", }); } - if (Object.keys(extraDefs)) { - def["!define"] = { ...extraDefs }; - extraDefs = {}; - } }); + if (Object.keys(extraDefsToDefine)) { + def["!define"] = { ...extraDefsToDefine }; + } + return { def, entityInfo: entityMap }; }; -export function generateTypeDef(obj: any): string | Def { - const type = getType(obj); - switch (type) { +export function generateTypeDef( + value: unknown, + extraDefsToDefine?: ExtraDef, + depth = 0, +): Def | string { + switch (getType(value)) { case Types.ARRAY: { - const arrayType = getType(obj[0]); - return `[${arrayType}]`; + const array = value as [unknown]; + if (depth > 5) { + return `[?]`; + } + + const arrayElementType = generateTypeDef( + array[0], + extraDefsToDefine, + depth + 1, + ); + + if (isObject(arrayElementType)) { + if (extraDefsToDefine) { + const uniqueDefName = uniqueId("def_"); + extraDefsToDefine[uniqueDefName] = arrayElementType; + return `[${uniqueDefName}]`; + } + return `[?]`; + } + return `[${arrayElementType}]`; } case Types.OBJECT: { const objType: Def = {}; - Object.keys(obj).forEach((k) => { - objType[k] = generateTypeDef(obj[k]); + const object = value as Record; + Object.keys(object).forEach((k) => { + objType[k] = generateTypeDef(object[k], extraDefsToDefine, depth); }); return objType; } diff --git a/app/client/src/utils/autocomplete/keywordCompletion.ts b/app/client/src/utils/autocomplete/keywordCompletion.ts new file mode 100644 index 0000000000..564aa960b3 --- /dev/null +++ b/app/client/src/utils/autocomplete/keywordCompletion.ts @@ -0,0 +1,144 @@ +import { Completion } from "./TernServer"; + +export const getCompletionsForKeyword = ( + completion: Completion, + cursorHorizontalPos: number, +) => { + const keywordName = completion.text; + // indentation needs to be positive number + const indentation = cursorHorizontalPos < 0 ? 0 : cursorHorizontalPos; + const indentationSpace = " ".repeat(indentation); + + const completions = []; + switch (keywordName) { + // loops + case "for": + completions.push({ + ...completion, + name: "for-loop", + text: `for(let i=0;i < array.length;i++){\n${indentationSpace}\tconst element = array[i];\n${indentationSpace}}`, + render: (element: HTMLElement) => { + element.setAttribute("keyword", "For Loop"); + element.innerHTML = completion.text; + }, + }); + completions.push({ + ...completion, + name: "for-in-loop", + text: `for(const key in object) {\n${indentationSpace}}`, + render: (element: HTMLElement) => { + element.setAttribute("keyword", "For-in Loop"); + element.innerHTML = "forin"; + }, + }); + completions.push({ + ...completion, + name: "for-of-loop", + text: `for(const iterator of object){\n${indentationSpace}}`, + render: (element: HTMLElement) => { + element.setAttribute("keyword", "For-of Loop"); + element.innerHTML = "forof"; + }, + }); + break; + + case "while": + completions.push({ + ...completion, + name: "while-loop", + text: `while(condition){\n${indentationSpace}}`, + render: (element: HTMLElement) => { + element.setAttribute("keyword", "While Statement"); + element.innerHTML = completion.text; + }, + }); + break; + + case "do": + completions.push({ + ...completion, + name: "do-while-statement", + text: `do{\n\n${indentationSpace}} while (condition);`, + render: (element: HTMLElement) => { + element.setAttribute("keyword", "do-While Statement"); + element.innerHTML = completion.text; + }, + }); + break; + + // conditional statement + case "if": + completions.push({ + ...completion, + name: "if-statement", + text: `if(condition){\n\n${indentationSpace}}`, + render: (element: HTMLElement) => { + element.setAttribute("keyword", "if Statement"); + element.innerHTML = completion.text; + }, + }); + + break; + case "switch": + completions.push({ + ...completion, + name: "switch-statement", + text: `switch(key){\n${indentationSpace}\tcase value:\n${indentationSpace}\t\tbreak;\n${indentationSpace}\tdefault:\n${indentationSpace}\t\tbreak;\n${indentationSpace}}`, + render: (element: HTMLElement) => { + element.setAttribute("keyword", "Switch Statement"); + element.innerHTML = completion.text; + }, + }); + + break; + case "function": + completions.push({ + ...completion, + name: "function-statement", + text: `function name(params){\n\n${indentationSpace}}`, + render: (element: HTMLElement) => { + element.setAttribute("keyword", "Function Statement"); + element.innerHTML = completion.text; + }, + }); + + break; + case "try": + completions.push({ + ...completion, + name: "try-catch", + text: `try{\n\n${indentationSpace}}catch(error){\n\n${indentationSpace}}`, + render: (element: HTMLElement) => { + element.setAttribute("keyword", "Try-catch Statement"); + element.innerHTML = "try-catch"; + }, + }); + break; + + case "throw": + completions.push({ + ...completion, + name: "throw-exception", + text: `throw new Error("");`, + render: (element: HTMLElement) => { + element.setAttribute("keyword", "Throw Exception"); + element.innerHTML = completion.text; + }, + }); + break; + case "new": + completions.push({ + ...completion, + name: "new-statement", + text: `const name = new type(arguments);`, + render: (element: HTMLElement) => { + element.setAttribute("keyword", "new Statement"); + element.innerHTML = completion.text; + }, + }); + + break; + } + + return completions; +}; From 662daa505c23417e90a3f61812e0a3f70b9270d7 Mon Sep 17 00:00:00 2001 From: Parthvi <80334441+Parthvi12@users.noreply.github.com> Date: Tue, 13 Sep 2022 12:50:09 +0530 Subject: [PATCH 16/54] test: update cypress test fork_template_spec (#16719) Co-authored-by: Parthvi Goswami --- .../ClientSideTests/OtherUIFeatures/Fork_Template_spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Fork_Template_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Fork_Template_spec.js index 545fa54a31..93be7987aa 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Fork_Template_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Fork_Template_spec.js @@ -5,10 +5,10 @@ describe("Fork a template to an workspace", () => { it("Fork a template to an workspace", () => { cy.NavigateToHome(); cy.get(templateLocators.templatesTab).click(); - cy.get(templateLocators.templateForkButton) - .first() - .click(); - + cy.wait(1000); + cy.xpath( + "//div[text()='Customer Support Dashboard']/following-sibling::div//button[contains(@class, 'fork-button')]", + ).click(); cy.get(templateLocators.dialogForkButton).click(); cy.get(commonlocators.canvas).should("be.visible"); }); From f7e69f59848adf73fb85bf7634785858c7ba8faf Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Tue, 13 Sep 2022 13:07:12 +0530 Subject: [PATCH 17/54] chore: Added username to error logs when signup is disabled (#16720) --- .../java/com/appsmith/server/exceptions/AppsmithError.java | 2 +- .../com/appsmith/server/services/ce/UserServiceCEImpl.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java index 975b9b064d..78d679ccc0 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java @@ -111,7 +111,7 @@ public enum AppsmithError { MARKETPLACE_NOT_CONFIGURED(500, 5007, "Marketplace is not configured.", AppsmithErrorAction.DEFAULT, null, ErrorType.CONFIGURATION_ERROR, null), PAYLOAD_TOO_LARGE(413, 4028, "The request payload is too large. Max allowed size for request payload is {0} KB", AppsmithErrorAction.DEFAULT, null, ErrorType.CONNECTIVITY_ERROR, null), - SIGNUP_DISABLED(403, 4033, "Signup is restricted on this instance of Appsmith. Please contact the administrator to get an invite.", + SIGNUP_DISABLED(403, 4033, "Signup is restricted on this instance of Appsmith. Please contact the administrator to get an invite for user {0}.", AppsmithErrorAction.DEFAULT, null, ErrorType.INTERNAL_ERROR, null), FAIL_UPDATE_USER_IN_SESSION(500, 5008, "Unable to update user in session.", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null), APPLICATION_FORKING_NOT_ALLOWED(403, 4034, "Forking this application is not permitted at this time.", AppsmithErrorAction.DEFAULT, null, ErrorType.INTERNAL_ERROR, null), diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java index 7aa65d24d9..c8c2cf014c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java @@ -534,7 +534,7 @@ public class UserServiceCEImpl extends BaseService if (commonConfig.isSignupDisabled()) { // Signing up has been globally disabled. Reject. - return Mono.error(new AppsmithException(AppsmithError.SIGNUP_DISABLED)); + return Mono.error(new AppsmithException(AppsmithError.SIGNUP_DISABLED, user.getUsername())); } final List allowedDomains = user.getSource() == LoginSource.FORM @@ -546,7 +546,7 @@ public class UserServiceCEImpl extends BaseService && !allowedDomains.contains(user.getEmail().split("@")[1])) { // There is an explicit whitelist of email address domains that should be allowed. If the new email is // of a different domain, reject. - return Mono.error(new AppsmithException(AppsmithError.SIGNUP_DISABLED)); + return Mono.error(new AppsmithException(AppsmithError.SIGNUP_DISABLED, user.getUsername())); } } else { isAdminUser = true; From d02e17cd075cbd48b920396a7661684863f19255 Mon Sep 17 00:00:00 2001 From: Jacob Gillespie Date: Tue, 13 Sep 2022 15:39:41 +0100 Subject: [PATCH 18/54] ci: fix tag reference to `github.sha` in depot build action (#16735) --- .github/workflows/test-build-docker-image.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-build-docker-image.yml b/.github/workflows/test-build-docker-image.yml index f2ded48503..4316161f92 100644 --- a/.github/workflows/test-build-docker-image.yml +++ b/.github/workflows/test-build-docker-image.yml @@ -1582,7 +1582,7 @@ jobs: context: app/client push: true tags: | - ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-editor:${GITHUB_SHA} + ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-editor:${{ github.sha }} ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-editor:nightly - name: Build and push release image to Docker Hub @@ -1607,7 +1607,7 @@ jobs: build-args: | APPSMITH_SEGMENT_CE_KEY=${{ secrets.APPSMITH_SEGMENT_CE_KEY }} tags: | - ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-ce:${GITHUB_SHA} + ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-ce:${{ github.sha }} ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-ce:nightly # - name: Check and push fat image to Docker Hub with commit tag @@ -1654,7 +1654,7 @@ jobs: build-args: | APPSMITH_SEGMENT_CE_KEY=${{ secrets.APPSMITH_SEGMENT_CE_KEY }} tags: | - ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:${GITHUB_SHA} + ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:${{ github.sha }} ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:nightly # Build release Docker image and push to Docker Hub @@ -1675,5 +1675,5 @@ jobs: context: app/rts push: true tags: | - ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-rts:${GITHUB_SHA} + ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-rts:${{ github.sha }} ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-rts:nightly From 3f978437a0d65dd51dd04c8070b9d867085c376f Mon Sep 17 00:00:00 2001 From: yatinappsmith <84702014+yatinappsmith@users.noreply.github.com> Date: Sun, 18 Sep 2022 12:38:40 +0530 Subject: [PATCH 19/54] ci: Fix ci cache & rts build for push workflow (#16850) * fix download rts * fix cache for ui-test --- .github/workflows/test-build-docker-image.yml | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-build-docker-image.yml b/.github/workflows/test-build-docker-image.yml index 64cddad0da..b3163e8e59 100644 --- a/.github/workflows/test-build-docker-image.yml +++ b/.github/workflows/test-build-docker-image.yml @@ -383,9 +383,8 @@ jobs: run: | ls -al dist/node_modules/@shared/ast tar -cvf rts-dist.tar dist - - # Upload the build artifact so that it can be used by the test & deploy job in the workflow - - name: Upload server build bundle + # Upload the build artifacts and dependencies so that it can be used by the test & deploy job in other workflows + - name: Upload rts build bundle uses: actions/upload-artifact@v2 with: name: rts-dist @@ -727,9 +726,15 @@ jobs: - name: Download the rts build artifact uses: actions/download-artifact@v2 with: - name: rts-build + name: rts-dist path: app/rts/dist + - name: Untar the rts folder + run: | + tar -xvf app/rts/dist/rts-dist.tar -C app/rts/ + echo "Cleaning up the tar files" + rm app/rts/dist/rts-dist.tar + - name: Download the rts build artifact uses: actions/download-artifact@v2 with: @@ -1073,7 +1078,7 @@ jobs: with: path: | ~/run_result - key: ${{ github.run_id }}-${{ github.job }}-${{ steps.timestamp.outputs.timestamp }}-${{ matrix.job }} + key: ${{ github.run_id }}-${{ github.job }}-${{ matrix.job }} restore-keys: | ${{ github.run_id }}-${{ github.job }}-${{ matrix.job }} @@ -1089,7 +1094,7 @@ jobs: with: path: | ~/combined_failed_spec - key: ${{ github.run_id }}-"ui-test-result"-${{ steps.timestamp.outputs.timestamp }} + key: ${{ github.run_id }}-"ui-test-result" restore-keys: | ${{ github.run_id }}-${{ github.job }} @@ -1401,7 +1406,7 @@ jobs: with: path: | app/client/cypress/snapshots/ - key: ${{ github.run_id }}-${{ github.job }}-${{ steps.timestamp.outputs.timestamp }}-${{ matrix.job }} + key: ${{ github.run_id }}-${{ github.job }}-${{ matrix.job }} restore-keys: | ${{ github.run_id }}-${{ github.job }}-${{ matrix.job }} @@ -1457,7 +1462,7 @@ jobs: with: path: | ~/combined_failed_spec - key: ${{ github.run_id }}-"ui-test-result"-${{ steps.timestamp.outputs.timestamp }} + key: ${{ github.run_id }}-"ui-test-result" restore-keys: | ${{ github.run_id }}-${{ github.job }} From 8e8f338b7dbbe018358f76448ddc455d28422abb Mon Sep 17 00:00:00 2001 From: yatinappsmith <84702014+yatinappsmith@users.noreply.github.com> Date: Mon, 19 Sep 2022 09:21:18 +0530 Subject: [PATCH 20/54] ci: removed rts download for modues (#16851) --- .github/workflows/test-build-docker-image.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/test-build-docker-image.yml b/.github/workflows/test-build-docker-image.yml index b3163e8e59..32e5d67b4a 100644 --- a/.github/workflows/test-build-docker-image.yml +++ b/.github/workflows/test-build-docker-image.yml @@ -735,12 +735,6 @@ jobs: echo "Cleaning up the tar files" rm app/rts/dist/rts-dist.tar - - name: Download the rts build artifact - uses: actions/download-artifact@v2 - with: - name: rts-build-deps - path: app/rts/node_modules/ - # We don't use Depot Docker builds because it's faster for local Docker images to be built locally. # It's slower and more expensive to build these Docker images on Depot and download it back to the CI node. - name: Build docker image From bd5d574ab6fefcbcc93df1a7cbbe1b3f631530cd Mon Sep 17 00:00:00 2001 From: yatinappsmith <84702014+yatinappsmith@users.noreply.github.com> Date: Mon, 19 Sep 2022 11:00:04 +0530 Subject: [PATCH 21/54] ci: Fix ci cache (#16852) Fix ci cache & rts build for push workflow ## Description Fix ci cache & rts build for push workflow ## Type of change - Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? code review ## Checklist: - [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 - [X] 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 --- .github/workflows/test-build-docker-image.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-build-docker-image.yml b/.github/workflows/test-build-docker-image.yml index 32e5d67b4a..f5a7638692 100644 --- a/.github/workflows/test-build-docker-image.yml +++ b/.github/workflows/test-build-docker-image.yml @@ -1373,7 +1373,7 @@ jobs: with: path: | ~/run_result - key: ${{ github.run_id }}-${{ github.job }}-${{ steps.timestamp.outputs.timestamp }}-${{ matrix.job }} + key: ${{ github.run_id }}-${{ github.job }}-${{ matrix.job }} restore-keys: | ${{ github.run_id }}-${{ github.job }}-${{ matrix.job }} @@ -1384,7 +1384,7 @@ jobs: with: path: | ~/failed_spec - key: ${{ github.run_id }}-${{ github.job }}-${{ steps.timestamp.outputs.timestamp }}-${{ matrix.job }} + key: ${{ github.run_id }}-${{ github.job }}-${{ matrix.job }} restore-keys: | ${{ github.run_id }}-${{ github.job }}-${{ matrix.job }} From ac01687557df5dac9d9612f0d7d444b4d1d9d1cc Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Mon, 19 Sep 2022 12:41:57 +0530 Subject: [PATCH 22/54] fix: Better support for disallowed hosts (#16842) --- .../restApiUtils/helpers/TriggerUtils.java | 11 +-- .../restApiUtils/helpers/URIUtils.java | 16 +-- .../com/appsmith/util/WebClientUtils.java | 97 ++++++++++++++++++- .../com/external/plugins/GraphQLPlugin.java | 19 +--- .../com/external/plugins/RestApiPlugin.java | 20 +--- 5 files changed, 104 insertions(+), 59 deletions(-) diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/TriggerUtils.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/TriggerUtils.java index 99987e1ce4..2f3556d864 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/TriggerUtils.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/TriggerUtils.java @@ -35,10 +35,8 @@ import reactor.netty.resources.ConnectionProvider; import javax.crypto.SecretKey; import java.io.IOException; -import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; -import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; @@ -46,7 +44,6 @@ import java.util.Date; import java.util.List; import java.util.Set; -import static com.appsmith.external.helpers.restApiUtils.helpers.URIUtils.DISALLOWED_HOSTS; import static org.apache.commons.lang3.StringUtils.isNotEmpty; @NoArgsConstructor @@ -204,14 +201,10 @@ public class TriggerUtils { * It redirects to partial URI : /api/character/ * In this scenario we should convert the partial URI to complete URI */ - URI redirectUri; + final URI redirectUri; try { redirectUri = new URI(redirectUrl); - if (DISALLOWED_HOSTS.contains(redirectUri.getHost()) - || DISALLOWED_HOSTS.contains(InetAddress.getByName(redirectUri.getHost()).getHostAddress())) { - return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, "Host not allowed.")); - } - } catch (URISyntaxException | UnknownHostException e) { + } catch (URISyntaxException e) { return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)); } diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/URIUtils.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/URIUtils.java index cc3c761b13..70c9b5af97 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/URIUtils.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/URIUtils.java @@ -3,30 +3,21 @@ package com.appsmith.external.helpers.restApiUtils.helpers; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.Property; -import lombok.AccessLevel; import lombok.NoArgsConstructor; -import org.apache.commons.lang3.StringUtils; import org.springframework.web.util.UriComponentsBuilder; -import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; -import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import java.util.Set; import static org.apache.commons.collections.CollectionUtils.isEmpty; import static org.apache.commons.lang3.StringUtils.isNotEmpty; @NoArgsConstructor public class URIUtils { - public static final Set DISALLOWED_HOSTS = Set.of( - "169.254.169.254", - "metadata.google.internal" - ); public URI createUriWithQueryParams(ActionConfiguration actionConfiguration, DatasourceConfiguration datasourceConfiguration, String url, @@ -53,7 +44,7 @@ public class URIUtils { for (Property queryParam : allQueryParams) { String key = queryParam.getKey(); if (isNotEmpty(key)) { - if (encodeParamsToggle == true) { + if (encodeParamsToggle) { uriBuilder.queryParam( URLEncoder.encode(key, StandardCharsets.UTF_8), URLEncoder.encode((String) queryParam.getValue(), StandardCharsets.UTF_8) @@ -78,9 +69,4 @@ public class URIUtils { return "http://" + url; } - public boolean isHostDisallowed(URI uri) throws UnknownHostException { - String host = uri.getHost(); - return StringUtils.isEmpty(host) || DISALLOWED_HOSTS.contains(host) - || DISALLOWED_HOSTS.contains(InetAddress.getByName(host).getHostAddress()); - } } diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/util/WebClientUtils.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/util/WebClientUtils.java index 8861dd16e5..f87b62fa30 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/util/WebClientUtils.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/util/WebClientUtils.java @@ -1,11 +1,31 @@ package com.appsmith.util; +import io.netty.resolver.AddressResolver; +import io.netty.resolver.AddressResolverGroup; +import io.netty.resolver.InetNameResolver; +import io.netty.resolver.InetSocketAddressResolver; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Promise; +import io.netty.util.internal.SocketUtils; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; import reactor.netty.http.client.HttpClient; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + public class WebClientUtils { + private static final Set DISALLOWED_HOSTS = Set.of( + "169.254.169.254", + "metadata.google.internal" + ); + private WebClientUtils() { } @@ -31,15 +51,86 @@ public class WebClientUtils { public static WebClient.Builder builder(HttpClient httpClient) { return WebClient.builder() - .clientConnector(new ReactorClientHttpConnector(applyProxyIfConfigured(httpClient))); + .clientConnector(new ReactorClientHttpConnector(makeSafeHttpClient(httpClient))); } - private static HttpClient applyProxyIfConfigured(HttpClient httpClient) { + private static HttpClient makeSafeHttpClient(HttpClient httpClient) { if (shouldUseSystemProxy()) { httpClient = httpClient.proxyWithSystemProperties(); } - return httpClient; + return httpClient.resolver(ResolverGroup.INSTANCE); + } + + private static class ResolverGroup extends AddressResolverGroup { + public static final ResolverGroup INSTANCE = new ResolverGroup(); + + @Override + protected AddressResolver newResolver(EventExecutor executor) { + return new InetSocketAddressResolver(executor, new NameResolver(executor)); + } + } + + @Slf4j + private static class NameResolver extends InetNameResolver { + + public NameResolver(EventExecutor executor) { + super(executor); + } + + private static boolean isDisallowedAndFail(String host, Promise promise) { + if (DISALLOWED_HOSTS.contains(host)) { + log.warn("Host {} is disallowed. Failing the request.", host); + promise.setFailure(new UnknownHostException("Host not allowed.")); + return true; + } + return false; + } + + @Override + protected void doResolve(String inetHost, Promise promise) { + if (isDisallowedAndFail(inetHost, promise)) { + return; + } + + final InetAddress address; + try { + address = SocketUtils.addressByName(inetHost); + } catch (UnknownHostException e) { + promise.setFailure(e); + return; + } + + if (isDisallowedAndFail(address.getHostAddress(), promise)) { + return; + } + + promise.setSuccess(address); + } + + @Override + protected void doResolveAll(String inetHost, Promise> promise) { + if (isDisallowedAndFail(inetHost, promise)) { + return; + } + + final List addresses; + try { + addresses = Arrays.asList(SocketUtils.allAddressesByName(inetHost)); + } catch (UnknownHostException e) { + promise.setFailure(e); + return; + } + + // Even if _one_ of the addresses is disallowed, we fail the request. + for (InetAddress address : addresses) { + if (isDisallowedAndFail(address.getHostAddress(), promise)) { + return; + } + } + + promise.setSuccess(addresses); + } } } diff --git a/app/server/appsmith-plugins/graphqlPlugin/src/main/java/com/external/plugins/GraphQLPlugin.java b/app/server/appsmith-plugins/graphqlPlugin/src/main/java/com/external/plugins/GraphQLPlugin.java index 8cda9bd184..8453494cce 100644 --- a/app/server/appsmith-plugins/graphqlPlugin/src/main/java/com/external/plugins/GraphQLPlugin.java +++ b/app/server/appsmith-plugins/graphqlPlugin/src/main/java/com/external/plugins/GraphQLPlugin.java @@ -27,7 +27,6 @@ import reactor.core.publisher.Mono; import java.net.URI; import java.net.URISyntaxException; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -37,12 +36,12 @@ import java.util.Set; import static com.appsmith.external.helpers.PluginUtils.getValueSafelyFromPropertyList; import static com.appsmith.external.helpers.PluginUtils.setValueSafelyInPropertyList; import static com.external.utils.GraphQLBodyUtils.PAGINATION_DATA_INDEX; -import static com.external.utils.GraphQLDataTypeUtils.smartlyReplaceGraphQLQueryBodyPlaceholderWithValue; -import static com.external.utils.GraphQLPaginationUtils.updateVariablesWithPaginationValues; import static com.external.utils.GraphQLBodyUtils.QUERY_VARIABLES_INDEX; import static com.external.utils.GraphQLBodyUtils.convertToGraphQLPOSTBodyFormat; import static com.external.utils.GraphQLBodyUtils.getGraphQLQueryParamsForBodyAndVariables; import static com.external.utils.GraphQLBodyUtils.validateBodyAndVariablesSyntax; +import static com.external.utils.GraphQLDataTypeUtils.smartlyReplaceGraphQLQueryBodyPlaceholderWithValue; +import static com.external.utils.GraphQLPaginationUtils.updateVariablesWithPaginationValues; import static java.lang.Boolean.TRUE; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -186,18 +185,6 @@ public class GraphQLPlugin extends BasePlugin { ActionExecutionRequest actionExecutionRequest = RequestCaptureFilter.populateRequestFields(actionConfiguration, uri, insertedParams, objectMapper); - try { - if (uriUtils.isHostDisallowed(uri)) { - errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Host not allowed.")); - errorResult.setRequest(actionExecutionRequest); - return Mono.just(errorResult); - } - } catch (UnknownHostException e) { - errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Unknown host.")); - errorResult.setRequest(actionExecutionRequest); - return Mono.just(errorResult); - } - WebClient.Builder webClientBuilder = triggerUtils.getWebClientBuilder(actionConfiguration, datasourceConfiguration); @@ -282,7 +269,7 @@ public class GraphQLPlugin extends BasePlugin { EXCHANGE_STRATEGIES, requestCaptureFilter); /* Triggering the actual REST API call */ - Set hintMessages = new HashSet(); + Set hintMessages = new HashSet<>(); return triggerUtils.triggerApiCall(client, httpMethod, uri, requestBodyObj, actionExecutionRequest, objectMapper, hintMessages, errorResult, requestCaptureFilter); diff --git a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java index ec1ae5b910..86f9101cca 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java @@ -5,6 +5,8 @@ import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.helpers.DataTypeStringUtils; import com.appsmith.external.helpers.MustacheHelper; +import com.appsmith.external.helpers.restApiUtils.connections.APIConnection; +import com.appsmith.external.helpers.restApiUtils.helpers.RequestCaptureFilter; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionRequest; import com.appsmith.external.models.ActionExecutionResult; @@ -15,18 +17,16 @@ import com.appsmith.external.models.Property; import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.BaseRestApiPluginExecutor; import com.appsmith.external.services.SharedConfig; -import com.appsmith.external.helpers.restApiUtils.connections.APIConnection; -import com.appsmith.external.helpers.restApiUtils.helpers.RequestCaptureFilter; import lombok.extern.slf4j.Slf4j; import org.pf4j.Extension; import org.pf4j.PluginWrapper; import org.springframework.http.HttpMethod; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; + import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; -import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashSet; @@ -125,7 +125,7 @@ public class RestApiPlugin extends BasePlugin { initUtils.initializeResponseWithError(errorResult); // Set of hint messages that can be returned to the user. - Set hintMessages = new HashSet(); + Set hintMessages = new HashSet<>(); // Initializing request URL String url = initUtils.initializeRequestUrl(actionConfiguration, datasourceConfiguration); @@ -148,18 +148,6 @@ public class RestApiPlugin extends BasePlugin { ActionExecutionRequest actionExecutionRequest = RequestCaptureFilter.populateRequestFields(actionConfiguration, uri, insertedParams, objectMapper); - try { - if (uriUtils.isHostDisallowed(uri)) { - errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Host not allowed.")); - errorResult.setRequest(actionExecutionRequest); - return Mono.just(errorResult); - } - } catch (UnknownHostException e) { - errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Unknown host.")); - errorResult.setRequest(actionExecutionRequest); - return Mono.just(errorResult); - } - WebClient.Builder webClientBuilder = triggerUtils.getWebClientBuilder(actionConfiguration, datasourceConfiguration); String reqContentType = headerUtils.getRequestContentType(actionConfiguration, datasourceConfiguration); From 37844fe85e2785eae7d724aa863823fb950b0048 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Mon, 19 Sep 2022 13:49:27 +0530 Subject: [PATCH 23/54] Fix key file permissions error on Windows (#16862) --- deploy/docker/entrypoint.sh | 12 ++++++++---- deploy/docker/templates/supervisord/mongodb.conf | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/deploy/docker/entrypoint.sh b/deploy/docker/entrypoint.sh index e637aa545f..a5fb92d76f 100755 --- a/deploy/docker/entrypoint.sh +++ b/deploy/docker/entrypoint.sh @@ -128,7 +128,7 @@ init_mongodb() { if [[ ! -f "$MONGO_DB_KEY" ]]; then openssl rand -base64 756 > "$MONGO_DB_KEY" fi - chmod-mongodb-key "$MONGO_DB_KEY" + use-mongodb-key "$MONGO_DB_KEY" fi } @@ -157,7 +157,7 @@ init_replica_set() { mongo "127.0.0.1/appsmith" /appsmith-stacks/configuration/mongo-init.js echo "Enabling Replica Set" mongod --dbpath "$MONGO_DB_PATH" --shutdown || true - mongod --fork --port 27017 --dbpath "$MONGO_DB_PATH" --logpath "$MONGO_LOG_PATH" --replSet mr1 --keyFile "$MONGO_DB_KEY" --bind_ip localhost + mongod --fork --port 27017 --dbpath "$MONGO_DB_PATH" --logpath "$MONGO_LOG_PATH" --replSet mr1 --keyFile /mongodb-key --bind_ip localhost echo "Waiting 10s for MongoDB to start with Replica Set" sleep 10 mongo "$APPSMITH_MONGODB_URI" --eval 'rs.initiate()' @@ -180,8 +180,12 @@ init_replica_set() { fi } -chmod-mongodb-key() { - chmod 600 "$1" +use-mongodb-key() { + # This is a little weird. We copy the MongoDB key file to `/mongodb-key`, so that we can reliably set its permissions to 600. + # What affects the reliability of this? When the host machine of this Docker container is Windows, file permissions cannot be set on files in volumes. + # So the key file should be somewhere inside the container, and not in a volume. + cp -v "$1" /mongodb-key + chmod 600 /mongodb-key } # Keep Let's Encrypt directory persistent diff --git a/deploy/docker/templates/supervisord/mongodb.conf b/deploy/docker/templates/supervisord/mongodb.conf index f1024c08c1..a1d6ae4c52 100644 --- a/deploy/docker/templates/supervisord/mongodb.conf +++ b/deploy/docker/templates/supervisord/mongodb.conf @@ -1,6 +1,6 @@ [program:mongodb] directory=/appsmith-stacks/data/mongodb -command=mongod --port 27017 --dbpath . --logpath /appsmith-stacks/logs/%(program_name)s/db.log --replSet mr1 --keyFile key --bind_ip localhost +command=mongod --port 27017 --dbpath . --logpath /appsmith-stacks/logs/%(program_name)s/db.log --replSet mr1 --keyFile /mongodb-key --bind_ip localhost priority=10 autostart=true autorestart=true From 8742663e21580073cf5966e8ff2692419dc2b5d7 Mon Sep 17 00:00:00 2001 From: Arpit Mohan Date: Tue, 20 Sep 2022 10:17:24 +0200 Subject: [PATCH 24/54] ci: Fixing the Github release workflow (#16893) --- .github/workflows/github-release.yml | 43 ++++++++++++++-------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index 6306ce8f38..1b739bdbf9 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -170,21 +170,20 @@ jobs: - name: Build run: | echo 'export const VERSION = "${{ needs.prelude.outputs.tag }}"' > src/version.js - ./build.sh - ls -l dist + yarn build - # Upload the build artifact so that it can be used by the test & deploy job in the workflow - - name: Upload RTS build bundle + # Tar the bundles to speed up the upload & download process + - name: Tar the rts bundles + run: | + ls -al dist/node_modules/@shared/ast + tar -cvf rts-dist.tar dist + + # Upload the build artifacts and dependencies so that it can be used by the test & deploy job in other workflows + - name: Upload rts build bundle uses: actions/upload-artifact@v2 with: - name: rts-build - path: app/rts/dist/ - - - name: Upload RTS dependencies bundle - uses: actions/upload-artifact@v2 - with: - name: rts-build-deps - path: app/rts/node_modules/ + name: rts-dist + path: app/rts/rts-dist.tar package: needs: [prelude, buildClient, buildServer, buildRts] @@ -216,14 +215,14 @@ jobs: - name: Download the rts build artifact uses: actions/download-artifact@v2 with: - name: rts-build + name: rts-dist path: app/rts/dist - - name: Download the rts dependencies artifact - uses: actions/download-artifact@v2 - with: - name: rts-build-deps - path: app/rts/node_modules/ + - name: Untar the rts folder + run: | + tar -xvf app/rts/dist/rts-dist.tar -C app/rts/ + echo "Cleaning up the tar files" + rm app/rts/dist/rts-dist.tar - name: Login to DockerHub uses: docker/login-action@v1 @@ -241,7 +240,7 @@ jobs: # Only build & tag with latest if the tag doesn't contain beta - name: Build and push client image latest - if: '!contains(needs.prelude.outputs.tag, "beta")' + if: contains(needs.prelude.outputs.tag, 'beta') == 'false' uses: depot/build-push-action@v1 with: context: app/client @@ -262,7 +261,7 @@ jobs: # Only build & tag with latest if the tag doesn't contain beta - name: Build and push fat image latest - if: '!contains(needs.prelude.outputs.tag, "beta")' + if: contains(needs.prelude.outputs.tag, 'beta') == 'false' uses: depot/build-push-action@v1 with: context: . @@ -285,7 +284,7 @@ jobs: # Only build & tag with latest if the tag doesn't contain beta - name: Build and push server image latest - if: '!contains(needs.prelude.outputs.tag, "beta")' + if: contains(needs.prelude.outputs.tag, 'beta') == 'false' uses: depot/build-push-action@v1 with: context: app/server @@ -306,7 +305,7 @@ jobs: # Only build & tag with latest if the tag doesn't contain beta - name: Build and push RTS image latest - if: '!contains(needs.prelude.outputs.tag, "beta")' + if: contains(needs.prelude.outputs.tag, 'beta') == 'false' uses: depot/build-push-action@v1 with: context: app/rts From 0d60de66d2f289d4d5fc87764f0f6879c97a5e86 Mon Sep 17 00:00:00 2001 From: Keyur Paralkar Date: Tue, 20 Sep 2022 17:20:09 +0530 Subject: [PATCH 25/54] fix: added a condition to check for accentColor (#16910) --- app/client/src/widgets/CodeScannerWidget/widget/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/src/widgets/CodeScannerWidget/widget/index.tsx b/app/client/src/widgets/CodeScannerWidget/widget/index.tsx index 56473070b6..d7b44fee9c 100644 --- a/app/client/src/widgets/CodeScannerWidget/widget/index.tsx +++ b/app/client/src/widgets/CodeScannerWidget/widget/index.tsx @@ -37,7 +37,7 @@ class CodeScannerWidget extends BaseWidget< Date: Tue, 20 Sep 2022 17:20:47 +0530 Subject: [PATCH 26/54] chore: Shutting down get applications endpoint (#16902) Shutting down get applications endpoint --- .../controllers/ce/ApplicationControllerCE.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java index cf1af1dd6e..e297ea4e19 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java @@ -27,6 +27,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.codec.multipart.Part; +import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -212,4 +213,14 @@ public class ApplicationControllerCE extends BaseController new ResponseDTO<>(HttpStatus.OK.value(), result, null)); } + + // !! This API endpoint should not be exposed !! + @Override + @GetMapping("") + public Mono>> getAll(@RequestParam MultiValueMap params, + @RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName) { + return Mono.just( + new ResponseDTO<>(HttpStatus.BAD_REQUEST.value(), null, AppsmithError.UNSUPPORTED_OPERATION.getMessage()) + ); + } } From 54c7604ede7918b347701a9380798e01dbde406f Mon Sep 17 00:00:00 2001 From: Arpit Mohan Date: Fri, 23 Sep 2022 05:57:24 +0200 Subject: [PATCH 27/54] ci: Fixing the github release workflow to push the latest tags for Docker images (#16999) --- .github/workflows/github-release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index 1b739bdbf9..08fddff194 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -240,7 +240,7 @@ jobs: # Only build & tag with latest if the tag doesn't contain beta - name: Build and push client image latest - if: contains(needs.prelude.outputs.tag, 'beta') == 'false' + if: needs.prelude.outputs.is_beta == 'false' uses: depot/build-push-action@v1 with: context: app/client @@ -261,7 +261,7 @@ jobs: # Only build & tag with latest if the tag doesn't contain beta - name: Build and push fat image latest - if: contains(needs.prelude.outputs.tag, 'beta') == 'false' + if: needs.prelude.outputs.is_beta == 'false' uses: depot/build-push-action@v1 with: context: . @@ -284,7 +284,7 @@ jobs: # Only build & tag with latest if the tag doesn't contain beta - name: Build and push server image latest - if: contains(needs.prelude.outputs.tag, 'beta') == 'false' + if: needs.prelude.outputs.is_beta == 'false' uses: depot/build-push-action@v1 with: context: app/server @@ -305,7 +305,7 @@ jobs: # Only build & tag with latest if the tag doesn't contain beta - name: Build and push RTS image latest - if: contains(needs.prelude.outputs.tag, 'beta') == 'false' + if: needs.prelude.outputs.is_beta == 'false' uses: depot/build-push-action@v1 with: context: app/rts From 4c3ce021b86589b56ea784b95240593ec9feb83a Mon Sep 17 00:00:00 2001 From: Arpit Mohan Date: Fri, 23 Sep 2022 10:15:38 +0530 Subject: [PATCH 28/54] ci: Removing cross platform builds for slim containers This is only required for the fat containers --- .github/workflows/github-release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index 08fddff194..acf8dcc036 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -289,7 +289,6 @@ jobs: with: context: app/server push: true - platforms: linux/arm64,linux/amd64 build-args: | APPSMITH_SEGMENT_CE_KEY=${{ secrets.APPSMITH_SEGMENT_CE_KEY }} tags: | @@ -310,7 +309,6 @@ jobs: with: context: app/rts push: true - platforms: linux/arm64,linux/amd64 build-args: | APPSMITH_SEGMENT_CE_KEY=${{ secrets.APPSMITH_SEGMENT_CE_KEY }} tags: | From fba4b73202770ed8f117d0c23257324087cdfac5 Mon Sep 17 00:00:00 2001 From: yatinappsmith <84702014+yatinappsmith@users.noreply.github.com> Date: Thu, 29 Sep 2022 10:21:31 +0530 Subject: [PATCH 29/54] ci: Release 29 sept2022 (#17161) * feat: audit-logs parity with ee (#17115) * fix: update rts logic to use updated shared AST logic (#16849) * update rts logic to use updated shared AST logic * Make changes to naming conventions * Add test cases for RTS and rename ast functions * Add running jest test to RTS workflow * Install dependencies and then trigger jest tests in workflow * Close server connection after test ends * Remove logs * Improve jest test descriptions * fix: RTE + Form + JSONForm reskinning (#16956) * update rich text editor styles * fix disalbed text color * add border * fix gaps and borders in json form * fix disabled state * fix checkbox disabled state color on checkbox + font size of title in json * fix disabled state of rte not re-rendering * remove unused import * fix cypress test * chore: Add view mode to the analytics events (#15791) * Add view mode to the analytics events * Add view mode to the analytics events * Fix UnSupported operation with template * Replace hardcoded strings * feat: Map Chart Reskinning (#16921) * feat: Map Chart Reskinning - Update font family based on theme - Fix caption, spacing, and legend styles * feat: Add migrations for Font Family of Map Chart * feat: Add migrations to test * fix: Fixed NPE issue with analytics event on authentication method configuration (#17112) * chore: Making the error pages conform to the design system (#17109) Co-authored-by: f0c1s Co-authored-by: Ayangade Adeoluwa <37867493+Irongade@users.noreply.github.com> Co-authored-by: Pawan Kumar Co-authored-by: Anagh Hegde Co-authored-by: Dhruvik Neharia Co-authored-by: Vishnu Gp Co-authored-by: Arpit Mohan --- .github/workflows/rts-build.yml | 10 + .../FormWidget_With_RichTextEditor_spec.js | 9 +- .../RTE/RichTextEditor_Validation_spec.js | 9 +- .../src/assets/images/empy-state-2x.png | Bin 0 -> 19704 bytes .../src/assets/images/empy-state-3x.png | Bin 0 -> 33597 bytes app/client/src/assets/images/empy-state.png | Bin 0 -> 8033 bytes app/client/src/ce/constants/messages.ts | 6 + .../src/ce/pages/AdminSettings/LeftPane.tsx | 2 +- .../src/components/wds/Checkbox/index.tsx | 1 + app/client/src/constants/WidgetConstants.tsx | 2 +- app/client/src/index.css | 13 - .../src/pages/Home/LeftPaneBottomSection.tsx | 4 +- app/client/src/pages/common/ClientError.tsx | 14 +- app/client/src/pages/common/MobileSidebar.tsx | 6 +- app/client/src/pages/common/PageNotFound.tsx | 16 +- .../src/pages/common/ProfileDropdown.tsx | 15 +- app/client/src/pages/common/ServerTimeout.tsx | 29 +- .../src/pages/common/ServerUnavailable.tsx | 29 +- app/client/src/utils/DSLMigration.test.ts | 10 + app/client/src/utils/DSLMigrations.ts | 6 + .../MapChartReskinningMigrations.ts | 25 + app/client/src/widgets/FormWidget/index.ts | 2 + .../widgets/JSONFormWidget/component/Form.tsx | 14 +- .../component/styleConstants.ts | 2 +- .../src/widgets/JSONFormWidget/index.ts | 3 + .../MapChartWidget/component/index.tsx | 46 +- .../widgets/MapChartWidget/widget/index.tsx | 2 + .../RichTextEditorWidget/component/index.tsx | 187 +- app/client/src/workers/DependencyMap/utils.ts | 4 +- app/rts/jest.config.js | 23 + app/rts/package.json | 6 + app/rts/src/controllers/Ast/AstController.ts | 11 +- app/rts/src/controllers/BaseController.ts | 3 +- app/rts/src/routes/ast_routes.ts | 8 +- app/rts/src/server.ts | 48 +- app/rts/src/services/AstService.ts | 14 +- app/rts/src/test/server.test.ts | 67 + app/rts/tsconfig.json | 2 +- app/rts/yarn.lock | 2285 ++++++++++++++++- .../external/constants/AnalyticsEvents.java | 1 + .../appsmith/server/constants/FieldName.java | 4 +- .../ce/ApplicationPageServiceCEImpl.java | 60 +- .../services/ce/ApplicationServiceCEImpl.java | 29 +- .../ce/ApplicationTemplateServiceCEImpl.java | 21 +- .../server/services/ce/GitServiceCEImpl.java | 6 + .../services/ce/NewActionServiceCEImpl.java | 39 +- .../server/solutions/ce/EnvManagerCEImpl.java | 2 +- app/shared/ast/index.ts | 18 +- app/shared/ast/src/index.test.ts | 198 +- app/shared/ast/src/index.ts | 26 +- 50 files changed, 3031 insertions(+), 306 deletions(-) create mode 100644 app/client/src/assets/images/empy-state-2x.png create mode 100644 app/client/src/assets/images/empy-state-3x.png create mode 100644 app/client/src/assets/images/empy-state.png create mode 100644 app/client/src/utils/migrations/MapChartReskinningMigrations.ts create mode 100644 app/rts/jest.config.js create mode 100644 app/rts/src/test/server.test.ts diff --git a/.github/workflows/rts-build.yml b/.github/workflows/rts-build.yml index 2f425fdcf1..79bd6ab790 100644 --- a/.github/workflows/rts-build.yml +++ b/.github/workflows/rts-build.yml @@ -119,6 +119,16 @@ jobs: echo ::set-output name=version::$next_version-SNAPSHOT echo ::set-output name=tag::$(echo ${GITHUB_REF:11}) + # Install all the dependencies + - name: Install dependencies + if: steps.run_result.outputs.run_result != 'success' + run: yarn install --frozen-lockfile + + # Run the Jest tests only if the workflow has been invoked in a PR + - name: Run the jest tests + if: steps.run_result.outputs.run_result != 'success' + run: yarn run test:unit + - name: Build if: steps.run_result.outputs.run_result != 'success' run: | diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Form/FormWidget_With_RichTextEditor_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Form/FormWidget_With_RichTextEditor_spec.js index a3b2c2bd2b..825a0cdc1d 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Form/FormWidget_With_RichTextEditor_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Form/FormWidget_With_RichTextEditor_spec.js @@ -29,10 +29,11 @@ describe("RichTextEditor Widget Functionality in Form", function() { cy.testJsontext("defaultvalue", ""); cy.wait(500); - cy.get( - formWidgetsPage.richTextEditorWidget + - " div[data-testid='rte-container'] > div", - ).should("have.css", "border", "1px solid rgb(242, 43, 43)"); + cy.get(formWidgetsPage.richTextEditorWidget + " .tox.tox-tinymce").should( + "have.css", + "border", + "1px solid rgb(217, 25, 33)", + ); cy.get(".t--draggable-formbuttonwidget button").should("be.disabled"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/RTE/RichTextEditor_Validation_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/RTE/RichTextEditor_Validation_spec.js index 7178ec9495..1b3423120e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/RTE/RichTextEditor_Validation_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/RTE/RichTextEditor_Validation_spec.js @@ -18,10 +18,11 @@ describe("RichTextEditor Widget Validation", function() { cy.wait(500); // check that input border is red - cy.get( - formWidgetsPage.richTextEditorWidget + - " div[data-testid='rte-container'] > div", - ).should("have.css", "border", "1px solid rgb(242, 43, 43)"); + cy.get(formWidgetsPage.richTextEditorWidget + " .tox.tox-tinymce").should( + "have.css", + "border", + "1px solid rgb(217, 25, 33)", + ); cy.closePropertyPane(); }); diff --git a/app/client/src/assets/images/empy-state-2x.png b/app/client/src/assets/images/empy-state-2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3ac653339785a0d0073e4cbf6828c68bd0ee9cdf GIT binary patch literal 19704 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 literal 0 HcmV?d00001 diff --git a/app/client/src/assets/images/empy-state.png b/app/client/src/assets/images/empy-state.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc37a722f01739a9cc06f9036f97d9d57c5c9bf GIT binary patch literal 8033 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 h`^SPi{-5-KWhg#^m8b1g_3uX "Sending Test Email Failed"; export const DISCONNECT_AUTH_ERROR = () => "Cannot disconnect the only connected authentication method."; export const MANDATORY_FIELDS_ERROR = () => "Mandatory fields cannot be empty"; + +// Audit logs begin +export const AUDIT_LOGS = () => "Audit logs"; +export const TRY_AGAIN_WITH_YOUR_FILTER = () => "Try again with your filter"; +// Audit logs end + // export const WELCOME_FORM_NON_SUPER_USER_ROLE_DROPDOWN = () => "Tell us more about what you do at work?"; diff --git a/app/client/src/ce/pages/AdminSettings/LeftPane.tsx b/app/client/src/ce/pages/AdminSettings/LeftPane.tsx index e403320d99..ce6dc2c0f1 100644 --- a/app/client/src/ce/pages/AdminSettings/LeftPane.tsx +++ b/app/client/src/ce/pages/AdminSettings/LeftPane.tsx @@ -1,7 +1,7 @@ import React from "react"; import { Link, useParams } from "react-router-dom"; import styled from "styled-components"; -import AdminConfig from "./config"; +import AdminConfig from "@appsmith/pages/AdminSettings/config"; import { Category } from "@appsmith/pages/AdminSettings/config/types"; import { adminSettingsCategoryUrl } from "RouteBuilder"; diff --git a/app/client/src/components/wds/Checkbox/index.tsx b/app/client/src/components/wds/Checkbox/index.tsx index ad710baf81..d5c27cb1a1 100644 --- a/app/client/src/components/wds/Checkbox/index.tsx +++ b/app/client/src/components/wds/Checkbox/index.tsx @@ -101,6 +101,7 @@ const Checkbox = styled(BlueprintCheckbox)` // not checked + disabled &.bp3-control.bp3-checkbox input:not(:checked):disabled ~ .bp3-control-indicator { background-color: var(--wds-color-bg-disabled) !important; + box-shadow: 0px 0px 0px 1px var(--wds-color-border-disabled); } // indeterminate diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx index 62548a32f0..e595dbe36f 100644 --- a/app/client/src/constants/WidgetConstants.tsx +++ b/app/client/src/constants/WidgetConstants.tsx @@ -70,7 +70,7 @@ export const layoutConfigurations: LayoutConfigurations = { FLUID: { minWidth: -1, maxWidth: -1 }, }; -export const LATEST_PAGE_VERSION = 63; +export const LATEST_PAGE_VERSION = 64; export const GridDefaults = { DEFAULT_CELL_SIZE: 1, diff --git a/app/client/src/index.css b/app/client/src/index.css index 5ea931b73a..86b66c8886 100755 --- a/app/client/src/index.css +++ b/app/client/src/index.css @@ -100,19 +100,6 @@ div.bp3-popover-arrow { border-radius: 0 !important; } -/* rich text editor styles */ -.tox-tinymce { - border-color: #E7E7E7 !important; -} - -.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type), .tox .tox-statusbar { - border-color: #E7E7E7 !important; -} - -.tox .tox-toolbar__primary { - background: white !important; - border-bottom: 1px solid #E7E7E7 !important; -} /* making both the Modal layer and the Dropdown layer */ .bp3-modal-widget, .appsmith_widget_0 > .bp3-portal { diff --git a/app/client/src/pages/Home/LeftPaneBottomSection.tsx b/app/client/src/pages/Home/LeftPaneBottomSection.tsx index 1997121f7f..febfa824be 100644 --- a/app/client/src/pages/Home/LeftPaneBottomSection.tsx +++ b/app/client/src/pages/Home/LeftPaneBottomSection.tsx @@ -4,10 +4,11 @@ import styled from "styled-components"; import { Classes as BlueprintClasses } from "@blueprintjs/core"; import { MenuItem } from "design-system"; import { + ADMIN_SETTINGS, + APPSMITH_DISPLAY_VERSION, createMessage, DOCUMENTATION, WELCOME_TOUR, - APPSMITH_DISPLAY_VERSION, } from "@appsmith/constants/messages"; import { getIsFetchingApplications } from "selectors/applicationSelectors"; import { getOnboardingWorkspaces } from "selectors/onboardingSelectors"; @@ -23,7 +24,6 @@ import { } from "../common/CustomizedDropdown/dropdownHelpers"; import { ADMIN_SETTINGS_CATEGORY_DEFAULT_PATH } from "constants/routes"; import { getCurrentUser } from "selectors/usersSelectors"; -import { ADMIN_SETTINGS } from "@appsmith/constants/messages"; const Wrapper = styled.div` padding-bottom: ${(props) => props.theme.spaces[3]}px; diff --git a/app/client/src/pages/common/ClientError.tsx b/app/client/src/pages/common/ClientError.tsx index c5b13bb882..9bcec6e05a 100644 --- a/app/client/src/pages/common/ClientError.tsx +++ b/app/client/src/pages/common/ClientError.tsx @@ -1,7 +1,7 @@ import React from "react"; import { connect } from "react-redux"; import styled from "styled-components"; -import Button from "components/editorComponents/Button"; +import { Button, Size } from "design-system"; import { flushErrors } from "actions/errorActions"; import PageUnavailableImage from "assets/images/404-image.png"; @@ -28,26 +28,26 @@ function ClientError(props: Props) { const { flushErrors } = props; return ( - + Page Unavailable -
+

Whoops something went wrong!

This is embarrassing, please contact Appsmith support for help

diff --git a/app/client/src/pages/common/MobileSidebar.tsx b/app/client/src/pages/common/MobileSidebar.tsx index b1726ac061..759bdef6e9 100644 --- a/app/client/src/pages/common/MobileSidebar.tsx +++ b/app/client/src/pages/common/MobileSidebar.tsx @@ -5,17 +5,17 @@ import ProfileImage from "pages/common/ProfileImage"; import { MenuItem } from "design-system"; import { ADMIN_SETTINGS_CATEGORY_DEFAULT_PATH } from "constants/routes"; import { - getOnSelectAction, DropdownOnSelectActions, + getOnSelectAction, } from "./CustomizedDropdown/dropdownHelpers"; import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; import { useSelector } from "react-redux"; import { getCurrentUser } from "selectors/usersSelectors"; import { - createMessage, ADMIN_SETTINGS, - DOCUMENTATION, APPSMITH_DISPLAY_VERSION, + createMessage, + DOCUMENTATION, } from "@appsmith/constants/messages"; import { getAppsmithConfigs } from "@appsmith/configs"; import { howMuchTimeBeforeText } from "utils/helpers"; diff --git a/app/client/src/pages/common/PageNotFound.tsx b/app/client/src/pages/common/PageNotFound.tsx index bdf0edaa0b..172d93bbf7 100644 --- a/app/client/src/pages/common/PageNotFound.tsx +++ b/app/client/src/pages/common/PageNotFound.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from "react"; import { connect } from "react-redux"; import styled from "styled-components"; import { APPLICATIONS_URL } from "constants/routes"; -import Button from "components/editorComponents/Button"; +import { Button, IconPositions, Size } from "design-system"; import { flushErrorsAndRedirect } from "actions/errorActions"; import PageUnavailableImage from "assets/images/404-image.png"; import { @@ -50,17 +50,19 @@ function PageNotFound(props: Props) {

Either this page doesn't exist, or you don't have access to{" "}
- this page. + this page

diff --git a/app/client/src/pages/common/ProfileDropdown.tsx b/app/client/src/pages/common/ProfileDropdown.tsx index b868db0b20..e76b987318 100644 --- a/app/client/src/pages/common/ProfileDropdown.tsx +++ b/app/client/src/pages/common/ProfileDropdown.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { CommonComponentProps, Classes } from "components/ads/common"; +import { Classes, CommonComponentProps } from "components/ads/common"; import { Menu, MenuDivider, @@ -9,14 +9,17 @@ import { TooltipComponent, } from "design-system"; import styled from "styled-components"; -import { Position, Classes as BlueprintClasses } from "@blueprintjs/core"; import { - getOnSelectAction, + Classes as BlueprintClasses, + PopperModifiers, + Position, +} from "@blueprintjs/core"; +import { DropdownOnSelectActions, + getOnSelectAction, } from "./CustomizedDropdown/dropdownHelpers"; import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; import ProfileImage from "./ProfileImage"; -import { PopperModifiers } from "@blueprintjs/core"; import { PROFILE } from "constants/routes"; import { Colors } from "constants/Colors"; import { ACCOUNT_TOOLTIP, createMessage } from "@appsmith/constants/messages"; @@ -35,6 +38,7 @@ const StyledMenuItem = styled(MenuItem)` width: 18px; height: 18px; fill: ${Colors.GRAY}; + path { fill: ${Colors.GRAY}; } @@ -56,6 +60,7 @@ const UserInformation = styled.div` white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + .${Classes.TEXT} { color: ${(props) => props.theme.colors.profileDropdown.userName}; } @@ -66,6 +71,7 @@ const UserInformation = styled.div` white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + .${Classes.TEXT} { color: ${(props) => props.theme.colors.profileDropdown.name}; } @@ -73,6 +79,7 @@ const UserInformation = styled.div` .user-image { margin-right: ${(props) => props.theme.spaces[4]}px; + div { cursor: default; } diff --git a/app/client/src/pages/common/ServerTimeout.tsx b/app/client/src/pages/common/ServerTimeout.tsx index 7d4171f69e..a8bb85b671 100644 --- a/app/client/src/pages/common/ServerTimeout.tsx +++ b/app/client/src/pages/common/ServerTimeout.tsx @@ -1,6 +1,7 @@ import React from "react"; import styled from "styled-components"; import AppTimeoutImage from "assets/images/timeout-image.png"; +import { Button, Size } from "design-system"; const Wrapper = styled.div` display: flex; @@ -21,33 +22,29 @@ const Wrapper = styled.div` } `; -const RetryButton = styled.button` - background-color: #f3672a; - color: white; - height: 40px; - width: 300px; - border: none; - cursor: pointer; - font-weight: 600; - font-size: 17px; -`; - function ServerTimeout() { return ( - + Page Unavailable -
+

Appsmith server is taking too long to respond

Please retry after some time

- window.location.reload()}> - {"Retry"} - +
); diff --git a/app/client/src/pages/common/ServerUnavailable.tsx b/app/client/src/pages/common/ServerUnavailable.tsx index f499aa16b3..e16b9ca8a7 100644 --- a/app/client/src/pages/common/ServerUnavailable.tsx +++ b/app/client/src/pages/common/ServerUnavailable.tsx @@ -1,6 +1,7 @@ import React from "react"; import styled from "styled-components"; import PageUnavailableImage from "assets/images/404-image.png"; +import { Button, Size } from "design-system"; const Wrapper = styled.div` height: calc(100vh - ${(props) => props.theme.headerHeight}); @@ -19,32 +20,28 @@ const Wrapper = styled.div` margin: auto; } `; -const RetryButton = styled.button` - background-color: #f3672a; - color: white; - height: 40px; - width: 300px; - border: none; - cursor: pointer; - font-weight: 600; - font-size: 17px; - margin-top: 16px; -`; function ServerUnavailable() { return ( - + Page Unavailable -
+

Appsmith server is unavailable

Please try again after some time

- window.location.reload()}> - {"Retry"} - +
); diff --git a/app/client/src/utils/DSLMigration.test.ts b/app/client/src/utils/DSLMigration.test.ts index 77caeb51a2..31dc34d76f 100644 --- a/app/client/src/utils/DSLMigration.test.ts +++ b/app/client/src/utils/DSLMigration.test.ts @@ -16,6 +16,7 @@ import * as radioGroupMigration from "./migrations/RadioGroupWidget"; import * as propertyPaneMigrations from "./migrations/PropertyPaneMigrations"; import * as themingMigration from "./migrations/ThemingMigrations"; import * as selectWidgetMigration from "./migrations/SelectWidget"; +import * as mapChartReskinningMigrations from "./migrations/MapChartReskinningMigrations"; import { LATEST_PAGE_VERSION } from "constants/WidgetConstants"; import { originalDSLForDSLMigrations } from "./testDSLs"; @@ -614,6 +615,15 @@ const migrations: Migration[] = [ ], version: 62, }, + { + functionLookup: [ + { + moduleObj: mapChartReskinningMigrations, + functionName: "migrateMapChartWidgetReskinningData", + }, + ], + version: 63, + }, ]; const mockFnObj: Record = {}; diff --git a/app/client/src/utils/DSLMigrations.ts b/app/client/src/utils/DSLMigrations.ts index 6ef2ac2638..34cb015542 100644 --- a/app/client/src/utils/DSLMigrations.ts +++ b/app/client/src/utils/DSLMigrations.ts @@ -61,6 +61,7 @@ import { migrateRadioGroupAlignmentProperty } from "./migrations/RadioGroupWidge import { migrateCheckboxSwitchProperty } from "./migrations/PropertyPaneMigrations"; import { migrateChartWidgetReskinningData } from "./migrations/ChartWidgetReskinningMigrations"; import { MigrateSelectTypeWidgetDefaultValue } from "./migrations/SelectWidget"; +import { migrateMapChartWidgetReskinningData } from "./migrations/MapChartReskinningMigrations"; /** * adds logBlackList key for all list widget children @@ -1115,6 +1116,11 @@ export const transformDSL = ( if (currentDSL.version === 62) { currentDSL = MigrateSelectTypeWidgetDefaultValue(currentDSL); + currentDSL.version = 63; + } + + if (currentDSL.version === 63) { + currentDSL = migrateMapChartWidgetReskinningData(currentDSL); currentDSL.version = LATEST_PAGE_VERSION; } diff --git a/app/client/src/utils/migrations/MapChartReskinningMigrations.ts b/app/client/src/utils/migrations/MapChartReskinningMigrations.ts new file mode 100644 index 0000000000..2bfd4677c7 --- /dev/null +++ b/app/client/src/utils/migrations/MapChartReskinningMigrations.ts @@ -0,0 +1,25 @@ +import { WidgetProps } from "widgets/BaseWidget"; +import { DSLWidget } from "widgets/constants"; + +export const migrateMapChartWidgetReskinningData = (currentDSL: DSLWidget) => { + currentDSL.children = currentDSL.children?.map((child: WidgetProps) => { + if (child.type === "MAP_CHART_WIDGET") { + if (!child.hasOwnProperty("fontFamily")) { + child.fontFamily = "{{appsmith.theme.fontFamily.appFont}}"; + + child.dynamicBindingPathList = [ + ...(child.dynamicBindingPathList || []), + { + key: "fontFamily", + }, + ]; + } + } else if (child.children && child.children.length > 0) { + child = migrateMapChartWidgetReskinningData(child); + } + + return child; + }); + + return currentDSL; +}; diff --git a/app/client/src/widgets/FormWidget/index.ts b/app/client/src/widgets/FormWidget/index.ts index 69cd4adce7..70a0afa945 100644 --- a/app/client/src/widgets/FormWidget/index.ts +++ b/app/client/src/widgets/FormWidget/index.ts @@ -13,6 +13,8 @@ export const CONFIG = { defaults: { rows: 40, columns: 24, + borderColor: Colors.GREY_5, + borderWidth: "1", animateLoading: true, widgetName: "Form", backgroundColor: Colors.WHITE, diff --git a/app/client/src/widgets/JSONFormWidget/component/Form.tsx b/app/client/src/widgets/JSONFormWidget/component/Form.tsx index 379146a9e7..49ab524329 100644 --- a/app/client/src/widgets/JSONFormWidget/component/Form.tsx +++ b/app/client/src/widgets/JSONFormWidget/component/Form.tsx @@ -15,7 +15,6 @@ import { Colors } from "constants/Colors"; import { FORM_PADDING_Y, FORM_PADDING_X } from "./styleConstants"; import { ROOT_SCHEMA_KEY, Schema } from "../constants"; import { convertSchemaItemToFormData, schemaItemDefaultValue } from "../helper"; -import { TEXT_SIZES } from "constants/WidgetConstants"; export type FormProps = PropsWithChildren<{ backgroundColor?: string; @@ -57,6 +56,7 @@ type StyledFooterProps = { const BUTTON_WIDTH = 110; const FOOTER_BUTTON_GAP = 10; +const TITLE_FONT_SIZE = "1.25rem"; const FOOTER_DEFAULT_BG_COLOR = "#fff"; const FOOTER_PADDING_TOP = FORM_PADDING_Y; const TITLE_MARGIN_BOTTOM = 16; @@ -67,6 +67,7 @@ const StyledFormFooter = styled.div` backgroundColor || FOOTER_DEFAULT_BG_COLOR}; bottom: 0; display: flex; + gap: ${FOOTER_BUTTON_GAP}px; justify-content: flex-end; padding: ${FORM_PADDING_Y}px ${FORM_PADDING_X}px; padding-top: ${FOOTER_PADDING_TOP}px; @@ -82,15 +83,6 @@ const StyledFormFooter = styled.div` && > div { width: ${BUTTON_WIDTH}px; } - - && > button, - && > div { - margin-right: ${FOOTER_BUTTON_GAP}px; - } - - & > button:last-of-type { - margin-right: 0; - } `; const StyledForm = styled.form` @@ -103,7 +95,7 @@ const StyledForm = styled.form` const StyledTitle = styled(Text)` font-weight: bold; - font-size: ${TEXT_SIZES.HEADING1}; + font-size: ${TITLE_FONT_SIZE}; word-break: break-word; margin-bottom: ${TITLE_MARGIN_BOTTOM}px; `; diff --git a/app/client/src/widgets/JSONFormWidget/component/styleConstants.ts b/app/client/src/widgets/JSONFormWidget/component/styleConstants.ts index af0c5a3f64..73c35f7f5f 100644 --- a/app/client/src/widgets/JSONFormWidget/component/styleConstants.ts +++ b/app/client/src/widgets/JSONFormWidget/component/styleConstants.ts @@ -2,4 +2,4 @@ export const FIELD_PADDING_X = 15; export const FIELD_PADDING_Y = 10; export const FIELD_MARGIN_BOTTOM = 16; export const FORM_PADDING_X = 25; -export const FORM_PADDING_Y = 15; +export const FORM_PADDING_Y = 25; diff --git a/app/client/src/widgets/JSONFormWidget/index.ts b/app/client/src/widgets/JSONFormWidget/index.ts index abfadfe5a2..1b0a187087 100644 --- a/app/client/src/widgets/JSONFormWidget/index.ts +++ b/app/client/src/widgets/JSONFormWidget/index.ts @@ -1,4 +1,5 @@ import IconSVG from "./icon.svg"; +import { Colors } from "constants/Colors"; import Widget, { JSONFormWidgetProps } from "./widget"; import { ButtonVariantTypes } from "components/constants"; import { BlueprintOperationTypes } from "widgets/constants"; @@ -28,6 +29,8 @@ export const CONFIG = { showReset: true, title: "Form", version: 1, + borderWidth: "1", + borderColor: Colors.GREY_5, widgetName: "JSONForm", autoGenerateForm: true, fieldLimitExceeded: false, diff --git a/app/client/src/widgets/MapChartWidget/component/index.tsx b/app/client/src/widgets/MapChartWidget/component/index.tsx index e5c18b64c7..b4f81f99c6 100644 --- a/app/client/src/widgets/MapChartWidget/component/index.tsx +++ b/app/client/src/widgets/MapChartWidget/component/index.tsx @@ -16,6 +16,7 @@ import FusionTheme from "fusioncharts/themes/fusioncharts.theme.fusion"; // Import the dataset and the colorRange of the map import { dataSetForWorld, MapTypes, MapColorObject } from "../constants"; import { CUSTOM_MAP_PLUGINS } from "../CustomMapConstants"; +import { Colors } from "constants/Colors"; // Adding the chart and theme as dependency to the core fusioncharts ReactFC.fcRoot(FusionCharts, FusionMaps, World, FusionTheme, USA); @@ -75,6 +76,9 @@ function MapChartComponent(props: MapChartComponentProps) { type, } = props; + const fontFamily = + props.fontFamily === "System Default" ? "inherit" : props.fontFamily; + // Creating the JSON object to store the chart configurations const defaultChartConfigs: ChartObject = { type: "maps/world", // The chart type @@ -85,12 +89,35 @@ function MapChartComponent(props: MapChartComponentProps) { // Map Configuration chart: { caption: "Average Annual Population Growth", - // subcaption: " 1955-2015", - // numbersuffix: "%", includevalueinlabels: "1", labelsepchar: ": ", entityFillHoverColor: "#FFF9C4", theme: "fusion", + + // Caption + captionFontSize: "24", + captionAlignment: "center", + captionPadding: "20", + captionFontColor: Colors.THUNDER, + captionFontBold: "1", + + // Legend + legendIconSides: "4", + legendIconBgAlpha: "100", + legendIconAlpha: "100", + legendItemFont: fontFamily, + legendPosition: "top", + valueFont: fontFamily, + + // Spacing + chartLeftMargin: "10", + chartTopMargin: "15", + chartRightMargin: "10", + chartBottomMargin: "10", + + // Base Styling + baseFont: fontFamily, + bgColor: Colors.WHITE, }, // Aesthetics; ranges synced with the slider colorRange: { @@ -118,6 +145,20 @@ function MapChartComponent(props: MapChartComponentProps) { }; }, [onDataPointClick]); + useEffect(() => { + const newChartConfigs: any = { + ...chartConfigs, + }; + const fontFamily = + props.fontFamily === "System Default" ? "inherit" : props.fontFamily; + + newChartConfigs["dataSource"]["chart"]["legendItemFont"] = fontFamily; + newChartConfigs["dataSource"]["chart"]["valueFont"] = fontFamily; + newChartConfigs["dataSource"]["chart"]["baseFont"] = fontFamily; + + setChartConfigs(newChartConfigs); + }, [props.fontFamily]); + useEffect(() => { const newChartConfigs: any = { ...chartConfigs, @@ -227,6 +268,7 @@ export interface MapChartComponentProps { type: MapType; borderRadius?: string; boxShadow?: string; + fontFamily?: string; } export default MapChartComponent; diff --git a/app/client/src/widgets/MapChartWidget/widget/index.tsx b/app/client/src/widgets/MapChartWidget/widget/index.tsx index ace6ac63f5..01f9dfebd8 100644 --- a/app/client/src/widgets/MapChartWidget/widget/index.tsx +++ b/app/client/src/widgets/MapChartWidget/widget/index.tsx @@ -613,6 +613,7 @@ class MapChartWidget extends BaseWidget { caption={mapTitle} colorRange={colorRange} data={data} + fontFamily={this.props.fontFamily ?? "Nunito Sans"} isVisible={isVisible} onDataPointClick={this.handleDataPointClick} showLabels={showLabels} @@ -631,6 +632,7 @@ export interface MapChartWidgetProps extends WidgetProps { colorRange: MapColorObject[]; borderRadius?: string; boxShadow?: string; + fontFamily?: string; } export default MapChartWidget; diff --git a/app/client/src/widgets/RichTextEditorWidget/component/index.tsx b/app/client/src/widgets/RichTextEditorWidget/component/index.tsx index e3d35156c4..c8b7b609b3 100644 --- a/app/client/src/widgets/RichTextEditorWidget/component/index.tsx +++ b/app/client/src/widgets/RichTextEditorWidget/component/index.tsx @@ -5,7 +5,9 @@ import { LabelPosition } from "components/constants"; import { Alignment } from "@blueprintjs/core"; import { TextSize } from "constants/WidgetConstants"; -import { Colors } from "constants/Colors"; +// @ts-expect-error: loader types not available +import cssVariables from "!!raw-loader!theme/wds.css"; + import { LabelWithTooltip, labelLayoutStyles } from "design-system"; import { isMacOs } from "utils/AppsmithUtils"; @@ -14,6 +16,8 @@ const StyledRTEditor = styled.div<{ boxShadow?: string; compactMode: boolean; labelPosition?: LabelPosition; + isValid?: boolean; + isDisabled?: boolean; }>` && { width: 100%; @@ -28,6 +32,8 @@ const StyledRTEditor = styled.div<{ } } .tox { + font-family: inherit; + width: 100%; .tox-tbtn { cursor: pointer; @@ -37,6 +43,168 @@ const StyledRTEditor = styled.div<{ } } + .tox .tox-toolbar__primary { + background: ${(props) => + props.isDisabled + ? "var(--wds-color-bg-disabled)" + : "var(--wds-color-bg)"}; + } + + .tox .tox-edit-area__iframe { + background: ${(props) => + props.isDisabled + ? "var(--wds-color-bg-disabled)" + : "var(--wds-color-bg)"}; + } + + .tox-tinymce { + border: 1px solid + ${(props) => + props.isValid + ? "var(--wds-color-border)" + : "var(--wds-color-border-danger)"}; + } + + &.disabled { + cursor: not-allowed !important; + } + + &.disabled .tox { + pointer-events: none; + } + + &:not(.disabled):hover .tox-tinymce { + border: 1px solid + ${(props) => + props.isValid + ? "var(--wds-color-border-hover)" + : "var(--wds-color-border-danger-hover)"}; + } + + .tox .tox-statusbar { + background: ${(props) => + props.isDisabled + ? "var(--wds-color-bg-disabled)" + : "var(--wds-color-bg)"}; + } + + .tox:not([dir="rtl"]) .tox-toolbar__group:not(:last-of-type) { + border-right: none; + border-bottom: none; + position: relative; + + &::after { + content: ""; + height: 39px; + width: 1px; + position: absolute; + right: 0; + background: var(--wds-color-border); + } + } + + .tox:not([dir="rtl"]) .tox-toolbar__group:not(:last-of-type), + .tox .tox-statusbar { + border-color: var(--wds-color-border); + } + + .tox .tox-tbtn svg, + #tox-icon-highlight-bg-color__color, + #tox-icon-text-color__color { + fill: ${(props) => + props.isDisabled + ? "var(--wds-color-icon-disabled)" + : "var(--wds-color-icon)"}; + } + + .tox .tox-tbtn { + margin: 3px 0 2px 0; + border-radius: ${({ borderRadius }) => borderRadius}; + + &:hover { + background: var(--wds-color-bg-hover); + } + } + + .tox .tox-toolbar, + .tox .tox-toolbar__overflow { + background: linear-gradient( + to bottom, + var(--wds-color-border) 1px, + transparent 1px + ); + background-color: ${(props) => + props.isDisabled + ? "var(--wds-color-bg-disabled)" + : "var(--wds-color-bg)"}; + background-size: auto 39px; + } + + .tox-editor-header { + border-bottom: 1px solid var(--wds-color-border); + } + + .tox-tbtn__select-label { + color: ${(props) => + props.isDisabled + ? "var(--wds-color-text-disabled)" + : "var(--wds-color-text)"}; + } + + .tox .tox-split-button { + margin: 3px 0 2px 0; + border-radius: ${({ borderRadius }) => borderRadius}; + + &:hover { + box-shadow: 0 0 0 1px var(--wds-color-border) inset; + } + &:focus { + background: var(--wds-color-bg-focus); + } + &:active { + background: var(--wds-color-bg-focus); + } + } + + .tox .tox-tbtn:focus:not(.tox-tbtn--disabled) { + background: var(--wds-color-bg-selected); + } + + .tox .tox-tbtn:active:not(.tox-tbtn--disabled) { + background: var(--wds-color-bg-focus); + } + + .tox .tox-split-button__chevron { + width: 24px; + padding-right: 0px; + } + + .tox .tox-tbtn--enabled { + background: var(--wds-color-bg-focus); + color: var(--wds-color-text); + + .tox-tbtn svg, + .tox-tbtn__icon-wrap svg, + #tox-icon-highlight-bg-color__color, + #tox-icon-text-color__color { + fill: ${(props) => + props.isDisabled + ? "var(--wds-color-icon-disabled)" + : "var(--wds-color-text)"}; + } + } + + .tox .tox-toolbar__group { + height: 39px; + } + + .tox .tox-tbtn--disabled svg, + .tox .tox-tbtn--disabled:hover svg, + .tox .tox-tbtn:disabled svg, + .tox .tox-tbtn:disabled:hover svg { + fill: var(--wds-color-icon-disabled); + } + ${labelLayoutStyles} `; @@ -48,7 +216,6 @@ export const RichTextEditorInputWrapper = styled.div<{ width: 100%; min-width: 0; height: 100%; - border: 1px solid ${(props) => (props.isValid ? "none" : Colors.DANGER_SOLID)}; border-radius: ${({ borderRadius }) => borderRadius}; `; @@ -126,9 +293,13 @@ export function RichtextEditorComponent(props: RichtextEditorComponentProps) { {labelText && ( @@ -160,6 +331,14 @@ export function RichtextEditorComponent(props: RichtextEditorComponentProps) { branding: false, resize: false, browser_spellcheck: true, + content_style: `${cssVariables} + * { + color: ${ + props.isDisabled + ? "var(--wds-color-text-disabled)" + : "var(--wds-color-text)" + }; + }`, plugins: [ "advlist autolink lists link image charmap print preview anchor", "searchreplace visualblocks code fullscreen", @@ -191,7 +370,7 @@ export function RichtextEditorComponent(props: RichtextEditorComponentProps) { }); }, }} - key={`editor_${props.isToolbarHidden}`} + key={`editor_${props.isToolbarHidden}_${props.isDisabled}`} onEditorChange={handleEditorChange} tinymceScriptSrc="https://cdnjs.cloudflare.com/ajax/libs/tinymce/5.10.1/tinymce.min.js" toolbar={props.isToolbarHidden ? false : toolbarConfig} diff --git a/app/client/src/workers/DependencyMap/utils.ts b/app/client/src/workers/DependencyMap/utils.ts index efeb609bb9..c66f6eed6d 100644 --- a/app/client/src/workers/DependencyMap/utils.ts +++ b/app/client/src/workers/DependencyMap/utils.ts @@ -7,7 +7,7 @@ import { getDynamicBindings, extraLibrariesNames, } from "utils/DynamicBindingUtils"; -import { extractInfoFromCode } from "@shared/ast"; +import { extractIdentifierInfoFromCode } from "@shared/ast"; import { convertPathToString, isWidget } from "../evaluationUtils"; import { DataTreeWidget } from "entities/DataTree/dataTreeFactory"; import { @@ -31,7 +31,7 @@ export const extractInfoFromBinding = ( script: string, allPaths: Record, ): { validReferences: string[]; invalidReferences: string[] } => { - const { references } = extractInfoFromCode( + const { references } = extractIdentifierInfoFromCode( script, self.evaluationVersion, invalidEntityIdentifiers, diff --git a/app/rts/jest.config.js b/app/rts/jest.config.js new file mode 100644 index 0000000000..e7a550d72a --- /dev/null +++ b/app/rts/jest.config.js @@ -0,0 +1,23 @@ +module.exports = { + roots: ["/src"], + transform: { + "^.+\\.(png|js|ts|tsx)$": "ts-jest", + }, + testTimeout: 9000, + testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(tsx|ts|js)?$", + moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node", "css"], + moduleDirectories: ["node_modules", "src", "test"], + moduleNameMapper: { + "@constants/(.*)": ["/src/constants/$1"], + "@services/(.*)": ["/src/services/$1"], + "@middlewares/(.*)": ["/src/middlewares/$1"], + "@controllers/(.*)": ["/src/controllers/$1"], + "@rules/(.*)": ["/src/middlewares/rules/$1"], + "@utils/(.*)": ["/src/utils/$1"], + }, + globals: { + "ts-jest": { + isolatedModules: true, + }, + }, +}; diff --git a/app/rts/package.json b/app/rts/package.json index a6cd00d6ac..30dc659c89 100644 --- a/app/rts/package.json +++ b/app/rts/package.json @@ -12,17 +12,22 @@ }, "devDependencies": { "@types/express": "^4.17.11", + "@types/jest": "^29.0.3", "@types/mongodb": "^3.6.10", "axios": "^0.21.2", "express": "^4.17.1", + "jest": "^29.0.3", "loglevel": "^1.7.1", "mongodb": "^3.6.4", "socket.io": "^4.5.1", "socket.io-adapter": "^2.3.2", "source-map-support": "^0.5.19", + "ts-jest": "^29.0.2", "typescript": "^4.2.3" }, "scripts": { + "test:unit": "export APPSMITH_API_BASE_URL=http APPSMITH_MONGODB_URI=mongodb && $(npm bin)/jest -b --colors --no-cache --silent --coverage --collectCoverage=true --coverageDirectory='../../' --coverageReporters='json-summary'", + "test:jest": "export APPSMITH_API_BASE_URL=http APPSMITH_MONGODB_URI=mongodb && $(npm bin)/jest --watch ", "preinstall": "CURRENT_SCOPE=rts node ../shared/build-shared-dep.js", "build": "./build.sh", "postinstall": "CURRENT_SCOPE=rts node ../shared/install-dependencies.js", @@ -31,6 +36,7 @@ "dependencies": { "express-validator": "^6.14.2", "http-status-codes": "^2.2.0", + "supertest": "^6.2.4", "tsc-alias": "^1.7.0" } } diff --git a/app/rts/src/controllers/Ast/AstController.ts b/app/rts/src/controllers/Ast/AstController.ts index cc72549d66..f67b6ba226 100644 --- a/app/rts/src/controllers/Ast/AstController.ts +++ b/app/rts/src/controllers/Ast/AstController.ts @@ -18,11 +18,11 @@ export default class AstController extends BaseController { super(); } - async getDependentIdentifiers(req: Request, res: Response) { + async getIdentifierDataFromScript(req: Request, res: Response) { try { // By default the application eval version is set to be 2 const { script, evalVersion = 2 }: ScriptToIdentifiersType = req.body; - const data = await AstService.getIdentifiersFromScript( + const data = await AstService.extractIdentifierDataFromScript( script, evalVersion ); @@ -37,7 +37,7 @@ export default class AstController extends BaseController { } } - async getMultipleDependentIdentifiers(req: Request, res: Response) { + async getIdentifierDataFromMultipleScripts(req: Request, res: Response) { try { // By default the application eval version is set to be 2 const { scripts, evalVersion = 2 }: MultipleScriptToIdentifiersType = @@ -46,7 +46,10 @@ export default class AstController extends BaseController { Promise.all( scripts.map( async (script) => - await AstService.getIdentifiersFromScript(script, evalVersion) + await AstService.extractIdentifierDataFromScript( + script, + evalVersion + ) ) ).then((data) => { return super.sendResponse(res, data); diff --git a/app/rts/src/controllers/BaseController.ts b/app/rts/src/controllers/BaseController.ts index 1b4aa4839d..c02017d44c 100644 --- a/app/rts/src/controllers/BaseController.ts +++ b/app/rts/src/controllers/BaseController.ts @@ -1,6 +1,7 @@ import { Response } from "express"; import { ValidationError } from "express-validator"; import { StatusCodes } from "http-status-codes"; +import { IdentifierInfo } from "@shared/ast"; type ErrorData = { error: string | string[]; @@ -16,7 +17,7 @@ type ErrorBag = { type ResponseData = { success: boolean; message?: string; - data: unknown; //setting unknown for now, to be modified later. + data: IdentifierInfo; }; export default class BaseController { diff --git a/app/rts/src/routes/ast_routes.ts b/app/rts/src/routes/ast_routes.ts index 9fbc75adff..46f1dda3cd 100644 --- a/app/rts/src/routes/ast_routes.ts +++ b/app/rts/src/routes/ast_routes.ts @@ -8,17 +8,17 @@ const astController = new AstController(); const validator = new Validator(); router.post( - "/single-script-identifiers", + "/single-script-data", AstRules.getScriptValidator(), validator.validateRequest, - astController.getDependentIdentifiers + astController.getIdentifierDataFromScript ); router.post( - "/multiple-script-identifiers", + "/multiple-script-data", AstRules.getMultipleScriptValidator(), validator.validateRequest, - astController.getMultipleDependentIdentifiers + astController.getIdentifierDataFromMultipleScripts ); export default router; diff --git a/app/rts/src/server.ts b/app/rts/src/server.ts index b95e4d01b3..778d516d66 100644 --- a/app/rts/src/server.ts +++ b/app/rts/src/server.ts @@ -10,7 +10,7 @@ import { initializeSockets } from "./sockets"; import ast_routes from "./routes/ast_routes"; const RTS_BASE_PATH = "/rts"; -const RTS_BASE_API_PATH = "/rts-api/v1"; +export const RTS_BASE_API_PATH = "/rts-api/v1"; // Setting the logLevel for all log messages const logLevel: LogLevelDesc = (process.env.APPSMITH_LOG_LEVEL || @@ -36,32 +36,30 @@ if (API_BASE_URL == null || API_BASE_URL === "") { const PORT = process.env.PORT || 8091; -main(); +//Disable x-powered-by header to prevent information disclosure +const app = express(); +app.disable("x-powered-by"); +const server = new http.Server(app); +const io = new Server(server, { + path: RTS_BASE_PATH, +}); -function main() { - const app = express(); - //Disable x-powered-by header to prevent information disclosure - app.disable("x-powered-by"); - const server = new http.Server(app); - const io = new Server(server, { - path: RTS_BASE_PATH, - }); +// Initializing Sockets +initializeSockets(io); - // Initializing Sockets - initializeSockets(io); +// parse incoming json requests +app.use(express.json({ limit: "5mb" })); +// Initializing Routes +app.use(express.static(path.join(__dirname, "static"))); +app.get("/", (_, res) => { + res.redirect("/index.html"); +}); - // parse incoming json requests - app.use(express.json({ limit: "5mb" })); - // Initializing Routes - app.use(express.static(path.join(__dirname, "static"))); - app.get("/", (_, res) => { - res.redirect("/index.html"); - }); +app.use(`${RTS_BASE_API_PATH}/ast`, ast_routes); - app.use(`${RTS_BASE_API_PATH}/ast`, ast_routes); +// Run the server +server.listen(PORT, () => { + log.info(`RTS version ${buildVersion} running at http://localhost:${PORT}`); +}); - // Run the server - server.listen(PORT, () => { - log.info(`RTS version ${buildVersion} running at http://localhost:${PORT}`); - }); -} +export default server; diff --git a/app/rts/src/services/AstService.ts b/app/rts/src/services/AstService.ts index 5b7b7b83cb..457564abc6 100644 --- a/app/rts/src/services/AstService.ts +++ b/app/rts/src/services/AstService.ts @@ -1,18 +1,20 @@ -import { extractInfoFromCode } from "@shared/ast"; +import { extractIdentifierInfoFromCode } from "@shared/ast"; export default class AstService { - static async getIdentifiersFromScript( + static async extractIdentifierDataFromScript( script, - evalVersion + evalVersion, + invalidIdentifiers = {} ): Promise { return new Promise((resolve, reject) => { try { - const extractions = extractInfoFromCode( + const identifierInfo = extractIdentifierInfoFromCode( script, - evalVersion + evalVersion, + invalidIdentifiers ); - resolve(extractions); + resolve(identifierInfo); } catch (err) { reject(err); } diff --git a/app/rts/src/test/server.test.ts b/app/rts/src/test/server.test.ts new file mode 100644 index 0000000000..e5ebf579ab --- /dev/null +++ b/app/rts/src/test/server.test.ts @@ -0,0 +1,67 @@ +import app, { RTS_BASE_API_PATH } from "../server"; +import supertest from "supertest"; + +const singleScript = { + script: + "(function abc() { let Api2 = { }; return Api2.data ? str.data + Api1.data : [] })()", +}; + +const multipleScripts = { + scripts: [ + "(function abc() { return Api1.data })() ", + "(function abc() { let str = ''; return str ? Api1.data : [] })()", + ], +}; + +afterAll((done) => { + app.close(); + done(); +}); + +describe("AST tests", () => { + it("Checks to see if single script is parsed correctly using the API", async () => { + const expectedResponse = { + references: ["str.data", "Api1.data"], + functionalParams: [], + variables: ["Api2"], + }; + + await supertest(app) + .post(`${RTS_BASE_API_PATH}/ast/single-script-data`, { + JSON: true, + }) + .send(singleScript) + .expect(200) + .then((response) => { + expect(response.body.success).toEqual(true); + expect(response.body.data).toEqual(expectedResponse); + }); + }); + + it("Checks to see if multiple scripts are parsed correctly using the API", async () => { + const expectedResponse = [ + { + references: ["Api1.data"], + functionalParams: [], + variables: [], + }, + { + references: ["Api1.data"], + functionalParams: [], + variables: ["str"], + }, + ]; + + await supertest(app) + .post(`${RTS_BASE_API_PATH}/ast/multiple-script-data`, { + JSON: true, + }) + .send(multipleScripts) + .expect(200) + .then((response) => { + expect(response.body.success).toEqual(true); + expect(response.body.data.length).toBeGreaterThan(1); + expect(response.body.data).toEqual(expectedResponse); + }); + }); +}); diff --git a/app/rts/tsconfig.json b/app/rts/tsconfig.json index 46c2f2bae1..9ee5400337 100644 --- a/app/rts/tsconfig.json +++ b/app/rts/tsconfig.json @@ -14,7 +14,7 @@ "@middlewares/*": ["./src/middlewares/*"], "@controllers/*": ["./src/controllers/*"], "@rules/*": ["./src/middlewares/rules/*"], - "@utils/*": ["./src/utils/*"], + "@utils/*": ["./src/utils/*"] } }, "lib": ["es2015"] diff --git a/app/rts/yarn.lock b/app/rts/yarn.lock index b0e00f2d71..b5c9bc54f1 100644 --- a/app/rts/yarn.lock +++ b/app/rts/yarn.lock @@ -2,6 +2,550 @@ # yarn lockfile v1 +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.1.tgz#72d647b4ff6a4f82878d184613353af1dd0290f9" + integrity sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.1.tgz#c8fa615c5e88e272564ace3d42fbc8b17bfeb22b" + integrity sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.1" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helpers" "^7.19.0" + "@babel/parser" "^7.19.1" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.1" + "@babel/types" "^7.19.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/generator@^7.19.0", "@babel/generator@^7.7.2": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.0.tgz#785596c06425e59334df2ccee63ab166b738419a" + integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== + dependencies: + "@babel/types" "^7.19.0" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz#7f630911d83b408b76fe584831c98e5395d7a17c" + integrity sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg== + dependencies: + "@babel/compat-data" "^7.19.1" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" + semver "^6.3.0" + +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + +"@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30" + integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.8.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== + +"@babel/helper-simple-access@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" + integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" + integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== + +"@babel/helper-validator-identifier@^7.18.6": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helpers@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18" + integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== + dependencies: + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.1.tgz#6f6d6c2e621aad19a92544cc217ed13f1aac5b4c" + integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz#1c09cd25795c7c2b8a4ba9ae49394576d4133285" + integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/template@^7.18.10", "@babel/template@^7.3.3": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" + +"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.7.2": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.1.tgz#0fafe100a8c2a603b4718b1d9bf2568d1d193347" + integrity sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.19.1" + "@babel/types" "^7.19.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600" + integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== + dependencies: + "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.0.3.tgz#a222ab87e399317a89db88a58eaec289519e807a" + integrity sha512-cGg0r+klVHSYnfE977S9wmpuQ9L+iYuYgL+5bPXiUlUynLLYunRxswEmhBzvrSKGof5AKiHuTTmUKAqRcDY9dg== + dependencies: + "@jest/types" "^29.0.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.0.3" + jest-util "^29.0.3" + slash "^3.0.0" + +"@jest/core@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.0.3.tgz#ba22a9cbd0c7ba36e04292e2093c547bf53ec1fd" + integrity sha512-1d0hLbOrM1qQE3eP3DtakeMbKTcXiXP3afWxqz103xPyddS2NhnNghS7MaXx1dcDt4/6p4nlhmeILo2ofgi8cQ== + dependencies: + "@jest/console" "^29.0.3" + "@jest/reporters" "^29.0.3" + "@jest/test-result" "^29.0.3" + "@jest/transform" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.0.0" + jest-config "^29.0.3" + jest-haste-map "^29.0.3" + jest-message-util "^29.0.3" + jest-regex-util "^29.0.0" + jest-resolve "^29.0.3" + jest-resolve-dependencies "^29.0.3" + jest-runner "^29.0.3" + jest-runtime "^29.0.3" + jest-snapshot "^29.0.3" + jest-util "^29.0.3" + jest-validate "^29.0.3" + jest-watcher "^29.0.3" + micromatch "^4.0.4" + pretty-format "^29.0.3" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.0.3.tgz#7745ec30a954e828e8cc6df6a13280d3b51d8f35" + integrity sha512-iKl272NKxYNQNqXMQandAIwjhQaGw5uJfGXduu8dS9llHi8jV2ChWrtOAVPnMbaaoDhnI3wgUGNDvZgHeEJQCA== + dependencies: + "@jest/fake-timers" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + jest-mock "^29.0.3" + +"@jest/expect-utils@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.0.3.tgz#f5bb86f5565bf2dacfca31ccbd887684936045b2" + integrity sha512-i1xUkau7K/63MpdwiRqaxgZOjxYs4f0WMTGJnYwUKubsNRZSeQbLorS7+I4uXVF9KQ5r61BUPAUMZ7Lf66l64Q== + dependencies: + jest-get-type "^29.0.0" + +"@jest/expect@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.0.3.tgz#9dc7c46354eeb7a348d73881fba6402f5fdb2c30" + integrity sha512-6W7K+fsI23FQ01H/BWccPyDZFrnU9QlzDcKOjrNVU5L8yUORFAJJIpmyxWPW70+X624KUNqzZwPThPMX28aXEQ== + dependencies: + expect "^29.0.3" + jest-snapshot "^29.0.3" + +"@jest/fake-timers@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.0.3.tgz#ad5432639b715d45a86a75c47fd75019bc36b22c" + integrity sha512-tmbUIo03x0TdtcZCESQ0oQSakPCpo7+s6+9mU19dd71MptkP4zCwoeZqna23//pgbhtT1Wq02VmA9Z9cNtvtCQ== + dependencies: + "@jest/types" "^29.0.3" + "@sinonjs/fake-timers" "^9.1.2" + "@types/node" "*" + jest-message-util "^29.0.3" + jest-mock "^29.0.3" + jest-util "^29.0.3" + +"@jest/globals@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.0.3.tgz#681950c430fdc13ff9aa89b2d8d572ac0e4a1bf5" + integrity sha512-YqGHT65rFY2siPIHHFjuCGUsbzRjdqkwbat+Of6DmYRg5shIXXrLdZoVE/+TJ9O1dsKsFmYhU58JvIbZRU1Z9w== + dependencies: + "@jest/environment" "^29.0.3" + "@jest/expect" "^29.0.3" + "@jest/types" "^29.0.3" + jest-mock "^29.0.3" + +"@jest/reporters@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.0.3.tgz#735f110e08b44b38729d8dbbb74063bdf5aba8a5" + integrity sha512-3+QU3d4aiyOWfmk1obDerie4XNCaD5Xo1IlKNde2yGEi02WQD+ZQD0i5Hgqm1e73sMV7kw6pMlCnprtEwEVwxw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.0.3" + "@jest/test-result" "^29.0.3" + "@jest/transform" "^29.0.3" + "@jest/types" "^29.0.3" + "@jridgewell/trace-mapping" "^0.3.15" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.0.3" + jest-util "^29.0.3" + jest-worker "^29.0.3" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + terminal-link "^2.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.0.0": + version "29.0.0" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.0.0.tgz#5f47f5994dd4ef067fb7b4188ceac45f77fe952a" + integrity sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA== + dependencies: + "@sinclair/typebox" "^0.24.1" + +"@jest/source-map@^29.0.0": + version "29.0.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.0.0.tgz#f8d1518298089f8ae624e442bbb6eb870ee7783c" + integrity sha512-nOr+0EM8GiHf34mq2GcJyz/gYFyLQ2INDhAylrZJ9mMWoW21mLBfZa0BUVPPMxVYrLjeiRe2Z7kWXOGnS0TFhQ== + dependencies: + "@jridgewell/trace-mapping" "^0.3.15" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.0.3.tgz#b03d8ef4c58be84cd5d5d3b24d4b4c8cabbf2746" + integrity sha512-vViVnQjCgTmbhDKEonKJPtcFe9G/CJO4/Np4XwYJah+lF2oI7KKeRp8t1dFvv44wN2NdbDb/qC6pi++Vpp0Dlg== + dependencies: + "@jest/console" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.0.3.tgz#0681061ad21fb8e293b49c4fdf7e631ca79240ba" + integrity sha512-Hf4+xYSWZdxTNnhDykr8JBs0yBN/nxOXyUQWfotBUqqy0LF9vzcFB0jm/EDNZCx587znLWTIgxcokW7WeZMobQ== + dependencies: + "@jest/test-result" "^29.0.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.0.3" + slash "^3.0.0" + +"@jest/transform@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.0.3.tgz#9eb1fed2072a0354f190569807d1250572fb0970" + integrity sha512-C5ihFTRYaGDbi/xbRQRdbo5ddGtI4VSpmL6AIcZxdhwLbXMa7PcXxxqyI91vGOFHnn5aVM3WYnYKCHEqmLVGzg== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.0.3" + "@jridgewell/trace-mapping" "^0.3.15" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.0.3" + jest-regex-util "^29.0.0" + jest-util "^29.0.3" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.1" + +"@jest/types@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.0.3.tgz#0be78fdddb1a35aeb2041074e55b860561c8ef63" + integrity sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A== + dependencies: + "@jest/schemas" "^29.0.0" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.15" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" + integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -23,6 +567,58 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@sinclair/typebox@^0.24.1": + version "0.24.43" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.43.tgz#2e2bce0e5e493aaf639beed0cd6c88cfde7dd3d7" + integrity sha512-1orQTvtazZmsPeBroJjysvsOQCYV2yjWlebkSY38pl5vr2tdLjEJ+LoxITlGNZaH2RE19WlAwQMkH/7C14wLfw== + +"@sinonjs/commons@^1.7.0": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^9.1.2": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" + integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@types/babel__core@^7.1.14": + version "7.1.19" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" + integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.2.tgz#235bf339d17185bdec25e024ca19cce257cc7309" + integrity sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg== + dependencies: + "@babel/types" "^7.3.0" + "@types/body-parser@*": version "1.19.0" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" @@ -79,6 +675,40 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/graceful-fs@^4.1.3": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.0.3.tgz#b61a5ed100850686b8d3c5e28e3a1926b2001b59" + integrity sha512-F6ukyCTwbfsEX5F2YmVYmM5TcTHy1q9P5rWlRbrk56KyMh3v9xRGUO3aa8+SkvMi0SHXtASJv1283enXimC0Og== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + "@types/mime@^1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" @@ -102,6 +732,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055" integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g== +"@types/prettier@^2.1.5": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e" + integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow== + "@types/qs@*": version "6.9.6" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1" @@ -120,6 +755,23 @@ "@types/mime" "^1" "@types/node" "*" +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^17.0.8": + version "17.0.13" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76" + integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg== + dependencies: + "@types/yargs-parser" "*" + accepts@~1.3.4, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -128,7 +780,38 @@ accepts@~1.3.4, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" -anymatch@~3.1.2: +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -136,6 +819,13 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -146,6 +836,16 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +asap@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + axios@^0.21.2: version "0.21.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.2.tgz#21297d5084b2aeeb422f5d38e7be4fbb82239017" @@ -153,6 +853,71 @@ axios@^0.21.2: dependencies: follow-redirects "^1.14.0" +babel-jest@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.0.3.tgz#64e156a47a77588db6a669a88dedff27ed6e260f" + integrity sha512-ApPyHSOhS/sVzwUOQIWJmdvDhBsMG01HX9z7ogtkp1TToHGGUWFlnXJUIzCgKPSfiYLn3ibipCYzsKSURHEwLg== + dependencies: + "@jest/transform" "^29.0.3" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.0.2" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.0.2.tgz#ae61483a829a021b146c016c6ad39b8bcc37c2c8" + integrity sha512-eBr2ynAEFjcebVvu8Ktx580BD1QKCrBG1XwEUTXJe285p9HA/4hOhfWCFRQhTKSyBV0VzjhG7H91Eifz9s29hg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.0.2.tgz#e14a7124e22b161551818d89e5bdcfb3b2b0eac7" + integrity sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA== + dependencies: + babel-plugin-jest-hoist "^29.0.2" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + base64id@2.0.0, base64id@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" @@ -187,6 +952,14 @@ body-parser@1.19.0: raw-body "2.4.0" type-is "~1.6.17" +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -194,6 +967,30 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" +browserslist@^4.21.3: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== + dependencies: + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" + node-releases "^2.0.6" + update-browserslist-db "^1.0.9" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + bson@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.5.tgz#2aaae98fcdf6750c0848b0cba1ddec3c73060a34" @@ -209,6 +1006,56 @@ bytes@3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001400: + version "1.0.30001412" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001412.tgz#30f67d55a865da43e0aeec003f073ea8764d5d7c" + integrity sha512-+TeEIee1gS5bYOiuf+PS/kp2mrXic37Hl66VY6EAfxasIk5fELTktK2oOezYed12H8w7jt3s512PpulQidPjwA== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -224,16 +1071,81 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +ci-info@^3.2.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.4.0.tgz#b28484fd436cbc267900364f096c9dc185efb251" + integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== + +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^9.0.0: version "9.4.0" resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.0.tgz#bc4a40918fefe52e22450c111ecd6b7acce6f11c" integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw== -component-emitter@~1.3.0: +component-emitter@^1.3.0, component-emitter@~1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + content-disposition@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -246,6 +1158,13 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -261,6 +1180,11 @@ cookie@~0.4.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== +cookiejar@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" + integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== + core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -274,6 +1198,15 @@ cors@~2.8.5: object-assign "^4" vary "^1" +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -281,6 +1214,13 @@ debug@2.6.9: dependencies: ms "2.0.0" +debug@^4.1.0, debug@^4.1.1, debug@^4.3.4, debug@~4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@~4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" @@ -288,12 +1228,20 @@ debug@~4.3.1: dependencies: ms "2.1.2" -debug@~4.3.2: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== denque@^1.4.1: version "1.5.0" @@ -310,6 +1258,24 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +dezalgo@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" + integrity sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ== + dependencies: + asap "^2.0.0" + wrappy "1" + +diff-sequences@^29.0.0: + version "29.0.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.0.0.tgz#bae49972ef3933556bcb0800b72e8579d19d9e4f" + integrity sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -322,6 +1288,21 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +electron-to-chromium@^1.4.251: + version "1.4.261" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.261.tgz#31f14ad60c6f95bec404a77a2fd5e1962248e112" + integrity sha512-fVXliNUGJ7XUVJSAasPseBbVgJIeyw5M1xIkgXdTSRjlmCqBbiSTsEdLOCJS31Fc8B7CaloQ/BFAg8By3ODLdg== + +emittery@^0.10.2: + version "0.10.2" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" + integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -348,16 +1329,74 @@ engine.io@~6.2.0: engine.io-parser "~5.0.3" ws "~8.2.3" +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.0.3.tgz#6be65ddb945202f143c4e07c083f4f39f3bd326f" + integrity sha512-t8l5DTws3212VbmPL+tBFXhjRHLmctHB0oQbL8eUc6S7NzZtYUhycrFO9mkxA0ZUC6FAWdNi7JchJSkODtcu1Q== + dependencies: + "@jest/expect-utils" "^29.0.3" + jest-get-type "^29.0.0" + jest-matcher-utils "^29.0.3" + jest-message-util "^29.0.3" + jest-util "^29.0.3" + express-validator@^6.14.2: version "6.14.2" resolved "https://registry.yarnpkg.com/express-validator/-/express-validator-6.14.2.tgz#6147893f7bec0e14162c3a88b3653121afc4678f" @@ -413,6 +1452,16 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-safe-stringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + fastq@^1.6.0: version "1.13.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" @@ -420,6 +1469,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -440,11 +1496,38 @@ finalhandler@~1.1.2: statuses "~1.5.0" unpipe "~1.0.0" +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + follow-redirects@^1.14.0: version "1.14.9" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +formidable@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.0.1.tgz#4310bc7965d185536f9565184dee74fbb75557ff" + integrity sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ== + dependencies: + dezalgo "1.0.3" + hexoid "1.0.0" + once "1.4.0" + qs "6.9.3" + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -455,11 +1538,50 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -fsevents@~2.3.2: +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -467,6 +1589,23 @@ glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + globby@^11.0.4: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -479,6 +1618,43 @@ globby@^11.0.4: merge2 "^1.4.1" slash "^3.0.0" +graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hexoid@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" + integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + http-errors@1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" @@ -506,6 +1682,11 @@ http-status-codes@^2.2.0: resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.2.0.tgz#bb2efe63d941dfc2be18e15f703da525169622be" integrity sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng== +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -518,21 +1699,47 @@ ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + inherits@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -inherits@2.0.4, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -540,11 +1747,28 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-core-module@^2.9.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" + integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== + dependencies: + has "^1.0.3" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -557,11 +1781,477 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" + integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" + integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^29.0.0: + version "29.0.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.0.0.tgz#aa238eae42d9372a413dd9a8dadc91ca1806dce0" + integrity sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ== + dependencies: + execa "^5.0.0" + p-limit "^3.1.0" + +jest-circus@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.0.3.tgz#90faebc90295291cfc636b27dbd82e3bfb9e7a48" + integrity sha512-QeGzagC6Hw5pP+df1+aoF8+FBSgkPmraC1UdkeunWh0jmrp7wC0Hr6umdUAOELBQmxtKAOMNC3KAdjmCds92Zg== + dependencies: + "@jest/environment" "^29.0.3" + "@jest/expect" "^29.0.3" + "@jest/test-result" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + is-generator-fn "^2.0.0" + jest-each "^29.0.3" + jest-matcher-utils "^29.0.3" + jest-message-util "^29.0.3" + jest-runtime "^29.0.3" + jest-snapshot "^29.0.3" + jest-util "^29.0.3" + p-limit "^3.1.0" + pretty-format "^29.0.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.0.3.tgz#fd8f0ef363a7a3d9c53ef62e0651f18eeffa77b9" + integrity sha512-aUy9Gd/Kut1z80eBzG10jAn6BgS3BoBbXyv+uXEqBJ8wnnuZ5RpNfARoskSrTIy1GY4a8f32YGuCMwibtkl9CQ== + dependencies: + "@jest/core" "^29.0.3" + "@jest/test-result" "^29.0.3" + "@jest/types" "^29.0.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^29.0.3" + jest-util "^29.0.3" + jest-validate "^29.0.3" + prompts "^2.0.1" + yargs "^17.3.1" + +jest-config@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.0.3.tgz#c2e52a8f5adbd18de79f99532d8332a19e232f13" + integrity sha512-U5qkc82HHVYe3fNu2CRXLN4g761Na26rWKf7CjM8LlZB3In1jadEkZdMwsE37rd9RSPV0NfYaCjHdk/gu3v+Ew== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.0.3" + "@jest/types" "^29.0.3" + babel-jest "^29.0.3" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.0.3" + jest-environment-node "^29.0.3" + jest-get-type "^29.0.0" + jest-regex-util "^29.0.0" + jest-resolve "^29.0.3" + jest-runner "^29.0.3" + jest-util "^29.0.3" + jest-validate "^29.0.3" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.0.3" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.0.3.tgz#41cc02409ad1458ae1bf7684129a3da2856341ac" + integrity sha512-+X/AIF5G/vX9fWK+Db9bi9BQas7M9oBME7egU7psbn4jlszLFCu0dW63UgeE6cs/GANq4fLaT+8sGHQQ0eCUfg== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.0.0" + jest-get-type "^29.0.0" + pretty-format "^29.0.3" + +jest-docblock@^29.0.0: + version "29.0.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.0.0.tgz#3151bcc45ed7f5a8af4884dcc049aee699b4ceae" + integrity sha512-s5Kpra/kLzbqu9dEjov30kj1n4tfu3e7Pl8v+f8jOkeWNqM6Ds8jRaJfZow3ducoQUrf2Z4rs2N5S3zXnb83gw== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.0.3.tgz#7ef3157580b15a609d7ef663dd4fc9b07f4e1299" + integrity sha512-wILhZfESURHHBNvPMJ0lZlYZrvOQJxAo3wNHi+ycr90V7M+uGR9Gh4+4a/BmaZF0XTyZsk4OiYEf3GJN7Ltqzg== + dependencies: + "@jest/types" "^29.0.3" + chalk "^4.0.0" + jest-get-type "^29.0.0" + jest-util "^29.0.3" + pretty-format "^29.0.3" + +jest-environment-node@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.0.3.tgz#293804b1e0fa5f0e354dacbe510655caa478a3b2" + integrity sha512-cdZqRCnmIlTXC+9vtvmfiY/40Cj6s2T0czXuq1whvQdmpzAnj4sbqVYuZ4zFHk766xTTJ+Ij3uUqkk8KCfXoyg== + dependencies: + "@jest/environment" "^29.0.3" + "@jest/fake-timers" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + jest-mock "^29.0.3" + jest-util "^29.0.3" + +jest-get-type@^29.0.0: + version "29.0.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.0.0.tgz#843f6c50a1b778f7325df1129a0fd7aa713aef80" + integrity sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw== + +jest-haste-map@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.0.3.tgz#d7f3f7180f558d760eacc5184aac5a67f20ef939" + integrity sha512-uMqR99+GuBHo0RjRhOE4iA6LmsxEwRdgiIAQgMU/wdT2XebsLDz5obIwLZm/Psj+GwSEQhw9AfAVKGYbh2G55A== + dependencies: + "@jest/types" "^29.0.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.0.0" + jest-util "^29.0.3" + jest-worker "^29.0.3" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.0.3.tgz#e85cf3391106a7a250850b6766b508bfe9c7bc6f" + integrity sha512-YfW/G63dAuiuQ3QmQlh8hnqLDe25WFY3eQhuc/Ev1AGmkw5zREblTh7TCSKLoheyggu6G9gxO2hY8p9o6xbaRQ== + dependencies: + jest-get-type "^29.0.0" + pretty-format "^29.0.3" + +jest-matcher-utils@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.0.3.tgz#b8305fd3f9e27cdbc210b21fc7dbba92d4e54560" + integrity sha512-RsR1+cZ6p1hDV4GSCQTg+9qjeotQCgkaleIKLK7dm+U4V/H2bWedU3RAtLm8+mANzZ7eDV33dMar4pejd7047w== + dependencies: + chalk "^4.0.0" + jest-diff "^29.0.3" + jest-get-type "^29.0.0" + pretty-format "^29.0.3" + +jest-message-util@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.0.3.tgz#f0254e1ffad21890c78355726202cc91d0a40ea8" + integrity sha512-7T8JiUTtDfppojosORAflABfLsLKMLkBHSWkjNQrjIltGoDzNGn7wEPOSfjqYAGTYME65esQzMJxGDjuLBKdOg== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.0.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.0.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.0.3.tgz#4f0093f6a9cb2ffdb9c44a07a3912f0c098c8de9" + integrity sha512-ort9pYowltbcrCVR43wdlqfAiFJXBx8l4uJDsD8U72LgBcetvEp+Qxj1W9ZYgMRoeAo+ov5cnAGF2B6+Oth+ww== + dependencies: + "@jest/types" "^29.0.3" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^29.0.0: + version "29.0.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.0.0.tgz#b442987f688289df8eb6c16fa8df488b4cd007de" + integrity sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug== + +jest-resolve-dependencies@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.0.3.tgz#f23a54295efc6374b86b198cf8efed5606d6b762" + integrity sha512-KzuBnXqNvbuCdoJpv8EanbIGObk7vUBNt/PwQPPx2aMhlv/jaXpUJsqWYRpP/0a50faMBY7WFFP8S3/CCzwfDw== + dependencies: + jest-regex-util "^29.0.0" + jest-snapshot "^29.0.3" + +jest-resolve@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.0.3.tgz#329a3431e3b9eb6629a2cd483e9bed95b26827b9" + integrity sha512-toVkia85Y/BPAjJasTC9zIPY6MmVXQPtrCk8SmiheC4MwVFE/CMFlOtMN6jrwPMC6TtNh8+sTMllasFeu1wMPg== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.0.3" + jest-pnp-resolver "^1.2.2" + jest-util "^29.0.3" + jest-validate "^29.0.3" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.0.3.tgz#2e47fe1e8777aea9b8970f37e8f83630b508fb87" + integrity sha512-Usu6VlTOZlCZoNuh3b2Tv/yzDpKqtiNAetG9t3kJuHfUyVMNW7ipCCJOUojzKkjPoaN7Bl1f7Buu6PE0sGpQxw== + dependencies: + "@jest/console" "^29.0.3" + "@jest/environment" "^29.0.3" + "@jest/test-result" "^29.0.3" + "@jest/transform" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.10.2" + graceful-fs "^4.2.9" + jest-docblock "^29.0.0" + jest-environment-node "^29.0.3" + jest-haste-map "^29.0.3" + jest-leak-detector "^29.0.3" + jest-message-util "^29.0.3" + jest-resolve "^29.0.3" + jest-runtime "^29.0.3" + jest-util "^29.0.3" + jest-watcher "^29.0.3" + jest-worker "^29.0.3" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.0.3.tgz#5a823ec5902257519556a4e5a71a868e8fd788aa" + integrity sha512-12gZXRQ7ozEeEHKTY45a+YLqzNDR/x4c//X6AqwKwKJPpWM8FY4vwn4VQJOcLRS3Nd1fWwgP7LU4SoynhuUMHQ== + dependencies: + "@jest/environment" "^29.0.3" + "@jest/fake-timers" "^29.0.3" + "@jest/globals" "^29.0.3" + "@jest/source-map" "^29.0.0" + "@jest/test-result" "^29.0.3" + "@jest/transform" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.0.3" + jest-message-util "^29.0.3" + jest-mock "^29.0.3" + jest-regex-util "^29.0.0" + jest-resolve "^29.0.3" + jest-snapshot "^29.0.3" + jest-util "^29.0.3" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.0.3.tgz#0a024706986a915a6eefae74d7343069d2fc8eef" + integrity sha512-52q6JChm04U3deq+mkQ7R/7uy7YyfVIrebMi6ZkBoDJ85yEjm/sJwdr1P0LOIEHmpyLlXrxy3QP0Zf5J2kj0ew== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.0.3" + "@jest/transform" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/babel__traverse" "^7.0.6" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.0.3" + graceful-fs "^4.2.9" + jest-diff "^29.0.3" + jest-get-type "^29.0.0" + jest-haste-map "^29.0.3" + jest-matcher-utils "^29.0.3" + jest-message-util "^29.0.3" + jest-util "^29.0.3" + natural-compare "^1.4.0" + pretty-format "^29.0.3" + semver "^7.3.5" + +jest-util@^29.0.0, jest-util@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.0.3.tgz#06d1d77f9a1bea380f121897d78695902959fbc0" + integrity sha512-Q0xaG3YRG8QiTC4R6fHjHQPaPpz9pJBEi0AeOE4mQh/FuWOijFjGXMMOfQEaU9i3z76cNR7FobZZUQnL6IyfdQ== + dependencies: + "@jest/types" "^29.0.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.0.3.tgz#f9521581d7344685428afa0a4d110e9c519aeeb6" + integrity sha512-OebiqqT6lK8cbMPtrSoS3aZP4juID762lZvpf1u+smZnwTEBCBInan0GAIIhv36MxGaJvmq5uJm7dl5gVt+Zrw== + dependencies: + "@jest/types" "^29.0.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.0.0" + leven "^3.1.0" + pretty-format "^29.0.3" + +jest-watcher@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.0.3.tgz#8e220d1cc4f8029875e82015d084cab20f33d57f" + integrity sha512-tQX9lU91A+9tyUQKUMp0Ns8xAcdhC9fo73eqA3LFxP2bSgiF49TNcc+vf3qgGYYK9qRjFpXW9+4RgF/mbxyOOw== + dependencies: + "@jest/test-result" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.10.2" + jest-util "^29.0.3" + string-length "^4.0.1" + +jest-worker@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.0.3.tgz#c2ba0aa7e41eec9eb0be8e8a322ae6518df72647" + integrity sha512-Tl/YWUugQOjoTYwjKdfJWkSOfhufJHO5LhXTSZC3TRoQKO+fuXnZAdoXXBlpLXKGODBL3OvdUasfDD4PcMe6ng== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.0.3.tgz#5227a0596d30791b2649eea347e4aa97f734944d" + integrity sha512-ElgUtJBLgXM1E8L6K1RW1T96R897YY/3lRYqq9uVcPWtP2AAl/nQ16IYDh/FzQOOQ12VEuLdcPU83mbhG2C3PQ== + dependencies: + "@jest/core" "^29.0.3" + "@jest/types" "^29.0.3" + import-local "^3.0.2" + jest-cli "^29.0.3" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -572,6 +2262,32 @@ loglevel@^1.7.1: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -587,15 +2303,20 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -methods@~1.1.2: +methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== micromatch@^4.0.4: version "4.0.5" @@ -610,6 +2331,18 @@ mime-db@1.46.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime-types@~2.1.24: version "2.1.29" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" @@ -622,6 +2355,23 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + mongodb@^3.6.4: version "3.6.4" resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.6.4.tgz#ca59fd65b06831308262372ef9df6b78f9da97be" @@ -655,21 +2405,48 @@ mylas@^2.1.9: resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.11.tgz#1827462533977bed1c4251317aa84254e3ca94c7" integrity sha512-krnPUl3n9/k52FGCltWMYcqp9SttxjRJEy0sWLk+g7mIa7wnZrmNSZ40Acx7ghzRSOsxt2rEqMbaq4jWlnTDKg== +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + object-assign@^4: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= +object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -677,11 +2454,81 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" +once@1.4.0, once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -692,11 +2539,28 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pirates@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + plimit-lit@^1.2.6: version "1.3.0" resolved "https://registry.yarnpkg.com/plimit-lit/-/plimit-lit-1.3.0.tgz#46908adbfcfc010e65a5a737652768b0fec21587" @@ -704,11 +2568,28 @@ plimit-lit@^1.2.6: dependencies: queue-lit "^1.3.0" +pretty-format@^29.0.0, pretty-format@^29.0.3: + version "29.0.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.0.3.tgz#23d5f8cabc9cbf209a77d49409d093d61166a811" + integrity sha512-cHudsvQr1K5vNVLbvYF/nv3Qy/F/BcEKxGuIeMiVMRHxPOO1RxXooP8g/ZrwAp7Dx+KdMZoOc7NxLHhMrP2f9Q== + dependencies: + "@jest/schemas" "^29.0.0" + ansi-styles "^5.0.0" + react-is "^18.0.0" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + proxy-addr@~2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" @@ -722,6 +2603,18 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@6.9.3: + version "6.9.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e" + integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw== + +qs@^6.10.3: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + queue-lit@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/queue-lit/-/queue-lit-1.3.0.tgz#a29e4cfd0d0e2c6594beb70a4726716a57ffce5b" @@ -747,6 +2640,11 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + readable-stream@^2.3.5: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" @@ -760,6 +2658,15 @@ readable-stream@^2.3.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -767,6 +2674,11 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + require_optional@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e" @@ -775,11 +2687,37 @@ require_optional@^1.0.1: resolve-from "^2.0.0" semver "^5.1.0" +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + resolve-from@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c= +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + +resolve@^1.20.0: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -797,7 +2735,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@^5.1.1, safe-buffer@^5.1.2: +safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -814,11 +2752,23 @@ saslprep@^1.0.0: dependencies: sparse-bitfield "^3.0.3" +semver@7.x, semver@^7.3.5, semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + semver@^5.1.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +semver@^6.0.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -853,6 +2803,37 @@ setprototypeof@1.1.1: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -889,6 +2870,14 @@ socket.io@^4.5.1: socket.io-adapter "~2.4.0" socket.io-parser "~4.0.4" +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-support@^0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" @@ -897,7 +2886,7 @@ source-map-support@^0.5.19: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0: +source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -909,11 +2898,47 @@ sparse-bitfield@^3.0.3: dependencies: memory-pager "^1.0.2" +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + dependencies: + escape-string-regexp "^2.0.0" + "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -921,6 +2946,114 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +superagent@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-8.0.0.tgz#2ea4587df4b81ef023ec01ebc6e1bcb9e2344cb6" + integrity sha512-iudipXEel+SzlP9y29UBWGDjB+Zzag+eeA1iLosaR2YHBRr1Q1kC29iBrF2zIVD9fqVbpZnXkN/VJmwFMVyNWg== + dependencies: + component-emitter "^1.3.0" + cookiejar "^2.1.3" + debug "^4.3.4" + fast-safe-stringify "^2.1.1" + form-data "^4.0.0" + formidable "^2.0.1" + methods "^1.1.2" + mime "2.6.0" + qs "^6.10.3" + readable-stream "^3.6.0" + semver "^7.3.7" + +supertest@^6.2.4: + version "6.2.4" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-6.2.4.tgz#3dcebe42f7fd6f28dd7ac74c6cba881f7101b2f0" + integrity sha512-M8xVnCNv+q2T2WXVzxDECvL2695Uv2uUj2O0utxsld/HRyJvOU8W9f1gvsYxSNU4wmIe0/L/ItnpU4iKq0emDA== + dependencies: + methods "^1.1.2" + superagent "^8.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -933,6 +3066,20 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +ts-jest@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.0.2.tgz#0c45a1ac45d14f8b3bf89bca9048a2840c7bd5ad" + integrity sha512-P03IUItnAjG6RkJXtjjD5pu0TryQFOwcb1YKmW63rO19V0UFqL3wiXZrmR5D7qYjI98btzIOAcYafLZ0GHAcQg== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^29.0.0" + json5 "^2.2.1" + lodash.memoize "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "^21.0.1" + tsc-alias@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.7.0.tgz#733482751133a25b97608ee424f8a1f085fcaaef" @@ -945,6 +3092,16 @@ tsc-alias@^1.7.0: normalize-path "^3.0.0" plimit-lit "^1.2.6" +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -963,16 +3120,33 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= -util-deprecate@~1.0.1: +update-browserslist-db@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz#2924d3927367a38d5c555413a7ce138fc95fcb18" + integrity sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +v8-to-istanbul@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz#b6f994b0b5d4ef255e17a0d17dc444a9f5132fa4" + integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + validator@^13.7.0: version "13.7.0" resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" @@ -983,7 +3157,76 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + ws@~8.2.3: version "8.2.3" resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^21.0.0, yargs-parser@^21.0.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.5.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e" + integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/AnalyticsEvents.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/AnalyticsEvents.java index bcad9d46e2..f394c903ea 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/AnalyticsEvents.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/AnalyticsEvents.java @@ -20,6 +20,7 @@ public enum AnalyticsEvents { UPDATE_LAYOUT, PUBLISH_APPLICATION("publish_APPLICATION"), FORK, + PAGE_REORDER, GENERATE_CRUD_PAGE("generate_CRUD_PAGE"), CREATE_SUPERUSER, SUBSCRIBE_MARKETING_EMAILS, diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java index 4130d8a063..c3e38831d8 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java @@ -137,8 +137,10 @@ public class FieldName { public static final String EVENT_DATA = "eventData"; public static final String MODE_OF_LOGIN = "modeOfLogin"; public static final String FORM_LOGIN = "FormLogin"; - public static final String VIEW_MODE = "viewMode"; + public static final String APP_MODE = "appMode"; + public static final String PAGE_ORDER = "order"; public static final String ACTION_EXECUTION_REQUEST = "actionExecutionRequest"; public static final String ACTION_EXECUTION_RESULT = "actionExecutionResult"; public static final String ACTION_EXECUTION_TIME = "actionExecutionTime"; + public static final String TEMPLATE_APPLICATION_NAME = "templateAppName"; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java index 0c981155af..56a69b4315 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java @@ -433,7 +433,17 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE { .then(newActionService.archiveActionsByApplicationId(application.getId(), MANAGE_ACTIONS)) .then(themeService.archiveApplicationThemes(application)) .flatMap(applicationService::archive) - .flatMap(analyticsService::sendDeleteEvent); + .flatMap(deletedApplication -> { + final Map eventData = Map.of( + FieldName.APP_MODE, ApplicationMode.EDIT.toString(), + FieldName.APPLICATION, deletedApplication + ); + final Map data = Map.of( + FieldName.EVENT_DATA, eventData + ); + + return analyticsService.sendDeleteEvent(deletedApplication, data); + }); } @Override @@ -836,7 +846,16 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE { } Mono archivedPageMono = newPageMono - .flatMap(analyticsService::sendDeleteEvent) + .flatMap(newPage -> { + final Map eventData = Map.of( + FieldName.APP_MODE, ApplicationMode.EDIT.toString() + ); + final Map data = Map.of( + FieldName.EVENT_DATA, eventData + ); + + return analyticsService.sendDeleteEvent(newPage, data); + }) .flatMap(newPage -> newPageService.getPageByViewMode(newPage, false)); /** @@ -1028,6 +1047,12 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE { extraProperties.put("isManual", defaultIfNull(isPublishedManually, "")); extraProperties.put("publishedAt", defaultIfNull(application.getLastDeployedAt(), "")); + final Map eventData = Map.of( + FieldName.APPLICATION, application, + FieldName.APP_MODE, ApplicationMode.EDIT.toString() + ); + extraProperties.put(FieldName.EVENT_DATA, eventData); + return analyticsService.sendObjectEvent(AnalyticsEvents.PUBLISH_APPLICATION, application, extraProperties); }); } @@ -1072,6 +1097,7 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE { return applicationRepository .setPages(application.getId(), pages) + .flatMap(updateResult -> sendPageOrderAnalyticsEvent(application, defaultPageId, order, branchName)) .then(newPageService.findApplicationPagesByApplicationIdViewMode(application.getId(), Boolean.FALSE, false)); }) .map(responseUtils::updateApplicationPagesDTOWithDefaultResources); @@ -1120,7 +1146,8 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE { final Map eventData = Map.of( FieldName.SOURCE_APPLICATION, sourceApplication, FieldName.APPLICATION, application, - FieldName.WORKSPACE, workspace + FieldName.WORKSPACE, workspace, + FieldName.APP_MODE, ApplicationMode.EDIT.toString() ); final Map data = Map.of( @@ -1141,12 +1168,10 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE { * @return NewPage */ private Mono sendPageViewAnalyticsEvent(NewPage newPage, boolean viewMode) { - if (!viewMode) { - return Mono.empty(); - } - + String view = viewMode ? ApplicationMode.EDIT.toString() : ApplicationMode.PUBLISHED.toString(); final Map eventData = Map.of( - FieldName.PAGE, newPage + FieldName.PAGE, newPage, + FieldName.APP_MODE, view ); final Map data = Map.of( @@ -1155,4 +1180,23 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE { return analyticsService.sendObjectEvent(AnalyticsEvents.VIEW, newPage, data); } + + private Mono sendPageOrderAnalyticsEvent(Application application, String pageId, int order, String branchName) { + final Map eventData = Map.of( + FieldName.APPLICATION, application, + FieldName.APP_MODE, ApplicationMode.EDIT.toString() + ); + + final Map data = Map.of( + FieldName.APPLICATION_ID, application.getId(), + FieldName.WORKSPACE_ID, application.getWorkspaceId(), + FieldName.PAGE_ID, pageId, + FieldName.PAGE_ORDER, order, + FieldName.EVENT_DATA, eventData, + FieldName.BRANCH_NAME, defaultIfNull(branchName, "") + ); + + return analyticsService.sendObjectEvent(AnalyticsEvents.PAGE_REORDER, application, data); + + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java index 5507a89ff7..5e71cfddb9 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java @@ -10,6 +10,7 @@ import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Action; import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ApplicationMode; import com.appsmith.server.domains.GitApplicationMetadata; import com.appsmith.server.domains.GitAuth; import com.appsmith.server.domains.NewAction; @@ -260,8 +261,18 @@ public class ApplicationServiceCEImpl extends BaseService { + final Map eventData = Map.of( + FieldName.APP_MODE, ApplicationMode.EDIT.toString(), + FieldName.APPLICATION, application1 + ); + final Map data = Map.of( + FieldName.APPLICATION_ID, application1.getId(), + FieldName.WORKSPACE_ID, application1.getWorkspaceId(), + FieldName.EVENT_DATA, eventData + ); + return analyticsService.sendUpdateEvent(application1, data); + })); } public Mono update(String defaultApplicationId, Application application, String branchName) { @@ -513,12 +524,16 @@ public class ApplicationServiceCEImpl extends BaseService { // Send generate SSH key analytics event assert application.getId() != null; - final Map data = Map.of( - "applicationId", application.getId(), - "organizationId", application.getWorkspaceId(), - "isRegeneratedKey", gitAuth.isRegeneratedKey() + final Map eventData = Map.of( + FieldName.APP_MODE, ApplicationMode.EDIT.toString(), + FieldName.APPLICATION, application + ); + final Map data = Map.of( + FieldName.APPLICATION_ID, application.getId(), + "organizationId", application.getWorkspaceId(), + "isRegeneratedKey", gitAuth.isRegeneratedKey(), + FieldName.EVENT_DATA, eventData ); - return analyticsService.sendObjectEvent(AnalyticsEvents.GENERATE_SSH_KEY, application, data) .onErrorResume(e -> { log.warn("Error sending ssh key generation data point", e); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationTemplateServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationTemplateServiceCEImpl.java index 9a42773326..aa99f5a84a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationTemplateServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationTemplateServiceCEImpl.java @@ -3,7 +3,9 @@ package com.appsmith.server.services.ce; import com.appsmith.external.constants.AnalyticsEvents; import com.appsmith.external.converters.GsonISOStringToInstantConverter; import com.appsmith.server.configurations.CloudServicesConfig; +import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ApplicationMode; import com.appsmith.server.domains.UserData; import com.appsmith.server.dtos.ApplicationImportDTO; import com.appsmith.server.dtos.ApplicationJson; @@ -191,11 +193,20 @@ public class ApplicationTemplateServiceCEImpl implements ApplicationTemplateServ Application application = applicationImportDTO.getApplication(); ApplicationTemplate applicationTemplate = new ApplicationTemplate(); applicationTemplate.setId(templateId); - Map extraProperties = new HashMap<>(); - extraProperties.put("templateAppName", application.getName()); - return userDataService.addTemplateIdToLastUsedList(templateId).then( - analyticsService.sendObjectEvent(AnalyticsEvents.FORK, applicationTemplate, extraProperties) - ).thenReturn(applicationImportDTO); + final Map eventData = Map.of( + FieldName.APP_MODE, ApplicationMode.EDIT.toString(), + FieldName.APPLICATION, application + ); + + final Map data = Map.of( + FieldName.APPLICATION_ID, application.getId(), + FieldName.WORKSPACE_ID, application.getWorkspaceId(), + FieldName.TEMPLATE_APPLICATION_NAME, application.getName(), + FieldName.EVENT_DATA, eventData + ); + + return analyticsService.sendObjectEvent(AnalyticsEvents.FORK, applicationTemplate, data) + .thenReturn(applicationImportDTO); }); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/GitServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/GitServiceCEImpl.java index c331263bcd..5ed08053a6 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/GitServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/GitServiceCEImpl.java @@ -17,6 +17,7 @@ import com.appsmith.server.constants.FieldName; import com.appsmith.server.constants.GitDefaultCommitMessage; import com.appsmith.server.constants.SerialiseApplicationObjective; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ApplicationMode; import com.appsmith.server.dtos.ApplicationJson; import com.appsmith.server.domains.GitApplicationMetadata; import com.appsmith.server.domains.GitAuth; @@ -2429,6 +2430,11 @@ public class GitServiceCEImpl implements GitServiceCE { "isRepoPrivate", defaultIfNull(isRepoPrivate, ""), "isSystemGenerated", defaultIfNull(isSystemGenerated, "") )); + final Map eventData = Map.of( + FieldName.APP_MODE, ApplicationMode.EDIT.toString(), + FieldName.APPLICATION, application + ); + analyticsProps.put(FieldName.EVENT_DATA, eventData); return sessionUserService.getCurrentUser() .map(user -> { analyticsService.sendEvent(eventName, user.getUsername(), analyticsProps); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewActionServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewActionServiceCEImpl.java index 4ebcd3a9dd..171b7ee741 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewActionServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewActionServiceCEImpl.java @@ -27,6 +27,7 @@ import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Action; import com.appsmith.server.domains.ActionProvider; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.ApplicationMode; import com.appsmith.server.domains.DatasourceContext; import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; @@ -571,8 +572,16 @@ public class NewActionServiceCEImpl extends BaseService data = this.getAnalyticsProperties(newAction1, datasource); + + final Map eventData = Map.of( + FieldName.APP_MODE, ApplicationMode.EDIT.toString(), + FieldName.ACTION, newAction1 + ); + data.put(FieldName.EVENT_DATA, eventData); + return analyticsService - .sendUpdateEvent(newAction1, this.getAnalyticsProperties(newAction1, datasource)) + .sendUpdateEvent(newAction1, data) .thenReturn(zippedActions.getT1()); }); @@ -1119,7 +1128,7 @@ public class NewActionServiceCEImpl extends BaseService eventData = Map.of( FieldName.ACTION, action, FieldName.DATASOURCE, datasource, - FieldName.VIEW_MODE, viewMode, + FieldName.APP_MODE, viewMode, FieldName.ACTION_EXECUTION_RESULT, actionExecutionResult, FieldName.ACTION_EXECUTION_TIME, timeElapsed, FieldName.ACTION_EXECUTION_REQUEST, request, @@ -1345,9 +1354,15 @@ public class NewActionServiceCEImpl extends BaseService { final Datasource datasource = zippedActions.getT2(); final NewAction newAction1 = zippedActions.getT1(); + final Map data = this.getAnalyticsProperties(newAction1, datasource); + final Map eventData = Map.of( + FieldName.APP_MODE, ApplicationMode.EDIT.toString(), + FieldName.ACTION, newAction1 + ); + data.put(FieldName.EVENT_DATA, eventData); return analyticsService - .sendArchiveEvent(newAction1, this.getAnalyticsProperties(newAction1, datasource)) + .sendArchiveEvent(newAction1, data) .thenReturn(zippedActions.getT1()); }) @@ -1368,9 +1383,15 @@ public class NewActionServiceCEImpl extends BaseService { final Datasource datasource = zippedActions.getT2(); final NewAction newAction1 = zippedActions.getT1(); + final Map data = this.getAnalyticsProperties(newAction1, datasource); + final Map eventData = Map.of( + FieldName.APP_MODE, ApplicationMode.EDIT.toString(), + FieldName.ACTION, newAction1 + ); + data.put(FieldName.EVENT_DATA, eventData); return analyticsService - .sendDeleteEvent(newAction1, this.getAnalyticsProperties(newAction1, datasource)) + .sendDeleteEvent(newAction1, data) .thenReturn(zippedActions.getT1()); }) @@ -1810,9 +1831,15 @@ public class NewActionServiceCEImpl extends BaseService { final Datasource datasource = zippedActions.getT2(); final NewAction newAction1 = zippedActions.getT1(); + final Map data = this.getAnalyticsProperties(newAction1, datasource); + final Map eventData = Map.of( + FieldName.APP_MODE, ApplicationMode.EDIT.toString(), + FieldName.ACTION, newAction1 + ); + data.put(FieldName.EVENT_DATA, eventData); return analyticsService - .sendDeleteEvent(newAction1, this.getAnalyticsProperties(newAction1, datasource)) + .sendDeleteEvent(newAction1, data) .thenReturn(zippedActions.getT1()); }) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java index e1ebba5977..75dd6d2c6b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java @@ -381,7 +381,7 @@ public class EnvManagerCEImpl implements EnvManagerCE { */ public void setAnalyticsEventAction(Map properties, String newVariable, String originalVariable, String authEnv) { // Authentication configuration added - if (!newVariable.isEmpty() && originalVariable.isEmpty()) { + if (!newVariable.isEmpty() && (originalVariable == null || originalVariable.isEmpty())) { properties.put("action", "Added"); } // Authentication configuration removed diff --git a/app/shared/ast/index.ts b/app/shared/ast/index.ts index b15331228e..285e9b8846 100644 --- a/app/shared/ast/index.ts +++ b/app/shared/ast/index.ts @@ -8,21 +8,27 @@ import { isPropertyNode, isPropertyAFunctionNode, getAST, - extractInfoFromCode, + extractIdentifierInfoFromCode, extractInvalidTopLevelMemberExpressionsFromCode, getFunctionalParamsFromNode, isTypeOfFunction, MemberExpressionData, -} from './src'; + IdentifierInfo, +} from "./src"; // constants -import { ECMA_VERSION, SourceType, NodeTypes } from './src/constants'; +import { ECMA_VERSION, SourceType, NodeTypes } from "./src/constants"; // JSObjects -import { parseJSObjectWithAST } from './src/jsObject'; +import { parseJSObjectWithAST } from "./src/jsObject"; // types or intefaces should be exported with type keyword, while enums can be exported like normal functions -export type { ObjectExpression, PropertyNode, MemberExpressionData }; +export type { + ObjectExpression, + PropertyNode, + MemberExpressionData, + IdentifierInfo, +}; export { isIdentifierNode, @@ -32,7 +38,7 @@ export { isPropertyNode, isPropertyAFunctionNode, getAST, - extractInfoFromCode, + extractIdentifierInfoFromCode, extractInvalidTopLevelMemberExpressionsFromCode, getFunctionalParamsFromNode, isTypeOfFunction, diff --git a/app/shared/ast/src/index.test.ts b/app/shared/ast/src/index.test.ts index 08d3e192c6..1f56d33c0e 100644 --- a/app/shared/ast/src/index.test.ts +++ b/app/shared/ast/src/index.test.ts @@ -1,85 +1,85 @@ -import { extractInfoFromCode } from '../src/index'; -import { parseJSObjectWithAST } from '../src/jsObject'; +import { extractIdentifierInfoFromCode } from "../src/index"; +import { parseJSObjectWithAST } from "../src/jsObject"; -describe('getAllIdentifiers', () => { - it('works properly', () => { +describe("getAllIdentifiers", () => { + it("works properly", () => { const cases: { script: string; expectedResults: string[] }[] = [ { // Entity reference - script: 'DirectTableReference', - expectedResults: ['DirectTableReference'], + script: "DirectTableReference", + expectedResults: ["DirectTableReference"], }, { // One level nesting - script: 'TableDataReference.data', - expectedResults: ['TableDataReference.data'], + script: "TableDataReference.data", + expectedResults: ["TableDataReference.data"], }, { // Deep nesting - script: 'TableDataDetailsReference.data.details', - expectedResults: ['TableDataDetailsReference.data.details'], + script: "TableDataDetailsReference.data.details", + expectedResults: ["TableDataDetailsReference.data.details"], }, { // Deep nesting - script: 'TableDataDetailsMoreReference.data.details.more', - expectedResults: ['TableDataDetailsMoreReference.data.details.more'], + script: "TableDataDetailsMoreReference.data.details.more", + expectedResults: ["TableDataDetailsMoreReference.data.details.more"], }, { // Deep optional chaining - script: 'TableDataOptionalReference.data?.details.more', - expectedResults: ['TableDataOptionalReference.data'], + script: "TableDataOptionalReference.data?.details.more", + expectedResults: ["TableDataOptionalReference.data"], }, { // Deep optional chaining with logical operator script: - 'TableDataOptionalWithLogical.data?.details.more || FallbackTableData.data', + "TableDataOptionalWithLogical.data?.details.more || FallbackTableData.data", expectedResults: [ - 'TableDataOptionalWithLogical.data', - 'FallbackTableData.data', + "TableDataOptionalWithLogical.data", + "FallbackTableData.data", ], }, { // null coalescing - script: 'TableDataOptionalWithLogical.data ?? FallbackTableData.data', + script: "TableDataOptionalWithLogical.data ?? FallbackTableData.data", expectedResults: [ - 'TableDataOptionalWithLogical.data', - 'FallbackTableData.data', + "TableDataOptionalWithLogical.data", + "FallbackTableData.data", ], }, { // Basic map function - script: 'Table5.data.map(c => ({ name: c.name }))', - expectedResults: ['Table5.data.map'], + script: "Table5.data.map(c => ({ name: c.name }))", + expectedResults: ["Table5.data.map"], }, { // Literal property search script: "Table6['data']", - expectedResults: ['Table6'], + expectedResults: ["Table6"], }, { // Deep literal property search script: "TableDataOptionalReference['data'].details", - expectedResults: ['TableDataOptionalReference'], + expectedResults: ["TableDataOptionalReference"], }, { // Array index search - script: 'array[8]', - expectedResults: ['array[8]'], + script: "array[8]", + expectedResults: ["array[8]"], }, { // Deep array index search - script: 'Table7.data[4]', - expectedResults: ['Table7.data[4]'], + script: "Table7.data[4]", + expectedResults: ["Table7.data[4]"], }, { // Deep array index search - script: 'Table7.data[4].value', - expectedResults: ['Table7.data[4].value'], + script: "Table7.data[4].value", + expectedResults: ["Table7.data[4].value"], }, { // string literal and array index search script: "Table['data'][9]", - expectedResults: ['Table'], + expectedResults: ["Table"], }, { // array index and string literal search @@ -88,29 +88,29 @@ describe('getAllIdentifiers', () => { }, { // Index identifier search - script: 'Table8.data[row][name]', - expectedResults: ['Table8.data', 'row'], + script: "Table8.data[row][name]", + expectedResults: ["Table8.data", "row"], }, { // Index identifier search with global - script: 'Table9.data[appsmith.store.row]', - expectedResults: ['Table9.data', 'appsmith.store.row'], + script: "Table9.data[appsmith.store.row]", + expectedResults: ["Table9.data", "appsmith.store.row"], }, { // Index literal with further nested lookups - script: 'Table10.data[row].name', - expectedResults: ['Table10.data', 'row'], + script: "Table10.data[row].name", + expectedResults: ["Table10.data", "row"], }, { // IIFE and if conditions script: - '(function(){ if(Table11.isVisible) { return Api1.data } else { return Api2.data } })()', - expectedResults: ['Table11.isVisible', 'Api1.data', 'Api2.data'], + "(function(){ if(Table11.isVisible) { return Api1.data } else { return Api2.data } })()", + expectedResults: ["Table11.isVisible", "Api1.data", "Api2.data"], }, { // Functions and arguments - script: 'JSObject1.run(Api1.data, Api2.data)', - expectedResults: ['JSObject1.run', 'Api1.data', 'Api2.data'], + script: "JSObject1.run(Api1.data, Api2.data)", + expectedResults: ["JSObject1.run", "Api1.data", "Api2.data"], }, { // IIFE - without braces @@ -124,7 +124,7 @@ describe('getAllIdentifiers', () => { return obj[index] }()`, - expectedResults: ['Input1.text'], + expectedResults: ["Input1.text"], }, { // IIFE @@ -138,7 +138,7 @@ describe('getAllIdentifiers', () => { return obj[index] })()`, - expectedResults: ['Input2.text'], + expectedResults: ["Input2.text"], }, { // arrow IIFE - without braces - will fail @@ -166,19 +166,19 @@ describe('getAllIdentifiers', () => { return obj[index] })()`, - expectedResults: ['Input4.text'], + expectedResults: ["Input4.text"], }, { // Direct object access script: `{ "a": 123 }[Input5.text]`, - expectedResults: ['Input5.text'], + expectedResults: ["Input5.text"], }, { // Function declaration and default arguments script: `function run(apiData = Api1.data) { return apiData; }`, - expectedResults: ['Api1.data'], + expectedResults: ["Api1.data"], }, { // Function declaration with arguments @@ -197,7 +197,7 @@ describe('getAllIdentifiers', () => { row = row += 1; } }`, - expectedResults: ['Table12.data'], + expectedResults: ["Table12.data"], }, { // function with variables @@ -209,17 +209,17 @@ describe('getAllIdentifiers', () => { row = row += 1; } }`, - expectedResults: ['Table13.data'], + expectedResults: ["Table13.data"], }, { // expression with arithmetic operations script: `Table14.data + 15`, - expectedResults: ['Table14.data'], + expectedResults: ["Table14.data"], }, { // expression with logical operations script: `Table15.data || [{}]`, - expectedResults: ['Table15.data'], + expectedResults: ["Table15.data"], }, // JavaScript built in classes should not be valid identifiers { @@ -229,7 +229,7 @@ describe('getAllIdentifiers', () => { const randomNumber = Math.random(); return Promise.all([firstApiRun, secondApiRun]) }()`, - expectedResults: ['Api1.run', 'Api2.run'], + expectedResults: ["Api1.run", "Api2.run"], }, // Global dependencies should not be valid identifiers { @@ -251,7 +251,7 @@ describe('getAllIdentifiers', () => { console.log(joinedName) return Api2.name }()`, - expectedResults: ['Api2.name'], + expectedResults: ["Api2.name"], }, // identifiers and member expressions derived from params should not be valid identifiers { @@ -274,19 +274,19 @@ describe('getAllIdentifiers', () => { script: `function(){ return appsmith.user }()`, - expectedResults: ['appsmith.user'], + expectedResults: ["appsmith.user"], }, ]; cases.forEach((perCase) => { - const { references } = extractInfoFromCode(perCase.script, 2); + const { references } = extractIdentifierInfoFromCode(perCase.script, 2); expect(references).toStrictEqual(perCase.expectedResults); }); }); }); -describe('parseJSObjectWithAST', () => { - it('parse js object', () => { +describe("parseJSObjectWithAST", () => { + it("parse js object", () => { const body = `{ myVar1: [], myVar2: {}, @@ -299,25 +299,25 @@ describe('parseJSObjectWithAST', () => { }`; const parsedObject = [ { - key: 'myVar1', - value: '[]', - type: 'ArrayExpression', + key: "myVar1", + value: "[]", + type: "ArrayExpression", }, { - key: 'myVar2', - value: '{}', - type: 'ObjectExpression', + key: "myVar2", + value: "{}", + type: "ObjectExpression", }, { - key: 'myFun1', - value: '() => {}', - type: 'ArrowFunctionExpression', + key: "myFun1", + value: "() => {}", + type: "ArrowFunctionExpression", arguments: [], }, { - key: 'myFun2', - value: 'async () => {}', - type: 'ArrowFunctionExpression', + key: "myFun2", + value: "async () => {}", + type: "ArrowFunctionExpression", arguments: [], }, ]; @@ -325,7 +325,7 @@ describe('parseJSObjectWithAST', () => { expect(resultParsedObject).toStrictEqual(parsedObject); }); - it('parse js object with literal', () => { + it("parse js object with literal", () => { const body = `{ myVar1: [], myVar2: { @@ -340,25 +340,25 @@ describe('parseJSObjectWithAST', () => { }`; const parsedObject = [ { - key: 'myVar1', - value: '[]', - type: 'ArrayExpression', + key: "myVar1", + value: "[]", + type: "ArrayExpression", }, { - key: 'myVar2', + key: "myVar2", value: '{\n "a": "app"\n}', - type: 'ObjectExpression', + type: "ObjectExpression", }, { - key: 'myFun1', - value: '() => {}', - type: 'ArrowFunctionExpression', + key: "myFun1", + value: "() => {}", + type: "ArrowFunctionExpression", arguments: [], }, { - key: 'myFun2', - value: 'async () => {}', - type: 'ArrowFunctionExpression', + key: "myFun2", + value: "async () => {}", + type: "ArrowFunctionExpression", arguments: [], }, ]; @@ -366,7 +366,7 @@ describe('parseJSObjectWithAST', () => { expect(resultParsedObject).toStrictEqual(parsedObject); }); - it('parse js object with variable declaration inside function', () => { + it("parse js object with variable declaration inside function", () => { const body = `{ myFun1: () => { const a = { @@ -382,7 +382,7 @@ describe('parseJSObjectWithAST', () => { }`; const parsedObject = [ { - key: 'myFun1', + key: "myFun1", value: `() => { const a = { conditions: [], @@ -391,13 +391,13 @@ describe('parseJSObjectWithAST', () => { testFunc2: function () {} }; }`, - type: 'ArrowFunctionExpression', + type: "ArrowFunctionExpression", arguments: [], }, { - key: 'myFun2', - value: 'async () => {}', - type: 'ArrowFunctionExpression', + key: "myFun2", + value: "async () => {}", + type: "ArrowFunctionExpression", arguments: [], }, ]; @@ -405,7 +405,7 @@ describe('parseJSObjectWithAST', () => { expect(resultParsedObject).toStrictEqual(parsedObject); }); - it('parse js object with params of all types', () => { + it("parse js object with params of all types", () => { const body = `{ myFun2: async (a,b = Array(1,2,3),c = "", d = [], e = this.myVar1, f = {}, g = function(){}, h = Object.assign({}), i = String(), j = storeValue()) => { //use async-await or promises @@ -414,49 +414,49 @@ describe('parseJSObjectWithAST', () => { const parsedObject = [ { - key: 'myFun2', + key: "myFun2", value: 'async (a, b = Array(1, 2, 3), c = "", d = [], e = this.myVar1, f = {}, g = function () {}, h = Object.assign({}), i = String(), j = storeValue()) => {}', - type: 'ArrowFunctionExpression', + type: "ArrowFunctionExpression", arguments: [ { - paramName: 'a', + paramName: "a", defaultValue: undefined, }, { - paramName: 'b', + paramName: "b", defaultValue: undefined, }, { - paramName: 'c', + paramName: "c", defaultValue: undefined, }, { - paramName: 'd', + paramName: "d", defaultValue: undefined, }, { - paramName: 'e', + paramName: "e", defaultValue: undefined, }, { - paramName: 'f', + paramName: "f", defaultValue: undefined, }, { - paramName: 'g', + paramName: "g", defaultValue: undefined, }, { - paramName: 'h', + paramName: "h", defaultValue: undefined, }, { - paramName: 'i', + paramName: "i", defaultValue: undefined, }, { - paramName: 'j', + paramName: "j", defaultValue: undefined, }, ], diff --git a/app/shared/ast/src/index.ts b/app/shared/ast/src/index.ts index b338e67107..8cf3eac0af 100644 --- a/app/shared/ast/src/index.ts +++ b/app/shared/ast/src/index.ts @@ -1,8 +1,8 @@ -import { parse, Node, SourceLocation, Options } from 'acorn'; -import { ancestor, simple } from 'acorn-walk'; -import { ECMA_VERSION, NodeTypes } from './constants/ast'; -import { has, isFinite, isString, memoize, toPath } from 'lodash'; -import { isTrueObject, sanitizeScript } from './utils'; +import { parse, Node, SourceLocation, Options } from "acorn"; +import { ancestor, simple } from "acorn-walk"; +import { ECMA_VERSION, NodeTypes } from "./constants/ast"; +import { has, isFinite, isString, memoize, toPath } from "lodash"; +import { isTrueObject, sanitizeScript } from "./utils"; /* * Valuable links: @@ -90,7 +90,7 @@ export interface PropertyNode extends Node { type: NodeTypes.Property; key: LiteralNode | IdentifierNode; value: Node; - kind: 'init' | 'get' | 'set'; + kind: "init" | "get" | "set"; } // Node with location details @@ -98,7 +98,7 @@ type NodeWithLocation = NodeType & { loc: SourceLocation; }; -type AstOptions = Omit; +type AstOptions = Omit; /* We need these functions to typescript casts the nodes with the correct types */ export const isIdentifierNode = (node: Node): node is IdentifierNode => { @@ -196,23 +196,23 @@ export const getAST = memoize((code: string, options?: AstOptions) => * @param code: The piece of script where references need to be extracted from */ -interface ExtractInfoFromCode { +export interface IdentifierInfo { references: string[]; functionalParams: string[]; variables: string[]; } -export const extractInfoFromCode = ( +export const extractIdentifierInfoFromCode = ( code: string, evaluationVersion: number, invalidIdentifiers?: Record -): ExtractInfoFromCode => { +): IdentifierInfo => { // List of all references found const references = new Set(); // List of variables declared within the script. All identifiers and member expressions derived from declared variables will be removed const variableDeclarations = new Set(); // List of functional params declared within the script. All identifiers and member expressions derived from functional params will be removed let functionalParams = new Set(); - let ast: Node = { end: 0, start: 0, type: '' }; + let ast: Node = { end: 0, start: 0, type: "" }; try { const sanitizedScript = sanitizeScript(code, evaluationVersion); /* wrapCode - Wrapping code in a function, since all code/script get wrapped with a function during evaluation. @@ -377,7 +377,7 @@ export const getFunctionalParamsFromNode = ( const constructFinalMemberExpIdentifier = ( node: MemberExpressionNode, - child = '' + child = "" ): string => { const propertyAccessor = getPropertyAccessor(node.property); if (isIdentifierNode(node.object)) { @@ -438,7 +438,7 @@ export const extractInvalidTopLevelMemberExpressionsFromCode = ( const invalidTopLevelMemberExpressions = new Set(); const variableDeclarations = new Set(); let functionalParams = new Set(); - let ast: Node = { end: 0, start: 0, type: '' }; + let ast: Node = { end: 0, start: 0, type: "" }; try { const sanitizedScript = sanitizeScript(code, evaluationVersion); const wrappedCode = wrapCode(sanitizedScript); From ab0160200d3c4dfd7972a0cb525208b7b216e0e8 Mon Sep 17 00:00:00 2001 From: Aman Agarwal Date: Thu, 29 Sep 2022 16:12:07 +0530 Subject: [PATCH 30/54] fix: failing rts build due to jest config (#17177) * fix: failing rts build due to jest config * fix: changed coverage directory (cherry picked from commit 9b8e684d177e6e6746e5071791d7515068ade319) --- app/rts/build.sh | 1 + app/rts/package.json | 2 +- app/rts/tsconfig.json | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/rts/build.sh b/app/rts/build.sh index 32a5956200..642edfae0f 100755 --- a/app/rts/build.sh +++ b/app/rts/build.sh @@ -3,6 +3,7 @@ set -o errexit cd "$(dirname "$0")" +rm -rf dist/ yarn install --frozen-lockfile npx tsc && npx tsc-alias # Copying node_modules directory into dist as rts server requires node_modules to run server build properly. diff --git a/app/rts/package.json b/app/rts/package.json index 30dc659c89..4312cb8f86 100644 --- a/app/rts/package.json +++ b/app/rts/package.json @@ -26,7 +26,7 @@ "typescript": "^4.2.3" }, "scripts": { - "test:unit": "export APPSMITH_API_BASE_URL=http APPSMITH_MONGODB_URI=mongodb && $(npm bin)/jest -b --colors --no-cache --silent --coverage --collectCoverage=true --coverageDirectory='../../' --coverageReporters='json-summary'", + "test:unit": "export APPSMITH_API_BASE_URL=http APPSMITH_MONGODB_URI=mongodb && $(npm bin)/jest -b --colors --no-cache --silent --coverage --collectCoverage=true --coverageDirectory='./' --coverageReporters='json-summary'", "test:jest": "export APPSMITH_API_BASE_URL=http APPSMITH_MONGODB_URI=mongodb && $(npm bin)/jest --watch ", "preinstall": "CURRENT_SCOPE=rts node ../shared/build-shared-dep.js", "build": "./build.sh", diff --git a/app/rts/tsconfig.json b/app/rts/tsconfig.json index 9ee5400337..d292fcb10f 100644 --- a/app/rts/tsconfig.json +++ b/app/rts/tsconfig.json @@ -17,5 +17,6 @@ "@utils/*": ["./src/utils/*"] } }, + "exclude": ["jest.config.js", "src/test"], "lib": ["es2015"] } From 47a0a48943b1d613611cb395be70b5f4a98f5988 Mon Sep 17 00:00:00 2001 From: Aishwarya-U-R <91450662+Aishwarya-U-R@users.noreply.github.com> Date: Thu, 13 Oct 2022 12:54:09 +0530 Subject: [PATCH 31/54] test: Script updates for flaky tests to unblock CI (#17469) --- .../ClientSideTests/BugTests/Bug16702_Spec.ts | 72 +++++++++++ .../BugTests/InvalidURL_Spec.ts | 11 +- .../ThemingTests/Theme_FormWidget_spec.js | 12 +- .../Widgets/List/List1_spec.js | 114 ++---------------- .../Widgets/List/List6_spec.js | 110 +++++++++++++++++ .../OnLoadTests/JSOnLoad1_Spec.ts | 71 ++++++----- .../OnLoadTests/OnLoadActions_Spec.ts | 25 ++-- .../cypress/support/Objects/CommonLocators.ts | 3 + .../cypress/support/Pages/AggregateHelper.ts | 23 +++- .../cypress/support/Pages/EntityExplorer.ts | 2 +- app/client/cypress/support/Pages/JSEditor.ts | 4 + 11 files changed, 290 insertions(+), 157 deletions(-) create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug16702_Spec.ts create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/List/List6_spec.js diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug16702_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug16702_Spec.ts new file mode 100644 index 0000000000..9e3d87615c --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug16702_Spec.ts @@ -0,0 +1,72 @@ +import datasourceFormData from "../../../../fixtures/datasources.json"; +import { ObjectsRegistry } from "../../../../support/Objects/Registry"; + +const locator = ObjectsRegistry.CommonLocators, + apiPage = ObjectsRegistry.ApiPage, + agHelper = ObjectsRegistry.AggregateHelper, + dataSources = ObjectsRegistry.DataSources, + jsEditor = ObjectsRegistry.JSEditor; + +const GRAPHQL_LIMIT_QUERY = ` + query { + launchesPast(limit: "__limit__", offset: "__offset__") { + mission_name + rocket { + rocket_name +`; + +const GRAPHQL_RESPONSE = { + mission_name: "Sentinel-6 Michael Freilich", +}; + +describe("Binding Expressions should not be truncated in Url and path extraction", function() { + it("Bug 16702, Moustache+Quotes formatting goes wrong in graphql body resulting in autocomplete failure", function() { + const jsObjectBody = `export default { + limitValue: 1, + offsetValue: 1, + }`; + + jsEditor.CreateJSObject(jsObjectBody, { + paste: true, + completeReplace: true, + toRun: false, + shouldCreateNewJSObj: true, + }); + + apiPage.CreateAndFillGraphqlApi(datasourceFormData.graphqlApiUrl); + dataSources.UpdateGraphqlQueryAndVariable({ + query: GRAPHQL_LIMIT_QUERY, + }); + + cy.get(".t--graphql-query-editor pre.CodeMirror-line span") + .contains("__offset__") + // .should($el => { + // expect(Cypress.dom.isDetached($el)).to.false; + // }) + //.trigger("mouseover") + .click() + .type("{{JSObject1."); + agHelper.GetNClickByContains(locator._hints, "offsetValue"); + agHelper.Sleep(200); + + /* Start: Block of code to remove error of detached node of codemirror for cypress reference */ + + apiPage.SelectPaneTab("Params"); + apiPage.SelectPaneTab("Body"); + /* End: Block of code to remove error of detached node of codemirror for cypress reference */ + + cy.get(".t--graphql-query-editor pre.CodeMirror-line span") + .contains("__limit__") + //.trigger("mouseover") + .click() + .type("{{JSObject1."); + agHelper.GetNClickByContains(locator._hints, "limitValue"); + agHelper.Sleep(); + //Commenting this since - many runs means - API response is 'You are doing too many launches' + // apiPage.RunAPI(false, 20, { + // expectedPath: "response.body.data.body.data.launchesPast[0].mission_name", + // expectedRes: GRAPHQL_RESPONSE.mission_name, + // }); + apiPage.RunAPI(); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/InvalidURL_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/InvalidURL_Spec.ts index 58f8e2c385..fa391c4538 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/InvalidURL_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/InvalidURL_Spec.ts @@ -1,7 +1,10 @@ import { ObjectsRegistry } from "../../../../support/Objects/Registry"; +import { WIDGET } from "../../../../locators/WidgetLocators"; const jsEditor = ObjectsRegistry.JSEditor, - agHelper = ObjectsRegistry.AggregateHelper; + agHelper = ObjectsRegistry.AggregateHelper, + ee = ObjectsRegistry.EntityExplorer, + deployMode = ObjectsRegistry.DeployMode; describe("Invalid page routing", () => { it("1. Bug #16047 - Shows Invalid URL UI for invalid JS Object page url", () => { @@ -33,4 +36,10 @@ describe("Invalid page routing", () => { ); }); }); + + // it("2. Multi select - test ", () => { + // ee.DragDropWidgetNVerify(WIDGET.MULTITREESELECT); + // deployMode.DeployApp(); + // agHelper.SelectFromMutliTree("Red"); + // }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.js index 8bad204ecc..3210faea4f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.js @@ -8,7 +8,7 @@ let themeBackgroudColor; let themeFont; describe("Theme validation usecases", function() { - it("Drag and drop form widget and validate Default font and list of font validation", function() { + it("1. Drag and drop form widget and validate Default font and list of font validation", function() { cy.log("Login Successful"); cy.reload(); // To remove the rename tooltip cy.get(explorer.addWidget).click(); @@ -133,7 +133,7 @@ describe("Theme validation usecases", function() { cy.contains("Color").click({ force: true }); }); - it("Publish the App and validate Font across the app", function() { + it("2. Publish the App and validate Font across the app", function() { cy.PublishtheApp(); cy.get(".bp3-button:contains('Sub')").should( "have.css", @@ -157,7 +157,7 @@ describe("Theme validation usecases", function() { ); }); - it("Validate Default Theme change across application", function() { + it("3. Validate Default Theme change across application", function() { cy.goToEditFromPublish(); cy.get(formWidgetsPage.formD).click(); cy.widgetText( @@ -194,7 +194,7 @@ describe("Theme validation usecases", function() { }); }); - it("Publish the App and validate Default Theme across the app", function() { + it("4. Publish the App and validate Default Theme across the app", function() { cy.PublishtheApp(); /* Bug Form backgroud colour reset in Publish mode cy.get(formWidgetsPage.formD) @@ -214,7 +214,7 @@ describe("Theme validation usecases", function() { }); }); - it("Validate Theme change across application", function() { + it("5. Validate Theme change across application", function() { cy.goToEditFromPublish(); cy.get(formWidgetsPage.formD).click(); cy.widgetText( @@ -304,7 +304,7 @@ describe("Theme validation usecases", function() { .and("eq", "rgb(255, 193, 61)"); }); - it("Publish the App and validate Theme across the app", function() { + it("6. Publish the App and validate Theme across the app", function() { cy.PublishtheApp(); //Bug Form backgroud colour reset in Publish mode /* diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/List/List1_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/List/List1_spec.js index 2918e0abb6..2565d0d4af 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/List/List1_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/List/List1_spec.js @@ -1,107 +1,10 @@ const dsl = require("../../../../../fixtures/listRegressionDsl.json"); -const publish = require("../../../../../locators/publishWidgetspage.json"); -const commonlocators = require("../../../../../locators/commonlocators.json"); -import { ObjectsRegistry } from "../../../../../support/Objects/Registry"; - -let propPane = ObjectsRegistry.PropertyPane; describe("Binding the list widget with text widget", function() { //const modifierKey = Cypress.platform === "darwin" ? "meta" : "ctrl"; - - before(() => { + it("1. Validate delete widget action from side bar", function() { cy.addDsl(dsl); - }); - - it("1. Validate text widget data based on changes in list widget Data1", function() { - cy.PublishtheApp(); - cy.wait(2000); - cy.get(".t--widget-textwidget span:contains('Vivek')").should( - "have.length", - 1, - ); - cy.get(".t--widget-textwidget span:contains('Pawan')").should( - "have.length", - 1, - ); - cy.get(publish.backToEditor).click({ force: true }); - cy.get(".t--text-widget-container:contains('Vivek')").should( - "have.length", - 1, - ); - cy.get(".t--text-widget-container:contains('Vivek')").should( - "have.length", - 1, - ); - }); - - it("2. Validate text widget data based on changes in list widget Data2", function() { - cy.SearchEntityandOpen("List1"); - propPane.UpdatePropertyFieldValue( - "Items", - '[[{ "name": "pawan"}, { "name": "Vivek" }], [{ "name": "Ashok"}, {"name": "rahul"}]]', - ); - cy.wait("@updateLayout").should( - "have.nested.property", - "response.body.responseMeta.status", - 200, - ); - cy.SearchEntityandOpen("Text3"); - cy.wait(1000); - propPane.UpdatePropertyFieldValue( - "Text", - '{{currentItem.map(item => item.name).join(", ")}}', - ); - cy.wait("@updateLayout").should( - "have.nested.property", - "response.body.responseMeta.status", - 200, - ); - cy.PublishtheApp(); - cy.wait(2000); - cy.get(".t--widget-textwidget span:contains('pawan, Vivek')").should( - "have.length", - 1, - ); - cy.get(".t--widget-textwidget span:contains('Ashok, rahul')").should( - "have.length", - 1, - ); - cy.get(publish.backToEditor).click({ force: true }); - }); - - it("3. Validate text widget data based on changes in list widget Data3", function() { - cy.SearchEntityandOpen("List1"); - propPane.UpdatePropertyFieldValue( - "Items", - '[{ "name": "pawan"}, { "name": "Vivek" }]', - ); - cy.wait("@updateLayout").should( - "have.nested.property", - "response.body.responseMeta.status", - 200, - ); - cy.SearchEntityandOpen("Text3"); - cy.wait(1000); - propPane.UpdatePropertyFieldValue("Text", "{{currentItem.name}}"); - cy.wait("@updateLayout").should( - "have.nested.property", - "response.body.responseMeta.status", - 200, - ); - cy.PublishtheApp(); - cy.wait(2000); - cy.get(".t--widget-textwidget span:contains('Vivek')").should( - "have.length", - 2, - ); - cy.get(".t--widget-textwidget span:contains('pawan')").should( - "have.length", - 2, - ); - cy.get(publish.backToEditor).click({ force: true }); - }); - - it("4. Validate delete widget action from side bar", function() { + cy.wait(3000); //for dsl to settle cy.openPropertyPane("listwidget"); cy.verifyUpdatedWidgetName("Test"); cy.verifyUpdatedWidgetName("#$%1234", "___1234"); @@ -110,12 +13,13 @@ describe("Binding the list widget with text widget", function() { cy.get(".t--toast-action span") .eq(0) .contains("56789 is removed"); - // cy.wait("@updateLayout").should( - // "have.nested.property", - // "response.body.responseMeta.status", - // 200, - // ); + cy.wait("@updateLayout").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); cy.reload(); - cy.wait(2000); + //cy.get(commonlocators.homeIcon).click({ force: true }); + // eslint-disable-next-line cypress/no-unnecessary-waiting }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/List/List6_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/List/List6_spec.js new file mode 100644 index 0000000000..b2530fd4ce --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/List/List6_spec.js @@ -0,0 +1,110 @@ +const dsl = require("../../../../../fixtures/listRegressionDsl.json"); +const publish = require("../../../../../locators/publishWidgetspage.json"); +import { ObjectsRegistry } from "../../../../../support/Objects/Registry"; + +let propPane = ObjectsRegistry.PropertyPane, + agHelper = ObjectsRegistry.AggregateHelper; + +describe("Binding the list widget with text widget", function() { + //const modifierKey = Cypress.platform === "darwin" ? "meta" : "ctrl"; + + before(() => { + cy.addDsl(dsl); + cy.wait(3000); //for dsl to settle + }); + + it("1. Validate text widget data based on changes in list widget Data1", function() { + cy.PublishtheApp(); + cy.wait(2000); + cy.get(".t--widget-textwidget span:contains('Vivek')").should( + "have.length", + 1, + ); + cy.get(".t--widget-textwidget span:contains('Pawan')").should( + "have.length", + 1, + ); + cy.get(publish.backToEditor).click({ force: true }); + cy.get(".t--text-widget-container:contains('Vivek')").should( + "have.length", + 1, + ); + cy.get(".t--text-widget-container:contains('Vivek')").should( + "have.length", + 1, + ); + }); + + it("2. Validate text widget data based on changes in list widget Data2", function() { + cy.SearchEntityandOpen("List1"); + propPane.UpdatePropertyFieldValue( + "Items", + '[[{ "name": "pawan"}, { "name": "Vivek" }], [{ "name": "Ashok"}, {"name": "rahul"}]]', + ); + cy.wait("@updateLayout").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.SearchEntityandOpen("Text3"); + cy.wait(1000); + propPane.UpdatePropertyFieldValue( + "Text", + '{{currentItem.map(item => item.name).join(", ")}}', + ); + cy.wait("@updateLayout").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.PublishtheApp(); + cy.wait(2000); + cy.get(".t--widget-textwidget span:contains('pawan, Vivek')").should( + "have.length", + 1, + ); + cy.get(".t--widget-textwidget span:contains('Ashok, rahul')").should( + "have.length", + 1, + ); + cy.get(publish.backToEditor).click({ force: true }); + }); + + it("3. Validate text widget data based on changes in list widget Data3", function() { + cy.SearchEntityandOpen("List1"); + propPane.UpdatePropertyFieldValue( + "Items", + '[{ "name": "pawan"}, { "name": "Vivek" }]', + ); + cy.wait("@updateLayout").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.SearchEntityandOpen("Text3"); + cy.wait(1000); + propPane.UpdatePropertyFieldValue("Text", "{{currentItem.name}}"); + cy.wait("@updateLayout").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.PublishtheApp(); + cy.wait(2000); + cy.get(".t--widget-textwidget span:contains('Vivek')").should( + "have.length", + 2, + ); + cy.get(".t--widget-textwidget span:contains('pawan')").should( + "have.length", + 2, + ); + cy.get(publish.backToEditor).click({ force: true }); + }); + + after(function() { + //-- Deleting the application by Api---// + cy.DeleteAppByApi(); + //-- LogOut Application---// + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad1_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad1_Spec.ts index b3f6228b81..992895d89e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad1_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad1_Spec.ts @@ -116,7 +116,7 @@ describe("JSObjects OnLoad Actions tests", function() { ee.ExpandCollapseEntity("Queries/JS"); ee.SelectEntityByName(jsName as string); jsEditor.EnableDisableAsyncFuncSettings("getEmployee", true, true); - deployMode.DeployApp(); + deployMode.DeployApp(locator._widgetInDeployed("tablewidget"), false); agHelper.AssertElementVisible(jsEditor._dialog("Confirmation Dialog")); agHelper.AssertElementVisible( jsEditor._dialogBody((jsName as string) + ".getEmployee"), @@ -302,39 +302,56 @@ describe("JSObjects OnLoad Actions tests", function() { deployMode.DeployApp(); - //One Quotes confirmation - for API true - agHelper.AssertElementVisible(jsEditor._dialogBody("Quotes")); - agHelper.ClickButton("No"); - agHelper.WaitUntilToastDisappear("Quotes was cancelled"); + //Commenting & changnig flow since either of confirmation modals can appear first! + + // //Confirmation - first JSObj then API + // agHelper.AssertElementVisible( + // jsEditor._dialogBody((jsName as string) + ".callTrump"), + // ); + // agHelper.ClickButton("No"); + // agHelper.WaitUntilToastDisappear( + // `${jsName + ".callTrump"} was cancelled`, + // ); //When Confirmation is NO validate error toast! + + agHelper.ClickButton("No"); + agHelper.AssertContains("was cancelled"); + + //One Quotes confirmation - for API true + // agHelper.AssertElementVisible(jsEditor._dialogBody("Quotes")); + // agHelper.ClickButton("No"); + // agHelper.WaitUntilToastDisappear("Quotes was cancelled"); + + agHelper.ClickButton("No"); + agHelper.AssertContains("was cancelled"); + + // //Another for API called via JS callQuotes() + // agHelper.AssertElementVisible(jsEditor._dialogBody("Quotes")); + // agHelper.ClickButton("No"); - //Another for API called via JS callQuotes() - agHelper.AssertElementVisible(jsEditor._dialogBody("Quotes")); agHelper.ClickButton("No"); //agHelper.WaitUntilToastDisappear('The action "Quotes" has failed');No toast appears! - //Confirmation - first JSObj then API - agHelper.AssertElementVisible( - jsEditor._dialogBody((jsName as string) + ".callTrump"), - ); - agHelper.ClickButton("No"); - agHelper.WaitUntilToastDisappear( - `${jsName + ".callTrump"} was cancelled`, - ); //When Confirmation is NO validate error toast! agHelper.AssertElementAbsence(jsEditor._dialogBody("WhatTrumpThinks")); //Since JS call is NO, dependent API confirmation should not appear agHelper.RefreshPage(); - agHelper.AssertElementVisible(jsEditor._dialogBody("Quotes")); + // agHelper.AssertElementVisible( + // jsEditor._dialogBody((jsName as string) + ".callTrump"), + // ); + agHelper.AssertElementExist(jsEditor._dialogInDeployView); agHelper.ClickButton("Yes"); - agHelper.AssertElementVisible(jsEditor._dialogBody("Quotes")); + agHelper.Sleep(); + //agHelper.AssertElementVisible(jsEditor._dialogBody("WhatTrumpThinks")); //Since JS call is Yes, dependent confirmation should appear aswell! + agHelper.AssertElementExist(jsEditor._dialogInDeployView); agHelper.ClickButton("Yes"); - agHelper.AssertElementVisible( - jsEditor._dialogBody((jsName as string) + ".callTrump"), - ); + //agHelper.AssertElementVisible(jsEditor._dialogBody("Quotes")); + agHelper.AssertElementExist(jsEditor._dialogInDeployView); agHelper.ClickButton("Yes"); - agHelper.AssertElementVisible(jsEditor._dialogBody("WhatTrumpThinks")); //Since JS call is Yes, dependent confirmation should appear aswell! + agHelper.Sleep(500); + //agHelper.AssertElementVisible(jsEditor._dialogBody("Quotes")); + agHelper.AssertElementExist(jsEditor._dialogInDeployView); agHelper.ClickButton("Yes"); agHelper.Sleep(4000); //to let the api's call be finished & populate the text fields before validation! @@ -355,20 +372,18 @@ describe("JSObjects OnLoad Actions tests", function() { it("10. Tc #1912 - API with OnPageLoad & Confirmation both enabled & called directly & setting previous Api's confirmation to false", () => { deployMode.NavigateBacktoEditor(); - agHelper.AssertElementVisible(jsEditor._dialogBody("Quotes")); + agHelper.AssertElementExist(jsEditor._dialogInDeployView); agHelper.ClickButton("No"); - agHelper.AssertContains("Quotes was cancelled"); + agHelper.AssertContains("was cancelled"); //agHelper.AssertContains("Quotes was cancelled"); agHelper.WaitUntilAllToastsDisappear(); - agHelper.AssertElementVisible(jsEditor._dialogBody("Quotes")); + agHelper.AssertElementExist(jsEditor._dialogInDeployView); agHelper.ClickButton("No"); //Ask Favour abt below //agHelper.ValidateToastMessage("callQuotes ran successfully"); //Verify this toast comes in EDIT page only - agHelper.AssertElementVisible( - jsEditor._dialogBody((jsName as string) + ".callTrump"), - ); + agHelper.AssertElementExist(jsEditor._dialogInDeployView); agHelper.ClickButton("No"); - agHelper.AssertContains(`${jsName + ".callTrump"} was cancelled`); + agHelper.AssertContains("was cancelled"); ee.ExpandCollapseEntity("Queries/JS"); apiPage.CreateAndFillApi("https://catfact.ninja/fact", "CatFacts", 30000); apiPage.ToggleOnPageLoadRun(true); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/OnLoadActions_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/OnLoadActions_Spec.ts index 86addcb24d..371472ea20 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/OnLoadActions_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/OnLoadActions_Spec.ts @@ -1,6 +1,4 @@ import { ObjectsRegistry } from "../../../../support/Objects/Registry"; - -let dsl: any; const agHelper = ObjectsRegistry.AggregateHelper, ee = ObjectsRegistry.EntityExplorer, apiPage = ObjectsRegistry.ApiPage, @@ -9,14 +7,18 @@ const agHelper = ObjectsRegistry.AggregateHelper, deployMode = ObjectsRegistry.DeployMode; describe("Layout OnLoad Actions tests", function() { - before(() => { - cy.fixture("onPageLoadActionsDsl").then((val: any) => { - agHelper.AddDsl(val); - dsl = val; - }); + beforeEach(() => { + agHelper.RestoreLocalStorageCache(); }); - it("1. Bug 8595: OnPageLoad execution - when No api to run on Pageload", function() { + afterEach(() => { + agHelper.SaveLocalStorageCache(); + }); + + it.only("1. Bug 8595: OnPageLoad execution - when No api to run on Pageload", function() { + cy.fixture("onPageLoadActionsDsl").then((val: any) => { + agHelper.AddDsl(val); + }); ee.SelectEntityByName("Widgets"); ee.SelectEntityByName("Page1"); cy.url().then((url) => { @@ -35,7 +37,9 @@ describe("Layout OnLoad Actions tests", function() { }); it("2. Bug 8595: OnPageLoad execution - when Query Parmas added via Params tab", function() { - agHelper.AddDsl(dsl, locator._imageWidget); + cy.fixture("onPageLoadActionsDsl").then((val: any) => { + agHelper.AddDsl(val, locator._imageWidget); + }); apiPage.CreateAndFillApi( "https://source.unsplash.com/collection/1599413", "RandomFlora", @@ -174,7 +178,8 @@ describe("Layout OnLoad Actions tests", function() { apiPage.CreateAndFillApi( "https://api.genderize.io?name={{RandomUser.data.results[0].name.first}}", - "Genderize", 30000 + "Genderize", + 30000, ); apiPage.ValidateQueryParams({ key: "name", diff --git a/app/client/cypress/support/Objects/CommonLocators.ts b/app/client/cypress/support/Objects/CommonLocators.ts index 0627e79a9c..882e183968 100644 --- a/app/client/cypress/support/Objects/CommonLocators.ts +++ b/app/client/cypress/support/Objects/CommonLocators.ts @@ -154,4 +154,7 @@ export class CommonLocators { _deployedPage = `.t--page-switch-tab`; _hints = "ul.CodeMirror-hints li"; _cancelActionExecution = ".t--cancel-action-button"; + _dropDownMultiTreeValue = (dropdownOption: string) => + "//span[@class='rc-tree-select-tree-title']/parent::span[@title='" + dropdownOption + "']"; + _dropDownMultiTreeSelect = ".rc-tree-select-multiple" } diff --git a/app/client/cypress/support/Pages/AggregateHelper.ts b/app/client/cypress/support/Pages/AggregateHelper.ts index 9d5ec08768..1a5fdf0496 100644 --- a/app/client/cypress/support/Pages/AggregateHelper.ts +++ b/app/client/cypress/support/Pages/AggregateHelper.ts @@ -60,8 +60,8 @@ export class AggregateHelper { dsl: string, elementToCheckPresenceaftDslLoad: string | "" = "", ) { - let pageid: string; - let layoutId; + let pageid: string, layoutId, appId: string | null; + appId = localStorage.getItem("applicationId"); cy.url().then((url) => { pageid = url .split("/")[5] @@ -75,7 +75,12 @@ export class AggregateHelper { // Dumping the DSL to the created page cy.request( "PUT", - "api/v1/layouts/" + layoutId + "/pages/" + pageid, + "api/v1/layouts/" + + layoutId + + "/pages/" + + pageid + + "?applicationId=" + + appId, dsl, ).then((dslDumpResp) => { //cy.log("Pages resposne is : " + dslDumpResp.body); @@ -136,9 +141,10 @@ export class AggregateHelper { public GetElement(selector: ElementType, timeout = 20000) { let locator; if (typeof selector == "string") { - locator = selector.startsWith("//") || selector.startsWith("(//") - ? cy.xpath(selector, { timeout: timeout }) - : cy.get(selector, { timeout: timeout }); + locator = + selector.startsWith("//") || selector.startsWith("(//") + ? cy.xpath(selector, { timeout: timeout }) + : cy.get(selector, { timeout: timeout }); } else locator = cy.wrap(selector); return locator; } @@ -309,6 +315,11 @@ export class AggregateHelper { this.Sleep(); //for selected value to reflect! } + public SelectFromMutliTree(dropdownOption: string) { + this.GetNClick(this.locator._dropDownMultiTreeSelect); + this.GetNClick(this.locator._dropDownMultiTreeValue(dropdownOption)); + } + public SelectFromDropDown( dropdownOption: string, insideParent = "", diff --git a/app/client/cypress/support/Pages/EntityExplorer.ts b/app/client/cypress/support/Pages/EntityExplorer.ts index 24639195ad..06abc9646f 100644 --- a/app/client/cypress/support/Pages/EntityExplorer.ts +++ b/app/client/cypress/support/Pages/EntityExplorer.ts @@ -162,7 +162,7 @@ export class EntityExplorer { this.agHelper.Sleep(500); } - public DragDropWidgetNVerify(widgetType: string, x: number, y: number) { + public DragDropWidgetNVerify(widgetType: string, x: number = 200, y: number =200) { this.NavigateToSwitcher("widgets"); this.agHelper.Sleep(); cy.get(this.locator._widgetPageIcon(widgetType)) diff --git a/app/client/cypress/support/Pages/JSEditor.ts b/app/client/cypress/support/Pages/JSEditor.ts index bf465bf949..5165c44790 100644 --- a/app/client/cypress/support/Pages/JSEditor.ts +++ b/app/client/cypress/support/Pages/JSEditor.ts @@ -83,6 +83,10 @@ export class JSEditor { "')]//*[contains(text(),'" + jsFuncName + "')]"; + _dialogInDeployView = + "//div[@class='bp3-dialog-body']//*[contains(text(), '" + + Cypress.env("MESSAGES").QUERY_CONFIRMATION_MODAL_MESSAGE() + + "')]"; _funcDropdown = ".t--formActionButtons div[role='listbox']"; _funcDropdownOptions = ".ads-dropdown-options-wrapper div > span div"; _getJSFunctionSettingsId = (JSFunctionName: string) => From 327b66775d5af31fe97d998ad26ba4f5fbf7cbdf Mon Sep 17 00:00:00 2001 From: subratadeypappu Date: Tue, 18 Oct 2022 11:32:37 +0600 Subject: [PATCH 32/54] Fix/16994 refactor common datatype handling (#17429) * fix:Add array datatype to execute request * feat: Consume and store type of array elements in Param class (#16994) * Append param instead of clientDataType in varargs (#16994) * Refactor common data type handling w.r.t newer structure (#16994) This commit takes care of the following items: - It minimizes the number of usage to the older stringToKnownDataTypeConverter method - Modifies the existing test cases to conform to the newer structure - Marks stringToKnownDataTypeConverter method as deprecated to discourage further use * Remove comma delimited numbers from valid test cases (#16994) * Fix extracting clientDataType from varargs in MySQL (#16994) * Pass param as a dedicated parameter in json smart replacement (#16994) * Remove varargs from json smart replacement method (#16994) * Move BsonType to mongoplugin module (#16994) * Introduce NullArrayType and refactor BsonType test cases (#16994) * Add new test cases on numeric string with leading zero (#16994) * Refactor test case name (#16994) * Add comment on the ordering of Json and Bson types (#16994) * Add comment on the ordering of Json and Bson types (#16994) * Add NullArrayType in Postgres and introduce postgres-specific types (#16994) * Add data type test cases for Postgres and change as per review comments (#16994) Co-authored-by: ChandanBalajiBP --- app/client/src/api/ActionAPI.tsx | 2 +- .../sagas/ActionExecution/PluginActionSaga.ts | 5 +- .../appsmith/external/constants/DataType.java | 8 +- .../external/datatypes/ArrayType.java | 19 +- .../external/datatypes/NullArrayType.java | 31 +++ .../appsmith/external/datatypes/NullType.java | 1 - .../external/dtos/ExecuteActionDTO.java | 18 +- .../external/helpers/DataTypeStringUtils.java | 10 +- .../com/appsmith/external/models/Param.java | 5 + .../plugins/SmartSubstitutionInterface.java | 2 +- .../helpers/DataTypeStringUtilsTest.java | 128 ++++------- .../com/external/plugins/AmazonS3Plugin.java | 4 +- .../external/plugins/AmazonS3PluginTest.java | 13 +- .../com/external/plugins/FirestorePlugin.java | 4 +- .../external/plugins/FirestorePluginTest.java | 5 + .../external/plugins/GoogleSheetsPlugin.java | 4 +- .../com/external/plugins/GraphQLPlugin.java | 4 +- .../external/plugins/GraphQLPluginTest.java | 13 ++ .../com/external/plugins/MongoPlugin.java | 14 +- .../external/plugins/datatypes/BsonType.java | 32 +++ .../datatypes/MongoSpecificDataTypes.java | 53 +++++ .../plugins/MongoPluginDataTypeTest.java | 77 +++++++ .../com/external/plugins/MongoPluginTest.java | 60 ++++- .../com/external/plugins/MssqlPlugin.java | 8 +- .../com/external/plugins/MssqlPluginTest.java | 53 +++++ .../com/external/plugins/MySqlPlugin.java | 6 +- .../com/external/plugins/PostgresPlugin.java | 15 +- .../datatypes/PostgresSpecificDataTypes.java | 53 +++++ .../plugins/PostgresPluginDataTypeTest.java | 215 ++++++++++++++++++ .../external/plugins/PostgresPluginTest.java | 110 ++++++++- .../com/external/plugins/RestApiPlugin.java | 4 +- .../external/plugins/RestApiPluginTest.java | 50 ++++ .../services/ce/NewActionServiceCEImpl.java | 22 +- 33 files changed, 908 insertions(+), 140 deletions(-) create mode 100644 app/server/appsmith-interfaces/src/main/java/com/appsmith/external/datatypes/NullArrayType.java create mode 100644 app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/datatypes/BsonType.java create mode 100644 app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/datatypes/MongoSpecificDataTypes.java create mode 100644 app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginDataTypeTest.java create mode 100644 app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/datatypes/PostgresSpecificDataTypes.java create mode 100644 app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginDataTypeTest.java diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index a9e08256d7..da493f151f 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -47,7 +47,7 @@ export interface ExecuteActionRequest extends APIRequest { params?: Property[]; paginationField?: PaginationField; viewMode: boolean; - paramProperties: Record; + paramProperties: Record>; } export type ExecuteActionResponse = ApiResponse & { diff --git a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts index b28d154595..7538efcbcf 100644 --- a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts +++ b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts @@ -301,7 +301,10 @@ function* evaluateActionParams( ); tempArr.push(newVal); } - executeActionRequest.paramProperties[`k${i}`] = "array"; + //Adding array datatype along with the datatype of first element of the array + executeActionRequest.paramProperties[`k${i}`] = { + array: [arrDatatype[0]], + }; value = tempArr; } else { // @ts-expect-error: Values can take many types diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/DataType.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/DataType.java index 66f1ebcf83..6f6044974c 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/DataType.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/DataType.java @@ -1,5 +1,10 @@ package com.appsmith.external.constants; +/* + There are a lot of occurrences where DataType enum is used. We already do have so many classes to define data types + e.g. ClientDataType, AppsmithType, DataType. Removing all the dependencies from the DataType enum might require a dedicated effort. + Let's consider this a tech-debt and we should pay back before it's too late. + */ public enum DataType { INTEGER, LONG, @@ -18,5 +23,6 @@ public enum DataType { TIMESTAMP, BSON, BSON_SPECIAL_DATA_TYPES, - BIGDECIMAL + BIGDECIMAL, + NULL_ARRAY } \ No newline at end of file diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/datatypes/ArrayType.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/datatypes/ArrayType.java index 412cb39795..96c1507f8a 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/datatypes/ArrayType.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/datatypes/ArrayType.java @@ -9,7 +9,6 @@ import net.minidev.json.JSONArray; import net.minidev.json.parser.JSONParser; import reactor.core.Exceptions; -import java.util.List; public class ArrayType implements AppsmithType { @@ -20,23 +19,7 @@ public class ArrayType implements AppsmithType { public boolean test(String s) { final String trimmedValue = s.trim(); - if (trimmedValue.startsWith("[") && trimmedValue.endsWith("]")) { - String betweenBraces = trimmedValue.substring(1, trimmedValue.length() - 1); - String trimmedInputBetweenBraces = betweenBraces.trim(); - // In case of no values in the array, set this as null otherwise plugins like postgres and ms-sql - // would break while creating a SQL array. - if (!trimmedInputBetweenBraces.isEmpty()) { - try { - new ObjectMapper().readValue(trimmedValue, List.class); - return true; - } catch (JsonProcessingException e) { - return false; - } - - } - } - - return false; + return (trimmedValue.startsWith("[") && trimmedValue.endsWith("]")); } @Override diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/datatypes/NullArrayType.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/datatypes/NullArrayType.java new file mode 100644 index 0000000000..1f5b079bb7 --- /dev/null +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/datatypes/NullArrayType.java @@ -0,0 +1,31 @@ +package com.appsmith.external.datatypes; + +import com.appsmith.external.constants.DataType; + + +public class NullArrayType implements AppsmithType { + @Override + public boolean test(String s) { + final String trimmedValue = s.trim(); + + if (trimmedValue.startsWith("[") && trimmedValue.endsWith("]")) { + // In case of no values in the array, set this as null. Otherwise plugins like postgres and ms-sql + // would break while creating a SQL array. + String betweenBraces = trimmedValue.substring(1, trimmedValue.length() - 1); + String trimmedInputBetweenBraces = betweenBraces.trim(); + return trimmedInputBetweenBraces.isEmpty(); + } + + return false; + } + + @Override + public String performSmartSubstitution(String s) { + return s; + } + + @Override + public DataType type() { + return DataType.NULL_ARRAY; + } +} diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/datatypes/NullType.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/datatypes/NullType.java index 74764cdced..4b2e8577dd 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/datatypes/NullType.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/datatypes/NullType.java @@ -2,7 +2,6 @@ package com.appsmith.external.datatypes; import com.appsmith.external.constants.DataType; -import javax.naming.OperationNotSupportedException; public class NullType implements AppsmithType { diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/dtos/ExecuteActionDTO.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/dtos/ExecuteActionDTO.java index 8074b754d5..639e5ad190 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/dtos/ExecuteActionDTO.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/dtos/ExecuteActionDTO.java @@ -23,7 +23,23 @@ public class ExecuteActionDTO { Boolean viewMode = false; - Map paramProperties; //e.g. "paramProperties": {"k1": "STRING", "k2": "NULL","k3": "NULL"} + /* Sample value of paramProperties + "paramProperties": { + "k1": "string", + "k2": "object", + "k3": "number", + "k4": { + "array": [ + "string", + "number", + "string", + "boolean" + ] + }, + "k5": "boolean" + } + */ + Map paramProperties; Map parameterMap; //e.g. {"Text1.text": "k1","Table1.data": "k2", "Api1.data": "k3"} Map invertParameterMap; //e.g. {"k1":"Text1.text","k2":"Table1.data", "k3": "Api1.data"} diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/DataTypeStringUtils.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/DataTypeStringUtils.java index 2fce0d1125..d645cfe967 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/DataTypeStringUtils.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/DataTypeStringUtils.java @@ -2,8 +2,10 @@ package com.appsmith.external.helpers; import com.appsmith.external.constants.DataType; import com.appsmith.external.constants.DisplayDataType; +import com.appsmith.external.datatypes.AppsmithType; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; +import com.appsmith.external.models.Param; import com.appsmith.external.models.ParsedDataType; import com.appsmith.external.plugins.SmartSubstitutionInterface; import com.fasterxml.jackson.core.JsonProcessingException; @@ -58,6 +60,7 @@ public class DataTypeStringUtils { private static final TypeAdapter strictGsonObjectAdapter = new Gson().getAdapter(JsonObject.class); + @Deprecated(since = "With the implementation of Data Type handling this function is marked as deprecated and is discouraged for further use") public static DataType stringToKnownDataTypeConverter(String input) { if (input == null) { @@ -196,17 +199,20 @@ public class DataTypeStringUtils { * @param insertedParams keeps a list of tuple (replacement, data_type) * @param smartSubstitutionUtils provides entry to plugin specific post-processing logic applied to replacement * value before the final substitution happens + * @param param the binding parameter having the clientDataType to be used in the data type identification process * @return */ public static String jsonSmartReplacementPlaceholderWithValue(String input, String replacement, DataType replacementDataType, List> insertedParams, - SmartSubstitutionInterface smartSubstitutionUtils) { + SmartSubstitutionInterface smartSubstitutionUtils, + Param param) { final DataType dataType; if (replacementDataType == null) { - dataType = DataTypeStringUtils.stringToKnownDataTypeConverter(replacement); + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(param.getClientDataType(), replacement); + dataType = appsmithType.type(); } else { dataType = replacementDataType; } diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Param.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Param.java index 6413dd9c2f..6321126421 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Param.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Param.java @@ -7,6 +7,8 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +import java.util.List; + @Getter @Setter @ToString @@ -20,6 +22,9 @@ public class Param { ClientDataType clientDataType; + //The type of each array elements are stored in this variable when the clientDataType is of ARRAY type and null otherwise + List dataTypesOfArrayElements; + /* In execute API the parameter map is sent this way {"Text1.text": "k1","Table1.data": "k2", "Api1.data": "k3"} where the key is the original name of the binding parameter and value is the pseudo name of the original binding parameter with a view to reducing the size of the payload. diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/SmartSubstitutionInterface.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/SmartSubstitutionInterface.java index 55cb5b5491..90d63dd829 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/SmartSubstitutionInterface.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/SmartSubstitutionInterface.java @@ -40,7 +40,7 @@ public interface SmartSubstitutionInterface { String value = matchingParam.get().getValue(); input = substituteValueInInput(i + 1, key, - value, input, insertedParams, append(args, matchingParam.get().getClientDataType())); + value, input, insertedParams, append(args, matchingParam.get())); } else { throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Uh oh! This is unexpected. " + "Did not receive any information for the binding " diff --git a/app/server/appsmith-interfaces/src/test/java/com/appsmith/external/helpers/DataTypeStringUtilsTest.java b/app/server/appsmith-interfaces/src/test/java/com/appsmith/external/helpers/DataTypeStringUtilsTest.java index fcba3f0908..68ba462fae 100644 --- a/app/server/appsmith-interfaces/src/test/java/com/appsmith/external/helpers/DataTypeStringUtilsTest.java +++ b/app/server/appsmith-interfaces/src/test/java/com/appsmith/external/helpers/DataTypeStringUtilsTest.java @@ -2,6 +2,8 @@ package com.appsmith.external.helpers; import com.appsmith.external.constants.DataType; import com.appsmith.external.constants.DisplayDataType; +import com.appsmith.external.datatypes.AppsmithType; +import com.appsmith.external.datatypes.ClientDataType; import com.appsmith.external.models.ParsedDataType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -14,15 +16,16 @@ import java.util.List; import java.util.Map; import static com.appsmith.external.helpers.DataTypeStringUtils.getDisplayDataTypes; -import static com.appsmith.external.helpers.DataTypeStringUtils.stringToKnownDataTypeConverter; import static org.assertj.core.api.Assertions.assertThat; + public class DataTypeStringUtilsTest { @Test public void checkTimeStampDataType() { String timestamp = "2021-03-24 14:05:34"; - DataType dataType = stringToKnownDataTypeConverter(timestamp); + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING, timestamp); + DataType dataType = appsmithType.type(); assertThat(dataType).isEqualByComparingTo(DataType.TIMESTAMP); } @@ -30,7 +33,8 @@ public class DataTypeStringUtilsTest { @Test public void checkDateDataType() { String date = "2021-03-24"; - DataType dataType = stringToKnownDataTypeConverter(date); + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING, date); + DataType dataType = appsmithType.type(); assertThat(dataType).isEqualByComparingTo(DataType.DATE); } @@ -38,7 +42,8 @@ public class DataTypeStringUtilsTest { @Test public void checkBooleanDataType() { String boolInput = "true"; - DataType dataType = stringToKnownDataTypeConverter(boolInput); + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.BOOLEAN, boolInput); + DataType dataType = appsmithType.type(); assertThat(dataType).isEqualByComparingTo(DataType.BOOLEAN); } @@ -47,7 +52,8 @@ public class DataTypeStringUtilsTest { public void checkStringDataType() { // Starting the string with number because earlier this was incorrectly being identified as JSON. String stringData = "2.1 In order to understand recursion, one must first understand recursion. -Anonymous"; - DataType dataType = stringToKnownDataTypeConverter(stringData); + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING, stringData); + DataType dataType = appsmithType.type(); assertThat(dataType).isEqualByComparingTo(DataType.STRING); } @@ -55,47 +61,17 @@ public class DataTypeStringUtilsTest { @Test public void checkIntegerDataType() { String intData = "1234"; - DataType dataType = stringToKnownDataTypeConverter(intData); + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.NUMBER, intData); + DataType dataType = appsmithType.type(); assertThat(dataType).isEqualByComparingTo(DataType.INTEGER); } - @Test - public void checkFloatDataType() { - String floatData = "12.34"; - DataType dataType = stringToKnownDataTypeConverter(floatData); - - assertThat(dataType).isEqualByComparingTo(DataType.FLOAT); - } - - @Test - public void checkCommaDelimitedIntegerValues() { - String strData = "54,024,464"; - DataType dataType = stringToKnownDataTypeConverter(strData); - - assertThat(dataType).isEqualByComparingTo(DataType.INTEGER); - } - - @Test - public void checkCommaDelimitedFloatValues() { - String floatData = "54,024,464,177.345300"; - DataType dataType = stringToKnownDataTypeConverter(floatData); - - assertThat(dataType).isEqualByComparingTo(DataType.FLOAT); - } - - @Test - public void checkCommaDelimitedLongValues() { - String strData = "454,024,464,454,987,777"; - DataType dataType = stringToKnownDataTypeConverter(strData); - - assertThat(dataType).isEqualByComparingTo(DataType.LONG); - } - @Test public void checkSimpleArrayDataType() { String arrayData = "[1,2,3,4]"; - DataType dataType = stringToKnownDataTypeConverter(arrayData); + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.ARRAY, arrayData); + DataType dataType = appsmithType.type(); assertThat(dataType).isEqualByComparingTo(DataType.ARRAY); } @@ -113,7 +89,8 @@ public class DataTypeStringUtilsTest { " \"key3\": \"value\"\n" + " }\n" + "]"; - DataType dataType = stringToKnownDataTypeConverter(arrayData); + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.ARRAY, arrayData); + DataType dataType = appsmithType.type(); assertThat(dataType).isEqualByComparingTo(DataType.ARRAY); } @@ -123,7 +100,8 @@ public class DataTypeStringUtilsTest { String jsonData = "{\n" + " \"key1\": \"value\"\n" + "}"; - DataType dataType = stringToKnownDataTypeConverter(jsonData); + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT, jsonData); + DataType dataType = appsmithType.type(); assertThat(dataType).isEqualByComparingTo(DataType.JSON_OBJECT); } @@ -131,7 +109,8 @@ public class DataTypeStringUtilsTest { @Test public void checkNullDataType() { String nullData = "null"; - DataType dataType = stringToKnownDataTypeConverter(nullData); + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.NULL, nullData); + DataType dataType = appsmithType.type(); assertThat(dataType).isEqualByComparingTo(DataType.NULL); } @@ -140,52 +119,45 @@ public class DataTypeStringUtilsTest { public void testJsonStrictParsing() { // https://static.javadoc.io/com.google.code.gson/gson/2.8.5/com/google/gson/stream/JsonReader.html#setLenient-boolean- // Streams that start with the non-execute prefix, ")]}'\n". - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("){}")); - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("]{}")); - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("}{}")); - // Streams that include multiple top-level values. With strict parsing, each stream must contain exactly one top-level value. - assertThat(DataType.BSON).isEqualByComparingTo(stringToKnownDataTypeConverter("{}{}")); - assertThat(DataType.BSON).isEqualByComparingTo(stringToKnownDataTypeConverter("{}[]null")); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"){}").type()); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"]{}").type()); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"}{}").type()); // Top-level values of any type. With strict parsing, the top-level value must be an object or an array. - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("")); - assertThat(DataType.NULL).isEqualByComparingTo(stringToKnownDataTypeConverter("null")); - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("Abracadabra")); - assertThat(DataType.INTEGER).isEqualByComparingTo(stringToKnownDataTypeConverter("13")); - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("\"literal\"")); - assertThat(DataType.NULL).isEqualByComparingTo(stringToKnownDataTypeConverter("[]")); - // Numbers may be NaNs or infinities. - assertThat(DataType.BSON).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"number\": NaN}")); - assertThat(DataType.BSON).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"number\": Infinity}")); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"").type()); + assertThat(DataType.NULL).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.NULL,"null").type()); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"Abracadabra").type()); + assertThat(DataType.INTEGER).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.NUMBER,"13").type()); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"\"literal\"").type()); + // End of line comments starting with // or # and ending with a newline character. - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("{//comment\n}")); - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("{#comment\n}")); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"{//comment\n}").type()); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"{#comment\n}").type()); + // C-style comments starting with /* and ending with */. Such comments may not be nested. - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("{/*comment*/}")); - // Names that are unquoted or 'single quoted'. - assertThat(DataType.BSON).isEqualByComparingTo(stringToKnownDataTypeConverter("{a: 1}")); - assertThat(DataType.BSON).isEqualByComparingTo(stringToKnownDataTypeConverter("{'a': 1}")); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"{/*comment*/}").type()); + // Strings that are unquoted or 'single quoted'. - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\": str}")); - assertThat(DataType.BSON).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\": ''}")); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"{\"a\": str}").type()); + // Array elements separated by ; instead of ,. - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\": [1;2]}")); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"{\"a\": [1;2]}").type()); // Unnecessary array separators. These are interpreted as if null was the omitted value. - assertThat(DataType.BSON).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\": [1,]}")); + // Names and values separated by = or => instead of :. - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\" = 13}")); - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\" => 13}")); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"{\"a\" = 13}").type()); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"{\"a\" => 13}").type()); // Name/value pairs separated by ; instead of ,. - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\": 1; \"b\": 2}")); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING,"{\"a\": 1; \"b\": 2}").type()); - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\": }")); - assertThat(DataType.STRING).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\": ,}")); - assertThat(DataType.BSON).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\": 0,}")); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING, "{\"a\": }").type()); + assertThat(DataType.STRING).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING, "{\"a\": ,}").type()); - assertThat(DataType.JSON_OBJECT).isEqualByComparingTo(stringToKnownDataTypeConverter("{} ")); - assertThat(DataType.JSON_OBJECT).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\": null} \n \n")); - assertThat(DataType.JSON_OBJECT).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\": 0}")); - assertThat(DataType.JSON_OBJECT).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\": \"\"}")); - assertThat(DataType.JSON_OBJECT).isEqualByComparingTo(stringToKnownDataTypeConverter("{\"a\": []}")); + assertThat(DataType.JSON_OBJECT).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT, "{} ").type()); + assertThat(DataType.JSON_OBJECT).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT, "{\"a\": null} \n \n").type()); + assertThat(DataType.JSON_OBJECT).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT, "{\"a\": 0}").type()); + assertThat(DataType.JSON_OBJECT).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT, "{\"a\": \"\"}").type()); + assertThat(DataType.JSON_OBJECT).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT, "{\"a\": []}").type()); + assertThat(DataType.ARRAY).isEqualByComparingTo(DataTypeServiceUtils.getAppsmithType(ClientDataType.ARRAY, "[]").type()); } @Test diff --git a/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java b/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java index 66b6ab85ff..44283f7057 100644 --- a/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java +++ b/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java @@ -31,6 +31,7 @@ import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DatasourceStructure; import com.appsmith.external.models.DatasourceTestResult; +import com.appsmith.external.models.Param; import com.appsmith.external.models.Property; import com.appsmith.external.models.RequestParamDTO; import com.appsmith.external.models.UQIDataFilterParams; @@ -1041,7 +1042,8 @@ public class AmazonS3Plugin extends BasePlugin { List> insertedParams, Object... args) { String jsonBody = (String) input; - return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(jsonBody, value, null, insertedParams, null); + Param param = (Param) args[0]; + return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(jsonBody, value, null, insertedParams, null, param); } private String getEncodedPayloadFromMultipartDTO(MultipartFormDataDTO multipartFormDataDTO) { diff --git a/app/server/appsmith-plugins/amazons3Plugin/src/test/java/com/external/plugins/AmazonS3PluginTest.java b/app/server/appsmith-plugins/amazons3Plugin/src/test/java/com/external/plugins/AmazonS3PluginTest.java index 1e63ba8e6d..b4bb1a26dc 100644 --- a/app/server/appsmith-plugins/amazons3Plugin/src/test/java/com/external/plugins/AmazonS3PluginTest.java +++ b/app/server/appsmith-plugins/amazons3Plugin/src/test/java/com/external/plugins/AmazonS3PluginTest.java @@ -3,8 +3,15 @@ package com.external.plugins; import com.amazonaws.AmazonServiceException; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.s3.model.*; +import com.amazonaws.services.s3.model.AmazonS3Exception; +import com.amazonaws.services.s3.model.Bucket; +import com.amazonaws.services.s3.model.DeleteObjectsResult; +import com.amazonaws.services.s3.model.ObjectListing; +import com.amazonaws.services.s3.model.S3Object; +import com.amazonaws.services.s3.model.S3ObjectInputStream; +import com.amazonaws.services.s3.model.S3ObjectSummary; import com.amazonaws.util.Base64; +import com.appsmith.external.datatypes.ClientDataType; import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException; @@ -412,7 +419,9 @@ public class AmazonS3PluginTest { public void testSmartSubstitutionJSONBody() { DatasourceConfiguration datasourceConfiguration = createDatasourceConfiguration(); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); - executeActionDTO.setParams(List.of(new Param("dynamicallyFoundFilePickerObject", "Random\"Unescaped'String"))); + Param param = new Param("dynamicallyFoundFilePickerObject", "Random\"Unescaped'String"); + param.setClientDataType(ClientDataType.OBJECT); + executeActionDTO.setParams(List.of(param)); AmazonS3Plugin.S3PluginExecutor pluginExecutor = new AmazonS3Plugin.S3PluginExecutor(); ActionConfiguration actionConfiguration = new ActionConfiguration(); diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java b/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java index 8f84c40217..4ee4899b4b 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java +++ b/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java @@ -13,6 +13,7 @@ import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DatasourceStructure; import com.appsmith.external.models.PaginationField; +import com.appsmith.external.models.Param; import com.appsmith.external.models.RequestParamDTO; import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.PluginExecutor; @@ -117,8 +118,9 @@ public class FirestorePlugin extends BasePlugin { List> insertedParams, Object... args) { String jsonBody = (String) input; + Param param = (Param) args[0]; return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(jsonBody, value, null, insertedParams, - null); + null, param); } @Override diff --git a/app/server/appsmith-plugins/firestorePlugin/src/test/java/com/external/plugins/FirestorePluginTest.java b/app/server/appsmith-plugins/firestorePlugin/src/test/java/com/external/plugins/FirestorePluginTest.java index 4434da7784..4e290e4bef 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/test/java/com/external/plugins/FirestorePluginTest.java +++ b/app/server/appsmith-plugins/firestorePlugin/src/test/java/com/external/plugins/FirestorePluginTest.java @@ -1,5 +1,6 @@ package com.external.plugins; +import com.appsmith.external.datatypes.ClientDataType; import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.exceptions.AppsmithErrorAction; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; @@ -1473,18 +1474,22 @@ public class FirestorePluginTest { Param param = new Param(); param.setKey("Input1.text"); param.setValue("Jon"); + param.setClientDataType(ClientDataType.STRING); params.add(param); param = new Param(); param.setKey("Input2.text"); param.setValue("Von Neumann"); + param.setClientDataType(ClientDataType.STRING); params.add(param); param = new Param(); param.setKey("Input3.text"); param.setValue("[\"Zuric\", \"Gottingen\"]"); + param.setClientDataType(ClientDataType.ARRAY); params.add(param); param = new Param(); param.setKey("Input4.text"); param.setValue("{\"computational complexity\": 100, \"math\": 100}"); + param.setClientDataType(ClientDataType.OBJECT); params.add(param); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); diff --git a/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/plugins/GoogleSheetsPlugin.java b/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/plugins/GoogleSheetsPlugin.java index 2d252f7401..848267b8a1 100644 --- a/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/plugins/GoogleSheetsPlugin.java +++ b/app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/plugins/GoogleSheetsPlugin.java @@ -9,6 +9,7 @@ import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.OAuth2; +import com.appsmith.external.models.Param; import com.appsmith.external.models.Property; import com.appsmith.external.models.TriggerRequestDTO; import com.appsmith.external.models.TriggerResultDTO; @@ -296,7 +297,8 @@ public class GoogleSheetsPlugin extends BasePlugin { List> insertedParams, Object... args) { String jsonBody = (String) input; - return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(jsonBody, value, null, insertedParams, null); + Param param = (Param) args[0]; + return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(jsonBody, value, null, insertedParams, null, param); } @Override diff --git a/app/server/appsmith-plugins/graphqlPlugin/src/main/java/com/external/plugins/GraphQLPlugin.java b/app/server/appsmith-plugins/graphqlPlugin/src/main/java/com/external/plugins/GraphQLPlugin.java index 8453494cce..31be99642c 100644 --- a/app/server/appsmith-plugins/graphqlPlugin/src/main/java/com/external/plugins/GraphQLPlugin.java +++ b/app/server/appsmith-plugins/graphqlPlugin/src/main/java/com/external/plugins/GraphQLPlugin.java @@ -13,6 +13,7 @@ import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.ApiContentType; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.PaginationType; +import com.appsmith.external.models.Param; import com.appsmith.external.models.Property; import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.BaseRestApiPluginExecutor; @@ -283,9 +284,10 @@ public class GraphQLPlugin extends BasePlugin { List> insertedParams, Object... args) { boolean isInputQueryBody = (boolean) args[0]; + Param param = (Param) args[1]; if (!isInputQueryBody) { String queryVariables = (String) input; - return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(queryVariables, value, null, insertedParams, null); + return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(queryVariables, value, null, insertedParams, null, param); } else { String queryBody = (String) input; diff --git a/app/server/appsmith-plugins/graphqlPlugin/src/test/java/com/external/plugins/GraphQLPluginTest.java b/app/server/appsmith-plugins/graphqlPlugin/src/test/java/com/external/plugins/GraphQLPluginTest.java index d2cc83cbdf..19b0b6426c 100644 --- a/app/server/appsmith-plugins/graphqlPlugin/src/test/java/com/external/plugins/GraphQLPluginTest.java +++ b/app/server/appsmith-plugins/graphqlPlugin/src/test/java/com/external/plugins/GraphQLPluginTest.java @@ -1,5 +1,6 @@ package com.external.plugins; +import com.appsmith.external.datatypes.ClientDataType; import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.helpers.restApiUtils.connections.APIConnection; @@ -356,18 +357,22 @@ public class GraphQLPluginTest { Param param1 = new Param(); param1.setKey("Input1.text"); param1.setValue("3"); + param1.setClientDataType(ClientDataType.NUMBER); params.add(param1); Param param2 = new Param(); param2.setKey("Input2.text"); param2.setValue("this is a string! Yay :D"); + param2.setClientDataType(ClientDataType.STRING); params.add(param2); Param param3 = new Param(); param3.setKey("Input3.text"); param3.setValue("true"); + param3.setClientDataType(ClientDataType.BOOLEAN); params.add(param3); Param param4 = new Param(); param4.setKey("Input4.text"); param4.setValue("id"); + param4.setClientDataType(ClientDataType.STRING); params.add(param4); executeActionDTO.setParams(params); @@ -438,6 +443,7 @@ public class GraphQLPluginTest { " }\n" + " }\n" + "}"); + param1.setClientDataType(ClientDataType.STRING); params.add(param1); executeActionDTO.setParams(params); @@ -508,30 +514,37 @@ public class GraphQLPluginTest { Param param1 = new Param(); param1.setKey("Input1.text"); param1.setValue("this is a string! Yay :D"); + param1.setClientDataType(ClientDataType.STRING); params.add(param1); Param param3 = new Param(); param3.setKey("Input2.text"); param3.setValue("true"); + param3.setClientDataType(ClientDataType.BOOLEAN); params.add(param3); Param param4 = new Param(); param4.setKey("Input3.text"); param4.setValue("0"); + param4.setClientDataType(ClientDataType.NUMBER); params.add(param4); Param param5 = new Param(); param5.setKey("Input4.text"); param5.setValue("12/01/2018"); + param5.setClientDataType(ClientDataType.STRING); params.add(param5); Param param6 = new Param(); param6.setKey("Input5.text"); param6.setValue("null"); + param6.setClientDataType(ClientDataType.NULL); params.add(param6); Param param7 = new Param(); param7.setKey("Table1.selectedRow"); param7.setValue("{ \"id\": 2381224, \"email\": \"michael.lawson@reqres.in\", \"userName\": \"Michael Lawson\", \"productName\": \"Chicken Sandwich\", \"orderAmount\": 4.99}"); + param7.setClientDataType(ClientDataType.OBJECT); params.add(param7); Param param8 = new Param(); param8.setKey("Table1.tableData"); param8.setValue("[ { \"id\": 2381224, \"email\": \"michael.lawson@reqres.in\", \"userName\": \"Michael Lawson\", \"productName\": \"Chicken Sandwich\", \"orderAmount\": 4.99 }, { \"id\": 2736212, \"email\": \"lindsay.ferguson@reqres.in\", \"userName\": \"Lindsay Ferguson\", \"productName\": \"Tuna Salad\", \"orderAmount\": 9.99 }, { \"id\": 6788734, \"email\": \"tobias.funke@reqres.in\", \"userName\": \"Tobias Funke\", \"productName\": \"Beef steak\", \"orderAmount\": 19.99 }]"); + param8.setClientDataType(ClientDataType.ARRAY); params.add(param8); executeActionDTO.setParams(params); diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java index d61ff5bc19..17f5c47298 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java @@ -2,10 +2,13 @@ package com.external.plugins; import com.appsmith.external.constants.DataType; import com.appsmith.external.constants.DisplayDataType; +import com.appsmith.external.datatypes.AppsmithType; +import com.appsmith.external.datatypes.ClientDataType; import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException; +import com.appsmith.external.helpers.DataTypeServiceUtils; import com.appsmith.external.helpers.DataTypeStringUtils; import com.appsmith.external.helpers.MustacheHelper; import com.appsmith.external.helpers.PluginUtils; @@ -26,6 +29,7 @@ import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.PluginExecutor; import com.appsmith.external.plugins.SmartSubstitutionInterface; import com.external.plugins.constants.MongoSpecialDataTypes; +import com.external.plugins.datatypes.MongoSpecificDataTypes; import com.external.plugins.utils.MongoErrorUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -930,8 +934,9 @@ public class MongoPlugin extends BasePlugin { List> insertedParams, Object... args) { String jsonBody = (String) input; - DataType dataType = stringToKnownMongoDBDataTypeConverter(value); - return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(jsonBody, value, dataType, insertedParams, this); + Param param = (Param) args[0]; + DataType dataType = stringToKnownMongoDBDataTypeConverter(value, param.getClientDataType()); + return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(jsonBody, value, dataType, insertedParams, this, param); } /** @@ -944,8 +949,9 @@ public class MongoPlugin extends BasePlugin { * @param replacement replacement value * @return identified data type of replacement value */ - private DataType stringToKnownMongoDBDataTypeConverter(String replacement) { - DataType dataType = DataTypeStringUtils.stringToKnownDataTypeConverter(replacement); + private DataType stringToKnownMongoDBDataTypeConverter(String replacement, ClientDataType clientDataType) { + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(clientDataType, replacement, MongoSpecificDataTypes.pluginSpecificTypes); + DataType dataType = appsmithType.type(); if (dataType == DataType.STRING) { for (MongoSpecialDataTypes specialType : MongoSpecialDataTypes.values()) { final String regex = MONGODB_SPECIAL_TYPE_INSIDE_QUOTES_REGEX_TEMPLATE.replace("E", diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/datatypes/BsonType.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/datatypes/BsonType.java new file mode 100644 index 0000000000..d38f2f52cb --- /dev/null +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/datatypes/BsonType.java @@ -0,0 +1,32 @@ +package com.external.plugins.datatypes; + +import com.appsmith.external.constants.DataType; +import com.appsmith.external.datatypes.AppsmithType; +import org.bson.BsonInvalidOperationException; +import org.bson.Document; +import org.bson.json.JsonParseException; +import java.util.regex.Matcher; + +public class BsonType implements AppsmithType { + + @Override + public boolean test(String s) { + try { + Document.parse(s); + return true; + } catch (JsonParseException | BsonInvalidOperationException e) { + // Not BSON + } + return false; + } + + @Override + public String performSmartSubstitution(String s) { + return Matcher.quoteReplacement(s); + } + + @Override + public DataType type() { + return DataType.BSON; + } +} diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/datatypes/MongoSpecificDataTypes.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/datatypes/MongoSpecificDataTypes.java new file mode 100644 index 0000000000..36e6092d86 --- /dev/null +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/datatypes/MongoSpecificDataTypes.java @@ -0,0 +1,53 @@ +package com.external.plugins.datatypes; + +import com.appsmith.external.datatypes.AppsmithType; +import com.appsmith.external.datatypes.ArrayType; +import com.appsmith.external.datatypes.BigDecimalType; +import com.appsmith.external.datatypes.BooleanType; +import com.appsmith.external.datatypes.ClientDataType; +import com.appsmith.external.datatypes.DateType; +import com.appsmith.external.datatypes.DoubleType; +import com.appsmith.external.datatypes.IntegerType; +import com.appsmith.external.datatypes.JsonObjectType; +import com.appsmith.external.datatypes.LongType; +import com.appsmith.external.datatypes.NullType; +import com.appsmith.external.datatypes.StringType; +import com.appsmith.external.datatypes.TimeType; +import com.appsmith.external.datatypes.TimestampType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MongoSpecificDataTypes { + public static final Map> pluginSpecificTypes; + + static { + pluginSpecificTypes = new HashMap<>(); + pluginSpecificTypes.put(ClientDataType.NULL, List.of(new NullType())); + + pluginSpecificTypes.put(ClientDataType.BOOLEAN, List.of(new BooleanType())); + + pluginSpecificTypes.put(ClientDataType.ARRAY, List.of(new ArrayType())); + + pluginSpecificTypes.put(ClientDataType.NUMBER, List.of( + new IntegerType(), + new LongType(), + new DoubleType(), + new BigDecimalType() + )); + + /* + BSON is the superset of JSON with more data types. + That's why JsonObjectType precedes otherwise BsonType would always take over. + */ + pluginSpecificTypes.put(ClientDataType.OBJECT, List.of(new JsonObjectType(), new BsonType())); + + pluginSpecificTypes.put(ClientDataType.STRING, List.of( + new TimeType(), + new DateType(), + new TimestampType(), + new StringType() + )); + } +} diff --git a/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginDataTypeTest.java b/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginDataTypeTest.java new file mode 100644 index 0000000000..2ae8b4b6cd --- /dev/null +++ b/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginDataTypeTest.java @@ -0,0 +1,77 @@ +package com.external.plugins; + +import com.appsmith.external.constants.DataType; +import com.appsmith.external.datatypes.AppsmithType; +import com.appsmith.external.datatypes.ArrayType; +import com.appsmith.external.datatypes.BigDecimalType; +import com.appsmith.external.datatypes.BooleanType; +import com.appsmith.external.datatypes.ClientDataType; +import com.appsmith.external.datatypes.DateType; +import com.appsmith.external.datatypes.DoubleType; +import com.appsmith.external.datatypes.IntegerType; +import com.appsmith.external.datatypes.JsonObjectType; +import com.appsmith.external.datatypes.LongType; +import com.appsmith.external.datatypes.NullType; +import com.appsmith.external.datatypes.StringType; +import com.appsmith.external.datatypes.TimeType; +import com.appsmith.external.datatypes.TimestampType; +import com.appsmith.external.helpers.DataTypeServiceUtils; +import com.external.plugins.datatypes.BsonType; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MongoPluginDataTypeTest { + public static final Map> pluginSpecificTypes; + + static { + pluginSpecificTypes = new HashMap<>(); + pluginSpecificTypes.put(ClientDataType.NULL, List.of(new NullType())); + + pluginSpecificTypes.put(ClientDataType.BOOLEAN, List.of(new BooleanType())); + + pluginSpecificTypes.put(ClientDataType.ARRAY, List.of(new ArrayType())); + + pluginSpecificTypes.put(ClientDataType.NUMBER, List.of( + new IntegerType(), + new LongType(), + new DoubleType(), + new BigDecimalType() + )); + + /* + BSON is the superset of JSON with more data types. + That's why JsonObjectType precedes otherwise BsonType would always take over. + */ + pluginSpecificTypes.put(ClientDataType.OBJECT, List.of(new JsonObjectType(), new BsonType())); + + pluginSpecificTypes.put(ClientDataType.STRING, List.of( + new TimeType(), + new DateType(), + new TimestampType(), + new StringType() + )); + } + @Test + public void testBsonTypes() { + // Streams that include multiple top-level values. With strict parsing, each stream must contain exactly one top-level value. + assertEquals(DataType.BSON, DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT,"{}{}", pluginSpecificTypes).type()); + assertEquals(DataType.BSON, DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT,"{}[]null", pluginSpecificTypes).type()); + // Numbers may be NaNs or infinities. + assertEquals(DataType.BSON, DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT,"{\"number\": NaN}", pluginSpecificTypes).type()); + assertEquals(DataType.BSON, DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT,"{\"number\": Infinity}", pluginSpecificTypes).type()); + // Names that are unquoted or 'single quoted'. + assertEquals(DataType.BSON, DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT,"{a: 1}", pluginSpecificTypes).type()); + assertEquals(DataType.BSON, DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT,"{'a': 1}", pluginSpecificTypes).type()); + // Strings that are unquoted or 'single quoted'. + assertEquals(DataType.BSON, DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT,"{\"a\": ''}", pluginSpecificTypes).type()); + // Unnecessary array separators. These are interpreted as if null was the omitted value. + assertEquals(DataType.BSON, DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT, "{\"a\": [1,]}", pluginSpecificTypes).type()); + + assertEquals(DataType.BSON, DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT,"{\"a\": 0,}", pluginSpecificTypes).type()); + } +} diff --git a/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java b/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java index 114113b82f..098354c5f9 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java @@ -1,5 +1,6 @@ package com.external.plugins; +import com.appsmith.external.datatypes.ClientDataType; import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; @@ -1085,18 +1086,22 @@ public class MongoPluginTest { Param param1 = new Param(); param1.setKey("Input1.text"); param1.setValue("{ age: { \"$gte\": 30 } }"); + param1.setClientDataType(ClientDataType.OBJECT); params.add(param1); Param param3 = new Param(); param3.setKey("Input2.text"); param3.setValue("1"); + param3.setClientDataType(ClientDataType.NUMBER); params.add(param3); Param param4 = new Param(); param4.setKey("Input3.text"); param4.setValue("10"); + param4.setClientDataType(ClientDataType.NUMBER); params.add(param4); Param param5 = new Param(); param5.setKey("Input4.text"); param5.setValue("users"); + param5.setClientDataType(ClientDataType.STRING); params.add(param5); executeActionDTO.setParams(params); @@ -1176,18 +1181,22 @@ public class MongoPluginTest { Param param1 = new Param(); param1.setKey("Input1.text"); param1.setValue("$gte"); + param1.setClientDataType(ClientDataType.STRING); params.add(param1); Param param3 = new Param(); param3.setKey("Input2.text"); param3.setValue("1"); + param3.setClientDataType(ClientDataType.NUMBER); params.add(param3); Param param4 = new Param(); param4.setKey("Input3.text"); param4.setValue("10"); + param4.setClientDataType(ClientDataType.NUMBER); params.add(param4); Param param5 = new Param(); param5.setKey("Input4.text"); param5.setValue("users"); + param5.setClientDataType(ClientDataType.STRING); params.add(param5); executeActionDTO.setParams(params); @@ -1708,18 +1717,22 @@ public class MongoPluginTest { Param param1 = new Param(); param1.setKey("Input1.text"); param1.setValue("{ age: { \"$gte\": 30 } }"); + param1.setClientDataType(ClientDataType.OBJECT); params.add(param1); Param param3 = new Param(); param3.setKey("Input2.text"); param3.setValue("1"); + param3.setClientDataType(ClientDataType.NUMBER); params.add(param3); Param param4 = new Param(); param4.setKey("Input3.text"); param4.setValue("10"); + param4.setClientDataType(ClientDataType.NUMBER); params.add(param4); Param param5 = new Param(); param5.setKey("Input4.text"); param5.setValue("users"); + param5.setClientDataType(ClientDataType.STRING); params.add(param5); executeActionDTO.setParams(params); @@ -1770,10 +1783,12 @@ public class MongoPluginTest { Param param1 = new Param(); param1.setKey("Input2.text"); param1.setValue("1"); + param1.setClientDataType(ClientDataType.NUMBER); params.add(param1); Param param2 = new Param(); param2.setKey("Input4.text"); param2.setValue("users"); + param2.setClientDataType(ClientDataType.STRING); params.add(param2); executeActionDTO.setParams(params); @@ -1881,10 +1896,12 @@ public class MongoPluginTest { Param param1 = new Param(); param1.setKey("Input1.text"); param1.setValue("This string contains ? symbol"); + param1.setClientDataType(ClientDataType.STRING); params.add(param1); Param param3 = new Param(); param3.setKey("Input2.text"); param3.setValue("F"); + param3.setClientDataType(ClientDataType.STRING); params.add(param3); executeActionDTO.setParams(params); @@ -2083,6 +2100,8 @@ public class MongoPluginTest { Param param1 = new Param(); param1.setKey("Input1.text"); param1.setValue(objectIdsAsArray); + param1.setClientDataType(ClientDataType.ARRAY); + param1.setDataTypesOfArrayElements(List.of(ClientDataType.OBJECT)); params.add(param1); executeActionDTO.setParams(params); @@ -2139,6 +2158,8 @@ public class MongoPluginTest { Param param1 = new Param(); param1.setKey("Input1.text"); param1.setValue(objectIdsAsArray); + param1.setClientDataType(ClientDataType.ARRAY); + param1.setDataTypesOfArrayElements(List.of(ClientDataType.OBJECT)); params.add(param1); executeActionDTO.setParams(params); @@ -2517,14 +2538,23 @@ public class MongoPluginTest { ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); final List params = new ArrayList<>(); final Param id = new Param("Input0", "[\"ObjectId('" + documentIds.get(0) + "')\"]"); + id.setClientDataType(ClientDataType.ARRAY); + id.setDataTypesOfArrayElements(List.of(ClientDataType.OBJECT)); params.add(id); final Param dob = new Param("Input1", "[\"ISODate('1970-01-01T00:00:00.000Z')\"]"); + dob.setClientDataType(ClientDataType.ARRAY); + dob.setDataTypesOfArrayElements(List.of(ClientDataType.OBJECT)); params.add(dob); final Param netWorth = new Param("Input2", "['NumberDecimal(\"123456.789012\")']"); + netWorth.setClientDataType(ClientDataType.ARRAY); + netWorth.setDataTypesOfArrayElements(List.of(ClientDataType.OBJECT)); params.add(netWorth); final Param aLong = new Param("Input3", "\"NumberLong(9000000000000000000)\""); + aLong.setClientDataType(ClientDataType.OBJECT); params.add(aLong); final Param ts = new Param("Input4", "[\"Timestamp(1421006159, 4)\"]"); + ts.setClientDataType(ClientDataType.ARRAY); + ts.setDataTypesOfArrayElements(List.of(ClientDataType.OBJECT)); params.add(ts); executeActionDTO.setParams(params); @@ -2565,18 +2595,22 @@ public class MongoPluginTest { "\"dob\": { \"$gte\": \"ISODate('2000-01-01T00:00:00.000Z')\" }, " + "\"netWorth\": { \"$in\": [\"NumberDecimal(123456.789012)\"] } " + "}"); + param1.setClientDataType(ClientDataType.OBJECT); params.add(param1); Param param3 = new Param(); param3.setKey("Input2.text"); param3.setValue("1"); + param3.setClientDataType(ClientDataType.NUMBER); params.add(param3); Param param4 = new Param(); param4.setKey("Input3.text"); param4.setValue("10"); + param4.setClientDataType(ClientDataType.NUMBER); params.add(param4); Param param5 = new Param(); param5.setKey("Input4.text"); param5.setValue("users"); + param5.setClientDataType(ClientDataType.STRING); params.add(param5); executeActionDTO.setParams(params); @@ -2634,14 +2668,19 @@ public class MongoPluginTest { ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); final List params = new ArrayList<>(); final Param id = new Param("Input0", "\"ObjectId(\\\"" + documentIds.get(0) + "\\\")\""); + id.setClientDataType(ClientDataType.OBJECT); params.add(id); final Param dob = new Param("Input1", "\"ISODate(\\\"1970-01-01T00:00:00.000Z\\\")\""); + dob.setClientDataType(ClientDataType.OBJECT); params.add(dob); final Param netWorth = new Param("Input2", "\"NumberDecimal(\\\"123456.789012\\\")\""); + netWorth.setClientDataType(ClientDataType.OBJECT); params.add(netWorth); final Param aLong = new Param("Input3", "\"NumberLong(9000000000000000000)\""); + aLong.setClientDataType(ClientDataType.OBJECT); params.add(aLong); final Param ts = new Param("Input4", "\"Timestamp(1421006159, 4)\""); + ts.setClientDataType(ClientDataType.OBJECT); params.add(ts); executeActionDTO.setParams(params); @@ -2680,6 +2719,7 @@ public class MongoPluginTest { ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); final List params = new ArrayList<>(); final Param dob = new Param("Input1", "{\"dob\": \"ISODate(\\\"1970-01-01T00:00:00.000Z\\\")\"}"); + dob.setClientDataType(ClientDataType.OBJECT); params.add(dob); executeActionDTO.setParams(params); @@ -2911,7 +2951,9 @@ public class MongoPluginTest { actionConfiguration.setFormData(configMap); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); - executeActionDTO.setParams(List.of(new Param("appsmith.store.variable", "[a-zA-Z]{0,3}.*Ci.*"))); + Param param = new Param("appsmith.store.variable", "[a-zA-Z]{0,3}.*Ci.*"); + param.setClientDataType(ClientDataType.STRING); + executeActionDTO.setParams(List.of(param)); Mono actionExecutionResultMono = dsConnectionMono.flatMap(clientConnection -> pluginExecutor.executeParameterized(clientConnection, executeActionDTO, @@ -2946,7 +2988,9 @@ public class MongoPluginTest { actionConfiguration.setFormData(configMap); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); - executeActionDTO.setParams(List.of(new Param("appsmith.store.variable", "35"))); + Param param = new Param("appsmith.store.variable", "35"); + param.setClientDataType(ClientDataType.NUMBER); + executeActionDTO.setParams(List.of(param)); Mono actionExecutionResultMono = dsConnectionMono.flatMap(clientConnection -> pluginExecutor.executeParameterized(clientConnection, executeActionDTO, @@ -2982,7 +3026,9 @@ public class MongoPluginTest { actionConfiguration.setFormData(configMap); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); - executeActionDTO.setParams(List.of(new Param("appsmith.store.variable", "5-.*"))); + Param param = new Param("appsmith.store.variable", "5-.*"); + param.setClientDataType(ClientDataType.STRING); + executeActionDTO.setParams(List.of(param)); Mono actionExecutionResultMono = dsConnectionMono.flatMap(clientConnection -> pluginExecutor.executeParameterized(clientConnection, executeActionDTO, @@ -3018,7 +3064,9 @@ public class MongoPluginTest { actionConfiguration.setFormData(configMap); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); - executeActionDTO.setParams(List.of(new Param("appsmith.store.variable", "-7"))); + Param param = new Param("appsmith.store.variable", "-7"); + param.setClientDataType(ClientDataType.NUMBER); + executeActionDTO.setParams(List.of(param)); Mono actionExecutionResultMono = dsConnectionMono.flatMap(clientConnection -> pluginExecutor.executeParameterized(clientConnection, executeActionDTO, @@ -3055,7 +3103,9 @@ public class MongoPluginTest { actionConfiguration.setFormData(configMap); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); - executeActionDTO.setParams(List.of(new Param("appsmith.store.variable", "-2.5"))); + Param param = new Param("appsmith.store.variable", "-2.5"); + param.setClientDataType(ClientDataType.NUMBER); + executeActionDTO.setParams(List.of(param)); Mono actionExecutionResultMono = dsConnectionMono.flatMap(clientConnection -> pluginExecutor.executeParameterized(clientConnection, executeActionDTO, diff --git a/app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java b/app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java index 8d354ad7a7..36a5bceed1 100644 --- a/app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java +++ b/app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java @@ -1,11 +1,12 @@ package com.external.plugins; import com.appsmith.external.constants.DataType; +import com.appsmith.external.datatypes.AppsmithType; import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException; -import com.appsmith.external.helpers.DataTypeStringUtils; +import com.appsmith.external.helpers.DataTypeServiceUtils; import com.appsmith.external.helpers.MustacheHelper; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionRequest; @@ -13,6 +14,7 @@ import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.Endpoint; +import com.appsmith.external.models.Param; import com.appsmith.external.models.Property; import com.appsmith.external.models.PsParameterDTO; import com.appsmith.external.models.RequestParamDTO; @@ -446,7 +448,9 @@ public class MssqlPlugin extends BasePlugin { Object... args) throws AppsmithPluginException { PreparedStatement preparedStatement = (PreparedStatement) input; - DataType valueType = DataTypeStringUtils.stringToKnownDataTypeConverter(value); + Param param = (Param) args[0]; + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(param.getClientDataType(), value); + DataType valueType = appsmithType.type(); Map.Entry parameter = new SimpleEntry<>(value, valueType.toString()); insertedParams.add(parameter); diff --git a/app/server/appsmith-plugins/mssqlPlugin/src/test/java/com/external/plugins/MssqlPluginTest.java b/app/server/appsmith-plugins/mssqlPlugin/src/test/java/com/external/plugins/MssqlPluginTest.java index ec6954e750..7f3bc45334 100755 --- a/app/server/appsmith-plugins/mssqlPlugin/src/test/java/com/external/plugins/MssqlPluginTest.java +++ b/app/server/appsmith-plugins/mssqlPlugin/src/test/java/com/external/plugins/MssqlPluginTest.java @@ -1,5 +1,6 @@ package com.external.plugins; +import com.appsmith.external.datatypes.ClientDataType; import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.models.ActionConfiguration; @@ -16,6 +17,7 @@ import com.appsmith.external.models.RequestParamDTO; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeType; import com.zaxxer.hikari.HikariDataSource; import org.bson.types.ObjectId; import org.junit.jupiter.api.Assertions; @@ -297,6 +299,7 @@ public class MssqlPluginTest { Param param = new Param(); param.setKey("binding1"); param.setValue("1"); + param.setClientDataType(ClientDataType.NUMBER); params.add(param); executeActionDTO.setParams(params); @@ -361,6 +364,7 @@ public class MssqlPluginTest { Param param = new Param(); param.setKey("binding1"); param.setValue("1"); + param.setClientDataType(ClientDataType.NUMBER); params.add(param); executeActionDTO.setParams(params); @@ -435,6 +439,7 @@ public class MssqlPluginTest { Param param = new Param(); param.setKey("binding1"); param.setValue("1"); + param.setClientDataType(ClientDataType.NUMBER); params.add(param); executeActionDTO.setParams(params); @@ -496,6 +501,7 @@ public class MssqlPluginTest { Param param = new Param(); param.setKey("binding1"); param.setValue("null"); + param.setClientDataType(ClientDataType.NULL); params.add(param); executeActionDTO.setParams(params); @@ -547,6 +553,7 @@ public class MssqlPluginTest { Param param = new Param(); param.setKey("binding1"); param.setValue(null); + param.setClientDataType(ClientDataType.NULL); params.add(param); executeActionDTO.setParams(params); @@ -643,4 +650,50 @@ public class MssqlPluginTest { }) .verifyComplete(); } + + @Test + public void testNumericStringHavingLeadingZeroWithPreparedStatement() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(); + + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setBody("SELECT {{binding1}} as numeric_string;"); + + List pluginSpecifiedTemplates = new ArrayList<>(); + pluginSpecifiedTemplates.add(new Property("preparedStatement", "true")); + actionConfiguration.setPluginSpecifiedTemplates(pluginSpecifiedTemplates); + + ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); + List params = new ArrayList<>(); + Param param1 = new Param(); + param1.setKey("binding1"); + param1.setValue("098765"); + param1.setClientDataType(ClientDataType.STRING); + params.add(param1); + executeActionDTO.setParams(params); + + Mono connectionCreateMono = pluginExecutor.datasourceCreate(dsConfig).cache(); + + Mono resultMono = connectionCreateMono + .flatMap(pool -> pluginExecutor.executeParameterized(pool, executeActionDTO, dsConfig, actionConfiguration)); + + StepVerifier.create(resultMono) + .assertNext(result -> { + assertTrue(result.getIsExecutionSuccess()); + final JsonNode node = ((ArrayNode) result.getBody()).get(0); + assertArrayEquals( + new String[] { + "numeric_string" + }, + new ObjectMapper() + .convertValue(node, LinkedHashMap.class) + .keySet() + .toArray()); + + // Verify value + assertEquals(JsonNodeType.STRING, node.get("numeric_string").getNodeType()); + assertEquals(param1.getValue(), node.get("numeric_string").asText()); + + }) + .verifyComplete(); + } } diff --git a/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java index ea4ffaf199..ab0e93039d 100644 --- a/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java +++ b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java @@ -15,6 +15,7 @@ import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DatasourceStructure; import com.appsmith.external.models.Endpoint; +import com.appsmith.external.models.Param; import com.appsmith.external.models.Property; import com.appsmith.external.models.PsParameterDTO; import com.appsmith.external.models.RequestParamDTO; @@ -395,9 +396,8 @@ public class MySqlPlugin extends BasePlugin { Object... args) { Statement connectionStatement = (Statement) input; - ClientDataType clientDataType = (ClientDataType) args[0]; - AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(clientDataType, value, MySQLSpecificDataTypes.pluginSpecificTypes); - + Param param = (Param) args[0]; + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(param.getClientDataType(), value, MySQLSpecificDataTypes.pluginSpecificTypes); Map.Entry parameter = new SimpleEntry<>(value, appsmithType.type().toString()); insertedParams.add(parameter); diff --git a/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java b/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java index f558d1ff64..43e2e43ece 100644 --- a/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java +++ b/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java @@ -1,11 +1,12 @@ package com.external.plugins; import com.appsmith.external.constants.DataType; +import com.appsmith.external.datatypes.AppsmithType; import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException; -import com.appsmith.external.helpers.DataTypeStringUtils; +import com.appsmith.external.helpers.DataTypeServiceUtils; import com.appsmith.external.helpers.MustacheHelper; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionRequest; @@ -14,6 +15,7 @@ import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DatasourceStructure; import com.appsmith.external.models.Endpoint; +import com.appsmith.external.models.Param; import com.appsmith.external.models.Property; import com.appsmith.external.models.PsParameterDTO; import com.appsmith.external.models.RequestParamDTO; @@ -22,6 +24,7 @@ import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.PluginExecutor; import com.appsmith.external.plugins.SmartSubstitutionInterface; import com.appsmith.external.services.SharedConfig; +import com.external.plugins.datatypes.PostgresSpecificDataTypes; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariPoolMXBean; @@ -800,12 +803,14 @@ public class PostgresPlugin extends BasePlugin { PreparedStatement preparedStatement = (PreparedStatement) input; HikariProxyConnection connection = (HikariProxyConnection) args[0]; List explicitCastDataTypes = (List) args[1]; + Param param = (Param) args[2]; DataType valueType; // If explicitly cast, set the user specified data type if (explicitCastDataTypes != null && explicitCastDataTypes.get(index - 1) != null) { valueType = explicitCastDataTypes.get(index - 1); } else { - valueType = DataTypeStringUtils.stringToKnownDataTypeConverter(value); + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(param.getClientDataType(), value, PostgresSpecificDataTypes.pluginSpecificTypes); + valueType = appsmithType.type(); } Map.Entry parameter = new SimpleEntry<>(value, valueType.toString()); @@ -854,6 +859,9 @@ public class PostgresPlugin extends BasePlugin { preparedStatement.setTimestamp(index, Timestamp.valueOf(value)); break; } + case NULL_ARRAY: + preparedStatement.setArray(index, null); + break; case ARRAY: { List arrayListFromInput = objectMapper.readValue(value, List.class); if (arrayListFromInput.isEmpty()) { @@ -861,7 +869,8 @@ public class PostgresPlugin extends BasePlugin { } // Find the type of the entries in the list Object firstEntry = arrayListFromInput.get(0); - DataType dataType = DataTypeStringUtils.stringToKnownDataTypeConverter((String.valueOf(firstEntry))); + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(param.getDataTypesOfArrayElements().get(0), String.valueOf(firstEntry)); + DataType dataType = appsmithType.type(); String typeName = toPostgresqlPrimitiveTypeName(dataType); // Create the Sql Array and set it. diff --git a/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/datatypes/PostgresSpecificDataTypes.java b/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/datatypes/PostgresSpecificDataTypes.java new file mode 100644 index 0000000000..b17627a5fd --- /dev/null +++ b/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/datatypes/PostgresSpecificDataTypes.java @@ -0,0 +1,53 @@ +package com.external.plugins.datatypes; + +import com.appsmith.external.datatypes.AppsmithType; +import com.appsmith.external.datatypes.ArrayType; +import com.appsmith.external.datatypes.BigDecimalType; +import com.appsmith.external.datatypes.BooleanType; +import com.appsmith.external.datatypes.ClientDataType; +import com.appsmith.external.datatypes.DateType; +import com.appsmith.external.datatypes.DoubleType; +import com.appsmith.external.datatypes.IntegerType; +import com.appsmith.external.datatypes.JsonObjectType; +import com.appsmith.external.datatypes.LongType; +import com.appsmith.external.datatypes.NullArrayType; +import com.appsmith.external.datatypes.NullType; +import com.appsmith.external.datatypes.StringType; +import com.appsmith.external.datatypes.TimeType; +import com.appsmith.external.datatypes.TimestampType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PostgresSpecificDataTypes { + public final static Map> pluginSpecificTypes = new HashMap<>(); + + static { + pluginSpecificTypes.put(ClientDataType.NULL, List.of(new NullType())); + + pluginSpecificTypes.put(ClientDataType.ARRAY, List.of(new NullArrayType(), new ArrayType())); + + pluginSpecificTypes.put(ClientDataType.BOOLEAN, List.of(new BooleanType())); + + pluginSpecificTypes.put(ClientDataType.NUMBER, List.of( + new IntegerType(), + new LongType(), + new DoubleType(), + new BigDecimalType() + )); + + /* + JsonObjectType is the preferred server-side data type when the client-side data type is of type OBJECT. + Fallback server-side data type for client-side OBJECT type is String. + */ + pluginSpecificTypes.put(ClientDataType.OBJECT, List.of(new JsonObjectType())); + + pluginSpecificTypes.put(ClientDataType.STRING, List.of( + new TimeType(), + new DateType(), + new TimestampType(), + new StringType() + )); + } +} diff --git a/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginDataTypeTest.java b/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginDataTypeTest.java new file mode 100644 index 0000000000..746554f234 --- /dev/null +++ b/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginDataTypeTest.java @@ -0,0 +1,215 @@ +package com.external.plugins; + +import com.appsmith.external.datatypes.AppsmithType; +import com.appsmith.external.datatypes.ArrayType; +import com.appsmith.external.datatypes.BigDecimalType; +import com.appsmith.external.datatypes.BooleanType; +import com.appsmith.external.datatypes.ClientDataType; +import com.appsmith.external.datatypes.DateType; +import com.appsmith.external.datatypes.DoubleType; +import com.appsmith.external.datatypes.FallbackType; +import com.appsmith.external.datatypes.IntegerType; +import com.appsmith.external.datatypes.JsonObjectType; +import com.appsmith.external.datatypes.LongType; +import com.appsmith.external.datatypes.NullArrayType; +import com.appsmith.external.datatypes.NullType; +import com.appsmith.external.datatypes.StringType; +import com.appsmith.external.datatypes.TimeType; +import com.appsmith.external.datatypes.TimestampType; +import com.appsmith.external.helpers.DataTypeServiceUtils; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class PostgresPluginDataTypeTest { + public final static Map> pluginSpecificTypes = new HashMap<>(); + + static { + pluginSpecificTypes.put(ClientDataType.NULL, List.of(new NullType())); + + pluginSpecificTypes.put(ClientDataType.ARRAY, List.of(new NullArrayType(), new ArrayType())); + + pluginSpecificTypes.put(ClientDataType.BOOLEAN, List.of(new BooleanType())); + + pluginSpecificTypes.put(ClientDataType.NUMBER, List.of( + new IntegerType(), + new LongType(), + new DoubleType(), + new BigDecimalType() + )); + + /* + JsonObjectType is the preferred server-side data type when the client-side data type is of type OBJECT. + Fallback server-side data type for client-side OBJECT type is String. + */ + pluginSpecificTypes.put(ClientDataType.OBJECT, List.of(new JsonObjectType())); + + pluginSpecificTypes.put(ClientDataType.STRING, List.of( + new TimeType(), + new DateType(), + new TimestampType(), + new StringType() + )); + } + + @Test + public void shouldBeNullType() { + String value = "null"; + + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.NULL, value, pluginSpecificTypes); + assertTrue(appsmithType instanceof NullType); + assertEquals(appsmithType.performSmartSubstitution(value), null); + } + + @Test + public void shouldBeBooleanType() { + String[] values = { + "true", + "false" + }; + + for (String value : values) { + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.BOOLEAN, value, pluginSpecificTypes); + assertTrue(appsmithType instanceof BooleanType); + assertEquals(appsmithType.performSmartSubstitution(value), value); + } + } + + @Test + public void shouldBeIntegerType() { + String[] values = { + "0", + "7166", + String.valueOf(Integer.MIN_VALUE), + String.valueOf(Integer.MAX_VALUE) + }; + + for (String value : values) { + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.NUMBER, value, pluginSpecificTypes); + assertTrue(appsmithType instanceof IntegerType); + assertEquals(appsmithType.performSmartSubstitution(value), value); + } + } + + @Test + public void shouldBeLongType() { + String[] values = { + "2147483648", + "-2147483649" + }; + + for (String value : values) { + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.NUMBER, value, pluginSpecificTypes); + assertTrue(appsmithType instanceof LongType); + assertEquals(appsmithType.performSmartSubstitution(value), value); + } + } + + @Test + public void shouldBeDoubleType() { + String[] values = { + "323.23", + String.valueOf(Double.MIN_VALUE), + String.valueOf(Double.MAX_VALUE), + String.valueOf(Double.POSITIVE_INFINITY), + String.valueOf(Double.NEGATIVE_INFINITY) + }; + + for (String value : values) { + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.NUMBER, value, pluginSpecificTypes); + assertTrue(appsmithType instanceof DoubleType); + assertEquals(appsmithType.performSmartSubstitution(value), value); + } + } + + @Test + public void shouldBeJsonObjectTypeOrStringType() { + String[] values = { + "{\"a\":97,\"A\":65}", + "{\"a\":97,\"A\":65" + }; + for (String value : values) { + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.OBJECT, value, pluginSpecificTypes); + assertTrue(appsmithType instanceof JsonObjectType || appsmithType instanceof FallbackType); + } + + } + + @Test + public void shouldBeTimeType() { + String[] values = { + "10:15:30", + "10:15" + }; + for (String value : values) { + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING, value, pluginSpecificTypes); + assertTrue(appsmithType instanceof TimeType); + } + } + + @Test + public void shouldBeDateType() { + String[] values = { + "2011-12-03" + }; + for (String value : values) { + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING, value, pluginSpecificTypes); + assertTrue(appsmithType instanceof DateType); + } + } + + @Test + public void shouldBeTimestampType() { + String[] values = { + "2021-03-24 14:05:34" + }; + for (String value : values) { + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING, value, pluginSpecificTypes); + assertTrue(appsmithType instanceof TimestampType); + } + } + + @Test + public void shouldBeStringType() { + String[] values = { + "Hello, world!", + "123", + "098876", + "2022/09/252", + "", + "10:15:30+06:00", + "2021-03-24 14:05:343" + }; + for (String value : values) { + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.STRING, value, pluginSpecificTypes); + assertTrue(appsmithType instanceof StringType); + } + } + + @Test + public void shouldNullArrayType() { + String[] values = { + "[]" + }; + for (String value : values) { + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.ARRAY, value, pluginSpecificTypes); + assertTrue(appsmithType instanceof NullArrayType); + } + } + + @Test + public void shouldArrayType() { + String[] values = { + "[71]" + }; + for (String value : values) { + AppsmithType appsmithType = DataTypeServiceUtils.getAppsmithType(ClientDataType.ARRAY, value, pluginSpecificTypes); + assertTrue(appsmithType instanceof ArrayType); + } + } +} diff --git a/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginTest.java b/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginTest.java index 7bce97c57f..02e66f86ce 100644 --- a/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginTest.java +++ b/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginTest.java @@ -1,5 +1,6 @@ package com.external.plugins; +import com.appsmith.external.datatypes.ClientDataType; import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException; import com.appsmith.external.models.ActionConfiguration; @@ -19,6 +20,7 @@ import com.appsmith.external.services.SharedConfig; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeType; import com.zaxxer.hikari.HikariDataSource; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -634,6 +636,8 @@ public class PostgresPluginTest { Param param = new Param(); param.setKey("binding1"); param.setValue("1"); + param.setClientDataType(ClientDataType.NUMBER); + params.add(param); executeActionDTO.setParams(params); @@ -708,6 +712,7 @@ public class PostgresPluginTest { Param param = new Param(); param.setKey("binding1"); param.setValue("1"); + param.setClientDataType(ClientDataType.NUMBER); params.add(param); executeActionDTO.setParams(params); @@ -791,6 +796,7 @@ public class PostgresPluginTest { Param param = new Param(); param.setKey("binding1"); param.setValue("1"); + param.setClientDataType(ClientDataType.NUMBER); params.add(param); executeActionDTO.setParams(params); @@ -870,6 +876,7 @@ public class PostgresPluginTest { Param param = new Param(); param.setKey("binding1"); param.setValue("null"); + param.setClientDataType(ClientDataType.NULL); params.add(param); executeActionDTO.setParams(params); @@ -934,6 +941,7 @@ public class PostgresPluginTest { Param param = new Param(); param.setKey("binding1"); param.setValue(null); + param.setClientDataType(ClientDataType.NULL); params.add(param); executeActionDTO.setParams(params); @@ -1156,6 +1164,7 @@ public class PostgresPluginTest { Param param = new Param(); param.setKey("binding1"); param.setValue("2021-03-24 14:05:34"); + param.setClientDataType(ClientDataType.STRING); params.add(param); executeActionDTO.setParams(params); @@ -1200,6 +1209,7 @@ public class PostgresPluginTest { Param param = new Param(); param.setKey("binding1"); param.setValue("2021-03-24 14:05:34"); + param.setClientDataType(ClientDataType.STRING); params.add(param); executeActionDTO.setParams(params); @@ -1255,12 +1265,30 @@ public class PostgresPluginTest { ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); List params = new ArrayList<>(); - params.add(new Param("id", "10")); - params.add(new Param("firstName", "1001")); - params.add(new Param("lastName", "LastName")); - params.add(new Param("email", "email@email.com")); - params.add(new Param("date", "2018-12-31")); - params.add(new Param("rating", String.valueOf(5.1))); + Param param = new Param("id", "10"); + param.setClientDataType(ClientDataType.NUMBER); + params.add(param); + + param = new Param("firstName", "1001"); + param.setClientDataType(ClientDataType.STRING); + params.add(param); + + param = new Param("lastName", "LastName"); + param.setClientDataType(ClientDataType.STRING); + params.add(param); + + param = new Param("email", "email@email.com"); + param.setClientDataType(ClientDataType.STRING); + params.add(param); + + param = new Param("date", "2018-12-31"); + param.setClientDataType(ClientDataType.STRING); + params.add(param); + + param = new Param("rating", String.valueOf(5.1)); + param.setClientDataType(ClientDataType.NUMBER); + params.add(param); + executeActionDTO.setParams(params); Mono connectionCreateMono = pluginExecutor.datasourceCreate(dsConfig).cache(); @@ -1331,7 +1359,9 @@ public class PostgresPluginTest { ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); List params = new ArrayList<>(); - params.add(new Param("createdTS", "2022-04-11T05:30:00Z")); + Param param = new Param("createdTS", "2022-04-11T05:30:00Z"); + param.setClientDataType(ClientDataType.STRING); + params.add(param); executeActionDTO.setParams(params); @@ -1398,10 +1428,22 @@ public class PostgresPluginTest { ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); List params = new ArrayList<>(); - params.add(new Param("id", "10")); - params.add(new Param("jsonObject1", "{\"type\":\"racket\", \"manufacturer\":\"butterfly\"}")); - params.add(new Param("jsonObject2", "{\"country\":\"japan\", \"city\":\"kyoto\"}")); - params.add(new Param("stringValue", "Something here")); + Param param = new Param("id", "10"); + param.setClientDataType(ClientDataType.NUMBER); + params.add(param); + + param = new Param("jsonObject1", "{\"type\":\"racket\", \"manufacturer\":\"butterfly\"}"); + param.setClientDataType(ClientDataType.OBJECT); + params.add(param); + + param = new Param("jsonObject2", "{\"country\":\"japan\", \"city\":\"kyoto\"}"); + param.setClientDataType(ClientDataType.OBJECT); + params.add(param); + + param = new Param("stringValue", "Something here"); + param.setClientDataType(ClientDataType.STRING); + params.add(param); + executeActionDTO.setParams(params); Mono connectionCreateMono = pluginExecutor.datasourceCreate(dsConfig).cache(); @@ -1445,4 +1487,50 @@ public class PostgresPluginTest { .flatMap(pool -> pluginExecutor.executeParameterized(pool, executeActionDTO, dsConfig, actionConfiguration)).block(); } + + @Test + public void testNumericStringHavingLeadingZeroWithPreparedStatement() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(); + + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setBody("SELECT {{binding1}} as numeric_string;"); + + List pluginSpecifiedTemplates = new ArrayList<>(); + pluginSpecifiedTemplates.add(new Property("preparedStatement", "true")); + actionConfiguration.setPluginSpecifiedTemplates(pluginSpecifiedTemplates); + + ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); + List params = new ArrayList<>(); + Param param1 = new Param(); + param1.setKey("binding1"); + param1.setValue("098765"); + param1.setClientDataType(ClientDataType.STRING); + params.add(param1); + executeActionDTO.setParams(params); + + Mono connectionCreateMono = pluginExecutor.datasourceCreate(dsConfig).cache(); + + Mono resultMono = connectionCreateMono + .flatMap(pool -> pluginExecutor.executeParameterized(pool, executeActionDTO, dsConfig, actionConfiguration)); + + StepVerifier.create(resultMono) + .assertNext(result -> { + assertTrue(result.getIsExecutionSuccess()); + final JsonNode node = ((ArrayNode) result.getBody()).get(0); + assertArrayEquals( + new String[] { + "numeric_string" + }, + new ObjectMapper() + .convertValue(node, LinkedHashMap.class) + .keySet() + .toArray()); + + // Verify value + assertEquals(JsonNodeType.STRING, node.get("numeric_string").getNodeType()); + assertEquals(param1.getValue(), node.get("numeric_string").asText()); + + }) + .verifyComplete(); + } } diff --git a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java index 86f9101cca..5e9221f3fb 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java @@ -13,6 +13,7 @@ import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.PaginationField; import com.appsmith.external.models.PaginationType; +import com.appsmith.external.models.Param; import com.appsmith.external.models.Property; import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.BaseRestApiPluginExecutor; @@ -211,7 +212,8 @@ public class RestApiPlugin extends BasePlugin { List> insertedParams, Object... args) { String jsonBody = (String) input; - return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(jsonBody, value, null, insertedParams, null); + Param param = (Param) args[0]; + return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(jsonBody, value, null, insertedParams, null, param); } } } diff --git a/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java index 13e7961ed3..861c174f35 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java @@ -1,5 +1,6 @@ package com.external.plugins; +import com.appsmith.external.datatypes.ClientDataType; import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.helpers.PluginUtils; import com.appsmith.external.helpers.restApiUtils.connections.APIConnection; @@ -371,30 +372,37 @@ public class RestApiPluginTest { Param param1 = new Param(); param1.setKey("Input1.text"); param1.setValue("this is a string! Yay :D"); + param1.setClientDataType(ClientDataType.STRING); params.add(param1); Param param3 = new Param(); param3.setKey("Input2.text"); param3.setValue("true"); + param3.setClientDataType(ClientDataType.BOOLEAN); params.add(param3); Param param4 = new Param(); param4.setKey("Input3.text"); param4.setValue("0"); + param4.setClientDataType(ClientDataType.NUMBER); params.add(param4); Param param5 = new Param(); param5.setKey("Input4.text"); param5.setValue("12/01/2018"); + param5.setClientDataType(ClientDataType.STRING); params.add(param5); Param param6 = new Param(); param6.setKey("Input5.text"); param6.setValue("null"); + param6.setClientDataType(ClientDataType.NULL); params.add(param6); Param param7 = new Param(); param7.setKey("Table1.selectedRow"); param7.setValue("{ \"id\": 2381224, \"email\": \"michael.lawson@reqres.in\", \"userName\": \"Michael Lawson\", \"productName\": \"Chicken Sandwich\", \"orderAmount\": 4.99}"); + param7.setClientDataType(ClientDataType.OBJECT); params.add(param7); Param param8 = new Param(); param8.setKey("Table1.tableData"); param8.setValue("[ { \"id\": 2381224, \"email\": \"michael.lawson@reqres.in\", \"userName\": \"Michael Lawson\", \"productName\": \"Chicken Sandwich\", \"orderAmount\": 4.99 }, { \"id\": 2736212, \"email\": \"lindsay.ferguson@reqres.in\", \"userName\": \"Lindsay Ferguson\", \"productName\": \"Tuna Salad\", \"orderAmount\": 9.99 }, { \"id\": 6788734, \"email\": \"tobias.funke@reqres.in\", \"userName\": \"Tobias Funke\", \"productName\": \"Beef steak\", \"orderAmount\": 19.99 }]"); + param8.setClientDataType(ClientDataType.ARRAY); params.add(param8); executeActionDTO.setParams(params); @@ -575,10 +583,12 @@ public class RestApiPluginTest { Param param1 = new Param(); param1.setKey("Input1.text"); param1.setValue("this is a string with a ? "); + param1.setClientDataType(ClientDataType.STRING); params.add(param1); Param param2 = new Param(); param2.setKey("Input2.text"); param2.setValue("email@email.com"); + param2.setClientDataType(ClientDataType.STRING); params.add(param2); executeActionDTO.setParams(params); @@ -1096,5 +1106,45 @@ public class RestApiPluginTest { }) .verifyComplete(); } + + @Test + public void testNumericStringHavingLeadingZero() { + DatasourceConfiguration dsConfig = new DatasourceConfiguration(); + dsConfig.setUrl("https://postman-echo.com/post"); + + ActionConfiguration actionConfig = new ActionConfiguration(); + final List headers = List.of(new Property("content-type", "application/json")); + actionConfig.setHeaders(headers); + actionConfig.setHttpMethod(HttpMethod.POST); + String requestBody = "{\"phoneNumber\":\"{{phoneNumber.text}}\"}"; + actionConfig.setBody(requestBody); + ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); + Param param = new Param(); + param.setKey("phoneNumber.text"); + param.setValue("017725617478"); + param.setClientDataType(ClientDataType.STRING); + List params = new ArrayList<>(); + params.add(param); + executeActionDTO.setParams(params); + + Mono resultMono = pluginExecutor.executeParameterized(null, executeActionDTO, dsConfig, actionConfig); + StepVerifier.create(resultMono) + .assertNext(result -> { + assertTrue(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + JsonNode data = ((ObjectNode) result.getBody()).get("data"); + assertEquals(requestBody.replace("{{phoneNumber.text}}", param.getValue()), data.toString()); + final ActionExecutionRequest request = result.getRequest(); + assertEquals("https://postman-echo.com/post", request.getUrl()); + assertEquals(HttpMethod.POST, request.getHttpMethod()); + final Iterator> fields = ((ObjectNode) result.getRequest().getHeaders()).fields(); + fields.forEachRemaining(field -> { + if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(field.getKey())) { + assertEquals("application/json", field.getValue().get(0).asText()); + } + }); + }) + .verifyComplete(); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewActionServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewActionServiceCEImpl.java index b819ba3679..bd30492d22 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewActionServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewActionServiceCEImpl.java @@ -92,6 +92,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; @@ -920,7 +921,26 @@ public class NewActionServiceCEImpl extends BaseService { String pseudoBindingName = param.getPseudoBindingName(); param.setKey(dto.getInvertParameterMap().get(pseudoBindingName)); - param.setClientDataType(ClientDataType.valueOf(dto.getParamProperties().get(pseudoBindingName).toUpperCase())); + //if the type is not an array e.g. "k1": "string" or "k1": "boolean" + if (dto.getParamProperties().get(pseudoBindingName) instanceof String) { + param.setClientDataType(ClientDataType.valueOf(String.valueOf(dto.getParamProperties().get(pseudoBindingName)).toUpperCase())); + } else if (dto.getParamProperties().get(pseudoBindingName) instanceof LinkedHashMap) { + //if the type is an array e.g. "k1": { "array": [ "string", "number", "string", "boolean"] + LinkedHashMap stringArrayListLinkedHashMap = + (LinkedHashMap) dto.getParamProperties().get(pseudoBindingName); + Optional firstKeyOpt = stringArrayListLinkedHashMap.keySet().stream().findFirst(); + if (firstKeyOpt.isPresent()) { + String firstKey = firstKeyOpt.get(); + param.setClientDataType(ClientDataType.valueOf(firstKey.toUpperCase())); + List individualTypes = stringArrayListLinkedHashMap.get(firstKey); + List dataTypesOfArrayElements = + individualTypes.stream() + .map(it -> ClientDataType.valueOf(String.valueOf(it).toUpperCase())) + .collect(Collectors.toList()); + param.setDataTypesOfArrayElements(dataTypesOfArrayElements); + } + } + } ); dto.setParams(params); From 0e73906a19b8ff060700ea3e766494ad73023251 Mon Sep 17 00:00:00 2001 From: Keyur Paralkar Date: Wed, 19 Oct 2022 11:09:01 +0530 Subject: [PATCH 33/54] fix: remove file references on click of cancel button (#17664) --- .../Widgets/Filepicker/FilePickerV2_spec.js | 14 ++++++++++++++ app/client/cypress/locators/Widgets.json | 2 ++ .../widgets/FilePickerWidgetV2/widget/index.tsx | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Filepicker/FilePickerV2_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Filepicker/FilePickerV2_spec.js index 4fdd39457b..7b75c20f25 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Filepicker/FilePickerV2_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Filepicker/FilePickerV2_spec.js @@ -55,4 +55,18 @@ describe("File picker widget v2", () => { ); cy.get(".t--widget-textwidget").should("contain", "testFile.mov"); }); + + it("Check if the uploaded file is removed on click of cancel button", () => { + cy.get(widgetsPage.filepickerwidgetv2).click(); + cy.get(widgetsPage.filepickerwidgetv2CancelBtn).click(); + cy.get(widgetsPage.filepickerwidgetv2).should("contain", "Select Files"); + cy.get(widgetsPage.filepickerwidgetv2CloseModalBtn).click(); + cy.get(widgetsPage.explorerSwitchId).click(); + cy.get(".t--entity-item:contains(Api1)").click(); + cy.get("[class*='t--actionConfiguration']") + .eq(0) + .click(); + cy.wait(1000); + cy.validateEvaluatedValue("[]"); + }); }); diff --git a/app/client/cypress/locators/Widgets.json b/app/client/cypress/locators/Widgets.json index f912064fc3..5bd3fdb7f0 100644 --- a/app/client/cypress/locators/Widgets.json +++ b/app/client/cypress/locators/Widgets.json @@ -191,5 +191,7 @@ "colorPickerV2Color": ".t--colorpicker-v2-color", "modalCloseButton": ".t--draggable-iconbuttonwidget .bp3-button", "filepickerwidgetv2": ".t--widget-filepickerwidgetv2", + "filepickerwidgetv2CancelBtn": "button.uppy-DashboardContent-back", + "filepickerwidgetv2CloseModalBtn": "button.uppy-Dashboard-close", "codescannerwidget": ".t--widget-codescannerwidget" } diff --git a/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx b/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx index 73455534b4..4eeb919f02 100644 --- a/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx @@ -205,8 +205,11 @@ class FilePickerWidget extends BaseWidget< FilePickerWidgetProps, FilePickerWidgetState > { + private isWidgetUnmounting: boolean; + constructor(props: FilePickerWidgetProps) { super(props); + this.isWidgetUnmounting = false; this.state = { isLoading: false, uppy: this.initializeUppy(), @@ -637,6 +640,10 @@ class FilePickerWidget extends BaseWidget< updatedFiles ?? [], ); } + + if (reason === "cancel-all" && !this.isWidgetUnmounting) { + this.props.updateWidgetMetaProperty("selectedFiles", []); + } }); this.state.uppy.on("files-added", (files: any[]) => { @@ -796,6 +803,7 @@ class FilePickerWidget extends BaseWidget< } componentWillUnmount() { + this.isWidgetUnmounting = true; this.state.uppy.close(); } From bda994ba4eb82f1858c12e2c1e246ba846d1219b Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Wed, 19 Oct 2022 12:12:55 +0530 Subject: [PATCH 34/54] Fix typo in cloud-hosting check and NPE from Segment (#17700) Signed-off-by: Shrikant Sharat Kandula (cherry picked from commit 973997c6063ee158f0d44ba30cab303bc0ace959) --- .../com/appsmith/server/configurations/SegmentConfig.java | 3 ++- .../com/appsmith/server/solutions/PingScheduledTaskImpl.java | 4 ++-- .../appsmith/server/solutions/ce/PingScheduledTaskCEImpl.java | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SegmentConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SegmentConfig.java index c3dd0ec3f0..fa29527dff 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SegmentConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SegmentConfig.java @@ -55,10 +55,11 @@ public class SegmentConfig { final LogProcessor logProcessorWithErrorHandler = new LogProcessor(); final Analytics analytics = Analytics.builder(analyticsWriteKey).log(logProcessorWithErrorHandler).build(); logProcessorWithErrorHandler.onError(logData -> { + final Throwable error = logData.getError(); analyticsOnAnalytics.enqueue(TrackMessage.builder("segment_error") .properties(Map.of( "message", logData.getMessage(), - "error", logData.getError().getMessage(), + "error", error == null ? "" : error.getMessage(), "args", ObjectUtils.defaultIfNull(logData.getArgs(), Collections.emptyList()) )) ); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/PingScheduledTaskImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/PingScheduledTaskImpl.java index dc5f050554..5f64e304e3 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/PingScheduledTaskImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/PingScheduledTaskImpl.java @@ -6,8 +6,8 @@ import com.appsmith.server.repositories.ApplicationRepository; import com.appsmith.server.repositories.DatasourceRepository; import com.appsmith.server.repositories.NewActionRepository; import com.appsmith.server.repositories.NewPageRepository; -import com.appsmith.server.repositories.WorkspaceRepository; import com.appsmith.server.repositories.UserRepository; +import com.appsmith.server.repositories.WorkspaceRepository; import com.appsmith.server.services.ConfigService; import com.appsmith.server.solutions.ce.PingScheduledTaskCEImpl; import lombok.extern.slf4j.Slf4j; @@ -19,7 +19,7 @@ import org.springframework.stereotype.Component; * This ping is only invoked if the Appsmith server is NOT running in Appsmith Clouud & the user has given Appsmith * permissions to collect anonymized data */ -@ConditionalOnExpression("!${is.cloud-hosted:false}") +@ConditionalOnExpression("!${is.cloud-hosting:false}") @Slf4j @Component public class PingScheduledTaskImpl extends PingScheduledTaskCEImpl implements PingScheduledTask { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/PingScheduledTaskCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/PingScheduledTaskCEImpl.java index 8cba129b02..10a73fc496 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/PingScheduledTaskCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/PingScheduledTaskCEImpl.java @@ -30,7 +30,7 @@ import java.util.Map; */ @Slf4j @RequiredArgsConstructor -@ConditionalOnExpression("!${is.cloud-hosted:false}") +@ConditionalOnExpression("!${is.cloud-hosting:false}") public class PingScheduledTaskCEImpl implements PingScheduledTaskCE { private final ConfigService configService; From 5a358ce0c36bacbd5b5ff8e57ad0dddf1ea5825b Mon Sep 17 00:00:00 2001 From: Tanvi Bhakta Date: Wed, 19 Oct 2022 15:37:25 +0530 Subject: [PATCH 35/54] chore: Correct the toast font on windows (#17671) --- app/client/package.json | 2 +- app/client/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/client/package.json b/app/client/package.json index 0a1418f2fc..509a3ccbe8 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -46,7 +46,7 @@ "cypress-log-to-output": "^1.1.2", "dayjs": "^1.10.6", "deep-diff": "^1.0.2", - "design-system": "npm:@appsmithorg/design-system@1.0.28", + "design-system": "npm:@appsmithorg/design-system@1.0.29", "downloadjs": "^1.4.7", "draft-js": "^0.11.7", "emoji-mart": "^3.0.1", diff --git a/app/client/yarn.lock b/app/client/yarn.lock index a3f096cbd7..55573303a8 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -6211,10 +6211,10 @@ depd@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" -"design-system@npm:@appsmithorg/design-system@1.0.28": - version "1.0.28" - resolved "https://registry.yarnpkg.com/@appsmithorg/design-system/-/design-system-1.0.28.tgz#40ad863ab73b83d960a7f8dfa932c250ea70efc5" - integrity sha512-Dsq2c+s4X64Bq0Q4bVnnmaYnstEFNuqLX6yvjiASmp3kRD46m01DgkI8aAQAMWvNKqAanmmi++QV3jfrUEQJVA== +"design-system@npm:@appsmithorg/design-system@1.0.29": + version "1.0.29" + resolved "https://registry.yarnpkg.com/@appsmithorg/design-system/-/design-system-1.0.29.tgz#a6a661d43facb934fba4974c01466d64e41e5b92" + integrity sha512-cWVP9/HRO3BmewK4TGNxmIc4kEqemxW/FLiCTIf9vi42cSx9VIne0BCYbV6iLLYVeAFJlXcdTUdHhbp8PNma1Q== dependencies: "@blueprintjs/datetime" "3.23.6" copy-to-clipboard "^3.3.1" From 1b9373e3b03ed260e89f0e53b2e6bc4fd5e9e9b5 Mon Sep 17 00:00:00 2001 From: Parthvi <80334441+Parthvi12@users.noreply.github.com> Date: Wed, 19 Oct 2022 19:43:51 +0530 Subject: [PATCH 36/54] test: Add cypress tests for template phase 2 (#17036) Co-authored-by: Parthvi Goswami --- .../Git/GitImport/GitImport_spec.js | 19 ++-- .../Fork_Template_To_App_spec.js | 20 ---- .../ForkTemplateToGitConnectedApp.js | 97 +++++++++++++++++++ .../Fork_Template_Existing_app_spec.js | 55 +++++++++++ .../Templates/Fork_Template_To_App_spec.js | 78 +++++++++++++++ .../Fork_Template_spec.js | 0 .../ServerSideTests/GenerateCRUD/S3_Spec.js | 37 +++++++ .../cypress/locators/TemplatesLocators.json | 6 +- app/client/cypress/support/commands.js | 17 ++++ 9 files changed, 301 insertions(+), 28 deletions(-) delete mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Fork_Template_To_App_spec.js create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/ForkTemplateToGitConnectedApp.js create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_Existing_app_spec.js create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_To_App_spec.js rename app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/{OtherUIFeatures => Templates}/Fork_Template_spec.js (100%) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitImport/GitImport_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitImport/GitImport_spec.js index da8e65cdb4..0c84b377de 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitImport/GitImport_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitImport/GitImport_spec.js @@ -20,7 +20,7 @@ describe("Git import flow", function() { cy.CreateAppForWorkspace(newWorkspaceName, newWorkspaceName); }); }); - it("Import an app from JSON with Postgres, MySQL, Mongo db", () => { + it("1. Import an app from JSON with Postgres, MySQL, Mongo db", () => { cy.NavigateToHome(); cy.get(homePage.optionsIcon) .first() @@ -69,7 +69,7 @@ describe("Git import flow", function() { }); }); }); - it("Import an app from Git and reconnect Postgres, MySQL and Mongo db ", () => { + it("2. Import an app from Git and reconnect Postgres, MySQL and Mongo db ", () => { cy.NavigateToHome(); cy.createWorkspace(); cy.wait("@createWorkspace").then((interception) => { @@ -114,8 +114,12 @@ describe("Git import flow", function() { "contain", "Application imported successfully", ); */ + cy.wait("@gitStatus").then((interception) => { + cy.log(interception.response.body.data); + cy.wait(1000); + }); }); - it("Verfiy imported app should have all the data binding visible in deploy and edit mode", () => { + it("3. Verfiy imported app should have all the data binding visible in view and edit mode", () => { // verify postgres data binded to table cy.get(".tbody") .first() @@ -129,7 +133,7 @@ describe("Git import flow", function() { // verify js object binded to input widget cy.xpath("//input[@value='Success']").should("be.visible"); }); - it("Create a new branch, clone page and validate data on that branch in deploy and edit mode", () => { + it("4. Create a new branch, clone page and validate data on that branch in view and edit mode", () => { cy.createGitBranch(newBranch); cy.get(".tbody") .first() @@ -169,7 +173,8 @@ describe("Git import flow", function() { cy.get(homePage.publishButton).click(); cy.get(gitSyncLocators.commitCommentInput).type("Initial Commit"); cy.get(gitSyncLocators.commitButton).click(); - cy.wait(8000); + cy.intercept("POST", "api/v1/git/commit/app/*").as("commit"); + cy.wait(10000); cy.get(gitSyncLocators.closeGitSyncModal).click(); cy.wait(2000); cy.merge(mainBranch); @@ -197,7 +202,7 @@ describe("Git import flow", function() { cy.get(commonlocators.backToEditor).click(); cy.wait(2000); }); - it("Switch to master and verify data in edit and deploy mode", () => { + it("5. Switch to master and verify data in edit and view mode", () => { cy.switchGitBranch("master"); cy.wait(2000); // validate data binding in edit and deploy mode @@ -219,7 +224,7 @@ describe("Git import flow", function() { cy.get(commonlocators.backToEditor).click(); cy.wait(2000); }); - it("Add widget to master, merge then checkout to child branch and verify data", () => { + it("6. Add widget to master, merge then checkout to child branch and verify data", () => { cy.get(explorer.widgetSwitchId).click(); cy.wait(2000); // wait for transition cy.dragAndDropToCanvas("buttonwidget", { x: 300, y: 600 }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Fork_Template_To_App_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Fork_Template_To_App_spec.js deleted file mode 100644 index 47214db82f..0000000000 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Fork_Template_To_App_spec.js +++ /dev/null @@ -1,20 +0,0 @@ -import widgetLocators from "../../../../locators/Widgets.json"; - -describe("Fork a template to the current app", () => { - it("Fork a template to the current app", () => { - cy.intercept("GET", "/api/v1/users/features", { - fixture: "featureFlags.json", - }).as("featureFlags"); - cy.reload(); - cy.get("[data-cy=start-from-template]").click(); - - cy.xpath( - "//div[text()='Customer Support Dashboard']/following-sibling::div//button[contains(@class, 'fork-button')]", - ).click(); - - cy.get(widgetLocators.toastAction).should( - "contain", - "template added successfully", - ); - }); -}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/ForkTemplateToGitConnectedApp.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/ForkTemplateToGitConnectedApp.js new file mode 100644 index 0000000000..59718911ec --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/ForkTemplateToGitConnectedApp.js @@ -0,0 +1,97 @@ +import template from "../../../../locators/TemplatesLocators.json"; +import gitSyncLocators from "../../../../locators/gitSyncLocators"; +import widgetLocators from "../../../../locators/Widgets.json"; +let repoName; +let appId; +const branchName = "test/template"; +const mainBranch = "master"; +const jsObject = "Utils"; +const homePage = require("../../../../locators/HomePage"); + +describe("Fork a template to the current app", () => { + before(() => { + cy.NavigateToHome(); + cy.generateUUID().then((id) => { + appId = id; + cy.CreateAppInFirstListedWorkspace(id); + localStorage.setItem("AppName", appId); + }); + cy.generateUUID().then((uid) => { + repoName = uid; + + cy.createTestGithubRepo(repoName); + cy.connectToGitRepo(repoName); + }); + }); + + it("1.Bug #17002 Forking a template into an existing app which is connected to git makes the application go into a bad state ", function() { + cy.get(template.startFromTemplateCard).click(); + cy.wait("@fetchTemplate").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.wait(1000); + cy.get(template.templateDialogBox).should("be.visible"); + cy.xpath( + "//div[text()='Customer Support Dashboard']/following-sibling::div//button[contains(@class, 'fork-button')]", + ).click(); + cy.wait("@getTemplatePages").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.get(widgetLocators.toastAction).should( + "contain", + "template added successfully", + ); + cy.commitAndPush(); + }); + it("2. Bug #17262 On forking template to a child branch of git connected app is throwing Page not found error ", function() { + cy.createGitBranch(branchName); + cy.CreatePage(); + cy.get(template.startFromTemplateCard).click(); + + cy.wait(1000); + cy.get(template.templateDialogBox).should("be.visible"); + cy.xpath("//div[text()='Customer Support Dashboard']").click(); + + cy.xpath(template.selectAllPages) + .next() + .click(); + cy.wait(1000); + cy.xpath("//span[text()='SEARCH']") + .parent() + .next() + .click(); + // [Bug]: On forking selected pages from a template, resource not found error is shown #17270 + cy.get(template.templateViewForkButton).click(); + + cy.wait(3000); + cy.get(widgetLocators.toastAction).should( + "contain", + "template added successfully", + ); + // [Bug]: On forking a template the JS Objects are not cloned #17425 + cy.CheckAndUnfoldEntityItem("Queries/JS"); + cy.get(`.t--entity-name:contains(${jsObject})`).should("have.length", 1); + cy.NavigateToHome(); + cy.get(homePage.searchInput) + .clear() + .type(appId); + cy.wait(2000); + cy.get(homePage.applicationCard) + .first() + .trigger("mouseover"); + cy.get(homePage.appEditIcon) + .first() + .click({ force: true }); + cy.wait(5000); + cy.switchGitBranch(branchName); + cy.get(homePage.publishButton).click(); + cy.get(gitSyncLocators.commitCommentInput).type("Initial Commit"); + cy.get(gitSyncLocators.commitButton).click(); + cy.wait(10000); + cy.get(gitSyncLocators.closeGitSyncModal).click(); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_Existing_app_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_Existing_app_spec.js new file mode 100644 index 0000000000..8a260ade15 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_Existing_app_spec.js @@ -0,0 +1,55 @@ +import widgetLocators from "../../../../locators/Widgets.json"; +import template from "../../../../locators/TemplatesLocators.json"; +const publish = require("../../../../locators/publishWidgetspage.json"); + +describe("Fork a template to the current app from new page popover", () => { + it("1. Fork template from page section", () => { + cy.AddPageFromTemplate(); + cy.wait(1000); + cy.get(template.templateDialogBox).should("be.visible"); + cy.wait(4000); + cy.xpath( + "//div[text()='Customer Support Dashboard']/following-sibling::div//button[contains(@class, 'fork-button')]", + ).click(); + cy.wait(1000); + cy.wait("@getTemplatePages").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.get(widgetLocators.toastAction).should( + "contain", + "template added successfully", + ); + }); + + it("2. Add selected page of template from page section", () => { + cy.AddPageFromTemplate(); + cy.wait(1000); + cy.get(template.templateDialogBox).should("be.visible"); + cy.wait(4000); + cy.xpath("//div[text()='Customer Support Dashboard']").click(); + cy.wait("@getTemplatePages").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.xpath(template.selectAllPages) + .next() + .click(); + cy.xpath("//span[text()='DASHBOARD']") + .parent() + .next() + .click(); + cy.get(template.templateViewForkButton).click(); + cy.wait("@fetchTemplate").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.get(widgetLocators.toastAction).should( + "contain", + "template added successfully", + ); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_To_App_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_To_App_spec.js new file mode 100644 index 0000000000..06f565e627 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_To_App_spec.js @@ -0,0 +1,78 @@ +import widgetLocators from "../../../../locators/Widgets.json"; +import template from "../../../../locators/TemplatesLocators.json"; +const publish = require("../../../../locators/publishWidgetspage.json"); + +describe("Fork a template to the current app", () => { + it("1. Fork a template to the current app", () => { + cy.get(template.startFromTemplateCard).click(); + cy.wait("@fetchTemplate").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.wait(1000); + cy.get(template.templateDialogBox).should("be.visible"); + cy.xpath( + "//div[text()='Customer Support Dashboard']/following-sibling::div//button[contains(@class, 'fork-button')]", + ).click(); + cy.wait("@getTemplatePages").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.get(widgetLocators.toastAction).should( + "contain", + "template added successfully", + ); + // [Bug]: Getting 'Resource not found' error on deploying template #17477 + cy.PublishtheApp(); + cy.get(".t--page-switch-tab") + .contains("Dashboard") + .click({ force: true }); + cy.wait(4000); + cy.get(publish.backToEditor).click(); + cy.wait(2000); + }); + + it("2. Add selected pages from template to an app", () => { + cy.CheckAndUnfoldEntityItem("Pages"); + cy.get(`.t--entity-name:contains(Page1)`) + .trigger("mouseover") + .click({ force: true }); + cy.wait(1000); + cy.get(template.startFromTemplateCard).click(); + cy.wait("@fetchTemplate").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.wait(1000); + cy.get(template.templateDialogBox).should("be.visible"); + cy.xpath("//div[text()='Customer Support Dashboard']").click(); + cy.wait("@getTemplatePages").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.xpath(template.selectAllPages) + .next() + .click(); + cy.wait(1000); + cy.xpath("//span[text()='SEARCH']") + .parent() + .next() + .click(); + // [Bug]: On forking selected pages from a template, resource not found error is shown #17270 + cy.get(template.templateViewForkButton).click(); + cy.wait("@fetchTemplate").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.wait(3000); + cy.get(widgetLocators.toastAction).should( + "contain", + "template added successfully", + ); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Fork_Template_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_spec.js similarity index 100% rename from app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Fork_Template_spec.js rename to app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_spec.js diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/GenerateCRUD/S3_Spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/GenerateCRUD/S3_Spec.js index 1cab539f02..de296f836d 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/GenerateCRUD/S3_Spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/GenerateCRUD/S3_Spec.js @@ -245,4 +245,41 @@ describe("Generate New CRUD Page Inside from entity explorer", function() { //cy.isNotInViewport("//div[text()='haiiii hello']") //cy.isNotInViewport("//div[text()='No data to display']") }); + + it("4. Generate CRUD page from the page menu", function() { + cy.GenerateCRUD(); + cy.NavigateToDSGeneratePage(datasourceName); + // fetch bucket + cy.wait("@getDatasourceStructure").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + + cy.get(generatePage.selectTableDropdown).click(); + cy.get(generatePage.dropdownOption) + .contains("assets-test.appsmith.com") + .scrollIntoView() + .should("be.visible") + .click(); + cy.get(generatePage.generatePageFormSubmitBtn).click(); + + cy.wait("@post_replaceLayoutCRUD").should( + "have.nested.property", + "response.body.responseMeta.status", + 201, + ); + cy.wait("@get_Actions").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.wait("@post_Execute").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + + cy.get("span:contains('GOT IT')").click(); + }); }); diff --git a/app/client/cypress/locators/TemplatesLocators.json b/app/client/cypress/locators/TemplatesLocators.json index 7e76bc99db..fa8eb68e2b 100644 --- a/app/client/cypress/locators/TemplatesLocators.json +++ b/app/client/cypress/locators/TemplatesLocators.json @@ -3,5 +3,9 @@ "templateForkButton": ".t--fork-template", "dialogForkButton": ".t--fork-template-button", "templateCard": "[data-cy='template-card']", - "templateViewForkButton": "[data-cy='template-fork-button']" + "templateViewForkButton": "[data-cy='template-fork-button']", + "startFromTemplateCard": "[data-cy=start-from-template]", + "templateDialogBox": "[data-testid='t--dialog-component']", + "selectAllPages": "//span[text()='Select all']" + } \ No newline at end of file diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index a07f069119..b5c6990b72 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -1005,6 +1005,9 @@ Cypress.Commands.add("startServerAndRoutes", () => { cy.intercept("PUT", "/api/v1/layouts/refactor").as("updateWidgetName"); cy.intercept("GET", "/api/v1/workspaces/*/members").as("getMembers"); cy.intercept("POST", "/api/v1/datasources/mocks").as("getMockDb"); + cy.intercept("GET", "/api/v1/app-templates").as("fetchTemplate"); + cy.intercept("POST", "/api/v1/app-templates/*").as("importTemplate"); + cy.intercept("GET", "/api/v1/app-templates/*").as("getTemplatePages"); }); Cypress.Commands.add("startErrorRoutes", () => { @@ -1909,3 +1912,17 @@ Cypress.Commands.add("CreatePage", () => { .click({ force: true }); cy.get("[data-cy='add-page']").click(); }); + +Cypress.Commands.add("GenerateCRUD", () => { + cy.get(pages.AddPage) + .first() + .click({ force: true }); + cy.get("[data-cy='generate-page']").click(); +}); + +Cypress.Commands.add("AddPageFromTemplate", () => { + cy.get(pages.AddPage) + .first() + .click({ force: true }); + cy.get("[data-cy='add-page-from-template']").click(); +}); From 694a7d342e7b28e1709165a6d2ed8bfca65ead49 Mon Sep 17 00:00:00 2001 From: Aishwarya-U-R <91450662+Aishwarya-U-R@users.noreply.github.com> Date: Thu, 20 Oct 2022 11:56:52 +0530 Subject: [PATCH 37/54] test: Script updates for flaky tests (#17603) * Skipping Bug16702 spec * Bug 16702 fix * timeout spec index update * Bug 16702 spec fix * Bug16702 fix * BUg16702 spec fix * Bug 16702 revert * fix template spec Co-authored-by: Parthvi Goswami --- .../ClientSideTests/BugTests/Bug16702_Spec.ts | 18 ++++++++++++------ .../Templates/ForkTemplateToGitConnectedApp.js | 3 ++- .../Fork_Template_Existing_app_spec.js | 4 ++-- .../Templates/Fork_Template_To_App_spec.js | 4 ++-- .../{timeout_spec.ts => SetTimeout_spec.ts} | 16 ++++++++++------ 5 files changed, 28 insertions(+), 17 deletions(-) rename app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/{timeout_spec.ts => SetTimeout_spec.ts} (92%) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug16702_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug16702_Spec.ts index 9e3d87615c..038551c5a0 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug16702_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug16702_Spec.ts @@ -9,7 +9,11 @@ const locator = ObjectsRegistry.CommonLocators, const GRAPHQL_LIMIT_QUERY = ` query { - launchesPast(limit: "__limit__", offset: "__offset__") { + launchesPast(limit: + "__limit__" + ,offset: + "__offset__" + ) { mission_name rocket { rocket_name @@ -44,10 +48,12 @@ describe("Binding Expressions should not be truncated in Url and path extraction // expect(Cypress.dom.isDetached($el)).to.false; // }) //.trigger("mouseover") - .click() + .dblclick() .type("{{JSObject1."); - agHelper.GetNClickByContains(locator._hints, "offsetValue"); - agHelper.Sleep(200); + agHelper.GetNAssertElementText(locator._hints, "offsetValue", "have.text", 1); + agHelper.Sleep(); + agHelper.TypeText(locator._codeMirrorTextArea, "offsetValue", 1); + agHelper.Sleep(2000); /* Start: Block of code to remove error of detached node of codemirror for cypress reference */ @@ -58,10 +64,10 @@ describe("Binding Expressions should not be truncated in Url and path extraction cy.get(".t--graphql-query-editor pre.CodeMirror-line span") .contains("__limit__") //.trigger("mouseover") - .click() + .dblclick() .type("{{JSObject1."); agHelper.GetNClickByContains(locator._hints, "limitValue"); - agHelper.Sleep(); + agHelper.Sleep(2000); //Commenting this since - many runs means - API response is 'You are doing too many launches' // apiPage.RunAPI(false, 20, { // expectedPath: "response.body.data.body.data.launchesPast[0].mission_name", diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/ForkTemplateToGitConnectedApp.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/ForkTemplateToGitConnectedApp.js index 59718911ec..35b49ba01e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/ForkTemplateToGitConnectedApp.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/ForkTemplateToGitConnectedApp.js @@ -25,6 +25,7 @@ describe("Fork a template to the current app", () => { }); it("1.Bug #17002 Forking a template into an existing app which is connected to git makes the application go into a bad state ", function() { + cy.wait(5000); cy.get(template.startFromTemplateCard).click(); cy.wait("@fetchTemplate").should( "have.nested.property", @@ -52,7 +53,7 @@ describe("Fork a template to the current app", () => { cy.CreatePage(); cy.get(template.startFromTemplateCard).click(); - cy.wait(1000); + cy.wait(5000); cy.get(template.templateDialogBox).should("be.visible"); cy.xpath("//div[text()='Customer Support Dashboard']").click(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_Existing_app_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_Existing_app_spec.js index 8a260ade15..075e30866b 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_Existing_app_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_Existing_app_spec.js @@ -5,7 +5,7 @@ const publish = require("../../../../locators/publishWidgetspage.json"); describe("Fork a template to the current app from new page popover", () => { it("1. Fork template from page section", () => { cy.AddPageFromTemplate(); - cy.wait(1000); + cy.wait(3000); cy.get(template.templateDialogBox).should("be.visible"); cy.wait(4000); cy.xpath( @@ -25,7 +25,7 @@ describe("Fork a template to the current app from new page popover", () => { it("2. Add selected page of template from page section", () => { cy.AddPageFromTemplate(); - cy.wait(1000); + cy.wait(3000); cy.get(template.templateDialogBox).should("be.visible"); cy.wait(4000); cy.xpath("//div[text()='Customer Support Dashboard']").click(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_To_App_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_To_App_spec.js index 06f565e627..5b2d20d204 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_To_App_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_To_App_spec.js @@ -10,7 +10,7 @@ describe("Fork a template to the current app", () => { "response.body.responseMeta.status", 200, ); - cy.wait(1000); + cy.wait(5000); cy.get(template.templateDialogBox).should("be.visible"); cy.xpath( "//div[text()='Customer Support Dashboard']/following-sibling::div//button[contains(@class, 'fork-button')]", @@ -46,7 +46,7 @@ describe("Fork a template to the current app", () => { "response.body.responseMeta.status", 200, ); - cy.wait(1000); + cy.wait(5000); cy.get(template.templateDialogBox).should("be.visible"); cy.xpath("//div[text()='Customer Support Dashboard']").click(); cy.wait("@getTemplatePages").should( diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/timeout_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/SetTimeout_spec.ts similarity index 92% rename from app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/timeout_spec.ts rename to app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/SetTimeout_spec.ts index f6bd939da6..002a4a22d8 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/timeout_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/SetTimeout_spec.ts @@ -6,7 +6,7 @@ const apiPage = ObjectsRegistry.ApiPage; const deployMode = ObjectsRegistry.DeployMode; describe("Tests setTimeout API", function() { - it("Executes showAlert after 3 seconds and uses default value", () => { + it("1. Executes showAlert after 3 seconds and uses default value", () => { jsEditor.CreateJSObject( `export default { myVar1: [], @@ -30,7 +30,8 @@ describe("Tests setTimeout API", function() { agHelper.Sleep(3000); agHelper.AssertContains("Hello world - default", "exist"); }); - it("Executes all three alerts in parallel after 3 seconds", () => { + + it("2. Executes all three alerts in parallel after 3 seconds", () => { jsEditor.CreateJSObject( `export default { myVar1: [], @@ -62,7 +63,8 @@ describe("Tests setTimeout API", function() { agHelper.AssertContains("Hello world - 2", "exist"); agHelper.AssertContains("Hello world - 3", "exist"); }); - it("Resolves promise after 3 seconds and shows alert", () => { + + it("3. Resolves promise after 3 seconds and shows alert", () => { jsEditor.CreateJSObject( `export default { myVar1: [], @@ -121,7 +123,7 @@ describe("Tests setTimeout API", function() { agHelper.GetNAssertContains(locators._debuggerLogMessage, "Working!"); }); - it("Resolves promise after 3 seconds and shows alert", () => { + it("4. Resolves promise after 3 seconds and shows alert", () => { jsEditor.CreateJSObject( `export default { myVar1: [], @@ -145,7 +147,8 @@ describe("Tests setTimeout API", function() { agHelper.Sleep(3000); agHelper.AssertContains("resolved"); }); - it("Access to args passed into success/error callback functions in API.run when using setTimeout", () => { + + it("5. Access to args passed into success/error callback functions in API.run when using setTimeout", () => { apiPage.CreateAndFillApi("https://mock-api.appsmith.com/users"); jsEditor.CreateJSObject( `export default { @@ -187,7 +190,8 @@ describe("Tests setTimeout API", function() { agHelper.Sleep(3000); agHelper.AssertContains("Barty Crouch"); }); - it("Verifies whether setTimeout executes on page load", () => { + + it("6. Verifies whether setTimeout executes on page load", () => { apiPage.CreateAndFillApi("https://mock-api.appsmith.com/users"); jsEditor.CreateJSObject( `export default { From daa9886a690a2911ca86335da2e2490d0f842e1d Mon Sep 17 00:00:00 2001 From: f0c1s Date: Fri, 21 Oct 2022 10:42:59 +0530 Subject: [PATCH 38/54] fix: upgrade page for audit logs with correct data (#17697) ## Description This PR fixes: - content that was repeated - has new images - removes comments - brings text to messages file ## Compare | OLD | NEW | | - | - | | ![security-and-compliance](https://user-images.githubusercontent.com/1573771/196636819-325d2a42-e992-43a3-91e6-9a1580ddd003.svg) | ![security-and-compliance](https://user-images.githubusercontent.com/1573771/196636392-f9778aef-4f8b-41a5-8408-4e0f636a1437.svg) | | ![debugging](https://user-images.githubusercontent.com/1573771/196636997-b19c1886-7d7c-460f-a0ea-69ae0030bbe8.svg) | ![debugging](https://user-images.githubusercontent.com/1573771/196637039-389ceb6d-f408-402d-8c0e-3d431fd0dda6.svg) | | ![incident-management](https://user-images.githubusercontent.com/1573771/196637122-743c03d3-7bd3-4f9b-8a3c-eb0e74735cde.svg) | ![incident-management](https://user-images.githubusercontent.com/1573771/196637182-281a318b-a609-4a74-b5d9-6dabc5d08bfd.svg) | --- .../AuditLogs/Audit_logs_spec.js | 4 +- .../svg/upgrade/audit-logs/debugging.svg | 209 +++----- .../audit-logs/incident-management.svg | 454 +++++------------- .../audit-logs/security-and-compliance.svg | 229 +++------ app/client/src/ce/constants/messages.ts | 23 +- .../ce/pages/Upgrade/AuditLogsUpgradePage.tsx | 42 +- app/client/src/ce/pages/Upgrade/Footer.tsx | 10 +- .../src/ce/pages/Upgrade/UpgradePage.tsx | 1 - 8 files changed, 310 insertions(+), 662 deletions(-) diff --git a/app/client/cypress/integration/Smoke_TestSuite_Fat/ClientSideTests/AuditLogs/Audit_logs_spec.js b/app/client/cypress/integration/Smoke_TestSuite_Fat/ClientSideTests/AuditLogs/Audit_logs_spec.js index 917155ce42..b13f673e60 100644 --- a/app/client/cypress/integration/Smoke_TestSuite_Fat/ClientSideTests/AuditLogs/Audit_logs_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite_Fat/ClientSideTests/AuditLogs/Audit_logs_spec.js @@ -44,12 +44,12 @@ describe("Audit logs", () => { cy.get(locators.Heading) .should("be.visible") - .should("have.text", "Audit Logs"); + .should("have.text", "Introducing Audit Logs"); cy.get(locators.SubHeadings) .should("be.visible") .should( "have.text", - "Your workspace audit log gives Workspace owners access to detailed information about security and safety-related activity.", + "See a timestamped trail of events in your workspace. Filter by type of event, user, resource ID, and time. Drill down into each event to investigate further.", ); cy.get(locators.Left) diff --git a/app/client/src/assets/svg/upgrade/audit-logs/debugging.svg b/app/client/src/assets/svg/upgrade/audit-logs/debugging.svg index ad5ff54b77..ec913a76db 100644 --- a/app/client/src/assets/svg/upgrade/audit-logs/debugging.svg +++ b/app/client/src/assets/svg/upgrade/audit-logs/debugging.svg @@ -1,139 +1,92 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + + + - - - + + + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + @@ -143,17 +96,7 @@ - - - - - - - - - - - + diff --git a/app/client/src/assets/svg/upgrade/audit-logs/incident-management.svg b/app/client/src/assets/svg/upgrade/audit-logs/incident-management.svg index 404eb9dd75..074acf6dc9 100644 --- a/app/client/src/assets/svg/upgrade/audit-logs/incident-management.svg +++ b/app/client/src/assets/svg/upgrade/audit-logs/incident-management.svg @@ -1,334 +1,118 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -338,27 +122,7 @@ - - - - - - - - - - - - - - - - - - - - - + @@ -372,10 +136,16 @@ - + - + + + + + + + diff --git a/app/client/src/assets/svg/upgrade/audit-logs/security-and-compliance.svg b/app/client/src/assets/svg/upgrade/audit-logs/security-and-compliance.svg index 69ece50ed8..559fd08b55 100644 --- a/app/client/src/assets/svg/upgrade/audit-logs/security-and-compliance.svg +++ b/app/client/src/assets/svg/upgrade/audit-logs/security-and-compliance.svg @@ -1,149 +1,78 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - + @@ -153,27 +82,7 @@ - - - - - - - - - - - - - - - - - - - - - + @@ -184,10 +93,10 @@ - + - + diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index 10b5b5a063..a8c428bd81 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -1022,8 +1022,29 @@ export const DISCONNECT_AUTH_ERROR = () => export const MANDATORY_FIELDS_ERROR = () => "Mandatory fields cannot be empty"; // Audit logs begin -export const AUDIT_LOGS = () => "Audit logs"; +export const AUDIT_LOGS = () => "Audit Logs"; export const TRY_AGAIN_WITH_YOUR_FILTER = () => "Try again with your filter"; + +// Audit logs Upgrade page begin +export const INTRODUCING = (featureName: string) => + `Introducing ${featureName}`; +export const AUDIT_LOGS_UPGRADE_PAGE_SUB_HEADING = () => + "See a timestamped trail of events in your workspace. Filter by type of event, user, resource ID, and time. Drill down into each event to investigate further."; +export const SECURITY_AND_COMPLIANCE = () => "Security & Compliance"; +export const SECURITY_AND_COMPLIANCE_DETAIL1 = () => + "Proactively derisk misconfigured permissions, roll back changes from a critical security event, and keep checks against your compliance policies."; +export const SECURITY_AND_COMPLIANCE_DETAIL2 = () => + "Exports to popular compliance tools coming soon"; +export const DEBUGGING = () => "Debugging"; +export const DEBUGGING_DETAIL1 = () => + "Debug with a timeline of events filtered by user and resource ID, correlate them with end-user and app developer actions, and investigate back to the last known good state of your app."; +export const INCIDENT_MANAGEMENT = () => "Incident Management"; +export const INCIDENT_MANAGEMENT_DETAIL1 = () => + "Go back in time from an incident to see who did what, correlate events with breaking changes, and run RCAs to remediate incidents for now and the future."; +export const AVAILABLE_ON_BUSINESS = () => "Available on a business plan only"; +export const EXCLUSIVE_TO_BUSINESS = (featureName: string) => + `The ${featureName} feature is exclusive to workspaces on the Enterprise Plan`; +// Audit logs Upgrade page end // Audit logs end // diff --git a/app/client/src/ce/pages/Upgrade/AuditLogsUpgradePage.tsx b/app/client/src/ce/pages/Upgrade/AuditLogsUpgradePage.tsx index 4dc89ca9b3..c9a0b277c9 100644 --- a/app/client/src/ce/pages/Upgrade/AuditLogsUpgradePage.tsx +++ b/app/client/src/ce/pages/Upgrade/AuditLogsUpgradePage.tsx @@ -6,39 +6,48 @@ import IncidentManagementImage from "assets/svg/upgrade/audit-logs/incident-mana import SecurityAndComplianceImage from "assets/svg/upgrade/audit-logs/security-and-compliance.svg"; import AnalyticsUtil from "../../../utils/AnalyticsUtil"; import { getAppsmithConfigs } from "../../configs"; -import { createMessage, UPGRADE } from "../../constants/messages"; +import { createMessage } from "design-system/build/constants/messages"; +import { + AUDIT_LOGS, + AUDIT_LOGS_UPGRADE_PAGE_SUB_HEADING, + DEBUGGING, + DEBUGGING_DETAIL1, + EXCLUSIVE_TO_BUSINESS, + INCIDENT_MANAGEMENT, + INCIDENT_MANAGEMENT_DETAIL1, + INTRODUCING, + SECURITY_AND_COMPLIANCE, + SECURITY_AND_COMPLIANCE_DETAIL1, + SECURITY_AND_COMPLIANCE_DETAIL2, + UPGRADE, +} from "../../constants/messages"; const { intercomAppID } = getAppsmithConfigs(); export function AuditLogsUpgradePage() { const header: Header = { - heading: "Audit Logs", - subHeadings: [ - "Your workspace audit log gives Workspace owners access to detailed information about security and safety-related activity.", - ], + heading: createMessage(INTRODUCING, createMessage(AUDIT_LOGS)), + subHeadings: [createMessage(AUDIT_LOGS_UPGRADE_PAGE_SUB_HEADING)], }; const carousel = { triggers: [ { icon: "lock-2-line", - heading: "Security & Compliance", + heading: createMessage(SECURITY_AND_COMPLIANCE), details: [ - "Debug with a timeline of events filtered by user and resource ID, correlate them with end-user and app developer actions, and investigate back to the last known good state of your app.", + createMessage(SECURITY_AND_COMPLIANCE_DETAIL1), + createMessage(SECURITY_AND_COMPLIANCE_DETAIL2), ], }, { icon: "search-eye-line", - heading: "Debugging", - details: [ - "Debug with a timeline of events filtered by user and resource ID, correlate them with end-user and app developer actions, and investigate back to the last known good state of your app.", - ], + heading: createMessage(DEBUGGING), + details: [createMessage(DEBUGGING_DETAIL1)], }, { icon: "alert-line", - heading: "Incident management", - details: [ - "Debug with a timeline of events filtered by user and resource ID, correlate them with end-user and app developer actions, and investigate back to the last known good state of your app.", - ], + heading: createMessage(INCIDENT_MANAGEMENT), + details: [createMessage(INCIDENT_MANAGEMENT_DETAIL1)], }, ], targets: [ @@ -65,8 +74,7 @@ export function AuditLogsUpgradePage() { window.Intercom("showNewMessage", createMessage(UPGRADE)); } }, - message: - "The audit log feature is exclusive to workspaces on the Enterprise Plan", + message: createMessage(EXCLUSIVE_TO_BUSINESS, ["audit logs"]), }; const props = { header, carousel, footer }; return ; diff --git a/app/client/src/ce/pages/Upgrade/Footer.tsx b/app/client/src/ce/pages/Upgrade/Footer.tsx index 47a50842c1..c52d58a05e 100644 --- a/app/client/src/ce/pages/Upgrade/Footer.tsx +++ b/app/client/src/ce/pages/Upgrade/Footer.tsx @@ -3,6 +3,8 @@ import React from "react"; import { Button, Size, Text, TextType } from "design-system"; import { Variant } from "design-system/build/constants/variants"; import { FooterProps } from "./types"; +import { createMessage } from "design-system/build/constants/messages"; +import { AVAILABLE_ON_BUSINESS, UPGRADE } from "../../constants/messages"; const FooterContainer = styled.div` position: fixed; @@ -24,10 +26,6 @@ const FooterContainer = styled.div` flex-grow: 9; display: flex; flex-direction: column; - - //& .text-container { - // width: 288px; - //} } & .right { @@ -44,7 +42,7 @@ export function FooterComponent(props: FooterProps) { >
- Available on a business plan only + {createMessage(AVAILABLE_ON_BUSINESS)}
{message} @@ -54,7 +52,7 @@ export function FooterComponent(props: FooterProps) {