11/* eslint-disable max-len */
22
3- import { PrometheusEndpoint , PrometheusResponse } from '@openshift-console/dynamic-plugin-sdk' ;
3+ import {
4+ consoleFetchJSON ,
5+ PrometheusEndpoint ,
6+ PrometheusResponse ,
7+ } from '@openshift-console/dynamic-plugin-sdk' ;
48import { getPrometheusBasePath , buildPrometheusUrl } from '../utils' ;
59import { PROMETHEUS_QUERY_INTERVAL_SECONDS } from './utils' ;
10+
11+ const MAX_URL_LENGTH = 2048 ;
12+
13+ /**
14+ * Creates a single Prometheus alert query string from a grouped alert value.
15+ * @param {Object } query - Single grouped alert object with src_ prefixed properties and layer/component.
16+ * @returns {string } - A string representing a single Prometheus alert query.
17+ */
18+ const createSingleAlertQuery = ( query ) => {
19+ // Dynamically get all keys starting with "src_"
20+ const srcKeys = Object . keys ( query ) . filter (
21+ ( key ) => key . startsWith ( 'src_' ) && key != 'src_silenced' ,
22+ ) ;
23+
24+ // Create the alertParts array using the dynamically discovered src_ keys,
25+ // but remove the "src_" prefix from the keys in the final query string.
26+ const alertParts = srcKeys
27+ . filter ( ( key ) => query [ key ] ) // Only include keys that are present in the query object
28+ . map ( ( key ) => `${ key . replace ( 'src_' , '' ) } ="${ query [ key ] } "` ) // Remove "src_" prefix from keys
29+ . join ( ', ' ) ;
30+
31+ // Construct the query string for each grouped alert
32+ return `ALERTS{${ alertParts } }` ;
33+ } ;
34+
635/**
736 * Creates a Prometheus alerts query string from grouped alert values.
837 * The function dynamically includes any properties in the input objects that have the "src_" prefix,
938 * but the prefix is removed from the keys in the final query string.
1039 *
1140 * @param {Object[] } groupedAlertsValues - Array of grouped alert objects.
12- * Each alert object should contain various properties, including "src_" prefixed properties,
13- * as well as "layer" and "component" for constructing the meta fields in the query.
41+ * Each alert object should contain various properties, including "src_" prefixed properties
1442 *
1543 * @param {string } groupedAlertsValues[].layer - The layer of the alert, used in the absent condition.
1644 * @param {string } groupedAlertsValues[].component - The component of the alert, used in the absent condition.
17- * @returns {string } - A string representing the combined Prometheus alerts query.
18- * Each alert query is formatted as `(ALERTS{key="value", ...} + on () group_left (component, layer) (absent(meta{layer="value", component="value"})))`
19- * and multiple queries are joined by "or".
45+ * @returns {string[] } - An array of strings representing the combined Prometheus alerts query.
46+ * Each alert query is formatted as `(ALERTS{key="value", ...} and multiple queries are joined by "or".
2047 *
2148 * @example
2249 * const alerts = [
@@ -38,63 +65,91 @@ import { PROMETHEUS_QUERY_INTERVAL_SECONDS } from './utils';
3865 *
3966 * const query = createAlertsQuery(alerts);
4067 * // Returns:
41- * // '( ALERTS{alertname="AlertmanagerReceiversNotConfigured", namespace="openshift-monitoring", severity="warning"} + on () group_left (component, layer) (absent(meta{layer="core", component="monitoring"}))) or
42- * // ( ALERTS{alertname="AnotherAlert", namespace="default", severity="critical"} + on () group_left (component, layer) (absent(meta{layer="app", component="frontend"})))'
68+ * // [' ALERTS{alertname="AlertmanagerReceiversNotConfigured", namespace="openshift-monitoring", severity="warning"} or
69+ * // ALERTS{alertname="AnotherAlert", namespace="default", severity="critical"}']
4370 */
44- export const createAlertsQuery = ( groupedAlertsValues ) => {
45- const alertsQuery = groupedAlertsValues
46- . map ( ( query ) => {
47- // Dynamically get all keys starting with "src_"
48- const srcKeys = Object . keys ( query ) . filter ( ( key ) => key . startsWith ( 'src_' ) ) ;
49-
50- // Create the alertParts array using the dynamically discovered src_ keys,
51- // but remove the "src_" prefix from the keys in the final query string.
52- const alertParts = srcKeys
53- . filter ( ( key ) => query [ key ] ) // Only include keys that are present in the query object
54- . map ( ( key ) => `${ key . replace ( 'src_' , '' ) } ="${ query [ key ] } "` ) // Remove "src_" prefix from keys
55- . join ( ', ' ) ;
56-
57- // Construct the query string for each grouped alert
58- return `(ALERTS{${ alertParts } } + on () group_left (component, layer) (absent(meta{layer="${ query . layer } ", component="${ query . component } "})))` ;
59- } )
60- . join ( ' or ' ) ; // Join all individual alert queries with "or"
61-
62- // TODO: remove duplicated conditions, optimize query
63-
64- return alertsQuery ;
71+ export const createAlertsQuery = ( groupedAlertsValues , max_url_length = MAX_URL_LENGTH ) => {
72+ const queries = [ ] ;
73+ const alertsMap = new Map < string , boolean > ( ) ;
74+
75+ let currentQueryParts = [ ] ;
76+ let currentQueryLength = 0 ;
77+
78+ for ( const alertValue of groupedAlertsValues ) {
79+ const singleAlertQuery = createSingleAlertQuery ( alertValue ) ;
80+ if ( alertsMap . has ( singleAlertQuery ) ) {
81+ continue ;
82+ }
83+ alertsMap . set ( singleAlertQuery , true ) ;
84+ const newQueryLength = currentQueryLength + singleAlertQuery . length + 4 ; // 4 for ' or '
85+
86+ if ( newQueryLength <= max_url_length ) {
87+ currentQueryParts . push ( singleAlertQuery ) ;
88+ currentQueryLength = newQueryLength ;
89+ continue ;
90+ }
91+ queries . push ( currentQueryParts . join ( ' or ' ) ) ;
92+ currentQueryParts = [ singleAlertQuery ] ;
93+ currentQueryLength = singleAlertQuery . length ;
94+ }
95+
96+ if ( currentQueryParts . length > 0 ) {
97+ queries . push ( currentQueryParts . join ( ' or ' ) ) ;
98+ }
99+
100+ return queries ;
65101} ;
66102
67- export const fetchDataForIncidentsAndAlerts = (
103+ export const fetchDataForIncidentsAndAlerts = async (
68104 fetch : ( url : string ) => Promise < PrometheusResponse > ,
69105 range : { endTime : number ; duration : number } ,
70- customQuery : string ,
106+ customQuery : string | string [ ] ,
71107) => {
72108 // Calculate samples to ensure step=PROMETHEUS_QUERY_INTERVAL_SECONDS (300s / 5 minutes)
73109 // For 24h duration: Math.ceil(86400000 / 288 / 1000) = 300 seconds
74110 const samples = Math . floor ( range . duration / ( PROMETHEUS_QUERY_INTERVAL_SECONDS * 1000 ) ) ;
111+ const queries = Array . isArray ( customQuery ) ? customQuery : [ customQuery ] ;
75112
76- const url = buildPrometheusUrl ( {
77- prometheusUrlProps : {
78- endpoint : PrometheusEndpoint . QUERY_RANGE ,
79- endTime : range . endTime ,
80- query : customQuery ,
81- samples,
82- timespan : range . duration ,
83- } ,
84- basePath : getPrometheusBasePath ( {
85- prometheus : 'cmo' ,
86- useTenancyPath : false ,
87- } ) ,
88- } ) ;
89-
90- if ( ! url ) {
91- // Return empty result when query is empty to avoid making invalid API calls
92- return Promise . resolve ( {
93- data : {
94- result : [ ] ,
113+ const promises = queries . map ( ( query ) => {
114+ const url = buildPrometheusUrl ( {
115+ prometheusUrlProps : {
116+ endpoint : PrometheusEndpoint . QUERY_RANGE ,
117+ endTime : range . endTime ,
118+ query,
119+ samples,
120+ timespan : range . duration ,
95121 } ,
122+ basePath : getPrometheusBasePath ( {
123+ prometheus : 'cmo' ,
124+ useTenancyPath : false ,
125+ } ) ,
96126 } ) ;
97- }
98127
99- return fetch ( url ) ;
128+ if ( ! url ) {
129+ // Return empty result when query is empty to avoid making invalid API calls
130+ return Promise . resolve ( {
131+ status : 'success' ,
132+ data : {
133+ resultType : 'matrix' ,
134+ result : [ ] ,
135+ } ,
136+ } as PrometheusResponse ) ;
137+ }
138+
139+ return consoleFetchJSON ( url ) ;
140+ } ) ;
141+
142+ const responses = await Promise . all ( promises ) ;
143+
144+ // Merge responses
145+ const combinedResult = responses . flatMap ( ( r ) => r . data ?. result || [ ] ) ;
146+
147+ // Construct a synthetic response
148+ return {
149+ status : 'success' ,
150+ data : {
151+ resultType : responses [ 0 ] ?. data ?. resultType || 'matrix' ,
152+ result : combinedResult ,
153+ } ,
154+ } as PrometheusResponse ;
100155} ;
0 commit comments