mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-05 18:47:41 +00:00
Container Copy Job implementation for SQL accounts (#2241)
* Initial dev for container copy * remove padding from label * Added Copy Job prerequisites screen * Added hooks to evaluate reader role access * added copyjob pre-requsite screen along with it's validations * Added monitor copy job list screen * added copy job list refresh and reset functionality * remove arm token dependency * fetch account details from account id instead of context * Fix lint & typescript checks * show copyjob screen from portal navigation * adding copy job details screen * remove duplicate code & show sql accounts only * ui fixes for list job page * pending icon * copy job details screen ui * reset .vscode/settings.json * Fixed existing UTs * disabling action buttons until it's in progress * fixed formatting * Adding loader on submit button and show job creation errors in the panel itself * updating disabling action menu item logic * added custom pager * fix lint and ts errors * updating file names and removing comments * remove comments * modularize the arom common code * Adding content and removing tooltip * updating job details screen * updating online copy enabled screen * Adding below changes - Don't show permission screen for same account in offline mode - Don't show identity permissions for same account in online mode - Show error message if selected containers are identical - Update abort signal messages * added feedback code from explorer * Add tooltips and long polling - Added tooltips to permission sections - Implemented long polling for PITR and online copy enabled sections - Long polling automatically stops after 15 minutes - After polling ends, a refresh button will be displayed --------- Co-authored-by: nishthaAhujaa <nishtha17354@iiittd.ac.in>
This commit is contained in:
@@ -93,6 +93,7 @@ export class CapabilityNames {
|
||||
public static readonly EnableDataMasking: string = "EnableDataMasking";
|
||||
public static readonly EnableDynamicDataMasking: string = "EnableDynamicDataMasking";
|
||||
public static readonly EnableNoSQLFullTextSearchPreviewFeatures: string = "EnableNoSQLFullTextSearchPreviewFeatures";
|
||||
public static readonly EnableOnlineCopyFeature: string = "EnableOnlineCopyFeature";
|
||||
}
|
||||
|
||||
export enum CapacityMode {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TagNames, WorkloadType } from "Common/Constants";
|
||||
import { Tags } from "Contracts/DataModels";
|
||||
import { isFabric } from "Platform/Fabric/FabricUtil";
|
||||
import { userContext } from "../UserContext";
|
||||
import { ApiType, userContext } from "../UserContext";
|
||||
|
||||
function isVirtualNetworkFilterEnabled() {
|
||||
return userContext.databaseAccount?.properties?.isVirtualNetworkFilterEnabled;
|
||||
@@ -33,3 +33,33 @@ export function isGlobalSecondaryIndexEnabled(): boolean {
|
||||
!isFabric() && userContext.apiType === "SQL" && userContext.databaseAccount?.properties?.enableMaterializedViews
|
||||
);
|
||||
}
|
||||
|
||||
export const getDatabaseEndpoint = (apiType: ApiType): string => {
|
||||
switch (apiType) {
|
||||
case "Mongo":
|
||||
return "mongodbDatabases";
|
||||
case "Cassandra":
|
||||
return "cassandraKeyspaces";
|
||||
case "Gremlin":
|
||||
return "gremlinDatabases";
|
||||
case "Tables":
|
||||
return "tables";
|
||||
default:
|
||||
case "SQL":
|
||||
return "sqlDatabases";
|
||||
}
|
||||
};
|
||||
|
||||
export const getCollectionEndpoint = (apiType: ApiType): string => {
|
||||
switch (apiType) {
|
||||
case "Mongo":
|
||||
return "collections";
|
||||
case "Cassandra":
|
||||
return "tables";
|
||||
case "Gremlin":
|
||||
return "graphs";
|
||||
default:
|
||||
case "SQL":
|
||||
return "containers";
|
||||
}
|
||||
};
|
||||
|
||||
13
src/Common/Pager/Pager.css
Normal file
13
src/Common/Pager/Pager.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.pager-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.pager-container > div {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
111
src/Common/Pager/index.tsx
Normal file
111
src/Common/Pager/index.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import { IconButton, Text } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import "./Pager.css";
|
||||
|
||||
export interface PagerProps {
|
||||
startIndex: number;
|
||||
totalCount: number;
|
||||
pageSize: number;
|
||||
onLoadPage: (startIndex: number, pageSize: number) => void;
|
||||
disabled?: boolean;
|
||||
showFirstLast?: boolean;
|
||||
showItemCount?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const iconButtonStyles = {
|
||||
root: {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
rootHovered: {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
rootPressed: {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
rootDisabled: {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
rootFocused: {
|
||||
backgroundColor: "transparent",
|
||||
outline: "none",
|
||||
},
|
||||
};
|
||||
|
||||
const Pager: React.FC<PagerProps> = ({
|
||||
startIndex,
|
||||
totalCount,
|
||||
pageSize,
|
||||
onLoadPage,
|
||||
disabled = false,
|
||||
showFirstLast = true,
|
||||
showItemCount = true,
|
||||
className,
|
||||
}) => {
|
||||
// Calculate current page and total pages from startIndex
|
||||
const currentPage = Math.floor(startIndex / pageSize) + 1;
|
||||
const totalPages = Math.ceil(totalCount / pageSize);
|
||||
const endIndex = Math.min(startIndex + pageSize, totalCount);
|
||||
|
||||
const handleFirstPage = () => onLoadPage(0, pageSize);
|
||||
const handlePreviousPage = () => onLoadPage(startIndex - pageSize, pageSize);
|
||||
const handleNextPage = () => onLoadPage(startIndex + pageSize, pageSize);
|
||||
const handleLastPage = () => onLoadPage((totalPages - 1) * pageSize, pageSize);
|
||||
|
||||
if (totalCount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className || "pager-container"}>
|
||||
{showItemCount && (
|
||||
<Text>
|
||||
Showing {startIndex + 1} - {endIndex} of {totalCount} items
|
||||
</Text>
|
||||
)}
|
||||
<div>
|
||||
{showFirstLast && (
|
||||
<IconButton
|
||||
iconProps={{ iconName: "DoubleChevronLeft" }}
|
||||
title="First page"
|
||||
ariaLabel="Go to first page"
|
||||
onClick={handleFirstPage}
|
||||
disabled={disabled || currentPage === 1}
|
||||
styles={iconButtonStyles}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
iconProps={{ iconName: "ChevronLeft" }}
|
||||
title="Previous page"
|
||||
ariaLabel="Go to previous page"
|
||||
onClick={handlePreviousPage}
|
||||
disabled={disabled || currentPage === 1}
|
||||
styles={iconButtonStyles}
|
||||
/>
|
||||
<Text>
|
||||
Page {currentPage} of {totalPages}
|
||||
</Text>
|
||||
<IconButton
|
||||
iconProps={{ iconName: "ChevronRight" }}
|
||||
title="Next page"
|
||||
ariaLabel="Go to next page"
|
||||
onClick={handleNextPage}
|
||||
disabled={disabled || currentPage === totalPages}
|
||||
styles={iconButtonStyles}
|
||||
/>
|
||||
{showFirstLast && (
|
||||
<IconButton
|
||||
iconProps={{ iconName: "DoubleChevronRight" }}
|
||||
title="Last page"
|
||||
ariaLabel="Go to last page"
|
||||
onClick={handleLastPage}
|
||||
disabled={disabled || currentPage === totalPages}
|
||||
styles={iconButtonStyles}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Pager;
|
||||
32
src/Common/ShimmerTree/ShimmerTree.tsx
Normal file
32
src/Common/ShimmerTree/ShimmerTree.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Shimmer, ShimmerElementType, Stack } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
|
||||
export interface IndentLevel {
|
||||
level: number;
|
||||
width?: string;
|
||||
}
|
||||
interface ShimmerTreeProps {
|
||||
indentLevels: IndentLevel[];
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
const ShimmerTree = ({ indentLevels, style = {} }: ShimmerTreeProps) => {
|
||||
const renderShimmers = (indent: IndentLevel) => (
|
||||
<Shimmer
|
||||
key={Math.random()}
|
||||
shimmerElements={[
|
||||
{ type: ShimmerElementType.gap, width: `${indent.level * 20}px` },
|
||||
{ type: ShimmerElementType.line, height: 16, width: indent.width || "100%" },
|
||||
]}
|
||||
style={{ marginBottom: 8 }}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 8 }} style={{ width: "50%", ...style }} data-testid="shimmer-stack">
|
||||
{indentLevels.map((indentLevel: IndentLevel) => renderShimmers(indentLevel))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShimmerTree;
|
||||
Reference in New Issue
Block a user