33import { PrometheusEndpoint , PrometheusResponse } from '@openshift-console/dynamic-plugin-sdk' ;
44import { getPrometheusBasePath , buildPrometheusUrl } from '../utils' ;
55import { PROMETHEUS_QUERY_INTERVAL_SECONDS } from './utils' ;
6+
7+ const MAX_URL_LENGTH = 5000 ;
8+
9+ /**
10+ * Creates a single Prometheus alert query string from a grouped alert value.
11+ * @param {Object } query - Single grouped alert object with src_ prefixed properties and layer/component.
12+ * @returns {string } - A string representing a single Prometheus alert query.
13+ */
14+ const createSingleAlertQuery = ( query ) => {
15+ // Dynamically get all keys starting with "src_"
16+ const srcKeys = Object . keys ( query ) . filter ( ( key ) => key . startsWith ( 'src_' ) ) ;
17+
18+ // Create the alertParts array using the dynamically discovered src_ keys,
19+ // but remove the "src_" prefix from the keys in the final query string.
20+ const alertParts = srcKeys
21+ . filter ( ( key ) => query [ key ] ) // Only include keys that are present in the query object
22+ . map ( ( key ) => `${ key . replace ( 'src_' , '' ) } ="${ query [ key ] } "` ) // Remove "src_" prefix from keys
23+ . join ( ', ' ) ;
24+
25+ // Construct the query string for each grouped alert
26+ return `ALERTS{${ alertParts } }` ;
27+ } ;
28+
629/**
730 * Creates a Prometheus alerts query string from grouped alert values.
831 * The function dynamically includes any properties in the input objects that have the "src_" prefix,
932 * but the prefix is removed from the keys in the final query string.
1033 *
1134 * @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.
35+ * Each alert object should contain various properties, including "src_" prefixed properties
1436 *
1537 * @param {string } groupedAlertsValues[].layer - The layer of the alert, used in the absent condition.
1638 * @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".
39+ * @returns {string[] } - An array of strings representing the combined Prometheus alerts query.
40+ * Each alert query is formatted as `(ALERTS{key="value", ...} and multiple queries are joined by "or".
2041 *
2142 * @example
2243 * const alerts = [
@@ -38,63 +59,85 @@ import { PROMETHEUS_QUERY_INTERVAL_SECONDS } from './utils';
3859 *
3960 * const query = createAlertsQuery(alerts);
4061 * // 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"})))'
62+ * // [' ALERTS{alertname="AlertmanagerReceiversNotConfigured", namespace="openshift-monitoring", severity="warning"} or
63+ * // ALERTS{alertname="AnotherAlert", namespace="default", severity="critical"}']
4364 */
4465export 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 ;
66+ const queries = [ ] ;
67+ let currentQueryParts = [ ] ;
68+ let currentQueryLength = 0 ;
69+
70+ for ( const alertValue of groupedAlertsValues ) {
71+ const singleAlertQuery = createSingleAlertQuery ( alertValue ) ;
72+ const newQueryLength = currentQueryLength + singleAlertQuery . length + 4 ; // 4 for ' or '
73+
74+ if ( newQueryLength <= MAX_URL_LENGTH ) {
75+ currentQueryParts . push ( singleAlertQuery ) ;
76+ currentQueryLength = newQueryLength ;
77+ continue ;
78+ }
79+ queries . push ( currentQueryParts . join ( ' or ' ) ) ;
80+ currentQueryParts = [ singleAlertQuery ] ;
81+ currentQueryLength = singleAlertQuery . length ;
82+ }
83+
84+ if ( currentQueryParts . length > 0 ) {
85+ queries . push ( currentQueryParts . join ( ' or ' ) ) ;
86+ }
87+
88+ return queries ;
6589} ;
6690
67- export const fetchDataForIncidentsAndAlerts = (
91+ export const fetchDataForIncidentsAndAlerts = async (
6892 fetch : ( url : string ) => Promise < PrometheusResponse > ,
6993 range : { endTime : number ; duration : number } ,
70- customQuery : string ,
94+ customQuery : string | string [ ] ,
7195) => {
7296 // Calculate samples to ensure step=PROMETHEUS_QUERY_INTERVAL_SECONDS (300s / 5 minutes)
7397 // For 24h duration: Math.ceil(86400000 / 288 / 1000) = 300 seconds
7498 const samples = Math . floor ( range . duration / ( PROMETHEUS_QUERY_INTERVAL_SECONDS * 1000 ) ) ;
99+ const queries = Array . isArray ( customQuery ) ? customQuery : [ customQuery ] ;
75100
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 : [ ] ,
101+ const promises = queries . map ( ( query ) => {
102+ const url = buildPrometheusUrl ( {
103+ prometheusUrlProps : {
104+ endpoint : PrometheusEndpoint . QUERY_RANGE ,
105+ endTime : range . endTime ,
106+ query,
107+ samples,
108+ timespan : range . duration ,
95109 } ,
110+ basePath : getPrometheusBasePath ( {
111+ prometheus : 'cmo' ,
112+ useTenancyPath : false ,
113+ } ) ,
96114 } ) ;
97- }
98115
99- return fetch ( url ) ;
116+ if ( ! url ) {
117+ // Return empty result when query is empty to avoid making invalid API calls
118+ return Promise . resolve ( {
119+ status : 'success' ,
120+ data : {
121+ resultType : 'matrix' ,
122+ result : [ ] ,
123+ } ,
124+ } as PrometheusResponse ) ;
125+ }
126+
127+ return fetch ( url ) ;
128+ } ) ;
129+
130+ const responses = await Promise . all ( promises ) ;
131+
132+ // Merge responses
133+ const combinedResult = responses . flatMap ( ( r ) => r . data ?. result || [ ] ) ;
134+
135+ // Construct a synthetic response
136+ return {
137+ status : 'success' ,
138+ data : {
139+ resultType : responses [ 0 ] ?. data ?. resultType || 'matrix' ,
140+ result : combinedResult ,
141+ } ,
142+ } as PrometheusResponse ;
100143} ;
0 commit comments