Compare commits

...

1 Commits

Author SHA1 Message Date
Ryan Laurie
a8a5f6dea7 prototype ag-grid 2024-08-15 12:20:06 -06:00
6 changed files with 192 additions and 97 deletions

View File

@@ -1,7 +1,12 @@
/* eslint-disable react/prop-types */
import { AgGridReact, type AgGridReactProps } from 'ag-grid-react'; // React Data Grid Component
import cx from "classnames";
import { type ComponentType, useCallback, useEffect, useState } from "react";
import { type ComponentType, useCallback, useState } from "react";
import { t } from "ttag";
import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the Data Grid
import { collectionApi, useListCollectionItemsQuery } from 'metabase/api';
import {
ALL_MODELS,
COLLECTION_PAGE_SIZE,
@@ -12,13 +17,18 @@ import type {
DeleteBookmark,
} from "metabase/collections/types";
import { isRootTrashCollection } from "metabase/collections/utils";
import DateTime from 'metabase/components/DateTime';
import { ItemsTable } from "metabase/components/ItemsTable";
import { EntityIconCheckBox } from 'metabase/components/ItemsTable/BaseItemsTable.styled';
import { Columns, getLastEditedBy } from 'metabase/components/ItemsTable/Columns';
import { PaginationControls } from "metabase/components/PaginationControls";
import CS from "metabase/css/core/index.css";
import { getIcon } from 'metabase/entities/questions';
import Search from "metabase/entities/search";
import { usePagination } from "metabase/hooks/use-pagination";
import { useSelector } from "metabase/lib/redux";
import { getIsEmbeddingSdk } from "metabase/selectors/embed";
import { Box, Button } from 'metabase/ui';
import type Database from "metabase-lib/v1/metadata/Database";
import type {
Bookmark,
@@ -29,11 +39,17 @@ import type {
} from "metabase-types/api";
import { SortDirection, type SortingOptions } from "metabase-types/api/sorting";
import ActionMenu from '../ActionMenu';
import {
CollectionEmptyContent,
CollectionTable,
} from "./CollectionContent.styled";
import type { IGetRowsParams } from 'ag-grid-community';
const getDefaultSortingOptions = (
collection: Collection | undefined,
): SortingOptions => {
@@ -113,25 +129,24 @@ export const CollectionItemsTable = ({
const [unpinnedItemsSorting, setUnpinnedItemsSorting] =
useState<SortingOptions>(() => getDefaultSortingOptions(collection));
const [total, setTotal] = useState<number | null>(null);
const [total, setTotal] = useState<number | null>(pageSize);
const { handleNextPage, handlePreviousPage, setPage, page, resetPage } =
usePagination();
const [page, setPage] = useState(0);
useEffect(() => {
if (collectionId) {
resetPage();
setTotal(null);
}
}, [collectionId, resetPage]);
// useEffect(() => {
// if (collectionId) {
// // resetPage();
// setTotal(null);
// }
// }, [collectionId, resetPage]);
const handleUnpinnedItemsSortingChange = useCallback(
(sortingOpts: SortingOptions) => {
setUnpinnedItemsSorting(sortingOpts);
setPage(0);
},
[setPage],
);
// const handleUnpinnedItemsSortingChange = useCallback(
// (sortingOpts: SortingOptions) => {
// setUnpinnedItemsSorting(sortingOpts);
// setPage(0);
// },
// [setPage],
// );
const showAllItems = isEmbeddingSdk || isRootTrashCollection(collection);
@@ -153,79 +168,133 @@ export const CollectionItemsTable = ({
}
};
// const { data: unpinnedItems } = useListCollectionItemsQuery({
// id: collectionId,
// pinned_state: "is_not_pinned",
// limit: pageSize,
// offset: pageSize * page,
// });
// Column Definitions: Defines the columns to be displayed.
const colDefs: AgGridReactProps["columnDefs"] = [
{ checkboxSelection: true, width: 40 },
{ field: "name", width: 300, filter: true },
// { field: "model", headerName: "Type", filter: true, width: 100,
// cellRenderer: ({ data: item }) =>
// (
// <EntityIconCheckBox
// variant="list"
// icon={getIcon(item)}
// pinned={false}
// />
// )
// },
// {
// field: "name",
// headerName: t`Name`,
// filter: () => {
// return (
// <Box p="lg">
// <img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExcHRuOG5pZXgzbTVuajA4MTU0Y3BqNnZ1NjhoZzJxbmFqZWkxZHI1MiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/vnGlErQHuF9BK/giphy.gif" />
// </Box>
// )
// },
// cellRenderer: ({ data: item }) => (
// <Columns.Name.Cell
// item={item}
// testIdPrefix="table"
// onClick={onClick}
// />
// )
// },
// {
// field: "last-edited-by",
// headerName: "Last Edited By",
// valueGetter: ({ data: item }: { data: CollectionItem }) => getLastEditedBy(item?.["last-edit-info"]),
// },
// {
// field: "last-edited-at",
// headerName: "Last Edited At",
// valueGetter: ({ data: item }: { data: CollectionItem }) => item?.["last-edit-info"]?.timestamp ?? '',
// cellRenderer: ({ value }: { value: string }) => (
// <DateTime unit="day" value={value} />
// )
// },
// {
// field: "action-menu",
// headerName: "",
// cellRenderer: ({ data: item }: { data: CollectionItem }) => {
// return (
// // <ActionMenu
// // item={item}
// // collection={collection}
// // databases={databases}
// // bookmarks={bookmarks}
// // // onCopy={onCopy}
// // // onMove={onMove}
// // createBookmark={createBookmark}
// // deleteBookmark={deleteBookmark}
// // />
// <>
// <Button onClick={() => console.log('copy', item)}>click me</Button>
// </>
// );
// },
// }
];
// if(!unpinnedItems) {
// return <div>Loading...</div>;
// }
const onGridReady = useCallback((params) => {
const dataSource = {
rowCount: pageSize,
getRows: async ({ startRow, successCallback, failCallback }) => {
console.log('asking for ' + params.startRow + ' to ' + params.endRow);
const response = await fetch(`/api/collection/${collectionId}/items?pinned_state=is_not_pinned&limit=${pageSize}&offset=${startRow / pageSize}`);
const body = await response.json();
const { data, total } = body;
setTotal(pageSize);
if(!data) {
failCallback();
}
console.log(data)
data
? successCallback(data)
: failCallback();
},
};
params.api.setGridOption('datasource', dataSource);
}, []);
return (
<Search.ListLoader
query={unpinnedQuery}
loadingAndErrorWrapper={false}
keepListWhileLoading
wrapped
onLoaded={onSearchListLoaded}
>
{({
list: unpinnedItems = [],
loading: loadingUnpinnedItems,
}: {
list: CollectionItem[];
loading: boolean;
}) => {
const hasPagination: boolean = total ? total > pageSize : false;
const unselected = getIsSelected
? unpinnedItems.filter(item => !getIsSelected(item))
: unpinnedItems;
const hasUnselected = unselected.length > 0;
const handleSelectAll = () => {
selectOnlyTheseItems?.(unpinnedItems);
};
const loading = loadingPinnedItems || loadingUnpinnedItems;
const isEmpty =
!loading && !hasPinnedItems && unpinnedItems.length === 0;
if (isEmpty && !loadingUnpinnedItems) {
return <EmptyContentComponent collection={collection} />;
}
return (
<CollectionTable data-testid="collection-table">
<ItemsTable
databases={databases}
bookmarks={bookmarks}
createBookmark={createBookmark}
deleteBookmark={deleteBookmark}
items={unpinnedItems}
collection={collection}
sortingOptions={unpinnedItemsSorting}
onSortingOptionsChange={handleUnpinnedItemsSortingChange}
selectedItems={selected}
hasUnselected={hasUnselected}
getIsSelected={getIsSelected}
onToggleSelected={toggleItem}
onDrop={clear}
onMove={handleMove}
onCopy={handleCopy}
onSelectAll={handleSelectAll}
onSelectNone={clear}
onClick={onClick}
showActionMenu={showActionMenu}
/>
<div className={cx(CS.flex, CS.justifyEnd, CS.my3)}>
{hasPagination && (
<PaginationControls
showTotal
page={page}
pageSize={pageSize}
total={total ?? undefined}
itemsLength={unpinnedItems.length}
onNextPage={handleNextPage}
onPreviousPage={handlePreviousPage}
/>
)}
</div>
</CollectionTable>
);
}}
</Search.ListLoader>
// wrapping container with theme & size
<>
<div
className="ag-theme-quartz" // applying the Data Grid theme
style={{ height: 400, width: '100%' }} // the Data Grid will fill the size of the parent container
nonce={window.MetabaseNonce}
>
<AgGridReact
columnDefs={colDefs}
infiniteInitialRowCount={25}
rowModelType='infinite'
onGridReady={onGridReady}
// rowData={unpinnedItems.data}
// rowSelection="multiple"
// enableCellTextSelection={false}
/>
</div>
</>
);
};

View File

@@ -1,5 +1,6 @@
import { AgGridReact } from 'ag-grid-react'; // React Data Grid Component
import type { HTMLAttributes, PropsWithChildren } from "react";
import { useMemo } from "react";
import { useMemo, useState } from "react";
import type {
CreateBookmark,
@@ -29,6 +30,9 @@ import {
import { Columns } from "../Columns";
import type { ResponsiveProps } from "../utils";
import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the Data Grid
import "ag-grid-community/styles/ag-theme-quartz.css"; // Optional Theme applied to the Data Grid
export type SortableColumnHeaderProps = {
name?: SortColumn;
sortingOptions?: SortingOptions;
@@ -153,7 +157,7 @@ export const BaseItemsTable = ({
collection?.can_write && typeof onToggleSelected === "function";
const isTrashed = !!collection && isTrashedCollection(collection);
return (
return (
<Table isInDragLayer={isInDragLayer} {...props}>
{includeColGroup && (
<colgroup>

View File

@@ -15,6 +15,7 @@ import { getFullName } from "metabase/lib/user";
import { PLUGIN_MODERATION } from "metabase/plugins";
import { getIsEmbeddingSdk } from "metabase/selectors/embed";
import type { IconProps } from "metabase/ui";
import { getUrl } from "metabase-lib/v1/urls";
import type { CollectionItem, SearchResult } from "metabase-types/api";
import type { SortableColumnHeaderProps } from "./BaseItemsTable";
@@ -49,7 +50,7 @@ const ItemLinkComponent = ({
return <ItemButton onClick={() => onClick?.(item)}>{children}</ItemButton>;
}
return (
<ItemLink to={item.getUrl()} onClick={() => onClick?.(item)}>
<ItemLink to={"FIXME"} onClick={() => onClick?.(item)}>
{children}
</ItemLink>
);
@@ -161,7 +162,6 @@ export const Columns = {
onClick?: (item: CollectionItem) => void;
}) => {
return (
<ItemNameCell data-testid={`${testIdPrefix}-name`}>
<ItemLinkComponent onClick={onClick} item={item}>
<EntityItem.Name name={item.name} variant="list" />
<PLUGIN_MODERATION.ModerationStatusIcon
@@ -180,7 +180,6 @@ export const Columns = {
/>
)}
</ItemLinkComponent>
</ItemNameCell>
);
},
},
@@ -327,7 +326,7 @@ export const Columns = {
},
};
const getLastEditedBy = (lastEditInfo?: Edit) => {
export const getLastEditedBy = (lastEditInfo?: Edit) => {
if (!lastEditInfo) {
return "";
}

View File

@@ -35,6 +35,8 @@
"@visx/shape": "^3.5.0",
"@visx/text": "^3.3.0",
"ace-builds": "^1.4.14",
"ag-grid-community": "^32.1.0",
"ag-grid-react": "^32.1.0",
"classnames": "^2.1.3",
"clipboardy": "^4.0.0",
"color": "^4.2.3",

View File

@@ -93,7 +93,8 @@
(when config/is-dev?
"http://localhost:9630")
"https://accounts.google.com"]
:font-src ["*"]
:font-src ["*"
"'self' data:"]
:img-src ["*"
"'self' data:"]
:connect-src ["'self'"

View File

@@ -6554,6 +6554,26 @@ address@^1.0.1:
resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==
ag-charts-types@10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/ag-charts-types/-/ag-charts-types-10.1.0.tgz#f265665f6b92742bb0bc2dcfab319b21e6d78749"
integrity sha512-pk9ft8hbgTXJ/thI/SEUR1BoauNplYExpcHh7tMOqVikoDsta1O15TB1ZL4XWnl4TPIzROBmONKsz7d8a2HBuQ==
ag-grid-community@32.1.0, ag-grid-community@^32.1.0:
version "32.1.0"
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-32.1.0.tgz#69d5b0af1b4d5cd5262705ecd87a3bd16c9c4155"
integrity sha512-RVvkjRH61nuCXwIqTKQPqNbKR+8cGBKw7S1qmmMXsy0pCBAJaQn4kL3v31hKHxDtV4bPscBXLFKGnKzHuss0GQ==
dependencies:
ag-charts-types "10.1.0"
ag-grid-react@^32.1.0:
version "32.1.0"
resolved "https://registry.yarnpkg.com/ag-grid-react/-/ag-grid-react-32.1.0.tgz#539620927a187961911b99439917df58076ad8bb"
integrity sha512-GDbtvU3aicSajWXWxvQio5ZaPqJDx2jzgRBKQf1RF1IVzL+XATDmLFNuMND0+wJ/VW/xUjBFjiq9W1fjXg/DCA==
dependencies:
ag-grid-community "32.1.0"
prop-types "^15.8.1"
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"