diff --git a/scripts/translations/.translation_state.json b/scripts/translations/.translation_state.json index 8a517ffb59..650115b95a 100644 --- a/scripts/translations/.translation_state.json +++ b/scripts/translations/.translation_state.json @@ -2383,7 +2383,6 @@ "evidence": "03867aea70acaf4c", "filterAll": "a52ace420f2175d0", "findingsHeading": "e171c2ff25b55e5a", - "lagImpact": "e058e98635dc1d23", "loadFailed": "d209798dda2325be", "noRecords": "d3b98b3f340b534b", "noneFoundDeclined": "2366f8dc1f762210", diff --git a/src/core/src/alerts/alert.rs b/src/core/src/alerts/alert.rs index eeee4e2afd..e48a05fc97 100644 --- a/src/core/src/alerts/alert.rs +++ b/src/core/src/alerts/alert.rs @@ -2384,115 +2384,6 @@ pub async fn trigger_by_id( }) } -pub async fn trigger_by_name( - org_id: &str, - stream_type: StreamType, - stream_name: &str, - name: &str, -) -> Result { - let (_folder, alert) = - match db::alerts::alert::get_by_name_with_folder(org_id, stream_type, stream_name, name) - .await - { - Ok(Some(found)) => found, - _ => { - return Err(AlertError::AlertNotFound); - } - }; - let now = Utc::now().timestamp_micros(); - #[cfg(feature = "enterprise")] - if let Some(downtime) = suppressed_manual_trigger(&alert, &_folder.folder_id, now).await { - return Ok(ManualTrigger::Suppressed(downtime)); - } - - // For creates_incident=true alerts the incident correlation path handles - // the notification. For all other cases send the direct notification. - #[cfg(feature = "enterprise")] - let (incident_notified, incident_notify_error) = if alert.creates_incident - && o2_enterprise::enterprise::common::config::get_config() - .incidents - .enabled - { - let synthetic_row = manual_trigger_row(&alert); - let notify = std::slice::from_ref(&synthetic_row); - - match crate::alerts::incidents::correlate_alert_to_incident( - &alert, - &synthetic_row, - notify, - now, - // Manual triggers evaluate nothing; no level to map. - None, - None, - ) - .await - { - Ok(Some(correlated)) => { - log::info!( - "Manual trigger for alert {org_id}/{} correlated to incident {} (service: {})", - alert.name, - correlated.outcome.incident_id(), - correlated.outcome.service_name(), - ); - ( - incident_path_notified(&correlated.outcome), - correlated.notify_error, - ) - } - Ok(None) => { - log::debug!( - "No incident correlation for manually triggered alert {org_id}/{}", - alert.name - ); - (false, None) - } - Err(e) => { - log::error!( - "Error correlating manual trigger to incident, falling back to direct notification: {e}" - ); - (false, None) - } - } - } else { - (false, None) - }; - - #[cfg(not(feature = "enterprise"))] - let (incident_notified, incident_notify_error) = (false, None); - - let trace_id = config::ider::generate_trace_id(); - let trace_id = format!("trig_name_{trace_id}"); - // Always sent: alert-attached workflows dispatch only from here. When the - // incident path already notified the destinations, they are skipped so the - // same manual trigger cannot page them twice. - let skip_destinations: &[String] = if incident_notified { - &alert.destinations - } else { - &[] - }; - let rows = vec![manual_trigger_row(&alert)]; - let outcome = alert - .send_notification( - &trace_id, - &rows, - now, - None, - now, - None, - None, - None, - skip_destinations, - None, - ) - .await?; - let error_message = with_incident_notify_error(outcome.error_message, incident_notify_error); - - Ok(ManualTrigger::Sent { - success_message: outcome.success_message, - error_message, - }) -} - /// A manual trigger inside a window is suppressed like a scheduled run: no page, no un-mute. #[cfg(feature = "enterprise")] async fn suppressed_manual_trigger( @@ -2678,8 +2569,8 @@ impl AlertExt for Alert { // every surface below (each destination's template, and the workflow // trigger metadata). An SLO alert reports its SLO where an ordinary // alert reports its stream, and the SLO's NAME is on the evaluation - // row rather than on the alert — so the rowless paths - // (`trigger_by_id`, `trigger_by_name`) need one primary-key read to + // row rather than on the alert — so the rowless path + // (`trigger_by_id`) needs one primary-key read to // say the same thing a firing says. Resolving it here rather than // inside the renderer is what keeps that at one read per notification diff --git a/src/core/src/alerts/grouping.rs b/src/core/src/alerts/grouping.rs index 7a1c3936fb..61c4935831 100644 --- a/src/core/src/alerts/grouping.rs +++ b/src/core/src/alerts/grouping.rs @@ -31,7 +31,7 @@ use config::{ }, utils::json, }; -use dashmap::DashMap; +use dashmap::{DashMap, mapref::entry::Entry}; /// Keyed by [`batch_key`]: a fingerprint carries no org, so it alone would merge two orgs' batches. static PENDING_BATCHES: Lazy>> = @@ -188,36 +188,38 @@ pub fn add_to_batch( // fingerprint — so this is set once, when the batch is created. group_labels: Option>, ) -> BatchAdmission { - let mut refused = false; - let full = PENDING_BATCHES - .entry(batch_key(&org_id, &fingerprint)) - .and_modify(|batch| { - if batch.add_alert(alert.clone(), rows.clone(), trigger_data.clone()) { - log::debug!( - "[grouping] Added alert '{}' to existing batch {} (count: {}/{})", - alert.name, - fingerprint, - batch.alerts.len(), - batch.max_group_size - ); - if batch.is_full() { - log::info!( - "[grouping] Batch {} reached max size ({}), ready to send", - fingerprint, - batch.max_group_size - ); - } - } else { - refused = true; + match PENDING_BATCHES.entry(batch_key(&org_id, &fingerprint)) { + Entry::Occupied(mut entry) => { + let batch = entry.get_mut(); + let alert_name = alert.name.clone(); + if !batch.add_alert(alert, rows, trigger_data) { log::warn!( "[grouping] Failed to add alert '{}' to batch {} (already full), org_id: {}", - alert.name, + alert_name, fingerprint, org_id ); + return BatchAdmission::Refused; } - }) - .or_insert_with(|| { + log::debug!( + "[grouping] Added alert '{}' to existing batch {} (count: {}/{})", + alert_name, + fingerprint, + batch.alerts.len(), + batch.max_group_size + ); + if batch.is_full() { + log::info!( + "[grouping] Batch {} reached max size ({}), ready to send", + fingerprint, + batch.max_group_size + ); + BatchAdmission::Ready + } else { + BatchAdmission::Queued + } + } + Entry::Vacant(entry) => { log::info!( "[grouping] Created new batch for fingerprint {}, alert: '{}', org: {}, wait_seconds: {}, max_size: {}", fingerprint, @@ -226,8 +228,8 @@ pub fn add_to_batch( group_wait_seconds, max_group_size ); - PendingBatch::new( - fingerprint.clone(), + let batch = entry.insert(PendingBatch::new( + fingerprint, org_id, alert, rows, @@ -236,15 +238,13 @@ pub fn add_to_batch( max_group_size, level, group_labels, - ) - }) - .is_full(); - if refused { - BatchAdmission::Refused - } else if full { - BatchAdmission::Ready - } else { - BatchAdmission::Queued + )); + if batch.is_full() { + BatchAdmission::Ready + } else { + BatchAdmission::Queued + } + } } } diff --git a/src/core/src/alerts/scheduler/calibration.rs b/src/core/src/alerts/scheduler/calibration.rs index 1e7ab7f43a..9a9c7c819b 100644 --- a/src/core/src/alerts/scheduler/calibration.rs +++ b/src/core/src/alerts/scheduler/calibration.rs @@ -186,7 +186,7 @@ pub(super) async fn handle_triggers( ) -> Result<(), anyhow::Error> { let started_at = now_micros(); let read = read_config(&trigger).await; - let (_, max_retries) = get_scheduler_max_retries(); + let max_retries = get_scheduler_max_retries(); let pass = Pass::decide(read.as_ref(), trigger.retries, max_retries); let analysis = match &pass { Pass::Due(config) => { diff --git a/src/core/src/alerts/scheduler/handlers.rs b/src/core/src/alerts/scheduler/handlers.rs index 31e8d8d90f..978fd9040b 100644 --- a/src/core/src/alerts/scheduler/handlers.rs +++ b/src/core/src/alerts/scheduler/handlers.rs @@ -2878,7 +2878,7 @@ async fn handle_alert_triggers( ) -> Result<(), anyhow::Error> { let query_trace_id = ider::generate_trace_id(); let scheduler_trace_id = format!("{trace_id}/{query_trace_id}"); - let (_, max_retries) = get_scheduler_max_retries(); + let max_retries = get_scheduler_max_retries(); log::debug!( "[SCHEDULER trace_id {scheduler_trace_id}] Inside handle_alert_triggers: processing trigger: {}", trigger.module_key @@ -4531,7 +4531,7 @@ async fn handle_report_triggers( let conn = get_orm_client_rw().await; let query_trace_id = ider::generate_trace_id(); let scheduler_trace_id = format!("{trace_id}/{query_trace_id}"); - let (_, max_retries) = get_scheduler_max_retries(); + let max_retries = get_scheduler_max_retries(); log::debug!( "[SCHEDULER trace_id {scheduler_trace_id}] Inside handle_report_trigger,org: {}, module_key: {}", trigger.org, @@ -4867,7 +4867,7 @@ async fn handle_derived_stream_triggers( "[SCHEDULER trace_id {scheduler_trace_id}] Inside handle_derived_stream_triggers processing trigger: {}", trigger.module_key ); - let (_, max_retries) = get_scheduler_max_retries(); + let max_retries = get_scheduler_max_retries(); // module_key format: stream_type/org_id/pipeline_name/pipeline_id let (org_id, stream_type, pipeline_name, pipeline_id) = match get_pipeline_info_from_module_key( @@ -5250,10 +5250,7 @@ async fn handle_derived_stream_triggers( return Ok(()); } - // In case the scheduler background job (watch_timeout) updates the trigger retries - // (not through this handler), we need to skip to the next run at but with the same - // trigger start time. If we don't handle here, in that case, the `clean_complete` - // background job will clear this job as it has reached max retries. + // watch_timeout can bump retries outside this handler; past the cap, skip to the next run. if trigger.retries >= max_retries { log::info!( "[SCHEDULER trace_id {scheduler_trace_id}] DerivedStream trigger: {}/{} has reached maximum possible retries. Skipping to next run", @@ -5612,7 +5609,7 @@ async fn handle_backfill_triggers( triggers::{DeletionStatus, ScheduledTriggerData}, }; - let (_, max_retries) = get_scheduler_max_retries(); + let max_retries = get_scheduler_max_retries(); let job_id = trigger.module_key.clone(); let org_id = trigger.org.clone(); let query_trace_id = ider::generate_trace_id(); diff --git a/src/infra/src/scheduler/mod.rs b/src/infra/src/scheduler/mod.rs index c1c6fa3a92..9497fff679 100644 --- a/src/infra/src/scheduler/mod.rs +++ b/src/infra/src/scheduler/mod.rs @@ -88,7 +88,6 @@ pub trait Scheduler: Sync + Send + 'static { async fn get(&self, org: &str, module: TriggerModule, key: &str) -> Result; async fn list(&self, module: Option) -> Result>; async fn list_by_org(&self, org: &str, module: Option) -> Result>; - async fn clean_complete(&self) -> Result<()>; async fn watch_timeout(&self) -> Result<()>; async fn len_module(&self, module: TriggerModule) -> usize; async fn len(&self) -> usize; @@ -255,13 +254,6 @@ pub async fn get(org: &str, module: TriggerModule, key: &str) -> Result CLIENT.get(org, module, key).await } -/// Background job that frequently (with the given interval) cleans "Completed" jobs -/// or jobs with retries >= scheduler_max_retries set through environment config -#[inline] -pub async fn clean_complete() -> Result<()> { - CLIENT.clean_complete().await -} - /// Background job that watches for timeout of a job /// Steps: /// - Select all the records with status = "Processing" @@ -306,11 +298,12 @@ pub async fn clear() -> Result<()> { CLIENT.clear().await } -/// Returns the scheduler_max_retries set through environment config -/// The bool element in the tuple indicates if the max retries value is included -pub fn get_scheduler_max_retries() -> (bool, i32) { - let max_retries = config::get_config().limit.scheduler_max_retries; - (max_retries > 0, max_retries.unsigned_abs() as i32) +/// `i32::MIN` has no positive magnitude, so `abs` would wrap it back to a negative cap. +pub fn get_scheduler_max_retries() -> i32 { + config::get_config() + .limit + .scheduler_max_retries + .saturating_abs() } /// Realtime alert triggers are cached on every node; the trigger rides in the @@ -407,10 +400,7 @@ mod tests { } #[test] - fn test_get_scheduler_max_retries_returns_tuple() { - let (has_max, _max) = get_scheduler_max_retries(); - // when config is default (0), should return false and abs(0) = 0 - let max_retries = config::get_config().limit.scheduler_max_retries; - assert_eq!(has_max, max_retries > 0); + fn test_get_scheduler_max_retries_is_never_negative() { + assert!(get_scheduler_max_retries() >= 0); } } diff --git a/src/infra/src/scheduler/postgres.rs b/src/infra/src/scheduler/postgres.rs index c0add4260e..e4e7ef7a53 100644 --- a/src/infra/src/scheduler/postgres.rs +++ b/src/infra/src/scheduler/postgres.rs @@ -21,7 +21,7 @@ use config::{ }; use sqlx::Row; -use super::{TRIGGERS_KEY, Trigger, TriggerModule, TriggerStatus, get_scheduler_max_retries}; +use super::{TRIGGERS_KEY, Trigger, TriggerModule, TriggerStatus}; use crate::{ db::{ self, IndexStatement, @@ -870,30 +870,6 @@ WHERE org = $1 AND module = $2 AND module_key = $3;"#; Ok(jobs) } - /// Background job that frequently (30 secs interval) cleans "Completed" jobs or jobs with - /// retries >= threshold set through environment - async fn clean_complete(&self) -> Result<()> { - let pool = CLIENT_RW.clone(); - log::debug!("[SCHEDULER] cleaning completed jobs"); - let (include_max, mut max_retries) = get_scheduler_max_retries(); - if include_max { - max_retries += 1; - } - DB_QUERY_NUMS - .with_label_values(&["delete", "scheduled_jobs"]) - .inc(); - // Since alert scheduled_jobs contain last_satisfied_at field, we should not delete them - sqlx::query( - r#"DELETE FROM scheduled_jobs WHERE (status = $1 OR retries >= $2) AND module != $3;"#, - ) - .bind(TriggerStatus::Completed) - .bind(max_retries) - .bind(TriggerModule::Alert) - .execute(&pool) - .await?; - Ok(()) - } - /// Background job that watches for timeout of a job /// Steps: /// - Select records with status = "Processing" whose `end_time` has passed diff --git a/src/infra/src/scheduler/sqlite.rs b/src/infra/src/scheduler/sqlite.rs index 31d8b594e7..e8ed325a03 100644 --- a/src/infra/src/scheduler/sqlite.rs +++ b/src/infra/src/scheduler/sqlite.rs @@ -18,7 +18,7 @@ use chrono::Duration; use config::utils::time::now_micros; use sqlx::Row; -use super::{TRIGGERS_KEY, Trigger, TriggerModule, TriggerStatus, get_scheduler_max_retries}; +use super::{TRIGGERS_KEY, Trigger, TriggerModule, TriggerStatus}; use crate::{ db::{ self, IndexStatement, @@ -550,26 +550,6 @@ WHERE org = $1 AND module = $2 AND module_key = $3;"#; Ok(jobs) } - /// Background job that frequently (30 secs interval) cleans "Completed" jobs or jobs with - /// retries >= threshold set through environment - async fn clean_complete(&self) -> Result<()> { - let client = CLIENT_RW.clone(); - let (include_max, mut max_retries) = get_scheduler_max_retries(); - if include_max { - max_retries += 1; - } - // Since alert scheduled_jobs contain last_satisfied_at field, we should not delete them - sqlx::query( - r#"DELETE FROM scheduled_jobs WHERE (status = $1 OR retries >= $2) AND module != $3;"#, - ) - .bind(TriggerStatus::Completed) - .bind(max_retries) - .bind(TriggerModule::Alert) - .execute(&client) - .await?; - Ok(()) - } - /// Background job that watches for timeout of a job /// Steps: /// - Select all the records with status = "Processing" diff --git a/src/infra/src/table/alert_eval_intervals.rs b/src/infra/src/table/alert_eval_intervals.rs index 9219b56867..0ccc21d027 100644 --- a/src/infra/src/table/alert_eval_intervals.rs +++ b/src/infra/src/table/alert_eval_intervals.rs @@ -189,7 +189,7 @@ pub async fn list_overlapping( range_start_us: i64, range_end_us: i64, ) -> Result, errors::Error> { - let client = get_orm_client_rw().await; + let client = get_orm_client_ro().await; list_overlapping_with(client, alert_id, range_start_us, range_end_us).await } diff --git a/web/src/components/alert_calibration/AlertCalibrationFindingCard.spec.ts b/web/src/components/alert_calibration/AlertCalibrationFindingCard.spec.ts index 6abf67e6a3..82a50e19b5 100644 --- a/web/src/components/alert_calibration/AlertCalibrationFindingCard.spec.ts +++ b/web/src/components/alert_calibration/AlertCalibrationFindingCard.spec.ts @@ -63,11 +63,18 @@ describe("AlertCalibrationFindingCard", () => { it("lists a cluster finding's worst orgs in place of a single alert", () => { const wrapper = mountCard(finding()); const rows = wrapper.findAll('[data-test="alert-calibration-finding-lag-row"]'); - expect(rows.map((row) => row.text())).toEqual([ - "globex3 skipped, 0 of 100 late", - "acme0 skipped, 40 of 100 late", + expect( + rows.map((row) => + row.findAll('[data-test="alert-calibration-finding-lag-cell"]').map((cell) => cell.text()), + ), + ).toEqual([ + ["globex", "3", "0", "100"], + ["acme", "0", "40", "100"], ]); - expect(wrapper.text()).toContain("Most affected orgs"); + const list = wrapper.find('[data-test="alert-calibration-finding-org_id-list"]'); + expect(list.text()).toContain("Most affected orgs"); + expect(list.text()).toContain("Skipped windows"); + expect(list.text()).toContain("Late evaluations"); expect( wrapper.find('[data-test="alert-calibration-finding-hour_start_micros-list"]').exists(), ).toBe(false); diff --git a/web/src/components/alert_calibration/AlertCalibrationFindingCard.vue b/web/src/components/alert_calibration/AlertCalibrationFindingCard.vue index 31359a92eb..af67c6aecf 100644 --- a/web/src/components/alert_calibration/AlertCalibrationFindingCard.vue +++ b/web/src/components/alert_calibration/AlertCalibrationFindingCard.vue @@ -53,49 +53,60 @@ along with this program. If not, see . - -
+ +
-
{{ row.label }}
- +
{{ row.label }}
+ {{ row.value }}
-
- {{ list.label }} -
    -
  • +
    + {{ list.label }} + + {{ t(column.labelKey) }} + +
    - + {{ row.label }} - - {{ - t("alertCalibration.detail.lagImpact", { - skipped: formatEvidence(t, row.skipped_windows, "count"), - late: formatEvidence(t, row.late_evaluations, "count"), - evaluations: formatEvidence(t, row.evaluations, "count"), - }) - }} + + {{ formatEvidence(t, row[column.field], "count") }} -
  • -
+
+
(); +const LAG_COLUMNS: { field: keyof LagRow; labelKey: I18nKey }[] = [ + { field: "skipped_windows", labelKey: "alertCalibration.evidence.skippedWindows" }, + { field: "late_evaluations", labelKey: "alertCalibration.evidence.lateEvaluations" }, + { field: "evaluations", labelKey: "alertCalibration.evidence.evaluations" }, +]; + const { t } = useI18nTyped(); const router = useRouter(); const orgId = useOrgId(); diff --git a/web/src/locales/languages/ar-SA.json b/web/src/locales/languages/ar-SA.json index d651ee2b52..a2a7bab636 100644 --- a/web/src/locales/languages/ar-SA.json +++ b/web/src/locales/languages/ar-SA.json @@ -8207,7 +8207,6 @@ "alert": "تنبيه", "topOrgs": "المؤسسات الأكثر تأثرًا", "worstHours": "أسوأ الساعات", - "lagImpact": "{skipped} متخطاة، و{late} من {evaluations} متأخرة", "proposedActions": "الإجراءات المقترحة", "openAlert": "فتح التنبيه" }, diff --git a/web/src/locales/languages/de-DE.json b/web/src/locales/languages/de-DE.json index 1a952c101a..abc1a84e29 100644 --- a/web/src/locales/languages/de-DE.json +++ b/web/src/locales/languages/de-DE.json @@ -8207,7 +8207,6 @@ "alert": "Alarm", "topOrgs": "Am stärksten betroffene Organisationen", "worstHours": "Schlimmste Stunden", - "lagImpact": "{skipped} übersprungen, {late} von {evaluations} verspätet", "proposedActions": "Vorgeschlagene Aktionen", "openAlert": "Alarm öffnen" }, diff --git a/web/src/locales/languages/en-US.json b/web/src/locales/languages/en-US.json index fc7ca5c6b9..5ebd81cf00 100644 --- a/web/src/locales/languages/en-US.json +++ b/web/src/locales/languages/en-US.json @@ -8207,7 +8207,6 @@ "alert": "Alert", "topOrgs": "Most affected orgs", "worstHours": "Worst hours", - "lagImpact": "{skipped} skipped, {late} of {evaluations} late", "proposedActions": "Proposed actions", "openAlert": "Open alert" }, diff --git a/web/src/locales/languages/es-ES.json b/web/src/locales/languages/es-ES.json index 9a54c59002..17c2e87f68 100644 --- a/web/src/locales/languages/es-ES.json +++ b/web/src/locales/languages/es-ES.json @@ -8207,7 +8207,6 @@ "alert": "Alerta", "topOrgs": "Organizaciones más afectadas", "worstHours": "Peores horas", - "lagImpact": "{skipped} omitidas, {late} de {evaluations} tardías", "proposedActions": "Acciones propuestas", "openAlert": "Abrir alerta" }, diff --git a/web/src/locales/languages/fr-FR.json b/web/src/locales/languages/fr-FR.json index f83515c9a4..ff6e6a2a5f 100644 --- a/web/src/locales/languages/fr-FR.json +++ b/web/src/locales/languages/fr-FR.json @@ -8207,7 +8207,6 @@ "alert": "Alerte", "topOrgs": "Organisations les plus affectées", "worstHours": "Heures les plus critiques", - "lagImpact": "{skipped} ignorées, {late} sur {evaluations} tardives", "proposedActions": "Actions proposées", "openAlert": "Ouvrir l'alerte" }, diff --git a/web/src/locales/languages/it-IT.json b/web/src/locales/languages/it-IT.json index 6ce05c7ea2..2308c173a8 100644 --- a/web/src/locales/languages/it-IT.json +++ b/web/src/locales/languages/it-IT.json @@ -8207,7 +8207,6 @@ "alert": "Avviso", "topOrgs": "Organizzazioni più interessate", "worstHours": "Ore peggiori", - "lagImpact": "{skipped} saltate, {late} di {evaluations} in ritardo", "proposedActions": "Azioni proposte", "openAlert": "Apri avviso" }, diff --git a/web/src/locales/languages/ja-JP.json b/web/src/locales/languages/ja-JP.json index 7c8c627073..999763cdf1 100644 --- a/web/src/locales/languages/ja-JP.json +++ b/web/src/locales/languages/ja-JP.json @@ -8207,7 +8207,6 @@ "alert": "アラート", "topOrgs": "最も影響を受けた組織", "worstHours": "最も深刻な時間帯", - "lagImpact": "{skipped} 件スキップ、{evaluations} 件中 {late} 件が遅延", "proposedActions": "提案されたアクション", "openAlert": "アラートを開く" }, diff --git a/web/src/locales/languages/ko-KR.json b/web/src/locales/languages/ko-KR.json index 4d75d28afb..da5ba36aed 100644 --- a/web/src/locales/languages/ko-KR.json +++ b/web/src/locales/languages/ko-KR.json @@ -8207,7 +8207,6 @@ "alert": "알림", "topOrgs": "영향을 가장 많이 받은 조직", "worstHours": "최악의 시간대", - "lagImpact": "{skipped}건 건너뜀, {evaluations}건 중 {late}건 지연", "proposedActions": "제안된 작업", "openAlert": "알림 열기" }, diff --git a/web/src/locales/languages/nl-NL.json b/web/src/locales/languages/nl-NL.json index 1861c4a1cf..be470c6aab 100644 --- a/web/src/locales/languages/nl-NL.json +++ b/web/src/locales/languages/nl-NL.json @@ -8207,7 +8207,6 @@ "alert": "Alarm", "topOrgs": "Meest getroffen organisaties", "worstHours": "Slechtste uren", - "lagImpact": "{skipped} overgeslagen, {late} van {evaluations} te laat", "proposedActions": "Voorgestelde acties", "openAlert": "Alarm openen" }, diff --git a/web/src/locales/languages/pl-PL.json b/web/src/locales/languages/pl-PL.json index 4bb5cd56ce..8822ec72fe 100644 --- a/web/src/locales/languages/pl-PL.json +++ b/web/src/locales/languages/pl-PL.json @@ -8207,7 +8207,6 @@ "alert": "Alert", "topOrgs": "Najbardziej dotknięte organizacje", "worstHours": "Najgorsze godziny", - "lagImpact": "{skipped} pominiętych, {late} z {evaluations} opóźnionych", "proposedActions": "Proponowane działania", "openAlert": "Otwórz alert" }, diff --git a/web/src/locales/languages/pt-PT.json b/web/src/locales/languages/pt-PT.json index fcef46b077..6afffa26d3 100644 --- a/web/src/locales/languages/pt-PT.json +++ b/web/src/locales/languages/pt-PT.json @@ -8207,7 +8207,6 @@ "alert": "Alerta", "topOrgs": "Organizações mais afetadas", "worstHours": "Piores horas", - "lagImpact": "{skipped} ignoradas, {late} de {evaluations} atrasadas", "proposedActions": "Ações propostas", "openAlert": "Abrir alerta" }, diff --git a/web/src/locales/languages/ru-RU.json b/web/src/locales/languages/ru-RU.json index 7aa3c7babd..d6d3b61de3 100644 --- a/web/src/locales/languages/ru-RU.json +++ b/web/src/locales/languages/ru-RU.json @@ -8207,7 +8207,6 @@ "alert": "Оповещение", "topOrgs": "Наиболее затронутые организации", "worstHours": "Худшие часы", - "lagImpact": "{skipped} пропущено, {late} из {evaluations} с задержкой", "proposedActions": "Предлагаемые действия", "openAlert": "Открыть оповещение" }, diff --git a/web/src/locales/languages/tr-TR.json b/web/src/locales/languages/tr-TR.json index 48968f1eec..befe4897e9 100644 --- a/web/src/locales/languages/tr-TR.json +++ b/web/src/locales/languages/tr-TR.json @@ -8207,7 +8207,6 @@ "alert": "Uyarı", "topOrgs": "En çok etkilenen kuruluşlar", "worstHours": "En kötü saatler", - "lagImpact": "{skipped} atlandı, {evaluations} değerlendirmenin {late} tanesi geç", "proposedActions": "Önerilen eylemler", "openAlert": "Uyarıyı aç" }, diff --git a/web/src/locales/languages/vi-VN.json b/web/src/locales/languages/vi-VN.json index daabc8df33..6773c784dc 100644 --- a/web/src/locales/languages/vi-VN.json +++ b/web/src/locales/languages/vi-VN.json @@ -8207,7 +8207,6 @@ "alert": "Cảnh báo", "topOrgs": "Các tổ chức bị ảnh hưởng nhiều nhất", "worstHours": "Khung giờ tệ nhất", - "lagImpact": "{skipped} bị bỏ qua, {late} trên {evaluations} bị trễ", "proposedActions": "Hành động đề xuất", "openAlert": "Mở cảnh báo" }, diff --git a/web/src/locales/languages/zh-CN.json b/web/src/locales/languages/zh-CN.json index 5d7cbb496a..c1ccf8acbd 100644 --- a/web/src/locales/languages/zh-CN.json +++ b/web/src/locales/languages/zh-CN.json @@ -8207,7 +8207,6 @@ "alert": "告警", "topOrgs": "受影响最严重的组织", "worstHours": "最糟糕的时段", - "lagImpact": "{skipped} 个跳过,{evaluations} 个中 {late} 个延迟", "proposedActions": "建议的操作", "openAlert": "打开告警" }, diff --git a/web/src/locales/languages/zh-TW.json b/web/src/locales/languages/zh-TW.json index 730510fc48..270010566d 100644 --- a/web/src/locales/languages/zh-TW.json +++ b/web/src/locales/languages/zh-TW.json @@ -8202,7 +8202,6 @@ "alert": "警示", "topOrgs": "受影響最嚴重的組織", "worstHours": "最嚴重的時段", - "lagImpact": "略過 {skipped},{evaluations} 次中有 {late} 次延遲", "proposedActions": "建議動作", "openAlert": "開啟警示" },