Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extract module and assessment metadata from app CCZ #46

Merged
merged 4 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions commcare_connect/opportunity/app_xml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import itertools
import re
import tempfile
import xml.etree.ElementTree as ET
import zipfile
from dataclasses import dataclass

import httpx
from django.conf import settings

XMLNS = "http://commcareconnect.com/data/v1/learn"
XMLNS_PREFIX = "{%s}" % XMLNS


@dataclass
class Module:
id: str
name: str
description: str
time_estimate: int


@dataclass
class Assessment:
id: str


def get_connect_blocks_for_app(domain: str, app_id: str) -> list[str]:
form_xmls = get_form_xml_for_app(domain, app_id)
return list(itertools.chain.from_iterable(extract_connect_blocks(form_xml) for form_xml in form_xmls))


def get_form_xml_for_app(domain: str, app_id: str) -> list[str]:
"""Download the CCZ for the given app and return the XML for each form."""
ccz_url = f"{settings.COMMCARE_HQ_URL}/a/{domain}/apps/api/download_ccz/"
params = {
"app_id": app_id,
"latest": "release",
}
response = httpx.get(ccz_url, params=params)
response.raise_for_status()

form_xml = []
with tempfile.NamedTemporaryFile() as file:
file.write(response.content)
file.seek(0)

form_re = re.compile(r"modules-\d+/forms-\d+\.xml")
with zipfile.ZipFile(file, "r") as zip_ref:
for file in zip_ref.namelist():
if form_re.match(file):
with zip_ref.open(file) as xml_file:
form_xml.append(xml_file.read().decode())
return form_xml


def extract_connect_blocks(form_xml):
xml = ET.fromstring(form_xml)
yield from extract_modules(xml)
yield from extract_assessments(xml)


def extract_assessments(xml: ET.ElementTree) -> list[str]:
for block in xml.findall(f".//{XMLNS_PREFIX}assessment"):
yield Assessment(block.get("id"))


def extract_modules(xml: ET.ElementTree):
for block in xml.findall(f".//{XMLNS_PREFIX}module"):
slug = block.get("id")
name = get_element_text(block, "name")
description = get_element_text(block, "description")
time_estimate = get_element_text(block, "time_estimate")
yield Module(slug, name, description, int(time_estimate) if time_estimate is not None else None)


def get_element_text(parent, name) -> str | None:
element = parent.find(f"{XMLNS_PREFIX}{name}")
return element.text if element is not None else None
7 changes: 7 additions & 0 deletions commcare_connect/opportunity/tests/data/demo_app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
This content comes from the https://staging.commcarehq.org/a/skelly/apps/view/a671db914232020002c1f294c675d1d8/ app.

To update it from the app:

1. Download the CCZ: https://staging.commcarehq.org/a/skelly/apps/api/download_ccz/?app_id=a671db914232020002c1f294c675d1d8
2. Extract the contents
3. Update the files in this folder
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<h:html xmlns:h="http://www.w3.org/1999/xhtml" xmlns:orx="http://openrosa.org/jr/xforms" xmlns="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:jr="http://openrosa.org/javarosa" xmlns:vellum="http://commcarehq.org/xforms/vellum">
<h:head>
<h:title>Module 1</h:title>
<model>
<instance>
<data xmlns:jrm="http://dev.commcarehq.org/jr/xforms" xmlns="http://openrosa.org/formdesigner/52F02F3E-320D-4D91-9EBF-FF4F06226E98" uiVersion="1" version="72" name="Module 1">
<welcome_note/>
<module_1>
<module xmlns="http://commcareconnect.com/data/v1/learn" id="module_1">
<name>Module 1</name>
<description>This is the first module in a series of modules
that will take you through all you need to know.</description>
<time_estimate>1</time_estimate>
</module>
</module_1>
<orx:meta xmlns:cc="http://commcarehq.org/xforms">
<orx:deviceID/>
<orx:timeStart/>
<orx:timeEnd/>
<orx:username/>
<orx:userID/>
<orx:instanceID/>
<cc:appVersion/>
<orx:drift/>
</orx:meta>
</data>
</instance>
<instance id="commcaresession" src="jr://instance/session"/>
<bind nodeset="/data/welcome_note"/>
<itext>
<translation lang="en" default="">
<text id="welcome_note-label">
<value>Welcome to module 1</value>
</text>
</translation>
</itext>
<setvalue ref="/data/meta/deviceID" value="instance('commcaresession')/session/context/deviceid" event="xforms-ready"/>
<setvalue ref="/data/meta/timeStart" value="now()" event="xforms-ready"/>
<bind nodeset="/data/meta/timeStart" type="xsd:dateTime"/>
<setvalue ref="/data/meta/timeEnd" value="now()" event="xforms-revalidate"/>
<bind nodeset="/data/meta/timeEnd" type="xsd:dateTime"/>
<setvalue ref="/data/meta/username" value="instance('commcaresession')/session/context/username" event="xforms-ready"/>
<setvalue ref="/data/meta/userID" value="instance('commcaresession')/session/context/userid" event="xforms-ready"/>
<setvalue ref="/data/meta/instanceID" value="uuid()" event="xforms-ready"/>
<setvalue ref="/data/meta/appVersion" value="instance('commcaresession')/session/context/appversion" event="xforms-ready"/>
<setvalue ref="/data/meta/drift" value="if(count(instance('commcaresession')/session/context/drift) = 1, instance('commcaresession')/session/context/drift, '')" event="xforms-revalidate"/>
</model>
</h:head>
<h:body>
<trigger ref="/data/welcome_note" appearance="minimal">
<label ref="jr:itext('welcome_note-label')"/>
</trigger>
</h:body>
</h:html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<h:html xmlns:h="http://www.w3.org/1999/xhtml" xmlns:orx="http://openrosa.org/jr/xforms" xmlns="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:jr="http://openrosa.org/javarosa" xmlns:vellum="http://commcarehq.org/xforms/vellum">
<h:head>
<h:title>Module 2</h:title>
<model>
<instance>
<data xmlns:jrm="http://dev.commcarehq.org/jr/xforms" xmlns="http://openrosa.org/formdesigner/EC1AD740-D2C9-4532-AECC-2D5CF5364696" uiVersion="1" version="72" name="Module 2">
<welcome_note/>
<module_2>
<module xmlns="http://commcareconnect.com/data/v1/learn" id="module_2">
<name>Module 2</name>
<description>This is module 2 of the series.</description>
<time_estimate>2</time_estimate>
</module>
</module_2>
<orx:meta xmlns:cc="http://commcarehq.org/xforms">
<orx:deviceID/>
<orx:timeStart/>
<orx:timeEnd/>
<orx:username/>
<orx:userID/>
<orx:instanceID/>
<cc:appVersion/>
<orx:drift/>
</orx:meta>
</data>
</instance>
<instance id="commcaresession" src="jr://instance/session"/>
<bind nodeset="/data/welcome_note"/>
<itext>
<translation lang="en" default="">
<text id="welcome_note-label">
<value>Welcome to module 2</value>
</text>
</translation>
</itext>
<setvalue ref="/data/meta/deviceID" value="instance('commcaresession')/session/context/deviceid" event="xforms-ready"/>
<setvalue ref="/data/meta/timeStart" value="now()" event="xforms-ready"/>
<bind nodeset="/data/meta/timeStart" type="xsd:dateTime"/>
<setvalue ref="/data/meta/timeEnd" value="now()" event="xforms-revalidate"/>
<bind nodeset="/data/meta/timeEnd" type="xsd:dateTime"/>
<setvalue ref="/data/meta/username" value="instance('commcaresession')/session/context/username" event="xforms-ready"/>
<setvalue ref="/data/meta/userID" value="instance('commcaresession')/session/context/userid" event="xforms-ready"/>
<setvalue ref="/data/meta/instanceID" value="uuid()" event="xforms-ready"/>
<setvalue ref="/data/meta/appVersion" value="instance('commcaresession')/session/context/appversion" event="xforms-ready"/>
<setvalue ref="/data/meta/drift" value="if(count(instance('commcaresession')/session/context/drift) = 1, instance('commcaresession')/session/context/drift, '')" event="xforms-revalidate"/>
</model>
</h:head>
<h:body>
<trigger ref="/data/welcome_note" appearance="minimal">
<label ref="jr:itext('welcome_note-label')"/>
</trigger>
</h:body>
</h:html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<h:html xmlns:h="http://www.w3.org/1999/xhtml" xmlns:orx="http://openrosa.org/jr/xforms" xmlns="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:jr="http://openrosa.org/javarosa" xmlns:vellum="http://commcarehq.org/xforms/vellum">
<h:head>
<h:title>Module 3</h:title>
<model>
<instance>
<data xmlns:jrm="http://dev.commcarehq.org/jr/xforms" xmlns="http://openrosa.org/formdesigner/11151AA2-1599-4C5E-8013-5E2197B6C68E" uiVersion="1" version="72" name="Module 3">
<welcome_note/>
<module_3>
<module xmlns="http://commcareconnect.com/data/v1/learn" id="module_3">
<name>Module 3</name>
<description>Module 3 in the series</description>
<time_estimate>3</time_estimate>
</module>
</module_3>
<orx:meta xmlns:cc="http://commcarehq.org/xforms">
<orx:deviceID/>
<orx:timeStart/>
<orx:timeEnd/>
<orx:username/>
<orx:userID/>
<orx:instanceID/>
<cc:appVersion/>
<orx:drift/>
</orx:meta>
</data>
</instance>
<instance id="commcaresession" src="jr://instance/session"/>
<bind nodeset="/data/welcome_note"/>
<itext>
<translation lang="en" default="">
<text id="welcome_note-label">
<value>Welcome to module 3</value>
</text>
</translation>
</itext>
<setvalue ref="/data/meta/deviceID" value="instance('commcaresession')/session/context/deviceid" event="xforms-ready"/>
<setvalue ref="/data/meta/timeStart" value="now()" event="xforms-ready"/>
<bind nodeset="/data/meta/timeStart" type="xsd:dateTime"/>
<setvalue ref="/data/meta/timeEnd" value="now()" event="xforms-revalidate"/>
<bind nodeset="/data/meta/timeEnd" type="xsd:dateTime"/>
<setvalue ref="/data/meta/username" value="instance('commcaresession')/session/context/username" event="xforms-ready"/>
<setvalue ref="/data/meta/userID" value="instance('commcaresession')/session/context/userid" event="xforms-ready"/>
<setvalue ref="/data/meta/instanceID" value="uuid()" event="xforms-ready"/>
<setvalue ref="/data/meta/appVersion" value="instance('commcaresession')/session/context/appversion" event="xforms-ready"/>
<setvalue ref="/data/meta/drift" value="if(count(instance('commcaresession')/session/context/drift) = 1, instance('commcaresession')/session/context/drift, '')" event="xforms-revalidate"/>
</model>
</h:head>
<h:body>
<trigger ref="/data/welcome_note" appearance="minimal">
<label ref="jr:itext('welcome_note-label')"/>
</trigger>
</h:body>
</h:html>
114 changes: 114 additions & 0 deletions commcare_connect/opportunity/tests/data/demo_app/modules-2/forms-0.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<h:html xmlns:h="http://www.w3.org/1999/xhtml" xmlns:orx="http://openrosa.org/jr/xforms" xmlns="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:jr="http://openrosa.org/javarosa" xmlns:vellum="http://commcarehq.org/xforms/vellum">
<h:head>
<h:title>Assessment</h:title>
<model>
<instance>
<data xmlns:jrm="http://dev.commcarehq.org/jr/xforms" xmlns="http://openrosa.org/formdesigner/BD70B3D5-6CB4-4A2E-AD5B-C8E3E7BC37A7" uiVersion="1" version="72" name="Assessment">
<did_you_lean_anything/>
<how_many_modules_are_there/>
<score/>
<your_score_is_score/>
<demo>
<assessment xmlns="http://commcareconnect.com/data/v1/learn" id="demo">
<user_score/>
</assessment>
</demo>
<orx:meta xmlns:cc="http://commcarehq.org/xforms">
<orx:deviceID/>
<orx:timeStart/>
<orx:timeEnd/>
<orx:username/>
<orx:userID/>
<orx:instanceID/>
<cc:appVersion/>
<orx:drift/>
</orx:meta>
</data>
</instance>
<instance id="commcaresession" src="jr://instance/session"/>
<bind nodeset="/data/did_you_lean_anything"/>
<bind nodeset="/data/how_many_modules_are_there"/>
<bind nodeset="/data/score" calculate="if(/data/did_you_lean_anything = 'yes', 20, 0) + if(/data/how_many_modules_are_there = 'three', 50, 0)"/>
<bind nodeset="/data/your_score_is_score"/>
<bind nodeset="/data/demo/assessment/user_score" calculate="/data/score"/>
<itext>
<translation lang="en" default="">
<text id="did_you_lean_anything-label">
<value>Did you lean anything?</value>
</text>
<text id="did_you_lean_anything-yes-label">
<value>Yes</value>
</text>
<text id="did_you_lean_anything-no-label">
<value>No</value>
</text>
<text id="how_many_modules_are_there-label">
<value>How many modules are there?</value>
</text>
<text id="how_many_modules_are_there-one-label">
<value>One</value>
</text>
<text id="how_many_modules_are_there-two-label">
<value>Two</value>
</text>
<text id="how_many_modules_are_there-three-label">
<value>Three</value>
</text>
<text id="how_many_modules_are_there-four-label">
<value>Four</value>
</text>
<text id="your_score_is_score-label">
<value>Your score is:
<output value="/data/score"/>
</value>
</text>
</translation>
</itext>
<setvalue ref="/data/meta/deviceID" value="instance('commcaresession')/session/context/deviceid" event="xforms-ready"/>
<setvalue ref="/data/meta/timeStart" value="now()" event="xforms-ready"/>
<bind nodeset="/data/meta/timeStart" type="xsd:dateTime"/>
<setvalue ref="/data/meta/timeEnd" value="now()" event="xforms-revalidate"/>
<bind nodeset="/data/meta/timeEnd" type="xsd:dateTime"/>
<setvalue ref="/data/meta/username" value="instance('commcaresession')/session/context/username" event="xforms-ready"/>
<setvalue ref="/data/meta/userID" value="instance('commcaresession')/session/context/userid" event="xforms-ready"/>
<setvalue ref="/data/meta/instanceID" value="uuid()" event="xforms-ready"/>
<setvalue ref="/data/meta/appVersion" value="instance('commcaresession')/session/context/appversion" event="xforms-ready"/>
<setvalue ref="/data/meta/drift" value="if(count(instance('commcaresession')/session/context/drift) = 1, instance('commcaresession')/session/context/drift, '')" event="xforms-revalidate"/>
</model>
</h:head>
<h:body>
<select ref="/data/did_you_lean_anything">
<label ref="jr:itext('did_you_lean_anything-label')"/>
<item>
<label ref="jr:itext('did_you_lean_anything-yes-label')"/>
<value>yes</value>
</item>
<item>
<label ref="jr:itext('did_you_lean_anything-no-label')"/>
<value>no</value>
</item>
</select>
<select1 ref="/data/how_many_modules_are_there">
<label ref="jr:itext('how_many_modules_are_there-label')"/>
<item>
<label ref="jr:itext('how_many_modules_are_there-one-label')"/>
<value>one</value>
</item>
<item>
<label ref="jr:itext('how_many_modules_are_there-two-label')"/>
<value>two</value>
</item>
<item>
<label ref="jr:itext('how_many_modules_are_there-three-label')"/>
<value>three</value>
</item>
<item>
<label ref="jr:itext('how_many_modules_are_there-four-label')"/>
<value>four</value>
</item>
</select1>
<trigger ref="/data/your_score_is_score" appearance="minimal">
<label ref="jr:itext('your_score_is_score-label')"/>
</trigger>
</h:body>
</h:html>
Loading