Skip to content

Commit 8db32c6

Browse files
authored
fix: Change interactive wizard to use include semantics (#12)
- Step 2 now asks "Select tables to INCLUDE" instead of exclude - Pressing Enter without selections includes ALL tables - Schema-only and time filter steps work with included tables - Review shows included tables with ✓ instead of excluded with ✗ - Filter uses include_tables parameter matching CLI --include-tables Closes #11
1 parent 13f6e99 commit 8db32c6

1 file changed

Lines changed: 54 additions & 38 deletions

File tree

src/interactive.rs

Lines changed: 54 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ struct CachedDbTables {
2828
///
2929
/// Presents a terminal UI for selecting:
3030
/// 1. Which databases to replicate (multi-select)
31-
/// 2. For each selected database: tables to exclude
31+
/// 2. For each selected database: tables to include (Enter = include all)
3232
/// 3. For each selected database: tables to replicate schema-only (no data)
3333
/// 4. For each selected database: time-based filters
3434
/// 5. Summary and confirmation
@@ -97,7 +97,7 @@ pub async fn select_databases_and_tables(
9797
let mut current_step = WizardStep::SelectDatabases;
9898

9999
// Track selections per database for back navigation
100-
let mut excluded_tables_by_db: std::collections::HashMap<String, Vec<String>> =
100+
let mut included_tables_by_db: std::collections::HashMap<String, Vec<String>> =
101101
std::collections::HashMap::new();
102102
let mut schema_only_by_db: std::collections::HashMap<String, Vec<(String, String)>> =
103103
std::collections::HashMap::new(); // (schema, table)
@@ -140,7 +140,7 @@ pub async fn select_databases_and_tables(
140140
}
141141

142142
// Clear previous selections when re-selecting databases
143-
excluded_tables_by_db.clear();
143+
included_tables_by_db.clear();
144144
schema_only_by_db.clear();
145145
time_filters_by_db.clear();
146146
table_cache.clear();
@@ -160,11 +160,12 @@ pub async fn select_databases_and_tables(
160160
WizardStep::SelectTablesForDb(db_idx) => {
161161
let db_name = &db_names[selected_db_indices[db_idx]].clone();
162162
print_header(&format!(
163-
"Step 2 of 5: Select Tables to Exclude ({}/{})",
163+
"Step 2 of 5: Select Tables to Include ({}/{})",
164164
db_idx + 1,
165165
selected_db_indices.len()
166166
));
167167
println!("Database: {}", db_name);
168+
println!("Press Enter without selecting to include ALL tables.");
168169
println!("Navigation: Space to toggle, Enter to continue, Esc to go back");
169170
println!();
170171

@@ -182,11 +183,11 @@ pub async fn select_databases_and_tables(
182183
continue;
183184
}
184185

185-
// Get previously excluded tables for this database (for back navigation)
186-
let previous_exclusions: Vec<usize> = excluded_tables_by_db
186+
// Get previously included tables for this database (for back navigation)
187+
let previous_inclusions: Vec<usize> = included_tables_by_db
187188
.get(db_name)
188-
.map(|excluded| {
189-
excluded
189+
.map(|included| {
190+
included
190191
.iter()
191192
.filter_map(|t| {
192193
// Strip db name prefix to match display names
@@ -202,23 +203,31 @@ pub async fn select_databases_and_tables(
202203
.unwrap_or_default();
203204

204205
let selections = MultiSelect::new(
205-
"Select tables to EXCLUDE (or press Enter to include all):",
206+
"Select tables to INCLUDE (Enter = include all):",
206207
cached.table_display_names.clone(),
207208
)
208-
.with_default(&previous_exclusions)
209+
.with_default(&previous_inclusions)
209210
.with_help_message("Space toggle, Enter confirm, Esc go back")
210211
.prompt();
211212

212213
match selections {
213-
Ok(selected_exclusions) => {
214-
// Build exclusion list for this database
215-
let db_exclusions: Vec<String> = selected_exclusions
216-
.iter()
217-
.map(|table_name| format!("{}.{}", db_name, table_name))
218-
.collect();
214+
Ok(selected_inclusions) => {
215+
// If nothing selected, include all tables
216+
let db_inclusions: Vec<String> = if selected_inclusions.is_empty() {
217+
cached
218+
.table_display_names
219+
.iter()
220+
.map(|table_name| format!("{}.{}", db_name, table_name))
221+
.collect()
222+
} else {
223+
selected_inclusions
224+
.iter()
225+
.map(|table_name| format!("{}.{}", db_name, table_name))
226+
.collect()
227+
};
219228

220229
// Store for back navigation
221-
excluded_tables_by_db.insert(db_name.clone(), db_exclusions);
230+
included_tables_by_db.insert(db_name.clone(), db_inclusions);
222231

223232
// Move to next database or schema-only step
224233
if db_idx + 1 < selected_db_indices.len() {
@@ -266,21 +275,21 @@ pub async fn select_databases_and_tables(
266275
continue;
267276
}
268277

269-
// Filter out excluded tables
270-
let excluded = excluded_tables_by_db.get(db_name);
278+
// Filter to only included tables
279+
let included = included_tables_by_db.get(db_name);
271280
let available_tables: Vec<(usize, String)> = cached
272281
.table_display_names
273282
.iter()
274283
.enumerate()
275284
.filter(|(_, name)| {
276285
let full_name = format!("{}.{}", db_name, name);
277-
!excluded.is_some_and(|ex| ex.contains(&full_name))
286+
included.is_some_and(|inc| inc.contains(&full_name))
278287
})
279288
.map(|(idx, name)| (idx, name.clone()))
280289
.collect();
281290

282291
if available_tables.is_empty() {
283-
println!(" All tables excluded from '{}'", db_name);
292+
println!(" No tables included from '{}'", db_name);
284293
if db_idx + 1 < selected_db_indices.len() {
285294
current_step = WizardStep::SelectSchemaOnlyForDb(db_idx + 1);
286295
} else {
@@ -380,21 +389,21 @@ pub async fn select_databases_and_tables(
380389
continue;
381390
}
382391

383-
// Filter out excluded and schema-only tables
384-
let excluded = excluded_tables_by_db.get(db_name);
392+
// Filter to included tables, excluding schema-only ones
393+
let included = included_tables_by_db.get(db_name);
385394
let schema_only = schema_only_by_db.get(db_name);
386395
let available_tables: Vec<(usize, String)> = cached
387396
.table_display_names
388397
.iter()
389398
.enumerate()
390399
.filter(|(idx, name)| {
391400
let full_name = format!("{}.{}", db_name, name);
392-
let is_excluded = excluded.is_some_and(|ex| ex.contains(&full_name));
401+
let is_included = included.is_some_and(|inc| inc.contains(&full_name));
393402
let t = &cached.all_tables[*idx];
394403
let is_schema_only = schema_only.is_some_and(|so| {
395404
so.iter().any(|(s, n)| s == &t.schema && n == &t.name)
396405
});
397-
!is_excluded && !is_schema_only
406+
is_included && !is_schema_only
398407
})
399408
.map(|(idx, name)| (idx, name.clone()))
400409
.collect();
@@ -550,9 +559,9 @@ pub async fn select_databases_and_tables(
550559
WizardStep::Review => {
551560
print_header("Step 5 of 5: Review Configuration");
552561

553-
// Collect all exclusions
554-
let excluded_tables: Vec<String> =
555-
excluded_tables_by_db.values().flatten().cloned().collect();
562+
// Collect all inclusions
563+
let included_tables: Vec<String> =
564+
included_tables_by_db.values().flatten().cloned().collect();
556565

557566
let selected_databases: Vec<String> = selected_db_indices
558567
.iter()
@@ -566,16 +575,22 @@ pub async fn select_databases_and_tables(
566575
}
567576
println!();
568577

569-
if !excluded_tables.is_empty() {
570-
println!("Tables to exclude: {}", excluded_tables.len());
571-
for table in &excluded_tables {
572-
println!(" {}", table);
578+
println!("Tables to replicate: {}", included_tables.len());
579+
if included_tables.len() <= 20 {
580+
for table in &included_tables {
581+
println!(" {}", table);
573582
}
574-
println!();
575583
} else {
576-
println!("Tables to exclude: none");
577-
println!();
584+
// Show first 10 and last 5 with ellipsis
585+
for table in included_tables.iter().take(10) {
586+
println!(" ✓ {}", table);
587+
}
588+
println!(" ... ({} more tables)", included_tables.len() - 15);
589+
for table in included_tables.iter().skip(included_tables.len() - 5) {
590+
println!(" ✓ {}", table);
591+
}
578592
}
593+
println!();
579594

580595
// Show schema-only tables
581596
let schema_only_count: usize = schema_only_by_db.values().map(|v| v.len()).sum();
@@ -647,16 +662,17 @@ pub async fn select_databases_and_tables(
647662
.map(|&i| db_names[i].clone())
648663
.collect();
649664

650-
let excluded_tables: Vec<String> = excluded_tables_by_db.values().flatten().cloned().collect();
665+
let included_tables: Vec<String> = included_tables_by_db.values().flatten().cloned().collect();
651666

652667
tracing::info!("");
653668
tracing::info!("✓ Configuration confirmed");
654669
tracing::info!("");
655670

656-
let filter = if excluded_tables.is_empty() {
671+
// Use include_tables filter (3rd parameter)
672+
let filter = if included_tables.is_empty() {
657673
ReplicationFilter::new(Some(selected_databases), None, None, None)?
658674
} else {
659-
ReplicationFilter::new(Some(selected_databases), None, None, Some(excluded_tables))?
675+
ReplicationFilter::new(Some(selected_databases), None, Some(included_tables), None)?
660676
};
661677

662678
// Build TableRules from selections

0 commit comments

Comments
 (0)