Skip to content

Commit

Permalink
fix: fixed and improved webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
Ankit Mahato authored and Ankit Mahato committed Nov 19, 2024
1 parent c155bb0 commit e1557b1
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ async fn create(
&experiments_webhook_config,
&inserted_experiment,
&config_version_id,
&tenant,
WebhookEvent::ExperimentCreated,
&state.http_client,
)
Expand Down Expand Up @@ -322,7 +323,7 @@ async fn conclude_handler(
custom_headers.config_tags,
req.into_inner(),
conn,
tenant,
tenant.clone(),
user,
)
.await?;
Expand All @@ -334,6 +335,7 @@ async fn conclude_handler(
&experiments_webhook_config,
&response,
&config_version_id,
&tenant,
WebhookEvent::ExperimentConcluded,
&state.http_client,
)
Expand Down Expand Up @@ -679,6 +681,7 @@ async fn ramp(
&experiments_webhook_config,
&updated_experiment,
&config_version_id,
&tenant,
webhook_event,
&data.http_client,
)
Expand Down Expand Up @@ -913,6 +916,7 @@ async fn update_overrides(
&experiments_webhook_config,
&updated_experiment,
&config_version_id,
&tenant,
WebhookEvent::ExperimentUpdated,
&state.http_client,
)
Expand Down
1 change: 1 addition & 0 deletions crates/service_utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
35 changes: 28 additions & 7 deletions crates/service_utils/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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,
};

Expand Down Expand Up @@ -416,14 +419,15 @@ pub async fn execute_webhook_call<T>(
webhook_config: &Webhook,
payload: &T,
config_version_opt: &Option<String>,
tenant: &Tenant,
event: WebhookEvent,
http_client: &reqwest::Client,
) -> Result<(), AppError>
where
T: Serialize,
{
let mut header_array = webhook_config
.headers
.service_headers
.clone()
.into_iter()
.filter_map(|key| match key {
Expand All @@ -434,17 +438,26 @@ where
None
}
}
HeadersEnum::TenantId => Some((key.to_string(), tenant.to_string())),
})
.collect::<Vec<(String, String)>>();

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)| {
Expand All @@ -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;

Expand Down
10 changes: 4 additions & 6 deletions crates/superposition/Superposition.cac.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
] } }
Expand All @@ -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" } } }
1 change: 1 addition & 0 deletions crates/superposition_types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
23 changes: 19 additions & 4 deletions crates/superposition_types/src/webhook.rs
Original file line number Diff line number Diff line change
@@ -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"),
}
}
}
Expand All @@ -30,8 +36,9 @@ pub struct Authorization {
pub struct Webhook {
pub url: String,
pub method: HttpMethod,
pub headers: Vec<HeadersEnum>,
pub authorization: Authorization,
pub custom_headers: HashMap<String, String>,
pub service_headers: Vec<HeadersEnum>,
pub authorization: Option<Authorization>,
}

#[derive(Serialize, Deserialize)]
Expand All @@ -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<String>,
}

#[derive(Serialize, Deserialize)]
pub struct WebhookResponse<T> {
pub event: WebhookEvent,
pub event_info: WebhookEventInfo,
pub payload: T,
}

Expand Down

0 comments on commit e1557b1

Please sign in to comment.