Skip to content

Comments

Fix checklist node copying#8925

Merged
carlobeltrame merged 7 commits intoecamp:develfrom
carlobeltrame:fix-checklist-node-copying
Jan 27, 2026
Merged

Fix checklist node copying#8925
carlobeltrame merged 7 commits intoecamp:develfrom
carlobeltrame:fix-checklist-node-copying

Conversation

@carlobeltrame
Copy link
Member

@carlobeltrame carlobeltrame commented Jan 24, 2026

We had some issues when copying activities containing checklist nodes across camps.
The checklist nodes have connections to checklist items. After copying such a checklist node to a new camp, the copy still has connections to the checklist items in the old camp. In the UI this does not display, because we only display checklist items from the current camp. But in the DB and API,
here are 48 such faulty connections on prod currently.

My proposed solution is to find replacement checklist items in the target camp. I thought about only matching the text of the item, but maybe there are courses with a checklist structure like:

  • LS
    • planen
    • durchführen
    • auswerten
  • LA
    • planen
    • durchführen
    • auswerten

So I opted for a little more advanced algorithm, matching the item text plus optionally the hierarchical parent items, plus the checklist name if all else matches.

To reproduce:

  1. Have a course with an activity or category with a checklist node and some selected checklist items (e.g. Quidditch in the course in the dev data)
  2. In a separate camp, add a checklist with the same or similar items (for very similar use preset PBS Basis Wolf, for fairly similar use preset PBS Basis Pfadi)
  3. Copy the Quidditch activity from the course to the camp and reload the page (reload is necessary due to the bug Checklist content node does not refresh when (un-)checking checklist items from a checklist that is not present yet #7816)
  4. Before this fix, no checklist items show up in the new activity, but are present in the DB and API. After this PR, only the items with an exact match are present and visible in the new activity.

There are also some leftover Aufbau Checklist Prototypes which I now deactivated.

Previously, we only manually disabled the Basis prototypes, but forgot
about the Aufbau prototypes.
We can completely remove the prototypes from the dev data.
@carlobeltrame carlobeltrame requested a review from a team January 24, 2026 16:56
@carlobeltrame carlobeltrame added the deploy! Creates a feature branch deployment for this PR label Jan 24, 2026
@carlobeltrame carlobeltrame temporarily deployed to feature-branch January 24, 2026 17:20 — with GitHub Actions Inactive
@github-actions
Copy link

github-actions bot commented Jan 24, 2026

Feature branch deployment ready!

Name Link
😎 Deployment https://pr8925.ecamp3.ch/
🔑 Login test@example.com / test
🕒 Last deployed at Tue Jan 27 2026 20:40:00 GMT+0100
🔨 Latest commit e50735c7b1279047513a51e62fc1efda1df864f1
🔍 Latest deploy log https://github.com/ecamp/ecamp3/actions/runs/21411310229/job/61649113298
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

@carlobeltrame carlobeltrame force-pushed the fix-checklist-node-copying branch from cba8d96 to a2aa911 Compare January 24, 2026 22:18
@carlobeltrame carlobeltrame temporarily deployed to feature-branch January 24, 2026 22:21 — with GitHub Actions Inactive
@carlobeltrame carlobeltrame force-pushed the fix-checklist-node-copying branch from a2aa911 to ef948aa Compare January 25, 2026 09:13
@carlobeltrame carlobeltrame temporarily deployed to feature-branch January 25, 2026 09:16 — with GitHub Actions Inactive
@carlobeltrame carlobeltrame force-pushed the fix-checklist-node-copying branch from ef948aa to c5fe5e0 Compare January 25, 2026 13:27
@carlobeltrame carlobeltrame temporarily deployed to feature-branch January 25, 2026 13:28 — with GitHub Actions Inactive
@carlobeltrame carlobeltrame force-pushed the fix-checklist-node-copying branch from c5fe5e0 to 1643a02 Compare January 25, 2026 13:30
@carlobeltrame carlobeltrame temporarily deployed to feature-branch January 25, 2026 13:31 — with GitHub Actions Inactive
Copy link
Contributor

@BacLuc BacLuc left a comment

Choose a reason for hiding this comment

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

Looks good to me as soon as the runtime of the migrations in prod is checked.

One a little mean question now that the work is already done:

We have 2 cases here:

  1. Copy the activity in the same camp: Easy, the checklistitems are in the same camp
  2. Copy an activity to another camp: Hard, and a perfect solution is nearly impossible.
    Maybe we could just not copy the checklistnode_checklistitem relation in this case?
    (virtual 3. Copy all activities and checklists from another camp: easy)


public function up(Schema $schema): void {
$this->addSql(<<<'EOF'
DELETE FROM public.checklistnode_checklistitem
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you run this statement as select statement in prod?
How long did it take?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. For activities, it takes 1 s 951 ms and gives the result of 49 faulty connections. For categories, it takes 648 ms and gives the result of 0.


/** @var ChecklistItem $existingParent */
$existingParent = $existingItem->getParent();
while (null !== $parent && null !== $existingParent && $score < 10) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The 10 here is just an upper bound that the loop aborts for sure?
I think we agreed on a maximum nexting level of 3.

Maybe put it in a constant with a name?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, good point. Yes, it is a failsafe. I now extracted it into a constant in 527614e.

$bestMatchIndex = array_find_key($matches, function ($match) use ($maxScore) {
return $match === $maxScore;
});
if ($maxScore > 0 && null !== $bestMatchIndex) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If there are only matches with score 1 (leaf has the same text),
but the parent checklistitem has another name, this will still lead to a match...

Copy link
Member Author

@carlobeltrame carlobeltrame Jan 25, 2026

Choose a reason for hiding this comment

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

Yes, that is intended. This algorithm is supposed to give the best match available, not find a perfect match (because the exact wording of checklist items and their parents may vary slightly over time). I also wrote tests for this: testCopyFromPrototypeAcrossCampsSearchesForItemOfSameName

$materialNodes = array_filter($contentNodes, fn ($cn) => 'Material' == $cn['contentTypeName']);
$this->assertCount(1, $materialNodes);

$materialNode = reset($materialNodes);
Copy link
Contributor

Choose a reason for hiding this comment

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

why not just use $materialNodes[0] or array_first?

I tried to find the next access to $materialNodes and why you are resetting the pointer here....

Copy link
Member Author

Choose a reason for hiding this comment

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

I just copied that from another test written in 9056cda and tried to be consistent. Maybe @pmattmann knows why?

@carlobeltrame carlobeltrame temporarily deployed to feature-branch January 25, 2026 15:29 — with GitHub Actions Inactive
@carlobeltrame
Copy link
Member Author

carlobeltrame commented Jan 25, 2026

One a little mean question now that the work is already done:
2. Copy an activity to another camp: Hard, and a perfect solution is nearly impossible.
Maybe we could just not copy the checklistnode_checklistitem relation in this case?

@BacLuc From a user perspective, this was the behaviour before, because the relations that were copied are never visible in the UI. I ran into this problem in a real course, and initially wanted to change it so that we can copy these connections. We already do something similar with categories (although implemented in the frontend in that case) and we go to great lengths to preserve the material items in copied activities as well. When trying to implement this similar improvement for checklist items, I discovered the bug.

Personally, I think with us supporting the use case of activity templates via publicly shared camps (#7481), the old behaviour is not satisfying. I know my proposed algorithm is not perfect with matching checklist items, particularly if there are some minor typo differences between the checklist items in both camps. I thought this could be a good starting point, and if we find more specific use cases, we can always improve the algorithm. However, if you insist on staying with the old behaviour, just not copying any connections, I can just drop the last two commits.

My ideal vision for the copy activity feature is that activities can be copied from old courses, and they "magically" adapt to the slightly changed checklist items in the new course. I think it's fair to assume that an activity from a course will probably be copied to a similar course, which probably has similar checklists (especially with the checklist templates). I imagine (but have no hard evidence for) that this scenario is what happens most of the time, and IMO we should make it easy to work in this case.

@carlobeltrame carlobeltrame requested a review from a team January 25, 2026 16:37
@manuelmeister manuelmeister requested review from Copilot and manuelmeister and removed request for a team and Copilot January 26, 2026 07:17
Copy link
Member

@manuelmeister manuelmeister left a comment

Choose a reason for hiding this comment

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

Core Meeting Decision

We agree that this makes sense. Thanks!

@carlobeltrame carlobeltrame temporarily deployed to feature-branch January 27, 2026 19:38 — with GitHub Actions Inactive
@carlobeltrame carlobeltrame added this pull request to the merge queue Jan 27, 2026
Merged via the queue into ecamp:devel with commit 3c1a999 Jan 27, 2026
35 checks passed
@carlobeltrame carlobeltrame deleted the fix-checklist-node-copying branch January 27, 2026 19:49
This was referenced Jan 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deploy! Creates a feature branch deployment for this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants