Skip to content

Commit

Permalink
Merge pull request #1651 from alfonsomthd/object-sidebar
Browse files Browse the repository at this point in the history
S3 Browser: Add Object Details Sidebar
  • Loading branch information
openshift-merge-bot[bot] authored Oct 25, 2024
2 parents 4bae5b0 + da6f55f commit 9f02793
Show file tree
Hide file tree
Showing 20 changed files with 500 additions and 130 deletions.
11 changes: 7 additions & 4 deletions locales/en/plugin__odf-console.json
Original file line number Diff line number Diff line change
Expand Up @@ -1158,8 +1158,14 @@
"No tags are attached to this bucket.": "No tags are attached to this bucket.",
"Add tag": "Add tag",
"Value (optional)": "Value (optional)",
"Delete objects": "Delete objects",
"Actions": "Actions",
"This object has multiple versions. You are currently viewing the latest version. To access or manage previous versions, use S3 interface or CLI.": "This object has multiple versions. You are currently viewing the latest version. To access or manage previous versions, use S3 interface or CLI.",
"Key": "Key",
"Last modified": "Last modified",
"Size": "Size",
"Entity tag (ETag)": "Entity tag (ETag)",
"Metadata": "Metadata",
"Delete objects": "Delete objects",
"Search objects in the bucket using prefix": "Search objects in the bucket using prefix",
"Create folder": "Create folder",
"Failed to delete {{ errorCount }} object from the bucket. View deletion summary for details.": "Failed to delete {{ errorCount }} object from the bucket. View deletion summary for details.",
Expand All @@ -1168,8 +1174,6 @@
"Successfully deleted {{ successCount }} object from the bucket.": "Successfully deleted {{ successCount }} object from the bucket.",
"Successfully deleted {{ successCount }} objects from the bucket.": "Successfully deleted {{ successCount }} objects from the bucket.",
"Objects are the fundamental entities stored in buckets.": "Objects are the fundamental entities stored in buckets.",
"Size": "Size",
"Last modified": "Last modified",
"Download": "Download",
"Preview": "Preview",
"Share with presigned URL": "Share with presigned URL",
Expand Down Expand Up @@ -1557,7 +1561,6 @@
"Oh no! Something went wrong.": "Oh no! Something went wrong.",
"Copied to clipboard": "Copied to clipboard",
"Drag to reorder": "Drag to reorder",
"Key": "Key",
"Value": "Value",
"Add more": "Add more",
"Node is degraded": "Node is degraded",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import * as React from 'react';
import { Tag } from '@aws-sdk/client-s3';
import { NoobaaS3Context } from '@odf/core/components/s3-browser/noobaa-context';
import {
OBJECT_CACHE_KEY_SUFFIX,
OBJECT_TAGGING_CACHE_KEY_SUFFIX,
PREFIX,
} from '@odf/core/constants';
import { ObjectCrFormat } from '@odf/core/types';
import { replacePathFromName } from '@odf/core/utils';
import {
DASH,
DrawerHead,
LoadingBox,
useCustomTranslation,
} from '@odf/shared';
import { CopyToClipboard } from '@odf/shared/utils/copy-to-clipboard';
import { useParams, useSearchParams } from 'react-router-dom-v5-compat';
import useSWR from 'swr';
import {
Alert,
AlertVariant,
Drawer,
DrawerActions,
DrawerCloseButton,
DrawerContent,
DrawerContentBody,
DrawerPanelBody,
DrawerPanelContent,
Dropdown,
DropdownItem,
DropdownList,
Grid,
GridItem,
Label,
LabelGroup,
Level,
LevelItem,
MenuToggle,
MenuToggleElement,
Title,
} from '@patternfly/react-core';
import { TagIcon } from '@patternfly/react-icons';
import { IAction } from '@patternfly/react-table';
import './object-details-sidebar.scss';

type ObjectDetailsSidebarContentProps = {
closeSidebar: () => void;
object: ObjectCrFormat;
objectActions: IAction[];
};

const ObjectDetailsSidebarContent: React.FC<ObjectDetailsSidebarContentProps> =
({ closeSidebar, object, objectActions }) => {
const { t } = useCustomTranslation();
const [isOpen, setIsOpen] = React.useState(false);
const onToggleClick = () => {
setIsOpen(!isOpen);
};
const onSelect = () => {
setIsOpen(false);
};

const { noobaaS3 } = React.useContext(NoobaaS3Context);
const [searchParams] = useSearchParams();
const { bucketName } = useParams();

const objectKey = object?.metadata?.name;
const foldersPath = searchParams.get(PREFIX);
const objName = object ? replacePathFromName(object, foldersPath) : '';
const { data: objectData, isLoading: isObjectDataLoading } = useSWR(
`${objectKey}-${OBJECT_CACHE_KEY_SUFFIX}`,
() =>
noobaaS3.getObject({
Bucket: bucketName,
Key: objectKey,
})
);
const { data: tagData, isLoading: isTagDataLoading } = useSWR(
`${objectKey}-${OBJECT_TAGGING_CACHE_KEY_SUFFIX}`,
() =>
noobaaS3.getObjectTagging({
Bucket: bucketName,
Key: objectKey,
})
);

const tags = tagData?.TagSet?.map((tag: Tag) => (
<Label className="pf-v5-u-mr-xs" color="grey" icon={<TagIcon />}>
{tag.Key}
{tag.Value && `=${tag.Value}`}
</Label>
));
const metadata = [];
// @TODO: investigate why the Metadata is not returned (unlike in the CLI response).
if (objectData?.Metadata) {
for (const [key, value] of Object.entries(objectData?.Metadata)) {
metadata.push(
<div>
{key}
{value && `=${value}`}
</div>
);
}
}

const dropdownItems = [];
if (Array.isArray(objectActions)) {
objectActions.forEach((action: IAction) =>
dropdownItems.push(
<DropdownItem onClick={action.onClick}>{action.title}</DropdownItem>
)
);
}

const isLoading = isObjectDataLoading || isTagDataLoading;

return isLoading ? (
<LoadingBox />
) : (
<>
<DrawerHead>
<Title headingLevel="h3">{objName}</Title>
<DrawerActions>
<DrawerCloseButton onClick={closeSidebar} />
</DrawerActions>
<Dropdown
isOpen={isOpen}
onSelect={onSelect}
toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
className="odf-object-sidebar__dropdown"
ref={toggleRef}
onClick={onToggleClick}
>
{t('Actions')}
</MenuToggle>
)}
>
<DropdownList>{dropdownItems}</DropdownList>
</Dropdown>
{objectData?.VersionId && (
<Alert
className="pf-v5-u-mt-md"
isInline
variant={AlertVariant.info}
title={t(
'This object has multiple versions. You are currently viewing the latest version. To access or manage previous versions, use S3 interface or CLI.'
)}
></Alert>
)}
</DrawerHead>
<DrawerPanelBody>
<Grid className="odf-object-sidebar__data-grid" hasGutter>
<GridItem span={6}>
<h5>{t('Name')}</h5>
{objName}
</GridItem>
<GridItem span={6}>
<h5>{t('Key')}</h5>
<Level>
<LevelItem className="odf-object-sidebar__key">
{objectKey}
</LevelItem>
<LevelItem>
<CopyToClipboard value={objectKey} iconOnly={true} />
</LevelItem>
</Level>
</GridItem>
<GridItem span={6}>
<h5>{t('Version')}</h5>
{objectData?.VersionId || DASH}
</GridItem>
<GridItem span={6}>
<h5>{t('Owner')}</h5>
{object.apiResponse?.ownerName}
</GridItem>
<GridItem span={6}>
<h5>{t('Type')}</h5>
{objectData?.ContentType}
</GridItem>
<GridItem span={6}>
<h5>{t('Last modified')}</h5>
{object.apiResponse.lastModified}
</GridItem>
<GridItem span={6}>
<h5>{t('Size')}</h5>
{object.apiResponse.size}
</GridItem>
<GridItem span={6}>
<h5>{t('Entity tag (ETag)')}</h5>
{objectData?.ETag}
</GridItem>
<GridItem span={12}>
<h5>{t('Tags')}</h5>
{tags?.length > 0 ? <LabelGroup>{tags}</LabelGroup> : DASH}
</GridItem>
<GridItem span={12}>
<h5>{t('Metadata')}</h5>
{metadata.length > 0 ? metadata : DASH}
</GridItem>
</Grid>
</DrawerPanelBody>
</>
);
};

type ObjectDetailsSidebarProps = {
closeSidebar?: () => void;
isExpanded: boolean;
object?: ObjectCrFormat;
objectActions?: IAction[];
wrappedContent: React.ReactNode;
};

export const ObjectDetailsSidebar: React.FC<ObjectDetailsSidebarProps> = ({
closeSidebar,
isExpanded,
object,
objectActions,
wrappedContent,
}) => {
return (
<Drawer isExpanded={isExpanded} position="right">
<DrawerContent
panelContent={
<DrawerPanelContent defaultSize="700px" isResizable>
{object && (
<ObjectDetailsSidebarContent
closeSidebar={closeSidebar}
object={object}
objectActions={objectActions}
/>
)}
</DrawerPanelContent>
}
>
<DrawerContentBody>{wrappedContent}</DrawerContentBody>
</DrawerContent>
</Drawer>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.odf-object-sidebar__data-grid {
width: 96%;
}

.odf-object-sidebar__dropdown {
margin-left: auto;
width: 35%;
}

.odf-object-sidebar__key {
width: 80%;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as React from 'react';
import { ObjectDetailsSidebar } from '@odf/core/components/s3-browser/object-details/ObjectDetailsSidebar';
import { ObjectCrFormat } from '@odf/core/types';
import { LoadingBox } from '@odf/shared/generic/status-box';
import { useParams } from 'react-router-dom-v5-compat';
import { IAction } from '@patternfly/react-table';
import { NoobaaS3Context } from '../noobaa-context';
import UploadSidebar from '../upload-objects';
import { UploadProgress } from '../upload-objects';
Expand All @@ -14,7 +17,12 @@ type ObjectListWithSidebarProps = {
export const ObjectListWithSidebar: React.FC<ObjectListWithSidebarProps> = ({
obj: { refresh, triggerRefresh },
}) => {
const [isExpanded, setExpanded] = React.useState(false);
const [isUploadSidebarExpanded, setUploadSidebarExpanded] =
React.useState(false);
const [isObjectSidebarExpanded, setObjectSidebarExpanded] =
React.useState(false);
const [object, setObject] = React.useState<ObjectCrFormat>(null);
const [objectActions, setObjectActions] = React.useState<IAction[]>([]);
const [uploadProgress, setUploadProgress] = React.useState<UploadProgress>(
{}
);
Expand All @@ -28,13 +36,27 @@ export const ObjectListWithSidebar: React.FC<ObjectListWithSidebarProps> = ({

const { noobaaS3 } = React.useContext(NoobaaS3Context);

const closeSidebar = () => setExpanded(false);
const showSidebar = () => setExpanded(true);
const closeObjectSidebar = () => setObjectSidebarExpanded(false);
const closeUploadSidebar = () => setUploadSidebarExpanded(false);
const showUploadSidebar = () => {
closeObjectSidebar();
setUploadSidebarExpanded(true);
};
const onRowClick = (
selectedObject: ObjectCrFormat,
actionItems: IAction[]
) => {
if (selectedObject.isFolder) return;
closeUploadSidebar();
setObject(selectedObject);
setObjectActions(actionItems);
setObjectSidebarExpanded(true);
};

return (
<UploadSidebar
isExpanded={isExpanded}
closeSidebar={closeSidebar}
isExpanded={isUploadSidebarExpanded}
closeSidebar={closeUploadSidebar}
uploadProgress={uploadProgress}
completionTime={completionTime}
mainContent={
Expand All @@ -44,12 +66,27 @@ export const ObjectListWithSidebar: React.FC<ObjectListWithSidebarProps> = ({
bucketName={bucketName}
uploadProgress={uploadProgress}
setUploadProgress={setUploadProgress}
showSidebar={showSidebar}
showSidebar={showUploadSidebar}
abortAll={abortAll}
setCompletionTime={setCompletionTime}
triggerRefresh={triggerRefresh}
/>
{refresh ? <ObjectsList /> : <LoadingBox />}
{refresh ? (
<ObjectDetailsSidebar
closeSidebar={closeObjectSidebar}
isExpanded={isObjectSidebarExpanded}
object={object}
objectActions={objectActions}
wrappedContent={
<ObjectsList
onRowClick={onRowClick}
closeObjectSidebar={closeObjectSidebar}
/>
}
/>
) : (
<LoadingBox />
)}
</>
}
/>
Expand Down
Loading

0 comments on commit 9f02793

Please sign in to comment.