Skip to content

Commit 1513661

Browse files
committed
fix(projects): refactor custom namespaces to be context-specific
1 parent 3e9f456 commit 1513661

3 files changed

Lines changed: 126 additions & 23 deletions

File tree

crates/aetheris-app/src/app/handler.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ impl App {
186186
self.projects.projects.push(Project {
187187
name: name.clone(),
188188
contexts: Vec::new(),
189-
custom_namespaces: Vec::new(),
189+
custom_namespaces_by_context: Vec::new(),
190190
});
191191
self.projects.selected_project = Some(name.clone());
192192
self.save_projects_or_toast();
@@ -207,7 +207,7 @@ impl App {
207207
self.projects.projects.push(Project {
208208
name: new_name.clone(),
209209
contexts: source.contexts,
210-
custom_namespaces: source.custom_namespaces,
210+
custom_namespaces_by_context: source.custom_namespaces_by_context,
211211
});
212212
self.projects.selected_project = Some(new_name.clone());
213213
self.save_projects_or_toast();

crates/aetheris-app/src/app/methods.rs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ impl App {
249249
pub(super) fn namespace_choices(&self) -> Vec<String> {
250250
let mut choices = self.namespaces.clone();
251251
if let Some(project) = self.projects.selected_project() {
252-
choices.extend(project.custom_namespaces.iter().cloned());
252+
choices.extend(project.custom_namespaces_for_context(self.selected_context.as_deref()));
253253
}
254254

255255
if !self.selected_namespace.is_empty()
@@ -272,10 +272,7 @@ impl App {
272272
pub(super) fn namespace_is_known(&self, namespace: &str) -> bool {
273273
self.namespaces.iter().any(|known| known == namespace)
274274
|| self.projects.selected_project().is_some_and(|project| {
275-
project
276-
.custom_namespaces
277-
.iter()
278-
.any(|known| known == namespace)
275+
project.has_custom_namespace(self.selected_context.as_deref(), namespace)
279276
})
280277
}
281278

@@ -288,16 +285,11 @@ impl App {
288285
self.namespaces.push(namespace.to_owned());
289286
}
290287

291-
if let Some(project) = self.projects.selected_project_mut() {
292-
if !project
293-
.custom_namespaces
294-
.iter()
295-
.any(|known| known == namespace)
296-
{
297-
project.custom_namespaces.push(namespace.to_owned());
298-
project.custom_namespaces.sort();
299-
project.custom_namespaces.dedup();
300-
self.save_projects_or_toast();
288+
if let Some(context) = self.selected_context.clone() {
289+
if let Some(project) = self.projects.selected_project_mut() {
290+
if project.add_custom_namespace(&context, namespace) {
291+
self.save_projects_or_toast();
292+
}
301293
}
302294
}
303295
}

crates/aetheris-app/src/app/projects.rs

Lines changed: 117 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ pub(super) struct Project {
1717
pub(super) name: String,
1818
pub(super) contexts: Vec<String>,
1919
#[serde(default)]
20-
pub(super) custom_namespaces: Vec<String>,
20+
pub(super) custom_namespaces_by_context: Vec<ContextNamespaces>,
21+
}
22+
23+
#[derive(Debug, Clone, Serialize, Deserialize)]
24+
pub(super) struct ContextNamespaces {
25+
pub(super) context: String,
26+
pub(super) namespaces: Vec<String>,
2127
}
2228

2329
#[derive(Debug, Clone)]
@@ -241,7 +247,7 @@ impl Default for ProjectStore {
241247
projects: vec![Project {
242248
name: String::from(DEFAULT_PROJECT_NAME),
243249
contexts: Vec::new(),
244-
custom_namespaces: Vec::new(),
250+
custom_namespaces_by_context: Vec::new(),
245251
}],
246252
selected_project: Some(String::from(DEFAULT_PROJECT_NAME)),
247253
visible_object_columns: default_object_columns(),
@@ -431,7 +437,7 @@ impl ProjectStore {
431437
self.projects.push(Project {
432438
name: String::from(DEFAULT_PROJECT_NAME),
433439
contexts: Vec::new(),
434-
custom_namespaces: Vec::new(),
440+
custom_namespaces_by_context: Vec::new(),
435441
});
436442
self.selected_project = Some(String::from(DEFAULT_PROJECT_NAME));
437443
}
@@ -447,6 +453,14 @@ impl ProjectStore {
447453
project
448454
.contexts
449455
.retain(|name| live_names.contains(name.as_str()));
456+
let project_contexts = project
457+
.contexts
458+
.iter()
459+
.map(String::as_str)
460+
.collect::<BTreeSet<_>>();
461+
project
462+
.custom_namespaces_by_context
463+
.retain(|entry| project_contexts.contains(entry.context.as_str()));
450464
}
451465

452466
let selected_exists = self
@@ -460,8 +474,7 @@ impl ProjectStore {
460474
for project in &mut self.projects {
461475
project.contexts.sort();
462476
project.contexts.dedup();
463-
project.custom_namespaces.sort();
464-
project.custom_namespaces.dedup();
477+
project.normalize_custom_namespaces();
465478
}
466479
}
467480

@@ -487,6 +500,9 @@ impl ProjectStore {
487500
return;
488501
};
489502
project.contexts.retain(|candidate| candidate != context);
503+
project
504+
.custom_namespaces_by_context
505+
.retain(|entry| entry.context != context);
490506
}
491507

492508
pub(super) fn selected_project_mut(&mut self) -> Option<&mut Project> {
@@ -497,6 +513,81 @@ impl ProjectStore {
497513
}
498514
}
499515

516+
impl Project {
517+
pub(super) fn custom_namespaces_for_context(&self, context: Option<&str>) -> Vec<String> {
518+
let Some(context) = context else {
519+
return Vec::new();
520+
};
521+
self.custom_namespaces_by_context
522+
.iter()
523+
.find(|entry| entry.context == context)
524+
.map(|entry| entry.namespaces.clone())
525+
.unwrap_or_default()
526+
}
527+
528+
pub(super) fn has_custom_namespace(&self, context: Option<&str>, namespace: &str) -> bool {
529+
self.custom_namespaces_for_context(context)
530+
.iter()
531+
.any(|known| known == namespace)
532+
}
533+
534+
pub(super) fn add_custom_namespace(&mut self, context: &str, namespace: &str) -> bool {
535+
if context.is_empty() || namespace.is_empty() {
536+
return false;
537+
}
538+
539+
if let Some(entry) = self
540+
.custom_namespaces_by_context
541+
.iter_mut()
542+
.find(|entry| entry.context == context)
543+
{
544+
if entry.namespaces.iter().any(|known| known == namespace) {
545+
return false;
546+
}
547+
entry.namespaces.push(namespace.to_owned());
548+
entry.namespaces.sort();
549+
entry.namespaces.dedup();
550+
return true;
551+
}
552+
553+
self.custom_namespaces_by_context.push(ContextNamespaces {
554+
context: context.to_owned(),
555+
namespaces: vec![namespace.to_owned()],
556+
});
557+
self.normalize_custom_namespaces();
558+
true
559+
}
560+
561+
fn normalize_custom_namespaces(&mut self) {
562+
for entry in &mut self.custom_namespaces_by_context {
563+
entry.context = entry.context.trim().to_owned();
564+
entry.namespaces.retain(|namespace| {
565+
let namespace = namespace.trim();
566+
!namespace.is_empty() && namespace != "all"
567+
});
568+
for namespace in &mut entry.namespaces {
569+
*namespace = namespace.trim().to_owned();
570+
}
571+
entry.namespaces.sort();
572+
entry.namespaces.dedup();
573+
}
574+
self.custom_namespaces_by_context
575+
.retain(|entry| !entry.context.is_empty() && !entry.namespaces.is_empty());
576+
self.custom_namespaces_by_context
577+
.sort_by(|left, right| left.context.cmp(&right.context));
578+
self.custom_namespaces_by_context.dedup_by(|left, right| {
579+
if left.context == right.context {
580+
left.namespaces.extend(right.namespaces.clone());
581+
left.namespaces.sort();
582+
left.namespaces.dedup();
583+
true
584+
} else {
585+
false
586+
}
587+
});
588+
}
589+
}
590+
500591
#[cfg(test)]
501592
mod tests {
502593
use super::*;
@@ -518,7 +609,7 @@ mod tests {
518609
projects: vec![Project {
519610
name: String::from("Work"),
520611
contexts: vec![String::from("local")],
521-
custom_namespaces: Vec::new(),
612+
custom_namespaces_by_context: Vec::new(),
522613
}],
523614
selected_project: Some(String::from("Work")),
524615
visible_object_columns: default_object_columns(),
@@ -531,4 +622,24 @@ mod tests {
531622
let project = store.selected_project().unwrap();
532623
assert_eq!(project.contexts, vec![String::from("local")]);
533624
}
625+
626+
#[test]
627+
fn custom_namespaces_are_scoped_by_context() {
628+
let mut project = Project {
629+
name: String::from("Work"),
630+
contexts: vec![String::from("prod"), String::from("stage")],
631+
custom_namespaces_by_context: Vec::new(),
632+
};
633+
634+
assert!(project.add_custom_namespace("prod", "billing"));
635+
636+
assert_eq!(
637+
project.custom_namespaces_for_context(Some("prod")),
638+
vec![String::from("billing")]
639+
);
640+
assert!(project
641+
.custom_namespaces_for_context(Some("stage"))
642+
.is_empty());
643+
assert!(!project.has_custom_namespace(Some("stage"), "billing"));
644+
}
534645
}

0 commit comments

Comments
 (0)