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:
parent
c6afa03372
commit
4c938676bd
|
|
@ -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_"));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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([]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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([]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }],
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export type WidgetQueryGenerationFormConfig = {
|
|||
searchableColumn: string;
|
||||
columns: string[];
|
||||
primaryColumn: string;
|
||||
connectionMode: string;
|
||||
} & GsheetConfig;
|
||||
|
||||
export type WidgetQueryGenerationConfig = {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ export function useTableOrSpreadsheet() {
|
|||
dataTableName: TableObj.value,
|
||||
pluginType: config.datasourcePluginType,
|
||||
pluginName: config.datasourcePluginName,
|
||||
connectionMode: config.datasourceConnectionMode,
|
||||
});
|
||||
},
|
||||
[
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ export function useColumns(alias: string) {
|
|||
propertyName: propertyName,
|
||||
pluginType: config.datasourcePluginType,
|
||||
pluginName: config.datasourcePluginName,
|
||||
connectionMode: config.datasourceConnectionMode,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export const datasourceToFormValues = (
|
|||
authType: authType,
|
||||
authentication: authentication,
|
||||
connection: connection,
|
||||
};
|
||||
} as ApiDatasourceForm;
|
||||
};
|
||||
|
||||
export const formValuesToDatasource = (
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user