From ebb0e2c9ad0581d09c136e325d3c5db52c49ee1c Mon Sep 17 00:00:00 2001
From: Ezra Odio <97199317+ezraodio1@users.noreply.github.com>
Date: Wed, 17 Jul 2024 09:59:47 -0400
Subject: [PATCH] Adding ability to fix table columns in place (#7019)
This change involved adding an extra option to the GridSettings editor,
adding the "fixed" option to columns, and adding styling for the fixed
columns. In order to change the number of fixed columns, which will
default to 0, one has to go to Edit visualization -> Grid -> Choose
number of columns to fix -> Save.
---
.../table/.mocks/wide-dataset.js | 33 +++++++++++
.../visualizations/table/table_spec.js | 45 +++++++++++++++
.../cypress/support/visualizations/table.js | 4 +-
.../table/Editor/GridSettings.tsx | 57 +++++++++++++------
.../ColumnsSettings.test.tsx.snap | 5 ++
viz-lib/src/visualizations/table/Renderer.tsx | 10 +++-
.../src/visualizations/table/getOptions.ts | 2 +
.../src/visualizations/table/renderer.less | 9 ++-
viz-lib/src/visualizations/table/utils.tsx | 3 +-
9 files changed, 146 insertions(+), 22 deletions(-)
create mode 100644 client/cypress/integration/visualizations/table/.mocks/wide-dataset.js
diff --git a/client/cypress/integration/visualizations/table/.mocks/wide-dataset.js b/client/cypress/integration/visualizations/table/.mocks/wide-dataset.js
new file mode 100644
index 0000000000..e72384ec53
--- /dev/null
+++ b/client/cypress/integration/visualizations/table/.mocks/wide-dataset.js
@@ -0,0 +1,33 @@
+const loremIpsum =
+"Lorem ipsum dolor sit amet consectetur adipiscing elit" +
+"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua";
+
+export const query = `
+ SELECT '${loremIpsum}' AS a, '${loremIpsum}' AS b, '${loremIpsum}' AS c, '${loremIpsum}' AS d, '${loremIpsum}' as e
+`;
+
+export const config = {
+ itemsPerPage: 10,
+ columns: [
+ {
+ name: "a",
+ displayAs: "string",
+ },
+ {
+ name: "b",
+ displayAs: "string",
+ },
+ {
+ name: "c",
+ displayAs: "string",
+ },
+ {
+ name: "d",
+ displayAs: "string",
+ },
+ {
+ name: "e",
+ displayAs: "string",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/client/cypress/integration/visualizations/table/table_spec.js b/client/cypress/integration/visualizations/table/table_spec.js
index b0fa8ec61c..2d499633d4 100644
--- a/client/cypress/integration/visualizations/table/table_spec.js
+++ b/client/cypress/integration/visualizations/table/table_spec.js
@@ -8,6 +8,7 @@ import * as AllCellTypes from "./.mocks/all-cell-types";
import * as MultiColumnSort from "./.mocks/multi-column-sort";
import * as SearchInData from "./.mocks/search-in-data";
import * as LargeDataset from "./.mocks/large-dataset";
+import * as WideDataSet from "./.mocks/wide-dataset";
function prepareVisualization(query, type, name, options) {
return cy
@@ -98,6 +99,50 @@ describe("Table", () => {
});
});
+ describe("Fixing columns", () => {
+ it("fixes the correct number of columns", () => {
+ const { query, config } = WideDataSet;
+ prepareVisualization(query, "TABLE", "All cell types", config);
+ cy.getByTestId("EditVisualization").click();
+ cy.contains("span", "Grid").click();
+ cy.getByTestId("FixedColumns").click();
+ cy.contains(".ant-select-item-option-content", "1").click();
+ cy.contains("Save").click();
+ // eslint-disable-next-line cypress/no-unnecessary-waiting
+ cy.wait(500); //add some waiting to make sure table visualization is saved
+
+ cy.get(".ant-table-thead")
+ .find("th.ant-table-cell-fix-left")
+ .then(fixedCols => {
+ expect(fixedCols.length).to.equal(1);
+ });
+
+ cy.get(".ant-table-content").scrollTo("right", { duration: 1000 });
+ cy.get(".ant-table-content").scrollTo("left", { duration: 1000 });
+ });
+
+ it("doesn't let user fix too many columns", () => {
+ const { query, config } = MultiColumnSort;
+ prepareVisualization(query, "TABLE", "Test data", config);
+ cy.getByTestId("EditVisualization").click();
+ cy.contains("span", "Grid").click();
+ cy.getByTestId("FixedColumns").click();
+ cy.get(".ant-select-item-option-content");
+ cy.contains(".ant-select-item-option-content", "3").should("not.exist");
+ cy.contains(".ant-select-item-option-content", "4").should("not.exist");
+ });
+
+ it("doesn't cause issues when freezing column off of page", () => {
+ const { query, config } = WideDataSet;
+ prepareVisualization(query, "TABLE", "Test data", config);
+ cy.getByTestId("EditVisualization").click();
+ cy.contains("span", "Grid").click();
+ cy.getByTestId("FixedColumns").click();
+ cy.contains(".ant-select-item-option-content", "4").click();
+ cy.contains("Save").click();
+ });
+ });
+
it("searches in multiple columns", () => {
const { query, config } = SearchInData;
prepareVisualization(query, "TABLE", "Search", config).then(({ visualizationId }) => {
diff --git a/client/cypress/support/visualizations/table.js b/client/cypress/support/visualizations/table.js
index 2095b9fb88..7b290a1250 100644
--- a/client/cypress/support/visualizations/table.js
+++ b/client/cypress/support/visualizations/table.js
@@ -1,12 +1,12 @@
export function expectTableToHaveLength(length) {
cy.getByTestId("TableVisualization")
- .find("tbody tr")
+ .find("tbody tr.ant-table-row")
.should("have.length", length);
}
export function expectFirstColumnToHaveMembers(values) {
cy.getByTestId("TableVisualization")
- .find("tbody tr td:first-child")
+ .find("tbody tr.ant-table-row td:first-child")
.then($cell => Cypress.$.map($cell, item => Cypress.$(item).text()))
.then(firstColumnCells => expect(firstColumnCells).to.have.members(values));
}
diff --git a/viz-lib/src/visualizations/table/Editor/GridSettings.tsx b/viz-lib/src/visualizations/table/Editor/GridSettings.tsx
index 73aa57ac21..dc0688b135 100644
--- a/viz-lib/src/visualizations/table/Editor/GridSettings.tsx
+++ b/viz-lib/src/visualizations/table/Editor/GridSettings.tsx
@@ -1,28 +1,51 @@
import { map } from "lodash";
-import React from "react";
+import React, { useState } from "react";
import { Section, Select } from "@/components/visualizations/editor";
import { EditorPropTypes } from "@/visualizations/prop-types";
const ALLOWED_ITEM_PER_PAGE = [5, 10, 15, 20, 25, 50, 100, 150, 200, 250, 500];
+const ALLOWED_COLS_TO_FIX = [0, 1, 2, 3, 4]
+
export default function GridSettings({ options, onOptionsChange }: any) {
+ const numCols = options.columns.length;
+ const maxColsToFix = Math.min(4, numCols - 1);
+
return (
- // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message
-
-
-
+
+ {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never' but its value is 'Element'. */}
+
+
+
+ {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never' but its value is 'Element'. */}
+
+
+
+
);
}
diff --git a/viz-lib/src/visualizations/table/Editor/__snapshots__/ColumnsSettings.test.tsx.snap b/viz-lib/src/visualizations/table/Editor/__snapshots__/ColumnsSettings.test.tsx.snap
index 669b2e57e0..b2175a0c4d 100644
--- a/viz-lib/src/visualizations/table/Editor/__snapshots__/ColumnsSettings.test.tsx.snap
+++ b/viz-lib/src/visualizations/table/Editor/__snapshots__/ColumnsSettings.test.tsx.snap
@@ -13,6 +13,7 @@ Object {
],
"dateTimeFormat": undefined,
"displayAs": "string",
+ "fixed": false,
"highlightLinks": false,
"imageHeight": "",
"imageTitleTemplate": "{{ @ }}",
@@ -46,6 +47,7 @@ Object {
],
"dateTimeFormat": undefined,
"displayAs": "number",
+ "fixed": false,
"highlightLinks": false,
"imageHeight": "",
"imageTitleTemplate": "{{ @ }}",
@@ -79,6 +81,7 @@ Object {
],
"dateTimeFormat": undefined,
"displayAs": "string",
+ "fixed": false,
"highlightLinks": false,
"imageHeight": "",
"imageTitleTemplate": "{{ @ }}",
@@ -112,6 +115,7 @@ Object {
],
"dateTimeFormat": undefined,
"displayAs": "string",
+ "fixed": false,
"highlightLinks": false,
"imageHeight": "",
"imageTitleTemplate": "{{ @ }}",
@@ -145,6 +149,7 @@ Object {
],
"dateTimeFormat": undefined,
"displayAs": "string",
+ "fixed": false,
"highlightLinks": false,
"imageHeight": "",
"imageTitleTemplate": "{{ @ }}",
diff --git a/viz-lib/src/visualizations/table/Renderer.tsx b/viz-lib/src/visualizations/table/Renderer.tsx
index 3812e82b82..6dbb57abd5 100644
--- a/viz-lib/src/visualizations/table/Renderer.tsx
+++ b/viz-lib/src/visualizations/table/Renderer.tsx
@@ -84,6 +84,13 @@ export default function Renderer({ options, data }: any) {
const [searchTerm, setSearchTerm] = useState("");
const [orderBy, setOrderBy] = useState([]);
+ const columnsToFix = new Set();
+ for (let i = 0; i < options.fixedColumns; i++) {
+ if (options.columns[i]) {
+ columnsToFix.add(options.columns[i].name);
+ }
+ }
+
const searchColumns = useMemo(() => filter(options.columns, "allowSearch"), [options.columns]);
const tableColumns = useMemo(() => {
@@ -97,7 +104,7 @@ export default function Renderer({ options, data }: any) {
// Remove text selection - may occur accidentally
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
document.getSelection().removeAllRanges();
- });
+ }, columnsToFix);
}, [options.columns, searchColumns, orderBy]);
const preparedRows = useMemo(() => sortRows(filterRows(initRows(data.rows), searchTerm, searchColumns), orderBy), [
@@ -134,6 +141,7 @@ export default function Renderer({ options, data }: any) {
showSizeChanger: false,
}}
showSorterTooltip={false}
+ scroll = {{x : 'max-content'}}
/>
);
diff --git a/viz-lib/src/visualizations/table/getOptions.ts b/viz-lib/src/visualizations/table/getOptions.ts
index bcde21bbd6..2c4a090e39 100644
--- a/viz-lib/src/visualizations/table/getOptions.ts
+++ b/viz-lib/src/visualizations/table/getOptions.ts
@@ -4,6 +4,7 @@ import { visualizationsSettings } from "@/visualizations/visualizationsSettings"
const DEFAULT_OPTIONS = {
itemsPerPage: 25,
paginationSize: "default", // not editable through Editor
+ fixedColumns: 0,
};
const filterTypes = ["filter", "multi-filter", "multiFilter"];
@@ -56,6 +57,7 @@ function getDefaultColumnsOptions(columns: any) {
// `string` cell options
allowHTML: true,
highlightLinks: false,
+ fixed: false,
}));
}
diff --git a/viz-lib/src/visualizations/table/renderer.less b/viz-lib/src/visualizations/table/renderer.less
index 1c538037ab..16408381e1 100644
--- a/viz-lib/src/visualizations/table/renderer.less
+++ b/viz-lib/src/visualizations/table/renderer.less
@@ -21,7 +21,6 @@
left: 0;
top: 0;
border-top: 0;
- z-index: 1;
background: #fafafa !important;
}
}
@@ -157,3 +156,11 @@
color: @text-color-secondary;
}
}
+
+.ant-table-cell-fix-left{
+ background-color: #fff !important;
+}
+
+.ant-table-tbody > tr.ant-table-row:hover > .ant-table-cell-fix-left {
+ background-color: rgb(248, 249, 250) !important;
+}
\ No newline at end of file
diff --git a/viz-lib/src/visualizations/table/utils.tsx b/viz-lib/src/visualizations/table/utils.tsx
index 298cdb7f96..64ceef1d30 100644
--- a/viz-lib/src/visualizations/table/utils.tsx
+++ b/viz-lib/src/visualizations/table/utils.tsx
@@ -50,7 +50,7 @@ function getOrderByInfo(orderBy: any) {
return result;
}
-export function prepareColumns(columns: any, searchInput: any, orderBy: any, onOrderByChange: any) {
+export function prepareColumns(columns: any, searchInput: any, orderBy: any, onOrderByChange: any, columnsToFix: Set) {
columns = filter(columns, "visible");
columns = sortBy(columns, "order");
@@ -96,6 +96,7 @@ export function prepareColumns(columns: any, searchInput: any, orderBy: any, onO
}),
onClick: (event: any) => onOrderByChange(toggleOrderBy(column.name, orderBy, event.shiftKey)),
}),
+ fixed: columnsToFix.has(column.name) ? 'left' : false
};
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message