Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(heureka): optimizes performance - separates total count and page info from main data #450

Merged
merged 12 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/kind-tigers-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudoperators/juno-app-heureka": minor
---

split fetch request to improve performance: separates total count and page info from main data request
6 changes: 6 additions & 0 deletions .changeset/sour-rocks-try.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
---

Optimized the data fetching for main views in Heureka to improve performance with large datasets (millions of issues). This change splits the fetch requests for all three views (IssueMatches, Services, and Components) by separating the total count and page info queries from the main data queries.
15 changes: 9 additions & 6 deletions apps/heureka/src/components/filters/FilterSelect.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ const FilterSelect = ({ entityName, isLoading, filterLabels, filterLabelValues,
handleFilterAdd(value)
}

// Define filter options by filtering out already selected values
const filterOptions = filterLabelValues?.[filterLabel]?.filter(
(value) => !activeFilters?.[filterLabel]?.includes(value)
)

return (
<Stack alignment="center" gap="8">
<InputGroup>
Expand All @@ -50,19 +55,17 @@ const FilterSelect = ({ entityName, isLoading, filterLabels, filterLabelValues,
disabled={!filterLabelValues[filterLabel]?.length}
className="filter-value-select w-96 bg-theme-background-lvl-0"
>
{filterLabelValues[filterLabel] //Ensure already selected values are not displayed in filterValue drop down to avoid duplicate selections
?.filter((value) => !activeFilters[filterLabel]?.includes(value)) // Filter out values that are already active
.map((value) => (
<SelectOption value={value} key={value} />
))}
{filterOptions?.map((value) => (
<SelectOption value={value} key={value} />
))}
</Select>
<Button icon="filterAlt" className="py-[0.3rem]" />
</InputGroup>
{activeFilters && Object.keys(activeFilters).length > 0 && (
<Button label="Clear all" onClick={() => clearActiveFilters(entityName)} variant="subdued" />
)}
<SearchInput
placeholder="Search term or regular expression"
placeholder="Search term or wildcard (e.g., *term*)"
className="w-96 ml-auto"
value={searchTerm || ""}
onSearch={(value) => setSearchTerm(entityName, value)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,19 @@ const IssueMatchesDetails = () => {
const queryClientFnReady = useGlobalsQueryClientFnReady()

const issueElem = useQuery({
queryKey: ["IssueMatches", { filter: { id: [showIssueDetail] } }],
queryKey: ["IssueMatchesMain", { filter: { id: [showIssueDetail] } }],
enabled: !!queryClientFnReady,
})
const issue = useMemo(() => {
if (!issueElem) return null
return issueElem?.data?.IssueMatches?.edges[0]?.node
}, [issueElem])

// Take description from the first issueVariant, because if there are multiple, they have the same priority (edge case).
const issueDescription = issue?.effectiveIssueVariants?.edges?.[0]?.node?.description

return (
<>
{/* todo add messageprovider here */}
<Stack direction="vertical" gap="4">
<DataGrid columns={2}>
<DataGridRow>
Expand All @@ -36,6 +38,12 @@ const IssueMatchesDetails = () => {
<LoadElement elem={issue?.issue?.primaryName} />
</DataGridCell>
</DataGridRow>
<DataGridRow>
<DataGridHeadCell>Description</DataGridHeadCell>
<DataGridCell>
<LoadElement elem={issueDescription} />
</DataGridCell>
</DataGridRow>
<DataGridRow>
<DataGridHeadCell>Target Remediation Date</DataGridHeadCell>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ const IssueMatchesList = ({ items, isLoading }) => {
return (
<>
{/* clickableTable Table allow changes the background by css when hovering or active*/}
<DataGrid columns={6} className="clickableTable">
<DataGrid columns={7} className="clickableTable">
<DataGridRow>
<DataGridHeadCell>Primary Name</DataGridHeadCell>
{/* <DataGridHeadCell>Secondary Name</DataGridHeadCell> */}
<DataGridHeadCell>CCRN</DataGridHeadCell>
<DataGridHeadCell>Target Remediation Date</DataGridHeadCell>
<DataGridHeadCell>Status</DataGridHeadCell>
<DataGridHeadCell>Severity</DataGridHeadCell>
Expand All @@ -25,7 +25,7 @@ const IssueMatchesList = ({ items, isLoading }) => {
</DataGridRow>
{isLoading && !items ? (
<DataGridRow>
<DataGridCell colSpan={6}>
<DataGridCell colSpan={7}>
<HintLoading className="my-4" text="Loading issue matches..." />
</DataGridCell>
</DataGridRow>
Expand All @@ -39,7 +39,7 @@ const IssueMatchesList = ({ items, isLoading }) => {
</>
) : (
<DataGridRow>
<DataGridCell colSpan={6}>
<DataGridCell colSpan={7}>
<HintNotFound text="No issue matches found" />
</DataGridCell>
</DataGridRow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,7 @@ const IssueMatchesListItem = ({ item }) => {
onClick={() => handleClick()}
>
<DataGridCell>{item?.node?.issue?.primaryName}</DataGridCell>
{/* <DataGridCell>
{listOfCommaSeparatedObjs(
item?.node?.effectiveIssueVariants,
"secondaryName"
)}
</DataGridCell> */}
<DataGridCell>{item?.node?.componentInstance?.ccrn}</DataGridCell>
<DataGridCell>{formatDate(item?.node?.targetRemediationDate)}</DataGridCell>
<DataGridCell>{item?.node?.status}</DataGridCell>
<DataGridCell>{item?.node?.severity?.value}</DataGridCell>
Expand Down
8 changes: 6 additions & 2 deletions apps/heureka/src/components/navEntries/NavEntryContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useGlobalsActions, useGlobalsActiveNavEntry } from "../../hooks/useAppS
import ServicesView from "../services/ServicesView"
import IssueMatchesView from "../issueMatches/IssueMatchesView"
import ComponentsView from "../components/ComponentsView"
import constants from "../shared/constants"

const NAV_CONFIG = [
{
Expand All @@ -34,7 +35,7 @@ const NAV_CONFIG = [
]

const NavEntryContext = () => {
const { setActiveNavEntry } = useGlobalsActions()
const { setActiveNavEntry, setShowPanel } = useGlobalsActions()
const activeNavEntry = useGlobalsActiveNavEntry()

// Memorized top navigation items
Expand All @@ -45,7 +46,10 @@ const NavEntryContext = () => {
key={nav.value}
label={nav.label}
active={activeNavEntry === nav.value} // Set the active item
onClick={() => setActiveNavEntry(nav.value)} // Trigger tab change
onClick={() => {
setActiveNavEntry(nav.value) // Trigger tab change
setShowPanel(constants.PANEL_NONE) // Hide the panel after switching
}}
/>
)),
[activeNavEntry, setActiveNavEntry]
Expand Down
2 changes: 1 addition & 1 deletion apps/heureka/src/components/services/ServicesDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const ServicesDetail = () => {
const { addMessage, resetMessages } = messageActions()

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

Expand Down
48 changes: 36 additions & 12 deletions apps/heureka/src/components/shared/ListController.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,33 @@ const ListController = ({ queryKey, entityName, ListComponent, activeFilters, se
const { addMessage, resetMessages } = messageActions()
const activeNavEntry = useGlobalsActiveNavEntry()

const { isLoading, data, error } = useQuery({
// Fetch view main data
const {
isLoading: isLoadingMain,
data: mainData,
error: mainError,
} = useQuery({
queryKey: [
queryKey,
`${queryKey}Main`,
{
...queryOptions,
filter: {
...activeFilters,
...(!!enableSearchAndFilter && searchTerm && searchTerm.length > 0 && { search: searchTerm }),
},
},
],
enabled: !!queryClientFnReady && queryKey === activeNavEntry,
})

// Fetch view count and pageInfo
const {
isLoading: isLoadingCount,
data: countData,
error: countError,
} = useQuery({
queryKey: [
`${queryKey}Count`,
{
...queryOptions,
filter: {
Expand All @@ -35,22 +59,22 @@ const ListController = ({ queryKey, entityName, ListComponent, activeFilters, se
const [currentPage, setCurrentPage] = useState(1)

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

useEffect(() => {
if (!error) return resetMessages()
if (!mainError && !countError) return resetMessages()
addMessage({
variant: "error",
text: parseError(error),
text: parseError(mainError || countError),
})
}, [error, addMessage, resetMessages])
}, [mainError, countError, addMessage, resetMessages])

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

const totalPages = useMemo(() => {
if (!pageInfo?.pages) return 0
Expand All @@ -74,7 +98,7 @@ const ListController = ({ queryKey, entityName, ListComponent, activeFilters, se
return (
<>
<Container py>
<ListComponent items={items} isLoading={isLoading} />
<ListComponent items={items} isLoading={isLoadingMain || isLoadingCount} />
</Container>
<Stack className="flex justify-end">
<Pagination
Expand Down
45 changes: 36 additions & 9 deletions apps/heureka/src/hooks/useQueryClientFn.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { useEffect } from "react"
import { useQueryClient } from "@tanstack/react-query"
import { useGlobalsApiEndpoint, useGlobalsActions } from "./useAppStore"
import { request } from "graphql-request"
import servicesQuery from "../lib/queries/services"
import issueMatchesQuery from "../lib/queries/issueMatches"
import { servicesMainQuery, servicesCountQuery } from "../lib/queries/services"
import { componentsMainQuery, componentsCountQuery } from "../lib/queries/components"
import { issueMatchesMainQuery, issueMatchesCountQuery } from "../lib/queries/issueMatches"
import serviceFilterValuesQuery from "../lib/queries/serviceFilterValues"
import issueMatchesFilterValuesQuery from "../lib/queries/issueMatchesFilterValues"
import componentsQuery from "../lib/queries/components"
import addRemoveServiceOwners from "../lib/queries/addRemoveServiceOwners"
import usersQuery from "../lib/queries/users"

Expand All @@ -26,24 +26,51 @@ const useQueryClientFn = () => {
useEffect(() => {
if (!queryClient || !endpoint) return

queryClient.setQueryDefaults(["Services"], {
// Services main query
queryClient.setQueryDefaults(["ServicesMain"], {
queryFn: async ({ queryKey }) => {
const [_key, options] = queryKey
return await request(endpoint, servicesQuery(), options)
return await request(endpoint, servicesMainQuery(), options)
},
})

queryClient.setQueryDefaults(["IssueMatches"], {
// Services count query (for totalCount and pageInfo)
queryClient.setQueryDefaults(["ServicesCount"], {
queryFn: async ({ queryKey }) => {
const [_key, options] = queryKey
return await request(endpoint, issueMatchesQuery(), options)
return await request(endpoint, servicesCountQuery(), options)
},
})

queryClient.setQueryDefaults(["Components"], {
// Components main query
queryClient.setQueryDefaults(["ComponentsMain"], {
queryFn: async ({ queryKey }) => {
const [_key, options] = queryKey
return await request(endpoint, componentsQuery(), options)
return await request(endpoint, componentsMainQuery(), options)
},
})

// Components count query (for totalCount and pageInfo)
queryClient.setQueryDefaults(["ComponentsCount"], {
queryFn: async ({ queryKey }) => {
const [_key, options] = queryKey
return await request(endpoint, componentsCountQuery(), options)
},
})

// Main IssueMatches query
queryClient.setQueryDefaults(["IssueMatchesMain"], {
queryFn: async ({ queryKey }) => {
const [_key, options] = queryKey
return await request(endpoint, issueMatchesMainQuery(), options)
},
})

// IssueMatches count query (for totalCount and pageInfo)
queryClient.setQueryDefaults(["IssueMatchesCount"], {
queryFn: async ({ queryKey }) => {
const [_key, options] = queryKey
return await request(endpoint, issueMatchesCountQuery(), options)
},
})

Expand Down
6 changes: 3 additions & 3 deletions apps/heureka/src/hooks/useUrlState.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const useUrlState = () => {
const { setFiltersFromURL, syncFiltersWithURL } = useFilterActions()

const activeNavEntry = useGlobalsActiveNavEntry()
const { setShowDetailsFor, setActiveNavEntry } = useGlobalsActions()
const { setShowPanel, setActiveNavEntry } = useGlobalsActions()
const detailsFor = useGlobalsShowPanel()

// Set initial state from URL (on login)
Expand All @@ -32,7 +32,7 @@ const useUrlState = () => {
const urlState = urlStateManager.currentState()
if (urlState) {
setFiltersFromURL(urlState[constants.ACTIVE_FILTERS], urlState[constants.SEARCH_TERM])
if (urlState[constants.DETAILS_FOR]) setShowDetailsFor(urlState[constants.DETAILS_FOR])
if (urlState[constants.DETAILS_FOR]) setShowPanel(urlState[constants.DETAILS_FOR])
if (urlState[constants.ACTIVE_NAV]) setActiveNavEntry(urlState[constants.ACTIVE_NAV])
}

Expand All @@ -53,7 +53,7 @@ const useUrlState = () => {
useEffect(() => {
const unregisterStateListener = urlStateManager.onChange((state) => {
setFiltersFromURL(state?.[constants.ACTIVE_FILTERS], state?.[constants.SEARCH_TERM])
setShowDetailsFor(state?.[constants.DETAILS_FOR])
setShowPanel(state?.[constants.DETAILS_FOR])
setActiveNavEntry(state?.[constants.ACTIVE_NAV])
})

Expand Down
15 changes: 12 additions & 3 deletions apps/heureka/src/lib/queries/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
*/

import { gql } from "graphql-request"

// gql
// It is there for convenience so that you can get the tooling support
// like prettier formatting and IDE syntax highlighting.
// You can use gql from graphql-tag if you need it for some reason too.
export default () => gql`

// Main query for fetching Components data (excluding totalCount and pageInfo)
export const componentsMainQuery = () => gql`
query ($filter: ComponentFilter, $first: Int, $after: String) {
Components(filter: $filter, first: $first, after: $after) {
totalCount
edges {
node {
id
Expand Down Expand Up @@ -42,6 +42,15 @@ export default () => gql`
}
cursor
}
}
}
`

// Count query for fetching totalCount and pageInfo for Components
export const componentsCountQuery = () => gql`
query ($filter: ComponentFilter, $first: Int, $after: String) {
Components(filter: $filter, first: $first, after: $after) {
totalCount
pageInfo {
hasNextPage
hasPreviousPage
Expand Down
Loading
Loading