mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-05-23 16:54:49 +01:00
Dynamic columns for pk
This commit is contained in:
parent
8c1a89403a
commit
8f5479923d
@ -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>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user