diff --git a/web/src/components/Incidents/AlertsChart/AlertsChart.jsx b/web/src/components/Incidents/AlertsChart/AlertsChart.tsx similarity index 84% rename from web/src/components/Incidents/AlertsChart/AlertsChart.jsx rename to web/src/components/Incidents/AlertsChart/AlertsChart.tsx index ab9e25f2..0fc9e463 100644 --- a/web/src/components/Incidents/AlertsChart/AlertsChart.jsx +++ b/web/src/components/Incidents/AlertsChart/AlertsChart.tsx @@ -14,28 +14,29 @@ import { Card, CardBody, CardTitle, EmptyState, EmptyStateBody } from '@patternf import { createAlertsChartBars, formatDate, generateDateArray } from '../utils'; import { getResizeObserver } from '@patternfly/react-core'; import { useDispatch, useSelector } from 'react-redux'; -import * as _ from 'lodash-es'; import { setAlertsAreLoading } from '../../../actions/observe'; import { t_global_color_status_danger_default, t_global_color_status_info_default, t_global_color_status_warning_default, } from '@patternfly/react-tokens'; +import { MonitoringState } from '../../../reducers/observe'; +import { VictoryPortal } from 'victory'; -const AlertsChart = ({ chartDays, theme }) => { +const AlertsChart = ({ chartDays, theme }: { chartDays: number; theme: 'light' | 'dark' }) => { const dispatch = useDispatch(); - const [chartContainerHeight, setChartContainerHeight] = React.useState(); - const [chartHeight, setChartHeight] = React.useState(); - const alertsData = useSelector((state) => + const [chartContainerHeight, setChartContainerHeight] = React.useState(); + const [chartHeight, setChartHeight] = React.useState(); + const alertsData = useSelector((state: MonitoringState) => state.plugins.mcp.getIn(['incidentsData', 'alertsData']), ); - const alertsAreLoading = useSelector((state) => + const alertsAreLoading = useSelector((state: MonitoringState) => state.plugins.mcp.getIn(['incidentsData', 'alertsAreLoading']), ); - const filteredData = useSelector((state) => + const filteredData = useSelector((state: MonitoringState) => state.plugins.mcp.getIn(['incidentsData', 'filteredIncidentsData']), ); - const incidentGroupId = useSelector((state) => + const incidentGroupId = useSelector((state: MonitoringState) => state.plugins.mcp.getIn(['incidentsData', 'groupId']), ); @@ -43,8 +44,8 @@ const AlertsChart = ({ chartDays, theme }) => { const chartData = React.useMemo(() => { if (!Array.isArray(alertsData) || alertsData.length === 0) return []; - return alertsData.map((alert) => createAlertsChartBars(alert, theme, dateValues)); - }, [alertsData, theme, dateValues]); + return alertsData.map((alert) => createAlertsChartBars(alert, dateValues)); + }, [alertsData, dateValues]); React.useEffect(() => { setChartContainerHeight(chartData?.length < 5 ? 300 : chartData?.length * 60); @@ -73,12 +74,12 @@ const AlertsChart = ({ chartDays, theme }) => { }, [handleResize]); return ( - +
Alerts Timeline {alertsAreLoading ? ( { ) : ( @@ -96,13 +97,14 @@ const AlertsChart = ({ chartDays, theme }) => { containerComponent={ -(x - x0) / 2} - dy={-5} // Position tooltip so pointer appears above bar - constrainToVisibleArea - labelComponent={} - /> + + -(x - x0) / 2} + dy={-5} // Position tooltip so pointer appears above bar + labelComponent={} + /> + } labels={({ datum }) => { if (datum.nodata) { @@ -165,7 +167,7 @@ const AlertsChart = ({ chartDays, theme }) => { tickFormat={(t) => new Date(t).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) } - tickValues={dateValues} + tickValues={dateValues.map((ts) => new Date(ts * 1000))} tickLabelComponent={ } diff --git a/web/src/components/Incidents/IncidentsChart/IncidentsChart.jsx b/web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx similarity index 78% rename from web/src/components/Incidents/IncidentsChart/IncidentsChart.jsx rename to web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx index 83c4567b..fb3d4ab4 100644 --- a/web/src/components/Incidents/IncidentsChart/IncidentsChart.jsx +++ b/web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx @@ -11,34 +11,50 @@ import { ChartTooltip, ChartVoronoiContainer, } from '@patternfly/react-charts/victory'; -import { Bullseye, Card, CardBody, CardTitle, Spinner } from '@patternfly/react-core'; import { - createIncidentsChartBars, - formatDate, - generateDateArray, - updateBrowserUrl, -} from '../utils'; -import { getResizeObserver } from '@patternfly/react-core'; -import { useDispatch, useSelector } from 'react-redux'; -import { setChooseIncident } from '../../../actions/observe'; + Bullseye, + Card, + CardBody, + CardTitle, + getResizeObserver, + Spinner, +} from '@patternfly/react-core'; import { t_global_color_status_danger_default, t_global_color_status_info_default, t_global_color_status_warning_default, } from '@patternfly/react-tokens'; -import { setAlertsAreLoading } from '../../../actions/observe'; +import { useDispatch, useSelector } from 'react-redux'; +import { setAlertsAreLoading, setChooseIncident } from '../../../actions/observe'; +import { MonitoringState } from '../../../reducers/observe'; +import { Incident } from '../model'; +import { + createIncidentsChartBars, + formatDate, + generateDateArray, + updateBrowserUrl, +} from '../utils'; +import { VictoryPortal } from 'victory'; -const IncidentsChart = ({ incidentsData, chartDays, theme }) => { +const IncidentsChart = ({ + incidentsData, + chartDays, + theme, +}: { + incidentsData: Array; + chartDays: number; + theme: 'light' | 'dark'; +}) => { const dispatch = useDispatch(); const [isLoading, setIsLoading] = React.useState(true); - const [chartContainerHeight, setChartContainerHeight] = React.useState(); - const [chartHeight, setChartHeight] = React.useState(); + const [chartContainerHeight, setChartContainerHeight] = React.useState(); + const [chartHeight, setChartHeight] = React.useState(); const dateValues = React.useMemo(() => generateDateArray(chartDays), [chartDays]); const chartData = React.useMemo(() => { if (!Array.isArray(incidentsData) || incidentsData.length === 0) return []; - return incidentsData.map((incident) => createIncidentsChartBars(incident, theme, dateValues)); - }, [incidentsData, theme, dateValues]); + return incidentsData.map((incident) => createIncidentsChartBars(incident, dateValues)); + }, [incidentsData, dateValues]); React.useEffect(() => { setIsLoading(false); @@ -63,8 +79,10 @@ const IncidentsChart = ({ incidentsData, chartDays, theme }) => { return () => observer(); }, []); - const selectedId = useSelector((state) => state.plugins.mcp.getIn(['incidentsData', 'groupId'])); - const incidentsActiveFilters = useSelector((state) => + const selectedId = useSelector((state: MonitoringState) => + state.plugins.mcp.getIn(['incidentsData', 'groupId']), + ); + const incidentsActiveFilters = useSelector((state: MonitoringState) => state.plugins.mcp.getIn(['incidentsData', 'incidentsActiveFilters']), ); @@ -97,8 +115,8 @@ const IncidentsChart = ({ incidentsData, chartDays, theme }) => { ); return ( - -
+ +
Incidents Timeline {isLoading ? ( @@ -107,7 +125,7 @@ const IncidentsChart = ({ incidentsData, chartDays, theme }) => { ) : ( @@ -115,21 +133,25 @@ const IncidentsChart = ({ incidentsData, chartDays, theme }) => { containerComponent={ -(x - x0) / 2} - dy={-5} // Position tooltip so pointer appears above bar - constrainToVisibleArea - labelComponent={} - /> + + -(x - x0) / 2} + dy={-5} // Position tooltip so pointer appears above bar + labelComponent={} + /> + } + voronoiPadding={0} labels={({ datum }) => { if (datum.nodata) { return null; } return `Severity: ${datum.name} Component: ${datum.componentList?.join(', ')} - Incident ID: ${datum.group_id} + Incident ID: + ${datum.group_id} Start: ${formatDate(new Date(datum.y0), true)} End: ${datum.firing ? '---' : formatDate(new Date(datum.y), true)}`; }} @@ -181,7 +203,7 @@ const IncidentsChart = ({ incidentsData, chartDays, theme }) => { tickFormat={(t) => new Date(t).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) } - tickValues={dateValues} + tickValues={dateValues.map((ts) => new Date(ts * 1000))} tickLabelComponent={ } @@ -192,7 +214,7 @@ const IncidentsChart = ({ incidentsData, chartDays, theme }) => { //we have several arrays and for each array we make a ChartBar datum.fill, @@ -203,6 +225,7 @@ const IncidentsChart = ({ incidentsData, chartDays, theme }) => { }} events={[ { + target: 'data', eventHandlers: { onClick: (props, datum) => clickHandler(props, datum), }, diff --git a/web/src/components/Incidents/IncidentsDetailsRowTable.jsx b/web/src/components/Incidents/IncidentsDetailsRowTable.tsx similarity index 97% rename from web/src/components/Incidents/IncidentsDetailsRowTable.jsx rename to web/src/components/Incidents/IncidentsDetailsRowTable.tsx index 11e65807..e5075919 100644 --- a/web/src/components/Incidents/IncidentsDetailsRowTable.jsx +++ b/web/src/components/Incidents/IncidentsDetailsRowTable.tsx @@ -25,6 +25,7 @@ import { SeverityBadge } from '../alerting/AlertUtils'; import { useAlertsPoller } from '../hooks/useAlertsPoller'; import { useSelector } from 'react-redux'; import isEqual from 'lodash/isEqual'; +import { MonitoringState } from 'src/reducers/observe'; function useDeepCompareMemoize(value) { const ref = React.useRef(); @@ -44,7 +45,7 @@ const IncidentsDetailsRowTable = ({ alerts }) => { const [alertsWithMatchedData, setAlertsWithMatchedData] = React.useState([]); const { t } = useTranslation(process.env.I18N_NAMESPACE); - const alertsWithLabels = useSelector((state) => + const alertsWithLabels = useSelector((state: MonitoringState) => getLegacyObserveState(perspective, state)?.get(alertsKey), ); @@ -67,7 +68,7 @@ const IncidentsDetailsRowTable = ({ alerts }) => { }, [memoizedAlerts, alertsWithLabels]); return ( - +
diff --git a/web/src/components/Incidents/IncidentsPage.jsx b/web/src/components/Incidents/IncidentsPage.tsx similarity index 88% rename from web/src/components/Incidents/IncidentsPage.jsx rename to web/src/components/Incidents/IncidentsPage.tsx index d7f85e48..2356b9c1 100644 --- a/web/src/components/Incidents/IncidentsPage.jsx +++ b/web/src/components/Incidents/IncidentsPage.tsx @@ -16,7 +16,6 @@ import { ToolbarItem, MenuToggle, Badge, - Title, PageSection, Stack, StackItem, @@ -30,6 +29,7 @@ import { } from './processIncidents'; import { filterIncident, + isIncidentFilter, onDeleteGroupIncidentFilterChip, onDeleteIncidentFilterChip, onIncidentFiltersSelect, @@ -56,6 +56,8 @@ import withFallback from '../console/console-shared/error/fallbacks/withFallback import IncidentsChart from './IncidentsChart/IncidentsChart'; import AlertsChart from './AlertsChart/AlertsChart'; import { usePatternFlyTheme } from '../hooks/usePatternflyTheme'; +import { MonitoringState } from 'src/reducers/observe'; +import { Incident } from './model'; const IncidentsPage = () => { const { t } = useTranslation(process.env.I18N_NAMESPACE); @@ -69,10 +71,12 @@ const IncidentsPage = () => { // days span is where we store the value for creating time ranges for // fetch incidents/alerts based on the length of time ranges // when days filter changes we set a new days span -> calculate new time range and fetch new data - const [daysSpan, setDaysSpan] = React.useState(); + const [daysSpan, setDaysSpan] = React.useState(); const [timeRanges, setTimeRanges] = React.useState([]); // data that is used for processing to serve it to the alerts table and chart - const [incidentForAlertProcessing, setIncidentForAlertProcessing] = React.useState([]); + const [incidentForAlertProcessing, setIncidentForAlertProcessing] = React.useState< + Array> + >([]); const [hideCharts, setHideCharts] = React.useState(false); const [incidentFilterIsExpanded, setIncidentIsExpanded] = React.useState(false); @@ -87,26 +91,28 @@ const IncidentsPage = () => { setDaysFilterIsExpanded(!daysFilterIsExpanded); }; - const incidentsInitialState = useSelector((state) => + const incidentsInitialState = useSelector((state: MonitoringState) => state.plugins.mcp.getIn(['incidentsData', 'incidentsInitialState']), ); - const incidents = useSelector((state) => state.plugins.mcp.getIn(['incidentsData', 'incidents'])); + const incidents = useSelector((state: MonitoringState) => + state.plugins.mcp.getIn(['incidentsData', 'incidents']), + ); - const incidentsActiveFilters = useSelector((state) => + const incidentsActiveFilters = useSelector((state: MonitoringState) => state.plugins.mcp.getIn(['incidentsData', 'incidentsActiveFilters']), ); - const incidentGroupId = useSelector((state) => + const incidentGroupId = useSelector((state: MonitoringState) => state.plugins.mcp.getIn(['incidentsData', 'groupId']), ); - const alertsData = useSelector((state) => + const alertsData = useSelector((state: MonitoringState) => state.plugins.mcp.getIn(['incidentsData', 'alertsData']), ); - const alertsAreLoading = useSelector((state) => + const alertsAreLoading = useSelector((state: MonitoringState) => state.plugins.mcp.getIn(['incidentsData', 'alertsAreLoading']), ); - const filteredData = useSelector((state) => + const filteredData = useSelector((state: MonitoringState) => state.plugins.mcp.getIn(['incidentsData', 'filteredIncidentsData']), ); React.useEffect(() => { @@ -185,7 +191,7 @@ const IncidentsPage = () => { }), ) .then((results) => { - const aggregatedData = results.reduce((acc, result) => acc.concat(result), []); + const aggregatedData = results.flat(); dispatch( setAlertsData({ alertsData: processAlerts(aggregatedData, incidentForAlertProcessing), @@ -222,7 +228,7 @@ const IncidentsPage = () => { }), ) .then((results) => { - const aggregatedData = results.reduce((acc, result) => acc.concat(result), []); + const aggregatedData = results.flat(); dispatch( setIncidents({ incidents: processIncidents(aggregatedData), @@ -259,7 +265,7 @@ const IncidentsPage = () => { }), ) .then((results) => { - const aggregatedData = results.reduce((acc, result) => acc.concat(result), []); + const aggregatedData = results.flat(); setIncidentForAlertProcessing(processIncidentsForAlerts(aggregatedData)); dispatch(setAlertsAreLoading({ alertsAreLoading: true })); setIncidentsAreLoading(false); @@ -295,16 +301,23 @@ const IncidentsPage = () => { id="toolbar-with-filter" collapseListedFiltersBreakpoint="xl" clearAllFilters={() => - onDeleteIncidentFilterChip('', '', incidentsActiveFilters, dispatch) + onDeleteIncidentFilterChip('', undefined, incidentsActiveFilters, dispatch) } > - onDeleteIncidentFilterChip(category, chip, incidentsActiveFilters, dispatch) - } + deleteLabel={(category, chip) => { + if (isIncidentFilter(chip) && typeof category === 'string') { + onDeleteIncidentFilterChip( + category, + chip, + incidentsActiveFilters, + dispatch, + ); + } + }} deleteLabelGroup={() => onDeleteGroupIncidentFilterChip(incidentsActiveFilters, dispatch) } @@ -316,9 +329,16 @@ const IncidentsPage = () => { aria-label="Filters" isOpen={incidentFilterIsExpanded} selected={incidentsActiveFilters.incidentFilters} - onSelect={(event, selection) => - onIncidentFiltersSelect(event, selection, dispatch, incidentsActiveFilters) - } + onSelect={(event, selection) => { + if (isIncidentFilter(selection)) { + onIncidentFiltersSelect( + event, + selection, + dispatch, + incidentsActiveFilters, + ); + } + }} onOpenChange={(isOpen) => setIncidentIsExpanded(isOpen)} toggle={(toggleRef) => ( { diff --git a/web/src/components/Incidents/IncidentsTable.jsx b/web/src/components/Incidents/IncidentsTable.tsx similarity index 80% rename from web/src/components/Incidents/IncidentsTable.jsx rename to web/src/components/Incidents/IncidentsTable.tsx index 98d237fa..c0a6cb71 100644 --- a/web/src/components/Incidents/IncidentsTable.jsx +++ b/web/src/components/Incidents/IncidentsTable.tsx @@ -1,22 +1,16 @@ +import { AlertSeverity, AlertStates } from '@openshift-console/dynamic-plugin-sdk'; +import { Bullseye, Card, CardBody, EmptyState, EmptyStateBody } from '@patternfly/react-core'; +import { SearchIcon } from '@patternfly/react-icons'; +import { ExpandableRowContent, Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; +import * as _ from 'lodash-es'; import React from 'react'; -import { Table, Thead, Tr, Th, Tbody, Td, ExpandableRowContent } from '@patternfly/react-table'; -import { - Bullseye, - Card, - CardBody, - EmptyState, - EmptyStateBody, - Label, -} from '@patternfly/react-core'; -import InfoCircleIcon from '@patternfly/react-icons/dist/esm/icons/info-circle-icon'; -import IncidentsDetailsRowTable from './IncidentsDetailsRowTable'; -import { BellIcon, BellSlashIcon, SearchIcon } from '@patternfly/react-icons'; import { useSelector } from 'react-redux'; -import * as _ from 'lodash-es'; -import { AlertState, AlertStateIcon, SeverityBadge } from '../alerting/AlertUtils'; -import { AlertSeverity, AlertStates } from '@openshift-console/dynamic-plugin-sdk'; +import { MonitoringState } from 'src/reducers/observe'; +import { AlertStateIcon, SeverityBadge } from '../alerting/AlertUtils'; +import IncidentsDetailsRowTable from './IncidentsDetailsRowTable'; +import { Alert } from './model'; -export const IncidentsTable = ({ namespace }) => { +export const IncidentsTable = () => { const columnNames = { checkbox: '', component: 'Component', @@ -24,16 +18,16 @@ export const IncidentsTable = ({ namespace }) => { state: 'State', }; const [expandedAlerts, setExpandedAlerts] = React.useState([]); - const setAlertExpanded = (alert, isExpanding = true) => + const setAlertExpanded = (alert: Alert, isExpanding = true) => setExpandedAlerts((prevExpanded) => { const otherAlertExpanded = prevExpanded.filter((r) => r !== alert.component); return isExpanding ? [...otherAlertExpanded, alert.component] : otherAlertExpanded; }); - const isAlertExpanded = (alert) => expandedAlerts.includes(alert.component); - const alertsTableData = useSelector((state) => + const isAlertExpanded = (alert: Alert) => expandedAlerts.includes(alert.component); + const alertsTableData = useSelector((state: MonitoringState) => state.plugins.mcp.getIn(['incidentsData', 'alertsTableData']), ); - const alertsAreLoading = useSelector((state) => + const alertsAreLoading = useSelector((state: MonitoringState) => state.plugins.mcp.getIn(['incidentsData', 'alertsAreLoading']), ); @@ -108,10 +102,7 @@ export const IncidentsTable = ({ namespace }) => { diff --git a/web/src/components/Incidents/api.js b/web/src/components/Incidents/api.ts similarity index 89% rename from web/src/components/Incidents/api.js rename to web/src/components/Incidents/api.ts index 9adea268..bab72130 100644 --- a/web/src/components/Incidents/api.js +++ b/web/src/components/Incidents/api.ts @@ -1,7 +1,8 @@ /* eslint-disable max-len */ -import { PrometheusEndpoint } from '@openshift-console/dynamic-plugin-sdk'; +import { PrometheusEndpoint, PrometheusResponse } from '@openshift-console/dynamic-plugin-sdk'; import { getPrometheusURL } from '../console/graphs/helpers'; +import { Perspective } from 'src/actions/observe'; /** * Creates a Prometheus alerts query string from grouped alert values. * The function dynamically includes any properties in the input objects that have the "src_" prefix, @@ -61,7 +62,12 @@ export const createAlertsQuery = (groupedAlertsValues) => { return alertsQuery; }; -export const fetchDataForIncidentsAndAlerts = (fetch, range, customQuery, perspective) => { +export const fetchDataForIncidentsAndAlerts = ( + fetch: (url: string) => Promise, + range: { startTime: number; endTime: number; duration: number }, + customQuery: string, + perspective: Perspective, +) => { return fetch( getPrometheusURL( { diff --git a/web/src/components/Incidents/model.ts b/web/src/components/Incidents/model.ts new file mode 100644 index 00000000..d0577a84 --- /dev/null +++ b/web/src/components/Incidents/model.ts @@ -0,0 +1,63 @@ +export type Timestamps = [number, string]; + +export type SpanDates = Array; + +export type AlertsIntervalsArray = [number, number, 'data' | 'nodata']; + +export type Incident = { + component: string; + componentList: Array; + layer: string; + firing: boolean; + group_id: string; + src_severity: string; + src_alertname: string; + src_namespace: string; + x: number; + values: Array; + metric: Metric; +}; + +// Define the interface for Metric +export type Metric = { + group_id: string; // The unique ID for grouping + component: string; // Component name + componentList?: string[]; // List of all unique components + [key: string]: any; // Allow other dynamic fields in Metric +}; + +export type ProcessedIncident = Incident & { + informative: boolean; + critical: boolean; + warning: boolean; + resolved: boolean; + firing: boolean; +}; + +export type Alert = { + alertname: string; + alertsStartFiring: number; + alertsEndFiring: number; + alertstate: string; + component: string; + layer: string; + name: string; + namespace: string; + resolved: boolean; + severity: Severity; + x: number; + values: Array; + alertsExpandedRowData?: Array; +}; + +export type DaysFilters = '1 day' | '3 days' | '7 days' | '15 days'; + +export type IncidentFilters = 'Critical' | 'Warning' | 'Firing' | 'Informative' | 'Resolved'; + +export type Severity = 'critical' | 'warning' | 'info'; + +export type IncidentFiltersCombined = { + days: Array; + incidentFilters: Array; + groupId?: Array; +}; diff --git a/web/src/components/Incidents/processAlerts.js b/web/src/components/Incidents/processAlerts.ts similarity index 80% rename from web/src/components/Incidents/processAlerts.js rename to web/src/components/Incidents/processAlerts.ts index ed2d098c..b1f28c04 100644 --- a/web/src/components/Incidents/processAlerts.js +++ b/web/src/components/Incidents/processAlerts.ts @@ -1,5 +1,7 @@ /* eslint-disable max-len */ +import { PrometheusResult } from '@openshift-console/dynamic-plugin-sdk'; +import { Alert, Incident, Severity } from './model'; import { sortObjectsByEarliestTimestamp } from './processIncidents'; /** @@ -29,7 +31,7 @@ import { sortObjectsByEarliestTimestamp } from './processIncidents'; * const groupedAlerts = groupAlerts(alerts); * // Returns an array where the two alerts are grouped together with deduplicated values. */ -export function groupAlerts(objects) { +export function groupAlerts(objects: Array): Array { // Step 1: Filter out all non firing alerts const filteredObjects = objects.filter((obj) => obj.metric.alertstate === 'firing'); const groupedObjects = new Map(); @@ -137,52 +139,58 @@ export function groupAlerts(objects) { * // ] */ -export function processAlerts(data, selectedIncidents) { +export function processAlerts( + data: Array, + selectedIncidents: Array>, +): Array { const firing = groupAlerts(data).filter((alert) => alert.metric.alertname !== 'Watchdog'); // Extract the first and last timestamps from selectedIncidents - const timestamps = selectedIncidents.flatMap((incident) => - incident.values.map((value) => new Date(value[0])), + const timestamps = selectedIncidents.flatMap( + (incident) => incident.values?.map((value) => value[0]) ?? [], ); - const firstTimestamp = new Date(Math.min(...timestamps)); - const lastTimestamp = new Date(Math.max(...timestamps)); - - return sortObjectsByEarliestTimestamp(firing).map((alert, index) => { - // Filter values based on firstTimestamp and lastTimestamp keep only values within range - const processedValues = alert.values - .map((value) => { - const timestamp = new Date(value[0] * 1000); - return [timestamp, value[1]]; - }) - .filter(([date]) => date >= firstTimestamp && date <= lastTimestamp); - - const sortedValues = processedValues.sort((a, b) => a[0] - b[0]); - - const alertsStartFiring = sortedValues[0][0]; - const alertsEndFiring = sortedValues[sortedValues.length - 1][0]; - const resolved = new Date() - alertsEndFiring > 10 * 60 * 1000; - - return { - alertname: alert.metric.alertname, - namespace: alert.metric.namespace, - severity: alert.metric.severity, - component: alert.metric.component, - layer: alert.metric.layer, - name: alert.metric.name, - alertstate: resolved ? 'resolved' : 'firing', - values: sortedValues, - alertsStartFiring, - alertsEndFiring, - resolved, - x: firing.length - index, - }; - }); + const firstTimestamp = Math.min(...timestamps); + const lastTimestamp = Math.max(...timestamps); + + return sortObjectsByEarliestTimestamp(firing) + .map((alert, index) => { + // Filter values based on firstTimestamp and lastTimestamp keep only values within range + const processedValues: Array<[number, string]> = alert.values.filter( + ([date]) => date >= firstTimestamp && date <= lastTimestamp, + ); + + const sortedValues = processedValues.sort((a, b) => a[0] - b[0]); + + if (sortedValues.length === 0) { + return null; + } + + const alertsStartFiring = sortedValues[0][0] * 1000; + const alertsEndFiring = sortedValues[sortedValues.length - 1][0] * 1000; + const resolved = Date.now() - alertsEndFiring > 10 * 60 * 1000; + + return { + alertname: alert.metric.alertname, + namespace: alert.metric.namespace, + severity: alert.metric.severity as Severity, + component: alert.metric.component, + layer: alert.metric.layer, + name: alert.metric.name, + alertstate: resolved ? 'resolved' : 'firing', + values: sortedValues, + alertsStartFiring, + alertsEndFiring, + resolved, + x: firing.length - index, + }; + }) + .filter((alert) => alert !== null); } -export const groupAlertsForTable = (alerts) => { +export const groupAlertsForTable = (alerts: Array): Array => { // group alerts by the component and coun - const groupedAlerts = alerts.reduce((acc, alert) => { + const groupedAlerts: Array = alerts.reduce((acc, alert) => { const { component, alertstate, severity, layer } = alert; const existingGroup = acc.find((group) => group.component === component); if (existingGroup) { diff --git a/web/src/components/Incidents/processIncidents.ts b/web/src/components/Incidents/processIncidents.ts index ebe05b4e..59a819a1 100644 --- a/web/src/components/Incidents/processIncidents.ts +++ b/web/src/components/Incidents/processIncidents.ts @@ -1,38 +1,10 @@ /* eslint-disable max-len */ -// Define the interface for Metric -interface Metric { - group_id: string; // The unique ID for grouping - component: string; // Component name - componentList?: string[]; // List of all unique components - [key: string]: any; // Allow other dynamic fields in Metric -} - -interface Incident { - metric: Metric; - values: Array<[number, string]>; -} - -interface ProcessedIncident { - component: string; - componentList?: string[]; - group_id: string; - severity: string; - alertname: string; - namespace: string; - name: string; - layer: string; - values: Array<[Date, string]>; - x: number; - informative: boolean; - critical: string; - warning: string; - resolved: boolean; - firing: boolean; -} +import { PrometheusLabels, PrometheusResult } from '@openshift-console/dynamic-plugin-sdk'; +import { Incident, Metric, ProcessedIncident } from './model'; //this will be moved to the utils.js file when I convert them to the Typescript -export function sortObjectsByEarliestTimestamp(incidents: Incident[]): Incident[] { +export function sortObjectsByEarliestTimestamp(incidents: PrometheusResult[]): PrometheusResult[] { return incidents.sort((a, b) => { const earliestA = Math.min(...a.values.map((value) => value[0])); const earliestB = Math.min(...b.values.map((value) => value[0])); @@ -40,19 +12,13 @@ export function sortObjectsByEarliestTimestamp(incidents: Incident[]): Incident[ }); } -export function processIncidents(data: Incident[]): ProcessedIncident[] { +export function processIncidents(data: PrometheusResult[]): ProcessedIncident[] { const incidents = groupById(data).filter( (incident) => incident.metric.src_alertname !== 'Watchdog', ); const sortedIncidents = sortObjectsByEarliestTimestamp(incidents); return sortedIncidents.map((incident, index) => { - const processedValues = incident.values.map((value) => { - const timestamp = value[0]; - const date = new Date(timestamp * 1000); - return [date, value[1]] as [Date, string]; - }); - // Determine severity flags based on values array let critical = false; let warning = false; @@ -67,11 +33,10 @@ export function processIncidents(data: Incident[]): ProcessedIncident[] { const timestamps = incident.values.map((value) => value[0]); // Extract timestamps const lastTimestamp = Math.max(...timestamps); // Last timestamp in seconds - const currentDate = new Date(); - const currentTimestamp = Math.floor(currentDate.valueOf() / 1000); // Current time in seconds + const currentTimestamp = Math.floor(Date.now() / 1000); // Current time in seconds // Firing and resolved logic - const firing = currentTimestamp - lastTimestamp <= 10 * 60; + const firing = currentTimestamp - lastTimestamp <= 10 * 60; // Firing if the last timestamp is within the last 10 minutes const resolved = !firing; // Persistent logic based on the first occurrence @@ -83,7 +48,7 @@ export function processIncidents(data: Incident[]): ProcessedIncident[] { componentList: incident.metric.componentList, group_id: incident.metric.group_id, layer: incident.metric.layer, - values: processedValues, + values: incident.values, x: incidents.length - index, critical, // Updated based on 'values' array warning, // Updated based on 'values' array @@ -102,7 +67,7 @@ export function processIncidents(data: Incident[]): ProcessedIncident[] { * @returns An object containing only the properties from metric that start with 'src_'. */ -function getSrcProperties(metric: Metric): Partial { +function getSrcProperties(metric: PrometheusLabels): Partial { return Object.keys(metric) .filter((key) => key.startsWith('src_')) .reduce((acc, key) => { @@ -119,8 +84,10 @@ function getSrcProperties(metric: Metric): Partial { * @returns Array of grouped alert objects with deduplicated values and combined properties. */ -export function groupById(objects: Incident[]): Incident[] { - const groupedObjects = new Map(); +export function groupById( + objects: PrometheusResult[], +): Array { + const groupedObjects = new Map(); for (const obj of objects) { const key = obj.metric.group_id; @@ -154,7 +121,7 @@ export function groupById(objects: Incident[]): Incident[] { metric: { ...obj.metric, componentList: [obj.metric.component], // Initialize componentList with the current component - }, + } as Metric, values: [...new Set(obj.values.map((v) => JSON.stringify(v)))].map((v) => JSON.parse(v)), }); } @@ -189,19 +156,14 @@ export const getIncidentsTimeRanges = ( return timeRanges; }; -export const processIncidentsForAlerts = (incidents) => { +export const processIncidentsForAlerts = ( + incidents: Array, +): Array> => { return incidents.map((incident, index) => { - // Process the values - const processedValues = incident.values.map((value): [Date, string] => { - const timestamp = value[0]; - const date = new Date(timestamp * 1000); - return [date, value[1]]; - }); - // Return the processed incident return { ...incident.metric, - values: processedValues, + values: incident.values, x: incidents.length - index, }; }); diff --git a/web/src/components/Incidents/utils.ts b/web/src/components/Incidents/utils.ts index e4d3ec09..3a5ccded 100644 --- a/web/src/components/Incidents/utils.ts +++ b/web/src/components/Incidents/utils.ts @@ -1,67 +1,29 @@ /* eslint-disable max-len */ -import { setIncidentsActiveFilters } from '../../actions/observe'; import { t_global_color_status_danger_default, t_global_color_status_info_default, t_global_color_status_warning_default, } from '@patternfly/react-tokens'; - -type Timestamps = [Date, string]; - -type SpanDates = [Date]; - -type Theme = 'dark' | 'light'; - -type AlertsIntervalsArray = [Date, Date, 'data' | 'nodata']; - -type Incident = { - component: string; - componentList: Array; - critical: boolean; - informative: boolean; - warning: boolean; - resolved: boolean; - layer: string; - firing: boolean; - group_id: string; - src_severity: string; - src_alertname: string; - src_namespace: string; - x: number; - values: Array; -}; - -type Alert = { - alertname: string; - alertsStartFiring: Date; - alertsEndFiring: Date; - alertstate: string; - component: string; - layer: string; - name: string; - namespace: string; - resolved: boolean; - severity: 'critical' | 'warning' | 'info'; - x: number; - values: Array; -}; - -type DaysFilters = '1 day' | '3 days' | '7 days' | '15 days'; - -type IncidentFilters = 'Critical' | 'Warning' | 'Firing' | 'Informative' | 'Resolved'; - -type IncidentFiltersCombined = { - days: Array; - incidentFilters: Array; +import { Dispatch } from 'redux'; +import { setIncidentsActiveFilters } from '../../actions/observe'; +import { + Alert, + AlertsIntervalsArray, + DaysFilters, + Incident, + IncidentFilters, + IncidentFiltersCombined, + SpanDates, + Timestamps, +} from './model'; + +export const isIncidentFilter = (filter: unknown): filter is IncidentFilters => { + return ( + typeof filter === 'string' && + ['Critical', 'Warning', 'Firing', 'Informative', 'Resolved'].includes(filter) + ); }; -/** - * Consolidates and merges intervals based on severity rankings. - * @param {Object} data - The input data containing timestamps and severity levels. - * @param {string[]} dateArray - The array of date strings defining the boundary. - * @returns {Array} - The consolidated intervals. - */ - function consolidateAndMergeIntervals(data: Incident, dateArray: SpanDates) { const severityRank = { 2: 2, 1: 1, 0: 0 }; const filteredValues = filterAndSortValues(data, severityRank); @@ -77,10 +39,10 @@ function consolidateAndMergeIntervals(data: Incident, dateArray: SpanDates) { function filterAndSortValues( data: Incident, severityRank: Record, -): Array<[Date, string]> { +): Array<[number, string]> { const highestSeverityValues: Record = data.values.reduce( (acc: Record, [timestamp, severity]) => { - const timestampStr = timestamp.toISOString(); + const timestampStr = new Date(timestamp * 1000).toISOString(); if (!acc[timestampStr] || severityRank[severity] > severityRank[acc[timestampStr]]) { acc[timestampStr] = severity; @@ -91,8 +53,11 @@ function filterAndSortValues( ); return Object.entries(highestSeverityValues) - .map(([timestamp, severity]) => [new Date(timestamp), severity] as [Date, string]) - .sort((a, b) => a[0].getTime() - b[0].getTime()); + .map( + ([timestamp, severity]) => + [new Date(timestamp).getTime() / 1000, severity] as [number, string], + ) + .sort((a, b) => a[0] - b[0]); } /** @@ -103,24 +68,20 @@ function filterAndSortValues( */ function generateIntervalsWithGaps(filteredValues: Array, dateArray: SpanDates) { const intervals = []; - const startBoundary = new Date(dateArray[0]); - const endBoundary = new Date(dateArray[dateArray.length - 1]); + const startBoundary = dateArray[0]; + const endBoundary = dateArray[dateArray.length - 1]; - let currentStart = filteredValues[0] ? filteredValues[0][0] : startBoundary.toISOString(); + let currentStart = filteredValues[0] ? filteredValues[0][0] : startBoundary; let currentSeverity = filteredValues[0] ? filteredValues[0][1] : 'nodata'; - if (!filteredValues.length) { - intervals.push([startBoundary.toISOString(), endBoundary.toISOString(), 'nodata']); + if (filteredValues.length === 0) { + intervals.push([startBoundary, endBoundary, 'nodata']); return intervals; } - const firstTimestamp = new Date(filteredValues[0][0]); + const firstTimestamp = filteredValues[0][0]; if (firstTimestamp > startBoundary) { - intervals.push([ - startBoundary.toISOString(), - new Date(firstTimestamp.getTime() - 1).toISOString(), - 'nodata', - ]); + intervals.push([startBoundary, firstTimestamp - 1, 'nodata']); } for (let i = 0; i < filteredValues.length; i++) { @@ -132,24 +93,19 @@ function generateIntervalsWithGaps(filteredValues: Array, dateArray: if (currentSeverity !== severity || i === 0) { if (i > 0) { - const endDate = new Date(timestamp); - endDate.setMilliseconds(endDate.getMilliseconds() - 1); - intervals.push([currentStart, endDate.toISOString(), currentSeverity]); + const endDate = timestamp - 1; + intervals.push([currentStart, endDate, currentSeverity]); } currentStart = timestamp; currentSeverity = severity; } } - const lastEndDate = new Date(filteredValues[filteredValues.length - 1][0]); - intervals.push([currentStart, lastEndDate.toISOString(), currentSeverity]); + const lastEndDate = filteredValues[filteredValues.length - 1][0]; + intervals.push([currentStart, lastEndDate, currentSeverity]); if (lastEndDate < endBoundary) { - intervals.push([ - new Date(lastEndDate.getTime() + 1).toISOString(), - endBoundary.toISOString(), - 'nodata', - ]); + intervals.push([lastEndDate + 1, endBoundary, 'nodata']); } return intervals; @@ -162,9 +118,9 @@ function generateIntervalsWithGaps(filteredValues: Array, dateArray: * @returns {boolean} - Whether a gap exists. */ function hasGap(filteredValues: Array, index: number) { - const previousTimestamp = new Date(filteredValues[index - 1][0]); - const currentTimestamp = new Date(filteredValues[index][0]); - return (currentTimestamp.getTime() - previousTimestamp.getTime()) / 1000 / 60 > 5; + const previousTimestamp = filteredValues[index - 1][0]; + const currentTimestamp = filteredValues[index][0]; + return (currentTimestamp - previousTimestamp) / 60 > 5; } /** @@ -174,16 +130,10 @@ function hasGap(filteredValues: Array, index: number) { * @returns {Array} - The "nodata" interval. */ function createNodataInterval(filteredValues: Array, index: number) { - const previousTimestamp = new Date(filteredValues[index - 1][0]); - const currentTimestamp = new Date(filteredValues[index][0]); + const previousTimestamp = filteredValues[index - 1][0]; + const currentTimestamp = filteredValues[index][0]; - const gapStart = new Date(previousTimestamp); - gapStart.setMilliseconds(gapStart.getMilliseconds() + 1); - - const gapEnd = new Date(currentTimestamp); - gapEnd.setMilliseconds(gapEnd.getMilliseconds() - 1); - - return [gapStart.toISOString(), gapEnd.toISOString(), 'nodata']; + return [previousTimestamp + 1, currentTimestamp - 1, 'nodata']; } /** @@ -192,11 +142,7 @@ function createNodataInterval(filteredValues: Array, index: number) * @param {Object} incident - The incident data containing values with timestamps and severity levels. * @returns {Array} - An array of incident objects with `y0`, `y`, `x`, and `name` fields representing the bars for the chart. */ -export const createIncidentsChartBars = ( - incident: Incident, - theme: Theme, - dateArray: SpanDates, -) => { +export const createIncidentsChartBars = (incident: Incident, dateArray: SpanDates) => { const groupedData = consolidateAndMergeIntervals(incident, dateArray); const data = []; const getSeverityName = (value) => { @@ -207,12 +153,13 @@ export const createIncidentsChartBars = ( info: t_global_color_status_info_default.var, warning: t_global_color_status_warning_default.var, }; + for (let i = 0; i < groupedData.length; i++) { const severity = getSeverityName(groupedData[i][2]); data.push({ - y0: new Date(groupedData[i][0]), - y: new Date(groupedData[i][1]), + y0: new Date(groupedData[i][0] * 1000), + y: new Date(groupedData[i][1] * 1000), x: incident.x, name: severity, firing: incident.firing, @@ -232,25 +179,22 @@ export const createIncidentsChartBars = ( }; function consolidateAndMergeAlertIntervals(data: Alert, dateArray: SpanDates) { - const sortedValues = data.values.sort( - (a, b) => new Date(a[0]).getTime() - new Date(b[0]).getTime(), - ); + if (!data.values || data.values.length === 0) { + return []; + } + const sortedValues = data.values.sort((a, b) => a[0] - b[0]); const intervals: Array = []; - let currentStart = sortedValues[0][0], - previousTimestamp = new Date(currentStart); + let currentStart = sortedValues[0][0]; + let previousTimestamp = currentStart; for (let i = 1; i < sortedValues.length; i++) { - const currentTimestamp = new Date(sortedValues[i][0]); - const timeDifference = (currentTimestamp.getTime() - previousTimestamp.getTime()) / 60000; // Convert to minutes + const currentTimestamp = sortedValues[i][0]; + const timeDifference = (currentTimestamp - previousTimestamp) / 60; // Convert to minutes if (timeDifference > 5) { intervals.push([currentStart, sortedValues[i - 1][0], 'data']); - intervals.push([ - new Date(previousTimestamp.getTime() + 1), - new Date(currentTimestamp.getTime() - 1), - 'nodata', - ]); + intervals.push([previousTimestamp + 1, currentTimestamp - 1, 'nodata']); currentStart = sortedValues[i][0]; } previousTimestamp = currentTimestamp; @@ -259,22 +203,22 @@ function consolidateAndMergeAlertIntervals(data: Alert, dateArray: SpanDates) { intervals.push([currentStart, sortedValues[sortedValues.length - 1][0], 'data']); // Handle gaps before and after the detected intervals - const startBoundary = new Date(dateArray[0]), - endBoundary = new Date(dateArray[dateArray.length - 1]); - const firstIntervalStart = new Date(intervals[0][0]), - lastIntervalEnd = new Date(intervals[intervals.length - 1][1]); + const startBoundary = dateArray[0], + endBoundary = dateArray[dateArray.length - 1]; + const firstIntervalStart = intervals[0][0], + lastIntervalEnd = intervals[intervals.length - 1][1]; if (firstIntervalStart > startBoundary) { - intervals.unshift([startBoundary, new Date(firstIntervalStart.getTime() - 1), 'nodata']); + intervals.unshift([startBoundary, firstIntervalStart - 1, 'nodata']); } if (lastIntervalEnd < endBoundary) { - intervals.push([new Date(lastIntervalEnd.getTime() + 1), endBoundary, 'nodata']); + intervals.push([lastIntervalEnd + 1, endBoundary, 'nodata']); } return intervals; } -export const createAlertsChartBars = (alert: Alert, theme: Theme, dateValues: SpanDates) => { +export const createAlertsChartBars = (alert: Alert, dateValues: SpanDates) => { const groupedData = consolidateAndMergeAlertIntervals(alert, dateValues); const barChartColorScheme = { critical: t_global_color_status_danger_default.var, @@ -286,8 +230,8 @@ export const createAlertsChartBars = (alert: Alert, theme: Theme, dateValues: Sp for (let i = 0; i < groupedData.length; i++) { data.push({ - y0: new Date(groupedData[i][0]), - y: new Date(groupedData[i][1]), + y0: new Date(groupedData[i][0] * 1000), + y: new Date(groupedData[i][1] * 1000), x: alert.x, severity: alert.severity[0].toUpperCase() + alert.severity.slice(1), name: alert.alertname, @@ -326,10 +270,10 @@ export const formatDate = (date: Date, isTime: boolean) => { * Generates an array of dates, each representing midnight (00:00:00) of the past `days` number of days, starting from today. * * @param {number} days - The number of days for which to generate the date array. The array will contain dates starting from `days` ago up to today. - * @returns {Array} An array of `Date` objects, each set to midnight (00:00:00) in UTC, for the past `days` number of days. + * @returns {Array} An array of timestamps (in seconds) representing midnight (00:00:00) in UTC, for the past `days` number of days. * * @description - * This function creates an array of `Date` objects, starting from `days` ago up to the current day. Each date in the array is set to midnight (00:00:00) to represent the start of the day. + * This function creates an array of timestamps, starting from `days` ago up to the current day. Each timestamp in the array is set to midnight (00:00:00) to represent the start of the day. * * The function works by subtracting days from the current date and setting the time to 00:00:00 for each day. * @@ -338,24 +282,24 @@ export const formatDate = (date: Date, isTime: boolean) => { * const dateArray = generateDateArray(7); * // Output example: * // [ - * // 2024-09-06T00:00:00.000Z, - * // 2024-09-07T00:00:00.000Z, - * // 2024-09-08T00:00:00.000Z, - * // 2024-09-09T00:00:00.000Z, - * // 2024-09-10T00:00:00.000Z, - * // 2024-09-11T00:00:00.000Z, - * // 2024-09-12T00:00:00.000Z + * // 1754381643, + * // 1754468043, + * // 1754554443, + * // 1754640843, + * // 1754727243, + * // 1754813643, + * // 1754900043 * // ] */ -export function generateDateArray(days: number) { +export function generateDateArray(days: number): Array { const currentDate = new Date(); - const dateArray = []; + const dateArray: Array = []; for (let i = 0; i < days; i++) { const newDate = new Date(currentDate); newDate.setDate(currentDate.getDate() - (days - 1 - i)); newDate.setHours(0, 0, 0, 0); - dateArray.push(newDate); + dateArray.push(newDate.getTime() / 1000); } return dateArray; @@ -364,9 +308,8 @@ export function generateDateArray(days: number) { /** * Filters incidents based on the specified filters. * - * @param {Object} filters - An object containing filter criteria. - * @param {string[]} filters.incidentFilters - An array of strings representing filter conditions such as "Critical", etc. - * @param {Array} incidents - An array of incidents to be filtered. + * @param {IncidentFiltersCombined} filters - An object containing filter criteria. + * @param {Array} incidents - An array of incidents to be filtered. * @returns {Array} A filtered array of incidents that match at least one of the specified filters. * * The `conditions` object maps filter keys to incident properties. If no filters are applied, all incidents are returned. @@ -420,8 +363,8 @@ export function filterIncident(filters: IncidentFiltersCombined, incidents: Arra } export const onDeleteIncidentFilterChip = ( - type: 'Filters' | '', - id: IncidentFilters, + type: string, + id: IncidentFilters | undefined, filters: IncidentFiltersCombined, setFilters, ) => { @@ -479,7 +422,7 @@ export const makeIncidentUrlParams = ( return new URLSearchParams(processedParams).toString(); }; -export const updateBrowserUrl = (params: IncidentFiltersCombined, incidentGroupId: string) => { +export const updateBrowserUrl = (params: IncidentFiltersCombined, incidentGroupId?: string) => { const queryString = makeIncidentUrlParams(params, incidentGroupId); const newUrl = `${window.location.origin}${window.location.pathname}?${queryString}`; @@ -487,7 +430,11 @@ export const updateBrowserUrl = (params: IncidentFiltersCombined, incidentGroupI window.history.replaceState(null, '', newUrl); }; -export const changeDaysFilter = (days: DaysFilters, dispatch, filters: IncidentFiltersCombined) => { +export const changeDaysFilter = ( + days: DaysFilters, + dispatch: Dispatch, + filters: IncidentFiltersCombined, +) => { dispatch( setIncidentsActiveFilters({ incidentsActiveFilters: { days: [days], incidentFilters: filters.incidentFilters }, @@ -532,7 +479,7 @@ const onSelect = ( export const parseUrlParams = (search) => { const params = new URLSearchParams(search); - const result = {}; + const result: { [key: string]: any } = {}; const arrayKeys = ['days', 'incidentFilters', 'groupId']; params.forEach((value, key) => { diff --git a/web/src/components/console/utils/units.js b/web/src/components/console/utils/units.ts similarity index 66% rename from web/src/components/console/utils/units.js rename to web/src/components/console/utils/units.ts index 0938f43d..4cf0200c 100644 --- a/web/src/components/console/utils/units.js +++ b/web/src/components/console/utils/units.ts @@ -1,7 +1,5 @@ import * as _ from 'lodash-es'; -const units = {}; - const TYPES = { numeric: { units: ['', 'k', 'm', 'b'], @@ -13,21 +11,11 @@ const TYPES = { space: true, divisor: 1024, }, - decimalBytes: { - units: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'], - space: true, - divisor: 1000, - }, SI: { units: ['', 'k', 'M', 'G', 'T', 'P', 'E'], space: false, divisor: 1000, }, - binaryBytesPerSec: { - units: ['Bps', 'KiBps', 'MiBps', 'GiBps', 'TiBps', 'PiBps'], - space: true, - divisor: 1024, - }, decimalBytesPerSec: { units: ['Bps', 'KBps', 'MBps', 'GBps', 'TBps', 'PBps', 'EBps'], space: true, @@ -45,7 +33,7 @@ const TYPES = { }, }; -const getType = (name) => { +const getType = (name: string) => { const type = TYPES[name]; if (!_.isPlainObject(type)) { return { @@ -57,7 +45,13 @@ const getType = (name) => { return type; }; -const convertBaseValueToUnits = (value, unitArray, divisor, initialUnit, preferredUnit) => { +const convertBaseValueToUnits = ( + value: number, + unitArray: Array, + divisor: number, + initialUnit: string, + preferredUnit: string, +) => { const sliceIndex = initialUnit ? unitArray.indexOf(initialUnit) : 0; const units_ = unitArray.slice(sliceIndex); @@ -79,7 +73,7 @@ const convertBaseValueToUnits = (value, unitArray, divisor, initialUnit, preferr return { value, unit }; }; -const getDefaultFractionDigits = (value) => { +const getDefaultFractionDigits = (value: number) => { if (value < 1) { return 3; } @@ -89,33 +83,32 @@ const getDefaultFractionDigits = (value) => { return 1; }; -const formatValue = (value, options) => { +const formatValue = (value: number) => { const fractionDigits = getDefaultFractionDigits(value); - const { locales, ...rest } = _.defaults(options, { - maximumFractionDigits: fractionDigits, - }); // 2nd check converts -0 to 0. if (!isFinite(value) || value === 0) { value = 0; } - return Intl.NumberFormat(locales, rest).format(value); + return Intl.NumberFormat(undefined, { + maximumFractionDigits: fractionDigits, + }).format(value); }; -const round = (units.round = (value, fractionDigits) => { +const round = (value: number, fractionDigits?: number) => { if (!isFinite(value)) { return 0; } const multiplier = Math.pow(10, fractionDigits || getDefaultFractionDigits(value)); return Math.round(value * multiplier) / multiplier; -}); +}; -const humanize = (units.humanize = ( - value, - typeName, +const humanize = ( + value: number, + typeName: string, useRound = false, - initialUnit, - preferredUnit, + initialUnit: string, + preferredUnit: string, ) => { const type = getType(typeName); @@ -149,21 +142,27 @@ const humanize = (units.humanize = ( unit: converted.unit, value: converted.value, }; -}); +}; -export const humanizeBinaryBytes = (v, initialUnit, preferredUnit) => +export const humanizeBinaryBytes = (v: number, initialUnit?: string, preferredUnit?: string) => humanize(v, 'binaryBytes', true, initialUnit, preferredUnit); -export const humanizeDecimalBytes = (v, initialUnit, preferredUnit) => +export const humanizeDecimalBytes = (v: number, initialUnit?: string, preferredUnit?: string) => humanize(v, 'decimalBytes', true, initialUnit, preferredUnit); -export const humanizeBinaryBytesPerSec = (v, initialUnit, preferredUnit) => - humanize(v, 'binaryBytesPerSec', true, initialUnit, preferredUnit); -export const humanizeDecimalBytesPerSec = (v, initialUnit, preferredUnit) => - humanize(v, 'decimalBytesPerSec', true, initialUnit, preferredUnit); -export const humanizePacketsPerSec = (v, initialUnit, preferredUnit) => +export const humanizeBinaryBytesPerSec = ( + v: number, + initialUnit?: string, + preferredUnit?: string, +) => humanize(v, 'binaryBytesPerSec', true, initialUnit, preferredUnit); +export const humanizeDecimalBytesPerSec = ( + v: number, + initialUnit?: string, + preferredUnit?: string, +) => humanize(v, 'decimalBytesPerSec', true, initialUnit, preferredUnit); +export const humanizePacketsPerSec = (v: number, initialUnit?: string, preferredUnit?: string) => humanize(v, 'packetsPerSec', true, initialUnit, preferredUnit); -export const humanizeNumber = (v, initialUnit, preferredUnit) => +export const humanizeNumber = (v: number, initialUnit?: string, preferredUnit?: string) => humanize(v, 'numeric', true, initialUnit, preferredUnit); -export const humanizeNumberSI = (v, initialUnit, preferredUnit) => +export const humanizeNumberSI = (v: number, initialUnit?: string, preferredUnit?: string) => humanize(v, 'SI', true, initialUnit, preferredUnit); -export const humanizeSeconds = (v, initialUnit, preferredUnit) => +export const humanizeSeconds = (v: number, initialUnit?: string, preferredUnit?: string) => humanize(v, 'seconds', true, initialUnit, preferredUnit);
{t('Alert Name')}
- +