-
Notifications
You must be signed in to change notification settings - Fork 165
Add comparison mode to sprint overview script #4722
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
Open
happz
wants to merge
1
commit into
main
Choose a base branch
from
happz-sprint-overview-compare
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+165
−5
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -48,6 +48,17 @@ DISPLAY_SECTIONS = [ | |||||
| ("closed-pull", "Closed Pull Requests"), | ||||||
| ] | ||||||
|
|
||||||
| DISPLAY_COMPARISON_SECTIONS = [ | ||||||
| ('completed', 'Completed'), | ||||||
| ('pending', 'Pending'), | ||||||
| ('added', 'Added'), | ||||||
| ('removed', 'Removed'), | ||||||
| ('completed-original', 'Completed original items'), | ||||||
| ('incomplete-original', 'Incomplete original items'), | ||||||
| ('complete-added', 'Completed added items'), | ||||||
| ('incomplete-added', 'Incompleted added items'), | ||||||
| ] | ||||||
|
|
||||||
| DISPLAY_TEMPLATE = jinja2.Template( | ||||||
| trim_blocks=True, | ||||||
| source=""" | ||||||
|
|
@@ -75,6 +86,32 @@ DISPLAY_TEMPLATE = jinja2.Template( | |||||
| ) | ||||||
|
|
||||||
|
|
||||||
| DISPLAY_COMPARISON_TEMPLATE = jinja2.Template( | ||||||
| trim_blocks=True, | ||||||
| source=""" | ||||||
| ================================================================================ | ||||||
| Sprint: {{ sprint_name }} | ||||||
| Total items: {{ count_base }} => {{ count_current }} | ||||||
| Total story points: {{ size_base }} => {{ size_current }} | ||||||
| Completed items: {{ count_completed }} vs {{ count_pending }} | ||||||
| Completed story points: {{ size_completed }} vs {{ size_pending }} | ||||||
| Added/removed items: {{ count_added }} vs {{ count_removed }} | ||||||
| Added/removed story points: {{ size_added }} vs {{ size_removed }} | ||||||
| ================================================================================ | ||||||
| {% for key, label in sections %} | ||||||
| {% if categories.get(key) %} | ||||||
|
|
||||||
| {{ label }} ({{ categories[key] | length }}) - {{ categories[key] | sum(attribute="safe_size") }} points | ||||||
| -------------------------------------------------------------------------------- | ||||||
| {% for item in categories[key] %} | ||||||
| {{ item }} | ||||||
| {% endfor %} | ||||||
| {% endif %} | ||||||
| {% endfor %} | ||||||
| """, # noqa: E501 | ||||||
| ) | ||||||
|
|
||||||
|
|
||||||
| @dataclasses.dataclass | ||||||
| class Item: | ||||||
| """ | ||||||
|
|
@@ -89,6 +126,10 @@ class Item: | |||||
| url: str | ||||||
| title: str | ||||||
|
|
||||||
| @property | ||||||
| def safe_size(self) -> int: | ||||||
| return self.size or 0 | ||||||
|
|
||||||
| def __str__(self) -> str: | ||||||
| size_str = f"[{self.size}]" if self.size is not None else "[-]" | ||||||
| identifier = f"{self.repo}#{self.id}" | ||||||
|
|
@@ -232,6 +273,95 @@ def display_items(sprint_name: str, items: list[Item]) -> None: | |||||
| ) | ||||||
|
|
||||||
|
|
||||||
| def display_comparison(sprint_name: str, base: list[Item], current: list[Item]) -> None: | ||||||
| """ | ||||||
| Display difference between two sets of items in a readable format. | ||||||
|
|
||||||
| :param base: list of the "base", initial items, representing a past | ||||||
| state of the sprint. | ||||||
| :param items: list of the current items, representing the current | ||||||
| state of the sprint. | ||||||
| """ | ||||||
|
|
||||||
| base.sort(key=lambda x: x.id) | ||||||
|
|
||||||
| base_map = {item.id: item for item in base} | ||||||
| current_map = {item.id: item for item in current} | ||||||
|
|
||||||
| completed: list[Item] = [] | ||||||
| pending: list[Item] = [] | ||||||
| added: list[Item] = [] | ||||||
| removed: list[Item] = [] | ||||||
|
|
||||||
| for base_id, base_item in base_map.items(): | ||||||
| current_item = current_map.pop(base_id, None) | ||||||
|
|
||||||
| if current_item is None: | ||||||
| removed.append(base_item) | ||||||
|
|
||||||
| elif current_item.status in ('closed', 'merged'): | ||||||
| completed.append(base_item) | ||||||
|
|
||||||
| else: | ||||||
| pending.append(base_item) | ||||||
|
|
||||||
| for current_item in current_map.values(): | ||||||
| added.append(current_item) | ||||||
|
|
||||||
| if current_item.status in ('closed', 'merged'): | ||||||
| completed.append(current_item) | ||||||
|
|
||||||
| else: | ||||||
| pending.append(current_item) | ||||||
|
|
||||||
| def size(items: list[Item]) -> int: | ||||||
| return sum(item.size or 0 for item in items) | ||||||
|
|
||||||
| count_base, count_current, size_base, size_current = ( | ||||||
| len(base), | ||||||
| len(current), | ||||||
| size(base), | ||||||
| size(current), | ||||||
| ) | ||||||
| count_completed, count_pending, size_completed, size_pending = ( | ||||||
| len(completed), | ||||||
| len(pending), | ||||||
| size(completed), | ||||||
| size(pending), | ||||||
| ) | ||||||
| count_added, size_added = len(added), size(added) | ||||||
| count_removed, size_removed = len(removed), size(removed) | ||||||
|
|
||||||
| click.echo( | ||||||
| DISPLAY_COMPARISON_TEMPLATE.render( | ||||||
| sections=DISPLAY_COMPARISON_SECTIONS, | ||||||
| categories={ | ||||||
| 'completed': completed, | ||||||
| 'pending': pending, | ||||||
| 'added': added, | ||||||
| 'removed': removed, | ||||||
| 'completed-original': [item for item in completed if item not in added], | ||||||
| 'incomplete-original': [item for item in pending if item not in added], | ||||||
| 'complete-added': [item for item in added if item in completed], | ||||||
| 'incomplete-added': [item for item in added if item not in completed], | ||||||
| }, | ||||||
| sprint_name=sprint_name, | ||||||
| count_base=count_base, | ||||||
| count_current=count_current, | ||||||
| size_base=size_base, | ||||||
| size_current=size_current, | ||||||
| count_completed=count_completed, | ||||||
| count_pending=count_pending, | ||||||
| size_completed=size_completed, | ||||||
| size_pending=size_pending, | ||||||
| count_added=count_added, | ||||||
| size_added=size_added, | ||||||
| count_removed=count_removed, | ||||||
| size_removed=size_removed, | ||||||
| ) | ||||||
| ) | ||||||
|
|
||||||
|
|
||||||
| @click.command() | ||||||
| @click.option( | ||||||
| '--sprint', | ||||||
|
|
@@ -245,20 +375,50 @@ def display_items(sprint_name: str, items: list[Item]) -> None: | |||||
| is_flag=True, | ||||||
| help='Output in YAML format for machine-readable processing.', | ||||||
| ) | ||||||
| def main(sprint: str, output_yaml: bool) -> None: | ||||||
| @click.option( | ||||||
| '--base', | ||||||
| 'base_path', | ||||||
| default=None, | ||||||
| help='Instead of reporting, compare the current sprint against this saved state.', | ||||||
| ) | ||||||
| @click.option( | ||||||
| '--current', | ||||||
| 'current_path', | ||||||
| default=None, | ||||||
| help='If set, compare --base against --current instead of the live sprint state.', | ||||||
| ) | ||||||
|
happz marked this conversation as resolved.
|
||||||
| def main( | ||||||
| sprint: str, | ||||||
| output_yaml: bool, | ||||||
| base_path: Optional[str] = None, | ||||||
| current_path: Optional[str] = None, | ||||||
| ) -> None: | ||||||
| """ | ||||||
| List all issues and pull requests in a GitHub Project sprint with story points. | ||||||
|
|
||||||
| Set the GITHUB_PERSONAL_ACCESS_TOKEN environment variable to avoid rate limits. | ||||||
| """ | ||||||
| items = fetch_sprint_items(sprint) | ||||||
|
|
||||||
| if not items: | ||||||
| click.echo(f"No items found in sprint '{sprint}'.") | ||||||
| return | ||||||
| if current_path is not None: | ||||||
| with open(current_path) as f: | ||||||
| items = YAML().load(f) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
We need to unpack these too, right? Otherwise I get: File "/home/psss/git/tmt/./scripts/sprint-overview", line 289, in display_comparison
current_map = {item.id: item for item in current}
^^^^^^^
AttributeError: 'CommentedMap' object has no attribute 'id' |
||||||
|
|
||||||
| else: | ||||||
| items = fetch_sprint_items(sprint) | ||||||
|
|
||||||
| if not items: | ||||||
| click.echo(f"No items found in sprint '{sprint}'.") | ||||||
| return | ||||||
|
|
||||||
| if output_yaml: | ||||||
| YAML().dump([dataclasses.asdict(item) for item in items], sys.stdout) | ||||||
|
|
||||||
| elif base_path is not None: | ||||||
| with open(base_path) as f: | ||||||
| base = [Item(**item) for item in YAML().load(f)] | ||||||
|
|
||||||
| display_comparison(sprint, base, items) | ||||||
|
|
||||||
| else: | ||||||
| display_items(sprint, items) | ||||||
|
|
||||||
|
|
||||||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Perhaps add a
metavarwithPATH? The defaultTEXTis a bit confusing:Similar below.