better-notion 2.0.0__py3-none-any.whl → 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of better-notion might be problematic. Click here for more details.
- better_notion/plugins/official/agents.py +64 -2
- better_notion/plugins/official/agents_cli.py +394 -27
- better_notion/plugins/official/agents_sdk/managers.py +353 -0
- better_notion/plugins/official/agents_sdk/models.py +273 -0
- {better_notion-2.0.0.dist-info → better_notion-2.1.0.dist-info}/METADATA +1 -1
- {better_notion-2.0.0.dist-info → better_notion-2.1.0.dist-info}/RECORD +9 -9
- {better_notion-2.0.0.dist-info → better_notion-2.1.0.dist-info}/WHEEL +0 -0
- {better_notion-2.0.0.dist-info → better_notion-2.1.0.dist-info}/entry_points.txt +0 -0
- {better_notion-2.0.0.dist-info → better_notion-2.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -910,8 +910,51 @@ class AgentsPlugin(CombinedPluginInterface):
|
|
|
910
910
|
typer.echo(agents_cli.tasks_complete(task_id, actual_hours))
|
|
911
911
|
|
|
912
912
|
@tasks_app.command("can-start")
|
|
913
|
-
def tasks_can_start_cmd(
|
|
914
|
-
|
|
913
|
+
def tasks_can_start_cmd(
|
|
914
|
+
task_id: str,
|
|
915
|
+
explain: bool = typer.Option(False, "--explain", help="Show detailed explanation of blocking tasks")
|
|
916
|
+
):
|
|
917
|
+
typer.echo(agents_cli.tasks_can_start(task_id, explain))
|
|
918
|
+
|
|
919
|
+
@tasks_app.command("deps")
|
|
920
|
+
def tasks_deps_cmd(task_id: str):
|
|
921
|
+
typer.echo(agents_cli.tasks_deps(task_id))
|
|
922
|
+
|
|
923
|
+
@tasks_app.command("ready")
|
|
924
|
+
def tasks_ready_cmd(
|
|
925
|
+
version_id: str = typer.Option(None, "--version-id", "-v", help="Filter by version ID")
|
|
926
|
+
):
|
|
927
|
+
typer.echo(agents_cli.tasks_ready(version_id))
|
|
928
|
+
|
|
929
|
+
@tasks_app.command("assign")
|
|
930
|
+
def tasks_assign_cmd(
|
|
931
|
+
task_id: str,
|
|
932
|
+
to: str = typer.Option(..., "--to", help="Person to assign to")
|
|
933
|
+
):
|
|
934
|
+
typer.echo(agents_cli.tasks_assign(task_id, to))
|
|
935
|
+
|
|
936
|
+
@tasks_app.command("unassign")
|
|
937
|
+
def tasks_unassign_cmd(task_id: str):
|
|
938
|
+
typer.echo(agents_cli.tasks_unassign(task_id))
|
|
939
|
+
|
|
940
|
+
@tasks_app.command("reassign")
|
|
941
|
+
def tasks_reassign_cmd(
|
|
942
|
+
task_id: str,
|
|
943
|
+
from_: str = typer.Option(..., "--from", help="Current assignee (for validation)"),
|
|
944
|
+
to: str = typer.Option(..., "--to", help="New assignee")
|
|
945
|
+
):
|
|
946
|
+
typer.echo(agents_cli.tasks_reassign(task_id, from_, to))
|
|
947
|
+
|
|
948
|
+
@tasks_app.command("list-by-assignee")
|
|
949
|
+
def tasks_list_by_assignee_cmd(
|
|
950
|
+
assignee: str,
|
|
951
|
+
status: str = typer.Option(None, "--status", "-s", help="Filter by status")
|
|
952
|
+
):
|
|
953
|
+
typer.echo(agents_cli.tasks_list_by_assignee(assignee, status))
|
|
954
|
+
|
|
955
|
+
@tasks_app.command("list-unassigned")
|
|
956
|
+
def tasks_list_unassigned_cmd():
|
|
957
|
+
typer.echo(agents_cli.tasks_list_unassigned())
|
|
915
958
|
|
|
916
959
|
agents_app.add_typer(tasks_app)
|
|
917
960
|
|
|
@@ -997,6 +1040,10 @@ class AgentsPlugin(CombinedPluginInterface):
|
|
|
997
1040
|
def work_issues_blockers_cmd(project_id: str):
|
|
998
1041
|
typer.echo(agents_cli.work_issues_blockers(project_id))
|
|
999
1042
|
|
|
1043
|
+
@work_issues_app.command("list-blocked-by")
|
|
1044
|
+
def work_issues_list_blocked_by_cmd(work_issue_id: str):
|
|
1045
|
+
typer.echo(agents_cli.work_issues_list_blocked_by(work_issue_id))
|
|
1046
|
+
|
|
1000
1047
|
agents_app.add_typer(work_issues_app)
|
|
1001
1048
|
|
|
1002
1049
|
# Incidents commands (under agents)
|
|
@@ -1041,6 +1088,21 @@ class AgentsPlugin(CombinedPluginInterface):
|
|
|
1041
1088
|
def incidents_sla_violations_cmd():
|
|
1042
1089
|
typer.echo(agents_cli.incidents_sla_violations())
|
|
1043
1090
|
|
|
1091
|
+
@incidents_app.command("link-to-work-issue")
|
|
1092
|
+
def incidents_link_to_work_issue_cmd(
|
|
1093
|
+
incident_id: str,
|
|
1094
|
+
work_issue_id: str
|
|
1095
|
+
):
|
|
1096
|
+
typer.echo(agents_cli.incidents_link_to_work_issue(incident_id, work_issue_id))
|
|
1097
|
+
|
|
1098
|
+
@incidents_app.command("unlink-work-issue")
|
|
1099
|
+
def incidents_unlink_work_issue_cmd(incident_id: str):
|
|
1100
|
+
typer.echo(agents_cli.incidents_unlink_work_issue(incident_id))
|
|
1101
|
+
|
|
1102
|
+
@incidents_app.command("list-caused-by")
|
|
1103
|
+
def incidents_list_caused_by_cmd(work_issue_id: str):
|
|
1104
|
+
typer.echo(agents_cli.incidents_list_caused_by(work_issue_id))
|
|
1105
|
+
|
|
1044
1106
|
agents_app.add_typer(incidents_app)
|
|
1045
1107
|
|
|
1046
1108
|
def register_sdk_models(self) -> dict[str, type]:
|
|
@@ -820,15 +820,17 @@ def tasks_complete(
|
|
|
820
820
|
return asyncio.run(_complete())
|
|
821
821
|
|
|
822
822
|
|
|
823
|
-
def tasks_can_start(task_id: str) -> str:
|
|
823
|
+
def tasks_can_start(task_id: str, explain: bool = False) -> str:
|
|
824
824
|
"""
|
|
825
825
|
Check if a task can start (all dependencies completed).
|
|
826
826
|
|
|
827
827
|
Args:
|
|
828
828
|
task_id: Task page ID
|
|
829
|
+
explain: Show detailed explanation of blocking tasks
|
|
829
830
|
|
|
830
831
|
Example:
|
|
831
832
|
$ notion tasks can-start task_123
|
|
833
|
+
$ notion tasks can-start task_123 --explain
|
|
832
834
|
"""
|
|
833
835
|
async def _can_start() -> str:
|
|
834
836
|
try:
|
|
@@ -837,33 +839,17 @@ def tasks_can_start(task_id: str) -> str:
|
|
|
837
839
|
# Register SDK plugin
|
|
838
840
|
register_agents_sdk_plugin(client)
|
|
839
841
|
|
|
840
|
-
#
|
|
842
|
+
# Use manager method for detailed info
|
|
841
843
|
manager = client.plugin_manager("tasks")
|
|
842
|
-
|
|
843
|
-
can_start = await task.can_start()
|
|
844
|
-
|
|
845
|
-
if not can_start:
|
|
846
|
-
# Get incomplete dependencies
|
|
847
|
-
incomplete = []
|
|
848
|
-
for dep in await task.dependencies():
|
|
849
|
-
if dep.status != "Completed":
|
|
850
|
-
incomplete.append({
|
|
851
|
-
"id": dep.id,
|
|
852
|
-
"title": dep.title,
|
|
853
|
-
"status": dep.status,
|
|
854
|
-
})
|
|
844
|
+
result = await manager.can_start(task_id)
|
|
855
845
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
"
|
|
859
|
-
"
|
|
860
|
-
}
|
|
846
|
+
if explain and not result["can_start"]:
|
|
847
|
+
result["explanation"] = {
|
|
848
|
+
"blocking_tasks": result["incomplete_dependencies"],
|
|
849
|
+
"suggestion": "Wait for dependencies to complete before starting"
|
|
850
|
+
}
|
|
861
851
|
|
|
862
|
-
return format_success(
|
|
863
|
-
"task_id": task.id,
|
|
864
|
-
"can_start": True,
|
|
865
|
-
"message": "All dependencies are completed",
|
|
866
|
-
})
|
|
852
|
+
return format_success(result)
|
|
867
853
|
|
|
868
854
|
except Exception as e:
|
|
869
855
|
return format_error("CAN_START_ERROR", str(e), retry=False)
|
|
@@ -871,6 +857,248 @@ def tasks_can_start(task_id: str) -> str:
|
|
|
871
857
|
return asyncio.run(_can_start())
|
|
872
858
|
|
|
873
859
|
|
|
860
|
+
def tasks_deps(task_id: str) -> str:
|
|
861
|
+
"""
|
|
862
|
+
List all dependencies of a task.
|
|
863
|
+
|
|
864
|
+
Args:
|
|
865
|
+
task_id: Task page ID
|
|
866
|
+
|
|
867
|
+
Example:
|
|
868
|
+
$ notion tasks deps task_123
|
|
869
|
+
"""
|
|
870
|
+
async def _deps() -> str:
|
|
871
|
+
try:
|
|
872
|
+
client = get_client()
|
|
873
|
+
|
|
874
|
+
# Register SDK plugin
|
|
875
|
+
register_agents_sdk_plugin(client)
|
|
876
|
+
|
|
877
|
+
# Use manager method
|
|
878
|
+
manager = client.plugin_manager("tasks")
|
|
879
|
+
result = await manager.deps(task_id)
|
|
880
|
+
|
|
881
|
+
return format_success(result)
|
|
882
|
+
|
|
883
|
+
except Exception as e:
|
|
884
|
+
return format_error("DEPS_ERROR", str(e), retry=False)
|
|
885
|
+
|
|
886
|
+
return asyncio.run(_deps())
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
def tasks_ready(version_id: Optional[str] = None) -> str:
|
|
890
|
+
"""
|
|
891
|
+
List all tasks ready to start (dependencies completed).
|
|
892
|
+
|
|
893
|
+
Args:
|
|
894
|
+
version_id: Filter by version ID
|
|
895
|
+
|
|
896
|
+
Example:
|
|
897
|
+
$ notion tasks ready
|
|
898
|
+
$ notion tasks ready --version-id ver_123
|
|
899
|
+
"""
|
|
900
|
+
async def _ready() -> str:
|
|
901
|
+
try:
|
|
902
|
+
client = get_client()
|
|
903
|
+
|
|
904
|
+
# Register SDK plugin
|
|
905
|
+
register_agents_sdk_plugin(client)
|
|
906
|
+
|
|
907
|
+
# Use manager method
|
|
908
|
+
manager = client.plugin_manager("tasks")
|
|
909
|
+
tasks = await manager.ready(version_id)
|
|
910
|
+
|
|
911
|
+
return format_success({
|
|
912
|
+
"ready_tasks": [
|
|
913
|
+
{
|
|
914
|
+
"id": task.id,
|
|
915
|
+
"title": task.title,
|
|
916
|
+
"status": task.status,
|
|
917
|
+
"priority": task.priority,
|
|
918
|
+
}
|
|
919
|
+
for task in tasks
|
|
920
|
+
],
|
|
921
|
+
"total": len(tasks)
|
|
922
|
+
})
|
|
923
|
+
|
|
924
|
+
except Exception as e:
|
|
925
|
+
return format_error("READY_ERROR", str(e), retry=False)
|
|
926
|
+
|
|
927
|
+
return asyncio.run(_ready())
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
def tasks_assign(task_id: str, to: str) -> str:
|
|
931
|
+
"""
|
|
932
|
+
Assign a task to a person.
|
|
933
|
+
|
|
934
|
+
Args:
|
|
935
|
+
task_id: Task page ID
|
|
936
|
+
to: Name of the person to assign to
|
|
937
|
+
|
|
938
|
+
Example:
|
|
939
|
+
$ notion tasks assign task_123 --to "Alice Chen"
|
|
940
|
+
"""
|
|
941
|
+
async def _assign() -> str:
|
|
942
|
+
try:
|
|
943
|
+
client = get_client()
|
|
944
|
+
|
|
945
|
+
# Register SDK plugin
|
|
946
|
+
register_agents_sdk_plugin(client)
|
|
947
|
+
|
|
948
|
+
# Use manager method
|
|
949
|
+
manager = client.plugin_manager("tasks")
|
|
950
|
+
result = await manager.assign(task_id, to)
|
|
951
|
+
|
|
952
|
+
return format_success(result)
|
|
953
|
+
|
|
954
|
+
except Exception as e:
|
|
955
|
+
return format_error("ASSIGN_ERROR", str(e), retry=False)
|
|
956
|
+
|
|
957
|
+
return asyncio.run(_assign())
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
def tasks_unassign(task_id: str) -> str:
|
|
961
|
+
"""
|
|
962
|
+
Unassign a task.
|
|
963
|
+
|
|
964
|
+
Args:
|
|
965
|
+
task_id: Task page ID
|
|
966
|
+
|
|
967
|
+
Example:
|
|
968
|
+
$ notion tasks unassign task_123
|
|
969
|
+
"""
|
|
970
|
+
async def _unassign() -> str:
|
|
971
|
+
try:
|
|
972
|
+
client = get_client()
|
|
973
|
+
|
|
974
|
+
# Register SDK plugin
|
|
975
|
+
register_agents_sdk_plugin(client)
|
|
976
|
+
|
|
977
|
+
# Use manager method
|
|
978
|
+
manager = client.plugin_manager("tasks")
|
|
979
|
+
result = await manager.unassign(task_id)
|
|
980
|
+
|
|
981
|
+
return format_success(result)
|
|
982
|
+
|
|
983
|
+
except Exception as e:
|
|
984
|
+
return format_error("UNASSIGN_ERROR", str(e), retry=False)
|
|
985
|
+
|
|
986
|
+
return asyncio.run(_unassign())
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
def tasks_reassign(task_id: str, from_: str, to: str) -> str:
|
|
990
|
+
"""
|
|
991
|
+
Reassign a task from one person to another.
|
|
992
|
+
|
|
993
|
+
Args:
|
|
994
|
+
task_id: Task page ID
|
|
995
|
+
from_: Current assignee (for validation)
|
|
996
|
+
to: New assignee
|
|
997
|
+
|
|
998
|
+
Example:
|
|
999
|
+
$ notion tasks reassign task_123 --from "Alice" --to "Bob"
|
|
1000
|
+
"""
|
|
1001
|
+
async def _reassign() -> str:
|
|
1002
|
+
try:
|
|
1003
|
+
client = get_client()
|
|
1004
|
+
|
|
1005
|
+
# Register SDK plugin
|
|
1006
|
+
register_agents_sdk_plugin(client)
|
|
1007
|
+
|
|
1008
|
+
# Use manager method
|
|
1009
|
+
manager = client.plugin_manager("tasks")
|
|
1010
|
+
result = await manager.reassign(task_id, from_, to)
|
|
1011
|
+
|
|
1012
|
+
return format_success(result)
|
|
1013
|
+
|
|
1014
|
+
except Exception as e:
|
|
1015
|
+
return format_error("REASSIGN_ERROR", str(e), retry=False)
|
|
1016
|
+
|
|
1017
|
+
return asyncio.run(_reassign())
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
def tasks_list_by_assignee(
|
|
1021
|
+
assignee: str,
|
|
1022
|
+
status: Optional[str] = None
|
|
1023
|
+
) -> str:
|
|
1024
|
+
"""
|
|
1025
|
+
List tasks assigned to a person.
|
|
1026
|
+
|
|
1027
|
+
Args:
|
|
1028
|
+
assignee: Name of the assignee
|
|
1029
|
+
status: Optional status filter
|
|
1030
|
+
|
|
1031
|
+
Example:
|
|
1032
|
+
$ notion tasks list-by-assignee "Alice Chen"
|
|
1033
|
+
$ notion tasks list-by-assignee "Alice" --status "In Progress"
|
|
1034
|
+
"""
|
|
1035
|
+
async def _list_by_assignee() -> str:
|
|
1036
|
+
try:
|
|
1037
|
+
client = get_client()
|
|
1038
|
+
|
|
1039
|
+
# Register SDK plugin
|
|
1040
|
+
register_agents_sdk_plugin(client)
|
|
1041
|
+
|
|
1042
|
+
# Use manager method
|
|
1043
|
+
manager = client.plugin_manager("tasks")
|
|
1044
|
+
tasks = await manager.list_by_assignee(assignee, status)
|
|
1045
|
+
|
|
1046
|
+
return format_success({
|
|
1047
|
+
"tasks": [
|
|
1048
|
+
{
|
|
1049
|
+
"id": task.id,
|
|
1050
|
+
"title": task.title,
|
|
1051
|
+
"status": task.status,
|
|
1052
|
+
"priority": task.priority,
|
|
1053
|
+
}
|
|
1054
|
+
for task in tasks
|
|
1055
|
+
],
|
|
1056
|
+
"total": len(tasks)
|
|
1057
|
+
})
|
|
1058
|
+
|
|
1059
|
+
except Exception as e:
|
|
1060
|
+
return format_error("LIST_BY_ASSIGNEE_ERROR", str(e), retry=False)
|
|
1061
|
+
|
|
1062
|
+
return asyncio.run(_list_by_assignee())
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
def tasks_list_unassigned() -> str:
|
|
1066
|
+
"""
|
|
1067
|
+
List unassigned tasks.
|
|
1068
|
+
|
|
1069
|
+
Example:
|
|
1070
|
+
$ notion tasks list-unassigned
|
|
1071
|
+
"""
|
|
1072
|
+
async def _list_unassigned() -> str:
|
|
1073
|
+
try:
|
|
1074
|
+
client = get_client()
|
|
1075
|
+
|
|
1076
|
+
# Register SDK plugin
|
|
1077
|
+
register_agents_sdk_plugin(client)
|
|
1078
|
+
|
|
1079
|
+
# Use manager method
|
|
1080
|
+
manager = client.plugin_manager("tasks")
|
|
1081
|
+
tasks = await manager.list_unassigned()
|
|
1082
|
+
|
|
1083
|
+
return format_success({
|
|
1084
|
+
"unassigned_tasks": [
|
|
1085
|
+
{
|
|
1086
|
+
"id": task.id,
|
|
1087
|
+
"title": task.title,
|
|
1088
|
+
"status": task.status,
|
|
1089
|
+
"priority": task.priority,
|
|
1090
|
+
}
|
|
1091
|
+
for task in tasks
|
|
1092
|
+
],
|
|
1093
|
+
"total": len(tasks)
|
|
1094
|
+
})
|
|
1095
|
+
|
|
1096
|
+
except Exception as e:
|
|
1097
|
+
return format_error("LIST_UNASSIGNED_ERROR", str(e), retry=False)
|
|
1098
|
+
|
|
1099
|
+
return asyncio.run(_list_unassigned())
|
|
1100
|
+
|
|
1101
|
+
|
|
874
1102
|
# ===== IDEAS =====
|
|
875
1103
|
|
|
876
1104
|
def ideas_list(
|
|
@@ -1432,6 +1660,46 @@ def work_issues_blockers(project_id: str) -> str:
|
|
|
1432
1660
|
return asyncio.run(_blockers())
|
|
1433
1661
|
|
|
1434
1662
|
|
|
1663
|
+
def work_issues_list_blocked_by(work_issue_id: str) -> str:
|
|
1664
|
+
"""
|
|
1665
|
+
List tasks blocked by a work issue.
|
|
1666
|
+
|
|
1667
|
+
Args:
|
|
1668
|
+
work_issue_id: Work issue page ID
|
|
1669
|
+
|
|
1670
|
+
Example:
|
|
1671
|
+
$ notion work-issues list-blocked-by issue_456
|
|
1672
|
+
"""
|
|
1673
|
+
async def _list_blocked_by() -> str:
|
|
1674
|
+
try:
|
|
1675
|
+
client = get_client()
|
|
1676
|
+
|
|
1677
|
+
# Register SDK plugin
|
|
1678
|
+
register_agents_sdk_plugin(client)
|
|
1679
|
+
|
|
1680
|
+
# Use manager method
|
|
1681
|
+
manager = client.plugin_manager("work_issues")
|
|
1682
|
+
tasks = await manager.list_blocked_by(work_issue_id)
|
|
1683
|
+
|
|
1684
|
+
return format_success({
|
|
1685
|
+
"blocked_tasks": [
|
|
1686
|
+
{
|
|
1687
|
+
"id": task.id,
|
|
1688
|
+
"title": task.title,
|
|
1689
|
+
"status": task.status,
|
|
1690
|
+
"priority": task.priority,
|
|
1691
|
+
}
|
|
1692
|
+
for task in tasks
|
|
1693
|
+
],
|
|
1694
|
+
"total": len(tasks)
|
|
1695
|
+
})
|
|
1696
|
+
|
|
1697
|
+
except Exception as e:
|
|
1698
|
+
return format_error("LIST_BLOCKED_BY_ERROR", str(e), retry=False)
|
|
1699
|
+
|
|
1700
|
+
return asyncio.run(_list_blocked_by())
|
|
1701
|
+
|
|
1702
|
+
|
|
1435
1703
|
# ===== INCIDENTS =====
|
|
1436
1704
|
|
|
1437
1705
|
def incidents_list(
|
|
@@ -1572,8 +1840,8 @@ def incidents_create(
|
|
|
1572
1840
|
"Project": {"relation": [{"id": project_id}]},
|
|
1573
1841
|
"Severity": {"select": {"name": severity}},
|
|
1574
1842
|
"Type": {"select": {"name": type}},
|
|
1575
|
-
"Status": {"select": {"name": "
|
|
1576
|
-
"
|
|
1843
|
+
"Status": {"select": {"name": "Open"}},
|
|
1844
|
+
"Detected Date": {
|
|
1577
1845
|
"date": {"start": datetime.now(timezone.utc).isoformat()}
|
|
1578
1846
|
},
|
|
1579
1847
|
}
|
|
@@ -1726,3 +1994,102 @@ def incidents_sla_violations() -> str:
|
|
|
1726
1994
|
return format_error("SLA_VIOLATIONS_ERROR", str(e), retry=False)
|
|
1727
1995
|
|
|
1728
1996
|
return asyncio.run(_sla_violations())
|
|
1997
|
+
|
|
1998
|
+
|
|
1999
|
+
def incidents_link_to_work_issue(incident_id: str, work_issue_id: str) -> str:
|
|
2000
|
+
"""
|
|
2001
|
+
Link an incident to a work issue (root cause).
|
|
2002
|
+
|
|
2003
|
+
Args:
|
|
2004
|
+
incident_id: Incident page ID
|
|
2005
|
+
work_issue_id: Work issue page ID
|
|
2006
|
+
|
|
2007
|
+
Example:
|
|
2008
|
+
$ notion incidents link-to-work-issue inc_123 issue_456
|
|
2009
|
+
"""
|
|
2010
|
+
async def _link_to_work_issue() -> str:
|
|
2011
|
+
try:
|
|
2012
|
+
client = get_client()
|
|
2013
|
+
|
|
2014
|
+
# Register SDK plugin
|
|
2015
|
+
register_agents_sdk_plugin(client)
|
|
2016
|
+
|
|
2017
|
+
# Use manager method
|
|
2018
|
+
manager = client.plugin_manager("incidents")
|
|
2019
|
+
result = await manager.link_to_work_issue(incident_id, work_issue_id)
|
|
2020
|
+
|
|
2021
|
+
return format_success(result)
|
|
2022
|
+
|
|
2023
|
+
except Exception as e:
|
|
2024
|
+
return format_error("LINK_TO_WORK_ISSUE_ERROR", str(e), retry=False)
|
|
2025
|
+
|
|
2026
|
+
return asyncio.run(_link_to_work_issue())
|
|
2027
|
+
|
|
2028
|
+
|
|
2029
|
+
def incidents_unlink_work_issue(incident_id: str) -> str:
|
|
2030
|
+
"""
|
|
2031
|
+
Unlink an incident from its work issue.
|
|
2032
|
+
|
|
2033
|
+
Args:
|
|
2034
|
+
incident_id: Incident page ID
|
|
2035
|
+
|
|
2036
|
+
Example:
|
|
2037
|
+
$ notion incidents unlink-work-issue inc_123
|
|
2038
|
+
"""
|
|
2039
|
+
async def _unlink_work_issue() -> str:
|
|
2040
|
+
try:
|
|
2041
|
+
client = get_client()
|
|
2042
|
+
|
|
2043
|
+
# Register SDK plugin
|
|
2044
|
+
register_agents_sdk_plugin(client)
|
|
2045
|
+
|
|
2046
|
+
# Use manager method
|
|
2047
|
+
manager = client.plugin_manager("incidents")
|
|
2048
|
+
result = await manager.unlink_work_issue(incident_id)
|
|
2049
|
+
|
|
2050
|
+
return format_success(result)
|
|
2051
|
+
|
|
2052
|
+
except Exception as e:
|
|
2053
|
+
return format_error("UNLINK_WORK_ISSUE_ERROR", str(e), retry=False)
|
|
2054
|
+
|
|
2055
|
+
return asyncio.run(_unlink_work_issue())
|
|
2056
|
+
|
|
2057
|
+
|
|
2058
|
+
def incidents_list_caused_by(work_issue_id: str) -> str:
|
|
2059
|
+
"""
|
|
2060
|
+
List all incidents caused by a work issue.
|
|
2061
|
+
|
|
2062
|
+
Args:
|
|
2063
|
+
work_issue_id: Work issue page ID
|
|
2064
|
+
|
|
2065
|
+
Example:
|
|
2066
|
+
$ notion incidents list-caused-by issue_456
|
|
2067
|
+
"""
|
|
2068
|
+
async def _list_caused_by() -> str:
|
|
2069
|
+
try:
|
|
2070
|
+
client = get_client()
|
|
2071
|
+
|
|
2072
|
+
# Register SDK plugin
|
|
2073
|
+
register_agents_sdk_plugin(client)
|
|
2074
|
+
|
|
2075
|
+
# Use manager method
|
|
2076
|
+
manager = client.plugin_manager("incidents")
|
|
2077
|
+
incidents = await manager.list_caused_by(work_issue_id)
|
|
2078
|
+
|
|
2079
|
+
return format_success({
|
|
2080
|
+
"incidents": [
|
|
2081
|
+
{
|
|
2082
|
+
"id": incident.id,
|
|
2083
|
+
"title": incident.title,
|
|
2084
|
+
"severity": incident.severity,
|
|
2085
|
+
"status": incident.status,
|
|
2086
|
+
}
|
|
2087
|
+
for incident in incidents
|
|
2088
|
+
],
|
|
2089
|
+
"total": len(incidents)
|
|
2090
|
+
})
|
|
2091
|
+
|
|
2092
|
+
except Exception as e:
|
|
2093
|
+
return format_error("LIST_CAUSED_BY_ERROR", str(e), retry=False)
|
|
2094
|
+
|
|
2095
|
+
return asyncio.run(_list_caused_by())
|
|
@@ -525,6 +525,241 @@ class TaskManager:
|
|
|
525
525
|
|
|
526
526
|
return blocked_tasks
|
|
527
527
|
|
|
528
|
+
async def assign(self, task_id: str, assignee: str) -> dict:
|
|
529
|
+
"""
|
|
530
|
+
Assign a task to a person.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
task_id: Task ID to assign
|
|
534
|
+
assignee: Name of the person to assign to
|
|
535
|
+
|
|
536
|
+
Returns:
|
|
537
|
+
Dict with task_id, assigned_to, and previous assignee
|
|
538
|
+
|
|
539
|
+
Example:
|
|
540
|
+
>>> result = await manager.assign("task_123", "Alice Chen")
|
|
541
|
+
"""
|
|
542
|
+
from better_notion.plugins.official.agents_sdk.models import Task
|
|
543
|
+
|
|
544
|
+
task = await Task.get(task_id, client=self._client)
|
|
545
|
+
previous = task.assignee
|
|
546
|
+
|
|
547
|
+
await task.assign_to(assignee)
|
|
548
|
+
|
|
549
|
+
return {
|
|
550
|
+
"task_id": task_id,
|
|
551
|
+
"assigned_to": assignee,
|
|
552
|
+
"previous": previous
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async def unassign(self, task_id: str) -> dict:
|
|
556
|
+
"""
|
|
557
|
+
Unassign a task.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
task_id: Task ID to unassign
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
Dict with task_id, assigned_to (None), and previous assignee
|
|
564
|
+
|
|
565
|
+
Example:
|
|
566
|
+
>>> result = await manager.unassign("task_123")
|
|
567
|
+
"""
|
|
568
|
+
from better_notion.plugins.official.agents_sdk.models import Task
|
|
569
|
+
|
|
570
|
+
task = await Task.get(task_id, client=self._client)
|
|
571
|
+
previous = task.assignee
|
|
572
|
+
|
|
573
|
+
await task.unassign()
|
|
574
|
+
|
|
575
|
+
return {
|
|
576
|
+
"task_id": task_id,
|
|
577
|
+
"assigned_to": None,
|
|
578
|
+
"previous": previous
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async def reassign(self, task_id: str, from_assignee: str, to_assignee: str) -> dict:
|
|
582
|
+
"""
|
|
583
|
+
Reassign a task from one person to another.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
task_id: Task ID to reassign
|
|
587
|
+
from_assignee: Current assignee (for validation)
|
|
588
|
+
to_assignee: New assignee
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
Dict with task_id, from, and to
|
|
592
|
+
|
|
593
|
+
Raises:
|
|
594
|
+
ValueError: If task is not assigned to from_assignee
|
|
595
|
+
|
|
596
|
+
Example:
|
|
597
|
+
>>> result = await manager.reassign("task_123", "Alice", "Bob")
|
|
598
|
+
"""
|
|
599
|
+
from better_notion.plugins.official.agents_sdk.models import Task
|
|
600
|
+
|
|
601
|
+
task = await Task.get(task_id, client=self._client)
|
|
602
|
+
|
|
603
|
+
if task.assignee != from_assignee:
|
|
604
|
+
raise ValueError(f"Task is not assigned to {from_assignee}")
|
|
605
|
+
|
|
606
|
+
await task.assign_to(to_assignee)
|
|
607
|
+
|
|
608
|
+
return {
|
|
609
|
+
"task_id": task_id,
|
|
610
|
+
"from": from_assignee,
|
|
611
|
+
"to": to_assignee
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
async def list_by_assignee(
|
|
615
|
+
self,
|
|
616
|
+
assignee: str,
|
|
617
|
+
status: str | None = None
|
|
618
|
+
) -> list:
|
|
619
|
+
"""
|
|
620
|
+
List tasks assigned to a person.
|
|
621
|
+
|
|
622
|
+
Args:
|
|
623
|
+
assignee: Name of the assignee
|
|
624
|
+
status: Optional status filter
|
|
625
|
+
|
|
626
|
+
Returns:
|
|
627
|
+
List of Task instances
|
|
628
|
+
|
|
629
|
+
Example:
|
|
630
|
+
>>> tasks = await manager.list_by_assignee("Alice Chen", status="In Progress")
|
|
631
|
+
"""
|
|
632
|
+
from better_notion.plugins.official.agents_sdk.models import Task
|
|
633
|
+
|
|
634
|
+
database_id = self._get_database_id("Tasks")
|
|
635
|
+
if not database_id:
|
|
636
|
+
return []
|
|
637
|
+
|
|
638
|
+
filters: list[dict[str, Any]] = [
|
|
639
|
+
{
|
|
640
|
+
"property": "Assignee",
|
|
641
|
+
"select": {"equals": assignee}
|
|
642
|
+
}
|
|
643
|
+
]
|
|
644
|
+
|
|
645
|
+
if status:
|
|
646
|
+
filters.append({
|
|
647
|
+
"property": "Status",
|
|
648
|
+
"select": {"equals": status}
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
response = await self._client._api.databases.query(
|
|
652
|
+
database_id=database_id,
|
|
653
|
+
filter={"and": filters} if len(filters) > 1 else filters[0]
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
return [
|
|
657
|
+
Task(self._client, page_data)
|
|
658
|
+
for page_data in response.get("results", [])
|
|
659
|
+
]
|
|
660
|
+
|
|
661
|
+
async def list_unassigned(self) -> list:
|
|
662
|
+
"""
|
|
663
|
+
List unassigned tasks.
|
|
664
|
+
|
|
665
|
+
Returns:
|
|
666
|
+
List of Task instances with no assignee
|
|
667
|
+
|
|
668
|
+
Example:
|
|
669
|
+
>>> tasks = await manager.list_unassigned()
|
|
670
|
+
"""
|
|
671
|
+
from better_notion.plugins.official.agents_sdk.models import Task
|
|
672
|
+
|
|
673
|
+
database_id = self._get_database_id("Tasks")
|
|
674
|
+
if not database_id:
|
|
675
|
+
return []
|
|
676
|
+
|
|
677
|
+
response = await self._client._api.databases.query(
|
|
678
|
+
database_id=database_id,
|
|
679
|
+
filter={
|
|
680
|
+
"property": "Assignee",
|
|
681
|
+
"select": {"is_empty": True}
|
|
682
|
+
}
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
return [
|
|
686
|
+
Task(self._client, page_data)
|
|
687
|
+
for page_data in response.get("results", [])
|
|
688
|
+
]
|
|
689
|
+
|
|
690
|
+
async def can_start(self, task_id: str) -> dict:
|
|
691
|
+
"""
|
|
692
|
+
Check if a task can start (all dependencies completed).
|
|
693
|
+
|
|
694
|
+
Args:
|
|
695
|
+
task_id: Task ID to check
|
|
696
|
+
|
|
697
|
+
Returns:
|
|
698
|
+
Dict with can_start (bool), task_id, and incomplete_dependencies
|
|
699
|
+
|
|
700
|
+
Example:
|
|
701
|
+
>>> result = await manager.can_start("task_123")
|
|
702
|
+
"""
|
|
703
|
+
from better_notion.plugins.official.agents_sdk.models import Task
|
|
704
|
+
|
|
705
|
+
task = await Task.get(task_id, client=self._client)
|
|
706
|
+
deps = await task.dependencies()
|
|
707
|
+
incomplete = [d for d in deps if d.status != "Completed"]
|
|
708
|
+
|
|
709
|
+
return {
|
|
710
|
+
"can_start": len(incomplete) == 0,
|
|
711
|
+
"task_id": task_id,
|
|
712
|
+
"incomplete_dependencies": [
|
|
713
|
+
{"id": d.id, "title": d.title, "status": d.status}
|
|
714
|
+
for d in incomplete
|
|
715
|
+
]
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
async def deps(self, task_id: str) -> dict:
|
|
719
|
+
"""
|
|
720
|
+
List all dependencies of a task.
|
|
721
|
+
|
|
722
|
+
Args:
|
|
723
|
+
task_id: Task ID
|
|
724
|
+
|
|
725
|
+
Returns:
|
|
726
|
+
Dict with task_id and dependencies list
|
|
727
|
+
|
|
728
|
+
Example:
|
|
729
|
+
>>> result = await manager.deps("task_123")
|
|
730
|
+
"""
|
|
731
|
+
from better_notion.plugins.official.agents_sdk.models import Task
|
|
732
|
+
|
|
733
|
+
task = await Task.get(task_id, client=self._client)
|
|
734
|
+
deps = await task.dependencies()
|
|
735
|
+
|
|
736
|
+
return {
|
|
737
|
+
"task_id": task_id,
|
|
738
|
+
"dependencies": [
|
|
739
|
+
{
|
|
740
|
+
"id": d.id,
|
|
741
|
+
"title": d.title,
|
|
742
|
+
"status": d.status
|
|
743
|
+
}
|
|
744
|
+
for d in deps
|
|
745
|
+
]
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
async def ready(self, version_id: str | None = None) -> list:
|
|
749
|
+
"""
|
|
750
|
+
List all tasks ready to start (dependencies completed).
|
|
751
|
+
|
|
752
|
+
Args:
|
|
753
|
+
version_id: Optional version filter
|
|
754
|
+
|
|
755
|
+
Returns:
|
|
756
|
+
List of Task instances ready to start
|
|
757
|
+
|
|
758
|
+
Example:
|
|
759
|
+
>>> tasks = await manager.ready(version_id="ver_123")
|
|
760
|
+
"""
|
|
761
|
+
return await self.find_ready(version_id)
|
|
762
|
+
|
|
528
763
|
def _get_database_id(self, name: str) -> str | None:
|
|
529
764
|
"""Get database ID from workspace config."""
|
|
530
765
|
return getattr(self._client, "_workspace_config", {}).get(name)
|
|
@@ -826,6 +1061,41 @@ class WorkIssueManager:
|
|
|
826
1061
|
|
|
827
1062
|
return issue
|
|
828
1063
|
|
|
1064
|
+
async def list_blocked_by(self, work_issue_id: str) -> list:
|
|
1065
|
+
"""
|
|
1066
|
+
List tasks blocked by a work issue.
|
|
1067
|
+
|
|
1068
|
+
Args:
|
|
1069
|
+
work_issue_id: Work issue ID
|
|
1070
|
+
|
|
1071
|
+
Returns:
|
|
1072
|
+
List of Task instances
|
|
1073
|
+
|
|
1074
|
+
Example:
|
|
1075
|
+
>>> tasks = await manager.list_blocked_by("issue_456")
|
|
1076
|
+
"""
|
|
1077
|
+
from better_notion.plugins.official.agents_sdk.models import Task
|
|
1078
|
+
|
|
1079
|
+
database_id = self._get_database_id("Tasks")
|
|
1080
|
+
if not database_id:
|
|
1081
|
+
return []
|
|
1082
|
+
|
|
1083
|
+
response = await self._client._api._request(
|
|
1084
|
+
"POST",
|
|
1085
|
+
f"/databases/{database_id}/query",
|
|
1086
|
+
json={
|
|
1087
|
+
"filter": {
|
|
1088
|
+
"property": "Related Work Issue",
|
|
1089
|
+
"relation": {"contains": work_issue_id}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
)
|
|
1093
|
+
|
|
1094
|
+
return [
|
|
1095
|
+
Task(self._client, page_data)
|
|
1096
|
+
for page_data in response.get("results", [])
|
|
1097
|
+
]
|
|
1098
|
+
|
|
829
1099
|
def _get_database_id(self, name: str) -> str | None:
|
|
830
1100
|
"""Get database ID from workspace config."""
|
|
831
1101
|
return getattr(self._client, "_workspace_config", {}).get(name)
|
|
@@ -992,6 +1262,89 @@ class IncidentManager:
|
|
|
992
1262
|
|
|
993
1263
|
return mttr
|
|
994
1264
|
|
|
1265
|
+
async def link_to_work_issue(self, incident_id: str, work_issue_id: str) -> dict:
|
|
1266
|
+
"""
|
|
1267
|
+
Link an incident to a work issue (root cause).
|
|
1268
|
+
|
|
1269
|
+
Args:
|
|
1270
|
+
incident_id: Incident ID
|
|
1271
|
+
work_issue_id: Work issue ID to link to
|
|
1272
|
+
|
|
1273
|
+
Returns:
|
|
1274
|
+
Dict with incident_id and work_issue_id
|
|
1275
|
+
|
|
1276
|
+
Example:
|
|
1277
|
+
>>> result = await manager.link_to_work_issue("inc_123", "issue_456")
|
|
1278
|
+
"""
|
|
1279
|
+
from better_notion.plugins.official.agents_sdk.models import Incident
|
|
1280
|
+
|
|
1281
|
+
incident = await Incident.get(incident_id, client=self._client)
|
|
1282
|
+
await incident.link_to_work_issue(work_issue_id)
|
|
1283
|
+
|
|
1284
|
+
return {
|
|
1285
|
+
"incident_id": incident_id,
|
|
1286
|
+
"work_issue_id": work_issue_id,
|
|
1287
|
+
"linked": True
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
async def unlink_work_issue(self, incident_id: str) -> dict:
|
|
1291
|
+
"""
|
|
1292
|
+
Unlink an incident from its work issue.
|
|
1293
|
+
|
|
1294
|
+
Args:
|
|
1295
|
+
incident_id: Incident ID
|
|
1296
|
+
|
|
1297
|
+
Returns:
|
|
1298
|
+
Dict with incident_id and unlinked status
|
|
1299
|
+
|
|
1300
|
+
Example:
|
|
1301
|
+
>>> result = await manager.unlink_work_issue("inc_123")
|
|
1302
|
+
"""
|
|
1303
|
+
from better_notion.plugins.official.agents_sdk.models import Incident
|
|
1304
|
+
|
|
1305
|
+
incident = await Incident.get(incident_id, client=self._client)
|
|
1306
|
+
await incident.unlink_work_issue()
|
|
1307
|
+
|
|
1308
|
+
return {
|
|
1309
|
+
"incident_id": incident_id,
|
|
1310
|
+
"unlinked": True
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
async def list_caused_by(self, work_issue_id: str) -> list:
|
|
1314
|
+
"""
|
|
1315
|
+
List all incidents caused by a work issue.
|
|
1316
|
+
|
|
1317
|
+
Args:
|
|
1318
|
+
work_issue_id: Work issue ID
|
|
1319
|
+
|
|
1320
|
+
Returns:
|
|
1321
|
+
List of Incident instances
|
|
1322
|
+
|
|
1323
|
+
Example:
|
|
1324
|
+
>>> incidents = await manager.list_caused_by("issue_456")
|
|
1325
|
+
"""
|
|
1326
|
+
from better_notion.plugins.official.agents_sdk.models import Incident
|
|
1327
|
+
|
|
1328
|
+
database_id = self._get_database_id("Incidents")
|
|
1329
|
+
if not database_id:
|
|
1330
|
+
return []
|
|
1331
|
+
|
|
1332
|
+
response = await self._client._api._request(
|
|
1333
|
+
"POST",
|
|
1334
|
+
f"/databases/{database_id}/query",
|
|
1335
|
+
json={
|
|
1336
|
+
"filter": {
|
|
1337
|
+
"property": "Root Cause Work Issue",
|
|
1338
|
+
"relation": {"contains": work_issue_id}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
)
|
|
1342
|
+
|
|
1343
|
+
return [
|
|
1344
|
+
Incident(self._client, page_data)
|
|
1345
|
+
for page_data in response.get("results", [])
|
|
1346
|
+
]
|
|
1347
|
+
|
|
995
1348
|
def _get_database_id(self, name: str) -> str | None:
|
|
996
1349
|
"""Get database ID from workspace config."""
|
|
997
1350
|
return getattr(self._client, "_workspace_config", {}).get(name)
|
|
@@ -1053,6 +1053,26 @@ class Task(DatabasePageEntityMixin, BaseEntity):
|
|
|
1053
1053
|
return hours_prop.get("number")
|
|
1054
1054
|
return None
|
|
1055
1055
|
|
|
1056
|
+
@property
|
|
1057
|
+
def assignee(self) -> str | None:
|
|
1058
|
+
"""Get the assignee of this task."""
|
|
1059
|
+
assignee_prop = self._data["properties"].get("Assignee") or self._data["properties"].get("assignee")
|
|
1060
|
+
if assignee_prop and assignee_prop.get("type") == "select":
|
|
1061
|
+
select_data = assignee_prop.get("select")
|
|
1062
|
+
if select_data:
|
|
1063
|
+
return select_data.get("name")
|
|
1064
|
+
return None
|
|
1065
|
+
|
|
1066
|
+
@property
|
|
1067
|
+
def related_work_issue_id(self) -> str | None:
|
|
1068
|
+
"""Get related work issue ID (blocking this task or caused by this task)."""
|
|
1069
|
+
issue_prop = self._data["properties"].get("Related Work Issue") or self._data["properties"].get("related_work_issue")
|
|
1070
|
+
if issue_prop and issue_prop.get("type") == "relation":
|
|
1071
|
+
relations = issue_prop.get("relation", [])
|
|
1072
|
+
if relations:
|
|
1073
|
+
return relations[0].get("id")
|
|
1074
|
+
return None
|
|
1075
|
+
|
|
1056
1076
|
# ===== AUTONOMOUS METHODS =====
|
|
1057
1077
|
|
|
1058
1078
|
@classmethod
|
|
@@ -1262,6 +1282,121 @@ class Task(DatabasePageEntityMixin, BaseEntity):
|
|
|
1262
1282
|
return False
|
|
1263
1283
|
return True
|
|
1264
1284
|
|
|
1285
|
+
async def related_work_issue(self) -> "WorkIssue | None":
|
|
1286
|
+
"""
|
|
1287
|
+
Get the related work issue (blocking this task or caused by this task).
|
|
1288
|
+
|
|
1289
|
+
Returns:
|
|
1290
|
+
WorkIssue instance or None
|
|
1291
|
+
|
|
1292
|
+
Example:
|
|
1293
|
+
>>> issue = await task.related_work_issue()
|
|
1294
|
+
"""
|
|
1295
|
+
from better_notion.plugins.official.agents_sdk.models import WorkIssue
|
|
1296
|
+
|
|
1297
|
+
issue_id = self.related_work_issue_id
|
|
1298
|
+
if not issue_id:
|
|
1299
|
+
return None
|
|
1300
|
+
|
|
1301
|
+
try:
|
|
1302
|
+
return await WorkIssue.get(issue_id, client=self._client)
|
|
1303
|
+
except Exception:
|
|
1304
|
+
return None
|
|
1305
|
+
|
|
1306
|
+
async def link_to_work_issue(self, work_issue_id: str) -> None:
|
|
1307
|
+
"""
|
|
1308
|
+
Link this task to a work issue.
|
|
1309
|
+
|
|
1310
|
+
Args:
|
|
1311
|
+
work_issue_id: Work issue ID to link to
|
|
1312
|
+
|
|
1313
|
+
Example:
|
|
1314
|
+
>>> await task.link_to_work_issue(issue_id)
|
|
1315
|
+
"""
|
|
1316
|
+
from better_notion._api.properties import Relation
|
|
1317
|
+
|
|
1318
|
+
await self._client._api.pages.update(
|
|
1319
|
+
page_id=self.id,
|
|
1320
|
+
properties={
|
|
1321
|
+
"Related Work Issue": Relation([work_issue_id]).to_dict(),
|
|
1322
|
+
},
|
|
1323
|
+
)
|
|
1324
|
+
|
|
1325
|
+
# Update local data
|
|
1326
|
+
self._data["properties"]["Related Work Issue"] = {
|
|
1327
|
+
"type": "relation",
|
|
1328
|
+
"relation": [{"id": work_issue_id}]
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
async def unlink_work_issue(self) -> None:
|
|
1332
|
+
"""
|
|
1333
|
+
Unlink this task from its work issue.
|
|
1334
|
+
|
|
1335
|
+
Example:
|
|
1336
|
+
>>> await task.unlink_work_issue()
|
|
1337
|
+
"""
|
|
1338
|
+
from better_notion._api.properties import Relation
|
|
1339
|
+
|
|
1340
|
+
await self._client._api.pages.update(
|
|
1341
|
+
page_id=self.id,
|
|
1342
|
+
properties={
|
|
1343
|
+
"Related Work Issue": Relation([]).to_dict(),
|
|
1344
|
+
},
|
|
1345
|
+
)
|
|
1346
|
+
|
|
1347
|
+
# Update local data
|
|
1348
|
+
self._data["properties"]["Related Work Issue"] = {
|
|
1349
|
+
"type": "relation",
|
|
1350
|
+
"relation": []
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
async def assign_to(self, assignee: str) -> None:
|
|
1354
|
+
"""
|
|
1355
|
+
Assign this task to a person.
|
|
1356
|
+
|
|
1357
|
+
Args:
|
|
1358
|
+
assignee: Name of the person to assign to
|
|
1359
|
+
|
|
1360
|
+
Example:
|
|
1361
|
+
>>> await task.assign_to("Alice Chen")
|
|
1362
|
+
"""
|
|
1363
|
+
from better_notion._api.properties import Select
|
|
1364
|
+
|
|
1365
|
+
await self._client._api.pages.update(
|
|
1366
|
+
page_id=self.id,
|
|
1367
|
+
properties={
|
|
1368
|
+
"Assignee": Select(name="Assignee", value=assignee).to_dict(),
|
|
1369
|
+
},
|
|
1370
|
+
)
|
|
1371
|
+
|
|
1372
|
+
# Update local data
|
|
1373
|
+
self._data["properties"]["Assignee"] = {
|
|
1374
|
+
"type": "select",
|
|
1375
|
+
"select": {"name": assignee}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
async def unassign(self) -> None:
|
|
1379
|
+
"""
|
|
1380
|
+
Unassign this task.
|
|
1381
|
+
|
|
1382
|
+
Example:
|
|
1383
|
+
>>> await task.unassign()
|
|
1384
|
+
"""
|
|
1385
|
+
from better_notion._api.properties import Select
|
|
1386
|
+
|
|
1387
|
+
await self._client._api.pages.update(
|
|
1388
|
+
page_id=self.id,
|
|
1389
|
+
properties={
|
|
1390
|
+
"Assignee": None,
|
|
1391
|
+
},
|
|
1392
|
+
)
|
|
1393
|
+
|
|
1394
|
+
# Update local data
|
|
1395
|
+
self._data["properties"]["Assignee"] = {
|
|
1396
|
+
"type": "select",
|
|
1397
|
+
"select": None
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1265
1400
|
|
|
1266
1401
|
class Idea(DatabasePageEntityMixin, BaseEntity):
|
|
1267
1402
|
"""
|
|
@@ -1790,6 +1925,24 @@ class WorkIssue(DatabasePageEntityMixin, BaseEntity):
|
|
|
1790
1925
|
return relations[0].get("id")
|
|
1791
1926
|
return None
|
|
1792
1927
|
|
|
1928
|
+
@property
|
|
1929
|
+
def caused_incident_ids(self) -> list[str]:
|
|
1930
|
+
"""Get incident IDs caused by this work issue."""
|
|
1931
|
+
incidents_prop = self._data["properties"].get("Caused Incidents") or self._data["properties"].get("caused_incidents")
|
|
1932
|
+
if incidents_prop and incidents_prop.get("type") == "relation":
|
|
1933
|
+
relations = incidents_prop.get("relation", [])
|
|
1934
|
+
return [r.get("id", "") for r in relations if r.get("id")]
|
|
1935
|
+
return []
|
|
1936
|
+
|
|
1937
|
+
@property
|
|
1938
|
+
def blocking_task_ids(self) -> list[str]:
|
|
1939
|
+
"""Get task IDs blocked by this work issue."""
|
|
1940
|
+
tasks_prop = self._data["properties"].get("Blocking Tasks") or self._data["properties"].get("blocking_tasks")
|
|
1941
|
+
if tasks_prop and tasks_prop.get("type") == "relation":
|
|
1942
|
+
relations = tasks_prop.get("relation", [])
|
|
1943
|
+
return [r.get("id", "") for r in relations if r.get("id")]
|
|
1944
|
+
return []
|
|
1945
|
+
|
|
1793
1946
|
# ===== AUTONOMOUS METHODS =====
|
|
1794
1947
|
|
|
1795
1948
|
@classmethod
|
|
@@ -2010,6 +2163,48 @@ class WorkIssue(DatabasePageEntityMixin, BaseEntity):
|
|
|
2010
2163
|
|
|
2011
2164
|
return idea
|
|
2012
2165
|
|
|
2166
|
+
async def caused_incidents(self) -> list["Incident"]:
|
|
2167
|
+
"""
|
|
2168
|
+
Get incidents caused by this work issue.
|
|
2169
|
+
|
|
2170
|
+
Returns:
|
|
2171
|
+
List of Incident instances
|
|
2172
|
+
|
|
2173
|
+
Example:
|
|
2174
|
+
>>> incidents = await issue.caused_incidents()
|
|
2175
|
+
"""
|
|
2176
|
+
from better_notion.plugins.official.agents_sdk.models import Incident
|
|
2177
|
+
|
|
2178
|
+
incidents = []
|
|
2179
|
+
for incident_id in self.caused_incident_ids:
|
|
2180
|
+
try:
|
|
2181
|
+
incident = await Incident.get(incident_id, client=self._client)
|
|
2182
|
+
incidents.append(incident)
|
|
2183
|
+
except Exception:
|
|
2184
|
+
pass
|
|
2185
|
+
return incidents
|
|
2186
|
+
|
|
2187
|
+
async def blocking_tasks(self) -> list["Task"]:
|
|
2188
|
+
"""
|
|
2189
|
+
Get tasks blocked by this work issue.
|
|
2190
|
+
|
|
2191
|
+
Returns:
|
|
2192
|
+
List of Task instances
|
|
2193
|
+
|
|
2194
|
+
Example:
|
|
2195
|
+
>>> tasks = await issue.blocking_tasks()
|
|
2196
|
+
"""
|
|
2197
|
+
from better_notion.plugins.official.agents_sdk.models import Task
|
|
2198
|
+
|
|
2199
|
+
tasks = []
|
|
2200
|
+
for task_id in self.blocking_task_ids:
|
|
2201
|
+
try:
|
|
2202
|
+
task = await Task.get(task_id, client=self._client)
|
|
2203
|
+
tasks.append(task)
|
|
2204
|
+
except Exception:
|
|
2205
|
+
pass
|
|
2206
|
+
return tasks
|
|
2207
|
+
|
|
2013
2208
|
|
|
2014
2209
|
class Incident(DatabasePageEntityMixin, BaseEntity):
|
|
2015
2210
|
"""
|
|
@@ -2153,6 +2348,16 @@ class Incident(DatabasePageEntityMixin, BaseEntity):
|
|
|
2153
2348
|
return date_data["start"]
|
|
2154
2349
|
return None
|
|
2155
2350
|
|
|
2351
|
+
@property
|
|
2352
|
+
def root_cause_work_issue_id(self) -> str | None:
|
|
2353
|
+
"""Get the work issue ID that caused this incident."""
|
|
2354
|
+
issue_prop = self._data["properties"].get("Root Cause Work Issue") or self._data["properties"].get("root_cause_work_issue")
|
|
2355
|
+
if issue_prop and issue_prop.get("type") == "relation":
|
|
2356
|
+
relations = issue_prop.get("relation", [])
|
|
2357
|
+
if relations:
|
|
2358
|
+
return relations[0].get("id")
|
|
2359
|
+
return None
|
|
2360
|
+
|
|
2156
2361
|
# ===== AUTONOMOUS METHODS =====
|
|
2157
2362
|
|
|
2158
2363
|
@classmethod
|
|
@@ -2405,3 +2610,71 @@ class Incident(DatabasePageEntityMixin, BaseEntity):
|
|
|
2405
2610
|
await self.assign(task.id)
|
|
2406
2611
|
|
|
2407
2612
|
return task
|
|
2613
|
+
|
|
2614
|
+
async def root_cause_work_issue(self) -> "WorkIssue | None":
|
|
2615
|
+
"""
|
|
2616
|
+
Get the work issue that caused this incident.
|
|
2617
|
+
|
|
2618
|
+
Returns:
|
|
2619
|
+
WorkIssue instance or None
|
|
2620
|
+
|
|
2621
|
+
Example:
|
|
2622
|
+
>>> issue = await incident.root_cause_work_issue()
|
|
2623
|
+
"""
|
|
2624
|
+
from better_notion.plugins.official.agents_sdk.models import WorkIssue
|
|
2625
|
+
|
|
2626
|
+
issue_id = self.root_cause_work_issue_id
|
|
2627
|
+
if not issue_id:
|
|
2628
|
+
return None
|
|
2629
|
+
|
|
2630
|
+
try:
|
|
2631
|
+
return await WorkIssue.get(issue_id, client=self._client)
|
|
2632
|
+
except Exception:
|
|
2633
|
+
return None
|
|
2634
|
+
|
|
2635
|
+
async def link_to_work_issue(self, work_issue_id: str) -> None:
|
|
2636
|
+
"""
|
|
2637
|
+
Link this incident to a work issue (root cause).
|
|
2638
|
+
|
|
2639
|
+
Args:
|
|
2640
|
+
work_issue_id: Work issue ID to link to
|
|
2641
|
+
|
|
2642
|
+
Example:
|
|
2643
|
+
>>> await incident.link_to_work_issue(issue_id)
|
|
2644
|
+
"""
|
|
2645
|
+
from better_notion._api.properties import Relation
|
|
2646
|
+
|
|
2647
|
+
await self._client._api.pages.update(
|
|
2648
|
+
page_id=self.id,
|
|
2649
|
+
properties={
|
|
2650
|
+
"Root Cause Work Issue": Relation([work_issue_id]).to_dict(),
|
|
2651
|
+
},
|
|
2652
|
+
)
|
|
2653
|
+
|
|
2654
|
+
# Update local data
|
|
2655
|
+
self._data["properties"]["Root Cause Work Issue"] = {
|
|
2656
|
+
"type": "relation",
|
|
2657
|
+
"relation": [{"id": work_issue_id}]
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
async def unlink_work_issue(self) -> None:
|
|
2661
|
+
"""
|
|
2662
|
+
Unlink this incident from its work issue.
|
|
2663
|
+
|
|
2664
|
+
Example:
|
|
2665
|
+
>>> await incident.unlink_work_issue()
|
|
2666
|
+
"""
|
|
2667
|
+
from better_notion._api.properties import Relation
|
|
2668
|
+
|
|
2669
|
+
await self._client._api.pages.update(
|
|
2670
|
+
page_id=self.id,
|
|
2671
|
+
properties={
|
|
2672
|
+
"Root Cause Work Issue": Relation([]).to_dict(),
|
|
2673
|
+
},
|
|
2674
|
+
)
|
|
2675
|
+
|
|
2676
|
+
# Update local data
|
|
2677
|
+
self._data["properties"]["Root Cause Work Issue"] = {
|
|
2678
|
+
"type": "relation",
|
|
2679
|
+
"relation": []
|
|
2680
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: better-notion
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: A high-level Python SDK for the Notion API with developer experience in mind.
|
|
5
5
|
Project-URL: Homepage, https://github.com/nesalia-inc/better-notion
|
|
6
6
|
Project-URL: Documentation, https://github.com/nesalia-inc/better-notion#readme
|
|
@@ -112,13 +112,13 @@ better_notion/plugins/base.py,sha256=3h9jOZzS--UqmVW3RREtcQ2h1GTWWPUryTencsJKhTM
|
|
|
112
112
|
better_notion/plugins/loader.py,sha256=zCWsMdJyvZs1IHFm0zjEiqm_l_5jB1Uw4x30Kq8rLS4,9527
|
|
113
113
|
better_notion/plugins/state.py,sha256=jH_tZWvC35hqLO4qwl2Kwq9ziWVavwCEUcCqy3s5wMY,3780
|
|
114
114
|
better_notion/plugins/official/__init__.py,sha256=rPg5vdk1cEANVstMPzxcWmImtsOpdSR40JSml7h1uUk,426
|
|
115
|
-
better_notion/plugins/official/agents.py,sha256=
|
|
116
|
-
better_notion/plugins/official/agents_cli.py,sha256=
|
|
115
|
+
better_notion/plugins/official/agents.py,sha256=usvB2dH1oQ_BUePg2D1nSlHUvk58K3Eq4cdbFG5Fb7Q,54191
|
|
116
|
+
better_notion/plugins/official/agents_cli.py,sha256=M7bdEPzxpv1_EuC5rX_tlz9MZ3XYmnecp5EzLOBktrg,62551
|
|
117
117
|
better_notion/plugins/official/agents_schema.py,sha256=NQRAJFoBAXRuxB9_9Eaf-4tVth-1OZh7GjmN56Yp9lA,39867
|
|
118
118
|
better_notion/plugins/official/productivity.py,sha256=_-whP4pYA4HufE1aUFbIdhrjU-O9njI7xUO_Id2M1J8,8726
|
|
119
119
|
better_notion/plugins/official/agents_sdk/__init__.py,sha256=luQBzZLsJ7fC5U0jFu8dfzMviiXj2SBZXcTohM53wkQ,725
|
|
120
|
-
better_notion/plugins/official/agents_sdk/managers.py,sha256=
|
|
121
|
-
better_notion/plugins/official/agents_sdk/models.py,sha256=
|
|
120
|
+
better_notion/plugins/official/agents_sdk/managers.py,sha256=A1SQlozecnYrX1HLXxNWZwN0aJXoyrHIr9D1R-Jf3ZQ,40999
|
|
121
|
+
better_notion/plugins/official/agents_sdk/models.py,sha256=vBSqf-9Q-gX14Z8779Nacr8yTI3WMi5cLtkJjONiPoY,91967
|
|
122
122
|
better_notion/plugins/official/agents_sdk/plugin.py,sha256=bs9O8Unv6SARGj4lBU5Gj9HGbLTUNqTacJ3RLUhdbI4,4479
|
|
123
123
|
better_notion/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
124
124
|
better_notion/utils/helpers.py,sha256=HgFuUQlG_HzBOB0z2GA9RxPLoXgwRc0DIxa9Fg6C-Jk,2337
|
|
@@ -133,8 +133,8 @@ better_notion/utils/agents/rbac.py,sha256=8ZA8Y7wbOiVZDbpjpH7iC35SZrZ0jl4fcJ3xWC
|
|
|
133
133
|
better_notion/utils/agents/schemas.py,sha256=eHfGhY90FAPXA3E8qE6gP75dgNzn-9z5Ju1FMwBKnQQ,22120
|
|
134
134
|
better_notion/utils/agents/state_machine.py,sha256=xUBEeDTbU1Xq-rsRo2sbr6AUYpYrV9DTHOtZT2cWES8,6699
|
|
135
135
|
better_notion/utils/agents/workspace.py,sha256=Uy8bqLsT_VFGYAPoiQJNuCvGdjMceaSiVGbR9saMpoU,16558
|
|
136
|
-
better_notion-2.
|
|
137
|
-
better_notion-2.
|
|
138
|
-
better_notion-2.
|
|
139
|
-
better_notion-2.
|
|
140
|
-
better_notion-2.
|
|
136
|
+
better_notion-2.1.0.dist-info/METADATA,sha256=CGwOskvV6KSl4YON4CUNgzo3W0TWLWfJ83Vd3aRa0Wk,11096
|
|
137
|
+
better_notion-2.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
138
|
+
better_notion-2.1.0.dist-info/entry_points.txt,sha256=D0bUcP7Z00Zyjxw7r2p29T95UrwioDO0aGDoHe9I6fo,55
|
|
139
|
+
better_notion-2.1.0.dist-info/licenses/LICENSE,sha256=BAdN3JpgMY_y_fWqZSCFSvSbC2mTHP-BKDAzF5FXQAI,1069
|
|
140
|
+
better_notion-2.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|