Repository navigation
Conversation
8df831e to
ea8f043
Compare
ea8f043 to
57e89d8
Compare
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
57e89d8 to
dcff78d
Compare
dcff78d to
2a227b2
Compare
brunopagno
left a comment
There was a problem hiding this comment.
I think I understand why we want a job to cleanup labels, but this affects the product substantially in my opinion. Admins will still be able to see "ghost" labels for a whole day before they get cleaned up. Maybe worth checkin with the team (if you haven't already)
The alternative would be to create labels "on save" instead of automagically inline while typing. Which would solve the "created_inline" flag. But I imagine this would create some extra frontend challenge. Is it worth the tradeoff?
| scope :never_used, -> { | ||
| where("NOT EXISTS (SELECT 1 FROM labelings WHERE labelings.label_id = labels.id)") | ||
| .where("NOT EXISTS (SELECT 1 FROM label_journals WHERE label_journals.label_id = labels.id)") |
There was a problem hiding this comment.
Why do we need to check journals for the never_used?
Just re read the PR description.
I am thinking if this makes it more difficult to reuse labels on a non journalized model, but I guess that's not really an issue since the query will simply not find any journal entries.
There was a problem hiding this comment.
I guess it's just the coupling with journals that bothers me.
I'll leave this comment in as something to give 10 seconds of thought and then forget 😅
There was a problem hiding this comment.
The coupling with journals is so that a label does not show up as "(deleted)" on the AT- the rationale is if it was used at least once; it's a legit label. AFAIK from product, the whole idea is to cleanup typos that are created within the inline creation phase.
We could use some discussion on this 👍🏾
| private | ||
|
|
||
| def candidate_ids | ||
| candidates.lock("FOR UPDATE SKIP LOCKED").pluck(:id) |
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
Good shout, I think it's worth a shot as it narrows the grace period significantly. The current approach would have a label have a lifetime for 24-48hrs. I'll take a stab at it, though I suspect this would be a new pattern on the frontend-side; but should be covered by the existing Lables::FindOrCreateService. 👍🏾 |
Admin-curated labels and labels created from the work package form need telling apart, since only the latter are removed again when unused. See COMMS-1050.
Only labels created through the API's find-or-create path are marked. Editing a label in the administration turns it into a curated one. See COMMS-1050.
Deletes labels created from the work package form that no work package carries and no journal ever snapshotted, so a label removed again inside the journal aggregation window vanishes while one that was part of a work package's history stays. The one day grace period covers the gap between creating the label through the API and saving the work package that uses it, which can be a form left open for a long time. See COMMS-1050.
Keeps the nightly cleanup from deleting a label between the existence check and the labeling insert of the same save. See COMMS-1050.
A work package save holds the labels it is about to use with a key share lock. Taking the rows for update and skipping locked ones means the job never deletes a label a save is attaching, and a save that starts after the job has locked a label fails its validation instead of the insert. See COMMS-1050.
Asserts the schedule itself and drops an example the model spec already covers.
The candidate scan reads usage from the SELECT snapshot; a work package save that commits during that scan leaves the label unlocked but used. Re-applying never_used in the DELETE sees that labeling.
Postgres cannot turn NOT IN (subselect) into an index-backed anti-join and label_journals grows with every work package journal version.
Keying the cleanup on created_at let a stale unused inline label be returned by find-or-create and deleted before the work package save.
A no-op re-submit of the admin form or an empty API PATCH is not curation and must not make an unused typo label immortal.
…a day Product accepts a short grace period, so an inline label creation books a cleanup run 15 minutes later and the job books its own next run while unused inline labels remain. The nightly cron entry stays as a safety net.
73264e3 to
8972186
Compare
Ticket
https://community.openproject.org/wp/80855
Stacked on #25320.
What are you trying to accomplish?
Add a cleanup strategy for unused labels created inline; possibly as a result of typos. Run a cleanup job to delete labels that have never been assigned to a work package.
What approach did you choose and why?
The origin is a boolean on the label (
created_inline), set only on the API's find-or-create path and cleared when an admin edits the label. Admin-created labels therefore survive while unused, which the epic asks for."Unused" means no
labelingsrow and nolabel_journalsrow, since the snapshot table keeps no foreign key to labels and a deleted label would otherwise leave holes in history. A 15 minute grace period onupdated_atcovers the gap between creating the label through the API and saving the work package that uses it. A form left open longer than that and then saved gets a "label does not exist" error on that label; product accepts that.Creating a label inline books a cleanup run 15 minutes later, and the job books its own next run while unused inline labels remain, so instances without inline activity stay quiet; the nightly entry stays as a safety net. Running it as a job rather than synchronously in the work package services covers every path that removes labelings (bulk edits, moves, deletions) without coupling those flows to label housekeeping. The job locks its candidates
FOR UPDATE SKIP LOCKEDbefore deleting: a work package save holds the labels it is about to use with a key share lock during validation, so a label being attached right now is skipped until the next run, and a save that starts after the job locked a label fails validation cleanly instead of losing its labeling to the cascade. A key share lock alone was not enough, because Postgres does not re-evaluate a blocked DELETE's conditions against a row that was merely locked.AI involvement
Collaborative – AI generated a substantial part of the code; I reviewed and understand every line.
Merge checklist