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
19 changes: 19 additions & 0 deletions src/component-library/atoms/misc/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { cn } from '@/component-library/utils';

interface ProgressBarProps {
className?: string;
percentage: number;
}

export default function ProgressBar({ className, percentage }: ProgressBarProps) {
const validPercentage = Math.min(Math.max(percentage, 0), 100);

const progressBarClasses = `rounded-full h-2.5 w-full bg-neutral-100 dark:bg-neutral-700 border border-neutral-900 border-opacity-10 dark:border-none`;
const progressClasses = `h-2.5 rounded-full bg-base-success-500 bg-opacity-80 dark:bg-opacity-60`;

return (
<div className={cn(progressBarClasses, className)}>
<div className={progressClasses} style={{ width: `${validPercentage}%` }} />
</div>
);
}
4 changes: 4 additions & 0 deletions src/component-library/outputtailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,10 @@ html {
white-space: nowrap;
}

.text-ellipsis {
text-overflow: ellipsis;
}

.whitespace-normal {
white-space: normal;
}
Expand Down
4 changes: 3 additions & 1 deletion src/component-library/pages/DID/details/DetailsDID.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { DetailsDIDParents } from '@/component-library/pages/DID/details/views/D
import { DetailsDIDContents } from '@/component-library/pages/DID/details/views/DetailsDIDContents';
import { DetailsDIDContentsReplicas } from '@/component-library/pages/DID/details/views/DetailsDIDContentsReplicas';
import { WarningField } from '@/component-library/features/fields/WarningField';
import { DetailsDIDDatasetReplicas } from './views/DetailsDIDDatasetReplicas';

type DetailsDIDTablesProps = {
scope: string;
Expand All @@ -30,6 +31,7 @@ export const DetailsDIDTables = ({ scope, name, type }: DetailsDIDTablesProps) =
const allTabs: Map<string, DetailsDIDView> = new Map([
['Attributes', DetailsDIDAttributes],
['Replicas', DetailsDIDFileReplicas],
['Dataset Replicas', DetailsDIDDatasetReplicas],
['Rules', DetailsDIDRules],
['Parents', DetailsDIDParents],
['Contents', DetailsDIDContents],
Expand All @@ -38,7 +40,7 @@ export const DetailsDIDTables = ({ scope, name, type }: DetailsDIDTablesProps) =

const tabsByType: Record<DIDType, string[]> = {
File: ['Replicas', 'Parents', 'Attributes'],
Dataset: ['Rules', 'Replicas', 'Contents Replicas', 'Attributes'],
Dataset: ['Rules', 'Dataset Replicas', 'Contents Replicas', 'Attributes'],
Container: ['Contents', 'Rules', 'Attributes'],
All: [],
Collection: [],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { useEffect, useRef, useState } from 'react';
import { DIDDatasetReplicasViewModel } from '@/lib/infrastructure/data/view-model/did';
import { DetailsDIDView, DetailsDIDProps } from '@/component-library/pages/DID/details/views/DetailsDIDView';
import useTableStreaming from '@/lib/infrastructure/hooks/useTableStreaming';
import { StreamedTable } from '@/component-library/features/table/StreamedTable/StreamedTable';
import { AgGridReact } from 'ag-grid-react';
import { DefaultTextFilterParams } from '@/component-library/features/utils/filter-parameters';
import ProgressBar from '@/component-library/atoms/misc/ProgressBar';
import { ReplicaState } from '@/lib/core/entity/rucio';
import { ReplicaStateBadge } from '@/component-library/features/badges/DID/ReplicaStateBadge';
import { badgeCellClasses, badgeCellWrapperStyle } from '@/component-library/features/table/cells/badge-cell';
import { ClickableCell } from '@/component-library/features/table/cells/ClickableCell';

const ProgressBarCell = ({ data }: { data: DIDDatasetReplicasViewModel }) => {
let percentage: number = 0;
if (data.length > 0) {
percentage = (data.available_files / data.length) * 100;
}

const getPercentageText = () => {
const baseText = `${data.available_files} files out of ${data.length}`;
if (data.length === 0) {
return baseText;
}
return `${baseText} (${percentage.toFixed(2)}%)`;
};

return (
<div className="flex flex-col w-full h-full justify-center space-y-2">
<ProgressBar percentage={percentage} />
<span className="text-ellipsis text-sm text-neutral-900 dark:text-neutral-100">{getPercentageText()}</span>
</div>
);
};

const StateCell = ({ data, className }: { data: DIDDatasetReplicasViewModel; className: string }) => {
const state = data.availability ? ReplicaState.AVAILABLE : ReplicaState.UNAVAILABLE;
return <ReplicaStateBadge value={state} className={`${className} h-7`} />;
};

const ClickableRSE = (props: { value: string }) => {
return <ClickableCell href={`/rse/page/${props.value}`}>{props.value}</ClickableCell>;
};

export const DetailsDIDDatasetReplicas: DetailsDIDView = ({ scope, name }: DetailsDIDProps) => {
const { gridApi, onGridReady, streamingHook, startStreaming, stopStreaming } = useTableStreaming<DIDDatasetReplicasViewModel>();
const gridRef = useRef<AgGridReact>(null);

useEffect(() => {
if (gridApi) {
const url = '/api/feature/list-dataset-replicas?' + new URLSearchParams({ scope, name });
startStreaming(url);
}
}, [gridApi]);

const [columnDefs] = useState([
{
headerName: 'RSE',
field: 'rse',
flex: 3,
sortable: false,
cellRenderer: ClickableRSE,
filter: true,
filterParams: DefaultTextFilterParams,
cellStyle: {
...badgeCellWrapperStyle,
alignItems: 'center',
},
},
{
headerName: 'State',
field: 'availability',
flex: 1,
cellStyle: {
...badgeCellWrapperStyle,
alignItems: 'center',
},
cellRendererParams: {
className: badgeCellClasses,
},
cellRenderer: StateCell,
},
{
headerName: 'Replication Progress',
flex: 3,
cellRenderer: ProgressBarCell,
cellStyle: {
...badgeCellWrapperStyle,
alignItems: 'center',
},
},
]);

return <StreamedTable columnDefs={columnDefs} tableRef={gridRef} onGridReady={onGridReady} streamingHook={streamingHook} rowHeight={75} />;
};
1 change: 1 addition & 0 deletions src/lib/core/entity/rucio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export enum DIDAvailability {
export type DIDDatasetReplicas = {
rse: string;
rseblocked: RSEBlockState;
length: number;
availability: boolean;
available_files: number;
available_bytes: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export function convertToDatasetReplicaDTO(replica: TRucioDatasetReplica): Datas
const dto: DatasetReplicasDTO = {
status: 'success',
rse: replica.rse,
length: replica.length,
availability: replica.state === 'AVAILABLE',
available_files: replica.available_length,
available_bytes: replica.available_bytes, // TODO: question: What is difference between available_bytes and bytes?
Expand Down
3 changes: 2 additions & 1 deletion test/api/replica/list-dataset-replicas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('List Dataset Replicas Feature Tests', () => {
rse: 'XRD3',
rse_id: '28e77f4f2c864f7d949eb602d6f05985',
bytes: 0,
length: 0,
length: 20,
available_bytes: 1000,
available_length: 10,
state: 'UNAVAILABLE',
Expand Down Expand Up @@ -86,6 +86,7 @@ describe('List Dataset Replicas Feature Tests', () => {
rseblocked: 0,
rse: 'XRD3',
availability: false,
length: 20,
available_files: 10,
available_bytes: 1000,
creation_date: 'Tue, 12 Sep 2023 09:12:11 UTC',
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/table-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ export function fixtureDIDDatasetReplicasViewModel(): DIDDatasetReplicasViewMode
availability: faker.datatype.boolean(),
available_files: faker.number.int({ min: 0, max: 1e6 }),
available_bytes: faker.number.int({ min: 0, max: 1e12 }),
length: faker.number.int({ min: 0, max: 1e6 }),
creation_date: faker.date.past().toISOString(),
last_accessed: faker.date.recent().toISOString(),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('Replica Gateway: List Dataset Replicas', () => {
rse: 'XRD3',
rse_id: '28e77f4f2c864f7d949eb602d6f05985',
bytes: 0,
length: 0,
length: 20,
available_bytes: 1000,
available_length: 10,
state: 'UNAVAILABLE',
Expand Down Expand Up @@ -76,6 +76,7 @@ describe('Replica Gateway: List Dataset Replicas', () => {
availability: false,
available_files: 10,
available_bytes: 1000,
length: 20,
creation_date: 'Tue, 12 Sep 2023 09:12:11 UTC',
last_accessed: '',
} as DatasetReplicasDTO,
Expand Down
Loading