fix: add select options field to new row (#22003)

## Description

This PR introduces new two new properties: `Same options in new row` and
`New row options` for the select column type.
We show these two fields when the allow add new row is turned on in
table
- SameOptionsInNewRow -- Boolean field ; When turned on the option for
the first row is used in the new row. Default is true
     - Label : Use same options in new row
- Tooltip : Toggle to display same choices for new row and editing
existing row in column.
- NewRowOptions -- Array field; when the above boolean is turned off we
show this option to let people add options specifically for new row.
Supports static and dynamic data, but doesn't support currentRow
    - Label: New row options
- Tooltip : Options exclusively displayed in the column for new row
addition.

Fixes #20230


## Type of change

- Bug fix (non-breaking change which fixes an issue)


## How Has This Been Tested?

- Cypress:
- When allowAddNewRow is turned on, same new option should be true and
new row option should be invisible.
- When turned on same option, options of the first row should appear
while editing as well as adding new row.
    - When turned off, New row option should be visible
- Fill new row options, the new row options should appear while adding
new row in select field.
    -  New row options should not have access to the currentRow
- Both the options should only be visible only when allowNewRow is true.
    -  Should support static and dynamic values in new row options

- Manual
- For the new DnD Tables. Check if the above functionality is working as
expected.
- For Existing tables, Check if the above functionality is working as
expected. This needed to be tested from migration standpoint

### Test Plan
> https://github.com/appsmithorg/TestSmith/issues/2367

### 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
- [ ] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


### QA activity:
- [ ] Test plan has been approved by relevant developers
- [ ] Test plan has been peer reviewed by QA
- [ ] Cypress test cases have been added and approved by either SDET or
manual QA
- [ ] Organized project review call with relevant stakeholders after
Round 1/2 of QA
- [ ] Added Test Plan Approved label after reveiwing all Cypress test
This commit is contained in:
Keyur Paralkar 2023-04-06 23:58:24 +05:30 committed by GitHub
parent f66410ebcb
commit 3ee46ffd14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 388 additions and 9 deletions

View File

@ -3,10 +3,12 @@ const widgetsPage = require("../../../../../../locators/Widgets.json");
import { ObjectsRegistry } from "../../../../../../support/Objects/Registry";
let dataSources = ObjectsRegistry.DataSources;
const propertyPane = ObjectsRegistry.PropertyPane;
const agHelper = ObjectsRegistry.AggregateHelper;
describe("Table widget - Select column type functionality", () => {
before(() => {
cy.dragAndDropToCanvas("tablewidgetv2", { x: 150, y: 300 });
cy.dragAndDropToCanvas("tablewidgetv2", { x: 350, y: 500 });
});
it("1. should check that select column is available in the column dropdown options", () => {
@ -197,7 +199,137 @@ describe("Table widget - Select column type functionality", () => {
cy.get(".menu-item-text").contains("#1").should("not.exist");
});
it("8. should check that server side filering is working", () => {
it("8. should check that 'same select option in new row' property is working", () => {
propertyPane.NavigateBackToPropertyPane();
const checkSameOptionsInNewRowWhileEditing = () => {
propertyPane.ToggleOnOrOff("Allow adding a row", "On");
propertyPane.OpenTableColumnSettings("step");
cy.get(".t--property-control-sameoptionsinnewrow label")
.last()
.should("have.class", "checked");
// Check if newrowoption is invisible when sameoptionsinnewrow is true
cy.get(".t--property-control-newrowoptions").should("not.exist");
cy.updateCodeInput(
".t--property-control-options",
`
{{[{
"label": "male",
"value": "male"
},
{
"label": "female",
"value": "female"
}
]}}
`,
);
cy.editTableSelectCell(0, 0);
// Check if the options appear in the table
cy.get(".menu-item-text").contains("male").should("exist");
cy.get(".menu-item-text").contains("female").should("exist");
cy.get("[data-colindex=1][data-rowindex=0]").click({ force: true });
};
const checkSameOptionsWhileAddingNewRow = () => {
// Check if the same options appears while adding a new row.
cy.get(".t--add-new-row").scrollIntoView().should("be.visible");
cy.get(".t--add-new-row").click({ force: true });
cy.wait(500);
// Check if new row is visible
cy.get(".tableWrap .new-row").should("be.visible");
// Click on the first cell of the table that hasa select dropdown
cy.get(
"[data-colindex=0][data-rowindex=0] [data-testid='selectbutton.btn.main']",
).click({ force: true });
cy.get(".menu-item-text").contains("male").should("exist");
cy.get(".menu-item-text").contains("female").should("exist");
cy.get(".t--discard-new-row").click({ force: true });
};
checkSameOptionsInNewRowWhileEditing();
cy.wait(500);
checkSameOptionsWhileAddingNewRow();
});
it("9. should check that 'new row select options' is working", () => {
const checkNewRowOptions = () => {
// New row select options should be visible when "Same options in new row" is turned off
propertyPane.ToggleOnOrOff("Same options in new row", "Off");
cy.get(".t--property-control-newrowoptions").should("exist");
// New row select options should appear in table
cy.updateCodeInput(
".t--property-control-newrowoptions",
`
[{"label": "abc", "value": "abc"}]
`,
);
cy.get(".t--add-new-row").scrollIntoView().should("be.visible");
cy.get(".t--add-new-row").click({ force: true });
cy.get(".tableWrap .new-row").should("exist");
cy.get(
"[data-colindex=0][data-rowindex=0] [data-testid='selectbutton.btn.main']",
).click({ force: true });
cy.get(".menu-item-text").contains("abc").should("exist");
};
const isCurrentRowAccessDisabled = () => {
// New row select options should not have access to the currentRow
cy.updateCodeInput(
".t--property-control-newrowoptions",
"{{currentRow}}",
);
agHelper.VerifyEvaluatedErrorMessage("currentRow is not defined");
};
const checkDynamicBindingSupport = () => {
//New row selection options should support dynamic values
cy.updateCodeInput(
".t--property-control-newrowoptions",
`
{{[{"label": "abc1", "value": "abc1"}]}}
`,
);
cy.get(
"[data-colindex=0][data-rowindex=0] [data-testid='selectbutton.btn.main']",
).click({ force: true });
cy.get(".menu-item-text").contains("abc1").should("exist");
};
const checkNoOptionState = () => {
// Check that no select options are present when new row options are cleared:
cy.updateCodeInput(".t--property-control-newrowoptions", ``); // Clear the field
cy.get(
"[data-colindex=0][data-rowindex=0] [data-testid='selectbutton.btn.main']",
).click({ force: true });
cy.get(".menu-item-text").contains("No Results Found").should("exist");
cy.get(".t--discard-new-row").click({ force: true });
};
checkNewRowOptions();
isCurrentRowAccessDisabled();
checkDynamicBindingSupport();
checkNoOptionState();
});
it("10. should check that server side filering is working", () => {
dataSources.CreateDataSource("Postgres");
dataSources.CreateQueryAfterDSSaved(
"SELECT * FROM public.astronauts {{this.params.filterText ? `WHERE name LIKE '%${this.params.filterText}%'` : ''}} LIMIT 10;",

View File

@ -70,7 +70,7 @@ export const layoutConfigurations: LayoutConfigurations = {
FLUID: { minWidth: -1, maxWidth: -1 },
};
export const LATEST_PAGE_VERSION = 77;
export const LATEST_PAGE_VERSION = 78;
export const GridDefaults = {
DEFAULT_CELL_SIZE: 1,

View File

@ -745,6 +745,15 @@ const migrations: Migration[] = [
],
version: 76,
},
{
functionLookup: [
{
moduleObj: tableMigrations,
functionName: "migrateTableSelectOptionAttributesForNewRow",
},
],
version: 77,
},
];
const mockFnObj: Record<number, any> = {};

View File

@ -26,6 +26,7 @@ import {
migrateMenuButtonDynamicItemsInsideTableWidget,
migrateTableWidgetV2SelectOption,
migrateColumnFreezeAttributes,
migrateTableSelectOptionAttributesForNewRow,
} from "./migrations/TableWidget";
import {
migrateTextStyleFromTextWidget,
@ -1168,6 +1169,11 @@ export const transformDSL = (currentDSL: DSLWidget, newPage = false) => {
if (currentDSL.version === 76) {
currentDSL = migrateColumnFreezeAttributes(currentDSL);
currentDSL.version = 77;
}
if (currentDSL.version === 77) {
currentDSL = migrateTableSelectOptionAttributesForNewRow(currentDSL);
currentDSL.version = LATEST_PAGE_VERSION;
}

View File

@ -22,7 +22,10 @@ import { getSubstringBetweenTwoWords } from "utils/helpers";
import { traverseDSLAndMigrate } from "utils/WidgetMigrationUtils";
import { isDynamicValue } from "utils/DynamicBindingUtils";
import { stringToJS } from "components/editorComponents/ActionCreator/utils";
import { StickyType } from "widgets/TableWidgetV2/component/Constants";
import {
type ColumnProperties as ColumnPropertiesV2,
StickyType,
} from "widgets/TableWidgetV2/component/Constants";
export const isSortableMigration = (currentDSL: DSLWidget) => {
currentDSL.children = currentDSL.children?.map((child: WidgetProps) => {
@ -713,3 +716,25 @@ export const migrateColumnFreezeAttributes = (currentDSL: DSLWidget) => {
}
});
};
export const migrateTableSelectOptionAttributesForNewRow = (
currentDSL: DSLWidget,
) => {
return traverseDSLAndMigrate(currentDSL, (widget: WidgetProps) => {
if (widget.type === "TABLE_WIDGET_V2") {
const primaryColumns = widget?.primaryColumns as ColumnPropertiesV2;
// Set default value for allowSameOptionsInNewRow
if (primaryColumns) {
Object.values(primaryColumns).forEach((column) => {
if (
column.hasOwnProperty("columnType") &&
column.columnType === "select"
) {
column.allowSameOptionsInNewRow = true;
}
});
}
}
});
};

View File

@ -330,6 +330,8 @@ export interface ColumnProperties
DateColumnProperties,
ColumnEditabilityProperties,
EditActionColumnProperties {
allowSameOptionsInNewRow?: boolean;
newRowSelectOptions?: DropdownOption[];
buttonLabel?: string;
menuButtonLabel?: string;
buttonColor?: string;

View File

@ -71,6 +71,7 @@ export const CONFIG = {
id: "step",
originalId: "step",
alias: "step",
allowSameOptionsInNewRow: true,
horizontalAlignment: "LEFT",
verticalAlignment: "CENTER",
columnType: "text",
@ -92,6 +93,7 @@ export const CONFIG = {
id: "task",
originalId: "task",
alias: "task",
allowSameOptionsInNewRow: true,
horizontalAlignment: "LEFT",
verticalAlignment: "CENTER",
columnType: "text",
@ -113,6 +115,7 @@ export const CONFIG = {
id: "status",
originalId: "status",
alias: "status",
allowSameOptionsInNewRow: true,
horizontalAlignment: "LEFT",
verticalAlignment: "CENTER",
columnType: "text",
@ -134,6 +137,7 @@ export const CONFIG = {
id: "action",
originalId: "action",
alias: "action",
allowSameOptionsInNewRow: true,
horizontalAlignment: "LEFT",
verticalAlignment: "CENTER",
columnType: "button",

View File

@ -1434,11 +1434,13 @@ class TableWidgetV2 extends BaseWidget<TableWidgetProps, WidgetState> {
originalIndex = row[ORIGINAL_INDEX_KEY] ?? rowIndex;
}
const isNewRow = this.props.isAddRowInProgress && rowIndex === 0;
/*
* cellProperties order or size does not change when filter/sorting/grouping is applied
* on the data thus original index is needed to identify the column's cell property.
*/
const cellProperties = getCellProperties(column, originalIndex);
const cellProperties = getCellProperties(column, originalIndex, isNewRow);
let isSelected = false;
if (this.props.transientTableData) {
@ -1460,8 +1462,6 @@ class TableWidgetV2 extends BaseWidget<TableWidgetProps, WidgetState> {
column.isEditable && isColumnTypeEditable(column.columnType);
const alias = props.cell.column.columnProperties.alias;
const isNewRow = this.props.isAddRowInProgress && rowIndex === 0;
const isCellEditable = isColumnEditable && cellProperties.isCellEditable;
const isCellEditMode =

View File

@ -1,7 +1,9 @@
import { ValidationTypes } from "constants/WidgetValidation";
import { get } from "lodash";
import type { TableWidgetProps } from "widgets/TableWidgetV2/constants";
import { ColumnTypes } from "widgets/TableWidgetV2/constants";
import {
getBasePropertyPath,
hideByColumnType,
selectColumnOptionsValidation,
} from "../../propertyUtils";
@ -35,6 +37,64 @@ export default {
return hideByColumnType(props, propertyPath, [ColumnTypes.SELECT]);
},
},
{
propertyName: "allowSameOptionsInNewRow",
defaultValue: true,
helpText:
"Toggle to display same choices for new row and editing existing row in column",
label: "Same options in new row",
controlType: "SWITCH",
isBindProperty: true,
isJSConvertible: true,
isTriggerProperty: false,
hidden: (props: TableWidgetProps) => {
return !props.allowAddNewRow;
},
dependencies: ["primaryColumns", "allowAddNewRow"],
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "newRowSelectOptions",
helpText:
"Options exclusively displayed in the column for new row addition",
label: "New row options",
controlType: "INPUT_TEXT",
isJSConvertible: false,
isBindProperty: true,
validation: {
type: ValidationTypes.FUNCTION,
params: {
expected: {
type: 'Array<{ "label": string | number, "value": string | number}>',
example: '[{"label": "abc", "value": "abc"}]',
},
fnString: selectColumnOptionsValidation.toString(),
},
},
isTriggerProperty: false,
dependencies: ["primaryColumns", "allowAddNewRow"],
hidden: (props: TableWidgetProps, propertyPath: string) => {
const baseProperty = getBasePropertyPath(propertyPath);
if (baseProperty) {
const columnType = get(props, `${baseProperty}.columnType`, "");
const allowSameOptionsInNewRow = get(
props,
`${baseProperty}.allowSameOptionsInNewRow`,
);
if (
columnType === ColumnTypes.SELECT &&
props.allowAddNewRow &&
!allowSameOptionsInNewRow
) {
return false;
} else {
return true;
}
}
},
},
{
propertyName: "placeholderText",
helpText: "Sets a Placeholder Text",

View File

@ -10,6 +10,7 @@ import {
getDerivedColumns,
getHeaderClassNameOnDragDirection,
getOriginalRowIndex,
getSelectOptions,
getSelectRowIndex,
getSelectRowIndices,
getSourceDataAndCaluclateKeysForEventAutoComplete,
@ -2553,3 +2554,120 @@ describe("getHeaderClassNameOnDragDirection", () => {
);
});
});
describe("getSelectOptions", () => {
it("Should return select options when user is not adding a new row", () => {
const columnProperties = {
allowSameOptionsInNewRow: true,
selectOptions: [
{
label: "male",
value: "male",
},
{
label: "female",
value: "female",
},
],
};
expect(
getSelectOptions(false, 0, columnProperties as ColumnProperties),
).toEqual([
{
label: "male",
value: "male",
},
{
label: "female",
value: "female",
},
]);
// Check when select options are inside dynamic binding
const columnPropertiesDynamicSelectOptions = {
allowSameOptionsInNewRow: true,
selectOptions: [
[
{
label: "abc",
value: "abc",
},
],
[
{
label: "efg",
value: "efg",
},
],
[
{
label: "xyz",
value: "xyz",
},
],
],
};
expect(
getSelectOptions(
false,
0,
columnPropertiesDynamicSelectOptions as ColumnProperties,
),
).toEqual([
{
label: "abc",
value: "abc",
},
]);
});
it("Should return select options while adding a new row and when 'Same options in new row' option is turned on", () => {
const columnProperties = {
allowSameOptionsInNewRow: true,
selectOptions: [
{
label: "male",
value: "male",
},
{
label: "female",
value: "female",
},
],
};
expect(
getSelectOptions(true, -1, columnProperties as ColumnProperties),
).toEqual([
{
label: "male",
value: "male",
},
{
label: "female",
value: "female",
},
]);
});
it("Should return new row options", () => {
const columnProperties = {
allowSameOptionsInNewRow: false,
newRowSelectOptions: [
{
label: "abc",
value: "abc",
},
],
};
expect(
getSelectOptions(true, -1, columnProperties as ColumnProperties),
).toEqual([
{
label: "abc",
value: "abc",
},
]);
});
});

View File

@ -188,6 +188,7 @@ export function getDefaultColumnProperties(
): ColumnProperties {
const columnProps = {
allowCellWrapping: false,
allowSameOptionsInNewRow: true,
index: index,
width: DEFAULT_COLUMN_WIDTH,
originalId: id,
@ -293,6 +294,7 @@ export const getArrayPropertyValue = (value: unknown, index: number) => {
export const getCellProperties = (
columnProperties: ColumnProperties,
rowIndex: number,
isAddRowInProgress = false,
) => {
if (columnProperties) {
return {
@ -493,9 +495,10 @@ export const getCellProperties = (
true,
),
shortcuts: getBooleanPropertyValue(columnProperties.shortcuts, rowIndex),
selectOptions: getArrayPropertyValue(
columnProperties.selectOptions,
selectOptions: getSelectOptions(
isAddRowInProgress,
rowIndex,
columnProperties,
),
timePrecision: getPropertyValue(
columnProperties.timePrecision,
@ -1102,3 +1105,23 @@ export const getDragHandlers = (
onDrop,
};
};
export const getSelectOptions = (
isNewRow: boolean,
rowIndex: number,
columnProperties: ColumnProperties,
) => {
if (isNewRow) {
if (
columnProperties.allowSameOptionsInNewRow &&
columnProperties?.selectOptions
) {
// Use select options from the first row
return getArrayPropertyValue(columnProperties.selectOptions, 0);
} else {
return columnProperties.newRowSelectOptions;
}
} else {
return getArrayPropertyValue(columnProperties.selectOptions, rowIndex);
}
};