From 2db47be82c337c94cd3b16344c8dc390edcbb48d Mon Sep 17 00:00:00 2001 From: MytsV Date: Sat, 12 Oct 2024 13:45:22 +0300 Subject: [PATCH 01/32] Make the get RSE endpoint return all the required data --- src/lib/core/dto/rse-dto.ts | 16 ++-------- src/lib/core/entity/rucio.ts | 13 ++++++++ .../port/secondary/rse-gateway-output-port.ts | 4 +-- src/lib/core/use-case/get-rse-usecase.ts | 12 +++---- .../usecase-models/get-rse-usecase-models.ts | 4 +-- src/lib/infrastructure/data/view-model/rse.ts | 20 +++++++++++- .../rse-gateway/endpoints/get-rse-endpoint.ts | 31 ++++++++++--------- .../gateway/rse-gateway/rse-gateway.ts | 4 +-- .../presenter/get-rse-presenter.ts | 12 +++---- 9 files changed, 68 insertions(+), 48 deletions(-) diff --git a/src/lib/core/dto/rse-dto.ts b/src/lib/core/dto/rse-dto.ts index 36b3b2677..91c9f7383 100644 --- a/src/lib/core/dto/rse-dto.ts +++ b/src/lib/core/dto/rse-dto.ts @@ -1,5 +1,5 @@ import { BaseDTO, BaseStreamableDTO } from '@/lib/sdk/dto'; -import { RSE, RSEAttribute, RSEProtocol, RSEType } from '@/lib/core/entity/rucio'; +import { RSE, RSEAttribute, RSEDetails, RSEProtocol, RSEType } from '@/lib/core/entity/rucio'; /** * The Data Transfer Object for the ListRSEsEndpoint which contains the stream @@ -9,7 +9,7 @@ export interface ListRSEsDTO extends BaseStreamableDTO {} /** * Data Transfer Object for GET RSE Endpoint */ -export interface RSEDTO extends BaseDTO, RSE {} +export interface RSEDetailsDTO extends BaseDTO, RSEDetails {} /** * Data Transfer Object for GET RSE Protocols Endpoint @@ -34,15 +34,3 @@ export interface RSEUsageDTO extends BaseDTO { files: number; updated_at: string; } - -export function getEmptyRSEDTO(): RSEDTO { - return { - status: 'error', - id: '', - name: '', - rse_type: RSEType.UNKNOWN, - volatile: false, - staging_area: false, - deterministic: false, - }; -} diff --git a/src/lib/core/entity/rucio.ts b/src/lib/core/entity/rucio.ts index b5e43dc86..2332e9845 100644 --- a/src/lib/core/entity/rucio.ts +++ b/src/lib/core/entity/rucio.ts @@ -284,6 +284,19 @@ export type RSEProtocol = { created_at?: DateISO; // TODO: rucio does not provide this }; +export type RSEDetails = { + id: string; + name: string; + rse_type: RSEType; + volatile: boolean; + deterministic: boolean; + staging_area: boolean; + availability_delete: boolean; + availability_read: boolean; + availability_write: boolean; + protocols: RSEProtocol[]; +}; + export type RSEAttribute = { key: string; value: string | DateISO | number | boolean | null; diff --git a/src/lib/core/port/secondary/rse-gateway-output-port.ts b/src/lib/core/port/secondary/rse-gateway-output-port.ts index b02402cee..eb564a111 100644 --- a/src/lib/core/port/secondary/rse-gateway-output-port.ts +++ b/src/lib/core/port/secondary/rse-gateway-output-port.ts @@ -1,4 +1,4 @@ -import { ListRSEsDTO, RSEAttributeDTO, RSEDTO, RSEProtocolDTO, RSEUsageDTO } from '@/lib/core/dto/rse-dto'; +import { ListRSEsDTO, RSEAttributeDTO, RSEDetailsDTO, RSEProtocolDTO, RSEUsageDTO } from '@/lib/core/dto/rse-dto'; export default interface RSEGatewayOutputPort { /** @@ -6,7 +6,7 @@ export default interface RSEGatewayOutputPort { * @param rucioAuthToken A valid Rucio Auth Token. * @param rseName The RSE to get. */ - getRSE(rucioAuthToken: string, rseName: string): Promise; + getRSE(rucioAuthToken: string, rseName: string): Promise; /** * Lists all supported protocols for a given RSE. diff --git a/src/lib/core/use-case/get-rse-usecase.ts b/src/lib/core/use-case/get-rse-usecase.ts index 60adb4d80..8a7c3f304 100644 --- a/src/lib/core/use-case/get-rse-usecase.ts +++ b/src/lib/core/use-case/get-rse-usecase.ts @@ -5,12 +5,12 @@ import { AuthenticatedRequestModel } from '@/lib/sdk/usecase-models'; import { GetRSEError, GetRSERequest, GetRSEResponse } from '@/lib/core/usecase-models/get-rse-usecase-models'; import { GetRSEInputPort, type GetRSEOutputPort } from '@/lib/core/port/primary/get-rse-ports'; -import { RSEDTO } from '@/lib/core/dto/rse-dto'; +import { RSEDetailsDTO } from '@/lib/core/dto/rse-dto'; import type RSEGatewayOutputPort from '@/lib/core/port/secondary/rse-gateway-output-port'; @injectable() export default class GetRSEUseCase - extends BaseSingleEndpointUseCase, GetRSEResponse, GetRSEError, RSEDTO> + extends BaseSingleEndpointUseCase, GetRSEResponse, GetRSEError, RSEDetailsDTO> implements GetRSEInputPort { constructor(protected readonly presenter: GetRSEOutputPort, private readonly gateway: RSEGatewayOutputPort) { @@ -21,12 +21,12 @@ export default class GetRSEUseCase return undefined; } - async makeGatewayRequest(requestModel: AuthenticatedRequestModel): Promise { + async makeGatewayRequest(requestModel: AuthenticatedRequestModel): Promise { const { rucioAuthToken, rseName } = requestModel; - const dto: RSEDTO = await this.gateway.getRSE(rucioAuthToken, rseName); + const dto: RSEDetailsDTO = await this.gateway.getRSE(rucioAuthToken, rseName); return dto; } - handleGatewayError(error: RSEDTO): GetRSEError { + handleGatewayError(error: RSEDetailsDTO): GetRSEError { return { status: 'error', message: error.errorMessage ? error.errorMessage : 'Gateway Error', @@ -35,7 +35,7 @@ export default class GetRSEUseCase } as GetRSEError; } - processDTO(dto: RSEDTO): { data: GetRSEResponse | GetRSEError; status: 'success' | 'error' } { + processDTO(dto: RSEDetailsDTO): { data: GetRSEResponse | GetRSEError; status: 'success' | 'error' } { const responseModel: GetRSEResponse = { ...dto, status: 'success', diff --git a/src/lib/core/usecase-models/get-rse-usecase-models.ts b/src/lib/core/usecase-models/get-rse-usecase-models.ts index f82eb95c3..092dc809f 100644 --- a/src/lib/core/usecase-models/get-rse-usecase-models.ts +++ b/src/lib/core/usecase-models/get-rse-usecase-models.ts @@ -1,5 +1,5 @@ import { BaseErrorResponseModel, BaseResponseModel } from '@/lib/sdk/usecase-models'; -import { RSE } from '@/lib/core/entity/rucio'; +import { RSE, RSEDetails } from '@/lib/core/entity/rucio'; /** * @interface GetRSERequest represents the RequestModel for get_rse usecase */ @@ -10,7 +10,7 @@ export interface GetRSERequest { /** * @interface GetRSEResponse represents the ResponseModel for get_rse usecase */ -export interface GetRSEResponse extends RSE, BaseResponseModel {} +export interface GetRSEResponse extends RSEDetails, BaseResponseModel {} /** * @interface GetRSEError represents the ErrorModel for get_rse usecase diff --git a/src/lib/infrastructure/data/view-model/rse.ts b/src/lib/infrastructure/data/view-model/rse.ts index fa815852f..b663e5db3 100644 --- a/src/lib/infrastructure/data/view-model/rse.ts +++ b/src/lib/infrastructure/data/view-model/rse.ts @@ -1,8 +1,10 @@ import { BaseViewModel } from '@/lib/sdk/view-models'; -import { RSE, RSEAttribute, RSEAccountUsage, RSEProtocol, RSEType } from '@/lib/core/entity/rucio'; +import { RSE, RSEAttribute, RSEAccountUsage, RSEProtocol, RSEType, RSEDetails } from '@/lib/core/entity/rucio'; export interface RSEViewModel extends RSE, BaseViewModel {} +export interface RSEDetailsViewModel extends RSEDetails, BaseViewModel {} + export interface RSEProtocolViewModel extends BaseViewModel { protocols: RSEProtocol[]; } @@ -53,6 +55,22 @@ export function generateEmptyRSEViewModel(): RSEViewModel { } as RSEViewModel; } +export function generateEmptyRSEDetailsViewModel(): RSEDetailsViewModel { + return { + status: 'error', + id: '', + name: '', + rse_type: RSEType.UNKNOWN, + volatile: false, + deterministic: false, + staging_area: false, + protocols: [], + availability_delete: false, + availability_read: false, + availability_write: false, + } as RSEDetailsViewModel; +} + export function generateEmptyRSEProtocolViewModel(): RSEProtocolViewModel { return { status: 'error', diff --git a/src/lib/infrastructure/gateway/rse-gateway/endpoints/get-rse-endpoint.ts b/src/lib/infrastructure/gateway/rse-gateway/endpoints/get-rse-endpoint.ts index 1844d1d9b..8f75d9a3c 100644 --- a/src/lib/infrastructure/gateway/rse-gateway/endpoints/get-rse-endpoint.ts +++ b/src/lib/infrastructure/gateway/rse-gateway/endpoints/get-rse-endpoint.ts @@ -1,10 +1,10 @@ -import { RSEDTO } from '@/lib/core/dto/rse-dto'; -import { RSEType } from '@/lib/core/entity/rucio'; +import { RSEDetailsDTO } from '@/lib/core/dto/rse-dto'; +import { RSEDetails, RSEType } from '@/lib/core/entity/rucio'; import { BaseEndpoint } from '@/lib/sdk/gateway-endpoints'; import { HTTPRequest } from '@/lib/sdk/http'; import { Response } from 'node-fetch'; -export default class GetRSEEndpoint extends BaseEndpoint { +export default class GetRSEEndpoint extends BaseEndpoint { constructor(private rucioAuthToken: string, private rseName: string) { super(); } @@ -32,11 +32,15 @@ export default class GetRSEEndpoint extends BaseEndpoint { /** * @implements */ - async reportErrors(statusCode: number, response: Response): Promise { - const dto: RSEDTO = { + async reportErrors(statusCode: number, response: Response): Promise { + const dto: RSEDetailsDTO = { status: 'error', errorCode: statusCode, errorType: 'gateway_endpoint_error', + protocols: [], + availability_write: false, + availability_delete: false, + availability_read: false, id: '', name: this.rseName, rse_type: RSEType.UNKNOWN, @@ -60,15 +64,8 @@ export default class GetRSEEndpoint extends BaseEndpoint { /** * @implements */ - createDTO(data: any): RSEDTO { - data = data as { - id: string; - rse: string; - rse_type: string; - volatile: boolean; - deterministic: boolean; - staging_area: boolean; - }; + createDTO(data: any): RSEDetailsDTO { + data = data as RSEDetails; switch (data.rse_type.toUpperCase()) { case 'DISK': @@ -81,7 +78,7 @@ export default class GetRSEEndpoint extends BaseEndpoint { data.rse_type = RSEType.UNKNOWN; break; } - const dto: RSEDTO = { + const dto: RSEDetailsDTO = { status: 'success', id: data.id, name: data.rse, @@ -89,6 +86,10 @@ export default class GetRSEEndpoint extends BaseEndpoint { volatile: data.volatile, deterministic: data.deterministic, staging_area: data.staging_area, + protocols: data.protocols, + availability_write: data.availability_write, + availability_read: data.availability_read, + availability_delete: data.availability_delete, }; return dto; } diff --git a/src/lib/infrastructure/gateway/rse-gateway/rse-gateway.ts b/src/lib/infrastructure/gateway/rse-gateway/rse-gateway.ts index ebafc8916..43d00cb3d 100644 --- a/src/lib/infrastructure/gateway/rse-gateway/rse-gateway.ts +++ b/src/lib/infrastructure/gateway/rse-gateway/rse-gateway.ts @@ -1,4 +1,4 @@ -import { ListRSEsDTO, RSEAttributeDTO, RSEDTO, RSEProtocolDTO, RSEUsageDTO } from '@/lib/core/dto/rse-dto'; +import { ListRSEsDTO, RSEAttributeDTO, RSEDetailsDTO, RSEProtocolDTO, RSEUsageDTO } from '@/lib/core/dto/rse-dto'; import RSEGatewayOutputPort from '@/lib/core/port/secondary/rse-gateway-output-port'; import { injectable } from 'inversify'; import ListRSEsEndpoint from './endpoints/list-rses-endpoint'; @@ -9,7 +9,7 @@ import GetRSEUsageEndpoint from '@/lib/infrastructure/gateway/rse-gateway/endpoi @injectable() export default class RSEGateway implements RSEGatewayOutputPort { - async getRSE(rucioAuthToken: string, rseName: string): Promise { + async getRSE(rucioAuthToken: string, rseName: string): Promise { const endpoint = new GetRSEEndpoint(rucioAuthToken, rseName); const dto = await endpoint.fetch(); return dto; diff --git a/src/lib/infrastructure/presenter/get-rse-presenter.ts b/src/lib/infrastructure/presenter/get-rse-presenter.ts index 7bf402029..b2322a41d 100644 --- a/src/lib/infrastructure/presenter/get-rse-presenter.ts +++ b/src/lib/infrastructure/presenter/get-rse-presenter.ts @@ -1,10 +1,10 @@ import { BasePresenter } from '@/lib/sdk/presenter'; import { GetRSEError, GetRSEResponse } from '@/lib/core/usecase-models/get-rse-usecase-models'; -import { generateEmptyRSEViewModel, RSEViewModel } from '@/lib/infrastructure/data/view-model/rse'; +import { generateEmptyRSEDetailsViewModel, RSEDetailsViewModel } from '@/lib/infrastructure/data/view-model/rse'; -export default class GetRSEPresenter extends BasePresenter { - convertResponseModelToViewModel(responseModel: GetRSEResponse): { viewModel: RSEViewModel; status: number } { - const viewModel: RSEViewModel = { +export default class GetRSEPresenter extends BasePresenter { + convertResponseModelToViewModel(responseModel: GetRSEResponse): { viewModel: RSEDetailsViewModel; status: number } { + const viewModel: RSEDetailsViewModel = { ...responseModel, }; return { @@ -13,8 +13,8 @@ export default class GetRSEPresenter extends BasePresenter Date: Tue, 15 Oct 2024 15:07:28 +0300 Subject: [PATCH 02/32] Reimplement the RSE page view --- src/app/(rucio)/rse/page/[name]/page.tsx | 42 +---- .../features/badges/NullBadge.tsx | 8 + .../badges/RSE/RSEAvailabilityBadge.tsx | 8 + .../features/key-value/KeyValueRow.tsx | 2 +- .../features/table/cells/AttributeCell.tsx | 23 +++ src/component-library/outputtailwind.css | 14 ++ .../pages/RSE/details/DetailsRSE.tsx | 160 ++++++++++++++++++ .../RSE/details/DetailsRSEAttributesTable.tsx | 35 ++++ .../RSE/details/DetailsRSEProtocolsTable.tsx | 108 ++++++++++++ .../pages/legacy/RSE/PageRSEProtocols.tsx | 3 - src/lib/core/entity/rucio.ts | 25 ++- 11 files changed, 383 insertions(+), 45 deletions(-) create mode 100644 src/component-library/features/badges/NullBadge.tsx create mode 100644 src/component-library/features/badges/RSE/RSEAvailabilityBadge.tsx create mode 100644 src/component-library/features/table/cells/AttributeCell.tsx create mode 100644 src/component-library/pages/RSE/details/DetailsRSE.tsx create mode 100644 src/component-library/pages/RSE/details/DetailsRSEAttributesTable.tsx create mode 100644 src/component-library/pages/RSE/details/DetailsRSEProtocolsTable.tsx diff --git a/src/app/(rucio)/rse/page/[name]/page.tsx b/src/app/(rucio)/rse/page/[name]/page.tsx index a91d1baf0..7f06afd48 100644 --- a/src/app/(rucio)/rse/page/[name]/page.tsx +++ b/src/app/(rucio)/rse/page/[name]/page.tsx @@ -1,43 +1,5 @@ -'use client'; -import { Loading } from '@/component-library/pages/legacy/Helpers/Loading'; -import { PageRSE as PageRSEStory } from '@/component-library/pages/legacy/RSE/PageRSE'; -import { RSEBlockState } from '@/lib/core/entity/rucio'; -import { RSEAttributeViewModel, RSEProtocolViewModel, RSEViewModel } from '@/lib/infrastructure/data/view-model/rse'; -import { useEffect, useState } from 'react'; - -async function getRSE(rseName: string): Promise { - const url = `${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/get-rse?` + new URLSearchParams({ rseName }); - const res = await fetch(url); - return await res.json(); -} - -async function getProtocols(rseName: string): Promise { - const url = `${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/get-rse-protocols?` + new URLSearchParams({ rseName }); - const res = await fetch(url); - return await res.json(); -} - -async function getAttributes(rseName: string): Promise { - const url = `${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/get-rse-attributes?` + new URLSearchParams({ rseName }); - const res = await fetch(url); - return await res.json(); -} +import { DetailsRSE } from '@/component-library/pages/RSE/details/DetailsRSE'; export default function Page({ params }: { params: { name: string } }) { - const [rse, setRSE] = useState({ status: 'pending' } as RSEViewModel); - const [protocols, setProtocols] = useState({ status: 'pending' } as RSEProtocolViewModel); - const [attributes, setAttributes] = useState({ status: 'pending' } as RSEAttributeViewModel); - useEffect(() => { - getRSE(params.name).then(setRSE); - }, [params.name]); - useEffect(() => { - getProtocols(params.name).then(setProtocols); - }, [params.name]); - useEffect(() => { - getAttributes(params.name).then(setAttributes); - }, [params.name]); - if (rse.status === 'pending' || protocols.status === 'pending' || attributes.status === 'pending') { - return ; - } - return ; + return ; } diff --git a/src/component-library/features/badges/NullBadge.tsx b/src/component-library/features/badges/NullBadge.tsx new file mode 100644 index 000000000..398ffe90d --- /dev/null +++ b/src/component-library/features/badges/NullBadge.tsx @@ -0,0 +1,8 @@ +import { cn } from '@/component-library/utils'; +import { Badge } from '@/component-library/atoms/misc/Badge'; +import React from 'react'; + +export const NullBadge = (props: { className?: string }) => { + const classes = cn('bg-neutral-200 text-neutral-400 dark:bg-neutral-800 dark:text-neutral-600', props.className); + return ; +}; diff --git a/src/component-library/features/badges/RSE/RSEAvailabilityBadge.tsx b/src/component-library/features/badges/RSE/RSEAvailabilityBadge.tsx new file mode 100644 index 000000000..c645a567d --- /dev/null +++ b/src/component-library/features/badges/RSE/RSEAvailabilityBadge.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { Badge } from '@/component-library/atoms/misc/Badge'; +import { cn } from '@/component-library/utils'; + +export const RSEAvailabilityBadge = (props: { operation: string; className?: string }) => { + const classes = cn('bg-neutral-200 dark:bg-neutral-800', props.className); + return ; +}; diff --git a/src/component-library/features/key-value/KeyValueRow.tsx b/src/component-library/features/key-value/KeyValueRow.tsx index 4011b1d75..6eb8f59d2 100644 --- a/src/component-library/features/key-value/KeyValueRow.tsx +++ b/src/component-library/features/key-value/KeyValueRow.tsx @@ -5,7 +5,7 @@ export const KeyValueRow = (props: { name: string; children: ReactNode }) => { return (
{props.name} - {props.children} + {props.children}
); }; diff --git a/src/component-library/features/table/cells/AttributeCell.tsx b/src/component-library/features/table/cells/AttributeCell.tsx new file mode 100644 index 000000000..d80870f8d --- /dev/null +++ b/src/component-library/features/table/cells/AttributeCell.tsx @@ -0,0 +1,23 @@ +import { DateISO } from '@/lib/core/entity/rucio'; +import Checkbox from '@/component-library/atoms/form/Checkbox'; +import React from 'react'; +import { formatDate } from '@/component-library/features/utils/text-formatters'; +import { NullBadge } from '@/component-library/features/badges/NullBadge'; + +const isDateISO = (value: unknown): value is DateISO => { + if (typeof value !== 'string') return false; + const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|([+-]\d{2}:\d{2}))$/; + return isoDateRegex.test(value); +}; + +export const AttributeCell = ({ value }: { value: string | DateISO | number | boolean | null }) => { + if (value === null) { + return ; + } else if (typeof value === 'boolean') { + return ; + } else if (isDateISO(value)) { + return formatDate(value); + } else { + return value; + } +}; diff --git a/src/component-library/outputtailwind.css b/src/component-library/outputtailwind.css index fffe648bc..414852ac3 100644 --- a/src/component-library/outputtailwind.css +++ b/src/component-library/outputtailwind.css @@ -2253,6 +2253,11 @@ html { color: rgb(241 245 249 / var(--tw-text-opacity)); } +.text-neutral-400 { + --tw-text-opacity: 1; + color: rgb(148 163 184 / var(--tw-text-opacity)); +} + .text-neutral-500 { --tw-text-opacity: 1; color: rgb(100 116 139 / var(--tw-text-opacity)); @@ -3295,6 +3300,11 @@ html { color: rgb(148 163 184 / var(--tw-text-opacity)); } +.dark .dark\:text-neutral-600 { + --tw-text-opacity: 1; + color: rgb(71 85 105 / var(--tw-text-opacity)); +} + .dark .dark\:text-red-400 { --tw-text-opacity: 1; color: rgb(248 113 113 / var(--tw-text-opacity)); @@ -3548,6 +3558,10 @@ html { display: inline; } + .sm\:flex { + display: flex; + } + .sm\:hidden { display: none; } diff --git a/src/component-library/pages/RSE/details/DetailsRSE.tsx b/src/component-library/pages/RSE/details/DetailsRSE.tsx new file mode 100644 index 000000000..ac65178df --- /dev/null +++ b/src/component-library/pages/RSE/details/DetailsRSE.tsx @@ -0,0 +1,160 @@ +'use client'; + +import { Heading } from '@/component-library/atoms/misc/Heading'; +import { RSEAttributeViewModel, RSEDetailsViewModel } from '@/lib/infrastructure/data/view-model/rse'; +import { useQuery } from '@tanstack/react-query'; +import { useToast } from '@/lib/infrastructure/hooks/useToast'; +import { BaseViewModelValidator } from '@/component-library/features/utils/BaseViewModelValidator'; +import { LoadingSpinner } from '@/component-library/atoms/loading/LoadingSpinner'; +import { KeyValueWrapper } from '@/component-library/features/key-value/KeyValueWrapper'; +import { KeyValueRow } from '@/component-library/features/key-value/KeyValueRow'; +import { RSETypeBadge } from '@/component-library/features/badges/RSE/RSETypeBadge'; +import Checkbox from '@/component-library/atoms/form/Checkbox'; +import { RSEAvailabilityBadge } from '@/component-library/features/badges/RSE/RSEAvailabilityBadge'; +import { DetailsRSEProtocolsTable } from '@/component-library/pages/RSE/details/DetailsRSEProtocolsTable'; +import { DetailsRSEAttributesTable } from '@/component-library/pages/RSE/details/DetailsRSEAttributesTable'; +import { WarningField } from '@/component-library/features/fields/WarningField'; +import { RSEDetailsProtocol } from '@/lib/core/entity/rucio'; +import { InfoField } from '@/component-library/features/fields/InfoField'; + +const DetailsRSEKeyValues = ({ meta }: { meta: RSEDetailsViewModel }) => { + return ( + +
+ + + + + {meta.availability_read && } + {meta.availability_write && } + {meta.availability_delete && } + + + + +
+
+ + + + + + +
+
+ ); +}; + +const DetailsRSEAttributes = ({ attributes }: { attributes: RSEAttributeViewModel }) => { + return attributes.attributes.length !== 0 ? ( + <> + + + + ) : ( + + No attributes found. + + ); +}; + +const DetailsRSEProtocols = ({ protocols }: { protocols: RSEDetailsProtocol[] }) => { + return protocols.length !== 0 ? ( + <> + + + + ) : ( + + No protocols found. + + ); +}; + +type DetailsRSEProps = { + name: string; +}; + +export const DetailsRSE = (props: DetailsRSEProps) => { + const { toast } = useToast(); + const validator = new BaseViewModelValidator(toast); + + const queryMeta = async () => { + const url = '/api/feature/get-rse?' + new URLSearchParams({ rseName: props.name }); + + const res = await fetch(url); + if (!res.ok) { + try { + const json = await res.json(); + toast({ + title: 'Fatal error', + description: json.message, + variant: 'error', + }); + } catch (e) {} + throw new Error(res.statusText); + } + + const json = await res.json(); + if (validator.isValid(json)) return json; + + return null; + }; + + const metaQueryKey = ['meta']; + const { + data: meta, + error: metaError, + isFetching: isMetaFetching, + } = useQuery({ + queryKey: metaQueryKey, + queryFn: queryMeta, + retry: false, + refetchOnWindowFocus: false, + }); + + const queryAttributes = async () => { + const url = '/api/feature/get-rse-attributes?' + new URLSearchParams({ rseName: props.name }); + + const res = await fetch(url); + if (!res.ok) throw new Error(res.statusText); + + const json = await res.json(); + if (validator.isValid(json)) return json; + + return null; + }; + + const attributesQueryKey = ['attributes']; + const { + data: attributes, + error: attributesError, + isFetching: isAttributesFetching, + } = useQuery({ + queryKey: attributesQueryKey, + queryFn: queryAttributes, + retry: false, + refetchOnWindowFocus: false, + }); + + const hasError = metaError || attributesError; + if (hasError) { + return ( + + Could not load the RSE. + + ); + } + + const isLoading = isMetaFetching || isAttributesFetching || attributes === undefined || meta === undefined; + if (isLoading) return ; + + return ( +
+ + + + +
+ ); +}; diff --git a/src/component-library/pages/RSE/details/DetailsRSEAttributesTable.tsx b/src/component-library/pages/RSE/details/DetailsRSEAttributesTable.tsx new file mode 100644 index 000000000..e5272f1ef --- /dev/null +++ b/src/component-library/pages/RSE/details/DetailsRSEAttributesTable.tsx @@ -0,0 +1,35 @@ +import React, { useRef, useState } from 'react'; +import { AgGridReact } from 'ag-grid-react'; +import { RegularTable } from '@/component-library/features/table/RegularTable/RegularTable'; +import { RSEAttribute } from '@/lib/core/entity/rucio'; +import { RSEAttributeViewModel } from '@/lib/infrastructure/data/view-model/rse'; +import { DefaultTextFilterParams } from '@/component-library/features/utils/filter-parameters'; +import { AttributeCell } from '@/component-library/features/table/cells/AttributeCell'; + +type DetailsRSEAttributesTableProps = { + viewModel: RSEAttributeViewModel; +}; + +export const DetailsRSEAttributesTable = (props: DetailsRSEAttributesTableProps) => { + const tableRef = useRef>(null); + + const [columnDefs] = useState([ + { + headerName: 'Key', + field: 'key', + flex: 1, + sortable: true, + filter: true, + filterParams: DefaultTextFilterParams, + }, + { + headerName: 'Value', + field: 'value', + cellRenderer: AttributeCell, + flex: 1, + sortable: false, + }, + ]); + + return ; +}; diff --git a/src/component-library/pages/RSE/details/DetailsRSEProtocolsTable.tsx b/src/component-library/pages/RSE/details/DetailsRSEProtocolsTable.tsx new file mode 100644 index 000000000..366e173c7 --- /dev/null +++ b/src/component-library/pages/RSE/details/DetailsRSEProtocolsTable.tsx @@ -0,0 +1,108 @@ +import React, { useRef, useState } from 'react'; +import { AgGridReact } from 'ag-grid-react'; +import { RegularTable } from '@/component-library/features/table/RegularTable/RegularTable'; +import { RSEDetailsProtocol } from '@/lib/core/entity/rucio'; +import { ValueGetterParams } from 'ag-grid-community'; + +type DetailsRSEProtocolsTableProps = { + rowData: RSEDetailsProtocol[]; +}; + +export const DetailsRSEProtocolsTable = (props: DetailsRSEProtocolsTableProps) => { + const tableRef = useRef>(null); + + const [columnDefs] = useState([ + { + headerName: 'Scheme', + field: 'scheme', + minWidth: 100, + sortable: false, + }, + { + headerName: 'Hostname', + field: 'hostname', + minWidth: 200, + flex: 1, + sortable: false, + }, + { + headerName: 'Port', + field: 'port', + minWidth: 80, + sortable: false, + }, + { + headerName: 'Prefix', + field: 'prefix', + minWidth: 250, + flex: 1, + sortable: false, + }, + { + headerName: 'LAN/R', + valueGetter: (params: ValueGetterParams) => { + return params.data?.domains.lan.read; + }, + minWidth: 80, + sortable: true, + }, + { + headerName: 'LAN/W', + valueGetter: (params: ValueGetterParams) => { + return params.data?.domains.lan.write; + }, + minWidth: 80, + sortable: true, + }, + { + headerName: 'LAN/D', + valueGetter: (params: ValueGetterParams) => { + return params.data?.domains.lan.delete; + }, + minWidth: 80, + sortable: true, + }, + { + headerName: 'WAN/R', + valueGetter: (params: ValueGetterParams) => { + return params.data?.domains.wan.read; + }, + minWidth: 80, + sortable: true, + }, + { + headerName: 'WAN/W', + valueGetter: (params: ValueGetterParams) => { + return params.data?.domains.wan.write; + }, + minWidth: 80, + sortable: true, + }, + { + headerName: 'WAN/D', + valueGetter: (params: ValueGetterParams) => { + return params.data?.domains.wan.delete; + }, + minWidth: 80, + sortable: true, + }, + { + headerName: 'TPC/R', + valueGetter: (params: ValueGetterParams) => { + return params.data?.domains.wan.third_party_copy_read; + }, + minWidth: 80, + sortable: true, + }, + { + headerName: 'TPC/W', + valueGetter: (params: ValueGetterParams) => { + return params.data?.domains.wan.third_party_copy_write; + }, + minWidth: 80, + sortable: true, + }, + ]); + + return ; +}; diff --git a/src/component-library/pages/legacy/RSE/PageRSEProtocols.tsx b/src/component-library/pages/legacy/RSE/PageRSEProtocols.tsx index 1de45ec8b..d90542efd 100644 --- a/src/component-library/pages/legacy/RSE/PageRSEProtocols.tsx +++ b/src/component-library/pages/legacy/RSE/PageRSEProtocols.tsx @@ -1,8 +1,5 @@ import { createColumnHelper } from '@tanstack/react-table'; import { P } from '../../../atoms/legacy/text/content/P/P'; -import { twMerge } from 'tailwind-merge'; -import { UseComDOM } from '@/lib/infrastructure/hooks/useComDOM'; -import { StreamedTable } from '@/component-library/features/legacy/StreamedTables/StreamedTable.stories'; import { TableSortUpDown } from '@/component-library/features/legacy/StreamedTables/TableSortUpDown'; import { H3 } from '../../../atoms/legacy/text/headings/H3/H3'; import { RSEProtocolViewModel } from '@/lib/infrastructure/data/view-model/rse'; diff --git a/src/lib/core/entity/rucio.ts b/src/lib/core/entity/rucio.ts index 2332e9845..0d0dcbc53 100644 --- a/src/lib/core/entity/rucio.ts +++ b/src/lib/core/entity/rucio.ts @@ -284,6 +284,29 @@ export type RSEProtocol = { created_at?: DateISO; // TODO: rucio does not provide this }; +export type RSEDetailsProtocol = { + domains: { + lan: { + read: number; + write: number; + delete: number; + }; + wan: { + read: number; + write: number; + delete: number; + third_party_copy_read: number; + third_party_copy_write: number; + }; + }; + scheme: string; + hostname: string; + port: number; + prefix: string; + impl: string; + extended_attributes?: Record; +}; + export type RSEDetails = { id: string; name: string; @@ -294,7 +317,7 @@ export type RSEDetails = { availability_delete: boolean; availability_read: boolean; availability_write: boolean; - protocols: RSEProtocol[]; + protocols: RSEDetailsProtocol[]; }; export type RSEAttribute = { From 4091ec1ed879487118b9b313e1b8417f09525ee1 Mon Sep 17 00:00:00 2001 From: MytsV Date: Tue, 15 Oct 2024 15:10:55 +0300 Subject: [PATCH 03/32] Center the loading spinner and the error field --- .../pages/Dashboard/Dashboard.tsx | 46 +++++++++---------- .../pages/RSE/details/DetailsRSE.tsx | 16 +++++-- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/component-library/pages/Dashboard/Dashboard.tsx b/src/component-library/pages/Dashboard/Dashboard.tsx index 8b3f6040c..f184aa549 100644 --- a/src/component-library/pages/Dashboard/Dashboard.tsx +++ b/src/component-library/pages/Dashboard/Dashboard.tsx @@ -1,16 +1,16 @@ -import {useQuery} from '@tanstack/react-query'; -import {SiteHeaderViewModel} from '@/lib/infrastructure/data/view-model/site-header'; -import {getSiteHeader} from '@/app/(rucio)/queries'; -import {LoadingSpinner} from '@/component-library/atoms/loading/LoadingSpinner'; -import {Heading} from '@/component-library/atoms/misc/Heading'; -import {WarningField} from '@/component-library/features/fields/WarningField'; -import {AccountRoleBadge} from '@/component-library/features/badges/account/AccountRoleBadge'; -import {TopRulesWidget} from '@/component-library/pages/Dashboard/widgets/TopRulesWidget'; -import {useEffect, useRef, useState} from 'react'; -import {RuleViewModel} from '@/lib/infrastructure/data/view-model/rule'; -import useStreamReader, {StreamingStatus} from '@/lib/infrastructure/hooks/useStreamReader'; -import {RSEAccountUsageViewModel} from '@/lib/infrastructure/data/view-model/rse'; -import {TopStorageUsageWidget} from '@/component-library/pages/Dashboard/widgets/TopStorageUsageWidget'; +import { useQuery } from '@tanstack/react-query'; +import { SiteHeaderViewModel } from '@/lib/infrastructure/data/view-model/site-header'; +import { getSiteHeader } from '@/app/(rucio)/queries'; +import { LoadingSpinner } from '@/component-library/atoms/loading/LoadingSpinner'; +import { Heading } from '@/component-library/atoms/misc/Heading'; +import { WarningField } from '@/component-library/features/fields/WarningField'; +import { AccountRoleBadge } from '@/component-library/features/badges/account/AccountRoleBadge'; +import { TopRulesWidget } from '@/component-library/pages/Dashboard/widgets/TopRulesWidget'; +import { useEffect, useRef, useState } from 'react'; +import { RuleViewModel } from '@/lib/infrastructure/data/view-model/rule'; +import useStreamReader, { StreamingStatus } from '@/lib/infrastructure/hooks/useStreamReader'; +import { RSEAccountUsageViewModel } from '@/lib/infrastructure/data/view-model/rse'; +import { TopStorageUsageWidget } from '@/component-library/pages/Dashboard/widgets/TopStorageUsageWidget'; const AccountHeading = () => { const querySiteHeader = async () => { @@ -34,7 +34,7 @@ const AccountHeading = () => { retry: false, }); - if (isHeaderFetching) return ; + if (isHeaderFetching) return ; if (headerError || !header?.activeAccount) { return ( @@ -46,8 +46,8 @@ const AccountHeading = () => { return (
- - + +
); }; @@ -55,7 +55,7 @@ const AccountHeading = () => { const UsageView = () => { const usageBuffer = useRef([]); const [usages, setUsages] = useState(); - const {start, stop, error, status} = useStreamReader(); + const { start, stop, error, status } = useStreamReader(); useEffect(() => { // TODO: handle error view models @@ -80,13 +80,13 @@ const UsageView = () => { const isLoading = (!usages && !error) || status === StreamingStatus.RUNNING; - return ; + return ; }; const RulesView = () => { const rulesBuffer = useRef([]); const [rules, setRules] = useState(); - const {start, stop, error, status} = useStreamReader(); + const { start, stop, error, status } = useStreamReader(); const getCreatedAfterDate = () => { // Only the rules that were created less than 15 days ago should get loaded @@ -122,17 +122,17 @@ const RulesView = () => { const isLoading = (!rules && !error) || status === StreamingStatus.RUNNING; - return ; + return ; }; export const Dashboard = () => { return (
- +
- - + +
); }; diff --git a/src/component-library/pages/RSE/details/DetailsRSE.tsx b/src/component-library/pages/RSE/details/DetailsRSE.tsx index ac65178df..2c37ab146 100644 --- a/src/component-library/pages/RSE/details/DetailsRSE.tsx +++ b/src/component-library/pages/RSE/details/DetailsRSE.tsx @@ -140,14 +140,22 @@ export const DetailsRSE = (props: DetailsRSEProps) => { const hasError = metaError || attributesError; if (hasError) { return ( - - Could not load the RSE. - +
+ + Could not load the RSE {props.name}. + +
); } const isLoading = isMetaFetching || isAttributesFetching || attributes === undefined || meta === undefined; - if (isLoading) return ; + if (isLoading) { + return ( +
+ +
+ ); + } return (
From eb6a385cbd35fb9f9b14c18e4ab5929cf8860476 Mon Sep 17 00:00:00 2001 From: MytsV Date: Tue, 15 Oct 2024 15:12:06 +0300 Subject: [PATCH 04/32] Delete the legacy RSE page --- .../pages/legacy/RSE/PageRSE.stories.tsx | 20 ---- .../pages/legacy/RSE/PageRSE.tsx | 92 --------------- .../legacy/RSE/PageRSEAttributes.stories.tsx | 15 --- .../pages/legacy/RSE/PageRSEAttributes.tsx | 34 ------ .../legacy/RSE/PageRSEProtocols.stories.tsx | 15 --- .../pages/legacy/RSE/PageRSEProtocols.tsx | 107 ------------------ 6 files changed, 283 deletions(-) delete mode 100644 src/component-library/pages/legacy/RSE/PageRSE.stories.tsx delete mode 100644 src/component-library/pages/legacy/RSE/PageRSE.tsx delete mode 100644 src/component-library/pages/legacy/RSE/PageRSEAttributes.stories.tsx delete mode 100644 src/component-library/pages/legacy/RSE/PageRSEAttributes.tsx delete mode 100644 src/component-library/pages/legacy/RSE/PageRSEProtocols.stories.tsx delete mode 100644 src/component-library/pages/legacy/RSE/PageRSEProtocols.tsx diff --git a/src/component-library/pages/legacy/RSE/PageRSE.stories.tsx b/src/component-library/pages/legacy/RSE/PageRSE.stories.tsx deleted file mode 100644 index 6db350365..000000000 --- a/src/component-library/pages/legacy/RSE/PageRSE.stories.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { RSEBlockState } from '@/lib/core/entity/rucio'; -import { StoryFn, Meta } from '@storybook/react'; -import { fixtureRSEViewModel, fixtureRSEProtocolViewModel, fixtureRSEAttributeViewModel } from '@/test/fixtures/table-fixtures'; -import { PageRSE as P } from './PageRSE'; - -export default { - title: 'Components/Pages/RSE', - component: P, -} as Meta; - -const Template: StoryFn = args =>

; - -export const PageRSE = Template.bind({}); -PageRSE.args = { - rse: fixtureRSEViewModel(), - rseblockstate: 7 as RSEBlockState, // 7 = all blocked - protocols: fixtureRSEProtocolViewModel(), - attributes: fixtureRSEAttributeViewModel(), - fromrselist: true, -}; diff --git a/src/component-library/pages/legacy/RSE/PageRSE.tsx b/src/component-library/pages/legacy/RSE/PageRSE.tsx deleted file mode 100644 index 8b9f7eef1..000000000 --- a/src/component-library/pages/legacy/RSE/PageRSE.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { RSEBlockState } from '@/lib/core/entity/rucio'; -import { twMerge } from 'tailwind-merge'; -import { Generaltable } from '../../../atoms/legacy/helpers/Metatable/Metatable'; -import { Titleth, Contenttd } from '../../../atoms/legacy/helpers/Metatable/Metatable'; -import { BoolTag } from '@/component-library/features/legacy/Tags/BoolTag'; -import { RSETypeTag } from '@/component-library/features/legacy/Tags/RSETypeTag'; -import { RSETag } from '@/component-library/features/legacy/Tags/RSETag'; -import { RSEAttributeViewModel, RSEProtocolViewModel, RSEViewModel } from '@/lib/infrastructure/data/view-model/rse'; -import { PageRSEProtocols } from './PageRSEProtocols'; -import { PageRSEAttributes } from './PageRSEAttributes'; -import { H2 } from '../../../atoms/legacy/text/headings/H2/H2'; -import { Body } from '@/component-library/pages/legacy/Helpers/Body'; -import { Heading } from '@/component-library/pages/legacy/Helpers/Heading'; -import { InfoField } from '@/component-library/features/fields/InfoField'; -import React from 'react'; - -type PageRSEProps = { - rse: RSEViewModel; - rseblockstate: RSEBlockState; - protocols: RSEProtocolViewModel; - attributes: RSEAttributeViewModel; - fromrselist?: boolean; -}; - -export const PageRSE = (props: PageRSEProps) => { - return ( -

- - This page is currently in development. We are working on improvements, so stay tuned! - - -
- - - Name - {props.rse.name} - - - RSE Type - - - - - - Availability - - - - - - - - Volatile - - - - - - Deterministic - - - - - - Staging Area - - - - - -
-
- -
-

RSE Protocols

- -
-
-

RSE Attributes

- -
- -
- ); -}; diff --git a/src/component-library/pages/legacy/RSE/PageRSEAttributes.stories.tsx b/src/component-library/pages/legacy/RSE/PageRSEAttributes.stories.tsx deleted file mode 100644 index 05141a291..000000000 --- a/src/component-library/pages/legacy/RSE/PageRSEAttributes.stories.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { StoryFn, Meta } from '@storybook/react'; -import { fixtureRSEAttributeViewModel } from '@/test/fixtures/table-fixtures'; -import { PageRSEAttributes as P } from './PageRSEAttributes'; - -export default { - title: 'Components/Pages/RSE', - component: P, -} as Meta; - -const Template: StoryFn = args =>

; - -export const PageRSEAttributes = Template.bind({}); -PageRSEAttributes.args = { - attributes: fixtureRSEAttributeViewModel().attributes, -}; diff --git a/src/component-library/pages/legacy/RSE/PageRSEAttributes.tsx b/src/component-library/pages/legacy/RSE/PageRSEAttributes.tsx deleted file mode 100644 index c1be17f10..000000000 --- a/src/component-library/pages/legacy/RSE/PageRSEAttributes.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { createColumnHelper } from '@tanstack/react-table'; -import { TableFilterString } from '@/component-library/features/legacy/StreamedTables/TableFilterString'; -import { P } from '../../../atoms/legacy/text/content/P/P'; -import { H3 } from '../../../atoms/legacy/text/headings/H3/H3'; -import { BoolTag } from '@/component-library/features/legacy/Tags/BoolTag'; -import { NullTag } from '@/component-library/features/legacy/Tags/NullTag'; -import { RSEAttribute } from '@/lib/core/entity/rucio'; -import { NormalTable } from '@/component-library/features/legacy/StreamedTables/NormalTable'; - -export const PageRSEAttributes = (props: { attributes: RSEAttribute[] }) => { - const columnHelper = createColumnHelper(); - const tablecolumns: any[] = [ - columnHelper.accessor('key', { - id: 'key', - header: info => , - cell: info =>

{info.getValue()}

, - }), - columnHelper.accessor('value', { - id: 'value', - header: info =>

Value

, - cell: info => { - const val = info.getValue(); - if (typeof val === 'boolean') { - return ; - } else if (val === null) { - return ; - } else { - return

{val}

; - } - }, - }), - ]; - return tabledata={props.attributes} tablecolumns={tablecolumns} />; -}; diff --git a/src/component-library/pages/legacy/RSE/PageRSEProtocols.stories.tsx b/src/component-library/pages/legacy/RSE/PageRSEProtocols.stories.tsx deleted file mode 100644 index f2e418a3b..000000000 --- a/src/component-library/pages/legacy/RSE/PageRSEProtocols.stories.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { StoryFn, Meta } from '@storybook/react'; -import { fixtureRSEProtocolViewModel, mockUseComDOM } from '@/test/fixtures/table-fixtures'; -import { PageRSEProtocols as P } from './PageRSEProtocols'; - -export default { - title: 'Components/Pages/RSE', - component: P, -} as Meta; - -const Template: StoryFn = args =>

; - -export const PageRSEProtocols = Template.bind({}); -PageRSEProtocols.args = { - tableData: fixtureRSEProtocolViewModel(), -}; diff --git a/src/component-library/pages/legacy/RSE/PageRSEProtocols.tsx b/src/component-library/pages/legacy/RSE/PageRSEProtocols.tsx deleted file mode 100644 index d90542efd..000000000 --- a/src/component-library/pages/legacy/RSE/PageRSEProtocols.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { createColumnHelper } from '@tanstack/react-table'; -import { P } from '../../../atoms/legacy/text/content/P/P'; -import { TableSortUpDown } from '@/component-library/features/legacy/StreamedTables/TableSortUpDown'; -import { H3 } from '../../../atoms/legacy/text/headings/H3/H3'; -import { RSEProtocolViewModel } from '@/lib/infrastructure/data/view-model/rse'; -import { RSEProtocol } from '@/lib/core/entity/rucio'; -import { NormalTable } from '@/component-library/features/legacy/StreamedTables/NormalTable'; -import { TableStyling } from '@/component-library/features/legacy/StreamedTables/types'; - -export const PageRSEProtocols = (props: { - // TODO: data will not be streamed, but loaded in one go - tableData: RSEProtocolViewModel; -}) => { - const shortstyle = { style: 'w-20' }; - const shortstyleblue = { style: 'w-20 bg-extra-indigo-500' }; - const shortstylepink = { style: 'w-20 bg-extra-rose-500' }; - const columnHelper = createColumnHelper(); - const tablecolumns: any[] = [ - columnHelper.accessor('scheme', { - id: 'scheme', - header: info =>

Scheme

, - cell: info =>

{info.getValue()}

, - meta: { style: 'w-24' }, - }), - columnHelper.accessor('hostname', { - id: 'hostname', - header: info =>

Hostname

, - cell: info =>

{info.getValue()}

, - }), - columnHelper.accessor('port', { - id: 'port', - header: info =>

Port

, - cell: info =>

{info.getValue()}

, - meta: { style: 'w-24' }, - }), - columnHelper.accessor('prefix', { - id: 'prefix', - header: info =>

Prefix

, - cell: info =>

{info.getValue()}

, - }), - columnHelper.accessor('priorities_lan.read', { - id: 'priorities_lan.read', - header: info => , - cell: info =>

{info.getValue()}

, - meta: shortstyleblue, - }), - columnHelper.accessor('priorities_lan.write', { - id: 'priorities_lan.write', - header: info => , - cell: info =>

{info.getValue()}

, - meta: shortstyleblue, - }), - columnHelper.accessor('priorities_lan.delete', { - id: 'priorities_lan.delete', - header: info => , - cell: info =>

{info.getValue()}

, - meta: shortstyleblue, - }), - columnHelper.accessor('priorities_wan.read', { - id: 'priorities_wan.read', - header: info => , - cell: info =>

{info.getValue()}

, - meta: shortstylepink, - }), - columnHelper.accessor('priorities_wan.write', { - id: 'priorities_wan.write', - header: info => , - cell: info =>

{info.getValue()}

, - meta: shortstylepink, - }), - columnHelper.accessor('priorities_wan.delete', { - id: 'priorities_wan.delete', - header: info => , - cell: info =>

{info.getValue()}

, - meta: shortstylepink, - }), - // columnHelper.accessor("priorities_wan.tpc", { - // id: "priorities_lan.tpc", - // header: info => , - // cell: info =>

{info.getValue()}

, - // meta: shortstylepink, - // }), - columnHelper.accessor('priorities_wan.tpcwrite', { - id: 'priorities_wan.tpcwrite', - header: info => , - cell: info =>

{info.getValue()}

, - meta: shortstylepink, - }), - columnHelper.accessor('priorities_wan.tpcread', { - id: 'priorities_wan.tpcread', - header: info => , - cell: info =>

{info.getValue()}

, - meta: shortstylepink, - }), - ]; - return ( - - tabledata={props.tableData.protocols || []} - tablecolumns={tablecolumns} - tablestyling={ - { - pageSize: 5, - } as TableStyling - } - /> - ); -}; From a185bad2539713a14fadb67502c15a3dede93d3b Mon Sep 17 00:00:00 2001 From: MytsV Date: Tue, 15 Oct 2024 16:38:29 +0300 Subject: [PATCH 05/32] Reimplement the meta values of DID page view --- .../(rucio)/did/page/[scope]/[name]/page.tsx | 4 + src/component-library/outputtailwind.css | 28 +++ .../pages/DID/details/DetailsDID.tsx | 162 ++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 src/component-library/pages/DID/details/DetailsDID.tsx diff --git a/src/app/(rucio)/did/page/[scope]/[name]/page.tsx b/src/app/(rucio)/did/page/[scope]/[name]/page.tsx index 8cdfe2df0..7d4e9412a 100644 --- a/src/app/(rucio)/did/page/[scope]/[name]/page.tsx +++ b/src/app/(rucio)/did/page/[scope]/[name]/page.tsx @@ -21,6 +21,7 @@ import { } from '@/lib/infrastructure/data/view-model/did'; import { didKeyValuePairsDataQuery, didMetaQueryBase } from '@/app/(rucio)/did/queries'; import { Loading } from '@/component-library/pages/legacy/Helpers/Loading'; +import {DetailsDID} from "@/component-library/pages/DID/details/DetailsDID"; export default function Page({ params }: { params: { scope: string; name: string } }) { const [didMeta, setDIDMeta] = useState({ status: 'pending' } as DIDMetaViewModel); @@ -118,6 +119,9 @@ export default function Page({ params }: { params: { scope: string; name: string }; setRequests(); }, []); + + return + if (didMeta.status === 'pending') { return ; } diff --git a/src/component-library/outputtailwind.css b/src/component-library/outputtailwind.css index 414852ac3..4d552a3cb 100644 --- a/src/component-library/outputtailwind.css +++ b/src/component-library/outputtailwind.css @@ -1340,6 +1340,10 @@ html { overflow-y: auto; } +.overflow-y-hidden { + overflow-y: hidden; +} + .overflow-y-visible { overflow-y: visible; } @@ -3821,6 +3825,10 @@ html { } @media (min-width: 1024px) { + .lg\:hidden { + display: none; + } + .lg\:w-32 { width: 8rem; } @@ -3833,10 +3841,22 @@ html { width: 50rem; } + .lg\:grow { + flex-grow: 1; + } + .lg\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .lg\:flex-row { + flex-direction: row; + } + + .lg\:justify-between { + justify-content: space-between; + } + .lg\:gap-x-2 { -moz-column-gap: 0.5rem; column-gap: 0.5rem; @@ -3877,7 +3897,15 @@ html { } @media (min-width: 1536px) { + .\32xl\:hidden { + display: none; + } + .\32xl\:w-44 { width: 11rem; } + + .\32xl\:flex-row { + flex-direction: row; + } } diff --git a/src/component-library/pages/DID/details/DetailsDID.tsx b/src/component-library/pages/DID/details/DetailsDID.tsx new file mode 100644 index 000000000..881581edc --- /dev/null +++ b/src/component-library/pages/DID/details/DetailsDID.tsx @@ -0,0 +1,162 @@ +'use client'; + +import {Heading} from '@/component-library/atoms/misc/Heading'; +import {useQuery} from '@tanstack/react-query'; +import {useToast} from '@/lib/infrastructure/hooks/useToast'; +import {BaseViewModelValidator} from '@/component-library/features/utils/BaseViewModelValidator'; +import {KeyValueWrapper} from '@/component-library/features/key-value/KeyValueWrapper'; +import {KeyValueRow} from '@/component-library/features/key-value/KeyValueRow'; +import Checkbox from '@/component-library/atoms/form/Checkbox'; +import {DIDMetaViewModel} from '@/lib/infrastructure/data/view-model/did'; +import {LoadingSpinner} from '@/component-library/atoms/loading/LoadingSpinner'; +import {DIDTypeBadge} from '@/component-library/features/badges/DID/DIDTypeBadge'; +import {Field} from '@/component-library/atoms/misc/Field'; +import {Divider} from '@/component-library/atoms/misc/Divider'; +import {formatDate, formatFileSize} from '@/component-library/features/utils/text-formatters'; +import {DIDType} from '@/lib/core/entity/rucio'; +import {DIDAvailabilityBadge} from '@/component-library/features/badges/DID/DIDAvailabilityBadge'; +import {CopyableField} from '@/component-library/features/fields/CopyableField'; + +const DetailsDIDMeta = ({meta}: { meta: DIDMetaViewModel }) => { + const getFileInformation = () => { + return ( +
+ + {meta.bytes && ( + + {formatFileSize(meta.bytes)} + + )} + {meta.guid && ( + + + + )} + {meta.adler32 && ( + + + + )} + {meta.md5 && ( + + + + )} +
+ ); + }; + + return ( + +
+
+ + + + + {meta.account} + + {meta.is_open !== null && ( + + + + )} + + + +
+ + + +
+ + {formatDate(meta.created_at)} + + + {formatDate(meta.updated_at)} + +
+ + + +
+ + + + + + + + + + + + + + + +
+
+ + {meta.did_type === DIDType.FILE && getFileInformation()} +
+ ); +}; + +type DetailsDIDProps = { + scope: string; + name: string; +}; + +export const DetailsDID = ({scope, name}: DetailsDIDProps) => { + const {toast} = useToast(); + const validator = new BaseViewModelValidator(toast); + + const queryMeta = async () => { + const url = '/api/feature/get-did-meta?' + new URLSearchParams({scope, name}); + + const res = await fetch(url); + if (!res.ok) { + try { + const json = await res.json(); + toast({ + title: 'Fatal error', + description: json.message, + variant: 'error', + }); + } catch (e) { + } + throw new Error(res.statusText); + } + + const json = await res.json(); + if (validator.isValid(json)) return json; + + return null; + }; + + const metaQueryKey = ['meta']; + const { + data: meta, + error: metaError, + isFetching: isMetaFetching, + } = useQuery({ + queryKey: metaQueryKey, + queryFn: queryMeta, + retry: false, + refetchOnWindowFocus: false, + }); + + const isLoading = meta === undefined || isMetaFetching; + + return isLoading ? ( + + ) : ( +
+
+ +
+ +
+ ); +}; From 74258ab2884dff699c3eb50a828c27493f6a574a Mon Sep 17 00:00:00 2001 From: MytsV Date: Tue, 15 Oct 2024 16:38:42 +0300 Subject: [PATCH 06/32] Add additional padding to the meta view in the RSE details --- src/component-library/pages/RSE/details/DetailsRSE.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/component-library/pages/RSE/details/DetailsRSE.tsx b/src/component-library/pages/RSE/details/DetailsRSE.tsx index 2c37ab146..27f4364f6 100644 --- a/src/component-library/pages/RSE/details/DetailsRSE.tsx +++ b/src/component-library/pages/RSE/details/DetailsRSE.tsx @@ -19,7 +19,7 @@ import { InfoField } from '@/component-library/features/fields/InfoField'; const DetailsRSEKeyValues = ({ meta }: { meta: RSEDetailsViewModel }) => { return ( - +
From 259e553c13dfe85b255c03ac47fac30fa5456138 Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 16 Oct 2024 21:13:27 +0300 Subject: [PATCH 07/32] Modify the get rule endpoint to return all the available data about a rule --- src/lib/core/dto/rule-dto.ts | 4 +- src/lib/core/entity/rucio.ts | 3 +- .../secondary/rule-gateway-output-port.ts | 4 +- .../endpoints/get-rule-endpoints.ts | 16 +-- .../rule-gateway/rule-gateway-utils.ts | 93 +++++++++++++++- .../gateway/rule-gateway/rule-gateway.ts | 4 +- .../rule/rule-gateway-get-rule.test.ts | 104 ++++++------------ 7 files changed, 142 insertions(+), 86 deletions(-) diff --git a/src/lib/core/dto/rule-dto.ts b/src/lib/core/dto/rule-dto.ts index 516074387..a94690560 100644 --- a/src/lib/core/dto/rule-dto.ts +++ b/src/lib/core/dto/rule-dto.ts @@ -1,11 +1,13 @@ import { BaseDTO, BaseStreamableDTO } from '@/lib/sdk/dto'; -import { LockState, Rule } from '../entity/rucio'; +import { LockState, Rule, RuleMeta } from '../entity/rucio'; /** * The Data Transfer Object for the ListRulesEndpoint which contains the stream */ export interface RuleDTO extends BaseDTO, Rule {} +export interface RuleMetaDTO extends BaseDTO, RuleMeta {} + /** * Data Transfer Object for Rule Replica Locks */ diff --git a/src/lib/core/entity/rucio.ts b/src/lib/core/entity/rucio.ts index b5e43dc86..3b1efcc74 100644 --- a/src/lib/core/entity/rucio.ts +++ b/src/lib/core/entity/rucio.ts @@ -133,6 +133,7 @@ export type RSEAccountUsage = { bytes_limit: number; }; +// TODO: complete with all the fields // copied from deployed rucio UI export type RuleMeta = { account: string; @@ -140,7 +141,7 @@ export type RuleMeta = { copies: number; created_at: DateISO; did_type: DIDType; - expires_at: DateISO; + expires_at: DateISO | null; grouping: DIDType; id: string; ignore_account_limit: boolean; diff --git a/src/lib/core/port/secondary/rule-gateway-output-port.ts b/src/lib/core/port/secondary/rule-gateway-output-port.ts index d8d3af0b6..5080c4a76 100644 --- a/src/lib/core/port/secondary/rule-gateway-output-port.ts +++ b/src/lib/core/port/secondary/rule-gateway-output-port.ts @@ -1,5 +1,5 @@ import { BaseStreamableDTO } from '@/lib/sdk/dto'; -import { CreateRuleDTO, ListRulesDTO, RuleDTO } from '../../dto/rule-dto'; +import { CreateRuleDTO, ListRulesDTO, RuleDTO, RuleMetaDTO } from '../../dto/rule-dto'; import { ListRulesFilter } from '@/lib/infrastructure/gateway/rule-gateway/rule-gateway-utils'; import { RuleCreationParameters } from '@/lib/core/entity/rucio'; @@ -9,7 +9,7 @@ export default interface RuleGatewayOutputPort { * @param rucioAuthToken A valid Rucio Auth Token. * @param ruleId The rule to get. */ - getRule(rucioAuthToken: string, ruleId: string): Promise; + getRule(rucioAuthToken: string, ruleId: string): Promise; /** * Lists all rules for a given account. diff --git a/src/lib/infrastructure/gateway/rule-gateway/endpoints/get-rule-endpoints.ts b/src/lib/infrastructure/gateway/rule-gateway/endpoints/get-rule-endpoints.ts index 5e6a7c83c..fadf8c286 100644 --- a/src/lib/infrastructure/gateway/rule-gateway/endpoints/get-rule-endpoints.ts +++ b/src/lib/infrastructure/gateway/rule-gateway/endpoints/get-rule-endpoints.ts @@ -1,10 +1,10 @@ -import { RuleDTO } from '@/lib/core/dto/rule-dto'; +import { RuleMetaDTO } from '@/lib/core/dto/rule-dto'; import { BaseEndpoint } from '@/lib/sdk/gateway-endpoints'; import { HTTPRequest } from '@/lib/sdk/http'; -import { convertToRuleDTO, getEmptyRuleDTO, TRucioRule } from '../rule-gateway-utils'; +import { convertToRuleMetaDTO, getEmptyRuleMetaDTO, TRucioRule } from '../rule-gateway-utils'; import { Response } from 'node-fetch'; -export default class GetRuleEndpoint extends BaseEndpoint { +export default class GetRuleEndpoint extends BaseEndpoint { constructor(private readonly rucioAuthToken: string, private readonly ruleId: string) { super(); } @@ -31,10 +31,10 @@ export default class GetRuleEndpoint extends BaseEndpoint { * @param response The reponse containing error data * @returns */ - async reportErrors(statusCode: number, response: Response): Promise { + async reportErrors(statusCode: number, response: Response): Promise { const data = await response.json(); - const errorDTO: RuleDTO = { - ...getEmptyRuleDTO(), + const errorDTO: RuleMetaDTO = { + ...getEmptyRuleMetaDTO(), status: 'error', errorMessage: data, errorCode: statusCode, @@ -49,8 +49,8 @@ export default class GetRuleEndpoint extends BaseEndpoint { * @param response The individual RSE object streamed from Rucio * @returns The RSEDTO object */ - createDTO(data: TRucioRule): RuleDTO { - const dto: RuleDTO = convertToRuleDTO(data); + createDTO(data: TRucioRule): RuleMetaDTO { + const dto: RuleMetaDTO = convertToRuleMetaDTO(data); return dto; } } diff --git a/src/lib/infrastructure/gateway/rule-gateway/rule-gateway-utils.ts b/src/lib/infrastructure/gateway/rule-gateway/rule-gateway-utils.ts index f7ad0fc54..0b05acb6b 100644 --- a/src/lib/infrastructure/gateway/rule-gateway/rule-gateway-utils.ts +++ b/src/lib/infrastructure/gateway/rule-gateway/rule-gateway-utils.ts @@ -1,5 +1,5 @@ -import { RuleDTO, RuleReplicaLockStateDTO } from '@/lib/core/dto/rule-dto'; -import { DateISO, LockState, RuleState } from '@/lib/core/entity/rucio'; +import { RuleDTO, RuleMetaDTO, RuleReplicaLockStateDTO } from '@/lib/core/dto/rule-dto'; +import { DIDType, LockState, RuleNotification, RuleState } from '@/lib/core/entity/rucio'; export type TRucioRule = { error: null | string; @@ -100,6 +100,65 @@ export function convertToRuleDTO(rule: TRucioRule): RuleDTO { }; } +function getDIDType(type: string): DIDType { + const cleanType = type.trim().toUpperCase(); + switch (cleanType) { + case 'FILE': + return DIDType.FILE; + case 'DATASET': + return DIDType.DATASET; + case 'CONTAINER': + return DIDType.CONTAINER; + default: + return DIDType.UNKNOWN; + } +} + +function getRuleNotification(notification: string): RuleNotification { + const cleanNotification = notification.trim().toUpperCase(); + switch (cleanNotification) { + // TODO: check if that's the return format of the rucio server + case 'Y': + return RuleNotification.Yes; + case 'C': + return RuleNotification.Close; + case 'P': + return RuleNotification.Progress; + default: + return RuleNotification.No; + } +} + +export function convertToRuleMetaDTO(rule: TRucioRule): RuleMetaDTO { + return { + status: 'success', + account: rule.account, + activity: rule.activity, + copies: rule.copies, + created_at: rule.created_at, + did_type: getDIDType(rule.did_type), + expires_at: rule.expires_at, + // TODO: should be of a separate type + grouping: getDIDType(rule.grouping), + id: rule.id, + ignore_account_limit: rule.ignore_account_limit, + ignore_availability: rule.ignore_availability, + locked: rule.locked, + locks_ok_cnt: rule.locks_ok_cnt, + locks_replicating_cnt: rule.locks_replicating_cnt, + locks_stuck_cnt: rule.locks_stuck_cnt, + name: rule.name, + notification: getRuleNotification(rule.notification), + priority: rule.priority, + purge_replicas: rule.purge_replicas, + rse_expression: rule.rse_expression, + scope: rule.scope, + split_container: rule.split_container, + state: getRuleState(rule.state), + updated_at: rule.updated_at, + }; +} + export function convertToRuleReplicaLockDTO(ruleReplicaLockState: TRucioRuleReplicaLock): RuleReplicaLockStateDTO { return { status: 'success', @@ -109,6 +168,7 @@ export function convertToRuleReplicaLockDTO(ruleReplicaLockState: TRucioRuleRepl state: getReplicaLockState(ruleReplicaLockState.state), }; } + export function getEmptyRuleDTO(): RuleDTO { return { status: 'error', @@ -126,6 +186,35 @@ export function getEmptyRuleDTO(): RuleDTO { }; } +export function getEmptyRuleMetaDTO(): RuleMetaDTO { + return { + status: 'error', + account: '', + activity: '', + copies: 0, + created_at: '', + did_type: DIDType.UNKNOWN, + expires_at: '', + grouping: DIDType.UNKNOWN, + id: '', + ignore_account_limit: false, + ignore_availability: false, + locked: false, + locks_ok_cnt: 0, + locks_replicating_cnt: 0, + locks_stuck_cnt: 0, + name: '', + notification: RuleNotification.No, + priority: 0, + purge_replicas: false, + rse_expression: '', + scope: '', + split_container: false, + state: RuleState.UNKNOWN, + updated_at: '', + }; +} + export function getEmptyRuleReplicaLockDTO(): RuleReplicaLockStateDTO { return { status: 'error', diff --git a/src/lib/infrastructure/gateway/rule-gateway/rule-gateway.ts b/src/lib/infrastructure/gateway/rule-gateway/rule-gateway.ts index 4b7d06e06..3f9479330 100644 --- a/src/lib/infrastructure/gateway/rule-gateway/rule-gateway.ts +++ b/src/lib/infrastructure/gateway/rule-gateway/rule-gateway.ts @@ -1,4 +1,4 @@ -import { CreateRuleDTO, ListRulesDTO, RuleDTO } from '@/lib/core/dto/rule-dto'; +import { CreateRuleDTO, ListRulesDTO, RuleDTO, RuleMetaDTO } from '@/lib/core/dto/rule-dto'; import RuleGatewayOutputPort from '@/lib/core/port/secondary/rule-gateway-output-port'; import { BaseStreamableDTO } from '@/lib/sdk/dto'; import { injectable } from 'inversify'; @@ -11,7 +11,7 @@ import CreateRuleEndpoint from '@/lib/infrastructure/gateway/rule-gateway/endpoi @injectable() export default class RuleGateway implements RuleGatewayOutputPort { - async getRule(rucioAuthToken: string, ruleId: string): Promise { + async getRule(rucioAuthToken: string, ruleId: string): Promise { const endpoint = new GetRuleEndpoint(rucioAuthToken, ruleId); const dto = await endpoint.fetch(); return dto; diff --git a/test/gateway/rule/rule-gateway-get-rule.test.ts b/test/gateway/rule/rule-gateway-get-rule.test.ts index 92b3b0631..7f83ad06c 100644 --- a/test/gateway/rule/rule-gateway-get-rule.test.ts +++ b/test/gateway/rule/rule-gateway-get-rule.test.ts @@ -1,14 +1,39 @@ -import { RuleDTO, RuleReplicaLockStateDTO } from '@/lib/core/dto/rule-dto'; -import { LockState, RuleState } from '@/lib/core/entity/rucio'; +import { RuleMetaDTO } from '@/lib/core/dto/rule-dto'; +import { DIDType, RuleNotification, RuleState } from '@/lib/core/entity/rucio'; import RuleGatewayOutputPort from '@/lib/core/port/secondary/rule-gateway-output-port'; import appContainer from '@/lib/infrastructure/ioc/container-config'; import GATEWAYS from '@/lib/infrastructure/ioc/ioc-symbols-gateway'; -import { BaseStreamableDTO } from '@/lib/sdk/dto'; import MockRucioServerFactory, { MockEndpoint } from 'test/fixtures/rucio-server'; -import { collectStreamedData } from 'test/fixtures/stream-test-utils'; -import { Readable } from 'stream'; +// TODO: extend the test describe('Rule Gateway', () => { + const expectedDTO: RuleMetaDTO = { + status: 'success', + locks_stuck_cnt: 0, + ignore_account_limit: false, + ignore_availability: false, + rse_expression: 'XRD3', + created_at: 'Mon, 27 Nov 2023 17:57:44 UTC', + account: 'root', + copies: 1, + activity: 'User Subscriptions', + priority: 3, + updated_at: 'Mon, 27 Nov 2023 17:57:44 UTC', + scope: 'test', + expires_at: null, + grouping: DIDType.DATASET, + name: 'container', + notification: RuleNotification.No, + did_type: DIDType.CONTAINER, + locked: false, + state: RuleState.REPLICATING, + locks_ok_cnt: 0, + purge_replicas: false, + id: '817b3030097446a38b3b842bf528e112', + locks_replicating_cnt: 4, + split_container: false, + }; + beforeEach(() => { fetchMock.doMock(); const getRuleEndpoint: MockEndpoint = { @@ -56,76 +81,15 @@ describe('Rule Gateway', () => { }), }, }; - - const replicaLockStates = [ - JSON.stringify({ - scope: 'test', - name: 'file1', - rse_id: 'c8b8113ddcdb4ec78e0846171e594280', - rse: 'XRD3', - state: 'REPLICATING', - rule_id: '817b3030097446a38b3b842bf528e112', - }), - JSON.stringify({ - scope: 'test', - name: 'file2', - rse_id: 'c8b8113ddcdb4ec78e0846171e594280', - rse: 'XRD3', - state: 'REPLICATING', - rule_id: '817b3030097446a38b3b842bf528e112', - }), - JSON.stringify({ - scope: 'test', - name: 'file3', - rse_id: 'c8b8113ddcdb4ec78e0846171e594280', - rse: 'XRD3', - state: 'REPLICATING', - rule_id: '817b3030097446a38b3b842bf528e112', - }), - JSON.stringify({ - scope: 'test', - name: 'file4', - rse_id: 'c8b8113ddcdb4ec78e0846171e594280', - rse: 'XRD3', - state: 'REPLICATING', - rule_id: '817b3030097446a38b3b842bf528e112', - }), - ]; - - const listRuleReplicaLocksEndpoint: MockEndpoint = { - url: `${MockRucioServerFactory.RUCIO_HOST}/rules/817b3030097446a38b3b842bf528e112/locks`, - method: 'GET', - endsWith: '817b3030097446a38b3b842bf528e112/locks', - response: { - status: 200, - headers: { - 'Content-Type': 'application/x-json-stream', - }, - body: Readable.from(replicaLockStates.join('\n')), - }, - }; - MockRucioServerFactory.createMockRucioServer(true, [getRuleEndpoint, listRuleReplicaLocksEndpoint]); + MockRucioServerFactory.createMockRucioServer(true, [getRuleEndpoint]); }); afterEach(() => { fetchMock.dontMock(); }); - it('Should fetch details of a rule', async () => { + it('Should fetch details for a rule', async () => { const ruleGateway: RuleGatewayOutputPort = appContainer.get(GATEWAYS.RULE); - const listRuleLockStatesDTO: BaseStreamableDTO = await ruleGateway.listRuleReplicaLockStates( - MockRucioServerFactory.VALID_RUCIO_TOKEN, - '817b3030097446a38b3b842bf528e112', - ); - expect(listRuleLockStatesDTO.status).toEqual('success'); - - const ruleStream = listRuleLockStatesDTO.stream; - if (ruleStream == null || ruleStream == undefined) { - fail('Rule stream is null or undefined'); - } - - const recievedData = await collectStreamedData(ruleStream); - expect(recievedData.length).toEqual(4); - expect(recievedData[0].name).toEqual('file1'); - expect(recievedData[0].state).toEqual(LockState.REPLICATING); + const ruleMetaDTO: RuleMetaDTO = await ruleGateway.getRule(MockRucioServerFactory.VALID_RUCIO_TOKEN, '817b3030097446a38b3b842bf528e112'); + expect(ruleMetaDTO).toEqual(expectedDTO); }); }); From 336c825c465d9adb51f5d53769dcaa879b6368a7 Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 16 Oct 2024 21:26:27 +0300 Subject: [PATCH 08/32] Express rule grouping with a separate enumerable --- src/lib/core/entity/rucio.ts | 8 +++++- .../rule-gateway/rule-gateway-utils.ts | 26 +++++++++++++------ .../rule/rule-gateway-get-rule.test.ts | 4 +-- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/lib/core/entity/rucio.ts b/src/lib/core/entity/rucio.ts index 3b1efcc74..2590c2771 100644 --- a/src/lib/core/entity/rucio.ts +++ b/src/lib/core/entity/rucio.ts @@ -142,7 +142,7 @@ export type RuleMeta = { created_at: DateISO; did_type: DIDType; expires_at: DateISO | null; - grouping: DIDType; + grouping: RuleGrouping; id: string; ignore_account_limit: boolean; ignore_availability: boolean; @@ -304,6 +304,12 @@ export enum LockState { UNKNOWN = 'U', } +export enum RuleGrouping { + ALL = 'A', + DATASET = 'D', + NONE = 'N', +} + // rucio.db.sqla.constants::RuleNotification export enum RuleNotification { Yes = 'Y', // notify when the rules state becomes OK diff --git a/src/lib/infrastructure/gateway/rule-gateway/rule-gateway-utils.ts b/src/lib/infrastructure/gateway/rule-gateway/rule-gateway-utils.ts index 0b05acb6b..b107b21ff 100644 --- a/src/lib/infrastructure/gateway/rule-gateway/rule-gateway-utils.ts +++ b/src/lib/infrastructure/gateway/rule-gateway/rule-gateway-utils.ts @@ -1,5 +1,5 @@ import { RuleDTO, RuleMetaDTO, RuleReplicaLockStateDTO } from '@/lib/core/dto/rule-dto'; -import { DIDType, LockState, RuleNotification, RuleState } from '@/lib/core/entity/rucio'; +import { DIDType, LockState, RuleGrouping, RuleNotification, RuleState } from '@/lib/core/entity/rucio'; export type TRucioRule = { error: null | string; @@ -117,18 +117,29 @@ function getDIDType(type: string): DIDType { function getRuleNotification(notification: string): RuleNotification { const cleanNotification = notification.trim().toUpperCase(); switch (cleanNotification) { - // TODO: check if that's the return format of the rucio server - case 'Y': + case 'YES': return RuleNotification.Yes; - case 'C': + case 'CLOSE': return RuleNotification.Close; - case 'P': + case 'PROGRESS': return RuleNotification.Progress; default: return RuleNotification.No; } } +function getRuleGrouping(grouping: string): RuleGrouping { + const cleanGrouping = grouping.trim().toUpperCase(); + switch (cleanGrouping) { + case 'ALL': + return RuleGrouping.ALL; + case 'DATASET': + return RuleGrouping.DATASET; + default: + return RuleGrouping.NONE; + } +} + export function convertToRuleMetaDTO(rule: TRucioRule): RuleMetaDTO { return { status: 'success', @@ -138,8 +149,7 @@ export function convertToRuleMetaDTO(rule: TRucioRule): RuleMetaDTO { created_at: rule.created_at, did_type: getDIDType(rule.did_type), expires_at: rule.expires_at, - // TODO: should be of a separate type - grouping: getDIDType(rule.grouping), + grouping: getRuleGrouping(rule.grouping), id: rule.id, ignore_account_limit: rule.ignore_account_limit, ignore_availability: rule.ignore_availability, @@ -195,7 +205,7 @@ export function getEmptyRuleMetaDTO(): RuleMetaDTO { created_at: '', did_type: DIDType.UNKNOWN, expires_at: '', - grouping: DIDType.UNKNOWN, + grouping: RuleGrouping.NONE, id: '', ignore_account_limit: false, ignore_availability: false, diff --git a/test/gateway/rule/rule-gateway-get-rule.test.ts b/test/gateway/rule/rule-gateway-get-rule.test.ts index 7f83ad06c..68cc914df 100644 --- a/test/gateway/rule/rule-gateway-get-rule.test.ts +++ b/test/gateway/rule/rule-gateway-get-rule.test.ts @@ -1,5 +1,5 @@ import { RuleMetaDTO } from '@/lib/core/dto/rule-dto'; -import { DIDType, RuleNotification, RuleState } from '@/lib/core/entity/rucio'; +import { DIDType, RuleGrouping, RuleNotification, RuleState } from '@/lib/core/entity/rucio'; import RuleGatewayOutputPort from '@/lib/core/port/secondary/rule-gateway-output-port'; import appContainer from '@/lib/infrastructure/ioc/container-config'; import GATEWAYS from '@/lib/infrastructure/ioc/ioc-symbols-gateway'; @@ -21,7 +21,7 @@ describe('Rule Gateway', () => { updated_at: 'Mon, 27 Nov 2023 17:57:44 UTC', scope: 'test', expires_at: null, - grouping: DIDType.DATASET, + grouping: RuleGrouping.DATASET, name: 'container', notification: RuleNotification.No, did_type: DIDType.CONTAINER, From 155ac67a477db088e6b033910439de96ce1fade6 Mon Sep 17 00:00:00 2001 From: MytsV Date: Thu, 17 Oct 2024 13:19:33 +0300 Subject: [PATCH 09/32] Implement get rule feature and connect it to the endpoint --- src/lib/core/port/primary/get-rule-ports.ts | 12 ++ src/lib/core/use-case/get-rule-usecase.ts | 49 ++++++++ .../usecase-models/get-rule-usecase-models.ts | 18 +++ .../controller/get-rule-controller.ts | 27 +++++ .../infrastructure/data/view-model/rule.ts | 32 +++++- .../infrastructure/ioc/container-config.ts | 2 + .../ioc/features/get-rule-feature.ts | 35 ++++++ .../ioc/ioc-symbols-controllers.ts | 1 + .../ioc/ioc-symbols-input-port.ts | 1 + .../ioc/ioc-symbols-usecase-factory.ts | 1 + .../presenter/get-rule-presenter.ts | 26 +++++ src/pages/api/feature/get-rule.ts | 34 ++++++ test/api/rule/get-rule.test.ts | 105 ++++++++++++++++++ 13 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 src/lib/core/port/primary/get-rule-ports.ts create mode 100644 src/lib/core/use-case/get-rule-usecase.ts create mode 100644 src/lib/core/usecase-models/get-rule-usecase-models.ts create mode 100644 src/lib/infrastructure/controller/get-rule-controller.ts create mode 100644 src/lib/infrastructure/ioc/features/get-rule-feature.ts create mode 100644 src/lib/infrastructure/presenter/get-rule-presenter.ts create mode 100644 src/pages/api/feature/get-rule.ts create mode 100644 test/api/rule/get-rule.test.ts diff --git a/src/lib/core/port/primary/get-rule-ports.ts b/src/lib/core/port/primary/get-rule-ports.ts new file mode 100644 index 000000000..6bedd626c --- /dev/null +++ b/src/lib/core/port/primary/get-rule-ports.ts @@ -0,0 +1,12 @@ +import { BaseAuthenticatedInputPort, BaseOutputPort } from '@/lib/sdk/primary-ports'; +import { GetRuleError, GetRuleRequest, GetRuleResponse } from '@/lib/core/usecase-models/get-rule-usecase-models'; + +/** + * @interface GetRuleInputPort representing the GetRule usecase. + */ +export interface GetRuleInputPort extends BaseAuthenticatedInputPort {} + +/** + * @interface GetRuleOutputPort representing the GetRule presenter. + */ +export interface GetRuleOutputPort extends BaseOutputPort {} diff --git a/src/lib/core/use-case/get-rule-usecase.ts b/src/lib/core/use-case/get-rule-usecase.ts new file mode 100644 index 000000000..c8291c02e --- /dev/null +++ b/src/lib/core/use-case/get-rule-usecase.ts @@ -0,0 +1,49 @@ +import { injectable } from 'inversify'; +import { BaseSingleEndpointUseCase } from '@/lib/sdk/usecase'; +import { AuthenticatedRequestModel } from '@/lib/sdk/usecase-models'; + +import { GetRuleError, GetRuleRequest, GetRuleResponse } from '@/lib/core/usecase-models/get-rule-usecase-models'; +import { GetRuleInputPort, type GetRuleOutputPort } from '@/lib/core/port/primary/get-rule-ports'; + +import { RuleMetaDTO } from '@/lib/core/dto/rule-dto'; +import type RuleGatewayOutputPort from '@/lib/core/port/secondary/rule-gateway-output-port'; + +@injectable() +export default class GetRuleUseCase + extends BaseSingleEndpointUseCase, GetRuleResponse, GetRuleError, RuleMetaDTO> + implements GetRuleInputPort +{ + constructor(protected readonly presenter: GetRuleOutputPort, private readonly gateway: RuleGatewayOutputPort) { + super(presenter); + } + + validateRequestModel(requestModel: AuthenticatedRequestModel): GetRuleError | undefined { + return undefined; + } + + async makeGatewayRequest(requestModel: AuthenticatedRequestModel): Promise { + const { rucioAuthToken, id } = requestModel; + const dto: RuleMetaDTO = await this.gateway.getRule(rucioAuthToken, id); + return dto; + } + handleGatewayError(error: RuleMetaDTO): GetRuleError { + return { + status: 'error', + message: error.errorMessage ? error.errorMessage : 'Gateway Error', + name: `Gateway Error`, + code: error.errorCode, + } as GetRuleError; + } + + processDTO(dto: RuleMetaDTO): { data: GetRuleResponse | GetRuleError; status: 'success' | 'error' } { + const responseModel: GetRuleResponse = { + ...dto, + status: 'success', + }; + + return { + status: 'success', + data: responseModel, + }; + } +} diff --git a/src/lib/core/usecase-models/get-rule-usecase-models.ts b/src/lib/core/usecase-models/get-rule-usecase-models.ts new file mode 100644 index 000000000..7cf44e098 --- /dev/null +++ b/src/lib/core/usecase-models/get-rule-usecase-models.ts @@ -0,0 +1,18 @@ +import { BaseErrorResponseModel, BaseResponseModel } from '@/lib/sdk/usecase-models'; +import { RuleMeta } from '@/lib/core/entity/rucio'; +/** + * @interface GetRuleRequest represents the RequestModel for get_rule usecase + */ +export interface GetRuleRequest { + id: string; +} + +/** + * @interface GetRuleResponse represents the ResponseModel for get_rule usecase + */ +export interface GetRuleResponse extends BaseResponseModel, RuleMeta {} + +/** + * @interface GetRuleError represents the ErrorModel for get_rule usecase + */ +export interface GetRuleError extends BaseErrorResponseModel {} diff --git a/src/lib/infrastructure/controller/get-rule-controller.ts b/src/lib/infrastructure/controller/get-rule-controller.ts new file mode 100644 index 000000000..ede8060c4 --- /dev/null +++ b/src/lib/infrastructure/controller/get-rule-controller.ts @@ -0,0 +1,27 @@ +import { injectable, inject } from 'inversify'; +import { NextApiResponse } from 'next'; + +import { AuthenticatedRequestModel } from '@/lib/sdk/usecase-models'; +import { BaseController, TAuthenticatedControllerParameters } from '@/lib/sdk/controller'; +import { GetRuleRequest } from '@/lib/core/usecase-models/get-rule-usecase-models'; +import { GetRuleInputPort } from '@/lib/core/port/primary/get-rule-ports'; +import USECASE_FACTORY from '@/lib/infrastructure/ioc/ioc-symbols-usecase-factory'; + +export type GetRuleControllerParameters = TAuthenticatedControllerParameters & { + id: string; +}; + +@injectable() +class GetRuleController extends BaseController> { + constructor(@inject(USECASE_FACTORY.GET_RULE) getRuleUseCaseFactory: (response: NextApiResponse) => GetRuleInputPort) { + super(getRuleUseCaseFactory); + } + prepareRequestModel(parameters: GetRuleControllerParameters): AuthenticatedRequestModel { + return { + rucioAuthToken: parameters.rucioAuthToken, + id: parameters.id, + }; + } +} + +export default GetRuleController; diff --git a/src/lib/infrastructure/data/view-model/rule.ts b/src/lib/infrastructure/data/view-model/rule.ts index 20737cfc7..061538fec 100644 --- a/src/lib/infrastructure/data/view-model/rule.ts +++ b/src/lib/infrastructure/data/view-model/rule.ts @@ -1,4 +1,4 @@ -import { Rule, RuleMeta, RulePageLockEntry, RuleState } from '@/lib/core/entity/rucio'; +import { DIDType, Rule, RuleGrouping, RuleMeta, RuleNotification, RulePageLockEntry, RuleState } from '@/lib/core/entity/rucio'; import { BaseViewModel } from '@/lib/sdk/view-models'; import { RSEAccountUsageLimitViewModel } from '@/lib/infrastructure/data/view-model/rse'; import { ListDIDsViewModel } from '@/lib/infrastructure/data/view-model/list-did'; @@ -82,3 +82,33 @@ export const getEmptyCreateRuleViewModel = (): CreateRuleViewModel => { rule_ids: [], }; }; + +export interface GetRuleViewModel extends BaseViewModel, RuleMeta {} +export const getEmptyGetRuleViewModel = (): GetRuleViewModel => { + return { + status: 'error', + account: '', + activity: '', + copies: 0, + created_at: '', + did_type: DIDType.UNKNOWN, + expires_at: null, + grouping: RuleGrouping.NONE, + id: '', + ignore_account_limit: false, + ignore_availability: false, + locked: false, + locks_ok_cnt: 0, + locks_replicating_cnt: 0, + locks_stuck_cnt: 0, + name: '', + notification: RuleNotification.No, + priority: 0, + purge_replicas: false, + rse_expression: '', + scope: '', + split_container: false, + state: RuleState.UNKNOWN, + updated_at: '', + }; +}; diff --git a/src/lib/infrastructure/ioc/container-config.ts b/src/lib/infrastructure/ioc/container-config.ts index a13bb7bc5..eaf84f17a 100644 --- a/src/lib/infrastructure/ioc/container-config.ts +++ b/src/lib/infrastructure/ioc/container-config.ts @@ -69,6 +69,7 @@ import CreateRuleFeature from '@/lib/infrastructure/ioc/features/create-rule-fea import AddDIDFeature from '@/lib/infrastructure/ioc/features/add-did-feature'; import AttachDIDsFeature from '@/lib/infrastructure/ioc/features/attach-dids-feature'; import SetDIDStatusFeature from '@/lib/infrastructure/ioc/features/set-did-status-feature'; +import GetRuleFeature from '@/lib/infrastructure/ioc/features/get-rule-feature'; /** * IoC Container configuration for the application. @@ -102,6 +103,7 @@ loadFeaturesSync(appContainer, [ new ListDatasetReplicasFeature(appContainer), new ListFileReplicasFeature(appContainer), new ListSubscriptionsFeature(appContainer), + new GetRuleFeature(appContainer), ]); // Features: Page RSE diff --git a/src/lib/infrastructure/ioc/features/get-rule-feature.ts b/src/lib/infrastructure/ioc/features/get-rule-feature.ts new file mode 100644 index 000000000..ea60f1c05 --- /dev/null +++ b/src/lib/infrastructure/ioc/features/get-rule-feature.ts @@ -0,0 +1,35 @@ +import RuleGatewayOutputPort from '@/lib/core/port/secondary/rule-gateway-output-port'; +import { GetRuleError, GetRuleRequest, GetRuleResponse } from '@/lib/core/usecase-models/get-rule-usecase-models'; +import { GetRuleControllerParameters } from '@/lib/infrastructure/controller/get-rule-controller'; +import GetRuleController from '@/lib/infrastructure/controller/get-rule-controller'; +import { GetRuleViewModel } from '@/lib/infrastructure/data/view-model/rule'; +import { BaseFeature, IOCSymbols } from '@/lib/sdk/ioc-helpers'; +import GATEWAYS from '@/lib/infrastructure/ioc/ioc-symbols-gateway'; +import CONTROLLERS from '@/lib/infrastructure/ioc/ioc-symbols-controllers'; +import INPUT_PORT from '@/lib/infrastructure/ioc/ioc-symbols-input-port'; +import USECASE_FACTORY from '@/lib/infrastructure/ioc/ioc-symbols-usecase-factory'; +import { Container } from 'inversify'; + +import GetRuleUseCase from '@/lib/core/use-case/get-rule-usecase'; + +import GetRulePresenter from '@/lib/infrastructure/presenter/get-rule-presenter'; + +export default class GetRuleFeature extends BaseFeature< + GetRuleControllerParameters, + GetRuleRequest, + GetRuleResponse, + GetRuleError, + GetRuleViewModel +> { + constructor(appContainer: Container) { + const rucioRuleGateway = appContainer.get(GATEWAYS.RULE); + + const symbols: IOCSymbols = { + CONTROLLER: CONTROLLERS.GET_RULE, + USECASE_FACTORY: USECASE_FACTORY.GET_RULE, + INPUT_PORT: INPUT_PORT.GET_RULE, + }; + const useCaseConstructorArgs = [rucioRuleGateway]; + super('GetRule', GetRuleController, GetRuleUseCase, useCaseConstructorArgs, GetRulePresenter, false, symbols); + } +} diff --git a/src/lib/infrastructure/ioc/ioc-symbols-controllers.ts b/src/lib/infrastructure/ioc/ioc-symbols-controllers.ts index 5e513e83b..d6c5fe179 100644 --- a/src/lib/infrastructure/ioc/ioc-symbols-controllers.ts +++ b/src/lib/infrastructure/ioc/ioc-symbols-controllers.ts @@ -36,6 +36,7 @@ const CONTROLLERS = { ADD_DID: Symbol.for('AddDIDController'), ATTACH_DIDS: Symbol.for('AttachDIDsController'), SET_DID_STATUS: Symbol.for('SetDIDStatusController'), + GET_RULE: Symbol.for('GetRuleController'), }; export default CONTROLLERS; diff --git a/src/lib/infrastructure/ioc/ioc-symbols-input-port.ts b/src/lib/infrastructure/ioc/ioc-symbols-input-port.ts index 12645e1dc..b604617eb 100644 --- a/src/lib/infrastructure/ioc/ioc-symbols-input-port.ts +++ b/src/lib/infrastructure/ioc/ioc-symbols-input-port.ts @@ -35,6 +35,7 @@ const INPUT_PORT = { ADD_DID: Symbol.for('AddDIDInputPort'), ATTACH_DIDS: Symbol.for('AttachDIDsInputPort'), SET_DID_STATUS: Symbol.for('SetDIDStatusInputPort'), + GET_RULE: Symbol.for('GetRuleInputPort'), }; export default INPUT_PORT; diff --git a/src/lib/infrastructure/ioc/ioc-symbols-usecase-factory.ts b/src/lib/infrastructure/ioc/ioc-symbols-usecase-factory.ts index 0773a0ef6..6240d2f84 100644 --- a/src/lib/infrastructure/ioc/ioc-symbols-usecase-factory.ts +++ b/src/lib/infrastructure/ioc/ioc-symbols-usecase-factory.ts @@ -36,6 +36,7 @@ const USECASE_FACTORY = { ADD_DID: Symbol.for('Factory'), ATTACH_DIDS: Symbol.for('Factory'), SET_DID_STATUS: Symbol.for('Factory'), + GET_RULE: Symbol.for('Factory'), }; export default USECASE_FACTORY; diff --git a/src/lib/infrastructure/presenter/get-rule-presenter.ts b/src/lib/infrastructure/presenter/get-rule-presenter.ts new file mode 100644 index 000000000..8876ffa6f --- /dev/null +++ b/src/lib/infrastructure/presenter/get-rule-presenter.ts @@ -0,0 +1,26 @@ +import { BasePresenter } from '@/lib/sdk/presenter'; +import { GetRuleError, GetRuleResponse } from '@/lib/core/usecase-models/get-rule-usecase-models'; +import { getEmptyGetRuleViewModel, GetRuleViewModel } from '@/lib/infrastructure/data/view-model/rule'; + +export default class GetRulePresenter extends BasePresenter { + convertResponseModelToViewModel(responseModel: GetRuleResponse): { viewModel: GetRuleViewModel; status: number } { + const viewModel: GetRuleViewModel = { + ...responseModel, + }; + return { + status: 200, + viewModel: viewModel, + }; + } + + convertErrorModelToViewModel(errorModel: GetRuleError): { viewModel: GetRuleViewModel; status: number } { + const viewModel: GetRuleViewModel = getEmptyGetRuleViewModel(); + const message = errorModel.message || errorModel.name; + viewModel.message = message; + const errorCode = errorModel.code || 500; + return { + status: errorCode, + viewModel: viewModel, + }; + } +} diff --git a/src/pages/api/feature/get-rule.ts b/src/pages/api/feature/get-rule.ts new file mode 100644 index 000000000..c3b883d55 --- /dev/null +++ b/src/pages/api/feature/get-rule.ts @@ -0,0 +1,34 @@ +import { SessionUser } from '@/lib/core/entity/auth-models'; + +import { withAuthenticatedSessionRoute } from '@/lib/infrastructure/auth/session-utils'; +import { GetRuleControllerParameters } from '@/lib/infrastructure/controller/get-rule-controller'; +import appContainer from '@/lib/infrastructure/ioc/container-config'; +import { GetRuleRequest } from '@/lib/core/usecase-models/get-rule-usecase-models'; +import CONTROLLERS from '@/lib/infrastructure/ioc/ioc-symbols-controllers'; +import { BaseController } from '@/lib/sdk/controller'; +import { NextApiRequest, NextApiResponse } from 'next'; + +async function getRule(req: NextApiRequest, res: NextApiResponse, rucioAuthToken: string, sessionUser?: SessionUser) { + if (req.method !== 'GET') { + res.status(405).json({ error: 'Method Not Allowed' }); + return; + } + + const { id } = req.query; + + if (!id || typeof id !== 'string') { + res.status(400).json({ error: 'Rule ID should be specified' }); + return; + } + + const controllerParameters: GetRuleControllerParameters = { + response: res, + rucioAuthToken: rucioAuthToken, + id, + }; + + const controller = appContainer.get>(CONTROLLERS.GET_RULE); + await controller.execute(controllerParameters); +} + +export default withAuthenticatedSessionRoute(getRule); diff --git a/test/api/rule/get-rule.test.ts b/test/api/rule/get-rule.test.ts new file mode 100644 index 000000000..feceaa58d --- /dev/null +++ b/test/api/rule/get-rule.test.ts @@ -0,0 +1,105 @@ +import appContainer from '@/lib/infrastructure/ioc/container-config'; +import CONTROLLERS from '@/lib/infrastructure/ioc/ioc-symbols-controllers'; +import { BaseController } from '@/lib/sdk/controller'; +import { NextApiResponse } from 'next'; +import { createHttpMocks } from 'test/fixtures/http-fixtures'; +import MockRucioServerFactory, { MockEndpoint } from 'test/fixtures/rucio-server'; +import { DIDType, RuleGrouping, RuleNotification, RuleState } from '@/lib/core/entity/rucio'; +import { GetRuleViewModel } from '@/lib/infrastructure/data/view-model/rule'; +import { GetRuleRequest } from '@/lib/core/usecase-models/get-rule-usecase-models'; +import { GetRuleControllerParameters } from '@/lib/infrastructure/controller/get-rule-controller'; + +describe('GET Rule API Test', () => { + const expectedViewModel: GetRuleViewModel = { + status: 'success', + locks_stuck_cnt: 0, + ignore_account_limit: false, + ignore_availability: false, + rse_expression: 'XRD3', + created_at: 'Mon, 27 Nov 2023 17:57:44 UTC', + account: 'root', + copies: 1, + activity: 'User Subscriptions', + priority: 3, + updated_at: 'Mon, 27 Nov 2023 17:57:44 UTC', + scope: 'test', + expires_at: null, + grouping: RuleGrouping.DATASET, + name: 'container', + notification: RuleNotification.No, + did_type: DIDType.CONTAINER, + locked: false, + state: RuleState.REPLICATING, + locks_ok_cnt: 0, + purge_replicas: false, + id: '817b3030097446a38b3b842bf528e112', + locks_replicating_cnt: 4, + split_container: false, + }; + + beforeEach(() => { + fetchMock.doMock(); + const getRuleEndpoint: MockEndpoint = { + url: `${MockRucioServerFactory.RUCIO_HOST}/rules/817b3030097446a38b3b842bf528e112`, + method: 'GET', + endsWith: '817b3030097446a38b3b842bf528e112', + response: { + status: 200, + headers: { + 'Content-Type': 'application/x-json-stream', + }, + body: JSON.stringify({ + error: null, + locks_stuck_cnt: 0, + ignore_availability: false, + meta: null, + subscription_id: null, + rse_expression: 'XRD3', + source_replica_expression: null, + ignore_account_limit: false, + created_at: 'Mon, 27 Nov 2023 17:57:44 UTC', + account: 'root', + copies: 1, + activity: 'User Subscriptions', + priority: 3, + updated_at: 'Mon, 27 Nov 2023 17:57:44 UTC', + scope: 'test', + expires_at: null, + grouping: 'DATASET', + name: 'container', + weight: null, + notification: 'NO', + comments: null, + did_type: 'CONTAINER', + locked: false, + stuck_at: null, + child_rule_id: null, + state: 'REPLICATING', + locks_ok_cnt: 0, + purge_replicas: false, + eol_at: null, + id: '817b3030097446a38b3b842bf528e112', + locks_replicating_cnt: 4, + split_container: false, + }), + }, + }; + MockRucioServerFactory.createMockRucioServer(true, [getRuleEndpoint]); + }); + afterEach(() => { + fetchMock.dontMock(); + }); + + test('it should get details for a rule', async () => { + const { req, res, session } = await createHttpMocks('/api/feature/get-rule?=817b3030097446a38b3b842bf528e112', 'GET', {}); + const getRuleController = appContainer.get>(CONTROLLERS.GET_RULE); + const getRuleControllerParams: GetRuleControllerParameters = { + rucioAuthToken: MockRucioServerFactory.VALID_RUCIO_TOKEN, + response: res as unknown as NextApiResponse, + id: '817b3030097446a38b3b842bf528e112', + }; + await getRuleController.execute(getRuleControllerParams); + const data = await res._getJSONData(); + expect(data).toEqual(expectedViewModel); + }); +}); From edff82881ec4c92042f04f850e7fab65a769506d Mon Sep 17 00:00:00 2001 From: MytsV Date: Mon, 11 Nov 2024 15:30:41 +0200 Subject: [PATCH 10/32] Connect the list locks endpoint to a feature --- src/lib/core/dto/rule-dto.ts | 2 + .../list-rule-replica-lock-states-ports.ts | 18 ++++ .../list-rule-replica-lock-states-usecase.ts | 88 +++++++++++++++++++ ...rule-replica-lock-states-usecase-models.ts | 18 ++++ ...ist-rule-replica-lock-states-controller.ts | 33 +++++++ .../infrastructure/data/view-model/rule.ts | 16 +++- .../list-rule-replica-lock-states-endpoint.ts | 9 +- .../gateway/rule-gateway/rule-gateway.ts | 4 +- .../infrastructure/ioc/container-config.ts | 2 + .../list-rule-replica-lock-states-feature.ts | 47 ++++++++++ .../ioc/ioc-symbols-controllers.ts | 1 + .../ioc/ioc-symbols-input-port.ts | 1 + .../ioc/ioc-symbols-usecase-factory.ts | 1 + ...list-rule-replica-lock-states-presenter.ts | 51 +++++++++++ .../feature/list-rule-replica-lock-states.ts | 36 ++++++++ .../list-rule-replica-lock-states.test.ts | 83 +++++++++++++++++ 16 files changed, 402 insertions(+), 8 deletions(-) create mode 100644 src/lib/core/port/primary/list-rule-replica-lock-states-ports.ts create mode 100644 src/lib/core/use-case/list-rule-replica-lock-states-usecase.ts create mode 100644 src/lib/core/usecase-models/list-rule-replica-lock-states-usecase-models.ts create mode 100644 src/lib/infrastructure/controller/list-rule-replica-lock-states-controller.ts create mode 100644 src/lib/infrastructure/ioc/features/list-rule-replica-lock-states-feature.ts create mode 100644 src/lib/infrastructure/presenter/list-rule-replica-lock-states-presenter.ts create mode 100644 src/pages/api/feature/list-rule-replica-lock-states.ts create mode 100644 test/api/rule/list-rule-replica-lock-states.test.ts diff --git a/src/lib/core/dto/rule-dto.ts b/src/lib/core/dto/rule-dto.ts index a94690560..7afb59318 100644 --- a/src/lib/core/dto/rule-dto.ts +++ b/src/lib/core/dto/rule-dto.ts @@ -18,6 +18,8 @@ export interface RuleReplicaLockStateDTO extends BaseDTO { state: LockState; } +export interface ListLocksDTO extends BaseStreamableDTO {} + export interface ListRulesDTO extends BaseStreamableDTO {} export interface CreateRuleDTO extends BaseDTO { diff --git a/src/lib/core/port/primary/list-rule-replica-lock-states-ports.ts b/src/lib/core/port/primary/list-rule-replica-lock-states-ports.ts new file mode 100644 index 000000000..63773f4e1 --- /dev/null +++ b/src/lib/core/port/primary/list-rule-replica-lock-states-ports.ts @@ -0,0 +1,18 @@ +import { BaseAuthenticatedInputPort, BaseStreamingOutputPort } from '@/lib/sdk/primary-ports'; +import { + ListRuleReplicaLockStatesResponse, + ListRuleReplicaLockStatesRequest, + ListRuleReplicaLockStatesError, +} from '@/lib/core/usecase-models/list-rule-replica-lock-states-usecase-models'; +import { ListRuleReplicaLockStatesViewModel } from '@/lib/infrastructure/data/view-model/rule'; +/** + * @interface ListRuleReplicaLockStatesInputPort that abstracts the usecase. + */ +export interface ListRuleReplicaLockStatesInputPort extends BaseAuthenticatedInputPort {} + +// TODO: Add viewmodel +/** + * @interface ListRuleReplicaLockStatesOutputPort that abtrsacts the presenter + */ +export interface ListRuleReplicaLockStatesOutputPort + extends BaseStreamingOutputPort {} diff --git a/src/lib/core/use-case/list-rule-replica-lock-states-usecase.ts b/src/lib/core/use-case/list-rule-replica-lock-states-usecase.ts new file mode 100644 index 000000000..82a5415e8 --- /dev/null +++ b/src/lib/core/use-case/list-rule-replica-lock-states-usecase.ts @@ -0,0 +1,88 @@ +import { injectable } from 'inversify'; +import { BaseSingleEndpointStreamingUseCase } from '@/lib/sdk/usecase'; +import { AuthenticatedRequestModel } from '@/lib/sdk/usecase-models'; + +import { + ListRuleReplicaLockStatesError, + ListRuleReplicaLockStatesRequest, + ListRuleReplicaLockStatesResponse, +} from '@/lib/core/usecase-models/list-rule-replica-lock-states-usecase-models'; +import { + ListRuleReplicaLockStatesInputPort, + type ListRuleReplicaLockStatesOutputPort, +} from '@/lib/core/port/primary/list-rule-replica-lock-states-ports'; +import { ListRuleReplicaLockStatesViewModel } from '@/lib/infrastructure/data/view-model/rule'; + +import { RuleReplicaLockStateDTO, ListLocksDTO } from '@/lib/core/dto/rule-dto'; +import type RuleGatewayOutputPort from '@/lib/core/port/secondary/rule-gateway-output-port'; + +@injectable() +export default class ListRuleReplicaLockStatesUseCase + extends BaseSingleEndpointStreamingUseCase< + AuthenticatedRequestModel, + ListRuleReplicaLockStatesResponse, + ListRuleReplicaLockStatesError, + ListLocksDTO, + RuleReplicaLockStateDTO, + ListRuleReplicaLockStatesViewModel + > + implements ListRuleReplicaLockStatesInputPort +{ + constructor(protected readonly presenter: ListRuleReplicaLockStatesOutputPort, private readonly gateway: RuleGatewayOutputPort) { + super(presenter); + } + + validateRequestModel(requestModel: AuthenticatedRequestModel): ListRuleReplicaLockStatesError | undefined { + return undefined; + } + + async makeGatewayRequest(requestModel: AuthenticatedRequestModel): Promise { + const { rucioAuthToken, id } = requestModel; + return await this.gateway.listRuleReplicaLockStates(rucioAuthToken, id); + } + + handleGatewayError(error: RuleReplicaLockStateDTO): ListRuleReplicaLockStatesError { + return { + status: 'error', + error: `Gateway retuned with ${error.errorCode}: ${error.errorMessage}`, + message: error.errorMessage ? error.errorMessage : 'Gateway Error', + name: `Gateway Error`, + code: error.errorCode, + } as ListRuleReplicaLockStatesError; + } + + processStreamedData(dto: RuleReplicaLockStateDTO): { + data: ListRuleReplicaLockStatesResponse | ListRuleReplicaLockStatesError; + status: 'success' | 'error'; + } { + if (dto.status === 'error') { + const errorModel: ListRuleReplicaLockStatesError = { + status: 'error', + code: dto.errorCode || 500, + message: dto.errorMessage || 'Could not fetch or process the fetched data', + name: dto.errorName || 'Gateway Error', + }; + return { + status: 'error', + data: errorModel, + }; + } + + const responseModel: ListRuleReplicaLockStatesResponse = { + ...dto, + status: 'success', + ddm_link: '', + fts_link: '', + }; + return { + status: 'success', + data: responseModel, + }; + } + + intializeRequest( + request: AuthenticatedRequestModel>, + ): Promise { + return Promise.resolve(undefined); + } +} diff --git a/src/lib/core/usecase-models/list-rule-replica-lock-states-usecase-models.ts b/src/lib/core/usecase-models/list-rule-replica-lock-states-usecase-models.ts new file mode 100644 index 000000000..9fda647f8 --- /dev/null +++ b/src/lib/core/usecase-models/list-rule-replica-lock-states-usecase-models.ts @@ -0,0 +1,18 @@ +import { BaseErrorResponseModel, BaseResponseModel } from '@/lib/sdk/usecase-models'; +import { RulePageLockEntry } from '@/lib/core/entity/rucio'; +/** + * @interface ListRuleReplicaLockStatesRequest represents the RequestModel for list_rule_replica_lock_states usecase + */ +export interface ListRuleReplicaLockStatesRequest { + id: string; +} + +/** + * @interface ListRuleReplicaLockStatesResponse represents the ResponseModel for list_rule_replica_lock_states usecase + */ +export interface ListRuleReplicaLockStatesResponse extends BaseResponseModel, RulePageLockEntry {} + +/** + * @interface ListRuleReplicaLockStatesError represents the ErrorModel for list_rule_replica_lock_states usecase + */ +export interface ListRuleReplicaLockStatesError extends BaseErrorResponseModel {} diff --git a/src/lib/infrastructure/controller/list-rule-replica-lock-states-controller.ts b/src/lib/infrastructure/controller/list-rule-replica-lock-states-controller.ts new file mode 100644 index 000000000..65101cebf --- /dev/null +++ b/src/lib/infrastructure/controller/list-rule-replica-lock-states-controller.ts @@ -0,0 +1,33 @@ +import { injectable, inject } from 'inversify'; +import { NextApiResponse } from 'next'; + +import { AuthenticatedRequestModel } from '@/lib/sdk/usecase-models'; +import { BaseController, TAuthenticatedControllerParameters } from '@/lib/sdk/controller'; +import { ListRuleReplicaLockStatesRequest } from '@/lib/core/usecase-models/list-rule-replica-lock-states-usecase-models'; +import { ListRuleReplicaLockStatesInputPort } from '@/lib/core/port/primary/list-rule-replica-lock-states-ports'; +import USECASE_FACTORY from '@/lib/infrastructure/ioc/ioc-symbols-usecase-factory'; + +export type ListRuleReplicaLockStatesControllerParameters = TAuthenticatedControllerParameters & { + id: string; +}; + +@injectable() +class ListRuleReplicaLockStatesController extends BaseController< + ListRuleReplicaLockStatesControllerParameters, + AuthenticatedRequestModel +> { + constructor( + @inject(USECASE_FACTORY.LIST_RULE_REPLICA_LOCK_STATES) + listRuleReplicaLockStatesUseCaseFactory: (response: NextApiResponse) => ListRuleReplicaLockStatesInputPort, + ) { + super(listRuleReplicaLockStatesUseCaseFactory); + } + prepareRequestModel(parameters: ListRuleReplicaLockStatesControllerParameters): AuthenticatedRequestModel { + return { + rucioAuthToken: parameters.rucioAuthToken, + id: parameters.id, + }; + } +} + +export default ListRuleReplicaLockStatesController; diff --git a/src/lib/infrastructure/data/view-model/rule.ts b/src/lib/infrastructure/data/view-model/rule.ts index 061538fec..9c740ecd6 100644 --- a/src/lib/infrastructure/data/view-model/rule.ts +++ b/src/lib/infrastructure/data/view-model/rule.ts @@ -1,4 +1,4 @@ -import { DIDType, Rule, RuleGrouping, RuleMeta, RuleNotification, RulePageLockEntry, RuleState } from '@/lib/core/entity/rucio'; +import { DIDType, LockState, Rule, RuleGrouping, RuleMeta, RuleNotification, RulePageLockEntry, RuleState } from '@/lib/core/entity/rucio'; import { BaseViewModel } from '@/lib/sdk/view-models'; import { RSEAccountUsageLimitViewModel } from '@/lib/infrastructure/data/view-model/rse'; import { ListDIDsViewModel } from '@/lib/infrastructure/data/view-model/list-did'; @@ -112,3 +112,17 @@ export const getEmptyGetRuleViewModel = (): GetRuleViewModel => { updated_at: '', }; }; + +export interface ListRuleReplicaLockStatesViewModel extends BaseViewModel, RulePageLockEntry {} + +export const getEmptyListRuleReplicaLockStatesViewModel = (): ListRuleReplicaLockStatesViewModel => { + return { + ddm_link: '', + fts_link: '', + name: '', + rse: '', + scope: '', + state: LockState.UNKNOWN, + status: 'error', + }; +}; diff --git a/src/lib/infrastructure/gateway/rule-gateway/endpoints/list-rule-replica-lock-states-endpoint.ts b/src/lib/infrastructure/gateway/rule-gateway/endpoints/list-rule-replica-lock-states-endpoint.ts index 79b78a68e..5ceb53864 100644 --- a/src/lib/infrastructure/gateway/rule-gateway/endpoints/list-rule-replica-lock-states-endpoint.ts +++ b/src/lib/infrastructure/gateway/rule-gateway/endpoints/list-rule-replica-lock-states-endpoint.ts @@ -1,10 +1,9 @@ -import { RuleReplicaLockStateDTO } from '@/lib/core/dto/rule-dto'; -import { BaseStreamableDTO } from '@/lib/sdk/dto'; +import { ListLocksDTO, RuleReplicaLockStateDTO } from '@/lib/core/dto/rule-dto'; import { BaseStreamableEndpoint } from '@/lib/sdk/gateway-endpoints'; import { HTTPRequest } from '@/lib/sdk/http'; import { Response } from 'node-fetch'; import { convertToRuleReplicaLockDTO, TRucioRuleReplicaLock } from '../rule-gateway-utils'; -export default class ListRuleReplicaLockStatesEndpoint extends BaseStreamableEndpoint { +export default class ListRuleReplicaLockStatesEndpoint extends BaseStreamableEndpoint { constructor(private readonly rucioAuthToken: string, private readonly ruleId: string) { super(true); } @@ -31,9 +30,9 @@ export default class ListRuleReplicaLockStatesEndpoint extends BaseStreamableEnd * @param response The reponse containing error data * @returns */ - async reportErrors(statusCode: number, response: Response): Promise { + async reportErrors(statusCode: number, response: Response): Promise { const data = await response.json(); - const errorDTO: BaseStreamableDTO = { + const errorDTO: ListLocksDTO = { status: 'error', errorMessage: data, errorCode: statusCode, diff --git a/src/lib/infrastructure/gateway/rule-gateway/rule-gateway.ts b/src/lib/infrastructure/gateway/rule-gateway/rule-gateway.ts index 3f9479330..e019ff856 100644 --- a/src/lib/infrastructure/gateway/rule-gateway/rule-gateway.ts +++ b/src/lib/infrastructure/gateway/rule-gateway/rule-gateway.ts @@ -1,4 +1,4 @@ -import { CreateRuleDTO, ListRulesDTO, RuleDTO, RuleMetaDTO } from '@/lib/core/dto/rule-dto'; +import { CreateRuleDTO, ListLocksDTO, ListRulesDTO, RuleMetaDTO } from '@/lib/core/dto/rule-dto'; import RuleGatewayOutputPort from '@/lib/core/port/secondary/rule-gateway-output-port'; import { BaseStreamableDTO } from '@/lib/sdk/dto'; import { injectable } from 'inversify'; @@ -39,7 +39,7 @@ export default class RuleGateway implements RuleGatewayOutputPort { } } - async listRuleReplicaLockStates(rucioAuthToken: string, ruleId: string): Promise { + async listRuleReplicaLockStates(rucioAuthToken: string, ruleId: string): Promise { try { const endpoint = new ListRuleReplicaLockStatesEndpoint(rucioAuthToken, ruleId); const errorDTO: BaseStreamableDTO | undefined = await endpoint.fetch(); diff --git a/src/lib/infrastructure/ioc/container-config.ts b/src/lib/infrastructure/ioc/container-config.ts index eaf84f17a..7af3a3d4c 100644 --- a/src/lib/infrastructure/ioc/container-config.ts +++ b/src/lib/infrastructure/ioc/container-config.ts @@ -70,6 +70,7 @@ import AddDIDFeature from '@/lib/infrastructure/ioc/features/add-did-feature'; import AttachDIDsFeature from '@/lib/infrastructure/ioc/features/attach-dids-feature'; import SetDIDStatusFeature from '@/lib/infrastructure/ioc/features/set-did-status-feature'; import GetRuleFeature from '@/lib/infrastructure/ioc/features/get-rule-feature'; +import ListRuleReplicaLockStatesFeature from '@/lib/infrastructure/ioc/features/list-rule-replica-lock-states-feature'; /** * IoC Container configuration for the application. @@ -133,6 +134,7 @@ loadFeaturesSync(appContainer, [new ListSubscriptionRuleStatesFeature(appContain // Features: List Rules loadFeaturesSync(appContainer, [new ListRulesFeature(appContainer)]); +loadFeaturesSync(appContainer, [new ListRuleReplicaLockStatesFeature(appContainer)]); // Features: Dashboard loadFeaturesSync(appContainer, [new ListAccountRSEUsageFeature(appContainer)]); diff --git a/src/lib/infrastructure/ioc/features/list-rule-replica-lock-states-feature.ts b/src/lib/infrastructure/ioc/features/list-rule-replica-lock-states-feature.ts new file mode 100644 index 000000000..25e4b37d4 --- /dev/null +++ b/src/lib/infrastructure/ioc/features/list-rule-replica-lock-states-feature.ts @@ -0,0 +1,47 @@ +import RuleGatewayOutputPort from '@/lib/core/port/secondary/rule-gateway-output-port'; +import { + ListRuleReplicaLockStatesError, + ListRuleReplicaLockStatesRequest, + ListRuleReplicaLockStatesResponse, +} from '@/lib/core/usecase-models/list-rule-replica-lock-states-usecase-models'; +import { ListRuleReplicaLockStatesControllerParameters } from '@/lib/infrastructure/controller/list-rule-replica-lock-states-controller'; +import ListRuleReplicaLockStatesController from '@/lib/infrastructure/controller/list-rule-replica-lock-states-controller'; +import { ListRuleReplicaLockStatesViewModel } from '@/lib/infrastructure/data/view-model/rule'; +import { BaseStreamableFeature, IOCSymbols } from '@/lib/sdk/ioc-helpers'; +import GATEWAYS from '@/lib/infrastructure/ioc/ioc-symbols-gateway'; +import CONTROLLERS from '@/lib/infrastructure/ioc/ioc-symbols-controllers'; +import INPUT_PORT from '@/lib/infrastructure/ioc/ioc-symbols-input-port'; +import USECASE_FACTORY from '@/lib/infrastructure/ioc/ioc-symbols-usecase-factory'; +import { Container } from 'inversify'; + +import ListRuleReplicaLockStatesUseCase from '@/lib/core/use-case/list-rule-replica-lock-states-usecase'; + +import ListRuleReplicaLockStatesPresenter from '@/lib/infrastructure/presenter/list-rule-replica-lock-states-presenter'; + +export default class ListRuleReplicaLockStatesFeature extends BaseStreamableFeature< + ListRuleReplicaLockStatesControllerParameters, + ListRuleReplicaLockStatesRequest, + ListRuleReplicaLockStatesResponse, + ListRuleReplicaLockStatesError, + ListRuleReplicaLockStatesViewModel +> { + constructor(appContainer: Container) { + const rucioRuleGateway = appContainer.get(GATEWAYS.RULE); + + const symbols: IOCSymbols = { + CONTROLLER: CONTROLLERS.LIST_RULE_REPLICA_LOCK_STATES, + USECASE_FACTORY: USECASE_FACTORY.LIST_RULE_REPLICA_LOCK_STATES, + INPUT_PORT: INPUT_PORT.LIST_RULE_REPLICA_LOCK_STATES, + }; + const useCaseConstructorArgs = [rucioRuleGateway]; + super( + 'ListRuleReplicaLockStates', + ListRuleReplicaLockStatesController, + ListRuleReplicaLockStatesUseCase, + useCaseConstructorArgs, + ListRuleReplicaLockStatesPresenter, + false, + symbols, + ); + } +} diff --git a/src/lib/infrastructure/ioc/ioc-symbols-controllers.ts b/src/lib/infrastructure/ioc/ioc-symbols-controllers.ts index d6c5fe179..18ebe3e33 100644 --- a/src/lib/infrastructure/ioc/ioc-symbols-controllers.ts +++ b/src/lib/infrastructure/ioc/ioc-symbols-controllers.ts @@ -37,6 +37,7 @@ const CONTROLLERS = { ATTACH_DIDS: Symbol.for('AttachDIDsController'), SET_DID_STATUS: Symbol.for('SetDIDStatusController'), GET_RULE: Symbol.for('GetRuleController'), + LIST_RULE_REPLICA_LOCK_STATES: Symbol.for('ListRuleReplicaLockStatesController'), }; export default CONTROLLERS; diff --git a/src/lib/infrastructure/ioc/ioc-symbols-input-port.ts b/src/lib/infrastructure/ioc/ioc-symbols-input-port.ts index b604617eb..88e948f42 100644 --- a/src/lib/infrastructure/ioc/ioc-symbols-input-port.ts +++ b/src/lib/infrastructure/ioc/ioc-symbols-input-port.ts @@ -36,6 +36,7 @@ const INPUT_PORT = { ATTACH_DIDS: Symbol.for('AttachDIDsInputPort'), SET_DID_STATUS: Symbol.for('SetDIDStatusInputPort'), GET_RULE: Symbol.for('GetRuleInputPort'), + LIST_RULE_REPLICA_LOCK_STATES: Symbol.for('ListRuleReplicaLockStatesInputPort'), }; export default INPUT_PORT; diff --git a/src/lib/infrastructure/ioc/ioc-symbols-usecase-factory.ts b/src/lib/infrastructure/ioc/ioc-symbols-usecase-factory.ts index 6240d2f84..3192ae3da 100644 --- a/src/lib/infrastructure/ioc/ioc-symbols-usecase-factory.ts +++ b/src/lib/infrastructure/ioc/ioc-symbols-usecase-factory.ts @@ -37,6 +37,7 @@ const USECASE_FACTORY = { ATTACH_DIDS: Symbol.for('Factory'), SET_DID_STATUS: Symbol.for('Factory'), GET_RULE: Symbol.for('Factory'), + LIST_RULE_REPLICA_LOCK_STATES: Symbol.for('Factory'), }; export default USECASE_FACTORY; diff --git a/src/lib/infrastructure/presenter/list-rule-replica-lock-states-presenter.ts b/src/lib/infrastructure/presenter/list-rule-replica-lock-states-presenter.ts new file mode 100644 index 000000000..20bee13a9 --- /dev/null +++ b/src/lib/infrastructure/presenter/list-rule-replica-lock-states-presenter.ts @@ -0,0 +1,51 @@ +import { + ListRuleReplicaLockStatesError, + ListRuleReplicaLockStatesResponse, +} from '@/lib/core/usecase-models/list-rule-replica-lock-states-usecase-models'; +import { NextApiResponse } from 'next'; +import { ListRuleReplicaLockStatesViewModel, getEmptyListRuleReplicaLockStatesViewModel } from '@/lib/infrastructure/data/view-model/rule'; +import { BaseStreamingPresenter } from '@/lib/sdk/presenter'; +import { ListRuleReplicaLockStatesOutputPort } from '@/lib/core/port/primary/list-rule-replica-lock-states-ports'; + +export default class ListRuleReplicaLockStatesPresenter + extends BaseStreamingPresenter + implements ListRuleReplicaLockStatesOutputPort +{ + response: NextApiResponse; + + constructor(response: NextApiResponse) { + super(response); + this.response = response; + } + + streamResponseModelToViewModel(responseModel: ListRuleReplicaLockStatesResponse): ListRuleReplicaLockStatesViewModel { + const viewModel: ListRuleReplicaLockStatesViewModel = { + ...responseModel, + }; + return viewModel; + } + + streamErrorModelToViewModel(error: ListRuleReplicaLockStatesError): ListRuleReplicaLockStatesViewModel { + const errorViewModel: ListRuleReplicaLockStatesViewModel = getEmptyListRuleReplicaLockStatesViewModel(); + errorViewModel.status = 'error'; + errorViewModel.message = `${error.name}: ${error.message}`; + return errorViewModel; + } + + /** + * Converts an error model to an error view model. + * @param errorModel The error model to convert. + * @returns The error view model that represents the error model. + */ + convertErrorModelToViewModel(errorModel: ListRuleReplicaLockStatesError): { viewModel: ListRuleReplicaLockStatesViewModel; status: number } { + const viewModel: ListRuleReplicaLockStatesViewModel = getEmptyListRuleReplicaLockStatesViewModel(); + const message = errorModel.message ? errorModel.message.toString() : errorModel.name; + const name = errorModel.name ? errorModel.name : ''; + viewModel.message = message; + const errorCode = errorModel.code ? errorModel.code : 500; + return { + status: errorCode, + viewModel: viewModel, + }; + } +} diff --git a/src/pages/api/feature/list-rule-replica-lock-states.ts b/src/pages/api/feature/list-rule-replica-lock-states.ts new file mode 100644 index 000000000..02724b689 --- /dev/null +++ b/src/pages/api/feature/list-rule-replica-lock-states.ts @@ -0,0 +1,36 @@ +import { SessionUser } from '@/lib/core/entity/auth-models'; + +import { withAuthenticatedSessionRoute } from '@/lib/infrastructure/auth/session-utils'; +import { ListRuleReplicaLockStatesControllerParameters } from '@/lib/infrastructure/controller/list-rule-replica-lock-states-controller'; +import appContainer from '@/lib/infrastructure/ioc/container-config'; +import { ListRuleReplicaLockStatesRequest } from '@/lib/core/usecase-models/list-rule-replica-lock-states-usecase-models'; +import CONTROLLERS from '@/lib/infrastructure/ioc/ioc-symbols-controllers'; +import { BaseController } from '@/lib/sdk/controller'; +import { NextApiRequest, NextApiResponse } from 'next'; + +async function listRuleReplicaLockStates(req: NextApiRequest, res: NextApiResponse, rucioAuthToken: string, sessionUser?: SessionUser) { + if (req.method !== 'GET') { + res.status(405).json({ error: 'Method Not Allowed' }); + return; + } + + const { id } = req.query; + + if (!id || typeof id !== 'string') { + res.status(400).json({ error: 'Rule ID should be specified' }); + return; + } + + const controllerParameters: ListRuleReplicaLockStatesControllerParameters = { + response: res, + rucioAuthToken: rucioAuthToken, + id, + }; + + const controller = appContainer.get>( + CONTROLLERS.LIST_RULE_REPLICA_LOCK_STATES, + ); + await controller.execute(controllerParameters); +} + +export default withAuthenticatedSessionRoute(listRuleReplicaLockStates); diff --git a/test/api/rule/list-rule-replica-lock-states.test.ts b/test/api/rule/list-rule-replica-lock-states.test.ts new file mode 100644 index 000000000..d76d7f4dc --- /dev/null +++ b/test/api/rule/list-rule-replica-lock-states.test.ts @@ -0,0 +1,83 @@ +import appContainer from '@/lib/infrastructure/ioc/container-config'; +import { ListRulesRequest } from '@/lib/core/usecase-models/list-rules-usecase-models'; +import { ListRulesControllerParameters } from '@/lib/infrastructure/controller/list-rules-controller'; +import CONTROLLERS from '@/lib/infrastructure/ioc/ioc-symbols-controllers'; + +import { NextApiResponse } from 'next'; +import { MockHttpStreamableResponseFactory } from 'test/fixtures/http-fixtures'; +import { Readable } from 'stream'; +import MockRucioServerFactory, { MockEndpoint } from '../../fixtures/rucio-server'; +import { BaseController } from '@/lib/sdk/controller'; +import ListRuleReplicaLockStates from '@/pages/api/feature/list-rule-replica-lock-states'; +import { ListRuleReplicaLockStatesRequest } from '@/lib/core/usecase-models/list-rule-replica-lock-states-usecase-models'; +import { ListRuleReplicaLockStatesControllerParameters } from '@/lib/infrastructure/controller/list-rule-replica-lock-states-controller'; + +describe('Feature: ListRules', () => { + beforeEach(() => { + fetchMock.doMock(); + const mockLock = { + scope: 'test', + name: 'file', + rse_id: 'da739c56d6df4a199badc7b1cf53c13c', + rse: 'MOCK', + state: 'REPLICATING', + rule_id: 'c0301e6bce06448e807503e608255130', + }; + + const listLocksMockEndpoint: MockEndpoint = { + url: `${MockRucioServerFactory.RUCIO_HOST}/rules/c0301e6bce06448e807503e608255130/locks`, + method: 'GET', + response: { + status: 200, + headers: { + 'Content-Type': 'application/json', + }, + body: Readable.from([JSON.stringify(mockLock), JSON.stringify(mockLock)].join('\n')), + }, + }; + MockRucioServerFactory.createMockRucioServer(true, [listLocksMockEndpoint]); + }); + + afterEach(() => { + fetchMock.dontMock(); + }); + + it('should return a view model for a valid request to ListRules feature', async () => { + const res = MockHttpStreamableResponseFactory.getMockResponse(); + + const listLocksController = appContainer.get>( + CONTROLLERS.LIST_RULE_REPLICA_LOCK_STATES, + ); + const listRulesControllerParams: ListRuleReplicaLockStatesControllerParameters = { + rucioAuthToken: MockRucioServerFactory.VALID_RUCIO_TOKEN, + id: 'c0301e6bce06448e807503e608255130', + response: res as unknown as NextApiResponse, + }; + + await listLocksController.execute(listRulesControllerParams); + + const receivedData: any[] = []; + const onData = (data: any) => { + receivedData.push(JSON.parse(data)); + }; + + // TODO: decompose + const done = new Promise((resolve, reject) => { + res.on('data', onData); + res.on('end', () => { + res.off('data', onData); + resolve(); + }); + res.on('error', err => { + res.off('data', onData); + reject(err); + }); + }); + + await done; + expect(receivedData.length).toEqual(2); + expect(receivedData[0].status).toEqual('success'); + expect(receivedData[0].name).toEqual('file'); + expect(receivedData[1].rse).toEqual('MOCK'); + }); +}); From b9aec76953d6bd2f1acf6d131c84ec83ff0fafa6 Mon Sep 17 00:00:00 2001 From: MytsV Date: Mon, 11 Nov 2024 19:26:34 +0200 Subject: [PATCH 11/32] Sort DID details into two base columns --- .../(rucio)/did/page/[scope]/[name]/page.tsx | 4 +- .../pages/DID/details/DetailsDID.tsx | 96 +++++++++---------- 2 files changed, 47 insertions(+), 53 deletions(-) diff --git a/src/app/(rucio)/did/page/[scope]/[name]/page.tsx b/src/app/(rucio)/did/page/[scope]/[name]/page.tsx index 7d4e9412a..4a4a590d7 100644 --- a/src/app/(rucio)/did/page/[scope]/[name]/page.tsx +++ b/src/app/(rucio)/did/page/[scope]/[name]/page.tsx @@ -21,7 +21,7 @@ import { } from '@/lib/infrastructure/data/view-model/did'; import { didKeyValuePairsDataQuery, didMetaQueryBase } from '@/app/(rucio)/did/queries'; import { Loading } from '@/component-library/pages/legacy/Helpers/Loading'; -import {DetailsDID} from "@/component-library/pages/DID/details/DetailsDID"; +import { DetailsDID } from '@/component-library/pages/DID/details/DetailsDID'; export default function Page({ params }: { params: { scope: string; name: string } }) { const [didMeta, setDIDMeta] = useState({ status: 'pending' } as DIDMetaViewModel); @@ -120,7 +120,7 @@ export default function Page({ params }: { params: { scope: string; name: string setRequests(); }, []); - return + return ; if (didMeta.status === 'pending') { return ; diff --git a/src/component-library/pages/DID/details/DetailsDID.tsx b/src/component-library/pages/DID/details/DetailsDID.tsx index 881581edc..f15c8a342 100644 --- a/src/component-library/pages/DID/details/DetailsDID.tsx +++ b/src/component-library/pages/DID/details/DetailsDID.tsx @@ -1,27 +1,27 @@ 'use client'; -import {Heading} from '@/component-library/atoms/misc/Heading'; -import {useQuery} from '@tanstack/react-query'; -import {useToast} from '@/lib/infrastructure/hooks/useToast'; -import {BaseViewModelValidator} from '@/component-library/features/utils/BaseViewModelValidator'; -import {KeyValueWrapper} from '@/component-library/features/key-value/KeyValueWrapper'; -import {KeyValueRow} from '@/component-library/features/key-value/KeyValueRow'; +import { Heading } from '@/component-library/atoms/misc/Heading'; +import { useQuery } from '@tanstack/react-query'; +import { useToast } from '@/lib/infrastructure/hooks/useToast'; +import { BaseViewModelValidator } from '@/component-library/features/utils/BaseViewModelValidator'; +import { KeyValueWrapper } from '@/component-library/features/key-value/KeyValueWrapper'; +import { KeyValueRow } from '@/component-library/features/key-value/KeyValueRow'; import Checkbox from '@/component-library/atoms/form/Checkbox'; -import {DIDMetaViewModel} from '@/lib/infrastructure/data/view-model/did'; -import {LoadingSpinner} from '@/component-library/atoms/loading/LoadingSpinner'; -import {DIDTypeBadge} from '@/component-library/features/badges/DID/DIDTypeBadge'; -import {Field} from '@/component-library/atoms/misc/Field'; -import {Divider} from '@/component-library/atoms/misc/Divider'; -import {formatDate, formatFileSize} from '@/component-library/features/utils/text-formatters'; -import {DIDType} from '@/lib/core/entity/rucio'; -import {DIDAvailabilityBadge} from '@/component-library/features/badges/DID/DIDAvailabilityBadge'; -import {CopyableField} from '@/component-library/features/fields/CopyableField'; - -const DetailsDIDMeta = ({meta}: { meta: DIDMetaViewModel }) => { +import { DIDMetaViewModel } from '@/lib/infrastructure/data/view-model/did'; +import { LoadingSpinner } from '@/component-library/atoms/loading/LoadingSpinner'; +import { DIDTypeBadge } from '@/component-library/features/badges/DID/DIDTypeBadge'; +import { Field } from '@/component-library/atoms/misc/Field'; +import { Divider } from '@/component-library/atoms/misc/Divider'; +import { formatDate, formatFileSize } from '@/component-library/features/utils/text-formatters'; +import { DIDType } from '@/lib/core/entity/rucio'; +import { DIDAvailabilityBadge } from '@/component-library/features/badges/DID/DIDAvailabilityBadge'; +import { CopyableField } from '@/component-library/features/fields/CopyableField'; + +const DetailsDIDMeta = ({ meta }: { meta: DIDMetaViewModel }) => { const getFileInformation = () => { return (
- + {meta.bytes && ( {formatFileSize(meta.bytes)} @@ -29,17 +29,17 @@ const DetailsDIDMeta = ({meta}: { meta: DIDMetaViewModel }) => { )} {meta.guid && ( - + )} {meta.adler32 && ( - + )} {meta.md5 && ( - + )}
@@ -51,49 +51,44 @@ const DetailsDIDMeta = ({meta}: { meta: DIDMetaViewModel }) => {
- + {meta.account} - {meta.is_open !== null && ( - - - - )} - - - -
- - - -
{formatDate(meta.created_at)} {formatDate(meta.updated_at)} + + + + {meta.is_open !== null && ( + + + + )}
- +
+ + + - + - + - + - - - - +
@@ -108,12 +103,12 @@ type DetailsDIDProps = { name: string; }; -export const DetailsDID = ({scope, name}: DetailsDIDProps) => { - const {toast} = useToast(); +export const DetailsDID = ({ scope, name }: DetailsDIDProps) => { + const { toast } = useToast(); const validator = new BaseViewModelValidator(toast); const queryMeta = async () => { - const url = '/api/feature/get-did-meta?' + new URLSearchParams({scope, name}); + const url = '/api/feature/get-did-meta?' + new URLSearchParams({ scope, name }); const res = await fetch(url); if (!res.ok) { @@ -124,8 +119,7 @@ export const DetailsDID = ({scope, name}: DetailsDIDProps) => { description: json.message, variant: 'error', }); - } catch (e) { - } + } catch (e) {} throw new Error(res.statusText); } @@ -150,13 +144,13 @@ export const DetailsDID = ({scope, name}: DetailsDIDProps) => { const isLoading = meta === undefined || isMetaFetching; return isLoading ? ( - + ) : (
- +
- +
); }; From fcfc07198f05a94a7bb160c38025af501542bfd9 Mon Sep 17 00:00:00 2001 From: MytsV Date: Mon, 11 Nov 2024 19:43:24 +0200 Subject: [PATCH 12/32] Implement a DID metadata table --- .../DetailsDIDMetadataTable.stories.tsx | 18 ++++++++++ .../DID/details/DetailsDIDMetadataTable.tsx | 35 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/component-library/pages/DID/details/DetailsDIDMetadataTable.stories.tsx create mode 100644 src/component-library/pages/DID/details/DetailsDIDMetadataTable.tsx diff --git a/src/component-library/pages/DID/details/DetailsDIDMetadataTable.stories.tsx b/src/component-library/pages/DID/details/DetailsDIDMetadataTable.stories.tsx new file mode 100644 index 000000000..147828094 --- /dev/null +++ b/src/component-library/pages/DID/details/DetailsDIDMetadataTable.stories.tsx @@ -0,0 +1,18 @@ +import { Meta, StoryFn } from '@storybook/react'; +import { fixtureDIDKeyValuePairsDataViewModel } from '@/test/fixtures/table-fixtures'; +import { DetailsDIDMetadataTable } from '@/component-library/pages/DID/details/DetailsDIDMetadataTable'; + +export default { + title: 'Components/Pages/DID/Details', + component: DetailsDIDMetadataTable, + parameters: { + docs: { disable: true }, + }, +} as Meta; + +const Template: StoryFn = args => ; + +export const Metadata = Template.bind({}); +Metadata.args = { + viewModel: fixtureDIDKeyValuePairsDataViewModel(), +}; diff --git a/src/component-library/pages/DID/details/DetailsDIDMetadataTable.tsx b/src/component-library/pages/DID/details/DetailsDIDMetadataTable.tsx new file mode 100644 index 000000000..c5e28de45 --- /dev/null +++ b/src/component-library/pages/DID/details/DetailsDIDMetadataTable.tsx @@ -0,0 +1,35 @@ +import React, { useRef, useState } from 'react'; +import { AgGridReact } from 'ag-grid-react'; +import { RegularTable } from '@/component-library/features/table/RegularTable/RegularTable'; +import { DIDKeyValuePair } from '@/lib/core/entity/rucio'; +import { DefaultTextFilterParams } from '@/component-library/features/utils/filter-parameters'; +import { AttributeCell } from '@/component-library/features/table/cells/AttributeCell'; +import { DIDKeyValuePairsDataViewModel } from '@/lib/infrastructure/data/view-model/did'; + +type DetailsDIDAttributesTableProps = { + viewModel: DIDKeyValuePairsDataViewModel; +}; + +export const DetailsDIDMetadataTable = (props: DetailsDIDAttributesTableProps) => { + const tableRef = useRef>(null); + + const [columnDefs] = useState([ + { + headerName: 'Key', + field: 'key', + flex: 1, + sortable: true, + filter: true, + filterParams: DefaultTextFilterParams, + }, + { + headerName: 'Value', + field: 'value', + cellRenderer: AttributeCell, + flex: 1, + sortable: false, + }, + ]); + + return ; +}; From f570477b13fc80f5ebcebe18d0983789e08a24f2 Mon Sep 17 00:00:00 2001 From: MytsV Date: Tue, 12 Nov 2024 17:02:57 +0200 Subject: [PATCH 13/32] Implement a tab switching component --- .../features/tabs/TabSwitcher.stories.tsx | 30 +++++++++++++++++ .../features/tabs/TabSwitcher.tsx | 32 +++++++++++++++++++ src/component-library/outputtailwind.css | 9 +++--- 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 src/component-library/features/tabs/TabSwitcher.stories.tsx create mode 100644 src/component-library/features/tabs/TabSwitcher.tsx diff --git a/src/component-library/features/tabs/TabSwitcher.stories.tsx b/src/component-library/features/tabs/TabSwitcher.stories.tsx new file mode 100644 index 000000000..d9d7c32b6 --- /dev/null +++ b/src/component-library/features/tabs/TabSwitcher.stories.tsx @@ -0,0 +1,30 @@ +import { StoryFn, Meta } from '@storybook/react'; +import {TabSwitcher} from "@/component-library/features/tabs/TabSwitcher"; +import {useState} from "react"; + +export default { + title: 'Components/Tabs', + component: TabSwitcher, + parameters: { + docs: { disable: true }, + }, +} as Meta; + +type TemplateArgs = { + tabs: string[] +}; + +const Template: StoryFn = ({tabs}) => { + const [activeIndex, setActiveIndex] = useState(0); + return ; +} + +export const DefaultSwitcher = Template.bind({}); +DefaultSwitcher.args = { + tabs: ['Tab 1', 'Tab 2', 'Tab 3'], +}; + +export const ExtendedSwitcher = Template.bind({}); +ExtendedSwitcher.args = { + tabs: ['Hello', 'World', 'Hi', 'Earth'], +}; \ No newline at end of file diff --git a/src/component-library/features/tabs/TabSwitcher.tsx b/src/component-library/features/tabs/TabSwitcher.tsx new file mode 100644 index 000000000..b0ac92cf1 --- /dev/null +++ b/src/component-library/features/tabs/TabSwitcher.tsx @@ -0,0 +1,32 @@ +import {KeyValueWrapper} from "@/component-library/features/key-value/KeyValueWrapper"; +import {cn} from "@/component-library/utils"; + +type TabSwitcherProps = { + tabs: string[]; + onSwitch: (index: number) => void; + activeIndex: number; +} + +export const TabSwitcher = ({tabs, onSwitch, activeIndex}: TabSwitcherProps) => { + const regularTabClasses = 'cursor-pointer flex flex-1 items-center justify-center py-1 px-2 text-neutral-600 dark:text-neutral-400'; + const activeTabClasses = cn( + 'rounded', + 'text-neutral-900 dark:text-neutral-100 whitespace-nowrap', + 'bg-neutral-200 dark:bg-neutral-700', + 'border border-neutral-900 dark:border-neutral-100 border-opacity-10 dark:border-opacity-10', + ); + + return ( + + {tabs.map((tab, index) => { + const isActive = index === activeIndex; + const tabClasses = cn(regularTabClasses, { [activeTabClasses]: isActive }); + return ( +
onSwitch(index)} key={tab} className={tabClasses}> + {tab} +
+ ); + })} +
+ ); +}; diff --git a/src/component-library/outputtailwind.css b/src/component-library/outputtailwind.css index 4d552a3cb..0f9f994bf 100644 --- a/src/component-library/outputtailwind.css +++ b/src/component-library/outputtailwind.css @@ -1238,6 +1238,11 @@ html { gap: 1rem; } +.gap-x-2 { + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; +} + .gap-y-2 { row-gap: 0.5rem; } @@ -3841,10 +3846,6 @@ html { width: 50rem; } - .lg\:grow { - flex-grow: 1; - } - .lg\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } From 3fd15a8c8f166db6d479bceaeddca596435bdef7 Mon Sep 17 00:00:00 2001 From: MytsV Date: Tue, 12 Nov 2024 18:00:34 +0200 Subject: [PATCH 14/32] Implement tab switching logic for the DID details --- .../features/badges/DID/ReplicaStateBadge.tsx | 29 +++++++ .../features/tabs/TabSwitcher.stories.tsx | 14 ++-- .../features/tabs/TabSwitcher.tsx | 16 ++-- .../pages/DID/details/DetailsDID.tsx | 31 ++++++-- .../DID/details/DetailsDIDAttributes.tsx | 79 +++++++++++++++++++ .../pages/DID/details/DetailsDIDComponent.ts | 6 ++ .../DID/details/DetailsDIDFileReplicas.tsx | 57 +++++++++++++ .../DetailsDIDMetadataTable.stories.tsx | 18 ----- .../DID/details/DetailsDIDMetadataTable.tsx | 35 -------- 9 files changed, 212 insertions(+), 73 deletions(-) create mode 100644 src/component-library/features/badges/DID/ReplicaStateBadge.tsx create mode 100644 src/component-library/pages/DID/details/DetailsDIDAttributes.tsx create mode 100644 src/component-library/pages/DID/details/DetailsDIDComponent.ts create mode 100644 src/component-library/pages/DID/details/DetailsDIDFileReplicas.tsx delete mode 100644 src/component-library/pages/DID/details/DetailsDIDMetadataTable.stories.tsx delete mode 100644 src/component-library/pages/DID/details/DetailsDIDMetadataTable.tsx diff --git a/src/component-library/features/badges/DID/ReplicaStateBadge.tsx b/src/component-library/features/badges/DID/ReplicaStateBadge.tsx new file mode 100644 index 000000000..c5960d632 --- /dev/null +++ b/src/component-library/features/badges/DID/ReplicaStateBadge.tsx @@ -0,0 +1,29 @@ +import { ReplicaState } from '@/lib/core/entity/rucio'; +import React from 'react'; +import { Badge } from '@/component-library/atoms/misc/Badge'; +import { cn } from '@/component-library/utils'; + +const stateString: Record = { + Available: 'Available', + Bad: 'Bad', + Being_Deleted: 'Being Deleted', + Copying: 'Copying', + Temporary_Unavailable: 'Temporary Unavailable', + Unavailable: 'Unavailable', + Unknown: 'Unknown', +}; + +const stateColorClasses: Record = { + Available: 'bg-base-success-500', + Bad: 'bg-base-error-500', + Being_Deleted: 'bg-base-error-300', + Copying: 'bg-base-info-500', + Temporary_Unavailable: 'bg-base-warning-400', + Unavailable: 'bg-neutral-0 dark:bg-neutral-800', + Unknown: 'bg-neutral-0 dark:bg-neutral-800', +}; + +export const ReplicaStateBadge = (props: { value: ReplicaState; className?: string }) => { + const classes = cn(stateColorClasses[props.value], props.className); + return ; +}; diff --git a/src/component-library/features/tabs/TabSwitcher.stories.tsx b/src/component-library/features/tabs/TabSwitcher.stories.tsx index d9d7c32b6..2bc165097 100644 --- a/src/component-library/features/tabs/TabSwitcher.stories.tsx +++ b/src/component-library/features/tabs/TabSwitcher.stories.tsx @@ -1,6 +1,6 @@ import { StoryFn, Meta } from '@storybook/react'; -import {TabSwitcher} from "@/component-library/features/tabs/TabSwitcher"; -import {useState} from "react"; +import { TabSwitcher } from '@/component-library/features/tabs/TabSwitcher'; +import { useState } from 'react'; export default { title: 'Components/Tabs', @@ -11,13 +11,13 @@ export default { } as Meta; type TemplateArgs = { - tabs: string[] + tabs: string[]; }; -const Template: StoryFn = ({tabs}) => { +const Template: StoryFn = ({ tabs }) => { const [activeIndex, setActiveIndex] = useState(0); - return ; -} + return ; +}; export const DefaultSwitcher = Template.bind({}); DefaultSwitcher.args = { @@ -27,4 +27,4 @@ DefaultSwitcher.args = { export const ExtendedSwitcher = Template.bind({}); ExtendedSwitcher.args = { tabs: ['Hello', 'World', 'Hi', 'Earth'], -}; \ No newline at end of file +}; diff --git a/src/component-library/features/tabs/TabSwitcher.tsx b/src/component-library/features/tabs/TabSwitcher.tsx index b0ac92cf1..ed44d94d0 100644 --- a/src/component-library/features/tabs/TabSwitcher.tsx +++ b/src/component-library/features/tabs/TabSwitcher.tsx @@ -1,13 +1,13 @@ -import {KeyValueWrapper} from "@/component-library/features/key-value/KeyValueWrapper"; -import {cn} from "@/component-library/utils"; +import { KeyValueWrapper } from '@/component-library/features/key-value/KeyValueWrapper'; +import { cn } from '@/component-library/utils'; type TabSwitcherProps = { - tabs: string[]; + tabNames: string[]; onSwitch: (index: number) => void; activeIndex: number; -} +}; -export const TabSwitcher = ({tabs, onSwitch, activeIndex}: TabSwitcherProps) => { +export const TabSwitcher = ({ tabNames, onSwitch, activeIndex }: TabSwitcherProps) => { const regularTabClasses = 'cursor-pointer flex flex-1 items-center justify-center py-1 px-2 text-neutral-600 dark:text-neutral-400'; const activeTabClasses = cn( 'rounded', @@ -18,12 +18,12 @@ export const TabSwitcher = ({tabs, onSwitch, activeIndex}: TabSwitcherProps) => return ( - {tabs.map((tab, index) => { + {tabNames.map((name, index) => { const isActive = index === activeIndex; const tabClasses = cn(regularTabClasses, { [activeTabClasses]: isActive }); return ( -
onSwitch(index)} key={tab} className={tabClasses}> - {tab} +
onSwitch(index)} key={name} className={tabClasses}> + {name}
); })} diff --git a/src/component-library/pages/DID/details/DetailsDID.tsx b/src/component-library/pages/DID/details/DetailsDID.tsx index f15c8a342..9f8f5cf44 100644 --- a/src/component-library/pages/DID/details/DetailsDID.tsx +++ b/src/component-library/pages/DID/details/DetailsDID.tsx @@ -16,6 +16,11 @@ import { formatDate, formatFileSize } from '@/component-library/features/utils/t import { DIDType } from '@/lib/core/entity/rucio'; import { DIDAvailabilityBadge } from '@/component-library/features/badges/DID/DIDAvailabilityBadge'; import { CopyableField } from '@/component-library/features/fields/CopyableField'; +import { TabSwitcher } from '@/component-library/features/tabs/TabSwitcher'; +import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; +import { DetailsDIDAttributes } from '@/component-library/pages/DID/details/DetailsDIDAttributes'; +import { DetailsDIDFileReplicas } from '@/component-library/pages/DID/details/DetailsDIDFileReplicas'; +import { useState } from 'react'; const DetailsDIDMeta = ({ meta }: { meta: DIDMetaViewModel }) => { const getFileInformation = () => { @@ -98,15 +103,17 @@ const DetailsDIDMeta = ({ meta }: { meta: DIDMetaViewModel }) => { ); }; -type DetailsDIDProps = { - scope: string; - name: string; -}; - export const DetailsDID = ({ scope, name }: DetailsDIDProps) => { const { toast } = useToast(); const validator = new BaseViewModelValidator(toast); + const tabs: Map = new Map([ + ['Attributes', DetailsDIDAttributes], + ['Replicas', DetailsDIDFileReplicas], + ]); + + const [activeIndex, setActiveIndex] = useState(0); + const queryMeta = async () => { const url = '/api/feature/get-did-meta?' + new URLSearchParams({ scope, name }); @@ -142,6 +149,7 @@ export const DetailsDID = ({ scope, name }: DetailsDIDProps) => { }); const isLoading = meta === undefined || isMetaFetching; + const tabNames = Array.from(tabs.keys()); return isLoading ? ( @@ -151,6 +159,19 @@ export const DetailsDID = ({ scope, name }: DetailsDIDProps) => {
+ + {tabNames.map((tabName, index) => { + const ViewComponent = tabs.get(tabName); + const visibilityClass = index === activeIndex ? 'block' : 'hidden'; + + if (ViewComponent === undefined) return; + + return ( +
+ +
+ ); + })}
); }; diff --git a/src/component-library/pages/DID/details/DetailsDIDAttributes.tsx b/src/component-library/pages/DID/details/DetailsDIDAttributes.tsx new file mode 100644 index 000000000..337c19825 --- /dev/null +++ b/src/component-library/pages/DID/details/DetailsDIDAttributes.tsx @@ -0,0 +1,79 @@ +import React, { useRef, useState } from 'react'; +import { AgGridReact } from 'ag-grid-react'; +import { RegularTable } from '@/component-library/features/table/RegularTable/RegularTable'; +import { DIDKeyValuePair } from '@/lib/core/entity/rucio'; +import { DefaultTextFilterParams } from '@/component-library/features/utils/filter-parameters'; +import { AttributeCell } from '@/component-library/features/table/cells/AttributeCell'; +import { DIDKeyValuePairsDataViewModel } from '@/lib/infrastructure/data/view-model/did'; +import { useQuery } from '@tanstack/react-query'; +import { useToast } from '@/lib/infrastructure/hooks/useToast'; +import { BaseViewModelValidator } from '@/component-library/features/utils/BaseViewModelValidator'; +import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; + +type DetailsDIDAttributesTableProps = { + viewModel: DIDKeyValuePairsDataViewModel; +}; + +export const DetailsDIDAttributesTable = (props: DetailsDIDAttributesTableProps) => { + const tableRef = useRef>(null); + + const [columnDefs] = useState([ + { + headerName: 'Key', + field: 'key', + flex: 1, + sortable: true, + filter: true, + filterParams: DefaultTextFilterParams, + }, + { + headerName: 'Value', + field: 'value', + cellRenderer: AttributeCell, + flex: 1, + sortable: false, + }, + ]); + + return ; +}; + +export const DetailsDIDAttributes: DetailsDIDComponent = ({ scope, name }: DetailsDIDProps) => { + const { toast } = useToast(); + const validator = new BaseViewModelValidator(toast); + + const queryKeyValuePairs = async () => { + const url = '/api/feature/get-did-keyvaluepairs?' + new URLSearchParams({ scope, name }); + + const res = await fetch(url); + if (!res.ok) { + const json = await res.json(); + throw new Error(json.message); + } + + const json = await res.json(); + if (validator.isValid(json)) return json; + + return null; + }; + + const keyValuePairsQueryKey = ['key-value-pairs']; + const { + data: keyValuePairs, + error: keyValuePairsError, + isFetching: areKeyValuePairsFetching, + } = useQuery({ + queryKey: keyValuePairsQueryKey, + queryFn: queryKeyValuePairs, + retry: false, + refetchOnWindowFocus: false, + }); + + const isLoading = keyValuePairs === undefined || areKeyValuePairsFetching; + + if (isLoading) { + return

Loading...

; + } + + return ; +}; diff --git a/src/component-library/pages/DID/details/DetailsDIDComponent.ts b/src/component-library/pages/DID/details/DetailsDIDComponent.ts new file mode 100644 index 000000000..1eb8c8d70 --- /dev/null +++ b/src/component-library/pages/DID/details/DetailsDIDComponent.ts @@ -0,0 +1,6 @@ +export type DetailsDIDProps = { + scope: string; + name: string; +}; + +export type DetailsDIDComponent = (props: DetailsDIDProps) => JSX.Element; diff --git a/src/component-library/pages/DID/details/DetailsDIDFileReplicas.tsx b/src/component-library/pages/DID/details/DetailsDIDFileReplicas.tsx new file mode 100644 index 000000000..5f2a8b2fb --- /dev/null +++ b/src/component-library/pages/DID/details/DetailsDIDFileReplicas.tsx @@ -0,0 +1,57 @@ +import React, { useRef, useState } from 'react'; +import { AgGridReact } from 'ag-grid-react'; +import { UseStreamReader } from '@/lib/infrastructure/hooks/useStreamReader'; +import { StreamedTable } from '@/component-library/features/table/StreamedTable/StreamedTable'; +import { ClickableCell } from '@/component-library/features/table/cells/ClickableCell'; +import { badgeCellClasses, badgeCellWrapperStyle } from '@/component-library/features/table/cells/badge-cell'; +import { buildDiscreteFilterParams } from '@/component-library/features/utils/filter-parameters'; +import { GridReadyEvent } from 'ag-grid-community'; +import { ReplicaState } from '@/lib/core/entity/rucio'; +import { FileReplicaStateViewModel } from '@/lib/infrastructure/data/view-model/did'; +import { ReplicaStateBadge } from '@/component-library/features/badges/DID/ReplicaStateBadge'; +import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; + +type DetailsDIDFileReplicasTableProps = { + streamingHook: UseStreamReader; + onGridReady: (event: GridReadyEvent) => void; +}; + +const ClickableRSE = (props: { value: string }) => { + return {props.value}; +}; + +export const DetailsDIDFileReplicasTable = (props: DetailsDIDFileReplicasTableProps) => { + const tableRef = useRef>(null); + + const [columnDefs] = useState([ + { + headerName: 'RSE', + field: 'rse', + minWidth: 390, + maxWidth: 390, + sortable: false, + cellRenderer: ClickableRSE, + }, + { + headerName: 'State', + field: 'state', + minWidth: 200, + maxWidth: 200, + cellStyle: badgeCellWrapperStyle, + cellRenderer: ReplicaStateBadge, + cellRendererParams: { + className: badgeCellClasses, + }, + filter: true, + sortable: false, + // TODO: fix the string values + filterParams: buildDiscreteFilterParams(Object.values(ReplicaState)), + }, + ]); + + return ; +}; + +export const DetailsDIDFileReplicas: DetailsDIDComponent = ({ scope, name }: DetailsDIDProps) => { + return
Hello!!!
; +}; diff --git a/src/component-library/pages/DID/details/DetailsDIDMetadataTable.stories.tsx b/src/component-library/pages/DID/details/DetailsDIDMetadataTable.stories.tsx deleted file mode 100644 index 147828094..000000000 --- a/src/component-library/pages/DID/details/DetailsDIDMetadataTable.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Meta, StoryFn } from '@storybook/react'; -import { fixtureDIDKeyValuePairsDataViewModel } from '@/test/fixtures/table-fixtures'; -import { DetailsDIDMetadataTable } from '@/component-library/pages/DID/details/DetailsDIDMetadataTable'; - -export default { - title: 'Components/Pages/DID/Details', - component: DetailsDIDMetadataTable, - parameters: { - docs: { disable: true }, - }, -} as Meta; - -const Template: StoryFn = args => ; - -export const Metadata = Template.bind({}); -Metadata.args = { - viewModel: fixtureDIDKeyValuePairsDataViewModel(), -}; diff --git a/src/component-library/pages/DID/details/DetailsDIDMetadataTable.tsx b/src/component-library/pages/DID/details/DetailsDIDMetadataTable.tsx deleted file mode 100644 index c5e28de45..000000000 --- a/src/component-library/pages/DID/details/DetailsDIDMetadataTable.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { useRef, useState } from 'react'; -import { AgGridReact } from 'ag-grid-react'; -import { RegularTable } from '@/component-library/features/table/RegularTable/RegularTable'; -import { DIDKeyValuePair } from '@/lib/core/entity/rucio'; -import { DefaultTextFilterParams } from '@/component-library/features/utils/filter-parameters'; -import { AttributeCell } from '@/component-library/features/table/cells/AttributeCell'; -import { DIDKeyValuePairsDataViewModel } from '@/lib/infrastructure/data/view-model/did'; - -type DetailsDIDAttributesTableProps = { - viewModel: DIDKeyValuePairsDataViewModel; -}; - -export const DetailsDIDMetadataTable = (props: DetailsDIDAttributesTableProps) => { - const tableRef = useRef>(null); - - const [columnDefs] = useState([ - { - headerName: 'Key', - field: 'key', - flex: 1, - sortable: true, - filter: true, - filterParams: DefaultTextFilterParams, - }, - { - headerName: 'Value', - field: 'value', - cellRenderer: AttributeCell, - flex: 1, - sortable: false, - }, - ]); - - return ; -}; From efa80717bdc3f63d46bdce28e00f6903bb6d6aa8 Mon Sep 17 00:00:00 2001 From: MytsV Date: Tue, 12 Nov 2024 18:05:07 +0200 Subject: [PATCH 15/32] Decompose the tab switching for better readability --- .../pages/DID/details/DetailsDID.tsx | 127 +++--------------- .../pages/DID/details/DetailsDIDMeta.tsx | 92 +++++++++++++ 2 files changed, 114 insertions(+), 105 deletions(-) create mode 100644 src/component-library/pages/DID/details/DetailsDIDMeta.tsx diff --git a/src/component-library/pages/DID/details/DetailsDID.tsx b/src/component-library/pages/DID/details/DetailsDID.tsx index 9f8f5cf44..3d6e52633 100644 --- a/src/component-library/pages/DID/details/DetailsDID.tsx +++ b/src/component-library/pages/DID/details/DetailsDID.tsx @@ -4,102 +4,39 @@ import { Heading } from '@/component-library/atoms/misc/Heading'; import { useQuery } from '@tanstack/react-query'; import { useToast } from '@/lib/infrastructure/hooks/useToast'; import { BaseViewModelValidator } from '@/component-library/features/utils/BaseViewModelValidator'; -import { KeyValueWrapper } from '@/component-library/features/key-value/KeyValueWrapper'; -import { KeyValueRow } from '@/component-library/features/key-value/KeyValueRow'; -import Checkbox from '@/component-library/atoms/form/Checkbox'; import { DIDMetaViewModel } from '@/lib/infrastructure/data/view-model/did'; import { LoadingSpinner } from '@/component-library/atoms/loading/LoadingSpinner'; -import { DIDTypeBadge } from '@/component-library/features/badges/DID/DIDTypeBadge'; -import { Field } from '@/component-library/atoms/misc/Field'; -import { Divider } from '@/component-library/atoms/misc/Divider'; -import { formatDate, formatFileSize } from '@/component-library/features/utils/text-formatters'; -import { DIDType } from '@/lib/core/entity/rucio'; -import { DIDAvailabilityBadge } from '@/component-library/features/badges/DID/DIDAvailabilityBadge'; -import { CopyableField } from '@/component-library/features/fields/CopyableField'; import { TabSwitcher } from '@/component-library/features/tabs/TabSwitcher'; import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; import { DetailsDIDAttributes } from '@/component-library/pages/DID/details/DetailsDIDAttributes'; import { DetailsDIDFileReplicas } from '@/component-library/pages/DID/details/DetailsDIDFileReplicas'; import { useState } from 'react'; +import { DetailsDIDMeta } from '@/component-library/pages/DID/details/DetailsDIDMeta'; -const DetailsDIDMeta = ({ meta }: { meta: DIDMetaViewModel }) => { - const getFileInformation = () => { - return ( -
- - {meta.bytes && ( - - {formatFileSize(meta.bytes)} - - )} - {meta.guid && ( - - - - )} - {meta.adler32 && ( - - - - )} - {meta.md5 && ( - - - - )} -
- ); - }; +export const DetailsDIDTables = ({ scope, name }: DetailsDIDProps) => { + const tabs: Map = new Map([ + ['Attributes', DetailsDIDAttributes], + ['Replicas', DetailsDIDFileReplicas], + ]); + const tabNames = Array.from(tabs.keys()); + const [activeIndex, setActiveIndex] = useState(0); return ( - -
-
- - - - - {meta.account} - - - {formatDate(meta.created_at)} - - - {formatDate(meta.updated_at)} - - - - - {meta.is_open !== null && ( - - - - )} -
- - + <> + + {tabNames.map((tabName, index) => { + const ViewComponent = tabs.get(tabName); + const visibilityClass = index === activeIndex ? 'block' : 'hidden'; -
- - - - - - - - - - - - - - - -
-
+ if (ViewComponent === undefined) return; - {meta.did_type === DIDType.FILE && getFileInformation()} -
+ return ( +
+ +
+ ); + })} + ); }; @@ -107,13 +44,6 @@ export const DetailsDID = ({ scope, name }: DetailsDIDProps) => { const { toast } = useToast(); const validator = new BaseViewModelValidator(toast); - const tabs: Map = new Map([ - ['Attributes', DetailsDIDAttributes], - ['Replicas', DetailsDIDFileReplicas], - ]); - - const [activeIndex, setActiveIndex] = useState(0); - const queryMeta = async () => { const url = '/api/feature/get-did-meta?' + new URLSearchParams({ scope, name }); @@ -149,7 +79,6 @@ export const DetailsDID = ({ scope, name }: DetailsDIDProps) => { }); const isLoading = meta === undefined || isMetaFetching; - const tabNames = Array.from(tabs.keys()); return isLoading ? ( @@ -159,19 +88,7 @@ export const DetailsDID = ({ scope, name }: DetailsDIDProps) => {
- - {tabNames.map((tabName, index) => { - const ViewComponent = tabs.get(tabName); - const visibilityClass = index === activeIndex ? 'block' : 'hidden'; - - if (ViewComponent === undefined) return; - - return ( -
- -
- ); - })} + ); }; diff --git a/src/component-library/pages/DID/details/DetailsDIDMeta.tsx b/src/component-library/pages/DID/details/DetailsDIDMeta.tsx new file mode 100644 index 000000000..8d8dc5ebf --- /dev/null +++ b/src/component-library/pages/DID/details/DetailsDIDMeta.tsx @@ -0,0 +1,92 @@ +import { DIDMetaViewModel } from '@/lib/infrastructure/data/view-model/did'; +import { Divider } from '@/component-library/atoms/misc/Divider'; +import { KeyValueRow } from '@/component-library/features/key-value/KeyValueRow'; +import { Field } from '@/component-library/atoms/misc/Field'; +import { formatDate, formatFileSize } from '@/component-library/features/utils/text-formatters'; +import { CopyableField } from '@/component-library/features/fields/CopyableField'; +import { KeyValueWrapper } from '@/component-library/features/key-value/KeyValueWrapper'; +import { DIDTypeBadge } from '@/component-library/features/badges/DID/DIDTypeBadge'; +import { DIDAvailabilityBadge } from '@/component-library/features/badges/DID/DIDAvailabilityBadge'; +import Checkbox from '@/component-library/atoms/form/Checkbox'; +import { DIDType } from '@/lib/core/entity/rucio'; + +export const DetailsDIDMeta = ({ meta }: { meta: DIDMetaViewModel }) => { + const getFileInformation = () => { + return ( +
+ + {meta.bytes && ( + + {formatFileSize(meta.bytes)} + + )} + {meta.guid && ( + + + + )} + {meta.adler32 && ( + + + + )} + {meta.md5 && ( + + + + )} +
+ ); + }; + + return ( + +
+
+ + + + + {meta.account} + + + {formatDate(meta.created_at)} + + + {formatDate(meta.updated_at)} + + + + + {meta.is_open !== null && ( + + + + )} +
+ + + +
+ + + + + + + + + + + + + + + +
+
+ + {meta.did_type === DIDType.FILE && getFileInformation()} +
+ ); +}; From c9335cc95b6f20de463331c4a22ede34792923a5 Mon Sep 17 00:00:00 2001 From: MytsV Date: Tue, 12 Nov 2024 19:45:16 +0200 Subject: [PATCH 16/32] Handle differing tabs for different DID types --- .../pages/DID/details/DetailsDID.tsx | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/component-library/pages/DID/details/DetailsDID.tsx b/src/component-library/pages/DID/details/DetailsDID.tsx index 3d6e52633..d57badb2f 100644 --- a/src/component-library/pages/DID/details/DetailsDID.tsx +++ b/src/component-library/pages/DID/details/DetailsDID.tsx @@ -12,20 +12,41 @@ import { DetailsDIDAttributes } from '@/component-library/pages/DID/details/Deta import { DetailsDIDFileReplicas } from '@/component-library/pages/DID/details/DetailsDIDFileReplicas'; import { useState } from 'react'; import { DetailsDIDMeta } from '@/component-library/pages/DID/details/DetailsDIDMeta'; +import { DIDType } from '@/lib/core/entity/rucio'; -export const DetailsDIDTables = ({ scope, name }: DetailsDIDProps) => { - const tabs: Map = new Map([ +type DetailsDIDTablesProps = { + scope: string; + name: string; + type: DIDType; +}; + +export const DetailsDIDTables = ({ scope, name, type }: DetailsDIDTablesProps) => { + const allTabs: Map = new Map([ ['Attributes', DetailsDIDAttributes], ['Replicas', DetailsDIDFileReplicas], ]); - const tabNames = Array.from(tabs.keys()); + + const tabsByType: Record = { + File: ['Replicas', 'Parents', 'Attributes'], + Dataset: ['Rules', 'Replicas', 'Content Replicas', 'Attributes'], + Container: ['Contents', 'Rules', 'Attributes'], + All: [], + Collection: [], + Derived: [], + Unknown: [], + }; + + const tabNames = tabsByType[type]; const [activeIndex, setActiveIndex] = useState(0); + // TODO: display an error + if (tabNames.length === 0) return <>; + return ( <> {tabNames.map((tabName, index) => { - const ViewComponent = tabs.get(tabName); + const ViewComponent = allTabs.get(tabName); const visibilityClass = index === activeIndex ? 'block' : 'hidden'; if (ViewComponent === undefined) return; @@ -88,7 +109,7 @@ export const DetailsDID = ({ scope, name }: DetailsDIDProps) => { - + ); }; From 84e3082e2e91063803402deb24ebd6c6b2041059 Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 13 Nov 2024 12:29:37 +0200 Subject: [PATCH 17/32] Implement DID file replicas loading --- .../DID/details/DetailsDIDFileReplicas.tsx | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/component-library/pages/DID/details/DetailsDIDFileReplicas.tsx b/src/component-library/pages/DID/details/DetailsDIDFileReplicas.tsx index 5f2a8b2fb..b2c6e09a5 100644 --- a/src/component-library/pages/DID/details/DetailsDIDFileReplicas.tsx +++ b/src/component-library/pages/DID/details/DetailsDIDFileReplicas.tsx @@ -1,15 +1,16 @@ -import React, { useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { AgGridReact } from 'ag-grid-react'; import { UseStreamReader } from '@/lib/infrastructure/hooks/useStreamReader'; import { StreamedTable } from '@/component-library/features/table/StreamedTable/StreamedTable'; import { ClickableCell } from '@/component-library/features/table/cells/ClickableCell'; import { badgeCellClasses, badgeCellWrapperStyle } from '@/component-library/features/table/cells/badge-cell'; -import { buildDiscreteFilterParams } from '@/component-library/features/utils/filter-parameters'; +import { buildDiscreteFilterParams, DefaultTextFilterParams } from '@/component-library/features/utils/filter-parameters'; import { GridReadyEvent } from 'ag-grid-community'; import { ReplicaState } from '@/lib/core/entity/rucio'; import { FileReplicaStateViewModel } from '@/lib/infrastructure/data/view-model/did'; import { ReplicaStateBadge } from '@/component-library/features/badges/DID/ReplicaStateBadge'; import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; +import useTableStreaming from '@/lib/infrastructure/hooks/useTableStreaming'; type DetailsDIDFileReplicasTableProps = { streamingHook: UseStreamReader; @@ -27,16 +28,16 @@ export const DetailsDIDFileReplicasTable = (props: DetailsDIDFileReplicasTablePr { headerName: 'RSE', field: 'rse', - minWidth: 390, - maxWidth: 390, + flex: 1, sortable: false, cellRenderer: ClickableRSE, + filter: true, + filterParams: DefaultTextFilterParams, }, { headerName: 'State', field: 'state', - minWidth: 200, - maxWidth: 200, + flex: 1, cellStyle: badgeCellWrapperStyle, cellRenderer: ReplicaStateBadge, cellRendererParams: { @@ -53,5 +54,14 @@ export const DetailsDIDFileReplicasTable = (props: DetailsDIDFileReplicasTablePr }; export const DetailsDIDFileReplicas: DetailsDIDComponent = ({ scope, name }: DetailsDIDProps) => { - return
Hello!!!
; + const { gridApi, onGridReady, streamingHook, startStreaming, stopStreaming } = useTableStreaming(); + + useEffect(() => { + if (gridApi) { + const url = '/api/feature/list-file-replicas?' + new URLSearchParams({ scope, name }); + startStreaming(url); + } + }, [gridApi]); + + return ; }; From dbd5e886cdd114f1aeb6fb99d4ca633cc92d9e4f Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 13 Nov 2024 13:01:30 +0200 Subject: [PATCH 18/32] Implement DID rules loading --- src/component-library/outputtailwind.css | 4 + .../pages/DID/details/DetailsDID.tsx | 9 +- .../pages/DID/details/DetailsDIDRules.tsx | 123 ++++++++++++++++++ 3 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 src/component-library/pages/DID/details/DetailsDIDRules.tsx diff --git a/src/component-library/outputtailwind.css b/src/component-library/outputtailwind.css index 0f9f994bf..1138a49e0 100644 --- a/src/component-library/outputtailwind.css +++ b/src/component-library/outputtailwind.css @@ -941,6 +941,10 @@ html { min-height: 300px; } +.min-h-\[450px\] { + min-height: 450px; +} + .min-h-screen { min-height: 100vh; } diff --git a/src/component-library/pages/DID/details/DetailsDID.tsx b/src/component-library/pages/DID/details/DetailsDID.tsx index d57badb2f..b37c73e3d 100644 --- a/src/component-library/pages/DID/details/DetailsDID.tsx +++ b/src/component-library/pages/DID/details/DetailsDID.tsx @@ -13,6 +13,8 @@ import { DetailsDIDFileReplicas } from '@/component-library/pages/DID/details/De import { useState } from 'react'; import { DetailsDIDMeta } from '@/component-library/pages/DID/details/DetailsDIDMeta'; import { DIDType } from '@/lib/core/entity/rucio'; +import { DetailsDIDRules } from '@/component-library/pages/DID/details/DetailsDIDRules'; +import { cn } from '@/component-library/utils'; type DetailsDIDTablesProps = { scope: string; @@ -24,6 +26,7 @@ export const DetailsDIDTables = ({ scope, name, type }: DetailsDIDTablesProps) = const allTabs: Map = new Map([ ['Attributes', DetailsDIDAttributes], ['Replicas', DetailsDIDFileReplicas], + ['Rules', DetailsDIDRules], ]); const tabsByType: Record = { @@ -47,12 +50,14 @@ export const DetailsDIDTables = ({ scope, name, type }: DetailsDIDTablesProps) = {tabNames.map((tabName, index) => { const ViewComponent = allTabs.get(tabName); - const visibilityClass = index === activeIndex ? 'block' : 'hidden'; + const visibilityClass = index === activeIndex ? 'flex' : 'hidden'; if (ViewComponent === undefined) return; + const viewClasses = cn('flex-col grow min-h-[450px]', visibilityClass); + return ( -
+
); diff --git a/src/component-library/pages/DID/details/DetailsDIDRules.tsx b/src/component-library/pages/DID/details/DetailsDIDRules.tsx new file mode 100644 index 000000000..1b7975862 --- /dev/null +++ b/src/component-library/pages/DID/details/DetailsDIDRules.tsx @@ -0,0 +1,123 @@ +import { UseStreamReader } from '@/lib/infrastructure/hooks/useStreamReader'; +import { DIDRulesViewModel } from '@/lib/infrastructure/data/view-model/did'; +import { GridReadyEvent, ValueFormatterParams } from 'ag-grid-community'; +import { ClickableCell } from '@/component-library/features/table/cells/ClickableCell'; +import React, { useEffect, useRef, useState } from 'react'; +import { AgGridReact } from 'ag-grid-react'; +import { buildDiscreteFilterParams, DefaultDateFilterParams, DefaultTextFilterParams } from '@/component-library/features/utils/filter-parameters'; +import { badgeCellClasses, badgeCellWrapperStyle } from '@/component-library/features/table/cells/badge-cell'; +import { RuleState } from '@/lib/core/entity/rucio'; +import { StreamedTable } from '@/component-library/features/table/StreamedTable/StreamedTable'; +import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; +import useTableStreaming from '@/lib/infrastructure/hooks/useTableStreaming'; +import { formatDate, formatSeconds } from '@/component-library/features/utils/text-formatters'; +import { RuleStateBadge } from '@/component-library/features/badges/Rule/RuleStateBadge'; + +type DetailsDIDRulesTableProps = { + streamingHook: UseStreamReader; + onGridReady: (event: GridReadyEvent) => void; +}; + +const ClickableRSE = (props: { value: string }) => { + return {props.value}; +}; + +const ClickableId = (props: { value: string }) => { + return {props.value}; +}; + +export const DetailsDIDRulesTable = (props: DetailsDIDRulesTableProps) => { + const tableRef = useRef>(null); + + const [columnDefs] = useState([ + { + headerName: 'ID', + field: 'id', + minWidth: 390, + maxWidth: 390, + sortable: false, + cellRenderer: ClickableId, + }, + { + headerName: 'RSE', + field: 'rse_expression', + minWidth: 150, + flex: 1, + filter: true, + filterParams: DefaultTextFilterParams, + cellRenderer: ClickableRSE, + }, + { + headerName: 'Created At', + field: 'created_at', + minWidth: 150, + maxWidth: 150, + valueFormatter: (params: ValueFormatterParams) => { + return formatDate(params.value); + }, + filter: 'agDateColumnFilter', + filterParams: DefaultDateFilterParams, + }, + { + headerName: 'Remaining', + field: 'remaining_lifetime', + minWidth: 125, + maxWidth: 125, + valueFormatter: (params: ValueFormatterParams) => { + return formatSeconds(params.value); + }, + }, + { + headerName: 'State', + field: 'state', + minWidth: 200, + maxWidth: 200, + cellStyle: badgeCellWrapperStyle, + cellRenderer: RuleStateBadge, + cellRendererParams: { + className: badgeCellClasses, + }, + filter: true, + sortable: false, + // TODO: fix the string values + filterParams: buildDiscreteFilterParams(Object.values(RuleState)), + }, + // TODO: minified header with a tooltip + { + headerName: 'OK', + field: 'locks_ok_cnt', + minWidth: 75, + maxWidth: 75, + sortable: false, + }, + { + headerName: 'Replicating', + field: 'locks_replicating_cnt', + minWidth: 135, + maxWidth: 135, + sortable: false, + }, + { + headerName: 'Stuck', + field: 'locks_stuck_cnt', + minWidth: 90, + maxWidth: 90, + sortable: false, + }, + ]); + + return ; +}; + +export const DetailsDIDRules: DetailsDIDComponent = ({ scope, name }: DetailsDIDProps) => { + const { gridApi, onGridReady, streamingHook, startStreaming, stopStreaming } = useTableStreaming(); + + useEffect(() => { + if (gridApi) { + const url = '/api/feature/list-did-rules?' + new URLSearchParams({ scope, name }); + startStreaming(url); + } + }, [gridApi]); + + return ; +}; From 2d476c686d31a44b923400ba48770e040e2caee8 Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 13 Nov 2024 13:40:39 +0200 Subject: [PATCH 19/32] Implement DID parents and contents loading --- .../pages/DID/details/DetailsDID.tsx | 4 ++ .../pages/DID/details/DetailsDIDContents.tsx | 18 +++++++ .../pages/DID/details/DetailsDIDParents.tsx | 18 +++++++ .../DID/details/DetailsDIDSimpleTable.tsx | 54 +++++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 src/component-library/pages/DID/details/DetailsDIDContents.tsx create mode 100644 src/component-library/pages/DID/details/DetailsDIDParents.tsx create mode 100644 src/component-library/pages/DID/details/DetailsDIDSimpleTable.tsx diff --git a/src/component-library/pages/DID/details/DetailsDID.tsx b/src/component-library/pages/DID/details/DetailsDID.tsx index b37c73e3d..f675b1858 100644 --- a/src/component-library/pages/DID/details/DetailsDID.tsx +++ b/src/component-library/pages/DID/details/DetailsDID.tsx @@ -15,6 +15,8 @@ import { DetailsDIDMeta } from '@/component-library/pages/DID/details/DetailsDID import { DIDType } from '@/lib/core/entity/rucio'; import { DetailsDIDRules } from '@/component-library/pages/DID/details/DetailsDIDRules'; import { cn } from '@/component-library/utils'; +import { DetailsDIDParents } from '@/component-library/pages/DID/details/DetailsDIDParents'; +import { DetailsDIDContents } from '@/component-library/pages/DID/details/DetailsDIDContents'; type DetailsDIDTablesProps = { scope: string; @@ -27,6 +29,8 @@ export const DetailsDIDTables = ({ scope, name, type }: DetailsDIDTablesProps) = ['Attributes', DetailsDIDAttributes], ['Replicas', DetailsDIDFileReplicas], ['Rules', DetailsDIDRules], + ['Parents', DetailsDIDParents], + ['Contents', DetailsDIDContents], ]); const tabsByType: Record = { diff --git a/src/component-library/pages/DID/details/DetailsDIDContents.tsx b/src/component-library/pages/DID/details/DetailsDIDContents.tsx new file mode 100644 index 000000000..86d078236 --- /dev/null +++ b/src/component-library/pages/DID/details/DetailsDIDContents.tsx @@ -0,0 +1,18 @@ +import { DIDViewModel } from '@/lib/infrastructure/data/view-model/did'; +import React, { useEffect } from 'react'; +import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; +import useTableStreaming from '@/lib/infrastructure/hooks/useTableStreaming'; +import { DetailsDIDSimpleTable } from '@/component-library/pages/DID/details/DetailsDIDSimpleTable'; + +export const DetailsDIDContents: DetailsDIDComponent = ({ scope, name }: DetailsDIDProps) => { + const { gridApi, onGridReady, streamingHook, startStreaming, stopStreaming } = useTableStreaming(); + + useEffect(() => { + if (gridApi) { + const url = '/api/feature/list-did-contents?' + new URLSearchParams({ scope, name }); + startStreaming(url); + } + }, [gridApi]); + + return ; +}; diff --git a/src/component-library/pages/DID/details/DetailsDIDParents.tsx b/src/component-library/pages/DID/details/DetailsDIDParents.tsx new file mode 100644 index 000000000..130bf74fd --- /dev/null +++ b/src/component-library/pages/DID/details/DetailsDIDParents.tsx @@ -0,0 +1,18 @@ +import { DIDViewModel } from '@/lib/infrastructure/data/view-model/did'; +import React, { useEffect } from 'react'; +import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; +import useTableStreaming from '@/lib/infrastructure/hooks/useTableStreaming'; +import { DetailsDIDSimpleTable } from '@/component-library/pages/DID/details/DetailsDIDSimpleTable'; + +export const DetailsDIDParents: DetailsDIDComponent = ({ scope, name }: DetailsDIDProps) => { + const { gridApi, onGridReady, streamingHook, startStreaming, stopStreaming } = useTableStreaming(); + + useEffect(() => { + if (gridApi) { + const url = '/api/feature/list-did-parents?' + new URLSearchParams({ scope, name }); + startStreaming(url); + } + }, [gridApi]); + + return ; +}; diff --git a/src/component-library/pages/DID/details/DetailsDIDSimpleTable.tsx b/src/component-library/pages/DID/details/DetailsDIDSimpleTable.tsx new file mode 100644 index 000000000..12796c663 --- /dev/null +++ b/src/component-library/pages/DID/details/DetailsDIDSimpleTable.tsx @@ -0,0 +1,54 @@ +import { UseStreamReader } from '@/lib/infrastructure/hooks/useStreamReader'; +import { DIDViewModel } from '@/lib/infrastructure/data/view-model/did'; +import { GridReadyEvent, ValueGetterParams } from 'ag-grid-community'; +import { ClickableCell } from '@/component-library/features/table/cells/ClickableCell'; +import React, { useRef, useState } from 'react'; +import { AgGridReact } from 'ag-grid-react'; +import { ListDIDsViewModel } from '@/lib/infrastructure/data/view-model/list-did'; +import { DIDTypeBadge } from '@/component-library/features/badges/DID/DIDTypeBadge'; +import { badgeCellClasses, badgeCellWrapperStyle } from '@/component-library/features/table/cells/badge-cell'; +import { StreamedTable } from '@/component-library/features/table/StreamedTable/StreamedTable'; + +type DetailsDIDSimpleTableProps = { + streamingHook: UseStreamReader; + onGridReady: (event: GridReadyEvent) => void; +}; + +const ClickableDID = (props: { value: string[] }) => { + console.log(props.value); + const [scope, name] = props.value; + return ( + + {scope}:{name} + + ); +}; + +export const DetailsDIDSimpleTable = (props: DetailsDIDSimpleTableProps) => { + const tableRef = useRef>(null); + + const [columnDefs] = useState([ + { + headerName: 'Identifier', + flex: 1, + valueGetter: (params: ValueGetterParams) => [params.data?.scope, params.data?.name], + cellRenderer: ClickableDID, + minWidth: 250, + sortable: false, + }, + { + headerName: 'Type', + field: 'did_type', + cellRenderer: DIDTypeBadge, + minWidth: 180, + maxWidth: 180, + cellStyle: badgeCellWrapperStyle, + cellRendererParams: { + className: badgeCellClasses, + }, + sortable: false, + }, + ]); + + return ; +}; From 19863118afaec3774e203d1de006dd7a83e5a3ab Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 13 Nov 2024 15:16:54 +0200 Subject: [PATCH 20/32] Refactor the details DID table views --- .../pages/DID/details/DetailsDID.tsx | 14 +++++++------- .../pages/DID/details/DetailsDIDComponent.ts | 6 ------ .../details/{ => tables}/DetailsDIDSimpleTable.tsx | 0 .../details/{ => views}/DetailsDIDAttributes.tsx | 4 ++-- .../DID/details/{ => views}/DetailsDIDContents.tsx | 6 +++--- .../details/{ => views}/DetailsDIDFileReplicas.tsx | 4 ++-- .../DID/details/{ => views}/DetailsDIDParents.tsx | 6 +++--- .../DID/details/{ => views}/DetailsDIDRules.tsx | 4 ++-- .../pages/DID/details/views/DetailsDIDView.ts | 6 ++++++ 9 files changed, 25 insertions(+), 25 deletions(-) delete mode 100644 src/component-library/pages/DID/details/DetailsDIDComponent.ts rename src/component-library/pages/DID/details/{ => tables}/DetailsDIDSimpleTable.tsx (100%) rename src/component-library/pages/DID/details/{ => views}/DetailsDIDAttributes.tsx (92%) rename src/component-library/pages/DID/details/{ => views}/DetailsDIDContents.tsx (73%) rename src/component-library/pages/DID/details/{ => views}/DetailsDIDFileReplicas.tsx (92%) rename src/component-library/pages/DID/details/{ => views}/DetailsDIDParents.tsx (73%) rename src/component-library/pages/DID/details/{ => views}/DetailsDIDRules.tsx (95%) create mode 100644 src/component-library/pages/DID/details/views/DetailsDIDView.ts diff --git a/src/component-library/pages/DID/details/DetailsDID.tsx b/src/component-library/pages/DID/details/DetailsDID.tsx index f675b1858..ff1069299 100644 --- a/src/component-library/pages/DID/details/DetailsDID.tsx +++ b/src/component-library/pages/DID/details/DetailsDID.tsx @@ -7,16 +7,16 @@ import { BaseViewModelValidator } from '@/component-library/features/utils/BaseV import { DIDMetaViewModel } from '@/lib/infrastructure/data/view-model/did'; import { LoadingSpinner } from '@/component-library/atoms/loading/LoadingSpinner'; import { TabSwitcher } from '@/component-library/features/tabs/TabSwitcher'; -import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; -import { DetailsDIDAttributes } from '@/component-library/pages/DID/details/DetailsDIDAttributes'; -import { DetailsDIDFileReplicas } from '@/component-library/pages/DID/details/DetailsDIDFileReplicas'; +import { DetailsDIDView, DetailsDIDProps } from '@/component-library/pages/DID/details/views/DetailsDIDView'; +import { DetailsDIDAttributes } from '@/component-library/pages/DID/details/views/DetailsDIDAttributes'; +import { DetailsDIDFileReplicas } from '@/component-library/pages/DID/details/views/DetailsDIDFileReplicas'; import { useState } from 'react'; import { DetailsDIDMeta } from '@/component-library/pages/DID/details/DetailsDIDMeta'; import { DIDType } from '@/lib/core/entity/rucio'; -import { DetailsDIDRules } from '@/component-library/pages/DID/details/DetailsDIDRules'; +import { DetailsDIDRules } from '@/component-library/pages/DID/details/views/DetailsDIDRules'; import { cn } from '@/component-library/utils'; -import { DetailsDIDParents } from '@/component-library/pages/DID/details/DetailsDIDParents'; -import { DetailsDIDContents } from '@/component-library/pages/DID/details/DetailsDIDContents'; +import { DetailsDIDParents } from '@/component-library/pages/DID/details/views/DetailsDIDParents'; +import { DetailsDIDContents } from '@/component-library/pages/DID/details/views/DetailsDIDContents'; type DetailsDIDTablesProps = { scope: string; @@ -25,7 +25,7 @@ type DetailsDIDTablesProps = { }; export const DetailsDIDTables = ({ scope, name, type }: DetailsDIDTablesProps) => { - const allTabs: Map = new Map([ + const allTabs: Map = new Map([ ['Attributes', DetailsDIDAttributes], ['Replicas', DetailsDIDFileReplicas], ['Rules', DetailsDIDRules], diff --git a/src/component-library/pages/DID/details/DetailsDIDComponent.ts b/src/component-library/pages/DID/details/DetailsDIDComponent.ts deleted file mode 100644 index 1eb8c8d70..000000000 --- a/src/component-library/pages/DID/details/DetailsDIDComponent.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type DetailsDIDProps = { - scope: string; - name: string; -}; - -export type DetailsDIDComponent = (props: DetailsDIDProps) => JSX.Element; diff --git a/src/component-library/pages/DID/details/DetailsDIDSimpleTable.tsx b/src/component-library/pages/DID/details/tables/DetailsDIDSimpleTable.tsx similarity index 100% rename from src/component-library/pages/DID/details/DetailsDIDSimpleTable.tsx rename to src/component-library/pages/DID/details/tables/DetailsDIDSimpleTable.tsx diff --git a/src/component-library/pages/DID/details/DetailsDIDAttributes.tsx b/src/component-library/pages/DID/details/views/DetailsDIDAttributes.tsx similarity index 92% rename from src/component-library/pages/DID/details/DetailsDIDAttributes.tsx rename to src/component-library/pages/DID/details/views/DetailsDIDAttributes.tsx index 337c19825..a5ccb42fd 100644 --- a/src/component-library/pages/DID/details/DetailsDIDAttributes.tsx +++ b/src/component-library/pages/DID/details/views/DetailsDIDAttributes.tsx @@ -8,7 +8,7 @@ import { DIDKeyValuePairsDataViewModel } from '@/lib/infrastructure/data/view-mo import { useQuery } from '@tanstack/react-query'; import { useToast } from '@/lib/infrastructure/hooks/useToast'; import { BaseViewModelValidator } from '@/component-library/features/utils/BaseViewModelValidator'; -import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; +import { DetailsDIDView, DetailsDIDProps } from '@/component-library/pages/DID/details/views/DetailsDIDView'; type DetailsDIDAttributesTableProps = { viewModel: DIDKeyValuePairsDataViewModel; @@ -38,7 +38,7 @@ export const DetailsDIDAttributesTable = (props: DetailsDIDAttributesTableProps) return ; }; -export const DetailsDIDAttributes: DetailsDIDComponent = ({ scope, name }: DetailsDIDProps) => { +export const DetailsDIDAttributes: DetailsDIDView = ({ scope, name }: DetailsDIDProps) => { const { toast } = useToast(); const validator = new BaseViewModelValidator(toast); diff --git a/src/component-library/pages/DID/details/DetailsDIDContents.tsx b/src/component-library/pages/DID/details/views/DetailsDIDContents.tsx similarity index 73% rename from src/component-library/pages/DID/details/DetailsDIDContents.tsx rename to src/component-library/pages/DID/details/views/DetailsDIDContents.tsx index 86d078236..1984b5bf5 100644 --- a/src/component-library/pages/DID/details/DetailsDIDContents.tsx +++ b/src/component-library/pages/DID/details/views/DetailsDIDContents.tsx @@ -1,10 +1,10 @@ import { DIDViewModel } from '@/lib/infrastructure/data/view-model/did'; import React, { useEffect } from 'react'; -import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; +import { DetailsDIDView, DetailsDIDProps } from '@/component-library/pages/DID/details/views/DetailsDIDView'; import useTableStreaming from '@/lib/infrastructure/hooks/useTableStreaming'; -import { DetailsDIDSimpleTable } from '@/component-library/pages/DID/details/DetailsDIDSimpleTable'; +import { DetailsDIDSimpleTable } from '@/component-library/pages/DID/details/tables/DetailsDIDSimpleTable'; -export const DetailsDIDContents: DetailsDIDComponent = ({ scope, name }: DetailsDIDProps) => { +export const DetailsDIDContents: DetailsDIDView = ({ scope, name }: DetailsDIDProps) => { const { gridApi, onGridReady, streamingHook, startStreaming, stopStreaming } = useTableStreaming(); useEffect(() => { diff --git a/src/component-library/pages/DID/details/DetailsDIDFileReplicas.tsx b/src/component-library/pages/DID/details/views/DetailsDIDFileReplicas.tsx similarity index 92% rename from src/component-library/pages/DID/details/DetailsDIDFileReplicas.tsx rename to src/component-library/pages/DID/details/views/DetailsDIDFileReplicas.tsx index b2c6e09a5..e92f62e05 100644 --- a/src/component-library/pages/DID/details/DetailsDIDFileReplicas.tsx +++ b/src/component-library/pages/DID/details/views/DetailsDIDFileReplicas.tsx @@ -9,7 +9,7 @@ import { GridReadyEvent } from 'ag-grid-community'; import { ReplicaState } from '@/lib/core/entity/rucio'; import { FileReplicaStateViewModel } from '@/lib/infrastructure/data/view-model/did'; import { ReplicaStateBadge } from '@/component-library/features/badges/DID/ReplicaStateBadge'; -import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; +import { DetailsDIDView, DetailsDIDProps } from '@/component-library/pages/DID/details/views/DetailsDIDView'; import useTableStreaming from '@/lib/infrastructure/hooks/useTableStreaming'; type DetailsDIDFileReplicasTableProps = { @@ -53,7 +53,7 @@ export const DetailsDIDFileReplicasTable = (props: DetailsDIDFileReplicasTablePr return ; }; -export const DetailsDIDFileReplicas: DetailsDIDComponent = ({ scope, name }: DetailsDIDProps) => { +export const DetailsDIDFileReplicas: DetailsDIDView = ({ scope, name }: DetailsDIDProps) => { const { gridApi, onGridReady, streamingHook, startStreaming, stopStreaming } = useTableStreaming(); useEffect(() => { diff --git a/src/component-library/pages/DID/details/DetailsDIDParents.tsx b/src/component-library/pages/DID/details/views/DetailsDIDParents.tsx similarity index 73% rename from src/component-library/pages/DID/details/DetailsDIDParents.tsx rename to src/component-library/pages/DID/details/views/DetailsDIDParents.tsx index 130bf74fd..46b2353c5 100644 --- a/src/component-library/pages/DID/details/DetailsDIDParents.tsx +++ b/src/component-library/pages/DID/details/views/DetailsDIDParents.tsx @@ -1,10 +1,10 @@ import { DIDViewModel } from '@/lib/infrastructure/data/view-model/did'; import React, { useEffect } from 'react'; -import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; +import { DetailsDIDView, DetailsDIDProps } from '@/component-library/pages/DID/details/views/DetailsDIDView'; import useTableStreaming from '@/lib/infrastructure/hooks/useTableStreaming'; -import { DetailsDIDSimpleTable } from '@/component-library/pages/DID/details/DetailsDIDSimpleTable'; +import { DetailsDIDSimpleTable } from '@/component-library/pages/DID/details/tables/DetailsDIDSimpleTable'; -export const DetailsDIDParents: DetailsDIDComponent = ({ scope, name }: DetailsDIDProps) => { +export const DetailsDIDParents: DetailsDIDView = ({ scope, name }: DetailsDIDProps) => { const { gridApi, onGridReady, streamingHook, startStreaming, stopStreaming } = useTableStreaming(); useEffect(() => { diff --git a/src/component-library/pages/DID/details/DetailsDIDRules.tsx b/src/component-library/pages/DID/details/views/DetailsDIDRules.tsx similarity index 95% rename from src/component-library/pages/DID/details/DetailsDIDRules.tsx rename to src/component-library/pages/DID/details/views/DetailsDIDRules.tsx index 1b7975862..541d42bd1 100644 --- a/src/component-library/pages/DID/details/DetailsDIDRules.tsx +++ b/src/component-library/pages/DID/details/views/DetailsDIDRules.tsx @@ -8,7 +8,7 @@ import { buildDiscreteFilterParams, DefaultDateFilterParams, DefaultTextFilterPa import { badgeCellClasses, badgeCellWrapperStyle } from '@/component-library/features/table/cells/badge-cell'; import { RuleState } from '@/lib/core/entity/rucio'; import { StreamedTable } from '@/component-library/features/table/StreamedTable/StreamedTable'; -import { DetailsDIDComponent, DetailsDIDProps } from '@/component-library/pages/DID/details/DetailsDIDComponent'; +import { DetailsDIDView, DetailsDIDProps } from '@/component-library/pages/DID/details/views/DetailsDIDView'; import useTableStreaming from '@/lib/infrastructure/hooks/useTableStreaming'; import { formatDate, formatSeconds } from '@/component-library/features/utils/text-formatters'; import { RuleStateBadge } from '@/component-library/features/badges/Rule/RuleStateBadge'; @@ -109,7 +109,7 @@ export const DetailsDIDRulesTable = (props: DetailsDIDRulesTableProps) => { return ; }; -export const DetailsDIDRules: DetailsDIDComponent = ({ scope, name }: DetailsDIDProps) => { +export const DetailsDIDRules: DetailsDIDView = ({ scope, name }: DetailsDIDProps) => { const { gridApi, onGridReady, streamingHook, startStreaming, stopStreaming } = useTableStreaming(); useEffect(() => { diff --git a/src/component-library/pages/DID/details/views/DetailsDIDView.ts b/src/component-library/pages/DID/details/views/DetailsDIDView.ts new file mode 100644 index 000000000..183d2e38b --- /dev/null +++ b/src/component-library/pages/DID/details/views/DetailsDIDView.ts @@ -0,0 +1,6 @@ +export type DetailsDIDProps = { + scope: string; + name: string; +}; + +export type DetailsDIDView = (props: DetailsDIDProps) => JSX.Element; From 68cc9af37b02d260dbe00b2cd38bdce582b268c5 Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 13 Nov 2024 15:40:34 +0200 Subject: [PATCH 21/32] Implement contents-replicas integrated view --- .../pages/DID/details/DetailsDID.tsx | 4 +- .../tables/DetailsDIDFileReplicasTable.tsx | 52 ++++++++++++++ .../details/tables/DetailsDIDSimpleTable.tsx | 13 ++-- .../views/DetailsDIDContentsReplicas.tsx | 69 +++++++++++++++++++ .../details/views/DetailsDIDFileReplicas.tsx | 53 +------------- 5 files changed, 134 insertions(+), 57 deletions(-) create mode 100644 src/component-library/pages/DID/details/tables/DetailsDIDFileReplicasTable.tsx create mode 100644 src/component-library/pages/DID/details/views/DetailsDIDContentsReplicas.tsx diff --git a/src/component-library/pages/DID/details/DetailsDID.tsx b/src/component-library/pages/DID/details/DetailsDID.tsx index ff1069299..e8f039d0b 100644 --- a/src/component-library/pages/DID/details/DetailsDID.tsx +++ b/src/component-library/pages/DID/details/DetailsDID.tsx @@ -17,6 +17,7 @@ import { DetailsDIDRules } from '@/component-library/pages/DID/details/views/Det import { cn } from '@/component-library/utils'; import { DetailsDIDParents } from '@/component-library/pages/DID/details/views/DetailsDIDParents'; import { DetailsDIDContents } from '@/component-library/pages/DID/details/views/DetailsDIDContents'; +import { DetailsDIDContentsReplicas } from '@/component-library/pages/DID/details/views/DetailsDIDContentsReplicas'; type DetailsDIDTablesProps = { scope: string; @@ -31,11 +32,12 @@ export const DetailsDIDTables = ({ scope, name, type }: DetailsDIDTablesProps) = ['Rules', DetailsDIDRules], ['Parents', DetailsDIDParents], ['Contents', DetailsDIDContents], + ['Contents Replicas', DetailsDIDContentsReplicas], ]); const tabsByType: Record = { File: ['Replicas', 'Parents', 'Attributes'], - Dataset: ['Rules', 'Replicas', 'Content Replicas', 'Attributes'], + Dataset: ['Rules', 'Replicas', 'Contents Replicas', 'Attributes'], Container: ['Contents', 'Rules', 'Attributes'], All: [], Collection: [], diff --git a/src/component-library/pages/DID/details/tables/DetailsDIDFileReplicasTable.tsx b/src/component-library/pages/DID/details/tables/DetailsDIDFileReplicasTable.tsx new file mode 100644 index 000000000..ce169699f --- /dev/null +++ b/src/component-library/pages/DID/details/tables/DetailsDIDFileReplicasTable.tsx @@ -0,0 +1,52 @@ +import { UseStreamReader } from '@/lib/infrastructure/hooks/useStreamReader'; +import { FileReplicaStateViewModel } from '@/lib/infrastructure/data/view-model/did'; +import { GridReadyEvent } from 'ag-grid-community'; +import { ClickableCell } from '@/component-library/features/table/cells/ClickableCell'; +import React, { useRef, useState } from 'react'; +import { AgGridReact } from 'ag-grid-react'; +import { buildDiscreteFilterParams, DefaultTextFilterParams } from '@/component-library/features/utils/filter-parameters'; +import { badgeCellClasses, badgeCellWrapperStyle } from '@/component-library/features/table/cells/badge-cell'; +import { ReplicaStateBadge } from '@/component-library/features/badges/DID/ReplicaStateBadge'; +import { ReplicaState } from '@/lib/core/entity/rucio'; +import { StreamedTable } from '@/component-library/features/table/StreamedTable/StreamedTable'; + +type DetailsDIDFileReplicasTableProps = { + streamingHook: UseStreamReader; + onGridReady: (event: GridReadyEvent) => void; +}; + +const ClickableRSE = (props: { value: string }) => { + return {props.value}; +}; + +export const DetailsDIDFileReplicasTable = (props: DetailsDIDFileReplicasTableProps) => { + const tableRef = useRef>(null); + + const [columnDefs] = useState([ + { + headerName: 'RSE', + field: 'rse', + flex: 1, + sortable: false, + cellRenderer: ClickableRSE, + filter: true, + filterParams: DefaultTextFilterParams, + }, + { + headerName: 'State', + field: 'state', + flex: 1, + cellStyle: badgeCellWrapperStyle, + cellRenderer: ReplicaStateBadge, + cellRendererParams: { + className: badgeCellClasses, + }, + filter: true, + sortable: false, + // TODO: fix the string values + filterParams: buildDiscreteFilterParams(Object.values(ReplicaState)), + }, + ]); + + return ; +}; diff --git a/src/component-library/pages/DID/details/tables/DetailsDIDSimpleTable.tsx b/src/component-library/pages/DID/details/tables/DetailsDIDSimpleTable.tsx index 12796c663..0332bbf66 100644 --- a/src/component-library/pages/DID/details/tables/DetailsDIDSimpleTable.tsx +++ b/src/component-library/pages/DID/details/tables/DetailsDIDSimpleTable.tsx @@ -1,6 +1,6 @@ import { UseStreamReader } from '@/lib/infrastructure/hooks/useStreamReader'; import { DIDViewModel } from '@/lib/infrastructure/data/view-model/did'; -import { GridReadyEvent, ValueGetterParams } from 'ag-grid-community'; +import { GridReadyEvent, SelectionChangedEvent, ValueGetterParams } from 'ag-grid-community'; import { ClickableCell } from '@/component-library/features/table/cells/ClickableCell'; import React, { useRef, useState } from 'react'; import { AgGridReact } from 'ag-grid-react'; @@ -11,11 +11,11 @@ import { StreamedTable } from '@/component-library/features/table/StreamedTable/ type DetailsDIDSimpleTableProps = { streamingHook: UseStreamReader; + onSelectionChanged?: (event: SelectionChangedEvent) => void; onGridReady: (event: GridReadyEvent) => void; }; const ClickableDID = (props: { value: string[] }) => { - console.log(props.value); const [scope, name] = props.value; return ( @@ -31,8 +31,11 @@ export const DetailsDIDSimpleTable = (props: DetailsDIDSimpleTableProps) => { { headerName: 'Identifier', flex: 1, - valueGetter: (params: ValueGetterParams) => [params.data?.scope, params.data?.name], - cellRenderer: ClickableDID, + valueGetter: (params: ValueGetterParams) => { + if (props.onSelectionChanged) return `${params.data?.scope}:${params.data?.name}`; + return [params.data?.scope, params.data?.name]; + }, + cellRenderer: props.onSelectionChanged ? undefined : ClickableDID, minWidth: 250, sortable: false, }, @@ -50,5 +53,5 @@ export const DetailsDIDSimpleTable = (props: DetailsDIDSimpleTableProps) => { }, ]); - return ; + return ; }; diff --git a/src/component-library/pages/DID/details/views/DetailsDIDContentsReplicas.tsx b/src/component-library/pages/DID/details/views/DetailsDIDContentsReplicas.tsx new file mode 100644 index 000000000..554ecf8d3 --- /dev/null +++ b/src/component-library/pages/DID/details/views/DetailsDIDContentsReplicas.tsx @@ -0,0 +1,69 @@ +import { DIDViewModel, FileReplicaStateViewModel } from '@/lib/infrastructure/data/view-model/did'; +import React, { useEffect, useState } from 'react'; +import { DetailsDIDView, DetailsDIDProps } from '@/component-library/pages/DID/details/views/DetailsDIDView'; +import useTableStreaming from '@/lib/infrastructure/hooks/useTableStreaming'; +import { DetailsDIDSimpleTable } from '@/component-library/pages/DID/details/tables/DetailsDIDSimpleTable'; +import { SelectionChangedEvent } from 'ag-grid-community'; +import { DetailsDIDFileReplicasTable } from '@/component-library/pages/DID/details/tables/DetailsDIDFileReplicasTable'; + +export const DetailsDIDContentsReplicas: DetailsDIDView = ({ scope, name }: DetailsDIDProps) => { + const { + gridApi: contentsGridApi, + onGridReady: onContentsGridReady, + streamingHook: contentsStreamingHook, + startStreaming: startContentsStreaming, + } = useTableStreaming(); + + useEffect(() => { + if (contentsGridApi) { + const url = '/api/feature/list-did-contents?' + new URLSearchParams({ scope, name }); + startContentsStreaming(url); + } + }, [contentsGridApi]); + + const [selectedContent, setSelectedContent] = useState(null); + + const onSelectionChanged = (event: SelectionChangedEvent) => { + const selectedRows = event.api.getSelectedRows(); + if (selectedRows.length === 1) { + setSelectedContent(selectedRows[0] as DIDViewModel); + } else { + setSelectedContent(null); + } + }; + + const { + onGridReady: onReplicasGridReady, + streamingHook: replicasStreamingHook, + startStreaming: startReplicasStreaming, + } = useTableStreaming(); + + // TODO: handle continuing streaming + + useEffect(() => { + if (selectedContent) { + const url = + '/api/feature/list-file-replicas?' + + new URLSearchParams({ + scope: selectedContent.scope, + name: setSelectedContent.name, + }); + startReplicasStreaming(url); + } + }, [selectedContent]); + + return ( +
+
+ +
+
+ +
+
+ ); +}; diff --git a/src/component-library/pages/DID/details/views/DetailsDIDFileReplicas.tsx b/src/component-library/pages/DID/details/views/DetailsDIDFileReplicas.tsx index e92f62e05..3024b75c5 100644 --- a/src/component-library/pages/DID/details/views/DetailsDIDFileReplicas.tsx +++ b/src/component-library/pages/DID/details/views/DetailsDIDFileReplicas.tsx @@ -1,57 +1,8 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { AgGridReact } from 'ag-grid-react'; -import { UseStreamReader } from '@/lib/infrastructure/hooks/useStreamReader'; -import { StreamedTable } from '@/component-library/features/table/StreamedTable/StreamedTable'; -import { ClickableCell } from '@/component-library/features/table/cells/ClickableCell'; -import { badgeCellClasses, badgeCellWrapperStyle } from '@/component-library/features/table/cells/badge-cell'; -import { buildDiscreteFilterParams, DefaultTextFilterParams } from '@/component-library/features/utils/filter-parameters'; -import { GridReadyEvent } from 'ag-grid-community'; -import { ReplicaState } from '@/lib/core/entity/rucio'; +import React, { useEffect } from 'react'; import { FileReplicaStateViewModel } from '@/lib/infrastructure/data/view-model/did'; -import { ReplicaStateBadge } from '@/component-library/features/badges/DID/ReplicaStateBadge'; import { DetailsDIDView, DetailsDIDProps } from '@/component-library/pages/DID/details/views/DetailsDIDView'; import useTableStreaming from '@/lib/infrastructure/hooks/useTableStreaming'; - -type DetailsDIDFileReplicasTableProps = { - streamingHook: UseStreamReader; - onGridReady: (event: GridReadyEvent) => void; -}; - -const ClickableRSE = (props: { value: string }) => { - return {props.value}; -}; - -export const DetailsDIDFileReplicasTable = (props: DetailsDIDFileReplicasTableProps) => { - const tableRef = useRef>(null); - - const [columnDefs] = useState([ - { - headerName: 'RSE', - field: 'rse', - flex: 1, - sortable: false, - cellRenderer: ClickableRSE, - filter: true, - filterParams: DefaultTextFilterParams, - }, - { - headerName: 'State', - field: 'state', - flex: 1, - cellStyle: badgeCellWrapperStyle, - cellRenderer: ReplicaStateBadge, - cellRendererParams: { - className: badgeCellClasses, - }, - filter: true, - sortable: false, - // TODO: fix the string values - filterParams: buildDiscreteFilterParams(Object.values(ReplicaState)), - }, - ]); - - return ; -}; +import { DetailsDIDFileReplicasTable } from '@/component-library/pages/DID/details/tables/DetailsDIDFileReplicasTable'; export const DetailsDIDFileReplicas: DetailsDIDView = ({ scope, name }: DetailsDIDProps) => { const { gridApi, onGridReady, streamingHook, startStreaming, stopStreaming } = useTableStreaming(); From 476ad811ae18dd8e286788e992b7295844740c39 Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 13 Nov 2024 15:51:14 +0200 Subject: [PATCH 22/32] Remove legacy components for the DID details --- .../(rucio)/did/page/[scope]/[name]/page.tsx | 130 +----------- .../pages/legacy/DID/DIDMetaView.tsx | 179 ---------------- .../pages/legacy/DID/PageDID.stories.tsx | 38 ---- .../pages/legacy/DID/PageDID.tsx | 199 ------------------ .../legacy/DID/PageDIDByType.stories.tsx | 16 -- .../pages/legacy/DID/PageDIDByType.tsx | 55 ----- .../DID/PageDIDDatasetReplicas.stories.tsx | 15 -- .../legacy/DID/PageDIDDatasetReplicas.tsx | 175 --------------- .../DID/PageDIDFileReplicas.stories.tsx | 16 -- .../pages/legacy/DID/PageDIDFileReplicas.tsx | 63 ------ .../DID/PageDIDFileReplicasD.stories.tsx | 23 -- .../pages/legacy/DID/PageDIDFileReplicasD.tsx | 65 ------ .../legacy/DID/PageDIDMetadata.stories.tsx | 15 -- .../pages/legacy/DID/PageDIDMetadata.tsx | 66 ------ .../pages/legacy/DID/PageDIDRules.stories.tsx | 15 -- .../pages/legacy/DID/PageDIDRules.tsx | 136 ------------ 16 files changed, 2 insertions(+), 1204 deletions(-) delete mode 100644 src/component-library/pages/legacy/DID/DIDMetaView.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDID.stories.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDID.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDIDByType.stories.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDIDByType.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDIDDatasetReplicas.stories.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDIDDatasetReplicas.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDIDFileReplicas.stories.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDIDFileReplicas.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDIDFileReplicasD.stories.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDIDFileReplicasD.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDIDMetadata.stories.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDIDMetadata.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDIDRules.stories.tsx delete mode 100644 src/component-library/pages/legacy/DID/PageDIDRules.tsx diff --git a/src/app/(rucio)/did/page/[scope]/[name]/page.tsx b/src/app/(rucio)/did/page/[scope]/[name]/page.tsx index 5d7602c91..f1d0a83f5 100644 --- a/src/app/(rucio)/did/page/[scope]/[name]/page.tsx +++ b/src/app/(rucio)/did/page/[scope]/[name]/page.tsx @@ -1,136 +1,10 @@ 'use client'; -import { PageDID as PageDIDStory } from '@/component-library/pages/legacy/DID/PageDID'; -import useComDOM from '@/lib/infrastructure/hooks/useComDOM'; -import { useEffect, useState } from 'react'; -import { HTTPRequest } from '@/lib/sdk/http'; -import { - DIDDatasetReplicasViewModel, - DIDKeyValuePairsDataViewModel, - DIDMetaViewModel, - DIDRulesViewModel, - DIDViewModel, - FileReplicaStateViewModel, -} from '@/lib/infrastructure/data/view-model/did'; -import { didKeyValuePairsDataQuery, didMetaQueryBase } from '@/app/(rucio)/did/queries'; -import { Loading } from '@/component-library/pages/legacy/Helpers/Loading'; + import { DetailsDID } from '@/component-library/pages/DID/details/DetailsDID'; export default function Page({ params }: { params: { scope: string; name: string } }) { const decodedScope = decodeURIComponent(params.scope); const decodedName = decodeURIComponent(params.name); - const [didMeta, setDIDMeta] = useState({ status: 'pending' } as DIDMetaViewModel); - const [didKeyValuePairsData, setDIDKeyValuePairsData] = useState({ status: 'pending' } as DIDKeyValuePairsDataViewModel); - const [fromDidList, setFromDidList] = useState('yosearch'); - useEffect(() => { - didMetaQueryBase(decodedScope, decodedName).then(setDIDMeta); - }, []); - useEffect(() => { - didKeyValuePairsDataQuery(decodedScope, decodedName).then(setDIDKeyValuePairsData); - }, []); - - const didParentsComDOM = useComDOM('page-did-parents-query', [], false, Infinity, 200, true); - const didContentsComDOM = useComDOM('page-did-contents-query', [], false, Infinity, 200, true); - const didFileReplicasComDOM = useComDOM('page-did-filereplicas-query', [], false, Infinity, 200, true); - const didFileReplicasDOnChange = (scope: string, name: string) => { - didFileReplicasComDOM.setRequest({ - url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/list-file-replicas`), - method: 'GET', - params: { - scope: scope, - name: name, - }, - headers: new Headers({ - 'Content-Type': 'application/json', - } as HeadersInit), - body: null, - } as HTTPRequest); - didFileReplicasComDOM.start(); - }; - const didRulesComDOM = useComDOM('page-did-rules-query', [], false, Infinity, 200, true); - const didDatasetReplicasComDOM = useComDOM('page-did-datasetreplicas-query', [], false, Infinity, 200, true); - useEffect(() => { - const setRequests = async () => { - await didContentsComDOM.setRequest({ - url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/list-did-contents`), - method: 'GET', - params: { - scope: decodedScope, - name: decodedName, - }, - headers: new Headers({ - 'Content-Type': 'application/json', - } as HeadersInit), - body: null, - } as HTTPRequest); - await didParentsComDOM.setRequest({ - url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/list-did-parents`), - method: 'GET', - params: { - scope: decodedScope, - name: decodedName, - }, - headers: new Headers({ - 'Content-Type': 'application/json', - } as HeadersInit), - body: null, - } as HTTPRequest); - await didFileReplicasComDOM.setRequest({ - url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/list-file-replicas`), - method: 'GET', - params: { - scope: decodedScope, - name: decodedName, - }, - headers: new Headers({ - 'Content-Type': 'application/json', - } as HeadersInit), - body: null, - } as HTTPRequest); - await didRulesComDOM.setRequest({ - url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/list-did-rules`), - method: 'GET', - params: { - scope: decodedScope, - name: decodedName, - }, - headers: new Headers({ - 'Content-Type': 'application/json', - } as HeadersInit), - body: null, - } as HTTPRequest); - await didDatasetReplicasComDOM.setRequest({ - url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/list-dataset-replicas`), - method: 'GET', - params: { - scope: decodedScope, - name: decodedName, - }, - headers: new Headers({ - 'Content-Type': 'application/json', - } as HeadersInit), - body: null, - } as HTTPRequest); - }; - setRequests(); - }, []); - - return ; - - if (didMeta.status === 'pending') { - return ; - } - return ( - - ); + return ; } diff --git a/src/component-library/pages/legacy/DID/DIDMetaView.tsx b/src/component-library/pages/legacy/DID/DIDMetaView.tsx deleted file mode 100644 index 411e85c90..000000000 --- a/src/component-library/pages/legacy/DID/DIDMetaView.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { twMerge } from 'tailwind-merge'; -import { DIDMetaViewModel } from '@/lib/infrastructure/data/view-model/did'; -import { FileSize } from '@/component-library/atoms/legacy/text/content/FileSize/FileSize'; -import { DIDTypeTag } from '@/component-library/features/legacy/Tags/DIDTypeTag'; -import { BoolTag } from '@/component-library/features/legacy/Tags/BoolTag'; -import { AvailabilityTag } from '@/component-library/features/legacy/Tags/AvailabilityTag'; -var format = require('date-format'); - -export const DIDMetaView = (props: { data: DIDMetaViewModel; show: boolean; horizontal?: boolean }) => { - const Titletd: React.FC = ({ ...props }) => { - const { className, ...otherprops } = props; - return ( - - {props.children} - - ); - }; - const Contenttd: React.FC = ({ ...props }) => { - const { className, ...otherprops } = props; - return ( - - {props.children} - - ); - }; - const meta = props.data; - // suspense: data is not loaded yet - if (props.data.status === 'pending') { - return ( -
- Loading DID Metadata -
- ); - } - // base case: data is loaded - return ( -
- - - - Scope - {meta.scope} - - - Name - {meta.name} - - -
- - - - Size - - - - - - GUID - {meta.guid as string} - - - Adler32 - {meta.adler32 as string} - - - MD5 - {meta.md5 as string} - - -
- - - - Created At - {format('yyyy-MM-dd', new Date(meta.created_at))} - - - Updated At - {format('yyyy-MM-dd', new Date(meta.updated_at))} - - -
- - - - DID Type - - - - Account - {meta.account} - - - Is Open - - - - Monotonic - - - -
- -
- -
- -
- - - - Obsolete - - - - Hidden - - - - Suppressed - - - - Purge Replicas - - - - Availability - - - -
- -
- -
- -
- -
- -
-
- ); -}; diff --git a/src/component-library/pages/legacy/DID/PageDID.stories.tsx b/src/component-library/pages/legacy/DID/PageDID.stories.tsx deleted file mode 100644 index 6d4b77952..000000000 --- a/src/component-library/pages/legacy/DID/PageDID.stories.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { StoryFn, Meta } from '@storybook/react'; -import { PageDID as PD } from './PageDID'; - -import { - fixtureDIDMetaViewModel, - mockUseComDOM, - fixtureDIDRulesViewModel, - fixtureDIDViewModel, - fixtureDIDDatasetReplicasViewModel, - fixtureFilereplicaStateViewModel, - fixtureFilereplicaStateDViewModel, - fixtureDIDKeyValuePairsDataViewModel, -} from '@/test/fixtures/table-fixtures'; - -export default { - title: 'Components/Pages/DID', - component: PD, -} as Meta; - -const Template: StoryFn = args => ; -export const PageDID = Template.bind({}); -PageDID.args = { - didMeta: fixtureDIDMetaViewModel(), - fromDidList: 'yosearch', - // Parent DIDs [FILE] - didParentsComDOM: mockUseComDOM(Array.from({ length: 100 }, (_, i) => fixtureDIDViewModel())), - // DID Metadata - didKeyValuePairsData: fixtureDIDKeyValuePairsDataViewModel(), - // Filereplicas - didFileReplicasComDOM: mockUseComDOM(Array.from({ length: 100 }, (_, i) => fixtureFilereplicaStateViewModel())), - didFileReplicasDOnChange: (scope: string, name: string) => { - console.log(scope, name, 'queried by FileReplicasDOnChange'); - }, - didRulesComDOM: mockUseComDOM(Array.from({ length: 100 }, (_, i) => fixtureDIDRulesViewModel())), - // Contents - didContentsComDOM: mockUseComDOM(Array.from({ length: 100 }, (_, i) => fixtureDIDViewModel())), - didDatasetReplicasComDOM: mockUseComDOM(Array.from({ length: 100 }, (_, i) => fixtureDIDDatasetReplicasViewModel())), -}; diff --git a/src/component-library/pages/legacy/DID/PageDID.tsx b/src/component-library/pages/legacy/DID/PageDID.tsx deleted file mode 100644 index d79ca8554..000000000 --- a/src/component-library/pages/legacy/DID/PageDID.tsx +++ /dev/null @@ -1,199 +0,0 @@ -// components -import { Tabs } from '../../../atoms/legacy/Tabs/Tabs'; -import { DIDTypeTag } from '@/component-library/features/legacy/Tags/DIDTypeTag'; -import { SubPage } from '../../../atoms/legacy/helpers/SubPage/SubPage'; -import { Heading } from '@/component-library/pages/legacy/Helpers/Heading'; -import { Body } from '@/component-library/pages/legacy/Helpers/Body'; - -// misc packages, react -import { twMerge } from 'tailwind-merge'; -import React, { useEffect, useState } from 'react'; - -// DTO etc -import { DIDType } from '@/lib/core/entity/rucio'; -import { PageDIDMetadata } from './PageDIDMetadata'; -import { PageDIDFilereplicas } from './PageDIDFileReplicas'; -import { PageDIDFilereplicasD } from './PageDIDFileReplicasD'; -import { PageDIDRules } from './PageDIDRules'; -import { PageDIDByType } from './PageDIDByType'; -import { PageDIDDatasetReplicas } from './PageDIDDatasetReplicas'; -import { UseComDOM } from '@/lib/infrastructure/hooks/useComDOM'; -import { - DIDDatasetReplicasViewModel, - DIDKeyValuePairsDataViewModel, - DIDMetaViewModel, - DIDRulesViewModel, - DIDViewModel, - FilereplicaStateDViewModel, - FileReplicaStateViewModel, -} from '@/lib/infrastructure/data/view-model/did'; -import { HTTPRequest } from '@/lib/sdk/http'; -import { DIDMetaView } from '@/component-library/pages/legacy/DID/DIDMetaView'; -import { InfoField } from '@/component-library/features/fields/InfoField'; - -export interface PageDIDPageProps { - didMeta: DIDMetaViewModel; - fromDidList?: string; // if coming from DIDList, this will be the DIDList's query - // Parent DIDs [FILE] - didParentsComDOM: UseComDOM; - // Metadata [BOTH] - didKeyValuePairsData: DIDKeyValuePairsDataViewModel; - // File Replica States [FILE] - didFileReplicasComDOM: UseComDOM; - // File Replica States [DATASET] simply uses Contents ComDOM - // But we supply an onchange function that is called when the contents comdom is selected - didFileReplicasDOnChange: (scope: string, name: string) => void; // This function changes the file replicas comdom's request to the selected file - // Rule State [DATASET] - didRulesComDOM: UseComDOM; - // Contents [COLLECTION] - didContentsComDOM: UseComDOM; - // Dataset Replica States [DATASET] - didDatasetReplicasComDOM: UseComDOM; -} - -export const PageDID = (props: PageDIDPageProps) => { - const didtype = props.didMeta.did_type; - const [subpageIndex, setSubpageIndex] = useState(0); - const showPageBools: Record boolean> = { - 'subpage-metadata': () => { - if (didtype === DIDType.FILE) { - return subpageIndex === 2; - } else if (didtype === DIDType.DATASET) { - return subpageIndex === 3; - } else if (didtype === DIDType.CONTAINER) { - return subpageIndex === 2; - } else { - return false; - } - }, - 'subpage-contents': () => { - return didtype === DIDType.CONTAINER && subpageIndex === 0; - }, - 'subpage-parent-dids': () => { - return didtype === DIDType.FILE && subpageIndex === 1; - }, - 'subpage-rules': () => { - if (didtype === DIDType.DATASET) { - return subpageIndex === 0; - } else if (didtype === DIDType.CONTAINER) { - return subpageIndex === 1; - } else { - return false; - } - }, - 'subpage-dataset-replicas': () => { - return didtype === DIDType.DATASET && subpageIndex === 1; - }, - 'subpage-file-replica-states': () => { - return didtype === DIDType.FILE && subpageIndex === 0; - }, - 'subpage-file-replica-states-d': () => { - return didtype === DIDType.DATASET && subpageIndex === 2; - }, - }; - return ( -
- - This page is currently in development and has not been optimized yet. We are working on improvements, so stay tuned! - - } - > -
- -
-
- - - { - setSubpageIndex(id); - }} - /> - { - if (props.didRulesComDOM.query.data.all.length === 0) { - props.didRulesComDOM.start(); - } - }} - id="subpage-rules" - > - - - { - if (props.didDatasetReplicasComDOM.query.data.all.length === 0) { - props.didDatasetReplicasComDOM.start(); - } - }} - id="subpage-dataset-replicas" - > - - - { - if (props.didFileReplicasComDOM.query.data.all.length === 0) { - props.didFileReplicasComDOM.start(); - } - }} - id="subpage-file-replica-states" - > - - - { - if (props.didContentsComDOM.query.data.all.length === 0) { - props.didContentsComDOM.start(); - } - }} - id="subpage-file-replica-states-d" - > - - - { - if (props.didParentsComDOM.query.data.all.length === 0) { - props.didParentsComDOM.start(); - } - }} - id="subpage-parent-dids" - > - - - - - - { - if (props.didContentsComDOM.query.data.all.length === 0) { - props.didContentsComDOM.start(); - } - }} - id="subpage-contents" - > - - - -
- ); -}; diff --git a/src/component-library/pages/legacy/DID/PageDIDByType.stories.tsx b/src/component-library/pages/legacy/DID/PageDIDByType.stories.tsx deleted file mode 100644 index 0f98c2d70..000000000 --- a/src/component-library/pages/legacy/DID/PageDIDByType.stories.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { StoryFn, Meta } from '@storybook/react'; -import { fixtureDIDViewModel, mockUseComDOM } from '@/test/fixtures/table-fixtures'; -import { PageDIDByType as P } from './PageDIDByType'; - -export default { - title: 'Components/Pages/DID', - component: P, -} as Meta; - -const Template: StoryFn = args =>

; - -export const PageDIDByType = Template.bind({}); -PageDIDByType.args = { - showDIDType: true, - comdom: mockUseComDOM(Array.from({ length: 100 }, (_, i) => fixtureDIDViewModel())), -}; diff --git a/src/component-library/pages/legacy/DID/PageDIDByType.tsx b/src/component-library/pages/legacy/DID/PageDIDByType.tsx deleted file mode 100644 index 8d84adee4..000000000 --- a/src/component-library/pages/legacy/DID/PageDIDByType.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { twMerge } from 'tailwind-merge'; -import { createColumnHelper } from '@tanstack/react-table'; - -import { P } from '../../../atoms/legacy/text/content/P/P'; -import { DIDTypeTag } from '@/component-library/features/legacy/Tags/DIDTypeTag'; -import { DIDType } from '@/lib/core/entity/rucio'; -import { StreamedTable } from '@/component-library/features/legacy/StreamedTables/StreamedTable'; -import { TableFilterString } from '@/component-library/features/legacy/StreamedTables/TableFilterString'; -import { TableFilterDiscrete } from '@/component-library/features/legacy/StreamedTables/TableFilterDiscrete'; -import { HiDotsHorizontal } from 'react-icons/hi'; -import { UseComDOM } from '@/lib/infrastructure/hooks/useComDOM'; -import { DIDViewModel } from '@/lib/infrastructure/data/view-model/did'; -import { TableInternalLink } from '@/component-library/features/legacy/StreamedTables/TableInternalLink'; - -export const PageDIDByType = (props: { comdom: UseComDOM; showDIDType?: boolean }) => { - const columnHelper = createColumnHelper(); - const tablecolumns: any[] = [ - columnHelper.accessor(row => `${row.scope}:${row.name}`, { - id: 'did', - header: info => { - return ; - }, - cell: info => { - return ( - {info.getValue()} - ); - }, - }), - columnHelper.accessor('did_type', { - id: 'did_type', - header: info => { - return ( - - name="DID Type" - keys={[DIDType.CONTAINER, DIDType.DATASET, DIDType.FILE]} - renderFunc={state => - state === undefined ? ( - - ) : ( - - ) - } - column={info.column} - /> - ); - }, - cell: info => , - meta: { - style: 'w-6 sm:w-8 md:w-36', - }, - }), - ]; - - return tablecomdom={props.comdom} tablecolumns={tablecolumns} tablestyling={{}} />; -}; diff --git a/src/component-library/pages/legacy/DID/PageDIDDatasetReplicas.stories.tsx b/src/component-library/pages/legacy/DID/PageDIDDatasetReplicas.stories.tsx deleted file mode 100644 index c91ed857f..000000000 --- a/src/component-library/pages/legacy/DID/PageDIDDatasetReplicas.stories.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { StoryFn, Meta } from '@storybook/react'; -import { fixtureDIDDatasetReplicasViewModel, mockUseComDOM } from '@/test/fixtures/table-fixtures'; -import { PageDIDDatasetReplicas as P } from './PageDIDDatasetReplicas'; - -export default { - title: 'Components/Pages/DID', - component: P, -} as Meta; - -const Template: StoryFn = args =>

; - -export const PageDIDDatasetReplicas = Template.bind({}); -PageDIDDatasetReplicas.args = { - comdom: mockUseComDOM(Array.from({ length: 100 }, (_, i) => fixtureDIDDatasetReplicasViewModel())), -}; diff --git a/src/component-library/pages/legacy/DID/PageDIDDatasetReplicas.tsx b/src/component-library/pages/legacy/DID/PageDIDDatasetReplicas.tsx deleted file mode 100644 index 74c574f78..000000000 --- a/src/component-library/pages/legacy/DID/PageDIDDatasetReplicas.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import { twMerge } from 'tailwind-merge'; -import { createColumnHelper } from '@tanstack/react-table'; -import { useEffect, useState } from 'react'; - -import { DateTag } from '@/component-library/features/legacy/Tags/DateTag'; -import { FileSize } from '../../../atoms/legacy/text/content/FileSize/FileSize'; -import { ReplicaStateTag } from '@/component-library/features/legacy/Tags/ReplicaStateTag'; -import { ReplicaState } from '@/lib/core/entity/rucio'; -import { RSETag } from '@/component-library/features/legacy/Tags/RSETag'; -import { StreamedTable } from '@/component-library/features/legacy/StreamedTables/StreamedTable'; -import { TableFilterString } from '@/component-library/features/legacy/StreamedTables/TableFilterString'; -import { TableSortUpDown } from '@/component-library/features/legacy/StreamedTables/TableSortUpDown'; -import { UseComDOM } from '@/lib/infrastructure/hooks/useComDOM'; -import { DIDDatasetReplicasViewModel } from '@/lib/infrastructure/data/view-model/did'; - -export const PageDIDDatasetReplicas = (props: { comdom: UseComDOM }) => { - const columnHelper = createColumnHelper(); - const tablecolumns: any[] = [ - columnHelper.accessor('rse', { - id: 'rse', - header: info => { - return ; - }, - cell: info => { - return ( - - - - {info.getValue()} - - - {info.row.original.availability ? '' : } - - ); - }, - meta: { - style: '', - filter: true, - }, - }), - columnHelper.accessor('rseblocked', { meta: { style: '' } }), - columnHelper.accessor('availability', { - // formerly known as `state` - id: 'availability', - cell: info => { - return ( - - ); - }, - meta: { - style: 'cursor-pointer md:w-44 pl-2', - }, - }), - columnHelper.accessor('available_files', { - id: 'available_files', - header: info => { - return ; - }, - cell: info => { - return ( - - {info.row.original.available_files} - - ); - }, - meta: { - style: 'cursor-pointer w-40 2xl:w-44 pt-2', - }, - }), - columnHelper.accessor('available_bytes', { - id: 'available_bytes', - header: info => { - return ; - }, - cell: info => { - return ( - - - - ); - }, - meta: { - style: 'cursor-pointer w-40 2xl:w-44 pt-2', - }, - }), - columnHelper.accessor('creation_date', { - id: 'creation_date', - header: info => { - return ; - }, - cell: info => { - return ( - - - - ); - }, - meta: { - style: 'cursor-pointer w-40 2xl:w-44 pt-2', - }, - }), - columnHelper.accessor('last_accessed', { - id: 'last_accessed', - header: info => { - return ; - }, - cell: info => { - return ( - - - - ); - }, - meta: { - style: 'cursor-pointer w-40 2xl:w-44 pt-2', - }, - }), - ]; - - // handle window resize - const [windowSize, setWindowSize] = useState([1920, 1080]); - useEffect(() => { - setWindowSize([window.innerWidth, window.innerHeight]); - - const handleWindowResize = () => { - setWindowSize([window.innerWidth, window.innerHeight]); - }; - - window.addEventListener('resize', handleWindowResize); - - return () => { - window.removeEventListener('resize', handleWindowResize); - }; - }, []); - const isLg = () => windowSize[0] > 1024; // 1024px is the breakpoint for lg => is minimum lg sized - - return ( - - tablecomdom={props.comdom} - tablecolumns={tablecolumns} - tablestyling={{ - visibility: { - rse: true, - availability: false, - available_files: isLg(), - available_bytes: isLg(), - creation_date: isLg(), - last_accessed: isLg(), - rseblocked: false, - }, - }} - tableselecting={{ - handleChange: (data: DIDDatasetReplicasViewModel[]) => {}, - enableRowSelection: !isLg(), - breakOut: { - breakoutVisibility: !isLg(), - keys: { - available_files: 'Available Files', - available_bytes: 'Available Bytes', - creation_date: 'Creation Date', - last_accessed: 'Last Accessed', - }, - }, - }} - /> - ); -}; diff --git a/src/component-library/pages/legacy/DID/PageDIDFileReplicas.stories.tsx b/src/component-library/pages/legacy/DID/PageDIDFileReplicas.stories.tsx deleted file mode 100644 index 22ebb7211..000000000 --- a/src/component-library/pages/legacy/DID/PageDIDFileReplicas.stories.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { StoryFn, Meta } from '@storybook/react'; -import { PageDIDFilereplicas as PDF } from './PageDIDFileReplicas'; -import { ReplicaState } from '@/lib/core/entity/rucio'; -import { fixtureFilereplicaStateViewModel, mockUseComDOM } from '@/test/fixtures/table-fixtures'; - -export default { - title: 'Components/Pages/DID', - component: PDF, -} as Meta; - -const Template: StoryFn = args => ; - -export const PageDIDFilereplicas = Template.bind({}); -PageDIDFilereplicas.args = { - comdom: mockUseComDOM(Array.from({ length: 100 }, (_, i) => fixtureFilereplicaStateViewModel())), -}; diff --git a/src/component-library/pages/legacy/DID/PageDIDFileReplicas.tsx b/src/component-library/pages/legacy/DID/PageDIDFileReplicas.tsx deleted file mode 100644 index 5a63c1413..000000000 --- a/src/component-library/pages/legacy/DID/PageDIDFileReplicas.tsx +++ /dev/null @@ -1,63 +0,0 @@ -// components -import { TableFilterDiscrete } from '@/component-library/features/legacy/StreamedTables/TableFilterDiscrete'; - -// misc packages, react -import { createColumnHelper } from '@tanstack/react-table'; -import { twMerge } from 'tailwind-merge'; -import { HiDotsHorizontal } from 'react-icons/hi'; - -// Viewmodels etc -import { ReplicaState } from '@/lib/core/entity/rucio'; -import { ReplicaStateTag } from '@/component-library/features/legacy/Tags/ReplicaStateTag'; -import { StreamedTable } from '@/component-library/features/legacy/StreamedTables/StreamedTable'; -import { TableFilterString } from '@/component-library/features/legacy/StreamedTables/TableFilterString'; -import { UseComDOM } from '@/lib/infrastructure/hooks/useComDOM'; -import { TableInternalLink } from '@/component-library/features/legacy/StreamedTables/TableInternalLink'; -import { FileReplicaStateViewModel } from '@/lib/infrastructure/data/view-model/did'; -import { TableStyling } from '@/component-library/features/legacy/StreamedTables/types'; - -export const PageDIDFilereplicas = (props: { comdom: UseComDOM; tablestyling?: TableStyling }) => { - const columnHelper = createColumnHelper(); - const tablecolumns: any[] = [ - columnHelper.accessor('rse', { - id: 'rse', - cell: info => { - // perhaps use this as a basis for links in tables - return {info.getValue()}; - }, - header: info => { - return ; - }, - filterFn: 'includesString', - }), - columnHelper.accessor('state', { - id: 'state', - cell: info => { - return ; - }, - header: info => { - return ( - - name="File Replica State" - keys={Object.values(ReplicaState)} - renderFunc={key => - key === undefined ? ( - - ) : ( - - ) - } - column={info.column} - /> - ); - }, - filterFn: 'equalsString', - meta: { - style: 'w-28 md:w-56', - }, - }), - ]; - return ( - tablecomdom={props.comdom} tablecolumns={tablecolumns} tablestyling={props.tablestyling ?? {}} /> - ); -}; diff --git a/src/component-library/pages/legacy/DID/PageDIDFileReplicasD.stories.tsx b/src/component-library/pages/legacy/DID/PageDIDFileReplicasD.stories.tsx deleted file mode 100644 index ffffd200f..000000000 --- a/src/component-library/pages/legacy/DID/PageDIDFileReplicasD.stories.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { StoryFn, Meta } from '@storybook/react'; -import { PageDIDFilereplicasD as PDFD } from './PageDIDFileReplicasD'; -import { - fixtureFilereplicaStateViewModel, - fixtureFilereplicaStateDViewModel, - mockUseComDOM, - fixtureDIDViewModel, -} from '@/test/fixtures/table-fixtures'; -import { useEffect, useState } from 'react'; - -export default { - title: 'Components/Pages/DID', - component: PDFD, -} as Meta; - -const Template: StoryFn = args => ; - -export const PageDIDFilereplicasD = Template.bind({}); -PageDIDFilereplicasD.args = { - datasetComDOM: mockUseComDOM(Array.from({ length: 100 }, (_, i) => fixtureDIDViewModel())), - replicaComDOM: mockUseComDOM(Array.from({ length: 100 }, (_, i) => fixtureFilereplicaStateViewModel())), - onChangeFileSelection: (scope: string, name: string) => console.log('onChangeFileSelection', scope, name), -}; diff --git a/src/component-library/pages/legacy/DID/PageDIDFileReplicasD.tsx b/src/component-library/pages/legacy/DID/PageDIDFileReplicasD.tsx deleted file mode 100644 index d691e0c21..000000000 --- a/src/component-library/pages/legacy/DID/PageDIDFileReplicasD.tsx +++ /dev/null @@ -1,65 +0,0 @@ -// components -import { P } from '../../../atoms/legacy/text/content/P/P'; -import { PageDIDFilereplicas } from './PageDIDFileReplicas'; - -// misc packages, react -import { createColumnHelper } from '@tanstack/react-table'; -import { twMerge } from 'tailwind-merge'; - -// Viewmodels etc -import { StreamedTable } from '@/component-library/features/legacy/StreamedTables/StreamedTable'; -import { TableFilterString } from '@/component-library/features/legacy/StreamedTables/TableFilterString'; -import { UseComDOM } from '@/lib/infrastructure/hooks/useComDOM'; -import { DIDViewModel, FileReplicaStateViewModel } from '@/lib/infrastructure/data/view-model/did'; -import { TableInternalLink } from '@/component-library/features/legacy/StreamedTables/TableInternalLink'; - -export const PageDIDFilereplicasD = (props: { - datasetComDOM: UseComDOM; // the files in the dataset - replicaComDOM: UseComDOM; // replicas of the selected file - onChangeFileSelection: (scope: string, name: string) => void; -}) => { - const { datasetComDOM, replicaComDOM, onChangeFileSelection } = props; - const columnHelper = createColumnHelper(); - const tablecolumns: any[] = [ - columnHelper.accessor(row => `${row.scope}:${row.name}`, { - id: 'did', - header: info => { - return ; - }, - cell: info => { - return ( - {info.getValue()} - ); - }, - }), - ]; - - return ( -

- Select a file and view the states of its replicas. -
- - tablecomdom={datasetComDOM} - tablecolumns={tablecolumns} - tablestyling={{ - tableFooterStack: true, - }} - tableselecting={{ - handleChange: (data: DIDViewModel[]) => { - if (data.length === 0) return; - onChangeFileSelection(data[0].scope, data[0].name); - }, - enableRowSelection: true, - enableMultiRowSelection: false, - }} - /> - -
-
- ); -}; diff --git a/src/component-library/pages/legacy/DID/PageDIDMetadata.stories.tsx b/src/component-library/pages/legacy/DID/PageDIDMetadata.stories.tsx deleted file mode 100644 index 449377b03..000000000 --- a/src/component-library/pages/legacy/DID/PageDIDMetadata.stories.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { StoryFn, Meta } from '@storybook/react'; -import { fixtureDIDKeyValuePairsDataViewModel, mockUseComDOM } from '@/test/fixtures/table-fixtures'; -import { PageDIDMetadata as P } from './PageDIDMetadata'; - -export default { - title: 'Components/Pages/DID', - component: P, -} as Meta; - -const Template: StoryFn = args =>

; - -export const PageDIDMetadata = Template.bind({}); -PageDIDMetadata.args = { - tabledata: fixtureDIDKeyValuePairsDataViewModel(), -}; diff --git a/src/component-library/pages/legacy/DID/PageDIDMetadata.tsx b/src/component-library/pages/legacy/DID/PageDIDMetadata.tsx deleted file mode 100644 index daeebf290..000000000 --- a/src/component-library/pages/legacy/DID/PageDIDMetadata.tsx +++ /dev/null @@ -1,66 +0,0 @@ -// components -import { H3 } from '../../../atoms/legacy/text/headings/H3/H3'; -import { BoolTag } from '@/component-library/features/legacy/Tags/BoolTag'; -import { AvailabilityTag } from '@/component-library/features/legacy/Tags/AvailabilityTag'; -import { DIDTypeTag } from '@/component-library/features/legacy/Tags/DIDTypeTag'; -import { DIDType, DIDKeyValuePair } from '@/lib/core/entity/rucio'; -import { NullTag } from '@/component-library/features/legacy/Tags/NullTag'; - -// misc packages, react -import { createColumnHelper } from '@tanstack/react-table'; -import { twMerge } from 'tailwind-merge'; - -// Viewmodels etc -import { DIDAvailability } from '@/lib/core/entity/rucio'; -import { TableFilterString } from '@/component-library/features/legacy/StreamedTables/TableFilterString'; -import { DIDKeyValuePairsDataViewModel } from '@/lib/infrastructure/data/view-model/did'; -import { NormalTable } from '@/component-library/features/legacy/StreamedTables/NormalTable'; - -export const PageDIDMetadata = (props: { - tabledata: DIDKeyValuePairsDataViewModel; // remember that this is ONLY the custom metadata -}) => { - const columnHelper = createColumnHelper(); - const tablecolumns: any[] = [ - columnHelper.accessor('key', { - id: 'key', - cell: info => { - return {info.getValue()}; - }, - header: info => { - return ; - }, - }), - columnHelper.accessor('value', { - id: 'value', - cell: info => { - const val = info.getValue(); - if (typeof val === 'boolean') { - return ; - } - if (val === null) { - return ; - } - if (Object.keys(DIDAvailability).includes(val as string)) { - return ; - } - if (Object.keys(DIDType).includes(val as DIDType)) { - return ; - } else { - return {val as string}; - } - }, - header: info => { - return

Value

; - }, - }), - ]; - - if (props.tabledata.status === 'pending') { - return ( -
- Loading DID Metadata -
- ); - } - return tabledata={props.tabledata.data || []} tablecolumns={tablecolumns} />; -}; diff --git a/src/component-library/pages/legacy/DID/PageDIDRules.stories.tsx b/src/component-library/pages/legacy/DID/PageDIDRules.stories.tsx deleted file mode 100644 index fdee24668..000000000 --- a/src/component-library/pages/legacy/DID/PageDIDRules.stories.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { StoryFn, Meta } from '@storybook/react'; -import { PageDIDRules as P } from './PageDIDRules'; -import { fixtureDIDRulesViewModel, mockUseComDOM } from '@/test/fixtures/table-fixtures'; - -export default { - title: 'Components/Pages/DID', - component: P, -} as Meta; - -const Template: StoryFn = args =>

; - -export const PageDIDRules = Template.bind({}); -PageDIDRules.args = { - comdom: mockUseComDOM(Array.from({ length: 100 }, (_, i) => fixtureDIDRulesViewModel())), -}; diff --git a/src/component-library/pages/legacy/DID/PageDIDRules.tsx b/src/component-library/pages/legacy/DID/PageDIDRules.tsx deleted file mode 100644 index e796efa41..000000000 --- a/src/component-library/pages/legacy/DID/PageDIDRules.tsx +++ /dev/null @@ -1,136 +0,0 @@ -// components -import { H3 } from '../../../atoms/legacy/text/headings/H3/H3'; -import { HiDotsHorizontal } from 'react-icons/hi'; -import { createColumnHelper } from '@tanstack/react-table'; -import { useEffect, useState } from 'react'; -import { twMerge } from 'tailwind-merge'; -import { RuleStateTag } from '@/component-library/features/legacy/Tags/RuleStateTag'; -import { DateTag } from '@/component-library/features/legacy/Tags/DateTag'; -import { RuleState } from '@/lib/core/entity/rucio'; -import { StreamedTable } from '@/component-library/features/legacy/StreamedTables/StreamedTable'; -import { TableFilterDiscrete } from '@/component-library/features/legacy/StreamedTables/TableFilterDiscrete'; -import { TableFilterString } from '@/component-library/features/legacy/StreamedTables/TableFilterString'; -import { TableSortUpDown } from '@/component-library/features/legacy/StreamedTables/TableSortUpDown.stories'; -import { UseComDOM } from '@/lib/infrastructure/hooks/useComDOM'; -import { DIDRulesViewModel } from '@/lib/infrastructure/data/view-model/did'; - -export const PageDIDRules = (props: { comdom: UseComDOM }) => { - const columnHelper = createColumnHelper(); - const tablecolumns: any[] = [ - columnHelper.accessor('id', { - id: 'id', - }), - columnHelper.accessor('name', { - id: 'Rule', - cell: info => { - return ( - - {info.getValue()} - 1024 ? 'hidden' : ''} /> - - ); - }, - header: info => { - return ; - }, - meta: { - style: 'pl-1', - }, - }), - columnHelper.accessor('state', { - id: 'state', - cell: info => { - return ; - }, - header: info => { - return ( - - name="Rule State" - keys={Object.values(RuleState)} - renderFunc={key => - key === undefined ? ( - - ) : ( - - ) - } - column={info.column} - /> - ); - }, - meta: { - style: 'w-28 md:w-44 cursor-pointer select-none', - }, - }), - columnHelper.accessor('account', { - id: 'Account', - cell: info => { - return

{info.getValue()}

; - }, - header: info => { - return ; - }, - meta: { - style: 'pl-1', - }, - }), - columnHelper.accessor('subscription', { - id: 'subscription', - cell: info => { - return

{info.getValue()?.name ?? ''}

; - }, - header: info => { - return

Subscription

; - }, - meta: { - style: '', - }, - }), - columnHelper.accessor('last_modified', { - id: 'last_modified', - cell: info => { - return ; - }, - header: info => { - return ; - }, - meta: { - style: 'w-48', - }, - }), - ]; - - // handle window resize - const [windowSize, setWindowSize] = useState([1920, 1080]); - - useEffect(() => { - setWindowSize([window.innerWidth, window.innerHeight]); - - const handleWindowResize = () => { - setWindowSize([window.innerWidth, window.innerHeight]); - }; - - window.addEventListener('resize', handleWindowResize); - - return () => { - window.removeEventListener('resize', handleWindowResize); - }; - }, []); - - return ( - - tablecomdom={props.comdom} - tablecolumns={tablecolumns} - tablestyling={{ - visibility: { - id: false, - Rule: true, - state: windowSize[0] > 1024, - Account: true, - subscription: windowSize[0] > 1024, - last_modified: windowSize[0] > 640, - }, - }} - /> - ); -}; From 74f657e59fd75cdd893335d09d31dd01a3f11c71 Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 13 Nov 2024 15:57:09 +0200 Subject: [PATCH 23/32] Add titles to the single view pages --- src/app/(rucio)/did/page/[scope]/[name]/page.tsx | 5 +++++ src/app/(rucio)/rse/page/[name]/page.tsx | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/app/(rucio)/did/page/[scope]/[name]/page.tsx b/src/app/(rucio)/did/page/[scope]/[name]/page.tsx index f1d0a83f5..70f85e7be 100644 --- a/src/app/(rucio)/did/page/[scope]/[name]/page.tsx +++ b/src/app/(rucio)/did/page/[scope]/[name]/page.tsx @@ -1,10 +1,15 @@ 'use client'; import { DetailsDID } from '@/component-library/pages/DID/details/DetailsDID'; +import {useEffect} from "react"; export default function Page({ params }: { params: { scope: string; name: string } }) { const decodedScope = decodeURIComponent(params.scope); const decodedName = decodeURIComponent(params.name); + useEffect(() => { + document.title = `${decodedScope}:${decodedName} - Rucio`; + }, []); + return ; } diff --git a/src/app/(rucio)/rse/page/[name]/page.tsx b/src/app/(rucio)/rse/page/[name]/page.tsx index 7f06afd48..4fb9f919a 100644 --- a/src/app/(rucio)/rse/page/[name]/page.tsx +++ b/src/app/(rucio)/rse/page/[name]/page.tsx @@ -3,3 +3,7 @@ import { DetailsRSE } from '@/component-library/pages/RSE/details/DetailsRSE'; export default function Page({ params }: { params: { name: string } }) { return ; } + +export const metadata = { + title: 'RSE - Rucio', +}; \ No newline at end of file From dc5623500a4b21def56924542c19f96f5ce32b71 Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 13 Nov 2024 16:03:40 +0200 Subject: [PATCH 24/32] Change the positioning of error field in RSE details --- src/component-library/pages/RSE/details/DetailsRSE.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/component-library/pages/RSE/details/DetailsRSE.tsx b/src/component-library/pages/RSE/details/DetailsRSE.tsx index 27f4364f6..f399ac1e2 100644 --- a/src/component-library/pages/RSE/details/DetailsRSE.tsx +++ b/src/component-library/pages/RSE/details/DetailsRSE.tsx @@ -140,11 +140,9 @@ export const DetailsRSE = (props: DetailsRSEProps) => { const hasError = metaError || attributesError; if (hasError) { return ( -
- - Could not load the RSE {props.name}. - -
+ + Could not load the RSE {props.name}. + ); } From 8531666842dcd40032e192f50f3b6e961ecfc793 Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 13 Nov 2024 16:06:40 +0200 Subject: [PATCH 25/32] Add error handling to details DID initial meta loading --- .../(rucio)/did/page/[scope]/[name]/page.tsx | 2 +- src/app/(rucio)/rse/page/[name]/page.tsx | 2 +- .../pages/DID/details/DetailsDID.tsx | 24 +++++++++++++++---- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/app/(rucio)/did/page/[scope]/[name]/page.tsx b/src/app/(rucio)/did/page/[scope]/[name]/page.tsx index 70f85e7be..d66bc35fe 100644 --- a/src/app/(rucio)/did/page/[scope]/[name]/page.tsx +++ b/src/app/(rucio)/did/page/[scope]/[name]/page.tsx @@ -1,7 +1,7 @@ 'use client'; import { DetailsDID } from '@/component-library/pages/DID/details/DetailsDID'; -import {useEffect} from "react"; +import { useEffect } from 'react'; export default function Page({ params }: { params: { scope: string; name: string } }) { const decodedScope = decodeURIComponent(params.scope); diff --git a/src/app/(rucio)/rse/page/[name]/page.tsx b/src/app/(rucio)/rse/page/[name]/page.tsx index 4fb9f919a..8f98b11e5 100644 --- a/src/app/(rucio)/rse/page/[name]/page.tsx +++ b/src/app/(rucio)/rse/page/[name]/page.tsx @@ -6,4 +6,4 @@ export default function Page({ params }: { params: { name: string } }) { export const metadata = { title: 'RSE - Rucio', -}; \ No newline at end of file +}; diff --git a/src/component-library/pages/DID/details/DetailsDID.tsx b/src/component-library/pages/DID/details/DetailsDID.tsx index e8f039d0b..05757111f 100644 --- a/src/component-library/pages/DID/details/DetailsDID.tsx +++ b/src/component-library/pages/DID/details/DetailsDID.tsx @@ -18,6 +18,7 @@ import { cn } from '@/component-library/utils'; import { DetailsDIDParents } from '@/component-library/pages/DID/details/views/DetailsDIDParents'; 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'; type DetailsDIDTablesProps = { scope: string; @@ -110,11 +111,26 @@ export const DetailsDID = ({ scope, name }: DetailsDIDProps) => { refetchOnWindowFocus: false, }); - const isLoading = meta === undefined || isMetaFetching; + if (metaError) { + return ( + + + Could not load the DID {scope}:{name}. + + + ); + } + + const isLoading = isMetaFetching || meta === undefined; + if (isLoading) { + return ( +
+ +
+ ); + } - return isLoading ? ( - - ) : ( + return (
From e6f17fbf98c01b3f3d466e4cbbc20d6ccc754d39 Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 13 Nov 2024 16:08:30 +0200 Subject: [PATCH 26/32] Add an error display in case of unknown DID type --- src/component-library/pages/DID/details/DetailsDID.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/component-library/pages/DID/details/DetailsDID.tsx b/src/component-library/pages/DID/details/DetailsDID.tsx index 05757111f..34dea16f5 100644 --- a/src/component-library/pages/DID/details/DetailsDID.tsx +++ b/src/component-library/pages/DID/details/DetailsDID.tsx @@ -49,8 +49,13 @@ export const DetailsDIDTables = ({ scope, name, type }: DetailsDIDTablesProps) = const tabNames = tabsByType[type]; const [activeIndex, setActiveIndex] = useState(0); - // TODO: display an error - if (tabNames.length === 0) return <>; + if (tabNames.length === 0) { + return ( + + Unsupported type of the DID. + + ); + } return ( <> From f8b949680e8bb1a89132e47a2552afbdf863b3ca Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 13 Nov 2024 16:15:10 +0200 Subject: [PATCH 27/32] Add error handling to loading DID attribute --- .../DID/details/views/DetailsDIDAttributes.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/component-library/pages/DID/details/views/DetailsDIDAttributes.tsx b/src/component-library/pages/DID/details/views/DetailsDIDAttributes.tsx index a5ccb42fd..0d3914a15 100644 --- a/src/component-library/pages/DID/details/views/DetailsDIDAttributes.tsx +++ b/src/component-library/pages/DID/details/views/DetailsDIDAttributes.tsx @@ -9,6 +9,8 @@ import { useQuery } from '@tanstack/react-query'; import { useToast } from '@/lib/infrastructure/hooks/useToast'; import { BaseViewModelValidator } from '@/component-library/features/utils/BaseViewModelValidator'; import { DetailsDIDView, DetailsDIDProps } from '@/component-library/pages/DID/details/views/DetailsDIDView'; +import { WarningField } from '@/component-library/features/fields/WarningField'; +import { LoadingSpinner } from '@/component-library/atoms/loading/LoadingSpinner'; type DetailsDIDAttributesTableProps = { viewModel: DIDKeyValuePairsDataViewModel; @@ -69,10 +71,21 @@ export const DetailsDIDAttributes: DetailsDIDView = ({ scope, name }: DetailsDID refetchOnWindowFocus: false, }); - const isLoading = keyValuePairs === undefined || areKeyValuePairsFetching; + if (keyValuePairsError) { + return ( + + Could not load DID attributes. + + ); + } + const isLoading = keyValuePairs === undefined || areKeyValuePairsFetching; if (isLoading) { - return

Loading...

; + return ( +
+ +
+ ); } return ; From 8fed81abeedd9b6ffe312a182ff53583baab0d1b Mon Sep 17 00:00:00 2001 From: MytsV Date: Wed, 13 Nov 2024 16:35:42 +0200 Subject: [PATCH 28/32] Update the rule details page to load actual data --- src/app/(rucio)/rule/page/[id]/page.tsx | 60 +------------ .../pages/Rule/details/DetailsRule.tsx | 87 +++++++++++++++++++ 2 files changed, 90 insertions(+), 57 deletions(-) create mode 100644 src/component-library/pages/Rule/details/DetailsRule.tsx diff --git a/src/app/(rucio)/rule/page/[id]/page.tsx b/src/app/(rucio)/rule/page/[id]/page.tsx index cbf43affe..cfad58b14 100644 --- a/src/app/(rucio)/rule/page/[id]/page.tsx +++ b/src/app/(rucio)/rule/page/[id]/page.tsx @@ -1,61 +1,7 @@ 'use client'; -import { PageRule as PageRuleStory } from '@/component-library/pages/legacy/Rule/PageRule'; -import { fixtureRuleMetaViewModel } from 'test/fixtures/table-fixtures'; -import { useState, useEffect } from 'react'; -import useComDOM from '@/lib/infrastructure/hooks/useComDOM'; -import { HTTPRequest } from '@/lib/sdk/http'; -import { RuleMetaViewModel, RulePageLockEntryViewModel } from '@/lib/infrastructure/data/view-model/rule'; -import { getSiteHeader } from '@/app/(rucio)/queries'; -import { SiteHeaderViewModel } from '@/lib/infrastructure/data/view-model/site-header'; -export default function PageRule({ params }: { params: { id: string } }) { - const comDOM = useComDOM('rule-page-lock-query', [], false, Infinity, 50, true); - const [isAdmin, setIsAdmin] = useState(false); - useEffect(() => { - getSiteHeader().then((vm: SiteHeaderViewModel) => setIsAdmin(vm.activeAccount?.role === 'admin')); - }, []); - const [meta, setMeta] = useState({} as RuleMetaViewModel); - - useEffect(() => { - // TODO get from mock endpoint - fetch(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/mock-get-rule-meta`) - .then(res => { - if (res.ok) { - return res.json(); - } - throw new Error(res.statusText); - }) - .then(data => { - setMeta({ ...data, id: params.id }); - }) - .catch(err => { - console.error(err); - }); - // setMeta({ ...fixtureRuleMetaViewModel(), id: params.id }) - }, []); +import { DetailsRule } from '@/component-library/pages/Rule/details/DetailsRule'; - useEffect(() => { - const runQuery = async () => { - const request: HTTPRequest = { - url: new URL(`${process.env.NEXT_PUBLIC_WEBUI_HOST}/api/feature/mock-list-rule-page-lock`), - method: 'GET', - headers: new Headers({ - 'Content-Type': 'application/json', - } as HeadersInit), - body: null, - }; - await comDOM.setRequest(request); - }; - runQuery(); - }, []); - return ( - { - console.log('boost not implemented'); - }} - ruleBoostShow={isAdmin} - /> - ); +export default function PageRule({ params }: { params: { id: string } }) { + return ; } diff --git a/src/component-library/pages/Rule/details/DetailsRule.tsx b/src/component-library/pages/Rule/details/DetailsRule.tsx new file mode 100644 index 000000000..a55314d25 --- /dev/null +++ b/src/component-library/pages/Rule/details/DetailsRule.tsx @@ -0,0 +1,87 @@ +'use client'; + +import { Heading } from '@/component-library/atoms/misc/Heading'; +import { useQuery } from '@tanstack/react-query'; +import { useToast } from '@/lib/infrastructure/hooks/useToast'; +import { BaseViewModelValidator } from '@/component-library/features/utils/BaseViewModelValidator'; +import { LoadingSpinner } from '@/component-library/atoms/loading/LoadingSpinner'; +import { TabSwitcher } from '@/component-library/features/tabs/TabSwitcher'; +import { useState } from 'react'; +import { WarningField } from '@/component-library/features/fields/WarningField'; +import { RuleMetaViewModel } from '@/lib/infrastructure/data/view-model/rule'; + +export const DetailsRuleTabs = ({ id }: { id: string }) => { + const tabNames = ['Attributes', 'Locks']; + const [activeIndex, setActiveIndex] = useState(0); + + return ( + <> + + + ); +}; + +export const DetailsRule = ({ id }: { id: string }) => { + const { toast } = useToast(); + const validator = new BaseViewModelValidator(toast); + + const queryMeta = async () => { + const url = '/api/feature/get-rule?' + new URLSearchParams({ id }); + + const res = await fetch(url); + if (!res.ok) { + try { + const json = await res.json(); + toast({ + title: 'Fatal error', + description: json.message, + variant: 'error', + }); + } catch (e) {} + throw new Error(res.statusText); + } + + const json = await res.json(); + if (validator.isValid(json)) return json; + + return null; + }; + + const metaQueryKey = ['rule-meta']; + const { + data: meta, + error: metaError, + isFetching: isMetaFetching, + } = useQuery({ + queryKey: metaQueryKey, + queryFn: queryMeta, + retry: false, + refetchOnWindowFocus: false, + }); + + if (metaError) { + return ( + + Could not load the rule with ID {id}. + + ); + } + + const isLoading = isMetaFetching || meta === undefined; + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( +
+
+ +
+ +
+ ); +}; From c2f8e910eea084980cb56888efbf057d68b2e9ee Mon Sep 17 00:00:00 2001 From: MytsV Date: Thu, 14 Nov 2024 10:04:43 +0200 Subject: [PATCH 29/32] Finish rule details view --- .../badges/Rule/RuleGroupingBadge.tsx | 21 ++++ .../badges/Rule/RuleNotificationBadge.tsx | 23 ++++ .../pages/Rule/details/DetailsRule.tsx | 20 +++- .../pages/Rule/details/DetailsRuleLocks.tsx | 72 +++++++++++++ .../pages/Rule/details/DetailsRuleMeta.tsx | 102 ++++++++++++++++++ 5 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 src/component-library/features/badges/Rule/RuleGroupingBadge.tsx create mode 100644 src/component-library/features/badges/Rule/RuleNotificationBadge.tsx create mode 100644 src/component-library/pages/Rule/details/DetailsRuleLocks.tsx create mode 100644 src/component-library/pages/Rule/details/DetailsRuleMeta.tsx diff --git a/src/component-library/features/badges/Rule/RuleGroupingBadge.tsx b/src/component-library/features/badges/Rule/RuleGroupingBadge.tsx new file mode 100644 index 000000000..d7b6256bc --- /dev/null +++ b/src/component-library/features/badges/Rule/RuleGroupingBadge.tsx @@ -0,0 +1,21 @@ +import { RuleGrouping } from '@/lib/core/entity/rucio'; +import React from 'react'; +import { Badge } from '@/component-library/atoms/misc/Badge'; +import { cn } from '@/component-library/utils'; + +const groupingString: Record = { + A: 'All', + D: 'Dataset', + N: 'None', +}; + +const groupingColorClasses: Record = { + A: 'bg-base-success-500', + D: 'bg-base-warning-400', + N: 'bg-base-error-500', +}; + +export const RuleGroupingBadge = (props: { value: RuleGrouping; className?: string }) => { + const classes = cn(groupingColorClasses[props.value], props.className); + return ; +}; diff --git a/src/component-library/features/badges/Rule/RuleNotificationBadge.tsx b/src/component-library/features/badges/Rule/RuleNotificationBadge.tsx new file mode 100644 index 000000000..4852abdfe --- /dev/null +++ b/src/component-library/features/badges/Rule/RuleNotificationBadge.tsx @@ -0,0 +1,23 @@ +import { RuleNotification } from '@/lib/core/entity/rucio'; +import React from 'react'; +import { Badge } from '@/component-library/atoms/misc/Badge'; +import { cn } from '@/component-library/utils'; + +const notificationString: Record = { + C: 'Close', + N: 'No', + P: 'Progress', + Y: 'Yes', +}; + +const notificationColorClasses: Record = { + Y: 'bg-base-success-500', + P: 'bg-base-info-500', + C: 'bg-base-warning-400', + N: 'bg-base-error-500', +}; + +export const RuleNotificationBadge = (props: { value: RuleNotification; className?: string }) => { + const classes = cn(notificationColorClasses[props.value], props.className); + return ; +}; diff --git a/src/component-library/pages/Rule/details/DetailsRule.tsx b/src/component-library/pages/Rule/details/DetailsRule.tsx index a55314d25..1cb1f17ca 100644 --- a/src/component-library/pages/Rule/details/DetailsRule.tsx +++ b/src/component-library/pages/Rule/details/DetailsRule.tsx @@ -9,14 +9,28 @@ import { TabSwitcher } from '@/component-library/features/tabs/TabSwitcher'; import { useState } from 'react'; import { WarningField } from '@/component-library/features/fields/WarningField'; import { RuleMetaViewModel } from '@/lib/infrastructure/data/view-model/rule'; +import { cn } from '@/component-library/utils'; +import { DetailsRuleLocks } from '@/component-library/pages/Rule/details/DetailsRuleLocks'; +import { DetailsRuleMeta } from '@/component-library/pages/Rule/details/DetailsRuleMeta'; -export const DetailsRuleTabs = ({ id }: { id: string }) => { +export const DetailsRuleTabs = ({ id, meta }: { id: string; meta: RuleMetaViewModel }) => { const tabNames = ['Attributes', 'Locks']; const [activeIndex, setActiveIndex] = useState(0); + const getViewClasses = (index: number) => { + const visibilityClass = index === activeIndex ? 'flex' : 'hidden'; + return cn('flex-col grow min-h-[450px]', visibilityClass); + }; + return ( <> +
+ +
+
+ +
); }; @@ -79,9 +93,9 @@ export const DetailsRule = ({ id }: { id: string }) => { return (
- +
- +
); }; diff --git a/src/component-library/pages/Rule/details/DetailsRuleLocks.tsx b/src/component-library/pages/Rule/details/DetailsRuleLocks.tsx new file mode 100644 index 000000000..1e5851f4a --- /dev/null +++ b/src/component-library/pages/Rule/details/DetailsRuleLocks.tsx @@ -0,0 +1,72 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { AgGridReact } from 'ag-grid-react'; +import { UseStreamReader } from '@/lib/infrastructure/hooks/useStreamReader'; +import { StreamedTable } from '@/component-library/features/table/StreamedTable/StreamedTable'; +import { badgeCellClasses, badgeCellWrapperStyle } from '@/component-library/features/table/cells/badge-cell'; +import { DefaultTextFilterParams, buildDiscreteFilterParams } from '@/component-library/features/utils/filter-parameters'; +import { GridReadyEvent, ValueGetterParams } from 'ag-grid-community'; +import { ListRuleReplicaLockStatesViewModel } from '@/lib/infrastructure/data/view-model/rule'; +import { LockState } from '@/lib/core/entity/rucio'; +import { LockStateBadge } from '@/component-library/features/badges/Rule/LockStateBadge'; +import useTableStreaming from '@/lib/infrastructure/hooks/useTableStreaming'; + +type DetailsRuleLocksTableProps = { + streamingHook: UseStreamReader; + onGridReady: (event: GridReadyEvent) => void; +}; + +const DetailsRuleLocksTable = (props: DetailsRuleLocksTableProps) => { + const tableRef = useRef>(null); + + const [columnDefs] = useState([ + { + headerName: 'DID', + valueGetter: (params: ValueGetterParams) => { + return params.data?.scope + ':' + params.data?.name; + }, + minWidth: 150, + flex: 1, + filter: true, + filterParams: DefaultTextFilterParams, + }, + { + headerName: 'RSE', + field: 'rse', + minWidth: 150, + flex: 1, + filter: true, + filterParams: DefaultTextFilterParams, + }, + { + headerName: 'State', + field: 'state', + minWidth: 200, + maxWidth: 200, + cellStyle: badgeCellWrapperStyle, + cellRenderer: LockStateBadge, + cellRendererParams: { + className: badgeCellClasses, + }, + filter: true, + sortable: false, + // TODO: fix the string values + filterParams: buildDiscreteFilterParams(Object.values(LockState)), + }, + // TODO: add links + ]); + + return ; +}; + +export const DetailsRuleLocks = ({ id }: { id: string }) => { + const { gridApi, onGridReady, streamingHook, startStreaming, stopStreaming } = useTableStreaming(); + + useEffect(() => { + if (gridApi) { + const url = '/api/feature/list-rule-replica-lock-states?' + new URLSearchParams({ id }); + startStreaming(url); + } + }, [gridApi]); + + return ; +}; diff --git a/src/component-library/pages/Rule/details/DetailsRuleMeta.tsx b/src/component-library/pages/Rule/details/DetailsRuleMeta.tsx new file mode 100644 index 000000000..c7fac459e --- /dev/null +++ b/src/component-library/pages/Rule/details/DetailsRuleMeta.tsx @@ -0,0 +1,102 @@ +import { Divider } from '@/component-library/atoms/misc/Divider'; +import { KeyValueRow } from '@/component-library/features/key-value/KeyValueRow'; +import { Field } from '@/component-library/atoms/misc/Field'; +import { formatDate } from '@/component-library/features/utils/text-formatters'; +import { KeyValueWrapper } from '@/component-library/features/key-value/KeyValueWrapper'; +import { DIDTypeBadge } from '@/component-library/features/badges/DID/DIDTypeBadge'; +import Checkbox from '@/component-library/atoms/form/Checkbox'; +import { RuleMetaViewModel } from '@/lib/infrastructure/data/view-model/rule'; +import { NullBadge } from '@/component-library/features/badges/NullBadge'; +import { RuleStateBadge } from '@/component-library/features/badges/Rule/RuleStateBadge'; +import { RuleGroupingBadge } from '@/component-library/features/badges/Rule/RuleGroupingBadge'; +import { RuleNotificationBadge } from '@/component-library/features/badges/Rule/RuleNotificationBadge'; + +export const DetailsRuleMeta = ({ meta }: { meta: RuleMetaViewModel }) => { + const getExpiredField = () => { + if (meta.expires_at) { + return formatDate(meta.expires_at); + } else { + return ; + } + }; + + return ( + +
+ + + + + + {meta.scope}:{meta.name} + + + + + {meta.rse_expression} + + + {meta.account} + + + + + + {meta.locks_ok_cnt} + + + {meta.locks_replicating_cnt} + + + {meta.locks_stuck_cnt} + + + + + + {formatDate(meta.created_at)} + + + {formatDate(meta.updated_at)} + + {getExpiredField()} + + + + + {meta.copies} + + + + + + + + + {meta.priority} + + + {meta.activity} + + + + + + + + + + + + + + + + + + + +
+
+ ); +}; From 4557de0ab0a81830a092f90486dce19629f8fd81 Mon Sep 17 00:00:00 2001 From: MytsV Date: Thu, 14 Nov 2024 10:08:18 +0200 Subject: [PATCH 30/32] Add title to the rule details page --- src/app/(rucio)/rule/page/[id]/page.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/app/(rucio)/rule/page/[id]/page.tsx b/src/app/(rucio)/rule/page/[id]/page.tsx index cfad58b14..4029057e0 100644 --- a/src/app/(rucio)/rule/page/[id]/page.tsx +++ b/src/app/(rucio)/rule/page/[id]/page.tsx @@ -1,7 +1,12 @@ 'use client'; import { DetailsRule } from '@/component-library/pages/Rule/details/DetailsRule'; +import {useEffect} from "react"; export default function PageRule({ params }: { params: { id: string } }) { + useEffect(() => { + document.title = 'Rule - Rucio'; + }, []); + return ; } From 25eda8e96d3c4b54dc481f473b63e6f52cf86580 Mon Sep 17 00:00:00 2001 From: MytsV Date: Thu, 14 Nov 2024 10:26:59 +0200 Subject: [PATCH 31/32] Remove legacy rule page components --- .../pages/legacy/Rule/PageRule.stories.tsx | 21 -- .../pages/legacy/Rule/PageRule.tsx | 300 ------------------ 2 files changed, 321 deletions(-) delete mode 100644 src/component-library/pages/legacy/Rule/PageRule.stories.tsx delete mode 100644 src/component-library/pages/legacy/Rule/PageRule.tsx diff --git a/src/component-library/pages/legacy/Rule/PageRule.stories.tsx b/src/component-library/pages/legacy/Rule/PageRule.stories.tsx deleted file mode 100644 index bb438b8b9..000000000 --- a/src/component-library/pages/legacy/Rule/PageRule.stories.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { StoryFn, Meta } from '@storybook/react'; -import { fixtureRulePageLockEntryViewModel, fixtureRuleMetaViewModel, mockUseComDOM } from '@/test/fixtures/table-fixtures'; -import { PageRule as P } from './PageRule'; -import { RulePageLockEntryViewModel } from '@/lib/infrastructure/data/view-model/rule'; - -export default { - title: 'Components/Pages/Rule', - component: P, -} as Meta; - -const Template: StoryFn = args =>

; - -export const PageRule = Template.bind({}); -PageRule.args = { - ruleMeta: fixtureRuleMetaViewModel(), - ruleLocks: mockUseComDOM(Array.from({ length: 100 }, () => fixtureRulePageLockEntryViewModel())), - ruleBoostFunc: () => { - console.log('boosted rule'); - }, - ruleBoostShow: true, -}; diff --git a/src/component-library/pages/legacy/Rule/PageRule.tsx b/src/component-library/pages/legacy/Rule/PageRule.tsx deleted file mode 100644 index a42fff695..000000000 --- a/src/component-library/pages/legacy/Rule/PageRule.tsx +++ /dev/null @@ -1,300 +0,0 @@ -import { useState, useEffect } from 'react'; - -import { twMerge } from 'tailwind-merge'; -var format = require('date-format'); - -import { LockState } from '@/lib/core/entity/rucio'; -import { Tabs } from '../../../atoms/legacy/Tabs/Tabs'; -import { SubPage } from '../../../atoms/legacy/helpers/SubPage/SubPage'; -import { H3 } from '../../../atoms/legacy/text/headings/H3/H3'; -import { P } from '../../../atoms/legacy/text/content/P/P'; -import { BoolTag } from '@/component-library/features/legacy/Tags/BoolTag'; -import { DIDTypeTag } from '@/component-library/features/legacy/Tags/DIDTypeTag'; -import { RuleStateTag } from '@/component-library/features/legacy/Tags/RuleStateTag'; -import { LockStateTag } from '@/component-library/features/legacy/Tags/LockStateTag'; -import { RuleNotificationTag } from '@/component-library/features/legacy/Tags/RuleNotificationTag'; -import { StreamedTable } from '@/component-library/features/legacy/StreamedTables/StreamedTable'; -import { createColumnHelper } from '@tanstack/react-table'; -import { HiDotsHorizontal, HiExternalLink } from 'react-icons/hi'; -import { TableExternalLink } from '@/component-library/features/legacy/StreamedTables/TableExternalLink'; -import { TableFilterDiscrete } from '@/component-library/features/legacy/StreamedTables/TableFilterDiscrete'; -import { TableFilterString } from '@/component-library/features/legacy/StreamedTables/TableFilterString'; -import { Titleth, Contenttd, Generaltable } from '../../../atoms/legacy/helpers/Metatable/Metatable'; -import { UseComDOM } from '@/lib/infrastructure/hooks/useComDOM'; -import { Heading } from '@/component-library/pages/legacy/Helpers/Heading'; -import { Body } from '@/component-library/pages/legacy/Helpers/Body'; -import { RuleMetaViewModel, RulePageLockEntryViewModel } from '@/lib/infrastructure/data/view-model/rule'; -import { Button } from '@/component-library/atoms/legacy/Button/Button'; -import { HiRocketLaunch } from 'react-icons/hi2'; -import { InfoField } from '@/component-library/features/fields/InfoField'; - -export interface PageRulePageProps { - ruleMeta: RuleMetaViewModel; - ruleLocks: UseComDOM; - ruleBoostFunc: () => void; - ruleBoostShow: boolean; -} - -export const PageRule = (props: PageRulePageProps) => { - const [subpageIndex, setSubpageIndex] = useState(0); - - useEffect(() => { - if (subpageIndex === 1 && props.ruleLocks.query.data.all.length === 0) { - // Opened locks tab, but no data yet => start load - console.log(props.ruleLocks.query.data); - props.ruleLocks.start(); - } - }, [subpageIndex]); - - const meta = props.ruleMeta; - - const columnHelper = createColumnHelper(); - const tablecolumns = [ - columnHelper.accessor(row => `${row.scope}:${row.name}`, { - id: 'did', - header: info => { - return ; - }, - cell: info => { - return

{info.getValue()}

; - }, - }), - columnHelper.accessor('rse', { - id: 'rse', - header: info => { - return ; - }, - cell: info => { - return

{info.getValue()}

; - }, - }), - columnHelper.accessor('state', { - id: 'state', - cell: info => , - header: info => { - return ( - - name="Lock" - keys={Object.values(LockState)} - renderFunc={state => - state === undefined ? ( - - ) : ( - - ) - } - column={info.column} - /> - ); - }, - meta: { - style: 'w-6 sm:w-8 md:w-32', - }, - }), - columnHelper.display({ - id: 'links', - header: info => { - return ( - -

Links

-
- ); - }, - cell: info => { - return ( - - - - - ); - }, - meta: { - style: 'w-32', - }, - }), - ]; - - const [windowSize, setWindowSize] = useState([1920, 1080]); - - useEffect(() => { - setWindowSize([window.innerWidth, window.innerHeight]); - - const handleWindowResize = () => { - setWindowSize([window.innerWidth, window.innerHeight]); - }; - - window.addEventListener('resize', handleWindowResize); - - return () => { - window.removeEventListener('resize', handleWindowResize); - }; - }, []); - - return ( -
- - - This page is currently using mock data for demonstration purposes. We are actively working on implementing real data soon! - - - -
-
-
- - { - setSubpageIndex(active); - }} - /> - -
- - - Scope - {meta.scope} - - - Name - {meta.name} - - - - - Created At - {format('yyyy-MM-dd', new Date(meta.created_at))} - - - Updated At - {format('yyyy-MM-dd', new Date(meta.updated_at))} - - - Expires At - - { - format('yyyy-MM-dd', new Date(meta.expires_at)) - // add ability to extend lifetime here => or maybe not?? i think this might be bad UX - } - - - - - - Locks OK - -

{meta.locks_ok_cnt}

-
- - - Locks Replicating - -

{meta.locks_replicating_cnt}

-
- - - Locks Stuck - -

{meta.locks_stuck_cnt}

-
- -
- - - Purge Replicas - {} - - - Split Container - {} - - - Ignore Account Limit - {} - - - Ignore Availability - {} - - - Locked - {} - - - - - Copies - {meta.copies} - - - ID - {meta.id} - - - DID Type - - - - - - Grouping - - - - - - Priority - {meta.priority} - - - - - RSE Expression - {meta.rse_expression} - - - Notification - - - - - - - - Account - {meta.account} - - - Activity - {meta.activity} - - - - - State - - - - - -
-
- - 768, - links: windowSize[0] > 768, - }, - }} - /> - - -
- ); -}; From 8dc69fae41dacb68c1149c7ec73233522f3594c4 Mon Sep 17 00:00:00 2001 From: MytsV Date: Thu, 14 Nov 2024 10:36:23 +0200 Subject: [PATCH 32/32] Fix type issues --- src/app/(rucio)/rule/page/[id]/page.tsx | 4 ++-- src/lib/core/dto/rse-dto.ts | 17 +++++++++++++++++ .../use-case/list-account-rse-quotas-usecase.ts | 6 +++--- src/lib/core/use-case/list-all-rses-usecase.ts | 7 +++---- .../use-case/list-rses/list-rses-usecase.ts | 6 +++--- ...pipeline-element-get-rse-pipeline-element.ts | 16 ++++++++-------- src/lib/core/utils/create-rule-utils.ts | 4 ++-- .../rse-gateway/endpoints/list-rses-endpoint.ts | 8 ++++---- .../gateway/rse-gateway/rse-gateway-utils.ts | 10 +++++++--- test/fixtures/table-fixtures.ts | 3 ++- 10 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/app/(rucio)/rule/page/[id]/page.tsx b/src/app/(rucio)/rule/page/[id]/page.tsx index 4029057e0..a8e3c1dc8 100644 --- a/src/app/(rucio)/rule/page/[id]/page.tsx +++ b/src/app/(rucio)/rule/page/[id]/page.tsx @@ -1,12 +1,12 @@ 'use client'; import { DetailsRule } from '@/component-library/pages/Rule/details/DetailsRule'; -import {useEffect} from "react"; +import { useEffect } from 'react'; export default function PageRule({ params }: { params: { id: string } }) { useEffect(() => { document.title = 'Rule - Rucio'; }, []); - + return ; } diff --git a/src/lib/core/dto/rse-dto.ts b/src/lib/core/dto/rse-dto.ts index 91c9f7383..ba7f901f1 100644 --- a/src/lib/core/dto/rse-dto.ts +++ b/src/lib/core/dto/rse-dto.ts @@ -1,5 +1,6 @@ import { BaseDTO, BaseStreamableDTO } from '@/lib/sdk/dto'; import { RSE, RSEAttribute, RSEDetails, RSEProtocol, RSEType } from '@/lib/core/entity/rucio'; +import { undefined } from 'zod'; /** * The Data Transfer Object for the ListRSEsEndpoint which contains the stream @@ -11,6 +12,22 @@ export interface ListRSEsDTO extends BaseStreamableDTO {} */ export interface RSEDetailsDTO extends BaseDTO, RSEDetails {} +export const getEmptyRSEDetailsDTO = (): RSEDetailsDTO => { + return { + availability_delete: false, + availability_read: false, + availability_write: false, + deterministic: false, + id: '', + name: '', + protocols: [], + rse_type: RSEType.UNKNOWN, + staging_area: false, + status: 'error', + volatile: false, + }; +}; + /** * Data Transfer Object for GET RSE Protocols Endpoint */ diff --git a/src/lib/core/use-case/list-account-rse-quotas-usecase.ts b/src/lib/core/use-case/list-account-rse-quotas-usecase.ts index 6fab72777..724dd47cc 100644 --- a/src/lib/core/use-case/list-account-rse-quotas-usecase.ts +++ b/src/lib/core/use-case/list-account-rse-quotas-usecase.ts @@ -5,7 +5,7 @@ import { collectStreamedData } from '@/lib/sdk/utils'; import { injectable } from 'inversify'; import { Transform, Readable, PassThrough } from 'stream'; import { AccountRSELimitDTO, AccountRSEUsageDTO } from '../dto/account-dto'; -import { ListRSEsDTO, RSEDTO } from '../dto/rse-dto'; +import { ListRSEsDTO, RSEDetailsDTO } from '../dto/rse-dto'; import { DIDLong } from '../entity/rucio'; import { TAccountRSEUsageAndLimits, TRSESummaryRow } from '../entity/rule-summary'; import type { ListAccountRSEQuotasInputPort, ListAccountRSEQuotasOutputPort } from '../port/primary/list-account-rse-quotas-ports'; @@ -24,7 +24,7 @@ export default class ListAccountRSEQuotasUseCase AuthenticatedRequestModel, ListAccountRSEQuotasResponse, ListAccountRSEQuotasError, - RSEDTO, + RSEDetailsDTO, RSEAccountUsageLimitViewModel > implements ListAccountRSEQuotasInputPort @@ -133,7 +133,7 @@ export default class ListAccountRSEQuotasUseCase } } - processStreamedData(rse: RSEDTO): { data: ListAccountRSEQuotasResponse | ListAccountRSEQuotasError; status: 'success' | 'error' } { + processStreamedData(rse: RSEDetailsDTO): { data: ListAccountRSEQuotasResponse | ListAccountRSEQuotasError; status: 'success' | 'error' } { const quotaInfo: (TRSESummaryRow & { status: 'success' }) | BaseErrorResponseModel = getQuotaInfo( rse, this.account, diff --git a/src/lib/core/use-case/list-all-rses-usecase.ts b/src/lib/core/use-case/list-all-rses-usecase.ts index 25bdfe7d7..9ffc5bbcd 100644 --- a/src/lib/core/use-case/list-all-rses-usecase.ts +++ b/src/lib/core/use-case/list-all-rses-usecase.ts @@ -6,8 +6,7 @@ import { ListAllRSEsError, ListAllRSEsRequest, ListAllRSEsResponse } from '@/lib import { ListAllRSEsInputPort, type ListAllRSEsOutputPort } from '@/lib/core/port/primary/list-all-rses-ports'; import { RSEViewModel } from '@/lib/infrastructure/data/view-model/rse'; -import { ListRSEsDTO } from '@/lib/core/dto/rse-dto'; -import { RSEDTO } from '@/lib/core/dto/rse-dto'; +import { ListRSEsDTO, RSEDetailsDTO } from '@/lib/core/dto/rse-dto'; import type RSEGatewayOutputPort from '@/lib/core/port/secondary/rse-gateway-output-port'; @injectable() @@ -17,7 +16,7 @@ export default class ListAllRSEsUseCase ListAllRSEsResponse, ListAllRSEsError, ListRSEsDTO, - RSEDTO, + RSEDetailsDTO, RSEViewModel > implements ListAllRSEsInputPort @@ -51,7 +50,7 @@ export default class ListAllRSEsUseCase } as ListAllRSEsError; } - processStreamedData(dto: RSEDTO): { data: ListAllRSEsResponse | ListAllRSEsError; status: 'success' | 'error' } { + processStreamedData(dto: RSEDetailsDTO): { data: ListAllRSEsResponse | ListAllRSEsError; status: 'success' | 'error' } { if (dto.status === 'error') { const errorModel: ListAllRSEsError = { status: 'error', diff --git a/src/lib/core/use-case/list-rses/list-rses-usecase.ts b/src/lib/core/use-case/list-rses/list-rses-usecase.ts index a7e501c51..a65d7f9ed 100644 --- a/src/lib/core/use-case/list-rses/list-rses-usecase.ts +++ b/src/lib/core/use-case/list-rses/list-rses-usecase.ts @@ -7,7 +7,7 @@ import { RSEViewModel } from '@/lib/infrastructure/data/view-model/rse'; import type RSEGatewayOutputPort from '@/lib/core/port/secondary/rse-gateway-output-port'; import { ListRSEsDTO } from '@/lib/core/dto/rse-dto'; -import { RSEDTO } from '@/lib/core/dto/rse-dto'; +import { RSEDetailsDTO } from '@/lib/core/dto/rse-dto'; import { ListRSEsInputPort, type ListRSEsOutputPort } from '@/lib/core/port/primary/list-rses-ports'; @@ -20,7 +20,7 @@ export default class ListRSEsUseCase ListRSEsResponse, ListRSEsError, ListRSEsDTO, - RSEDTO, + RSEDetailsDTO, RSEViewModel > implements ListRSEsInputPort @@ -69,7 +69,7 @@ export default class ListRSEsUseCase } as ListRSEsError; } - processStreamedData(dto: RSEDTO): { data: ListRSEsResponse | ListRSEsError; status: 'success' | 'error' } { + processStreamedData(dto: RSEDetailsDTO): { data: ListRSEsResponse | ListRSEsError; status: 'success' | 'error' } { if (dto.status === 'error') { const errorModel: ListRSEsError = { status: 'error', diff --git a/src/lib/core/use-case/list-rses/pipeline-element-get-rse-pipeline-element.ts b/src/lib/core/use-case/list-rses/pipeline-element-get-rse-pipeline-element.ts index eadaebba0..07ac02b5c 100644 --- a/src/lib/core/use-case/list-rses/pipeline-element-get-rse-pipeline-element.ts +++ b/src/lib/core/use-case/list-rses/pipeline-element-get-rse-pipeline-element.ts @@ -1,7 +1,7 @@ import { BaseStreamingPostProcessingPipelineElement } from '@/lib/sdk/postprocessing-pipeline-elements'; import { AuthenticatedRequestModel } from '@/lib/sdk/usecase-models'; -import { RSEDTO, getEmptyRSEDTO } from '@/lib/core/dto/rse-dto'; +import { getEmptyRSEDetailsDTO, RSEDetailsDTO } from '@/lib/core/dto/rse-dto'; import RSEGatewayOutputPort from '@/lib/core/port/secondary/rse-gateway-output-port'; import { ListRSEsError, ListRSEsRequest, ListRSEsResponse } from '@/lib/core/usecase-models/list-rses-usecase-models'; @@ -9,27 +9,27 @@ export default class GetRSEPipelineElement extends BaseStreamingPostProcessingPi ListRSEsRequest, ListRSEsResponse, ListRSEsError, - RSEDTO + RSEDetailsDTO > { constructor(private gateway: RSEGatewayOutputPort) { super(); } - async makeGatewayRequest(requestModel: AuthenticatedRequestModel, responseModel: ListRSEsResponse): Promise { + async makeGatewayRequest(requestModel: AuthenticatedRequestModel, responseModel: ListRSEsResponse): Promise { try { const { rucioAuthToken } = requestModel; const rseName = responseModel.name; if (!rseName) { - const errorDTO: RSEDTO = getEmptyRSEDTO(); + const errorDTO: RSEDetailsDTO = getEmptyRSEDetailsDTO(); errorDTO.status = 'error'; errorDTO.errorCode = 400; errorDTO.errorName = 'Invalid Request'; errorDTO.errorMessage = 'RSE Name not found in response model'; return errorDTO; } - const dto: RSEDTO = await this.gateway.getRSE(rucioAuthToken, rseName); + const dto: RSEDetailsDTO = await this.gateway.getRSE(rucioAuthToken, rseName); return dto; } catch (error: any) { - const errorDTO: RSEDTO = getEmptyRSEDTO(); + const errorDTO: RSEDetailsDTO = getEmptyRSEDetailsDTO(); errorDTO.status = 'error'; errorDTO.errorCode = 500; errorDTO.errorName = 'Gateway Error'; @@ -38,7 +38,7 @@ export default class GetRSEPipelineElement extends BaseStreamingPostProcessingPi } } - handleGatewayError(dto: RSEDTO): ListRSEsError { + handleGatewayError(dto: RSEDetailsDTO): ListRSEsError { const errorModel: ListRSEsError = { status: 'error', name: dto.name, @@ -48,7 +48,7 @@ export default class GetRSEPipelineElement extends BaseStreamingPostProcessingPi return errorModel; } - transformResponseModel(responseModel: ListRSEsResponse, dto: RSEDTO): ListRSEsResponse { + transformResponseModel(responseModel: ListRSEsResponse, dto: RSEDetailsDTO): ListRSEsResponse { responseModel.id = dto.id; responseModel.deterministic = dto.deterministic; responseModel.rse_type = dto.rse_type; diff --git a/src/lib/core/utils/create-rule-utils.ts b/src/lib/core/utils/create-rule-utils.ts index 7b4f36f32..248b78a1d 100644 --- a/src/lib/core/utils/create-rule-utils.ts +++ b/src/lib/core/utils/create-rule-utils.ts @@ -1,6 +1,6 @@ import { BaseErrorResponseModel } from '@/lib/sdk/usecase-models'; import { AccountRSELimitDTO, AccountRSEUsageDTO } from '../dto/account-dto'; -import { RSEDTO } from '../dto/rse-dto'; +import { RSEDetailsDTO } from '../dto/rse-dto'; import { DIDLong, DIDType, RSEAccountUsage } from '../entity/rucio'; import { TAccountRSEUsageAndLimits, TDIDSummaryRow, TRSESummaryRow } from '../entity/rule-summary'; import { BaseDTO } from '@/lib/sdk/dto'; @@ -83,7 +83,7 @@ export const createAccountRSEUsageAndLimitMap = ( * If the account has no limits specified, a BaseErrorResponseModel is returned. */ export const getQuotaInfo = ( - rse: RSEDTO, + rse: RSEDetailsDTO, account: string, accountRSEUsageAndLimits: TAccountRSEUsageAndLimits, totalDIDBytesRequested: number, diff --git a/src/lib/infrastructure/gateway/rse-gateway/endpoints/list-rses-endpoint.ts b/src/lib/infrastructure/gateway/rse-gateway/endpoints/list-rses-endpoint.ts index 9f8a272f3..d6a7cf467 100644 --- a/src/lib/infrastructure/gateway/rse-gateway/endpoints/list-rses-endpoint.ts +++ b/src/lib/infrastructure/gateway/rse-gateway/endpoints/list-rses-endpoint.ts @@ -1,10 +1,10 @@ -import { ListRSEsDTO, RSEDTO } from '@/lib/core/dto/rse-dto'; +import { ListRSEsDTO, RSEDetailsDTO } from '@/lib/core/dto/rse-dto'; import { BaseStreamableEndpoint } from '@/lib/sdk/gateway-endpoints'; import { HTTPRequest } from '@/lib/sdk/http'; import { Response } from 'node-fetch'; import { convertToRSEDTO, TRucioRSE } from '../rse-gateway-utils'; -export default class ListRSEsEndpoint extends BaseStreamableEndpoint { +export default class ListRSEsEndpoint extends BaseStreamableEndpoint { constructor(private readonly rucioAuthToken: string, private readonly rseExpression: string) { super(true); } @@ -53,9 +53,9 @@ export default class ListRSEsEndpoint extends BaseStreamableEndpoint([DIDType.CONTAINER, DIDType.DATASET, DIDType.FILE]), expires_at: faker.date.future().toISOString(), - grouping: faker.helpers.arrayElement([DIDType.CONTAINER, DIDType.DATASET, DIDType.FILE]), + grouping: faker.helpers.arrayElement([RuleGrouping.ALL, RuleGrouping.DATASET, RuleGrouping.NONE]), id: faker.string.uuid(), ignore_account_limit: faker.datatype.boolean(), ignore_availability: faker.datatype.boolean(),