Skip to content

Add tag management dialog with bulk operations#618

Open
MichaelvanLaar wants to merge 2 commits intoalmarklein:mainfrom
MichaelvanLaar:upstream/tag-management
Open

Add tag management dialog with bulk operations#618
MichaelvanLaar wants to merge 2 commits intoalmarklein:mainfrom
MichaelvanLaar:upstream/tag-management

Conversation

@MichaelvanLaar
Copy link
Copy Markdown

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):

  • Configure color, target, and primary/secondary priority (opens the existing tag settings dialog)
  • Rename the tag across all records
  • Delete the tag from all records

Bulk operations (accordion footer, applies to all checked tags):

  • Color — set a hex color, reset to default, or pick a random color, then apply to all selected tags at once
  • Priority — set all selected tags to Primary or Secondary in one step
  • Rename / Merge — rename all selected tags to a new name (effectively merging them if they differ)

Tag list

  • Shows all tags ever used, not just those visible in the current time range (addresses the "non-visible tags" gap noted in 💡 central configuration of tags #134)
  • Displays usage count and last-used date for each tag
  • Search/filter field to narrow the list
  • Select-all checkbox

Changes

  • timetagger/app/stores.py — add get_all_tags_stats() to RecordStore for tag usage aggregation
  • timetagger/app/dialogs.py — implement TagManagementDialog; add rename_tag_in_records() helper to TagRenameDialog
  • timetagger/app/front.py — register dialog on canvas and wire "Manage tags" menu entry
  • tests/test_client_recordstore.py — test coverage for get_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.

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()
Copilot AI review requested due to automatic review settings May 5, 2026 13:17
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 TagManagementDialog UI + menu entry, plus a helper on TagRenameDialog to 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 thread timetagger/app/stores.py Outdated
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 thread timetagger/app/stores.py
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 thread timetagger/app/dialogs.py
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, {})
Comment thread timetagger/app/dialogs.py Outdated
self._bulk_color_input.style.borderColor = clr

def _bulk_set_random_color(self):
clr = "#" + Math.floor(Math.random() * 16777215).toString(16)
Comment thread timetagger/app/dialogs.py
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 thread timetagger/app/dialogs.py
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tag management/bulk editing

2 participants