Skip to content

Commit

Permalink
refactor(supernova): fix all typescript migration leftovers for Regio…
Browse files Browse the repository at this point in the history
…ns and Alerts (#780)

* refactor(supernova): type region and alertSlice

* refactor(supernova): corrected testdata and types

* chore(supernova): bump version

* refactor(supernova): type

* fix(supernova): corrected Type

* fix(supernova): retyping

* refactor(supernova): typing silences and refactor passing of options

* fix(supernova):  include SilencesSlice in AppState

* fix(supernova): return empty Silences if none are existant

* fix(supernova): no initalisation values

* fix(supernova): give alertsslice 3 argument for zustand

* fix(supernova): add argument store to storeprov

* chore(supernova): shape more consistant types

* chore(supernova): shape more consistant types

* chore(supernova): revoked AlterItem changes

* chore(supernova): del any in global slice

* fix(supernova): change order of imports
  • Loading branch information
TilmanHaupt authored Feb 21, 2025
1 parent ad03fff commit d595b9d
Show file tree
Hide file tree
Showing 10 changed files with 511 additions and 435 deletions.
5 changes: 5 additions & 0 deletions .changeset/five-tigers-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudoperators/juno-app-supernova": patch
---

reducing unnecessary @ts-ignore directives, improving type safety and code clarity in region components and alert slice
39 changes: 20 additions & 19 deletions apps/supernova/src/components/StoreProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import React, { createContext, useContext } from "react"
import { createStore, useStore, StoreApi } from "zustand"
import { devtools } from "zustand/middleware"

import createSilencesSlice from "../lib/createSilencesSlice"
import createAlertsSlice from "../lib/createAlertsSlice"
import createFiltersSlice, { FilterSlice, FilterActions } from "../lib/createFiltersSlice"
import createSilencesSlice, { SilencesSlice } from "../lib/createSilencesSlice"
import createAlertsSlice, { AlertsSlice } from "../lib/createAlertsSlice"
import createFiltersSlice, { FilterSlice } from "../lib/createFiltersSlice"
import createGlobalsSlice from "../lib/createGlobalsSlice"
import createUserActivitySlice from "../lib/createUserActivitySlice"

const StoreContext = createContext<StoreApi<AppState> | null>(null)

type AppState = FilterSlice & Record<string, any>
export type AppState = FilterSlice & AlertsSlice & SilencesSlice & Record<string, any>

interface StoreProviderProps {
options?: Record<string, any> // should be defined later on for the props
Expand All @@ -25,12 +25,12 @@ interface StoreProviderProps {
export const StoreProvider = ({ options, children }: StoreProviderProps) => {
return (
<StoreContext.Provider
value={createStore<AppState>((set, get) => ({
...createGlobalsSlice(set, get, options),
value={createStore<AppState>((set, get, store) => ({
...createGlobalsSlice(options)(set, get, store),
...createUserActivitySlice(set),
...createAlertsSlice(set, get),
...createFiltersSlice(set, get, options),
...createSilencesSlice(set, get, options),
...createAlertsSlice(set, get, store),
...createFiltersSlice(options)(set, get, store),
...createSilencesSlice(options)(set, get, store),
}))}
>
{children}
Expand Down Expand Up @@ -64,16 +64,17 @@ export const useUserIsActive = () => useAppStore((state: any) => state.userActiv
export const useUserActivityActions = () => useAppStore((state: any) => state.userActivity.actions)

// Alert exports
export const useAlertsItems = () => useAppStore((state: any) => state.alerts.items)
export const useAlertsItemsFiltered = () => useAppStore((state: any) => state.alerts.itemsFiltered)
export const useAlertsTotalCounts = () => useAppStore((state: any) => state.alerts.totalCounts)
export const useAlertsSeverityCountsPerRegion = () => useAppStore((state: any) => state.alerts.severityCountsPerRegion)
export const useAlertsRegions = () => useAppStore((state: any) => state.alerts.regions)
export const useAlertsRegionsFiltered = () => useAppStore((state: any) => state.alerts.regionsFiltered)
export const useAlertsUpdatedAt = () => useAppStore((state: any) => state.alerts.updatedAt)
export const useAlertEnrichedLabels = () => useAppStore((state: any) => state.alerts.enrichedLabels)

export const useAlertsActions = () => useAppStore((state: any) => state.alerts.actions)
export const useAlertsItems = () => useAppStore((state: AppState) => state.alerts.items)
export const useAlertsItemsFiltered = () => useAppStore((state: AppState) => state.alerts.itemsFiltered)
export const useAlertsTotalCounts = () => useAppStore((state: AppState) => state.alerts.totalCounts)
export const useAlertsSeverityCountsPerRegion = () =>
useAppStore((state: AppState) => state.alerts.severityCountsPerRegion)
export const useAlertsRegions = () => useAppStore((state: AppState) => state.alerts.regions)
export const useAlertsRegionsFiltered = () => useAppStore((state: AppState) => state.alerts.regionsFiltered)
export const useAlertsUpdatedAt = () => useAppStore((state: AppState) => state.alerts.updatedAt)
export const useAlertEnrichedLabels = () => useAppStore((state: AppState) => state.alerts.enrichedLabels)

export const useAlertsActions = () => useAppStore((state: AppState) => state.alerts.actions)

// Filter exports
export const useFilterLabels = () => useAppStore((state: AppState) => state.filters.labels)
Expand Down
13 changes: 2 additions & 11 deletions apps/supernova/src/components/alerts/AlertDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,13 @@ import AlertRegion from "./shared/AlertRegion"
import AlertSilences from "./AlertSilences"
import { Messages } from "@cloudoperators/juno-messages-provider"
import { useBoundQuery } from "../../hooks/useBoundQuery"
import { AlertItem } from "../../lib/createAlertsSlice"

const AlertDetail = () => {
const alertID = useShowDetailsFor()
// @ts-ignore
const { setShowDetailsFor } = useGlobalsActions()
// @ts-ignore
const { getAlertByFingerprint } = useAlertsActions()
const [alert, setAlert] = useState(null)
const [alert, setAlert] = useState<AlertItem | undefined>(undefined)
const alerts = useAlertsItems()

const onPanelClose = () => {
Expand All @@ -51,7 +50,6 @@ const AlertDetail = () => {
const { isLoading } = useBoundQuery("alerts")
useEffect(() => {
// wait for the alerts to be loaded
// @ts-ignore
if (alerts?.length > 0) {
setAlert(getAlertByFingerprint(alertID))
}
Expand All @@ -61,9 +59,7 @@ const AlertDetail = () => {
<Panel
heading={
<Stack gap="2">
{/* @ts-expect-error TS(2339): Property 'labels' does not exist on type 'never'. // @ts-expect-error TS(2339): */}
<AlertIcon severity={alert?.labels?.severity} />
{/* @ts-expect-error TS(2339): Property 'annotations' does not exist on type 'nev... Remove this comment to see */}
<span>{alert?.annotations?.summary || "Not found"}</span>
</Stack>
}
Expand Down Expand Up @@ -101,26 +97,22 @@ const AlertDetail = () => {
<DataGridRow>
<DataGridHeadCell>Firing Since</DataGridHeadCell>
<DataGridCell>
{/* @ts-expect-error TS(2339): Property 'startsAt' does not exist on type 'never'... Remove this */}
<AlertTimestamp startTimestamp={alert?.startsAt} />
</DataGridCell>
</DataGridRow>
<DataGridRow>
<DataGridHeadCell>Service</DataGridHeadCell>
{/* @ts-expect-error TS(2339): Property 'labels' does not exist on type 'never'. // */}
<DataGridCell>{alert?.labels?.service}</DataGridCell>
</DataGridRow>
<DataGridRow>
<DataGridHeadCell>Region</DataGridHeadCell>
<DataGridCell>
{/* @ts-expect-error TS(2339): Property 'labels' does not exist on type 'never'. // */}
<AlertRegion region={alert?.labels?.region} cluster={alert?.labels?.cluster} />
</DataGridCell>
</DataGridRow>
<DataGridRow>
<DataGridHeadCell>Description</DataGridHeadCell>
<DataGridCell>
{/* @ts-expect-error TS(2339): Property 'annotations' does not exist on type 'nev... Remove this */}
<AlertDescription description={alert?.annotations?.description} />
</DataGridCell>
</DataGridRow>
Expand All @@ -137,7 +129,6 @@ const AlertDetail = () => {
</DataGridCell>
</DataGridRow>
</DataGrid>

<AlertSilences alert={alert} />
</>
)}
Expand Down
2 changes: 0 additions & 2 deletions apps/supernova/src/components/regions/Region.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,11 @@ const regionHeader = `
`

const Region = ({ region, severityCounts }: any) => {
//@ts-ignore
const { addActiveFilter, removeActiveFilter } = useFilterActions()
const activeFilters = useActiveFilters()

const handleRegionClick = () => {
// if the region is already active, remove it
// @ts-ignore
if (activeFilters?.region?.includes(region)) {
removeActiveFilter("region", region)
} else {
Expand Down
13 changes: 3 additions & 10 deletions apps/supernova/src/components/regions/RegionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,17 @@ const regionsClasses = `
`

const RegionsList: React.FC = () => {
// @ts-ignore
const { isLoading } = useBoundQuery("alerts")
// @ts-ignore
const severityCountsPerRegion = useAlertsSeverityCountsPerRegion()
// @ts-ignore
const regions = useAlertsRegionsFiltered()

const renderRegions = () => {
if (!isLoading && regions) {
return (
<div className={`regions ${regionsClasses}`}>
{
// @ts-ignore
regions.map((region) => (
// @ts-ignore
<Region key={`${region}`} region={region} severityCounts={severityCountsPerRegion[region]} />
))
}
{regions.map((region) => (
<Region key={`${region}`} region={region} severityCounts={severityCountsPerRegion[region]} />
))}
</div>
)
}
Expand Down
99 changes: 73 additions & 26 deletions apps/supernova/src/lib/createAlertsSlice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,60 @@
*/

import { produce } from "immer"
import { StateCreator } from "zustand"
import { countAlerts } from "./utils"
import { Filter } from "./createFiltersSlice"
import { AppState } from "../components/StoreProvider"

export interface AlertsSlice {
alerts: AlertsState
}

interface AlertsState {
items: AlertItem[]
itemsFiltered: AlertItem[]
totalCounts: AlertCounts
severityCountsPerRegion: Record<string, SeverityCounts>
regions: string[]
regionsFiltered: string[]
enrichedLabels: string[]
updatedAt: number | null
actions: AlertsActions
}

export interface AlertItem {
fingerprint: string
labels: Record<string, string>
[key: string]: any
}

export interface AlertCounts {
total?: number
critical?: number
warning?: number
info?: number
[key: string]: number | undefined
}

export interface SeverityCounts {
total: number
critical?: { total: number; suppressed: number }
warning?: { total: number; suppressed: number }
info?: { total: number; suppressed: number }
[key: string]: any
}

export interface AlertsActions {
setAlertsData: (data: {
items: AlertItem[]
counts: { global: AlertCounts; regions: Record<string, SeverityCounts> }
}) => void
filterItems: () => void
setFilteredItems: (items: AlertItem[]) => void
setRegionsFiltered: (regions: string[]) => void
updateFilteredCounts: () => void
getAlertByFingerprint: (fingerprint: string) => AlertItem | undefined
}

const initialAlertsState = {
items: [],
Expand All @@ -17,14 +70,13 @@ const initialAlertsState = {
updatedAt: null,
}

// @ts-expect-error TS(7006) FIXME: Parameter 'set' implicitly has an 'any' type.
const createAlertsSlice = (set, get) => ({
const createAlertsSlice: StateCreator<AppState, [], [], AlertsSlice> = (set, get, store) => ({
alerts: {
...initialAlertsState,
actions: {
setAlertsData: ({ items, counts }: any) => {
setAlertsData: ({ items, counts }) => {
set(
produce((state: any) => {
produce((state: AppState) => {
state.alerts.items = items
state.alerts.totalCounts = counts?.global
state.alerts.severityCountsPerRegion = counts?.regions
Expand All @@ -46,8 +98,7 @@ const createAlertsSlice = (set, get) => ({
// reload previously loaded filter label values (they might have changed since last load)
// state.filters.filterLabelValues = {} // -> do NOT just reset them, reload instead
}),
false,
"alerts.setAlertsData"
false
)
// if there are already active filters or active predefined filters, filter the new list
if (Object.keys(get().filters.activeFilters)?.length > 0 || get().filters.activePredefinedFilter) {
Expand All @@ -56,14 +107,15 @@ const createAlertsSlice = (set, get) => ({
},

filterItems: () => {
let activePredefinedFilter: any = null
// the actual active predefinedFilter and not the name saved activePredefinedFilter in Zustand
let activePredefinedFilter: Filter | undefined
if (get().filters.predefinedFilters && get().filters.activePredefinedFilter) {
activePredefinedFilter = get().filters.predefinedFilters.find(
(filter: any) => filter.name === get().filters.activePredefinedFilter
(filter: Filter) => filter.name === get().filters.activePredefinedFilter
)
}

const filteredRegions = new Set()
const filteredRegions: Set<string> = new Set()

// reduce active filters to only those that are not paused (this will make the filter logic more intuitive)
const unpausedActiveFilters = Object.keys(get().filters.activeFilters).reduce((acc, key) => {
Expand All @@ -81,7 +133,7 @@ const createAlertsSlice = (set, get) => ({
}, {})

set(
produce((state: any) => {
produce((state) => {
state.alerts.itemsFiltered = state.alerts.items.filter((item: any) => {
let visible = true

Expand All @@ -96,7 +148,6 @@ const createAlertsSlice = (set, get) => ({
// if it doesn't match, set visible to false and break out of the loop
activePredefinedFilter &&
Object.entries(activePredefinedFilter.matchers).forEach(([key, value]) => {
// @ts-ignore
if (!new RegExp(value, "i").test(item.labels[key])) {
visible = false
return
Expand Down Expand Up @@ -138,8 +189,7 @@ const createAlertsSlice = (set, get) => ({
return visible
})
}),
false,
"alerts.filterItems"
false
)
get().alerts.actions.updateFilteredCounts()
if (filteredRegions.size > 0) {
Expand All @@ -150,41 +200,38 @@ const createAlertsSlice = (set, get) => ({
}
},

setFilteredItems: (items: any) => {
setFilteredItems: (items) => {
set(
produce((state: any) => {
produce((state) => {
state.alerts.itemsFiltered = items
}),
false,
"alerts.setFilteredItems"
false
)
get().alerts.actions.updateFilteredCounts()
},

setRegionsFiltered: (regions: any) => {
setRegionsFiltered: (regions) => {
set(
produce((state: any) => {
produce((state) => {
state.alerts.regionsFiltered = regions
}),
false,
"alerts.setRegionsFiltered"
false
)
},

updateFilteredCounts: () => {
const counts = countAlerts(get().alerts.itemsFiltered)
set(
produce((state: any) => {
produce((state) => {
state.alerts.totalCounts = counts.global
state.alerts.severityCountsPerRegion = counts.regions
}),
false,
"alerts.updateFilteredCounts"
false
)
},

getAlertByFingerprint: (fingerprint: any) => {
return get().alerts.items.find((alert: any) => alert.fingerprint === fingerprint)
getAlertByFingerprint: (fingerprint) => {
return get().alerts.items.find((alert) => alert.fingerprint === fingerprint)
},
},
},
Expand Down
Loading

0 comments on commit d595b9d

Please sign in to comment.