Skip to content

Commit

Permalink
feat(heureka): adds pagination for component instances on a service d…
Browse files Browse the repository at this point in the history
…etails panel (#577)

* feat(heureka): adds pagination for component instances list on service details page

* chore(heureka): removes the commented out code

* chore(heureka): optimises graphql queries

* chore(heureka): adjusts tests

* chore(heureka): adds changeset

* feat(heureka): renames name to ccrn for Services, Components and Support Group in all queries

* feat(heureka): renames name to ccrn for Services, Components and Support Group in all queries

* feat(heureka): adds a test for ComponentInstancesList

* feat(heureka): add tests for pagination component
  • Loading branch information
hodanoori authored Nov 8, 2024
1 parent 25255c8 commit eea5de6
Show file tree
Hide file tree
Showing 20 changed files with 335 additions and 140 deletions.
6 changes: 6 additions & 0 deletions .changeset/grumpy-squids-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@cloudoperators/juno-app-heureka": minor
"@cloudoperators/juno-app-greenhouse": patch
---

The pagination is added to the list of component instances for a selected service on the service details page.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const sumTotalInstances = (versions) => {
const ComponentsListItem = ({ item }) => {
return (
<DataGridRow>
<DataGridCell>{item?.node?.name}</DataGridCell>
<DataGridCell>{item?.node?.ccrn}</DataGridCell>
<DataGridCell>{item?.node?.type}</DataGridCell>
<DataGridCell>{item?.node?.componentVersions?.totalCount}</DataGridCell>
<DataGridCell>{sumTotalInstances(item?.node?.componentVersions?.edges)}</DataGridCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,23 @@ const IssueMatchesDetails = () => {
<DataGridHeadCell>Service Name</DataGridHeadCell>

<DataGridCell>
<LoadElement elem={issue?.componentInstance?.service?.name} />
<LoadElement elem={issue?.componentInstance?.service?.ccrn} />
</DataGridCell>
</DataGridRow>

<DataGridRow>
<DataGridHeadCell>Support Group Name</DataGridHeadCell>

<DataGridCell>
<LoadElement elem={listOfCommaSeparatedObjs(issue?.componentInstance?.service?.supportGroups, "name")} />
<LoadElement elem={listOfCommaSeparatedObjs(issue?.componentInstance?.service?.supportGroups, "ccrn")} />
</DataGridCell>
</DataGridRow>

<DataGridRow>
<DataGridHeadCell>Component Name</DataGridHeadCell>

<DataGridCell>
<LoadElement elem={issue?.componentInstance?.componentVersion?.component?.name} />
<LoadElement elem={issue?.componentInstance?.componentVersion?.component?.ccrn} />
</DataGridCell>
</DataGridRow>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ const IssueMatchesListItem = ({ item }) => {
<div className={cellSeverityClasses(severity)}>{severity}</div>
</DataGridCell>
<DataGridCell>{item?.node?.issue?.primaryName}</DataGridCell>
<DataGridCell>{item?.node?.componentInstance?.service?.name}</DataGridCell>
<DataGridCell>{item?.node?.componentInstance?.service?.ccrn}</DataGridCell>
<DataGridCell>{extractedCcrn}</DataGridCell>
<DataGridCell>
{listOfCommaSeparatedObjs(item?.node?.componentInstance?.service?.supportGroups, "name")}
{listOfCommaSeparatedObjs(item?.node?.componentInstance?.service?.supportGroups, "ccrn")}
</DataGridCell>
<DataGridCell>{item?.node?.status}</DataGridCell>
<DataGridCell>{formatDate(item?.node?.targetRemediationDate)}</DataGridCell>
Expand Down
86 changes: 86 additions & 0 deletions apps/heureka/src/components/services/ComponentInstancesList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from "react"
import { useQuery } from "@tanstack/react-query"
import {
ContentHeading,
Container,
DataGrid,
DataGridRow,
DataGridCell,
DataGridHeadCell,
} from "@cloudoperators/juno-ui-components"
import { useGlobalsQueryClientFnReady, useGlobalsQueryOptions, useGlobalsActions } from "../StoreProvider"
import LoadElement from "../shared/LoadElement"
import { severityString, highestSeverity } from "../shared/Helper"
import PaginationComponent from "../shared/PaginationComponent"
import HintNotFound from "../shared/HintNotFound"

const ComponentInstancesList = ({ serviceCcrn }) => {
const queryOptions = useGlobalsQueryOptions("ComponentInstancesOfService")
const { setQueryOptions } = useGlobalsActions()
const queryClientFnReady = useGlobalsQueryClientFnReady()

const { data, isLoading } = useQuery({
queryKey: [
"ComponentInstancesOfService",
{
...queryOptions,
filter: { serviceCcrn: [serviceCcrn] },
},
],
enabled: !!queryClientFnReady && !!serviceCcrn,
})

const items = data?.ComponentInstances?.edges || []

return (
<>
<ContentHeading className="mt-8 mb-2" heading="Component Instances" />
<DataGrid columns={4}>
<DataGridRow>
<DataGridHeadCell>Component</DataGridHeadCell>
<DataGridHeadCell>Version</DataGridHeadCell>
<DataGridHeadCell>Total Number of Issues</DataGridHeadCell>
<DataGridHeadCell>Highest Severity</DataGridHeadCell>
</DataGridRow>
{isLoading ? (
<DataGridRow>
<DataGridCell colSpan={4}>
<Container py>
<LoadElement />
</Container>
</DataGridCell>
</DataGridRow>
) : items.length === 0 ? (
<HintNotFound text="No component instances available." />
) : (
items.map((componentInstance, i) => (
<DataGridRow key={i}>
<DataGridCell>{componentInstance?.node?.ccrn}</DataGridCell>
<DataGridCell className="break-all overflow-hidden">
{componentInstance?.node?.componentVersion?.version}
</DataGridCell>
<DataGridCell>{componentInstance?.node?.issueMatches?.totalCount}</DataGridCell>
<DataGridCell>
{severityString(highestSeverity(componentInstance?.node?.issueMatches?.edges))}
</DataGridCell>
</DataGridRow>
))
)}
</DataGrid>
<PaginationComponent
queryKey="ComponentInstancesOfService"
queryOptions={queryOptions}
entityName="ComponentInstances"
setQueryOptions={setQueryOptions}
countData={data}
/>
</>
)
}

export default ComponentInstancesList
37 changes: 5 additions & 32 deletions apps/heureka/src/components/services/ServicesDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ import { useQuery, useMutation } from "@tanstack/react-query"
import LoadElement from "../shared/LoadElement"
import { useActions as messageActions } from "@cloudoperators/juno-messages-provider"
import { parseError } from "../../helpers"
import { listOfCommaSeparatedObjs, severityString, highestSeverity } from "../shared/Helper"
import { listOfCommaSeparatedObjs } from "../shared/Helper"
import ComponentInstancesList from "./ComponentInstancesList"

const ServicesDetail = () => {
const showServiceDetail = useGlobalsShowServiceDetail()
const queryClientFnReady = useGlobalsQueryClientFnReady()
const { addMessage, resetMessages } = messageActions()

const serviceElem = useQuery({
queryKey: ["ServicesDetails", { filter: { serviceName: [showServiceDetail] } }],
queryKey: ["ServicesDetails", { filter: { serviceCcrn: [showServiceDetail] } }],
enabled: !!queryClientFnReady && !!showServiceDetail,
})

Expand Down Expand Up @@ -216,41 +217,13 @@ const ServicesDetail = () => {
<DataGridRow>
<DataGridHeadCell nowrap={true}>Support Group</DataGridHeadCell>
<DataGridCell>
<LoadElement elem={<ul>{listOfCommaSeparatedObjs(service?.supportGroups, "name")}</ul>} />
<LoadElement elem={<ul>{listOfCommaSeparatedObjs(service?.supportGroups, "ccrn")}</ul>} />
</DataGridCell>
</DataGridRow>
</DataGrid>

<Container py px={false}>
<ContentHeading className="mt-8 mb-2" heading="Component Instances" />
<DataGrid columns={4}>
<DataGridRow>
<DataGridHeadCell>Component</DataGridHeadCell>
<DataGridHeadCell>Version</DataGridHeadCell>
<DataGridHeadCell>Total Number of Issues</DataGridHeadCell>
<DataGridHeadCell>Highest Severity</DataGridHeadCell>
</DataGridRow>
{!service?.componentInstances?.edges && (
<DataGridRow colSpan={4}>
<Container py>
<LoadElement />
</Container>
</DataGridRow>
)}

{service?.componentInstances?.edges?.map((componentInstance, i) => (
<DataGridRow key={i}>
<DataGridCell>{componentInstance?.node?.ccrn}</DataGridCell>
<DataGridCell className="break-all overflow-hidden">
{componentInstance?.node?.componentVersion?.version}
</DataGridCell>
<DataGridCell>{componentInstance?.node?.issueMatches?.totalCount}</DataGridCell>
<DataGridCell>
{severityString(highestSeverity(componentInstance?.node?.issueMatches?.edges))}
</DataGridCell>
</DataGridRow>
))}
</DataGrid>
<ComponentInstancesList serviceCcrn={showServiceDetail} />
</Container>
</>
)
Expand Down
10 changes: 5 additions & 5 deletions apps/heureka/src/components/services/ServicesListItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,27 @@ const ServicesListItem = ({ item }) => {
}, [item])

const handleClick = () => {
if (showServiceDetail === service?.name && showPanel === constants.PANEL_SERVICE) {
if (showServiceDetail === service?.ccrn && showPanel === constants.PANEL_SERVICE) {
{
setShowServiceDetail(null)
setShowPanel(constants.PANEL_NONE)
}
} else {
setShowServiceDetail(service?.name)
setShowServiceDetail(service?.ccrn)
setShowPanel(constants.PANEL_SERVICE)
}
}

return (
<DataGridRow
className={`cursor-pointer ${
showServiceDetail === service?.name && showPanel === constants.PANEL_SERVICE ? "active" : ""
showServiceDetail === service?.ccrn && showPanel === constants.PANEL_SERVICE ? "active" : ""
}`}
onClick={() => handleClick()}
>
<DataGridCell>{service?.name}</DataGridCell>
<DataGridCell>{service?.ccrn}</DataGridCell>
<DataGridCell>{listOfCommaSeparatedObjs(service?.owners, "name")}</DataGridCell>
<DataGridCell>{listOfCommaSeparatedObjs(service?.supportGroups, "name")}</DataGridCell>
<DataGridCell>{listOfCommaSeparatedObjs(service?.supportGroups, "ccrn")}</DataGridCell>
<DataGridCell>{service?.metadata?.componentInstanceCount}</DataGridCell>
<DataGridCell>{service?.metadata?.issueMatchCount}</DataGridCell>
</DataGridRow>
Expand Down
6 changes: 3 additions & 3 deletions apps/heureka/src/components/shared/HintNotFound.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
*/

import React from "react"
import { Stack } from "@cloudoperators/juno-ui-components"
import { Container } from "@cloudoperators/juno-ui-components"

const HintNotFound = ({ text }) => {
return (
<Stack alignment="center" distribution="center" direction="vertical" className="h-full">
<Container py px="false">
<span>{text}</span>
</Stack>
</Container>
)
}

Expand Down
61 changes: 10 additions & 51 deletions apps/heureka/src/components/shared/ListController.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useEffect, useMemo, useState } from "react"
import React, { useEffect } from "react"
import { useQuery } from "@tanstack/react-query"
import {
useGlobalsQueryClientFnReady,
useGlobalsQueryOptions,
useGlobalsActions,
useGlobalsActiveView,
} from "../StoreProvider"
import { Pagination, Container, Stack } from "@cloudoperators/juno-ui-components"
import { useActions as messageActions } from "@cloudoperators/juno-messages-provider"
import { parseError } from "../../helpers"
import PaginationComponent from "./PaginationComponent"

const ListController = ({ queryKey, entityName, ListComponent, activeFilters, searchTerm, enableSearchAndFilter }) => {
const queryClientFnReady = useGlobalsQueryClientFnReady()
Expand Down Expand Up @@ -60,12 +60,7 @@ const ListController = ({ queryKey, entityName, ListComponent, activeFilters, se
enabled: !!queryClientFnReady && queryKey === activeView,
})

const [currentPage, setCurrentPage] = useState(1)

const items = useMemo(() => {
if (!mainData) return null
return mainData?.[entityName]?.edges || []
}, [mainData, entityName])
const items = mainData?.[entityName]?.edges || []

useEffect(() => {
if (!mainError && !countError) return resetMessages()
Expand All @@ -75,52 +70,16 @@ const ListController = ({ queryKey, entityName, ListComponent, activeFilters, se
})
}, [mainError, countError, addMessage, resetMessages])

const pageInfo = useMemo(() => {
if (!countData) return null
return countData?.[entityName]?.pageInfo
}, [countData, entityName])

const totalPages = useMemo(() => {
if (!pageInfo?.pages) return 0
return pageInfo.pages.length
}, [pageInfo])

const onPaginationChanged = (newPage) => {
setCurrentPage(newPage)
if (!pageInfo?.pages) return
const pages = pageInfo.pages
const currentPageIndex = pages?.findIndex((page) => page?.pageNumber === parseInt(newPage))
if (currentPageIndex > -1) {
const after = pages[currentPageIndex]?.after
setQueryOptions(queryKey, {
...queryOptions,
after: `${after}`,
})
}
}

return (
<>
<ListComponent items={items} isLoading={isLoadingMain || isLoadingCount} />
<Container py px={false}>
<Stack className="flex justify-end">
<Pagination
currentPage={currentPage}
isFirstPage={currentPage === 1}
isLastPage={currentPage === totalPages}
onPressNext={() => onPaginationChanged(currentPage + 1)}
onPressPrevious={() => onPaginationChanged(currentPage - 1)}
onKeyPress={(oKey) => {
if (oKey.code === "Enter") {
onPaginationChanged(parseInt(oKey.currentTarget.value))
}
}}
onSelectChange={onPaginationChanged}
pages={totalPages}
variant="input"
/>
</Stack>
</Container>
<PaginationComponent
queryKey={queryKey}
queryOptions={queryOptions}
entityName={entityName}
setQueryOptions={setQueryOptions}
countData={countData}
/>
</>
)
}
Expand Down
Loading

0 comments on commit eea5de6

Please sign in to comment.