1use crate::jobs::{Answer, Job, SampleDraw};
11use crate::sample_modal::SampleForm;
12use crate::table_sample::{Limit, MemoryCheck, MemoryProbe};
13use crate::widgets::datatable::DataTableState;
14use crate::{App, AppEvent, InputMode, form, sampling};
15use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
16use std::sync::Arc;
17
18const DRAWING: &str = "Sampling...";
20
21pub(crate) enum Submitted {
23 Stays,
25 Clear,
27 Draw {
29 sample: sampling::Sample,
30 anyway: bool,
31 },
32}
33
34impl App {
35 pub(crate) fn open_table_sample_form(&mut self) {
37 let Some(state) = self.data_table_state.as_ref() else {
38 return;
39 };
40 let sample = match state.sampled() {
41 Some(sampled) => sampled.sample().clone(),
42 None => sampling::Sample {
43 rows: match self.app_config.analysis.sample_rows {
44 0 => sampling::DEFAULT_SAMPLE_ROWS,
45 rows => rows,
46 },
47 seed: crate::sample_modal::new_seed(),
48 ..sampling::Sample::default()
49 },
50 };
51 let context = self.sample_context(state.unsampled());
52 let mut form = SampleForm::new(&sample, context, &self.theme);
53 form.view = true;
54 form.bytes_per_row = Some(state.unsampled().sample_row_bytes(false));
55 form.source_bytes_per_row = Some(state.unsampled().sample_row_bytes(true));
56 self.sample_form = Some(form);
57 self.input_mode = InputMode::Sample;
58 }
59
60 pub(crate) fn table_sample_form_key(&mut self, event: &KeyEvent) -> Option<AppEvent> {
62 self.sample_form.as_ref()?;
63 match self.sample_form_edit(event) {
64 form::FormKey::Cancel => {
65 self.sample_form = None;
66 self.input_mode = InputMode::Normal;
67 }
68 form::FormKey::Submit => match Self::submit_view_sample(
69 self.sample_form.as_mut()?,
70 MemoryCheck {
71 limit: Limit::of_setting(self.app_config.analysis.sample_memory_limit),
72 probe: Arc::clone(&self.memory_probe),
73 },
74 ) {
75 Submitted::Stays => {}
76 Submitted::Clear => {
77 self.sample_form = None;
78 self.input_mode = InputMode::Normal;
79 self.clear_table_sample();
80 }
81 Submitted::Draw { sample, anyway } => {
82 self.sample_form = None;
83 self.input_mode = InputMode::Normal;
84 self.apply_table_sample(sample, None, anyway, false);
85 }
86 },
87 _ => {}
88 }
89 None
90 }
91
92 fn sample_form_edit(
95 &mut self,
96 event: &KeyEvent,
97 ) -> form::FormKey<crate::sample_modal::SampleField> {
98 let Some(form) = self.sample_form.as_mut() else {
99 return form::FormKey::Other;
100 };
101 let file_count = form.context.files.len();
102 let key = form::key(form, event);
103 match &key {
104 form::FormKey::Step(_, delta) => {
105 form.adjust(*delta > 0);
106 form.edited();
107 }
108 form::FormKey::Text(crate::sample_modal::SampleField::Files)
109 if matches!(event.code, KeyCode::PageDown | KeyCode::PageUp) =>
110 {
111 form.file_offset = if event.code == KeyCode::PageDown {
112 (form.file_offset + crate::widgets::sample_form::FILES_SHOWN)
113 .min(file_count.saturating_sub(1))
114 } else {
115 form.file_offset
116 .saturating_sub(crate::widgets::sample_form::FILES_SHOWN)
117 };
118 }
119 form::FormKey::Text(_) => {
120 if let Some(input) = form.input_mut(form.field) {
121 let _ = input.handle_key(event, None);
122 }
123 form.edited();
124 }
125 _ => {}
126 }
127 key
128 }
129
130 pub(crate) fn submit_view_sample(form: &mut SampleForm, memory: MemoryCheck) -> Submitted {
134 if form.no_sample() {
135 return Submitted::Clear;
136 }
137 let sample = match form.finish() {
138 Ok(sample) => sample,
139 Err(error) => {
140 form.error = Some(error);
141 return Submitted::Stays;
142 }
143 };
144 if form.anyway {
145 return Submitted::Draw {
146 sample,
147 anyway: true,
148 };
149 }
150 if let Some(warning) = form.estimate().and_then(|bytes| memory.refuses(bytes)) {
151 form.error = Some(warning);
152 form.anyway = true;
153 return Submitted::Stays;
154 }
155 Submitted::Draw {
156 sample,
157 anyway: false,
158 }
159 }
160
161 pub(crate) fn memory_check(&self) -> MemoryCheck {
164 MemoryCheck {
165 limit: Limit::of_setting(self.app_config.analysis.sample_memory_limit),
166 probe: Arc::clone(&self.memory_probe),
167 }
168 }
169
170 pub fn set_memory_probe(&mut self, probe: MemoryProbe) {
173 self.memory_probe = probe;
174 }
175
176 pub(crate) fn apply_table_sample(
182 &mut self,
183 sample: sampling::Sample,
184 replay: Option<crate::view::ViewSettings>,
185 anyway: bool,
186 then_analyze: bool,
187 ) {
188 self.draw_table_sample(sample, None, replay, anyway, then_analyze);
189 }
190
191 pub(crate) fn draw_table_sample(
194 &mut self,
195 sample: sampling::Sample,
196 path: Option<crate::table_sample::DrawPath>,
197 replay: Option<crate::view::ViewSettings>,
198 anyway: bool,
199 then_analyze: bool,
200 ) {
201 use crate::data_quality::QualityScope;
202 let Some(state) = self.data_table_state.as_ref() else {
203 return;
204 };
205 let source = state.unsampled();
206 let reads_view = !sample.scope.uses_source();
210 let ranged = matches!(
211 sample.scope,
212 QualityScope::FirstRows(_) | QualityScope::ViewRows { .. }
213 );
214 let sorted = !source.get_sort_columns().is_empty() || !source.get_sort_ascending();
215 let through = reads_view
216 && (source.changes_rows() || !source.column_changes().is_empty() || ranged && sorted);
217 let replay = replay.or_else(|| match state.sampled() {
220 Some(_) => Some(crate::view_settings_of(state)),
221 None => (!through).then(|| crate::view_settings_of(source)),
222 });
223 if replay
226 .as_ref()
227 .is_some_and(|settings| settings.pivot.is_some())
228 {
229 self.error_modal.show(PIVOT_OVER_A_SAMPLE.to_string());
230 return;
231 }
232 self.put_down_sample_draw();
233 let Some(state) = self.data_table_state.as_ref() else {
234 return;
235 };
236 let source = state.unsampled();
237 let (cut, known_total) = Self::table_sample_source(source, &sample.scope);
238 let path_key = Self::sample_path_key(source, &sample.scope);
242 let path = (sample.method == sampling::SampleMethod::Spread).then(|| {
243 path.or_else(|| {
244 self.sample_paths
245 .iter()
246 .find(|(key, _)| *key == path_key)
247 .map(|(_, path)| *path)
248 })
249 .unwrap_or(match known_total {
250 Some(of) => crate::table_sample::DrawPath::Bernoulli { of },
251 None => crate::table_sample::DrawPath::Reservoir,
252 })
253 });
254 let bytes_per_row = Some(source.sample_row_bytes(sample.scope.uses_source()));
255 let streaming = self.app_config.performance.streaming;
256 let rows = Arc::new(crate::table_sample::SampleRows::default());
257 let (memory, watch) = if anyway {
258 (MemoryCheck::off(), sampling::ReadWatch::default())
259 } else {
260 let memory = self.memory_check();
261 let held = memory.clone();
263 let watch = sampling::ReadWatch::judging_held(Arc::new(move |bytes, rows| {
264 held.holds_too_much(bytes, rows)
265 }));
266 (memory, watch)
267 };
268 let job = Job::SampleDraw(Box::new(SampleDraw {
269 sample: sample.clone(),
270 rows: Arc::clone(&rows),
271 watch: watch.clone(),
272 through,
273 replay,
274 then_analyze,
275 path,
276 path_key,
277 schema: None,
278 }));
279 self.spawn_job(job, Some(DRAWING), move |worker| {
280 let report = worker.reporter();
281 let failed = |error: color_eyre::eyre::Report| {
282 crate::error_display::user_message_from_report(&error, None)
283 };
284 let lf = cut.cut(&sample.scope).map_err(failed)?;
285 let schema = lf.clone().collect_schema().map_err(|e| failed(e.into()))?;
286 report(crate::Progress::SampleBegun(schema));
287 let live = crate::table_sample::Live {
288 rows,
289 notify: Arc::new(move || report(crate::Progress::SampleGrew)),
290 memory,
291 watch,
292 bytes_per_row,
293 };
294 let drawn =
295 crate::table_sample::draw(&lf, &sample, known_total, path, streaming, &live)
296 .map_err(failed)?;
297 Ok(Answer::SampleDrawn(drawn))
298 });
299 }
300
301 fn sample_path_key(
304 source: &DataTableState,
305 scope: &crate::data_quality::QualityScope,
306 ) -> String {
307 let settings = crate::view::ViewSettings {
308 sample: None,
309 chart: None,
310 ..crate::view_settings_of(source)
311 };
312 let steps = if scope.uses_source() {
313 String::new()
314 } else {
315 serde_json::to_string(&settings).unwrap_or_default()
316 };
317 format!("{}\n{steps}", scope.command())
318 }
319
320 fn table_sample_source(
324 state: &DataTableState,
325 scope: &crate::data_quality::QualityScope,
326 ) -> (sampling::SampleSource, Option<usize>) {
327 use crate::data_quality::QualityScope;
328 if scope.uses_source() {
329 let (lf, source) = state.data_quality_source_scan();
330 return (sampling::SampleSource::loaded(lf, source), None);
331 }
332 let lf = match scope {
333 QualityScope::FirstRows(_) | QualityScope::ViewRows { .. } => state.lf().clone(),
334 _ => state.analysis_lf(),
335 };
336 let columns: Vec<polars::prelude::Expr> = state
337 .schema()
338 .iter_names()
339 .map(|name| polars::prelude::col(name.clone()))
340 .collect();
341 (
342 sampling::SampleSource::view(lf.select(columns)),
343 sampling::view_scope_rows(state.num_rows_if_valid(), scope),
344 )
345 }
346
347 pub(crate) fn clear_table_sample(&mut self) {
351 let Some(sampled) = self.data_table_state.as_ref().and_then(|s| s.sampled()) else {
352 return;
353 };
354 let through = sampled.through();
355 let settings = self.data_table_state.as_ref().map(crate::view_settings_of);
356 if !through && settings.as_ref().is_some_and(|s| s.pivot.is_some()) {
359 self.error_modal.show(PIVOT_OFF_A_SAMPLE.to_string());
360 return;
361 }
362 self.put_down_sample_draw();
363 let Some(state) = self.data_table_state.take() else {
364 return;
365 };
366 let mut source = state.into_unsampled();
367 if !through && let Some(settings) = settings {
368 let laid = source.deferred(|s| {
369 s.reset_view_for_replay();
370 Self::replay_view(s, &settings, None).map(|_| ())
371 });
372 if let Err(error) = laid {
373 self.error_modal.show(format!(
374 "The view's steps did not go back on the source: {error}"
375 ));
376 }
377 }
378 self.data_table_state = Some(source);
379 self.sample_changed();
380 self.flash_note("Sample cleared".to_string());
381 self.spawn_async_collect(Self::LOADING_BUFFER);
382 }
383
384 pub(crate) fn sample_changed(&mut self) {
387 self.forget_the_rows_read();
388 self.chart_cache.clear();
389 let modal = &mut self.analysis_modal;
390 modal.describe_results = None;
391 modal.distribution_results = None;
392 modal.correlation_results = None;
393 modal.data_quality_results = None;
394 modal.data_quality_last_plan = None;
395 let sampled = self
396 .data_table_state
397 .as_ref()
398 .is_some_and(|state| state.sampled().is_some());
399 self.analysis_modal.follow_view_sample(sampled);
400 self.sync_quality_plan();
401 }
402
403 pub(crate) fn sample_draw(&self) -> Option<&SampleDraw> {
405 match self.jobs.current(|job| matches!(job, Job::SampleDraw(_))) {
406 Some((_, Job::SampleDraw(draw))) => Some(draw),
407 _ => None,
408 }
409 }
410
411 pub fn sample_drawing(&self) -> bool {
413 self.sample_draw().is_some()
414 }
415
416 pub(crate) fn stop_sample_draw(&mut self) {
418 if let Some(draw) = self.sample_draw() {
419 draw.watch.stop();
420 }
421 }
422
423 pub(crate) fn put_down_sample_draw(&mut self) {
426 if let Some(draw) = self.sample_draw() {
427 draw.watch.stop();
428 }
429 self.jobs.supersede(|job| matches!(job, Job::SampleDraw(_)));
430 }
431
432 fn draw_fills_view(&self, draw: &SampleDraw) -> bool {
434 self.data_table_state
435 .as_ref()
436 .and_then(|state| state.sampled())
437 .is_some_and(|sampled| sampled.holds(&draw.rows))
438 }
439
440 pub(crate) fn sample_begun(&mut self, schema: &polars::prelude::SchemaRef) {
443 if let Some(Job::SampleDraw(draw)) = self
444 .jobs
445 .current_mut(|job| matches!(job, Job::SampleDraw(_)))
446 {
447 draw.schema = Some(schema.clone());
448 }
449 }
450
451 fn take_on_sample(&mut self, draw: &SampleDraw) -> bool {
456 if self.draw_fills_view(draw) {
457 return true;
458 }
459 let Some(schema) = draw.schema.as_ref() else {
460 return false;
461 };
462 let Some(state) = self.data_table_state.take() else {
463 return false;
464 };
465 let source = state.into_unsampled();
466 let mut view = match DataTableState::sampled_from(
467 source,
468 draw.sample.clone(),
469 schema,
470 Arc::clone(&draw.rows),
471 draw.through,
472 draw.path,
473 ) {
474 Ok(view) => view,
475 Err(error) => {
476 self.put_down_sample_draw();
477 self.error_modal
478 .show(format!("Cannot show the sample: {error}"));
479 return false;
480 }
481 };
482 if let Some(settings) = &draw.replay {
483 let laid = view.deferred(|s| Self::replay_view(s, settings, None));
484 match laid {
485 Ok(crate::Replayed::Planned) => {}
486 Ok(crate::Replayed::Pivot(_)) => {
488 self.error_modal.show(PIVOT_OVER_A_SAMPLE.to_string());
489 }
490 Err(error) => self.flash_note(format!(
491 "The view's steps did not apply to the sample: {error}"
492 )),
493 }
494 }
495 self.data_table_state = Some(view);
496 self.sample_changed();
497 true
498 }
499
500 pub(crate) fn sample_grew(&mut self) {
502 let Some(draw) = self.sample_draw().cloned() else {
503 return;
504 };
505 if !self.take_on_sample(&draw) {
506 return;
507 }
508 let grew = self
509 .data_table_state
510 .as_mut()
511 .and_then(DataTableState::sample_grew);
512 if grew == Some(false) {
515 self.forget_the_rows_read();
516 }
517 if grew.is_some() && self.rows_in_flight().is_none() && self.in_normal_table_view() {
520 self.spawn_collect(None);
521 }
522 }
523
524 pub(crate) fn sample_drawn(
527 &mut self,
528 job: Job,
529 current: bool,
530 drawn: crate::table_sample::Drawn,
531 ) -> Option<AppEvent> {
532 let Job::SampleDraw(draw) = job else {
533 return None;
534 };
535 if !current || !self.take_on_sample(&draw) {
536 return None;
537 }
538 if let Some(path) = drawn.path {
539 self.sample_paths.retain(|(key, _)| *key != draw.path_key);
540 self.sample_paths.push((draw.path_key.clone(), path));
541 }
542 if let Some(state) = self.data_table_state.as_mut() {
543 state.sample_drawn(drawn);
544 }
545 self.forget_the_rows_read();
546 if let Some(reason) = draw.rows.stopped() {
547 self.flash_note(reason);
548 }
549 self.spawn_collect(None);
550 if draw.then_analyze && self.analysis_modal.active {
551 self.analysis_modal.computing = None;
552 return self.start_analysis_run();
553 }
554 None
555 }
556
557 pub(crate) fn sample_draw_failed(&mut self, job: &Job, current: bool, message: &str) {
560 let Job::SampleDraw(draw) = job else {
561 return;
562 };
563 if !current {
564 return;
565 }
566 if self.draw_fills_view(draw)
567 && let Some(state) = self.data_table_state.as_mut()
568 {
569 state.sample_drawn(crate::table_sample::Drawn {
570 cut: true,
571 path: draw.path,
572 ..Default::default()
573 });
574 self.forget_the_rows_read();
575 self.spawn_collect(None);
576 }
577 if draw.then_analyze {
578 self.analysis_modal.computing = None;
579 }
580 if message == sampling::CANCELLED {
581 self.flash_note("Sample stopped".to_string());
582 } else {
583 self.error_modal.show(message.to_string());
584 }
585 }
586
587 pub(crate) fn key_acts_while_sampling(&self, key: &KeyEvent) -> bool {
592 if self.busy
593 || self.loading.waits()
594 || !self
595 .jobs
596 .keys_held_only_by(|job| matches!(job, Job::SampleDraw(_)))
597 {
598 return false;
599 }
600 match self.input_mode {
601 InputMode::Inspect | InputMode::Info => return true,
602 InputMode::Editing if self.input_type == Some(crate::InputType::Find) => return true,
603 InputMode::Normal if self.help.is_open() => return true,
604 InputMode::Normal if self.in_normal_table_view() => {}
605 _ => return false,
606 }
607 let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
608 match key.code {
609 KeyCode::Up
610 | KeyCode::Down
611 | KeyCode::PageUp
612 | KeyCode::PageDown
613 | KeyCode::Home
614 | KeyCode::End
615 | KeyCode::Char(' ') => true,
616 KeyCode::Char('f' | 'b' | 'd' | 'u') if ctrl => true,
617 KeyCode::Char('j' | 'k' | 'G' | '/' | 'f' | 'n' | 'N' | 'g' | 'i') => !ctrl,
618 KeyCode::Enter => self.enter_inspects(),
619 _ => false,
620 }
621 }
622
623 pub(crate) fn chart_notes_of(
627 &self,
628 prepared: &crate::chart_jobs::ChartPrepared,
629 middot: &str,
630 ) -> Vec<String> {
631 let mut notes = prepared.notes(middot);
632 let sampled = self.data_table_state.as_ref().and_then(|s| s.sampled());
633 if let Some(sampled) = sampled {
634 let mut note = sampled.label();
635 if matches!(
636 sampled.sample().method,
637 sampling::SampleMethod::Spread | sampling::SampleMethod::PerPartition { .. }
638 ) {
639 note.push_str(&format!(" {middot} seed {}", sampled.sample().seed));
640 }
641 notes.insert(0, note);
642 }
643 notes
644 }
645
646 pub(crate) fn apply_sampled_view(
650 &mut self,
651 view: &crate::view::SavedView,
652 saved: &crate::view::SavedSample,
653 why: Option<crate::view::MatchReason>,
654 ) -> color_eyre::Result<()> {
655 let sample = saved.sample()?;
656 let through = saved.through.as_deref();
657 if through.is_some_and(|through| through.pivot.is_some()) || view.settings.pivot.is_some() {
658 return Err(color_eyre::eyre::eyre!("{PIVOT_OVER_A_SAMPLE}"));
659 }
660 self.put_down_sample_draw();
661 let Some(state) = self.data_table_state.take() else {
662 return Ok(());
663 };
664 let mut source = state.into_unsampled();
665 let replayed = source.try_transition(|s| {
666 s.reset_view_for_replay();
667 match through {
668 Some(through) => Self::replay_view(s, through, None).map(|_| ()),
669 None => Ok(()),
670 }
671 });
672 self.data_table_state = Some(source);
673 replayed?;
674 self.sample_changed();
675 if let Some(path) = &self.path {
676 use crate::logging::LogFailure;
677 self.view_manager
678 .record_use(&view.id, path)
679 .or_log("record a view's use");
680 }
681 self.active_view_id = Some(view.id.clone());
682 self.restore_view_chart(view.settings.chart.as_ref());
683 let mut settings = view.settings.clone();
684 settings.sample = None;
685 self.draw_table_sample(sample, saved.path, Some(settings), false, false);
686 if let Some(why) = why {
687 self.flash_view_applied(&view.name, why);
688 }
689 self.first_rows_settled();
690 Ok(())
691 }
692}
693
694const PIVOT_OVER_A_SAMPLE: &str = "A pivot cannot be laid on a sample as it is drawn: sample \
696 the pivoted view (Rows from: All rows), or take the pivot away with R";
697
698const PIVOT_OFF_A_SAMPLE: &str = "The pivot on the sample cannot move to the source: R takes \
700 away both";