Repository: learningequality/kolibri

Issue:
After deleting resources, they are still annotated as non-importable when selecting them to re-import
## Observed behavior

[Watch this video of me deleting, then re-importing content](https://drive.google.com/file/d/1jUln-Ay-HViqrVZH5rvTwkTRvRFiPNxi/view?usp=sharing) from Touchable Earth.

Steps:

1. I delete all the contents of a topic (but not the entire topic itself)
2. I wait for the deletion task to finish
3. I go back to TE > Import more
4. I go to the topic and it is flagged as non-importable (the checkbox is disabled)
5. But if I follow the link into the topic, the child nodes are flagged as importable.

![CleanShot 2021-04-16 at 16 31 03](https://user-images.githubusercontent.com/10248067/115094381-2a019680-9ed2-11eb-9c5c-8cfea007d1dc.png)

![CleanShot 2021-04-16 at 16 30 55](https://user-images.githubusercontent.com/10248067/115094390-2ff77780-9ed2-11eb-8e4b-13f9f750060f.png)

## Expected behavior

After deleting objects, if I go back to re-import them, the topic node is properly annotated as "importable"

## Root cause

My suspiscion is that there is something wrong in the `ContentNodeGranular` viewset. It may have to do with caching.

## Steps to reproduce

Do something similar to the instructions in "Observed Behavior"

## Context

Kolibri 0.14.7


Discussion:
I think this is an issue with the importability stats being stored in the process cache, and then not getting cleared when the channel gets updated via deletion.
@jonboiser @rtibbles I think that problem here is that the topic is not being annotated when all its content is deleted.

https://github.com/learningequality/kolibri/blob/0c2b49a5e7d7df399216119f96098d48e3554b76/kolibri/core/content/utils/annotation.py#L622-L641

Line 634 in the above code checks if nodes exist for a topic. If we delete all the contents of a topic, the condition fails, due to which the topic does not get annotated.

One solution to the above problem might be to iterate over the topics with no available nodes and then explicitly set `on_device_resources=0` for them.  We can achieve this by adding a code similar to the below code to the code referenced above.
```
connection.execute(
    ContentNodeTable.update()
    .where(
        and_(
            ContentNodeTable.c.level == level - 1,
            ContentNodeTable.c.channel_id == channel_id,
            ContentNodeTable.c.kind == content_kinds.TOPIC,
        )
    )
    .where(~exists(available_nodes))
    .values(
        on_device_resources=0,
    )
)
``` 
This is taken into account by this line:
https://github.com/learningequality/kolibri/blob/0c2b49a5e7d7df399216119f96098d48e3554b76/kolibri/core/content/utils/annotation.py#L552

Here, all topics in a channel are first marked as not available, so only those with any descendants are marked as available.

I think this is much more likely failure to invalidate the importability stats cache.
> This is taken into account by this line:
> https://github.com/learningequality/kolibri/blob/0c2b49a5e7d7df399216119f96098d48e3554b76/kolibri/core/content/utils/annotation.py#L552
> 
> Here, all topics in a channel are first marked as not available, so only those with any descendants are marked as available.
> 
Since all the topics are first marked as not available, and only those with any descendants are updated later, the topics without any descendants have the `on_device_resources` field unchanged. 
In the frontend, the checkbox is disabled when the condition `on_device_resources === total_resources` holds true. Since the `on_device_resources` field is unchanged in the backend, we are unable to import the topic from the frontend.
https://github.com/learningequality/kolibri/blob/0c2b49a5e7d7df399216119f96098d48e3554b76/kolibri/plugins/device/assets/src/views/SelectContentPage/treeViewUtils.js#L87-L94
In that case, we need to update the earlier update to `available=False, on_device_resources=0` - that would seem to account for it!

Resolving patch:
diff --git a/kolibri/core/content/utils/annotation.py b/kolibri/core/content/utils/annotation.py
--- a/kolibri/core/content/utils/annotation.py
+++ b/kolibri/core/content/utils/annotation.py
@@ -559,7 +559,10 @@ def recurse_annotation_up_tree(channel_id):
                 ContentNodeTable.c.kind == content_kinds.TOPIC,
             )
         )
-        .values(available=False)
+        .values(
+            available=False,
+            on_device_resources=0,
+        )
     )
 
     # Expression to capture all available child nodes of a contentnode


Test patch: