Skip to content

Commit

Permalink
feat: delete dimension api and ui
Browse files Browse the repository at this point in the history
  • Loading branch information
Pratik Mishra authored and Pratik Mishra committed Sep 5, 2024
1 parent 204d536 commit d703da7
Show file tree
Hide file tree
Showing 12 changed files with 406 additions and 29 deletions.
67 changes: 59 additions & 8 deletions crates/context_aware_config/src/api/dimension/handlers.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
use crate::{
api::dimension::types::CreateReq,
db::{models::Dimension, schema::dimensions::dsl::*},
api::dimension::{types::CreateReq, utils::get_dimension_usage_context_ids},
db::{
models::Dimension,
schema::{dimensions, dimensions::dsl::*},
},
helpers::validate_jsonschema,
};
use actix_web::{
get, put,
web::{self, Data},
delete, get, put,
web::{self, Data, Path},
HttpResponse, Scope,
};
use chrono::Utc;
use diesel::RunQueryDsl;
use diesel::{
delete, Connection, ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper,
};
use jsonschema::{Draft, JSONSchema};
use serde_json::Value;
use superposition_macros::{bad_argument, unexpected_error};
use superposition_macros::{bad_argument, not_found, unexpected_error};
use superposition_types::{result as superposition, SuperpositionUser, User};

use service_utils::service::types::{AppState, DbConnection, Tenant};

use super::types::DimensionWithMandatory;
use super::types::{DeleteReq, DimensionWithMandatory};

pub fn endpoints() -> Scope {
Scope::new("").service(create).service(get)
Scope::new("")
.service(create)
.service(get)
.service(delete_dimension)
}

#[put("")]
Expand Down Expand Up @@ -131,3 +139,46 @@ async fn get(

Ok(HttpResponse::Ok().json(dimensions_with_mandatory))
}

#[delete("/{name}")]
async fn delete_dimension(
path: Path<DeleteReq>,
user: User,
db_conn: DbConnection,
) -> superposition::Result<HttpResponse> {
let name: String = path.into_inner().into();
let DbConnection(mut conn) = db_conn;
dimensions::dsl::dimensions
.filter(dimensions::dimension.eq(&name))
.select(Dimension::as_select())
.get_result(&mut conn)?;
let context_ids = get_dimension_usage_context_ids(&name, &mut conn)
.map_err(|_| unexpected_error!("Something went wrong"))?;
if context_ids.is_empty() {
conn.transaction::<_, superposition::AppError, _>(|transaction_conn| {
use dimensions::dsl;
diesel::update(dsl::dimensions)
.filter(dsl::dimension.eq(&name))
.set((
dsl::last_modified_at.eq(Utc::now().naive_utc()),
dsl::last_modified_by.eq(user.get_email()),
))
.execute(transaction_conn)?;
let deleted_row = delete(dsl::dimensions.filter(dsl::dimension.eq(&name)))
.execute(transaction_conn);
match deleted_row {
Ok(0) => Err(not_found!("Dimension `{}` doesn't exists", name)),
Ok(_) => Ok(HttpResponse::NoContent().finish()),
Err(e) => {
log::error!("dimension delete query failed with error: {e}");
Err(unexpected_error!("Something went wrong."))
}
}
})
} else {
Err(bad_argument!(
"Given key already in use in contexts: {}",
context_ids.join(",")
))
}
}
16 changes: 16 additions & 0 deletions crates/context_aware_config/src/api/dimension/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,19 @@ impl DimensionWithMandatory {
}
}
}

#[derive(Debug, Deserialize, AsRef, Deref, DerefMut, Into)]
#[serde(try_from = "String")]
pub struct DeleteReq(String);

impl TryFrom<String> for DeleteReq {
type Error = String;
fn try_from(name: String) -> Result<Self, Self::Error> {
let name = name.trim();
if name == "variantIds" {
Err("variantIds cannot be deleted".to_string())
} else {
Ok(Self(name.to_owned()))
}
}
}
43 changes: 37 additions & 6 deletions crates/context_aware_config/src/api/dimension/utils.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use std::collections::HashMap;

use crate::db::{models::Dimension, schema::dimensions::dsl::*};
use diesel::RunQueryDsl;
use crate::db::{
models::{Context, Dimension},
schema::{contexts::dsl::contexts, dimensions::dsl::*},
};
use diesel::{
r2d2::{ConnectionManager, PooledConnection},
PgConnection,
PgConnection, RunQueryDsl,
};
use jsonschema::{Draft, JSONSchema};
use superposition_types::result as superposition;
use serde_json::Map;
use service_utils::helpers::extract_dimensions;
use std::collections::HashMap;
use superposition_macros::{db_error, unexpected_error};
use superposition_types::{result as superposition, Cac, Condition};

pub fn get_all_dimension_schema_map(
conn: &mut PooledConnection<ConnectionManager<PgConnection>>,
Expand All @@ -28,3 +32,30 @@ pub fn get_all_dimension_schema_map(

Ok(dimension_schema_map)
}

pub fn get_dimension_usage_context_ids(
key: &str,
conn: &mut PooledConnection<ConnectionManager<PgConnection>>,
) -> superposition::Result<Vec<String>> {
let result: Vec<Context> = contexts.load(conn).map_err(|err| {
log::error!("failed to fetch contexts with error: {}", err);
db_error!(err)
})?;

let mut context_ids = vec![];
for context in result.iter() {
let condition = Cac::<Condition>::try_from_db(
context.value.as_object().unwrap_or(&Map::new()).clone(),
)
.map_err(|err| {
log::error!("generate_cac : failed to decode context from db {}", err);
unexpected_error!(err)
})?
.into_inner();

extract_dimensions(&condition)?
.get(key)
.map(|_| context_ids.push(context.id.to_owned()));
}
Ok(context_ids)
}
15 changes: 15 additions & 0 deletions crates/frontend/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,21 @@ pub async fn delete_default_config(key: String, tenant: String) -> Result<(), St
Ok(())
}

pub async fn delete_dimension(name: String, tenant: String) -> Result<(), String> {
let host = get_host();
let url = format!("{host}/dimension/{name}");

request(
url,
reqwest::Method::DELETE,
None::<serde_json::Value>,
construct_request_headers(&[("x-tenant", &tenant)])?,
)
.await?;

Ok(())
}

pub async fn fetch_types(
tenant: String,
page: i64,
Expand Down
71 changes: 59 additions & 12 deletions crates/frontend/src/pages/dimensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ use crate::components::dimension_form::DimensionForm;
use crate::components::drawer::{close_drawer, open_drawer, Drawer, DrawerBtn};
use crate::components::skeleton::Skeleton;
use crate::components::{
delete_modal::DeleteModal,
stat::Stat,
table::{types::Column, Table},
};
use leptos::*;
use serde_json::{json, Map, Value};

use crate::api::fetch_dimensions;
use crate::api::{delete_dimension, fetch_dimensions};

#[derive(Clone, Debug, Default)]
pub struct RowData {
Expand All @@ -22,6 +23,8 @@ pub struct RowData {
#[component]
pub fn dimensions() -> impl IntoView {
let tenant_rs = use_context::<ReadSignal<String>>().unwrap();
let (delete_modal_visible_rs, delete_modal_visible_ws) = create_signal(false);
let (delete_id_rs, delete_id_ws) = create_signal::<Option<String>>(None);
let dimensions_resource = create_blocking_resource(
move || tenant_rs.get(),
|current_tenant| async move {
Expand All @@ -32,10 +35,30 @@ pub fn dimensions() -> impl IntoView {
},
);

let confirm_delete = Callback::new(move |_| {
if let Some(id) = delete_id_rs.get().clone() {
spawn_local(async move {
let result = delete_dimension(id, tenant_rs.get()).await;

match result {
Ok(_) => {
logging::log!("Dimension deleted successfully");
dimensions_resource.refetch();
}
Err(e) => {
logging::log!("Error deleting Dimension: {:?}", e);
}
}
});
}
delete_id_ws.set(None);
delete_modal_visible_ws.set(false);
});

let selected_dimension = create_rw_signal::<Option<RowData>>(None);

let table_columns = create_memo(move |_| {
let edit_col_formatter = move |_: &str, row: &Map<String, Value>| {
let action_col_formatter = move |_: &str, row: &Map<String, Value>| {
logging::log!("Dimension row: {:?}", row);
let row_dimension = row["dimension"].to_string().replace('"', "");
let row_priority_str = row["priority"].to_string().replace('"', "");
Expand All @@ -50,6 +73,7 @@ pub fn dimensions() -> impl IntoView {
_ => Some(json!(function_name.replace('"', ""))),
};
let mandatory = row["mandatory"].as_bool().unwrap_or(false);
let dimension_name = row_dimension.clone();

let edit_click_handler = move |_| {
let row_data = RowData {
Expand All @@ -64,15 +88,32 @@ pub fn dimensions() -> impl IntoView {
open_drawer("dimension_drawer");
};

let edit_icon: HtmlElement<html::I> =
view! { <i class="ri-pencil-line ri-xl text-blue-500"></i> };

view! {
<span class="cursor-pointer" on:click=edit_click_handler>
{edit_icon}
</span>
if dimension_name.clone() == String::from("variantIds") {
view! {
<div class="join">
<span class="cursor-pointer" on:click=edit_click_handler>
<i class="ri-pencil-line ri-xl text-blue-500"></i>
</span>
</div>
}
.into_view()
} else {
let handle_dimension_delete = move |_| {
delete_id_ws.set(Some(dimension_name.clone()));
delete_modal_visible_ws.set(true);
};
view! {
<div class="join">
<span class="cursor-pointer" on:click=edit_click_handler>
<i class="ri-pencil-line ri-xl text-blue-500"></i>
</span>
<span class="cursor-pointer text-red-500" on:click=handle_dimension_delete>
<i class="ri-delete-bin-5-line ri-xl text-red-500"></i>
</span>
</div>
}
.into_view()
}
.into_view()
};
vec![
Column::default("dimension".to_string()),
Expand All @@ -82,7 +123,7 @@ pub fn dimensions() -> impl IntoView {
Column::default("function_name".to_string()),
Column::default("created_by".to_string()),
Column::default("created_at".to_string()),
Column::new("EDIT".to_string(), None, edit_col_formatter),
Column::new("actions".to_string(), None, action_col_formatter),
]
});

Expand Down Expand Up @@ -173,7 +214,13 @@ pub fn dimensions() -> impl IntoView {
</div>
}
}}

<DeleteModal
modal_visible=delete_modal_visible_rs
confirm_delete=confirm_delete
set_modal_visible=delete_modal_visible_ws
header_text="Are you sure you want to delete this dimension? Action is irreversible."
.to_string()
/>
</Suspense>
</div>
}
Expand Down
19 changes: 17 additions & 2 deletions crates/frontend/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,14 +459,29 @@ where
.map_err(|err| err.to_string())?;

let status = response.status();
if status.is_client_error() || status.is_server_error() {
if status.is_client_error() {
let resp_bytes = response
.bytes()
.await
.map_err(|_| String::from("Something went wrong"))?;
let error_msg = serde_json::from_slice::<ErrorResponse>(&resp_bytes).map_or(
String::from(
std::str::from_utf8(&resp_bytes.to_vec())
.unwrap_or("Something went wrong"),
),
|error| error.message,
);
logging::error!("{}", error_msg);
enqueue_alert(error_msg.clone(), AlertType::Error, 5000);
return Err(error_msg);
}
if status.is_server_error() {
let error_msg = response
.json::<ErrorResponse>()
.await
.map_or(String::from("Something went wrong"), |error| error.message);
logging::error!("{}", error_msg);
enqueue_alert(error_msg.clone(), AlertType::Error, 5000);

return Err(error_msg);
}

Expand Down
Loading

0 comments on commit d703da7

Please sign in to comment.