diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/columnTypes/HTMLCell_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/columnTypes/HTMLCell_spec.ts index be2ef5b3d3df..cac2e52a2b3a 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/columnTypes/HTMLCell_spec.ts +++ b/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/columnTypes/HTMLCell_spec.ts @@ -12,6 +12,9 @@ describe( { tags: ["@tag.Widget", "@tag.Table"] }, function () { before(() => { + featureFlagIntercept({ + release_table_html_column_type_enabled: true, + }); entityExplorer.DragDropWidgetNVerify("tablewidgetv2", 650, 250); propPane.EnterJSContext("Table data", JSON.stringify(htmlTableData)); }); diff --git a/app/client/src/ce/entities/FeatureFlag.ts b/app/client/src/ce/entities/FeatureFlag.ts index ec3785c35740..6d8c77d384d6 100644 --- a/app/client/src/ce/entities/FeatureFlag.ts +++ b/app/client/src/ce/entities/FeatureFlag.ts @@ -39,6 +39,8 @@ export const FEATURE_FLAG = { release_custom_widget_ai_builder: "release_custom_widget_ai_builder", ab_request_new_integration_enabled: "ab_request_new_integration_enabled", release_evaluation_scope_cache: "release_evaluation_scope_cache", + release_table_html_column_type_enabled: + "release_table_html_column_type_enabled", release_gs_all_sheets_options_enabled: "release_gs_all_sheets_options_enabled", release_git_modularisation_enabled: "release_git_modularisation_enabled", @@ -87,6 +89,7 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = { release_custom_widget_ai_builder: false, ab_request_new_integration_enabled: false, release_evaluation_scope_cache: false, + release_table_html_column_type_enabled: false, release_gs_all_sheets_options_enabled: false, release_git_modularisation_enabled: false, release_git_api_contracts_enabled: false, diff --git a/app/client/src/widgets/TableWidgetV2/constants.ts b/app/client/src/widgets/TableWidgetV2/constants.ts index 870040afec5f..35d1e7eaee1b 100644 --- a/app/client/src/widgets/TableWidgetV2/constants.ts +++ b/app/client/src/widgets/TableWidgetV2/constants.ts @@ -243,3 +243,6 @@ export const DEFAULT_COLUMN_NAME = "Table Column"; export const ALLOW_TABLE_WIDGET_SERVER_SIDE_FILTERING = FEATURE_FLAG["release_table_serverside_filtering_enabled"]; + +export const HTML_COLUMN_TYPE_ENABLED = + FEATURE_FLAG["release_table_html_column_type_enabled"]; diff --git a/app/client/src/widgets/TableWidgetV2/widget/index.tsx b/app/client/src/widgets/TableWidgetV2/widget/index.tsx index 9beaac53d9ef..b81f652b779f 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/widget/index.tsx @@ -1,6 +1,6 @@ +import React, { lazy, Suspense } from "react"; import log from "loglevel"; import memoizeOne from "memoize-one"; -import React, { lazy, Suspense } from "react"; import _, { filter, @@ -19,61 +19,16 @@ import _, { xorWith, } from "lodash"; -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"; +import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; +import BaseWidget from "widgets/BaseWidget"; import { RenderModes, WIDGET_PADDING, WIDGET_TAGS, } from "constants/WidgetConstants"; -import type { SetterConfig, Stylesheet } from "entities/AppTheming"; -import equal from "fast-deep-equal/es6"; -import { - FlexVerticalAlignment, - ResponsiveBehavior, -} from "layoutSystems/common/utils/constants"; +import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; +import Skeleton from "components/utils/Skeleton"; import { noop, retryPromise } from "utils/AppsmithUtils"; -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"; import type { ColumnProperties, ReactTableColumnProps, @@ -87,7 +42,6 @@ import { SortOrderTypes, StickyType, } from "../component/Constants"; -import { CellWrapper } from "../component/TableStyledWrappers"; import type { EditableCell, OnColumnEventArgs, @@ -104,23 +58,13 @@ import { DEFAULT_MENU_VARIANT, defaultEditableCell, EditableCellActions, + HTML_COLUMN_TYPE_ENABLED, InlineEditingSaveOptions, ORIGINAL_INDEX_KEY, PaginationDirection, TABLE_COLUMN_ORDER_KEY, } from "../constants"; -import IconSVG from "../icon.svg"; -import ThumbnailSVG from "../thumbnail.svg"; import derivedProperties from "./parseDerivedProperties"; -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"; import { createEditActionColumn, deleteLocalTableColumnOrderByWidgetId, @@ -140,6 +84,64 @@ import { isColumnTypeEditable, updateAndSyncTableLocalColumnOrders, } from "./utilities"; +import contentConfig from "./propertyConfig/contentConfig"; +import styleConfig from "./propertyConfig/styleConfig"; +import type { BatchPropertyUpdatePayload } from "actions/controlActions"; +import type { IconName } from "@blueprintjs/icons"; +import { IconNames } from "@blueprintjs/icons"; +import { Colors } from "constants/Colors"; +import equal from "fast-deep-equal/es6"; +import { + DefaultAutocompleteDefinitions, + sanitizeKey, +} from "widgets/WidgetUtils"; +import PlainTextCell from "../component/cellComponents/PlainTextCell"; +import { ButtonCell } from "../component/cellComponents/ButtonCell"; +import { MenuButtonCell } from "../component/cellComponents/MenuButtonCell"; +import { ImageCell } from "../component/cellComponents/ImageCell"; +import { VideoCell } from "../component/cellComponents/VideoCell"; +import { IconButtonCell } from "../component/cellComponents/IconButtonCell"; +import { EditActionCell } from "../component/cellComponents/EditActionsCell"; +import { CheckboxCell } from "../component/cellComponents/CheckboxCell"; +import { SwitchCell } from "../component/cellComponents/SwitchCell"; +import { SelectCell } from "../component/cellComponents/SelectCell"; +import { CellWrapper } from "../component/TableStyledWrappers"; +import localStorage from "utils/localStorage"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; +import { DateCell } from "../component/cellComponents/DateCell"; +import type { MenuItem } from "widgets/MenuButtonWidget/constants"; +import { MenuItemsSource } from "widgets/MenuButtonWidget/constants"; +import { TimePrecision } from "widgets/DatePickerWidget2/constants"; +import type { getColumns } from "./reactTableUtils/getColumnsPureFn"; +import { getMemoiseGetColumnsWithLocalStorageFn } from "./reactTableUtils/getColumnsPureFn"; +import type { + tableData, + transformDataWithEditableCell, +} from "./reactTableUtils/transformDataPureFn"; +import { getMemoiseTransformDataWithEditableCell } from "./reactTableUtils/transformDataPureFn"; +import type { ExtraDef } from "utils/autocomplete/defCreatorUtils"; +import { generateTypeDef } from "utils/autocomplete/defCreatorUtils"; +import type { + AnvilConfig, + AutocompletionDefinitions, + PropertyUpdates, + SnipingModeProperty, +} from "WidgetProvider/constants"; +import type { + WidgetQueryConfig, + WidgetQueryGenerationFormConfig, +} from "WidgetQueryGenerators/types"; +import type { DynamicPath } from "utils/DynamicBindingUtils"; +import { FILL_WIDGET_MIN_WIDTH } from "constants/minWidthConstants"; +import { + FlexVerticalAlignment, + ResponsiveBehavior, +} from "layoutSystems/common/utils/constants"; +import IconSVG from "../icon.svg"; +import ThumbnailSVG from "../thumbnail.svg"; +import { klonaRegularWithTelemetry } from "utils/helpers"; +import HTMLCell from "../component/cellComponents/HTMLCell"; +import { objectKeys } from "@appsmith/utils"; const ReactTableComponent = lazy(async () => retryPromise(async () => import("../component")), @@ -912,6 +914,43 @@ class TableWidgetV2 extends BaseWidget { //dont neet to batch this since single action this.hydrateStickyColumns(); } + + /** + * Why we are doing this? + * This is a safety net! Consider this scenario: + * 1. HTML column type is enabled. + * 2. User creates a table with HTML columns. + * 3. HTML column type is disabled. (For any reason) + * + * In this scenario, we don't want incomplete experience for the user. + * Without this safety net, the property pane will not show the HTML as type and the `ColumnType` will be lost(and empty), which is confusing for the user. + * With this safety net, we will update the column type to TEXT. + * @rahulbarwal Remove this once we remove the feature flag + */ + if (!TableWidgetV2.getFeatureFlag(HTML_COLUMN_TYPE_ENABLED)) { + let hasHTMLColumns = false; + const { primaryColumns } = this.props; + + const updatedPrimaryColumns = objectKeys(primaryColumns).reduce( + (acc, widgetId) => { + const column = primaryColumns[widgetId]; + + if (column.columnType === ColumnTypes.HTML) { + acc[widgetId] = { ...column, columnType: ColumnTypes.TEXT }; + hasHTMLColumns = true; + } else { + acc[widgetId] = column; + } + + return acc; + }, + {} as Record, + ); + + if (hasHTMLColumns) { + this.updateWidgetProperty("primaryColumns", updatedPrimaryColumns); + } + } } componentDidUpdate(prevProps: TableWidgetProps) { diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data/ColumnType.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data/ColumnType.ts index 94ccc6b19f12..ee66fa0aa194 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data/ColumnType.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data/ColumnType.ts @@ -1,9 +1,11 @@ import { ColumnTypes, + HTML_COLUMN_TYPE_ENABLED, type TableWidgetProps, } from "widgets/TableWidgetV2/constants"; import { composePropertyUpdateHook } from "widgets/WidgetUtils"; +import Widget from "../../../index"; import { showByColumnType, updateCurrencyDefaultValues, @@ -28,10 +30,6 @@ const ColumnTypeOptions = [ label: "Date", value: ColumnTypes.DATE, }, - { - label: "HTML", - value: ColumnTypes.HTML, - }, { label: "Icon button", value: ColumnTypes.ICON_BUTTON, @@ -70,12 +68,22 @@ const ColumnTypeOptions = [ }, ]; +// TODO: @rahulbarwal Remove this once we have a feature flag for this +// This is a temporary solution to position the HTML column type alphabetically +const columnTypeWithHtml = [ + ...ColumnTypeOptions.slice(0, 4), + { label: "HTML", value: ColumnTypes.HTML }, + ...ColumnTypeOptions.slice(4), +]; + export const columnTypeConfig = { propertyName: "columnType", label: "Column type", helpText: "Type of column to be shown corresponding to the data of the column", controlType: "DROP_DOWN", + // TODO: Remove this once we have a feature flag for this + // Since we want to position the column types alphabetically, right now this is hardcoded options: ColumnTypeOptions, updateHook: composePropertyUpdateHook([ updateNumberColumnTypeTextAlignment, @@ -86,6 +94,44 @@ export const columnTypeConfig = { dependencies: ["primaryColumns", "columnOrder", "childStylesheet"], isBindProperty: false, isTriggerProperty: false, - hidden: (props: TableWidgetProps, propertyPath: string) => - showByColumnType(props, propertyPath, [ColumnTypes.EDIT_ACTIONS]), + hidden: (props: TableWidgetProps, propertyPath: string) => { + const isHTMLColumnTypeEnabled = Widget.getFeatureFlag( + HTML_COLUMN_TYPE_ENABLED, + ); + + return ( + isHTMLColumnTypeEnabled || + showByColumnType(props, propertyPath, [ColumnTypes.EDIT_ACTIONS]) + ); + }, +}; + +export const columnTypeWithHtmlConfig = { + propertyName: "columnType", + label: "Column type", + helpText: + "Type of column to be shown corresponding to the data of the column", + controlType: "DROP_DOWN", + // TODO: Remove this once we have a feature flag for this + // Since we want to position the column types alphabetically, right now this is hardcoded + options: columnTypeWithHtml, + updateHook: composePropertyUpdateHook([ + updateNumberColumnTypeTextAlignment, + updateThemeStylesheetsInColumns, + updateMenuItemsSource, + updateCurrencyDefaultValues, + ]), + dependencies: ["primaryColumns", "columnOrder", "childStylesheet"], + isBindProperty: false, + isTriggerProperty: false, + hidden: (props: TableWidgetProps, propertyPath: string) => { + const isHTMLColumnTypeEnabled = Widget.getFeatureFlag( + HTML_COLUMN_TYPE_ENABLED, + ); + + return ( + !isHTMLColumnTypeEnabled || + showByColumnType(props, propertyPath, [ColumnTypes.EDIT_ACTIONS]) + ); + }, }; diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data/index.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data/index.ts index de4a5685fdb9..6296da47e1a2 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data/index.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data/index.ts @@ -9,12 +9,13 @@ import { hideByColumnType, uniqueColumnAliasValidation, } from "../../../propertyUtils"; -import { columnTypeConfig } from "./ColumnType"; +import { columnTypeConfig, columnTypeWithHtmlConfig } from "./ColumnType"; export default { sectionName: "Data", children: [ columnTypeConfig, + columnTypeWithHtmlConfig, { helpText: "The alias that you use in selectedrow", propertyName: "alias",