fix: Incorrect updation of selctedRowIndex when primary column is set. (#36393)

## Description
<ins> Problem statement </ins>
1. Table should have a primary column set.
2. The table should be filtered(via search or client side filters)
3. there should be a selectedRow in table
a. One way to ensure is add a text widget and bind it to table's
`selectedRowIndex` property.

If you now run a query that updates the data behind the table: the
`selectedRow` is updated to (seemingly) random row and selectedRowIndex
is updated as well

<ins> Rootcause </ins>
We have a mechanism of preserving `selectedRow` when data updates
happen.
Underlying logic gets the previous selected row and tries to find it in
the new data using the primary key.
* If it finds it, it updates the selectedRowIndex to the
`__original_index__` of the found row i.e. its position in the original
data before any filters were applied.

<ins> Solution </ins>
We update the selectedRowIndex to the index in the new filtered data.

<ins> How to test </ins>
1. Create a table with a primary key
2. Add a text widget and bind it to table's `selectedRowIndex` property
3. Filter the table
4. Click on a row to select it
5. Run a query that updates the data behind the table
6. Assert that the selected row is the same and that the
selectedRowIndex is updated to the new index


Fixes #36080
_or_  
Fixes `Issue URL`
> [!WARNING]  
> _If no issue exists, please create an issue first, and check with the
maintainers if the issue is valid._

## Automation

/ok-to-test tags="@tag.Table, @tag.Binding, @tag.Sanity"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/10953679202>
> Commit: 1a2d64467e453229940b67b29da1ce9d0e9ac24d
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10953679202&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Table, @tag.Binding, @tag.Sanity`
> Spec:
> <hr>Fri, 20 Sep 2024 06:30:08 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **Improvements**
- Enhanced the efficiency of the TableWidgetV2 by streamlining the
method for retrieving the index of selected rows.
- Improved code readability and maintainability without altering the
overall functionality.

- **New Features**
- Introduced new test cases to validate filtering and searching
functionalities within the Table Widget V2.
- Added a JSON configuration for a data-driven table interface,
supporting sorting, filtering, and dynamic data binding.
- Introduced fixture data for testing, allowing for comprehensive
validation of table behavior under various conditions.
- Added a JavaScript object containing employee data to facilitate
filtering and display in the table.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Rahul Barwal 2024-09-20 12:25:23 +05:30 committed by GitHub
parent 5998495cf3
commit 6507f90e05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 648 additions and 63 deletions

View File

@ -1,55 +0,0 @@
import EditorNavigation, {
EntityType,
} from "../../../../../support/Pages/EditorNavigation";
const widgetsPage = require("../../../../../locators/Widgets.json");
const commonlocators = require("../../../../../locators/commonlocators.json");
const publish = require("../../../../../locators/publishWidgetspage.json");
const dsl = require("../../../../../fixtures/tableV2AndTextDsl.json");
import * as _ from "../../../../../support/Objects/ObjectsCore";
describe(
"Table Widget V2 Filtered Table data in autocomplete",
{ tags: ["@tag.Widget", "@tag.Table", "@tag.Sanity"] },
function () {
before("Table Widget V2 Functionality", () => {
_.agHelper.AddDsl("tableV2AndTextDsl");
cy.openPropertyPane("tablewidgetv2");
});
it("1. Table Widget V2 Functionality To Filter and search data", function () {
cy.get(publish.searchInput).first().type("query");
cy.get(publish.filterBtn).click({ force: true });
cy.get(publish.attributeDropdown).click({ force: true });
cy.get(publish.attributeValue).contains("task").click({ force: true });
cy.get(publish.conditionDropdown).click({ force: true });
cy.get(publish.attributeValue)
.contains("contains")
.click({ force: true });
cy.get(publish.tableFilterInputValue).type("bind", { force: true });
cy.wait(500);
cy.get(widgetsPage.filterApplyBtn).click({ force: true });
cy.wait(500);
cy.get(".t--close-filter-btn").click({ force: true });
});
it("2. Table Widget V2 Functionality to validate filtered table data", function () {
EditorNavigation.SelectEntityByName("Text1", EntityType.Widget);
cy.testJsontext("text", "{{Table1.filteredTableData[0].task}}");
cy.readTableV2data("0", "1").then((tabData) => {
const tableData = tabData;
cy.get(commonlocators.labelTextStyle).should("have.text", tableData);
});
//Table Widget V2 Functionality to validate filtered table data with actual table data
cy.readTableV2data("0", "1").then((tabData) => {
const tableData = JSON.parse(dsl.dsl.children[0].tableData);
cy.get(commonlocators.labelTextStyle).should(
"have.text",
tableData[2].task,
);
});
});
},
);

View File

@ -0,0 +1,114 @@
import EditorNavigation, {
EntityType,
PageLeftPane,
PagePaneSegment,
} from "../../../../../support/Pages/EditorNavigation";
import dsl from "../../../../../fixtures/tableV2AndTextDsl.json";
import widgetsPage from "../../../../../locators/Widgets.json";
import commonlocators from "../../../../../locators/commonlocators.json";
import publish from "../../../../../locators/publishWidgetspage.json";
import { tableDataJSObject } from "../../../../../fixtures/tableV2FilteringWithPrimaryColumnJSObjectWidthData";
import {
agHelper,
jsEditor,
locators,
table,
} from "../../../../../support/Objects/ObjectsCore";
describe(
"Table Widget V2 Filtered Table data in autocomplete",
{ tags: ["@tag.Widget", "@tag.Table", "@tag.Sanity"] },
function () {
before("Table Widget V2 Functionality", () => {
agHelper.AddDsl("tableV2AndTextDsl");
agHelper.GetNClick(locators._widgetInCanvas("tablewidgetv2"));
});
it("1. Table Widget V2 Functionality To Filter and search data", function () {
table.SearchTable("query");
agHelper.GetNClick(publish.filterBtn, 0, true);
agHelper.GetNClick(publish.attributeDropdown, 0, true);
agHelper
.GetElement(publish.attributeValue)
.contains("task")
.click({ force: true });
agHelper.GetNClick(publish.conditionDropdown, 0, true);
agHelper
.GetElement(publish.attributeValue)
.contains("contains")
.click({ force: true });
agHelper.TypeText(publish.tableFilterInputValue, "bind");
agHelper.GetNClick(widgetsPage.filterApplyBtn, 0, true);
table.CloseFilter();
});
it("2. Table Widget V2 Functionality to validate filtered table data", function () {
EditorNavigation.SelectEntityByName("Text1", EntityType.Widget);
(cy as any).testJsontext("text", "{{Table1.filteredTableData[0].task}}");
table.ReadTableRowColumnData(0, 1, "v2").then((tabData: string) => {
agHelper.AssertText(
commonlocators.labelTextStyle,
"text",
tabData as string,
);
});
//Table Widget V2 Functionality to validate filtered table data with actual table data
const tableData = JSON.parse(dsl.dsl.children[0].tableData as string);
agHelper.AssertText(
commonlocators.labelTextStyle,
"text",
tableData[1].task as string,
);
});
it("3. When primary key is set, selectedRowIndex should not updated after data update", function () {
// https://github.com/appsmithorg/appsmith/issues/36080
jsEditor.CreateJSObject(tableDataJSObject, {
paste: true,
completeReplace: true,
toRun: false,
shouldCreateNewJSObj: true,
prettify: true,
});
jsEditor.CreateJSObject(manipulateDataJSObject, {
paste: true,
completeReplace: true,
toRun: true,
shouldCreateNewJSObj: true,
prettify: true,
});
PageLeftPane.switchSegment(PagePaneSegment.UI);
agHelper.AddDsl("tableV2FilteringWithPrimaryColumn");
table.SearchTable("Engineering");
table.ReadTableRowColumnData(2, 0, "v2").then((text) => {
expect(text).to.equal("Michael Wilson");
});
table.SelectTableRow(2, 0, true, "v2");
agHelper.GetText(locators._textWidget, "text").then((text) => {
agHelper.ClickButton("Submit");
table.ReadTableRowColumnData(2, 0, "v2").then((text) => {
expect(text).to.equal("Michael Wilson1");
});
agHelper.AssertText(locators._textWidget, "text", text as string);
});
});
},
);
export const manipulateDataJSObject = `export default {
data: JSObject1.initData,
makeNewCopy() {
const my = _.cloneDeep(this.data);
my[5].name =my[5].name+"1";
this.data = my;
}
}`;

View File

@ -0,0 +1,449 @@
{
"dsl": {
"widgetName": "MainContainer",
"backgroundColor": "none",
"rightColumn": 4896,
"snapColumns": 64,
"detachFromLayout": true,
"widgetId": "0",
"topRow": 0,
"bottomRow": 1282,
"containerStyle": "none",
"snapRows": 124,
"parentRowSpace": 1,
"type": "CANVAS_WIDGET",
"canExtend": true,
"version": 90,
"minHeight": 1292,
"dynamicTriggerPathList": [],
"parentColumnSpace": 1,
"dynamicBindingPathList": [],
"leftColumn": 0,
"children": [
{
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}",
"borderColor": "#E0DEDE",
"isVisibleDownload": true,
"topRow": 1,
"isSortable": true,
"type": "TABLE_WIDGET_V2",
"inlineEditingSaveOption": "ROW_LEVEL",
"animateLoading": true,
"dynamicBindingPathList": [
{
"key": "accentColor"
},
{
"key": "borderRadius"
},
{
"key": "boxShadow"
},
{
"key": "primaryColumns.name.computedValue"
},
{
"key": "primaryColumns.department.computedValue"
},
{
"key": "primaryColumns.location.computedValue"
},
{
"key": "tableData"
},
{
"key": "primaryColumns.employeeId.computedValue"
},
{
"key": "primaryColumns.position.computedValue"
},
{
"key": "primaryColumns.salary.computedValue"
}
],
"leftColumn": 0,
"delimiter": ",",
"defaultSelectedRowIndex": 0,
"flexVerticalAlignment": "start",
"accentColor": "{{appsmith.theme.colors.primaryColor}}",
"isVisibleFilters": true,
"isVisible": true,
"enableClientSideSearch": true,
"version": 2,
"totalRecordsCount": 0,
"isLoading": false,
"childStylesheet": {
"button": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"boxShadow": "none"
},
"menuButton": {
"menuColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"boxShadow": "none"
},
"iconButton": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"boxShadow": "none"
},
"editActions": {
"saveButtonColor": "{{appsmith.theme.colors.primaryColor}}",
"saveBorderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"discardButtonColor": "{{appsmith.theme.colors.primaryColor}}",
"discardBorderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
}
},
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"columnUpdatedAt": 1726719516718,
"primaryColumnId": "employeeId",
"defaultSelectedRowIndices": [0],
"needsErrorInfo": false,
"mobileBottomRow": 35,
"widgetName": "Table1",
"defaultPageSize": 0,
"columnOrder": [
"name",
"department",
"location",
"employeeId",
"position",
"salary"
],
"dynamicPropertyPathList": [
{
"key": "tableData"
}
],
"bottomRow": 29,
"columnWidthMap": {},
"parentRowSpace": 10,
"mobileRightColumn": 46,
"parentColumnSpace": 10.546875,
"dynamicTriggerPathList": [],
"borderWidth": "1",
"primaryColumns": {
"name": {
"allowCellWrapping": false,
"allowSameOptionsInNewRow": true,
"index": 1,
"width": 150,
"originalId": "name",
"id": "name",
"alias": "name",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "0.875rem",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellEditable": false,
"isEditable": false,
"isCellVisible": true,
"isDerived": false,
"label": "name",
"isSaveVisible": true,
"isDiscardVisible": true,
"computedValue": "{{Table1.processedTableData.map((currentRow, currentIndex) => ( currentRow[\"name\"]))}}",
"sticky": "",
"validation": {},
"currencyCode": "USD",
"decimals": 0,
"thousandSeparator": true,
"notation": "standard"
},
"department": {
"allowCellWrapping": false,
"allowSameOptionsInNewRow": true,
"index": 4,
"width": 150,
"originalId": "department",
"id": "department",
"alias": "department",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "0.875rem",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellEditable": false,
"isEditable": false,
"isCellVisible": true,
"isDerived": false,
"label": "department",
"isSaveVisible": true,
"isDiscardVisible": true,
"computedValue": "{{Table1.processedTableData.map((currentRow, currentIndex) => ( currentRow[\"department\"]))}}",
"sticky": "",
"validation": {},
"currencyCode": "USD",
"decimals": 0,
"thousandSeparator": true,
"notation": "standard"
},
"location": {
"allowCellWrapping": false,
"allowSameOptionsInNewRow": true,
"index": 5,
"width": 150,
"originalId": "location",
"id": "location",
"alias": "location",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textSize": "0.875rem",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellEditable": false,
"isEditable": false,
"isCellVisible": true,
"isDerived": false,
"label": "location",
"isSaveVisible": true,
"isDiscardVisible": true,
"computedValue": "{{Table1.processedTableData.map((currentRow, currentIndex) => ( currentRow[\"location\"]))}}",
"sticky": "",
"validation": {},
"currencyCode": "USD",
"decimals": 0,
"thousandSeparator": true,
"notation": "standard"
},
"employeeId": {
"allowCellWrapping": false,
"allowSameOptionsInNewRow": true,
"index": 0,
"width": 150,
"originalId": "employeeId",
"id": "employeeId",
"alias": "employeeId",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "number",
"textColor": "",
"textSize": "0.875rem",
"fontStyle": "",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellEditable": false,
"isEditable": false,
"isCellVisible": true,
"isDerived": false,
"label": "employeeId",
"isSaveVisible": true,
"isDiscardVisible": true,
"computedValue": "{{Table1.processedTableData.map((currentRow, currentIndex) => ( currentRow[\"employeeId\"]))}}",
"sticky": "",
"validation": {},
"currencyCode": "USD",
"decimals": 0,
"thousandSeparator": true,
"notation": "standard",
"cellBackground": ""
},
"position": {
"allowCellWrapping": false,
"allowSameOptionsInNewRow": true,
"index": 3,
"width": 150,
"originalId": "position",
"id": "position",
"alias": "position",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "text",
"textColor": "",
"textSize": "0.875rem",
"fontStyle": "",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellEditable": false,
"isEditable": false,
"isCellVisible": true,
"isDerived": false,
"label": "position",
"isSaveVisible": true,
"isDiscardVisible": true,
"computedValue": "{{Table1.processedTableData.map((currentRow, currentIndex) => ( currentRow[\"position\"]))}}",
"sticky": "",
"validation": {},
"currencyCode": "USD",
"decimals": 0,
"thousandSeparator": true,
"notation": "standard",
"cellBackground": ""
},
"salary": {
"allowCellWrapping": false,
"allowSameOptionsInNewRow": true,
"index": 5,
"width": 150,
"originalId": "salary",
"id": "salary",
"alias": "salary",
"horizontalAlignment": "LEFT",
"verticalAlignment": "CENTER",
"columnType": "number",
"textColor": "",
"textSize": "0.875rem",
"fontStyle": "",
"enableFilter": true,
"enableSort": true,
"isVisible": true,
"isDisabled": false,
"isCellEditable": false,
"isEditable": false,
"isCellVisible": true,
"isDerived": false,
"label": "salary",
"isSaveVisible": true,
"isDiscardVisible": true,
"computedValue": "{{Table1.processedTableData.map((currentRow, currentIndex) => ( currentRow[\"salary\"]))}}",
"sticky": "",
"validation": {},
"currencyCode": "USD",
"decimals": 0,
"thousandSeparator": true,
"notation": "standard",
"cellBackground": ""
}
},
"key": "1vnt7cehmt",
"canFreezeColumn": true,
"rightColumn": 64,
"textSize": "0.875rem",
"widgetId": "gt19wbzlit",
"minWidth": 450,
"tableData": "{{JSObject2.data}}",
"label": "Data",
"searchKey": "",
"parentId": "0",
"renderMode": "CANVAS",
"mobileTopRow": 7,
"horizontalAlignment": "LEFT",
"isVisibleSearch": true,
"responsiveBehavior": "fill",
"mobileLeftColumn": 12,
"isVisiblePagination": true,
"verticalAlignment": "CENTER"
},
{
"needsErrorInfo": false,
"mobileBottomRow": 44,
"widgetName": "Text1",
"topRow": 34,
"bottomRow": 38,
"parentRowSpace": 10,
"type": "TEXT_WIDGET",
"mobileRightColumn": 18,
"animateLoading": true,
"overflow": "NONE",
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"parentColumnSpace": 10.546875,
"dynamicTriggerPathList": [],
"leftColumn": 2,
"dynamicBindingPathList": [
{
"key": "truncateButtonColor"
},
{
"key": "fontFamily"
},
{
"key": "borderRadius"
},
{
"key": "text"
}
],
"shouldTruncate": false,
"truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}",
"text": "{{Table1.selectedRowIndex}}",
"key": "dlet8mfjip",
"rightColumn": 18,
"textAlign": "LEFT",
"dynamicHeight": "AUTO_HEIGHT",
"widgetId": "irgoh659po",
"minWidth": 450,
"isVisible": true,
"fontStyle": "BOLD",
"textColor": "#231F20",
"version": 1,
"parentId": "0",
"renderMode": "CANVAS",
"isLoading": false,
"mobileTopRow": 40,
"responsiveBehavior": "fill",
"originalTopRow": 40,
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"mobileLeftColumn": 2,
"maxDynamicHeight": 9000,
"originalBottomRow": 44,
"fontSize": "1rem",
"minDynamicHeight": 4
},
{
"resetFormOnClick": false,
"needsErrorInfo": false,
"boxShadow": "none",
"mobileBottomRow": 44,
"widgetName": "Button1",
"onClick": "{{JSObject2.makeNewCopy();}}",
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"topRow": 34,
"bottomRow": 38,
"parentRowSpace": 10,
"type": "BUTTON_WIDGET",
"mobileRightColumn": 55,
"animateLoading": true,
"parentColumnSpace": 10.546875,
"dynamicTriggerPathList": [
{
"key": "onClick"
}
],
"leftColumn": 39,
"dynamicBindingPathList": [
{
"key": "buttonColor"
},
{
"key": "borderRadius"
}
],
"text": "Submit",
"isDisabled": false,
"key": "mzyu2305os",
"rightColumn": 55,
"isDefaultClickDisabled": true,
"widgetId": "q29byryymd",
"minWidth": 120,
"isVisible": true,
"recaptchaType": "V3",
"version": 1,
"parentId": "0",
"renderMode": "CANVAS",
"isLoading": false,
"mobileTopRow": 40,
"responsiveBehavior": "hug",
"disabledWhenInvalid": false,
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"mobileLeftColumn": 39,
"buttonVariant": "PRIMARY",
"placement": "CENTER"
}
]
}
}

View File

@ -0,0 +1,84 @@
export const tableDataJSObject = `export default {
initData :[
{
"employeeId": 101,
"name": "John Doe",
"department": "Engineering",
"position": "Software Engineer",
"location": "San Francisco",
"salary": 120000
},
{
"employeeId": 102,
"name": "Jane Smith",
"department": "Engineering",
"position": "DevOps Engineer",
"location": "New York",
"salary": 115000
},
{
"employeeId": 103,
"name": "Alice Johnson",
"department": "Marketing",
"position": "Marketing Manager",
"location": "Chicago",
"salary": 90000
},
{
"employeeId": 104,
"name": "Robert Brown",
"department": "Sales",
"position": "Sales Executive",
"location": "Los Angeles",
"salary": 95000
},
{
"employeeId": 105,
"name": "Linda Davis",
"department": "HR",
"position": "HR Manager",
"location": "San Francisco",
"salary": 85000
},
{
"employeeId": 106,
"name": "Michael Wilson",
"department": "Engineering",
"position": "Frontend Developer",
"location": "San Francisco",
"salary": 105000
},
{
"employeeId": 107,
"name": "Emily Clark",
"department": "Marketing",
"position": "Content Specialist",
"location": "Chicago",
"salary": 75000
},
{
"employeeId": 108,
"name": "David Martinez",
"department": "Sales",
"position": "Sales Manager",
"location": "New York",
"salary": 110000
},
{
"employeeId": 109,
"name": "Sarah Lee",
"department": "HR",
"position": "Recruiter",
"location": "Los Angeles",
"salary": 70000
},
{
"employeeId": 110,
"name": "James Anderson",
"department": "Engineering",
"position": "Backend Developer",
"location": "New York",
"salary": 118000
}
]
}`;

View File

@ -19,7 +19,6 @@ import {
DEFAULT_BUTTON_COLOR,
DEFAULT_COLUMN_WIDTH,
TABLE_COLUMN_ORDER_KEY,
ORIGINAL_INDEX_KEY,
} from "../constants";
import { SelectColumnOptionsValidations } from "./propertyUtils";
import type { TableWidgetProps } from "../constants";
@ -65,13 +64,7 @@ export const getOriginalRowIndex = (
}
if (!!primaryKey && tableData) {
const selectedRow = tableData.find(
(row) => row[primaryColumnId] === primaryKey,
);
if (selectedRow) {
index = selectedRow[ORIGINAL_INDEX_KEY] as number;
}
index = tableData.findIndex((row) => row[primaryColumnId] === primaryKey);
}
return index;