Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 73 additions & 13 deletions components/analysis_main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from analyzer_interface import AnalyzerInterface
from storage import Project, Storage
from colorama import Fore

from analyzer_interface.suite import AnalyzerSuite
from storage import AnalysisModel, Storage
from terminal_tools import draw_box, open_directory_explorer, prompts, wait_for_key
from terminal_tools.inception import TerminalContext

Expand All @@ -10,17 +12,38 @@
def analysis_main(
context: TerminalContext,
storage: Storage,
project: Project,
analyzer: AnalyzerInterface,
suite: AnalyzerSuite,
analysis: AnalysisModel,
):
while True:
with context.nest(draw_box(f"Analysis: {analyzer.name}", padding_lines=0)):
analyzer = suite.get_primary_analyzer(analysis.primary_analyzer_id)
has_web_server = suite.find_web_presenters(analyzer)
is_draft = analysis.is_draft

with context.nest(
draw_box(f"Analysis: {analysis.display_name}", padding_lines=0)
):
if is_draft:
print("⚠️ This analysis didn't complete successfully. ⚠️")

action = prompts.list_input(
"What would you like to do?",
choices=[
("Open output directory", "open_output_dir"),
("Export outputs", "export_output"),
("Launch Web Server", "web_server"),
*(
[
("Open output directory", "open_output_dir"),
("Export outputs", "export_output"),
]
if not is_draft
else []
),
*(
[("Launch Web Server", "web_server")]
if (not is_draft) and has_web_server
else []
),
("Rename", "rename"),
("Delete", "delete"),
("(Back)", None),
],
)
Expand All @@ -30,16 +53,53 @@ def analysis_main(

if action == "open_output_dir":
print("Starting file explorer")
open_directory_explorer(
storage._get_project_exports_root_path(project.id, analyzer.id)
)
open_directory_explorer(storage._get_project_exports_root_path(analysis))
wait_for_key(True)
continue

if action == "export_output":
export_outputs(context, storage, project, analyzer)
export_outputs(context, storage, suite, analysis)
continue

if action == "web_server":
analysis_web_server(context, storage, project, analyzer)
analysis_web_server(context, storage, suite, analysis)
continue

if action == "rename":
new_name = prompts.text("Enter new name", default=analysis.display_name)
if new_name is None:
print("Rename canceled")
wait_for_key(True)
continue

analysis.display_name = new_name
storage.save_analysis(analysis)
print("Analysis renamed")
wait_for_key(True)
continue

if action == "delete":
print(
f"⚠️ Warning ⚠️\n\n"
f"This will permanently delete the analysis and all its outputs, "
f"including the default export directory. "
f"**Be sure to copy out any exports you want to keep before proceeding.**\n\n"
f"The web dashboad will also no longer be accessible.\n\n"
)
confirm = prompts.confirm("Are you sure you want to delete this analysis?")
if not confirm:
print("Deletion canceled.")
wait_for_key(True)
continue

safephrase = f"DELETE {analysis.display_name}"
print(f"Type {Fore.RED}{safephrase}{Fore.RESET} to confirm deletion.")
if prompts.text(f"(type the above to confirm)") != safephrase:
print("Deletion canceled.")
wait_for_key(True)
continue

storage.delete_analysis(analysis)
print("🔥 Analysis deleted.")
wait_for_key(True)
return
17 changes: 9 additions & 8 deletions components/analysis_web_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@
from flask import Flask, render_template
from waitress import serve

from analyzer_interface import AnalyzerInterface
from analyzers import suite
from analyzer_interface.suite import AnalyzerSuite
from context import WebPresenterContext
from storage import Project, Storage
from storage import AnalysisModel, Storage
from terminal_tools import wait_for_key
from terminal_tools.inception import TerminalContext


def analysis_web_server(
context: TerminalContext,
storage: Storage,
project: Project,
analyzer: AnalyzerInterface,
suite: AnalyzerSuite,
analysis: AnalysisModel,
):
analyzer = suite.get_primary_analyzer(analysis.primary_analyzer_id)
project = storage.get_project(analysis.project_id)

# These paths need to be resolved at runtime in order to run with
# pyinstaller bundle
parent_path = str(Path(__file__).resolve().parent)
Expand All @@ -46,8 +48,7 @@ def analysis_web_server(
)
temp_dir = tempfile.TemporaryDirectory()
presenter_context = WebPresenterContext(
project_id=project.id,
primary_analyzer=analyzer,
analysis=analysis,
web_presenter=presenter,
store=storage,
temp_dir=temp_dir.name,
Expand All @@ -62,7 +63,7 @@ def index():
return render_template(
"index.html",
panels=[(presenter.id, presenter.name) for presenter in web_presenters],
project_name=project.display_name,
project_name=project.display_name if project else "(Unknown Project)",
analyzer_name=analyzer.name,
)

Expand Down
52 changes: 21 additions & 31 deletions components/export_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
AnalyzerOutput,
SecondaryAnalyzerInterface,
)
from analyzers import suite
from storage import Project, Storage, SupportedOutputExtension
from analyzer_interface.suite import AnalyzerSuite
from storage import AnalysisModel, Storage, SupportedOutputExtension
from terminal_tools import (
ProgressReporter,
open_directory_explorer,
Expand All @@ -24,14 +24,15 @@
def export_outputs(
context: TerminalContext,
storage: Storage,
project: Project,
analyzer: AnalyzerInterface,
suite: AnalyzerSuite,
analysis: AnalysisModel,
*,
all=False,
):
analyzer = suite.get_primary_analyzer(analysis.primary_analyzer_id)
with context.nest("[Export Output]\n\n") as scope:
outputs = sorted(
get_all_exportable_outputs(storage, project, analyzer),
get_all_exportable_outputs(storage, suite, analysis),
key=lambda output: (
"0" if output.secondary is None else "1_" + output.secondary.name,
output.output.name,
Expand Down Expand Up @@ -64,19 +65,17 @@ def export_outputs(
return

scope.refresh()
export_outputs_sequence(storage, project, analyzer, selected_outputs, format)
export_outputs_sequence(storage, analysis, selected_outputs, format)


def export_outputs_sequence(
storage: Storage,
project: Project,
analyzer: AnalyzerInterface,
analysis: AnalysisModel,
selected_outputs: list["Output"],
format: SupportedOutputExtension,
):
has_large_dfs = any(
output.height(project.id, analyzer.id, storage) > 50_000
for output in selected_outputs
output.height(analysis, storage) > 50_000 for output in selected_outputs
)

export_chunk_size = None
Expand Down Expand Up @@ -122,8 +121,7 @@ def export_outputs_sequence(
for selected_output in selected_outputs:
with ProgressReporter(f"Exporting {selected_output.name}") as progress:
export_progress = selected_output.export(
project.id,
analyzer.id,
analysis,
storage,
format=format,
export_chunk_size=export_chunk_size,
Expand All @@ -140,9 +138,7 @@ def export_outputs_sequence(
if prompts.confirm(
"Would you like to open the containing directory?", default=True
):
open_directory_explorer(
storage._get_project_exports_root_path(project.id, analyzer.id)
)
open_directory_explorer(storage._get_project_exports_root_path(analysis))
print("Directory opened")
else:
print("All done!")
Expand All @@ -163,8 +159,9 @@ def export_format_prompt():


def get_all_exportable_outputs(
storage: Storage, project: Project, analyzer: AnalyzerInterface
storage: Storage, suite: AnalyzerSuite, analysis: AnalysisModel
):
analyzer = suite.get_primary_analyzer(analysis.primary_analyzer_id)
return [
*(
Output(output=output, secondary=None)
Expand All @@ -173,12 +170,10 @@ def get_all_exportable_outputs(
),
*(
Output(output=output, secondary=secondary)
for secondary_id in storage.list_project_secondary_analyses(
project.id, analyzer.id
)
for secondary_id in storage.list_secondary_analyses(analysis)
if (
secondary := suite.get_secondary_analyzer_by_id(
analyzer.id, secondary_id
analysis.primary_analyzer_id, secondary_id
)
)
is not None
Expand All @@ -200,44 +195,39 @@ def name(self):

def export(
self,
project_id: str,
analyzer_id: str,
analysis: AnalysisModel,
storage: Storage,
*,
format: SupportedOutputExtension,
export_chunk_size: Optional[int] = None,
):
if self.secondary is None:
return storage.export_project_primary_output(
project_id,
analyzer_id,
analysis,
self.output.id,
extension=format,
spec=self.output,
export_chunk_size=export_chunk_size,
)
else:
return storage.export_project_secondary_output(
project_id,
analyzer_id,
analysis,
self.secondary.id,
self.output.id,
extension=format,
spec=self.output,
export_chunk_size=export_chunk_size,
)

def height(self, project_id: str, analyzer_id: str, storage: Storage):
def height(self, analysis: AnalysisModel, storage: Storage):
if self.secondary is None:
return self.df_height(
storage.get_primary_output_parquet_path(
project_id, analyzer_id, self.output.id
)
storage.get_primary_output_parquet_path(analysis, self.output.id)
)
else:
return self.df_height(
storage.get_secondary_output_parquet_path(
project_id, analyzer_id, self.secondary.id, self.output.id
analysis, self.secondary.id, self.output.id
)
)

Expand Down
13 changes: 7 additions & 6 deletions components/main_menu.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from sys import exit

from analyzer_interface.suite import AnalyzerSuite
from storage import Storage
from terminal_tools import draw_box, prompts
from terminal_tools.inception import TerminalContext
Expand All @@ -11,7 +12,7 @@
from .select_project import select_project


def main_menu(context: TerminalContext, storage: Storage):
def main_menu(context: TerminalContext, storage: Storage, suite: AnalyzerSuite):
while True:
exit_instruction = "⟪ Hit Ctrl+C at any time to exit a menu ⟫"
with context.nest(draw_box("CIB Mango Tree") + "\n" + exit_instruction + "\n"):
Expand All @@ -35,10 +36,10 @@ def main_menu(context: TerminalContext, storage: Storage):
project = new_project(context, storage)

if project is not None:
analyzer = new_analysis(context, storage, project)
if analyzer is not None:
analysis_main(context, storage, project, analyzer)
project_main(context, storage, project)
analysis = new_analysis(context, storage, suite, project)
if analysis is not None:
analysis_main(context, storage, suite, analysis)
project_main(context, storage, suite, project)
continue

if action == "load_project":
Expand All @@ -50,5 +51,5 @@ def main_menu(context: TerminalContext, storage: Storage):
):
project = select_project(context, storage)
if project is not None:
project_main(context, storage, project)
project_main(context, storage, suite, project)
continue
Loading