Dynamic columns for pk

This commit is contained in:
Laurent Nguyen 2024-03-20 13:56:09 +01:00
parent 8c1a89403a
commit 8f5479923d
2 changed files with 334 additions and 268 deletions

View File

@ -1,20 +1,20 @@
import { ItemDefinition, QueryIterator, Resource } from '@azure/cosmos'; import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import { FluentProvider } from '@fluentui/react-components'; import { FluentProvider } from "@fluentui/react-components";
import Split from '@uiw/react-split'; import Split from "@uiw/react-split";
import { KeyCodes, QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants"; import { KeyCodes, QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
import { getErrorMessage, getErrorStack } from 'Common/ErrorHandlingUtils'; import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { queryDocuments } from 'Common/dataAccess/queryDocuments'; import { queryDocuments } from "Common/dataAccess/queryDocuments";
import { readDocument } from 'Common/dataAccess/readDocument'; import { readDocument } from "Common/dataAccess/readDocument";
import { useDialog } from 'Explorer/Controls/Dialog'; import { useDialog } from "Explorer/Controls/Dialog";
import { EditorReact } from 'Explorer/Controls/Editor/EditorReact'; import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import { querySampleDocuments, readSampleDocument } from 'Explorer/QueryCopilot/QueryCopilotUtilities'; import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
import DocumentsTab from 'Explorer/Tabs/DocumentsTab'; import DocumentsTab from "Explorer/Tabs/DocumentsTab";
import { dataExplorerLightTheme } from 'Explorer/Theme/ThemeUtil'; import { dataExplorerLightTheme } from "Explorer/Theme/ThemeUtil";
import { QueryConstants } from 'Shared/Constants'; import { QueryConstants } from "Shared/Constants";
import { LocalStorageUtility, StorageKey } from 'Shared/StorageUtility'; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { Action } from 'Shared/Telemetry/TelemetryConstants'; import { Action } from "Shared/Telemetry/TelemetryConstants";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { logConsoleError } from 'Utils/NotificationConsoleUtils'; import { logConsoleError } from "Utils/NotificationConsoleUtils";
import React, { KeyboardEventHandler, useEffect, useMemo, useRef, useState } from "react"; import React, { KeyboardEventHandler, useEffect, useMemo, useRef, useState } from "react";
import { format } from "react-string-format"; import { format } from "react-string-format";
import CloseIcon from "../../../../images/close-black.svg"; import CloseIcon from "../../../../images/close-black.svg";
@ -42,7 +42,8 @@ export class DocumentsTabV2 extends TabsBase {
} }
public render(): JSX.Element { public render(): JSX.Element {
return <DocumentsTabComponent return (
<DocumentsTabComponent
isPreferredApiMongoDB={undefined} isPreferredApiMongoDB={undefined}
documentIds={this.documentIds} documentIds={this.documentIds}
tabId={this.tabId} tabId={this.tabId}
@ -50,7 +51,8 @@ export class DocumentsTabV2 extends TabsBase {
partitionKey={this.partitionKey} partitionKey={this.partitionKey}
onLoadStartKey={this.onLoadStartKey} onLoadStartKey={this.onLoadStartKey}
tabTitle={this.title} tabTitle={this.title}
/>; />
);
} }
public onActivate(): void { public onActivate(): void {
@ -84,8 +86,8 @@ const DocumentsTabComponent: React.FunctionComponent<{
// Query // Query
const [documentsIterator, setDocumentsIterator] = useState<{ const [documentsIterator, setDocumentsIterator] = useState<{
iterator: QueryIterator<ItemDefinition & Resource>, iterator: QueryIterator<ItemDefinition & Resource>;
applyFilterButtonPressed: boolean applyFilterButtonPressed: boolean;
}>(undefined); }>(undefined);
const [queryAbortController, setQueryAbortController] = useState<AbortController>(undefined); const [queryAbortController, setQueryAbortController] = useState<AbortController>(undefined);
const [resourceTokenPartitionKey, setResourceTokenPartitionKey] = useState<string>(undefined); const [resourceTokenPartitionKey, setResourceTokenPartitionKey] = useState<string>(undefined);
@ -95,7 +97,6 @@ const DocumentsTabComponent: React.FunctionComponent<{
const [isExecutionError, setIsExecutionError] = useState<boolean>(false); const [isExecutionError, setIsExecutionError] = useState<boolean>(false);
const [onLoadStartKey, setOnLoadStartKey] = useState<number>(props.onLoadStartKey); const [onLoadStartKey, setOnLoadStartKey] = useState<number>(props.onLoadStartKey);
const [currentDocument, setCurrentDocument] = useState<unknown>(undefined); const [currentDocument, setCurrentDocument] = useState<unknown>(undefined);
// TODO remove this? // TODO remove this?
@ -106,14 +107,17 @@ const DocumentsTabComponent: React.FunctionComponent<{
const documentContentsContainerId = `documentContentsContainer${props.tabId}`; const documentContentsContainerId = `documentContentsContainer${props.tabId}`;
const documentContentsGridId = `documentContentsGrid${props.tabId}`; const documentContentsGridId = `documentContentsGrid${props.tabId}`;
const partitionKey: DataModels.PartitionKey = props.partitionKey || (props.collection && props.collection.partitionKey); const partitionKey: DataModels.PartitionKey =
props.partitionKey || (props.collection && props.collection.partitionKey);
const partitionKeyPropertyHeaders: string[] = props.collection?.partitionKeyPropertyHeaders || partitionKey?.paths; const partitionKeyPropertyHeaders: string[] = props.collection?.partitionKeyPropertyHeaders || partitionKey?.paths;
const partitionKeyProperties = partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader) => const partitionKeyProperties = partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader) =>
partitionKeyPropertyHeader.replace(/[/]+/g, ".").substring(1).replace(/[']+/g, ""), partitionKeyPropertyHeader.replace(/[/]+/g, ".").substring(1).replace(/[']+/g, ""),
); );
const isPreferredApiMongoDB = useMemo(() => userContext.apiType === "Mongo" || props.isPreferredApiMongoDB, const isPreferredApiMongoDB = useMemo(
[props.isPreferredApiMongoDB]); () => userContext.apiType === "Mongo" || props.isPreferredApiMongoDB,
[props.isPreferredApiMongoDB],
);
useEffect(() => { useEffect(() => {
setDocumentIds(props.documentIds); setDocumentIds(props.documentIds);
@ -125,12 +129,10 @@ const DocumentsTabComponent: React.FunctionComponent<{
try { try {
refreshDocumentsGrid(); refreshDocumentsGrid();
// // Select first document and load content // // Select first document and load content
// if (documentIds.length > 0) { // if (documentIds.length > 0) {
// documentIds[0].click(); // documentIds[0].click();
// } // }
} catch (error) { } catch (error) {
if (onLoadStartKey !== null && onLoadStartKey !== undefined) { if (onLoadStartKey !== null && onLoadStartKey !== undefined) {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
@ -190,7 +192,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
return isQueryCopilotSampleContainer return isQueryCopilotSampleContainer
? querySampleDocuments(query, options) ? querySampleDocuments(query, options)
: queryDocuments(props.collection.databaseId, props.collection.id(), query, options); : queryDocuments(props.collection.databaseId, props.collection.id(), query, options);
} };
/** /**
* Query first page of documents * Query first page of documents
@ -224,7 +226,6 @@ const DocumentsTabComponent: React.FunctionComponent<{
applyFilterButtonPressed, applyFilterButtonPressed,
}); });
// collapse filter // collapse filter
setAppliedFilter(filterContent); setAppliedFilter(filterContent);
setIsFilterExpanded(false); setIsFilterExpanded(false);
@ -331,11 +332,15 @@ const DocumentsTabComponent: React.FunctionComponent<{
partitionKeyPropertyHeader.replace(/[/]+/g, ".").substring(1).replace(/[']+/g, ""), partitionKeyPropertyHeader.replace(/[/]+/g, ".").substring(1).replace(/[']+/g, ""),
); );
return new DocumentId({ return new DocumentId(
{
partitionKey, partitionKey,
partitionKeyPropertyHeaders, partitionKeyPropertyHeaders,
partitionKeyProperties partitionKeyProperties,
} as DocumentsTab, rawDocument, partitionKeyValue); } as DocumentsTab,
rawDocument,
partitionKeyValue,
);
}); });
const merged = currentDocuments.concat(nextDocumentIds); const merged = currentDocuments.concat(nextDocumentIds);
@ -423,46 +428,59 @@ const DocumentsTabComponent: React.FunctionComponent<{
props.collection?.databaseId === QueryCopilotSampleDatabaseId && props.collection?.databaseId === QueryCopilotSampleDatabaseId &&
props.collection?.id() === QueryCopilotSampleContainerId; props.collection?.id() === QueryCopilotSampleContainerId;
// Table config here // Table config here
const tableItems: DocumentsTableComponentItem[] = documentIds.map((documentId) => ({ const tableItems: DocumentsTableComponentItem[] = documentIds.map((documentId) => {
const item: Record<string, string> = {
id: documentId.id(), id: documentId.id(),
// TODO: for now, merge all the pk values into a single string/column };
type: documentId.partitionKeyProperties ? documentId.stringPartitionKeyValues.join(",") : undefined,
}));
if (partitionKeyPropertyHeaders && documentId.partitionKeyProperties) {
for (let i = 0; i < partitionKeyPropertyHeaders.length; i++) {
item[partitionKeyPropertyHeaders[i]] = documentId.stringPartitionKeyValues[i];
}
}
return item;
// TODO: for now, merge all the pk values into a single string/column
// type: documentId.partitionKeyProperties ? documentId.stringPartitionKeyValues.join(",") : undefined,
});
const onSelectedDocument = (index: number) => readSingleDocument(documentIds[index]); const onSelectedDocument = (index: number) => readSingleDocument(documentIds[index]);
// TODO: replicate logic of selectedDocument.click(); // TODO: replicate logic of selectedDocument.click();
// TODO: Check if editor is dirty // TODO: Check if editor is dirty
const readSingleDocument = (documentId: DocumentId) => (_isQueryCopilotSampleContainer const readSingleDocument = (documentId: DocumentId) =>
? readSampleDocument(documentId) (_isQueryCopilotSampleContainer ? readSampleDocument(documentId) : readDocument(props.collection, documentId)).then(
: readDocument(props.collection, documentId)).then((content) => { (content) => {
// this.initDocumentEditor(documentId, content); // this.initDocumentEditor(documentId, content);
setCurrentDocument(content); setCurrentDocument(content);
}); },
);
const tableContainerRef = useRef(null); const tableContainerRef = useRef(null);
const [tableContainerSizePx, setTableContainerSizePx] = useState<{ height: number, width: number }>(undefined); const [tableContainerSizePx, setTableContainerSizePx] = useState<{ height: number; width: number }>(undefined);
useEffect(() => { useEffect(() => {
if (!tableContainerRef.current) { if (!tableContainerRef.current) {
return undefined; return undefined;
} }
const resizeObserver = new ResizeObserver(() => setTableContainerSizePx({ const resizeObserver = new ResizeObserver(() =>
setTableContainerSizePx({
height: tableContainerRef.current.offsetHeight, height: tableContainerRef.current.offsetHeight,
width: tableContainerRef.current.offsetWidth, width: tableContainerRef.current.offsetWidth,
})); }),
);
resizeObserver.observe(tableContainerRef.current); resizeObserver.observe(tableContainerRef.current);
return () => resizeObserver.disconnect(); // clean up return () => resizeObserver.disconnect(); // clean up
}, []); }, []);
const columnHeaders = { const columnHeaders = {
idHeader: isPreferredApiMongoDB ? "_id" : "id", idHeader: isPreferredApiMongoDB ? "_id" : "id",
pkeyHeaders: partitionKeyPropertyHeaders partitionKeyHeaders: partitionKeyPropertyHeaders || [],
}; };
return <FluentProvider theme={dataExplorerLightTheme} style={{ height: "100%" }}> return (
<FluentProvider theme={dataExplorerLightTheme} style={{ height: "100%" }}>
<div <div
className="tab-pane active" className="tab-pane active"
/* data-bind=" /* data-bind="
@ -476,49 +494,66 @@ const DocumentsTabComponent: React.FunctionComponent<{
style={{ display: "flex" }} style={{ display: "flex" }}
> >
{/* <!-- Filter - Start --> */} {/* <!-- Filter - Start --> */}
{isFilterCreated && {isFilterCreated && (
<div className="filterdivs" /*data-bind="visible: isFilterCreated "*/> <div className="filterdivs" /*data-bind="visible: isFilterCreated "*/>
{/* <!-- Read-only Filter - Start --> */} {/* <!-- Read-only Filter - Start --> */}
{!isFilterExpanded && !isPreferredApiMongoDB && {!isFilterExpanded && !isPreferredApiMongoDB && (
<div className="filterDocCollapsed" /*data-bind="visible: !isFilterExpanded() && !isPreferredApiMongoDB"*/> <div
className="filterDocCollapsed" /*data-bind="visible: !isFilterExpanded() && !isPreferredApiMongoDB"*/
>
<span className="selectQuery">SELECT * FROM c</span> <span className="selectQuery">SELECT * FROM c</span>
<span className="appliedQuery" /*data-bind="text: appliedFilter"*/>{appliedFilter}</span> <span className="appliedQuery" /*data-bind="text: appliedFilter"*/>{appliedFilter}</span>
<button className="filterbtnstyle queryButton" onClick={onShowFilterClick} <button
/*data-bind="click: onShowFilterClick"*/>Edit Filter</button> className="filterbtnstyle queryButton"
</div> onClick={onShowFilterClick}
} /*data-bind="click: onShowFilterClick"*/
{!isFilterExpanded && isPreferredApiMongoDB && >
<div className="filterDocCollapsed" /*data-bind="visible: !isFilterExpanded() && isPreferredApiMongoDB"*/>
{appliedFilter.length > 0 &&
<span className="selectQuery" /*data-bind="visible: appliedFilter().length > 0"*/>Filter :</span>
}
{!(appliedFilter.length > 0) &&
<span className="noFilterApplied" /*data-bind="visible: !appliedFilter().length > 0"*/>No filter applied</span>
}
<span className="appliedQuery" /*data-bind="text: appliedFilter"*/>{appliedFilter}</span>
<button className="filterbtnstyle queryButton" onClick={onShowFilterClick} /*data-bind="click: onShowFilterClick"*/>
Edit Filter Edit Filter
</button> </button>
</div> </div>
} )}
{!isFilterExpanded && isPreferredApiMongoDB && (
<div className="filterDocCollapsed" /*data-bind="visible: !isFilterExpanded() && isPreferredApiMongoDB"*/>
{appliedFilter.length > 0 && (
<span className="selectQuery" /*data-bind="visible: appliedFilter().length > 0"*/>Filter :</span>
)}
{!(appliedFilter.length > 0) && (
<span className="noFilterApplied" /*data-bind="visible: !appliedFilter().length > 0"*/>
No filter applied
</span>
)}
<span className="appliedQuery" /*data-bind="text: appliedFilter"*/>{appliedFilter}</span>
<button
className="filterbtnstyle queryButton"
onClick={onShowFilterClick} /*data-bind="click: onShowFilterClick"*/
>
Edit Filter
</button>
</div>
)}
{/* <!-- Read-only Filter - End --> */} {/* <!-- Read-only Filter - End --> */}
{/* <!-- Editable Filter - start --> */} {/* <!-- Editable Filter - start --> */}
{isFilterExpanded && {isFilterExpanded && (
<div className="filterDocExpanded" /*data-bind="visible: isFilterExpanded"*/> <div className="filterDocExpanded" /*data-bind="visible: isFilterExpanded"*/>
<div> <div>
<div className="editFilterContainer"> <div className="editFilterContainer">
{!isPreferredApiMongoDB && {!isPreferredApiMongoDB && (
<span className="filterspan" /*data-bind="visible: !isPreferredApiMongoDB"*/> SELECT * FROM c </span> <span className="filterspan" /*data-bind="visible: !isPreferredApiMongoDB"*/>
} {" "}
SELECT * FROM c{" "}
</span>
)}
<input <input
type="text" type="text"
list="filtersList" list="filtersList"
className={`querydropdown ${filterContent.length === 0 ? "placeholderVisible" : ""}`} className={`querydropdown ${filterContent.length === 0 ? "placeholderVisible" : ""}`}
title="Type a query predicate or choose one from the list." title="Type a query predicate or choose one from the list."
placeholder={isPreferredApiMongoDB ? placeholder={
"Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents." : isPreferredApiMongoDB
"Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents."} ? "Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents."
: "Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents."
}
value={filterContent} value={filterContent}
onChange={(e) => setFilterContent(e.target.value)} onChange={(e) => setFilterContent(e.target.value)}
/* /*
@ -532,49 +567,65 @@ const DocumentsTabComponent: React.FunctionComponent<{
/> />
<datalist id="filtersList" /*data-bind="foreach: lastFilterContents"*/> <datalist id="filtersList" /*data-bind="foreach: lastFilterContents"*/>
{lastFilterContents.map((filter) => {lastFilterContents.map((filter) => (
<option key={filter} value={filter} /*data-bind="value: $data"*/ /> <option key={filter} value={filter} /*data-bind="value: $data"*/ />
)} ))}
</datalist> </datalist>
<span className="filterbuttonpad"> <span className="filterbuttonpad">
<button className="filterbtnstyle queryButton" onClick={() => refreshDocumentsGrid(true)} <button
className="filterbtnstyle queryButton"
onClick={() => refreshDocumentsGrid(true)}
disabled={!applyFilterButton.enabled} disabled={!applyFilterButton.enabled}
/* data-bind=" /* data-bind="
click: refreshDocumentsGrid.bind($data, true), click: refreshDocumentsGrid.bind($data, true),
enable: applyFilterButton.enabled" enable: applyFilterButton.enabled"
*/ */
aria-label="Apply filter" tabIndex={0}>Apply Filter</button> aria-label="Apply filter"
tabIndex={0}
>
Apply Filter
</button>
</span> </span>
<span className="filterbuttonpad"> <span className="filterbuttonpad">
{!isPreferredApiMongoDB && isExecuting && {!isPreferredApiMongoDB && isExecuting && (
<button className="filterbtnstyle queryButton" <button
className="filterbtnstyle queryButton"
/* data-bind=" /* data-bind="
visible: !isPreferredApiMongoDB && isExecuting, visible: !isPreferredApiMongoDB && isExecuting,
click: onAbortQueryClick" click: onAbortQueryClick"
*/ */
aria-label="Cancel Query" tabIndex={0}>Cancel Query</button> aria-label="Cancel Query"
} tabIndex={0}
>
Cancel Query
</button>
)}
</span> </span>
<span className="filterclose" role="button" aria-label="close filter" tabIndex={0} <span
onClick={() => onHideFilterClick()} onKeyDown={onCloseButtonKeyDown} className="filterclose"
/*data-bind="click: onHideFilterClick, event: { keydown: onCloseButtonKeyDown }"*/> role="button"
aria-label="close filter"
tabIndex={0}
onClick={() => onHideFilterClick()}
onKeyDown={onCloseButtonKeyDown}
/*data-bind="click: onHideFilterClick, event: { keydown: onCloseButtonKeyDown }"*/
>
<img src={CloseIcon} style={{ height: 14, width: 14 }} alt="Hide filter" /> <img src={CloseIcon} style={{ height: 14, width: 14 }} alt="Hide filter" />
</span> </span>
</div> </div>
</div> </div>
</div> </div>
} )}
{/* <!-- Editable Filter - End --> */} {/* <!-- Editable Filter - End --> */}
</div> </div>
} )}
{/* <!-- Filter - End --> */} {/* <!-- Filter - End --> */}
{/* <Split> doesn't like to be a flex child */} {/* <Split> doesn't like to be a flex child */}
<div style={{ overflow: "hidden", height: "100%" }}> <div style={{ overflow: "hidden", height: "100%" }}>
<Split> <Split>
<div style={{ minWidth: 480, width: "20%" }} <div style={{ minWidth: 480, width: "20%" }} ref={tableContainerRef}>
ref={tableContainerRef}>
<DocumentsTableComponent <DocumentsTableComponent
style={{ width: 200 }} style={{ width: 200 }}
items={tableItems} items={tableItems}
@ -582,7 +633,9 @@ const DocumentsTabComponent: React.FunctionComponent<{
size={tableContainerSizePx} size={tableContainerSizePx}
columnHeaders={columnHeaders} columnHeaders={columnHeaders}
/> />
<a className="loadMore" role="button" onClick={() => loadNextPage(false)}>Load more</a> <a className="loadMore" role="button" onClick={() => loadNextPage(false)}>
Load more
</a>
</div> </div>
<div style={{ minWidth: "20%", width: "100%" }}> <div style={{ minWidth: "20%", width: "100%" }}>
<EditorReact <EditorReact
@ -597,7 +650,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
</div> </div>
</Split> </Split>
</div> </div>
</div > </div>
</FluentProvider >; </FluentProvider>
} );
};

View File

@ -1,54 +1,48 @@
import { Menu, MenuItem, MenuList, MenuPopover, MenuTrigger, TableRowData as RowStateBase, Table, TableBody, TableCell, TableCellLayout, TableColumnDefinition, TableColumnSizingOptions, TableHeader, TableHeaderCell, TableRow, TableRowId, TableSelectionCell, createTableColumn, useArrowNavigationGroup, useFluent, useScrollbarWidth, useTableColumnSizing_unstable, useTableFeatures, useTableSelection } from '@fluentui/react-components'; import {
import React, { useEffect } from 'react'; Menu,
MenuItem,
MenuList,
MenuPopover,
MenuTrigger,
TableRowData as RowStateBase,
Table,
TableBody,
TableCell,
TableCellLayout,
TableColumnDefinition,
TableColumnSizingOptions,
TableHeader,
TableHeaderCell,
TableRow,
TableRowId,
TableSelectionCell,
createTableColumn,
useArrowNavigationGroup,
useFluent,
useScrollbarWidth,
useTableColumnSizing_unstable,
useTableFeatures,
useTableSelection,
} from "@fluentui/react-components";
import React, { useEffect, useMemo } from "react";
import { FixedSizeList as List, ListChildComponentProps } from "react-window"; import { FixedSizeList as List, ListChildComponentProps } from "react-window";
export type DocumentsTableComponentItem = { export type DocumentsTableComponentItem = {
id: string; id: string;
type: string; } & Record<string, string>;
};
export type ColumnHeaders = {
idHeader: string;
partitionKeyHeaders: string[];
};
export interface IDocumentsTableComponentProps { export interface IDocumentsTableComponentProps {
items: DocumentsTableComponentItem[]; items: DocumentsTableComponentItem[];
onSelectedItem: (index: number) => void; onSelectedItem: (index: number) => void;
size: { height: number; width: number }; size: { height: number; width: number };
columnHeaders: { columnHeaders: ColumnHeaders;
idHeader: string;
partitionKeyHeader: string;
};
style?: React.CSSProperties; style?: React.CSSProperties;
} }
const columns: TableColumnDefinition<DocumentsTableComponentItem>[] = [
createTableColumn<DocumentsTableComponentItem>({
columnId: "id",
compare: (a, b) => {
return a.id.localeCompare(b.id);
},
renderHeaderCell: () => {
return "id";
},
renderCell: (item) => {
return (
<TableCellLayout truncate>{item.id}</TableCellLayout>
);
},
}),
createTableColumn<DocumentsTableComponentItem>({
columnId: "type",
compare: (a, b) => {
return a.type.localeCompare(b.type);
},
renderHeaderCell: () => {
return "/type";
},
renderCell: (item) => {
return (
<TableCellLayout truncate>{item.type}</TableCellLayout>
);
},
}),
];
interface TableRowData extends RowStateBase<DocumentsTableComponentItem> { interface TableRowData extends RowStateBase<DocumentsTableComponentItem> {
onClick: (e: React.MouseEvent) => void; onClick: (e: React.MouseEvent) => void;
onKeyDown: (e: React.KeyboardEvent) => void; onKeyDown: (e: React.KeyboardEvent) => void;
@ -60,7 +54,11 @@ interface ReactWindowRenderFnProps extends ListChildComponentProps {
} }
export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> = ({ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> = ({
items, onSelectedItem, style, size, items,
onSelectedItem,
style,
size,
columnHeaders,
}: IDocumentsTableComponentProps) => { }: IDocumentsTableComponentProps) => {
const { targetDocument } = useFluent(); const { targetDocument } = useFluent();
const scrollbarWidth = useScrollbarWidth({ targetDocument }); const scrollbarWidth = useScrollbarWidth({ targetDocument });
@ -72,11 +70,12 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
idealWidth: 280, idealWidth: 280,
// minWidth: 273, // minWidth: 273,
}, },
type: { // TODO FIX THIS
defaultWidth: 100 // type: {
// minWidth: 110, // defaultWidth: 100,
// defaultWidth: 120, // // minWidth: 110,
}, // // defaultWidth: 120,
// },
}); });
const onColumnResize = React.useCallback((_, { columnId, width }) => { const onColumnResize = React.useCallback((_, { columnId, width }) => {
@ -89,19 +88,47 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
})); }));
}, []); }, []);
const [selectedRows, setSelectedRows] = React.useState<Set<TableRowId>>( const [selectedRows, setSelectedRows] = React.useState<Set<TableRowId>>(() => new Set<TableRowId>([0]));
() => new Set<TableRowId>([0])
// Columns must be a static object and cannot change on re-renders otherwise React will complain about too many refreshes
const columns: TableColumnDefinition<DocumentsTableComponentItem>[] = useMemo(
() =>
[
createTableColumn<DocumentsTableComponentItem>({
columnId: "id",
compare: (a, b) => {
return a.id.localeCompare(b.id);
},
renderHeaderCell: () => {
return "id";
},
renderCell: (item) => {
return <TableCellLayout truncate>{item.id}</TableCellLayout>;
},
}),
].concat(
columnHeaders.partitionKeyHeaders.map((pkHeader) =>
createTableColumn<DocumentsTableComponentItem>({
columnId: pkHeader,
compare: (a, b) => {
return a[pkHeader].localeCompare(b[pkHeader]);
},
renderHeaderCell: () => {
return `/${pkHeader}`;
},
renderCell: (item) => {
return <TableCellLayout truncate>{item[pkHeader]}</TableCellLayout>;
},
}),
),
),
[columnHeaders],
); );
const RenderRow = ({ index, style, data }: ReactWindowRenderFnProps) => { const RenderRow = ({ index, style, data }: ReactWindowRenderFnProps) => {
const { item, selected, appearance, onClick, onKeyDown } = data[index]; const { item, selected, appearance, onClick, onKeyDown } = data[index];
return <TableRow return (
aria-rowindex={index + 2} <TableRow aria-rowindex={index + 2} style={style} key={item.id} aria-selected={selected} appearance={appearance}>
style={style}
key={item.id}
aria-selected={selected}
appearance={appearance}
>
<TableSelectionCell <TableSelectionCell
checked={selected} checked={selected}
checkboxIndicator={{ "aria-label": "Select row" }} checkboxIndicator={{ "aria-label": "Select row" }}
@ -118,20 +145,15 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
{column.renderCell(item)} {column.renderCell(item)}
</TableCell> </TableCell>
))} ))}
</TableRow>; </TableRow>
);
}; };
const { const {
getRows, getRows,
columnSizing_unstable: columnSizing, columnSizing_unstable: columnSizing,
tableRef, tableRef,
selection: { selection: { allRowsSelected, someRowsSelected, toggleAllRows, toggleRow, isRowSelected },
allRowsSelected,
someRowsSelected,
toggleAllRows,
toggleRow,
isRowSelected,
},
} = useTableFeatures( } = useTableFeatures(
{ {
columns, columns,
@ -144,7 +166,7 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
selectedItems: selectedRows, selectedItems: selectedRows,
onSelectionChange: (e, data) => setSelectedRows(data.selectedItems), onSelectionChange: (e, data) => setSelectedRows(data.selectedItems),
}), }),
] ],
); );
const rows: TableRowData[] = getRows((row) => { const rows: TableRowData[] = getRows((row) => {
@ -170,7 +192,7 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
e.preventDefault(); e.preventDefault();
} }
}, },
[toggleAllRows] [toggleAllRows],
); );
// Load document depending on selection // Load document depending on selection
@ -202,30 +224,21 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableSelectionCell <TableSelectionCell
checked={ checked={allRowsSelected ? true : someRowsSelected ? "mixed" : false}
allRowsSelected ? true : someRowsSelected ? "mixed" : false
}
onClick={toggleAllRows} onClick={toggleAllRows}
onKeyDown={toggleAllKeydown} onKeyDown={toggleAllKeydown}
checkboxIndicator={{ "aria-label": "Select all rows " }} checkboxIndicator={{ "aria-label": "Select all rows " }}
/> />
{columns.map((column, /* index */) => ( {columns.map((column /* index */) => (
<Menu openOnContext key={column.columnId}> <Menu openOnContext key={column.columnId}>
<MenuTrigger> <MenuTrigger>
<TableHeaderCell <TableHeaderCell key={column.columnId} {...columnSizing.getTableHeaderCellProps(column.columnId)}>
key={column.columnId}
{...columnSizing.getTableHeaderCellProps(column.columnId)}
>
{column.renderHeaderCell()} {column.renderHeaderCell()}
</TableHeaderCell> </TableHeaderCell>
</MenuTrigger> </MenuTrigger>
<MenuPopover> <MenuPopover>
<MenuList> <MenuList>
<MenuItem <MenuItem onClick={columnSizing.enableKeyboardMode(column.columnId)}>
onClick={columnSizing.enableKeyboardMode(
column.columnId
)}
>
Keyboard Column Resizing Keyboard Column Resizing
</MenuItem> </MenuItem>
</MenuList> </MenuList>