chore: Miscellaneous one click binding updates (#24957)

## Description
- Remove the primary column from the insert and update queries.
- Save/Discard button isSaveDisabled and isDiscardDisabled properties
should be in js mode.
- Don't create insert and update query if datasource is read only

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

#### Type of change

- Bug fix (non-breaking change which fixes an issue)
- 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
- [ ] Jest
- [x] Cypress
>
>
#### Test Plan
> Add Testsmith test cases links that relate to this PR
>
>
#### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)
>
>
>
## Checklist:
#### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


#### QA activity:
- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-)
have been covered
- [ ] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-)
- [ ] Test plan has been peer reviewed by project stakeholders and other
QA members
- [ ] 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-07-20 11:52:20 +05:30 committed by GitHub
parent c6afa03372
commit 4c938676bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 924 additions and 56 deletions

View File

@ -27,8 +27,8 @@ describe("Table widget one click binding feature", () => {
oneClickBinding.ChooseAndAssertForm(
`${dsName}`,
dsName,
"public.users",
"name",
"public.employees",
"first_name",
);
});
@ -38,12 +38,19 @@ describe("Table widget one click binding feature", () => {
agHelper.Sleep(2000);
["id", "gender", "dob", "name", "email", "phoneNo"].forEach((column) => {
[
"employee_id",
"last_name",
"first_name",
"title",
"title_of_courtesy",
"birth_date",
"hire_date",
].forEach((column) => {
agHelper.AssertElementExist(table._headerCell(column));
});
agHelper.AssertElementExist(table._showPageItemsCount);
table.EnableEditableOfColumn("id", "v2");
table.AddNewRow();
@ -51,25 +58,28 @@ describe("Table widget one click binding feature", () => {
//cy.log("randomeNumber: " + randomNumber);
// table.EditTableCell(0, 0, randomNumber.toString(), false);//Bug 24623 - since 2 digit id is not typed properly
table.EditTableCell(0, 0, 2, false);
table.UpdateTableCell(0, 1, "cypress@appsmith");
table.UpdateTableCell(0, 2, " 2016-06-22 19:10:25-07");
table.UpdateTableCell(0, 3, " 2016-06-22 19:10:25-07");
agHelper.GetNClick(oneClickBindingLocator.dateInput, 0, true);
table.UpdateTableCell(0, 1, "_");
table.UpdateTableCell(0, 2, "appsmith_");
agHelper.GetNClick(oneClickBindingLocator.dateInput, 0, true);
agHelper.GetNClick(oneClickBindingLocator.dayViewFromDate, 0, true);
agHelper.GetNClick(oneClickBindingLocator.dateInput, 1, true);
agHelper.GetNClick(oneClickBindingLocator.dayViewFromDate, 0, true);
table.UpdateTableCell(0, 16, "1");
agHelper.Sleep(2000);
agHelper.GetNClick(table._saveNewRow, 0, true);
assertHelper.AssertNetworkStatus("@postExecute");
agHelper.TypeText(table._searchInput, "cypress@appsmith");
agHelper.TypeText(table._searchInput, "appsmith_");
assertHelper.AssertNetworkStatus("@postExecute");
agHelper.AssertElementExist(table._bodyCell("cypress@appsmith"));
agHelper.AssertElementExist(table._bodyCell("appsmith_"));
agHelper.Sleep();
@ -77,15 +87,15 @@ describe("Table widget one click binding feature", () => {
agHelper.Sleep(500);
table.EditTableCell(0, 1, "automation@appsmith");
table.EditTableCell(0, 2, "cypress");
//(cy as any).enterTableCellValue(1, 0, "automation@appsmith{enter}");
agHelper.Sleep();
(cy as any).AssertTableRowSavable(12, 0);
(cy as any).AssertTableRowSavable(18, 0);
(cy as any).saveTableRow(12, 0);
(cy as any).saveTableRow(18, 0);
assertHelper.AssertNetworkStatus("@postExecute");
@ -95,22 +105,23 @@ describe("Table widget one click binding feature", () => {
agHelper.ClearTextField(table._searchInput);
agHelper.TypeText(table._searchInput, "automation@appsmith");
agHelper.TypeText(table._searchInput, "cypress");
assertHelper.AssertNetworkStatus("@postExecute");
agHelper.Sleep(2000);
agHelper.AssertElementExist(table._bodyCell("automation@appsmith"));
agHelper.AssertElementExist(table._bodyCell("cypress"));
agHelper.ClearTextField(table._searchInput);
//TODO: Commenting out until cypress double click issue is resolved.
// agHelper.ClearTextField(table._searchInput);
agHelper.TypeText(table._searchInput, "cypress@appsmith");
// agHelper.TypeText(table._searchInput, "appsmith_");
assertHelper.AssertNetworkStatus("@postExecute");
// assertHelper.AssertNetworkStatus("@postExecute");
agHelper.Sleep(2000);
// agHelper.Sleep(2000);
agHelper.AssertElementAbsence(table._bodyCell("cypress@appsmith"));
// agHelper.AssertElementAbsence(table._bodyCell("appsmith_"));
});
});

View File

@ -1,3 +1,4 @@
import { DatasourceConnectionMode } from "entities/Datasource";
import GSheets from ".";
describe("GSheets WidgetQueryGenerator", () => {
@ -60,6 +61,111 @@ describe("GSheets WidgetQueryGenerator", () => {
primaryColumn: "",
sheetName: "someSheet",
tableHeaderIndex: 1,
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
expect(expr).toEqual([
{
name: "Find_someSheet",
payload: {
formData: {
command: {
data: "FETCH_MANY",
},
entityType: {
data: "ROWS",
},
pagination: {
data: {
limit: "{{data_table.pageSize}}",
offset: "{{(data_table.pageNo - 1) * data_table.pageSize}}",
},
},
projection: {
data: [],
},
queryFormat: {
data: "ROWS",
},
range: {
data: "",
},
sheetName: {
data: "someSheet",
},
sheetUrl: {
data: "someTableUrl",
},
smartSubstitution: {
data: true,
},
sortBy: {
data: [
{
column: "{{data_table.sortOrder.column || 'genres'}}",
order: 'data_table.sortOrder.order == "desc" ? -1 : 1',
},
],
},
tableHeaderIndex: {
data: "1",
},
where: {
data: {
children: [
{
condition: "CONTAINS",
key: '{{data_table.searchText ? "title" : ""}}',
value: "{{data_table.searchText}}",
},
],
condition: "AND",
},
},
},
},
type: "select",
dynamicBindingPathList: [
{
key: "formData.where.data",
},
{
key: "formData.sortBy.data",
},
{
key: "formData.pagination.data",
},
],
},
]);
});
test("should build select form data without write permissions", () => {
const expr = GSheets.build(
{
select: {
limit: "data_table.pageSize",
where: "data_table.searchText",
offset: "(data_table.pageNo - 1) * data_table.pageSize",
orderBy: "data_table.sortOrder.column || 'genres'",
sortOrder: 'data_table.sortOrder.order == "desc" ? -1 : 1',
},
totalRecord: false,
},
{
tableName: "someTableUrl",
datasourceId: "someId",
// ignore columns
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: [],
primaryColumn: "",
sheetName: "someSheet",
tableHeaderIndex: 1,
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
@ -159,6 +265,7 @@ describe("GSheets WidgetQueryGenerator", () => {
primaryColumn: "",
sheetName: "someSheet",
tableHeaderIndex: 1,
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -203,6 +310,34 @@ describe("GSheets WidgetQueryGenerator", () => {
},
]);
});
test("should not build update form data without write permissions ", () => {
const expr = GSheets.build(
{
update: {
value: "update_form.formData",
},
totalRecord: false,
},
{
tableName: "someTableUrl",
datasourceId: "someId",
// ignore columns
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: [],
primaryColumn: "",
sheetName: "someSheet",
tableHeaderIndex: 1,
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
expect(expr).toEqual([]);
});
test("should build insert form data correctly ", () => {
const expr = GSheets.build(
{
@ -222,6 +357,7 @@ describe("GSheets WidgetQueryGenerator", () => {
primaryColumn: "",
sheetName: "someSheet",
tableHeaderIndex: 1,
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -265,4 +401,30 @@ describe("GSheets WidgetQueryGenerator", () => {
},
]);
});
test("should not build insert form data without write permissions ", () => {
const expr = GSheets.build(
{
create: {
value: "insert_form.formData",
},
totalRecord: false,
},
{
tableName: "someTableUrl",
datasourceId: "someId",
// ignore columns
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: [],
primaryColumn: "",
sheetName: "someSheet",
tableHeaderIndex: 1,
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
expect(expr).toEqual([]);
});
});

View File

@ -8,6 +8,7 @@ import type {
ActionConfigurationGSheets,
} from "WidgetQueryGenerators/types";
import { removeSpecialChars } from "utils/helpers";
import { DatasourceConnectionMode } from "entities/Datasource";
enum COMMAND_TYPES {
"FIND" = "FETCH_MANY",
@ -254,7 +255,10 @@ export default abstract class GSheets extends BaseQueryGenerator {
),
);
}
if (widgetConfig.update) {
if (
widgetConfig.update &&
formConfig.connectionMode === DatasourceConnectionMode.READ_WRITE
) {
configs.push(
this.createPayload(
initialValues,
@ -263,7 +267,10 @@ export default abstract class GSheets extends BaseQueryGenerator {
),
);
}
if (widgetConfig.create) {
if (
widgetConfig.create &&
formConfig.connectionMode === DatasourceConnectionMode.READ_WRITE
) {
configs.push(
this.createPayload(
initialValues,

View File

@ -1,3 +1,4 @@
import { DatasourceConnectionMode } from "entities/Datasource";
import MSSQL from ".";
describe("MSSQL WidgetQueryGenerator", () => {
@ -26,6 +27,62 @@ describe("MSSQL WidgetQueryGenerator", () => {
searchableColumn: "title",
columns: [],
primaryColumn: "genres",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
const res = `SELECT
*
FROM
someTable
WHERE
title LIKE '%{{data_table.searchText || \"\"}}%'
ORDER BY
{{data_table.sortOrder.column || 'genres'}} {{data_table.sortOrder.order || 'ASC' ? \"\" : \"DESC\"}}
OFFSET
{{(data_table.pageNo - 1) * data_table.pageSize}} ROWS
FETCH NEXT
{{data_table.pageSize}} ROWS ONLY`;
expect(expr).toEqual([
{
name: "Select_someTable",
type: "select",
dynamicBindingPathList: [
{
key: "body",
},
],
payload: {
pluginSpecifiedTemplates: [{ value: false }],
body: res,
},
},
]);
});
test("should build select form data correctly without read write permissions", () => {
const expr = MSSQL.build(
{
select: {
limit: "data_table.pageSize",
where: 'data_table.searchText || ""',
offset: "(data_table.pageNo - 1) * data_table.pageSize",
orderBy: "data_table.sortOrder.column",
sortOrder: "data_table.sortOrder.order || 'ASC'",
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: [],
primaryColumn: "genres",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
@ -80,6 +137,7 @@ FETCH NEXT
searchableColumn: "title",
columns: [],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -129,6 +187,32 @@ FETCH NEXT
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
expect(expr).toEqual([]);
});
test("should not build update form data without read write permissions", () => {
const expr = MSSQL.build(
{
update: {
value: `update_form.fieldState'`,
where: `"id" = {{data_table.selectedRow.id}}`,
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
@ -153,6 +237,7 @@ FETCH NEXT
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -191,6 +276,31 @@ FETCH NEXT
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
expect(expr).toEqual([]);
});
test("should not build insert form data without read write permissions", () => {
const expr = MSSQL.build(
{
create: {
value: `update_form.fieldState`,
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
// ignore columns
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
@ -214,6 +324,7 @@ FETCH NEXT
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);

View File

@ -8,6 +8,7 @@ import type {
} from "../types";
import { removeSpecialChars } from "utils/helpers";
import without from "lodash/without";
import { DatasourceConnectionMode } from "entities/Datasource";
export default abstract class MSSQL extends BaseQueryGenerator {
private static buildSelect(
widgetConfig: WidgetQueryGenerationConfig,
@ -193,11 +194,19 @@ export default abstract class MSSQL extends BaseQueryGenerator {
allBuildConfigs.push(this.buildSelect(widgetConfig, formConfig));
}
if (widgetConfig.update && formConfig.primaryColumn) {
if (
widgetConfig.update &&
formConfig.primaryColumn &&
formConfig.connectionMode === DatasourceConnectionMode.READ_WRITE
) {
allBuildConfigs.push(this.buildUpdate(widgetConfig, formConfig));
}
if (widgetConfig.create && formConfig.primaryColumn) {
if (
widgetConfig.create &&
formConfig.primaryColumn &&
formConfig.connectionMode === DatasourceConnectionMode.READ_WRITE
) {
allBuildConfigs.push(this.buildInsert(widgetConfig, formConfig));
}

View File

@ -1,3 +1,4 @@
import { DatasourceConnectionMode } from "entities/Datasource";
import MongoDB from ".";
describe("Mongo WidgetQueryGenerator", () => {
@ -37,6 +38,82 @@ describe("Mongo WidgetQueryGenerator", () => {
searchableColumn: "title",
columns: [],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
expect(expr).toEqual([
{
type: "select",
name: "Find_someTable",
dynamicBindingPathList: [
{
key: "formData.find.skip.data",
},
{
key: "formData.find.query.data",
},
{
key: "formData.find.sort.data",
},
{
key: "formData.find.limit.data",
},
],
payload: {
formData: {
collection: {
data: "someTable",
},
smartSubstitution: { data: true },
aggregate: { limit: { data: "10" } },
command: {
data: "FIND",
},
find: {
data: "",
limit: {
data: "{{data_table.pageSize}}",
},
query: {
data: '{{{ title: {$regex: data_table.searchText||""} }}}',
},
skip: {
data: "{{(data_table.pageNo - 1) * data_table.pageSize}}",
},
sort: {
data: "{{ data_table.sortOrder.column || 'genres' ? { [data_table.sortOrder.column || 'genres']: data_table.sortOrder.order == \"desc\" ? -1 : 1 ? 1 : -1 } : {}}}",
},
},
},
},
},
]);
});
test("should build select form data without write permissions", () => {
const expr = MongoDB.build(
{
select: {
limit: "data_table.pageSize",
where: 'data_table.searchText||""',
offset: "(data_table.pageNo - 1) * data_table.pageSize",
orderBy: "data_table.sortOrder.column || 'genres'",
sortOrder: 'data_table.sortOrder.order == "desc" ? -1 : 1',
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
// ignore columns
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: [],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
@ -108,6 +185,7 @@ describe("Mongo WidgetQueryGenerator", () => {
searchableColumn: "title",
columns: [],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -148,6 +226,33 @@ describe("Mongo WidgetQueryGenerator", () => {
},
]);
});
test("should not build update form data without write permissions ", () => {
const expr = MongoDB.build(
{
update: {
value: "{rating : {$gte : 9}}",
where: "{ $inc: { score: 1 } }",
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
// ignore columns
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: [],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
expect(expr).toEqual([]);
});
test("should build insert form data correctly ", () => {
const expr = MongoDB.build(
{
@ -165,6 +270,7 @@ describe("Mongo WidgetQueryGenerator", () => {
searchableColumn: "title",
columns: [],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -198,4 +304,28 @@ describe("Mongo WidgetQueryGenerator", () => {
},
]);
});
test("should not build insert form data without write permissions ", () => {
const expr = MongoDB.build(
{
create: {
value: "insert_form.formData",
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
// ignore columns
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: [],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
expect(expr).toEqual([]);
});
});

View File

@ -8,6 +8,7 @@ import type {
MongoDBFormData,
} from "WidgetQueryGenerators/types";
import { removeSpecialChars } from "utils/helpers";
import { DatasourceConnectionMode } from "entities/Datasource";
enum COMMAND_TYPES {
"FIND" = "FIND",
@ -196,7 +197,10 @@ export default abstract class MongoDB extends BaseQueryGenerator {
),
);
}
if (widgetConfig.update) {
if (
widgetConfig.update &&
formConfig.connectionMode === DatasourceConnectionMode.READ_WRITE
) {
configs.push(
this.createPayload(
initialValues,
@ -205,7 +209,10 @@ export default abstract class MongoDB extends BaseQueryGenerator {
),
);
}
if (widgetConfig.create) {
if (
widgetConfig.create &&
formConfig.connectionMode === DatasourceConnectionMode.READ_WRITE
) {
configs.push(
this.createPayload(
initialValues,

View File

@ -1,3 +1,4 @@
import { DatasourceConnectionMode } from "entities/Datasource";
import MySQl from ".";
describe("MySQl WidgetQueryGenerator", () => {
@ -26,6 +27,62 @@ describe("MySQl WidgetQueryGenerator", () => {
searchableColumn: "title",
columns: [],
primaryColumn: "genres",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
const res = `SELECT
*
FROM
someTable
WHERE
title LIKE '%{{data_table.searchText || \"\"}}%'
ORDER BY
{{data_table.sortOrder.column || 'genres'}} {{data_table.sortOrder.order || 'ASC' ? \"\" : \"DESC\"}}
LIMIT
{{data_table.pageSize}}
OFFSET
{{(data_table.pageNo - 1) * data_table.pageSize}}`;
expect(expr).toEqual([
{
name: "Select_someTable",
type: "select",
dynamicBindingPathList: [
{
key: "body",
},
],
payload: {
pluginSpecifiedTemplates: [{ value: false }],
body: res,
},
},
]);
});
test("should build select form data correctly without read write permissions", () => {
const expr = MySQl.build(
{
select: {
limit: "data_table.pageSize",
where: 'data_table.searchText || ""',
offset: "(data_table.pageNo - 1) * data_table.pageSize",
orderBy: "data_table.sortOrder.column",
sortOrder: "data_table.sortOrder.order || 'ASC'",
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: [],
primaryColumn: "genres",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
@ -80,6 +137,7 @@ OFFSET
searchableColumn: "title",
columns: [],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -129,6 +187,32 @@ OFFSET
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
expect(expr).toEqual([]);
});
test("should not build update form data without read write", () => {
const expr = MySQl.build(
{
update: {
value: `update_form.fieldState'`,
where: `"id" = {{data_table.selectedRow.id}}`,
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
@ -153,6 +237,7 @@ OFFSET
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -191,6 +276,31 @@ OFFSET
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
expect(expr).toEqual([]);
});
test("should not build insert form data without read write permissions", () => {
const expr = MySQl.build(
{
create: {
value: `update_form.fieldState`,
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
// ignore columns
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
@ -214,6 +324,7 @@ OFFSET
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);

View File

@ -8,6 +8,7 @@ import type {
} from "../types";
import { removeSpecialChars } from "utils/helpers";
import without from "lodash/without";
import { DatasourceConnectionMode } from "entities/Datasource";
export default abstract class MySQL extends BaseQueryGenerator {
private static buildSelect(
widgetConfig: WidgetQueryGenerationConfig,
@ -193,11 +194,19 @@ export default abstract class MySQL extends BaseQueryGenerator {
allBuildConfigs.push(this.buildSelect(widgetConfig, formConfig));
}
if (widgetConfig.update && formConfig.primaryColumn) {
if (
widgetConfig.update &&
formConfig.primaryColumn &&
formConfig.connectionMode === DatasourceConnectionMode.READ_WRITE
) {
allBuildConfigs.push(this.buildUpdate(widgetConfig, formConfig));
}
if (widgetConfig.create && formConfig.primaryColumn) {
if (
widgetConfig.create &&
formConfig.primaryColumn &&
formConfig.connectionMode === DatasourceConnectionMode.READ_WRITE
) {
allBuildConfigs.push(this.buildInsert(widgetConfig, formConfig));
}

View File

@ -1,3 +1,4 @@
import { DatasourceConnectionMode } from "entities/Datasource";
import PostgreSQL from ".";
describe("PostgreSQL WidgetQueryGenerator", () => {
@ -26,6 +27,7 @@ describe("PostgreSQL WidgetQueryGenerator", () => {
searchableColumn: "title",
columns: [],
primaryColumn: "genres",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -80,6 +82,7 @@ OFFSET
searchableColumn: "title",
columns: [],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -112,6 +115,61 @@ OFFSET
]);
});
test("should build select form data correctly without write permissions", () => {
const expr = PostgreSQL.build(
{
select: {
limit: "data_table.pageSize",
where: 'data_table.searchText || ""',
offset: "(data_table.pageNo - 1) * data_table.pageSize",
orderBy: "data_table.sortOrder.column",
sortOrder: "data_table.sortOrder.order || 'ASC'",
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: [],
primaryColumn: "genres",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
const res = `SELECT
*
FROM
someTable
WHERE
\"title\" ilike '%{{data_table.searchText || \"\"}}%'
ORDER BY
\"{{data_table.sortOrder.column || 'genres'}}\" {{data_table.sortOrder.order || 'ASC' ? \"\" : \"DESC\"}}
LIMIT
{{data_table.pageSize}}
OFFSET
{{(data_table.pageNo - 1) * data_table.pageSize}}`;
expect(expr).toEqual([
{
name: "Select_someTable",
type: "select",
dynamicBindingPathList: [
{
key: "body",
},
],
payload: {
pluginSpecifiedTemplates: [{ value: false }],
body: res,
},
},
]);
});
test("should not build update form data without primary key ", () => {
const expr = PostgreSQL.build(
{
@ -129,6 +187,32 @@ OFFSET
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
expect(expr).toEqual([]);
});
test("should not build update form data without write permissions ", () => {
const expr = PostgreSQL.build(
{
update: {
value: `update_form.fieldState'`,
where: `"id" = {{data_table.selectedRow.id}}`,
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
@ -153,6 +237,7 @@ OFFSET
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -167,7 +252,7 @@ OFFSET
},
],
payload: {
body: "UPDATE someTable SET \"id\"= '{{update_form.fieldState'.id}}', \"name\"= '{{update_form.fieldState'.name}}' WHERE \"id\"= {{data_table.selectedRow.id}};",
body: 'UPDATE someTable SET "name"= \'{{update_form.fieldState\'.name}}\' WHERE "id"= {{data_table.selectedRow.id}};',
pluginSpecifiedTemplates: [{ value: false }],
},
},
@ -191,12 +276,38 @@ OFFSET
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
expect(expr).toEqual([]);
});
test("should not build insert form data without write permissions ", () => {
const expr = PostgreSQL.build(
{
create: {
value: `update_form.fieldState`,
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
// ignore columns
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
expect(expr).toEqual([]);
});
test("should build insert form data correctly ", () => {
const expr = PostgreSQL.build(
{
@ -214,6 +325,7 @@ OFFSET
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -227,7 +339,7 @@ OFFSET
},
],
payload: {
body: "INSERT INTO someTable (\"id\",\"name\") VALUES ('{{update_form.fieldState.id}}','{{update_form.fieldState.name}}')",
body: "INSERT INTO someTable (\"name\") VALUES ('{{update_form.fieldState.name}}')",
pluginSpecifiedTemplates: [{ value: false }],
},
},

View File

@ -7,6 +7,8 @@ import type {
ActionConfigurationSQL,
} from "../types";
import { removeSpecialChars } from "utils/helpers";
import { without } from "lodash";
import { DatasourceConnectionMode } from "entities/Datasource";
export default abstract class PostgreSQL extends BaseQueryGenerator {
private static buildSelect(
widgetConfig: WidgetQueryGenerationConfig,
@ -113,11 +115,13 @@ export default abstract class PostgreSQL extends BaseQueryGenerator {
const { value, where } = update;
const columns = without(formConfig.columns, formConfig.primaryColumn);
return {
type: QUERY_TYPE.UPDATE,
name: `Update_${removeSpecialChars(formConfig.tableName)}`,
payload: {
body: `UPDATE ${formConfig.tableName} SET ${formConfig.columns
body: `UPDATE ${formConfig.tableName} SET ${columns
.map((column) => `"${column}"= '{{${value}.${column}}}'`)
.join(", ")} WHERE "${formConfig.primaryColumn}"= {{${where}.${
formConfig.primaryColumn
@ -141,13 +145,15 @@ export default abstract class PostgreSQL extends BaseQueryGenerator {
return;
}
const columns = without(formConfig.columns, formConfig.primaryColumn);
return {
type: QUERY_TYPE.CREATE,
name: `Insert_${removeSpecialChars(formConfig.tableName)}`,
payload: {
body: `INSERT INTO ${formConfig.tableName} (${formConfig.columns.map(
body: `INSERT INTO ${formConfig.tableName} (${columns.map(
(a) => `"${a}"`,
)}) VALUES (${formConfig.columns
)}) VALUES (${columns
.map((d) => `'{{${create.value}.${d}}}'`)
.toString()})`,
},
@ -197,11 +203,19 @@ export default abstract class PostgreSQL extends BaseQueryGenerator {
allBuildConfigs.push(this.buildSelect(widgetConfig, formConfig));
}
if (widgetConfig.update && formConfig.primaryColumn) {
if (
widgetConfig.update &&
formConfig.primaryColumn &&
formConfig.connectionMode === DatasourceConnectionMode.READ_WRITE
) {
allBuildConfigs.push(this.buildUpdate(widgetConfig, formConfig));
}
if (widgetConfig.create && formConfig.primaryColumn) {
if (
widgetConfig.create &&
formConfig.primaryColumn &&
formConfig.connectionMode === DatasourceConnectionMode.READ_WRITE
) {
allBuildConfigs.push(this.buildInsert(widgetConfig, formConfig));
}

View File

@ -1,3 +1,4 @@
import { DatasourceConnectionMode } from "entities/Datasource";
import Snowflake from ".";
describe("Snowflake WidgetQueryGenerator", () => {
@ -26,6 +27,62 @@ describe("Snowflake WidgetQueryGenerator", () => {
searchableColumn: "title",
columns: [],
primaryColumn: "genres",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
const res = `SELECT
*
FROM
someTable
WHERE
title LIKE '%{{data_table.searchText || \"\"}}%'
ORDER BY
{{data_table.sortOrder.column || 'genres'}} {{data_table.sortOrder.order || 'ASC' ? \"\" : \"DESC\"}}
LIMIT
{{data_table.pageSize}}
OFFSET
{{(data_table.pageNo - 1) * data_table.pageSize}}`;
expect(expr).toEqual([
{
name: "Select_someTable",
type: "select",
dynamicBindingPathList: [
{
key: "body",
},
],
payload: {
pluginSpecifiedTemplates: [{ value: false }],
body: res,
},
},
]);
});
test("should build select form data correctly with read permissions", () => {
const expr = Snowflake.build(
{
select: {
limit: "data_table.pageSize",
where: 'data_table.searchText || ""',
offset: "(data_table.pageNo - 1) * data_table.pageSize",
orderBy: "data_table.sortOrder.column",
sortOrder: "data_table.sortOrder.order || 'ASC'",
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: [],
primaryColumn: "genres",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
@ -80,6 +137,7 @@ OFFSET
searchableColumn: "title",
columns: [],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -129,6 +187,32 @@ OFFSET
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
expect(expr).toEqual([]);
});
test("should not build update form data without read write ", () => {
const expr = Snowflake.build(
{
update: {
value: `update_form.fieldState'`,
where: `"id" = {{data_table.selectedRow.id}}`,
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
@ -153,6 +237,7 @@ OFFSET
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
@ -191,6 +276,31 @@ OFFSET
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);
expect(expr).toEqual([]);
});
test("should not build insert form data without read write permissions", () => {
const expr = Snowflake.build(
{
create: {
value: `update_form.fieldState`,
},
totalRecord: false,
},
{
tableName: "someTable",
datasourceId: "someId",
// ignore columns
aliases: [{ name: "someColumn1", alias: "someColumn1" }],
widgetId: "someWidgetId",
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_ONLY,
},
initialValues,
);
@ -214,6 +324,7 @@ OFFSET
searchableColumn: "title",
columns: ["id", "name"],
primaryColumn: "id",
connectionMode: DatasourceConnectionMode.READ_WRITE,
},
initialValues,
);

View File

@ -8,6 +8,7 @@ import type {
} from "../types";
import { removeSpecialChars } from "utils/helpers";
import { without } from "lodash";
import { DatasourceConnectionMode } from "entities/Datasource";
export default abstract class Snowflake extends BaseQueryGenerator {
private static buildSelect(
@ -197,11 +198,19 @@ export default abstract class Snowflake extends BaseQueryGenerator {
allBuildConfigs.push(this.buildSelect(widgetConfig, formConfig));
}
if (widgetConfig.update && formConfig.primaryColumn) {
if (
widgetConfig.update &&
formConfig.primaryColumn &&
formConfig.connectionMode === DatasourceConnectionMode.READ_WRITE
) {
allBuildConfigs.push(this.buildUpdate(widgetConfig, formConfig));
}
if (widgetConfig.create && formConfig.primaryColumn) {
if (
widgetConfig.create &&
formConfig.primaryColumn &&
formConfig.connectionMode === DatasourceConnectionMode.READ_WRITE
) {
allBuildConfigs.push(this.buildInsert(widgetConfig, formConfig));
}

View File

@ -13,6 +13,7 @@ export type WidgetQueryGenerationFormConfig = {
searchableColumn: string;
columns: string[];
primaryColumn: string;
connectionMode: string;
} & GsheetConfig;
export type WidgetQueryGenerationConfig = {

View File

@ -50,3 +50,13 @@ export const isEnvironmentValid = (
datasource.datasourceStorages[environment]?.isValid;
return isValid ? isValid : false;
};
/*
* Functiont to check get the datasource configuration for current ENV
*/
export const getEnvironmentConfiguration = (
datasource: Datasource | null,
environment = getCurrentEnvironment(),
) => {
return datasource?.datasourceStorages?.[environment]?.datasourceConfiguration;
};

View File

@ -8,6 +8,7 @@ import type { ExplorerURLParams } from "@appsmith/pages/Editor/Explorer/helpers"
import { INTEGRATION_TABS } from "constants/routes";
import { PluginPackageName } from "entities/Action";
import type { Datasource, MockDatasource } from "entities/Datasource";
import { DatasourceConnectionMode } from "entities/Datasource";
import { useContext, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useParams } from "react-router";
@ -35,7 +36,10 @@ import { getWidget } from "sagas/selectors";
import type { AppState } from "@appsmith/reducers";
import { DatasourceCreateEntryPoints } from "constants/Datasource";
import { getCurrentWorkspaceId } from "@appsmith/selectors/workspaceSelectors";
import { isEnvironmentValid } from "@appsmith/utils/Environments";
import {
getEnvironmentConfiguration,
isEnvironmentValid,
} from "@appsmith/utils/Environments";
import type { ActionDataState } from "reducers/entityReducers/actionsReducer";
import { getDatatype } from "utils/AppsmithUtils";
@ -144,6 +148,8 @@ export function useDatasource(searchText: string) {
isValid: isEnvironmentValid(datasource),
pluginPackageName: pluginsPackageNamesMap[datasource.pluginId],
isSample: false,
connectionMode:
getEnvironmentConfiguration(datasource)?.connection?.mode,
},
icon: (
<ImageWrapper>
@ -167,6 +173,9 @@ export function useDatasource(searchText: string) {
datasource: valueOption?.id,
datasourcePluginType: plugin?.type,
datasourcePluginName: plugin?.name,
datasourceConnectionMode:
valueOption?.data.connectionMode ||
DatasourceConnectionMode.READ_ONLY,
});
if (valueOption?.id) {
@ -192,6 +201,7 @@ export function useDatasource(searchText: string) {
propertyName: propertyName,
pluginType: plugin?.type,
pluginName: plugin?.name,
connectionMode: valueOption?.data.connectionMode,
isSampleDb: datasource.isMock,
});
}
@ -205,7 +215,6 @@ export function useDatasource(searchText: string) {
if (mockDatasources.length) {
mockDatasourceOptions = mockDatasourceOptions.concat(
mockDatasources
.filter(({ packageName }) => {
if (!WidgetQueryGeneratorRegistry.has(packageName)) {
return false;
@ -265,6 +274,7 @@ export function useDatasource(searchText: string) {
datasource: valueOption?.id,
datasourcePluginType: plugin?.type,
datasourcePluginName: plugin?.name,
datasourceConnectionMode: DatasourceConnectionMode.READ_WRITE,
});
setIsMockDatasourceSelected(true);
@ -362,6 +372,7 @@ export function useDatasource(searchText: string) {
datasource: "",
datasourcePluginType: "",
datasourcePluginName: "",
datasourceConnectionMode: "",
});
AnalyticsUtil.logEvent("BIND_EXISTING_QUERY_TO_WIDGET", {
@ -389,11 +400,19 @@ export function useDatasource(searchText: string) {
!isDatasourceLoading &&
actualDatasourceOptions.length
) {
const datasource =
actualDatasourceOptions[actualDatasourceOptions.length - 1];
const plugin = plugins.find((d) => d.id === datasource.data.pluginId);
setIsMockDatasourceSelected(false);
updateConfig(
"datasource",
actualDatasourceOptions[actualDatasourceOptions.length - 1].id,
);
updateConfig({
datasource: datasource.id,
datasourceConnectionMode: datasource.data.connectionMode,
datasourcePluginType: plugin?.type,
datasourcePluginName: plugin?.name,
});
}
}, [isMockDatasourceSelected, isDatasourceLoading, datasourceOptions]);

View File

@ -123,6 +123,7 @@ export function useTableOrSpreadsheet() {
dataTableName: TableObj.value,
pluginType: config.datasourcePluginType,
pluginName: config.datasourcePluginName,
connectionMode: config.datasourceConnectionMode,
});
},
[

View File

@ -36,6 +36,7 @@ export function useConnectData() {
searchableColumn: config.searchableColumn,
columns: columns.map((column) => column.name),
primaryColumn,
connectionMode: config.datasourceConnectionMode,
};
dispatch({
@ -49,6 +50,7 @@ export function useConnectData() {
propertyName: propertyName,
pluginType: config.datasourcePluginType,
pluginName: config.datasourcePluginName,
connectionMode: config.datasourceConnectionMode,
additionalData: {
dataTableName: config.table,
searchableColumn: config.searchableColumn,

View File

@ -67,6 +67,7 @@ export function useSheets() {
sheetName: sheetObj.value,
pluginType: config.datasourcePluginType,
pluginName: config.datasourcePluginName,
connectionMode: config.datasourceConnectionMode,
});
},
[config, updateConfig, dispatch, widget, selectedDatasource, propertyName],

View File

@ -133,6 +133,7 @@ export function useColumns(alias: string) {
propertyName: propertyName,
pluginType: config.datasourcePluginType,
pluginName: config.datasourcePluginName,
connectionMode: config.datasourceConnectionMode,
});
}
},

View File

@ -28,6 +28,7 @@ type WidgetQueryGeneratorFormContextType = {
tableHeaderIndex: number;
datasourcePluginType: string;
datasourcePluginName: string;
datasourceConnectionMode: string;
};
updateConfig: (
property: string | Record<string, unknown>,
@ -49,6 +50,7 @@ const DEFAULT_CONFIG_VALUE = {
tableHeaderIndex: 1,
datasourcePluginType: "",
datasourcePluginName: "",
datasourceConnectionMode: "",
};
const DEFAULT_CONTEXT_VALUE = {
@ -141,6 +143,7 @@ function WidgetQueryGeneratorForm(props: Props) {
set(draftConfig, "alias", {});
set(draftConfig, "datasourcePluginType", "");
set(draftConfig, "datasourcePluginName", "");
set(draftConfig, "datasourceConnectionMode", "");
}
if (

View File

@ -1,6 +1,7 @@
import type { APIResponseError } from "api/ApiResponses";
import type { ActionConfig, Property } from "entities/Action";
import _ from "lodash";
import type { SSL } from "./RestAPIForm";
export enum AuthType {
OAUTH2 = "oAuth2",
@ -105,6 +106,11 @@ export interface EmbeddedRestDatasource extends BaseDatasource {
isValid: boolean;
}
export enum DatasourceConnectionMode {
READ_ONLY = "READ_ONLY",
READ_WRITE = "READ_WRITE",
}
export interface DatasourceConfiguration {
url: string;
authentication?: DatasourceAuthentication;
@ -112,6 +118,10 @@ export interface DatasourceConfiguration {
headers?: Property[];
queryParameters?: Property[];
databaseName?: string;
connection?: {
mode: DatasourceConnectionMode;
ssl: SSL;
};
}
export interface Datasource extends BaseDatasource {

View File

@ -199,10 +199,11 @@ const PropertyControl = memo((props: Props) => {
// we don't want to remove the path from dynamic binding list
// on toggling of js in case of few widgets
if (
SHOULD_NOT_REJECT_DYNAMIC_BINDING_LIST_FOR.includes(
(SHOULD_NOT_REJECT_DYNAMIC_BINDING_LIST_FOR.includes(
props.controlType,
) &&
isDynamicValue(propertyValue)
isDynamicValue(propertyValue)) ||
!shouldValidateValueOnDynamicPropertyOff
) {
shouldRejectDynamicBindingPathList = false;
}

View File

@ -259,7 +259,7 @@ function* BindWidgetToDatasource(
const updatedWidget: WidgetProps = yield select(getWidgetByID(widgetId));
const updates = getPropertyUpdatesForQueryBinding(
const { dynamicUpdates, modify } = getPropertyUpdatesForQueryBinding(
queryBindingConfig,
updatedWidget,
action.payload,
@ -270,8 +270,9 @@ function* BindWidgetToDatasource(
payload: {
widgetId,
updates: {
modify: updates,
modify,
},
dynamicUpdates,
},
});

View File

@ -80,7 +80,7 @@ export const datasourceToFormValues = (
authType: authType,
authentication: authentication,
connection: connection,
};
} as ApiDatasourceForm;
};
export const formValuesToDatasource = (

View File

@ -118,6 +118,7 @@ import type {
WidgetQueryConfig,
WidgetQueryGenerationFormConfig,
} from "WidgetQueryGenerators/types";
import type { DynamicPath } from "utils/DynamicBindingUtils";
const ReactTableComponent = lazy(() =>
retryPromise(() => import("../component")),
@ -170,10 +171,11 @@ class TableWidgetV2 extends BaseWidget<TableWidgetProps, WidgetState> {
widget: TableWidgetProps,
formConfig: WidgetQueryGenerationFormConfig,
) {
let updates = {};
let modify = {};
const dynamicPropertyPathList: DynamicPath[] = [];
if (queryConfig.select) {
updates = merge(updates, {
modify = merge(modify, {
tableData: queryConfig.select.data,
onPageChange: queryConfig.select.run,
serverSidePaginationEnabled: true,
@ -188,7 +190,7 @@ class TableWidgetV2 extends BaseWidget<TableWidgetProps, WidgetState> {
}
if (queryConfig.create) {
updates = merge(updates, {
modify = merge(modify, {
onAddNewRowSave: queryConfig.create.run,
allowAddNewRow: true,
...Object.keys(widget.primaryColumns).reduce(
@ -218,29 +220,42 @@ class TableWidgetV2 extends BaseWidget<TableWidgetProps, WidgetState> {
editAction = Object.values(createEditActionColumn(widget)).reduce(
(
prev: Record<string, unknown>,
curr: { propertyPath: string; propertyValue: unknown },
curr: {
propertyPath: string;
propertyValue: unknown;
isDynamicPropertyPath?: boolean;
},
) => {
prev[curr.propertyPath] = curr.propertyValue;
if (curr.isDynamicPropertyPath) {
dynamicPropertyPathList.push({ key: curr.propertyPath });
}
return prev;
},
{},
);
}
updates = merge(updates, {
modify = merge(modify, {
...editAction,
[`primaryColumns.EditActions1.onSave`]: queryConfig.update.run,
});
}
if (queryConfig.total_record) {
updates = merge(updates, {
modify = merge(modify, {
totalRecordsCount: queryConfig.total_record.data,
});
}
return updates;
return {
modify,
dynamicUpdates: {
dynamicPropertyPathList,
},
};
}
static getPropertyPaneContentConfig() {