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+
29+
630/**
731 * Creates a Prometheus alerts query string from grouped alert values.
832 * The function dynamically includes any properties in the input objects that have the "src_" prefix,
933 * but the prefix is removed from the keys in the final query string.
1034 *
1135 * @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.
36+ * Each alert object should contain various properties, including "src_" prefixed properties
1437 *
1538 * @param {string } groupedAlertsValues[].layer - The layer of the alert, used in the absent condition.
1639 * @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".
40+ * @returns {string[] } - An array of strings representing the combined Prometheus alerts query.
41+ * Each alert query is formatted as `(ALERTS{key="value", ...} and multiple queries are joined by "or".
2042 *
2143 * @example
2244 * const alerts = [
@@ -38,63 +60,85 @@ import { PROMETHEUS_QUERY_INTERVAL_SECONDS } from './utils';
3860 *
3961 * const query = createAlertsQuery(alerts);
4062 * // 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"})))'
63+ * // [' ALERTS{alertname="AlertmanagerReceiversNotConfigured", namespace="openshift-monitoring", severity="warning"} or
64+ * // ALERTS{alertname="AnotherAlert", namespace="default", severity="critical"}']
4365 */
4466export 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 ;
67+ const queries = [ ] ;
68+ let currentQueryParts = [ ] ;
69+ let currentQueryLength = 0 ;
70+
71+ for ( const alertValue of groupedAlertsValues ) {
72+ const singleAlertQuery = createSingleAlertQuery ( alertValue ) ;
73+ const newQueryLength = currentQueryLength + singleAlertQuery . length + 4 ; // 4 for ' or '
74+
75+ if ( newQueryLength <= MAX_URL_LENGTH ) {
76+ currentQueryParts . push ( singleAlertQuery ) ;
77+ currentQueryLength = newQueryLength ;
78+ continue ;
79+ }
80+ queries . push ( currentQueryParts . join ( ' or ' ) ) ;
81+ currentQueryParts = [ singleAlertQuery ] ;
82+ currentQueryLength = singleAlertQuery . length ;
83+ }
84+
85+ if ( currentQueryParts . length > 0 ) {
86+ queries . push ( currentQueryParts . join ( ' or ' ) ) ;
87+ }
88+
89+ return queries ;
6590} ;
6691
67- export const fetchDataForIncidentsAndAlerts = (
92+ export const fetchDataForIncidentsAndAlerts = async (
6893 fetch : ( url : string ) => Promise < PrometheusResponse > ,
6994 range : { endTime : number ; duration : number } ,
70- customQuery : string ,
95+ customQuery : string | string [ ] ,
7196) => {
7297 // Calculate samples to ensure step=PROMETHEUS_QUERY_INTERVAL_SECONDS (300s / 5 minutes)
7398 // For 24h duration: Math.ceil(86400000 / 288 / 1000) = 300 seconds
7499 const samples = Math . floor ( range . duration / ( PROMETHEUS_QUERY_INTERVAL_SECONDS * 1000 ) ) ;
100+ const queries = Array . isArray ( customQuery ) ? customQuery : [ customQuery ] ;
75101
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 : [ ] ,
102+ const promises = queries . map ( ( query ) => {
103+ const url = buildPrometheusUrl ( {
104+ prometheusUrlProps : {
105+ endpoint : PrometheusEndpoint . QUERY_RANGE ,
106+ endTime : range . endTime ,
107+ query,
108+ samples,
109+ timespan : range . duration ,
95110 } ,
111+ basePath : getPrometheusBasePath ( {
112+ prometheus : 'cmo' ,
113+ useTenancyPath : false ,
114+ } ) ,
96115 } ) ;
97- }
98116
99- return fetch ( url ) ;
117+ if ( ! url ) {
118+ // Return empty result when query is empty to avoid making invalid API calls
119+ return Promise . resolve ( {
120+ status : 'success' ,
121+ data : {
122+ resultType : 'matrix' ,
123+ result : [ ] ,
124+ } ,
125+ } as PrometheusResponse ) ;
126+ }
127+
128+ return fetch ( url ) ;
129+ } ) ;
130+
131+ const responses = await Promise . all ( promises ) ;
132+
133+ // Merge responses
134+ const combinedResult = responses . flatMap ( ( r ) => r . data ?. result || [ ] ) ;
135+
136+ // Construct a synthetic response
137+ return {
138+ status : 'success' ,
139+ data : {
140+ resultType : responses [ 0 ] ?. data ?. resultType || 'matrix' ,
141+ result : combinedResult ,
142+ } ,
143+ } as PrometheusResponse ;
100144} ;
0 commit comments