diff --git a/crates/context_aware_config/src/api/context/handlers.rs b/crates/context_aware_config/src/api/context/handlers.rs index a499c1cb..4bb99b90 100644 --- a/crates/context_aware_config/src/api/context/handlers.rs +++ b/crates/context_aware_config/src/api/context/handlers.rs @@ -43,6 +43,7 @@ use crate::{ context::types::{ ContextAction, ContextBulkResponse, ContextFilterSortBy, ContextFilters, DimensionCondition, MoveReq, PriorityRecomputeResponse, PutReq, PutResp, + WeightageRecomputeResponse, }, dimension::{get_dimension_data, get_dimension_data_map}, }, @@ -78,6 +79,7 @@ pub fn endpoints() -> Scope { .service(get_context_from_condition) .service(get_context) .service(priority_recompute) + .service(weightage_recompute) } type DBConnection = PooledConnection>; @@ -938,3 +940,84 @@ async fn priority_recompute( )); Ok(http_resp.json(response)) } + +#[put("/weightage/recompute")] +async fn weightage_recompute( + state: Data, + custom_headers: CustomHeaders, + db_conn: DbConnection, + #[cfg(feature = "high-performance-mode")] tenant: Tenant, + _user: User, +) -> superposition::Result { + use crate::db::schema::contexts::dsl::*; + let DbConnection(mut conn) = db_conn; + + let result: Vec = contexts.load(&mut conn).map_err(|err| { + log::error!("failed to fetch contexts with error: {}", err); + unexpected_error!("Something went wrong") + })?; + + let dimension_data = get_dimension_data(&mut conn)?; + let dimension_data_map = get_dimension_data_map(&dimension_data)?; + let mut response: Vec = vec![]; + let tags = parse_config_tags(custom_headers.config_tags)?; + + let update_contexts = result + .clone() + .into_iter() + .map(|context| { + let new_weightage = calculate_context_weightage( + &Value::Object(context.value.clone().into()), + &dimension_data_map, + ) + .map_err(|err| { + log::error!("failed to calculate context priority: {}", err); + unexpected_error!("Something went wrong") + }); + + match new_weightage { + Ok(val) => { + response.push(WeightageRecomputeResponse { + id: context.id.clone(), + condition: context.value.clone(), + old_weightage: context.weightage.clone(), + new_weightage: val.clone(), + }); + Ok(Context { + weightage: val, + ..context.clone() + }) + } + Err(e) => Err(e), + } + }) + .collect::>>()?; + + let config_version_id = + conn.transaction::<_, superposition::AppError, _>(|transaction_conn| { + let insert = diesel::insert_into(contexts) + .values(&update_contexts) + .on_conflict(id) + .do_update() + .set(weightage.eq(excluded(weightage))) + .execute(transaction_conn); + let version_id = add_config_version(&state, tags, transaction_conn)?; + match insert { + Ok(_) => Ok(version_id), + Err(err) => { + log::error!( + "Failed to execute query while recomputing priority, error: {err}" + ); + Err(db_error!(err)) + } + } + })?; + #[cfg(feature = "high-performance-mode")] + put_config_in_redis(config_version_id, state, tenant, &mut conn).await?; + let mut http_resp = HttpResponse::Ok(); + http_resp.insert_header(( + AppHeader::XConfigVersion.to_string(), + config_version_id.to_string(), + )); + Ok(http_resp.json(response)) +} diff --git a/crates/context_aware_config/src/api/context/types.rs b/crates/context_aware_config/src/api/context/types.rs index 15f38802..3a7e2323 100644 --- a/crates/context_aware_config/src/api/context/types.rs +++ b/crates/context_aware_config/src/api/context/types.rs @@ -83,6 +83,14 @@ pub struct PriorityRecomputeResponse { pub new_priority: i32, } +#[derive(Serialize)] +pub struct WeightageRecomputeResponse { + pub id: String, + pub condition: Condition, + pub old_weightage: BigDecimal, + pub new_weightage: BigDecimal, +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/context_aware_config/src/api/dimension/handlers.rs b/crates/context_aware_config/src/api/dimension/handlers.rs index 51dfd30e..ff8726d3 100644 --- a/crates/context_aware_config/src/api/dimension/handlers.rs +++ b/crates/context_aware_config/src/api/dimension/handlers.rs @@ -71,11 +71,18 @@ async fn create( )); } }; + let n_dimensions: i64 = dimensions.count().get_result(&mut conn)?; + if Into::::into(create_req.position.clone()) > n_dimensions.clone() as i32 { + return Err(bad_argument!( + "Expected psoition value less than {}", + n_dimensions + )); + } let new_dimension = Dimension { dimension: create_req.dimension.into(), priority: create_req.priority.into(), - position: create_req.position.into(), + position: create_req.position.clone().into(), schema: schema_value, created_by: user.get_email(), created_at: Utc::now(), @@ -84,40 +91,47 @@ async fn create( last_modified_by: user.get_email(), }; - let upsert = diesel::insert_into(dimensions) - .values(&new_dimension) - .on_conflict(dimensions::dimension) - .do_update() - .set(&new_dimension) - .get_result::(&mut conn); - - match upsert { - Ok(upserted_dimension) => { - let is_mandatory = tenant_config - .mandatory_dimensions - .contains(&upserted_dimension.dimension); - Ok(HttpResponse::Created().json(DimensionWithMandatory::new( - upserted_dimension, - is_mandatory, - ))) - } - Err(diesel::result::Error::DatabaseError( - diesel::result::DatabaseErrorKind::ForeignKeyViolation, - e, - )) => { - log::error!("{fun_name:?} function not found with error: {e:?}"); - Err(bad_argument!( - "Function {} doesn't exists", - fun_name.unwrap_or(String::new()) - )) - } - Err(e) => { - log::error!("Dimension upsert failed with error: {e}"); - Err(unexpected_error!( - "Something went wrong, failed to create/update dimension" - )) + conn.transaction::<_, superposition::AppError, _>(|transaction_conn| { + diesel::update(dimensions::dsl::dimensions) + .filter(dimensions::position.ge(create_req.position.clone() as i32)) + .set(dimensions::position.eq(dimensions::position + 1)) + .execute(transaction_conn)?; + + let upsert = diesel::insert_into(dimensions) + .values(&new_dimension) + .on_conflict(dimensions::dimension) + .do_update() + .set(&new_dimension) + .get_result::(transaction_conn); + + match upsert { + Ok(upserted_dimension) => { + let is_mandatory = tenant_config + .mandatory_dimensions + .contains(&upserted_dimension.dimension); + Ok(HttpResponse::Created().json(DimensionWithMandatory::new( + upserted_dimension, + is_mandatory, + ))) + } + Err(diesel::result::Error::DatabaseError( + diesel::result::DatabaseErrorKind::ForeignKeyViolation, + e, + )) => { + log::error!("{fun_name:?} function not found with error: {e:?}"); + Err(bad_argument!( + "Function {} doesn't exists", + fun_name.unwrap_or(String::new()) + )) + } + Err(e) => { + log::error!("Dimension upsert failed with error: {e}"); + Err(unexpected_error!( + "Something went wrong, failed to create/update dimension" + )) + } } - } + }) } #[get("")] diff --git a/crates/context_aware_config/src/api/dimension/types.rs b/crates/context_aware_config/src/api/dimension/types.rs index 4b5c2b8c..b24df5b8 100644 --- a/crates/context_aware_config/src/api/dimension/types.rs +++ b/crates/context_aware_config/src/api/dimension/types.rs @@ -21,8 +21,8 @@ pub struct CreateReq { pub struct Priority(i32); impl Priority { fn validate_data(priority_val: i32) -> Result { - if priority_val <= 0 { - return Err("Priority should be greater than 0".to_string()); + if priority_val < 0 { + return Err("Priority should be greater than equal to 0".to_string()); } else { Ok(Self(priority_val)) } diff --git a/postman/cac.postman_collection.json b/postman/cac.postman_collection.json index d6b97227..6c9cbd25 100644 --- a/postman/cac.postman_collection.json +++ b/postman/cac.postman_collection.json @@ -443,7 +443,7 @@ "language": "json" } }, - "raw": "{\"dimension\":\"clientId\",\"priority\":100,\"position\":1,\"schema\":{\"type\":\"string\",\"pattern\":\"^[a-z0-9].*$\"}}" + "raw": "{\"dimension\":\"clientId\",\"priority\":100,\"position\":0,\"schema\":{\"type\":\"string\",\"pattern\":\"^[a-z0-9].*$\"}}" }, "url": { "raw": "{{host}}/dimension", @@ -483,6 +483,7 @@ " \"type\": \"string\",", " \"pattern\": \".*\"", " }", + " \"position\": 1,", " })", " }", " };", @@ -989,7 +990,7 @@ " \"override\": {", " \"key1\": \"value3\"", " }", - " \"weightage\": \"2\",", + " \"weightage\": \"1\",", "};", "", "pm.test(\"200 check\", function() {", @@ -1210,6 +1211,127 @@ } } }, + { + "name": "Recompute Weightage Context", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const host = pm.variables.get(\"host\");", + "", + "function update_dimension_position() {", + " const options = {", + " 'method': 'PUT',", + " 'url': `${host}/dimension`,", + " 'header': {", + " 'x-tenant': 'test',", + " 'Content-Type': 'application/json'", + " },", + " \"body\": {", + " \"mode\": \"raw\",", + " \"raw\": JSON.stringify({", + " \"dimension\": \"clientId\",", + " \"priority\": 200,", + " \"schema\": {", + " \"type\": \"string\",", + " \"pattern\": \"^[a-z0-9].*$\"", + " }", + " \"position\": 2,", + " })", + " }", + " };", + " pm.sendRequest(options, function (error, response) {", + " if (error) {", + " console.log(`Error updating dimension: clientId`);", + " console.log(error);", + " return;", + " }", + " console.log(`Updated dimension: clientId`);", + " });", + " ", + "}", + "", + "update_dimension_position();" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "const host = pm.variables.get(\"host\");", + "const context_id = pm.environment.get(\"context_id\");", + "", + "", + "function getContextAndTest() {", + " const getContext = {", + " url : `${host}/context/${context_id}`,", + " method: 'GET',", + " header: {", + " 'Content-Type': 'application/json',", + " 'x-tenant': 'test',", + " }", + " ", + " };", + " pm.sendRequest(getContext, (error, response) => {", + " if(error) {", + " console.log(\"Failed to fetch context\");", + " throw error;", + " }", + " console.log(response.json())", + " pm.expect(response.json().weightage).to.be.eq(\"4\");", + "", + " })", + " ", + "}", + "", + "pm.test(\"200 check\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "", + "pm.test(\"Check weightage update\", function () {", + " getContextAndTest()", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "x-tenant", + "value": "test", + "type": "default" + } + ], + "url": { + "raw": "{{host}}/context/weightage/recompute", + "host": [ + "{{host}}" + ], + "path": [ + "context", + "weightage", + "recompute" + ] + } + } + }, { "name": "Delete Context", "event": [ diff --git a/postman/cac/Context/.meta.json b/postman/cac/Context/.meta.json index 1f1ef437..3087c98d 100644 --- a/postman/cac/Context/.meta.json +++ b/postman/cac/Context/.meta.json @@ -6,6 +6,7 @@ "Get Context", "List Context", "Recompute Priority Context", + "Recompute Weightage Context", "Delete Context" ] } diff --git a/postman/cac/Context/Get Context/event.test.js b/postman/cac/Context/Get Context/event.test.js index fd51a014..124742ff 100644 --- a/postman/cac/Context/Get Context/event.test.js +++ b/postman/cac/Context/Get Context/event.test.js @@ -13,7 +13,7 @@ const expected_context = { "override": { "key1": "value3" }, - "weightage": "2" + "weightage": "1" }; pm.test("200 check", function() { diff --git a/postman/cac/Context/Recompute Weightage Context/.event.meta.json b/postman/cac/Context/Recompute Weightage Context/.event.meta.json new file mode 100644 index 00000000..eab9002e --- /dev/null +++ b/postman/cac/Context/Recompute Weightage Context/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.prerequest.js", + "event.test.js" + ] +} \ No newline at end of file diff --git a/postman/cac/Context/Recompute Weightage Context/event.prerequest.js b/postman/cac/Context/Recompute Weightage Context/event.prerequest.js new file mode 100644 index 00000000..4f16a45b --- /dev/null +++ b/postman/cac/Context/Recompute Weightage Context/event.prerequest.js @@ -0,0 +1,35 @@ +const host = pm.variables.get("host"); + +function update_dimension_position() { + const options = { + 'method': 'PUT', + 'url': `${host}/dimension`, + 'header': { + 'x-tenant': 'test', + 'Content-Type': 'application/json' + }, + "body": { + "mode": "raw", + "raw": JSON.stringify({ + "dimension": "clientId", + "priority": 200, + "schema": { + "type": "string", + "pattern": "^[a-z0-9].*$" + }, + "position": 2, + }) + } + }; + pm.sendRequest(options, function (error, response) { + if (error) { + console.log(`Error updating dimension: clientId`); + console.log(error); + return; + } + console.log(`Updated dimension: clientId`); + }); + +} + +update_dimension_position(); \ No newline at end of file diff --git a/postman/cac/Context/Recompute Weightage Context/event.test.js b/postman/cac/Context/Recompute Weightage Context/event.test.js new file mode 100644 index 00000000..cb357868 --- /dev/null +++ b/postman/cac/Context/Recompute Weightage Context/event.test.js @@ -0,0 +1,34 @@ +const host = pm.variables.get("host"); +const context_id = pm.environment.get("context_id"); + + +function getContextAndTest() { + const getContext = { + url : `${host}/context/${context_id}`, + method: 'GET', + header: { + 'Content-Type': 'application/json', + 'x-tenant': 'test', + } + + }; + pm.sendRequest(getContext, (error, response) => { + if(error) { + console.log("Failed to fetch context"); + throw error; + } + console.log(response.json()) + pm.expect(response.json().weightage).to.be.eq("4"); + + }) + +} + +pm.test("200 check", function () { + pm.response.to.have.status(200); +}); + + +pm.test("Check priority update", function () { + getContextAndTest() +}); \ No newline at end of file diff --git a/postman/cac/Context/Recompute Weightage Context/request.json b/postman/cac/Context/Recompute Weightage Context/request.json new file mode 100644 index 00000000..e7ea5a12 --- /dev/null +++ b/postman/cac/Context/Recompute Weightage Context/request.json @@ -0,0 +1,31 @@ +{ + "method": "PUT", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{token}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "x-tenant", + "value": "test", + "type": "default" + } + ], + "url": { + "raw": "{{host}}/context/weightage/recompute", + "host": [ + "{{host}}" + ], + "path": [ + "context", + "weightage", + "recompute" + ] + } + } \ No newline at end of file diff --git a/postman/cac/Dimension/Create Dimension/request.json b/postman/cac/Dimension/Create Dimension/request.json index 0fab5e01..ade4edfa 100644 --- a/postman/cac/Dimension/Create Dimension/request.json +++ b/postman/cac/Dimension/Create Dimension/request.json @@ -27,7 +27,7 @@ "raw_json_formatted": { "dimension": "clientId", "priority": 100, - "position": 1, + "position": 0, "schema": { "type": "string", "pattern": "^[a-z0-9].*$" diff --git a/postman/cac/Dimension/Delete Dimension/event.prerequest.js b/postman/cac/Dimension/Delete Dimension/event.prerequest.js index b9bf91ba..4d12fb4d 100644 --- a/postman/cac/Dimension/Delete Dimension/event.prerequest.js +++ b/postman/cac/Dimension/Delete Dimension/event.prerequest.js @@ -16,7 +16,8 @@ function add_dimension() { "schema": { "type": "string", "pattern": ".*" - } + }, + "position":1 }) } };