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