Merge branch 'release' into feat/13412-improve-import-app-flow-ui

This commit is contained in:
haojin111 2022-05-23 13:31:28 +08:00
commit 7e5332ceb7
36 changed files with 1361 additions and 770 deletions

2
.github/config.json vendored

File diff suppressed because one or more lines are too long

View File

@ -1,209 +1,256 @@
{
"dsl": {
"widgetName": "MainContainer",
"backgroundColor": "none",
"rightColumn": 1270,
"snapColumns": 64,
"detachFromLayout": true,
"widgetId": "0",
"topRow": 0,
"bottomRow": 820,
"containerStyle": "none",
"snapRows": 125,
"parentRowSpace": 1,
"type": "CANVAS_WIDGET",
"canExtend": true,
"version": 23,
"minHeight": 830,
"parentColumnSpace": 1,
"dynamicTriggerPathList": [],
"dynamicBindingPathList": [],
"leftColumn": 0,
"children": [
{
"widgetName": "Chart1",
"rightColumn": 26,
"allowScroll": false,
"widgetId": "ypstklohw5",
"topRow": 4,
"bottomRow": 36,
"parentRowSpace": 10,
"isVisible": true,
"type": "CHART_WIDGET",
"version": 1,
"parentId": "0",
"isLoading": false,
"chartData": {
"3jzahcrorq": {
"seriesName": "Sales",
"data": [
{
"x": "Mon",
"y": 10000
},
{
"x": "Tue",
"y": 12000
},
{
"x": "Wed",
"y": 32000
},
{
"x": "Thu",
"y": 28000
},
{
"x": "Fri",
"y": 14000
},
{
"x": "Sat",
"y": 19000
},
{
"x": "Sun",
"y": 36000
}
]
}
},
"yAxisName": "Total Order Revenue $",
"parentColumnSpace": 19.65625,
"chartName": "Last week's revenue",
"leftColumn": 2,
"xAxisName": "Last Week",
"customFusionChartConfig": {
"type": "column2d",
"dataSource": {
"chart": {
"caption": "Last week's revenue",
"xAxisName": "Last Week",
"yAxisName": "Total Order Revenue $",
"theme": "fusion"
"dsl": {
"widgetName": "MainContainer",
"backgroundColor": "none",
"rightColumn": 1270,
"snapColumns": 64,
"detachFromLayout": true,
"widgetId": "0",
"topRow": 0,
"bottomRow": 1050,
"containerStyle": "none",
"snapRows": 125,
"parentRowSpace": 1,
"type": "CANVAS_WIDGET",
"canExtend": true,
"version": 58,
"minHeight": 830,
"parentColumnSpace": 1,
"dynamicTriggerPathList": [],
"dynamicBindingPathList": [],
"leftColumn": 0,
"children": [
{
"labelTextSize": "0.875rem",
"boxShadow": "none",
"widgetName": "Chart1",
"rightColumn": 26,
"widgetId": "ypstklohw5",
"topRow": 4,
"bottomRow": 36,
"parentRowSpace": 10,
"isVisible": true,
"type": "CHART_WIDGET",
"version": 1,
"parentId": "0",
"isLoading": false,
"chartData": {
"3jzahcrorq": {
"seriesName": "Sales",
"data": [
{
"x": "Mon",
"y": 10000
},
"data": [
{
"label": "Mon",
"value": 10000
},
{
"label": "Tue",
"value": 12000
},
{
"label": "Wed",
"value": 32000
},
{
"label": "Thu",
"value": 28000
},
{
"label": "Fri",
"value": 14000
},
{
"label": "Sat",
"value": 19000
},
{
"label": "Sun",
"value": 36000
}
],
"trendlines": [
{
"line": [
{
"startvalue": "38000",
"valueOnRight": "1",
"displayvalue": "Weekly Target"
}
]
}
]
}
},
"chartType": "LINE_CHART"
{
"x": "Tue",
"y": 12000
},
{
"x": "Wed",
"y": 32000
},
{
"x": "Thu",
"y": 28000
},
{
"x": "Fri",
"y": 14000
},
{
"x": "Sat",
"y": 19000
},
{
"x": "Sun",
"y": 36000
}
]
}
},
{
"backgroundColor": "#FFFFFF",
"widgetName": "Container1",
"rightColumn": 62,
"widgetId": "6y21iarlp4",
"containerStyle": "card",
"topRow": 11,
"bottomRow": 51,
"parentRowSpace": 10,
"isVisible": true,
"type": "CONTAINER_WIDGET",
"version": 1,
"parentId": "0",
"isLoading": false,
"parentColumnSpace": 19.65625,
"leftColumn": 30,
"children": [
{
"widgetName": "Canvas1",
"rightColumn": 629,
"detachFromLayout": true,
"widgetId": "jqasr1uss5",
"containerStyle": "none",
"topRow": 0,
"bottomRow": 400,
"parentRowSpace": 1,
"isVisible": true,
"canExtend": false,
"type": "CANVAS_WIDGET",
"version": 1,
"parentId": "6y21iarlp4",
"minHeight": 400,
"isLoading": false,
"parentColumnSpace": 1,
"leftColumn": 0,
"children": []
}
]
"yAxisName": "Total Order Revenue $",
"parentColumnSpace": 19.65625,
"chartName": "Last week's revenue",
"leftColumn": 2,
"borderRadius": "0px",
"xAxisName": "Last Week",
"customFusionChartConfig": {
"type": "column2d",
"dataSource": {
"chart": {
"caption": "Last week's revenue",
"xAxisName": "Last Week",
"yAxisName": "Total Order Revenue $",
"theme": "fusion"
},
"data": [
{
"label": "Mon",
"value": 10000
},
{
"label": "Tue",
"value": 12000
},
{
"label": "Wed",
"value": 32000
},
{
"label": "Thu",
"value": 28000
},
{
"label": "Fri",
"value": 14000
},
{
"label": "Sat",
"value": 19000
},
{
"label": "Sun",
"value": 36000
}
],
"trendlines": [
{
"line": [
{
"startvalue": "38000",
"valueOnRight": "1",
"displayvalue": "Weekly Target"
}
]
}
]
}
},
{
"image": "",
"widgetName": "Image1",
"rightColumn": 22,
"widgetId": "1t50avy6f1",
"topRow": 58,
"bottomRow": 70,
"parentRowSpace": 10,
"isVisible": true,
"type": "IMAGE_WIDGET",
"version": 1,
"parentId": "0",
"isLoading": false,
"maxZoomLevel": 1,
"parentColumnSpace": 19.65625,
"imageShape": "RECTANGLE",
"leftColumn": 6,
"defaultImage": "https://res.cloudinary.com/drako999/image/upload/v1589196259/default.png"
},
{
"widgetName": "Button1",
"rightColumn": 18,
"isDefaultClickDisabled": true,
"widgetId": "41wgbhd5vp",
"buttonStyle": "PRIMARY_BUTTON",
"topRow": 44,
"bottomRow": 48,
"parentRowSpace": 10,
"isVisible": true,
"type": "BUTTON_WIDGET",
"version": 1,
"parentId": "0",
"isLoading": false,
"parentColumnSpace": 19.65625,
"leftColumn": 10,
"text": "Submit",
"isDisabled": false
}
]
}
}
"chartType": "LINE_CHART"
},
{
"labelTextSize": "0.875rem",
"boxShadow": "none",
"backgroundColor": "#FFFFFF",
"widgetName": "Container1",
"rightColumn": 62,
"widgetId": "6y21iarlp4",
"containerStyle": "card",
"topRow": 11,
"bottomRow": 51,
"parentRowSpace": 10,
"isVisible": true,
"type": "CONTAINER_WIDGET",
"version": 1,
"parentId": "0",
"isLoading": false,
"parentColumnSpace": 19.65625,
"leftColumn": 30,
"borderRadius": "0px",
"children": [
{
"labelTextSize": "0.875rem",
"boxShadow": "none",
"widgetName": "Canvas1",
"rightColumn": 629,
"detachFromLayout": true,
"widgetId": "jqasr1uss5",
"containerStyle": "none",
"topRow": 0,
"bottomRow": 400,
"parentRowSpace": 1,
"isVisible": true,
"canExtend": false,
"type": "CANVAS_WIDGET",
"version": 1,
"parentId": "6y21iarlp4",
"minHeight": 400,
"isLoading": false,
"parentColumnSpace": 1,
"leftColumn": 0,
"borderRadius": "0px",
"children": []
}
]
},
{
"labelTextSize": "0.875rem",
"image": "",
"boxShadow": "none",
"widgetName": "Image1",
"rightColumn": 22,
"widgetId": "1t50avy6f1",
"topRow": 58,
"bottomRow": 70,
"parentRowSpace": 10,
"isVisible": true,
"type": "IMAGE_WIDGET",
"version": 1,
"parentId": "0",
"isLoading": false,
"maxZoomLevel": 1,
"parentColumnSpace": 19.65625,
"imageShape": "RECTANGLE",
"leftColumn": 6,
"borderRadius": "0px",
"defaultImage": "https://res.cloudinary.com/drako999/image/upload/v1589196259/default.png"
},
{
"labelTextSize": "0.875rem",
"boxShadow": "none",
"widgetName": "Button1",
"rightColumn": 18,
"isDefaultClickDisabled": true,
"buttonColor": "#03B365",
"widgetId": "41wgbhd5vp",
"topRow": 44,
"bottomRow": 48,
"parentRowSpace": 10,
"isVisible": true,
"type": "BUTTON_WIDGET",
"version": 1,
"recaptchaType": "V3",
"parentId": "0",
"isLoading": false,
"parentColumnSpace": 19.65625,
"leftColumn": 10,
"borderRadius": "0px",
"buttonVariant": "PRIMARY",
"text": "Submit",
"isDisabled": false
},
{
"boxShadow": "none",
"widgetName": "Camera1",
"isCanvas": false,
"displayName": "Camera",
"iconSVG": "/static/media/icon.79c0d6de.svg",
"topRow": 70,
"bottomRow": 103,
"parentRowSpace": 10,
"type": "CAMERA_WIDGET",
"hideCard": false,
"mode": "CAMERA",
"parentColumnSpace": 12.5625,
"leftColumn": 2,
"dynamicBindingPathList": [
{
"key": "borderRadius"
}
],
"isDisabled": false,
"key": "bybcx1x9lk",
"isMirrored": true,
"rightColumn": 27,
"widgetId": "wv1qtmzsbm",
"isVisible": true,
"version": 1,
"parentId": "0",
"renderMode": "CANVAS",
"isLoading": false,
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
}
]
}
}

View File

@ -9,9 +9,8 @@ let homePage = ObjectsRegistry.HomePage,
jsEditor = ObjectsRegistry.JSEditor,
locator = ObjectsRegistry.CommonLocators;
describe("AForce - Community Issues page validations", function () {
before(function () {
describe("AForce - Community Issues page validations", function() {
before(function() {
agHelper.clearLocalStorageCache();
});
@ -28,231 +27,267 @@ describe("AForce - Community Issues page validations", function () {
cy.visit("/applications");
homePage.ImportApp("CommunityIssuesExport.json");
cy.wait("@importNewApplication").then((interception: any) => {
agHelper.Sleep()
agHelper.Sleep();
const { isPartialImport } = interception.response.body.data;
if (isPartialImport) {
// should reconnect modal
dataSources.ReconnectDataSourcePostgres("AForceDB")
dataSources.ReconnectDataSourcePostgres("AForceDB");
} else {
homePage.AssertImport()
homePage.AssertImport();
}
//Validate table is not empty!
table.WaitUntilTableLoad()
table.WaitUntilTableLoad();
//Validating order of header columns!
table.AssertTableHeaderOrder("TypeTitleStatus+1CommentorsVotesAnswerUpVoteStatesupvote_ididgithub_issue_idauthorcreated_atdescriptionlabelsstatelinkupdated_at")
table.AssertTableHeaderOrder(
"TypeTitleStatus+1CommentorsVotesAnswerUpVoteStatesupvote_ididgithub_issue_idauthorcreated_atdescriptionlabelsstatelinkupdated_at",
);
//Validating hidden columns:
table.AssertHiddenColumns(['States', 'upvote_id', 'id', 'github_issue_id', 'author', 'created_at', 'description', 'labels', 'state', 'link', 'updated_at'])
table.AssertHiddenColumns([
"States",
"upvote_id",
"id",
"github_issue_id",
"author",
"created_at",
"description",
"labels",
"state",
"link",
"updated_at",
]);
});
});
it("2. Validate table navigation with Server Side pagination enabled with Default selected row", () => {
ee.SelectEntityByName("Table1", 'WIDGETS')
agHelper.AssertExistingToggleState("serversidepagination", 'checked')
ee.SelectEntityByName("Table1", "WIDGETS");
agHelper.AssertExistingToggleState("serversidepagination", "checked");
agHelper.EvaluateExistingPropertyFieldValue("Default Selected Row")
.then($selectedRow => {
agHelper
.EvaluateExistingPropertyFieldValue("Default Selected Row")
.then(($selectedRow) => {
selectedRow = Number($selectedRow);
table.AssertSelectedRow(selectedRow)
table.AssertSelectedRow(selectedRow);
});
agHelper.DeployApp()
table.WaitUntilTableLoad()
agHelper.DeployApp();
table.WaitUntilTableLoad();
//Verify hidden columns are infact hidden in deployed app!
table.AssertTableHeaderOrder("TypeTitleStatus+1CommentorsVotesAnswerUpVote")//from case #1
table.AssertTableHeaderOrder(
"TypeTitleStatus+1CommentorsVotesAnswerUpVote",
); //from case #1
table.AssertSelectedRow(selectedRow)//Assert default selected row
table.AssertSelectedRow(selectedRow); //Assert default selected row
table.AssertPageNumber(1);
table.NavigateToNextPage()//page 2
agHelper.Sleep(3000)//wait for table navigation to take effect!
table.WaitUntilTableLoad()
table.AssertSelectedRow(selectedRow)
table.NavigateToNextPage(); //page 2
agHelper.Sleep(3000); //wait for table navigation to take effect!
table.WaitUntilTableLoad();
table.AssertSelectedRow(selectedRow);
table.NavigateToNextPage(); //page 3
agHelper.Sleep(3000); //wait for table navigation to take effect!
table.WaitForTableEmpty(); //page 3
table.NavigateToPreviousPage(); //page 2
agHelper.Sleep(3000); //wait for table navigation to take effect!
table.WaitUntilTableLoad();
table.AssertSelectedRow(selectedRow);
table.NavigateToNextPage()//page 3
agHelper.Sleep(3000)//wait for table navigation to take effect!
table.WaitForTableEmpty()//page 3
table.NavigateToPreviousPage()//page 2
agHelper.Sleep(3000)//wait for table navigation to take effect!
table.WaitUntilTableLoad()
table.AssertSelectedRow(selectedRow)
table.NavigateToPreviousPage()//page 1
agHelper.Sleep(3000)//wait for table navigation to take effect!
table.WaitUntilTableLoad()
table.AssertSelectedRow(selectedRow)
table.NavigateToPreviousPage(); //page 1
agHelper.Sleep(3000); //wait for table navigation to take effect!
table.WaitUntilTableLoad();
table.AssertSelectedRow(selectedRow);
table.AssertPageNumber(1);
})
});
it("3. Validate table navigation with Server Side pagination disabled with Default selected row selection", () => {
agHelper.NavigateBacktoEditor()
table.WaitUntilTableLoad()
ee.SelectEntityByName("Table1", 'WIDGETS')
agHelper.ToggleOnOrOff('serversidepagination', 'Off')
agHelper.DeployApp()
table.WaitUntilTableLoad()
table.AssertPageNumber(1, 'Off');
table.AssertSelectedRow(selectedRow)
agHelper.NavigateBacktoEditor()
table.WaitUntilTableLoad()
ee.SelectEntityByName("Table1", 'WIDGETS')
agHelper.ToggleOnOrOff('serversidepagination', 'On')
agHelper.NavigateBacktoEditor();
table.WaitUntilTableLoad();
ee.SelectEntityByName("Table1", "WIDGETS");
agHelper.ToggleOnOrOff("serversidepagination", "Off");
agHelper.DeployApp();
table.WaitUntilTableLoad();
table.AssertPageNumber(1, "Off");
table.AssertSelectedRow(selectedRow);
agHelper.NavigateBacktoEditor();
table.WaitUntilTableLoad();
ee.SelectEntityByName("Table1", "WIDGETS");
agHelper.ToggleOnOrOff("serversidepagination", "On");
});
it("4. Change Default selected row in table and verify", () => {
jsEditor.EnterJSContext("Default Selected Row", "1")
agHelper.DeployApp()
table.WaitUntilTableLoad()
jsEditor.EnterJSContext("Default Selected Row", "1");
agHelper.DeployApp();
table.WaitUntilTableLoad();
table.AssertPageNumber(1);
table.AssertSelectedRow(1)
table.NavigateToNextPage()//page 2
table.AssertSelectedRow(1);
table.NavigateToNextPage(); //page 2
table.AssertPageNumber(2);
table.AssertSelectedRow(1)
agHelper.NavigateBacktoEditor()
table.WaitUntilTableLoad()
table.AssertSelectedRow(1);
agHelper.NavigateBacktoEditor();
table.WaitUntilTableLoad();
});
it.skip("5. Verify Default search text in table as per 'Default Search Text' property set + Bug 12228", () => {
ee.SelectEntityByName("Table1", "WIDGETS");
jsEditor.EnterJSContext("Default Search Text", "Bug", false);
agHelper.DeployApp();
table.AssertSearchText("Bug");
table.WaitUntilTableLoad();
table.WaitUntilTableLoad();
agHelper.NavigateBacktoEditor();
ee.SelectEntityByName("Table1", 'WIDGETS')
jsEditor.EnterJSContext("Default Search Text", "Bug", false)
agHelper.DeployApp()
table.AssertSearchText('Bug')
table.WaitUntilTableLoad()
table.WaitUntilTableLoad()
agHelper.NavigateBacktoEditor()
ee.SelectEntityByName("Table1", "WIDGETS");
jsEditor.EnterJSContext("Default Search Text", "Question", false);
agHelper.DeployApp();
table.AssertSearchText("Question");
table.WaitUntilTableLoad();
agHelper.NavigateBacktoEditor();
table.WaitUntilTableLoad();
ee.SelectEntityByName("Table1", 'WIDGETS')
jsEditor.EnterJSContext("Default Search Text", "Question", false)
agHelper.DeployApp()
table.AssertSearchText('Question')
table.WaitUntilTableLoad()
agHelper.NavigateBacktoEditor()
table.WaitUntilTableLoad()
ee.SelectEntityByName("Table1", 'WIDGETS')
jsEditor.EnterJSContext("Default Search Text", "Epic", false)//Bug 12228 - Searching based on hidden column value should not be allowed
agHelper.DeployApp()
table.AssertSearchText('Epic')
table.WaitForTableEmpty()
agHelper.NavigateBacktoEditor()
table.WaitUntilTableLoad()
ee.SelectEntityByName("Table1", 'WIDGETS')
jsEditor.RemoveText('defaultsearchtext')
table.WaitUntilTableLoad()
ee.SelectEntityByName("Table1", "WIDGETS");
jsEditor.EnterJSContext("Default Search Text", "Epic", false); //Bug 12228 - Searching based on hidden column value should not be allowed
agHelper.DeployApp();
table.AssertSearchText("Epic");
table.WaitForTableEmpty();
agHelper.NavigateBacktoEditor();
table.WaitUntilTableLoad();
ee.SelectEntityByName("Table1", "WIDGETS");
jsEditor.RemoveText("defaultsearchtext");
table.WaitUntilTableLoad();
});
it.skip("6. Validate Search table with Client Side Search enabled & disabled", () => {
ee.SelectEntityByName("Table1", 'WIDGETS')
agHelper.AssertExistingToggleState("enableclientsidesearch", 'checked')
ee.SelectEntityByName("Table1", "WIDGETS");
agHelper.AssertExistingToggleState("enableclientsidesearch", "checked");
agHelper.DeployApp()
table.WaitUntilTableLoad()
agHelper.DeployApp();
table.WaitUntilTableLoad();
table.SearchTable('Bug')
table.WaitUntilTableLoad()
cy.xpath(table._searchBoxCross).click()
table.SearchTable("Bug");
table.WaitUntilTableLoad();
cy.xpath(table._searchBoxCross).click();
table.SearchTable('Question')
table.WaitUntilTableLoad()
cy.xpath(table._searchBoxCross).click()
table.SearchTable("Question");
table.WaitUntilTableLoad();
cy.xpath(table._searchBoxCross).click();
agHelper.NavigateBacktoEditor()
table.WaitUntilTableLoad()
agHelper.NavigateBacktoEditor();
table.WaitUntilTableLoad();
ee.SelectEntityByName("Table1", 'WIDGETS')
agHelper.ToggleOnOrOff("enableclientsidesearch", 'Off')
ee.SelectEntityByName("Table1", "WIDGETS");
agHelper.ToggleOnOrOff("enableclientsidesearch", "Off");
agHelper.DeployApp()
table.WaitUntilTableLoad()
agHelper.DeployApp();
table.WaitUntilTableLoad();
table.SearchTable('Bug')
table.WaitForTableEmpty()
cy.xpath(table._searchBoxCross).click()
table.SearchTable("Bug");
table.WaitForTableEmpty();
cy.xpath(table._searchBoxCross).click();
table.SearchTable('Question')
table.WaitForTableEmpty()
cy.xpath(table._searchBoxCross).click()
table.SearchTable("Question");
table.WaitForTableEmpty();
cy.xpath(table._searchBoxCross).click();
agHelper.NavigateBacktoEditor()
table.WaitUntilTableLoad()
ee.SelectEntityByName("Table1", 'WIDGETS')
agHelper.ToggleOnOrOff("enableclientsidesearch", 'On')
})
agHelper.NavigateBacktoEditor();
table.WaitUntilTableLoad();
ee.SelectEntityByName("Table1", "WIDGETS");
agHelper.ToggleOnOrOff("enableclientsidesearch", "On");
});
it("7. Validate Filter table", () => {
agHelper.DeployApp()
table.WaitUntilTableLoad()
var filterTitle = new Array();
agHelper.DeployApp();
table.WaitUntilTableLoad();
//One filter
table.OpenNFilterTable("Type", "is exactly", "Bug")
table.ReadTableRowColumnData(0, 1).then(($cellData) => {
expect($cellData).to.eq("[Bug]: Postgres queries unable to execute with more than 9 placeholders");
});
table.ReadTableRowColumnData(2, 1).then(($cellData) => {
expect($cellData).to.eq("[Bug]: Input updates with default values are not captured");
});
table.RemoveFilterNVerify("Question", true, false)
table.OpenNFilterTable("Type", "is exactly", "Bug");
for (let i = 0; i < 3; i++) {
table.ReadTableRowColumnData(i, 0, 200).then(($cellData) => {
expect($cellData).to.eq("Bug");
});
}
table.RemoveFilterNVerify("Question", true, false);
//Two filters - OR
table.OpenNFilterTable("Type", "starts with", "Trouble")
table.ReadTableRowColumnData(0, 0).then(($cellData) => {
expect($cellData).to.eq("Troubleshooting");
table.OpenNFilterTable("Type", "starts with", "Trouble");
for (let i = 0; i < 5; i++) {
table.ReadTableRowColumnData(i, 0, 200).then(($cellData) => {
expect($cellData).to.eq("Troubleshooting");
});
}
table.OpenNFilterTable("Title", "contains", "query", "OR", 1);
table.ReadTableRowColumnData(1, 0, 200).then(($cellData) => {
expect($cellData).to.be.oneOf(["Troubleshooting", "Question"]);
});
for (let i = 0; i < 8; i++) {
table.ReadTableRowColumnData(i, 1, 100).then(($cellData) => {
if ($cellData.toLowerCase().includes("query"))
filterTitle.push($cellData);
});
}
cy.wrap(filterTitle).as("filterTitleText"); // alias it for later
cy.get("@filterTitleText")
.its("length")
.should("eq", 2);
table.RemoveFilterNVerify("Question", true, false);
//Two filters - AND
table.OpenNFilterTable("Votes", "greater than", "2");
table.ReadTableRowColumnData(0, 1).then(($cellData) => {
expect($cellData).to.eq("Renew expired SSL certificate on a self-hosted instance");
expect($cellData).to.eq("Combine queries from different datasources");
});
table.OpenNFilterTable("Title", "contains", "query", 'OR', 1)
table.ReadTableRowColumnData(1, 0).then(($cellData) => {
expect($cellData).to.be.oneOf(['Troubleshooting','Question'])
table.OpenNFilterTable("Title", "contains", "button", "AND", 1);
table.ReadTableRowColumnData(0, 1).then(($cellData) => {
expect($cellData).to.eq(
"Change the video in the video player with a button click",
);
});
table.ReadTableRowColumnData(6, 1).then(($cellData) => {
expect($cellData).to.eq("Run storeValue commands before a Query.run()");
});
table.RemoveFilterNVerify("Question", true, false)
//Two filters - AND
table.OpenNFilterTable("Votes", "greater than", "3")
table.ReadTableRowColumnData(1, 1).then(($cellData) => {
expect($cellData).to.eq("Combine queries from different datasources");
});
table.OpenNFilterTable("Title", "contains", "button", 'AND', 1)
table.ReadTableRowColumnData(0, 1).then(($cellData) => {
expect($cellData).to.eq("Change the video in the video player with a button click");
});
table.RemoveFilterNVerify("Question", true, false)
})
table.RemoveFilterNVerify("Question", true, false);
});
it("8. Validate Adding a New issue from Add Modal", () => {
// agHelper.DeployApp()
// table.WaitUntilTableLoad()
cy.get(table._addIcon).closest('div').click()
agHelper.AssertElementPresence(locator._modal)
agHelper.SelectFromDropDown('Suggestion', 't--modal-widget')
cy.get(table._addIcon)
.closest("div")
.click();
agHelper.AssertElementPresence(locator._modal);
agHelper.SelectFromDropDown("Suggestion", "t--modal-widget");
cy.get(locator._inputWidgetv1InDeployed).eq(3).type("Adding Title Suggestion via script")
cy.get(locator._textAreainputWidgetv1InDeployed).eq(1).type("Adding Description Suggestion via script")
cy.get(locator._inputWidgetv1InDeployed).eq(4).type("https://github.com/appsmithorg/appsmith/issues/12532")
agHelper.SelectFromMultiSelect(['Epic', 'Task'], 1)
cy.xpath(table._visibleTextSpan('Labels')).click()
cy.get(locator._inputWidgetv1InDeployed).eq(5).type("https://release.app.appsmith.com/applications/62486d45ab307a026918639e/pages/62486d45ab307a02691863a7")
agHelper.SelectFromMultiSelect(['Documented', 'Needs App'], 1, true, 'multiselectwidget')
cy.get(locator._inputWidgetv1InDeployed)
.eq(3)
.type("Adding Title Suggestion via script");
cy.get(locator._textAreainputWidgetv1InDeployed)
.eq(1)
.type("Adding Description Suggestion via script");
cy.get(locator._inputWidgetv1InDeployed)
.eq(4)
.type("https://github.com/appsmithorg/appsmith/issues/12532");
agHelper.SelectFromMultiSelect(["Epic", "Task"], 1);
cy.xpath(table._visibleTextSpan("Labels")).click();
cy.get(locator._inputWidgetv1InDeployed)
.eq(5)
.type(
"https://release.app.appsmith.com/applications/62486d45ab307a026918639e/pages/62486d45ab307a02691863a7",
);
agHelper.SelectFromMultiSelect(
["Documented", "Needs App"],
1,
true,
"multiselectwidget",
);
agHelper.ClickButton('Confirm')
agHelper.Sleep(3000)
table.SearchTable('Suggestion', 2)
table.WaitUntilTableLoad()
agHelper.ClickButton("Confirm");
agHelper.Sleep(3000);
table.SearchTable("Suggestion", 2);
table.WaitUntilTableLoad();
table.ReadTableRowColumnData(0, 0, 1000).then((cellData) => {
expect(cellData).to.be.equal("Suggestion");
@ -261,22 +296,32 @@ describe("AForce - Community Issues page validations", function () {
table.ReadTableRowColumnData(0, 1, 1000).then((cellData) => {
expect(cellData).to.be.equal("Adding Title Suggestion via script");
});
})
});
it("9. Validate Updating issue from Details tab", () => {
agHelper.AssertElementAbsence(locator._widgetInDeployed('tabswidget'))
table.SelectTableRow(0)
agHelper.AssertElementPresence(locator._widgetInDeployed('tabswidget'))
agHelper.GetNClick(locator._inputWidgetv1InDeployed).type("-updating title")
agHelper.GetNClick(locator._textAreainputWidgetv1InDeployed).type("-updating desc")
agHelper.GetNClick(locator._inputWidgetv1InDeployed, 1).type("-updating issue link")
agHelper.SelectFromDropDown('Troubleshooting', 't--widget-tabswidget')
agHelper.SelectFromMultiSelect(['Epic', 'Task'], 0, false)
agHelper.SelectFromMultiSelect(['High', 'Dependencies'], 0, true)
agHelper.SelectFromDropDown('[Bug] TypeError: o is undefined', 't--widget-tabswidget', 1)
agHelper.GetNClick(locator._inputWidgetv1InDeployed, 2).type("-updating answer link")
agHelper.AssertElementAbsence(locator._widgetInDeployed("tabswidget"));
table.SelectTableRow(0);
agHelper.AssertElementPresence(locator._widgetInDeployed("tabswidget"));
agHelper
.GetNClick(locator._inputWidgetv1InDeployed)
.type("-updating title");
agHelper
.GetNClick(locator._textAreainputWidgetv1InDeployed)
.type("-updating desc");
agHelper
.GetNClick(locator._inputWidgetv1InDeployed, 1)
.type("-updating issue link");
agHelper.SelectFromDropDown("Troubleshooting", "t--widget-tabswidget");
agHelper.SelectFromMultiSelect(["Epic", "Task"], 0, false);
agHelper.SelectFromMultiSelect(["High", "Dependencies"], 0, true);
agHelper.SelectFromDropDown(
"[Bug] TypeError: o is undefined",
"t--widget-tabswidget",
1,
);
agHelper
.GetNClick(locator._inputWidgetv1InDeployed, 2)
.type("-updating answer link");
//cy.get("body").tab().type("{enter}")
@ -288,34 +333,41 @@ describe("AForce - Community Issues page validations", function () {
// key: 'Enter',
// })
//agHelper.Sleep(2000)
//cy.get("body").type("{enter}")
agHelper.RemoveMultiSelectItems(['Documented', 'Needs App'])
agHelper.RemoveMultiSelectItems(["Documented", "Needs App"]);
//agHelper.SelectFromMultiSelect(['Documented', 'Needs App', 'App Built'], 0, false, 'multiselectwidget')
agHelper.SelectFromMultiSelect(['Needs Product'], 0, true, 'multiselectwidget')
agHelper.ClickButton('Save')
agHelper.SelectFromMultiSelect(
["Needs Product"],
0,
true,
"multiselectwidget",
);
agHelper.ClickButton("Save");
table.ReadTableRowColumnData(0, 0, 1000).then((cellData) => {
expect(cellData).to.be.equal("Troubleshooting");
});
table.ReadTableRowColumnData(0, 1, 1000).then((cellData) => {
expect(cellData).to.be.equal("Adding Title Suggestion via script-updating title");
expect(cellData).to.be.equal(
"Adding Title Suggestion via script-updating title",
);
});
})
});
it("10. Validate Deleting the newly created issue", () => {
agHelper.AssertElementAbsence(locator._widgetInDeployed('tabswidget'))
table.SelectTableRow(0)
agHelper.AssertElementPresence(locator._widgetInDeployed('tabswidget'))
agHelper.Sleep()
cy.get(table._trashIcon).closest('div').click()
agHelper.AssertElementAbsence(locator._widgetInDeployed('tabswidget'))
table.WaitForTableEmpty()
agHelper.AssertElementAbsence(locator._widgetInDeployed("tabswidget"));
table.SelectTableRow(0);
agHelper.AssertElementPresence(locator._widgetInDeployed("tabswidget"));
agHelper.Sleep();
cy.get(table._trashIcon)
.closest("div")
.click();
agHelper.AssertElementAbsence(locator._widgetInDeployed("tabswidget"));
table.WaitForTableEmpty();
//2nd search is not working, hence commenting below
// cy.xpath(table._searchBoxCross).click()

View File

@ -113,7 +113,7 @@ describe("Validate API request body panel", () => {
paste: true,
completeReplace: true,
toRun: false,
shouldNavigate: true,
shouldCreateNewJSObj: true,
},
);

View File

@ -19,7 +19,7 @@ describe("Validate JSObjects binding to Input widget", () => {
paste: false,
completeReplace: false,
toRun: true,
shouldNavigate: true,
shouldCreateNewJSObj: true,
});
ee.expandCollapseEntity("WIDGETS"); //to expand widgets
ee.expandCollapseEntity("Form1");

View File

@ -32,7 +32,7 @@ describe("Validate JSObj binding to Table widget", () => {
paste: false,
completeReplace: false,
toRun: true,
shouldNavigate: true,
shouldCreateNewJSObj: true,
});
cy.get("@jsObjName").then((jsObj) => {
jsName = jsObj;

View File

@ -319,7 +319,7 @@ showAlert("Wonderful! all apis executed", "success")).catch(() => showAlert("Ple
return Promise.any([this.func2(), this.func3(), this.func1()]).then((value) => showAlert("Resolved promise is:" + value))
}
}`,
{ paste: true, completeReplace: true, toRun: true, shouldNavigate: true },
{ paste: true, completeReplace: true, toRun: true, shouldCreateNewJSObj: true },
);
ee.SelectEntityByName("Button1", "WIDGETS");
cy.get("@jsObjName").then((jsObjName) => {

View File

@ -15,7 +15,7 @@ describe("JS Function Execution", function() {
paste: true,
completeReplace: true,
toRun: false,
shouldNavigate: true,
shouldCreateNewJSObj: true,
},
);
@ -33,7 +33,7 @@ describe("JS Function Execution", function() {
paste: true,
completeReplace: true,
toRun: false,
shouldNavigate: true,
shouldCreateNewJSObj: true,
},
);
@ -41,8 +41,8 @@ describe("JS Function Execution", function() {
});
it("3. Prioritizes parse errors that render JS Object invalid over function execution parse errors in debugger callouts", function() {
const JSObjectWithFunctionExecutionParseErrors = `export default {
myFun1 :()=>{
const JSObjectWithFunctionExecutionParseErrors = `export default {
myFun1 :()=>{
return f
}
}`;
@ -58,7 +58,7 @@ describe("JS Function Execution", function() {
paste: true,
completeReplace: true,
toRun: true,
shouldNavigate: true,
shouldCreateNewJSObj: true,
});
// Assert presence of function execution parse error callout
@ -69,7 +69,7 @@ describe("JS Function Execution", function() {
paste: true,
completeReplace: true,
toRun: false,
shouldNavigate: false,
shouldCreateNewJSObj: false,
});
// Assert presence of parse error callout (entire JS Object is invalid)

View File

@ -33,5 +33,17 @@ describe("Widget Grouping", function() {
.should("have.length", 2);
cy.get(`@group`).find(`.t--draggable-buttonwidget`);
cy.get(`@group`).find(`.t--draggable-imagewidget`);
// verify the position so that the camera widget is still below the newly grouped container
cy.get(`.t--widget-containerwidget`)
.eq(1)
.then((element) => {
const elementTop = parseFloat(element.css("top"));
const elementHeight = parseFloat(element.css("height"));
const containerBottom = (elementTop + elementHeight).toString() + "px";
cy.get(`.t--widget-camerawidget`)
.invoke("attr", "style")
.should("contain", `top: ${containerBottom}`);
});
});
});

View File

@ -43,7 +43,7 @@ describe("Validate basic operations on Entity explorer JSEditor structure", () =
it("5. Validate Move JSObject", function() {
const newPageId = "Page2";
agHelper.AddNewPage();
ee.AddNewPage();
ee.AssertEntityPresenceInExplorer(newPageId);
ee.SelectEntityByName(pageId);
ee.expandCollapseEntity("QUERIES/JS");

View File

@ -39,7 +39,7 @@ describe("JSObjects OnLoad Actions tests", function() {
paste: true,
completeReplace: true,
toRun: false,
shouldNavigate: true,
shouldCreateNewJSObj: true,
},
);
jsEditor.EnableDisableAsyncFuncSettings("getId", false, true); //Only before calling confirmation is enabled by User here
@ -189,7 +189,7 @@ describe("JSObjects OnLoad Actions tests", function() {
paste: true,
completeReplace: true,
toRun: false,
shouldNavigate: true,
shouldCreateNewJSObj: true,
},
);
@ -251,7 +251,7 @@ describe("JSObjects OnLoad Actions tests", function() {
paste: true,
completeReplace: true,
toRun: false,
shouldNavigate: true,
shouldCreateNewJSObj: true,
},
);
@ -440,7 +440,7 @@ describe("JSObjects OnLoad Actions tests", function() {
paste: true,
completeReplace: true,
toRun: false,
shouldNavigate: true,
shouldCreateNewJSObj: true,
},
);
@ -481,7 +481,7 @@ describe("JSObjects OnLoad Actions tests", function() {
// `{{` +
// jsObjName +
// `.callCountry();
// showAlert('Your country is: ' + getCountry.data[0].country, 'info')}}`,
// Select1.selectedOptionValue? showAlert('Your country is: ' + getCountry.data[0].country, 'info'): null`,
// true,
// true,
// );

View File

@ -38,7 +38,7 @@ describe("[Bug] - 10784 - Passing params from JS to SQL query should not break",
paste: true,
completeReplace: false,
toRun: false,
shouldNavigate: true,
shouldCreateNewJSObj: true,
},
);
});

View File

@ -74,6 +74,7 @@
"evaluatedType": ".t--CodeEditor-evaluatedValue > div:first-of-type pre",
"evaluatedCurrentValue": "div:last-of-type .t--CodeEditor-evaluatedValue > div:last-of-type pre",
"entityExplorersearch": "#entity-explorer-search",
"searchEntityInExplorer": "#search-entity",
"entitySearchResult": ".t--entity-name:contains('",
"saveStatusContainer": ".t--save-status-container",
"saveStatusIsSaving": "t--save-status-is-saving",
@ -156,9 +157,9 @@
"debugErrorMsg": ".t--debugger-message",
"tableButtonVariant": ".t--property-control-buttonvariant .bp3-popover-target",
"debuggerLabel": "span.debugger-label",
"debuggerContextMenu":".t--debugger-contextual-error-menu",
"debuggerContextMenu": ".t--debugger-contextual-error-menu",
"cyclicDependencyError": "//div[@class='Toastify']//span[contains(text(),'Cyclic dependency found while evaluating')]",
"openDocumentationfromErrorTab":"//span[@name='book-line']",
"openDocumentationfromErrorTab": "//span[@name='book-line']",
"appNameDeployMenu": ".t--app-name-menu-deploy-parent",
"appNameDeployMenuPublish": ".t--app-name-menu-deploy",
"appNameDeployMenuCurrentVersion": ".t--app-name-menu-deploy-current-version",
@ -178,4 +179,4 @@
"saveThemeBtn": ".t--save-theme-btn",
"selectThemeBackBtn": ".t--theme-select-back-btn",
"themeAppBorderRadiusBtn": ".t--theme-appBorderRadius"
}
}

View File

@ -134,17 +134,6 @@ export class AggregateHelper {
localStorage.setItem("inDeployedMode", "true");
}
public AddNewPage() {
cy.get(this.locator._newPage)
.first()
.click();
cy.wait("@createPage").should(
"have.nested.property",
"response.body.responseMeta.status",
201,
);
}
public ValidateToastMessage(text: string, length = 1) {
cy.get(this.locator._toastMsg)
.should("have.length", length)
@ -409,6 +398,17 @@ export class AggregateHelper {
.wait(500);
}
public GetNClickByContains(
selector: string,
containsText: string,
index = 0,
) {
cy.get(selector)
.contains(containsText)
.eq(index)
.click().wait(200);
}
public ToggleOnOrOff(propertyName: string, toggle: "On" | "Off") {
if (toggle == "On") {
cy.get(this.locator._propertyToggle(propertyName))
@ -605,7 +605,11 @@ export class AggregateHelper {
locator.should("not.exist");
}
public GetText(selector: string, textOrValue : 'text'| 'val' = 'text', index = 0) {
public GetText(
selector: string,
textOrValue: "text" | "val" = "text",
index = 0,
) {
let locator = selector.startsWith("//")
? cy.xpath(selector)
: cy.get(selector);

View File

@ -34,26 +34,28 @@ export class DataSources {
_reconnectModal = "div.reconnect-datasource-modal";
_activeDSListReconnectModal = (dbName: string) =>
"//div[contains(@class, 't--ds-list')]//span[text()='" + dbName + "']";
_apiQueryBtn = ".t--run-query";
_runQueryBtn = ".t--run-query";
_newDatabases = "#new-datasources";
_selectDatasourceDropdown = "[data-cy=t--datasource-dropdown]";
_datasourceDropdownOption = "[data-cy=t--datasource-dropdown-option]";
_selectTableDropdown = "[data-cy=t--table-dropdown]";
_tableDropdownOption = ".bp3-popover-content .t--dropdown-option";
_generatePageBtn = "[data-cy=t--generate-page-form-submit]";
public NavigateToDSAdd() {
public CreatePlugIn(pluginName: string) {
cy.get(this._createNewPlgin(pluginName)).trigger("click");
}
public NavigateToDSCreateNew() {
cy.get(this._addNewDataSource)
.last()
.scrollIntoView()
.should("be.visible")
.click({ force: true });
}
public CreatePlugIn(pluginName: string) {
cy.get(this._createNewPlgin(pluginName)).click();
}
public NavigateToDSCreateNew() {
this.NavigateToDSAdd();
cy.get(this._dsCreateNewTab)
.should("be.visible")
.click({ force: true });
cy.get(this.locator._loading).should("not.exist");
// cy.get(this._dsCreateNewTab)
// .should("be.visible")
// .click({ force: true });
cy.get(this._newDatabases).should("be.visible");
}
public FillPostgresDSForm(shouldAddTrailingSpaces = false) {
@ -73,6 +75,18 @@ export class DataSources {
cy.get(this._password).type(datasourceFormData["postgres-password"]);
}
public FillMongoDSForm(shouldAddTrailingSpaces = false) {
const hostAddress = shouldAddTrailingSpaces
? datasourceFormData["mongo-host"] + " "
: datasourceFormData["mongo-host"];
cy.get(this._host).type(hostAddress);
cy.get(this._port).type(datasourceFormData["mongo-port"].toString());
cy.get(this._sectionAuthentication).click();
cy.get(this._databaseName)
.clear()
.type(datasourceFormData["mongo-databaseName"]);
}
public TestSaveDatasource(expectedRes = true) {
this.TestDatasource(expectedRes);
this.SaveDatasource();
@ -94,7 +108,7 @@ export class DataSources {
}
public NavigateToActiveDSQueryPane(datasourceName: string) {
this.NavigateToDSAdd();
this.NavigateToDSCreateNew();
this.agHelper.GetNClick(this.locator._activeTab);
cy.get(this._datasourceCard)
.contains(datasourceName)
@ -145,7 +159,7 @@ export class DataSources {
}
RunQuery() {
cy.get(this._apiQueryBtn).click({ force: true });
cy.get(this._runQueryBtn).click({ force: true });
this.agHelper.ValidateNetworkExecutionSuccess("@postExecute");
}
}

View File

@ -1,69 +1,88 @@
import { ObjectsRegistry } from "../Objects/Registry"
import { ObjectsRegistry } from "../Objects/Registry";
export class EntityExplorer {
public agHelper = ObjectsRegistry.AggregateHelper;
public locator = ObjectsRegistry.CommonLocators;
public agHelper = ObjectsRegistry.AggregateHelper
public locator = ObjectsRegistry.CommonLocators;
public SelectEntityByName(
entityNameinLeftSidebar: string,
section: "WIDGETS" | "QUERIES/JS" | "DATASOURCES" | "" = "",
) {
if (section) this.expandCollapseEntity(section); //to expand respective section
cy.xpath(this.locator._entityNameInExplorer(entityNameinLeftSidebar))
.last()
.click({ multiple: true });
this.agHelper.Sleep();
}
public SelectEntityByName(entityNameinLeftSidebar: string, section: 'WIDGETS' | 'QUERIES/JS' | 'DATASOURCES' | '' = '') {
if (section)
this.expandCollapseEntity(section)//to expand respective section
cy.xpath(this.locator._entityNameInExplorer(entityNameinLeftSidebar))
.last()
.click({ multiple: true })
this.agHelper.Sleep()
public AddNewPage() {
cy.get(this.locator._newPage)
.first()
.click();
this.agHelper.ValidateNetworkStatus("@createPage", 201);
}
public NavigateToSwitcher(navigationTab: "explorer" | "widgets") {
cy.get(this.locator._openNavigationTab(navigationTab)).click();
}
public AssertEntityPresenceInExplorer(entityNameinLeftSidebar: string) {
cy.xpath(
this.locator._entityNameInExplorer(entityNameinLeftSidebar),
).should("have.length", 1);
}
public AssertEntityAbsenceInExplorer(entityNameinLeftSidebar: string) {
cy.xpath(
this.locator._entityNameInExplorer(entityNameinLeftSidebar),
).should("not.exist");
}
public expandCollapseEntity(entityName: string, expand = true) {
cy.xpath(this.locator._expandCollapseArrow(entityName))
.invoke("attr", "name")
.then((arrow) => {
if (expand && arrow == "arrow-right")
cy.xpath(this.locator._expandCollapseArrow(entityName))
.trigger("click", { multiple: true })
.wait(1000);
else if (!expand && arrow == "arrow-down")
cy.xpath(this.locator._expandCollapseArrow(entityName))
.trigger("click", { multiple: true })
.wait(1000);
else this.agHelper.Sleep(500);
});
}
public ActionContextMenuByEntityName(
entityNameinLeftSidebar: string,
action = "Delete",
subAction = "",
) {
this.agHelper.Sleep();
cy.xpath(this.locator._contextMenu(entityNameinLeftSidebar))
.last()
.click({ force: true });
cy.xpath(this.locator._contextMenuItem(action)).click({ force: true });
this.agHelper.Sleep(500);
if (subAction) {
cy.xpath(this.locator._contextMenuItem(subAction)).click({ force: true });
this.agHelper.Sleep(500);
}
}
public NavigateToSwitcher(navigationTab: 'explorer' | 'widgets') {
cy.get(this.locator._openNavigationTab(navigationTab)).click()
}
public AssertEntityPresenceInExplorer(entityNameinLeftSidebar: string) {
cy.xpath(this.locator._entityNameInExplorer(entityNameinLeftSidebar))
.should("have.length", 1);
}
public AssertEntityAbsenceInExplorer(entityNameinLeftSidebar: string) {
cy.xpath(this.locator._entityNameInExplorer(entityNameinLeftSidebar)).should('not.exist');
}
public expandCollapseEntity(entityName: string, expand = true) {
cy.xpath(this.locator._expandCollapseArrow(entityName)).invoke('attr', 'name').then((arrow) => {
if (expand && arrow == 'arrow-right')
cy.xpath(this.locator._expandCollapseArrow(entityName)).trigger('click', { multiple: true }).wait(1000);
else if (!expand && arrow == 'arrow-down')
cy.xpath(this.locator._expandCollapseArrow(entityName)).trigger('click', { multiple: true }).wait(1000);
else
this.agHelper.Sleep(500)
})
}
public ActionContextMenuByEntityName(entityNameinLeftSidebar: string, action = "Delete", subAction = "") {
this.agHelper.Sleep();
cy.xpath(this.locator._contextMenu(entityNameinLeftSidebar))
.last()
.click({ force: true });
cy.xpath(this.locator._contextMenuItem(action))
.click({ force: true })
this.agHelper.Sleep(500)
if (subAction) {
cy.xpath(this.locator._contextMenuItem(subAction))
.click({ force: true })
this.agHelper.Sleep(500)
}
}
public DragDropWidgetNVerify(widgetType: string, x: number, y: number) {
this.NavigateToSwitcher('widgets')
this.agHelper.Sleep()
cy.get(this.locator._widgetPageIcon(widgetType)).first()
.trigger("dragstart", { force: true })
.trigger("mousemove", x, y, { force: true });
cy.get(this.locator._dropHere)
.trigger("mousemove", x, y, { eventConstructor: "MouseEvent" })
.trigger("mousemove", x, y, { eventConstructor: "MouseEvent" })
.trigger("mouseup", x, y, { eventConstructor: "MouseEvent" });
this.agHelper.AssertAutoSave()//settling time for widget on canvas!
cy.get(this.locator._widgetInCanvas(widgetType)).should('exist')
}
public DragDropWidgetNVerify(widgetType: string, x: number, y: number) {
this.NavigateToSwitcher("widgets");
this.agHelper.Sleep();
cy.get(this.locator._widgetPageIcon(widgetType))
.first()
.trigger("dragstart", { force: true })
.trigger("mousemove", x, y, { force: true });
cy.get(this.locator._dropHere)
.trigger("mousemove", x, y, { eventConstructor: "MouseEvent" })
.trigger("mousemove", x, y, { eventConstructor: "MouseEvent" })
.trigger("mouseup", x, y, { eventConstructor: "MouseEvent" });
this.agHelper.AssertAutoSave(); //settling time for widget on canvas!
cy.get(this.locator._widgetInCanvas(widgetType)).should("exist");
}
}

View File

@ -29,7 +29,7 @@ export class HomePage {
private _editAppName = "bp3-editable-text-editing"
private _appMenu = ".t--editor-appname-menu-portal .bp3-menu-item"
private _buildFromScratchActionCard = ".t--BuildFromScratch"
private _buildFromDataTableActionCard = ".t--GenerateCRUDPage"
_buildFromDataTableActionCard = ".t--GenerateCRUDPage"
private _selectRole = "//span[text()='Select a role']/ancestor::div"
private _searchInput = "input[type='text']"
_appHoverIcon = (action: string) => ".t--application-" + action + "-link"

View File

@ -4,13 +4,13 @@ export interface ICreateJSObjectOptions {
paste: boolean;
completeReplace: boolean;
toRun: boolean;
shouldNavigate: boolean;
shouldCreateNewJSObj: boolean;
}
const DEFAULT_CREATE_JS_OBJECT_OPTIONS = {
paste: true,
completeReplace: false,
toRun: true,
shouldNavigate: true,
shouldCreateNewJSObj: true,
};
export class JSEditor {
@ -82,7 +82,7 @@ export class JSEditor {
//#endregion
//#region Page functions
public NavigateToJSEditor() {
public NavigateToNewJSEditor() {
cy.get(this.locator._createNew)
.last()
.click({ force: true });
@ -107,9 +107,9 @@ export class JSEditor {
JSCode: string,
options: ICreateJSObjectOptions = DEFAULT_CREATE_JS_OBJECT_OPTIONS,
) {
const { completeReplace, paste, shouldNavigate, toRun } = options;
const { completeReplace, paste, shouldCreateNewJSObj, toRun } = options;
shouldNavigate && this.NavigateToJSEditor();
shouldCreateNewJSObj && this.NavigateToNewJSEditor();
if (!completeReplace) {
cy.get(this.locator._codeMirrorTextArea)
.first()
@ -377,7 +377,7 @@ export class JSEditor {
this.agHelper.GetNClick(this._codeTab);
}
/**
*
*
There are two types of parse errors in the JS Editor
1. Parse errors that render the JS Object invalid and all functions unrunnable
2. Parse errors within functions that throw errors when executing those functions

View File

@ -313,7 +313,7 @@ Cypress.Commands.add("SearchApp", (appname) => {
});
Cypress.Commands.add("SearchEntity", (apiname1, apiname2) => {
cy.get(commonlocators.entityExplorersearch)
cy.get(commonlocators.searchEntityInExplorer)
.clear({ force: true })
.type(apiname1, { force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
@ -329,7 +329,7 @@ Cypress.Commands.add("SearchEntity", (apiname1, apiname2) => {
Cypress.Commands.add("GlobalSearchEntity", (apiname1, dontAssertVisibility) => {
// entity explorer search will be hidden
cy.get(commonlocators.entityExplorersearch)
cy.get(commonlocators.searchEntityInExplorer)
.clear({ force: true })
.type(apiname1, { force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
@ -413,7 +413,7 @@ Cypress.Commands.add("CheckAndUnfoldWidgets", () => {
});
Cypress.Commands.add("SearchEntityandOpen", (apiname1) => {
cy.get(commonlocators.entityExplorersearch)
cy.get(commonlocators.searchEntityInExplorer)
.clear({ force: true })
.type(apiname1, { force: true });
cy.CheckAndUnfoldWidgets();
@ -432,7 +432,7 @@ Cypress.Commands.add("SearchEntityandOpen", (apiname1) => {
});
Cypress.Commands.add("SearchEntityAndUnfold", (apiname1) => {
cy.get(commonlocators.entityExplorersearch)
cy.get(commonlocators.searchEntityInExplorer)
.clear({ force: true })
.type(apiname1, { force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
@ -451,7 +451,7 @@ Cypress.Commands.add("SearchEntityAndUnfold", (apiname1) => {
Cypress.Commands.add("OpenBindings", (apiname1) => {
cy.wait(500);
cy.get(commonlocators.entityExplorersearch)
cy.get(commonlocators.searchEntityInExplorer)
.clear({ force: true })
.type(apiname1, { force: true });
cy.CheckAndUnfoldWidgets();

View File

@ -7,22 +7,17 @@ import React, {
useMemo,
} from "react";
import classNames from "classnames";
import history from "utils/history";
import * as Sentry from "@sentry/react";
import { PanelStack } from "@blueprintjs/core";
import { useDispatch, useSelector } from "react-redux";
import PerformanceTracker, {
PerformanceTransactionName,
} from "utils/PerformanceTracker";
import { AppState } from "reducers";
import {
getFirstTimeUserOnboardingComplete,
getIsFirstTimeUserOnboardingEnabled,
} from "selectors/onboardingSelectors";
import Explorer from "pages/Editor/Explorer";
import Switcher from "components/ads/Switcher";
import { trimQueryString } from "utils/helpers";
import AppComments from "comments/AppComments/AppComments";
import { setExplorerActiveAction } from "actions/explorerActions";
import {
@ -33,14 +28,10 @@ import { tailwindLayers } from "constants/Layers";
import TooltipComponent from "components/ads/Tooltip";
import { previewModeSelector } from "selectors/editorSelectors";
import useHorizontalResize from "utils/hooks/useHorizontalResize";
import { forceOpenWidgetPanel } from "actions/widgetSidebarActions";
import { toggleInOnboardingWidgetSelection } from "actions/onboardingActions";
import OnboardingStatusbar from "pages/Editor/FirstTimeUserOnboarding/Statusbar";
import Pages from "pages/Editor/Explorer/Pages";
import { Colors } from "constants/Colors";
import { EntityProperties } from "pages/Editor/Explorer/Entity/EntityProperties";
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import { builderURL } from "RouteBuilder";
type Props = {
width: number;
@ -58,38 +49,12 @@ export const EntityExplorerSidebar = memo((props: Props) => {
const enableFirstTimeUserOnboarding = useSelector(
getIsFirstTimeUserOnboardingEnabled,
);
const isFirstTimeUserOnboardingEnabled = useSelector(
getIsFirstTimeUserOnboardingEnabled,
);
const resizer = useHorizontalResize(
sidebarRef,
props.onWidthChange,
props.onDragEnd,
);
const switches = [
{
id: "explorer",
text: "Explorer",
action: () => dispatch(forceOpenWidgetPanel(false)),
},
{
id: "widgets",
text: "Widgets",
action: () => {
!(trimQueryString(builderURL()) === window.location.pathname) &&
history.push(builderURL());
setTimeout(() => dispatch(forceOpenWidgetPanel(true)), 0);
if (isFirstTimeUserOnboardingEnabled) {
dispatch(toggleInOnboardingWidgetSelection(true));
}
},
},
];
const [activeSwitch, setActiveSwitch] = useState(switches[0]);
const [tooltipIsOpen, setTooltipIsOpen] = useState(false);
const isForceOpenWidgetPanel = useSelector(
(state: AppState) => state.ui.onBoarding.forceOpenWidgetPanel,
);
const isFirstTimeUserOnboardingComplete = useSelector(
getFirstTimeUserOnboardingComplete,
);
@ -98,14 +63,6 @@ export const EntityExplorerSidebar = memo((props: Props) => {
PerformanceTracker.stopTracking();
});
useEffect(() => {
if (isForceOpenWidgetPanel) {
setActiveSwitch(switches[1]);
} else {
setActiveSwitch(switches[0]);
}
}, [isForceOpenWidgetPanel]);
// registering event listeners
useEffect(() => {
document.addEventListener("mousemove", onMouseMove);
@ -191,7 +148,7 @@ export const EntityExplorerSidebar = memo((props: Props) => {
<div
className={classNames({
[`js-entity-explorer t--entity-explorer transform transition-all flex h-full duration-400 border-r border-gray-200 ${tailwindLayers.entityExplorer}`]: true,
"relative ": pinned && !isPreviewMode,
relative: pinned && !isPreviewMode,
"-translate-x-full": (!pinned && !active) || isPreviewMode,
"shadow-xl": !pinned,
fixed: !pinned || isPreviewMode,
@ -199,7 +156,7 @@ export const EntityExplorerSidebar = memo((props: Props) => {
>
{/* SIDEBAR */}
<div
className="flex flex-col p-0 overflow-y-auto bg-white t--sidebar min-w-48 max-w-96 group"
className="flex flex-col p-0 bg-white t--sidebar min-w-48 max-w-96 group"
ref={sidebarRef}
style={{ width: props.width }}
>
@ -209,19 +166,8 @@ export const EntityExplorerSidebar = memo((props: Props) => {
<Pages />
{/* Popover that contains the bindings info */}
<EntityProperties />
{/* SWITCHER */}
<div
className={`px-3 mt-1 py-2 border-t border-b border-[${Colors.Gallery}]`}
>
<Switcher activeObj={activeSwitch} switches={switches} />
</div>
<PanelStack
className="flex-grow"
initialPanel={{
component: Explorer,
}}
showPanelHeader={false}
/>
{/* Contains entity explorer & widgets library along with a switcher*/}
<Explorer />
<AppComments />
</div>
{/* RESIZER */}

View File

@ -1,2 +1,3 @@
export const ENTITY_EXPLORER_SEARCH_ID = "entity-explorer-search";
export const WIDGETS_SEARCH_ID = "#widgets-search";
export const SEARCH_ENTITY = "search-entity";

View File

@ -220,11 +220,11 @@ export const Entity = forwardRef(
/* eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
if (props.isDefaultExpanded) {
if (props.isDefaultExpanded || props.searchKeyword) {
open(true);
props.onToggle && props.onToggle(true);
}
}, [props.isDefaultExpanded]);
}, [props.isDefaultExpanded, props.searchKeyword]);
useEffect(() => {
if (!props.searchKeyword && !props.isDefaultExpanded) {
open(false);

View File

@ -8,9 +8,7 @@ import React, {
import styled from "styled-components";
import Divider from "components/editorComponents/Divider";
import Search from "./ExplorerSearch";
import { NonIdealState, Classes, IPanelProps } from "@blueprintjs/core";
import WidgetSidebar from "../WidgetSidebar";
import history from "utils/history";
import { NonIdealState, Classes } from "@blueprintjs/core";
import JSDependencies from "./JSDependencies";
import PerformanceTracker, {
PerformanceTransactionName,
@ -29,6 +27,8 @@ import Datasources from "./Datasources";
import Files from "./Files";
import ExplorerWidgetGroup from "./Widgets/WidgetGroup";
import { builderURL } from "RouteBuilder";
import history from "utils/history";
import { SEARCH_ENTITY } from "constants/Explorer";
const Wrapper = styled.div`
height: 100%;
@ -71,7 +71,7 @@ const StyledDivider = styled(Divider)`
border-bottom-color: #f0f0f0;
`;
function EntityExplorer(props: IPanelProps) {
function EntityExplorer({ isActive }: { isActive: boolean }) {
const dispatch = useDispatch();
const [searchKeyword, setSearchKeyword] = useState("");
const searchInputRef: MutableRefObject<HTMLInputElement | null> = useRef(
@ -86,15 +86,13 @@ function EntityExplorer(props: IPanelProps) {
getIsFirstTimeUserOnboardingEnabled,
);
const noResults = false;
const { openPanel } = props;
const showWidgetsSidebar = useCallback(() => {
history.push(builderURL());
openPanel({ component: WidgetSidebar });
dispatch(forceOpenWidgetPanel(true));
if (isFirstTimeUserOnboardingEnabled) {
dispatch(toggleInOnboardingWidgetSelection(true));
}
}, [openPanel, isFirstTimeUserOnboardingEnabled]);
}, [isFirstTimeUserOnboardingEnabled]);
/**
* filter entitites
@ -112,10 +110,14 @@ function EntityExplorer(props: IPanelProps) {
};
return (
<Wrapper className={"relative"} ref={explorerRef}>
<Wrapper
className={`relative overflow-y-auto ${isActive ? "" : "hidden"}`}
ref={explorerRef}
>
{/* SEARCH */}
<Search
clear={clearSearchInput}
id={SEARCH_ENTITY}
isHidden
onChange={search}
ref={searchInputRef}

View File

@ -15,6 +15,7 @@ export const ExplorerSearch = forwardRef(
autoFocus?: boolean;
isHidden?: boolean;
onChange?: (e: any) => void;
id?: string;
},
ref: Ref<HTMLInputElement>,
) => {
@ -62,7 +63,7 @@ export const ExplorerSearch = forwardRef(
autoComplete="off"
autoFocus
className="flex-grow py-2 text-gray-800 bg-transparent placeholder-trueGray-500"
id={ENTITY_EXPLORER_SEARCH_ID}
id={props.id || ENTITY_EXPLORER_SEARCH_ID}
onBlur={() => setFocussed(false)}
onChange={onChange}
onFocus={() => setFocussed(true)}
@ -78,8 +79,7 @@ export const ExplorerSearch = forwardRef(
</div>
<div
className={classNames({
"border-b border-primary-500 transition-all duration-400 absolute bottom-0": true,
"w-0": !focussed,
"border-b border-primary-500 absolute bottom-0": true,
"w-full": focussed,
})}
/>

View File

@ -1,23 +1,73 @@
import { IPanelProps } from "@blueprintjs/core";
import React from "react";
import { useEffect } from "react";
import { useSelector } from "react-redux";
import { toggleInOnboardingWidgetSelection } from "actions/onboardingActions";
import { forceOpenWidgetPanel } from "actions/widgetSidebarActions";
import { Switcher } from "components/ads";
import { Colors } from "constants/Colors";
import { tailwindLayers } from "constants/Layers";
import React, { useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { AppState } from "reducers";
import { builderURL } from "RouteBuilder";
import { getIsFirstTimeUserOnboardingEnabled } from "selectors/onboardingSelectors";
import { trimQueryString } from "utils/helpers";
import history from "utils/history";
import WidgetSidebar from "../WidgetSidebar";
import EntityExplorer from "./EntityExplorer";
const isForceOpenWidgetPanelSelector = (state: AppState) =>
const selectForceOpenWidgetPanel = (state: AppState) =>
state.ui.onBoarding.forceOpenWidgetPanel;
function ExplorerContent(props: IPanelProps) {
const isForceOpenWidgetPanel = useSelector(isForceOpenWidgetPanelSelector);
useEffect(() => {
if (isForceOpenWidgetPanel) {
props.openPanel({ component: WidgetSidebar });
}
}, [isForceOpenWidgetPanel]);
function ExplorerContent() {
const dispatch = useDispatch();
const isFirstTimeUserOnboardingEnabled = useSelector(
getIsFirstTimeUserOnboardingEnabled,
);
const switches = useMemo(
() => [
{
id: "explorer",
text: "Explorer",
action: () => dispatch(forceOpenWidgetPanel(false)),
},
{
id: "widgets",
text: "Widgets",
action: () => {
!(trimQueryString(builderURL()) === window.location.pathname) &&
history.push(builderURL());
dispatch(forceOpenWidgetPanel(true));
if (isFirstTimeUserOnboardingEnabled) {
dispatch(toggleInOnboardingWidgetSelection(true));
}
},
},
],
[
dispatch,
forceOpenWidgetPanel,
isFirstTimeUserOnboardingEnabled,
toggleInOnboardingWidgetSelection,
],
);
const [activeSwitch, setActiveSwitch] = useState(switches[0]);
const openWidgetPanel = useSelector(selectForceOpenWidgetPanel);
return <EntityExplorer {...props} />;
useEffect(() => {
setActiveSwitch(switches[openWidgetPanel ? 1 : 0]);
}, [openWidgetPanel]);
return (
<div
className={`flex-1 flex flex-col overflow-hidden ${tailwindLayers.entityExplorer}`}
>
<div
className={`flex-shrink-0 px-3 mt-1 py-2 border-t border-b border-[${Colors.Gallery}]`}
>
<Switcher activeObj={activeSwitch} switches={switches} />
</div>
<WidgetSidebar isActive={activeSwitch.id === "widgets"} />
<EntityExplorer isActive={activeSwitch.id === "explorer"} />
</div>
);
}
export default ExplorerContent;

View File

@ -33,7 +33,6 @@ import {
ONBOARDING_STATUS_STEPS_THIRD_ALT,
} from "@appsmith/constants/messages";
import { getTypographyByKey } from "constants/DefaultTheme";
import { Colors } from "constants/Colors";
import { onboardingCheckListUrl } from "RouteBuilder";

View File

@ -1,22 +1,16 @@
import React, { useRef, useEffect, useState } from "react";
import React, { useRef, useState } from "react";
import { useSelector } from "react-redux";
import WidgetCard from "./WidgetCard";
import { getWidgetCards } from "selectors/editorSelectors";
import { IPanelProps } from "@blueprintjs/core";
import ExplorerSearch from "./Explorer/ExplorerSearch";
import { debounce } from "lodash";
import produce from "immer";
import { useLocation } from "react-router";
import {
createMessage,
WIDGET_SIDEBAR_CAPTION,
} from "@appsmith/constants/messages";
import { matchBuilderPath } from "constants/routes";
import { AppState } from "reducers";
function WidgetSidebar(props: IPanelProps) {
const location = useLocation();
function WidgetSidebar({ isActive }: { isActive: boolean }) {
const cards = useSelector(getWidgetCards);
const [filteredCards, setFilteredCards] = useState(cards);
const searchInputRef = useRef<HTMLInputElement | null>(null);
@ -33,17 +27,6 @@ function WidgetSidebar(props: IPanelProps) {
}
setFilteredCards(filteredCards);
};
const isForceOpenWidgetPanel = useSelector(
(state: AppState) => state.ui.onBoarding.forceOpenWidgetPanel,
);
const onCanvas = matchBuilderPath(window.location.pathname);
useEffect(() => {
if (!onCanvas || isForceOpenWidgetPanel === false) {
props.closePanel();
}
}, [onCanvas, location, isForceOpenWidgetPanel]);
/**
* filter widgets
@ -64,7 +47,9 @@ function WidgetSidebar(props: IPanelProps) {
};
return (
<div className="flex flex-col overflow-hidden">
<div
className={`flex flex-col overflow-hidden ${isActive ? "" : "hidden"}`}
>
<ExplorerSearch
autoFocus
clear={clearSearchInput}

View File

@ -25,6 +25,7 @@ import {
} from "./StyledComponents";
import { getCurrentUser as refreshCurrentUser } from "actions/authActions";
import { getAppsmithConfigs } from "@appsmith/configs";
import { ANONYMOUS_USERNAME } from "constants/userConstants";
const { disableLoginForm } = getAppsmithConfigs();
const ForgotPassword = styled.a`
@ -73,6 +74,8 @@ function General() {
dispatch(refreshCurrentUser());
}, []);
if (user?.email === ANONYMOUS_USERNAME) return null;
return (
<Wrapper>
<FieldWrapper>

View File

@ -128,7 +128,12 @@ import {
collisionCheckPostReflow,
getBottomRowAfterReflow,
} from "utils/reflowHookUtils";
import { PrevReflowState, ReflowDirection, SpaceMap } from "reflow/reflowTypes";
import {
GridProps,
PrevReflowState,
ReflowDirection,
SpaceMap,
} from "reflow/reflowTypes";
import { WidgetSpace } from "constants/CanvasEditorConstants";
import { reflow } from "reflow";
import { getBottomMostRow } from "reflow/reflowUtils";
@ -845,7 +850,6 @@ export function calculateNewWidgetPosition(
* @param copiedTotalWidth total width of the copied widgets
* @param copiedTopMostRow top row of the top most copied widget
* @param copiedLeftMostColumn left column of the left most copied widget
* @param shouldGroup boolean to indicate if the user is grouping instead of pasting
* @returns
*/
const getNewPositions = function*(
@ -854,11 +858,7 @@ const getNewPositions = function*(
copiedTotalWidth: number,
copiedTopMostRow: number,
copiedLeftMostColumn: number,
shouldGroup: boolean,
) {
// if it is grouping instead of pasting then skip the pasting logic
if (shouldGroup) return {};
const selectedWidgetIDs: string[] = yield select(getSelectedWidgets);
const canvasWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
const {
@ -1187,16 +1187,18 @@ function* pasteWidgetSaga(
let widgets: CanvasWidgetsReduxState = canvasWidgets;
const selectedWidget: FlattenedWidgetProps<undefined> = yield getSelectedWidgetWhenPasting();
let reflowedMovementMap,
bottomMostRow: number | undefined,
gridProps: GridProps | undefined,
newPastingPositionMap: SpaceMap | undefined,
canvasId;
let pastingIntoWidgetId: string = yield getParentWidgetIdForPasting(
canvasWidgets,
selectedWidget,
);
let isThereACollision: boolean = yield isSelectedWidgetsColliding(
widgets,
copiedWidgetGroups,
pastingIntoWidgetId,
);
let isThereACollision = false;
// if this is true, selected widgets will be grouped in container
if (shouldGroup) {
@ -1204,7 +1206,6 @@ function* pasteWidgetSaga(
pastingIntoWidgetId = yield getParentWidgetIdForGrouping(
widgets,
copiedWidgetGroups,
pastingIntoWidgetId,
);
widgets = yield filterOutSelectedWidgets(
copiedWidgetGroups[0].parentId,
@ -1216,10 +1217,20 @@ function* pasteWidgetSaga(
pastingIntoWidgetId,
);
copiedWidgetGroups = yield groupWidgetsIntoContainer(
//while grouping, the container around the selected widgets will increase by 2 rows,
//hence if there are any widgets in that path then we reflow those widgets
// If there are already widgets inside the selection box even before grouping
//then we will have to move it down to the bottom most row
({
bottomMostRow,
copiedWidgetGroups,
gridProps,
reflowedMovementMap,
} = yield groupWidgetsIntoContainer(
copiedWidgetGroups,
pastingIntoWidgetId,
);
isThereACollision,
));
} else if (isCopiedModalWidget(copiedWidgetGroups, widgets)) {
pastingIntoWidgetId = MAIN_CONTAINER_WIDGET_ID;
}
@ -1242,25 +1253,27 @@ function* pasteWidgetSaga(
widgets,
);
// new pasting positions, the variables are undefined if the positions cannot be calculated,
// then it pastes the regular way at the bottom of the canvas
const {
bottomMostRow,
canvasId,
gridProps,
newPastingPositionMap,
reflowedMovementMap,
}: NewPastePositionVariables = yield call(
getNewPositions,
copiedWidgetGroups,
action.payload.mouseLocation,
copiedTotalWidth,
topMostWidget.topRow,
leftMostWidget.leftColumn,
shouldGroup,
);
// skip new position calculation if grouping
if (!shouldGroup) {
// new pasting positions, the variables are undefined if the positions cannot be calculated,
// then it pastes the regular way at the bottom of the canvas
({
bottomMostRow,
canvasId,
gridProps,
newPastingPositionMap,
reflowedMovementMap,
} = yield call(
getNewPositions,
copiedWidgetGroups,
action.payload.mouseLocation,
copiedTotalWidth,
topMostWidget.topRow,
leftMostWidget.leftColumn,
));
if (canvasId) pastingIntoWidgetId = canvasId;
if (canvasId) pastingIntoWidgetId = canvasId;
}
yield all(
copiedWidgetGroups.map((copiedWidgets) =>

View File

@ -33,15 +33,24 @@ import {
import { getNextEntityName } from "utils/AppsmithUtils";
import WidgetFactory from "utils/WidgetFactory";
import { getParentWithEnhancementFn } from "./WidgetEnhancementHelpers";
import { OccupiedSpace } from "constants/CanvasEditorConstants";
import { OccupiedSpace, WidgetSpace } from "constants/CanvasEditorConstants";
import { areIntersecting } from "utils/WidgetPropsUtils";
import { GridProps, ReflowedSpaceMap, SpaceMap } from "reflow/reflowTypes";
import {
GridProps,
PrevReflowState,
ReflowDirection,
ReflowedSpaceMap,
SpaceMap,
} from "reflow/reflowTypes";
import {
getBaseWidgetClassName,
getSlidingCanvasName,
getStickyCanvasName,
POSITIONED_WIDGET,
} from "constants/componentClassNameConstants";
import { getWidgetSpacesSelectorForContainer } from "selectors/editorSelectors";
import { reflow } from "reflow";
import { getBottomRowAfterReflow } from "utils/reflowHookUtils";
export interface CopiedWidgetGroup {
widgetId: string;
@ -992,6 +1001,7 @@ export function isDropTarget(type: WidgetType, includeCanvasWidget = false) {
export const groupWidgetsIntoContainer = function*(
copiedWidgetGroups: CopiedWidgetGroup[],
pastingIntoWidgetId: string,
isThereACollision: boolean,
) {
const containerWidgetId = generateReactKey();
const evalTree: DataTree = yield select(getDataTree);
@ -1006,6 +1016,7 @@ export const groupWidgetsIntoContainer = function*(
"CANVAS_WIDGET",
evalTree,
);
let reflowedMovementMap, bottomMostRow, gridProps;
const {
bottomMostWidget,
leftMostWidget,
@ -1018,8 +1029,12 @@ export const groupWidgetsIntoContainer = function*(
(w) => w.widgetId === copiedWidgetGroup.widgetId,
),
);
//calculating parentColumnSpace because the values stored inside widget DSL are not entirely reliable
const parentColumnSpace =
copiedWidgetGroups[0].list[0].parentColumnSpace || 1;
getParentColumnSpace(canvasWidgets, pastingIntoWidgetId) ||
copiedWidgetGroups[0].list[0].parentColumnSpace ||
1;
const boundary = {
top: _.minBy(copiedWidgets, (copiedWidget) => copiedWidget?.topRow),
@ -1124,13 +1139,73 @@ export const groupWidgetsIntoContainer = function*(
const flatList = _.flattenDeep(list);
return [
{
list: [newContainerWidget, newCanvasWidget, ...flatList],
widgetId: newContainerWidget.widgetId,
parentId: pastingIntoWidgetId,
},
];
// if there are no collision already then reflow the below widgets by 2 rows.
if (!isThereACollision) {
const widgetSpacesSelector = getWidgetSpacesSelectorForContainer(
pastingIntoWidgetId,
);
const widgetSpaces: WidgetSpace[] = yield select(widgetSpacesSelector) ||
[];
const copiedWidgetIds = copiedWidgets
.map((widget) => widget?.widgetId)
.filter((id) => !!id);
// filter out copiedWidgets from occupied spaces
const widgetOccupiedSpaces = widgetSpaces.filter(
(widgetSpace) => copiedWidgetIds.indexOf(widgetSpace.id) === -1,
);
// create the object of the new container in the form of OccupiedSpace
const containerSpace = {
id: "1",
left: newContainerWidget.leftColumn,
top: newContainerWidget.topRow,
right: newContainerWidget.rightColumn,
bottom: newContainerWidget.bottomRow,
};
gridProps = {
parentColumnSpace,
parentRowSpace: GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
maxGridColumns: GridDefaults.DEFAULT_GRID_COLUMNS,
};
//get movement map of reflowed widgets
const { movementMap } = reflow(
[containerSpace],
[containerSpace],
widgetOccupiedSpaces,
ReflowDirection.BOTTOM,
gridProps,
true,
false,
{ prevSpacesMap: {} } as PrevReflowState,
);
reflowedMovementMap = movementMap;
//get the new calculated bottom row
bottomMostRow = getBottomRowAfterReflow(
reflowedMovementMap,
containerSpace.bottom,
widgetOccupiedSpaces,
gridProps,
);
}
return {
reflowedMovementMap,
bottomMostRow,
gridProps,
copiedWidgetGroups: [
{
list: [newContainerWidget, newCanvasWidget, ...flatList],
widgetId: newContainerWidget.widgetId,
parentId: pastingIntoWidgetId,
},
],
};
};
/**
@ -1225,27 +1300,20 @@ export const isSelectedWidgetsColliding = function*(
widget.parentId === pastingIntoWidgetId && widget.type !== "MODAL_WIDGET",
);
let isColliding = false;
for (let i = 0; i < widgetsArray.length; i++) {
const widget = widgetsArray[i];
if (
widget.bottomRow + 2 < topMostWidget.topRow ||
widget.topRow > bottomMostWidget.bottomRow
) {
isColliding = false;
} else if (
widget.rightColumn < leftMostWidget.leftColumn ||
widget.leftColumn > rightMostWidget.rightColumn
) {
isColliding = false;
} else {
!(
widget.leftColumn >= rightMostWidget.rightColumn ||
widget.rightColumn <= leftMostWidget.leftColumn ||
widget.topRow >= bottomMostWidget.bottomRow ||
widget.bottomRow <= topMostWidget.topRow
)
)
return true;
}
}
return isColliding;
return false;
};
/**
@ -1351,8 +1419,8 @@ export const getParentBottomRowAfterAddingWidget = (
export function* getParentWidgetIdForGrouping(
widgets: CanvasWidgetsReduxState,
copiedWidgetGroups: CopiedWidgetGroup[],
pastingIntoWidgetId: string,
) {
const pastingIntoWidgetId = copiedWidgetGroups[0]?.parentId;
const widgetIds = copiedWidgetGroups.map(
(widgetGroup) => widgetGroup.widgetId,
);
@ -1445,6 +1513,33 @@ export function purgeOrphanedDynamicPaths(widget: WidgetProps) {
return widget;
}
/**
*
* @param canvasWidgets
* @param pastingIntoWidgetId
* @returns
*/
export function getParentColumnSpace(
canvasWidgets: CanvasWidgetsReduxState,
pastingIntoWidgetId: string,
) {
const containerId = getContainerIdForCanvas(pastingIntoWidgetId);
const containerWidget = canvasWidgets[containerId];
const canvasDOM = document.querySelector(
`#${getSlidingCanvasName(pastingIntoWidgetId)}`,
);
if (!canvasDOM || !containerWidget) return;
const rect = canvasDOM.getBoundingClientRect();
// get Grid values such as snapRowSpace and snapColumnSpace
const { snapGrid } = getSnappedGrid(containerWidget, rect.width);
return snapGrid?.snapColumnSpace;
}
/*
* Function to extend the lodash's get function to check
* paths which have dots in it's key

View File

@ -62,7 +62,8 @@ describe("#parse", () => {
);
});
it("returns unmodified schema when existing field's value in data source changes to null/undefined", () => {
it("returns unmodified schema when existing field's value in data source changes to null and back", () => {
// Get the initial schema
const initialSchema = SchemaParser.parse(widgetName, {
currSourceData: testData.initialDataset.dataSource,
schema: {},
@ -71,45 +72,390 @@ describe("#parse", () => {
expect(initialSchema).toEqual(testData.initialDataset.schemaOutput);
// With null field
const nulledDataSource = klona(testData.initialDataset.dataSource);
set(nulledDataSource, "dob", null);
// Set all keys to null
const nulledSourceData = klona(testData.initialDataset.dataSource);
set(nulledSourceData, "name", null);
set(nulledSourceData, "age", null);
set(nulledSourceData, "dob", null);
set(nulledSourceData, "boolean", null);
set(nulledSourceData, "hobbies", null);
set(nulledSourceData, "%%", null);
set(nulledSourceData, "हिन्दि", null);
set(nulledSourceData, "education", null);
set(nulledSourceData, "address", null);
const expectedNulledSchema = klona(initialSchema);
set(expectedNulledSchema, "__root_schema__.children.dob.sourceData", null);
set(expectedNulledSchema, "__root_schema__.sourceData.dob", null);
// Set the sourceData entry in each SchemaItem to null (only property that changes)
const expectedSchema = klona(initialSchema);
set(expectedSchema, "__root_schema__.children.name.sourceData", null);
set(expectedSchema, "__root_schema__.sourceData.name", null);
set(expectedSchema, "__root_schema__.children.age.sourceData", null);
set(expectedSchema, "__root_schema__.sourceData.age", null);
set(expectedSchema, "__root_schema__.children.dob.sourceData", null);
set(expectedSchema, "__root_schema__.sourceData.dob", null);
set(expectedSchema, "__root_schema__.children.boolean.sourceData", null);
set(expectedSchema, "__root_schema__.sourceData.boolean", null);
set(expectedSchema, "__root_schema__.children.hobbies.sourceData", null);
set(expectedSchema, "__root_schema__.sourceData.hobbies", null);
set(expectedSchema, "__root_schema__.children.education.sourceData", null);
set(expectedSchema, "__root_schema__.sourceData.education", null);
set(expectedSchema, "__root_schema__.children.__.sourceData", null);
set(expectedSchema, "__root_schema__.sourceData['%%']", null);
set(
expectedSchema,
"__root_schema__.children.xn__j2bd4cyac6f.sourceData",
null,
);
set(expectedSchema, "__root_schema__.sourceData.हिन्दि", null);
set(expectedSchema, "__root_schema__.children.address.sourceData", null);
set(expectedSchema, "__root_schema__.sourceData.address", null);
const schemaWithNulledField = SchemaParser.parse(widgetName, {
currSourceData: nulledDataSource,
// Parse with the nulled sourceData
const schemaWithNullKeys = SchemaParser.parse(widgetName, {
currSourceData: nulledSourceData,
schema: initialSchema,
fieldThemeStylesheets: testData.fieldThemeStylesheets,
});
expect(schemaWithNulledField).toEqual(expectedNulledSchema);
expect(schemaWithNullKeys).toEqual(expectedSchema);
// With undefined field
const undefinedDataSource = klona(nulledDataSource);
set(undefinedDataSource, "boolean", undefined);
const expectedUndefinedSchema = klona(expectedNulledSchema);
set(
expectedUndefinedSchema,
"__root_schema__.children.boolean.sourceData",
undefined,
);
set(
expectedUndefinedSchema,
"__root_schema__.sourceData.boolean",
undefined,
);
const schemaWithUndefinedField = SchemaParser.parse(widgetName, {
currSourceData: undefinedDataSource,
schema: schemaWithNulledField,
/**
* Parse with initial sourceData to check if previous schema with null sourceData
* can still retain the schema structure
*/
const schemaWithRevertedData = SchemaParser.parse(widgetName, {
currSourceData: testData.initialDataset.dataSource,
schema: schemaWithNullKeys,
fieldThemeStylesheets: testData.fieldThemeStylesheets,
});
expect(schemaWithUndefinedField).toEqual(expectedUndefinedSchema);
expect(schemaWithRevertedData).toEqual(
testData.initialDataset.schemaOutput,
);
});
it("returns unmodified schema when existing fields value in data source changes to undefined and back", () => {
// Get the initial schema
const initialSchema = SchemaParser.parse(widgetName, {
currSourceData: testData.initialDataset.dataSource,
schema: {},
fieldThemeStylesheets: testData.fieldThemeStylesheets,
});
expect(initialSchema).toEqual(testData.initialDataset.schemaOutput);
// Set all keys to undefined
const undefinedDataSource = klona(testData.initialDataset.dataSource);
set(undefinedDataSource, "name", undefined);
set(undefinedDataSource, "age", undefined);
set(undefinedDataSource, "dob", undefined);
set(undefinedDataSource, "boolean", undefined);
set(undefinedDataSource, "hobbies", undefined);
set(undefinedDataSource, "%%", undefined);
set(undefinedDataSource, "हिन्दि", undefined);
set(undefinedDataSource, "education", undefined);
set(undefinedDataSource, "address", undefined);
// Set the sourceData entry in each SchemaItem to undefined (only property that changes)
const expectedSchema = klona(initialSchema);
set(expectedSchema, "__root_schema__.children.name.sourceData", undefined);
set(expectedSchema, "__root_schema__.sourceData.name", undefined);
set(expectedSchema, "__root_schema__.children.age.sourceData", undefined);
set(expectedSchema, "__root_schema__.sourceData.age", undefined);
set(expectedSchema, "__root_schema__.children.dob.sourceData", undefined);
set(expectedSchema, "__root_schema__.sourceData.dob", undefined);
set(
expectedSchema,
"__root_schema__.children.boolean.sourceData",
undefined,
);
set(expectedSchema, "__root_schema__.sourceData.boolean", undefined);
set(
expectedSchema,
"__root_schema__.children.hobbies.sourceData",
undefined,
);
set(expectedSchema, "__root_schema__.sourceData.hobbies", undefined);
set(
expectedSchema,
"__root_schema__.children.education.sourceData",
undefined,
);
set(expectedSchema, "__root_schema__.sourceData.education", undefined);
set(expectedSchema, "__root_schema__.children.__.sourceData", undefined);
set(expectedSchema, "__root_schema__.sourceData['%%']", undefined);
set(
expectedSchema,
"__root_schema__.children.xn__j2bd4cyac6f.sourceData",
undefined,
);
set(expectedSchema, "__root_schema__.sourceData.हिन्दि", undefined);
set(
expectedSchema,
"__root_schema__.children.address.sourceData",
undefined,
);
set(expectedSchema, "__root_schema__.sourceData.address", undefined);
// Parse with the undefined sourceData keys
const schemaWithUndefinedKeys = SchemaParser.parse(widgetName, {
currSourceData: undefinedDataSource,
schema: initialSchema,
fieldThemeStylesheets: testData.fieldThemeStylesheets,
});
expect(schemaWithUndefinedKeys).toEqual(expectedSchema);
/**
* Parse with initial sourceData to check if previous schema with null sourceData
* can still retain the schema structure
*/
const schemaWithRevertedData = SchemaParser.parse(widgetName, {
currSourceData: testData.initialDataset.dataSource,
schema: schemaWithUndefinedKeys,
fieldThemeStylesheets: testData.fieldThemeStylesheets,
});
expect(schemaWithRevertedData).toEqual(
testData.initialDataset.schemaOutput,
);
});
it("returns unmodified schema when existing inner field's value in data source changes to null and back", () => {
// Get the initial schema
const initialSchema = SchemaParser.parse(widgetName, {
currSourceData: testData.initialDataset.dataSource,
schema: {},
fieldThemeStylesheets: testData.fieldThemeStylesheets,
});
expect(initialSchema).toEqual(testData.initialDataset.schemaOutput);
// Set all keys to null
const nulledSourceData = klona(testData.initialDataset.dataSource);
set(nulledSourceData, "address.Line1", null);
set(nulledSourceData, "address.city", null);
set(nulledSourceData, "education[0].college", null);
set(nulledSourceData, "education[0].number", null);
set(nulledSourceData, "education[0].graduationDate", null);
set(nulledSourceData, "education[0].boolean", null);
// Set the sourceData entry in each SchemaItem to null (only property that changes)
const expectedSchema = klona(initialSchema);
set(
expectedSchema,
"__root_schema__.children.address.children.Line1.sourceData",
null,
);
set(expectedSchema, "__root_schema__.sourceData.address.Line1", null);
set(
expectedSchema,
"__root_schema__.children.address.children.city.sourceData",
null,
);
set(expectedSchema, "__root_schema__.sourceData.address.city", null);
set(
expectedSchema,
"__root_schema__.children.education.children.__array_item__.children.college.sourceData",
null,
);
set(expectedSchema, "__root_schema__.children.address.sourceData", {
Line1: null,
city: null,
});
set(
expectedSchema,
"__root_schema__.sourceData.education[0].college",
null,
);
set(
expectedSchema,
"__root_schema__.children.education.children.__array_item__.children.number.sourceData",
null,
);
set(expectedSchema, "__root_schema__.sourceData.education[0].number", null);
set(
expectedSchema,
"__root_schema__.children.education.children.__array_item__.children.graduationDate.sourceData",
null,
);
set(
expectedSchema,
"__root_schema__.sourceData.education[0].graduationDate",
null,
);
set(
expectedSchema,
"__root_schema__.children.education.children.__array_item__.children.boolean.sourceData",
null,
);
set(
expectedSchema,
"__root_schema__.sourceData.education[0].boolean",
null,
);
set(expectedSchema, "__root_schema__.children.education.sourceData", [
{
college: null,
number: null,
graduationDate: null,
boolean: null,
},
]);
set(
expectedSchema,
"__root_schema__.children.education.children.__array_item__.sourceData",
{
college: null,
number: null,
graduationDate: null,
boolean: null,
},
);
// Parse with the nulled sourceData
const schemaWithNullKeys = SchemaParser.parse(widgetName, {
currSourceData: nulledSourceData,
schema: initialSchema,
fieldThemeStylesheets: testData.fieldThemeStylesheets,
});
expect(schemaWithNullKeys).toEqual(expectedSchema);
/**
* Parse with initial sourceData to check if previous schema with null sourceData
* can still retain the schema structure
*/
const schemaWithRevertedData = SchemaParser.parse(widgetName, {
currSourceData: testData.initialDataset.dataSource,
schema: schemaWithNullKeys,
fieldThemeStylesheets: testData.fieldThemeStylesheets,
});
expect(schemaWithRevertedData).toEqual(
testData.initialDataset.schemaOutput,
);
});
it("returns unmodified schema when existing inner field's value in data source changes to undefined and back", () => {
// Get the initial schema
const initialSchema = SchemaParser.parse(widgetName, {
currSourceData: testData.initialDataset.dataSource,
schema: {},
fieldThemeStylesheets: testData.fieldThemeStylesheets,
});
expect(initialSchema).toEqual(testData.initialDataset.schemaOutput);
// Set all keys to undefined
const undefinedSourceData = klona(testData.initialDataset.dataSource);
set(undefinedSourceData, "address.Line1", undefined);
set(undefinedSourceData, "address.city", undefined);
set(undefinedSourceData, "education[0].college", undefined);
set(undefinedSourceData, "education[0].number", undefined);
set(undefinedSourceData, "education[0].graduationDate", undefined);
set(undefinedSourceData, "education[0].boolean", undefined);
// Set the sourceData entry in each SchemaItem to undefined (only property that changes)
const expectedSchema = klona(initialSchema);
set(
expectedSchema,
"__root_schema__.children.address.children.Line1.sourceData",
undefined,
);
set(expectedSchema, "__root_schema__.sourceData.address.Line1", undefined);
set(
expectedSchema,
"__root_schema__.children.address.children.city.sourceData",
undefined,
);
set(expectedSchema, "__root_schema__.sourceData.address.city", undefined);
set(
expectedSchema,
"__root_schema__.children.education.children.__array_item__.children.college.sourceData",
undefined,
);
set(expectedSchema, "__root_schema__.children.address.sourceData", {
Line1: undefined,
city: undefined,
});
set(
expectedSchema,
"__root_schema__.sourceData.education[0].college",
undefined,
);
set(
expectedSchema,
"__root_schema__.children.education.children.__array_item__.children.number.sourceData",
undefined,
);
set(
expectedSchema,
"__root_schema__.sourceData.education[0].number",
undefined,
);
set(
expectedSchema,
"__root_schema__.children.education.children.__array_item__.children.graduationDate.sourceData",
undefined,
);
set(
expectedSchema,
"__root_schema__.sourceData.education[0].graduationDate",
undefined,
);
set(
expectedSchema,
"__root_schema__.children.education.children.__array_item__.children.boolean.sourceData",
undefined,
);
set(
expectedSchema,
"__root_schema__.sourceData.education[0].boolean",
undefined,
);
set(expectedSchema, "__root_schema__.children.education.sourceData", [
{
college: undefined,
number: undefined,
graduationDate: undefined,
boolean: undefined,
},
]);
set(
expectedSchema,
"__root_schema__.children.education.children.__array_item__.sourceData",
{
college: undefined,
number: undefined,
graduationDate: undefined,
boolean: undefined,
},
);
// Parse with the undefined sourceData
const schemaWithUndefinedKeys = SchemaParser.parse(widgetName, {
currSourceData: undefinedSourceData,
schema: initialSchema,
fieldThemeStylesheets: testData.fieldThemeStylesheets,
});
expect(schemaWithUndefinedKeys).toEqual(expectedSchema);
/**
* Parse with initial sourceData to check if previous schema with undefined sourceData
* can still retain the schema structure
*/
const schemaWithRevertedData = SchemaParser.parse(widgetName, {
currSourceData: testData.initialDataset.dataSource,
schema: schemaWithUndefinedKeys,
fieldThemeStylesheets: testData.fieldThemeStylesheets,
});
expect(schemaWithRevertedData).toEqual(
testData.initialDataset.schemaOutput,
);
});
});

View File

@ -27,11 +27,10 @@ import {
} from "./constants";
import { getFieldStylesheet } from "./helper";
type Obj = Record<string, any>;
type JSON = Obj | Obj[];
type Obj = Record<string, unknown>;
type ParserOptions = {
currSourceData?: JSON | string;
currSourceData?: unknown;
fieldThemeStylesheets?: FieldThemeStylesheet;
fieldType?: FieldType;
isCustomField?: boolean;
@ -60,11 +59,15 @@ type GetKeysFromSchemaOptions = {
};
type ParseOptions = {
currSourceData?: JSON;
currSourceData?: unknown;
schema?: Schema;
fieldThemeStylesheets?: FieldThemeStylesheet;
};
function isObject(val: unknown): val is Obj {
return typeof val === "object" && !Array.isArray(val) && val !== null;
}
/**
*
* This method takes in array of object and squishes every object in the
@ -604,7 +607,7 @@ class SchemaParser {
// This method deals with the conversion of array data to a schema
static convertArrayToSchema = ({
currSourceData = [],
currSourceData,
fieldThemeStylesheets,
prevSchema = {},
sourceDataPath,
@ -612,7 +615,12 @@ class SchemaParser {
...rest
}: Omit<ParserOptions, "identifier">): Schema => {
const schema = klona(prevSchema);
const currData = normalizeArrayValue(currSourceData as any[]);
if (!Array.isArray(currSourceData)) {
return schema;
}
const currData = normalizeArrayValue(currSourceData);
const prevDataType = schema[ARRAY_ITEM_KEY]?.dataType;
const currDataType = dataTypeFor(currData);
@ -644,7 +652,7 @@ class SchemaParser {
// This method deals with the conversion of object data to a schema
static convertObjectToSchema = ({
currSourceData = {},
currSourceData,
prevSchema = {},
sourceDataPath,
widgetName,
@ -654,8 +662,10 @@ class SchemaParser {
const origIdentifierToIdentifierMap = mapOriginalIdentifierToSanitizedIdentifier(
schema,
);
const currObj = currSourceData as Obj;
if (!isObject(currSourceData)) {
return schema;
}
const customFieldAccessors = getKeysFromSchema(prevSchema, ["accessor"], {
onlyCustomFieldKeys: true,
});
@ -683,7 +693,7 @@ class SchemaParser {
modifiedKeys.forEach((modifiedKey) => {
const identifier = origIdentifierToIdentifierMap[modifiedKey];
const prevSchemaItem = klona(schema[identifier]);
const currData = currObj[modifiedKey];
const currData = currSourceData[modifiedKey];
const prevData = prevSchemaItem.sourceData;
const currDataType = dataTypeFor(currData);
const prevDataType = schema[identifier].dataType;
@ -743,7 +753,7 @@ class SchemaParser {
newKeys.forEach((newKey) => {
const schemaItem = SchemaParser.getSchemaItemFor(newKey, {
...rest,
currSourceData: currObj[newKey],
currSourceData: currSourceData[newKey],
sourceDataPath: getSourcePath(newKey, sourceDataPath),
identifier: sanitizeSchemaItemKey(newKey, schema),
widgetName,

View File

@ -106,7 +106,7 @@ class PhoneInputWidget extends BaseInputWidget<
propertyName: "defaultText",
label: "Default Text",
controlType: "INPUT_TEXT",
placeholderText: "John Doe",
placeholderText: "(000) 000-0000",
isBindProperty: true,
isTriggerProperty: false,
validation: {
@ -115,7 +115,7 @@ class PhoneInputWidget extends BaseInputWidget<
fn: defaultValueValidation,
expected: {
type: "string",
example: `000 0000`,
example: `(000) 000-0000`,
autocompleteDataType: AutocompleteDataType.STRING,
},
},

View File

@ -89,13 +89,7 @@ public class ApplicationControllerCE extends BaseController<ApplicationService,
public Mono<ResponseDTO<Boolean>> publish(@PathVariable String defaultApplicationId,
@RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName) {
return applicationPageService.publish(defaultApplicationId, branchName, true)
.flatMap(application ->
// This event should parallel a similar event sent from the client, so we want it to be sent by the
// controller and not the service method.
applicationPageService.sendApplicationPublishedEvent(application)
// This will only be called when the publishing was successful, so we can always return `true` here.
.thenReturn(new ResponseDTO<>(HttpStatus.OK.value(), true, null))
);
.thenReturn(new ResponseDTO<>(HttpStatus.OK.value(), true, null));
}
@PutMapping("/{defaultApplicationId}/page/{defaultPageId}/makeDefault")

View File

@ -51,8 +51,6 @@ public interface ApplicationPageServiceCE {
void generateAndSetPagePolicies(Application application, PageDTO page);
Mono<Void> sendApplicationPublishedEvent(Application application);
Mono<ApplicationPagesDTO> reorderPage(String applicationId, String pageId, Integer order, String branchName);
Mono<Application> deleteApplicationByResource(Application application);

View File

@ -892,7 +892,7 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
application -> themeService.publishTheme(application.getId())
);
Flux<NewPage> publishApplicationAndPages = applicationMono
Mono<List<NewPage>> publishApplicationAndPages = applicationMono
//Return all the pages in the Application
.flatMap(application -> {
List<ApplicationPage> pages = application.getPages();
@ -952,10 +952,11 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
page.setPublishedPage(page.getUnpublishedPage());
return page;
}))
.flatMap(newPageService::save)
.collectList()
.flatMapMany(newPageService::saveAll);
.cache(); // caching as we'll need this to send analytics attributes after publishing the app
Flux<NewAction> publishedActionsFlux = newActionService
Mono<List<NewAction>> publishedActionsListMono = newActionService
.findAllByApplicationIdAndViewMode(applicationId, false, MANAGE_ACTIONS, null)
.flatMap(newAction -> {
// If the action was deleted in edit mode, now this document can be safely archived
@ -967,10 +968,11 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
newAction.setPublishedAction(newAction.getUnpublishedAction());
return Mono.just(newAction);
})
.flatMap(newActionService::save)
.collectList()
.flatMapMany(newActionService::saveAll);
.cache(); // caching as we'll need this to send analytics attributes after publishing the app
Flux<ActionCollection> publishedCollectionsFlux = actionCollectionService
Mono<List<ActionCollection>> publishedActionCollectionsListMono = actionCollectionService
.findAllByApplicationIdAndViewMode(applicationId, false, MANAGE_ACTIONS, null)
.flatMap(collection -> {
// If the collection was deleted in edit mode, now this can be safely deleted from the repository
@ -982,16 +984,42 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
collection.setPublishedCollection(collection.getUnpublishedCollection());
return Mono.just(collection);
})
.collectList()
.flatMapMany(actionCollectionService::saveAll);
.flatMap(actionCollectionService::save)
.collectList();
return Mono.when(
publishApplicationAndPages.collectList(),
publishedActionsFlux.collectList(),
publishedCollectionsFlux,
publishApplicationAndPages,
publishedActionsListMono,
publishedActionCollectionsListMono,
publishThemeMono
)
.then(applicationMono);
.then(sendApplicationPublishedEvent(publishApplicationAndPages, publishedActionsListMono, publishedActionCollectionsListMono, applicationId));
}
private Mono<Application> sendApplicationPublishedEvent(Mono<List<NewPage>> publishApplicationAndPages,
Mono<List<NewAction>> publishedActionsFlux,
Mono<List<ActionCollection>> publishedActionsCollectionFlux,
String applicationId) {
return Mono.zip(
publishApplicationAndPages,
publishedActionsFlux,
publishedActionsCollectionFlux,
// not using existing applicationMono because we need the latest Application after published
applicationService.findById(applicationId, MANAGE_APPLICATIONS)
)
.flatMap(objects -> {
Application application = objects.getT4();
Map<String, Object> extraProperties = new HashMap<>();
extraProperties.put("pageCount", objects.getT1().size());
extraProperties.put("queryCount", objects.getT2().size());
extraProperties.put("actionCollectionCount", objects.getT3().size());
extraProperties.put("appId", defaultIfNull(application.getId(), ""));
extraProperties.put("appName", defaultIfNull(application.getName(), ""));
extraProperties.put("orgId", defaultIfNull(application.getOrganizationId(), ""));
extraProperties.put("publishedAt", defaultIfNull(application.getLastDeployedAt(), ""));
return analyticsService.sendObjectEvent(AnalyticsEvents.PUBLISH_APPLICATION, application, extraProperties);
});
}
@Override
@ -1001,34 +1029,6 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
.map(responseUtils::updateApplicationWithDefaultResources);
}
@Override
public Mono<Void> sendApplicationPublishedEvent(Application application) {
if (!analyticsService.isActive()) {
return Mono.empty();
}
return sessionUserService.getCurrentUser()
.flatMap(user -> {
int publishedPageCount = 0;
if(application.getPublishedPages() != null) {
publishedPageCount = application.getPublishedPages().size();
}
analyticsService.sendEvent(
AnalyticsEvents.PUBLISH_APPLICATION.getEventName(),
user.getUsername(),
Map.of(
"appId", defaultIfNull(application.getId(), ""),
"appName", defaultIfNull(application.getName(), ""),
"orgId", defaultIfNull(application.getOrganizationId(), ""),
"pageCount", publishedPageCount + "",
"publishedAt", defaultIfNull(application.getLastDeployedAt(), "")
)
);
return Mono.empty();
});
}
/** This function walks through all the pages and reorders them and updates the order as per the user preference.
* A page can be moved up or down from the current position and accordingly the order of the remaining page changes.
* @param defaultAppId The id of the Application