Skip to content

Commit

Permalink
Add service catalog to CSP role.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alice1319 committed Jun 17, 2024
1 parent de74838 commit 772b581
Show file tree
Hide file tree
Showing 9 changed files with 648 additions and 1 deletion.
15 changes: 15 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
registerInvalidRoute,
registerPageRoute,
registerSuccessfulRoute,
registeredServicesPageRoute,
reportsRoute,
serviceReviewsPageRoute,
servicesPageRoute,
Expand All @@ -44,6 +45,7 @@ const Policies = lazy(() => import('.//components/content/policies/Policies.tsx'
const Reports = lazy(() => import('./components/content/deployedServices/reports/Reports.tsx'));
const Workflows = lazy(() => import('./components/content/workflows/Workflows.tsx'));
const ServiceReviews = lazy(() => import('./components/content/review/ServiceReviews.tsx'));
const AvailableServices = lazy(() => import('./components/content/registeredServices/RegisteredServices.tsx'));
const CreateService = lazy(() => import('./components/content/order/create/CreateService.tsx'));
const OrderSubmitPage = lazy(() => import('./components/content/order/create/OrderSubmit.tsx'));
const NotFoundPage = lazy(() => import('./components/notFound/NotFoundPage.tsx'));
Expand Down Expand Up @@ -254,6 +256,19 @@ function App(): React.JSX.Element {
</OidcSecure>
}
/>
<Route
key={registeredServicesPageRoute}
path={registeredServicesPageRoute}
element={
<OidcSecure>
<Protected allowedRole={['csp']}>
<Suspense fallback={<FallbackSkeleton />}>
<AvailableServices />
</Suspense>
</Protected>
</OidcSecure>
}
/>
<Route
path='*'
element={
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* SPDX-License-Identifier: Apache-2.0
* SPDX-FileCopyrightText: Huawei Inc.
*/

import { DataNode } from 'antd/es/tree';
import { ServiceTemplateDetailVo } from '../../../../xpanse-api/generated';

export const groupServiceTemplatesByNamespace = (
serviceTemplateList: ServiceTemplateDetailVo[]
): Map<string, ServiceTemplateDetailVo[]> => {
const serviceMapperByNamespace: Map<string, ServiceTemplateDetailVo[]> = new Map<
string,
ServiceTemplateDetailVo[]
>();
for (const serviceTemplate of serviceTemplateList) {
if (serviceTemplate.namespace) {
if (!serviceMapperByNamespace.has(serviceTemplate.namespace)) {
serviceMapperByNamespace.set(
serviceTemplate.namespace,
serviceTemplateList.filter((data) => data.namespace === serviceTemplate.namespace)
);
}
}
}

return serviceMapperByNamespace;
};

export const groupServicesByCategoryForSpecificNamespace = (
currentNamespace: string,
serviceTemplateList: ServiceTemplateDetailVo[]
): Map<string, ServiceTemplateDetailVo[]> => {
const categoryMapper: Map<string, ServiceTemplateDetailVo[]> = new Map<string, ServiceTemplateDetailVo[]>();
const namespaceMapper: Map<string, ServiceTemplateDetailVo[]> =
groupServiceTemplatesByNamespace(serviceTemplateList);
namespaceMapper.forEach((serviceList, namespace) => {
if (namespace === currentNamespace) {
for (const service of serviceList) {
if (service.category.toString()) {
if (!categoryMapper.has(service.category.toString())) {
categoryMapper.set(
service.category.toString(),
serviceList.filter((data) => data.category === service.category)
);
}
}
}
}
});
return categoryMapper;
};

export const groupServicesByNameForSpecificCategory = (
currentNamespace: string,
currentCategory: string,
serviceTemplateList: ServiceTemplateDetailVo[]
): Map<string, ServiceTemplateDetailVo[]> => {
const serviceNameMapper: Map<string, ServiceTemplateDetailVo[]> = new Map<string, ServiceTemplateDetailVo[]>();
const categoryMapper: Map<string, ServiceTemplateDetailVo[]> = groupServicesByCategoryForSpecificNamespace(
currentNamespace,
serviceTemplateList
);
categoryMapper.forEach((serviceList, category) => {
if (category === currentCategory) {
for (const service of serviceList) {
if (service.name) {
if (!serviceNameMapper.has(service.name)) {
serviceNameMapper.set(
service.name,
serviceList.filter((data) => data.name === service.name)
);
}
}
}
}
});
return serviceNameMapper;
};

export const groupRegisteredServicesByVersionForSpecificServiceName = (
currentNamespace: string,
currentCategory: string,
currentServiceName: string,
serviceTemplateList: ServiceTemplateDetailVo[]
): Map<string, ServiceTemplateDetailVo[]> => {
const versionMapper: Map<string, ServiceTemplateDetailVo[]> = new Map<string, ServiceTemplateDetailVo[]>();
const serviceNameMapper: Map<string, ServiceTemplateDetailVo[]> = groupServicesByNameForSpecificCategory(
currentNamespace,
currentCategory,
serviceTemplateList
);
serviceNameMapper.forEach((serviceList, serviceName) => {
if (serviceName === currentServiceName) {
for (const service of serviceList) {
if (service.version) {
if (!versionMapper.has(service.version)) {
versionMapper.set(
service.version,
serviceList.filter((data) => data.version === service.version)
);
}
}
}
}
});
return versionMapper;
};

export function getFourthLevelKeysFromAvailableServicesTree(treeData: DataNode[]): React.Key[] {
const fourthLevelKeys: React.Key[] = [];

const traverseTree = (nodes: DataNode[], level: number) => {
for (const node of nodes) {
if (level === 4) {
fourthLevelKeys.push(node.key);
}
if (node.children && level < 4) {
traverseTree(node.children, level + 1);
}
}
};

traverseTree(treeData, 1); // Start traversal from level 1
return fourthLevelKeys;
}
142 changes: 142 additions & 0 deletions src/components/content/registeredServices/RegisteredServices.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* SPDX-License-Identifier: Apache-2.0
* SPDX-FileCopyrightText: Huawei Inc.
*/

import { CloudServerOutlined, GroupOutlined, TagOutlined } from '@ant-design/icons';
import { Alert, Empty, Skeleton } from 'antd';
import { DataNode } from 'antd/es/tree';
import React from 'react';
import catalogStyles from '../../../styles/catalog.module.css';
import servicesEmptyStyles from '../../../styles/services-empty.module.css';
import { ApiError, Response, ServiceTemplateDetailVo } from '../../../xpanse-api/generated';
import { convertStringArrayToUnorderedList } from '../../utils/generateUnorderedList.tsx';
import {
groupRegisteredServicesByVersionForSpecificServiceName,
groupServiceTemplatesByNamespace,
groupServicesByCategoryForSpecificNamespace,
groupServicesByNameForSpecificCategory,
} from '../common/registeredServices/registeredServiceProps.ts';
import useListAllServiceTemplatesQuery from '../review/query/useListAllServiceTemplatesQuery.ts';
import { RegisteredServicesFullView } from './tree/RegisteredServicesFullView.tsx';

export default function RegisteredServices(): React.JSX.Element {
const treeData: DataNode[] = [];
let availableServiceList: ServiceTemplateDetailVo[] = [];

const availableServiceTemplatesQuery = useListAllServiceTemplatesQuery();

if (availableServiceTemplatesQuery.isSuccess && availableServiceTemplatesQuery.data.length > 0) {
availableServiceList = availableServiceTemplatesQuery.data;
const availableServicesData: Map<string, ServiceTemplateDetailVo[]> =
groupServiceTemplatesByNamespace(availableServiceList);

availableServicesData.forEach((_value: ServiceTemplateDetailVo[], namespace: string) => {
const dataNode: DataNode = {
title: <div className={catalogStyles.catalogTreeNode}>{namespace}</div>,
key: namespace || '',
children: [],
};

const categoryServiceMapper = groupServicesByCategoryForSpecificNamespace(namespace, availableServiceList);
categoryServiceMapper.forEach((_value: ServiceTemplateDetailVo[], category: string) => {
const categoryNode: DataNode = {
title: category,
key: `${namespace}@${category}`,
icon: <GroupOutlined />,
children: [],
};

const serviceMapper = groupServicesByNameForSpecificCategory(namespace, category, availableServiceList);
serviceMapper.forEach((_value: ServiceTemplateDetailVo[], serviceName: string) => {
const serviceNode: DataNode = {
title: serviceName,
key: `${namespace}@${category}@${serviceName}`,
icon: <CloudServerOutlined />,
children: [],
};

const versionMapper = groupRegisteredServicesByVersionForSpecificServiceName(
namespace,
category,
serviceName,
availableServiceList
);
versionMapper.forEach((_value: ServiceTemplateDetailVo[], version: string) => {
const versionNode: DataNode = {
title: version,
key: `${namespace}@${category}@${serviceName}@${version}`,
icon: <TagOutlined />,
children: [],
};

serviceNode.children?.push(versionNode);
});

categoryNode.children?.push(serviceNode);
});

dataNode.children?.push(categoryNode);
});

treeData.push(dataNode);
});
}

if (availableServiceTemplatesQuery.isError) {
if (
availableServiceTemplatesQuery.error instanceof ApiError &&
availableServiceTemplatesQuery.error.body &&
'details' in availableServiceTemplatesQuery.error.body
) {
const response: Response = availableServiceTemplatesQuery.error.body as Response;
return (
<Alert
message={response.resultType.valueOf()}
description={convertStringArrayToUnorderedList(response.details)}
type={'error'}
closable={true}
className={catalogStyles.catalogSkeleton}
/>
);
} else {
return (
<Alert
message='Fetching Service Details Failed'
description={availableServiceTemplatesQuery.error.message}
type={'error'}
closable={true}
className={catalogStyles.catalogSkeleton}
/>
);
}
}

if (availableServiceTemplatesQuery.isLoading || availableServiceTemplatesQuery.isFetching) {
return (
<Skeleton
className={catalogStyles.catalogSkeleton}
active={true}
loading={true}
paragraph={{ rows: 2, width: ['20%', '20%'] }}
title={{ width: '5%' }}
/>
);
}

if (availableServiceTemplatesQuery.data && availableServiceTemplatesQuery.data.length === 0) {
return (
<div className={servicesEmptyStyles.serviceBlankClass}>
<Empty description={'No services available.'} />
</div>
);
}

return (
<div className={catalogStyles.catalogMiddleware}>
<div className={catalogStyles.container}>
<RegisteredServicesFullView treeData={treeData} availableServiceList={availableServiceList} />
</div>
</div>
);
}
Loading

0 comments on commit 772b581

Please sign in to comment.