Merge remote-tracking branch 'origin/release' into fix-11468_dropdown_performance

This commit is contained in:
Preet 2022-03-07 13:13:22 +05:30
commit f4e6341add
55 changed files with 1928 additions and 475 deletions

View File

@ -16,8 +16,7 @@
"**/Smoke_TestSuite/Application/PgAdmin_spec*.js",
"**/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Filter_spec*.js",
"**/Smoke_TestSuite/ClientSideTests/Onboarding/FirstTimeUserOnboarding_spec*.js",
"**/Smoke_TestSuite/ClientSideTests/LayoutValidation/AppPageLayout.spec.js",
"**/Smoke_TestSuite/ClientSideTests/GitSync/*"
"**/Smoke_TestSuite/ClientSideTests/LayoutValidation/AppPageLayout.spec.js"
],
"chromeWebSecurity": false,
"viewportHeight": 900,

View File

@ -0,0 +1,404 @@
{
"dsl": {
"widgetName": "MainContainer",
"backgroundColor": "none",
"rightColumn": 816,
"snapColumns": 64,
"detachFromLayout": true,
"widgetId": "0",
"topRow": 0,
"bottomRow": 760,
"containerStyle": "none",
"snapRows": 73,
"parentRowSpace": 1,
"type": "CANVAS_WIDGET",
"canExtend": true,
"version": 51,
"minHeight": 740,
"parentColumnSpace": 1,
"dynamicBindingPathList": [],
"leftColumn": 0,
"children": [
{
"widgetName": "Table1",
"defaultPageSize": 0,
"columnOrder": [
"id",
"name",
"createdAt",
"updatedAt",
"status",
"gender",
"avatar",
"email",
"address",
"role",
"dob",
"phoneNo"
],
"isVisibleDownload": true,
"dynamicPropertyPathList": [],
"displayName": "Table",
"iconSVG": "/static/media/icon.db8a9cbd.svg",
"topRow": 19,
"bottomRow": 47,
"isSortable": true,
"parentRowSpace": 10,
"type": "TABLE_WIDGET",
"defaultSelectedRow": "0",
"hideCard": false,
"animateLoading": true,
"parentColumnSpace": 12.5625,
"dynamicTriggerPathList": [],
"dynamicBindingPathList": [
{
"key": "primaryColumns.status.computedValue"
},
{
"key": "tableData"
},
{
"key": "primaryColumns.id.computedValue"
},
{
"key": "primaryColumns.name.computedValue"
},
{
"key": "primaryColumns.createdAt.computedValue"
},
{
"key": "primaryColumns.updatedAt.computedValue"
},
{
"key": "primaryColumns.gender.computedValue"
},
{
"key": "primaryColumns.avatar.computedValue"
},
{
"key": "primaryColumns.email.computedValue"
},
{
"key": "primaryColumns.address.computedValue"
},
{
"key": "primaryColumns.role.computedValue"
},
{
"key": "primaryColumns.dob.computedValue"
},
{
"key": "primaryColumns.phoneNo.computedValue"
}
],
"leftColumn": 14,
"primaryColumns": {
"status": {
"index": 2,
"width": 150,
"id": "status",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "PARAGRAPH",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isCellVisible": true,
"isDerived": false,
"label": "status",
"computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.status))}}",
"buttonColor": "#03B365",
"menuColor": "#03B365",
"labelColor": "#FFFFFF"
},
"id": {
"index": 0,
"width": 150,
"id": "id",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "PARAGRAPH",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellVisible": true,
"isDerived": false,
"label": "id",
"computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.id))}}"
},
"name": {
"index": 1,
"width": 150,
"id": "name",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "PARAGRAPH",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellVisible": true,
"isDerived": false,
"label": "name",
"computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.name))}}"
},
"createdAt": {
"index": 2,
"width": 150,
"id": "createdAt",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "PARAGRAPH",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellVisible": true,
"isDerived": false,
"label": "createdAt",
"computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.createdAt))}}"
},
"updatedAt": {
"index": 3,
"width": 150,
"id": "updatedAt",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "PARAGRAPH",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellVisible": true,
"isDerived": false,
"label": "updatedAt",
"computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.updatedAt))}}"
},
"gender": {
"index": 5,
"width": 150,
"id": "gender",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "PARAGRAPH",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellVisible": true,
"isDerived": false,
"label": "gender",
"computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.gender))}}"
},
"avatar": {
"index": 6,
"width": 150,
"id": "avatar",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "PARAGRAPH",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellVisible": true,
"isDerived": false,
"label": "avatar",
"computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.avatar))}}"
},
"email": {
"index": 7,
"width": 150,
"id": "email",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "PARAGRAPH",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellVisible": true,
"isDerived": false,
"label": "email",
"computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.email))}}"
},
"address": {
"index": 8,
"width": 150,
"id": "address",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "PARAGRAPH",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellVisible": true,
"isDerived": false,
"label": "address",
"computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.address))}}"
},
"role": {
"index": 9,
"width": 150,
"id": "role",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "PARAGRAPH",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellVisible": true,
"isDerived": false,
"label": "role",
"computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.role))}}"
},
"dob": {
"index": 10,
"width": 150,
"id": "dob",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "PARAGRAPH",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellVisible": true,
"isDerived": false,
"label": "dob",
"computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.dob))}}"
},
"phoneNo": {
"index": 11,
"width": 150,
"id": "phoneNo",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "PARAGRAPH",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellVisible": true,
"isDerived": false,
"label": "phoneNo",
"computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.phoneNo))}}"
}
},
"delimiter": ",",
"key": "9flri9lh3m",
"derivedColumns": {},
"rightColumn": 48,
"textSize": "PARAGRAPH",
"widgetId": "24cxf11c77",
"isVisibleFilters": true,
"tableData": "{{NoiseTestQuery.data}}",
"isVisible": true,
"label": "Data",
"searchKey": "",
"enableClientSideSearch": true,
"version": 3,
"totalRecordsCount": 0,
"parentId": "0",
"renderMode": "CANVAS",
"isLoading": false,
"horizontalAlignment": "LEFT",
"isVisibleSearch": true,
"isVisiblePagination": true,
"verticalAlignment": "CENTER",
"columnSizeMap": {
"task": 245,
"step": 62,
"status": 75
}
},
{
"widgetName": "Button1",
"onClick": "{{killSession.run()}}",
"buttonColor": "#27647e",
"displayName": "Button",
"iconSVG": "/static/media/icon.cca02633.svg",
"topRow": 49,
"bottomRow": 53,
"parentRowSpace": 10,
"type": "BUTTON_WIDGET",
"hideCard": false,
"animateLoading": true,
"parentColumnSpace": 12.5625,
"dynamicTriggerPathList": [
{
"key": "onClick"
}
],
"leftColumn": 14,
"dynamicBindingPathList": [],
"text": "Kill Session",
"isDisabled": false,
"key": "oyyqr9a87m",
"rightColumn": 29,
"isDefaultClickDisabled": true,
"widgetId": "h1ga2gebsk",
"isVisible": true,
"recaptchaType": "V3",
"version": 1,
"parentId": "0",
"renderMode": "CANVAS",
"isLoading": false,
"buttonVariant": "PRIMARY",
"placement": "CENTER",
"boxShadowColor": "#27647e"
},
{
"widgetName": "Button2",
"onClick": "{{NoiseTestQuery.run()}}",
"buttonColor": "#27647e",
"dynamicPropertyPathList": [],
"displayName": "Button",
"iconSVG": "/static/media/icon.cca02633.svg",
"topRow": 49,
"bottomRow": 53,
"parentRowSpace": 10,
"type": "BUTTON_WIDGET",
"hideCard": false,
"animateLoading": true,
"parentColumnSpace": 12.5625,
"dynamicTriggerPathList": [
{
"key": "onClick"
}
],
"leftColumn": 33,
"dynamicBindingPathList": [],
"text": "Refresh Query",
"isDisabled": false,
"key": "r7znf2jmhk",
"rightColumn": 48,
"isDefaultClickDisabled": true,
"widgetId": "ddg5ybjic1",
"isVisible": true,
"recaptchaType": "V3",
"version": 1,
"parentId": "0",
"renderMode": "CANVAS",
"isLoading": false,
"buttonVariant": "PRIMARY",
"placement": "CENTER"
}
]
}
}

View File

@ -1,54 +1,49 @@
// import { AggregateHelper } from "../../../../support/Pages/AggregateHelper";
// import { JSEditor } from "../../../../support/Pages/JSEditor";
// import { CommonLocators } from "../../../../support/Objects/CommonLocators";
import { AggregateHelper } from "../../../../support/Pages/AggregateHelper";
import { JSEditor } from "../../../../support/Pages/JSEditor";
import { CommonLocators } from "../../../../support/Objects/CommonLocators";
// const agHelper = new AggregateHelper();
// const jsEditor = new JSEditor();
// const locator = new CommonLocators();
const agHelper = new AggregateHelper();
const jsEditor = new JSEditor();
const locator = new CommonLocators();
// describe("Validate Create Api and Bind to Table widget via JSObject", () => {
// before(() => {
// cy.fixture('formInputTableDsl').then((val: any) => {
// agHelper.AddDsl(val)
// });
// });
describe("Validate Create Api and Bind to Table widget via JSObject", () => {
before(() => {
cy.fixture('formInputTableDsl').then((val: any) => {
agHelper.AddDsl(val)
});
});
// it("1. Bind Input widget with JSObject", function () {
// jsEditor.CreateJSObject('return "Success";', false);
// agHelper.SelectEntityByName("Widgets")//to expand widgets
// agHelper.expandCollapseEntity("Form1")
// agHelper.SelectEntityByName("Input2")
// cy.get("@jsObjName").then((jsObjName) => {
// jsEditor.EnterJSContext("defaulttext", "{{" + jsObjName + ".myFun1()}}")
// });
// cy.wait("@updateLayout").should(
// "have.nested.property",
// "response.body.responseMeta.status",
// 200,
// );
// cy.get(locator._inputWidget).last().invoke("attr", "value").should("equal", 'Success');
// // cy.get(locator._inputWidget)
// // .last()
// // .within(() => {
// // cy.get("input")
// // .invoke("attr", "value")
// // .should("equal", 'Success');
// // });
// });
it("1. Bind Input widget with JSObject", function () {
jsEditor.CreateJSObject('return "Success";', false);
agHelper.SelectEntityByName("WIDGETS")//to expand widgets
agHelper.expandCollapseEntity("Form1")
agHelper.SelectEntityByName("Input2")
cy.get("@jsObjName").then((jsObjName) => {
jsEditor.EnterJSContext("defaulttext", "{{" + jsObjName + ".myFun1()}}")
});
cy.get(locator._inputWidget).last().invoke("attr", "value").should("equal", 'Success');
// cy.get(locator._inputWidget)
// .last()
// .within(() => {
// cy.get("input")
// .invoke("attr", "value")
// .should("equal", 'Success');
// });
});
// it.skip("2. Bug 10284, 11529 - Verify timeout issue with running JS Objects", function () {
// jsEditor.CreateJSObject('return "Success";', true);
// agHelper.expandCollapseEntity("Form1")
// agHelper.SelectEntityByName("Input2")
// cy.get("@jsObjName").then((jsObjName) => {
// jsEditor.EnterJSContext("defaulttext", "{{" + jsObjName + ".myFun1()}}")
// });
// cy.wait("@updateLayout").should(
// "have.nested.property",
// "response.body.responseMeta.status",
// 200,
// );
// cy.get(locator._inputWidget).last().invoke("attr", "value").should("equal", 'Success');
// });
it.skip("2. Bug 10284, 11529 - Verify timeout issue with running JS Objects", function () {
jsEditor.CreateJSObject('return "Success";', true);
agHelper.expandCollapseEntity("Form1")
agHelper.SelectEntityByName("Input2")
cy.get("@jsObjName").then((jsObjName) => {
jsEditor.EnterJSContext("defaulttext", "{{" + jsObjName + ".myFun1()}}")
});
cy.wait("@updateLayout").should(
"have.nested.property",
"response.body.responseMeta.status",
200,
);
cy.get(locator._inputWidget).last().invoke("attr", "value").should("equal", 'Success');
});
// });
});

View File

@ -1,82 +1,83 @@
// import { ApiPage } from "../../../../support/Pages/ApiPage";
// import { AggregateHelper } from "../../../../support/Pages/AggregateHelper";
// import { JSEditor } from "../../../../support/Pages/JSEditor";
// import { CommonLocators } from "../../../../support/Objects/CommonLocators";
import { ApiPage } from "../../../../support/Pages/ApiPage";
import { AggregateHelper } from "../../../../support/Pages/AggregateHelper";
import { JSEditor } from "../../../../support/Pages/JSEditor";
import { CommonLocators } from "../../../../support/Objects/CommonLocators";
// const apiPage = new ApiPage();
// const agHelper = new AggregateHelper();
// const jsEditor = new JSEditor();
// const locator = new CommonLocators();
const apiPage = new ApiPage();
const agHelper = new AggregateHelper();
const jsEditor = new JSEditor();
const locator = new CommonLocators();
// let dataSet: any, valueToTest: any, jsName: any;
let dataSet: any, valueToTest: any, jsName: any;
// describe("Validate Create Api and Bind to Table widget via JSObject", () => {
// before(() => {
// cy.fixture('listwidgetdsl').then((val: any) => {
// agHelper.AddDsl(val)
// });
describe("Validate Create Api and Bind to Table widget via JSObject", () => {
before(() => {
cy.fixture('listwidgetdsl').then((val: any) => {
agHelper.AddDsl(val)
});
// cy.fixture("example").then(function (data: any) {
// dataSet = data;
// });
// });
cy.fixture("example").then(function (data: any) {
dataSet = data;
});
});
// it("1. Add users api and bind to JSObject", () => {
// apiPage.CreateAndFillApi(dataSet.userApi + "/users")
// apiPage.RunAPI()
// apiPage.ReadApiResponsebyKey("name");
// cy.get("@apiResp").then((value) => {
// valueToTest = value;
// cy.log("valueToTest to test returned is :" + valueToTest)
// //cy.log("value to test returned is :" + value)
// })
// jsEditor.CreateJSObject("return Api1.data.users;", false);
// cy.get("@jsObjName").then((jsObj) => {
// jsName = jsObj;
// cy.log("jsName returned is :" + jsName)
// })
// });
it("1. Add users api and bind to JSObject", () => {
apiPage.CreateAndFillApi(dataSet.userApi + "/users")
apiPage.RunAPI()
apiPage.ReadApiResponsebyKey("name");
cy.get("@apiResp").then((value) => {
valueToTest = value;
cy.log("valueToTest to test returned is :" + valueToTest)
//cy.log("value to test returned is :" + value)
})
jsEditor.CreateJSObject("return Api1.data.users;", false);
cy.get("@jsObjName").then((jsObj) => {
jsName = jsObj;
cy.log("jsName returned is :" + jsName)
})
});
// it("2. Validate the Api data is updated on List widget", function () {
// agHelper.SelectEntityByName("Widgets")//to expand widgets
// agHelper.SelectEntityByName("List1");
// jsEditor.EnterJSContext("items", "{{" + jsName as string + ".myFun1()}}")
// cy.get(locator._textWidget).should("have.length", 8);
// cy.get(locator._textWidget)
// .first()
// .invoke("text")
// .then((text) => {
// expect(text).to.equal((valueToTest as string).trimEnd());
// });
// agHelper.DeployApp();
// cy.get(locator._textWidgetInDeployed).should("have.length", 8);
// cy.get(locator._textWidgetInDeployed)
// .first()
// .invoke("text")
// .then((text) => {
// expect(text).to.equal((valueToTest as string).trimEnd());
// });
// });
it("2. Validate the Api data is updated on List widget", function () {
agHelper.SelectEntityByName("WIDGETS")//to expand widgets
agHelper.SelectEntityByName("List1");
jsEditor.EnterJSContext("items", "{{" + jsName as string + ".myFun1()}}")
cy.get(locator._textWidget).should("have.length", 8);
cy.get(locator._textWidget)
.first()
.invoke("text")
.then((text) => {
expect(text).to.equal((valueToTest as string).trimEnd());
});
agHelper.DeployApp();
agHelper.WaitUntilEleAppear(locator._textWidgetInDeployed)
cy.get(locator._textWidgetInDeployed).should("have.length", 8);
cy.get(locator._textWidgetInDeployed)
.first()
.invoke("text")
.then((text) => {
expect(text).to.equal((valueToTest as string).trimEnd());
});
});
// it("3. Validate the List widget ", function () {
// agHelper.NavigateBacktoEditor()
// agHelper.SelectEntityByName("Widgets")//to expand widgets
// agHelper.SelectEntityByName("List1");
// jsEditor.EnterJSContext("itemspacing\\(px\\)", "50")
// cy.get(locator._textWidget).should("have.length", 6);
// cy.get(locator._textWidget)
// .first()
// .invoke("text")
// .then((text) => {
// expect(text).to.equal((valueToTest as string).trimEnd());
// });
// agHelper.DeployApp();
// cy.get(locator._textWidgetInDeployed).should("have.length", 6);
// cy.get(locator._textWidgetInDeployed).first()
// .invoke("text")
// .then((text) => {
// expect(text).to.equal((valueToTest as string).trimEnd());
// });
// agHelper.NavigateBacktoEditor()
// });
// });
it("3. Validate the List widget ", function () {
agHelper.NavigateBacktoEditor()
agHelper.SelectEntityByName("WIDGETS")//to expand widgets
agHelper.SelectEntityByName("List1");
jsEditor.EnterJSContext("itemspacing\\(px\\)", "50")
cy.get(locator._textWidget).should("have.length", 6);
cy.get(locator._textWidget)
.first()
.invoke("text")
.then((text) => {
expect(text).to.equal((valueToTest as string).trimEnd());
});
agHelper.DeployApp();
cy.get(locator._textWidgetInDeployed).should("have.length", 6);
cy.get(locator._textWidgetInDeployed).first()
.invoke("text")
.then((text) => {
expect(text).to.equal((valueToTest as string).trimEnd());
});
agHelper.NavigateBacktoEditor()
});
});

View File

@ -43,7 +43,7 @@ describe("Migration Validate", function() {
//Validating Latitude & Longitude are hidden columns:
cy.xpath(
"//div[@class='tableWrap']//div[@class='thead']//div[@class='tr'][1]//div[@role='columnheader']/div[text()='latitude']",
"//div[@class='tableWrap']//div[@class='thead']//div[@class='tr'][1]//div[@role='columnheader']//div[text()='latitude']/parent::div/parent::div",
)
.invoke("attr", "class")
.then((classes) => {
@ -52,7 +52,7 @@ describe("Migration Validate", function() {
});
cy.xpath(
"//div[@class='tableWrap']//div[@class='thead']//div[@class='tr'][1]//div[@role='columnheader']/div[text()='longitude']",
"//div[@class='tableWrap']//div[@class='thead']//div[@class='tr'][1]//div[@role='columnheader']//div[text()='longitude']/parent::div/parent::div",
)
.invoke("attr", "class")
.then((classes) => {
@ -74,7 +74,7 @@ describe("Migration Validate", function() {
//Validating Id column sorting happens as Datatype is Number in app!
cy.xpath(
"//div[@class='tableWrap']//div[@class='thead']//div[@class='tr'][1]//div[@role='columnheader']/div[text()='id']",
"//div[@class='tableWrap']//div[@class='thead']//div[@class='tr'][1]//div[@role='columnheader']//div[text()='id']",
)
.click()
.wait(2000);
@ -93,7 +93,7 @@ describe("Migration Validate", function() {
//Revert the Id column sorting!
cy.xpath(
"//div[@class='tableWrap']//div[@class='thead']//div[@class='tr'][1]//div[@role='columnheader']/div[text()='id']",
"//div[@class='tableWrap']//div[@class='thead']//div[@class='tr'][1]//div[@role='columnheader']//div[text()='id']",
)
.click()
.wait(2000);
@ -112,7 +112,7 @@ describe("Migration Validate", function() {
//Validating image column is present:
cy.getTableDataSelector("0", "10").then((selector) => {
cy.get(selector + " div div")
cy.get(selector + " div")
.invoke("attr", "class")
.then((classes) => {
cy.log("classes are:" + classes);

View File

@ -2,9 +2,7 @@
const widgetsPage = require("../../../../locators/Widgets.json");
const commonlocators = require("../../../../locators/commonlocators.json");
const publish = require("../../../../locators/publishWidgetspage.json");
const dsl = require("../../../../fixtures/tableNewDsl.json");
const pages = require("../../../../locators/Pages.json");
const testdata = require("../../../../fixtures/testdata.json");
describe("Table Widget property pane feature validation", function() {
@ -185,67 +183,71 @@ describe("Table Widget property pane feature validation", function() {
cy.get(".draggable-header:contains('Email Address')").should("be.visible");
cy.get(commonlocators.editPropBackButton).click({ force: true });
});
it("Edit Row height and test table for changes", function() {
cy.openPropertyPane("tablewidget");
cy.get(widgetsPage.rowHeight)
.last()
.click({ force: true });
cy.get(".t--dropdown-option")
.contains("Short")
.click({ force: true });
cy.wait(1000);
cy.readTabledataValidateCSS("0", "0", "height", "19px");
});
it("Test to validate text color and text background", function() {
// Open property pane
cy.openPropertyPane("tablewidget");
// Click on text color input field
cy.get(widgetsPage.textColor)
.first()
.click({ force: true });
// Select green color
cy.xpath(widgetsPage.greenColor).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.wait("@updateLayout");
// Verify the text color is green
cy.readTabledataValidateCSS("1", "0", "color", "rgb(3, 179, 101)");
// Change the text color and enter purple in input field
cy.get(widgetsPage.textColor)
.clear({ force: true })
.type("purple", { force: true });
cy.wait("@updateLayout");
// Verify the text color is purple
cy.readTabledataValidateCSS("1", "0", "color", "rgb(128, 0, 128)");
// Click on cell background color
cy.get(`${widgetsPage.cellBackground} input`)
.first()
.click({ force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
// select the green color
cy.xpath(widgetsPage.greenColor)
.first()
.click();
cy.wait("@updateLayout");
// Verify the cell background color is green
cy.readTabledataValidateCSS(
"1",
"0",
"background",
"rgb(3, 179, 101) none repeat scroll 0% 0% / auto padding-box border-box",
);
// Change the cell background color and enter purple in input field
cy.get(`${widgetsPage.cellBackground} input`)
.clear({ force: true })
.type("purple", { force: true });
cy.wait("@updateLayout");
// Verify the cell background color is purple
cy.readTabledataValidateCSS(
"1",
"0",
"background",
"rgb(128, 0, 128) none repeat scroll 0% 0% / auto padding-box border-box",
);
});
// it("Edit Row height and test table for changes", function() {
// cy.openPropertyPane("tablewidget");
// cy.get(widgetsPage.rowHeight)
// .last()
// .click({ force: true });
// cy.get(".t--dropdown-option")
// .contains("Short")
// .click({ force: true });
// cy.wait(1000);
// cy.readTabledataValidateCSS("0", "1", "height", "19px", true);
// });
// it("Test to validate text color and text background", function() {
// // Open property pane
// cy.openPropertyPane("tablewidget");
// // Click on text color input field
// cy.get(widgetsPage.textColor)
// .first()
// .click({ force: true });
// // Select green color
// cy.xpath(widgetsPage.greenColor).click();
// // eslint-disable-next-line cypress/no-unnecessary-waiting
// cy.wait(500);
// cy.wait("@updateLayout");
// // Verify the text color is green
// cy.readTabledataValidateCSS("1", "0", "color", "rgb(3, 179, 101)");
// // Change the text color and enter purple in input field
// cy.get(widgetsPage.textColor)
// .scrollIntoView()
// .clear({ force: true })
// .type("purple", { force: true });
// cy.wait("@updateLayout");
// // Verify the text color is purple
// cy.readTabledataValidateCSS("1", "0", "color", "rgb(128, 0, 128)");
// // Click on cell background color
// cy.get(`${widgetsPage.cellBackground} input`)
// .first()
// .scrollIntoView()
// .click({ force: true });
// // eslint-disable-next-line cypress/no-unnecessary-waiting
// cy.wait(500);
// // select the green color
// cy.xpath(widgetsPage.greenColor)
// .first()
// .click();
// cy.wait("@updateLayout");
// // Verify the cell background color is green
// cy.readTabledataValidateCSS(
// "1",
// "0",
// "background",
// "rgb(3, 179, 101) none repeat scroll 0% 0% / auto padding-box border-box",
// );
// // Change the cell background color and enter purple in input field
// cy.get(`${widgetsPage.cellBackground} input`)
// .clear({ force: true })
// .type("purple", { force: true });
// cy.wait("@updateLayout");
// // Verify the cell background color is purple
// cy.readTabledataValidateCSS(
// "1",
// "0",
// "background",
// "rgb(128, 0, 128) none repeat scroll 0% 0% / auto padding-box border-box",
// );
// });
});

View File

@ -126,10 +126,12 @@ describe("Table Widget property pane feature validation", function() {
});
it("Column Detail - Edit column name and validate test for computed value based on column type selected", function() {
cy.wait(1000);
cy.makeColumnVisible("email");
cy.makeColumnVisible("userName");
cy.makeColumnVisible("productName");
cy.makeColumnVisible("orderAmount");
cy.openPropertyPane("tablewidget");
// Open column detail to be edited by draggable id
cy.editColumn("id");
@ -210,8 +212,8 @@ describe("Table Widget property pane feature validation", function() {
cy.changeColumnType("URL");
// "Image" to "url"
cy.updateComputedValue(testdata.currentRowEmail);
cy.readTabledataPublish("1", "0").then((tabData2) => {
expect(tabData2).to.not.equal("lindsay.ferguson@reqres.in");
cy.readTabledataPublish("1", "0", true).then((tabData2) => {
expect(tabData2).not.to.equal("lindsay.ferguson@reqres.in");
cy.log("computed value of URL is " + tabData2);
});
});
@ -222,19 +224,25 @@ describe("Table Widget property pane feature validation", function() {
cy.get(widgetsPage.centerAlign)
.first()
.click({ force: true });
cy.readTabledataValidateCSS("1", "0", "justify-content", "center");
cy.readTabledataValidateCSS("1", "0", "justify-content", "center", true);
// Verifying Right Alignment
cy.get(widgetsPage.rightAlign)
.first()
.click({ force: true });
cy.readTabledataValidateCSS("1", "0", "justify-content", "flex-end");
cy.readTabledataValidateCSS("1", "0", "justify-content", "flex-end", true);
// Verifying Left Alignment
cy.get(widgetsPage.leftAlign)
.first()
.click({ force: true });
cy.readTabledataValidateCSS("0", "0", "justify-content", "flex-start");
cy.readTabledataValidateCSS(
"0",
"0",
"justify-content",
"flex-start",
true,
);
});
it("Test to validate text format", function() {
@ -249,17 +257,17 @@ describe("Table Widget property pane feature validation", function() {
it("Test to validate vertical allignment", function() {
// Validate vertical alignemnt of Cell text to TOP
cy.get(widgetsPage.verticalTop).click({ force: true });
cy.readTabledataValidateCSS("1", "0", "align-items", "flex-start");
cy.readTabledataValidateCSS("1", "0", "align-items", "flex-start", true);
// Validate vertical alignemnt of Cell text to Center
cy.get(widgetsPage.verticalCenter)
.last()
.click({ force: true });
cy.readTabledataValidateCSS("1", "0", "align-items", "center");
cy.readTabledataValidateCSS("1", "0", "align-items", "center", true);
// Validate vertical alignemnt of Cell text to Bottom
cy.get(widgetsPage.verticalBottom)
.last()
.click({ force: true });
cy.readTabledataValidateCSS("0", "0", "align-items", "flex-end");
cy.readTabledataValidateCSS("0", "0", "align-items", "flex-end", true);
});
it("Test to validate text color and text background", function() {
@ -271,12 +279,12 @@ describe("Table Widget property pane feature validation", function() {
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(5000);
cy.wait("@updateLayout");
cy.readTabledataValidateCSS("1", "0", "color", "rgb(3, 179, 101)");
cy.readTabledataValidateCSS("1", "0", "color", "rgb(3, 179, 101)", true);
// Changing text color to PURPLE and validate using JS
cy.get(widgetsPage.toggleJsColor).click();
cy.testCodeMirrorLast("purple");
cy.wait("@updateLayout");
cy.readTabledataValidateCSS("1", "0", "color", "rgb(128, 0, 128)");
cy.readTabledataValidateCSS("1", "0", "color", "rgb(128, 0, 128)", true);
// Changing Cell backgroud color to GREEN and validate
cy.get(widgetsPage.backgroundColor)
.first()
@ -288,6 +296,7 @@ describe("Table Widget property pane feature validation", function() {
"0",
"background",
"rgb(3, 179, 101) none repeat scroll 0% 0% / auto padding-box border-box",
true,
);
// Changing Cell backgroud color to PURPLE and validate using JS
cy.get(widgetsPage.toggleJsBcgColor).click();
@ -298,6 +307,7 @@ describe("Table Widget property pane feature validation", function() {
"0",
"background",
"rgb(128, 0, 128) none repeat scroll 0% 0% / auto padding-box border-box",
true,
);
// close property pane
cy.closePropertyPane();

View File

@ -196,7 +196,7 @@ describe("Git sync:", function() {
cy.get("[data-cy=t--tab-DEPLOY]")
.invoke("attr", "aria-selected")
.should("eq", "true");
cy.get(gitSyncLocators.closeGitSyncModal).click();
cy.get(gitSyncLocators.closeGitSyncModal).click({ force: true });
});
after(() => {

View File

@ -1,78 +1,78 @@
// import gitSyncLocators from "../../../../locators/gitSyncLocators";
import gitSyncLocators from "../../../../locators/gitSyncLocators";
// let repoName1, repoName2, repoName3, repoName4, windowOpenSpy;
// describe("Repo Limit Exceeded Error Modal", function() {
// before(() => {
// const uuid = require("uuid");
// repoName1 = uuid.v4().split("-")[0];
// repoName2 = uuid.v4().split("-")[0];
// repoName3 = uuid.v4().split("-")[0];
// repoName4 = uuid.v4().split("-")[0];
// });
let repoName1, repoName2, repoName3, repoName4, windowOpenSpy;
describe("Repo Limit Exceeded Error Modal", function() {
before(() => {
const uuid = require("uuid");
repoName1 = uuid.v4().split("-")[0];
repoName2 = uuid.v4().split("-")[0];
repoName3 = uuid.v4().split("-")[0];
repoName4 = uuid.v4().split("-")[0];
});
// it.only("modal should be opened with proper components", function() {
// cy.createAppAndConnectGit(repoName1, false);
// cy.createAppAndConnectGit(repoName2, false);
// cy.createAppAndConnectGit(repoName3, false);
// cy.createAppAndConnectGit(repoName4, false, true);
it("modal should be opened with proper components", function() {
cy.createAppAndConnectGit(repoName1, false);
cy.createAppAndConnectGit(repoName2, false);
cy.createAppAndConnectGit(repoName3, false);
cy.createAppAndConnectGit(repoName4, false, true);
// cy.get(gitSyncLocators.repoLimitExceededErrorModal).should("exist");
cy.get(gitSyncLocators.repoLimitExceededErrorModal).should("exist");
// // title and info text checking
// cy.get(gitSyncLocators.repoLimitExceededErrorModal).contains(
// Cypress.env("MESSAGES").REPOSITORY_LIMIT_REACHED(),
// );
// cy.get(gitSyncLocators.repoLimitExceededErrorModal).contains(
// Cypress.env("MESSAGES").REPOSITORY_LIMIT_REACHED_INFO(),
// );
// cy.get(gitSyncLocators.repoLimitExceededErrorModal).contains(
// Cypress.env("MESSAGES").CONTACT_SUPPORT_TO_UPGRADE(),
// );
// cy.get(gitSyncLocators.contactSalesButton).should("exist");
// cy.get(gitSyncLocators.repoLimitExceededErrorModal).contains(
// Cypress.env("MESSAGES").DISCONNECT_CAUSE_APPLICATION_BREAK(),
// );
// title and info text checking
cy.get(gitSyncLocators.repoLimitExceededErrorModal).contains(
Cypress.env("MESSAGES").REPOSITORY_LIMIT_REACHED(),
);
cy.get(gitSyncLocators.repoLimitExceededErrorModal).contains(
Cypress.env("MESSAGES").REPOSITORY_LIMIT_REACHED_INFO(),
);
cy.get(gitSyncLocators.repoLimitExceededErrorModal).contains(
Cypress.env("MESSAGES").CONTACT_SUPPORT_TO_UPGRADE(),
);
cy.get(gitSyncLocators.contactSalesButton).should("exist");
cy.get(gitSyncLocators.repoLimitExceededErrorModal).contains(
Cypress.env("MESSAGES").DISCONNECT_CAUSE_APPLICATION_BREAK(),
);
// // learn more link checking
// cy.window().then((window) => {
// windowOpenSpy = cy.stub(window, "open").callsFake((url) => {
// expect(url.startsWith("https://docs.appsmith.com/")).to.be.true;
// windowOpenSpy.restore();
// });
// });
// cy.get(gitSyncLocators.learnMoreOnRepoLimitModal).click();
// learn more link checking
cy.window().then((window) => {
windowOpenSpy = cy.stub(window, "open").callsFake((url) => {
expect(url.startsWith("https://docs.appsmith.com/")).to.be.true;
windowOpenSpy.restore();
});
});
cy.get(gitSyncLocators.learnMoreOnRepoLimitModal).click();
// cy.get(gitSyncLocators.connectedApplication).should("have.length", 3);
// cy.get(gitSyncLocators.diconnectLink)
// .first()
// .click();
cy.get(gitSyncLocators.connectedApplication).should("have.length", 3);
cy.get(gitSyncLocators.diconnectLink)
.first()
.click();
// cy.get(gitSyncLocators.repoLimitExceededErrorModal).should("not.exist");
// cy.get(gitSyncLocators.disconnectGitModal).should("exist");
cy.get(gitSyncLocators.repoLimitExceededErrorModal).should("not.exist");
cy.get(gitSyncLocators.disconnectGitModal).should("exist");
// cy.request({
// method: "DELETE",
// url: "api/v1/applications/" + repoName1,
// failOnStatusCode: false,
// });
// cy.request({
// method: "DELETE",
// url: "api/v1/applications/" + repoName2,
// failOnStatusCode: false,
// });
// cy.request({
// method: "DELETE",
// url: "api/v1/applications/" + repoName3,
// failOnStatusCode: false,
// });
// cy.request({
// method: "DELETE",
// url: "api/v1/applications/" + repoName4,
// failOnStatusCode: false,
// });
// cy.deleteTestGithubRepo(repoName1);
// cy.deleteTestGithubRepo(repoName2);
// cy.deleteTestGithubRepo(repoName3);
// cy.deleteTestGithubRepo(repoName4);
// });
// });
cy.request({
method: "DELETE",
url: "api/v1/applications/" + repoName1,
failOnStatusCode: false,
});
cy.request({
method: "DELETE",
url: "api/v1/applications/" + repoName2,
failOnStatusCode: false,
});
cy.request({
method: "DELETE",
url: "api/v1/applications/" + repoName3,
failOnStatusCode: false,
});
cy.request({
method: "DELETE",
url: "api/v1/applications/" + repoName4,
failOnStatusCode: false,
});
cy.deleteTestGithubRepo(repoName1);
cy.deleteTestGithubRepo(repoName2);
cy.deleteTestGithubRepo(repoName3);
cy.deleteTestGithubRepo(repoName4);
});
});

View File

@ -0,0 +1,71 @@
const queryLocators = require("../../../../locators/QueryEditor.json");
const datasourceEditor = require("../../../../locators/DatasourcesEditor.json");
const dsl = require("../../../../fixtures/noiseDsl.json");
const commonlocators = require("../../../../locators/commonlocators.json");
describe("MySQL noise test", function() {
let datasourceName;
beforeEach(() => {
cy.addDsl(dsl);
cy.startRoutesForDatasource();
});
it("Verify after killing MySQL session, app should not crash", function() {
cy.NavigateToDatasourceEditor();
cy.get(datasourceEditor.MySQL).click();
cy.generateUUID().then((uid) => {
datasourceName = uid;
cy.get(".t--edit-datasource-name").click();
cy.get(".t--edit-datasource-name input")
.clear()
.type(datasourceName, { force: true })
.should("have.value", datasourceName)
.blur();
cy.getPluginFormsAndCreateDatasource();
cy.fillMySQLDatasourceForm();
cy.testSaveDatasource();
cy.NavigateToActiveDSQueryPane(datasourceName);
});
cy.get(queryLocators.queryNameField).type("NoiseTestQuery");
cy.get(queryLocators.templateMenu).click();
// mySQL query to fetch data
cy.get(".CodeMirror textarea")
.first()
.focus()
.type("SELECT * FROM users where role = 'Admin' ORDER BY id LIMIT 10", {
force: true,
parseSpecialCharSequences: false,
});
cy.WaitAutoSave();
cy.runQuery();
cy.NavigateToAPI_Panel();
cy.log("Navigation to API Panel screen successful");
// API for killing mySQL session
cy.CreateAPI("killSession");
cy.enterDatasourceAndPath("http://localhost:5001/", "v1/noise/killmysql");
cy.SaveAndRunAPI();
cy.ResponseCheck("killed");
cy.get('.t--entity-name:contains("Page1")').click({ force: true });
cy.wait(2000);
// run kill query
cy.get(".bp3-button-text:contains('Kill Session')").should("be.visible");
cy.get(".bp3-button-text:contains('Kill Session')").click({ force: true });
// run refresh query
cy.get(".bp3-button-text:contains('Refresh Query')").click({ force: true });
cy.wait(2000);
cy.get(commonlocators.toastmsg).contains(
"UncaughtPromiseRejection: NoiseTestQuery failed to execute",
);
cy.wait("@postExecute", { timeout: 8000 }).then(({ response }) => {
expect(response.body.data.statusCode).to.eq("200 OK");
});
cy.wait("@postExecute", { timeout: 8000 }).then(({ response }) => {
expect(response.body.data.statusCode).to.eq("200 OK");
});
cy.wait("@postExecute", { timeout: 8000 }).then(({ response }) => {
expect(response.body.data.statusCode).to.eq("5004");
expect(response.body.data.title).to.eq(
"Datasource configuration is invalid",
);
});
});
});

View File

@ -1,12 +1,14 @@
import { AggregateHelper } from "../../../../support/Pages/AggregateHelper";
import { JSEditor } from "../../../../support/Pages/JSEditor";
import { DataSources } from "../../../../support/Pages/DataSources";
import { CommonLocators } from "../../../../support/Objects/CommonLocators";
const agHelper = new AggregateHelper();
const jsEditor = new JSEditor();
const dataSources = new DataSources();
const locator = new CommonLocators();
describe("Validate Create Api and Bind to Table widget via JSObject", () => {
describe("[Bug] - 10784 - Passing params from JS to SQL query should not break", () => {
before(() => {
cy.fixture('paramsDsl').then((val: any) => {
agHelper.AddDsl(val)
@ -15,7 +17,7 @@ describe("Validate Create Api and Bind to Table widget via JSObject", () => {
let guid: any;
it("1. [Bug] - 10784 : Passing params from JS to SQL query should not break", function () {
it("1. With Optional chaining : {{ this?.params?.condition }}", function () {
agHelper.NavigateToDSCreateNew()
dataSources.CreatePlugIn('PostgreSQL')
dataSources.FillPostgresDSForm();
@ -27,18 +29,297 @@ describe("Validate Create Api and Bind to Table widget via JSObject", () => {
cy.log("ds name is :" + guid)
dataSources.NavigateToActiveDSQueryPane(guid);
agHelper.GetNClick(dataSources._templateMenu)
agHelper.RenameWithInPane("Params")
agHelper.RenameWithInPane("Params1")
agHelper.EnterValue("SELECT * FROM public.users where id = {{this?.params?.condition || '1=1'}} order by id");
jsEditor.CreateJSObject('Params.run(() => {},() => {},{"condition": selRecordFilter.selectedOptionValue})');
jsEditor.CreateJSObject('Params1.run(() => {},() => {},{"condition": selRecordFilter.selectedOptionValue})');
})
agHelper.SelectEntityByName("WIDGETS")
agHelper.SelectEntityByName("Table1") //tabledata
jsEditor.EnterJSContext('tabledata', "{{Params.data}}");
agHelper.SelectEntityByName("WIDGETS")
agHelper.SelectEntityByName("Button1")
cy.get("@jsObjName").then((jsObjName) => {
jsEditor.EnterJSContext('onclick', "{{" + jsObjName + ".myFun1()}}", true, true);
});
agHelper.SelectEntityByName("Table1")
jsEditor.EnterJSContext('tabledata', "{{Params1.data}}");
agHelper.ClickButton("Submit")
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('8');
});
agHelper.SelectDropDown("selectwidget", '7')
agHelper.Sleep(2000)
agHelper.ClickButton("Submit")
agHelper.Sleep(2000)
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('7');
});
});
it("2. With Optional chaining : {{ (function() { return this?.params?.condition })() }}", function () {
dataSources.NavigateToActiveDSQueryPane(guid);
agHelper.GetNClick(dataSources._templateMenu)
agHelper.RenameWithInPane("Params2")
agHelper.EnterValue("SELECT * FROM public.users where id = {{(function() { return this?.params?.condition })() || '1=1'}} order by id");
jsEditor.CreateJSObject('Params2.run(() => {},() => {},{"condition": selRecordFilter.selectedOptionValue})');
agHelper.SelectEntityByName("Button1")
cy.get("@jsObjName").then((jsObjName) => {
jsEditor.EnterJSContext('onclick', "{{" + jsObjName + ".myFun1()}}", true, true);
});
agHelper.SelectEntityByName("Table1")
jsEditor.EnterJSContext('tabledata', "{{Params2.data}}");
agHelper.ClickButton("Submit")
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('7');
});
agHelper.SelectDropDown("selectwidget", '9')
agHelper.Sleep(2000)
agHelper.ClickButton("Submit")
agHelper.Sleep(2000)
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('9');
});
});
it("3. With Optional chaining : {{ (() => { return this?.params?.condition })() }}", function () {
dataSources.NavigateToActiveDSQueryPane(guid);
agHelper.GetNClick(dataSources._templateMenu)
agHelper.RenameWithInPane("Params3")
agHelper.EnterValue("SELECT * FROM public.users where id = {{(() => { return this?.params?.condition })() || '1=1'}} order by id");
jsEditor.CreateJSObject('Params3.run(() => {},() => {},{"condition": selRecordFilter.selectedOptionValue})');
agHelper.SelectEntityByName("Button1")
cy.get("@jsObjName").then((jsObjName) => {
jsEditor.EnterJSContext('onclick', "{{" + jsObjName + ".myFun1()}}", true, true);
});
agHelper.SelectEntityByName("Table1")
jsEditor.EnterJSContext('tabledata', "{{Params3.data}}");
agHelper.ClickButton("Submit")
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('9');
});
agHelper.SelectDropDown("selectwidget", '8')
agHelper.Sleep(2000)
agHelper.ClickButton("Submit")
agHelper.Sleep(2000)
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('8');
});
});
it("4. With Optional chaining : {{ this?.params.condition }}", function () {
dataSources.NavigateToActiveDSQueryPane(guid);
agHelper.GetNClick(dataSources._templateMenu)
agHelper.RenameWithInPane("Params4")
agHelper.EnterValue("SELECT * FROM public.users where id = {{this?.params.condition || '1=1'}} order by id");
jsEditor.CreateJSObject('Params4.run(() => {},() => {},{"condition": selRecordFilter.selectedOptionValue})');
agHelper.SelectEntityByName("Button1")
cy.get("@jsObjName").then((jsObjName) => {
jsEditor.EnterJSContext('onclick', "{{" + jsObjName + ".myFun1()}}", true, true);
});
agHelper.SelectEntityByName("Table1")
jsEditor.EnterJSContext('tabledata', "{{Params4.data}}");
agHelper.ClickButton("Submit")
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('8');
});
agHelper.SelectDropDown("selectwidget", '7')
agHelper.Sleep(2000)
agHelper.ClickButton("Submit")
agHelper.Sleep(2000)
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('7');
});
});
it("5. With Optional chaining : {{ (function() { return this?.params.condition })() }}", function () {
dataSources.NavigateToActiveDSQueryPane(guid);
agHelper.GetNClick(dataSources._templateMenu)
agHelper.RenameWithInPane("Params5")
agHelper.EnterValue("SELECT * FROM public.users where id = {{(function() { return this?.params.condition })() || '1=1'}} order by id");
jsEditor.CreateJSObject('Params5.run(() => {},() => {},{"condition": selRecordFilter.selectedOptionValue})');
agHelper.SelectEntityByName("Button1")
cy.get("@jsObjName").then((jsObjName) => {
jsEditor.EnterJSContext('onclick', "{{" + jsObjName + ".myFun1()}}", true, true);
});
agHelper.SelectEntityByName("Table1")
jsEditor.EnterJSContext('tabledata', "{{Params5.data}}");
agHelper.ClickButton("Submit")
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('7');
});
agHelper.SelectDropDown("selectwidget", '9')
agHelper.Sleep(2000)
agHelper.ClickButton("Submit")
agHelper.Sleep(2000)
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('9');
});
});
it("6. With Optional chaining : {{ (() => { return this?.params.condition })() }}", function () {
dataSources.NavigateToActiveDSQueryPane(guid);
agHelper.GetNClick(dataSources._templateMenu)
agHelper.RenameWithInPane("Params6")
agHelper.EnterValue("SELECT * FROM public.users where id = {{(() => { return this?.params.condition })() || '1=1'}} order by id");
jsEditor.CreateJSObject('Params6.run(() => {},() => {},{"condition": selRecordFilter.selectedOptionValue})');
agHelper.SelectEntityByName("Button1")
cy.get("@jsObjName").then((jsObjName) => {
jsEditor.EnterJSContext('onclick', "{{" + jsObjName + ".myFun1()}}", true, true);
});
agHelper.SelectEntityByName("Table1")
jsEditor.EnterJSContext('tabledata', "{{Params6.data}}");
agHelper.ClickButton("Submit")
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('9');
});
agHelper.SelectDropDown("selectwidget", '8')
agHelper.Sleep(2000)
agHelper.ClickButton("Submit")
agHelper.Sleep(2000)
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('8');
});
});
it("7. With No Optional chaining : {{ this.params.condition }}", function () {
dataSources.NavigateToActiveDSQueryPane(guid);
agHelper.GetNClick(dataSources._templateMenu)
agHelper.RenameWithInPane("Params7")
agHelper.EnterValue("SELECT * FROM public.users where id = {{this.params.condition || '1=1'}} order by id");
jsEditor.CreateJSObject('Params7.run(() => {},() => {},{"condition": selRecordFilter.selectedOptionValue})');
agHelper.SelectEntityByName("Button1")
cy.get("@jsObjName").then((jsObjName) => {
jsEditor.EnterJSContext('onclick', "{{" + jsObjName + ".myFun1()}}", true, true);
});
agHelper.SelectEntityByName("Table1")
jsEditor.EnterJSContext('tabledata', "{{Params7.data}}");
agHelper.ClickButton("Submit")
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('8');
});
agHelper.SelectDropDown("selectwidget", '7')
agHelper.Sleep(2000)
agHelper.ClickButton("Submit")
agHelper.Sleep(2000)
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('7');
});
});
it("8. With No Optional chaining : {{ (function() { return this.params.condition })() }}", function () {
dataSources.NavigateToActiveDSQueryPane(guid);
agHelper.GetNClick(dataSources._templateMenu)
agHelper.RenameWithInPane("Params8")
agHelper.EnterValue("SELECT * FROM public.users where id = {{(function() { return this.params.condition })() || '1=1'}} order by id");
jsEditor.CreateJSObject('Params8.run(() => {},() => {},{"condition": selRecordFilter.selectedOptionValue})');
agHelper.SelectEntityByName("Button1")
cy.get("@jsObjName").then((jsObjName) => {
jsEditor.EnterJSContext('onclick', "{{" + jsObjName + ".myFun1()}}", true, true);
});
agHelper.SelectEntityByName("Table1")
jsEditor.EnterJSContext('tabledata', "{{Params8.data}}");
agHelper.ClickButton("Submit")
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('7');
});
agHelper.SelectDropDown("selectwidget", '9')
agHelper.Sleep(2000)
agHelper.ClickButton("Submit")
agHelper.Sleep(2000)
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('9');
});
});
it("9. With No Optional chaining : {{ (() => { return this.params.condition })() }}", function () {
dataSources.NavigateToActiveDSQueryPane(guid);
agHelper.GetNClick(dataSources._templateMenu)
agHelper.RenameWithInPane("Params9")
agHelper.EnterValue("SELECT * FROM public.users where id = {{(() => { return this.params.condition })() || '1=1'}} order by id");
jsEditor.CreateJSObject('Params9.run(() => {},() => {},{"condition": selRecordFilter.selectedOptionValue})');
agHelper.SelectEntityByName("Button1")
cy.get("@jsObjName").then((jsObjName) => {
jsEditor.EnterJSContext('onclick', "{{" + jsObjName + ".myFun1()}}", true, true);
});
agHelper.SelectEntityByName("Table1")
jsEditor.EnterJSContext('tabledata', "{{Params9.data}}");
agHelper.ClickButton("Submit")
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('9');
});
agHelper.SelectDropDown("selectwidget", '8')
agHelper.Sleep(2000)
agHelper.ClickButton("Submit")
agHelper.Sleep(2000)
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('8');
});
});
it("10. With Optional chaining : {{ this?.params?.condition }} && no optional paramter passed", function () {
dataSources.NavigateToActiveDSQueryPane(guid);
agHelper.GetNClick(dataSources._templateMenu)
agHelper.RenameWithInPane("Params10")
agHelper.EnterValue("SELECT * FROM public.users where id = {{(() => { return this.params.condition })() || '7'}} order by id");
jsEditor.CreateJSObject('Params10.run(() => {},() => {},{"condition": selRecordFilter.selectedOptionValue})');
agHelper.SelectEntityByName("Button1")
cy.get("@jsObjName").then((jsObjName) => {
jsEditor.EnterJSContext('onclick', "{{" + jsObjName + ".myFun1()}}", true, true);
});
agHelper.SelectEntityByName("Table1")
jsEditor.EnterJSContext('tabledata', "{{Params10.data}}");
//When No selected option passed
cy.xpath(locator._selectWidgetDropdown("selectwidget")).within(() => cy.get(locator._crossBtn).click())
agHelper.ClickButton("Submit")
agHelper.Sleep(2000)
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
agHelper.ReadTableRowColumnData(0, 0).then((cellData) => {
expect(cellData).to.be.equal('7');
});
});
});

View File

@ -37,7 +37,7 @@ export class CommonLocators {
_entityNameEditing = (entityNameinLeftSidebar: string) => "//span[text()='" + entityNameinLeftSidebar + "']/parent::div[contains(@class, 't--entity-name editing')]/input"
_jsToggle = (controlToToggle: string) => ".t--property-control-" + controlToToggle + " .t--js-toggle"
_spanButton = (btnVisibleText: string) => "//span[text()='" + btnVisibleText + "']/parent::button"
_selectDropdown = (ddName: string) => "//div[contains(@class, 't--property-control-" + ddName + "')]//button"
_selectPropDropdown = (ddName: string) => "//div[contains(@class, 't--property-control-" + ddName + "')]//button"
_dropDownValue = (ddOption: string) => ".single-select:contains('" + ddOption + "')"
_actionTextArea = (actionName: string) => "//label[text()='" + actionName + "']/following-sibling::div//div[contains(@class, 'CodeMirror')]//textarea"
_existingDefaultTextInput = ".t--property-control-defaulttext .CodeMirror-code"
@ -46,8 +46,12 @@ export class CommonLocators {
_widgetInDeployed = (widgetType: string) => `.t--widget-${widgetType}`
_propertyToggle = (controlToToggle: string) => ".t--property-control-" + controlToToggle + " input[type='checkbox']"
_openNavigationTab = (tabToOpen: string) => `#switcher--${tabToOpen}`
_selectWidgetDropdown = (widgetType: string) => "//div[contains(@class, 't--draggable-" + widgetType + "')]//button"
_createNewPlgin = (pluginName: string) => ".t--plugin-name:contains('" + pluginName + "')"
_inputFieldByName = (fieldName: string) => "//p[text()='" + fieldName + "']/parent::label/following-sibling::div"
_evaluatedCurrentValue = "div:last-of-type .t--CodeEditor-evaluatedValue > div:last-of-type pre"
_tableRowColumn = (rowNum: number, colNum: number) => `.t--widget-tablewidget .tbody .td[data-rowindex=${rowNum}][data-colindex=${colNum}] div div`
_crossBtn = "span.cancel-icon"
_createNew = ".t--entity-add-btn.group.files"
}

View File

@ -43,7 +43,7 @@ export class AggregateHelper {
}
public NavigateToDSAdd() {
cy.get(locator._addNewDataSource).last()
cy.get(locator._addNewDataSource).last().scrollIntoView()
.should("be.visible")
.click({ force: true });
}
@ -71,7 +71,7 @@ export class AggregateHelper {
}
public SelectEntityByName(entityNameinLeftSidebar: string) {
cy.xpath(locator._entityNameInExplorer(entityNameinLeftSidebar))
cy.xpath(locator._entityNameInExplorer(entityNameinLeftSidebar), {timeout: 30000})
.last()
.click({ multiple: true })
this.Sleep()
@ -164,7 +164,7 @@ export class AggregateHelper {
}).then(() => this.Sleep(timeout))
}
public ValidateNetworkCallRespPost(aliasName: string, expectedRes = true) {
public ValidateNetworkExecutionSuccess(aliasName: string, expectedRes = true) {
cy.wait(aliasName).should(
"have.nested.property",
"response.body.data.isExecutionSuccess",
@ -172,6 +172,14 @@ export class AggregateHelper {
)
}
public ValidateNetworkStatus(aliasName: string, expectedRes = 200) {
cy.wait(aliasName).should(
"have.nested.property",
"response.body.responseMeta.status",
expectedRes,
)
}
public ValidateNetworkCallRespPut(aliasName: string, expectedStatus = 200) {
cy.wait(aliasName).should(
"have.nested.property",
@ -181,13 +189,22 @@ export class AggregateHelper {
}
public SelectPropertiesDropDown(endp: string, ddOption: string,) {
cy.xpath(locator._selectDropdown(endp))
cy.xpath(locator._selectPropDropdown(endp))
.first()
.scrollIntoView()
.click()
cy.get(locator._dropDownValue(ddOption)).click()
}
public SelectDropDown(endp: string, ddOption: string,) {
cy.xpath(locator._selectWidgetDropdown(endp))
.first()
.scrollIntoView()
.click()
cy.get(locator._dropDownValue(ddOption)).click({ force: true })
this.Sleep(2000)
}
public EnterActionValue(actionName: string, value: string, paste = true) {
cy.xpath(locator._actionTextArea(actionName))
.first()
@ -274,7 +291,8 @@ export class AggregateHelper {
}
public GetObjectName() {
cy.get(locator._queryName).invoke("text").then((text) => cy.wrap(text).as("queryName"));
//cy.get(locator._queryName).invoke("text").then((text) => cy.wrap(text).as("queryName")); or below syntax
return cy.get(locator._queryName).invoke("text");
}
public Sleep(timeout = 1000) {
@ -288,7 +306,6 @@ export class AggregateHelper {
cy.get(locator._homePageAppCreateBtn)
.should("be.visible")
.should("be.enabled");
//cy.get(this._homePageAppCreateBtn);
}
public CreateNewApplication() {
@ -364,4 +381,8 @@ export class AggregateHelper {
if ($text.text()) expect($text.text()).to.eq(currentValue);
});
}
public ReadTableRowColumnData(rowNum: number, colNum: number) {
return cy.get(locator._tableRowColumn(rowNum, colNum)).invoke("text");
}
}

View File

@ -1,6 +1,8 @@
import { AggregateHelper } from "./AggregateHelper";
import explorer from "../../locators/explorerlocators.json";
import { CommonLocators } from "../Objects/CommonLocators";
const agHelper = new AggregateHelper();
const locator = new CommonLocators()
export class ApiPage {
@ -15,16 +17,14 @@ export class ApiPage {
private _queryTimeout = "//input[@name='actionConfiguration.timeoutInMillisecond']"
private _apiTab = (tabValue: string) => "span:contains('" + tabValue + "')"
_responseBody = ".CodeMirror-code span.cm-string.cm-property"
private _blankAPI = "span:contains('New Blank API')"
CreateAndFillApi(url: string, apiname: string = "", queryTimeout = 30000) {
cy.get(explorer.createNew).click({ force: true });
cy.get(explorer.blankAPI).click({ force: true });
cy.wait("@createNewApi").should(
"have.nested.property",
"response.body.responseMeta.status",
201,
);
cy.get(locator._createNew).click({ force: true });
cy.get(this._blankAPI).click({ force: true });
agHelper.ValidateNetworkStatus("@createNewApi", 201)
// cy.get("@createNewApi").then((response: any) => {
// expect(response.response.body.responseMeta.success).to.eq(true);
// cy.get(agHelper._actionName)
@ -35,6 +35,7 @@ export class ApiPage {
// expect(someText).to.equal(response.response.body.data.name);
// });
// }); // to check if Api1 = Api1 when Create Api invoked
if (apiname)
agHelper.RenameWithInPane(apiname)
cy.get(this._resourceUrl).should("be.visible");
@ -83,11 +84,7 @@ export class ApiPage {
RunAPI() {
cy.get(this._apiRunBtn).click({ force: true });
cy.wait("@postExecute").should(
"have.nested.property",
"response.body.data.isExecutionSuccess",
true,
);
agHelper.ValidateNetworkExecutionSuccess("@postExecute")
}
SetAPITimeout(timeout: number) {
@ -109,15 +106,15 @@ export class ApiPage {
}
ReadApiResponsebyKey(key: string) {
let apiResp: string = "";
cy.get(this._responseBody)
.contains(key)
.siblings("span")
.invoke("text")
.then((text) => {
apiResp = `${text.match(/"(.*)"/)![0].split('"').join("") } `;
cy.log("Key value in api response is :" + apiResp);
cy.wrap(apiResp).as("apiResp")
});
let apiResp: string = "";
cy.get(this._responseBody)
.contains(key)
.siblings("span")
.invoke("text")
.then((text) => {
apiResp = `${text.match(/"(.*)"/)![0].split('"').join("")} `;
cy.log("Key value in api response is :" + apiResp);
cy.wrap(apiResp).as("apiResp")
});
}
}

View File

@ -6,17 +6,15 @@ const agHelper = new AggregateHelper();
const locator = new CommonLocators();
export class JSEditor {
private _runButton =
"//li//*[local-name() = 'svg' and @class='run-button']/parent::li";
private _outputConsole = ".CodeEditorTarget";
private _jsObjName = ".t--js-action-name-edit-field span";
private _jsObjTxt = ".t--js-action-name-edit-field input";
private _addEntityJSEditor = ".t--entity-add-btn.group.files"
private _runButton = "//li//*[local-name() = 'svg' and @class='run-button']/parent::li"
private _outputConsole = ".CodeEditorTarget"
private _jsObjName = ".t--js-action-name-edit-field span"
private _jsObjTxt = ".t--js-action-name-edit-field input"
private _newJSobj = "span:contains('New JS Object')"
private _bindingsClose = ".t--entity-property-close"
public NavigateToJSEditor() {
cy.get(this._addEntityJSEditor)
cy.get(locator._createNew)
.last()
.click({ force: true });
cy.get(this._newJSobj).click({ force: true });
@ -46,8 +44,10 @@ export class JSEditor {
}
});
agHelper.WaitAutoSave()//Ample wait due to open bug # 10284
agHelper.Sleep(5000)//Ample wait due to open bug # 10284
//clicking 2 times each with interval of 1 second!
//clicking 1 times & waits for 3 second for result to be populated!
Cypress._.times(1, () => {
cy.xpath(this._runButton)
.first()
@ -62,8 +62,14 @@ export class JSEditor {
public EnterJSContext(endp: string, value: string, paste = true, toToggleOnJS = false) {
if (toToggleOnJS) {
cy.get(locator._jsToggle(endp))
.first()
.click({ force: true });
.invoke("attr", "class")
.then((classes: any) => {
if (!classes.includes("is-active")) {
cy.get(locator._jsToggle(endp))
.first()
.click({ force: true });
}
});
}
cy.get(locator._propertyControl + endp + " " + locator._codeMirrorTextArea)
.first()
@ -140,5 +146,5 @@ export class JSEditor {
});
cy.get(this._bindingsClose).click({ force: true });
}
}

View File

@ -1721,8 +1721,10 @@ Cypress.Commands.add("editColumn", (colId) => {
Cypress.Commands.add(
"readTabledataValidateCSS",
(rowNum, colNum, cssProperty, cssValue) => {
const selector = `.t--widget-tablewidget .tbody .td[data-rowindex=${rowNum}][data-colindex=${colNum}] div`;
(rowNum, colNum, cssProperty, cssValue, shouldNotGotOneLeveDeeper) => {
const selector = `.t--widget-tablewidget .tbody .td[data-rowindex=${rowNum}][data-colindex=${colNum}] div ${
!shouldNotGotOneLeveDeeper ? "div" : ""
}`;
cy.get(selector).should("have.css", cssProperty, cssValue);
},
);
@ -2862,7 +2864,7 @@ Cypress.Commands.add("isSelectRow", (index) => {
Cypress.Commands.add("readTabledata", (rowNum, colNum) => {
// const selector = `.t--draggable-tablewidget .e-gridcontent.e-lib.e-droppable td[index=${rowNum}][aria-colindex=${colNum}]`;
const selector = `.tbody .td[data-rowindex="${rowNum}"][data-colindex="${colNum}"] div`;
const selector = `.tbody .td[data-rowindex="${rowNum}"][data-colindex="${colNum}"] div div`;
const tabVal = cy.get(selector).invoke("text");
return tabVal;
});
@ -3059,16 +3061,21 @@ Cypress.Commands.add("ExportVerify", (togglecss, name) => {
});
Cypress.Commands.add("getTableDataSelector", (rowNum, colNum) => {
const selector = `.t--widget-tablewidget .tbody .td[data-rowindex=${rowNum}][data-colindex=${colNum}] div`;
const selector = `.t--widget-tablewidget .tbody .td[data-rowindex=${rowNum}][data-colindex=${colNum}] div div`;
return selector;
});
Cypress.Commands.add("readTabledataPublish", (rowNum, colNum) => {
// const selector = `.t--widget-tablewidget .e-gridcontent.e-lib.e-droppable td[index=${rowNum}][aria-colindex=${colNum}]`;
const selector = `.t--widget-tablewidget .tbody .td[data-rowindex=${rowNum}][data-colindex=${colNum}] div`;
const tabVal = cy.get(selector).invoke("text");
return tabVal;
});
Cypress.Commands.add(
"readTabledataPublish",
(rowNum, colNum, shouldNotGotOneLeveDeeper) => {
// const selector = `.t--widget-tablewidget .e-gridcontent.e-lib.e-droppable td[index=${rowNum}][aria-colindex=${colNum}]`;
const selector = `.t--widget-tablewidget .tbody .td[data-rowindex=${rowNum}][data-colindex=${colNum}] div ${
!shouldNotGotOneLeveDeeper ? "div" : ""
}`;
const tabVal = cy.get(selector).invoke("text");
return tabVal;
},
);
Cypress.Commands.add(
"readTabledataFromSpecificIndex",
@ -3095,7 +3102,7 @@ Cypress.Commands.add("tablefirstdataRow", () => {
});
Cypress.Commands.add("scrollTabledataPublish", (rowNum, colNum) => {
const selector = `.t--widget-tablewidget .tbody .td[data-rowindex=${rowNum}][data-colindex=${colNum}] div`;
const selector = `.t--widget-tablewidget .tbody .td[data-rowindex=${rowNum}][data-colindex=${colNum}] div div`;
const tabVal = cy
.get(selector)
.scrollIntoView()

View File

@ -44,6 +44,7 @@ module.exports = {
enableGithubOAuth: parseConfig("__APPSMITH_OAUTH2_GITHUB_CLIENT_ID__"),
disableLoginForm: parseConfig("__APPSMITH_FORM_LOGIN_DISABLED__"),
disableSignup: parseConfig("__APPSMITH_SIGNUP_DISABLED__"),
disableTelemetry: parseConfig("__APPSMITH_DISABLE_TELEMETRY__"),
enableRapidAPI: parseConfig("__APPSMITH_MARKETPLACE_ENABLED__"),
segment: {
apiKey: parseConfig("__APPSMITH_SEGMENT_KEY__"),

View File

@ -195,6 +195,7 @@
enableGithubOAuth: parseConfig("__APPSMITH_OAUTH2_GITHUB_CLIENT_ID__"),
disableLoginForm: parseConfig("__APPSMITH_FORM_LOGIN_DISABLED__"),
disableSignup: parseConfig("__APPSMITH_SIGNUP_DISABLED__"),
disableTelemetry: parseConfig("__APPSMITH_DISABLE_TELEMETRY__"),
enableRapidAPI: parseConfig("__APPSMITH_MARKETPLACE_ENABLED__"),
segment: {
apiKey: parseConfig("__APPSMITH_SEGMENT_KEY__"),

View File

@ -17,6 +17,7 @@ export interface INJECTED_CONFIGS {
enableGithubOAuth: boolean;
disableLoginForm: boolean;
disableSignup: boolean;
disableTelemetry: boolean;
enableRapidAPI: boolean;
segment: {
apiKey: string;
@ -77,6 +78,9 @@ export const getConfigsFromEnvVars = (): INJECTED_CONFIGS => {
disableSignup: process.env.APPSMITH_SIGNUP_DISABLED
? process.env.APPSMITH_SIGNUP_DISABLED.length > 0
: false,
disableTelemetry: process.env.APPSMITH_DISABLE_TELEMETRY
? process.env.APPSMITH_DISABLE_TELEMETRY.length > 0
: false,
segment: {
apiKey: process.env.REACT_APP_SEGMENT_KEY || "",
ceKey: process.env.REACT_APP_SEGMENT_CE_KEY || "",
@ -246,6 +250,8 @@ export const getAppsmithConfigs = (): AppsmithUIConfigs => {
ENV_CONFIG.disableLoginForm || APPSMITH_FEATURE_CONFIGS.disableLoginForm,
disableSignup:
ENV_CONFIG.disableSignup || APPSMITH_FEATURE_CONFIGS.disableSignup,
disableTelemetry:
ENV_CONFIG.disableTelemetry || APPSMITH_FEATURE_CONFIGS.disableTelemetry,
enableGoogleOAuth:
ENV_CONFIG.enableGoogleOAuth ||
APPSMITH_FEATURE_CONFIGS.enableGoogleOAuth,

View File

@ -46,6 +46,7 @@ export interface AppsmithUIConfigs {
enableGithubOAuth: boolean;
disableLoginForm: boolean;
disableSignup: boolean;
disableTelemetry: boolean;
enableMixpanel: boolean;
enableTNCPP: boolean;

View File

@ -6,6 +6,7 @@ import {
} from "constants/ThirdPartyConstants";
import {
SettingCategories,
SettingSubCategories,
SettingTypes,
SettingSubtype,
AdminConfigType,
@ -34,7 +35,7 @@ const Form_Auth: AdminConfigType = {
{
id: "APPSMITH_FORM_LOGIN_DISABLED",
category: SettingCategories.FORM_AUTH,
subCategory: "form login",
subCategory: SettingSubCategories.FORMLOGIN,
controlType: SettingTypes.TOGGLE,
label: "Form Login Option",
toggleText: (value: boolean) => {
@ -48,12 +49,12 @@ const Form_Auth: AdminConfigType = {
{
id: "APPSMITH_SIGNUP_DISABLED",
category: SettingCategories.FORM_AUTH,
subCategory: "form signup",
subCategory: SettingSubCategories.FORMLOGIN,
controlType: SettingTypes.TOGGLE,
label: "Signup",
toggleText: (value: boolean) => {
if (value) {
return "Allow only invited users to signup";
return "Restrict Signups";
} else {
return " Allow all users to signup";
}
@ -62,7 +63,7 @@ const Form_Auth: AdminConfigType = {
{
id: "APPSMITH_FORM_CALLOUT_BANNER",
category: SettingCategories.FORM_AUTH,
subCategory: "form signup",
subCategory: SettingSubCategories.FORMLOGIN,
controlType: SettingTypes.LINK,
label:
"User emails are not verified. This can lead to a breach in your application.",
@ -83,7 +84,7 @@ const Google_Auth: AdminConfigType = {
{
id: "APPSMITH_OAUTH2_GOOGLE_READ_MORE",
category: SettingCategories.GOOGLE_AUTH,
subCategory: "google signup",
subCategory: SettingSubCategories.GOOGLE,
controlType: SettingTypes.LINK,
label: "How to configure?",
url: GOOGLE_SIGNUP_SETUP_DOC,
@ -91,7 +92,7 @@ const Google_Auth: AdminConfigType = {
{
id: "APPSMITH_OAUTH2_GOOGLE_CLIENT_ID",
category: SettingCategories.GOOGLE_AUTH,
subCategory: "google signup",
subCategory: SettingSubCategories.GOOGLE,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.TEXT,
label: "Client ID",
@ -99,7 +100,7 @@ const Google_Auth: AdminConfigType = {
{
id: "APPSMITH_OAUTH2_GOOGLE_CLIENT_SECRET",
category: SettingCategories.GOOGLE_AUTH,
subCategory: "google signup",
subCategory: SettingSubCategories.GOOGLE,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.TEXT,
label: "Client Secret",
@ -107,7 +108,7 @@ const Google_Auth: AdminConfigType = {
{
id: "APPSMITH_SIGNUP_ALLOWED_DOMAINS",
category: SettingCategories.GOOGLE_AUTH,
subCategory: "google signup",
subCategory: SettingSubCategories.GOOGLE,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.TEXT,
label: "Allowed Domains",
@ -128,7 +129,7 @@ const Github_Auth: AdminConfigType = {
{
id: "APPSMITH_OAUTH2_GITHUB_READ_MORE",
category: SettingCategories.GITHUB_AUTH,
subCategory: "github signup",
subCategory: SettingSubCategories.GITHUB,
controlType: SettingTypes.LINK,
label: "How to configure?",
url: GITHUB_SIGNUP_SETUP_DOC,
@ -136,7 +137,7 @@ const Github_Auth: AdminConfigType = {
{
id: "APPSMITH_OAUTH2_GITHUB_CLIENT_ID",
category: SettingCategories.GITHUB_AUTH,
subCategory: "github signup",
subCategory: SettingSubCategories.GITHUB,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.TEXT,
label: "Client ID",
@ -144,7 +145,7 @@ const Github_Auth: AdminConfigType = {
{
id: "APPSMITH_OAUTH2_GITHUB_CLIENT_SECRET",
category: SettingCategories.GITHUB_AUTH,
subCategory: "github signup",
subCategory: SettingSubCategories.GITHUB,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.TEXT,
label: "Client Secret",

View File

@ -71,6 +71,12 @@ export const SettingCategories = {
GITHUB_AUTH: "github-auth",
};
export const SettingSubCategories = {
GOOGLE: "google signup",
GITHUB: "github signup",
FORMLOGIN: "form login",
};
export type AdminConfigType = {
type: string;
controlType: SettingTypes;

View File

@ -11,18 +11,21 @@ export const connectedMethods = [
!disableLoginForm,
].filter(Boolean);
/* settings is the updated & unsaved settings on Admin settings page */
export const saveAllowed = (settings: any) => {
if (
connectedMethods.length >= 2 ||
(connectedMethods.length === 1 &&
((!("APPSMITH_FORM_LOGIN_DISABLED" in settings) && !disableLoginForm) ||
(settings["APPSMITH_OAUTH2_GOOGLE_CLIENT_ID"] !== "" &&
enableGoogleOAuth) ||
(settings["APPSMITH_OAUTH2_GITHUB_CLIENT_ID"] !== "" &&
enableGithubOAuth)))
) {
return true;
if (connectedMethods.length === 1) {
const checkFormLogin = !(
"APPSMITH_FORM_LOGIN_DISABLED" in settings || disableLoginForm
),
checkGoogleAuth =
settings["APPSMITH_OAUTH2_GOOGLE_CLIENT_ID"] !== "" &&
enableGoogleOAuth,
checkGithubAuth =
settings["APPSMITH_OAUTH2_GITHUB_CLIENT_ID"] !== "" &&
enableGithubOAuth;
return checkFormLogin || checkGoogleAuth || checkGithubAuth;
} else {
return false;
return connectedMethods.length >= 2;
}
};

View File

@ -147,6 +147,7 @@ import Settings2LineIcon from "remixicon-react/Settings2LineIcon";
import FileListLineIcon from "remixicon-react/FileListLineIcon";
import HamburgerIcon from "remixicon-react/MenuLineIcon";
import MagicLineIcon from "remixicon-react/MagicLineIcon";
import UserHeartLineIcon from "remixicon-react/UserHeartLineIcon";
export enum IconSize {
XXS = "extraExtraSmall",
@ -294,6 +295,7 @@ const ICON_LOOKUP = {
"trending-flat": <TrendingFlat />,
"unread-pin": <UnreadPin />,
"user-2": <UserV2Icon />,
"user-heart-line": <UserHeartLineIcon />,
"view-all": <RightArrowIcon />,
"view-less": <LeftArrowIcon />,
"warning-line": <WarningLineIcon />,
@ -333,6 +335,7 @@ const ICON_LOOKUP = {
loader: <LoaderLineIcon />,
logout: <LogoutIcon />,
manage: <ManageIcon />,
member: <UserHeartLineIcon />,
mobile: <MobileIcon />,
open: <OpenIcon />,
pin: <Pin />,

View File

@ -21,7 +21,7 @@ const InputCopyWrapper = styled.div`
align-items: center;
svg {
margin-left: 10px;
margin-left: 12px;
cursor: pointer;
}
`;

View File

@ -0,0 +1,244 @@
import React from "react";
import "@testing-library/jest-dom";
import {
render,
screen,
waitFor,
waitForElementToBeRemoved,
} from "@testing-library/react";
import IconSelectControl from "./IconSelectControl";
import userEvent from "@testing-library/user-event";
import { noop } from "lodash";
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
describe("<IconSelectControl /> - Keyboard navigation", () => {
const getTestComponent = (
onPropertyChange: (
propertyName: string,
propertyValue: string,
) => void = noop,
) => (
<IconSelectControl
additionalDynamicData={{
dummy: {
dummy: 1,
},
}}
controlType="add"
deleteProperties={noop}
evaluatedValue={undefined}
isBindProperty={false}
isTriggerProperty={false}
label="Icon"
onPropertyChange={onPropertyChange}
openNextPanel={noop}
parentPropertyName="iconName"
parentPropertyValue="add"
propertyName="iconName"
theme={EditorTheme.LIGHT}
widgetProperties={undefined}
/>
);
it("Pressing tab should focus the component", () => {
render(getTestComponent());
userEvent.tab();
expect(screen.getByRole("button")).toHaveFocus();
});
it.each(["{Enter}", " ", "{ArrowDown}", "{ArrowUp}"])(
"Pressing '%s' should open the icon selector",
async (key) => {
render(getTestComponent());
userEvent.tab();
expect(screen.queryByRole("list")).toBeNull();
userEvent.keyboard(key);
expect(screen.queryByRole("list")).toBeInTheDocument();
// Makes sure search bar is having focus
await waitFor(() => {
expect(screen.queryByRole("textbox")).toHaveFocus();
});
},
);
it("Pressing '{Escape}' should close the icon selector", async () => {
render(getTestComponent());
userEvent.tab();
expect(screen.queryByRole("list")).toBeNull();
userEvent.keyboard("{Enter}");
expect(screen.queryByRole("list")).toBeInTheDocument();
userEvent.keyboard("{Escape}");
await waitForElementToBeRemoved(screen.getAllByRole("list"));
});
it("Pressing '{ArrowDown}' while search is in focus should remove the focus", async () => {
render(getTestComponent());
userEvent.tab();
userEvent.keyboard("{Enter}");
expect(screen.queryByRole("list")).toBeInTheDocument();
await waitFor(() => {
expect(screen.queryByRole("textbox")).toHaveFocus();
});
userEvent.keyboard("{ArrowDown}");
expect(screen.queryByRole("textbox")).not.toHaveFocus();
});
it("Pressing '{Shift} + {ArrowUp}' while search is not in focus should focus search box", async () => {
render(getTestComponent());
userEvent.tab();
userEvent.keyboard("{Enter}");
expect(screen.queryByRole("list")).toBeInTheDocument();
await waitFor(() => {
expect(screen.queryByRole("textbox")).toHaveFocus();
});
userEvent.keyboard("{ArrowDown}");
expect(screen.queryByRole("textbox")).not.toHaveFocus();
userEvent.keyboard("{Shift}{ArrowUp}");
await waitFor(() => {
expect(screen.queryByRole("textbox")).toHaveFocus();
});
});
/*
Icon Arrangement
(none) add add-column-left add-column-right
add-row-bottom add-row-top add-to-artifact add-to-folder
airplane align-center align-justify align-left
*/
it("Pressing '{ArrowDown}' should navigate the icon selection downwards", async () => {
render(getTestComponent());
userEvent.tab();
userEvent.keyboard("{Enter}");
await waitFor(() => {
expect(screen.queryByRole("textbox")).toHaveFocus();
});
expect(document.querySelector("a.bp3-active")?.children[0]).toHaveClass(
"bp3-icon-(none)",
);
// used to shift the focus from search
userEvent.keyboard("{ArrowDown}");
userEvent.keyboard("{ArrowDown}");
expect(document.querySelector("a.bp3-active")?.children[0]).toHaveClass(
"bp3-icon-add-row-bottom",
);
});
it("Pressing '{ArrowUp}' should navigate the icon selection upwards", async () => {
render(getTestComponent());
userEvent.tab();
userEvent.keyboard("{Enter}");
await waitFor(() => {
expect(screen.queryByRole("textbox")).toHaveFocus();
});
expect(document.querySelector("a.bp3-active")?.children[0]).toHaveClass(
"bp3-icon-(none)",
);
// used to shift the focus from search
userEvent.keyboard("{ArrowDown}");
userEvent.keyboard("{ArrowDown}");
userEvent.keyboard("{ArrowDown}");
userEvent.keyboard("{ArrowDown}");
expect(document.querySelector("a.bp3-active")?.children[0]).toHaveClass(
"bp3-icon-align-right",
);
userEvent.keyboard("{ArrowUp}");
expect(document.querySelector("a.bp3-active")?.children[0]).toHaveClass(
"bp3-icon-airplane",
);
});
it("Pressing '{ArrowRight}' should navigate the icon selection towards right", async () => {
render(getTestComponent());
userEvent.tab();
userEvent.keyboard("{Enter}");
await waitFor(() => {
expect(screen.queryByRole("textbox")).toHaveFocus();
});
expect(document.querySelector("a.bp3-active")?.children[0]).toHaveClass(
"bp3-icon-(none)",
);
// used to shift the focus from search
userEvent.keyboard("{ArrowDown}");
userEvent.keyboard("{ArrowRight}");
expect(document.querySelector("a.bp3-active")?.children[0]).toHaveClass(
"bp3-icon-add",
);
});
it("Pressing '{ArrowLeft}' should navigate the icon selection towards left", async () => {
render(getTestComponent());
userEvent.tab();
userEvent.keyboard("{Enter}");
await waitFor(() => {
expect(screen.queryByRole("textbox")).toHaveFocus();
});
expect(document.querySelector("a.bp3-active")?.children[0]).toHaveClass(
"bp3-icon-(none)",
);
// used to shift the focus from search
userEvent.keyboard("{ArrowDown}");
userEvent.keyboard("{ArrowRight}");
userEvent.keyboard("{ArrowRight}");
userEvent.keyboard("{ArrowRight}");
expect(document.querySelector("a.bp3-active")?.children[0]).toHaveClass(
"bp3-icon-add-column-right",
);
userEvent.keyboard("{ArrowLeft}");
expect(document.querySelector("a.bp3-active")?.children[0]).toHaveClass(
"bp3-icon-add-column-left",
);
});
it("Pressing '{Enter}' or ' ' should select the icon", async () => {
const handleOnSelect = jest.fn();
render(getTestComponent(handleOnSelect));
userEvent.tab();
expect(screen.queryByRole("button")?.textContent).toEqual(
"(none)caret-down",
);
userEvent.keyboard("{Enter}");
await waitFor(() => {
expect(screen.queryByRole("textbox")).toHaveFocus();
});
expect(document.querySelector("a.bp3-active")?.children[0]).toHaveClass(
"bp3-icon-(none)",
);
// used to shift the focus from search
userEvent.keyboard("{ArrowDown}");
userEvent.keyboard("{ArrowDown}");
userEvent.keyboard("{ArrowRight}");
expect(document.querySelector("a.bp3-active")?.children[0]).toHaveClass(
"bp3-icon-add-row-top",
);
userEvent.keyboard(" ");
expect(handleOnSelect).toHaveBeenCalledTimes(1);
expect(handleOnSelect).toHaveBeenLastCalledWith("iconName", "add-row-top");
await waitForElementToBeRemoved(screen.getByRole("list"));
userEvent.keyboard("{Enter}");
expect(screen.queryByRole("list")).toBeInTheDocument();
await waitFor(() => {
expect(screen.queryByRole("textbox")).toHaveFocus();
});
expect(document.querySelector("a.bp3-active")?.children[0]).toHaveClass(
"bp3-icon-add-row-top",
);
userEvent.keyboard("{ArrowDown}");
userEvent.keyboard("{ArrowRight}");
userEvent.keyboard(" ");
expect(handleOnSelect).toHaveBeenCalledTimes(2);
expect(handleOnSelect).toHaveBeenLastCalledWith(
"iconName",
"add-to-artifact",
);
});
});

View File

@ -1,13 +1,19 @@
import * as React from "react";
import styled, { createGlobalStyle } from "styled-components";
import { Alignment, Button, Classes, Menu, MenuItem } from "@blueprintjs/core";
import { Alignment, Button, Classes, MenuItem } from "@blueprintjs/core";
import { IconName, IconNames } from "@blueprintjs/icons";
import { ItemListRenderer, ItemRenderer, Select } from "@blueprintjs/select";
import {
GridListProps,
VirtuosoGrid,
VirtuosoGridHandle,
} from "react-virtuoso";
import BaseControl, { ControlProps } from "./BaseControl";
import TooltipComponent from "components/ads/Tooltip";
import { Colors } from "constants/Colors";
import { replayHighlightClass } from "globalStyles/portals";
import _ from "lodash";
const IconSelectContainerStyles = createGlobalStyle<{
targetWidth: number | undefined;
@ -37,7 +43,7 @@ const StyledButton = styled(Button)`
}
`;
const StyledMenu = styled(Menu)`
const StyledMenu = styled.ul<GridListProps>`
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: minmax(50px, auto);
@ -54,6 +60,9 @@ const StyledMenu = styled(Menu)`
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: #939090;
}
& li {
list-style: none;
}
`;
const StyledMenuItem = styled(MenuItem)`
@ -82,7 +91,9 @@ export interface IconSelectControlProps extends ControlProps {
}
export interface IconSelectControlState {
activeIcon: IconType;
popoverTargetWidth: number | undefined;
isOpen: boolean;
}
const NONE = "(none)";
@ -99,14 +110,44 @@ class IconSelectControl extends BaseControl<
IconSelectControlState
> {
private iconSelectTargetRef: React.RefObject<HTMLButtonElement>;
private virtuosoRef: React.RefObject<VirtuosoGridHandle>;
private initialItemIndex: number;
private filteredItems: Array<IconType>;
private searchInput: React.RefObject<HTMLInputElement>;
private timer?: number;
constructor(props: IconSelectControlProps) {
super(props);
this.iconSelectTargetRef = React.createRef();
this.state = { popoverTargetWidth: 0 };
this.virtuosoRef = React.createRef();
this.searchInput = React.createRef();
this.initialItemIndex = 0;
this.filteredItems = [];
this.state = {
activeIcon: props.propertyValue ?? NONE,
popoverTargetWidth: 0,
isOpen: false,
};
}
// debouncedSetState is used to fix the following bug:
// https://github.com/appsmithorg/appsmith/pull/10460#issuecomment-1022895174
private debouncedSetState = _.debounce(
(obj: any, callback?: () => void) => {
this.setState((prevState: IconSelectControlState) => {
return {
...prevState,
...obj,
};
}, callback);
},
300,
{
leading: true,
trailing: false,
},
);
componentDidMount() {
this.timer = setTimeout(() => {
const iconSelectTargetElement = this.iconSelectTargetRef.current;
@ -118,30 +159,49 @@ class IconSelectControl extends BaseControl<
};
});
}, 0);
// keydown event is attached to body so that it will not interfere with the keydown handler in GlobalHotKeys
document.body.addEventListener("keydown", this.handleKeydown);
}
componentWillUnmount() {
if (this.timer) {
clearTimeout(this.timer);
}
document.body.removeEventListener("keydown", this.handleKeydown);
}
private handleQueryChange = _.debounce(() => {
if (this.filteredItems.length === 2)
this.setState({ activeIcon: this.filteredItems[1] });
}, 50);
public render() {
const { defaultIconName, propertyValue: iconName } = this.props;
const { popoverTargetWidth } = this.state;
const { activeIcon, popoverTargetWidth } = this.state;
return (
<>
<IconSelectContainerStyles targetWidth={popoverTargetWidth} />
<TypedSelect
activeItem={iconName || defaultIconName || NONE}
activeItem={activeIcon || defaultIconName || NONE}
className="icon-select-container"
inputProps={{
inputRef: this.searchInput,
}}
itemListRenderer={this.renderMenu}
itemPredicate={this.filterIconName}
itemRenderer={this.renderIconItem}
items={ICON_NAMES}
noResults={<MenuItem disabled text="No results" />}
onItemSelect={this.handleIconChange}
popoverProps={{ minimal: true }}
onQueryChange={this.handleQueryChange}
popoverProps={{
enforceFocus: false,
minimal: true,
isOpen: this.state.isOpen,
onInteraction: (state) => {
if (this.state.isOpen !== state)
this.debouncedSetState({ isOpen: state });
},
}}
>
<StyledButton
alignText={Alignment.LEFT}
@ -151,6 +211,7 @@ class IconSelectControl extends BaseControl<
elementRef={this.iconSelectTargetRef}
fill
icon={iconName || defaultIconName}
onClick={this.handleButtonClick}
rightIcon="caret-down"
text={iconName || defaultIconName || NONE}
/>
@ -159,14 +220,142 @@ class IconSelectControl extends BaseControl<
);
}
private setActiveIcon(iconIndex: number) {
this.setState(
{
activeIcon: this.filteredItems[iconIndex],
},
() => {
if (this.virtuosoRef.current) {
this.virtuosoRef.current.scrollToIndex(iconIndex);
}
},
);
}
private handleKeydown = (e: KeyboardEvent) => {
if (this.state.isOpen) {
switch (e.key) {
case "Tab":
e.preventDefault();
this.setState({
isOpen: false,
activeIcon: this.props.propertyValue ?? NONE,
});
break;
case "ArrowDown":
case "Down": {
if (document.activeElement === this.searchInput.current) {
(document.activeElement as HTMLElement).blur();
if (this.initialItemIndex < 0) this.initialItemIndex = -4;
else break;
}
const nextIndex = this.initialItemIndex + 4;
if (nextIndex < this.filteredItems.length)
this.setActiveIcon(nextIndex);
e.preventDefault();
break;
}
case "ArrowUp":
case "Up": {
if (document.activeElement === this.searchInput.current) {
break;
} else if (
(e.shiftKey ||
(this.initialItemIndex >= 0 && this.initialItemIndex < 4)) &&
this.searchInput.current
) {
this.searchInput.current.focus();
break;
}
const nextIndex = this.initialItemIndex - 4;
if (nextIndex >= 0) this.setActiveIcon(nextIndex);
e.preventDefault();
break;
}
case "ArrowRight":
case "Right": {
if (document.activeElement === this.searchInput.current) {
break;
}
const nextIndex = this.initialItemIndex + 1;
if (nextIndex < this.filteredItems.length)
this.setActiveIcon(nextIndex);
e.preventDefault();
break;
}
case "ArrowLeft":
case "Left": {
if (document.activeElement === this.searchInput.current) {
break;
}
const nextIndex = this.initialItemIndex - 1;
if (nextIndex >= 0) this.setActiveIcon(nextIndex);
e.preventDefault();
break;
}
case " ":
case "Enter": {
if (
this.searchInput.current === document.activeElement &&
this.filteredItems.length !== 2
)
break;
this.handleIconChange(this.filteredItems[this.initialItemIndex]);
this.debouncedSetState({ isOpen: false });
e.preventDefault();
e.stopPropagation();
break;
}
case "Escape": {
this.setState({
isOpen: false,
activeIcon: this.props.propertyValue ?? NONE,
});
e.stopPropagation();
}
}
} else if (
this.iconSelectTargetRef.current === document.activeElement &&
(e.key === "ArrowUp" ||
e.key === "Up" ||
e.key === "ArrowDown" ||
e.key === "Down")
) {
this.debouncedSetState({ isOpen: true }, this.handleButtonClick);
}
};
private handleButtonClick = () => {
setTimeout(() => {
if (this.virtuosoRef.current) {
this.virtuosoRef.current.scrollToIndex(this.initialItemIndex);
}
}, 0);
};
private renderMenu: ItemListRenderer<IconType> = ({
items,
itemsParentRef,
activeItem,
filteredItems,
renderItem,
}) => {
const renderedItems = items.map(renderItem).filter((item) => item != null);
this.filteredItems = filteredItems;
this.initialItemIndex = filteredItems.findIndex((x) => x === activeItem);
return <StyledMenu ulRef={itemsParentRef}>{renderedItems}</StyledMenu>;
return (
<VirtuosoGrid
components={{
List: StyledMenu,
}}
computeItemKey={(index) => filteredItems[index]}
initialItemCount={16}
itemContent={(index) => renderItem(filteredItems[index], index)}
ref={this.virtuosoRef}
style={{ height: "165px" }}
tabIndex={-1}
totalCount={filteredItems.length}
/>
);
};
private renderIconItem: ItemRenderer<IconName | typeof NONE> = (
@ -184,6 +373,7 @@ class IconSelectControl extends BaseControl<
key={icon}
onClick={handleClick}
text={icon === NONE ? NONE : undefined}
textClassName={icon === NONE ? "bp3-icon-(none)" : ""}
/>
</TooltipComponent>
);
@ -199,11 +389,13 @@ class IconSelectControl extends BaseControl<
return iconName.toLowerCase().indexOf(query.toLowerCase()) >= 0;
};
private handleIconChange = (icon: IconType) =>
private handleIconChange = (icon: IconType) => {
this.setState({ activeIcon: icon });
this.updateProperty(
this.props.propertyName,
icon === NONE ? undefined : icon,
);
};
static getControlType() {
return "ICON_SELECT";

View File

@ -774,7 +774,7 @@ function ApplicationsSection(props: any) {
</div>
<MenuItem
cypressSelector="t--org-setting"
icon="general"
icon="settings-2-line"
onSelect={() =>
getOnSelectAction(
DropdownOnSelectActions.REDIRECT,
@ -783,7 +783,7 @@ function ApplicationsSection(props: any) {
},
)
}
text="Organization Settings"
text="Settings"
/>
{enableImportExport && (
<MenuItem
@ -816,12 +816,12 @@ function ApplicationsSection(props: any) {
/>
)}
<MenuItem
icon="share"
icon="share-line"
onSelect={() => setSelectedOrgId(organization.id)}
text="Share"
/>
<MenuItem
icon="user"
icon="member"
onSelect={() =>
getOnSelectAction(
DropdownOnSelectActions.REDIRECT,

View File

@ -254,7 +254,7 @@ function DatasourceCard(props: DatasourceCardProps) {
<Queries className={`t--queries-for-${plugin.type}`}>
{queriesWithThisDatasource
? `${queriesWithThisDatasource} ${QUERY} on this page`
: "No query is using this datasource"}
: "No query in this application is using this datasource"}
</Queries>
</div>
<ButtonsWrapper className="action-wrapper">

View File

@ -1,27 +1,23 @@
import React, { useCallback, useState, useMemo } from "react";
import { Space } from "./StyledComponents";
import {
createMessage,
USER_PROFILE_SETTINGS_TITLE,
AUTHOR_NAME,
AUTHOR_EMAIL,
FORM_VALIDATION_INVALID_EMAIL,
AUTHOR_NAME,
AUTHOR_NAME_CANNOT_BE_EMPTY,
FORM_VALIDATION_INVALID_EMAIL,
USER_PROFILE_SETTINGS_TITLE,
USE_DEFAULT_CONFIGURATION,
createMessage,
} from "@appsmith/constants/messages";
import styled from "styled-components";
import TextInput, { emailValidator } from "components/ads/TextInput";
import Checkbox from "components/ads/Checkbox";
import { GIT_PROFILE_ROUTE } from "constants/routes";
import history from "utils/history";
import { Colors } from "constants/Colors";
import { ReactComponent as RightArrow } from "assets/icons/ads/arrow-right-line.svg";
import { useSelector } from "react-redux";
import {
getIsFetchingGlobalGitConfig,
getIsFetchingLocalGitConfig,
} from "selectors/gitSyncSelectors";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { getTypographyByKey } from "constants/DefaultTheme";
const LabelContainer = styled.div`
@ -48,24 +44,6 @@ const MainContainer = styled.div`
width: calc(100% - 30px);
`;
const ButtonWrapper = styled.div`
display: flex;
flex-direction: row;
padding-top: 2px;
margin-left: ${(props) => props.theme.spaces[6]}px;
cursor: pointer;
.edit-config-link {
font-size: 12px;
display: flex;
color: ${Colors.GRAY};
}
`;
const IconWrapper = styled.div`
margin-left: 2px;
`;
const DefaultConfigContainer = styled.div`
display: flex;
align-items: flex-start;
@ -126,11 +104,6 @@ type UserGitProfileSettingsProps = {
triedSubmit: boolean;
};
const goToGitProfile = () => {
AnalyticsUtil.logEvent("GS_DEFAULT_CONFIGURATION_EDIT_BUTTON_CLICK");
history.push(GIT_PROFILE_ROUTE);
};
function UserGitProfileSettings({
authorInfo,
setAuthorInfo,
@ -195,12 +168,6 @@ function UserGitProfileSettings({
label={createMessage(USE_DEFAULT_CONFIGURATION)}
onCheckChange={toggleUseDefaultConfig}
/>
<ButtonWrapper onClick={goToGitProfile}>
<span className="edit-config-link">EDIT</span>
<IconWrapper>
<RightArrow width={14} />
</IconWrapper>
</ButtonWrapper>
</DefaultConfigContainer>
) : null}

View File

@ -9,7 +9,7 @@ import { Icon, IconSize } from "components/ads";
const AccordionWrapper = styled.div`
margin-top: 40px;
max-width: 634px;
max-width: 40rem;
`;
const AccordionHeader = styled(StyledLabel)`

View File

@ -6,7 +6,8 @@ import { FormGroup, SettingComponentProps } from "./Common";
export default function TextInput({ setting }: SettingComponentProps) {
return (
<FormGroup
className={`t--admin-settings-text-input t--admin-settings-${setting.name}`}
className={`t--admin-settings-text-input t--admin-settings-${setting.name ||
setting.id}`}
setting={setting}
>
<FormTextField

View File

@ -193,9 +193,9 @@ export function SettingsForm(
const validate = (values: Record<string, any>) => {
const errors: any = {};
_.filter(values, (value, name) => {
const message = AdminConfig.validate(name, value);
if (message) {
errors[name] = message;
const err_message = AdminConfig.validate(name, value);
if (err_message) {
errors[name] = err_message;
}
});
return errors;

View File

@ -12,7 +12,7 @@ export const config: AdminConfigType = {
settings: [
{
id: "APPSMITH_MONGODB_URI",
category: "advanced",
category: SettingCategories.ADVANCED,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.TEXT,
label: "MongoDB URI",
@ -21,7 +21,7 @@ export const config: AdminConfigType = {
},
{
id: "APPSMITH_REDIS_URL",
category: "advanced",
category: SettingCategories.ADVANCED,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.TEXT,
label: "Redis URL",
@ -30,7 +30,7 @@ export const config: AdminConfigType = {
},
{
id: "APPSMITH_CUSTOM_DOMAIN",
category: "advanced",
category: SettingCategories.ADVANCED,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.TEXT,
label: "Custom Domain",

View File

@ -21,14 +21,14 @@ export const config: AdminConfigType = {
settings: [
{
id: "APPSMITH_MAIL_READ_MORE",
category: "email",
category: SettingCategories.EMAIL,
controlType: SettingTypes.LINK,
label: "How to configure?",
url: EMAIL_SETUP_DOC,
},
{
id: "APPSMITH_MAIL_HOST",
category: "email",
category: SettingCategories.EMAIL,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.TEXT,
label: "SMTP Host",
@ -36,7 +36,7 @@ export const config: AdminConfigType = {
},
{
id: "APPSMITH_MAIL_PORT",
category: "email",
category: SettingCategories.EMAIL,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.NUMBER,
placeholder: "25",
@ -50,7 +50,7 @@ export const config: AdminConfigType = {
},
{
id: "APPSMITH_MAIL_FROM",
category: "email",
category: SettingCategories.EMAIL,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.TEXT,
label: "From Address",
@ -65,13 +65,13 @@ export const config: AdminConfigType = {
},
{
id: "APPSMITH_MAIL_SMTP_TLS_ENABLED",
category: "email",
category: SettingCategories.EMAIL,
controlType: SettingTypes.TOGGLE,
label: "TLS Protected Connection",
},
{
id: "APPSMITH_MAIL_USERNAME",
category: "email",
category: SettingCategories.EMAIL,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.TEXT,
label: "SMTP Username",
@ -81,7 +81,7 @@ export const config: AdminConfigType = {
},
{
id: "APPSMITH_MAIL_PASSWORD",
category: "email",
category: SettingCategories.EMAIL,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.PASSWORD,
label: "SMTP Password",
@ -91,7 +91,7 @@ export const config: AdminConfigType = {
},
{
id: "APPSMITH_MAIL_TEST_EMAIL",
category: "email",
category: SettingCategories.EMAIL,
action: (dispatch: Dispatch<ReduxAction<any>>, settings: any = {}) => {
dispatch &&
dispatch({

View File

@ -16,7 +16,7 @@ export const config: AdminConfigType = {
settings: [
{
id: "APPSMITH_INSTANCE_NAME",
category: "general",
category: SettingCategories.GENERAL,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.TEXT,
label: "Instance Name",
@ -24,7 +24,7 @@ export const config: AdminConfigType = {
},
{
id: "APPSMITH_ADMIN_EMAILS",
category: "general",
category: SettingCategories.GENERAL,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.EMAIL,
label: "Admin Email",
@ -51,14 +51,14 @@ export const config: AdminConfigType = {
"_blank",
);
},
category: "general",
category: SettingCategories.GENERAL,
controlType: SettingTypes.BUTTON,
label: "Generated Docker Compose File",
text: "Download",
},
{
id: "APPSMITH_DISABLE_TELEMETRY",
category: "general",
category: SettingCategories.GENERAL,
controlType: SettingTypes.TOGGLE,
label: "Disable Sharing Anonymous Usage Data",
subText: "Share anonymous usage data to help improve the product",
@ -66,7 +66,7 @@ export const config: AdminConfigType = {
if (value) {
return "Don't share any data";
} else {
return "Share data & make appsmith better!";
return "Share Anonymous Telemetry";
}
},
},

View File

@ -14,14 +14,14 @@ export const config: AdminConfigType = {
settings: [
{
id: "APPSMITH_GOOGLE_MAPS_READ_MORE",
category: "google-maps",
category: SettingCategories.GOOGLE_MAPS,
controlType: SettingTypes.LINK,
label: "How to configure?",
url: GOOGLE_MAPS_SETUP_DOC,
},
{
id: "APPSMITH_GOOGLE_MAPS_API_KEY",
category: "google-maps",
category: SettingCategories.GOOGLE_MAPS,
controlType: SettingTypes.TEXTINPUT,
controlSubType: SettingSubtype.TEXT,
label: "Google Maps API Key",

View File

@ -14,7 +14,7 @@ export const config: AdminConfigType = {
settings: [
{
id: "APPSMITH_CURRENT_VERSION",
category: "version",
category: SettingCategories.VERSION,
controlType: SettingTypes.TEXT,
label: "Current version",
},
@ -27,7 +27,7 @@ export const config: AdminConfigType = {
payload: true,
});
},
category: "version",
category: SettingCategories.VERSION,
controlType: SettingTypes.LINK,
label: "Release Notes",
},

View File

@ -288,6 +288,7 @@ function* createNewQueryForDatasourceSaga(
const createActionPayload = {
name: newQueryName,
pageId,
pluginId: datasource?.pluginId,
datasource: {
id: datasourceId,
},

View File

@ -133,7 +133,7 @@ function* SendTestEmail(action: ReduxAction<SendTestEmailPayload>) {
});
} catch (e) {
Toaster.show({
text: createMessage(TEST_EMAIL_FAILURE),
text: e?.message || createMessage(TEST_EMAIL_FAILURE),
hideProgressBar: true,
variant: Variant.danger,
});

View File

@ -112,7 +112,7 @@ export const getDifferenceInJSCollection = (
organizationId: jsAction.organizationId,
actionConfiguration: {
body: action.body,
isAsync: false,
isAsync: action.isAsync,
timeoutInMilliseconds: 0,
jsArguments: [],
},
@ -206,8 +206,8 @@ export const createDummyJSCollectionActions = (
organizationId,
executeOnLoad: false,
actionConfiguration: {
body: "() => {\n\t\t//write code here\n\t}",
isAsync: false,
body: "async () => {\n\t\t//write code here\n\t}",
isAsync: true,
timeoutInMilliseconds: 0,
jsArguments: [],
},

View File

@ -44,6 +44,7 @@ import { ReactComponent as ExitFullScreenIcon } from "assets/icons/widget/camera
const overlayerMixin = css`
position: absolute;
height: 100%;
width: 100%;
object-fit: contain;
top: 50%;
@ -81,6 +82,7 @@ const CameraContainer = styled.div<CameraContainerProps>`
}
video {
height: 100%;
width: 100%;
object-fit: contain;
}

View File

@ -1,6 +1,6 @@
import React, { createRef, useEffect, useState } from "react";
import { Tooltip } from "@blueprintjs/core";
import { CellWrapper } from "./TableStyledWrappers";
import { CellWrapper, ColumnWrapper } from "./TableStyledWrappers";
import { CellLayoutProperties, ColumnTypes } from "./Constants";
import { ReactComponent as OpenNewTabIcon } from "assets/icons/control/open-new-tab.svg";
import styled from "styled-components";
@ -19,6 +19,7 @@ export const OpenNewTabIconWrapper = styled.div`
interface Props {
isHidden?: boolean;
isCellVisible?: boolean;
noPadding?: boolean;
children: React.ReactNode;
title: string;
cellProperties?: CellLayoutProperties;
@ -89,30 +90,34 @@ function AutoToolTipComponent(props: Props) {
return <LinkWrapper {...props} />;
}
return (
<CellWrapper
cellProperties={props.cellProperties}
isCellVisible={props.isCellVisible}
isHidden={props.isHidden}
isTextType
ref={ref}
>
{useToolTip && props.children ? (
<Tooltip
autoFocus={false}
content={
<TooltipContentWrapper width={(props.tableWidth || 300) - 32}>
{props.title}
</TooltipContentWrapper>
}
hoverOpenDelay={1000}
position="top"
>
{props.children}
</Tooltip>
) : (
props.children
)}
</CellWrapper>
<ColumnWrapper>
<CellWrapper
cellProperties={props.cellProperties}
isCellVisible={props.isCellVisible}
isHidden={props.isHidden}
isPadding={!props.noPadding}
isTextType
ref={ref}
>
{useToolTip && props.children ? (
<Tooltip
autoFocus={false}
content={
<TooltipContentWrapper width={(props.tableWidth || 300) - 32}>
{props.title}
</TooltipContentWrapper>
}
hoverOpenDelay={1000}
position="top"
>
{props.children}
</Tooltip>
) : (
props.children
)}
</CellWrapper>
{useToolTip && props.children && "..."}
</ColumnWrapper>
);
}

View File

@ -317,6 +317,7 @@ export function Table(props: TableProps) {
isSortable={props.isSortable}
key={columnIndex}
sortTableColumn={props.sortTableColumn}
width={props.width}
/>
);
},

View File

@ -134,7 +134,7 @@ export const TableWrapper = styled.div<{
cursor: pointer;
display: inline-block;
width: 100%;
height: 38px;
height: 32px;
&.reorder-line {
width: 1px;
height: 100%;
@ -353,6 +353,7 @@ export const DraggableHeaderWrapper = styled.div<{
export const CellWrapper = styled.div<{
isHidden?: boolean;
isPadding?: boolean;
cellProperties?: CellLayoutProperties;
isHyperLink?: boolean;
useLinkToolTip?: boolean;
@ -360,17 +361,16 @@ export const CellWrapper = styled.div<{
isTextType?: boolean;
}>`
display: ${(props) => (props.isCellVisible !== false ? "flex" : "none")};
align-items: center;
align-items: ${(props) => (props.isPadding ? "center" : "flex-start")};
justify-content: flex-start;
width: 100%;
width: ${(props) => (props.isPadding ? "100%" : "calc(100% - 10px)")};
height: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
opacity: ${(props) => (props.isHidden ? "0.6" : "1")};
${TableStyles};
padding: 0 10px;
padding: ${(props) => (props.isPadding ? "0 10px" : " 0px")};
line-height: 28px;
.image-cell-wrapper {
width: 100%;
@ -574,3 +574,8 @@ export const MenuCategoryWrapper = styled.div`
export const MenuStyledOptionHeader = styled.div`
font-weight: 600;
`;
export const ColumnWrapper = styled.div`
display: flex;
align-items: center;
`;

View File

@ -580,8 +580,9 @@ export function TableHeaderCell(props: {
column: any;
editMode?: boolean;
isSortable?: boolean;
width: number;
}) {
const { column, editMode, isSortable } = props;
const { column, editMode, isSortable, width } = props;
const handleSortColumn = () => {
if (props.isResizingColumn) return;
let columnIndex = props.columnIndex;
@ -604,7 +605,13 @@ export function TableHeaderCell(props: {
className={!props.isHidden ? `draggable-header` : "hidden-header"}
horizontalAlignment={column.columnProperties.horizontalAlignment}
>
{props.columnName}
<AutoToolTipComponent
noPadding
tableWidth={width}
title={props.columnName}
>
{props.columnName}
</AutoToolTipComponent>
</DraggableHeaderWrapper>
{props.isAscOrder !== undefined ? (
<div>

View File

@ -37,6 +37,7 @@ import static com.appsmith.server.constants.Url.ACTION_COLLECTION_URL;
import static com.appsmith.server.constants.Url.ACTION_URL;
import static com.appsmith.server.constants.Url.APPLICATION_URL;
import static com.appsmith.server.constants.Url.PAGE_URL;
import static com.appsmith.server.constants.Url.THEME_URL;
import static com.appsmith.server.constants.Url.USER_URL;
import static java.time.temporal.ChronoUnit.DAYS;
@ -124,6 +125,7 @@ public class SecurityConfig {
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, ACTION_COLLECTION_URL + "/view"),
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, PAGE_URL + "/**"),
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, APPLICATION_URL + "/**"),
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, THEME_URL + "/**"),
ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, ACTION_URL + "/execute")
)
.permitAll()

View File

@ -2,6 +2,7 @@ package com.appsmith.server.controllers.ce;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.constants.Url;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.dtos.ApplicationPagesDTO;
import com.appsmith.server.dtos.CRUDPageResourceDTO;
import com.appsmith.server.dtos.CRUDPageResponseDTO;
@ -24,6 +25,7 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@ -147,4 +149,24 @@ public class PageControllerCE {
return newPageService.updatePageByDefaultPageIdAndBranch(defaultPageId, resource, branchName)
.map(updatedResource -> new ResponseDTO<>(HttpStatus.OK.value(), updatedResource, null));
}
/**
* Returns a list of pages. It takes either Application ID or a Page ID and mode as input.
* If Application ID is present, it'll fetch all pages of that application in the provided mode.
* if Page ID is present, it'll fetch all pages of the corresponding Application.
* If both IDs are present, it'll use the Application ID only and ignore the Page ID
* @param applicationId Id of the application
* @param pageId id of a page
* @param mode In which mode it's in
* @param branchName name of the current branch
* @return List of ApplicationPagesDTO along with other meta data
*/
@GetMapping
public Mono<ResponseDTO<ApplicationPagesDTO>> getAllPages(@RequestParam(required = false) String applicationId,
@RequestParam(required = false) String pageId,
@RequestParam(required = true, defaultValue = "EDIT") ApplicationMode mode,
@RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName) {
return newPageService.findApplicationPages(applicationId, pageId, branchName, mode)
.map(resources -> new ResponseDTO<>(HttpStatus.OK.value(), resources, null));
}
}

View File

@ -127,8 +127,12 @@ public class UserControllerCE extends BaseController<UserService, User, String>
public Mono<ResponseDTO<Boolean>> forgotPasswordRequest(@RequestBody ResetUserPasswordDTO userPasswordDTO,
@RequestHeader("Origin") String originHeader) {
userPasswordDTO.setBaseUrl(originHeader);
// We shouldn't leak information on whether this operation was successful or not to the client. This can enable
// username scraping, where the response of this API can prove whether an email has an account or not.
return service.forgotPasswordTokenGenerate(userPasswordDTO)
.map(result -> new ResponseDTO<>(HttpStatus.OK.value(), result, null));
.defaultIfEmpty(true)
.onErrorReturn(true)
.thenReturn(new ResponseDTO<>(HttpStatus.OK.value(), true, null));
}
@GetMapping("/verifyPasswordResetToken")

View File

@ -1,5 +1,6 @@
package com.appsmith.server.dtos;
import com.appsmith.server.domains.Application;
import lombok.Getter;
import lombok.Setter;
@ -11,6 +12,8 @@ public class ApplicationPagesDTO {
String organizationId;
Application application;
List<PageNameIdDTO> pages;
}

View File

@ -2061,6 +2061,9 @@ public class GitServiceCEImpl implements GitServiceCE {
.flatMap(application -> {
GitApplicationMetadata gitApplicationMetadata = application.getGitApplicationMetadata();
Path repoPath = Paths.get(application.getOrganizationId(), defaultApplicationId, gitApplicationMetadata.getRepoName());
if(branchName.equals(gitApplicationMetadata.getDefaultBranchName())) {
return Mono.error(new AppsmithException(AppsmithError.GIT_ACTION_FAILED, " delete branch", "Cannot delete default branch"));
}
return gitExecutor.deleteBranch(repoPath, branchName)
.flatMap(isBranchDeleted -> {
if(Boolean.FALSE.equals(isBranchDeleted)) {

View File

@ -1,6 +1,7 @@
package com.appsmith.server.services.ce;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.dtos.ApplicationPagesDTO;
@ -38,6 +39,8 @@ public interface NewPageServiceCE extends CrudService<NewPage, String> {
Boolean view,
boolean markApplicationAsRecentlyAccessed);
Mono<ApplicationPagesDTO> findApplicationPages(String applicationId, String pageId, String branchName, ApplicationMode mode);
Mono<ApplicationPagesDTO> findApplicationPagesByApplicationIdViewMode(
String applicationId, Boolean view, boolean markApplicationAsRecentlyAccessed
);
@ -70,6 +73,8 @@ public interface NewPageServiceCE extends CrudService<NewPage, String> {
Mono<String> findBranchedPageId(String branchName, String defaultPageId, AclPermission permission);
Mono<String> findBranchedApplicationIdFromNewPage(String branchName, String defaultPageId);
Mono<NewPage> findByGitSyncIdAndDefaultApplicationId(String defaultApplicationId, String gitSyncId, AclPermission permission);
Flux<NewPage> findPageSlugsByApplicationIds(List<String> applicationIds, AclPermission aclPermission);

View File

@ -4,6 +4,7 @@ import com.appsmith.external.models.DefaultResources;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.ApplicationPage;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.NewPage;
@ -46,6 +47,7 @@ import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNewFieldValues
import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES;
import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
import static com.appsmith.server.acl.AclPermission.READ_PAGES;
import static com.appsmith.server.exceptions.AppsmithError.INVALID_PARAMETER;
@Slf4j
@ -333,10 +335,13 @@ public class NewPageServiceCEImpl extends BaseService<NewPageRepository, NewPage
return Mono.zip(applicationMono, pagesListMono)
.map(tuple -> {
Application application = tuple.getT1();
application.setPages(null);
application.setPublishedPages(null);
List<PageNameIdDTO> nameIdDTOList = tuple.getT2();
ApplicationPagesDTO applicationPagesDTO = new ApplicationPagesDTO();
applicationPagesDTO.setOrganizationId(application.getOrganizationId());
applicationPagesDTO.setPages(nameIdDTOList);
applicationPagesDTO.setApplication(application);
return applicationPagesDTO;
});
}
@ -525,6 +530,24 @@ public class NewPageServiceCEImpl extends BaseService<NewPageRepository, NewPage
.map(NewPage::getId);
}
@Override
public Mono<String> findBranchedApplicationIdFromNewPage(String branchName, String defaultPageId) {
Mono<NewPage> getPageMono;
if (!StringUtils.hasLength(branchName)) {
if (!StringUtils.hasLength(defaultPageId)) {
return Mono.error(new AppsmithException(INVALID_PARAMETER, FieldName.PAGE_ID, defaultPageId));
}
getPageMono = repository.findById(defaultPageId, READ_PAGES);
} else {
getPageMono = repository.findPageByBranchNameAndDefaultPageId(branchName, defaultPageId, READ_PAGES);
}
return getPageMono
.switchIfEmpty(Mono.error(
new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.PAGE_ID, defaultPageId + ", " + branchName))
)
.map(NewPage::getApplicationId);
}
@Override
public Mono<NewPage> findByGitSyncIdAndDefaultApplicationId(String defaultApplicationId, String gitSyncId, AclPermission permission) {
return repository.findByGitSyncIdAndDefaultApplicationId(defaultApplicationId, gitSyncId, permission);
@ -534,4 +557,29 @@ public class NewPageServiceCEImpl extends BaseService<NewPageRepository, NewPage
public Flux<NewPage> findPageSlugsByApplicationIds(List<String> applicationIds, AclPermission aclPermission) {
return repository.findSlugsByApplicationIds(applicationIds, aclPermission);
}
/**
* Returns a list of pages of an Application.
* If Application ID is present, it'll fetch all pages of that application in the provided mode.
* if Page ID is present, it'll fetch all pages of the corresponding Application.
* If both IDs are present, it'll use the Application ID only and ignore the Page ID
* @param applicationId Id of the application
* @param pageId id of a page
* @param branchName name of the current branch
* @param mode In which mode it's in
* @return List of ApplicationPagesDTO
*/
@Override
public Mono<ApplicationPagesDTO> findApplicationPages(String applicationId, String pageId, String branchName, ApplicationMode mode) {
boolean isViewMode = (mode == ApplicationMode.PUBLISHED);
if(StringUtils.hasLength(applicationId)) {
return findApplicationPagesByApplicationIdViewModeAndBranch(applicationId, branchName, isViewMode, true);
} else if(StringUtils.hasLength(pageId)) {
return findBranchedApplicationIdFromNewPage(branchName, pageId)
.flatMap(branchedApplicationId -> findApplicationPagesByApplicationIdViewMode(branchedApplicationId, isViewMode, true))
.map(responseUtils::updateApplicationPagesDTOWithDefaultResources);
} else {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.APPLICATION_ID + " or " + FieldName.PAGE_ID));
}
}
}

View File

@ -2523,6 +2523,8 @@ public class GitServiceTest {
@WithUserDetails(value ="api_user")
public void deleteBranch_staleBranchNotInDB_Success() throws IOException {
Application application = createApplicationConnectedToGit("deleteBranch_staleBranchNotInDB_Success", "master");
application.getGitApplicationMetadata().setDefaultBranchName("master");
applicationService.save(application).block();
Mockito.when(gitExecutor.deleteBranch(Mockito.any(Path.class), Mockito.anyString()))
.thenReturn(Mono.just(true));
@ -2541,6 +2543,8 @@ public class GitServiceTest {
@WithUserDetails(value ="api_user")
public void deleteBranch_existsInDB_Success() throws IOException {
Application application = createApplicationConnectedToGit("deleteBranch_existsInDB_Success", "master");
application.getGitApplicationMetadata().setDefaultBranchName("test");
applicationService.save(application).block();
Mockito.when(gitExecutor.deleteBranch(Mockito.any(Path.class), Mockito.anyString()))
.thenReturn(Mono.just(true));
@ -2559,6 +2563,8 @@ public class GitServiceTest {
@WithUserDetails(value = "api_user")
public void deleteBranch_branchDoesNotExist_ThrowError() throws IOException {
Application application = createApplicationConnectedToGit("deleteBranch_branchDoesNotExist_ThrowError", "master");
application.getGitApplicationMetadata().setDefaultBranchName("test");
applicationService.save(application).block();
Mockito.when(gitExecutor.deleteBranch(Mockito.any(Path.class), Mockito.anyString()))
.thenReturn(Mono.just(false));
@ -2570,4 +2576,22 @@ public class GitServiceTest {
throwable.getMessage().contains("delete branch. Branch does not exists in the repo"))
.verify();
}
@Test
@WithUserDetails(value = "api_user")
public void deleteBranch_defaultBranch_ThrowError() throws IOException {
Application application = createApplicationConnectedToGit("deleteBranch_defaultBranch_ThrowError", "master");
application.getGitApplicationMetadata().setDefaultBranchName("master");
applicationService.save(application).block();
Mockito.when(gitExecutor.deleteBranch(Mockito.any(Path.class), Mockito.anyString()))
.thenReturn(Mono.just(false));
Mono<Application> applicationMono = gitService.deleteBranch(application.getId(), "master");
StepVerifier
.create(applicationMono)
.expectErrorMatches(throwable -> throwable instanceof AppsmithException &&
throwable.getMessage().contains("Cannot delete default branch"))
.verify();
}
}

View File

@ -0,0 +1,97 @@
package com.appsmith.server.services;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.Organization;
import com.appsmith.server.dtos.ApplicationPagesDTO;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.exceptions.AppsmithException;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithUserDetails;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@RunWith(SpringRunner.class)
class NewPageServiceTest {
@Autowired
NewPageService newPageService;
@Autowired
ApplicationPageService applicationPageService;
@Autowired
OrganizationService organizationService;
@Test
@WithUserDetails("api_user")
void findApplicationPages_WhenApplicationIdAndPageIdNotPresent_ThrowsException() {
StepVerifier.create(
newPageService.findApplicationPages(null, null, "master", ApplicationMode.EDIT)
)
.expectError(AppsmithException.class)
.verify();
}
@Test
@WithUserDetails("api_user")
void findApplicationPages_WhenApplicationIdPresent_ReturnsPages() {
String randomId = UUID.randomUUID().toString();
Organization organization = new Organization();
organization.setName("org_" + randomId);
Mono<ApplicationPagesDTO> applicationPagesDTOMono = organizationService.create(organization).flatMap(createdOrg -> {
Application application = new Application();
application.setName("app_" + randomId);
return applicationPageService.createApplication(application, createdOrg.getId());
}).flatMap(application -> {
PageDTO pageDTO = new PageDTO();
pageDTO.setName("page_" + randomId);
pageDTO.setApplicationId(application.getId());
return applicationPageService.createPage(pageDTO);
}).flatMap(pageDTO ->
newPageService.findApplicationPages(pageDTO.getApplicationId(), null, null, ApplicationMode.EDIT)
);
StepVerifier.create(applicationPagesDTOMono).assertNext(applicationPagesDTO -> {
assertThat(applicationPagesDTO.getApplication()).isNotNull();
assertThat(applicationPagesDTO.getApplication().getName()).isEqualTo("app_"+randomId);
assertThat(applicationPagesDTO.getPages()).isNotEmpty();
}).verifyComplete();
}
@Test
@WithUserDetails("api_user")
void findApplicationPages_WhenPageIdPresent_ReturnsPages() {
String randomId = UUID.randomUUID().toString();
Organization organization = new Organization();
organization.setName("org_" + randomId);
Mono<ApplicationPagesDTO> applicationPagesDTOMono = organizationService.create(organization).flatMap(createdOrg -> {
Application application = new Application();
application.setName("app_" + randomId);
return applicationPageService.createApplication(application, createdOrg.getId());
}).flatMap(application -> {
PageDTO pageDTO = new PageDTO();
pageDTO.setName("page_" + randomId);
pageDTO.setApplicationId(application.getId());
return applicationPageService.createPage(pageDTO);
}).flatMap(pageDTO ->
newPageService.findApplicationPages(null, pageDTO.getId(), null, ApplicationMode.EDIT)
);
StepVerifier.create(applicationPagesDTOMono).assertNext(applicationPagesDTO -> {
assertThat(applicationPagesDTO.getApplication()).isNotNull();
assertThat(applicationPagesDTO.getApplication().getName()).isEqualTo("app_"+randomId);
assertThat(applicationPagesDTO.getPages()).isNotEmpty();
}).verifyComplete();
}
}