Add tag management dialog with bulk operations#618
Open
MichaelvanLaar wants to merge 2 commits intoalmarklein:mainfrom
Open
Add tag management dialog with bulk operations#618MichaelvanLaar wants to merge 2 commits intoalmarklein:mainfrom
MichaelvanLaar wants to merge 2 commits intoalmarklein:mainfrom
Conversation
Introduces a new "Manage tags" dialog accessible from the main menu. The dialog lists all tags with usage stats and supports per-tag configure/rename/delete actions as well as bulk color, priority, and rename/merge operations across selected tags. - Add get_all_tags_stats() to RecordStore for tag usage aggregation - Add rename_tag_in_records() helper to TagRenameDialog - Implement TagManagementDialog with search/filter, per-tag actions, and accordion bulk-operation footer (color with Default/Random/Apply, priority, rename/merge) - Register dialog on canvas and wire "Manage tags" menu entry - Add test coverage for get_all_tags_stats()
There was a problem hiding this comment.
Pull request overview
This PR introduces a new Tag Management dialog (reachable from the main menu) to view and manage tags across all records, including bulk operations like renaming/merging, setting colors, and setting priority.
Changes:
- Add
RecordStore.get_all_tags_stats()to aggregate per-tag totals and last-used timestamps across the full dataset. - Add
TagManagementDialogUI + menu entry, plus a helper onTagRenameDialogto rename/delete a tag across all records. - Add unit tests for
get_all_tags_stats().
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| timetagger/app/stores.py | Adds full-dataset tag aggregation used by the new management dialog. |
| timetagger/app/dialogs.py | Adds menu entry, tag-management dialog UI/logic, and tag rename/delete helper. |
| timetagger/app/front.py | Registers the new dialog on the canvas instance. |
| tests/test_client_recordstore.py | Adds tests for the new tag stats aggregation method. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+409
to
+419
| for record in self.get_dump(): | ||
| tags = self.tags_from_record(record) | ||
| if tags[0] == "#untagged": | ||
| continue | ||
| duration = max(0, record.t2 - record.t1) | ||
| for tag in tags: | ||
| if tag not in result: | ||
| result[tag] = {"total_t": 0, "last_t2": 0} | ||
| result[tag]["total_t"] += duration | ||
| if record.t2 > result[tag]["last_t2"]: | ||
| result[tag]["last_t2"] = record.t2 |
Comment on lines
+405
to
+407
| """Get stats for all tags across all records, ignoring time range. | ||
| Returns dict of tag -> {total_t, last_t2}. Excludes #untagged. | ||
| """ |
Comment on lines
+2584
to
+2589
| if tag_to is not None: | ||
| info = window.store.settings.get_tag_info(tag_from) | ||
| window.store.settings.set_tag_info(tag_from, {}) | ||
| window.store.settings.set_tag_info(tag_to, info) | ||
| else: | ||
| window.store.settings.set_tag_info(tag_from, {}) |
| self._bulk_color_input.style.borderColor = clr | ||
|
|
||
| def _bulk_set_random_color(self): | ||
| clr = "#" + Math.floor(Math.random() * 16777215).toString(16) |
Comment on lines
+4965
to
+4970
| def _make_bulk_confirm_handler(self, selected, tag_to): | ||
| def handler(): | ||
| for tag_from in selected: | ||
| self._canvas.tag_rename_dialog.rename_tag_in_records( | ||
| tag_from, tag_to | ||
| ) |
Comment on lines
+4536
to
+4538
| <div id='tmgmt-list' style='overflow-y:auto; max-height:55vh; | ||
| margin-bottom:0.5em;'> | ||
| </div> |
- Fix running record duration in get_all_tags_stats() — records where t1 == t2 now use dt.now() as effective_t2 instead of producing 0 s - Add count field to get_all_tags_stats() stats dict - Fix random color hex padding in _bulk_set_random_color() to always produce 6-digit hex strings - Add bulk_rename_tags_in_records() for single-pass O(records) bulk rename that avoids repeated settings overwrites and duplicate tags - Update _make_bulk_confirm_handler() to use bulk method for multi-tag selections and single method for single-tag selections - Add select-all checkbox above tag list with full state sync - Extend test suite for get_all_tags_stats() (count, running records)
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
This PR adds a Tag Management dialog accessible from the main menu via a new "Manage tags" entry. It addresses the bulk tag editing use case raised in #366 and extends the per-tag configuration from #139.
What's included
Per-tag actions (via a gear icon next to each tag in the list):
Bulk operations (accordion footer, applies to all checked tags):
Tag list
Changes
timetagger/app/stores.py— addget_all_tags_stats()toRecordStorefor tag usage aggregationtimetagger/app/dialogs.py— implementTagManagementDialog; addrename_tag_in_records()helper toTagRenameDialogtimetagger/app/front.py— register dialog on canvas and wire "Manage tags" menu entrytests/test_client_recordstore.py— test coverage forget_all_tags_stats()Partially closes #366 — bulk add/remove tags on individual records is not yet covered, but bulk operations across all records for a given tag are.