Commit
Β·
2c888f0
1
Parent(s):
7725750
feat(ui): add durations per task and copyable mermaid diagram text
Browse files- Adds an editable table for durations per task in minutes, with state storage
- uses per-task durations if provided
- Adds output code blocks to let user easily copy/paste the raw mermaid.js code for Gantt and flowchart visualizations
- UI in main gradio interface updated, including sync events
π€ Generated with opencode
Co-Authored-By: opencode <[email protected]>
- OpenCode.md +6 -5
- app.py +112 -23
- test_app.py +13 -1
OpenCode.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
| 1 |
# OpenCode Guide for or-tools repo
|
| 2 |
|
| 3 |
## Commands
|
| 4 |
-
- **Install deps:** `
|
| 5 |
-
- **
|
| 6 |
-
- **
|
| 7 |
-
- **
|
|
|
|
| 8 |
- **No tests currently found.**
|
| 9 |
|
| 10 |
## Code Style & Conventions
|
|
@@ -16,6 +17,6 @@
|
|
| 16 |
- **Error handling:** Prefer informative exceptions & user messages. Wrap untrusted input in try/except when user-facing.
|
| 17 |
- **Docstrings:** Use triple-double-quote for all public functions/classes. Prefer Google or NumPy style.
|
| 18 |
- **No tests yet:** Place tests in files named `test_*.py` when adding.
|
| 19 |
-
- **Dependencies:** Only add to `pyproject.toml
|
| 20 |
|
| 21 |
*See https://docs.astral.sh/ruff/ for further lint rules. Contribute new code in line with this guide.*
|
|
|
|
| 1 |
# OpenCode Guide for or-tools repo
|
| 2 |
|
| 3 |
## Commands
|
| 4 |
+
- **Install deps:** `uv sync`
|
| 5 |
+
- **Add deps: `uv add dep`
|
| 6 |
+
- **Lint:** `uv run ruff check app.py`
|
| 7 |
+
- **Format:** `uv run ruff format app.py`
|
| 8 |
+
- **Run Gradio app:** `uv run app.py`
|
| 9 |
- **No tests currently found.**
|
| 10 |
|
| 11 |
## Code Style & Conventions
|
|
|
|
| 17 |
- **Error handling:** Prefer informative exceptions & user messages. Wrap untrusted input in try/except when user-facing.
|
| 18 |
- **Docstrings:** Use triple-double-quote for all public functions/classes. Prefer Google or NumPy style.
|
| 19 |
- **No tests yet:** Place tests in files named `test_*.py` when adding.
|
| 20 |
+
- **Dependencies:** Only add to `pyproject.toml`
|
| 21 |
|
| 22 |
*See https://docs.astral.sh/ruff/ for further lint rules. Contribute new code in line with this guide.*
|
app.py
CHANGED
|
@@ -139,29 +139,39 @@ def parse_tasks(tasks_text):
|
|
| 139 |
return sorted(tasks), original_names
|
| 140 |
|
| 141 |
|
| 142 |
-
def generate_mermaid_gantt(task_order, original_names):
|
| 143 |
-
"""Generate Mermaid Gantt chart syntax."""
|
| 144 |
if not task_order:
|
| 145 |
-
return "gantt\n title Task Execution Timeline\n dateFormat YYYY-MM-DD\n section No Tasks\n No tasks to display : 2024-01-01,
|
| 146 |
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
| 149 |
|
| 150 |
gantt = "```mermaid\ngantt\n"
|
| 151 |
gantt += " title Task Execution Timeline\n"
|
| 152 |
-
gantt += " dateFormat YYYY-MM-DD\n"
|
| 153 |
gantt += " section Tasks\n"
|
| 154 |
|
| 155 |
-
|
| 156 |
for i, task in enumerate(task_order):
|
| 157 |
display_name = original_names.get(task, display_task_name(task))
|
| 158 |
# Clean task name for Mermaid (remove special chars)
|
| 159 |
clean_name = re.sub(r"[^a-zA-Z0-9\s]", "", display_name)
|
| 160 |
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
-
|
|
|
|
|
|
|
| 165 |
|
| 166 |
gantt += "```"
|
| 167 |
return gantt
|
|
@@ -302,12 +312,17 @@ def format_dependencies_display(dependencies_list):
|
|
| 302 |
return display
|
| 303 |
|
| 304 |
|
| 305 |
-
def solve_dependencies(tasks_text, dependencies_list):
|
| 306 |
"""Solve the task ordering problem."""
|
| 307 |
tasks, task_original_names = parse_tasks(tasks_text)
|
| 308 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
if not tasks:
|
| 310 |
-
return "β Please enter some tasks!", "", "", "", ""
|
| 311 |
|
| 312 |
if not dependencies_list:
|
| 313 |
# No dependencies, just return tasks in alphabetical order
|
|
@@ -319,9 +334,12 @@ def solve_dependencies(tasks_text, dependencies_list):
|
|
| 319 |
output += f"{i}. {task_display}\n"
|
| 320 |
|
| 321 |
json_output = json.dumps(display_tasks, indent=2)
|
| 322 |
-
gantt = generate_mermaid_gantt(tasks, task_original_names)
|
| 323 |
flowchart = generate_mermaid_flowchart({}, tasks, task_original_names)
|
| 324 |
-
|
|
|
|
|
|
|
|
|
|
| 325 |
|
| 326 |
try:
|
| 327 |
dependencies, all_tasks, dep_original_names = parse_requirements(
|
|
@@ -368,10 +386,12 @@ def solve_dependencies(tasks_text, dependencies_list):
|
|
| 368 |
result_display.append(task_display)
|
| 369 |
|
| 370 |
json_output = json.dumps(result_display, indent=2)
|
| 371 |
-
gantt = generate_mermaid_gantt(result, all_original_names)
|
| 372 |
flowchart = generate_mermaid_flowchart(
|
| 373 |
dependencies, all_tasks, all_original_names
|
| 374 |
)
|
|
|
|
|
|
|
| 375 |
|
| 376 |
else:
|
| 377 |
# Try maximum subset
|
|
@@ -401,22 +421,26 @@ def solve_dependencies(tasks_text, dependencies_list):
|
|
| 401 |
output += f"β’ {task_display}\n"
|
| 402 |
|
| 403 |
json_output = json.dumps(result_display, indent=2)
|
| 404 |
-
gantt = generate_mermaid_gantt(result, all_original_names)
|
| 405 |
flowchart = generate_mermaid_flowchart(
|
| 406 |
dependencies, all_tasks, all_original_names
|
| 407 |
)
|
|
|
|
|
|
|
| 408 |
else:
|
| 409 |
output = "β **No solution found!** There might be complex circular dependencies."
|
| 410 |
json_output = "[]"
|
| 411 |
-
gantt = generate_mermaid_gantt([], all_original_names)
|
| 412 |
flowchart = generate_mermaid_flowchart(
|
| 413 |
dependencies, all_tasks, all_original_names
|
| 414 |
)
|
|
|
|
|
|
|
| 415 |
|
| 416 |
-
return output, dep_summary, json_output, gantt, flowchart
|
| 417 |
|
| 418 |
except Exception as e:
|
| 419 |
-
return f"β **Error:** {str(e)}", "", "", "", ""
|
| 420 |
|
| 421 |
|
| 422 |
# Example tasks
|
|
@@ -438,13 +462,16 @@ with gr.Blocks(title="Task Dependency Solver", theme=gr.themes.Soft()) as demo:
|
|
| 438 |
|
| 439 |
**How to use:**
|
| 440 |
1. Enter your tasks (one per line or comma-separated)
|
| 441 |
-
2.
|
| 442 |
-
3.
|
| 443 |
-
4. Click "
|
|
|
|
|
|
|
| 444 |
""")
|
| 445 |
|
| 446 |
# State to store current dependencies
|
| 447 |
dependencies_state = gr.State([])
|
|
|
|
| 448 |
|
| 449 |
with gr.Row():
|
| 450 |
with gr.Column(scale=1):
|
|
@@ -457,6 +484,18 @@ with gr.Blocks(title="Task Dependency Solver", theme=gr.themes.Soft()) as demo:
|
|
| 457 |
info="Case-insensitive: 'Sleep' and 'sleep' are treated the same",
|
| 458 |
)
|
| 459 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 460 |
gr.Markdown("### π Step 2: Build Dependencies")
|
| 461 |
task_dropdown = gr.Dropdown(
|
| 462 |
label="Select Task",
|
|
@@ -512,12 +551,14 @@ with gr.Blocks(title="Task Dependency Solver", theme=gr.themes.Soft()) as demo:
|
|
| 512 |
gantt_chart = gr.Markdown(
|
| 513 |
value="gantt\n title Task Execution Timeline\n dateFormat YYYY-MM-DD\n section Tasks\n Click Solve to see timeline : 2024-01-01, 1d"
|
| 514 |
)
|
|
|
|
| 515 |
|
| 516 |
with gr.Column(scale=1):
|
| 517 |
gr.Markdown("#### π Dependency Flowchart")
|
| 518 |
dependency_flowchart = gr.Markdown(
|
| 519 |
value="flowchart TD\n A[Click Solve to see dependencies]"
|
| 520 |
)
|
|
|
|
| 521 |
|
| 522 |
# Examples section
|
| 523 |
with gr.Accordion("π Quick Examples", open=False):
|
|
@@ -623,12 +664,52 @@ with gr.Blocks(title="Task Dependency Solver", theme=gr.themes.Soft()) as demo:
|
|
| 623 |
gr.CheckboxGroup(choices=[], value=[]),
|
| 624 |
)
|
| 625 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 626 |
# Wire up events
|
| 627 |
tasks_input.change(
|
| 628 |
fn=update_ui_after_tasks_change,
|
| 629 |
inputs=[tasks_input, dependencies_state],
|
| 630 |
outputs=[task_dropdown, requirements_checkbox, remove_deps],
|
| 631 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 632 |
|
| 633 |
add_btn.click(
|
| 634 |
fn=handle_add_dependencies,
|
|
@@ -656,13 +737,15 @@ with gr.Blocks(title="Task Dependency Solver", theme=gr.themes.Soft()) as demo:
|
|
| 656 |
|
| 657 |
solve_btn.click(
|
| 658 |
fn=solve_dependencies,
|
| 659 |
-
inputs=[tasks_input, dependencies_state],
|
| 660 |
outputs=[
|
| 661 |
result_output,
|
| 662 |
dep_analysis,
|
| 663 |
json_output,
|
| 664 |
gantt_chart,
|
| 665 |
dependency_flowchart,
|
|
|
|
|
|
|
| 666 |
],
|
| 667 |
)
|
| 668 |
|
|
@@ -688,6 +771,12 @@ with gr.Blocks(title="Task Dependency Solver", theme=gr.themes.Soft()) as demo:
|
|
| 688 |
inputs=[tasks_input, dependencies_state],
|
| 689 |
outputs=[task_dropdown, requirements_checkbox, remove_deps],
|
| 690 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 691 |
|
| 692 |
if __name__ == "__main__":
|
| 693 |
demo.launch()
|
|
|
|
| 139 |
return sorted(tasks), original_names
|
| 140 |
|
| 141 |
|
| 142 |
+
def generate_mermaid_gantt(task_order, original_names, durations=None, start_time=None):
|
| 143 |
+
"""Generate Mermaid Gantt chart syntax with minute granularity and selectable start time."""
|
| 144 |
if not task_order:
|
| 145 |
+
return "gantt\n title Task Execution Timeline\n dateFormat YYYY-MM-DD HH:mm\n section No Tasks\n No tasks to display : 2024-01-01 08:00, 1m"
|
| 146 |
|
| 147 |
+
if start_time is None:
|
| 148 |
+
start_date = datetime.now().replace(second=0, microsecond=0)
|
| 149 |
+
else:
|
| 150 |
+
start_date = start_time.replace(second=0, microsecond=0)
|
| 151 |
|
| 152 |
gantt = "```mermaid\ngantt\n"
|
| 153 |
gantt += " title Task Execution Timeline\n"
|
| 154 |
+
gantt += " dateFormat YYYY-MM-DD HH:mm\n"
|
| 155 |
gantt += " section Tasks\n"
|
| 156 |
|
| 157 |
+
current_dt = start_date
|
| 158 |
for i, task in enumerate(task_order):
|
| 159 |
display_name = original_names.get(task, display_task_name(task))
|
| 160 |
# Clean task name for Mermaid (remove special chars)
|
| 161 |
clean_name = re.sub(r"[^a-zA-Z0-9\s]", "", display_name)
|
| 162 |
|
| 163 |
+
# Get duration for this task in minutes (default 1m if not specified)
|
| 164 |
+
minutes = 1
|
| 165 |
+
if durations and task in durations:
|
| 166 |
+
minutes = durations[task]
|
| 167 |
+
elif durations is None:
|
| 168 |
+
minutes = 1
|
| 169 |
+
else:
|
| 170 |
+
minutes = durations.get(task, 1)
|
| 171 |
|
| 172 |
+
task_start = current_dt.strftime("%Y-%m-%d %H:%M")
|
| 173 |
+
gantt += f" {clean_name} : {task_start}, {minutes}m\n"
|
| 174 |
+
current_dt += timedelta(minutes=minutes)
|
| 175 |
|
| 176 |
gantt += "```"
|
| 177 |
return gantt
|
|
|
|
| 312 |
return display
|
| 313 |
|
| 314 |
|
| 315 |
+
def solve_dependencies(tasks_text, dependencies_list, durations_state=None):
|
| 316 |
"""Solve the task ordering problem."""
|
| 317 |
tasks, task_original_names = parse_tasks(tasks_text)
|
| 318 |
|
| 319 |
+
durations = durations_state or {}
|
| 320 |
+
# durations is a dict: {normalized_task_name: int_minutes}, may be string-int if loaded from input
|
| 321 |
+
# normalize keys and intify
|
| 322 |
+
durations = {normalize_task_name(k): int(v) for k, v in durations.items() if str(v).isdigit() and int(v) > 0}
|
| 323 |
+
|
| 324 |
if not tasks:
|
| 325 |
+
return "β Please enter some tasks!", "", "", "", "", "", ""
|
| 326 |
|
| 327 |
if not dependencies_list:
|
| 328 |
# No dependencies, just return tasks in alphabetical order
|
|
|
|
| 334 |
output += f"{i}. {task_display}\n"
|
| 335 |
|
| 336 |
json_output = json.dumps(display_tasks, indent=2)
|
| 337 |
+
gantt = generate_mermaid_gantt(tasks, task_original_names, durations=durations)
|
| 338 |
flowchart = generate_mermaid_flowchart({}, tasks, task_original_names)
|
| 339 |
+
# Provide raw for copy-paste, strip code block
|
| 340 |
+
gantt_raw = gantt.replace('```mermaid\n', '').rstrip('`\n')
|
| 341 |
+
flow_raw = flowchart.replace('```mermaid\n', '').rstrip('`\n')
|
| 342 |
+
return output, "", json_output, gantt, flowchart, gantt_raw, flow_raw
|
| 343 |
|
| 344 |
try:
|
| 345 |
dependencies, all_tasks, dep_original_names = parse_requirements(
|
|
|
|
| 386 |
result_display.append(task_display)
|
| 387 |
|
| 388 |
json_output = json.dumps(result_display, indent=2)
|
| 389 |
+
gantt = generate_mermaid_gantt(result, all_original_names, durations=durations)
|
| 390 |
flowchart = generate_mermaid_flowchart(
|
| 391 |
dependencies, all_tasks, all_original_names
|
| 392 |
)
|
| 393 |
+
gantt_raw = gantt.replace('```mermaid\n', '').rstrip('`\n')
|
| 394 |
+
flow_raw = flowchart.replace('```mermaid\n', '').rstrip('`\n')
|
| 395 |
|
| 396 |
else:
|
| 397 |
# Try maximum subset
|
|
|
|
| 421 |
output += f"β’ {task_display}\n"
|
| 422 |
|
| 423 |
json_output = json.dumps(result_display, indent=2)
|
| 424 |
+
gantt = generate_mermaid_gantt(result, all_original_names, durations=durations)
|
| 425 |
flowchart = generate_mermaid_flowchart(
|
| 426 |
dependencies, all_tasks, all_original_names
|
| 427 |
)
|
| 428 |
+
gantt_raw = gantt.replace('```mermaid\n', '').rstrip('`\n')
|
| 429 |
+
flow_raw = flowchart.replace('```mermaid\n', '').rstrip('`\n')
|
| 430 |
else:
|
| 431 |
output = "β **No solution found!** There might be complex circular dependencies."
|
| 432 |
json_output = "[]"
|
| 433 |
+
gantt = generate_mermaid_gantt([], all_original_names, durations=durations)
|
| 434 |
flowchart = generate_mermaid_flowchart(
|
| 435 |
dependencies, all_tasks, all_original_names
|
| 436 |
)
|
| 437 |
+
gantt_raw = gantt.replace('```mermaid\n', '').rstrip('`\n')
|
| 438 |
+
flow_raw = flowchart.replace('```mermaid\n', '').rstrip('`\n')
|
| 439 |
|
| 440 |
+
return output, dep_summary, json_output, gantt, flowchart, gantt_raw, flow_raw
|
| 441 |
|
| 442 |
except Exception as e:
|
| 443 |
+
return f"β **Error:** {str(e)}", "", "", "", "", "", ""
|
| 444 |
|
| 445 |
|
| 446 |
# Example tasks
|
|
|
|
| 462 |
|
| 463 |
**How to use:**
|
| 464 |
1. Enter your tasks (one per line or comma-separated)
|
| 465 |
+
2. (Optional) Enter the expected duration (in minutes) for each task.
|
| 466 |
+
3. Select a task, then select all its requirements at once
|
| 467 |
+
4. Click "Add Dependencies" to add them all
|
| 468 |
+
5. Click "Solve Dependencies" to get the optimal execution order with visualizations
|
| 469 |
+
6. You can copy and paste the generated Mermaid.js text below each diagram for use elsewhere!
|
| 470 |
""")
|
| 471 |
|
| 472 |
# State to store current dependencies
|
| 473 |
dependencies_state = gr.State([])
|
| 474 |
+
durations_state = gr.State({}) # dict: normalized_task -> int (user input durations)
|
| 475 |
|
| 476 |
with gr.Row():
|
| 477 |
with gr.Column(scale=1):
|
|
|
|
| 484 |
info="Case-insensitive: 'Sleep' and 'sleep' are treated the same",
|
| 485 |
)
|
| 486 |
|
| 487 |
+
gr.Markdown("### β±οΈ (Optional) Step 1.5: Set Task Durations (minutes)")
|
| 488 |
+
durations_table = gr.Dataframe(
|
| 489 |
+
headers=["Task", "Minutes"],
|
| 490 |
+
datatype=["str", "number"],
|
| 491 |
+
value=[],
|
| 492 |
+
col_count=(2, "fixed"),
|
| 493 |
+
row_count=(0, "dynamic"),
|
| 494 |
+
label="Set duration (minutes) for each listed task.",
|
| 495 |
+
interactive=True,
|
| 496 |
+
wrap=True
|
| 497 |
+
)
|
| 498 |
+
|
| 499 |
gr.Markdown("### π Step 2: Build Dependencies")
|
| 500 |
task_dropdown = gr.Dropdown(
|
| 501 |
label="Select Task",
|
|
|
|
| 551 |
gantt_chart = gr.Markdown(
|
| 552 |
value="gantt\n title Task Execution Timeline\n dateFormat YYYY-MM-DD\n section Tasks\n Click Solve to see timeline : 2024-01-01, 1d"
|
| 553 |
)
|
| 554 |
+
gantt_mermaid = gr.Code(label="Copy-paste Gantt Mermaid.js", value="", language="markdown")
|
| 555 |
|
| 556 |
with gr.Column(scale=1):
|
| 557 |
gr.Markdown("#### π Dependency Flowchart")
|
| 558 |
dependency_flowchart = gr.Markdown(
|
| 559 |
value="flowchart TD\n A[Click Solve to see dependencies]"
|
| 560 |
)
|
| 561 |
+
flow_mermaid = gr.Code(label="Copy-paste Flowchart Mermaid.js", value="", language="markdown")
|
| 562 |
|
| 563 |
# Examples section
|
| 564 |
with gr.Accordion("π Quick Examples", open=False):
|
|
|
|
| 664 |
gr.CheckboxGroup(choices=[], value=[]),
|
| 665 |
)
|
| 666 |
|
| 667 |
+
# Utility: update durations table to match tasks
|
| 668 |
+
def update_durations_table(tasks_text, old_duration_rows):
|
| 669 |
+
tasks, original_names = parse_tasks(tasks_text)
|
| 670 |
+
shown_task_set = set()
|
| 671 |
+
updated_rows = []
|
| 672 |
+
prev_map = {normalize_task_name(row[0]): row[1] for row in old_duration_rows if len(row) >= 2 and str(row[0]).strip()}
|
| 673 |
+
for task in tasks:
|
| 674 |
+
disp = original_names.get(task, display_task_name(task))
|
| 675 |
+
shown_task_set.add(task)
|
| 676 |
+
duration = prev_map.get(task, "")
|
| 677 |
+
updated_rows.append([disp, duration])
|
| 678 |
+
return updated_rows
|
| 679 |
+
|
| 680 |
+
# Converts Dataframe rows to durations dict (normalized)
|
| 681 |
+
def table_rows_to_durations(table_rows):
|
| 682 |
+
durations = {}
|
| 683 |
+
for row in table_rows:
|
| 684 |
+
if len(row) >= 2 and str(row[0]).strip() and str(row[1]).strip():
|
| 685 |
+
try:
|
| 686 |
+
val = int(float(row[1]))
|
| 687 |
+
if val > 0:
|
| 688 |
+
durations[normalize_task_name(row[0])] = val
|
| 689 |
+
except Exception:
|
| 690 |
+
continue
|
| 691 |
+
return durations
|
| 692 |
+
|
| 693 |
+
def update_durations_state(rows):
|
| 694 |
+
return table_rows_to_durations(rows)
|
| 695 |
+
|
| 696 |
# Wire up events
|
| 697 |
tasks_input.change(
|
| 698 |
fn=update_ui_after_tasks_change,
|
| 699 |
inputs=[tasks_input, dependencies_state],
|
| 700 |
outputs=[task_dropdown, requirements_checkbox, remove_deps],
|
| 701 |
)
|
| 702 |
+
# Keep durations table in sync
|
| 703 |
+
tasks_input.change(
|
| 704 |
+
fn=update_durations_table,
|
| 705 |
+
inputs=[tasks_input, durations_table],
|
| 706 |
+
outputs=[durations_table],
|
| 707 |
+
)
|
| 708 |
+
durations_table.change(
|
| 709 |
+
fn=update_durations_state,
|
| 710 |
+
inputs=[durations_table],
|
| 711 |
+
outputs=[durations_state],
|
| 712 |
+
)
|
| 713 |
|
| 714 |
add_btn.click(
|
| 715 |
fn=handle_add_dependencies,
|
|
|
|
| 737 |
|
| 738 |
solve_btn.click(
|
| 739 |
fn=solve_dependencies,
|
| 740 |
+
inputs=[tasks_input, dependencies_state, durations_state],
|
| 741 |
outputs=[
|
| 742 |
result_output,
|
| 743 |
dep_analysis,
|
| 744 |
json_output,
|
| 745 |
gantt_chart,
|
| 746 |
dependency_flowchart,
|
| 747 |
+
gantt_mermaid,
|
| 748 |
+
flow_mermaid,
|
| 749 |
],
|
| 750 |
)
|
| 751 |
|
|
|
|
| 771 |
inputs=[tasks_input, dependencies_state],
|
| 772 |
outputs=[task_dropdown, requirements_checkbox, remove_deps],
|
| 773 |
)
|
| 774 |
+
# Also set durations table on load
|
| 775 |
+
demo.load(
|
| 776 |
+
fn=update_durations_table,
|
| 777 |
+
inputs=[tasks_input, durations_table],
|
| 778 |
+
outputs=[durations_table],
|
| 779 |
+
)
|
| 780 |
|
| 781 |
if __name__ == "__main__":
|
| 782 |
demo.launch()
|
test_app.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
import pytest
|
| 2 |
-
from app import parse_requirements, parse_tasks, solve_all_tasks, solve_maximum_subset
|
|
|
|
| 3 |
|
| 4 |
def test_parse_requirements_with_spaces():
|
| 5 |
reqs = [
|
|
@@ -47,3 +48,14 @@ def test_solve_maximum_subset_cycle():
|
|
| 47 |
result = solve_maximum_subset(dependencies, all_tasks)
|
| 48 |
# Only d can run because a<->b<->c form a cycle
|
| 49 |
assert result == ["d"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import pytest
|
| 2 |
+
from app import parse_requirements, parse_tasks, solve_all_tasks, solve_maximum_subset, generate_mermaid_gantt
|
| 3 |
+
from datetime import datetime, timedelta
|
| 4 |
|
| 5 |
def test_parse_requirements_with_spaces():
|
| 6 |
reqs = [
|
|
|
|
| 48 |
result = solve_maximum_subset(dependencies, all_tasks)
|
| 49 |
# Only d can run because a<->b<->c form a cycle
|
| 50 |
assert result == ["d"]
|
| 51 |
+
|
| 52 |
+
def test_generate_mermaid_gantt_minutes():
|
| 53 |
+
order = ["a", "b", "c"]
|
| 54 |
+
names = {"a": "Alpha", "b": "Beta", "c": "Gamma"}
|
| 55 |
+
durations = {"a": 15, "b": 30, "c": 45}
|
| 56 |
+
base = datetime(2024, 1, 1, 8, 0)
|
| 57 |
+
gantt = generate_mermaid_gantt(order, names, durations=durations, start_time=base)
|
| 58 |
+
assert "dateFormat YYYY-MM-DD HH:mm" in gantt
|
| 59 |
+
assert ": 2024-01-01 08:00, 15m" in gantt
|
| 60 |
+
assert ": 2024-01-01 08:15, 30m" in gantt
|
| 61 |
+
assert ": 2024-01-01 08:45, 45m" in gantt
|