2022-07-14 07:02:35 +00:00
import log from "loglevel" ;
2023-03-30 04:54:29 +00:00
import memoizeOne from "memoize-one" ;
2025-02-10 09:20:25 +00:00
import React , { lazy , Suspense } from "react" ;
2023-03-30 04:54:29 +00:00
2022-07-14 07:02:35 +00:00
import _ , {
2023-10-03 08:10:51 +00:00
filter ,
2022-07-14 07:02:35 +00:00
isArray ,
isEmpty ,
2023-10-03 08:10:51 +00:00
isNil ,
isNumber ,
2022-11-18 10:54:35 +00:00
isObject ,
2023-10-03 08:10:51 +00:00
isString ,
2023-06-01 17:26:05 +00:00
merge ,
2023-10-03 08:10:51 +00:00
orderBy ,
pickBy ,
union ,
without ,
xor ,
xorWith ,
2022-07-14 07:02:35 +00:00
} from "lodash" ;
2025-02-10 09:20:25 +00:00
import type { IconName } from "@blueprintjs/icons" ;
import { IconNames } from "@blueprintjs/icons" ;
import type { BatchPropertyUpdatePayload } from "actions/controlActions" ;
import Skeleton from "components/utils/Skeleton" ;
import { EventType } from "constants/AppsmithActionConstants/ActionConstants" ;
import { Colors } from "constants/Colors" ;
import { FILL_WIDGET_MIN_WIDTH } from "constants/minWidthConstants" ;
2023-10-03 08:10:51 +00:00
import {
RenderModes ,
WIDGET_PADDING ,
WIDGET_TAGS ,
} from "constants/WidgetConstants" ;
2025-02-10 09:20:25 +00:00
import type { SetterConfig , Stylesheet } from "entities/AppTheming" ;
import equal from "fast-deep-equal/es6" ;
import {
FlexVerticalAlignment ,
ResponsiveBehavior ,
} from "layoutSystems/common/utils/constants" ;
2022-07-14 07:02:35 +00:00
import { noop , retryPromise } from "utils/AppsmithUtils" ;
2025-02-10 09:20:25 +00:00
import type { ExtraDef } from "utils/autocomplete/defCreatorUtils" ;
import { generateTypeDef } from "utils/autocomplete/defCreatorUtils" ;
import type { DynamicPath } from "utils/DynamicBindingUtils" ;
import { klonaRegularWithTelemetry } from "utils/helpers" ;
import localStorage from "utils/localStorage" ;
import type {
AnvilConfig ,
AutocompletionDefinitions ,
PropertyUpdates ,
SnipingModeProperty ,
} from "WidgetProvider/constants" ;
import type {
WidgetQueryConfig ,
WidgetQueryGenerationFormConfig ,
} from "WidgetQueryGenerators/types" ;
import type { WidgetProps , WidgetState } from "widgets/BaseWidget" ;
import BaseWidget from "widgets/BaseWidget" ;
import { TimePrecision } from "widgets/DatePickerWidget2/constants" ;
import type { MenuItem } from "widgets/MenuButtonWidget/constants" ;
import { MenuItemsSource } from "widgets/MenuButtonWidget/constants" ;
import {
DefaultAutocompleteDefinitions ,
sanitizeKey ,
} from "widgets/WidgetUtils" ;
import { ButtonCell } from "../component/cellComponents/ButtonCell" ;
import { CheckboxCell } from "../component/cellComponents/CheckboxCell" ;
import { DateCell } from "../component/cellComponents/DateCell" ;
import { EditActionCell } from "../component/cellComponents/EditActionsCell" ;
import HTMLCell from "../component/cellComponents/HTMLCell" ;
import { IconButtonCell } from "../component/cellComponents/IconButtonCell" ;
import { ImageCell } from "../component/cellComponents/ImageCell" ;
import { MenuButtonCell } from "../component/cellComponents/MenuButtonCell" ;
import PlainTextCell from "../component/cellComponents/PlainTextCell" ;
import { SelectCell } from "../component/cellComponents/SelectCell" ;
import { SwitchCell } from "../component/cellComponents/SwitchCell" ;
import { VideoCell } from "../component/cellComponents/VideoCell" ;
2023-10-03 08:10:51 +00:00
import type {
ColumnProperties ,
ReactTableColumnProps ,
ReactTableFilter ,
} from "../component/Constants" ;
import {
AddNewRowActions ,
CompactModeTypes ,
DEFAULT_FILTER ,
SORT_ORDER ,
SortOrderTypes ,
StickyType ,
} from "../component/Constants" ;
2025-02-10 09:20:25 +00:00
import { CellWrapper } from "../component/TableStyledWrappers" ;
chore: upgrade to prettier v2 + enforce import types (#21013)Co-authored-by: Satish Gandham <hello@satishgandham.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
## Description
This PR upgrades Prettier to v2 + enforces TypeScript’s [`import
type`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export)
syntax where applicable. It’s submitted as a separate PR so we can merge
it easily.
As a part of this PR, we reformat the codebase heavily:
- add `import type` everywhere where it’s required, and
- re-format the code to account for Prettier 2’s breaking changes:
https://prettier.io/blog/2020/03/21/2.0.0.html#breaking-changes
This PR is submitted against `release` to make sure all new code by team
members will adhere to new formatting standards, and we’ll have fewer
conflicts when merging `bundle-optimizations` into `release`. (I’ll
merge `release` back into `bundle-optimizations` once this PR is
merged.)
### Why is this needed?
This PR is needed because, for the Lodash optimization from
https://github.com/appsmithorg/appsmith/commit/7cbb12af886621256224be0c93e6a465dd710ad3,
we need to use `import type`. Otherwise, `babel-plugin-lodash` complains
that `LoDashStatic` is not a lodash function.
However, just using `import type` in the current codebase will give you
this:
<img width="962" alt="Screenshot 2023-03-08 at 17 45 59"
src="https://user-images.githubusercontent.com/2953267/223775744-407afa0c-e8b9-44a1-90f9-b879348da57f.png">
That’s because Prettier 1 can’t parse `import type` at all. To parse it,
we need to upgrade to Prettier 2.
### Why enforce `import type`?
Apart from just enabling `import type` support, this PR enforces
specifying `import type` everywhere it’s needed. (Developers will get
immediate TypeScript and ESLint errors when they forget to do so.)
I’m doing this because I believe `import type` improves DX and makes
refactorings easier.
Let’s say you had a few imports like below. Can you tell which of these
imports will increase the bundle size? (Tip: it’s not all of them!)
```ts
// app/client/src/workers/Linting/utils.ts
import { Position } from "codemirror";
import { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
It’s pretty hard, right?
What about now?
```ts
// app/client/src/workers/Linting/utils.ts
import type { Position } from "codemirror";
import type { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
Now, it’s clear that only `lodash` will be bundled.
This helps developers to see which imports are problematic, but it
_also_ helps with refactorings. Now, if you want to see where
`codemirror` is bundled, you can just grep for `import \{.*\} from
"codemirror"` – and you won’t get any type-only imports.
This also helps (some) bundlers. Upon transpiling, TypeScript erases
type-only imports completely. In some environment (not ours), this makes
the bundle smaller, as the bundler doesn’t need to bundle type-only
imports anymore.
## Type of change
- Chore (housekeeping or task changes that don't impact user perception)
## How Has This Been Tested?
This was tested to not break the build.
### 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
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] 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
---------
Co-authored-by: Satish Gandham <hello@satishgandham.com>
Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
2023-03-16 11:41:47 +00:00
import type {
EditableCell ,
OnColumnEventArgs ,
TableWidgetProps ,
TransientDataPayload ,
} from "../constants" ;
2022-11-05 09:54:20 +00:00
import {
ActionColumnTypes ,
2023-10-12 08:24:03 +00:00
ALLOW_TABLE_WIDGET_SERVER_SIDE_FILTERING ,
2022-07-14 07:02:35 +00:00
ColumnTypes ,
DEFAULT_BUTTON_LABEL ,
DEFAULT_COLUMN_WIDTH ,
DEFAULT_MENU_BUTTON_LABEL ,
DEFAULT_MENU_VARIANT ,
2023-10-03 08:10:51 +00:00
defaultEditableCell ,
2022-07-14 07:02:35 +00:00
EditableCellActions ,
InlineEditingSaveOptions ,
ORIGINAL_INDEX_KEY ,
2023-04-13 10:08:46 +00:00
PaginationDirection ,
2023-10-03 08:10:51 +00:00
TABLE_COLUMN_ORDER_KEY ,
2022-07-14 07:02:35 +00:00
} from "../constants" ;
2025-02-10 09:20:25 +00:00
import IconSVG from "../icon.svg" ;
import ThumbnailSVG from "../thumbnail.svg" ;
2022-07-14 07:02:35 +00:00
import derivedProperties from "./parseDerivedProperties" ;
2025-02-10 09:20:25 +00:00
import contentConfig from "./propertyConfig/contentConfig" ;
import styleConfig from "./propertyConfig/styleConfig" ;
import type { getColumns } from "./reactTableUtils/getColumnsPureFn" ;
import { getMemoiseGetColumnsWithLocalStorageFn } from "./reactTableUtils/getColumnsPureFn" ;
import type {
tableData ,
transformDataWithEditableCell ,
} from "./reactTableUtils/transformDataPureFn" ;
import { getMemoiseTransformDataWithEditableCell } from "./reactTableUtils/transformDataPureFn" ;
2022-07-14 07:02:35 +00:00
import {
2023-10-03 08:10:51 +00:00
createEditActionColumn ,
deleteLocalTableColumnOrderByWidgetId ,
generateLocalNewColumnOrderFromStickyValue ,
generateNewColumnOrderFromStickyValue ,
getAllStickyColumnsCount ,
2022-07-14 07:02:35 +00:00
getAllTableColumnKeys ,
2023-10-03 08:10:51 +00:00
getBooleanPropertyValue ,
getCellProperties ,
getColumnOrderByWidgetIdFromLS ,
getColumnType ,
2022-07-14 07:02:35 +00:00
getDefaultColumnProperties ,
getDerivedColumns ,
getSelectRowIndex ,
getSelectRowIndices ,
2023-10-03 08:10:51 +00:00
getTableStyles ,
2022-07-14 07:02:35 +00:00
isColumnTypeEditable ,
2023-02-15 11:42:46 +00:00
updateAndSyncTableLocalColumnOrders ,
2022-07-14 07:02:35 +00:00
} from "./utilities" ;
2025-04-30 09:34:03 +00:00
import resetWidget from "workers/Evaluation/fns/resetWidget" ;
2022-07-14 07:02:35 +00:00
2023-10-09 13:54:06 +00:00
const ReactTableComponent = lazy ( async ( ) = >
retryPromise ( async ( ) = > import ( "../component" ) ) ,
2022-07-14 07:02:35 +00:00
) ;
2024-07-31 15:41:28 +00:00
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2023-03-30 04:54:29 +00:00
const emptyArr : any = [ ] ;
type addNewRowToTable = (
tableData : tableData ,
isAddRowInProgress : boolean ,
newRowContent : Record < string , unknown > ,
) = > tableData ;
const getMemoisedAddNewRow = ( ) : addNewRowToTable = >
memoizeOne ( ( tableData , isAddRowInProgress , newRowContent ) = > {
if ( isAddRowInProgress ) {
return [ newRowContent , . . . tableData ] ;
}
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
return tableData ;
} ) ;
2022-07-14 07:02:35 +00:00
class TableWidgetV2 extends BaseWidget < TableWidgetProps , WidgetState > {
2022-09-13 05:41:59 +00:00
inlineEditTimer : number | null = null ;
2023-03-30 04:54:29 +00:00
memoisedAddNewRow : addNewRowToTable ;
2024-07-31 15:41:28 +00:00
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2023-03-30 04:54:29 +00:00
memoiseGetColumnsWithLocalStorage : ( localStorage : any ) = > getColumns ;
memoiseTransformDataWithEditableCell : transformDataWithEditableCell ;
2022-07-14 07:02:35 +00:00
2023-09-06 12:15:04 +00:00
static type = "TABLE_WIDGET_V2" ;
static getConfig() {
2023-06-01 17:26:05 +00:00
return {
2023-09-06 12:15:04 +00:00
name : "Table" ,
iconSVG : IconSVG ,
2024-04-09 01:16:46 +00:00
thumbnailSVG : ThumbnailSVG ,
2023-09-06 12:15:04 +00:00
tags : [ WIDGET_TAGS . SUGGESTED_WIDGETS , WIDGET_TAGS . DISPLAY ] ,
needsMeta : true ,
needsHeightForContent : true ,
2023-06-01 17:26:05 +00:00
} ;
}
2023-09-06 12:15:04 +00:00
static getDefaults() {
return {
2023-10-05 12:57:39 +00:00
flexVerticalAlignment : FlexVerticalAlignment.Top ,
2023-09-06 12:15:04 +00:00
responsiveBehavior : ResponsiveBehavior.Fill ,
minWidth : FILL_WIDGET_MIN_WIDTH ,
rows : 28 ,
canFreezeColumn : true ,
columnUpdatedAt : Date.now ( ) ,
columns : 34 ,
animateLoading : true ,
defaultSelectedRowIndex : 0 ,
defaultSelectedRowIndices : [ 0 ] ,
label : "Data" ,
widgetName : "Table" ,
searchKey : "" ,
textSize : "0.875rem" ,
horizontalAlignment : "LEFT" ,
verticalAlignment : "CENTER" ,
totalRecordsCount : 0 ,
defaultPageSize : 0 ,
dynamicPropertyPathList : [ ] ,
borderColor : Colors.GREY_5 ,
borderWidth : "1" ,
dynamicBindingPathList : [ ] ,
primaryColumns : { } ,
tableData : "" ,
columnWidthMap : { } ,
columnOrder : [ ] ,
enableClientSideSearch : true ,
isVisibleSearch : true ,
2024-07-05 07:46:11 +00:00
isVisibleFilters : false ,
2023-09-06 12:15:04 +00:00
isVisibleDownload : true ,
isVisiblePagination : true ,
isSortable : true ,
delimiter : "," ,
version : 2 ,
inlineEditingSaveOption : InlineEditingSaveOptions.ROW_LEVEL ,
2023-10-12 08:24:03 +00:00
enableServerSideFiltering : TableWidgetV2.getFeatureFlag (
ALLOW_TABLE_WIDGET_SERVER_SIDE_FILTERING ,
)
? false
: undefined ,
feat: Update TableWidgetV2 to include customIsLoading property (#36857)
## Description
<ins>Problem</ins>
There are many problems with table loader logic, for which many users
try to implement a modal for loader. These problems stem from dependency
and delay on eval, discussed comprehensively in #12308
<ins>Solution</ins>
This PR updates the TableWidgetV2 component to include a new property
called `customIsLoading`. This property controls the loading state of
the widget and is added to the TableWidgetProps interface. Additionally,
the component's state is updated to include the `customIsLoading`
property.
The `contentConfig` file for the TableWidgetV2 is also modified to
include the `customIsLoading` property with its corresponding label,
control type, help text, and validation.
These changes improve the flexibility and customization options of the
TableWidgetV2 component.
Fixes #12308
_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"
### :mag: 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/11456273525>
> Commit: 1c6f4f9caabc3aa45ec3916e5ccb465d946ab0a1
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=11456273525&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Table`
> Spec:
> <hr>Tue, 22 Oct 2024 09:17:37 UTC
<!-- end of auto-generated comment: Cypress test results -->
## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Introduced a new feature flag for custom loading states in the table
widget.
- Added properties for managing custom loading behavior in the
`TableWidgetV2`.
- **Bug Fixes**
- Enhanced loading state management to ensure accurate representation
based on new properties.
- **Tests**
- Added unit tests for loading behavior in the `TableWidgetV2`
component, covering default and custom loading scenarios.
- **Documentation**
- Updated help text for properties related to loading states to improve
clarity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-10-22 09:21:45 +00:00
customIsLoading : false ,
customIsLoadingValue : "" ,
2025-04-02 08:32:54 +00:00
cachedTableData : { } ,
endOfData : false ,
2023-09-06 12:15:04 +00:00
} ;
}
2023-06-01 17:26:05 +00:00
2023-09-06 12:15:04 +00:00
static getMethods() {
return {
getQueryGenerationConfig : ( widget : WidgetProps ) = > {
return {
select : {
limit : ` ${ widget . widgetName } .pageSize ` ,
where : ` ${ widget . widgetName } .searchText ` ,
offset : ` ${ widget . widgetName } .pageOffset ` ,
orderBy : ` ${ widget . widgetName } .sortOrder.column ` ,
sortOrder : ` ${ widget . widgetName } .sortOrder.order !== "desc" ` ,
2023-06-01 17:26:05 +00:00
} ,
2023-09-06 12:15:04 +00:00
create : {
value : ` ( ${ widget . widgetName } .newRow || {}) ` ,
} ,
update : {
value : ` ${ widget . widgetName } .updatedRow ` ,
where : ` ${ widget . widgetName } .updatedRow ` ,
} ,
totalRecord : true ,
} ;
} ,
getPropertyUpdatesForQueryBinding : (
queryConfig : WidgetQueryConfig ,
_widget : WidgetProps ,
formConfig : WidgetQueryGenerationFormConfig ,
) = > {
const widget = _widget as TableWidgetProps ;
let modify = { } ;
const dynamicPropertyPathList : DynamicPath [ ] = [ ] ;
if ( queryConfig . select ) {
modify = merge ( modify , {
tableData : queryConfig.select.data ,
onPageChange : queryConfig.select.run ,
serverSidePaginationEnabled : true ,
onSearchTextChanged : formConfig.searchableColumn
? queryConfig . select . run
: undefined ,
onSort : queryConfig.select.run ,
enableClientSideSearch : ! formConfig . searchableColumn ,
primaryColumnId : formConfig.primaryColumn ,
isVisibleDownload : false ,
} ) ;
2024-02-26 08:33:36 +00:00
chore: removed old flags for airgap instances (#36609)
## Description
Removed all the occurrences of listed flags in the codebase:
1. ab_ds_binding_enabled
2. ab_ds_schema_enabled
3. ab_gsheet_schema_enabled
4. ab_learnability_discoverability_collapse_all_except_data_enabled
5. ab_learnability_ease_of_initial_use_enabled
6. ab_mock_mongo_schema_enabled
7. ab_start_with_data_default_enabled
8. rollout_js_enabled_one_click_binding_enabled
Fixes #36256
_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.All"
### :mag: 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/11177173738>
> Commit: bfbf6bbe77b963c5d257c29cf5bac35139417a07
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=11177173738&attempt=2"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Fri, 04 Oct 2024 10:31:10 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
- **New Features**
- Enhanced test coverage for the Community Issues page, focusing on
pagination, search, filtering, and issue management.
- Improved functionality for adding new rows to table widgets, including
visibility controls and state validations.
- **Bug Fixes**
- Resolved issues related to the visibility of UI elements when adding
new rows and ensured accurate data reflection in the table.
- **Tests**
- Expanded tests for pagination, row selection, search functionality,
and filtering logic in table widgets.
- Added comprehensive assertions for client-side search and filtering
scenarios, including checks for modal visibility during issue
management.
- **Chores**
- Removed obsolete feature flags and streamlined logic for managing
feature flags across components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-10-07 09:56:25 +00:00
dynamicPropertyPathList . push ( { key : "tableData" } ) ;
2023-09-06 12:15:04 +00:00
}
2023-06-01 17:26:05 +00:00
2023-09-06 12:15:04 +00:00
if ( queryConfig . create ) {
modify = merge ( modify , {
onAddNewRowSave : queryConfig.create.run ,
allowAddNewRow : true ,
. . . Object . keys ( widget . primaryColumns ) . reduce (
( prev : Record < string , boolean > , curr ) = > {
if ( formConfig . primaryColumn !== curr ) {
prev [ ` primaryColumns. ${ curr } .isEditable ` ] = true ;
prev [ ` primaryColumns. ${ curr } .isCellEditable ` ] = true ;
}
prev [ ` showInlineEditingOptionDropdown ` ] = true ;
return prev ;
} ,
{ } ,
) ,
} ) ;
}
2023-06-01 17:26:05 +00:00
2023-09-06 12:15:04 +00:00
if ( queryConfig . update ) {
let editAction = { } ;
2023-06-01 17:26:05 +00:00
2023-09-06 12:15:04 +00:00
if (
! Object . values ( widget . primaryColumns ) . some (
( column ) = > column . columnType === ColumnTypes . EDIT_ACTIONS ,
)
) {
editAction = Object . values ( createEditActionColumn ( widget ) ) . reduce (
(
prev : Record < string , unknown > ,
curr : {
propertyPath : string ;
propertyValue : unknown ;
isDynamicPropertyPath? : boolean ;
} ,
) = > {
prev [ curr . propertyPath ] = curr . propertyValue ;
if ( curr . isDynamicPropertyPath ) {
dynamicPropertyPathList . push ( { key : curr.propertyPath } ) ;
}
return prev ;
} ,
{ } ,
) ;
}
2023-07-20 06:22:20 +00:00
2023-09-06 12:15:04 +00:00
modify = merge ( modify , {
. . . editAction ,
[ ` primaryColumns.EditActions1.onSave ` ] : queryConfig . update . run ,
} ) ;
}
2023-06-01 17:26:05 +00:00
2023-09-06 12:15:04 +00:00
if ( queryConfig . total_record ) {
modify = merge ( modify , {
totalRecordsCount : queryConfig.total_record.data ,
} ) ;
}
2023-06-01 17:26:05 +00:00
2023-09-06 12:15:04 +00:00
return {
modify ,
dynamicUpdates : {
dynamicPropertyPathList ,
} ,
} ;
} ,
getSnipingModeUpdates : (
propValueMap : SnipingModeProperty ,
) : PropertyUpdates [ ] = > {
return [
{
propertyPath : "tableData" ,
propertyValue : propValueMap.data ,
2023-09-08 07:42:48 +00:00
isDynamicPropertyPath : ! ! propValueMap . isDynamicPropertyPath ,
2023-09-06 12:15:04 +00:00
} ,
] ;
} ,
2023-10-03 08:10:51 +00:00
getOneClickBindingConnectableWidgetConfig : ( widget : WidgetProps ) = > {
return {
widgetBindPath : ` ${ widget . widgetName } .selectedRow ` ,
message : ` Make sure ${ widget . widgetName } is bound to the same data source ` ,
} ;
} ,
2023-09-06 12:15:04 +00:00
} ;
}
2023-06-01 17:26:05 +00:00
2023-09-06 12:15:04 +00:00
static getAutoLayoutConfig() {
2023-07-20 06:22:20 +00:00
return {
2023-09-06 12:15:04 +00:00
widgetSize : [
{
viewportMinWidth : 0 ,
configuration : ( ) = > {
return {
minWidth : "280px" ,
minHeight : "300px" ,
} ;
} ,
} ,
] ,
2023-07-20 06:22:20 +00:00
} ;
2023-06-01 17:26:05 +00:00
}
2023-10-19 20:27:40 +00:00
static getAnvilConfig ( ) : AnvilConfig | null {
return {
2023-11-14 05:25:48 +00:00
isLargeWidget : false ,
2023-10-19 20:27:40 +00:00
widgetSize : {
maxHeight : { } ,
maxWidth : { } ,
minHeight : { base : "300px" } ,
minWidth : { base : "280px" } ,
} ,
} ;
}
2022-08-17 18:23:53 +00:00
static getPropertyPaneContentConfig() {
return contentConfig ;
}
static getPropertyPaneStyleConfig() {
return styleConfig ;
}
2023-06-01 17:26:05 +00:00
2023-03-30 04:54:29 +00:00
constructor ( props : TableWidgetProps ) {
super ( props ) ;
// generate new cache instances so that each table widget instance has its own respective cache instance
this . memoisedAddNewRow = getMemoisedAddNewRow ( ) ;
this . memoiseGetColumnsWithLocalStorage =
getMemoiseGetColumnsWithLocalStorageFn ( ) ;
this . memoiseTransformDataWithEditableCell =
getMemoiseTransformDataWithEditableCell ( ) ;
}
2022-08-17 18:23:53 +00:00
2024-07-31 15:41:28 +00:00
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2022-07-14 07:02:35 +00:00
static getMetaPropertiesMap ( ) : Record < string , any > {
return {
pageNo : 1 ,
selectedRowIndex : undefined ,
selectedRowIndices : undefined ,
searchText : undefined ,
triggeredRowIndex : undefined ,
filters : [ ] ,
sortOrder : {
column : "" ,
order : null ,
} ,
transientTableData : { } ,
2022-11-25 04:39:59 +00:00
updatedRowIndex : - 1 ,
2022-09-13 05:41:59 +00:00
editableCell : defaultEditableCell ,
columnEditableCellValue : { } ,
2022-09-30 04:03:53 +00:00
selectColumnFilterText : { } ,
2022-11-05 09:54:20 +00:00
isAddRowInProgress : false ,
newRowContent : undefined ,
newRow : undefined ,
2023-04-13 10:08:46 +00:00
previousPageVisited : false ,
nextPageVisited : false ,
2022-07-14 07:02:35 +00:00
} ;
}
2023-04-14 06:27:49 +00:00
static getAutocompleteDefinitions ( ) : AutocompletionDefinitions {
return ( widget : TableWidgetProps , extraDefsToDefine? : ExtraDef ) = > {
2023-08-22 11:27:02 +00:00
const config : AutocompletionDefinitions = {
2023-04-14 06:27:49 +00:00
"!doc" :
"The Table is the hero widget of Appsmith. You can display data from an API in a table, trigger an action when a user selects a row and even work with large paginated data sets" ,
"!url" : "https://docs.appsmith.com/widget-reference/table" ,
selectedRow : generateTypeDef ( widget . selectedRow , extraDefsToDefine ) ,
selectedRows : generateTypeDef ( widget . selectedRows , extraDefsToDefine ) ,
selectedRowIndices : generateTypeDef ( widget . selectedRowIndices ) ,
triggeredRow : generateTypeDef ( widget . triggeredRow ) ,
updatedRow : generateTypeDef ( widget . updatedRow ) ,
selectedRowIndex : "number" ,
tableData : generateTypeDef ( widget . tableData , extraDefsToDefine ) ,
pageNo : "number" ,
pageSize : "number" ,
isVisible : DefaultAutocompleteDefinitions.isVisible ,
searchText : "string" ,
totalRecordsCount : "number" ,
sortOrder : {
column : "string" ,
order : [ "asc" , "desc" ] ,
} ,
updatedRows : generateTypeDef ( widget . updatedRows , extraDefsToDefine ) ,
updatedRowIndices : generateTypeDef ( widget . updatedRowIndices ) ,
triggeredRowIndex : generateTypeDef ( widget . triggeredRowIndex ) ,
pageOffset : generateTypeDef ( widget . pageOffset ) ,
tableHeaders : generateTypeDef ( widget . tableHeaders ) ,
newRow : generateTypeDef ( widget . newRow ) ,
isAddRowInProgress : "bool" ,
previousPageVisited : generateTypeDef ( widget . previousPageVisited ) ,
nextPageVisited : generateTypeDef ( widget . nextPageButtonClicked ) ,
} ;
2023-07-08 14:07:26 +00:00
2023-10-12 08:24:03 +00:00
if ( this . getFeatureFlag ( ALLOW_TABLE_WIDGET_SERVER_SIDE_FILTERING ) ) {
config [ "filters" ] = generateTypeDef ( widget . filters ) ;
}
2023-04-14 06:27:49 +00:00
return config ;
} ;
}
2022-07-14 07:02:35 +00:00
static getDerivedPropertiesMap() {
return {
selectedRow : ` {{(()=>{ ${ derivedProperties . getSelectedRow } })()}} ` ,
triggeredRow : ` {{(()=>{ ${ derivedProperties . getTriggeredRow } })()}} ` ,
selectedRows : ` {{(()=>{ ${ derivedProperties . getSelectedRows } })()}} ` ,
pageSize : ` {{(()=>{ ${ derivedProperties . getPageSize } })()}} ` ,
triggerRowSelection : "{{!!this.onRowSelected}}" ,
processedTableData : ` {{(()=>{ ${ derivedProperties . getProcessedTableData } })()}} ` ,
orderedTableColumns : ` {{(()=>{ ${ derivedProperties . getOrderedTableColumns } })()}} ` ,
filteredTableData : ` {{(()=>{ ${ derivedProperties . getFilteredTableData } })()}} ` ,
updatedRows : ` {{(()=>{ ${ derivedProperties . getUpdatedRows } })()}} ` ,
updatedRowIndices : ` {{(()=>{ ${ derivedProperties . getUpdatedRowIndices } })()}} ` ,
2022-11-25 04:39:59 +00:00
updatedRow : ` {{(()=>{ ${ derivedProperties . getUpdatedRow } })()}} ` ,
2022-09-01 10:01:02 +00:00
pageOffset : ` {{(()=>{ ${ derivedProperties . getPageOffset } })()}} ` ,
2022-11-05 09:54:20 +00:00
isEditableCellsValid : ` {{(()=>{ ${ derivedProperties . getEditableCellValidity } })()}} ` ,
2022-11-25 06:12:23 +00:00
tableHeaders : ` {{(()=>{ ${ derivedProperties . getTableHeaders } })()}} ` ,
2022-07-14 07:02:35 +00:00
} ;
}
static getDefaultPropertiesMap ( ) : Record < string , string > {
return {
searchText : "defaultSearchText" ,
selectedRowIndex : "defaultSelectedRowIndex" ,
selectedRowIndices : "defaultSelectedRowIndices" ,
} ;
}
2022-09-13 05:40:08 +00:00
static getLoadingProperties ( ) : Array < RegExp > | undefined {
return [ /\.tableData$/ ] ;
}
2022-11-28 04:44:31 +00:00
static getStylesheetConfig ( ) : Stylesheet {
return {
accentColor : "{{appsmith.theme.colors.primaryColor}}" ,
borderRadius : "{{appsmith.theme.borderRadius.appBorderRadius}}" ,
boxShadow : "{{appsmith.theme.boxShadow.appBoxShadow}}" ,
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}}" ,
} ,
} ,
} ;
}
2023-07-08 14:07:26 +00:00
static getSetterConfig ( ) : SetterConfig {
return {
__setters : {
setVisibility : {
path : "isVisible" ,
type : "string" ,
} ,
setSelectedRowIndex : {
path : "defaultSelectedRowIndex" ,
type : "number" ,
disabled : "return options.entity.multiRowSelection" ,
} ,
setSelectedRowIndices : {
path : "defaultSelectedRowIndices" ,
type : "array" ,
disabled : "return !options.entity.multiRowSelection" ,
} ,
setData : {
path : "tableData" ,
2024-02-07 11:06:01 +00:00
type : "array" ,
2023-07-08 14:07:26 +00:00
} ,
} ,
} ;
}
2022-07-14 07:02:35 +00:00
/ *
* Function to get the table columns with appropriate render functions
* based on columnType
* /
getTableColumns = ( ) = > {
2024-01-22 04:45:50 +00:00
const {
columnWidthMap ,
isPreviewMode ,
orderedTableColumns ,
renderMode ,
widgetId ,
} = this . props ;
2022-09-22 11:15:00 +00:00
const { componentWidth } = this . getPaddingAdjustedDimensions ( ) ;
2023-03-30 04:54:29 +00:00
const widgetLocalStorageState = getColumnOrderByWidgetIdFromLS ( widgetId ) ;
const memoisdGetColumnsWithLocalStorage =
this . memoiseGetColumnsWithLocalStorage ( widgetLocalStorageState ) ;
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
return memoisdGetColumnsWithLocalStorage (
this . renderCell ,
columnWidthMap ,
orderedTableColumns ,
componentWidth ,
renderMode ,
2024-01-22 04:45:50 +00:00
isPreviewMode ,
2023-03-30 04:54:29 +00:00
) ;
2022-07-14 07:02:35 +00:00
} ;
transformData = (
tableData : Array < Record < string , unknown > > ,
columns : ReactTableColumnProps [ ] ,
) = > {
2023-03-30 04:54:29 +00:00
return this . memoiseTransformDataWithEditableCell (
this . props . editableCell ,
tableData ,
columns ,
) ;
2022-07-14 07:02:35 +00:00
} ;
updateDerivedColumnsIndex = (
derivedColumns : Record < string , ColumnProperties > ,
tableColumnCount : number ,
) = > {
if ( ! derivedColumns ) {
return [ ] ;
}
//update index property of all columns in new derived columns
return Object . values ( derivedColumns ) . map (
( column : ColumnProperties , index : number ) = > {
return {
. . . column ,
index : index + tableColumnCount ,
} ;
} ,
) ;
} ;
/ *
* Function to create new primary Columns from the tableData
* gets called on component mount and on component update
* /
createTablePrimaryColumns = ( ) :
| Record < string , ColumnProperties >
| undefined = > {
2023-10-11 07:14:38 +00:00
const { primaryColumns = { } , tableData = [ ] } = this . props ;
2022-07-14 07:02:35 +00:00
if ( ! _ . isArray ( tableData ) || tableData . length === 0 ) {
return ;
}
const existingColumnIds = Object . keys ( primaryColumns ) ;
const newTableColumns : Record < string , ColumnProperties > = { } ;
const tableStyles = getTableStyles ( this . props ) ;
const columnKeys : string [ ] = getAllTableColumnKeys ( tableData ) ;
/ *
* Generate default column properties for all columns
* But do not replace existing columns with the same id
* /
columnKeys . forEach ( ( columnKey , index ) = > {
const existingColumn = this . getColumnByOriginalId ( columnKey ) ;
if ( ! ! existingColumn ) {
// Use the existing column properties
newTableColumns [ existingColumn . id ] = existingColumn ;
} else {
const hashedColumnKey = sanitizeKey ( columnKey , {
existingKeys : union ( existingColumnIds , Object . keys ( newTableColumns ) ) ,
} ) ;
// Create column properties for the new column
2022-09-29 05:26:08 +00:00
const columnType = getColumnType ( tableData , columnKey ) ;
2022-07-14 07:02:35 +00:00
const columnProperties = getDefaultColumnProperties (
columnKey ,
hashedColumnKey ,
index ,
this . props . widgetName ,
2022-09-29 05:26:08 +00:00
false ,
columnType ,
2022-07-14 07:02:35 +00:00
) ;
newTableColumns [ columnProperties . id ] = {
. . . columnProperties ,
. . . tableStyles ,
} ;
}
} ) ;
chore: upgrade to prettier v2 + enforce import types (#21013)Co-authored-by: Satish Gandham <hello@satishgandham.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
## Description
This PR upgrades Prettier to v2 + enforces TypeScript’s [`import
type`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export)
syntax where applicable. It’s submitted as a separate PR so we can merge
it easily.
As a part of this PR, we reformat the codebase heavily:
- add `import type` everywhere where it’s required, and
- re-format the code to account for Prettier 2’s breaking changes:
https://prettier.io/blog/2020/03/21/2.0.0.html#breaking-changes
This PR is submitted against `release` to make sure all new code by team
members will adhere to new formatting standards, and we’ll have fewer
conflicts when merging `bundle-optimizations` into `release`. (I’ll
merge `release` back into `bundle-optimizations` once this PR is
merged.)
### Why is this needed?
This PR is needed because, for the Lodash optimization from
https://github.com/appsmithorg/appsmith/commit/7cbb12af886621256224be0c93e6a465dd710ad3,
we need to use `import type`. Otherwise, `babel-plugin-lodash` complains
that `LoDashStatic` is not a lodash function.
However, just using `import type` in the current codebase will give you
this:
<img width="962" alt="Screenshot 2023-03-08 at 17 45 59"
src="https://user-images.githubusercontent.com/2953267/223775744-407afa0c-e8b9-44a1-90f9-b879348da57f.png">
That’s because Prettier 1 can’t parse `import type` at all. To parse it,
we need to upgrade to Prettier 2.
### Why enforce `import type`?
Apart from just enabling `import type` support, this PR enforces
specifying `import type` everywhere it’s needed. (Developers will get
immediate TypeScript and ESLint errors when they forget to do so.)
I’m doing this because I believe `import type` improves DX and makes
refactorings easier.
Let’s say you had a few imports like below. Can you tell which of these
imports will increase the bundle size? (Tip: it’s not all of them!)
```ts
// app/client/src/workers/Linting/utils.ts
import { Position } from "codemirror";
import { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
It’s pretty hard, right?
What about now?
```ts
// app/client/src/workers/Linting/utils.ts
import type { Position } from "codemirror";
import type { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
Now, it’s clear that only `lodash` will be bundled.
This helps developers to see which imports are problematic, but it
_also_ helps with refactorings. Now, if you want to see where
`codemirror` is bundled, you can just grep for `import \{.*\} from
"codemirror"` – and you won’t get any type-only imports.
This also helps (some) bundlers. Upon transpiling, TypeScript erases
type-only imports completely. In some environment (not ours), this makes
the bundle smaller, as the bundler doesn’t need to bundle type-only
imports anymore.
## Type of change
- Chore (housekeeping or task changes that don't impact user perception)
## How Has This Been Tested?
This was tested to not break the build.
### 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
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] 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
---------
Co-authored-by: Satish Gandham <hello@satishgandham.com>
Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
2023-03-16 11:41:47 +00:00
const derivedColumns : Record < string , ColumnProperties > =
getDerivedColumns ( primaryColumns ) ;
2022-07-14 07:02:35 +00:00
const updatedDerivedColumns = this . updateDerivedColumnsIndex (
derivedColumns ,
Object . keys ( newTableColumns ) . length ,
) ;
//add derived columns to new Table columns
updatedDerivedColumns . forEach ( ( derivedColumn : ColumnProperties ) = > {
newTableColumns [ derivedColumn . id ] = derivedColumn ;
} ) ;
const newColumnIds = Object . keys ( newTableColumns ) ;
// check if the columns ids differ
if ( _ . xor ( existingColumnIds , newColumnIds ) . length > 0 ) {
return newTableColumns ;
} else {
return ;
}
} ;
/ *
* Function to update primaryColumns when the tablData schema changes
* /
updateColumnProperties = (
tableColumns? : Record < string , ColumnProperties > ,
2023-06-27 05:15:41 +00:00
shouldPersistLocalOrderWhenTableDataChanges = false ,
2022-07-14 07:02:35 +00:00
) = > {
const { columnOrder = [ ] , primaryColumns = { } } = this . props ;
const derivedColumns = getDerivedColumns ( primaryColumns ) ;
if ( tableColumns ) {
const existingColumnIds = Object . keys ( primaryColumns ) ;
const existingDerivedColumnIds = Object . keys ( derivedColumns ) ;
const newColumnIds = Object . keys ( tableColumns ) ;
//Check if there is any difference in the existing and new columns ids
if ( _ . xor ( existingColumnIds , newColumnIds ) . length > 0 ) {
const newColumnIdsToAdd = _ . without ( newColumnIds , . . . existingColumnIds ) ;
const propertiesToAdd : Record < string , unknown > = { } ;
newColumnIdsToAdd . forEach ( ( columnId : string ) = > {
// id could be an empty string
if ( ! ! columnId ) {
Object . entries ( tableColumns [ columnId ] ) . forEach ( ( [ key , value ] ) = > {
propertiesToAdd [ ` primaryColumns. ${ columnId } . ${ key } ` ] = value ;
} ) ;
}
} ) ;
/ *
* If new columnOrders have different values from the original columnOrders
* Only update when there are new Columns ( Derived or Primary )
* /
if (
! ! newColumnIds . length &&
! ! _ . xor ( newColumnIds , columnOrder ) . length &&
2022-09-02 09:16:30 +00:00
! equal ( _ . sortBy ( newColumnIds ) , _ . sortBy ( existingDerivedColumnIds ) )
2022-07-14 07:02:35 +00:00
) {
2022-09-01 06:10:19 +00:00
// Maintain original columnOrder and keep new columns at the end
let newColumnOrder = _ . intersection ( columnOrder , newColumnIds ) ;
2024-09-18 16:35:28 +00:00
2022-09-01 06:10:19 +00:00
newColumnOrder = _ . union ( newColumnOrder , newColumnIds ) ;
2023-05-03 06:26:08 +00:00
const compareColumns = ( a : string , b : string ) = > {
const aSticky = tableColumns [ a ] . sticky || "none" ;
const bSticky = tableColumns [ b ] . sticky || "none" ;
if ( aSticky === bSticky ) {
return 0 ;
}
return SORT_ORDER [ aSticky ] - SORT_ORDER [ bSticky ] ;
} ;
// Sort the column order to retain the position of frozen columns
newColumnOrder . sort ( compareColumns ) ;
2022-09-01 06:10:19 +00:00
propertiesToAdd [ "columnOrder" ] = newColumnOrder ;
2023-06-27 05:15:41 +00:00
/ * *
* As the table data changes in Deployed app , we also update the local storage .
*
* this . updateColumnProperties gets executed on mount and on update of the component .
* On mount we get new tableColumns that may not have any sticky columns .
* This will lead to loss of sticky column that were frozen by the user .
* To avoid this and to maintain user ' s sticky columns we use shouldPersistLocalOrderWhenTableDataChanges below
* so as to avoid updating the local storage on mount .
* * /
if (
this . props . renderMode === RenderModes . PAGE &&
shouldPersistLocalOrderWhenTableDataChanges
) {
const leftOrder = newColumnOrder . filter (
( col : string ) = > tableColumns [ col ] . sticky === StickyType . LEFT ,
) ;
const rightOrder = newColumnOrder . filter (
( col : string ) = > tableColumns [ col ] . sticky === StickyType . RIGHT ,
) ;
2024-09-18 16:35:28 +00:00
2023-06-27 05:15:41 +00:00
this . persistColumnOrder ( newColumnOrder , leftOrder , rightOrder ) ;
}
2022-07-14 07:02:35 +00:00
}
const propertiesToUpdate : BatchPropertyUpdatePayload = {
modify : propertiesToAdd ,
} ;
const pathsToDelete : string [ ] = [ ] ;
const columnsIdsToDelete = without ( existingColumnIds , . . . newColumnIds ) ;
if ( ! ! columnsIdsToDelete . length ) {
columnsIdsToDelete . forEach ( ( id : string ) = > {
if ( ! primaryColumns [ id ] . isDerived ) {
pathsToDelete . push ( ` primaryColumns. ${ id } ` ) ;
}
} ) ;
propertiesToUpdate . remove = pathsToDelete ;
}
super . batchUpdateWidgetProperty ( propertiesToUpdate , false ) ;
}
}
} ;
2023-03-30 04:54:29 +00:00
//no need to batch meta updates
2023-02-15 11:42:46 +00:00
hydrateStickyColumns = ( ) = > {
const localTableColumnOrder = getColumnOrderByWidgetIdFromLS (
this . props . widgetId ,
) ;
const leftLen : number = Object . keys (
pickBy ( this . props . primaryColumns , ( col ) = > col . sticky === "left" ) ,
) . length ;
const leftOrder = [ . . . ( this . props . columnOrder || [ ] ) ] . slice ( 0 , leftLen ) ;
const rightLen : number = Object . keys (
pickBy ( this . props . primaryColumns , ( col ) = > col . sticky !== "right" ) ,
) . length ;
const rightOrder : string [ ] = [ . . . ( this . props . columnOrder || [ ] ) ] . slice (
rightLen ,
) ;
if ( localTableColumnOrder ) {
2023-06-27 05:15:41 +00:00
const {
columnOrder ,
columnUpdatedAt ,
leftOrder : localLeftOrder ,
rightOrder : localRightOrder ,
} = localTableColumnOrder ;
2023-02-15 11:42:46 +00:00
if ( this . props . columnUpdatedAt !== columnUpdatedAt ) {
// Delete and set the column orders defined by the developer
deleteLocalTableColumnOrderByWidgetId ( this . props . widgetId ) ;
this . persistColumnOrder (
this . props . columnOrder ? ? [ ] ,
leftOrder ,
rightOrder ,
) ;
} else {
2023-06-27 05:15:41 +00:00
const propertiesToAdd : Record < string , string > = { } ;
propertiesToAdd [ "columnOrder" ] = columnOrder ;
/ * *
* We reset the sticky values of the columns that were frozen by the developer .
* /
if ( Object . keys ( this . props . primaryColumns ) . length > 0 ) {
columnOrder . forEach ( ( colName : string ) = > {
if (
this . props . primaryColumns [ colName ] ? . sticky !== StickyType . NONE
) {
propertiesToAdd [ ` primaryColumns. ${ colName } .sticky ` ] =
StickyType . NONE ;
}
} ) ;
}
/ * *
* We pickup the left and the right frozen columns from the localstorage
* and update the sticky value of these columns respectively .
* /
if ( localLeftOrder . length > 0 ) {
localLeftOrder . forEach ( ( colName : string ) = > {
propertiesToAdd [ ` primaryColumns. ${ colName } .sticky ` ] =
StickyType . LEFT ;
} ) ;
}
if ( localRightOrder . length > 0 ) {
localRightOrder . forEach ( ( colName : string ) = > {
propertiesToAdd [ ` primaryColumns. ${ colName } .sticky ` ] =
StickyType . RIGHT ;
} ) ;
}
const propertiesToUpdate = {
modify : propertiesToAdd ,
} ;
2024-09-18 16:35:28 +00:00
2023-06-27 05:15:41 +00:00
super . batchUpdateWidgetProperty ( propertiesToUpdate ) ;
2023-02-15 11:42:46 +00:00
}
} else {
// If user deletes local storage or no column orders for the given table widget exists hydrate it with the developer changes.
this . persistColumnOrder (
this . props . columnOrder ? ? [ ] ,
leftOrder ,
rightOrder ,
) ;
}
} ;
2022-07-14 07:02:35 +00:00
componentDidMount() {
2023-02-15 11:42:46 +00:00
const { canFreezeColumn , renderMode , tableData } = this . props ;
2022-07-14 07:02:35 +00:00
if ( _ . isArray ( tableData ) && ! ! tableData . length ) {
const newPrimaryColumns = this . createTablePrimaryColumns ( ) ;
// When the Table data schema changes
if ( newPrimaryColumns && ! ! Object . keys ( newPrimaryColumns ) . length ) {
this . updateColumnProperties ( newPrimaryColumns ) ;
}
}
2023-06-27 05:15:41 +00:00
if ( canFreezeColumn && renderMode === RenderModes . PAGE ) {
//dont neet to batch this since single action
this . hydrateStickyColumns ( ) ;
}
2025-04-02 08:32:54 +00:00
// Commit Batch Updates property `true` is passed as commitBatchMetaUpdates is not called on componentDidMount and we need to call it for updating the batch updates
this . updateInfiniteScrollProperties ( true ) ;
2022-07-14 07:02:35 +00:00
}
componentDidUpdate ( prevProps : TableWidgetProps ) {
const {
feat: reset table when infinite scroll is turned on (#40066)
## 🐞 Problem
We've identified several issues with the TableWidgetV2's infinite scroll
functionality:
- **Stale Data After Toggle**
When users enable infinite scroll for the first time, the table's state
and cached data aren't properly reset, potentially leading to incorrect
data display and inconsistent behavior.
- **Height Changes Breaking Existing Data**
When a table's height is increased while infinite scroll is enabled, the
existing cached data becomes invalid due to changing offsets, but the
table wasn't resetting its state accordingly.
- **Empty Initial View**
When loading a table with infinite scroll enabled, rows were not visible
during the initial load until data was fetched, creating a jarring user
experience.
- **Disabled Properties Still Rendering Controls**
Property controls that should be disabled (based on section disabling
conditions) were still being rendered and active, causing unexpected
behavior.
---
## ✅ Solution
### 1. Implement Table Reset on Infinite Scroll Toggle
Added a new method `resetTableForInfiniteScroll()` that properly resets
the table's state when infinite scroll is enabled. This method:
- Clears cached table data
- Resets the "end of data" flag
- Resets all meta properties to their default values
- Sets the page number back to `1` and triggers a page load
```ts
resetTableForInfiniteScroll = () => {
const { infiniteScrollEnabled, pushBatchMetaUpdates } = this.props;
if (infiniteScrollEnabled) {
// reset the cachedRows
pushBatchMetaUpdates("cachedTableData", {});
pushBatchMetaUpdates("endOfData", false);
// reset the meta properties
const metaProperties = Object.keys(TableWidgetV2.getMetaPropertiesMap());
metaProperties.forEach((prop) => {
if (prop !== "pageNo") {
const defaultValue = TableWidgetV2.getMetaPropertiesMap()[prop];
this.props.updateWidgetMetaProperty(prop, defaultValue);
}
});
// reset and reload page
this.updatePageNumber(1, EventType.ON_NEXT_PAGE);
}
};
```
---
### 2. Reset on Height Changes
Added a check in `componentDidUpdate` to detect height changes and reset
the table when needed:
```ts
// Reset widget state when height changes while infinite scroll is enabled
if (
infiniteScrollEnabled &&
prevProps.componentHeight !== componentHeight
) {
this.resetTableForInfiniteScroll();
}
```
---
### 3. Improved Empty State Rendering
Modified the `InfiniteScrollBodyComponent` to show placeholder rows
during initial load by using the maximum of `rows.length` and
`pageSize`:
```ts
const itemCount = useMemo(
() => Math.max(rows.length, pageSize),
[rows.length, pageSize],
);
```
This ensures the table maintains its expected height and appearance even
before data is loaded.
---
### 4. Fixed Property Control Rendering
Fixed the `PropertyControl` component to respect the `isControlDisabled`
flag by conditionally rendering the control:
```ts
{!isControlDisabled &&
PropertyControlFactory.createControl(
config,
{
onPropertyChange: onPropertyChange,
// ...other props
},
// ...other args
)}
```
This prevents disabled controls from being rendered and potentially
causing issues.
---
These improvements significantly enhance the stability and user
experience of **TableWidgetV2**'s infinite scroll functionality.
Fixes #39377
## Automation
/ok-to-test tags="@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity,
@tag.PropertyPane"
### :mag: 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/14373134089>
> Commit: 2b0715bbbe2e9a254cd287f831329be529a17c3c
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14373134089&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity,
@tag.PropertyPane`
> Spec:
> <hr>Thu, 10 Apr 2025 07:15:53 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
- **New Features**
- Property panels now display controls only when enabled, enhancing
clarity.
- Table widgets offer smoother infinite scrolling with automatic resets
on state or size changes.
- Columns dynamically adjust for optimal display when infinite scrolling
is active.
- **Bug Fixes**
- Improved handling of item counts and loading states in infinite
scrolling.
- **Refactor**
- Improved performance through optimized item computations and
streamlined scrolling logic.
- Removed redundant loading button logic for a cleaner user experience.
- **Tests**
- Expanded test scenarios to verify improved content wrapping and rich
HTML rendering in table cells, with a focus on internal logic and
behavior.
- Enhanced clarity and robustness of infinite scroll tests by verifying
loading through scrolling actions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Rahul Barwal <rahul.barwal@appsmith.com>
2025-04-10 10:58:15 +00:00
commitBatchMetaUpdates ,
componentHeight ,
2022-07-14 07:02:35 +00:00
defaultSelectedRowIndex ,
defaultSelectedRowIndices ,
feat: reset table when infinite scroll is turned on (#40066)
## 🐞 Problem
We've identified several issues with the TableWidgetV2's infinite scroll
functionality:
- **Stale Data After Toggle**
When users enable infinite scroll for the first time, the table's state
and cached data aren't properly reset, potentially leading to incorrect
data display and inconsistent behavior.
- **Height Changes Breaking Existing Data**
When a table's height is increased while infinite scroll is enabled, the
existing cached data becomes invalid due to changing offsets, but the
table wasn't resetting its state accordingly.
- **Empty Initial View**
When loading a table with infinite scroll enabled, rows were not visible
during the initial load until data was fetched, creating a jarring user
experience.
- **Disabled Properties Still Rendering Controls**
Property controls that should be disabled (based on section disabling
conditions) were still being rendered and active, causing unexpected
behavior.
---
## ✅ Solution
### 1. Implement Table Reset on Infinite Scroll Toggle
Added a new method `resetTableForInfiniteScroll()` that properly resets
the table's state when infinite scroll is enabled. This method:
- Clears cached table data
- Resets the "end of data" flag
- Resets all meta properties to their default values
- Sets the page number back to `1` and triggers a page load
```ts
resetTableForInfiniteScroll = () => {
const { infiniteScrollEnabled, pushBatchMetaUpdates } = this.props;
if (infiniteScrollEnabled) {
// reset the cachedRows
pushBatchMetaUpdates("cachedTableData", {});
pushBatchMetaUpdates("endOfData", false);
// reset the meta properties
const metaProperties = Object.keys(TableWidgetV2.getMetaPropertiesMap());
metaProperties.forEach((prop) => {
if (prop !== "pageNo") {
const defaultValue = TableWidgetV2.getMetaPropertiesMap()[prop];
this.props.updateWidgetMetaProperty(prop, defaultValue);
}
});
// reset and reload page
this.updatePageNumber(1, EventType.ON_NEXT_PAGE);
}
};
```
---
### 2. Reset on Height Changes
Added a check in `componentDidUpdate` to detect height changes and reset
the table when needed:
```ts
// Reset widget state when height changes while infinite scroll is enabled
if (
infiniteScrollEnabled &&
prevProps.componentHeight !== componentHeight
) {
this.resetTableForInfiniteScroll();
}
```
---
### 3. Improved Empty State Rendering
Modified the `InfiniteScrollBodyComponent` to show placeholder rows
during initial load by using the maximum of `rows.length` and
`pageSize`:
```ts
const itemCount = useMemo(
() => Math.max(rows.length, pageSize),
[rows.length, pageSize],
);
```
This ensures the table maintains its expected height and appearance even
before data is loaded.
---
### 4. Fixed Property Control Rendering
Fixed the `PropertyControl` component to respect the `isControlDisabled`
flag by conditionally rendering the control:
```ts
{!isControlDisabled &&
PropertyControlFactory.createControl(
config,
{
onPropertyChange: onPropertyChange,
// ...other props
},
// ...other args
)}
```
This prevents disabled controls from being rendered and potentially
causing issues.
---
These improvements significantly enhance the stability and user
experience of **TableWidgetV2**'s infinite scroll functionality.
Fixes #39377
## Automation
/ok-to-test tags="@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity,
@tag.PropertyPane"
### :mag: 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/14373134089>
> Commit: 2b0715bbbe2e9a254cd287f831329be529a17c3c
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14373134089&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity,
@tag.PropertyPane`
> Spec:
> <hr>Thu, 10 Apr 2025 07:15:53 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
- **New Features**
- Property panels now display controls only when enabled, enhancing
clarity.
- Table widgets offer smoother infinite scrolling with automatic resets
on state or size changes.
- Columns dynamically adjust for optimal display when infinite scrolling
is active.
- **Bug Fixes**
- Improved handling of item counts and loading states in infinite
scrolling.
- **Refactor**
- Improved performance through optimized item computations and
streamlined scrolling logic.
- Removed redundant loading button logic for a cleaner user experience.
- **Tests**
- Expanded test scenarios to verify improved content wrapping and rich
HTML rendering in table cells, with a focus on internal logic and
behavior.
- Enhanced clarity and robustness of infinite scroll tests by verifying
loading through scrolling actions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Rahul Barwal <rahul.barwal@appsmith.com>
2025-04-10 10:58:15 +00:00
infiniteScrollEnabled ,
2022-07-14 07:02:35 +00:00
pageNo ,
pageSize ,
primaryColumns = { } ,
feat: reset table when infinite scroll is turned on (#40066)
## 🐞 Problem
We've identified several issues with the TableWidgetV2's infinite scroll
functionality:
- **Stale Data After Toggle**
When users enable infinite scroll for the first time, the table's state
and cached data aren't properly reset, potentially leading to incorrect
data display and inconsistent behavior.
- **Height Changes Breaking Existing Data**
When a table's height is increased while infinite scroll is enabled, the
existing cached data becomes invalid due to changing offsets, but the
table wasn't resetting its state accordingly.
- **Empty Initial View**
When loading a table with infinite scroll enabled, rows were not visible
during the initial load until data was fetched, creating a jarring user
experience.
- **Disabled Properties Still Rendering Controls**
Property controls that should be disabled (based on section disabling
conditions) were still being rendered and active, causing unexpected
behavior.
---
## ✅ Solution
### 1. Implement Table Reset on Infinite Scroll Toggle
Added a new method `resetTableForInfiniteScroll()` that properly resets
the table's state when infinite scroll is enabled. This method:
- Clears cached table data
- Resets the "end of data" flag
- Resets all meta properties to their default values
- Sets the page number back to `1` and triggers a page load
```ts
resetTableForInfiniteScroll = () => {
const { infiniteScrollEnabled, pushBatchMetaUpdates } = this.props;
if (infiniteScrollEnabled) {
// reset the cachedRows
pushBatchMetaUpdates("cachedTableData", {});
pushBatchMetaUpdates("endOfData", false);
// reset the meta properties
const metaProperties = Object.keys(TableWidgetV2.getMetaPropertiesMap());
metaProperties.forEach((prop) => {
if (prop !== "pageNo") {
const defaultValue = TableWidgetV2.getMetaPropertiesMap()[prop];
this.props.updateWidgetMetaProperty(prop, defaultValue);
}
});
// reset and reload page
this.updatePageNumber(1, EventType.ON_NEXT_PAGE);
}
};
```
---
### 2. Reset on Height Changes
Added a check in `componentDidUpdate` to detect height changes and reset
the table when needed:
```ts
// Reset widget state when height changes while infinite scroll is enabled
if (
infiniteScrollEnabled &&
prevProps.componentHeight !== componentHeight
) {
this.resetTableForInfiniteScroll();
}
```
---
### 3. Improved Empty State Rendering
Modified the `InfiniteScrollBodyComponent` to show placeholder rows
during initial load by using the maximum of `rows.length` and
`pageSize`:
```ts
const itemCount = useMemo(
() => Math.max(rows.length, pageSize),
[rows.length, pageSize],
);
```
This ensures the table maintains its expected height and appearance even
before data is loaded.
---
### 4. Fixed Property Control Rendering
Fixed the `PropertyControl` component to respect the `isControlDisabled`
flag by conditionally rendering the control:
```ts
{!isControlDisabled &&
PropertyControlFactory.createControl(
config,
{
onPropertyChange: onPropertyChange,
// ...other props
},
// ...other args
)}
```
This prevents disabled controls from being rendered and potentially
causing issues.
---
These improvements significantly enhance the stability and user
experience of **TableWidgetV2**'s infinite scroll functionality.
Fixes #39377
## Automation
/ok-to-test tags="@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity,
@tag.PropertyPane"
### :mag: 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/14373134089>
> Commit: 2b0715bbbe2e9a254cd287f831329be529a17c3c
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14373134089&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity,
@tag.PropertyPane`
> Spec:
> <hr>Thu, 10 Apr 2025 07:15:53 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
- **New Features**
- Property panels now display controls only when enabled, enhancing
clarity.
- Table widgets offer smoother infinite scrolling with automatic resets
on state or size changes.
- Columns dynamically adjust for optimal display when infinite scrolling
is active.
- **Bug Fixes**
- Improved handling of item counts and loading states in infinite
scrolling.
- **Refactor**
- Improved performance through optimized item computations and
streamlined scrolling logic.
- Removed redundant loading button logic for a cleaner user experience.
- **Tests**
- Expanded test scenarios to verify improved content wrapping and rich
HTML rendering in table cells, with a focus on internal logic and
behavior.
- Enhanced clarity and robustness of infinite scroll tests by verifying
loading through scrolling actions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Rahul Barwal <rahul.barwal@appsmith.com>
2025-04-10 10:58:15 +00:00
pushBatchMetaUpdates ,
2022-07-14 07:02:35 +00:00
serverSidePaginationEnabled ,
totalRecordsCount ,
} = this . props ;
// Bail out if tableData is a string. This signifies an error in evaluations
if ( isString ( this . props . tableData ) ) {
return ;
}
2023-02-15 11:42:46 +00:00
if (
this . props . primaryColumns &&
( ! equal ( prevProps . columnOrder , this . props . columnOrder ) ||
filter ( prevProps . orderedTableColumns , { isVisible : false } ) . length !==
filter ( this . props . orderedTableColumns , { isVisible : false } ) . length ||
getAllStickyColumnsCount ( prevProps . orderedTableColumns ) !==
getAllStickyColumnsCount ( this . props . orderedTableColumns ) )
) {
if ( this . props . renderMode === RenderModes . CANVAS ) {
super . batchUpdateWidgetProperty (
{
modify : {
columnUpdatedAt : Date.now ( ) ,
} ,
} ,
false ,
) ;
}
}
2023-03-30 04:54:29 +00:00
//check if necessary we are batching now updates
2022-07-14 07:02:35 +00:00
// Check if tableData is modifed
2024-03-21 09:46:00 +00:00
const isTableDataModified = this . props . tableData !== prevProps . tableData ;
2023-06-01 17:26:05 +00:00
2022-07-14 07:02:35 +00:00
// If the user has changed the tableData OR
// The binding has returned a new value
if ( isTableDataModified ) {
2023-03-30 04:54:29 +00:00
this . pushMetaRowDataUpdates (
2022-07-14 07:02:35 +00:00
prevProps . filteredTableData ,
this . props . filteredTableData ,
) ;
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "triggeredRowIndex" , - 1 ) ;
2022-07-14 07:02:35 +00:00
const newColumnIds : string [ ] = getAllTableColumnKeys (
this . props . tableData ,
) ;
const primaryColumnIds = Object . keys ( primaryColumns ) . filter (
( id : string ) = > ! primaryColumns [ id ] . isDerived ,
) ;
if ( xor ( newColumnIds , primaryColumnIds ) . length > 0 ) {
const newTableColumns = this . createTablePrimaryColumns ( ) ;
if ( newTableColumns ) {
2023-06-27 05:15:41 +00:00
this . updateColumnProperties ( newTableColumns , isTableDataModified ) ;
2022-07-14 07:02:35 +00:00
}
2022-12-01 05:24:48 +00:00
2023-08-22 11:27:02 +00:00
pushBatchMetaUpdates ( "filters" , [ ] ) ;
2022-07-14 07:02:35 +00:00
}
2025-04-02 08:32:54 +00:00
/ *
* Clear transient table data and editablecell when tableData changes
* /
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "transientTableData" , { } ) ;
2022-11-25 04:39:59 +00:00
// reset updatedRowIndex whenever transientTableData is flushed.
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "updatedRowIndex" , - 1 ) ;
2022-11-25 04:39:59 +00:00
2025-04-02 08:32:54 +00:00
/ *
* Updating the caching layer on table data modification
* Commit Batch Updates property ` false ` is passed as commitBatchMetaUpdates is called on componentDidUpdate
* and we need not to explicitly call it for updating the batch updates
* * /
this . updateInfiniteScrollProperties ( ) ;
2023-03-30 04:54:29 +00:00
this . pushClearEditableCellsUpdates ( ) ;
pushBatchMetaUpdates ( "selectColumnFilterText" , { } ) ;
2025-04-02 08:32:54 +00:00
} else {
// TODO: reset the widget on any property change, like if the toggle of infinite scroll is enabled and previously it was disabled, currently we update cachedTableData property to the current tableData at pageNo.
/ *
* Commit Batch Updates property ` false ` is passed as commitBatchMetaUpdates is called on componentDidUpdate
* and we need not to explicitly call it for updating the batch updates
* * /
if (
! prevProps . infiniteScrollEnabled &&
this . props . infiniteScrollEnabled
) {
this . updateInfiniteScrollProperties ( ) ;
}
2022-07-14 07:02:35 +00:00
}
if ( ! pageNo ) {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "pageNo" , 1 ) ;
2023-04-13 10:08:46 +00:00
this . updatePaginationDirectionFlags ( PaginationDirection . INITIAL ) ;
2022-07-14 07:02:35 +00:00
}
//check if pageNo does not excede the max Page no, due to change of totalRecordsCount
2023-04-13 10:08:46 +00:00
if ( serverSidePaginationEnabled !== prevProps . serverSidePaginationEnabled ) {
2022-07-14 07:02:35 +00:00
//reset pageNo when serverSidePaginationEnabled is toggled
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "pageNo" , 1 ) ;
2023-04-13 10:08:46 +00:00
this . updatePaginationDirectionFlags ( PaginationDirection . INITIAL ) ;
} else {
//check if pageNo does not excede the max Page no, due to change of totalRecordsCount or change of pageSize
if ( serverSidePaginationEnabled && totalRecordsCount ) {
const maxAllowedPageNumber = Math . ceil ( totalRecordsCount / pageSize ) ;
if ( pageNo > maxAllowedPageNumber ) {
pushBatchMetaUpdates ( "pageNo" , maxAllowedPageNumber ) ;
this . updatePaginationDirectionFlags ( PaginationDirection . NEXT_PAGE ) ;
}
}
2022-07-14 07:02:35 +00:00
}
feat: reset table when infinite scroll is turned on (#40066)
## 🐞 Problem
We've identified several issues with the TableWidgetV2's infinite scroll
functionality:
- **Stale Data After Toggle**
When users enable infinite scroll for the first time, the table's state
and cached data aren't properly reset, potentially leading to incorrect
data display and inconsistent behavior.
- **Height Changes Breaking Existing Data**
When a table's height is increased while infinite scroll is enabled, the
existing cached data becomes invalid due to changing offsets, but the
table wasn't resetting its state accordingly.
- **Empty Initial View**
When loading a table with infinite scroll enabled, rows were not visible
during the initial load until data was fetched, creating a jarring user
experience.
- **Disabled Properties Still Rendering Controls**
Property controls that should be disabled (based on section disabling
conditions) were still being rendered and active, causing unexpected
behavior.
---
## ✅ Solution
### 1. Implement Table Reset on Infinite Scroll Toggle
Added a new method `resetTableForInfiniteScroll()` that properly resets
the table's state when infinite scroll is enabled. This method:
- Clears cached table data
- Resets the "end of data" flag
- Resets all meta properties to their default values
- Sets the page number back to `1` and triggers a page load
```ts
resetTableForInfiniteScroll = () => {
const { infiniteScrollEnabled, pushBatchMetaUpdates } = this.props;
if (infiniteScrollEnabled) {
// reset the cachedRows
pushBatchMetaUpdates("cachedTableData", {});
pushBatchMetaUpdates("endOfData", false);
// reset the meta properties
const metaProperties = Object.keys(TableWidgetV2.getMetaPropertiesMap());
metaProperties.forEach((prop) => {
if (prop !== "pageNo") {
const defaultValue = TableWidgetV2.getMetaPropertiesMap()[prop];
this.props.updateWidgetMetaProperty(prop, defaultValue);
}
});
// reset and reload page
this.updatePageNumber(1, EventType.ON_NEXT_PAGE);
}
};
```
---
### 2. Reset on Height Changes
Added a check in `componentDidUpdate` to detect height changes and reset
the table when needed:
```ts
// Reset widget state when height changes while infinite scroll is enabled
if (
infiniteScrollEnabled &&
prevProps.componentHeight !== componentHeight
) {
this.resetTableForInfiniteScroll();
}
```
---
### 3. Improved Empty State Rendering
Modified the `InfiniteScrollBodyComponent` to show placeholder rows
during initial load by using the maximum of `rows.length` and
`pageSize`:
```ts
const itemCount = useMemo(
() => Math.max(rows.length, pageSize),
[rows.length, pageSize],
);
```
This ensures the table maintains its expected height and appearance even
before data is loaded.
---
### 4. Fixed Property Control Rendering
Fixed the `PropertyControl` component to respect the `isControlDisabled`
flag by conditionally rendering the control:
```ts
{!isControlDisabled &&
PropertyControlFactory.createControl(
config,
{
onPropertyChange: onPropertyChange,
// ...other props
},
// ...other args
)}
```
This prevents disabled controls from being rendered and potentially
causing issues.
---
These improvements significantly enhance the stability and user
experience of **TableWidgetV2**'s infinite scroll functionality.
Fixes #39377
## Automation
/ok-to-test tags="@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity,
@tag.PropertyPane"
### :mag: 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/14373134089>
> Commit: 2b0715bbbe2e9a254cd287f831329be529a17c3c
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14373134089&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity,
@tag.PropertyPane`
> Spec:
> <hr>Thu, 10 Apr 2025 07:15:53 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
- **New Features**
- Property panels now display controls only when enabled, enhancing
clarity.
- Table widgets offer smoother infinite scrolling with automatic resets
on state or size changes.
- Columns dynamically adjust for optimal display when infinite scrolling
is active.
- **Bug Fixes**
- Improved handling of item counts and loading states in infinite
scrolling.
- **Refactor**
- Improved performance through optimized item computations and
streamlined scrolling logic.
- Removed redundant loading button logic for a cleaner user experience.
- **Tests**
- Expanded test scenarios to verify improved content wrapping and rich
HTML rendering in table cells, with a focus on internal logic and
behavior.
- Enhanced clarity and robustness of infinite scroll tests by verifying
loading through scrolling actions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Rahul Barwal <rahul.barwal@appsmith.com>
2025-04-10 10:58:15 +00:00
// Reset widget state when infinite scroll is initially enabled
// This should come after all updateInfiniteScrollProperties are done
if ( ! prevProps . infiniteScrollEnabled && infiniteScrollEnabled ) {
this . resetTableForInfiniteScroll ( ) ;
}
// Reset widget state when height changes while infinite scroll is enabled
if (
infiniteScrollEnabled &&
prevProps . componentHeight !== componentHeight
) {
this . resetTableForInfiniteScroll ( ) ;
}
2022-07-14 07:02:35 +00:00
/ *
* When defaultSelectedRowIndex or defaultSelectedRowIndices
* is changed from property pane
* /
if (
2022-09-02 09:16:30 +00:00
! equal ( defaultSelectedRowIndex , prevProps . defaultSelectedRowIndex ) ||
! equal ( defaultSelectedRowIndices , prevProps . defaultSelectedRowIndices )
2022-07-14 07:02:35 +00:00
) {
2023-03-30 04:54:29 +00:00
this . pushUpdateSelectedRowIndexUpdates ( ) ;
2022-07-14 07:02:35 +00:00
}
2023-03-30 04:54:29 +00:00
this . pushResetPageNoUpdates ( prevProps ) ;
2022-07-14 07:02:35 +00:00
2023-03-30 04:54:29 +00:00
this . pushResetRowSelectionPropertiesUpdates ( prevProps ) ;
commitBatchMetaUpdates ( ) ;
2022-07-14 07:02:35 +00:00
}
2023-03-30 04:54:29 +00:00
pushResetPageNoUpdates = ( prevProps : TableWidgetProps ) = > {
const { onPageSizeChange , pageSize , pushBatchMetaUpdates } = this . props ;
2022-07-14 07:02:35 +00:00
if ( pageSize !== prevProps . pageSize ) {
if ( onPageSizeChange ) {
2023-04-13 10:08:46 +00:00
this . updatePaginationDirectionFlags ( PaginationDirection . INITIAL ) ;
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "pageNo" , 1 , {
2022-07-14 07:02:35 +00:00
triggerPropertyName : "onPageSizeChange" ,
dynamicString : onPageSizeChange ,
event : {
type : EventType . ON_PAGE_SIZE_CHANGE ,
} ,
} ) ;
} else {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "pageNo" , 1 ) ;
2023-04-13 10:08:46 +00:00
this . updatePaginationDirectionFlags ( PaginationDirection . INITIAL ) ;
2022-07-14 07:02:35 +00:00
}
}
} ;
2023-03-30 04:54:29 +00:00
pushResetRowSelectionPropertiesUpdates = ( prevProps : TableWidgetProps ) = > {
2022-07-14 07:02:35 +00:00
const {
defaultSelectedRowIndex ,
defaultSelectedRowIndices ,
multiRowSelection ,
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ,
2022-07-14 07:02:35 +00:00
} = this . props ;
// reset selectedRowIndices and selectedRowIndex to defaults
if ( multiRowSelection !== prevProps . multiRowSelection ) {
if ( multiRowSelection ) {
if (
defaultSelectedRowIndices &&
_ . isArray ( defaultSelectedRowIndices ) &&
defaultSelectedRowIndices . every ( ( i ) = > _ . isFinite ( i ) )
) {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "selectedRowIndices" , defaultSelectedRowIndices ) ;
2022-07-14 07:02:35 +00:00
}
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "selectedRowIndex" , - 1 ) ;
2022-07-14 07:02:35 +00:00
} else {
2023-10-02 19:41:05 +00:00
if (
! isNil ( defaultSelectedRowIndex ) &&
parseInt ( defaultSelectedRowIndex ? . toString ( ) , 10 ) > - 1
) {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "selectedRowIndex" , defaultSelectedRowIndex ) ;
2022-07-14 07:02:35 +00:00
}
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "selectedRowIndices" , [ ] ) ;
2022-07-14 07:02:35 +00:00
}
}
} ;
/ *
* Function to update selectedRowIndices & selectedRowIndex from
* defaultSelectedRowIndices & defaultSelectedRowIndex respectively
* /
2023-03-30 04:54:29 +00:00
pushUpdateSelectedRowIndexUpdates = ( ) = > {
2022-07-14 07:02:35 +00:00
const {
defaultSelectedRowIndex ,
defaultSelectedRowIndices ,
multiRowSelection ,
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ,
2022-07-14 07:02:35 +00:00
} = this . props ;
if ( multiRowSelection ) {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "selectedRowIndices" , defaultSelectedRowIndices ) ;
2022-07-14 07:02:35 +00:00
} else {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "selectedRowIndex" , defaultSelectedRowIndex ) ;
2022-07-14 07:02:35 +00:00
}
} ;
/ *
* Function to update selectedRow details when order of tableData changes
* /
2023-03-30 04:54:29 +00:00
pushMetaRowDataUpdates = (
2022-07-14 07:02:35 +00:00
oldTableData : Array < Record < string , unknown > > ,
newTableData : Array < Record < string , unknown > > ,
) = > {
const {
defaultSelectedRowIndex ,
defaultSelectedRowIndices ,
multiRowSelection ,
primaryColumnId ,
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ,
2022-07-14 07:02:35 +00:00
selectedRowIndex ,
selectedRowIndices ,
} = this . props ;
if ( multiRowSelection ) {
const indices = getSelectRowIndices (
oldTableData ,
newTableData ,
defaultSelectedRowIndices ,
selectedRowIndices ,
primaryColumnId ,
) ;
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "selectedRowIndices" , indices ) ;
2022-07-14 07:02:35 +00:00
} else {
const index = getSelectRowIndex (
oldTableData ,
newTableData ,
defaultSelectedRowIndex ,
selectedRowIndex ,
primaryColumnId ,
) ;
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "selectedRowIndex" , index ) ;
2022-07-14 07:02:35 +00:00
}
} ;
getSelectedRowIndices = ( ) = > {
const { multiRowSelection , selectedRowIndices } = this . props ;
let indices : number [ ] | undefined ;
if ( multiRowSelection ) {
if ( _ . isArray ( selectedRowIndices ) ) {
indices = selectedRowIndices ;
} else if ( _ . isNumber ( selectedRowIndices ) ) {
indices = [ selectedRowIndices ] ;
} else {
indices = [ ] ;
}
} else {
indices = undefined ;
}
return indices ;
} ;
updateFilters = ( filters : ReactTableFilter [ ] ) = > {
2023-08-22 11:27:02 +00:00
const {
commitBatchMetaUpdates ,
enableServerSideFiltering ,
onTableFilterUpdate ,
pushBatchMetaUpdates ,
} = this . props ;
2023-03-30 04:54:29 +00:00
this . pushResetSelectedRowIndexUpdates ( ) ;
2023-08-22 11:27:02 +00:00
if ( enableServerSideFiltering ) {
pushBatchMetaUpdates ( "filters" , filters , {
triggerPropertyName : "onTableFilterUpdate" ,
dynamicString : onTableFilterUpdate ,
event : {
type : EventType . ON_FILTER_UPDATE ,
} ,
} ) ;
} else {
pushBatchMetaUpdates ( "filters" , filters ) ;
}
2022-07-14 07:02:35 +00:00
// Reset Page only when a filter is added
2023-02-09 11:29:06 +00:00
if ( ! isEmpty ( xorWith ( filters , [ DEFAULT_FILTER ] , equal ) ) ) {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "pageNo" , 1 ) ;
2023-04-13 10:08:46 +00:00
this . updatePaginationDirectionFlags ( PaginationDirection . INITIAL ) ;
2022-07-14 07:02:35 +00:00
}
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates ( ) ;
2022-07-14 07:02:35 +00:00
} ;
toggleDrag = ( disable : boolean ) = > {
this . disableDrag ( disable ) ;
} ;
2022-09-22 11:15:00 +00:00
getPaddingAdjustedDimensions = ( ) = > {
// eslint-disable-next-line prefer-const
2023-09-11 15:55:11 +00:00
let { componentHeight , componentWidth } = this . props ;
2024-09-18 16:35:28 +00:00
2022-09-22 11:15:00 +00:00
// (2 * WIDGET_PADDING) gives the total horizontal padding (i.e. paddingLeft + paddingRight)
componentWidth = componentWidth - 2 * WIDGET_PADDING ;
2024-09-18 16:35:28 +00:00
2022-09-22 11:15:00 +00:00
return { componentHeight , componentWidth } ;
} ;
2023-09-11 15:55:11 +00:00
getWidgetView() {
2022-07-14 07:02:35 +00:00
const {
feat: Update TableWidgetV2 to include customIsLoading property (#36857)
## Description
<ins>Problem</ins>
There are many problems with table loader logic, for which many users
try to implement a modal for loader. These problems stem from dependency
and delay on eval, discussed comprehensively in #12308
<ins>Solution</ins>
This PR updates the TableWidgetV2 component to include a new property
called `customIsLoading`. This property controls the loading state of
the widget and is added to the TableWidgetProps interface. Additionally,
the component's state is updated to include the `customIsLoading`
property.
The `contentConfig` file for the TableWidgetV2 is also modified to
include the `customIsLoading` property with its corresponding label,
control type, help text, and validation.
These changes improve the flexibility and customization options of the
TableWidgetV2 component.
Fixes #12308
_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"
### :mag: 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/11456273525>
> Commit: 1c6f4f9caabc3aa45ec3916e5ccb465d946ab0a1
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=11456273525&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Table`
> Spec:
> <hr>Tue, 22 Oct 2024 09:17:37 UTC
<!-- end of auto-generated comment: Cypress test results -->
## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Introduced a new feature flag for custom loading states in the table
widget.
- Added properties for managing custom loading behavior in the
`TableWidgetV2`.
- **Bug Fixes**
- Enhanced loading state management to ensure accurate representation
based on new properties.
- **Tests**
- Added unit tests for loading behavior in the `TableWidgetV2`
component, covering default and custom loading scenarios.
- **Documentation**
- Updated help text for properties related to loading states to improve
clarity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-10-22 09:21:45 +00:00
customIsLoading ,
customIsLoadingValue ,
feat: adds custom sort function feature flag for table widget (#39649)
## Description
### Custom Sort Function for Table Widget
This PR introduces a new custom sort function feature for the Table
Widget, allowing users to define their own sorting logic for table data.
**Feature Flag Implementation**
- Added a new feature flag `release_table_custom_sort_function_enabled`
to control the availability of this feature
- Set the default value to `false` to allow for controlled rollout
**Table Widget Updates**
- Added `customSortFunction` property to the TableWidgetV2 props
interface
- Updated property configuration to include the custom sort function
control
- Added helper text and placeholder to guide users on proper
implementation
- Implemented visibility condition based on the feature flag and
sortable state
**Control Updates**
- Enhanced `TableCustomSortControl` to support the new custom sorting
logic
- Improved error handling in the sort function implementation
- Updated the binding structure to provide access to original data with
`original_` prefix
- Modified the function signature to use `(tableData, column, order)`
parameters
**Testing**
- Updated test cases to verify the extraction and computation of custom
sort expressions
- Implemented round-trip testing to ensure proper function parsing and
generation
- Added tests for edge cases like empty strings and non-matching formats
This feature enables users to implement complex sorting logic beyond the
default column-based sorting, such as multi-column sorting, custom
collation, or business-specific ordering rules.
Fixes #https://github.com/appsmithorg/appsmith-ee/issues/6503
_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.Sanity, @tag.Datasource"
### :mag: 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/13986693183>
> Commit: b64c2919c5d8eb1b82f6f8e6c76b98a60c2a6e22
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=13986693183&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Table, @tag.Sanity, @tag.Datasource`
> Spec:
> <hr>Fri, 21 Mar 2025 08:35:48 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
- **New Features**
- Introduced custom table sorting, enabling users to define and apply
their own sorting logic in table displays.
- Added a new control interface for configuring custom sort functions,
offering enhanced flexibility in data presentation.
- Integrated an option to enable or disable the custom sort
functionality based on feature flags.
- Enhanced the table widget to support custom sorting function data,
allowing for dynamic data presentation.
- Added a new property configuration for custom sorting functions within
the table widget.
- **Tests**
- Implemented comprehensive tests to verify custom sorting input
processing and ensure accurate data computation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-03-21 10:15:29 +00:00
customSortFunction : customSortFunctionData ,
2022-07-14 07:02:35 +00:00
delimiter ,
filteredTableData = [ ] ,
isVisibleDownload ,
isVisibleFilters ,
isVisiblePagination ,
isVisibleSearch ,
2023-10-11 07:14:38 +00:00
pageSize ,
2023-06-01 17:26:05 +00:00
primaryColumns ,
2023-10-11 07:14:38 +00:00
totalRecordsCount ,
2022-07-14 07:02:35 +00:00
} = this . props ;
2023-06-01 17:26:05 +00:00
2023-03-30 04:54:29 +00:00
const tableColumns = this . getTableColumns ( ) || emptyArr ;
feat: adds custom sort function feature flag for table widget (#39649)
## Description
### Custom Sort Function for Table Widget
This PR introduces a new custom sort function feature for the Table
Widget, allowing users to define their own sorting logic for table data.
**Feature Flag Implementation**
- Added a new feature flag `release_table_custom_sort_function_enabled`
to control the availability of this feature
- Set the default value to `false` to allow for controlled rollout
**Table Widget Updates**
- Added `customSortFunction` property to the TableWidgetV2 props
interface
- Updated property configuration to include the custom sort function
control
- Added helper text and placeholder to guide users on proper
implementation
- Implemented visibility condition based on the feature flag and
sortable state
**Control Updates**
- Enhanced `TableCustomSortControl` to support the new custom sorting
logic
- Improved error handling in the sort function implementation
- Updated the binding structure to provide access to original data with
`original_` prefix
- Modified the function signature to use `(tableData, column, order)`
parameters
**Testing**
- Updated test cases to verify the extraction and computation of custom
sort expressions
- Implemented round-trip testing to ensure proper function parsing and
generation
- Added tests for edge cases like empty strings and non-matching formats
This feature enables users to implement complex sorting logic beyond the
default column-based sorting, such as multi-column sorting, custom
collation, or business-specific ordering rules.
Fixes #https://github.com/appsmithorg/appsmith-ee/issues/6503
_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.Sanity, @tag.Datasource"
### :mag: 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/13986693183>
> Commit: b64c2919c5d8eb1b82f6f8e6c76b98a60c2a6e22
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=13986693183&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Table, @tag.Sanity, @tag.Datasource`
> Spec:
> <hr>Fri, 21 Mar 2025 08:35:48 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
- **New Features**
- Introduced custom table sorting, enabling users to define and apply
their own sorting logic in table displays.
- Added a new control interface for configuring custom sort functions,
offering enhanced flexibility in data presentation.
- Integrated an option to enable or disable the custom sort
functionality based on feature flags.
- Enhanced the table widget to support custom sorting function data,
allowing for dynamic data presentation.
- Added a new property configuration for custom sorting functions within
the table widget.
- **Tests**
- Implemented comprehensive tests to verify custom sorting input
processing and ensure accurate data computation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-03-21 10:15:29 +00:00
let data = filteredTableData ;
if ( customSortFunctionData && Array . isArray ( customSortFunctionData ) ) {
data = customSortFunctionData ;
}
const transformedData = this . transformData ( data , tableColumns ) ;
2022-07-14 07:02:35 +00:00
const isVisibleHeaderOptions =
isVisibleDownload ||
isVisibleFilters ||
isVisiblePagination ||
isVisibleSearch ;
chore: upgrade to prettier v2 + enforce import types (#21013)Co-authored-by: Satish Gandham <hello@satishgandham.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
## Description
This PR upgrades Prettier to v2 + enforces TypeScript’s [`import
type`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export)
syntax where applicable. It’s submitted as a separate PR so we can merge
it easily.
As a part of this PR, we reformat the codebase heavily:
- add `import type` everywhere where it’s required, and
- re-format the code to account for Prettier 2’s breaking changes:
https://prettier.io/blog/2020/03/21/2.0.0.html#breaking-changes
This PR is submitted against `release` to make sure all new code by team
members will adhere to new formatting standards, and we’ll have fewer
conflicts when merging `bundle-optimizations` into `release`. (I’ll
merge `release` back into `bundle-optimizations` once this PR is
merged.)
### Why is this needed?
This PR is needed because, for the Lodash optimization from
https://github.com/appsmithorg/appsmith/commit/7cbb12af886621256224be0c93e6a465dd710ad3,
we need to use `import type`. Otherwise, `babel-plugin-lodash` complains
that `LoDashStatic` is not a lodash function.
However, just using `import type` in the current codebase will give you
this:
<img width="962" alt="Screenshot 2023-03-08 at 17 45 59"
src="https://user-images.githubusercontent.com/2953267/223775744-407afa0c-e8b9-44a1-90f9-b879348da57f.png">
That’s because Prettier 1 can’t parse `import type` at all. To parse it,
we need to upgrade to Prettier 2.
### Why enforce `import type`?
Apart from just enabling `import type` support, this PR enforces
specifying `import type` everywhere it’s needed. (Developers will get
immediate TypeScript and ESLint errors when they forget to do so.)
I’m doing this because I believe `import type` improves DX and makes
refactorings easier.
Let’s say you had a few imports like below. Can you tell which of these
imports will increase the bundle size? (Tip: it’s not all of them!)
```ts
// app/client/src/workers/Linting/utils.ts
import { Position } from "codemirror";
import { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
It’s pretty hard, right?
What about now?
```ts
// app/client/src/workers/Linting/utils.ts
import type { Position } from "codemirror";
import type { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
Now, it’s clear that only `lodash` will be bundled.
This helps developers to see which imports are problematic, but it
_also_ helps with refactorings. Now, if you want to see where
`codemirror` is bundled, you can just grep for `import \{.*\} from
"codemirror"` – and you won’t get any type-only imports.
This also helps (some) bundlers. Upon transpiling, TypeScript erases
type-only imports completely. In some environment (not ours), this makes
the bundle smaller, as the bundler doesn’t need to bundle type-only
imports anymore.
## Type of change
- Chore (housekeeping or task changes that don't impact user perception)
## How Has This Been Tested?
This was tested to not break the build.
### 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
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] 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
---------
Co-authored-by: Satish Gandham <hello@satishgandham.com>
Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
2023-03-16 11:41:47 +00:00
const { componentHeight , componentWidth } =
this . getPaddingAdjustedDimensions ( ) ;
2023-03-30 04:54:29 +00:00
const finalTableData = this . memoisedAddNewRow (
transformedData ,
this . props . isAddRowInProgress ,
this . props . newRowContent ,
) ;
2022-11-05 09:54:20 +00:00
2022-07-14 07:02:35 +00:00
return (
< Suspense fallback = { < Skeleton / > } >
< ReactTableComponent
accentColor = { this . props . accentColor }
2022-11-05 09:54:20 +00:00
allowAddNewRow = { this . props . allowAddNewRow }
allowRowSelection = { ! this . props . isAddRowInProgress }
allowSorting = { ! this . props . isAddRowInProgress }
2022-07-14 07:02:35 +00:00
applyFilter = { this . updateFilters }
2022-10-14 04:53:31 +00:00
borderColor = { this . props . borderColor }
2022-07-14 07:02:35 +00:00
borderRadius = { this . props . borderRadius }
2022-10-14 04:53:31 +00:00
borderWidth = { this . props . borderWidth }
2022-07-14 07:02:35 +00:00
boxShadow = { this . props . boxShadow }
2023-02-15 11:42:46 +00:00
canFreezeColumn = { this . props . canFreezeColumn }
2022-07-14 07:02:35 +00:00
columnWidthMap = { this . props . columnWidthMap }
columns = { tableColumns }
compactMode = { this . props . compactMode || CompactModeTypes . DEFAULT }
delimiter = { delimiter }
disableDrag = { this . toggleDrag }
2022-11-05 09:54:20 +00:00
disabledAddNewRowSave = { this . hasInvalidColumnCell ( ) }
2022-07-14 07:02:35 +00:00
editMode = { this . props . renderMode === RenderModes . CANVAS }
editableCell = { this . props . editableCell }
2025-04-02 08:32:54 +00:00
endOfData = { this . props . endOfData }
2022-07-14 07:02:35 +00:00
filters = { this . props . filters }
2023-02-15 11:42:46 +00:00
handleColumnFreeze = { this . handleColumnFreeze }
2022-07-14 07:02:35 +00:00
handleReorderColumn = { this . handleReorderColumn }
handleResizeColumn = { this . handleResizeColumn }
height = { componentHeight }
2022-11-05 09:54:20 +00:00
isAddRowInProgress = { this . props . isAddRowInProgress }
isEditableCellsValid = { this . props . isEditableCellsValid }
2025-02-14 09:39:24 +00:00
isInfiniteScrollEnabled = { this . props . infiniteScrollEnabled }
feat: Update TableWidgetV2 to include customIsLoading property (#36857)
## Description
<ins>Problem</ins>
There are many problems with table loader logic, for which many users
try to implement a modal for loader. These problems stem from dependency
and delay on eval, discussed comprehensively in #12308
<ins>Solution</ins>
This PR updates the TableWidgetV2 component to include a new property
called `customIsLoading`. This property controls the loading state of
the widget and is added to the TableWidgetProps interface. Additionally,
the component's state is updated to include the `customIsLoading`
property.
The `contentConfig` file for the TableWidgetV2 is also modified to
include the `customIsLoading` property with its corresponding label,
control type, help text, and validation.
These changes improve the flexibility and customization options of the
TableWidgetV2 component.
Fixes #12308
_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"
### :mag: 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/11456273525>
> Commit: 1c6f4f9caabc3aa45ec3916e5ccb465d946ab0a1
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=11456273525&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Table`
> Spec:
> <hr>Tue, 22 Oct 2024 09:17:37 UTC
<!-- end of auto-generated comment: Cypress test results -->
## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Introduced a new feature flag for custom loading states in the table
widget.
- Added properties for managing custom loading behavior in the
`TableWidgetV2`.
- **Bug Fixes**
- Enhanced loading state management to ensure accurate representation
based on new properties.
- **Tests**
- Added unit tests for loading behavior in the `TableWidgetV2`
component, covering default and custom loading scenarios.
- **Documentation**
- Updated help text for properties related to loading states to improve
clarity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-10-22 09:21:45 +00:00
isLoading = {
customIsLoading
? customIsLoadingValue || this . props . isLoading
: this . props . isLoading
}
2022-07-14 07:02:35 +00:00
isSortable = { this . props . isSortable ? ? true }
isVisibleDownload = { isVisibleDownload }
isVisibleFilters = { isVisibleFilters }
isVisiblePagination = { isVisiblePagination }
isVisibleSearch = { isVisibleSearch }
2022-11-05 09:54:20 +00:00
multiRowSelection = {
this . props . multiRowSelection && ! this . props . isAddRowInProgress
}
2022-07-14 07:02:35 +00:00
nextPageClick = { this . handleNextPageClick }
2022-11-05 09:54:20 +00:00
onAddNewRow = { this . handleAddNewRowClick }
onAddNewRowAction = { this . handleAddNewRowAction }
2022-07-14 07:02:35 +00:00
onBulkEditDiscard = { this . onBulkEditDiscard }
onBulkEditSave = { this . onBulkEditSave }
2023-06-01 17:26:05 +00:00
onConnectData = { this . onConnectData }
2022-07-14 07:02:35 +00:00
onRowClick = { this . handleRowClick }
pageNo = { this . props . pageNo }
pageSize = {
isVisibleHeaderOptions ? Math . max ( 1 , pageSize ) : pageSize + 1
}
prevPageClick = { this . handlePrevPageClick }
2022-10-06 09:32:09 +00:00
primaryColumnId = { this . props . primaryColumnId }
2022-07-14 07:02:35 +00:00
searchKey = { this . props . searchText }
searchTableData = { this . handleSearchTable }
selectAllRow = { this . handleAllRowSelect }
selectedRowIndex = {
this . props . selectedRowIndex === undefined
? - 1
: this . props . selectedRowIndex
}
selectedRowIndices = { this . getSelectedRowIndices ( ) }
serverSidePaginationEnabled = { ! ! this . props . serverSidePaginationEnabled }
2023-06-01 17:26:05 +00:00
showConnectDataOverlay = {
primaryColumns &&
! Object . keys ( primaryColumns ) . length &&
this . props . renderMode === RenderModes . CANVAS
}
2022-07-14 07:02:35 +00:00
sortTableColumn = { this . handleColumnSorting }
2023-03-30 04:54:29 +00:00
tableData = { finalTableData }
2022-07-14 07:02:35 +00:00
totalRecordsCount = { totalRecordsCount }
triggerRowSelection = { this . props . triggerRowSelection }
unSelectAllRow = { this . unSelectAllRow }
updatePageNo = { this . updatePageNumber }
2022-10-14 04:53:31 +00:00
variant = { this . props . variant }
2022-07-14 07:02:35 +00:00
widgetId = { this . props . widgetId }
widgetName = { this . props . widgetName }
width = { componentWidth }
/ >
< / Suspense >
) ;
}
2023-02-15 11:42:46 +00:00
/ * *
* Function to update or add the tableWidgetColumnOrder key in the local storage
* tableWidgetColumnOrder = {
* < widget - id > : {
* columnOrder : [ ] ,
* leftOrder : [ ] ,
* rightOrder : [ ] ,
* }
* }
* /
persistColumnOrder = (
newColumnOrder : string [ ] ,
leftOrder : string [ ] ,
rightOrder : string [ ] ,
) = > {
const widgetId = this . props . widgetId ;
const localTableWidgetColumnOrder = localStorage . getItem (
TABLE_COLUMN_ORDER_KEY ,
) ;
let newTableColumnOrder ;
if ( localTableWidgetColumnOrder ) {
try {
let parsedTableWidgetColumnOrder = JSON . parse (
localTableWidgetColumnOrder ,
) ;
let columnOrder ;
if ( newColumnOrder ) {
columnOrder = newColumnOrder ;
} else if ( parsedTableWidgetColumnOrder [ widgetId ] ) {
columnOrder = parsedTableWidgetColumnOrder [ widgetId ] ;
} else {
columnOrder = this . props . columnOrder ;
}
parsedTableWidgetColumnOrder = {
. . . parsedTableWidgetColumnOrder ,
[ widgetId ] : {
columnOrder ,
columnUpdatedAt : this.props.columnUpdatedAt ,
leftOrder ,
rightOrder ,
} ,
} ;
newTableColumnOrder = parsedTableWidgetColumnOrder ;
} catch ( e ) {
log . debug ( "Unable to parse local column order:" , { e } ) ;
}
} else {
const tableWidgetColumnOrder = {
[ widgetId ] : {
columnOrder : newColumnOrder ,
columnUpdatedAt : this.props.columnUpdatedAt ,
leftOrder ,
rightOrder ,
} ,
} ;
2024-09-18 16:35:28 +00:00
2023-02-15 11:42:46 +00:00
newTableColumnOrder = tableWidgetColumnOrder ;
}
2024-09-18 16:35:28 +00:00
2023-02-15 11:42:46 +00:00
localStorage . setItem (
TABLE_COLUMN_ORDER_KEY ,
JSON . stringify ( newTableColumnOrder ) ,
) ;
} ;
handleColumnFreeze = ( columnName : string , sticky? : StickyType ) = > {
if ( this . props . columnOrder ) {
let newColumnOrder ;
const localTableColumnOrder = getColumnOrderByWidgetIdFromLS (
this . props . widgetId ,
) ;
2024-09-18 16:35:28 +00:00
2023-02-15 11:42:46 +00:00
if ( this . props . renderMode === RenderModes . CANVAS ) {
newColumnOrder = generateNewColumnOrderFromStickyValue (
this . props . primaryColumns ,
this . props . columnOrder ,
columnName ,
sticky ,
) ;
// Updating these properties in batch so that undo/redo gets executed in a combined way.
super . batchUpdateWidgetProperty (
{
modify : {
[ ` primaryColumns. ${ columnName } .sticky ` ] : sticky ,
columnOrder : newColumnOrder ,
} ,
} ,
true ,
) ;
} else if (
localTableColumnOrder &&
this . props . renderMode === RenderModes . PAGE
) {
const { leftOrder , rightOrder } = localTableColumnOrder ;
2024-09-18 16:35:28 +00:00
2023-02-15 11:42:46 +00:00
newColumnOrder = generateLocalNewColumnOrderFromStickyValue (
localTableColumnOrder . columnOrder ,
columnName ,
sticky ,
leftOrder ,
rightOrder ,
) ;
const updatedOrders = updateAndSyncTableLocalColumnOrders (
columnName ,
leftOrder ,
rightOrder ,
sticky ,
) ;
2024-09-18 16:35:28 +00:00
2023-02-15 11:42:46 +00:00
this . persistColumnOrder (
newColumnOrder ,
updatedOrders . leftOrder ,
updatedOrders . rightOrder ,
) ;
2023-06-27 05:15:41 +00:00
super . batchUpdateWidgetProperty (
{
modify : {
[ ` primaryColumns. ${ columnName } .sticky ` ] : sticky ,
columnOrder : newColumnOrder ,
} ,
} ,
true ,
) ;
2023-02-15 11:42:46 +00:00
}
}
} ;
2022-07-14 07:02:35 +00:00
handleReorderColumn = ( columnOrder : string [ ] ) = > {
columnOrder = columnOrder . map ( ( alias ) = > this . getColumnIdByAlias ( alias ) ) ;
2023-06-27 05:15:41 +00:00
if (
this . props . canFreezeColumn &&
this . props . renderMode === RenderModes . PAGE
) {
const localTableColumnOrder = getColumnOrderByWidgetIdFromLS (
this . props . widgetId ,
) ;
2024-09-18 16:35:28 +00:00
2023-06-27 05:15:41 +00:00
if ( localTableColumnOrder ) {
const { leftOrder , rightOrder } = localTableColumnOrder ;
2024-09-18 16:35:28 +00:00
2023-06-27 05:15:41 +00:00
this . persistColumnOrder ( columnOrder , leftOrder , rightOrder ) ;
} else {
this . persistColumnOrder ( columnOrder , [ ] , [ ] ) ;
2023-02-15 11:42:46 +00:00
}
2022-07-14 07:02:35 +00:00
}
2023-06-27 05:15:41 +00:00
super . updateWidgetProperty ( "columnOrder" , columnOrder ) ;
2022-07-14 07:02:35 +00:00
} ;
handleColumnSorting = ( columnAccessor : string , isAsc : boolean ) = > {
const columnId = this . getColumnIdByAlias ( columnAccessor ) ;
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates , pushBatchMetaUpdates } = this . props ;
this . pushResetSelectedRowIndexUpdates ( false ) ;
2022-07-14 07:02:35 +00:00
let sortOrderProps ;
if ( columnId ) {
sortOrderProps = {
column : columnId ,
order : isAsc ? SortOrderTypes.asc : SortOrderTypes.desc ,
} ;
} else {
sortOrderProps = {
column : "" ,
order : null ,
} ;
}
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "sortOrder" , sortOrderProps , {
2022-07-14 07:02:35 +00:00
triggerPropertyName : "onSort" ,
dynamicString : this.props.onSort ,
event : {
type : EventType . ON_SORT ,
} ,
} ) ;
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates ( ) ;
2022-07-14 07:02:35 +00:00
} ;
handleResizeColumn = ( columnWidthMap : { [ key : string ] : number } ) = > {
if ( this . props . renderMode === RenderModes . CANVAS ) {
super . updateWidgetProperty ( "columnWidthMap" , columnWidthMap ) ;
} else {
2023-03-30 04:54:29 +00:00
//single action no need to batch
2022-07-14 07:02:35 +00:00
this . props . updateWidgetMetaProperty ( "columnWidthMap" , columnWidthMap ) ;
}
} ;
2024-07-31 15:41:28 +00:00
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2022-07-14 07:02:35 +00:00
handleSearchTable = ( searchKey : any ) = > {
2023-03-30 04:54:29 +00:00
const {
commitBatchMetaUpdates ,
multiRowSelection ,
onSearchTextChanged ,
pushBatchMetaUpdates ,
} = this . props ;
2022-07-14 07:02:35 +00:00
/ *
* Clear rowSelection to avoid selecting filtered rows
* based on stale selection indices
* /
if ( multiRowSelection ) {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "selectedRowIndices" , [ ] ) ;
2022-07-14 07:02:35 +00:00
} else {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "selectedRowIndex" , - 1 ) ;
2022-07-14 07:02:35 +00:00
}
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "pageNo" , 1 ) ;
2023-04-13 10:08:46 +00:00
this . updatePaginationDirectionFlags ( PaginationDirection . INITIAL ) ;
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "searchText" , searchKey , {
2022-07-14 07:02:35 +00:00
triggerPropertyName : "onSearchTextChanged" ,
dynamicString : onSearchTextChanged ,
event : {
type : EventType . ON_SEARCH ,
} ,
} ) ;
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates ( ) ;
2022-07-14 07:02:35 +00:00
} ;
2023-03-30 04:54:29 +00:00
/ * *
* This function just pushes the meta update
* /
pushOnColumnEvent = ( {
action ,
2023-10-11 07:14:38 +00:00
additionalData = { } ,
2023-03-30 04:54:29 +00:00
eventType ,
2023-10-11 07:14:38 +00:00
onComplete = noop ,
2023-03-30 04:54:29 +00:00
row ,
2023-10-11 07:14:38 +00:00
rowIndex ,
triggerPropertyName ,
2023-03-30 04:54:29 +00:00
} : OnColumnEventArgs ) = > {
const { filteredTableData = [ ] , pushBatchMetaUpdates } = this . props ;
const currentRow = row || filteredTableData [ rowIndex ] ;
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates (
"triggeredRowIndex" ,
currentRow ? . [ ORIGINAL_INDEX_KEY ] ,
{
triggerPropertyName : triggerPropertyName ,
dynamicString : action ,
event : {
type : eventType ,
callback : onComplete ,
} ,
globalContext : { currentRow , . . . additionalData } ,
} ,
) ;
} ;
2022-07-14 07:02:35 +00:00
/ *
* Function to handle customColumn button type click interactions
* /
onColumnEvent = ( {
action ,
2023-10-11 07:14:38 +00:00
additionalData = { } ,
2022-07-14 07:02:35 +00:00
eventType ,
2023-10-11 07:14:38 +00:00
onComplete = noop ,
2022-07-14 07:02:35 +00:00
row ,
2023-10-11 07:14:38 +00:00
rowIndex ,
triggerPropertyName ,
2022-07-14 07:02:35 +00:00
} : OnColumnEventArgs ) = > {
2023-03-30 04:54:29 +00:00
if ( action ) {
const { commitBatchMetaUpdates } = this . props ;
2022-07-14 07:02:35 +00:00
2023-03-30 04:54:29 +00:00
this . pushOnColumnEvent ( {
rowIndex ,
action ,
onComplete ,
triggerPropertyName ,
eventType ,
row ,
additionalData ,
} ) ;
commitBatchMetaUpdates ( ) ;
} else {
onComplete ( ) ;
2022-07-14 07:02:35 +00:00
}
} ;
onDropdownOptionSelect = ( action : string ) = > {
super . executeAction ( {
dynamicString : action ,
event : {
type : EventType . ON_OPTION_CHANGE ,
} ,
} ) ;
} ;
handleAllRowSelect = ( pageData : Record < string , unknown > [ ] ) = > {
if ( this . props . multiRowSelection ) {
const selectedRowIndices = pageData . map (
( row : Record < string , unknown > ) = > row . index ,
) ;
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
//single action no need to batch
2022-07-14 07:02:35 +00:00
this . props . updateWidgetMetaProperty (
"selectedRowIndices" ,
selectedRowIndices ,
) ;
}
} ;
handleRowClick = ( row : Record < string , unknown > , selectedIndex : number ) = > {
chore: upgrade to prettier v2 + enforce import types (#21013)Co-authored-by: Satish Gandham <hello@satishgandham.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
## Description
This PR upgrades Prettier to v2 + enforces TypeScript’s [`import
type`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export)
syntax where applicable. It’s submitted as a separate PR so we can merge
it easily.
As a part of this PR, we reformat the codebase heavily:
- add `import type` everywhere where it’s required, and
- re-format the code to account for Prettier 2’s breaking changes:
https://prettier.io/blog/2020/03/21/2.0.0.html#breaking-changes
This PR is submitted against `release` to make sure all new code by team
members will adhere to new formatting standards, and we’ll have fewer
conflicts when merging `bundle-optimizations` into `release`. (I’ll
merge `release` back into `bundle-optimizations` once this PR is
merged.)
### Why is this needed?
This PR is needed because, for the Lodash optimization from
https://github.com/appsmithorg/appsmith/commit/7cbb12af886621256224be0c93e6a465dd710ad3,
we need to use `import type`. Otherwise, `babel-plugin-lodash` complains
that `LoDashStatic` is not a lodash function.
However, just using `import type` in the current codebase will give you
this:
<img width="962" alt="Screenshot 2023-03-08 at 17 45 59"
src="https://user-images.githubusercontent.com/2953267/223775744-407afa0c-e8b9-44a1-90f9-b879348da57f.png">
That’s because Prettier 1 can’t parse `import type` at all. To parse it,
we need to upgrade to Prettier 2.
### Why enforce `import type`?
Apart from just enabling `import type` support, this PR enforces
specifying `import type` everywhere it’s needed. (Developers will get
immediate TypeScript and ESLint errors when they forget to do so.)
I’m doing this because I believe `import type` improves DX and makes
refactorings easier.
Let’s say you had a few imports like below. Can you tell which of these
imports will increase the bundle size? (Tip: it’s not all of them!)
```ts
// app/client/src/workers/Linting/utils.ts
import { Position } from "codemirror";
import { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
It’s pretty hard, right?
What about now?
```ts
// app/client/src/workers/Linting/utils.ts
import type { Position } from "codemirror";
import type { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
Now, it’s clear that only `lodash` will be bundled.
This helps developers to see which imports are problematic, but it
_also_ helps with refactorings. Now, if you want to see where
`codemirror` is bundled, you can just grep for `import \{.*\} from
"codemirror"` – and you won’t get any type-only imports.
This also helps (some) bundlers. Upon transpiling, TypeScript erases
type-only imports completely. In some environment (not ours), this makes
the bundle smaller, as the bundler doesn’t need to bundle type-only
imports anymore.
## Type of change
- Chore (housekeeping or task changes that don't impact user perception)
## How Has This Been Tested?
This was tested to not break the build.
### 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
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] 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
---------
Co-authored-by: Satish Gandham <hello@satishgandham.com>
Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
2023-03-16 11:41:47 +00:00
const { multiRowSelection , selectedRowIndex , selectedRowIndices } =
this . props ;
2023-03-30 04:54:29 +00:00
// no need to batch actions here because it a time only one will execute
2022-07-14 07:02:35 +00:00
if ( multiRowSelection ) {
let indices : Array < number > ;
if ( _ . isArray ( selectedRowIndices ) ) {
indices = [ . . . selectedRowIndices ] ;
} else {
indices = [ ] ;
}
/ *
* Deselect if the index is already present
* /
if ( indices . includes ( selectedIndex ) ) {
indices . splice ( indices . indexOf ( selectedIndex ) , 1 ) ;
this . props . updateWidgetMetaProperty ( "selectedRowIndices" , indices ) ;
} else {
/ *
* select if the index is not present already
* /
indices . push ( selectedIndex ) ;
this . props . updateWidgetMetaProperty ( "selectedRowIndices" , indices , {
triggerPropertyName : "onRowSelected" ,
dynamicString : this.props.onRowSelected ,
event : {
type : EventType . ON_ROW_SELECTED ,
} ,
} ) ;
}
} else {
let index ;
if ( isNumber ( selectedRowIndex ) ) {
index = selectedRowIndex ;
} else {
index = - 1 ;
}
if ( index !== selectedIndex ) {
this . props . updateWidgetMetaProperty ( "selectedRowIndex" , selectedIndex , {
triggerPropertyName : "onRowSelected" ,
dynamicString : this.props.onRowSelected ,
event : {
type : EventType . ON_ROW_SELECTED ,
} ,
} ) ;
} else {
this . props . updateWidgetMetaProperty ( "selectedRowIndex" , - 1 ) ;
}
}
} ;
updatePageNumber = ( pageNo : number , event? : EventType ) = > {
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates , pushBatchMetaUpdates } = this . props ;
2023-04-13 10:08:46 +00:00
const paginationDirection =
event == EventType . ON_NEXT_PAGE
? PaginationDirection . NEXT_PAGE
: PaginationDirection . PREVIOUS_PAGE ;
2024-09-18 16:35:28 +00:00
2023-04-13 10:08:46 +00:00
this . updatePaginationDirectionFlags ( paginationDirection ) ;
2022-07-14 07:02:35 +00:00
if ( event ) {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "pageNo" , pageNo , {
2022-07-14 07:02:35 +00:00
triggerPropertyName : "onPageChange" ,
dynamicString : this.props.onPageChange ,
event : {
type : event ,
} ,
} ) ;
} else {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "pageNo" , pageNo ) ;
2022-07-14 07:02:35 +00:00
}
if ( this . props . onPageChange ) {
2023-03-30 04:54:29 +00:00
this . pushResetSelectedRowIndexUpdates ( ) ;
2022-07-14 07:02:35 +00:00
}
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates ( ) ;
2022-07-14 07:02:35 +00:00
} ;
2023-04-13 10:08:46 +00:00
updatePaginationDirectionFlags = ( direction? : PaginationDirection ) = > {
const { pushBatchMetaUpdates } = this . props ;
let previousButtonFlag = false ;
let nextButtonFlag = false ;
if ( direction ) {
switch ( direction ) {
case PaginationDirection . INITIAL : {
previousButtonFlag = false ;
nextButtonFlag = false ;
break ;
}
case PaginationDirection . NEXT_PAGE : {
nextButtonFlag = true ;
break ;
}
case PaginationDirection . PREVIOUS_PAGE : {
previousButtonFlag = true ;
break ;
}
}
}
pushBatchMetaUpdates ( "previousPageVisited" , previousButtonFlag ) ;
pushBatchMetaUpdates ( "nextPageVisited" , nextButtonFlag ) ;
} ;
2022-07-14 07:02:35 +00:00
handleNextPageClick = ( ) = > {
const pageNo = ( this . props . pageNo || 1 ) + 1 ;
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates , pushBatchMetaUpdates } = this . props ;
2022-07-14 07:02:35 +00:00
2023-04-13 10:08:46 +00:00
this . updatePaginationDirectionFlags ( PaginationDirection . NEXT_PAGE ) ;
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "pageNo" , pageNo , {
2022-07-14 07:02:35 +00:00
triggerPropertyName : "onPageChange" ,
dynamicString : this.props.onPageChange ,
event : {
type : EventType . ON_NEXT_PAGE ,
} ,
} ) ;
if ( this . props . onPageChange ) {
2023-03-30 04:54:29 +00:00
this . pushResetSelectedRowIndexUpdates ( ) ;
2022-07-14 07:02:35 +00:00
}
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates ( ) ;
2022-07-14 07:02:35 +00:00
} ;
2023-03-30 04:54:29 +00:00
pushResetSelectedRowIndexUpdates = ( skipDefault? : boolean ) = > {
const { pushBatchMetaUpdates } = this . props ;
2022-07-14 07:02:35 +00:00
const {
defaultSelectedRowIndex ,
defaultSelectedRowIndices ,
multiRowSelection ,
} = this . props ;
if ( multiRowSelection ) {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates (
2022-07-14 07:02:35 +00:00
"selectedRowIndices" ,
skipDefault ? [ ] : defaultSelectedRowIndices ,
) ;
} else {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates (
2022-07-14 07:02:35 +00:00
"selectedRowIndex" ,
skipDefault ? - 1 : defaultSelectedRowIndex ,
) ;
}
} ;
unSelectAllRow = ( ) = > {
this . props . updateWidgetMetaProperty ( "selectedRowIndices" , [ ] ) ;
} ;
handlePrevPageClick = ( ) = > {
const pageNo = ( this . props . pageNo || 1 ) - 1 ;
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates , pushBatchMetaUpdates } = this . props ;
2022-07-14 07:02:35 +00:00
if ( pageNo >= 1 ) {
2023-04-13 10:08:46 +00:00
this . updatePaginationDirectionFlags ( PaginationDirection . PREVIOUS_PAGE ) ;
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "pageNo" , pageNo , {
2022-07-14 07:02:35 +00:00
triggerPropertyName : "onPageChange" ,
dynamicString : this.props.onPageChange ,
event : {
type : EventType . ON_PREV_PAGE ,
} ,
} ) ;
if ( this . props . onPageChange ) {
2023-03-30 04:54:29 +00:00
this . pushResetSelectedRowIndexUpdates ( ) ;
2022-07-14 07:02:35 +00:00
}
}
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates ( ) ;
2022-07-14 07:02:35 +00:00
} ;
getColumnIdByAlias ( alias : string ) {
const { primaryColumns } = this . props ;
if ( primaryColumns ) {
const column = Object . values ( primaryColumns ) . find (
( column ) = > column . alias === alias ,
) ;
if ( column ) {
return column . id ;
}
}
return alias ;
}
getColumnByOriginalId ( originalId : string ) {
return Object . values ( this . props . primaryColumns ) . find ( ( column ) = > {
return column . originalId === originalId ;
} ) ;
}
2023-03-30 04:54:29 +00:00
pushTransientTableDataActionsUpdates = ( data : TransientDataPayload ) = > {
2022-11-05 09:54:20 +00:00
const { __originalIndex__ , . . . transientData } = data ;
2023-03-30 04:54:29 +00:00
const { pushBatchMetaUpdates } = this . props ;
2022-07-14 07:02:35 +00:00
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "transientTableData" , {
2022-07-14 07:02:35 +00:00
. . . this . props . transientTableData ,
2022-11-05 09:54:20 +00:00
[ __originalIndex__ ] : {
. . . this . props . transientTableData [ __originalIndex__ ] ,
2022-07-14 07:02:35 +00:00
. . . transientData ,
} ,
} ) ;
2022-11-25 04:39:59 +00:00
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "updatedRowIndex" , __originalIndex__ ) ;
2022-07-14 07:02:35 +00:00
} ;
2022-11-05 09:54:20 +00:00
removeRowFromTransientTableData = ( index : number ) = > {
2024-08-29 07:05:48 +00:00
const newTransientTableData = klonaRegularWithTelemetry (
this . props . transientTableData ,
"TableWidgetV2.removeRowFromTransientTableData" ,
) ;
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates , pushBatchMetaUpdates } = this . props ;
2022-07-14 07:02:35 +00:00
if ( newTransientTableData ) {
2022-11-05 09:54:20 +00:00
delete newTransientTableData [ index ] ;
2022-07-14 07:02:35 +00:00
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "transientTableData" , newTransientTableData ) ;
2022-07-14 07:02:35 +00:00
}
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "updatedRowIndex" , - 1 ) ;
commitBatchMetaUpdates ( ) ;
2022-07-14 07:02:35 +00:00
} ;
getRowOriginalIndex = ( index : number ) = > {
const { filteredTableData } = this . props ;
if ( filteredTableData ) {
const row = filteredTableData [ index ] ;
if ( row ) {
return row [ ORIGINAL_INDEX_KEY ] ;
}
}
return - 1 ;
} ;
onBulkEditSave = ( ) = > {
this . props . updateWidgetMetaProperty (
"transientTableData" ,
this . props . transientTableData ,
{
triggerPropertyName : "onBulkSave" ,
dynamicString : this.props.onBulkSave ,
event : {
type : EventType . ON_BULK_SAVE ,
} ,
} ,
) ;
} ;
onBulkEditDiscard = ( ) = > {
this . props . updateWidgetMetaProperty (
"transientTableData" ,
{ } ,
{
triggerPropertyName : "onBulkDiscard" ,
dynamicString : this.props.onBulkDiscard ,
event : {
type : EventType . ON_BULK_DISCARD ,
} ,
} ,
) ;
} ;
2024-07-31 15:41:28 +00:00
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2022-09-30 04:03:53 +00:00
renderCell = ( props : any ) = > {
const column =
this . getColumnByOriginalId (
props . cell . column . columnProperties . originalId ,
) || props . cell . column . columnProperties ;
2022-11-05 09:54:20 +00:00
const rowIndex = props . cell . row . index ;
/ *
* We don 't need to render cells that don' t display data ( button , iconButton , etc )
* /
if (
this . props . isAddRowInProgress &&
rowIndex === 0 &&
ActionColumnTypes . includes ( column . columnType )
) {
return < CellWrapper / > ;
}
2022-07-14 07:02:35 +00:00
const isHidden = ! column . isVisible ;
const {
2023-10-11 07:14:38 +00:00
compactMode = CompactModeTypes . DEFAULT ,
2022-07-14 07:02:35 +00:00
filteredTableData = [ ] ,
multiRowSelection ,
selectedRowIndex ,
selectedRowIndices ,
} = this . props ;
2022-11-05 09:54:20 +00:00
let row ;
let originalIndex : number ;
2022-07-14 07:02:35 +00:00
2022-11-05 09:54:20 +00:00
/ *
* In add new row flow , a temporary row is injected at the top of the tableData , which doesn ' t
* have original row index value . so we are using - 1 as the value
* /
if ( this . props . isAddRowInProgress ) {
row = filteredTableData [ rowIndex - 1 ] ;
2025-04-02 08:32:54 +00:00
originalIndex =
rowIndex === 0 ? - 1 : row?. [ ORIGINAL_INDEX_KEY ] ? ? rowIndex ;
2022-11-05 09:54:20 +00:00
} else {
row = filteredTableData [ rowIndex ] ;
feat: add basic table infinite scroll function (#39441)
## Infinite Scroll Improvements for TableWidgetV2
This PR enhances the infinite scroll functionality in `TableWidgetV2`
with several key improvements:
---
### 🚀 Major Changes
#### 1️⃣ Improved Loading State Management
- Added a `shouldShowSkeleton()` function to determine when to display
loading skeletons.
- Loading skeletons now only show when:
- Loading without infinite scroll enabled.
- Loading with infinite scroll but no data loaded yet.
- Added a dedicated loading indicator for infinite scroll that appears
at the bottom while fetching more data.
#### 2️⃣ Enhanced Infinite Scroll Implementation
- Completely refactored `useInfiniteVirtualization` hook to properly
cache and manage loaded rows.
- Added row caching to maintain previously loaded pages when new data
arrives.
- Improved detection of end-of-data conditions to prevent unnecessary
load attempts.
- Fixed issues with item count calculations and loading state
management.
#### 3️⃣ Better Virtualization Support
- Fixed issues with virtual list rendering by properly passing
`itemCount`.
- Added support for `totalRecordsCount` to optimize infinite scroll
behavior.
- Improved table wrapper class name management with a dedicated
function.
---
### 🧪 Testing
This PR includes **comprehensive test coverage** for the infinite scroll
functionality, with **12 detailed test cases** covering:
- ✅ Page loading and caching behavior.
- ✅ End-of-data detection.
- ✅ Partial page handling.
- ✅ Loading state management.
Fixes #39083
## Automation
/ok-to-test tags="@tag.Binding, @tag.Sanity, @tag.Table, @tag.Widget"
### :mag: 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/13651510022>
> Commit: 3bbfda7143ee29eff70e59d234d6e89c74d8bb88
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=13651510022&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Binding, @tag.Sanity, @tag.Table, @tag.Widget`
> Spec:
> <hr>Tue, 04 Mar 2025 11:47:11 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
- **New Features**
- Introduced support for displaying the total number of records,
improving context for pagination and data navigation.
- Added a loading indicator that displays conditionally based on the
loading state.
- **Refactor**
- Refined loading state visuals so that the skeleton indicator appears
only when appropriate.
- Enhanced infinite scrolling and virtualization behavior for smoother
and more reliable data rendering.
- Strengthened row indexing to improve overall robustness.
- Improved the structure and readability of the `Table` component's
rendering logic.
- Updated test suite for dynamic row generation and various loading
scenarios, enhancing flexibility and comprehensiveness.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-03-05 08:21:35 +00:00
originalIndex = row ? row [ ORIGINAL_INDEX_KEY ] ? ? rowIndex : rowIndex ;
2022-11-05 09:54:20 +00:00
}
2022-07-14 07:02:35 +00:00
2023-04-06 18:28:24 +00:00
const isNewRow = this . props . isAddRowInProgress && rowIndex === 0 ;
2022-11-05 09:54:20 +00:00
/ *
* 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 .
* /
2023-04-06 18:28:24 +00:00
const cellProperties = getCellProperties ( column , originalIndex , isNewRow ) ;
2022-07-14 07:02:35 +00:00
let isSelected = false ;
if ( this . props . transientTableData ) {
2022-09-30 04:03:53 +00:00
cellProperties . hasUnsavedChanges =
2022-07-14 07:02:35 +00:00
this . props . transientTableData . hasOwnProperty ( originalIndex ) &&
this . props . transientTableData [ originalIndex ] . hasOwnProperty (
props . cell . column . columnProperties . alias ,
) ;
}
if ( multiRowSelection ) {
isSelected =
_ . isArray ( selectedRowIndices ) && selectedRowIndices . includes ( rowIndex ) ;
} else {
isSelected = selectedRowIndex === rowIndex ;
}
const isColumnEditable =
column . isEditable && isColumnTypeEditable ( column . columnType ) ;
2022-09-16 04:34:11 +00:00
const alias = props . cell . column . columnProperties . alias ;
2022-11-05 09:54:20 +00:00
const isCellEditable = isColumnEditable && cellProperties . isCellEditable ;
2022-09-08 11:05:59 +00:00
const isCellEditMode =
2022-11-05 09:54:20 +00:00
( props . cell . column . alias === this . props . editableCell ? . column &&
rowIndex === this . props . editableCell ? . index ) ||
( isNewRow && isColumnEditable ) ;
const shouldDisableEdit =
( this . props . inlineEditingSaveOption ===
InlineEditingSaveOptions . ROW_LEVEL &&
this . props . updatedRowIndices . length &&
this . props . updatedRowIndices . indexOf ( originalIndex ) === - 1 ) ||
( this . hasInvalidColumnCell ( ) && ! isNewRow ) ;
const disabledEditMessage = ` Save or discard the ${
this . props . isAddRowInProgress ? "newly added" : "unsaved"
} row to start editing here ` ;
if ( this . props . isAddRowInProgress ) {
cellProperties . isCellDisabled = rowIndex !== 0 ;
if ( rowIndex === 0 ) {
cellProperties . cellBackground = "" ;
}
}
2022-07-14 07:02:35 +00:00
switch ( column . columnType ) {
case ColumnTypes . BUTTON :
return (
< ButtonCell
allowCellWrapping = { cellProperties . allowCellWrapping }
cellBackground = { cellProperties . cellBackground }
columnActions = { [
{
backgroundColor :
cellProperties . buttonColor || this . props . accentColor ,
eventType : EventType.ON_CLICK ,
id : column.id ,
isVisible : true ,
label : cellProperties.buttonLabel || DEFAULT_BUTTON_LABEL ,
dynamicTrigger : column.onClick || "" ,
variant : cellProperties.buttonVariant ,
borderRadius :
cellProperties . borderRadius || this . props . borderRadius ,
boxShadow : cellProperties.boxShadow ,
} ,
] }
compactMode = { compactMode }
fontStyle = { cellProperties . fontStyle }
horizontalAlignment = { cellProperties . horizontalAlignment }
2022-11-05 09:54:20 +00:00
isCellDisabled = { cellProperties . isCellDisabled }
2022-07-14 07:02:35 +00:00
isCellVisible = { cellProperties . isCellVisible ? ? true }
isDisabled = { ! ! cellProperties . isDisabled }
isHidden = { isHidden }
isSelected = { isSelected }
onCommandClick = { ( action : string , onComplete : ( ) = > void ) = >
this . onColumnEvent ( {
rowIndex ,
action ,
onComplete ,
triggerPropertyName : "onClick" ,
eventType : EventType.ON_CLICK ,
} )
}
textColor = { cellProperties . textColor }
textSize = { cellProperties . textSize }
verticalAlignment = { cellProperties . verticalAlignment }
/ >
) ;
case ColumnTypes . EDIT_ACTIONS :
return (
< EditActionCell
allowCellWrapping = { cellProperties . allowCellWrapping }
cellBackground = { cellProperties . cellBackground }
columnActions = { [
{
id : EditableCellActions.SAVE ,
label : cellProperties.saveActionLabel ,
dynamicTrigger : column.onSave || "" ,
eventType : EventType.ON_ROW_SAVE ,
iconName : cellProperties.saveActionIconName ,
variant : cellProperties.saveButtonVariant ,
backgroundColor :
cellProperties . saveButtonColor || this . props . accentColor ,
iconAlign : cellProperties.saveIconAlign ,
borderRadius :
cellProperties . saveBorderRadius || this . props . borderRadius ,
isVisible : cellProperties.isSaveVisible ,
2022-09-13 05:41:59 +00:00
isDisabled :
2022-11-05 09:54:20 +00:00
cellProperties . isSaveDisabled || this . hasInvalidColumnCell ( ) ,
2022-07-14 07:02:35 +00:00
boxShadow : cellProperties.boxShadow ,
} ,
{
id : EditableCellActions.DISCARD ,
label : cellProperties.discardActionLabel ,
dynamicTrigger : column.onDiscard || "" ,
eventType : EventType.ON_ROW_DISCARD ,
iconName : cellProperties.discardActionIconName ,
variant : cellProperties.discardButtonVariant ,
backgroundColor :
cellProperties . discardButtonColor || this . props . accentColor ,
iconAlign : cellProperties.discardIconAlign ,
borderRadius :
cellProperties . discardBorderRadius || this . props . borderRadius ,
isVisible : cellProperties.isDiscardVisible ,
2022-09-13 05:41:59 +00:00
isDisabled :
cellProperties . isDiscardDisabled ||
2022-11-05 09:54:20 +00:00
this . hasInvalidColumnCell ( ) ,
2022-07-14 07:02:35 +00:00
boxShadow : cellProperties.boxShadow ,
} ,
] }
compactMode = { compactMode }
fontStyle = { cellProperties . fontStyle }
horizontalAlignment = { cellProperties . horizontalAlignment }
2022-11-05 09:54:20 +00:00
isCellDisabled = { cellProperties . isCellDisabled }
isCellVisible = { cellProperties . isCellVisible }
2022-07-14 07:02:35 +00:00
isHidden = { isHidden }
isSelected = { isSelected }
onCommandClick = { (
action : string ,
onComplete : ( ) = > void ,
eventType : EventType ,
) = >
this . onColumnEvent ( {
rowIndex ,
action ,
onComplete ,
triggerPropertyName : "onClick" ,
eventType : eventType ,
} )
}
onDiscard = { ( ) = >
this . removeRowFromTransientTableData ( originalIndex )
}
textColor = { cellProperties . textColor }
textSize = { cellProperties . textSize }
verticalAlignment = { cellProperties . verticalAlignment }
/ >
) ;
2022-09-30 04:03:53 +00:00
case ColumnTypes . SELECT :
return (
< SelectCell
accentColor = { this . props . accentColor }
alias = { props . cell . column . columnProperties . alias }
allowCellWrapping = { cellProperties . allowCellWrapping }
2022-11-05 09:54:20 +00:00
autoOpen = { ! this . props . isAddRowInProgress }
2022-09-30 04:03:53 +00:00
borderRadius = { cellProperties . borderRadius }
cellBackground = { cellProperties . cellBackground }
columnType = { column . columnType }
compactMode = { compactMode }
disabledEditIcon = {
2022-11-05 09:54:20 +00:00
shouldDisableEdit || this . props . isAddRowInProgress
2022-09-30 04:03:53 +00:00
}
2022-11-05 09:54:20 +00:00
disabledEditIconMessage = { disabledEditMessage }
2022-09-30 04:03:53 +00:00
filterText = {
this . props . selectColumnFilterText ? . [
this . props . editableCell ? . column || column . alias
]
}
fontStyle = { cellProperties . fontStyle }
hasUnsavedChanges = { cellProperties . hasUnsavedChanges }
horizontalAlignment = { cellProperties . horizontalAlignment }
2022-11-05 09:54:20 +00:00
isCellDisabled = { cellProperties . isCellDisabled }
2022-09-30 04:03:53 +00:00
isCellEditMode = { isCellEditMode }
2022-11-05 09:54:20 +00:00
isCellEditable = { isCellEditable }
2022-09-30 04:03:53 +00:00
isCellVisible = { cellProperties . isCellVisible ? ? true }
isEditable = { isColumnEditable }
fix: isRequired validation property for table select column (#36375)
## Description
**Problem**
The select column of the table widget does not have a validation
property within its property pane to allow users add an isRequired
validation to the table select column.
**Solution**
Added a Validation section to the table select column's property pane,
which includes an isRequired toggle. When enabled, this feature will
trigger a visual indication (error border colour) around the select
widget if a required field is left unselected during "Add new row" or
inline editing.
Fixes #30091
## Automation
/ok-to-test tags="@tag.Widget, @tag.Table, @tag.Binding, @tag.Sanity,
@tag.Select"
### :mag: 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/10957896180>
> Commit: d2597e6a26938f2b99f2f997fca7bc110e5c2091
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10957896180&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Widget, @tag.Table, @tag.Binding, @tag.Sanity,
@tag.Select`
> Spec:
> <hr>Fri, 20 Sep 2024 12:23:29 UTC
<!-- end of auto-generated comment: Cypress test results -->
## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Introduced end-to-end tests for Select column validation in Table
widgets.
- Enhanced validation logic to support Select column types in the Table
widget.
- Added visual feedback for required Select fields during row addition
and inline editing.
- Improved locator for single-select widget button control to enhance UI
interaction.
- **Bug Fixes**
- Improved error handling and visual representation for invalid editable
Select cells.
- **Documentation**
- Updated validation configuration to include Select column types for
better usability.
- **Refactor**
- Enhanced code clarity for styled components related to Select fields.
- Modified method to improve versatility in handling table interactions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Sai Charan <saicharan.chetpelly@zemosolabs.com>
Co-authored-by: Pawan Kumar <pawankumar@Pawans-MacBook-Pro-2.local>
2024-09-23 07:44:46 +00:00
isEditableCellValid = { this . isColumnCellValid ( alias ) }
2022-09-30 04:03:53 +00:00
isFilterable = { cellProperties . isFilterable }
isHidden = { isHidden }
2022-11-05 09:54:20 +00:00
isNewRow = { isNewRow }
2022-09-30 04:03:53 +00:00
key = { props . key }
onFilterChange = { this . onSelectFilterChange }
onFilterChangeActionString = { column . onFilterUpdate }
onItemSelect = { this . onOptionSelect }
onOptionSelectActionString = { column . onOptionChange }
2022-12-16 04:35:51 +00:00
options = { cellProperties . selectOptions }
2022-09-30 04:03:53 +00:00
placeholderText = { cellProperties . placeholderText }
resetFilterTextOnClose = { cellProperties . resetFilterTextOnClose }
rowIndex = { rowIndex }
serverSideFiltering = { cellProperties . serverSideFiltering }
2023-09-11 15:55:11 +00:00
tableWidth = { this . props . componentWidth }
2022-09-30 04:03:53 +00:00
textColor = { cellProperties . textColor }
textSize = { cellProperties . textSize }
toggleCellEditMode = { this . toggleCellEditMode }
value = { props . cell . value }
verticalAlignment = { cellProperties . verticalAlignment }
width = {
this . props . columnWidthMap ? . [ column . id ] || DEFAULT_COLUMN_WIDTH
}
/ >
) ;
2022-07-14 07:02:35 +00:00
case ColumnTypes . IMAGE :
const onClick = column . onClick
? ( ) = >
this . onColumnEvent ( {
rowIndex ,
action : column.onClick ,
triggerPropertyName : "onClick" ,
eventType : EventType.ON_CLICK ,
} )
: noop ;
return (
< ImageCell
allowCellWrapping = { cellProperties . allowCellWrapping }
cellBackground = { cellProperties . cellBackground }
compactMode = { compactMode }
fontStyle = { cellProperties . fontStyle }
horizontalAlignment = { cellProperties . horizontalAlignment }
2022-10-14 12:21:54 +00:00
imageSize = { cellProperties . imageSize }
2022-11-05 09:54:20 +00:00
isCellDisabled = { cellProperties . isCellDisabled }
2022-07-14 07:02:35 +00:00
isCellVisible = { cellProperties . isCellVisible ? ? true }
isHidden = { isHidden }
isSelected = { isSelected }
onClick = { onClick }
textColor = { cellProperties . textColor }
textSize = { cellProperties . textSize }
value = { props . cell . value }
verticalAlignment = { cellProperties . verticalAlignment }
/ >
) ;
case ColumnTypes . MENU_BUTTON :
2022-12-30 10:52:11 +00:00
const getVisibleItems = ( rowIndex : number ) = > {
chore: upgrade to prettier v2 + enforce import types (#21013)Co-authored-by: Satish Gandham <hello@satishgandham.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
## Description
This PR upgrades Prettier to v2 + enforces TypeScript’s [`import
type`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export)
syntax where applicable. It’s submitted as a separate PR so we can merge
it easily.
As a part of this PR, we reformat the codebase heavily:
- add `import type` everywhere where it’s required, and
- re-format the code to account for Prettier 2’s breaking changes:
https://prettier.io/blog/2020/03/21/2.0.0.html#breaking-changes
This PR is submitted against `release` to make sure all new code by team
members will adhere to new formatting standards, and we’ll have fewer
conflicts when merging `bundle-optimizations` into `release`. (I’ll
merge `release` back into `bundle-optimizations` once this PR is
merged.)
### Why is this needed?
This PR is needed because, for the Lodash optimization from
https://github.com/appsmithorg/appsmith/commit/7cbb12af886621256224be0c93e6a465dd710ad3,
we need to use `import type`. Otherwise, `babel-plugin-lodash` complains
that `LoDashStatic` is not a lodash function.
However, just using `import type` in the current codebase will give you
this:
<img width="962" alt="Screenshot 2023-03-08 at 17 45 59"
src="https://user-images.githubusercontent.com/2953267/223775744-407afa0c-e8b9-44a1-90f9-b879348da57f.png">
That’s because Prettier 1 can’t parse `import type` at all. To parse it,
we need to upgrade to Prettier 2.
### Why enforce `import type`?
Apart from just enabling `import type` support, this PR enforces
specifying `import type` everywhere it’s needed. (Developers will get
immediate TypeScript and ESLint errors when they forget to do so.)
I’m doing this because I believe `import type` improves DX and makes
refactorings easier.
Let’s say you had a few imports like below. Can you tell which of these
imports will increase the bundle size? (Tip: it’s not all of them!)
```ts
// app/client/src/workers/Linting/utils.ts
import { Position } from "codemirror";
import { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
It’s pretty hard, right?
What about now?
```ts
// app/client/src/workers/Linting/utils.ts
import type { Position } from "codemirror";
import type { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
Now, it’s clear that only `lodash` will be bundled.
This helps developers to see which imports are problematic, but it
_also_ helps with refactorings. Now, if you want to see where
`codemirror` is bundled, you can just grep for `import \{.*\} from
"codemirror"` – and you won’t get any type-only imports.
This also helps (some) bundlers. Upon transpiling, TypeScript erases
type-only imports completely. In some environment (not ours), this makes
the bundle smaller, as the bundler doesn’t need to bundle type-only
imports anymore.
## Type of change
- Chore (housekeeping or task changes that don't impact user perception)
## How Has This Been Tested?
This was tested to not break the build.
### 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
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] 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
---------
Co-authored-by: Satish Gandham <hello@satishgandham.com>
Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
2023-03-16 11:41:47 +00:00
const { configureMenuItems , menuItems , menuItemsSource , sourceData } =
cellProperties ;
2022-12-30 10:52:11 +00:00
if ( menuItemsSource === MenuItemsSource . STATIC && menuItems ) {
const visibleItems = Object . values ( menuItems ) ? . filter ( ( item ) = >
getBooleanPropertyValue ( item . isVisible , rowIndex ) ,
) ;
return visibleItems ? . length
? orderBy ( visibleItems , [ "index" ] , [ "asc" ] )
: [ ] ;
} else if (
menuItemsSource === MenuItemsSource . DYNAMIC &&
isArray ( sourceData ) &&
sourceData ? . length &&
configureMenuItems ? . config
) {
const { config } = configureMenuItems ;
const getValue = (
propertyName : keyof MenuItem ,
index : number ,
rowIndex : number ,
) = > {
const value = config [ propertyName ] ;
if ( isArray ( value ) && isArray ( value [ rowIndex ] ) ) {
return value [ rowIndex ] [ index ] ;
} else if ( isArray ( value ) ) {
return value [ index ] ;
}
return value ? ? null ;
} ;
const visibleItems = sourceData
. map ( ( item , index ) = > ( {
. . . item ,
id : index.toString ( ) ,
isVisible : getValue ( "isVisible" , index , rowIndex ) ,
isDisabled : getValue ( "isDisabled" , index , rowIndex ) ,
index : index ,
widgetId : "" ,
label : getValue ( "label" , index , rowIndex ) ,
onClick : config?.onClick ,
textColor : getValue ( "textColor" , index , rowIndex ) ,
backgroundColor : getValue ( "backgroundColor" , index , rowIndex ) ,
iconAlign : getValue ( "iconAlign" , index , rowIndex ) ,
iconColor : getValue ( "iconColor" , index , rowIndex ) ,
iconName : getValue ( "iconName" , index , rowIndex ) ,
} ) )
. filter ( ( item ) = > item . isVisible === true ) ;
return visibleItems ;
}
return [ ] ;
} ;
2022-07-14 07:02:35 +00:00
return (
< MenuButtonCell
allowCellWrapping = { cellProperties . allowCellWrapping }
borderRadius = {
cellProperties . borderRadius || this . props . borderRadius
}
boxShadow = { cellProperties . boxShadow }
cellBackground = { cellProperties . cellBackground }
compactMode = { compactMode }
2022-12-30 10:52:11 +00:00
configureMenuItems = { cellProperties . configureMenuItems }
2022-07-14 07:02:35 +00:00
fontStyle = { cellProperties . fontStyle }
2022-12-30 10:52:11 +00:00
getVisibleItems = { getVisibleItems }
2022-07-14 07:02:35 +00:00
horizontalAlignment = { cellProperties . horizontalAlignment }
iconAlign = { cellProperties . iconAlign }
iconName = { cellProperties . menuButtoniconName || undefined }
2022-11-05 09:54:20 +00:00
isCellDisabled = { cellProperties . isCellDisabled }
2022-07-14 07:02:35 +00:00
isCellVisible = { cellProperties . isCellVisible ? ? true }
isCompact = { ! ! cellProperties . isCompact }
isDisabled = { ! ! cellProperties . isDisabled }
isHidden = { isHidden }
isSelected = { isSelected }
label = { cellProperties . menuButtonLabel ? ? DEFAULT_MENU_BUTTON_LABEL }
menuColor = {
cellProperties . menuColor || this . props . accentColor || Colors . GREEN
}
menuItems = { cellProperties . menuItems }
2022-12-30 10:52:11 +00:00
menuItemsSource = { cellProperties . menuItemsSource }
2022-07-14 07:02:35 +00:00
menuVariant = { cellProperties . menuVariant ? ? DEFAULT_MENU_VARIANT }
2022-12-30 10:52:11 +00:00
onCommandClick = { (
action : string ,
index? : number ,
onComplete ? : ( ) = > void ,
) = > {
const additionalData : Record <
string ,
string | number | Record < string , unknown >
> = { } ;
if ( cellProperties ? . sourceData && _ . isNumber ( index ) ) {
additionalData . currentItem = cellProperties . sourceData [ index ] ;
additionalData . currentIndex = index ;
}
return this . onColumnEvent ( {
2022-07-14 07:02:35 +00:00
rowIndex ,
action ,
onComplete ,
triggerPropertyName : "onClick" ,
eventType : EventType.ON_CLICK ,
2022-12-30 10:52:11 +00:00
additionalData ,
} ) ;
} }
2022-08-01 05:01:34 +00:00
rowIndex = { originalIndex }
2022-12-30 10:52:11 +00:00
sourceData = { cellProperties . sourceData }
2022-07-14 07:02:35 +00:00
textColor = { cellProperties . textColor }
textSize = { cellProperties . textSize }
verticalAlignment = { cellProperties . verticalAlignment }
/ >
) ;
case ColumnTypes . ICON_BUTTON :
return (
< IconButtonCell
allowCellWrapping = { cellProperties . allowCellWrapping }
borderRadius = {
cellProperties . borderRadius || this . props . borderRadius
}
boxShadow = { cellProperties . boxShadow || "NONE" }
buttonColor = {
cellProperties . buttonColor ||
this . props . accentColor ||
Colors . GREEN
}
buttonVariant = { cellProperties . buttonVariant || "PRIMARY" }
cellBackground = { cellProperties . cellBackground }
columnActions = { [
{
id : column.id ,
dynamicTrigger : column.onClick || "" ,
} ,
] }
compactMode = { compactMode }
disabled = { ! ! cellProperties . isDisabled }
fontStyle = { cellProperties . fontStyle }
horizontalAlignment = { cellProperties . horizontalAlignment }
iconName = { ( cellProperties . iconName || IconNames . ADD ) as IconName }
2022-11-05 09:54:20 +00:00
isCellDisabled = { cellProperties . isCellDisabled }
2022-07-14 07:02:35 +00:00
isCellVisible = { cellProperties . isCellVisible ? ? true }
isHidden = { isHidden }
isSelected = { isSelected }
onCommandClick = { ( action : string , onComplete : ( ) = > void ) = >
this . onColumnEvent ( {
rowIndex ,
action ,
onComplete ,
triggerPropertyName : "onClick" ,
eventType : EventType.ON_CLICK ,
} )
}
textColor = { cellProperties . textColor }
textSize = { cellProperties . textSize }
verticalAlignment = { cellProperties . verticalAlignment }
/ >
) ;
case ColumnTypes . VIDEO :
return (
< VideoCell
allowCellWrapping = { cellProperties . allowCellWrapping }
cellBackground = { cellProperties . cellBackground }
compactMode = { compactMode }
fontStyle = { cellProperties . fontStyle }
horizontalAlignment = { cellProperties . horizontalAlignment }
2022-11-05 09:54:20 +00:00
isCellDisabled = { cellProperties . isCellDisabled }
2022-07-14 07:02:35 +00:00
isCellVisible = { cellProperties . isCellVisible ? ? true }
isHidden = { isHidden }
textColor = { cellProperties . textColor }
textSize = { cellProperties . textSize }
value = { props . cell . value }
verticalAlignment = { cellProperties . verticalAlignment }
/ >
) ;
2022-09-08 11:05:59 +00:00
case ColumnTypes . CHECKBOX :
return (
< CheckboxCell
accentColor = { this . props . accentColor }
borderRadius = {
cellProperties . borderRadius || this . props . borderRadius
}
cellBackground = { cellProperties . cellBackground }
compactMode = { compactMode }
disabledCheckbox = {
2022-11-05 09:54:20 +00:00
shouldDisableEdit || ( this . props . isAddRowInProgress && ! isNewRow )
2022-09-08 11:05:59 +00:00
}
2022-11-05 09:54:20 +00:00
disabledCheckboxMessage = { disabledEditMessage }
2022-09-30 04:03:53 +00:00
hasUnSavedChanges = { cellProperties . hasUnsavedChanges }
2022-09-08 11:05:59 +00:00
horizontalAlignment = { cellProperties . horizontalAlignment }
2022-11-05 09:54:20 +00:00
isCellDisabled = { cellProperties . isCellDisabled }
isCellEditable = { isCellEditable }
2022-09-08 11:05:59 +00:00
isCellVisible = { cellProperties . isCellVisible ? ? true }
isHidden = { isHidden }
2022-11-05 09:54:20 +00:00
onChange = { ( ) = >
this . onCheckChange (
column ,
props . cell . row . values ,
! props . cell . value ,
alias ,
originalIndex ,
2022-09-16 04:34:11 +00:00
rowIndex ,
2022-11-05 09:54:20 +00:00
)
}
2022-09-16 04:34:11 +00:00
value = { props . cell . value }
verticalAlignment = { cellProperties . verticalAlignment }
/ >
) ;
case ColumnTypes . SWITCH :
return (
< SwitchCell
accentColor = { this . props . accentColor }
cellBackground = { cellProperties . cellBackground }
compactMode = { compactMode }
disabledSwitch = {
2022-11-05 09:54:20 +00:00
shouldDisableEdit || ( this . props . isAddRowInProgress && ! isNewRow )
2022-09-16 04:34:11 +00:00
}
2022-11-05 09:54:20 +00:00
disabledSwitchMessage = { disabledEditMessage }
2022-09-30 04:03:53 +00:00
hasUnSavedChanges = { cellProperties . hasUnsavedChanges }
2022-09-16 04:34:11 +00:00
horizontalAlignment = { cellProperties . horizontalAlignment }
2022-11-05 09:54:20 +00:00
isCellDisabled = { cellProperties . isCellDisabled }
isCellEditable = { isCellEditable }
2022-09-16 04:34:11 +00:00
isCellVisible = { cellProperties . isCellVisible ? ? true }
isHidden = { isHidden }
2022-11-05 09:54:20 +00:00
onChange = { ( ) = >
this . onCheckChange (
column ,
props . cell . row . values ,
! props . cell . value ,
alias ,
originalIndex ,
2022-09-08 11:05:59 +00:00
rowIndex ,
2022-11-05 09:54:20 +00:00
)
}
2022-09-08 11:05:59 +00:00
value = { props . cell . value }
verticalAlignment = { cellProperties . verticalAlignment }
/ >
) ;
2023-01-16 09:23:56 +00:00
case ColumnTypes . DATE :
return (
< DateCell
accentColor = { this . props . accentColor }
alias = { props . cell . column . columnProperties . alias }
borderRadius = { this . props . borderRadius }
cellBackground = { cellProperties . cellBackground }
closeOnSelection
columnType = { column . columnType }
compactMode = { compactMode }
disabledEditIcon = {
shouldDisableEdit || this . props . isAddRowInProgress
}
disabledEditIconMessage = { disabledEditMessage }
firstDayOfWeek = { props . cell . column . columnProperties . firstDayOfWeek }
fontStyle = { cellProperties . fontStyle }
hasUnsavedChanges = { cellProperties . hasUnsavedChanges }
horizontalAlignment = { cellProperties . horizontalAlignment }
inputFormat = { cellProperties . inputFormat }
isCellDisabled = { cellProperties . isCellDisabled }
isCellEditMode = { isCellEditMode }
isCellEditable = { isCellEditable }
isCellVisible = { cellProperties . isCellVisible ? ? true }
isEditableCellValid = { this . isColumnCellValid ( alias ) }
isHidden = { isHidden }
isNewRow = { isNewRow }
isRequired = {
props . cell . column . columnProperties . validation
. isColumnEditableCellRequired
}
maxDate = { props . cell . column . columnProperties . validation . maxDate }
minDate = { props . cell . column . columnProperties . validation . minDate }
onCellTextChange = { this . onCellTextChange }
onDateSave = { this . onDateSave }
onDateSelectedString = {
props . cell . column . columnProperties . onDateSelected
}
outputFormat = { cellProperties . outputFormat }
rowIndex = { rowIndex }
shortcuts = { cellProperties . shortcuts }
2023-09-11 15:55:11 +00:00
tableWidth = { this . props . componentWidth }
2023-01-16 09:23:56 +00:00
textColor = { cellProperties . textColor }
textSize = { cellProperties . textSize }
timePrecision = { cellProperties . timePrecision || TimePrecision . NONE }
toggleCellEditMode = { this . toggleCellEditMode }
updateNewRowValues = { this . updateNewRowValues }
validationErrorMessage = "This field is required"
value = { props . cell . value }
verticalAlignment = { cellProperties . verticalAlignment }
widgetId = { this . props . widgetId }
/ >
) ;
2024-12-11 07:37:30 +00:00
case ColumnTypes . HTML :
return (
< HTMLCell
allowCellWrapping = { cellProperties . allowCellWrapping }
cellBackground = { cellProperties . cellBackground }
compactMode = { compactMode }
fontStyle = { cellProperties . fontStyle }
horizontalAlignment = { cellProperties . horizontalAlignment }
isCellDisabled = { cellProperties . isCellDisabled }
isCellVisible = { cellProperties . isCellVisible ? ? true }
isHidden = { isHidden }
renderMode = { this . props . renderMode }
textColor = { cellProperties . textColor }
textSize = { cellProperties . textSize }
value = { props . cell . value }
verticalAlignment = { cellProperties . verticalAlignment }
/ >
) ;
2022-07-14 07:02:35 +00:00
default :
2022-09-13 05:41:59 +00:00
let validationErrorMessage ;
if ( isCellEditMode ) {
validationErrorMessage =
column . validation . isColumnEditableCellRequired &&
2022-11-05 09:54:20 +00:00
( isNil ( props . cell . value ) || props . cell . value === "" )
2022-09-13 05:41:59 +00:00
? "This field is required"
: column . validation ? . errorMessage ;
}
2022-07-14 07:02:35 +00:00
return (
2022-09-30 04:03:53 +00:00
< PlainTextCell
2022-07-14 07:02:35 +00:00
accentColor = { this . props . accentColor }
alias = { props . cell . column . columnProperties . alias }
allowCellWrapping = { cellProperties . allowCellWrapping }
cellBackground = { cellProperties . cellBackground }
columnType = { column . columnType }
compactMode = { compactMode }
2023-11-06 05:35:26 +00:00
currencyCode = { cellProperties . currencyCode }
decimals = { cellProperties . decimals }
2022-11-05 09:54:20 +00:00
disabledEditIcon = {
shouldDisableEdit || this . props . isAddRowInProgress
}
disabledEditIconMessage = { disabledEditMessage }
2022-07-14 07:02:35 +00:00
displayText = { cellProperties . displayText }
fontStyle = { cellProperties . fontStyle }
2022-09-30 04:03:53 +00:00
hasUnsavedChanges = { cellProperties . hasUnsavedChanges }
2022-07-14 07:02:35 +00:00
horizontalAlignment = { cellProperties . horizontalAlignment }
2022-11-05 09:54:20 +00:00
isCellDisabled = { cellProperties . isCellDisabled }
2022-07-14 07:02:35 +00:00
isCellEditMode = { isCellEditMode }
2022-11-05 09:54:20 +00:00
isCellEditable = { isCellEditable }
2022-07-14 07:02:35 +00:00
isCellVisible = { cellProperties . isCellVisible ? ? true }
2022-11-05 09:54:20 +00:00
isEditableCellValid = { this . isColumnCellValid ( alias ) }
2022-07-14 07:02:35 +00:00
isHidden = { isHidden }
2022-11-05 09:54:20 +00:00
isNewRow = { isNewRow }
2023-11-06 05:35:26 +00:00
notation = { cellProperties . notation }
2022-11-05 09:54:20 +00:00
onCellTextChange = { this . onCellTextChange }
2022-07-14 07:02:35 +00:00
onSubmitString = { props . cell . column . columnProperties . onSubmit }
rowIndex = { rowIndex }
2023-09-11 15:55:11 +00:00
tableWidth = { this . props . componentWidth }
2022-07-14 07:02:35 +00:00
textColor = { cellProperties . textColor }
textSize = { cellProperties . textSize }
2023-11-06 05:35:26 +00:00
thousandSeparator = { cellProperties . thousandSeparator }
2022-07-14 07:02:35 +00:00
toggleCellEditMode = { this . toggleCellEditMode }
2022-09-13 05:41:59 +00:00
validationErrorMessage = { validationErrorMessage }
2022-07-14 07:02:35 +00:00
value = { props . cell . value }
verticalAlignment = { cellProperties . verticalAlignment }
2022-09-13 05:41:59 +00:00
widgetId = { this . props . widgetId }
2022-07-14 07:02:35 +00:00
/ >
) ;
}
} ;
2022-11-05 09:54:20 +00:00
onCellTextChange = (
2022-09-13 05:41:59 +00:00
value : EditableCell [ "value" ] ,
inputValue : string ,
2022-11-05 09:54:20 +00:00
alias : string ,
2022-09-13 05:41:59 +00:00
) = > {
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates , pushBatchMetaUpdates } = this . props ;
2022-11-05 09:54:20 +00:00
if ( this . props . isAddRowInProgress ) {
this . updateNewRowValues ( alias , inputValue , value ) ;
} else {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "editableCell" , {
2022-11-05 09:54:20 +00:00
. . . this . props . editableCell ,
value : value ,
inputValue ,
2022-09-30 04:03:53 +00:00
} ) ;
2022-11-05 09:54:20 +00:00
if ( this . props . editableCell ? . column ) {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "columnEditableCellValue" , {
2022-11-05 09:54:20 +00:00
. . . this . props . columnEditableCellValue ,
[ this . props . editableCell ? . column ] : value ,
} ) ;
}
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates ( ) ;
2022-09-30 04:03:53 +00:00
}
2022-07-14 07:02:35 +00:00
} ;
toggleCellEditMode = (
enable : boolean ,
rowIndex : number ,
alias : string ,
value : string | number ,
2022-09-30 04:03:53 +00:00
onSubmit? : string ,
action? : EditableCellActions ,
2022-07-14 07:02:35 +00:00
) = > {
2022-11-05 09:54:20 +00:00
if ( this . props . isAddRowInProgress ) {
return ;
}
2022-07-14 07:02:35 +00:00
if ( enable ) {
2022-09-13 05:41:59 +00:00
if ( this . inlineEditTimer ) {
clearTimeout ( this . inlineEditTimer ) ;
2022-07-14 07:02:35 +00:00
}
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates , pushBatchMetaUpdates } = this . props ;
2022-07-14 07:02:35 +00:00
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "editableCell" , {
2022-07-14 07:02:35 +00:00
column : alias ,
index : rowIndex ,
value : value ,
// To revert back to previous on discard
initialValue : value ,
2022-09-13 05:41:59 +00:00
inputValue : value ,
fix: Fixes currentRow calculation logic in table(property pane) (#35390)
## Description
<ins>Problem</ins>
`currentRow` variable which is availabe in property pane of col settings
- is not getting correct value during runtime if the table is sorted or
filtered.
<ins>Root cause</ins>
* We are considering `processedTableData` to get the `currentRow`.
* This property is not updated during filtering and sorting, another
property `filteredTableData` is updated instead.
* We CANNOT use `filteredTableData` as it depends on `primaryColumns`
property which we intend to update as well - this is leading to cyclic
dependency during evaluations.
<ins>Solution</ins>
Since the problem is related to edit cases and given the constraints
around using `filteredTableData` directly, we fixed the problem by
adding a new property to `editableCellValue` object called
`__originalIndex__`.
* This property stores the index of the row being edited in
`processedTableData`
* On top of this change, the PR adds a migration for updating the
current row binding in TableWidgetV2, ensuring accurate current row
calculation and improving the functionality of the widget.
* We also added unit test for migration changes.
* Additionally, This pull request refactors the DSLs for TableWidgetV2
migration test cases to update the DSLs to separate folder, drastically
reducing the file size to its core logic, improving the readability of
the code.
* We also updated relevant test cases to account for this change.
[Testing
plan](https://www.notion.so/appsmith/Issue-34346-currentRow-doesn-t-work-correctly-when-the-table-is-filtered-449225ae822c485493036599c2b19487)
[Counter part EE
pr](https://github.com/appsmithorg/appsmith-ee/pull/4879)
Fixes #34346
_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.All"
### :mag: 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/10451549845>
> Commit: d1d65c6898c223bf3f6dfbfe93b8e8de214fcc7d
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10451549845&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Mon, 19 Aug 2024 11:15:04 UTC
<!-- end of auto-generated comment: Cypress test results -->
## Communication
Should the DevRel and Marketing teams inform users about this change?
- [x] Yes
- [ ] No
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Summary by CodeRabbit
- **New Features**
- Updated DSL migration process to support version 90, enhancing
compatibility and robustness.
- Introduced new migration logic for table widget data bindings,
improving inline editing capabilities.
- Enhanced validation logic for editable cells in table widgets,
allowing for more dynamic data handling.
- Added a method to discard edits in specific table cells, improving
user interaction.
- Introduced a new message constant for required fields, enhancing user
feedback.
- **Bug Fixes**
- Improved validation checks for table cells based on updated indices
and values.
- **Tests**
- Added comprehensive tests to validate migration functions related to
Table Widget, ensuring all aspects function correctly post-update.
- Enhanced test specifications for improved validation logic and
coverage in table widget functionalities.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: sneha122 <sneha@appsmith.com>
2024-08-20 08:01:45 +00:00
__originalIndex__ : this.getRowOriginalIndex ( rowIndex ) ,
2022-09-13 05:41:59 +00:00
} ) ;
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "columnEditableCellValue" , {
2022-09-13 05:41:59 +00:00
. . . this . props . columnEditableCellValue ,
[ alias ] : value ,
2022-07-14 07:02:35 +00:00
} ) ;
/ *
* We need to clear the selectedRowIndex and selectedRowIndices
* if the rows are sorted , to avoid selectedRow jumping to
* different page .
* /
if ( this . props . sortOrder . column ) {
if ( this . props . multiRowSelection ) {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "selectedRowIndices" , [ ] ) ;
2022-07-14 07:02:35 +00:00
} else {
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "selectedRowIndex" , - 1 ) ;
2022-07-14 07:02:35 +00:00
}
}
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates ( ) ;
2022-07-14 07:02:35 +00:00
} else {
if (
2022-11-05 09:54:20 +00:00
this . isColumnCellValid ( alias ) &&
2022-07-14 07:02:35 +00:00
action === EditableCellActions . SAVE &&
2022-09-15 07:47:15 +00:00
value !== this . props . editableCell ? . initialValue
2022-07-14 07:02:35 +00:00
) {
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates } = this . props ;
this . pushTransientTableDataActionsUpdates ( {
2022-11-05 09:54:20 +00:00
[ ORIGINAL_INDEX_KEY ] : this . getRowOriginalIndex ( rowIndex ) ,
2022-09-15 07:47:15 +00:00
[ alias ] : this . props . editableCell ? . value ,
2022-07-14 07:02:35 +00:00
} ) ;
2022-09-30 04:03:53 +00:00
if ( onSubmit && this . props . editableCell ? . column ) {
2023-03-30 04:54:29 +00:00
//since onSubmit is truthy that makes action truthy as well, so we can push this event
this . pushOnColumnEvent ( {
2022-07-14 07:02:35 +00:00
rowIndex : rowIndex ,
action : onSubmit ,
triggerPropertyName : "onSubmit" ,
eventType : EventType.ON_SUBMIT ,
row : {
. . . this . props . filteredTableData [ rowIndex ] ,
2022-09-30 04:03:53 +00:00
[ this . props . editableCell . column ] : this . props . editableCell . value ,
2022-07-14 07:02:35 +00:00
} ,
} ) ;
}
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates ( ) ;
2022-09-13 05:41:59 +00:00
this . clearEditableCell ( ) ;
} else if (
action === EditableCellActions . DISCARD ||
2022-09-15 07:47:15 +00:00
value === this . props . editableCell ? . initialValue
2022-09-13 05:41:59 +00:00
) {
this . clearEditableCell ( ) ;
2022-07-14 07:02:35 +00:00
}
2022-09-13 05:41:59 +00:00
}
} ;
2023-01-16 09:23:56 +00:00
onDateSave = (
rowIndex : number ,
alias : string ,
value : string ,
onSubmit? : string ,
) = > {
fix: mandatory date column enforcement (#35613)
## Description
**Problem:**
When the `isRequired` property is set for date columns in the table
widget, the validation doesn't work as expected. Users can add new rows
without filling in the date field, even though it is marked as required.
This results in rows being added with missing date values, which can
lead to incomplete or invalid data entries.
**Root Cause:**
The validation logic for the date column is currently handled within the
DateCell component. However, the isRequired validation functionality was
not implemented within this component. Additionally, the general
validation logic in the `getEditableCellValidity` function, located in
the derived.js file, does not account date cells in its isRequired
validation.
**Solution:**
To fix this issue:
**Enhance the `getEditableCellValidity` function:** Extend the existing
validation logic in getEditableCellValidity to include the date cell in
its validation, specifically checking for the isRequired property.
**Integrate with DateCell validation:** Ensure that the isRequired
validation is properly executed in conjunction with the existing date
validations inside the DateCell component. This will enforce the
requirement and prevent new rows from being added if the date field is
left empty.
**Test Plan**
https://www.notion.so/appsmith/Test-Plan-Date-Column-Marked-as-Required-Doesn-t-Enforce-Mandatory-Entry-When-Adding-New-Table-Row-c73b764af60842a188cba056bdda6d79?pvs=4
Fixes #34258
## Automation
/ok-to-test tags="@tag.All"
### :mag: 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/10453174231>
> Commit: 40fe2eaf7d45024bef00f8031e971a5bac2a4e3e
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10453174231&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Mon, 19 Aug 2024 13:17:23 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
- **New Features**
- Introduced validation for "Date" column types, ensuring that required
fields are correctly enforced.
- **Enhancements**
- Improved validation logic for more accurate user feedback in date
cells.
- Expanded support for validating "date" columns in the table widget.
- **Bug Fixes**
- Enhanced error handling to ensure proper indication of cell validity
based on new validation criteria.
- Updated visual feedback for cell editor state in the UI.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-08-20 06:09:51 +00:00
const { commitBatchMetaUpdates } = this . props ;
2023-01-16 09:23:56 +00:00
fix: mandatory date column enforcement (#35613)
## Description
**Problem:**
When the `isRequired` property is set for date columns in the table
widget, the validation doesn't work as expected. Users can add new rows
without filling in the date field, even though it is marked as required.
This results in rows being added with missing date values, which can
lead to incomplete or invalid data entries.
**Root Cause:**
The validation logic for the date column is currently handled within the
DateCell component. However, the isRequired validation functionality was
not implemented within this component. Additionally, the general
validation logic in the `getEditableCellValidity` function, located in
the derived.js file, does not account date cells in its isRequired
validation.
**Solution:**
To fix this issue:
**Enhance the `getEditableCellValidity` function:** Extend the existing
validation logic in getEditableCellValidity to include the date cell in
its validation, specifically checking for the isRequired property.
**Integrate with DateCell validation:** Ensure that the isRequired
validation is properly executed in conjunction with the existing date
validations inside the DateCell component. This will enforce the
requirement and prevent new rows from being added if the date field is
left empty.
**Test Plan**
https://www.notion.so/appsmith/Test-Plan-Date-Column-Marked-as-Required-Doesn-t-Enforce-Mandatory-Entry-When-Adding-New-Table-Row-c73b764af60842a188cba056bdda6d79?pvs=4
Fixes #34258
## Automation
/ok-to-test tags="@tag.All"
### :mag: 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/10453174231>
> Commit: 40fe2eaf7d45024bef00f8031e971a5bac2a4e3e
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10453174231&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Mon, 19 Aug 2024 13:17:23 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
- **New Features**
- Introduced validation for "Date" column types, ensuring that required
fields are correctly enforced.
- **Enhancements**
- Improved validation logic for more accurate user feedback in date
cells.
- Expanded support for validating "date" columns in the table widget.
- **Bug Fixes**
- Enhanced error handling to ensure proper indication of cell validity
based on new validation criteria.
- Updated visual feedback for cell editor state in the UI.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-08-20 06:09:51 +00:00
this . pushTransientTableDataActionsUpdates ( {
[ ORIGINAL_INDEX_KEY ] : this . getRowOriginalIndex ( rowIndex ) ,
[ alias ] : value ,
} ) ;
2023-01-16 09:23:56 +00:00
fix: mandatory date column enforcement (#35613)
## Description
**Problem:**
When the `isRequired` property is set for date columns in the table
widget, the validation doesn't work as expected. Users can add new rows
without filling in the date field, even though it is marked as required.
This results in rows being added with missing date values, which can
lead to incomplete or invalid data entries.
**Root Cause:**
The validation logic for the date column is currently handled within the
DateCell component. However, the isRequired validation functionality was
not implemented within this component. Additionally, the general
validation logic in the `getEditableCellValidity` function, located in
the derived.js file, does not account date cells in its isRequired
validation.
**Solution:**
To fix this issue:
**Enhance the `getEditableCellValidity` function:** Extend the existing
validation logic in getEditableCellValidity to include the date cell in
its validation, specifically checking for the isRequired property.
**Integrate with DateCell validation:** Ensure that the isRequired
validation is properly executed in conjunction with the existing date
validations inside the DateCell component. This will enforce the
requirement and prevent new rows from being added if the date field is
left empty.
**Test Plan**
https://www.notion.so/appsmith/Test-Plan-Date-Column-Marked-as-Required-Doesn-t-Enforce-Mandatory-Entry-When-Adding-New-Table-Row-c73b764af60842a188cba056bdda6d79?pvs=4
Fixes #34258
## Automation
/ok-to-test tags="@tag.All"
### :mag: 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/10453174231>
> Commit: 40fe2eaf7d45024bef00f8031e971a5bac2a4e3e
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10453174231&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Mon, 19 Aug 2024 13:17:23 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
- **New Features**
- Introduced validation for "Date" column types, ensuring that required
fields are correctly enforced.
- **Enhancements**
- Improved validation logic for more accurate user feedback in date
cells.
- Expanded support for validating "date" columns in the table widget.
- **Bug Fixes**
- Enhanced error handling to ensure proper indication of cell validity
based on new validation criteria.
- Updated visual feedback for cell editor state in the UI.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-08-20 06:09:51 +00:00
if ( onSubmit && this . props . editableCell ? . column ) {
//since onSubmit is truthy this makes action truthy as well, so we can push this event
this . pushOnColumnEvent ( {
rowIndex : rowIndex ,
action : onSubmit ,
triggerPropertyName : "onSubmit" ,
eventType : EventType.ON_SUBMIT ,
row : {
. . . this . props . filteredTableData [ rowIndex ] ,
[ this . props . editableCell . column ] : value ,
} ,
} ) ;
2023-01-16 09:23:56 +00:00
}
fix: mandatory date column enforcement (#35613)
## Description
**Problem:**
When the `isRequired` property is set for date columns in the table
widget, the validation doesn't work as expected. Users can add new rows
without filling in the date field, even though it is marked as required.
This results in rows being added with missing date values, which can
lead to incomplete or invalid data entries.
**Root Cause:**
The validation logic for the date column is currently handled within the
DateCell component. However, the isRequired validation functionality was
not implemented within this component. Additionally, the general
validation logic in the `getEditableCellValidity` function, located in
the derived.js file, does not account date cells in its isRequired
validation.
**Solution:**
To fix this issue:
**Enhance the `getEditableCellValidity` function:** Extend the existing
validation logic in getEditableCellValidity to include the date cell in
its validation, specifically checking for the isRequired property.
**Integrate with DateCell validation:** Ensure that the isRequired
validation is properly executed in conjunction with the existing date
validations inside the DateCell component. This will enforce the
requirement and prevent new rows from being added if the date field is
left empty.
**Test Plan**
https://www.notion.so/appsmith/Test-Plan-Date-Column-Marked-as-Required-Doesn-t-Enforce-Mandatory-Entry-When-Adding-New-Table-Row-c73b764af60842a188cba056bdda6d79?pvs=4
Fixes #34258
## Automation
/ok-to-test tags="@tag.All"
### :mag: 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/10453174231>
> Commit: 40fe2eaf7d45024bef00f8031e971a5bac2a4e3e
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10453174231&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Mon, 19 Aug 2024 13:17:23 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
- **New Features**
- Introduced validation for "Date" column types, ensuring that required
fields are correctly enforced.
- **Enhancements**
- Improved validation logic for more accurate user feedback in date
cells.
- Expanded support for validating "date" columns in the table widget.
- **Bug Fixes**
- Enhanced error handling to ensure proper indication of cell validity
based on new validation criteria.
- Updated visual feedback for cell editor state in the UI.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-08-20 06:09:51 +00:00
commitBatchMetaUpdates ( ) ;
this . clearEditableCell ( ) ;
2023-01-16 09:23:56 +00:00
} ;
2023-03-30 04:54:29 +00:00
pushClearEditableCellsUpdates = ( ) = > {
const { pushBatchMetaUpdates } = this . props ;
pushBatchMetaUpdates ( "editableCell" , defaultEditableCell ) ;
pushBatchMetaUpdates ( "columnEditableCellValue" , { } ) ;
} ;
2023-01-16 09:23:56 +00:00
2022-09-13 05:41:59 +00:00
clearEditableCell = ( skipTimeout? : boolean ) = > {
const clear = ( ) = > {
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates } = this . props ;
this . pushClearEditableCellsUpdates ( ) ;
commitBatchMetaUpdates ( ) ;
2022-09-13 05:41:59 +00:00
} ;
2022-07-14 07:02:35 +00:00
2022-09-13 05:41:59 +00:00
if ( skipTimeout ) {
clear ( ) ;
} else {
2022-07-14 07:02:35 +00:00
/ *
* We need to let the evaulations compute derived property ( filteredTableData )
* before we clear the editableCell to avoid the text flickering
* /
2022-09-13 05:41:59 +00:00
this . inlineEditTimer = setTimeout ( clear , 100 ) ;
2022-07-14 07:02:35 +00:00
}
} ;
2022-09-13 05:41:59 +00:00
isColumnCellEditable = ( column : ColumnProperties , rowIndex : number ) = > {
return (
2022-09-15 07:47:15 +00:00
column . alias === this . props . editableCell ? . column &&
rowIndex === this . props . editableCell ? . index
2022-09-13 05:41:59 +00:00
) ;
} ;
2022-09-30 04:03:53 +00:00
onOptionSelect = (
2022-10-04 10:45:33 +00:00
value : string | number ,
2022-09-30 04:03:53 +00:00
rowIndex : number ,
column : string ,
action? : string ,
) = > {
2022-11-05 09:54:20 +00:00
if ( this . props . isAddRowInProgress ) {
this . updateNewRowValues ( column , value , value ) ;
} else {
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates , pushBatchMetaUpdates } = this . props ;
this . pushTransientTableDataActionsUpdates ( {
2022-11-05 09:54:20 +00:00
[ ORIGINAL_INDEX_KEY ] : this . getRowOriginalIndex ( rowIndex ) ,
[ column ] : value ,
} ) ;
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "editableCell" , defaultEditableCell ) ;
2022-09-30 04:03:53 +00:00
2022-11-05 09:54:20 +00:00
if ( action && this . props . editableCell ? . column ) {
2023-03-30 04:54:29 +00:00
//since action is truthy we can push this event
this . pushOnColumnEvent ( {
2022-11-05 09:54:20 +00:00
rowIndex ,
2023-03-30 04:54:29 +00:00
action ,
2022-11-05 09:54:20 +00:00
triggerPropertyName : "onOptionChange" ,
eventType : EventType.ON_OPTION_CHANGE ,
row : {
. . . this . props . filteredTableData [ rowIndex ] ,
[ this . props . editableCell . column ] : value ,
} ,
} ) ;
}
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates ( ) ;
2022-09-30 04:03:53 +00:00
}
} ;
onSelectFilterChange = (
text : string ,
rowIndex : number ,
serverSideFiltering : boolean ,
alias : string ,
action? : string ,
) = > {
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates , pushBatchMetaUpdates } = this . props ;
pushBatchMetaUpdates ( "selectColumnFilterText" , {
2022-09-30 04:03:53 +00:00
. . . this . props . selectColumnFilterText ,
[ alias ] : text ,
} ) ;
if ( action && serverSideFiltering ) {
2023-03-30 04:54:29 +00:00
//since action is truthy we can push this event
this . pushOnColumnEvent ( {
2022-09-30 04:03:53 +00:00
rowIndex ,
2023-03-30 04:54:29 +00:00
action ,
2022-09-30 04:03:53 +00:00
triggerPropertyName : "onFilterUpdate" ,
eventType : EventType.ON_FILTER_UPDATE ,
row : {
. . . this . props . filteredTableData [ rowIndex ] ,
} ,
additionalData : {
filterText : text ,
} ,
} ) ;
}
2024-09-18 16:35:28 +00:00
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates ( ) ;
2022-09-30 04:03:53 +00:00
} ;
2022-11-05 09:54:20 +00:00
onCheckChange = (
2024-07-31 15:41:28 +00:00
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2022-11-05 09:54:20 +00:00
column : any ,
row : Record < string , unknown > ,
value : boolean ,
alias : string ,
originalIndex : number ,
rowIndex : number ,
) = > {
if ( this . props . isAddRowInProgress ) {
this . updateNewRowValues ( alias , value , value ) ;
} else {
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates } = this . props ;
this . pushTransientTableDataActionsUpdates ( {
2022-11-05 09:54:20 +00:00
[ ORIGINAL_INDEX_KEY ] : originalIndex ,
[ alias ] : value ,
} ) ;
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates ( ) ;
//cannot batch this update because we are not sure if it action is truthy or not
2022-11-05 09:54:20 +00:00
this . onColumnEvent ( {
rowIndex ,
action : column.onCheckChange ,
triggerPropertyName : "onCheckChange" ,
eventType : EventType.ON_CHECK_CHANGE ,
row : {
. . . row ,
[ alias ] : value ,
} ,
} ) ;
}
} ;
handleAddNewRowClick = ( ) = > {
const defaultNewRow = this . props . defaultNewRow || { } ;
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates , pushBatchMetaUpdates } = this . props ;
pushBatchMetaUpdates ( "isAddRowInProgress" , true ) ;
pushBatchMetaUpdates ( "newRowContent" , defaultNewRow ) ;
pushBatchMetaUpdates ( "newRow" , defaultNewRow ) ;
2022-11-05 09:54:20 +00:00
// New row gets added at the top of page 1 when client side pagination enabled
if ( ! this . props . serverSidePaginationEnabled ) {
2023-04-13 10:08:46 +00:00
this . updatePaginationDirectionFlags ( PaginationDirection . INITIAL ) ;
2022-11-05 09:54:20 +00:00
}
//Since we're adding a newRowContent thats not part of tableData, the index changes
// so we're resetting the row selection
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "selectedRowIndex" , - 1 ) ;
pushBatchMetaUpdates ( "selectedRowIndices" , [ ] ) ;
commitBatchMetaUpdates ( ) ;
2022-11-05 09:54:20 +00:00
} ;
handleAddNewRowAction = (
type : AddNewRowActions ,
onActionComplete : ( ) = > void ,
) = > {
let triggerPropertyName , action , eventType ;
const onComplete = ( ) = > {
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates , pushBatchMetaUpdates } = this . props ;
pushBatchMetaUpdates ( "isAddRowInProgress" , false ) ;
pushBatchMetaUpdates ( "newRowContent" , undefined ) ;
pushBatchMetaUpdates ( "newRow" , undefined ) ;
commitBatchMetaUpdates ( ) ;
2022-11-05 09:54:20 +00:00
onActionComplete ( ) ;
} ;
if ( type === AddNewRowActions . SAVE ) {
triggerPropertyName = "onAddNewRowSave" ;
action = this . props . onAddNewRowSave ;
eventType = EventType . ON_ADD_NEW_ROW_SAVE ;
} else {
triggerPropertyName = "onAddNewRowDiscard" ;
action = this . props . onAddNewRowDiscard ;
eventType = EventType . ON_ADD_NEW_ROW_DISCARD ;
}
if ( action ) {
super . executeAction ( {
triggerPropertyName : triggerPropertyName ,
dynamicString : action ,
event : {
type : eventType ,
callback : onComplete ,
} ,
} ) ;
} else {
onComplete ( ) ;
}
} ;
isColumnCellValid = ( columnsAlias : string ) = > {
if ( this . props . isEditableCellsValid ? . hasOwnProperty ( columnsAlias ) ) {
return this . props . isEditableCellsValid [ columnsAlias ] ;
}
return true ;
} ;
hasInvalidColumnCell = ( ) = > {
2022-11-18 10:54:35 +00:00
if ( isObject ( this . props . isEditableCellsValid ) ) {
return Object . values ( this . props . isEditableCellsValid ) . some ( ( d ) = > ! d ) ;
} else {
return false ;
}
2022-11-05 09:54:20 +00:00
} ;
updateNewRowValues = (
alias : string ,
value : unknown ,
parsedValue : unknown ,
) = > {
2023-03-30 04:54:29 +00:00
const { commitBatchMetaUpdates , pushBatchMetaUpdates } = this . props ;
2022-11-05 09:54:20 +00:00
/ *
* newRowContent holds whatever the user types while newRow holds the parsed value
* newRowContent is being used to populate the cell while newRow is being used
* for validations .
* /
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "newRowContent" , {
2022-11-05 09:54:20 +00:00
. . . this . props . newRowContent ,
[ alias ] : value ,
} ) ;
2023-03-30 04:54:29 +00:00
pushBatchMetaUpdates ( "newRow" , {
2022-11-05 09:54:20 +00:00
. . . this . props . newRow ,
[ alias ] : parsedValue ,
} ) ;
2023-03-30 04:54:29 +00:00
commitBatchMetaUpdates ( ) ;
2022-11-05 09:54:20 +00:00
} ;
2023-06-01 17:26:05 +00:00
onConnectData = ( ) = > {
if ( this . props . renderMode === RenderModes . CANVAS ) {
super . updateOneClickBindingOptionsVisibility ( true ) ;
}
} ;
2025-04-02 08:32:54 +00:00
updateInfiniteScrollProperties ( shouldCommitBatchUpdates? : boolean ) {
const {
cachedTableData ,
commitBatchMetaUpdates ,
infiniteScrollEnabled ,
pageNo ,
pageSize ,
processedTableData ,
pushBatchMetaUpdates ,
tableData ,
totalRecordsCount ,
} = this . props ;
if ( infiniteScrollEnabled ) {
// Update the cache key for a particular page whenever this function is called. The pageNo data is updated with the tableData.
const updatedCachedTableData = {
. . . ( cachedTableData || { } ) ,
[ pageNo ] : tableData ,
} ;
pushBatchMetaUpdates ( "cachedTableData" , updatedCachedTableData ) ;
// The check (!!totalRecordsCount && processedTableData.length === totalRecordsCount) is added if the totalRecordsCount property is set then match the length with the processedTableData which has all flatted data from each page in a single array except the current tableData page i.e. [ ...array of page 1 data, ...array of page 2 data ]. Another 'or' check is if (tableData.length < pageSize) when totalRecordsCount is undefined. Table data has a single page data and if the data comes out to be lesser than the pageSize, it is assumed that the data is finished.
if (
( ! ! totalRecordsCount &&
processedTableData . length + tableData . length === totalRecordsCount ) ||
( ! totalRecordsCount && tableData . length < pageSize )
) {
pushBatchMetaUpdates ( "endOfData" , true ) ;
} else {
pushBatchMetaUpdates ( "endOfData" , false ) ;
}
if ( shouldCommitBatchUpdates ) {
commitBatchMetaUpdates ( ) ;
}
}
}
feat: reset table when infinite scroll is turned on (#40066)
## 🐞 Problem
We've identified several issues with the TableWidgetV2's infinite scroll
functionality:
- **Stale Data After Toggle**
When users enable infinite scroll for the first time, the table's state
and cached data aren't properly reset, potentially leading to incorrect
data display and inconsistent behavior.
- **Height Changes Breaking Existing Data**
When a table's height is increased while infinite scroll is enabled, the
existing cached data becomes invalid due to changing offsets, but the
table wasn't resetting its state accordingly.
- **Empty Initial View**
When loading a table with infinite scroll enabled, rows were not visible
during the initial load until data was fetched, creating a jarring user
experience.
- **Disabled Properties Still Rendering Controls**
Property controls that should be disabled (based on section disabling
conditions) were still being rendered and active, causing unexpected
behavior.
---
## ✅ Solution
### 1. Implement Table Reset on Infinite Scroll Toggle
Added a new method `resetTableForInfiniteScroll()` that properly resets
the table's state when infinite scroll is enabled. This method:
- Clears cached table data
- Resets the "end of data" flag
- Resets all meta properties to their default values
- Sets the page number back to `1` and triggers a page load
```ts
resetTableForInfiniteScroll = () => {
const { infiniteScrollEnabled, pushBatchMetaUpdates } = this.props;
if (infiniteScrollEnabled) {
// reset the cachedRows
pushBatchMetaUpdates("cachedTableData", {});
pushBatchMetaUpdates("endOfData", false);
// reset the meta properties
const metaProperties = Object.keys(TableWidgetV2.getMetaPropertiesMap());
metaProperties.forEach((prop) => {
if (prop !== "pageNo") {
const defaultValue = TableWidgetV2.getMetaPropertiesMap()[prop];
this.props.updateWidgetMetaProperty(prop, defaultValue);
}
});
// reset and reload page
this.updatePageNumber(1, EventType.ON_NEXT_PAGE);
}
};
```
---
### 2. Reset on Height Changes
Added a check in `componentDidUpdate` to detect height changes and reset
the table when needed:
```ts
// Reset widget state when height changes while infinite scroll is enabled
if (
infiniteScrollEnabled &&
prevProps.componentHeight !== componentHeight
) {
this.resetTableForInfiniteScroll();
}
```
---
### 3. Improved Empty State Rendering
Modified the `InfiniteScrollBodyComponent` to show placeholder rows
during initial load by using the maximum of `rows.length` and
`pageSize`:
```ts
const itemCount = useMemo(
() => Math.max(rows.length, pageSize),
[rows.length, pageSize],
);
```
This ensures the table maintains its expected height and appearance even
before data is loaded.
---
### 4. Fixed Property Control Rendering
Fixed the `PropertyControl` component to respect the `isControlDisabled`
flag by conditionally rendering the control:
```ts
{!isControlDisabled &&
PropertyControlFactory.createControl(
config,
{
onPropertyChange: onPropertyChange,
// ...other props
},
// ...other args
)}
```
This prevents disabled controls from being rendered and potentially
causing issues.
---
These improvements significantly enhance the stability and user
experience of **TableWidgetV2**'s infinite scroll functionality.
Fixes #39377
## Automation
/ok-to-test tags="@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity,
@tag.PropertyPane"
### :mag: 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/14373134089>
> Commit: 2b0715bbbe2e9a254cd287f831329be529a17c3c
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14373134089&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity,
@tag.PropertyPane`
> Spec:
> <hr>Thu, 10 Apr 2025 07:15:53 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
- **New Features**
- Property panels now display controls only when enabled, enhancing
clarity.
- Table widgets offer smoother infinite scrolling with automatic resets
on state or size changes.
- Columns dynamically adjust for optimal display when infinite scrolling
is active.
- **Bug Fixes**
- Improved handling of item counts and loading states in infinite
scrolling.
- **Refactor**
- Improved performance through optimized item computations and
streamlined scrolling logic.
- Removed redundant loading button logic for a cleaner user experience.
- **Tests**
- Expanded test scenarios to verify improved content wrapping and rich
HTML rendering in table cells, with a focus on internal logic and
behavior.
- Enhanced clarity and robustness of infinite scroll tests by verifying
loading through scrolling actions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Rahul Barwal <rahul.barwal@appsmith.com>
2025-04-10 10:58:15 +00:00
resetTableForInfiniteScroll = ( ) = > {
2025-04-30 09:34:03 +00:00
resetWidget ( this . props . widgetId , false ) ;
2025-05-07 14:23:02 +00:00
this . updatePageNumber ( 0 , EventType . ON_NEXT_PAGE ) ;
feat: reset table when infinite scroll is turned on (#40066)
## 🐞 Problem
We've identified several issues with the TableWidgetV2's infinite scroll
functionality:
- **Stale Data After Toggle**
When users enable infinite scroll for the first time, the table's state
and cached data aren't properly reset, potentially leading to incorrect
data display and inconsistent behavior.
- **Height Changes Breaking Existing Data**
When a table's height is increased while infinite scroll is enabled, the
existing cached data becomes invalid due to changing offsets, but the
table wasn't resetting its state accordingly.
- **Empty Initial View**
When loading a table with infinite scroll enabled, rows were not visible
during the initial load until data was fetched, creating a jarring user
experience.
- **Disabled Properties Still Rendering Controls**
Property controls that should be disabled (based on section disabling
conditions) were still being rendered and active, causing unexpected
behavior.
---
## ✅ Solution
### 1. Implement Table Reset on Infinite Scroll Toggle
Added a new method `resetTableForInfiniteScroll()` that properly resets
the table's state when infinite scroll is enabled. This method:
- Clears cached table data
- Resets the "end of data" flag
- Resets all meta properties to their default values
- Sets the page number back to `1` and triggers a page load
```ts
resetTableForInfiniteScroll = () => {
const { infiniteScrollEnabled, pushBatchMetaUpdates } = this.props;
if (infiniteScrollEnabled) {
// reset the cachedRows
pushBatchMetaUpdates("cachedTableData", {});
pushBatchMetaUpdates("endOfData", false);
// reset the meta properties
const metaProperties = Object.keys(TableWidgetV2.getMetaPropertiesMap());
metaProperties.forEach((prop) => {
if (prop !== "pageNo") {
const defaultValue = TableWidgetV2.getMetaPropertiesMap()[prop];
this.props.updateWidgetMetaProperty(prop, defaultValue);
}
});
// reset and reload page
this.updatePageNumber(1, EventType.ON_NEXT_PAGE);
}
};
```
---
### 2. Reset on Height Changes
Added a check in `componentDidUpdate` to detect height changes and reset
the table when needed:
```ts
// Reset widget state when height changes while infinite scroll is enabled
if (
infiniteScrollEnabled &&
prevProps.componentHeight !== componentHeight
) {
this.resetTableForInfiniteScroll();
}
```
---
### 3. Improved Empty State Rendering
Modified the `InfiniteScrollBodyComponent` to show placeholder rows
during initial load by using the maximum of `rows.length` and
`pageSize`:
```ts
const itemCount = useMemo(
() => Math.max(rows.length, pageSize),
[rows.length, pageSize],
);
```
This ensures the table maintains its expected height and appearance even
before data is loaded.
---
### 4. Fixed Property Control Rendering
Fixed the `PropertyControl` component to respect the `isControlDisabled`
flag by conditionally rendering the control:
```ts
{!isControlDisabled &&
PropertyControlFactory.createControl(
config,
{
onPropertyChange: onPropertyChange,
// ...other props
},
// ...other args
)}
```
This prevents disabled controls from being rendered and potentially
causing issues.
---
These improvements significantly enhance the stability and user
experience of **TableWidgetV2**'s infinite scroll functionality.
Fixes #39377
## Automation
/ok-to-test tags="@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity,
@tag.PropertyPane"
### :mag: 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/14373134089>
> Commit: 2b0715bbbe2e9a254cd287f831329be529a17c3c
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14373134089&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Table, @tag.Widget, @tag.Binding, @tag.Sanity,
@tag.PropertyPane`
> Spec:
> <hr>Thu, 10 Apr 2025 07:15:53 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
- **New Features**
- Property panels now display controls only when enabled, enhancing
clarity.
- Table widgets offer smoother infinite scrolling with automatic resets
on state or size changes.
- Columns dynamically adjust for optimal display when infinite scrolling
is active.
- **Bug Fixes**
- Improved handling of item counts and loading states in infinite
scrolling.
- **Refactor**
- Improved performance through optimized item computations and
streamlined scrolling logic.
- Removed redundant loading button logic for a cleaner user experience.
- **Tests**
- Expanded test scenarios to verify improved content wrapping and rich
HTML rendering in table cells, with a focus on internal logic and
behavior.
- Enhanced clarity and robustness of infinite scroll tests by verifying
loading through scrolling actions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Rahul Barwal <rahul.barwal@appsmith.com>
2025-04-10 10:58:15 +00:00
} ;
2022-07-14 07:02:35 +00:00
}
export default TableWidgetV2 ;