Skip to content

Commit 2b7c3d2

Browse files
author
Rudraprasad Das
committed
Merge branch 'release' of github.com:appsmithorg/appsmith into chore/git-mod-6
2 parents 65e9ea6 + 83db020 commit 2b7c3d2

24 files changed

Lines changed: 1382 additions & 126 deletions

File tree

.github/workflows/client-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ jobs:
249249
cp ../../../../../build.tar ./
250250
git lfs track "build.tar"
251251
git add build.tar
252-
git commit -m "Update Latest build.tar"
252+
git commit --allow-empty -m "Update Latest build.tar"
253253
git push
254254
255255
# Set status = success
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { htmlTableData } from "../../../../../../fixtures/htmlCellInTableWidgetV2";
2+
import { featureFlagIntercept } from "../../../../../../support/Objects/FeatureFlags";
3+
import {
4+
agHelper,
5+
entityExplorer,
6+
propPane,
7+
table,
8+
} from "../../../../../../support/Objects/ObjectsCore";
9+
10+
describe(
11+
"Table Filter for HTML Cell",
12+
{ tags: ["@tag.Widget", "@tag.Table"] },
13+
function () {
14+
before(() => {
15+
featureFlagIntercept({
16+
release_table_html_column_type_enabled: true,
17+
});
18+
entityExplorer.DragDropWidgetNVerify("tablewidgetv2", 650, 250);
19+
propPane.EnterJSContext("Table data", JSON.stringify(htmlTableData));
20+
});
21+
22+
it("1. Ensures HTML column type is available", function () {
23+
table.ReadTableRowColumnData(1, 3, "v2").then(($cellData) => {
24+
expect($cellData).to.include("Active");
25+
});
26+
});
27+
28+
it("2. Verify HTML columns are searchable", function () {
29+
table.ReadTableRowColumnData(1, 3, "v2").then(($cellData) => {
30+
expect($cellData).to.include("Active");
31+
table.SearchTable($cellData);
32+
table.ReadTableRowColumnData(0, 3, "v2").then((afterSearch) => {
33+
expect(afterSearch).to.eq($cellData);
34+
});
35+
});
36+
table.RemoveSearchTextNVerify("1", "v2");
37+
});
38+
39+
it("3. Verify Table Filter for HTML columns", function () {
40+
propPane.ExpandIfCollapsedSection("search\\&filters");
41+
agHelper.AssertExistingToggleState("Allow filtering", "false");
42+
propPane.TogglePropertyState("Allow filtering", "On");
43+
44+
table.OpenNFilterTable("status", "contains", "Active");
45+
table.ReadTableRowColumnData(0, 3, "v2").then(($cellData) => {
46+
expect($cellData).to.include("Active");
47+
});
48+
table.RemoveFilterNVerify("1", true, true, 0, "v2");
49+
50+
table.OpenNFilterTable("status", "contains", "Suspended");
51+
table.ReadTableRowColumnData(0, 3, "v2").then(($cellData) => {
52+
expect($cellData).to.include("Suspended");
53+
});
54+
table.RemoveFilterNVerify("1", true, true, 0, "v2");
55+
56+
table.OpenNFilterTable("status", "empty", "");
57+
table.ReadTableRowColumnData(0, 0, "v2").then(($cellData) => {
58+
expect($cellData).to.include("1");
59+
});
60+
table.RemoveFilterNVerify("1", true, true, 0, "v2");
61+
62+
table.OpenNFilterTable("status", "not empty", "");
63+
table.ReadTableRowColumnData(0, 0, "v2").then(($cellData) => {
64+
expect($cellData).to.include("2");
65+
});
66+
table.RemoveFilterNVerify("1", true, true, 0, "v2");
67+
});
68+
69+
it("4. Verify Table sorting for HTML columns", function () {
70+
table.SortColumn("status", "asc");
71+
table.ReadTableRowColumnData(0, 3, "v2").then(($cellData) => {
72+
expect($cellData).to.include("Active");
73+
});
74+
table.SortColumn("status", "desc");
75+
table.ReadTableRowColumnData(0, 3, "v2").then(($cellData) => {
76+
expect($cellData).to.include("Suspended");
77+
});
78+
});
79+
},
80+
);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
export const htmlTableData = [
2+
{
3+
id: 1,
4+
name: "John Smith",
5+
email: "john.smith@email.com",
6+
role: undefined,
7+
status: null,
8+
applicationDate: "2024-02-15",
9+
lastUpdated: "2024-03-20",
10+
department: "Engineering",
11+
},
12+
{
13+
id: 2,
14+
name: "Emma Wilson",
15+
email: "emma.w@email.com",
16+
role: "Designer",
17+
status:
18+
"<span style='background-color: #22c55e; color: white; padding: 4px 12px; border-radius: 20px; font-size: 14px;'><strong>Active</strong></span>",
19+
applicationDate: "2024-03-01",
20+
lastUpdated: "2024-03-19",
21+
department: "Design",
22+
},
23+
{
24+
id: 3,
25+
name: "Michael Brown",
26+
email: "m.brown@email.com",
27+
role: "Manager",
28+
status:
29+
"<span style='background-color: #ef4444; color: white; padding: 4px 12px; border-radius: 20px; font-size: 14px;'><strong>Suspended</strong></span>",
30+
applicationDate: "2024-01-10",
31+
lastUpdated: "2024-03-18",
32+
department: "Operations",
33+
},
34+
{
35+
id: 4,
36+
name: "Sarah Davis",
37+
email: "sarah.d@email.com",
38+
role: "Developer",
39+
status:
40+
"<span style='background-color: #22c55e; color: white; padding: 4px 12px; border-radius: 20px; font-size: 14px;'><strong>Active</strong></span>",
41+
applicationDate: "2024-02-20",
42+
lastUpdated: "2024-03-17",
43+
department: "Engineering",
44+
},
45+
{
46+
id: 5,
47+
name: "James Wilson",
48+
email: "j.wilson@email.com",
49+
role: "Analyst",
50+
status:
51+
"<span style='background-color: #3b82f6; color: white; padding: 4px 12px; border-radius: 20px; font-size: 14px;'><strong>Reviewing</strong></span>",
52+
applicationDate: "2024-03-05",
53+
lastUpdated: "2024-03-16",
54+
department: "Analytics",
55+
},
56+
];

app/client/cypress/support/Pages/Table.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ type columnTypeValues =
2727
| "Button"
2828
| "Menu button"
2929
| "Icon button"
30-
| "Select";
30+
| "Select"
31+
| "HTML";
3132

3233
export class Table {
3334
private agHelper = ObjectsRegistry.AggregateHelper;

app/client/src/ce/entities/FeatureFlag.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export const FEATURE_FLAG = {
4343
"release_table_custom_loading_state_enabled",
4444
release_custom_widget_ai_builder: "release_custom_widget_ai_builder",
4545
ab_request_new_integration_enabled: "ab_request_new_integration_enabled",
46+
release_table_html_column_type_enabled:
47+
"release_table_html_column_type_enabled",
4648
} as const;
4749

4850
export type FeatureFlag = keyof typeof FEATURE_FLAG;
@@ -81,6 +83,7 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = {
8183
release_table_custom_loading_state_enabled: false,
8284
release_custom_widget_ai_builder: false,
8385
ab_request_new_integration_enabled: false,
86+
release_table_html_column_type_enabled: false,
8487
};
8588

8689
export const AB_TESTING_EVENT_KEYS = {

app/client/src/ce/utils/analyticsUtilTypes.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,8 @@ export type EventName =
354354
| "CANVAS_HOVER"
355355
| "MALFORMED_USAGE_PULSE"
356356
| "REQUEST_INTEGRATION_CTA"
357-
| "REQUEST_INTEGRATION_SUBMITTED";
357+
| "REQUEST_INTEGRATION_SUBMITTED"
358+
| "TABLE_WIDGET_V2_HTML_CELL_USAGE";
358359

359360
type HOMEPAGE_CREATE_APP_FROM_TEMPLATE_EVENTS =
360361
| "TEMPLATE_DROPDOWN_CLICK"

app/client/src/widgets/TableWidgetV2/component/Table.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ import {
3030
} from "./Constants";
3131
import { Colors } from "constants/Colors";
3232
import type { EventType } from "constants/AppsmithActionConstants/ActionConstants";
33-
import type { EditableCell, TableVariant } from "../constants";
33+
import {
34+
ColumnTypes,
35+
type EditableCell,
36+
type TableVariant,
37+
} from "../constants";
3438
import SimpleBar from "simplebar-react";
3539
import "simplebar-react/dist/simplebar.min.css";
3640
import { createGlobalStyle } from "styled-components";
@@ -323,10 +327,19 @@ export function Table(props: TableProps) {
323327
props.width,
324328
]);
325329

330+
/**
331+
* What this really translates is to fixed height rows:
332+
* shouldUseVirtual: false -> fixed height row, irrespective of content small or big
333+
* shouldUseVirtual: true -> height adjusts acc to content
334+
* Right now all HTML content is dynamic height in nature hence
335+
* for server paginated tables it needs this extra handling.
336+
*/
326337
const shouldUseVirtual =
327338
props.serverSidePaginationEnabled &&
328339
!props.columns.some(
329-
(column) => !!column.columnProperties.allowCellWrapping,
340+
(column) =>
341+
!!column.columnProperties.allowCellWrapping ||
342+
column.metaProperties?.type === ColumnTypes.HTML,
330343
);
331344

332345
useEffect(() => {
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import type { RenderMode } from "constants/WidgetConstants";
2+
import Interweave from "interweave";
3+
import { isEqual } from "lodash";
4+
import React, { useEffect, useMemo, useRef } from "react";
5+
import styled from "styled-components";
6+
import LinkFilter from "widgets/TextWidget/component/filters/LinkFilter";
7+
import type { BaseCellComponentProps } from "../../Constants";
8+
import { CellWrapper } from "../../TableStyledWrappers";
9+
import { extractHTMLTags, sendHTMLCellAnalytics } from "./utils";
10+
11+
const HTMLContainer = styled.div`
12+
& {
13+
height: 100%;
14+
width: 100%;
15+
position: relative;
16+
}
17+
ul {
18+
list-style-type: disc;
19+
list-style-position: inside;
20+
}
21+
ol {
22+
list-style-type: decimal;
23+
list-style-position: inside;
24+
}
25+
ul ul,
26+
ol ul {
27+
list-style-type: circle;
28+
list-style-position: inside;
29+
margin-left: 15px;
30+
}
31+
ol ol,
32+
ul ol {
33+
list-style-type: lower-latin;
34+
list-style-position: inside;
35+
margin-left: 15px;
36+
}
37+
h1 {
38+
font-size: 2em;
39+
margin: 0.67em 0;
40+
}
41+
h2 {
42+
font-size: 1.5em;
43+
margin: 0.75em 0;
44+
}
45+
h3 {
46+
font-size: 1.17em;
47+
margin: 0.83em 0;
48+
}
49+
h5 {
50+
font-size: 0.83em;
51+
margin: 1.5em 0;
52+
}
53+
h6 {
54+
font-size: 0.75em;
55+
margin: 1.67em 0;
56+
}
57+
h1,
58+
h2,
59+
h3,
60+
h4,
61+
h5,
62+
h6 {
63+
font-weight: bold;
64+
}
65+
a {
66+
color: #106ba3;
67+
text-decoration: none;
68+
&:hover {
69+
text-decoration: underline;
70+
}
71+
}
72+
`;
73+
74+
export interface HTMLCellProps extends BaseCellComponentProps {
75+
value: string;
76+
fontSize?: string;
77+
renderMode: RenderMode;
78+
}
79+
80+
const HTMLCell = (props: HTMLCellProps) => {
81+
const {
82+
allowCellWrapping,
83+
cellBackground,
84+
compactMode,
85+
fontStyle,
86+
horizontalAlignment,
87+
isCellDisabled,
88+
isCellVisible,
89+
isHidden,
90+
renderMode,
91+
textColor,
92+
textSize,
93+
value,
94+
verticalAlignment,
95+
} = props;
96+
97+
const previousTagsRef = useRef<string[]>([]);
98+
99+
const interweaveCompatibleValue = useMemo(() => {
100+
if (value === null || value === undefined) return "";
101+
102+
return String(value);
103+
}, [value]);
104+
105+
/**
106+
* For analytics, we want to know what tags are being used by users in HTMLCell?
107+
* This will help us in knowing usage patterns and identifying if something is not working out.
108+
*/
109+
const extractedTags = useMemo(() => {
110+
if (!interweaveCompatibleValue) return [];
111+
112+
return extractHTMLTags(interweaveCompatibleValue);
113+
}, [interweaveCompatibleValue]);
114+
115+
useEffect(() => {
116+
const areTagsChanged = !isEqual(
117+
[...extractedTags].sort(),
118+
[...previousTagsRef.current].sort(),
119+
);
120+
121+
if (extractedTags.length > 0 && areTagsChanged) {
122+
sendHTMLCellAnalytics(extractedTags);
123+
previousTagsRef.current = extractedTags;
124+
}
125+
}, [extractedTags, renderMode]);
126+
127+
return (
128+
<CellWrapper
129+
allowCellWrapping={allowCellWrapping}
130+
cellBackground={cellBackground}
131+
className="cell-wrapper"
132+
compactMode={compactMode}
133+
fontStyle={fontStyle}
134+
horizontalAlignment={horizontalAlignment}
135+
isCellDisabled={isCellDisabled}
136+
isCellVisible={isCellVisible}
137+
isHidden={isHidden}
138+
textColor={textColor}
139+
textSize={textSize}
140+
verticalAlignment={verticalAlignment}
141+
>
142+
<HTMLContainer data-testid="t--table-widget-v2-html-cell">
143+
<Interweave
144+
content={interweaveCompatibleValue}
145+
filters={[new LinkFilter()]}
146+
newWindow
147+
/>
148+
</HTMLContainer>
149+
</CellWrapper>
150+
);
151+
};
152+
153+
export default HTMLCell;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
2+
import { debounce } from "lodash";
3+
4+
export const sendHTMLCellAnalytics = debounce(
5+
(tags: string[]) => {
6+
AnalyticsUtil.logEvent("TABLE_WIDGET_V2_HTML_CELL_USAGE", {
7+
tags: tags,
8+
});
9+
},
10+
1000,
11+
{ leading: true, trailing: false, maxWait: 5000 },
12+
);
13+
14+
export function extractHTMLTags(htmlString: string): string[] {
15+
const div = document.createElement("div");
16+
17+
div.innerHTML = htmlString;
18+
const elements = Array.from(div.getElementsByTagName("*"));
19+
const uniqueTags = new Set(
20+
elements.map((element) => element.tagName.toLowerCase()),
21+
);
22+
23+
return Array.from(uniqueTags);
24+
}

0 commit comments

Comments
 (0)