feat: Integrate one click binding to sourceData of select and multi select widget (#25750)

## Description
We have changed the property control of sourceData of select and multi
select widget to dropdowns which has one click-binding items.

#### PR fixes following issue(s)
Fixes https://github.com/appsmithorg/appsmith/issues/24780

#### Type of change

- New feature (non-breaking change which adds functionality)

## Testing
>
#### How Has This Been Tested?
> Please describe the tests that you ran to verify your changes. Also
list any relevant details for your test configuration.
> Delete anything that is not relevant
- [x] Manual
- [ ] Cypress
>
>
#### Test Plan
>(https://github.com/appsmithorg/TestSmith/issues/2472)
>
>
#### Issues raised during DP testing
>
(https://github.com/appsmithorg/appsmith/pull/25750#issuecomment-1665077044)
>
>
>
## Checklist:
#### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


#### QA activity:
- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-)
have been covered
- [x] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-)
- [x] Test plan has been peer reviewed by project stakeholders and other
QA members
- [x] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [ ] Cypress test cases have been added and approved by SDET/manual QA
- [ ] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
This commit is contained in:
balajisoundar 2023-08-10 10:51:19 +05:30 committed by GitHub
parent 0d533dab90
commit 727d30ad92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 982 additions and 243 deletions

View File

@ -2,6 +2,7 @@ const testdata = require("../../../../fixtures/testdata.json");
import {
entityExplorer,
agHelper,
propPane,
} from "../../../../support/Objects/ObjectsCore";
describe("Binding the multiple widgets and validating default data", function () {
@ -11,6 +12,7 @@ describe("Binding the multiple widgets and validating default data", function ()
it("1. Dropdown widget test with invalid binding value", function () {
entityExplorer.SelectEntityByName("Dropdown1");
propPane.ToggleJSMode("sourcedata");
cy.testJsontext("sourcedata", JSON.stringify(testdata.defaultdataBinding));
cy.evaluateErrorMessage(testdata.dropdownErrorMsg);
//Table widget test with invalid binding value

View File

@ -20,6 +20,7 @@ describe("Binding the multiple widgets and validating default data", function ()
);
//Dropdown widget test with default value from table widget
entityExplorer.SelectEntityByName("Dropdown1");
propPane.ToggleJSMode("sourcedata");
cy.testJsontext(
"sourcedata",
JSON.stringify(testdata.deafultDropDownWidget),

View File

@ -147,7 +147,10 @@ describe("excludeForAirgap", "Widget property navigation", () => {
);
_.agHelper.GetNClick(OneClickBindingLocator.searchableColumn);
_.agHelper.GetNClick(
OneClickBindingLocator.searchableColumnDropdownOption(),
OneClickBindingLocator.columnDropdownOption(
"searchableColumn",
"imdb_id",
),
);
_.agHelper.GetNClick(OneClickBindingLocator.connectData);

View File

@ -0,0 +1,97 @@
import oneClickBindingLocator from "../../../../../locators/OneClickBindingLocator";
import { OneClickBinding } from "../spec_utility";
import {
agHelper,
entityExplorer,
dataSources,
draggableWidgets,
assertHelper,
propPane,
} from "../../../../../support/Objects/ObjectsCore";
import formWidgetsPage from "../../../../../locators/FormWidgets.json";
import widgetsPage from "../../../../../locators/Widgets.json";
import commonlocators from "../../../../../locators/commonlocators.json";
const oneClickBinding = new OneClickBinding();
describe("Table widget one click binding feature", () => {
it("should check that queries are created and bound to table widget properly", () => {
entityExplorer.DragDropWidgetNVerify(
draggableWidgets.MULTISELECT,
450,
200,
);
entityExplorer.NavigateToSwitcher("Explorer");
dataSources.CreateDataSource("Mongo");
cy.get("@dsName").then((dsName) => {
entityExplorer.NavigateToSwitcher("Widgets");
entityExplorer.SelectEntityByName("MultiSelect1", "Widgets");
oneClickBinding.ChooseAndAssertForm(`${dsName}`, dsName, "netflix", {
label: "name",
value: "director",
});
});
agHelper.GetNClick(oneClickBindingLocator.connectData);
assertHelper.AssertNetworkStatus("@postExecute");
agHelper.Sleep(2000);
entityExplorer.DragDropWidgetNVerify(draggableWidgets.TEXT, 450, 500);
propPane.UpdatePropertyFieldValue(
"Text",
`{{MultiSelect1.selectedOptionLabels.toString()}}:{{MultiSelect1.selectedOptionValues.toString()}}`,
);
[
{
label: "I Care a Lot",
text: "I Care a Lot:J Blakeson",
},
{
label: "tick, tick...BOOM!",
text: "I Care a Lot,tick, tick...BOOM!:J Blakeson,Lin-Manuel Miranda",
},
{
label: "Munich The Edge of War",
text: "I Care a Lot,tick, tick...BOOM!,Munich The Edge of War:J Blakeson,Lin-Manuel Miranda,Christian Schwochow",
},
].forEach((d) => {
cy.get(formWidgetsPage.multiSelectWidget)
.find(".rc-select-selector")
.click({
force: true,
});
cy.get(".rc-select-item").contains(d.label).click({
force: true,
});
cy.get(commonlocators.TextInside).first().should("have.text", d.text);
});
agHelper.Sleep(2000);
cy.get(formWidgetsPage.multiselectWidgetv2search)
.first()
.focus({ force: true } as any)
.type("Haunting", { force: true });
assertHelper.AssertNetworkStatus("@postExecute");
agHelper.Sleep(2000);
cy.get(
".rc-select-item-option:contains('The Haunting of Hill House')",
).should("have.length", 1);
cy.get(".rc-select-item")
.contains("The Haunting of Hill House")
.should("exist");
});
});

View File

@ -0,0 +1,104 @@
import oneClickBindingLocator from "../../../../../locators/OneClickBindingLocator";
import { OneClickBinding } from "../spec_utility";
import {
agHelper,
entityExplorer,
dataSources,
draggableWidgets,
assertHelper,
propPane,
} from "../../../../../support/Objects/ObjectsCore";
import formWidgetsPage from "../../../../../locators/FormWidgets.json";
import widgetsPage from "../../../../../locators/Widgets.json";
import commonlocators from "../../../../../locators/commonlocators.json";
const oneClickBinding = new OneClickBinding();
describe("Table widget one click binding feature", () => {
it("should check that queries are created and bound to table widget properly", () => {
entityExplorer.DragDropWidgetNVerify(
draggableWidgets.MULTISELECT,
450,
200,
);
entityExplorer.NavigateToSwitcher("Explorer");
dataSources.CreateDataSource("Postgres");
cy.get("@dsName").then((dsName) => {
entityExplorer.NavigateToSwitcher("Widgets");
entityExplorer.SelectEntityByName("MultiSelect1", "Widgets");
oneClickBinding.ChooseAndAssertForm(
`${dsName}`,
dsName,
"public.employees",
{
label: "first_name",
value: "last_name",
},
);
});
agHelper.GetNClick(oneClickBindingLocator.connectData);
assertHelper.AssertNetworkStatus("@postExecute");
agHelper.Sleep(2000);
entityExplorer.DragDropWidgetNVerify(draggableWidgets.TEXT, 450, 500);
propPane.UpdatePropertyFieldValue(
"Text",
`{{MultiSelect1.selectedOptionLabels.toString()}}:{{MultiSelect1.selectedOptionValues.toString()}}`,
);
[
{
label: "Andrew",
text: "Andrew:Fuller",
},
{
label: "Janet",
text: "Andrew,Janet:Fuller,Leverling",
},
{
label: "Margaret",
text: "Andrew,Janet,Margaret:Fuller,Leverling,Peacock",
},
].forEach((d) => {
cy.get(formWidgetsPage.multiSelectWidget)
.find(".rc-select-selector")
.click({
force: true,
});
cy.get(".rc-select-item").contains(d.label).click({
force: true,
});
cy.get(commonlocators.TextInside).first().should("have.text", d.text);
});
agHelper.Sleep(2000);
cy.get(formWidgetsPage.multiSelectWidget)
.find(".rc-select-selector")
.click({
force: true,
});
cy.get(formWidgetsPage.multiselectwidgetv2)
.find(".rc-select-selection-search-input")
.first()
.focus({ force: true } as any)
.type("Anne", { force: true });
assertHelper.AssertNetworkStatus("@postExecute");
agHelper.Sleep(2000);
cy.get(".rc-select-item").contains("Anne").should("exist");
});
});

View File

@ -99,23 +99,17 @@ describe("excludeForAirgap", "One click binding control", () => {
propPane.ToggleJSMode("Table data", false);
oneClickBinding.ChooseAndAssertForm(
"Users",
"Users",
"public.users",
"gender",
);
oneClickBinding.ChooseAndAssertForm("Users", "Users", "public.users", {
searchableColumn: "gender",
});
propPane.MoveToTab("Style");
propPane.MoveToTab("Content");
oneClickBinding.ChooseAndAssertForm(
"sample Movies",
"movies",
"movies",
"status",
);
oneClickBinding.ChooseAndAssertForm("sample Movies", "movies", "movies", {
searchableColumn: "status",
});
entityExplorer.NavigateToSwitcher("Explorer");
dataSources.NavigateToDSCreateNew();

View File

@ -0,0 +1,98 @@
import oneClickBindingLocator from "../../../../../locators/OneClickBindingLocator";
import { OneClickBinding } from "../spec_utility";
import {
agHelper,
entityExplorer,
dataSources,
draggableWidgets,
assertHelper,
propPane,
} from "../../../../../support/Objects/ObjectsCore";
import formWidgetsPage from "../../../../../locators/FormWidgets.json";
import widgetsPage from "../../../../../locators/Widgets.json";
import commonlocators from "../../../../../locators/commonlocators.json";
const oneClickBinding = new OneClickBinding();
describe("Table widget one click binding feature", () => {
it("should check that queries are created and bound to table widget properly", () => {
entityExplorer.DragDropWidgetNVerify(draggableWidgets.SELECT, 450, 200);
entityExplorer.NavigateToSwitcher("Explorer");
dataSources.CreateDataSource("Mongo");
cy.get("@dsName").then((dsName) => {
entityExplorer.NavigateToSwitcher("Widgets");
entityExplorer.SelectEntityByName("Select1", "Widgets");
oneClickBinding.ChooseAndAssertForm(`${dsName}`, dsName, "netflix", {
label: "name",
value: "director",
});
});
agHelper.GetNClick(oneClickBindingLocator.connectData);
assertHelper.AssertNetworkStatus("@postExecute");
agHelper.Sleep(2000);
entityExplorer.DragDropWidgetNVerify(draggableWidgets.TEXT, 450, 500);
propPane.UpdatePropertyFieldValue(
"Text",
`{{Select1.selectedOptionLabel}}:{{Select1.selectedOptionValue}}`,
);
[
{
label: "I Care a Lot",
text: "I Care a Lot:J Blakeson",
},
{
label: "tick, tick...BOOM!",
text: "tick, tick...BOOM!:Lin-Manuel Miranda",
},
{
label: "Munich The Edge of War",
text: "Munich The Edge of War:Christian Schwochow",
},
].forEach((d) => {
cy.get(formWidgetsPage.selectWidget)
.find(widgetsPage.dropdownSingleSelect)
.click({
force: true,
});
cy.get(commonlocators.singleSelectWidgetMenuItem)
.contains(d.label)
.click({
force: true,
});
cy.get(commonlocators.TextInside).first().should("have.text", d.text);
});
cy.get(formWidgetsPage.selectWidget)
.find(widgetsPage.dropdownSingleSelect)
.click({
force: true,
});
cy.get(commonlocators.selectInputSearch).type("I Care a Lot");
assertHelper.AssertNetworkStatus("@postExecute");
agHelper.Sleep(2000);
cy.get(".select-popover-wrapper .menu-item-link")
.children()
.should("have.length", 1);
agHelper.AssertElementExist(
commonlocators.singleSelectWidgetMenuItem + `:contains(I Care a Lot)`,
);
});
});

View File

@ -0,0 +1,103 @@
import oneClickBindingLocator from "../../../../../locators/OneClickBindingLocator";
import { OneClickBinding } from "../spec_utility";
import {
agHelper,
entityExplorer,
dataSources,
draggableWidgets,
assertHelper,
propPane,
} from "../../../../../support/Objects/ObjectsCore";
import formWidgetsPage from "../../../../../locators/FormWidgets.json";
import widgetsPage from "../../../../../locators/Widgets.json";
import commonlocators from "../../../../../locators/commonlocators.json";
const oneClickBinding = new OneClickBinding();
describe("Table widget one click binding feature", () => {
it("should check that queries are created and bound to table widget properly", () => {
entityExplorer.DragDropWidgetNVerify(draggableWidgets.SELECT, 450, 200);
entityExplorer.NavigateToSwitcher("Explorer");
dataSources.CreateDataSource("Postgres");
cy.get("@dsName").then((dsName) => {
entityExplorer.NavigateToSwitcher("Widgets");
entityExplorer.SelectEntityByName("Select1", "Widgets");
oneClickBinding.ChooseAndAssertForm(
`${dsName}`,
dsName,
"public.employees",
{
label: "first_name",
value: "last_name",
},
);
});
agHelper.GetNClick(oneClickBindingLocator.connectData);
assertHelper.AssertNetworkStatus("@postExecute");
agHelper.Sleep(2000);
entityExplorer.DragDropWidgetNVerify(draggableWidgets.TEXT, 450, 500);
propPane.UpdatePropertyFieldValue(
"Text",
`{{Select1.selectedOptionLabel}}:{{Select1.selectedOptionValue}}`,
);
[
{
label: "Andrew",
text: "Andrew:Fuller",
},
{
label: "Janet",
text: "Janet:Leverling",
},
{
label: "Margaret",
text: "Margaret:Peacock",
},
].forEach((d) => {
cy.get(formWidgetsPage.selectWidget)
.find(widgetsPage.dropdownSingleSelect)
.click({
force: true,
});
cy.get(commonlocators.singleSelectWidgetMenuItem)
.contains(d.label)
.click({
force: true,
});
cy.get(commonlocators.TextInside).first().should("have.text", d.text);
});
cy.get(formWidgetsPage.selectWidget)
.find(widgetsPage.dropdownSingleSelect)
.click({
force: true,
});
cy.get(commonlocators.selectInputSearch).type("Anne");
assertHelper.AssertNetworkStatus("@postExecute");
agHelper.Sleep(2000);
cy.get(".select-popover-wrapper .menu-item-link")
.children()
.should("have.length", 1);
agHelper.AssertElementExist(
commonlocators.singleSelectWidgetMenuItem + `:contains(Anne)`,
);
});
});

View File

@ -25,12 +25,9 @@ describe("one click binding mongodb datasource", function () {
cy.get("@dsName").then((dsName) => {
entityExplorer.SelectEntityByName("Table1", "Widgets");
oneClickBinding.ChooseAndAssertForm(
`${dsName}`,
dsName,
"netflix",
"creator",
);
oneClickBinding.ChooseAndAssertForm(`${dsName}`, dsName, "netflix", {
searchableColumn: "creator",
});
});
agHelper.GetNClick(oneClickBindingLocator.connectData);

View File

@ -23,12 +23,9 @@ describe.skip("Table widget one click binding feature", () => {
entityExplorer.SelectEntityByName("Table1", "Widgets");
oneClickBinding.ChooseAndAssertForm(
`${dsName}`,
dsName,
"configs",
"configName",
);
oneClickBinding.ChooseAndAssertForm(`${dsName}`, dsName, "configs", {
searchableColumn: "configName",
});
});
agHelper.GetNClick(oneClickBindingLocator.connectData);

View File

@ -28,7 +28,9 @@ describe("Table widget one click binding feature", () => {
`${dsName}`,
dsName,
"public.employees",
"first_name",
{
searchableColumn: "first_name",
},
);
});

View File

@ -6,7 +6,7 @@ export class OneClickBinding {
source?: string,
selectedSource?: any,
table?: string,
column?: string,
column: Record<string, string> = {},
) {
agHelper.GetNClick(oneClickBindingLocator.datasourceDropdownSelector);
@ -37,17 +37,19 @@ export class OneClickBinding {
oneClickBindingLocator.tableOrSpreadsheetSelectedOption(table),
);
agHelper.AssertElementExist(oneClickBindingLocator.searchableColumn);
Object.entries(column).forEach(([key, value]) => {
agHelper.AssertElementExist((oneClickBindingLocator as any)[key]);
agHelper.GetNClick(oneClickBindingLocator.searchableColumn);
agHelper.GetNClick((oneClickBindingLocator as any)[key]);
agHelper.GetNClick(
oneClickBindingLocator.searchableColumnDropdownOption(column),
);
agHelper.GetNClick(
oneClickBindingLocator.columnDropdownOption(key, value),
);
agHelper.AssertElementExist(
oneClickBindingLocator.searchableColumnSelectedOption(column),
);
agHelper.AssertElementExist(
oneClickBindingLocator.columnSelectedOption(key, value),
);
});
agHelper.AssertElementExist(oneClickBindingLocator.connectData);

View File

@ -17,6 +17,7 @@ describe("Dropdown Widget Functionality", function () {
it("should check that empty value is allowed in options", () => {
cy.openPropertyPane("selectwidget");
_.propPane.ToggleJSMode("sourcedata");
cy.updateCodeInput(
".t--property-control-sourcedata",
`[
@ -35,16 +36,16 @@ describe("Dropdown Widget Functionality", function () {
]`,
);
_.propPane.ToggleJSMode("label");
_.propPane.ToggleJSMode("label key");
cy.updateCodeInput(
".t--property-control-wrapper.t--property-control-label",
".t--property-control-wrapper.t--property-control-labelkey",
`label`,
);
_.propPane.ToggleJSMode("value");
cy.updateCodeInput(".t--property-control-value", `value`);
_.propPane.ToggleJSMode("value key");
cy.updateCodeInput(".t--property-control-valuekey", `value`);
cy.get(".t--property-control-value .t--codemirror-has-error").should(
cy.get(".t--property-control-valuekey .t--codemirror-has-error").should(
"not.exist",
);
});
@ -68,7 +69,7 @@ describe("Dropdown Widget Functionality", function () {
}
]`,
);
cy.get(".t--property-control-value .t--codemirror-has-error").should(
cy.get(".t--property-control-valuekey .t--codemirror-has-error").should(
"exist",
);
});
@ -91,9 +92,7 @@ describe("Dropdown Widget Functionality", function () {
}]`,
);
cy.updateCodeInput(".t--property-control-defaultselectedvalue", "BLUE");
cy.get(".t--property-control-value .t--codemirror-has-error").should(
"not.exist",
);
cy.get(".t--property-key .t--codemirror-has-error").should("not.exist");
cy.get(
".t--property-control-defaultselectedvalue .t--codemirror-has-error",
).should("not.exist");

View File

@ -21,6 +21,8 @@ describe("Select Widgets", function () {
100,
);
_.propPane.ToggleJSMode("sourcedata");
_.propPane.UpdatePropertyFieldValue(
"Source Data",
`{{[{
@ -29,14 +31,14 @@ describe("Select Widgets", function () {
}]}}`,
);
_.propPane.ToggleJSMode("label");
_.propPane.ToggleJSMode("label key");
cy.updateCodeInput(
".t--property-control-wrapper.t--property-control-label",
".t--property-control-wrapper.t--property-control-labelkey",
`label`,
);
_.propPane.ToggleJSMode("value");
cy.updateCodeInput(".t--property-control-value", `value`);
_.propPane.ToggleJSMode("value key");
cy.updateCodeInput(".t--property-control-valuekey", `value`);
_.propPane.UpdatePropertyFieldValue(
"Default selected values",
@ -50,6 +52,8 @@ describe("Select Widgets", function () {
_.entityExplorer.DragDropWidgetNVerify(_.draggableWidgets.SELECT, 250, 300);
_.propPane.ToggleJSMode("sourcedata");
_.propPane.UpdatePropertyFieldValue(
"Source Data",
`{{[{
@ -58,14 +62,14 @@ describe("Select Widgets", function () {
}]}}`,
);
_.propPane.ToggleJSMode("label");
_.propPane.ToggleJSMode("label key");
cy.updateCodeInput(
".t--property-control-wrapper.t--property-control-label",
".t--property-control-wrapper.t--property-control-labelkey",
`label`,
);
_.propPane.ToggleJSMode("value");
cy.updateCodeInput(".t--property-control-value", `value`);
_.propPane.ToggleJSMode("value key");
cy.updateCodeInput(".t--property-control-valuekey", `value`);
_.propPane.UpdatePropertyFieldValue(
"Default selected value",

View File

@ -12,6 +12,7 @@ describe("MultiSelect Widget Functionality", function () {
_.entityExplorer.DragDropWidgetNVerify(_.draggableWidgets.MULTISELECT);
//should check that empty value is allowed in options", () => {
cy.openPropertyPane("multiselectwidgetv2");
_.propPane.ToggleJSMode("sourcedata");
cy.updateCodeInput(
".t--property-control-sourcedata",
`[
@ -30,16 +31,16 @@ describe("MultiSelect Widget Functionality", function () {
]`,
);
_.propPane.ToggleJSMode("label");
_.propPane.ToggleJSMode("labelkey");
cy.updateCodeInput(
".t--property-control-wrapper.t--property-control-label",
".t--property-control-wrapper.t--property-control-labelkey",
`label`,
);
_.propPane.ToggleJSMode("value");
cy.updateCodeInput(".t--property-control-value", `value`);
_.propPane.ToggleJSMode("valuekey");
cy.updateCodeInput(".t--property-control-valuekey", `value`);
cy.get(".t--property-control-value .t--codemirror-has-error").should(
cy.get(".t--property-control-valuekey .t--codemirror-has-error").should(
"not.exist",
);
});
@ -63,7 +64,7 @@ describe("MultiSelect Widget Functionality", function () {
}
]`,
);
cy.get(".t--property-control-value .t--codemirror-has-error").should(
cy.get(".t--property-control-valuekey .t--codemirror-has-error").should(
"exist",
);
});
@ -96,7 +97,7 @@ describe("MultiSelect Widget Functionality", function () {
}
]`,
);
cy.get(".t--property-control-value .t--codemirror-has-error").should(
cy.get(".t--property-control-valuekey .t--codemirror-has-error").should(
"not.exist",
);
cy.get(
@ -135,7 +136,7 @@ describe("MultiSelect Widget Functionality", function () {
"RED"
]`,
);
cy.get(".t--property-control-value .t--codemirror-has-error").should(
cy.get(".t--property-control-valuekey .t--codemirror-has-error").should(
"not.exist",
);
cy.get(

View File

@ -18,19 +18,20 @@ describe("MultiSelect Widget Functionality", function () {
it("1. Selects value with invalid default value", () => {
cy.openPropertyPane("multiselectwidgetv2");
_.propPane.ToggleJSMode("sourcedata");
_.propPane.UpdatePropertyFieldValue(
"Source Data",
JSON.stringify(data.input),
);
_.propPane.ToggleJSMode("label");
_.propPane.ToggleJSMode("labelkey");
cy.updateCodeInput(
".t--property-control-wrapper.t--property-control-label",
".t--property-control-wrapper.t--property-control-labelkey",
`label`,
);
_.propPane.ToggleJSMode("value");
cy.updateCodeInput(".t--property-control-value", `value`);
_.propPane.ToggleJSMode("valuekey");
cy.updateCodeInput(".t--property-control-valuekey", `value`);
_.propPane.UpdatePropertyFieldValue(
"Default selected values",

View File

@ -17,6 +17,7 @@ describe("MultiSelect Widget Functionality", function () {
});
it("1. Add new multiselect widget", () => {
_.entityExplorer.DragDropWidgetNVerify(_.draggableWidgets.MULTISELECT);
_.propPane.ToggleJSMode("sourcedata");
_.propPane.UpdatePropertyFieldValue(
"Source Data",
`[
@ -35,14 +36,14 @@ describe("MultiSelect Widget Functionality", function () {
]`,
);
_.propPane.ToggleJSMode("label");
_.propPane.ToggleJSMode("labelkey");
cy.updateCodeInput(
".t--property-control-wrapper.t--property-control-label",
".t--property-control-wrapper.t--property-control-labelkey",
`label`,
);
_.propPane.ToggleJSMode("value");
cy.updateCodeInput(".t--property-control-value", `value`);
_.propPane.ToggleJSMode("valuekey");
cy.updateCodeInput(".t--property-control-valuekey", `value`);
_.propPane.UpdatePropertyFieldValue(
"Default selected values",

View File

@ -20,6 +20,8 @@ describe("Select Widget Functionality", function () {
);
_.agHelper.AssertElementExist(".t--widget-multiselectwidgetv2");
_.propPane.ToggleJSMode("sourcedata");
_.propPane.UpdatePropertyFieldValue(
"Source Data",
`[
@ -39,12 +41,12 @@ describe("Select Widget Functionality", function () {
]`,
);
_.agHelper.GetNClick(_.propPane._selectPropDropdown("label"));
_.agHelper.GetNClick(_.propPane._selectPropDropdown("labelkey"));
["1", "2", "3"].forEach((d) => {
_.agHelper.AssertElementExist(_.propPane._dropDownValue(d));
});
_.agHelper.GetNClick(_.propPane._selectPropDropdown("value"), 0, true);
_.agHelper.GetNClick(_.propPane._selectPropDropdown("valuekey"), 0, true);
["1", "2", "3"].forEach((d) => {
_.agHelper.AssertElementExist(_.propPane._dropDownValue(d));
});

View File

@ -16,6 +16,8 @@ describe("Select Widget Functionality", function () {
_.entityExplorer.DragDropWidgetNVerify(_.draggableWidgets.SELECT, 450, 200);
_.agHelper.AssertElementExist(".t--widget-selectwidget");
_.propPane.ToggleJSMode("sourcedata");
_.propPane.UpdatePropertyFieldValue(
"Source Data",
`[
@ -35,12 +37,12 @@ describe("Select Widget Functionality", function () {
]`,
);
_.agHelper.GetNClick(_.propPane._selectPropDropdown("label"));
_.agHelper.GetNClick(_.propPane._selectPropDropdown("labelkey"));
["1", "2", "3"].forEach((d) => {
_.agHelper.AssertElementExist(_.propPane._dropDownValue(d));
});
_.agHelper.GetNClick(_.propPane._selectPropDropdown("value"), 0, true);
_.agHelper.GetNClick(_.propPane._selectPropDropdown("valuekey"), 0, true);
["1", "2", "3"].forEach((d) => {
_.agHelper.AssertElementExist(_.propPane._dropDownValue(d));
});

View File

@ -20,6 +20,7 @@ describe("Select Widget Functionality", function () {
cy.get(explorer.addWidget).click();
cy.dragAndDropToCanvas("selectwidget", { x: 300, y: 300 });
cy.get(".t--widget-selectwidget").should("exist");
_.propPane.ToggleJSMode("sourcedata");
cy.updateCodeInput(
".t--property-control-sourcedata",
`[
@ -38,14 +39,14 @@ describe("Select Widget Functionality", function () {
]`,
);
_.propPane.ToggleJSMode("label");
_.propPane.ToggleJSMode("labelkey");
cy.updateCodeInput(
".t--property-control-wrapper.t--property-control-label",
".t--property-control-wrapper.t--property-control-labelkey",
`label`,
);
_.propPane.ToggleJSMode("value");
cy.updateCodeInput(".t--property-control-value", `value`);
_.propPane.ToggleJSMode("valuekey");
cy.updateCodeInput(".t--property-control-valuekey", `value`);
cy.updateCodeInput(
".t--property-control-defaultselectedvalue",

View File

@ -20,6 +20,7 @@ describe("Select Widget Functionality", function () {
it("should check that virtualization works well", () => {
cy.openPropertyPane("selectwidget");
_.propPane.ToggleJSMode("sourcedata");
cy.updateCodeInput(
".t--property-control-sourcedata",
`[
@ -50,14 +51,14 @@ describe("Select Widget Functionality", function () {
]`,
);
_.propPane.ToggleJSMode("label");
_.propPane.ToggleJSMode("labelkey");
cy.updateCodeInput(
".t--property-control-wrapper.t--property-control-label",
".t--property-control-wrapper.t--property-control-labelkey",
`label`,
);
_.propPane.ToggleJSMode("value");
cy.updateCodeInput(".t--property-control-value", `value`);
_.propPane.ToggleJSMode("valuekey");
cy.updateCodeInput(".t--property-control-valuekey", `value`);
cy.get(".t--property-control-value .t--codemirror-has-error").should(
"not.exist",

View File

@ -304,6 +304,7 @@ describe("JSObjects OnLoad Actions tests", function () {
//jsEditor.EnableDisableAsyncFuncSettings("callCountry", false, true); Bug # 13826
entityExplorer.SelectEntityByName("Select1", "Widgets");
propPane.ToggleJSMode("sourcedata");
propPane.UpdatePropertyFieldValue(
"Source Data",
`{{ getCitiesList.data.map((row) => {

View File

@ -18,19 +18,20 @@ describe("Bug #10784 - Passing params from JS to SQL query should not break", ()
before(() => {
entityExplorer.DragDropWidgetNVerify(draggableWidgets.BUTTON, 100, 100);
entityExplorer.DragDropWidgetNVerify(draggableWidgets.SELECT, 500, 100);
propPane.ToggleJSMode("sourcedata");
propPane.UpdatePropertyFieldValue(
"Source Data",
`[\n {\n \"label\": \"7\",\n \"value\": \"7\"\n },\n {\n \"label\": \"8\",\n \"value\": \"8\"\n },\n {\n \"label\": \"9\",\n \"value\": \"9\"\n }\n]`,
);
propPane.ToggleJSMode("label");
propPane.ToggleJSMode("labelkey");
(cy as any).updateCodeInput(
".t--property-control-wrapper.t--property-control-label",
".t--property-control-wrapper.t--property-control-labelkey",
`label`,
);
propPane.ToggleJSMode("value");
(cy as any).updateCodeInput(".t--property-control-value", `value`);
propPane.ToggleJSMode("valuekey");
(cy as any).updateCodeInput(".t--property-control-valuekey", `value`);
propPane.UpdatePropertyFieldValue(
"Default selected value",

View File

@ -150,7 +150,9 @@ describe("Validate MsSQL connection & basic querying with UI flows", () => {
it.skip("3.One click binding - should check that queries are created and bound to table widget properly", () => {
entityExplorer.DragDropWidgetNVerify(draggableWidgets.TABLE, 450, 200);
oneClickBinding.ChooseAndAssertForm(dsName, dsName, "Simpsons", "title");
oneClickBinding.ChooseAndAssertForm(dsName, dsName, "Simpsons", {
searchableColumn: "title",
});
agHelper.GetNClick(oneClickBindingLocator.connectData);

View File

@ -42,6 +42,7 @@
"sourceData": "",
"optionLabel": "label",
"optionValue": "value",
"dynamicPropertyPathList": [{"key": "sourceData"}],
"widgetName": "Dropdown1",
"defaultOptionValue": {
"value":"VEG",

View File

@ -147,6 +147,7 @@
"bottomRow": 23,
"parentId": "anq2not518",
"dynamicBindingPathList": [],
"dynamicPropertyPathList": [{"key": "sourceData"}],
"dynamicTriggerPathList": []
}
],

View File

@ -140,6 +140,7 @@
"sourceData": "[\n {\n \"label\": \"Blue\",\n \"value\": 0\n },\n {\n \"label\": \"Green\",\n \"value\": \"GREEN\"\n },\n {\n \"label\": \"Red\",\n \"value\": \"RED\"\n },\n\t{\n \"label\": \"Red2\",\n \"value\": \"\"\n }\n]",
"optionLabel": "label",
"optionValue": "value",
"dynamicPropertyPathList": [{"key": "sourceData"}],
"placeholderText": "Select option",
"isDisabled": false,
"key": "ber0u80gjl",

View File

@ -137,6 +137,7 @@
"sourceData": "",
"optionLabel": "label",
"optionValue": "value",
"dynamicPropertyPathList": [{"key": "sourceData"}],
"isDisabled": false
},
{

View File

@ -95,6 +95,7 @@
"sourceData": "",
"optionLabel": "label",
"optionValue": "value",
"dynamicPropertyPathList": [{"key": "sourceData"}],
"widgetName": "Dropdown1",
"type": "SELECT_WIDGET",
"isLoading": false,
@ -135,6 +136,7 @@
"leftColumn": 10,
"optionLable": "label",
"optionValue": "value",
"dynamicPropertyPathList": [{"key": "sourceData"}],
"sourceData": [
{
"label": "Hashirama Senju",

View File

@ -4,6 +4,7 @@
"dropdownWidget": ".t--draggable-selectwidget",
"menuButtonWidget": ".t--draggable-menubuttonwidget",
"multiselectwidgetv2": ".t--draggable-multiselectwidgetv2",
"multiselectWidgetv2search": ".multi-select-dropdown .bp3-input",
"multiselecttreeWidget": ".t--draggable-multiselecttreewidget",
"singleselecttreeWidget": ".t--draggable-singleselecttreewidget",
"dropdownSelectionType": ".t--property-control-selectiontype .bp3-popover-target",

View File

@ -34,16 +34,6 @@ export default {
`[data-testid="t--one-click-binding-table-selector"] .rc-select-selection-item${
table ? `:contains(${table})` : ""
}`,
searchableColumn:
'[data-testId="t--one-click-binding-column-searchableColumn"]',
searchableColumnDropdownOption: (column?: string) =>
`[data-testId='t--one-click-binding-column-searchableColumn--column']${
column ? `:contains(${column})` : ""
}`,
searchableColumnSelectedOption: (column?: string) =>
`[data-testId="t--one-click-binding-column-searchableColumn"] .rc-select-selection-item${
column ? `:contains(${column})` : ""
}`,
validTableRowData:
'.t--widget-tablewidgetv2 [role="rowgroup"] [role="button"]',
tableError: (error: string) =>
@ -52,4 +42,16 @@ export default {
dayViewFromDate: ".DayPicker-Day",
loadMore: "[data-testId='t--one-click-binding-datasource--load-more']",
datasourceSearch: `[data-testId="t--one-click-binding-datasource--search"]`,
searchableColumn:
'[data-testId="t--one-click-binding-column-searchableColumn"]',
label: '[data-testId="t--one-click-binding-column-label"]',
value: '[data-testId="t--one-click-binding-column-value"]',
columnDropdownOption: (column: string, value?: string) =>
`[data-testId='t--one-click-binding-column-${column}--column']${
value ? `:contains(${value})` : ""
}`,
columnSelectedOption: (column: string, value?: string) =>
`[data-testId="t--one-click-binding-column-${column}"] .rc-select-selection-item${
value ? `:contains(${value})` : ""
}`,
};

View File

@ -54,35 +54,10 @@ export default abstract class GSheets extends BaseQueryGenerator {
const { select } = widgetConfig;
if (select && formConfig.sheetName) {
return {
const queryPayload: any = {
type: QUERY_TYPE.SELECT,
name: `Find_${removeSpecialChars(formConfig.sheetName)}`,
formData: {
where: {
data: {
children: [
{
condition: "CONTAINS",
key: `{{${select["where"]} ? "${formConfig.searchableColumn}" : ""}}`,
value: `{{${select["where"]}}}`,
},
],
},
},
sortBy: {
data: [
{
column: `{{${select["orderBy"]}}}`,
order: select["sortOrder"],
},
],
},
pagination: {
data: {
limit: `{{${select["limit"]}}}`,
offset: `{{${select["offset"]}}}`,
},
},
...this.buildBasicConfig(
COMMAND_TYPES.FIND,
formConfig.tableName,
@ -90,18 +65,56 @@ export default abstract class GSheets extends BaseQueryGenerator {
formConfig.tableHeaderIndex,
),
},
dynamicBindingPathList: [
{
key: "formData.where.data",
},
{
key: "formData.sortBy.data",
},
{
key: "formData.pagination.data",
},
],
dynamicBindingPathList: [],
};
if (select["where"]) {
queryPayload.formData.where = {
data: {
children: [
{
condition: "CONTAINS",
key: `{{${select["where"]} ? "${formConfig.searchableColumn}" : ""}}`,
value: `{{${select["where"]}}}`,
},
],
},
};
queryPayload.dynamicBindingPathList.push({
key: "formData.where.data",
});
}
if (select["sortOrder"] && select["orderBy"]) {
queryPayload.formData.sortBy = {
data: [
{
column: `{{${select["orderBy"]}}}`,
order: select["sortOrder"],
},
],
};
queryPayload.dynamicBindingPathList.push({
key: "formData.sortBy.data",
});
}
if (select["limit"] && select["offset"]) {
queryPayload.formData.pagination = {
data: {
limit: `{{${select["limit"]}}}`,
offset: `{{${select["offset"]}}}`,
},
};
queryPayload.dynamicBindingPathList.push({
key: "formData.pagination.data",
});
}
return queryPayload;
}
}

View File

@ -77,7 +77,7 @@ describe("Mongo WidgetQueryGenerator", () => {
data: "{{data_table.pageSize}}",
},
query: {
data: '{{{ title: {$regex: data_table.searchText||""} }}}',
data: `{{{ title: {$regex: data_table.searchText||"", '$options' : 'i'} }}}`,
},
skip: {
data: "{{(data_table.pageNo - 1) * data_table.pageSize}}",
@ -152,7 +152,7 @@ describe("Mongo WidgetQueryGenerator", () => {
data: "{{data_table.pageSize}}",
},
query: {
data: '{{{ title: {$regex: data_table.searchText||""} }}}',
data: `{{{ title: {$regex: data_table.searchText||"", '$options' : 'i'} }}}`,
},
skip: {
data: "{{(data_table.pageNo - 1) * data_table.pageSize}}",

View File

@ -30,39 +30,66 @@ export default abstract class MongoDB extends BaseQueryGenerator {
const { select } = widgetConfig;
if (select) {
return {
const queryPayload: any = {
type: QUERY_TYPE.SELECT,
name: `Find_${removeSpecialChars(formConfig.tableName)}`,
formData: {
find: {
skip: { data: `{{${select["offset"]}}}` },
skip: { data: "" },
query: {
data: formConfig.searchableColumn
? `{{{ ${formConfig.searchableColumn}: {$regex: ${select["where"]}} }}}`
: "",
data: "",
},
sort: {
data: `{{ ${select["orderBy"]} ? { [${select["orderBy"]}]: ${select["sortOrder"]} ? 1 : -1 } : {}}}`,
data: "",
},
limit: {
data: "",
},
limit: { data: `{{${select["limit"]}}}` },
},
...this.buildBasicConfig(COMMAND_TYPES.FIND, formConfig.tableName),
},
dynamicBindingPathList: [
{
key: "formData.find.skip.data",
},
{
key: "formData.find.query.data",
},
{
key: "formData.find.sort.data",
},
{
key: "formData.find.limit.data",
},
],
dynamicBindingPathList: [],
};
if (select["offset"]) {
queryPayload.formData.find.skip = { data: `{{${select["offset"]}}}` };
queryPayload.dynamicBindingPathList.push({
key: "formData.find.skip.data",
});
}
if (formConfig.searchableColumn) {
queryPayload.formData.find.query = {
data: formConfig.searchableColumn
? `{{{ ${formConfig.searchableColumn}: {$regex: ${select["where"]}, '$options' : 'i'} }}}`
: "",
};
queryPayload.dynamicBindingPathList.push({
key: "formData.find.query.data",
});
}
if (select["orderBy"] && select["sortOrder"]) {
queryPayload.formData.find.sort = {
data: `{{ ${select["orderBy"]} ? { [${select["orderBy"]}]: ${select["sortOrder"]} ? 1 : -1 } : {}}}`,
};
queryPayload.dynamicBindingPathList.push({
key: "formData.find.sort.data",
});
}
if (select["limit"]) {
queryPayload.formData.find.limit = { data: `{{${select["limit"]}}}` };
queryPayload.dynamicBindingPathList.push({
key: "formData.find.limit.data",
});
}
return queryPayload;
}
}

View File

@ -18,11 +18,11 @@ export type WidgetQueryGenerationFormConfig = {
export type WidgetQueryGenerationConfig = {
select?: {
limit: string;
offset: string;
where: string;
orderBy: string;
sortOrder: string;
limit?: string;
offset?: string;
where?: string;
orderBy?: string;
sortOrder?: string;
};
create?: {
value: string;
@ -31,7 +31,7 @@ export type WidgetQueryGenerationConfig = {
value: string;
where?: string;
};
totalRecord: boolean;
totalRecord?: boolean;
};
export enum QUERY_TYPE {

View File

@ -284,6 +284,7 @@ function getWidgetProps(
{ key: "sourceData" },
{ key: "defaultOptionValue" },
],
dynamicPropertyPathList: [{ key: "sourceData" }],
},
};
case "TEXT_WIDGET":

View File

@ -106,6 +106,7 @@ export function useDatasource(searchText: string) {
onSourceClose,
propertyName,
propertyValue,
sampleData,
updateConfig,
widgetId,
} = useContext(WidgetQueryGeneratorFormContext);
@ -321,7 +322,7 @@ export function useDatasource(searchText: string) {
const { pageId: currentPageId } = useParams<ExplorerURLParams>();
const otherOptions = useMemo(() => {
return [
const options = [
{
icon: <Icon name="plus" size="md" />,
id: "Connect new datasource",
@ -350,7 +351,35 @@ export function useDatasource(searchText: string) {
},
},
];
}, [currentPageId, history, propertyName]);
if (sampleData) {
options.push({
icon: <Icon name="code" size="md" />,
id: "Sample data",
label: "Sample data",
value: "Sample data",
onSelect: () => {
addBinding(sampleData, false);
updateConfig({
datasource: "",
datasourcePluginType: "",
datasourcePluginName: "",
datasourceConnectionMode: "",
});
AnalyticsUtil.logEvent("BIND_OTHER_ACTIONS", {
widgetName: widget.widgetName,
widgetType: widget.type,
propertyName: propertyName,
selectedAction: "Sample data",
});
},
});
}
return options;
}, [currentPageId, history, propertyName, sampleData, addBinding]);
const queries = useSelector(getActionsForCurrentPage);
@ -439,31 +468,47 @@ export function useDatasource(searchText: string) {
];
}, [searchText, datasourceOptions, otherOptions, queryOptions]);
const selected = useMemo(() => {
let source;
if (config.datasource) {
source = datasourceOptions.find(
(option) => option.id === config.datasource,
);
} else if (
sampleData ===
(typeof propertyValue === "string"
? propertyValue
: JSON.stringify(propertyValue, null, 2))
) {
source = otherOptions.find((option) => option.value === "Sample data");
} else if (propertyValue) {
source = queryOptions.find((option) => option.value === propertyValue);
}
if (source) {
return (
<DropdownOption
label={source?.label?.replace("sample ", "")}
leftIcon={source?.icon}
/>
);
} else {
return <Placeholder>Connect data</Placeholder>;
}
}, [
config,
datasourceOptions,
sampleData,
propertyValue,
otherOptions,
queryOptions,
]);
return {
datasourceOptions: filteredDatasourceOptions,
otherOptions,
selected: (() => {
let source;
if (config.datasource) {
source = datasourceOptions.find(
(option) => option.id === config.datasource,
);
} else if (propertyValue) {
source = queryOptions.find((option) => option.value === propertyValue);
}
if (source) {
return (
<DropdownOption
label={source?.label?.replace("sample ", "")}
leftIcon={source?.icon}
/>
);
} else {
return <Placeholder>Connect data</Placeholder>;
}
})(),
selected,
queryOptions: filteredQueryOptions,
isSourceOpen,
onSourceClose,

View File

@ -1,7 +1,7 @@
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import type { AppState } from "@appsmith/reducers";
import { PluginPackageName } from "entities/Action";
import { useContext } from "react";
import { useContext, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getWidget } from "sagas/selectors";
import { getPluginPackageFromDatasourceId } from "selectors/entitiesSelector";
@ -14,7 +14,7 @@ import { useColumns } from "../WidgetSpecificControls/ColumnDropdown/useColumns"
export function useConnectData() {
const dispatch = useDispatch();
const { config, propertyName, widgetId } = useContext(
const { aliases, config, propertyName, widgetId } = useContext(
WidgetQueryGeneratorFormContext,
);
@ -27,16 +27,30 @@ export function useConnectData() {
);
const onClick = () => {
const searchableColumn = (() => {
if (config.searchableColumn) {
return config.searchableColumn;
} else {
const alias = aliases?.find((d) => d.isSearcheable)?.name;
return alias && config.alias[alias];
}
})();
const payload = {
tableName: config.table,
sheetName: config.sheet,
datasourceId: config.datasource,
widgetId: widgetId,
tableHeaderIndex: config.tableHeaderIndex,
searchableColumn: config.searchableColumn,
searchableColumn,
columns: columns.map((column) => column.name),
primaryColumn,
connectionMode: config.datasourceConnectionMode,
aliases: Object.entries(config.alias).map(([key, value]) => ({
name: key,
alias: value,
})),
};
dispatch({
@ -54,6 +68,7 @@ export function useConnectData() {
additionalData: {
dataTableName: config.table,
searchableColumn: config.searchableColumn,
alias: config.alias,
},
});
};
@ -64,10 +79,17 @@ export function useConnectData() {
const show = !!config.datasource;
const disabled =
!config.table ||
(selectedDatasourcePluginPackageName === PluginPackageName.GOOGLE_SHEETS &&
!isValidGsheetConfig(config));
const disabled = useMemo(() => {
return (
!config.table ||
(selectedDatasourcePluginPackageName ===
PluginPackageName.GOOGLE_SHEETS &&
!isValidGsheetConfig(config)) ||
aliases?.some((alias) => {
return alias.isRequired && !config.alias[alias.name];
})
);
}, [config, aliases]);
return {
show,

View File

@ -6,6 +6,7 @@ import { ErrorMessage, Label, SelectWrapper } from "../../styles";
import { useColumns } from "./useColumns";
type Props = {
id: string;
alias: string;
label: string;
onSelect: () => void;
@ -29,7 +30,7 @@ function ColumnDropdown(props: Props) {
<Label>{props.label}</Label>
<Select
allowClear
data-testId={`t--one-click-binding-column-${props.alias}`}
data-testId={`t--one-click-binding-column-${props.id}`}
dropdownStyle={{
minWidth: "350px",
maxHeight: "300px",
@ -51,7 +52,7 @@ function ColumnDropdown(props: Props) {
{options.map((option) => {
return (
<Option
data-testId={`t--one-click-binding-column-${props.alias}--column`}
data-testId={`t--one-click-binding-column-${props.id}--column`}
key={option.id}
value={option.value}
>

View File

@ -3,7 +3,6 @@ import type { AppState } from "@appsmith/reducers";
import { Icon } from "design-system";
import { PluginPackageName } from "entities/Action";
import { get, isArray } from "lodash";
import { ALLOWED_SEARCH_DATATYPE } from "pages/Editor/GeneratePage/components/constants";
import { useCallback, useContext, useMemo } from "react";
import { useSelector } from "react-redux";
import {
@ -70,28 +69,21 @@ export function useColumns(alias: string) {
};
});
} else if (isArray(columns)) {
return columns
.filter((column: any) => {
return (
column.type &&
ALLOWED_SEARCH_DATATYPE.includes(column.type.toLowerCase())
);
})
.map((column: any) => {
return {
id: column.name,
label: column.name,
value: column.name,
subText: column.type,
icon: (
<Icon
color="var(--ads-v2-color-fg)"
name="layout-column-line"
size="md"
/>
),
};
});
return columns.map((column: any) => {
return {
id: column.name,
label: column.name,
value: column.name,
subText: column.type,
icon: (
<Icon
color="var(--ads-v2-color-fg)"
name="layout-column-line"
size="md"
/>
),
};
});
} else {
return [];
}

View File

@ -1,37 +1,48 @@
import React from "react";
import ColumnDropdown from "./ColumnDropdown";
import { noop } from "lodash";
import type { Alias } from "../types";
type Props = {
hasSearchableColumn?: boolean;
hasAliasPicker?: boolean;
aliases?: string[];
aliases?: Alias[];
};
export default function WidgetSpecificControls(props: Props) {
let searchableColumn = null;
let aliasPicker = null;
let aliases = null;
if (props.hasSearchableColumn) {
searchableColumn = (
<ColumnDropdown
alias="searchableColumn"
id="searchableColumn"
label="Select a searchable column"
onSelect={noop}
/>
);
}
if (props.hasAliasPicker && props.aliases) {
aliasPicker = props.aliases.map((alias) => {
<ColumnDropdown alias={`alias.${alias}`} label={alias} onSelect={noop} />;
if (props.aliases?.length) {
aliases = props.aliases.map(({ name }) => {
const label = name.slice(0, 1).toUpperCase() + name.slice(1);
return (
<ColumnDropdown
alias={`alias.${name}`}
id={name}
key={name}
label={label}
onSelect={noop}
/>
);
});
}
return (
<>
{searchableColumn}
{aliasPicker}
{aliases}
</>
);
}

View File

@ -14,6 +14,7 @@ import {
getOneClickBindingConfigForWidget,
} from "selectors/oneClickBindingSelectors";
import { updateOneClickBindingOptionsVisibility } from "actions/oneClickBindingActions";
import type { Alias } from "./types";
type WidgetQueryGeneratorFormContextType = {
widgetId: string;
@ -39,6 +40,8 @@ type WidgetQueryGeneratorFormContextType = {
onSourceClose: () => void;
errorMsg: string;
expectedType: string;
sampleData: string;
aliases: Alias[];
};
const DEFAULT_CONFIG_VALUE = {
@ -64,6 +67,8 @@ const DEFAULT_CONTEXT_VALUE = {
errorMsg: "",
propertyName: "",
expectedType: "",
sampleData: "",
aliases: [],
};
export const WidgetQueryGeneratorFormContext =
@ -78,6 +83,9 @@ type Props = {
widgetId: string;
errorMsg: string;
expectedType: string;
aliases: Alias[];
searchableColumn: boolean;
sampleData: string;
};
function WidgetQueryGeneratorForm(props: Props) {
@ -86,11 +94,13 @@ function WidgetQueryGeneratorForm(props: Props) {
const [pristine, setPristine] = useState(true);
const {
aliases,
errorMsg,
expectedType,
onUpdate,
propertyPath,
propertyValue,
sampleData,
widgetId,
} = props;
@ -197,6 +207,8 @@ function WidgetQueryGeneratorForm(props: Props) {
errorMsg,
propertyName: propertyPath,
expectedType,
sampleData,
aliases,
};
}, [
config,
@ -208,6 +220,8 @@ function WidgetQueryGeneratorForm(props: Props) {
onSourceClose,
errorMsg,
propertyPath,
sampleData,
aliases,
]);
useEffect(() => {
@ -221,7 +235,10 @@ function WidgetQueryGeneratorForm(props: Props) {
<WidgetQueryGeneratorFormContext.Provider value={contextValue}>
<CommonControls />
<DatasourceSpecificControls />
<WidgetSpecificControls hasSearchableColumn />
<WidgetSpecificControls
aliases={props.aliases}
hasSearchableColumn={props.searchableColumn}
/>
<ConnectData />
</WidgetQueryGeneratorFormContext.Provider>
</Wrapper>

View File

@ -6,3 +6,9 @@ export interface DropdownOptionType {
onSelect?: (value: string, option: DropdownOptionType) => void;
data?: any;
}
export interface Alias {
name: string;
isSearcheable?: boolean;
isRequired?: boolean;
}

View File

@ -1,6 +1,7 @@
import WidgetQueryGeneratorForm from "components/editorComponents/WidgetQueryGeneratorForm";
import type { Alias } from "components/editorComponents/WidgetQueryGeneratorForm/types";
import React from "react";
import type { ControlData, ControlProps } from "./BaseControl";
import type { ControlProps } from "./BaseControl";
import BaseControl from "./BaseControl";
class OneClickBindingControl extends BaseControl<OneClickBindingControlProps> {
constructor(props: OneClickBindingControlProps) {
@ -15,9 +16,15 @@ class OneClickBindingControl extends BaseControl<OneClickBindingControlProps> {
* Commenting out as we're not able to switch between the js modes without value being overwritten
* with default value by platform
*/
static canDisplayValueInUI(config: ControlData, value: any): boolean {
// {{query1.data}}
return /^{{[^.]*\.data}}$/gi.test(value);
static canDisplayValueInUI(
config: OneClickBindingControlProps,
value: any,
): boolean {
// {{query1.data}} || sample data
return (
/^{{[^.]*\.data}}$/gi.test(value) ||
config.controlConfig?.sampleData === value
);
}
static shouldValidateValueOnDynamicPropertyOff() {
@ -52,11 +59,14 @@ class OneClickBindingControl extends BaseControl<OneClickBindingControlProps> {
public render() {
return (
<WidgetQueryGeneratorForm
aliases={this.props.controlConfig.aliases}
errorMsg={this.getErrorMessage()}
expectedType={this.props.expected?.autocompleteDataType || ""}
onUpdate={this.onUpdatePropertyValue}
propertyPath={this.props.propertyName}
propertyValue={this.props.propertyValue}
sampleData={this.props.controlConfig.sampleData}
searchableColumn={this.props.controlConfig.searchableColumn}
widgetId={this.props.widgetProperties.widgetId}
/>
);
@ -65,4 +75,10 @@ class OneClickBindingControl extends BaseControl<OneClickBindingControlProps> {
export default OneClickBindingControl;
export type OneClickBindingControlProps = ControlProps;
export type OneClickBindingControlProps = ControlProps & {
controlConfig: {
aliases: Alias[];
searchableColumn: boolean;
sampleData: string;
};
};

View File

@ -219,7 +219,11 @@ function* BindWidgetToDatasource(
data: `{{${queryNameMap[QUERY_TYPE.SELECT]}.data}}`,
run: `{{
${queryNameMap[QUERY_TYPE.SELECT]}.run();
${queryNameMap[QUERY_TYPE.TOTAL_RECORD]}.run();
${
createdQueryNames.includes(queryNameMap[QUERY_TYPE.TOTAL_RECORD])
? queryNameMap[QUERY_TYPE.TOTAL_RECORD] + ".run()"
: ""
}
}}`,
};
}

View File

@ -6,6 +6,11 @@ import { DynamicHeight } from "utils/WidgetFeatures";
import IconSVG from "./icon.svg";
import Widget from "./widget";
import { WIDGET_TAGS } from "constants/WidgetConstants";
import type { WidgetProps } from "widgets/BaseWidget";
import type {
WidgetQueryConfig,
WidgetQueryGenerationFormConfig,
} from "WidgetQueryGenerators/types";
export const CONFIG = {
features: {
@ -60,6 +65,22 @@ export const CONFIG = {
autocompleteDefinitions: Widget.getAutocompleteDefinitions(),
setterConfig: Widget.getSetterConfig(),
},
methods: {
getQueryGenerationConfig: (widgetProps: WidgetProps) => {
return Widget.getQueryGenerationConfig(widgetProps);
},
getPropertyUpdatesForQueryBinding: (
queryConfig: WidgetQueryConfig,
widget: WidgetProps,
formConfig: WidgetQueryGenerationFormConfig,
) => {
return Widget.getPropertyUpdatesForQueryBinding(
queryConfig,
widget,
formConfig,
);
},
},
autoLayout: {
disabledPropsDefaults: {
labelPosition: LabelPosition.Top,

View File

@ -35,11 +35,46 @@ import {
getLabelValueKeyOptions,
valueKeyValidation,
} from "./propertyUtils";
import type {
WidgetQueryConfig,
WidgetQueryGenerationFormConfig,
} from "WidgetQueryGenerators/types";
class MultiSelectWidget extends BaseWidget<
MultiSelectWidgetProps,
WidgetState
> {
static getQueryGenerationConfig(widget: WidgetProps) {
return {
select: {
where: `${widget.widgetName}.filterText`,
},
};
}
static getPropertyUpdatesForQueryBinding(
queryConfig: WidgetQueryConfig,
widget: WidgetProps,
formConfig: WidgetQueryGenerationFormConfig,
) {
let modify;
if (queryConfig.select) {
modify = {
sourceData: queryConfig.select.data,
optionLabel: formConfig.aliases.find((d) => d.name === "label")?.alias,
optionValue: formConfig.aliases.find((d) => d.name === "value")?.alias,
defaultOptionValue: "",
serverSideFiltering: true,
onFilterUpdate: queryConfig.select.run,
};
}
return {
modify,
};
}
static getAutocompleteDefinitions(): AutocompletionDefinitions {
return {
"!doc":
@ -78,11 +113,33 @@ class MultiSelectWidget extends BaseWidget<
"Takes in an array of objects to display options. Bind data from an API using {{}}",
propertyName: "sourceData",
label: "Source Data",
controlType: "INPUT_TEXT",
controlType: "ONE_CLICK_BINDING_CONTROL",
controlConfig: {
aliases: [
{
name: "label",
isSearcheable: true,
isRequired: true,
},
{
name: "value",
isRequired: true,
},
],
sampleData: JSON.stringify(
[
{ name: "Blue", code: "BLUE" },
{ name: "Green", code: "GREEN" },
{ name: "Red", code: "RED" },
],
null,
2,
),
},
isJSConvertible: true,
placeholderText: '[{ "label": "Option1", "value": "Option2" }]',
isBindProperty: true,
isTriggerProperty: false,
isJSConvertible: false,
validation: {
type: ValidationTypes.ARRAY,
params: {
@ -98,9 +155,10 @@ class MultiSelectWidget extends BaseWidget<
EvaluationSubstitutionType.SMART_SUBSTITUTE,
},
{
helpText: "Sets the label of the option",
helpText:
"Choose or set a field from source data as the display label",
propertyName: "optionLabel",
label: "Label",
label: "Label key",
controlType: "DROP_DOWN",
customJSControl: "WRAPPED_CODE_EDITOR",
controlConfig: {
@ -130,9 +188,9 @@ class MultiSelectWidget extends BaseWidget<
additionalAutoComplete: getLabelValueAdditionalAutocompleteData,
},
{
helpText: "Sets the value of the option",
helpText: "Choose or set a field from source data as the value",
propertyName: "optionValue",
label: "Value",
label: "Value key",
controlType: "DROP_DOWN",
customJSControl: "WRAPPED_CODE_EDITOR",
controlConfig: {

View File

@ -3,11 +3,15 @@ import { LabelPosition } from "components/constants";
import { FILL_WIDGET_MIN_WIDTH } from "constants/minWidthConstants";
import { ResponsiveBehavior } from "utils/autoLayout/constants";
import { DynamicHeight } from "utils/WidgetFeatures";
import IconSVG from "./icon.svg";
import Widget from "./widget";
import type { SnipingModeProperty, PropertyUpdates } from "widgets/constants";
import { WIDGET_TAGS } from "constants/WidgetConstants";
import type { WidgetProps } from "widgets/BaseWidget";
import type {
WidgetQueryConfig,
WidgetQueryGenerationFormConfig,
} from "WidgetQueryGenerators/types";
export const CONFIG = {
features: {
@ -62,6 +66,20 @@ export const CONFIG = {
setterConfig: Widget.getSetterConfig(),
},
methods: {
getQueryGenerationConfig: (widgetProps: WidgetProps) => {
return Widget.getQueryGenerationConfig(widgetProps);
},
getPropertyUpdatesForQueryBinding: (
queryConfig: WidgetQueryConfig,
widget: WidgetProps,
formConfig: WidgetQueryGenerationFormConfig,
) => {
return Widget.getPropertyUpdatesForQueryBinding(
queryConfig,
widget,
formConfig,
);
},
getSnipingModeUpdates: (
propValueMap: SnipingModeProperty,
): PropertyUpdates[] => {

View File

@ -36,12 +36,47 @@ import {
getLabelValueKeyOptions,
valueKeyValidation,
} from "./propertyUtils";
import type {
WidgetQueryConfig,
WidgetQueryGenerationFormConfig,
} from "WidgetQueryGenerators/types";
class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
constructor(props: SelectWidgetProps) {
super(props);
}
static getQueryGenerationConfig(widget: WidgetProps) {
return {
select: {
where: `${widget.widgetName}.filterText`,
},
};
}
static getPropertyUpdatesForQueryBinding(
queryConfig: WidgetQueryConfig,
widget: WidgetProps,
formConfig: WidgetQueryGenerationFormConfig,
) {
let modify;
if (queryConfig.select) {
modify = {
sourceData: queryConfig.select.data,
optionLabel: formConfig.aliases.find((d) => d.name === "label")?.alias,
optionValue: formConfig.aliases.find((d) => d.name === "value")?.alias,
defaultOptionValue: "",
serverSideFiltering: true,
onFilterUpdate: queryConfig.select.run,
};
}
return {
modify,
};
}
static getAutocompleteDefinitions(): AutocompletionDefinitions {
return {
"!doc":
@ -79,7 +114,30 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
"Takes in an array of objects to display options. Bind data from an API using {{}}",
propertyName: "sourceData",
label: "Source Data",
controlType: "INPUT_TEXT",
controlType: "ONE_CLICK_BINDING_CONTROL",
controlConfig: {
aliases: [
{
name: "label",
isSearcheable: true,
isRequired: true,
},
{
name: "value",
isRequired: true,
},
],
sampleData: JSON.stringify(
[
{ name: "Blue", code: "BLUE" },
{ name: "Green", code: "GREEN" },
{ name: "Red", code: "RED" },
],
null,
2,
),
},
isJSConvertible: true,
placeholderText: '[{ "label": "label1", "value": "value1" }]',
isBindProperty: true,
isTriggerProperty: false,
@ -98,9 +156,10 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
EvaluationSubstitutionType.SMART_SUBSTITUTE,
},
{
helpText: "Sets the label of the option",
helpText:
"Choose or set a field from source data as the display label",
propertyName: "optionLabel",
label: "Label",
label: "Label key",
controlType: "DROP_DOWN",
customJSControl: "WRAPPED_CODE_EDITOR",
controlConfig: {
@ -131,9 +190,9 @@ class SelectWidget extends BaseWidget<SelectWidgetProps, WidgetState> {
additionalAutoComplete: getLabelValueAdditionalAutocompleteData,
},
{
helpText: "Sets the value of the option",
helpText: "Choose or set a field from source data as the value",
propertyName: "optionValue",
label: "Value",
label: "Value key",
controlType: "DROP_DOWN",
customJSControl: "WRAPPED_CODE_EDITOR",
controlConfig: {

View File

@ -30,6 +30,9 @@ export default [
propertyName: "tableData",
label: "Table data",
controlType: "ONE_CLICK_BINDING_CONTROL",
controlConfig: {
searchableColumn: true,
},
placeholderText: '[{ "name": "John" }]',
inputType: "ARRAY",
isBindProperty: true,