-
Notifications
You must be signed in to change notification settings - Fork 12
Implement automatic AssignedCommitteeAction reminders
#496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
20e6524 to
546327d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Implements automatic reminders for assigned committee actions by introducing new configuration options and a background task.
- Adds environment settings for enabling reminders and configuring their interval.
- Introduces
get_user_actionshelper to centralize action-fetching logic and refactors existing list commands to use it. - Defines a new
CommitteeActionsTrackingRemindersTaskCogto run a periodic Discord reminder task.
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| config.py | Added _setup_committee_actions_reminders and _setup_committee_actions_reminders_interval to parse new env vars. |
| cogs/committee_actions_tracking.py | Added get_user_actions overloads, refactored list commands, and created the reminders task cog. |
| cogs/init.py | Registered CommitteeActionsTrackingRemindersTaskCog. |
Comments suppressed due to low confidence (1)
cogs/committee_actions_tracking.py:205
- [nitpick] There are no tests covering the background reminders task. Adding unit tests for the enabled/disabled path and interval parsing would ensure this feature works reliably.
@tasks.loop(**settings["COMMITTEE_ACTIONS_REMINDERS_INTERVAL"])
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated 20 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| cls, value: "LiteralString", emoji: "LiteralString | None" = None | ||
| ) -> "AssignedCommitteeAction.Status": | ||
| if not emoji: # NOTE: Will also check for empty strings | ||
| raise ValueError |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message for the ValueError should be descriptive to help with debugging. Currently, raising ValueError with no message makes it difficult to understand what went wrong.
| raise ValueError | |
| raise ValueError("The 'emoji' argument must be provided and non-empty when creating a Status.") |
| def values(self) -> "Sequence[str]": | ||
| return [status.value for status in self.__iter__()] | ||
|
|
||
| def emojis(self) -> "Sequence[str]": |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The return type annotation should be list[str] instead of Sequence[str] since the method explicitly returns a list. This provides more accurate type information to callers.
| def emojis(self) -> "Sequence[str]": | |
| def emojis(self) -> "list[str]": |
| ] = await self._get_actions_grouped_by_member_id( | ||
| filter_query=Q(raw_status=AssignedCommitteeAction.Status(value=raw_status).value) | ||
| if raw_status is not None | ||
| else Q(raw_status__in=AssignedCommitteeAction.Status.TODO_FILTER) |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] For clarity and explicitness, consider using TODO_FILTER.values() instead of TODO_FILTER directly. While the current code should work because Status enums inherit from str, using .values() makes it clearer that you're filtering by the status value strings.
| else Q(raw_status__in=AssignedCommitteeAction.Status.TODO_FILTER) | |
| else Q(raw_status__in=AssignedCommitteeAction.Status.TODO_FILTER.values()) |
| f"\n{committee}, Actions:" | ||
| f"\n{ | ||
| ', \n'.join( | ||
| action.status.emoji + f' {action.description} ({action.status.label})' | ||
| for action in actions | ||
| ) | ||
| }" | ||
| for committee, actions in all_actions.items() |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The variable name committee is ambiguous. It's actually a discord.Member or discord.User object representing a committee member. Consider renaming it to committee_member or member for clarity.
| f"\n{committee}, Actions:" | |
| f"\n{ | |
| ', \n'.join( | |
| action.status.emoji + f' {action.description} ({action.status.label})' | |
| for action in actions | |
| ) | |
| }" | |
| for committee, actions in all_actions.items() | |
| f"\n{committee_member}, Actions:" | |
| f"\n{ | |
| ', \n'.join( | |
| action.status.emoji + f' {action.description} ({action.status.label})' | |
| for action in actions | |
| ) | |
| }" | |
| for committee_member, actions in all_actions.items() |
| return obj | ||
|
|
||
| @classproperty | ||
| def TODO_FILTER(cls) -> "AssignedCommitteeAction._StatusCollection": # noqa: D102, N802 |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The TODO_FILTER class property lacks documentation. Consider adding a docstring to explain its purpose and what statuses it includes, e.g.:
@classproperty
def TODO_FILTER(cls) -> "AssignedCommitteeAction._StatusCollection":
"""Return a collection of statuses representing incomplete actions (In Progress, Blocked, Not Started)."""
return AssignedCommitteeAction._StatusCollection(
[cls.IN_PROGRESS, cls.BLOCKED, cls.NOT_STARTED]
)| def TODO_FILTER(cls) -> "AssignedCommitteeAction._StatusCollection": # noqa: D102, N802 | |
| def TODO_FILTER(cls) -> "AssignedCommitteeAction._StatusCollection": # noqa: D102, N802 | |
| """Return a collection of statuses representing incomplete actions (In Progress, Blocked, Not Started).""" |
| iterable = list(iterable) | ||
| if not iterable: | ||
| NO_STATUSES_GIVEN_MESSAGE: Final[str] = ( | ||
| f"Cannot instantiate {type(Self).__name__} with no 'statuses'." |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message references Self which is a type hint, not a runtime type. This should be cls instead. The correct message should be:
f"Cannot instantiate {cls.__name__} with no 'statuses'."| f"Cannot instantiate {type(Self).__name__} with no 'statuses'." | |
| f"Cannot instantiate {cls.__name__} with no 'statuses'." |
|
|
||
| if raw_send_committee_actions_reminders not in TRUE_VALUES | FALSE_VALUES: | ||
| INVALID_SEND_COMMITTEE_ACTIONS_REMINDERS_MESSAGE: Final[str] = ( | ||
| "SEND_COMMITTEE_ACTIONS_REMINDERS must be a boolean value." |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two extra spaces before "must" in the error message. It should be:
"SEND_COMMITTEE_ACTIONS_REMINDERS must be a boolean value."| "SEND_COMMITTEE_ACTIONS_REMINDERS must be a boolean value." | |
| "SEND_COMMITTEE_ACTIONS_REMINDERS must be a boolean value." |
| action.status.emoji | ||
| + f' {action.description} ({action.status.label})' | ||
| for action in actions | ||
| if action.discord_member.discord_id == discord_id |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This filter condition is redundant. The actions collection from filtered_actions.items() already contains only actions for the specific discord_id due to the grouping in _get_actions_grouped_by_member_id. This unnecessary check should be removed for clarity and performance.
| if action.discord_member.discord_id == discord_id |
| return self.Status(self.raw_status) | ||
|
|
||
| @status.setter | ||
| def status(self, value: Status, /) -> None: |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The status property setter lacks a docstring. Consider adding documentation to explain that it sets the raw_status field from a Status enum value, e.g.:
@status.setter
def status(self, value: Status, /) -> None:
"""Set the action's status from a Status enum value."""
self.raw_status = value.value| def status(self, value: Status, /) -> None: | |
| def status(self, value: Status, /) -> None: | |
| """Set the action's status from a Status enum value.""" |
| return obj | ||
|
|
||
| @classproperty | ||
| def TODO_FILTER(cls) -> "AssignedCommitteeAction._StatusCollection": # noqa: D102, N802 |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normal methods should have 'self', rather than 'cls', as their first parameter.
|
This pull request has a merge conflict with the base branch! Please resolve the conflict manually, remove the conflict label and re-add the filter label (if applicable). |
No description provided.