Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/shaggy-cats-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@cobaltcore-dev/aurora": patch
---

fix(aurora): fix CreateBucketModal not rendering when bucket list is empty

Restructured BucketTableView to conditionally render empty state or table
content, ensuring CreateBucketModal can be displayed regardless of bucket
list state. Added test coverage for modal rendering with empty bucket list.
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,11 @@ describe("BucketTableView", () => {
expect(screen.queryByTestId("create-bucket-modal")).not.toBeInTheDocument()
})

test("renders CreateBucketModal even when bucket list is empty", () => {
renderTableView({ buckets: [], createModalOpen: true })
expect(screen.getByTestId("create-bucket-modal")).toBeInTheDocument()
})

test("closes CreateBucketModal when Cancel clicked", async () => {
const user = userEvent.setup()
const mockSetCreateOpen = vi.fn()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,26 +90,7 @@ export const BucketTableView = ({
}
}

if (!buckets || buckets.length === 0) {
return (
<DataGrid columns={4} className="buckets" data-testid="no-buckets">
<DataGridRow>
<DataGridCell colSpan={4}>
<div className="py-8 text-center">
<h3 className="text-lg font-semibold">
<Trans>No buckets found</Trans>
</h3>
<p className="text-theme-light mt-2">
<Trans>
There are no buckets available with the current search criteria. Try adjusting your search term.
</Trans>
</p>
</div>
</DataGridCell>
</DataGridRow>
</DataGrid>
)
}
const isEmpty = !buckets || buckets.length === 0
Comment thread
KirylSAP marked this conversation as resolved.

// Column template — drops the leading 40px selection track when bulk actions are unavailable.
// The header and the absolutely-positioned virtual rows must share an identical track string.
Expand All @@ -120,143 +101,162 @@ export const BucketTableView = ({

return (
<>
<div className="relative">
{/* Table Header with scrollbar padding */}
<div style={{ paddingRight: `${scrollbarWidth}px` }}>
<DataGrid
columns={columnCount}
gridColumnTemplate={gridColumnTemplate}
className="buckets"
data-testid="buckets-table-header"
>
<DataGridRow>
{hasAnyBulkAction && <DataGridHeadCell />}
<DataGridHeadCell>
<Trans>Bucket Name</Trans>
</DataGridHeadCell>
<DataGridHeadCell>
<Trans>Object Count</Trans>
</DataGridHeadCell>
<DataGridHeadCell>
<Trans>Last Modified</Trans>
</DataGridHeadCell>
<DataGridHeadCell>
<Trans>Total Size</Trans>
</DataGridHeadCell>
<DataGridHeadCell style={{ marginRight: `-${scrollbarWidth}px` }} />
</DataGridRow>
</DataGrid>
</div>
{isEmpty ? (
<DataGrid columns={4} className="buckets" data-testid="no-buckets">
<DataGridRow>
<DataGridCell colSpan={4}>
<div className="py-8 text-center">
<h3 className="text-lg font-semibold">
<Trans>No buckets found</Trans>
</h3>
<p className="text-theme-light mt-2">
<Trans>
There are no buckets available with the current search criteria. Try adjusting your search term.
</Trans>
</p>
</div>
</DataGridCell>
</DataGridRow>
</DataGrid>
) : (
<div className="relative">
{/* Table Header with scrollbar padding */}
<div style={{ paddingRight: `${scrollbarWidth}px` }}>
<DataGrid
columns={columnCount}
gridColumnTemplate={gridColumnTemplate}
className="buckets"
data-testid="buckets-table-header"
>
<DataGridRow>
{hasAnyBulkAction && <DataGridHeadCell />}
<DataGridHeadCell>
<Trans>Bucket Name</Trans>
</DataGridHeadCell>
<DataGridHeadCell>
<Trans>Object Count</Trans>
</DataGridHeadCell>
<DataGridHeadCell>
<Trans>Last Modified</Trans>
</DataGridHeadCell>
<DataGridHeadCell>
<Trans>Total Size</Trans>
</DataGridHeadCell>
<DataGridHeadCell style={{ marginRight: `-${scrollbarWidth}px` }} />
</DataGridRow>
</DataGrid>
</div>

{/* Virtualized Table Body with dynamic height */}
<div
ref={parentRef}
className="overflow-auto"
style={{
height: "calc(100vh - 490px)", // Dynamic height based on viewport
}}
data-testid="buckets-table-body"
>
{/* Virtualized Table Body with dynamic height */}
<div
ref={parentRef}
className="overflow-auto"
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
width: "100%",
position: "relative",
height: "calc(100vh - 490px)", // Dynamic height based on viewport
}}
data-testid="buckets-table-body"
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const bucket = buckets[virtualRow.index]
const isSelected = selectedBuckets.includes(bucket.name)
<div
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
width: "100%",
position: "relative",
}}
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const bucket = buckets[virtualRow.index]
const isSelected = selectedBuckets.includes(bucket.name)

const handleRowNavigate = () =>
navigate({
to: "/projects/$projectId/storage/$provider/$storageType/$containerName/objects",
params: {
projectId: projectId ?? "",
provider: (provider as string) ?? "ceph",
storageType: (storageType as string) ?? "buckets",
containerName: bucket.name,
},
})
const handleRowNavigate = () =>
navigate({
to: "/projects/$projectId/storage/$provider/$storageType/$containerName/objects",
params: {
projectId: projectId ?? "",
provider: (provider as string) ?? "ceph",
storageType: (storageType as string) ?? "buckets",
containerName: bucket.name,
},
})

return (
<div
key={bucket.name}
data-index={virtualRow.index}
ref={rowVirtualizer.measureElement}
className="juno-datagrid group hover:bg-theme-background-lvl-1 cursor-pointer"
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
transform: `translateY(${virtualRow.start}px)`,
display: "grid",
gridTemplateColumns: gridColumnTemplate,
alignItems: "stretch",
}}
data-testid={`bucket-row-${bucket.name}`}
role="link"
tabIndex={0}
onClick={handleRowNavigate}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
handleRowNavigate()
}
}}
>
{hasAnyBulkAction && (
<DataGridCell
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.stopPropagation()
}
}}
>
<Checkbox
checked={isSelected}
onChange={() => handleSelectBucket(bucket.name)}
data-testid={`select-bucket-${bucket.name}`}
/>
</DataGridCell>
)}
<DataGridCell className="min-w-0 overflow-hidden">
<span className="block truncate" title={bucket.name}>
{bucket.name}
</span>
</DataGridCell>
<DataGridCell>{bucket.count.toLocaleString()}</DataGridCell>
<DataGridCell>{formatDate(bucket.last_modified || bucket.creationDate || "")}</DataGridCell>
<DataGridCell>{formatBytesBinary(bucket.bytes)}</DataGridCell>
<DataGridCell onClick={(e) => e.stopPropagation()}>
<PopupMenu>
<PopupMenuOptions>
<PopupMenuItem
label={t`Show Details`}
onClick={handleRowNavigate}
data-testid={`show-details-action-${bucket.name}`}
return (
<div
key={bucket.name}
data-index={virtualRow.index}
ref={rowVirtualizer.measureElement}
className="juno-datagrid group hover:bg-theme-background-lvl-1 cursor-pointer"
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
transform: `translateY(${virtualRow.start}px)`,
display: "grid",
gridTemplateColumns: gridColumnTemplate,
alignItems: "stretch",
}}
data-testid={`bucket-row-${bucket.name}`}
role="link"
tabIndex={0}
onClick={handleRowNavigate}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
handleRowNavigate()
}
}}
>
{hasAnyBulkAction && (
<DataGridCell
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.stopPropagation()
}
}}
>
<Checkbox
checked={isSelected}
onChange={() => handleSelectBucket(bucket.name)}
data-testid={`select-bucket-${bucket.name}`}
/>
<PopupMenuItem
label={t`Empty`}
onClick={() => setEmptyModalBucket(bucket)}
data-testid={`empty-action-${bucket.name}`}
/>
<PopupMenuItem
label={t`Delete`}
onClick={() => setDeleteModalBucket(bucket)}
data-testid={`delete-action-${bucket.name}`}
/>
</PopupMenuOptions>
</PopupMenu>
</DataGridCell>
</div>
)
})}
</DataGridCell>
)}
<DataGridCell className="min-w-0 overflow-hidden">
<span className="block truncate" title={bucket.name}>
{bucket.name}
</span>
</DataGridCell>
<DataGridCell>{bucket.count.toLocaleString()}</DataGridCell>
<DataGridCell>{formatDate(bucket.last_modified || bucket.creationDate || "")}</DataGridCell>
<DataGridCell>{formatBytesBinary(bucket.bytes)}</DataGridCell>
<DataGridCell onClick={(e) => e.stopPropagation()}>
<PopupMenu>
<PopupMenuOptions>
<PopupMenuItem
label={t`Show Details`}
onClick={handleRowNavigate}
data-testid={`show-details-action-${bucket.name}`}
/>
<PopupMenuItem
label={t`Empty`}
onClick={() => setEmptyModalBucket(bucket)}
data-testid={`empty-action-${bucket.name}`}
/>
<PopupMenuItem
label={t`Delete`}
onClick={() => setDeleteModalBucket(bucket)}
data-testid={`delete-action-${bucket.name}`}
/>
</PopupMenuOptions>
</PopupMenu>
</DataGridCell>
</div>
)
})}
</div>
</div>
</div>
</div>
)}

<CreateBucketModal
isOpen={createModalOpen}
Expand Down
Loading