Skip to content

Commit

Permalink
add search services feature
Browse files Browse the repository at this point in the history
  • Loading branch information
swaroopar committed Jul 25, 2024
1 parent 4416e60 commit 756849f
Show file tree
Hide file tree
Showing 9 changed files with 237 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* SPDX-License-Identifier: Apache-2.0
* SPDX-FileCopyrightText: Huawei Inc.
*/

import { UserOrderableServiceVo } from '../../../../../xpanse-api/generated';
import { sortVersion } from '../../../../utils/Sort.ts';
import { UserServiceLatestVersionDisplayType } from '../../services/UserServiceLatestVersionDisplayType.ts';
import { groupServicesByName, groupVersionsForService } from '../../services/userServiceHelper.ts';

export function groupServicesByLatestVersion(
allServices: UserOrderableServiceVo[],
filterByServiceName: string
): UserServiceLatestVersionDisplayType[] {
const serviceList: UserServiceLatestVersionDisplayType[] = [];
const servicesGroupedByName: Map<string, UserOrderableServiceVo[]> = groupServicesByName(allServices);
servicesGroupedByName.forEach((orderableServicesList, _) => {
const versionMapper: Map<string, UserOrderableServiceVo[]> = groupVersionsForService(orderableServicesList);
const versionList: string[] = Array.from(versionMapper.keys());
const latestVersion = sortVersion(versionList)[0];
if (versionMapper.has(latestVersion) && versionMapper.get(latestVersion)) {
if (versionMapper.get(latestVersion)?.[0].name.includes(filterByServiceName)) {
const serviceItem = {
name: versionMapper.get(latestVersion)?.[0].name,
content: versionMapper.get(latestVersion)?.[0].description,
icon: versionMapper.get(latestVersion)?.[0].icon,
latestVersion: latestVersion,
category: versionMapper.get(latestVersion)?.[0].category,
};
serviceList.push(serviceItem as UserServiceLatestVersionDisplayType);
}
}
});
return serviceList;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { useQuery } from '@tanstack/react-query';
import { category, listOrderableServices, type ListOrderableServicesData } from '../../../../xpanse-api/generated';

export default function UserOrderableServicesQuery(category: category, serviceName: string | undefined) {
export default function UserOrderableServicesQuery(category: category | undefined, serviceName: string | undefined) {
return useQuery({
queryKey: ['orderableServices', category, serviceName],
queryFn: () => {
Expand Down
24 changes: 4 additions & 20 deletions src/components/content/order/services/Services.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ import serviceOrderStyles from '../../../../styles/service-order.module.css';
import serviceEmptyStyles from '../../../../styles/services-empty.module.css';
import tableStyles from '../../../../styles/table.module.css';
import { UserOrderableServiceVo, category } from '../../../../xpanse-api/generated';
import { sortVersion } from '../../../utils/Sort';
import { createServicePageRoute } from '../../../utils/constants';
import { groupServicesByLatestVersion } from '../common/utils/groupServicesByLatestVersion.ts';
import ServicesLoadingError from '../query/ServicesLoadingError';
import userOrderableServicesQuery from '../query/userOrderableServicesQuery';
import { useOrderFormStore } from '../store/OrderFormStore';
import ServicesSkeleton from './ServicesSkeleton';
import { UserServiceDisplayType } from './UserServiceDisplayType';
import { groupServicesByName, groupVersionsForService } from './userServiceHelper';
import { UserServiceLatestVersionDisplayType } from './UserServiceLatestVersionDisplayType.ts';

function Services(): React.JSX.Element {
const { Paragraph } = Typography;
Expand Down Expand Up @@ -51,24 +50,9 @@ function Services(): React.JSX.Element {
}

const userOrderableServiceList: UserOrderableServiceVo[] | undefined = orderableServicesQuery.data;
const serviceList: UserServiceDisplayType[] = [];
let serviceList: UserServiceLatestVersionDisplayType[] = [];
if (userOrderableServiceList !== undefined && userOrderableServiceList.length > 0) {
const servicesGroupedByName: Map<string, UserOrderableServiceVo[]> =
groupServicesByName(userOrderableServiceList);
servicesGroupedByName.forEach((orderableServicesList, _) => {
const versionMapper: Map<string, UserOrderableServiceVo[]> = groupVersionsForService(orderableServicesList);
const versionList: string[] = Array.from(versionMapper.keys());
const latestVersion = sortVersion(versionList)[0];
if (versionMapper.has(latestVersion) && versionMapper.get(latestVersion)) {
const serviceItem = {
name: versionMapper.get(latestVersion)?.[0].name,
content: versionMapper.get(latestVersion)?.[0].description,
icon: versionMapper.get(latestVersion)?.[0].icon,
latestVersion: latestVersion,
};
serviceList.push(serviceItem as UserServiceDisplayType);
}
});
serviceList = groupServicesByLatestVersion(userOrderableServiceList, '');
}

if (serviceList.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
* SPDX-FileCopyrightText: Huawei Inc.
*/

export interface UserServiceDisplayType {
import { category } from '../../../../xpanse-api/generated';

export interface UserServiceLatestVersionDisplayType {
name: string;
content: string;
icon: string;
latestVersion: string;
category: category;
}
3 changes: 2 additions & 1 deletion src/components/layouts/header/HeaderUserRoles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { OidcIdToken } from '@axa-fr/react-oidc/dist/ReactOidc';
import { Divider, Dropdown, MenuProps, Space, theme } from 'antd';
import React from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import headerStyles from '../../../styles/header.module.css';
import Logout from '../../content/login/Logout';
import { allowRoleList, getRolesOfUser, getUserName } from '../../oidc/OidcConfig';
import { homePageRoute } from '../../utils/constants';
Expand Down Expand Up @@ -93,7 +94,7 @@ export function HeaderUserRoles({ oidcIdToken }: { oidcIdToken: OidcIdToken }):
};

return (
<Space align='baseline'>
<Space align='baseline' className={headerStyles.userInfoSpacing}>
<Dropdown
menu={menuProps}
dropdownRender={(menu) => (
Expand Down
7 changes: 6 additions & 1 deletion src/components/layouts/header/LayoutHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@ import { Space } from 'antd';
import { Header } from 'antd/es/layout/layout';
import React from 'react';
import appStyles from '../../../styles/app.module.css';
import headerStyles from '../../../styles/header.module.css';
import SystemStatusBar from '../../content/systemStatus/SystemStatusBar';
import { HeaderUserRoles } from './HeaderUserRoles';
import { SearchServices } from './SearchServices.tsx';
import { useCurrentUserRoleStore } from './useCurrentRoleStore.ts';

function LayoutHeader(): React.JSX.Element {
const oidcIdToken: OidcIdToken = useOidcIdToken();
const currentRole = useCurrentUserRoleStore((state) => state.currentUserRole);
return (
<Header style={{ width: '100%', background: '#ffffff' }}>
<Header className={headerStyles.layoutHeader}>
<div className={appStyles.headerMenu}>
<Space align='baseline'>
{currentRole && currentRole === 'user' ? <SearchServices /> : <></>}
<SystemStatusBar />
{oidcIdToken.idToken ? (
<HeaderUserRoles oidcIdToken={oidcIdToken} key={oidcIdToken.idToken as string} />
Expand Down
128 changes: 128 additions & 0 deletions src/components/layouts/header/SearchServices.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* SPDX-License-Identifier: Apache-2.0
* SPDX-FileCopyrightText: Huawei Inc.
*/

import { AlertTwoTone, LoadingOutlined, SearchOutlined } from '@ant-design/icons';
import { Button, Input, Modal, Space } from 'antd';
import { SearchProps } from 'antd/lib/input';
import { useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import appStyles from '../../../styles/app.module.css';
import searchServiceStyle from '../../../styles/search-services.module.css';
import { category } from '../../../xpanse-api/generated';
import { groupServicesByLatestVersion } from '../../content/order/common/utils/groupServicesByLatestVersion.ts';
import userOrderableServicesQuery from '../../content/order/query/userOrderableServicesQuery.ts';
import { UserServiceLatestVersionDisplayType } from '../../content/order/services/UserServiceLatestVersionDisplayType.ts';
import { createServicePageRoute } from '../../utils/constants.tsx';

export function SearchServices() {
const navigate = useNavigate();
const orderableServicesQuery = userOrderableServicesQuery(undefined, undefined);
const [isSearchClicked, setIsSearchClicked] = useState<boolean>(false);
const [searchText, setSearchText] = useState<string>('');
const { Search } = Input;
const onSelectService = function (serviceName: string, latestVersion: string, category: category) {
setIsSearchClicked(false);
setSearchText('');
navigate(
createServicePageRoute
.concat('?serviceName=', serviceName)
.concat('&latestVersion=', latestVersion.replace(' ', ''))
.concat('#', category)
);
};

const onSearch: SearchProps['onSearch'] = (value, _e, _info) => {
setSearchText(value);
};

const filteredServices: UserServiceLatestVersionDisplayType[] = useMemo(() => {
let serviceList: UserServiceLatestVersionDisplayType[] = [];
if (orderableServicesQuery.data && searchText) {
serviceList = groupServicesByLatestVersion(orderableServicesQuery.data, searchText);
}
return serviceList;
}, [orderableServicesQuery.data, searchText]);

const retryRequest = () => {
void orderableServicesQuery.refetch();
};

return (
<>
<Modal
title='Search Services'
width={1000}
open={isSearchClicked}
onCancel={() => {
setIsSearchClicked(false);
setSearchText('');
}}
footer={null}
>
<Search
loading={false}
onSearch={onSearch}
value={searchText}
onChange={(e) => {
setSearchText(e.target.value);
}}
/>
{searchText.length > 0 ? (
filteredServices.length > 0 ? (
filteredServices.map((service) => {
return (
<div key={service.name}>
<Button
onClick={() => {
onSelectService(service.name, service.latestVersion, service.category);
}}
type={'link'}
className={searchServiceStyle.searchResults}
>
{service.name}
</Button>
<Space>{service.content}</Space>
</div>
);
})
) : orderableServicesQuery.isError ? (
<div className={searchServiceStyle.searchErrorBlock}>
<AlertTwoTone twoToneColor='#eb2f96' className={searchServiceStyle.searchErrorIcon} />
<div className={searchServiceStyle.searchResults}>
{'Error loading services - ' + orderableServicesQuery.error.toString()}
</div>
<Button
type={'link'}
onClick={retryRequest}
className={searchServiceStyle.searchErrorRetryButton}
>
retry
</Button>
</div>
) : orderableServicesQuery.isLoading ? (
<div className={searchServiceStyle.searchServicesLoading}>
<div className={searchServiceStyle.searchResults}>{'Loading Services'}</div>
<LoadingOutlined spin className={searchServiceStyle.searchLoadingIcon} />
</div>
) : (
<p className={searchServiceStyle.searchServicesNoMatchFound}>
Your search <span className={searchServiceStyle.searchTextColor}>{searchText}</span> did not
match any service names
</p>
)
) : undefined}
</Modal>
<Button
className={appStyles.headerMenuButton}
icon={<SearchOutlined />}
onClick={() => {
setIsSearchClicked(true);
}}
>
Search Services
</Button>
</>
);
}
13 changes: 13 additions & 0 deletions src/styles/header.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* SPDX-License-Identifier: Apache-2.0
* SPDX-FileCopyrightText: Huawei Inc.
*/

.user-info-spacing {
margin-left: 15px;
}

.layout-header {
width: 100%;
background: #ffffff;
}
44 changes: 44 additions & 0 deletions src/styles/search-services.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* SPDX-License-Identifier: Apache-2.0
* SPDX-FileCopyrightText: Huawei Inc.
*/

.search-results {
padding-top: 10px;
}

.search-loading-icon {
padding: 10px;
margin-top: 5px;
}

.search-error-icon {
padding-left: 15px;
padding-right: 10px;
margin-top: 5px;
}

.search-error-retry-button {
padding: 10px;
margin-top: 5px;
}

.search-services-loading {
display: flex;
margin: 0;
padding-left: 15px;
}

.search-services-no-match-found {
margin: 0;
padding-left: 15px;
padding-top: 8px;
}

.search-error-block {
display: flex;
}

.search-text-color {
color: #1677ff;
}

0 comments on commit 756849f

Please sign in to comment.