From e1557b1cd47427ae045514319402d005dfe2aa7a Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Thu, 14 Nov 2024 16:03:04 +0530 Subject: [PATCH] fix: fixed and improved webhooks --- .../src/api/experiments/handlers.rs | 6 +++- crates/service_utils/Cargo.toml | 1 + crates/service_utils/src/helpers.rs | 35 +++++++++++++++---- crates/superposition/Superposition.cac.toml | 10 +++--- crates/superposition_types/Cargo.toml | 1 + crates/superposition_types/src/webhook.rs | 23 +++++++++--- 6 files changed, 58 insertions(+), 18 deletions(-) diff --git a/crates/experimentation_platform/src/api/experiments/handlers.rs b/crates/experimentation_platform/src/api/experiments/handlers.rs index ce1698c1..4432b95d 100644 --- a/crates/experimentation_platform/src/api/experiments/handlers.rs +++ b/crates/experimentation_platform/src/api/experiments/handlers.rs @@ -294,6 +294,7 @@ async fn create( &experiments_webhook_config, &inserted_experiment, &config_version_id, + &tenant, WebhookEvent::ExperimentCreated, &state.http_client, ) @@ -322,7 +323,7 @@ async fn conclude_handler( custom_headers.config_tags, req.into_inner(), conn, - tenant, + tenant.clone(), user, ) .await?; @@ -334,6 +335,7 @@ async fn conclude_handler( &experiments_webhook_config, &response, &config_version_id, + &tenant, WebhookEvent::ExperimentConcluded, &state.http_client, ) @@ -679,6 +681,7 @@ async fn ramp( &experiments_webhook_config, &updated_experiment, &config_version_id, + &tenant, webhook_event, &data.http_client, ) @@ -913,6 +916,7 @@ async fn update_overrides( &experiments_webhook_config, &updated_experiment, &config_version_id, + &tenant, WebhookEvent::ExperimentUpdated, &state.http_client, ) diff --git a/crates/service_utils/Cargo.toml b/crates/service_utils/Cargo.toml index a256bd04..8e9d89a4 100644 --- a/crates/service_utils/Cargo.toml +++ b/crates/service_utils/Cargo.toml @@ -26,6 +26,7 @@ strum_macros = { workspace = true } superposition_types = { path = "../superposition_types", features = ["result"] } urlencoding = "~2.1.2" fred = { workspace = true, optional = true } +chrono = { workspace = true } [features] high-performance-mode = ["dep:fred"] diff --git a/crates/service_utils/src/helpers.rs b/crates/service_utils/src/helpers.rs index 2ea9d57d..701002e9 100644 --- a/crates/service_utils/src/helpers.rs +++ b/crates/service_utils/src/helpers.rs @@ -1,6 +1,7 @@ -use crate::service::types::AppState; +use crate::service::types::{AppState, Tenant}; use actix_web::{error::ErrorInternalServerError, web::Data, Error}; use anyhow::anyhow; +use chrono::Utc; use jsonschema::{error::ValidationErrorKind, ValidationError}; use log::info; use regex::Regex; @@ -20,7 +21,9 @@ use std::{ }; use superposition_types::{ result::{self, AppError}, - webhook::{HeadersEnum, HttpMethod, Webhook, WebhookEvent, WebhookResponse}, + webhook::{ + HeadersEnum, HttpMethod, Webhook, WebhookEvent, WebhookEventInfo, WebhookResponse, + }, Condition, }; @@ -416,6 +419,7 @@ pub async fn execute_webhook_call( webhook_config: &Webhook, payload: &T, config_version_opt: &Option, + tenant: &Tenant, event: WebhookEvent, http_client: &reqwest::Client, ) -> Result<(), AppError> @@ -423,7 +427,7 @@ where T: Serialize, { let mut header_array = webhook_config - .headers + .service_headers .clone() .into_iter() .filter_map(|key| match key { @@ -434,17 +438,26 @@ where None } } + HeadersEnum::TenantId => Some((key.to_string(), tenant.to_string())), }) .collect::>(); - let auth_token_value: String = - get_from_env_unsafe(&webhook_config.authorization.value).map_err(|err| { + webhook_config + .custom_headers + .clone() + .into_iter() + .for_each(|(key, value)| header_array.push((key, value))); + + if let Some(auth) = &webhook_config.authorization { + let auth_token_value: String = + get_from_env_unsafe(&auth.value).map_err(|err| { log::error!("Failed to retrieve authentication token for the webhook with error: {}", err); AppError::WebhookError( String::from("Failed to retrieve authentication token for the webhook. Please verify the credentials in TenantConfig.") ) })?; - header_array.push((webhook_config.authorization.key.clone(), auth_token_value)); + header_array.push((auth.key.clone(), auth_token_value)); + } let mut headers = HeaderMap::new(); header_array.iter().for_each(|(name, value)| { @@ -463,7 +476,15 @@ where let response = request_builder .headers(headers.into()) - .json(&WebhookResponse { event, payload }) + .json(&WebhookResponse { + event_info: WebhookEventInfo { + webhook_event: event, + time: Utc::now().naive_utc(), + tenant_id: tenant.to_string(), + config_version: config_version_opt.clone(), + }, + payload, + }) .send() .await; diff --git a/crates/superposition/Superposition.cac.toml b/crates/superposition/Superposition.cac.toml index 4cfadfac..4f70022d 100644 --- a/crates/superposition/Superposition.cac.toml +++ b/crates/superposition/Superposition.cac.toml @@ -4,16 +4,14 @@ mandatory_dimensions = { "value" = [ experiments_webhook_config = { "value" = { "enabled" = false }, "schema" = { "type" = "object", "properties" = { "enabled" = { "type" = "boolean" }, "configuration" = { "type" = "object", "properties" = { "url" = { "type" = "string" }, "method" = { "enum" = [ "Post", "Get", -], "type" = "string" }, "headers" = { "type" = "array", "items" = { "type" = "string", "enum" = [ - "ConfigVersion", +], "type" = "string" }, "custom_headers" = { "type" = "object" }, "service_headers" = { "type" = "array", "items" = { "type" = "string", "enum" = [ + "ConfigVersion", "TenantId" ] } }, "authorization" = { "type" = "object", "properties" = { "key" = { "type" = "string" }, "value" = { "type" = "string" } }, "required" = [ "key", "value", ] }, "required" = [ "url", "method", - "headers", - "authorization", ], "additionalProperties" = false } } }, "required" = [ "enabled", ] } } @@ -23,6 +21,6 @@ tenant = { schema = { "type" = "string", "enum" = ["test", "dev"] } } [context."$tenant == 'dev'"] mandatory_dimensions = [] -experiments_webhook_config = { "enabled" = false, "configuration" = { "url" = "http://localhost:8080/config/test", "method" = "Get", "headers" = [ - "ConfigVersion", +experiments_webhook_config = { "enabled" = false, "configuration" = { "url" = "http://localhost:8080/config/test", "method" = "Get", "custom_headers" = { "x-tenant" = "dev"}, "service_headers" = [ + "ConfigVersion", "TenantId" ], "authorization" = { key = "Authorization", value = "TOKEN_FOR_WEBHOOK" } } } diff --git a/crates/superposition_types/Cargo.toml b/crates/superposition_types/Cargo.toml index 2f8ced81..46bfc89d 100644 --- a/crates/superposition_types/Cargo.toml +++ b/crates/superposition_types/Cargo.toml @@ -18,6 +18,7 @@ anyhow = { workspace = true, optional = true } jsonlogic = { workspace = true } regex = { workspace = true } superposition_derives = { path = "../superposition_derives", optional = true } +chrono = { workspace = true } [features] default = ["server"] diff --git a/crates/superposition_types/src/webhook.rs b/crates/superposition_types/src/webhook.rs index 4dda2d6d..314322ec 100644 --- a/crates/superposition_types/src/webhook.rs +++ b/crates/superposition_types/src/webhook.rs @@ -1,15 +1,21 @@ +use chrono::NaiveDateTime; use serde::{Deserialize, Deserializer, Serialize}; -use std::fmt::{self}; +use std::{ + collections::HashMap, + fmt::{self}, +}; #[derive(Clone, Debug, Serialize, Deserialize)] pub enum HeadersEnum { ConfigVersion, + TenantId, } impl fmt::Display for HeadersEnum { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::ConfigVersion => write!(f, "x-config-version"), + Self::TenantId => write!(f, "x-tenant"), } } } @@ -30,8 +36,9 @@ pub struct Authorization { pub struct Webhook { pub url: String, pub method: HttpMethod, - pub headers: Vec, - pub authorization: Authorization, + pub custom_headers: HashMap, + pub service_headers: Vec, + pub authorization: Option, } #[derive(Serialize, Deserialize)] @@ -43,9 +50,17 @@ pub enum WebhookEvent { ExperimentConcluded, } +#[derive(Serialize, Deserialize)] +pub struct WebhookEventInfo { + pub webhook_event: WebhookEvent, + pub time: NaiveDateTime, + pub tenant_id: String, + pub config_version: Option, +} + #[derive(Serialize, Deserialize)] pub struct WebhookResponse { - pub event: WebhookEvent, + pub event_info: WebhookEventInfo, pub payload: T, }