From: Christian Heller Date: Thu, 6 Jun 2024 00:26:29 +0000 (+0200) Subject: Add suppression of implicit ProcessSteps to Process configuration. X-Git-Url: https://plomlompom.com/repos/%7B%7B%20web_path%20%7D%7D/decks/%7B%7Bprefix%7D%7D/static/blog?a=commitdiff_plain;h=48ed70167c50303f46309c5808f93f2ba169b34f;p=plomtask Add suppression of implicit ProcessSteps to Process configuration. --- diff --git a/migrations/init_4.sql b/migrations/init_4.sql deleted file mode 100644 index 067d934..0000000 --- a/migrations/init_4.sql +++ /dev/null @@ -1,130 +0,0 @@ -CREATE TABLE condition_descriptions ( - parent INTEGER NOT NULL, - timestamp TEXT NOT NULL, - description TEXT NOT NULL, - PRIMARY KEY (parent, timestamp), - FOREIGN KEY (parent) REFERENCES conditions(id) -); -CREATE TABLE condition_titles ( - parent INTEGER NOT NULL, - timestamp TEXT NOT NULL, - title TEXT NOT NULL, - PRIMARY KEY (parent, timestamp), - FOREIGN KEY (parent) REFERENCES conditions(id) -); -CREATE TABLE conditions ( - id INTEGER PRIMARY KEY, - is_active BOOLEAN NOT NULL -); -CREATE TABLE days ( - id TEXT PRIMARY KEY, - comment TEXT NOT NULL -); -CREATE TABLE process_blockers ( - process INTEGER NOT NULL, - condition INTEGER NOT NULL, - PRIMARY KEY (process, condition), - FOREIGN KEY (process) REFERENCES processes(id), - FOREIGN KEY (condition) REFERENCES conditions(id) -); -CREATE TABLE process_conditions ( - process INTEGER NOT NULL, - condition INTEGER NOT NULL, - PRIMARY KEY (process, condition), - FOREIGN KEY (process) REFERENCES processes(id), - FOREIGN KEY (condition) REFERENCES conditions(id) -); -CREATE TABLE process_descriptions ( - parent INTEGER NOT NULL, - timestamp TEXT NOT NULL, - description TEXT NOT NULL, - PRIMARY KEY (parent, timestamp), - FOREIGN KEY (parent) REFERENCES processes(id) -); -CREATE TABLE process_disables ( - process INTEGER NOT NULL, - condition INTEGER NOT NULL, - PRIMARY KEY(process, condition), - FOREIGN KEY (process) REFERENCES processes(id), - FOREIGN KEY (condition) REFERENCES conditions(id) -); -CREATE TABLE process_efforts ( - parent INTEGER NOT NULL, - timestamp TEXT NOT NULL, - effort REAL NOT NULL, - PRIMARY KEY (parent, timestamp), - FOREIGN KEY (parent) REFERENCES processes(id) -); -CREATE TABLE process_enables ( - process INTEGER NOT NULL, - condition INTEGER NOT NULL, - PRIMARY KEY(process, condition), - FOREIGN KEY (process) REFERENCES processes(id), - FOREIGN KEY (condition) REFERENCES conditions(id) -); -CREATE TABLE process_steps ( - id INTEGER PRIMARY KEY, - owner INTEGER NOT NULL, - step_process INTEGER NOT NULL, - parent_step INTEGER, - FOREIGN KEY (owner) REFERENCES processes(id), - FOREIGN KEY (step_process) REFERENCES processes(id), - FOREIGN KEY (parent_step) REFERENCES process_steps(step_id) -); -CREATE TABLE process_titles ( - parent INTEGER NOT NULL, - timestamp TEXT NOT NULL, - title TEXT NOT NULL, - PRIMARY KEY (parent, timestamp), - FOREIGN KEY (parent) REFERENCES processes(id) -); -CREATE TABLE processes ( - id INTEGER PRIMARY KEY, - calendarize BOOLEAN NOT NULL DEFAULT FALSE -); -CREATE TABLE todo_blockers ( - todo INTEGER NOT NULL, - condition INTEGER NOT NULL, - PRIMARY KEY (todo, condition), - FOREIGN KEY (todo) REFERENCES todos(id), - FOREIGN KEY (condition) REFERENCES conditions(id) -); -CREATE TABLE todo_children ( - parent INTEGER NOT NULL, - child INTEGER NOT NULL, - PRIMARY KEY (parent, child), - FOREIGN KEY (parent) REFERENCES todos(id), - FOREIGN KEY (child) REFERENCES todos(id) -); -CREATE TABLE todo_conditions ( - todo INTEGER NOT NULL, - condition INTEGER NOT NULL, - PRIMARY KEY(todo, condition), - FOREIGN KEY (todo) REFERENCES todos(id), - FOREIGN KEY (condition) REFERENCES conditions(id) -); -CREATE TABLE todo_disables ( - todo INTEGER NOT NULL, - condition INTEGER NOT NULL, - PRIMARY KEY(todo, condition), - FOREIGN KEY (todo) REFERENCES todos(id), - FOREIGN KEY (condition) REFERENCES conditions(id) -); -CREATE TABLE todo_enables ( - todo INTEGER NOT NULL, - condition INTEGER NOT NULL, - PRIMARY KEY(todo, condition), - FOREIGN KEY (todo) REFERENCES todos(id), - FOREIGN KEY (condition) REFERENCES conditions(id) -); -CREATE TABLE todos ( - id INTEGER PRIMARY KEY, - process INTEGER NOT NULL, - is_done BOOLEAN NOT NULL, - day TEXT NOT NULL, - comment TEXT NOT NULL DEFAULT "", - effort REAL, - calendarize BOOLEAN NOT NULL DEFAULT FALSE, - FOREIGN KEY (process) REFERENCES processes(id), - FOREIGN KEY (day) REFERENCES days(id) -); diff --git a/migrations/init_5.sql b/migrations/init_5.sql new file mode 100644 index 0000000..d539446 --- /dev/null +++ b/migrations/init_5.sql @@ -0,0 +1,137 @@ +CREATE TABLE condition_descriptions ( + parent INTEGER NOT NULL, + timestamp TEXT NOT NULL, + description TEXT NOT NULL, + PRIMARY KEY (parent, timestamp), + FOREIGN KEY (parent) REFERENCES conditions(id) +); +CREATE TABLE condition_titles ( + parent INTEGER NOT NULL, + timestamp TEXT NOT NULL, + title TEXT NOT NULL, + PRIMARY KEY (parent, timestamp), + FOREIGN KEY (parent) REFERENCES conditions(id) +); +CREATE TABLE conditions ( + id INTEGER PRIMARY KEY, + is_active BOOLEAN NOT NULL +); +CREATE TABLE days ( + id TEXT PRIMARY KEY, + comment TEXT NOT NULL +); +CREATE TABLE process_blockers ( + process INTEGER NOT NULL, + condition INTEGER NOT NULL, + PRIMARY KEY (process, condition), + FOREIGN KEY (process) REFERENCES processes(id), + FOREIGN KEY (condition) REFERENCES conditions(id) +); +CREATE TABLE process_conditions ( + process INTEGER NOT NULL, + condition INTEGER NOT NULL, + PRIMARY KEY (process, condition), + FOREIGN KEY (process) REFERENCES processes(id), + FOREIGN KEY (condition) REFERENCES conditions(id) +); +CREATE TABLE process_descriptions ( + parent INTEGER NOT NULL, + timestamp TEXT NOT NULL, + description TEXT NOT NULL, + PRIMARY KEY (parent, timestamp), + FOREIGN KEY (parent) REFERENCES processes(id) +); +CREATE TABLE process_disables ( + process INTEGER NOT NULL, + condition INTEGER NOT NULL, + PRIMARY KEY(process, condition), + FOREIGN KEY (process) REFERENCES processes(id), + FOREIGN KEY (condition) REFERENCES conditions(id) +); +CREATE TABLE process_efforts ( + parent INTEGER NOT NULL, + timestamp TEXT NOT NULL, + effort REAL NOT NULL, + PRIMARY KEY (parent, timestamp), + FOREIGN KEY (parent) REFERENCES processes(id) +); +CREATE TABLE process_enables ( + process INTEGER NOT NULL, + condition INTEGER NOT NULL, + PRIMARY KEY(process, condition), + FOREIGN KEY (process) REFERENCES processes(id), + FOREIGN KEY (condition) REFERENCES conditions(id) +); +CREATE TABLE process_step_suppressions ( + process INTEGER NOT NULL, + process_step INTEGER NOT NULL, + PRIMARY KEY (process, process_step), + FOREIGN KEY (process) REFERENCES processes(id), + FOREIGN KEY (process_step) REFERENCES process_steps(id) +); +CREATE TABLE process_steps ( + id INTEGER PRIMARY KEY, + owner INTEGER NOT NULL, + step_process INTEGER NOT NULL, + parent_step INTEGER, + FOREIGN KEY (owner) REFERENCES processes(id), + FOREIGN KEY (step_process) REFERENCES processes(id), + FOREIGN KEY (parent_step) REFERENCES process_steps(step_id) +); +CREATE TABLE process_titles ( + parent INTEGER NOT NULL, + timestamp TEXT NOT NULL, + title TEXT NOT NULL, + PRIMARY KEY (parent, timestamp), + FOREIGN KEY (parent) REFERENCES processes(id) +); +CREATE TABLE processes ( + id INTEGER PRIMARY KEY, + calendarize BOOLEAN NOT NULL DEFAULT FALSE +); +CREATE TABLE todo_blockers ( + todo INTEGER NOT NULL, + condition INTEGER NOT NULL, + PRIMARY KEY (todo, condition), + FOREIGN KEY (todo) REFERENCES todos(id), + FOREIGN KEY (condition) REFERENCES conditions(id) +); +CREATE TABLE todo_children ( + parent INTEGER NOT NULL, + child INTEGER NOT NULL, + PRIMARY KEY (parent, child), + FOREIGN KEY (parent) REFERENCES todos(id), + FOREIGN KEY (child) REFERENCES todos(id) +); +CREATE TABLE todo_conditions ( + todo INTEGER NOT NULL, + condition INTEGER NOT NULL, + PRIMARY KEY(todo, condition), + FOREIGN KEY (todo) REFERENCES todos(id), + FOREIGN KEY (condition) REFERENCES conditions(id) +); +CREATE TABLE todo_disables ( + todo INTEGER NOT NULL, + condition INTEGER NOT NULL, + PRIMARY KEY(todo, condition), + FOREIGN KEY (todo) REFERENCES todos(id), + FOREIGN KEY (condition) REFERENCES conditions(id) +); +CREATE TABLE todo_enables ( + todo INTEGER NOT NULL, + condition INTEGER NOT NULL, + PRIMARY KEY(todo, condition), + FOREIGN KEY (todo) REFERENCES todos(id), + FOREIGN KEY (condition) REFERENCES conditions(id) +); +CREATE TABLE todos ( + id INTEGER PRIMARY KEY, + process INTEGER NOT NULL, + is_done BOOLEAN NOT NULL, + day TEXT NOT NULL, + comment TEXT NOT NULL DEFAULT "", + effort REAL, + calendarize BOOLEAN NOT NULL DEFAULT FALSE, + FOREIGN KEY (process) REFERENCES processes(id), + FOREIGN KEY (day) REFERENCES days(id) +); diff --git a/plomtask/db.py b/plomtask/db.py index 90ec833..2ea7421 100644 --- a/plomtask/db.py +++ b/plomtask/db.py @@ -8,7 +8,7 @@ from typing import Any, Self, TypeVar, Generic from plomtask.exceptions import HandledException, NotFoundException from plomtask.dating import valid_date -EXPECTED_DB_VERSION = 4 +EXPECTED_DB_VERSION = 5 MIGRATIONS_DIR = 'migrations' FILENAME_DB_SCHEMA = f'init_{EXPECTED_DB_VERSION}.sql' PATH_DB_SCHEMA = f'{MIGRATIONS_DIR}/{FILENAME_DB_SCHEMA}' diff --git a/plomtask/http.py b/plomtask/http.py index 2e0fc76..f28b097 100644 --- a/plomtask/http.py +++ b/plomtask/http.py @@ -376,6 +376,8 @@ class TaskHandler(BaseHTTPRequestHandler): process.set_blockers(self.conn, self.form_data.get_all_int('blocker')) process.set_enables(self.conn, self.form_data.get_all_int('enables')) process.set_disables(self.conn, self.form_data.get_all_int('disables')) + process.set_step_suppressions(self.conn, + self.form_data.get_all_int('suppresses')) process.calendarize = self.form_data.get_all_str('calendarize') != [] process.save(self.conn) assert isinstance(process.id_, int) diff --git a/plomtask/processes.py b/plomtask/processes.py index 4ad6e75..6222046 100644 --- a/plomtask/processes.py +++ b/plomtask/processes.py @@ -17,7 +17,8 @@ class ProcessStepsNode: parent_id: int | None is_explicit: bool steps: dict[int, ProcessStepsNode] - seen: bool + seen: bool = False + is_suppressed: bool = False class Process(BaseModel[int], ConditionsRelations): @@ -29,7 +30,9 @@ class Process(BaseModel[int], ConditionsRelations): to_save_relations = [('process_conditions', 'process', 'conditions', 0), ('process_blockers', 'process', 'blockers', 0), ('process_enables', 'process', 'enables', 0), - ('process_disables', 'process', 'disables', 0)] + ('process_disables', 'process', 'disables', 0), + ('process_step_suppressions', 'process', + 'suppressed_steps', 0)] to_search = ['title.newest', 'description.newest'] def __init__(self, id_: int | None, calendarize: bool = False) -> None: @@ -39,6 +42,7 @@ class Process(BaseModel[int], ConditionsRelations): self.description = VersionedAttribute(self, 'process_descriptions', '') self.effort = VersionedAttribute(self, 'process_efforts', 1.0) self.explicit_steps: list[ProcessStep] = [] + self.suppressed_steps: list[ProcessStep] = [] self.calendarize = calendarize @classmethod @@ -55,6 +59,10 @@ class Process(BaseModel[int], ConditionsRelations): process.id_): step = ProcessStep.from_table_row(db_conn, row_) process.explicit_steps += [step] # pylint: disable=no-member + for row_ in db_conn.row_where('process_step_suppressions', 'process', + process.id_): + step = ProcessStep.by_id(db_conn, row_[1]) + process.suppressed_steps += [step] # pylint: disable=no-member for name in ('conditions', 'blockers', 'enables', 'disables'): table = f'process_{name}' assert isinstance(process.id_, int) @@ -78,30 +86,27 @@ class Process(BaseModel[int], ConditionsRelations): Process | None = None) -> dict[int, ProcessStepsNode]: """Return tree of depended-on explicit and implicit ProcessSteps.""" - def make_node(step: ProcessStep) -> ProcessStepsNode: + def make_node(step: ProcessStep, suppressed: bool) -> ProcessStepsNode: is_explicit = False if external_owner is not None: is_explicit = step.owner_id == external_owner.id_ process = self.__class__.by_id(db_conn, step.step_process_id) - step_steps = process.get_steps(db_conn, external_owner) + step_steps = {} + if not suppressed: + step_steps = process.get_steps(db_conn, external_owner) return ProcessStepsNode(process, step.parent_step_id, - is_explicit, step_steps, False) + is_explicit, step_steps, False, suppressed) def walk_steps(node_id: int, node: ProcessStepsNode) -> None: + node.seen = node_id in seen_step_ids + seen_step_ids.add(node_id) + if node.is_suppressed: + return explicit_children = [s for s in self.explicit_steps if s.parent_step_id == node_id] for child in explicit_children: assert isinstance(child.id_, int) - node.steps[child.id_] = make_node(child) - # # ensure that one (!) explicit step of process replaces - # # one (!) implicit step of same process - # for i in [i for i, s in node.steps.items() - # if not s.process_step.owner_id == child.id_ - # and s.process.id_ == child.step_process_id]: - # del node.steps[i] - # break - node.seen = node_id in seen_step_ids - seen_step_ids.add(node_id) + node.steps[child.id_] = make_node(child, False) for id_, step in node.steps.items(): walk_steps(id_, step) @@ -112,11 +117,20 @@ class Process(BaseModel[int], ConditionsRelations): for step in [s for s in self.explicit_steps if s.parent_step_id is None]: assert isinstance(step.id_, int) - steps[step.id_] = make_node(step) + new_node = make_node(step, step in external_owner.suppressed_steps) + steps[step.id_] = new_node for step_id, step_node in steps.items(): walk_steps(step_id, step_node) return steps + def set_step_suppressions(self, db_conn: DatabaseConnection, + step_ids: list[int]) -> None: + """Set self.suppressed_steps from step_ids.""" + assert isinstance(self.id_, int) + db_conn.delete_where('process_step_suppressions', 'process', self.id_) + self.suppressed_steps = [ProcessStep.by_id(db_conn, s) + for s in step_ids] + def set_steps(self, db_conn: DatabaseConnection, steps: list[ProcessStep]) -> None: """Set self.explicit_steps in bulk. diff --git a/plomtask/todos.py b/plomtask/todos.py index 69a19c9..de6438c 100644 --- a/plomtask/todos.py +++ b/plomtask/todos.py @@ -91,6 +91,8 @@ class Todo(BaseModel[int], ConditionsRelations): sub_step_nodes = list(step_node.steps.values()) sub_step_nodes.sort(key=key_order_func) for sub_node in sub_step_nodes: + if sub_node.is_suppressed: + continue n_slots = len([n for n in sub_step_nodes if n.process == sub_node.process]) filled_slots = len([t for t in satisfier.children @@ -107,6 +109,8 @@ class Todo(BaseModel[int], ConditionsRelations): todo.save(db_conn) steps_tree = process.get_steps(db_conn) for step_node in steps_tree.values(): + if step_node.is_suppressed: + continue todo.add_child(walk_steps(todo, step_node)) todo.save(db_conn) return todo diff --git a/templates/process.html b/templates/process.html index 9df8b45..200c6ea 100644 --- a/templates/process.html +++ b/templates/process.html @@ -14,15 +14,19 @@ {% endif %} {% for i in range(indent) %}+{%endfor %} -{% if (not step_node.is_explicit) and step_node.seen %} +{% if step_node.is_suppressed %}{% endif %} +{% if step_node.seen %} ({{step_node.process.title.newest|e}}) {% else %} {{step_node.process.title.newest|e}} {% endif %} +{% if step_node.is_suppressed %}{% endif %} {% if step_node.is_explicit %} add sub-step: +{% elif step_node.seen %} + suppress {% endif %} @@ -90,7 +94,7 @@ add sub-step: add: - + step of @@ -99,14 +103,14 @@ add: {{owner.title.newest|e}}
{% endfor %} - + todos {{n_todos}}
- + {{ macros.edit_buttons() }} diff --git a/tests/processes.py b/tests/processes.py index 930e560..7701aa0 100644 --- a/tests/processes.py +++ b/tests/processes.py @@ -84,6 +84,7 @@ class TestsWithDB(TestCaseWithDB): def test_Process_steps(self) -> None: """Test addition, nesting, and non-recursion of ProcessSteps""" # pylint: disable=too-many-locals + # pylint: disable=too-many-statements p1, p2, p3 = self.three_processes() assert isinstance(p1.id_, int) assert isinstance(p2.id_, int) @@ -94,13 +95,13 @@ class TestsWithDB(TestCaseWithDB): steps_p1 += [s_p2_to_p1] p1.set_steps(self.db_conn, steps_p1) p1_dict: dict[int, ProcessStepsNode] = {} - p1_dict[1] = ProcessStepsNode(p2, None, True, {}, False) + p1_dict[1] = ProcessStepsNode(p2, None, True, {}) self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict) # add step of process p3 as second (top-level) step to p1 s_p3_to_p1 = ProcessStep(None, p1.id_, p3.id_, None) steps_p1 += [s_p3_to_p1] p1.set_steps(self.db_conn, steps_p1) - p1_dict[2] = ProcessStepsNode(p3, None, True, {}, False) + p1_dict[2] = ProcessStepsNode(p3, None, True, {}) self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict) # add step of process p3 as first (top-level) step to p2, steps_p2: list[ProcessStep] = [] @@ -108,7 +109,7 @@ class TestsWithDB(TestCaseWithDB): steps_p2 += [s_p3_to_p2] p2.set_steps(self.db_conn, steps_p2) # expect it as implicit sub-step of p1's second (p3) step - p2_dict = {3: ProcessStepsNode(p3, None, False, {}, False)} + p2_dict = {3: ProcessStepsNode(p3, None, False, {})} p1_dict[1].steps[3] = p2_dict[3] self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict) # add step of process p2 as explicit sub-step to p1's second sub-step @@ -117,21 +118,21 @@ class TestsWithDB(TestCaseWithDB): p1.set_steps(self.db_conn, steps_p1) seen_3 = ProcessStepsNode(p3, None, False, {}, True) p1_dict[2].steps[4] = ProcessStepsNode(p2, s_p3_to_p1.id_, True, - {3: seen_3}, False) + {3: seen_3}) self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict) # add step of process p3 as explicit sub-step to non-existing p1 # sub-step (of id=999), expect it to become another p1 top-level step s_p3_to_p1_999 = ProcessStep(None, p1.id_, p3.id_, 999) steps_p1 += [s_p3_to_p1_999] p1.set_steps(self.db_conn, steps_p1) - p1_dict[5] = ProcessStepsNode(p3, None, True, {}, False) + p1_dict[5] = ProcessStepsNode(p3, None, True, {}) self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict) # add step of process p3 as explicit sub-step to p1's implicit p3 # sub-step, expect it to become another p1 top-level step s_p3_to_p1_impl_p3 = ProcessStep(None, p1.id_, p3.id_, s_p3_to_p2.id_) steps_p1 += [s_p3_to_p1_impl_p3] p1.set_steps(self.db_conn, steps_p1) - p1_dict[6] = ProcessStepsNode(p3, None, True, {}, False) + p1_dict[6] = ProcessStepsNode(p3, None, True, {}) self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict) self.assertEqual(p1.used_as_step_by(self.db_conn), []) self.assertEqual(p2.used_as_step_by(self.db_conn), [p1]) @@ -140,7 +141,7 @@ class TestsWithDB(TestCaseWithDB): # # expect it to eliminate implicit p3 sub-step # s_p3_to_p1_first_explicit = ProcessStep(None, p1.id_, p3.id_, # s_p2_to_p1.id_) - # p1_dict[1].steps = {7: ProcessStepsNode(p3, 1, True, {}, False)} + # p1_dict[1].steps = {7: ProcessStepsNode(p3, 1, True, {})} # p1_dict[2].steps[4].steps[3].seen = False # steps_p1 += [s_p3_to_p1_first_explicit] # p1.set_steps(self.db_conn, steps_p1) @@ -149,11 +150,18 @@ class TestsWithDB(TestCaseWithDB): s_p3_to_p2_first = ProcessStep(None, p2.id_, p3.id_, s_p3_to_p2.id_) steps_p2 += [s_p3_to_p2_first] p2.set_steps(self.db_conn, steps_p2) - p1_dict[1].steps[3].steps[7] = ProcessStepsNode(p3, 3, False, {}, - False) + p1_dict[1].steps[3].steps[7] = ProcessStepsNode(p3, 3, False, {}) p1_dict[2].steps[4].steps[3].steps[7] = ProcessStepsNode(p3, 3, False, {}, True) self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict) + # ensure suppressed step nodes are hidden + assert isinstance(s_p3_to_p2.id_, int) + p1.set_step_suppressions(self.db_conn, [s_p3_to_p2.id_]) + p1_dict[1].steps[3].steps = {} + p1_dict[1].steps[3].is_suppressed = True + p1_dict[2].steps[4].steps[3].steps = {} + p1_dict[2].steps[4].steps[3].is_suppressed = True + self.assertEqual(p1.get_steps(self.db_conn), p1_dict) def test_Process_conditions(self) -> None: """Test setting Process.conditions/enables/disables."""