Merge pull request #256

* Update environment model to get ready for request/folder environments

* Folder environments in UI

* Folder environments working

* Tweaks and fixes

* Tweak environment encryption UX

* Tweak environment encryption UX

* Address comments

* Update fn name

* Add tsc back to lint rules

* Update src-web/components/EnvironmentEditor.tsx

* Merge remote-tracking branch 'origin/folder-environments' into folder…
This commit is contained in:
Gregory Schier
2025-09-21 07:54:26 -07:00
committed by GitHub
parent 46b049c72b
commit eb3d1c409b
85 changed files with 776 additions and 534 deletions

View File

@@ -89,7 +89,7 @@ impl<'a> DbContext<'a> {
col: impl IntoColumnRef,
value: impl Into<SimpleExpr>,
limit: Option<u64>,
) -> crate::error::Result<Vec<M>>
) -> Result<Vec<M>>
where
M: Into<AnyModel> + Clone + UpsertModelInfo,
{

View File

@@ -29,6 +29,9 @@ pub enum Error {
#[error("Multiple base environments for {0}. Delete duplicates before continuing.")]
MultipleBaseEnvironments(String),
#[error("Multiple folder environments for {0}. Delete duplicates before continuing.")]
MultipleFolderEnvironments(String),
#[error("unknown error")]
Unknown,

View File

@@ -533,9 +533,10 @@ pub struct Environment {
pub name: String,
pub public: bool,
pub base: bool,
pub variables: Vec<EnvironmentVariable>,
pub color: Option<String>,
pub parent_model: String,
pub parent_id: Option<String>,
}
impl UpsertModelInfo for Environment {
@@ -568,7 +569,8 @@ impl UpsertModelInfo for Environment {
(CreatedAt, upsert_date(source, self.created_at)),
(UpdatedAt, upsert_date(source, self.updated_at)),
(WorkspaceId, self.workspace_id.into()),
(Base, self.base.into()),
(ParentId, self.parent_id.into()),
(ParentModel, self.parent_model.into()),
(Color, self.color.into()),
(Name, self.name.trim().into()),
(Public, self.public.into()),
@@ -579,7 +581,8 @@ impl UpsertModelInfo for Environment {
fn update_columns() -> Vec<impl IntoIden> {
vec![
EnvironmentIden::UpdatedAt,
EnvironmentIden::Base,
EnvironmentIden::ParentId,
EnvironmentIden::ParentModel,
EnvironmentIden::Color,
EnvironmentIden::Name,
EnvironmentIden::Public,
@@ -598,7 +601,8 @@ impl UpsertModelInfo for Environment {
workspace_id: row.get("workspace_id")?,
created_at: row.get("created_at")?,
updated_at: row.get("updated_at")?,
base: row.get("base")?,
parent_id: row.get("parent_id")?,
parent_model: row.get("parent_model")?,
color: row.get("color")?,
name: row.get("name")?,
public: row.get("public")?,
@@ -2072,6 +2076,17 @@ macro_rules! define_any_model {
)*
}
impl AnyModel {
#[inline]
pub fn id(&self) -> &str {
match self {
$(
AnyModel::$type(inner) => &inner.id,
)*
}
}
}
$(
impl From<$type> for AnyModel {
fn from(value: $type) -> Self {

View File

@@ -1,5 +1,7 @@
use crate::db_context::DbContext;
use crate::error::Error::{MissingBaseEnvironment, MultipleBaseEnvironments};
use crate::error::Error::{
MissingBaseEnvironment, MultipleBaseEnvironments, MultipleFolderEnvironments,
};
use crate::error::Result;
use crate::models::{Environment, EnvironmentIden, EnvironmentVariable};
use crate::util::UpdateSource;
@@ -10,21 +12,31 @@ impl<'a> DbContext<'a> {
self.find_one(EnvironmentIden::Id, id)
}
pub fn get_environment_by_folder_id(&self, folder_id: &str) -> Result<Option<Environment>> {
let environments: Vec<Environment> =
self.find_many(EnvironmentIden::ParentId, folder_id, None)?;
if environments.len() > 1 {
return Err(MultipleFolderEnvironments(folder_id.to_string()));
}
Ok(environments.get(0).cloned())
}
pub fn get_base_environment(&self, workspace_id: &str) -> Result<Environment> {
let environments = self.list_environments_ensure_base(workspace_id)?;
let base_environments =
environments.into_iter().filter(|e| e.base).collect::<Vec<Environment>>();
let base_environments = environments
.into_iter()
.filter(|e| e.parent_id.is_none())
.collect::<Vec<Environment>>();
if base_environments.len() > 1 {
return Err(MultipleBaseEnvironments(workspace_id.to_string()));
}
let base_environment = base_environments.into_iter().find(|e| e.base).ok_or(
Ok(base_environments.first().cloned().ok_or(
// Should never happen because one should be created above if it does not exist
MissingBaseEnvironment(workspace_id.to_string()),
)?;
Ok(base_environment)
)?)
}
/// Lists environments and will create a base environment if one doesn't exist
@@ -32,13 +44,12 @@ impl<'a> DbContext<'a> {
let mut environments =
self.find_many::<Environment>(EnvironmentIden::WorkspaceId, workspace_id, None)?;
let base_environment = environments.iter().find(|e| e.base);
let base_environment = environments.iter().find(|e| e.parent_id.is_none());
if let None = base_environment {
let e = self.upsert_environment(
&Environment {
workspace_id: workspace_id.to_string(),
base: true,
name: "Global Variables".to_string(),
..Default::default()
},
@@ -98,4 +109,43 @@ impl<'a> DbContext<'a> {
source,
)
}
pub fn resolve_environments(
&self,
workspace_id: &str,
folder_id: Option<&str>,
active_environment_id: Option<&str>,
) -> Result<Vec<Environment>> {
let mut environments = Vec::new();
if let Some(folder_id) = folder_id {
let folder = self.get_folder(folder_id)?;
// Add current folder's environment
if let Some(e) = self.get_environment_by_folder_id(folder_id)? {
environments.push(e);
};
// Recurse up
let ancestors = self.resolve_environments(
workspace_id,
folder.folder_id.as_deref(),
active_environment_id,
)?;
environments.extend(ancestors);
} else {
// Add active and base environments
if let Some(id) = active_environment_id {
if let Ok(e) = self.get_environment(&id) {
// Add active sub environment
environments.push(e);
};
};
// Add the base environment
environments.push(self.get_base_environment(workspace_id)?);
}
Ok(environments)
}
}

View File

@@ -1,10 +1,7 @@
use crate::connection_or_tx::ConnectionOrTx;
use crate::db_context::DbContext;
use crate::error::Result;
use crate::models::{
Folder, FolderIden, GrpcRequest, GrpcRequestIden, HttpRequest, HttpRequestHeader,
HttpRequestIden, WebsocketRequest, WebsocketRequestIden,
};
use crate::models::{Environment, EnvironmentIden, Folder, FolderIden, GrpcRequest, GrpcRequestIden, HttpRequest, HttpRequestHeader, HttpRequestIden, WebsocketRequest, WebsocketRequestIden};
use crate::util::UpdateSource;
use serde_json::Value;
use std::collections::BTreeMap;
@@ -37,6 +34,10 @@ impl<'a> DbContext<'a> {
self.delete_websocket_request(&m, source)?;
}
for e in self.find_many(EnvironmentIden::ParentId, fid, None)? {
self.delete_environment(&e, source)?;
}
// Recurse down into child folders
for folder in self.find_many::<Folder>(FolderIden::FolderId, fid, None)? {
self.delete_folder(&folder, source)?;
@@ -99,6 +100,17 @@ impl<'a> DbContext<'a> {
)?;
}
for m in self.find_many::<Environment>(EnvironmentIden::ParentId, fid, None)? {
self.upsert_environment(
&Environment {
id: "".into(),
parent_id: Some(new_folder.id.clone()),
..m
},
source,
)?;
}
for m in self.find_many::<Folder>(FolderIden::FolderId, fid, None)? {
// Recurse down
self.duplicate_folder(

View File

@@ -64,7 +64,7 @@ impl QueryManager {
}
}
pub fn connect(&self) -> DbContext {
pub fn connect(&self) -> DbContext<'_> {
let conn = self
.pool
.lock()

View File

@@ -1,14 +1,10 @@
use std::collections::HashMap;
use crate::models::{Environment, EnvironmentVariable};
use std::collections::HashMap;
pub fn make_vars_hashmap(
base_environment: &Environment,
environment: Option<&Environment>,
) -> HashMap<String, String> {
pub fn make_vars_hashmap(environment_chain: Vec<Environment>) -> HashMap<String, String> {
let mut variables = HashMap::new();
variables = add_variable_to_map(variables, &base_environment.variables);
if let Some(e) = environment {
for e in environment_chain.iter().rev() {
variables = add_variable_to_map(variables, &e.variables);
}
@@ -31,4 +27,3 @@ fn add_variable_to_map(
map
}