diff --git a/.github/workflows/test-tutor-aspects.yml b/.github/workflows/test-tutor-aspects.yml index 699a8bb32..913129584 100644 --- a/.github/workflows/test-tutor-aspects.yml +++ b/.github/workflows/test-tutor-aspects.yml @@ -33,6 +33,20 @@ jobs: run: tutor config save - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@d5af243ce7bacb67384aa6c5b1fc5f169e30903e + with: + # this might remove tools that are actually needed, + # if set to "true" but frees about 6 GB + tool-cache: false + + # all of these default to true, but feel free to set to + # "false" if necessary for your workflow + android: true + dotnet: true + haskell: true + large-packages: false + swap-storage: true - name: Tutor build openedx run: tutor images build openedx aspects aspects-superset - name: Tutor start @@ -71,6 +85,20 @@ jobs: run: tutor config save - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@d5af243ce7bacb67384aa6c5b1fc5f169e30903e + with: + # this might remove tools that are actually needed, + # if set to "true" but frees about 6 GB + tool-cache: false + + # all of these default to true, but feel free to set to + # "false" if necessary for your workflow + android: true + dotnet: true + haskell: true + large-packages: false + swap-storage: true - name: Tutor build openedx run: tutor images build openedx-dev aspects aspects-superset - name: Tutor start diff --git a/.gitignore b/.gitignore index 50cd6869f..dabb1d8c4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ __pycache__ /.idea/ venv/ env/ +transifex_input.yaml diff --git a/Makefile b/Makefile index 9bac70c43..0983b2078 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ upgrade: $(COMMON_CONSTRAINTS_TXT) pip install -r requirements/pip-tools.txt $(UPGRADE) -o requirements/base.txt requirements/base.in $(UPGRADE) -o requirements/dev.txt requirements/dev.in + $(UPGRADE) -o requirements/translations.txt requirements/translations.in requirements: ## Install packages from base requirement files pip install -r requirements/pip.txt @@ -42,6 +43,10 @@ dev-requirements: ## Install packages from developer requirement files pip uninstall --yes $(PROJECT) pip install -e . +translation-requirements: ## Install packages from translation requirements + pip install -r requirements/pip.txt + pip install -r requirements/translations.txt + build-pythonpackage: ## Build Python packages ready to upload to pypi python setup.py sdist bdist_wheel @@ -78,17 +83,15 @@ release-push: git push origin $(TAG) ###### Additional commands -push_translations: - TUTOR_ROOT=$(TUTOR_ROOT) tutor config save - python scripts/translate.py $(TUTOR_ROOT) push +pull_translations: translation-requirements + rm -rf tutoraspects/templates/aspects/apps/superset/conf/locale/*; + atlas pull $(OPENEDX_ATLAS_ARGS) translations/tutor-contrib-aspects/tutoraspects/templates/aspects/apps/superset/conf/locale/:tutoraspects/templates/aspects/apps/superset/conf/locale/ -compile_translations: - TUTOR_ROOT=$(TUTOR_ROOT) tutor config save - python scripts/translate.py $(TUTOR_ROOT) compile + @echo "Translations have been pulled via Atlas." + python scripts/translate.py . compile -list_translations: - TUTOR_ROOT=$(TUTOR_ROOT) tutor config save - python scripts/translate.py $(TUTOR_ROOT) list +extract_translations: translation-requirements + python scripts/translate.py . extract version: ## Print the current tutor version @python -c 'import io, os; about = {}; exec(io.open(os.path.join("$(PACKAGE)", "__about__.py"), "rt", encoding="utf-8").read(), about); print(about["__version__"])' diff --git a/requirements/base.in b/requirements/base.in index a303579ac..40579b28b 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -1,4 +1,5 @@ click -tutor>=15 bcrypt +openedx-atlas transifex-python +tutor>=15 diff --git a/requirements/base.txt b/requirements/base.txt index 5d54d5288..b55f0d8c5 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -43,6 +43,8 @@ oauthlib==3.2.2 # via # kubernetes # requests-oauthlib +openedx-atlas==0.5.0 + # via -r requirements/base.in parsimonious==0.10.0 # via pyseeyou pyasn1==0.5.0 diff --git a/requirements/dev.txt b/requirements/dev.txt index e1c9cf2f3..34ccce5db 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -106,6 +106,8 @@ oauthlib==3.2.2 # -r requirements/base.txt # kubernetes # requests-oauthlib +openedx-atlas==0.5.0 + # via -r requirements/base.txt packaging==23.2 # via # black diff --git a/requirements/translations.in b/requirements/translations.in new file mode 100644 index 000000000..6952e02e0 --- /dev/null +++ b/requirements/translations.in @@ -0,0 +1,2 @@ +# Requirements needed to run in openedx-translations to manage localization +click diff --git a/requirements/translations.txt b/requirements/translations.txt new file mode 100644 index 000000000..3a8facebc --- /dev/null +++ b/requirements/translations.txt @@ -0,0 +1,8 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# make upgrade +# +click==8.1.7 + # via -r requirements/translations.in diff --git a/scripts/translate.py b/scripts/translate.py index 3092785aa..951e34218 100644 --- a/scripts/translate.py +++ b/scripts/translate.py @@ -2,15 +2,7 @@ import sys import click -from transifex.native import init -from utils import (LANGUAGES, compile_translations, get_text_for_translations, - push_translations) - -init( - os.getenv("TRANSIFEX_TOKEN"), - LANGUAGES, - os.getenv("TRANSIFEX_SECRET"), -) +from utils import compile_translations, extract_translations, get_text_for_translations @click.command() @@ -18,8 +10,8 @@ @click.argument("action") def command(root, action): """Interface for the translations.""" - if action == "push": - push_translations(root) + if action == "extract": + extract_translations(root) elif action == "compile": compile_translations(root) elif action == "list": diff --git a/scripts/utils.py b/scripts/utils.py index cbd8999f7..256984c89 100644 --- a/scripts/utils.py +++ b/scripts/utils.py @@ -1,50 +1,46 @@ +import os import yaml -from transifex.native import tx -from transifex.native.parsing import SourceString ASSET_FOLDER_MAPPING = { "dashboard_title": "dashboards", "slice_name": "charts", - "database_name": "databases", - "table_name": "datasets", } -LANGUAGES = [ - "es", - "it", - "fr", - "zh", - "ja", - "de", - "pt", - "ru", - "ko", - "sk", - "sl", - "nl", -] - def get_text_for_translations(root_path): - assets_file = ( - root_path + "/env/plugins/aspects/apps/superset/pythonpath/assets.yaml" + assets_path = ( + os.path.join(root_path, "tutoraspects/templates/openedx-assets/assets/") ) + print(f"Assets path: {assets_path}") + strings = [] - file = yaml.load(open(assets_file, "r"), Loader=yaml.FullLoader) + for root, dirs, files in os.walk(assets_path): + for file in files: + if not file.endswith(".yaml"): + continue + path = os.path.join(root, file) + print(f"Reading {path}") + with open(path, 'r') as asset_file: + asset_str = asset_file.read().replace("{{", "'") + asset_str = asset_str.replace("}}", "'") - for asset in file: - strings.extend(mark_text_for_translation(asset)) + asset = yaml.safe_load(asset_str) + strings.extend(mark_text_for_translation(asset)) return strings def mark_text_for_translation(asset): - """For every asset extract the text and mark it for translation""" + """ + For every asset extract the text and mark it for translation + """ def extract_text(asset, type): - """Extract text from an asset""" + """ + Extract text from an asset + """ strings = [] if type == "dashboards": strings.append(asset["dashboard_title"]) @@ -73,64 +69,82 @@ def extract_text(asset, type): if asset["params"].get("y_axis_label"): strings.append(asset["params"]["y_axis_label"]) - elif type == "databases": - # WARNING: Databases are not translated - pass - elif type == "datasets": - # WARNING: Datasets are not translated - pass return strings for key, value in ASSET_FOLDER_MAPPING.items(): if key in asset: strings = extract_text(asset, value) print( - f"Extracting text from {value} {asset.get('uuid')}", - strings, + f"Extracted {len(strings)} strings from {value} {asset.get('uuid')}" ) return strings + # If we get here it's a type of asset that we don't translate, return nothing. + return [] -def compile_translations(root_path): - print("Fetching translations...") - tx.fetch_translations() - translation_file = ( - "tutoraspects/templates/aspects/apps/superset/localization/locale.yaml" +def compile_translations(root_path): + """ + Combine translated files into the single file we use for translation. + + This should be called after we pull translations using Atlas, see the + pull_translations make target. + """ + translations_path = ( + os.path.join( + root_path, + "tutoraspects/templates/aspects/apps/superset/conf/locale" + ) ) - file = open(translation_file, "w") - STRINGS = get_text_for_translations(root_path) - - translations = {} + all_translations = {} + for root, dirs, files in os.walk(translations_path): + for file in files: + if not file.endswith(".yaml"): + continue + + lang = root.split(os.sep)[-1] + path = os.path.join(root, file) + with open(path, 'r') as asset_file: + loc_str = asset_file.read() + all_translations[lang] = yaml.safe_load(loc_str)[lang] + + out_path = ( + os.path.join( + root_path, + "tutoraspects/templates/aspects/apps/superset/localization/locale.yaml" + ) + ) - print("Compiling translations...") + print(f"Writing all translations out to {out_path}") + with open(out_path, 'w') as outfile: + outfile.write("---\n") + yaml.safe_dump(all_translations, outfile) + outfile.write("\n{{ patch('superset-extra-asset-translations')}}\n") - for language in LANGUAGES: - print("Processing language", language) - translations[language] = {} - for string in STRINGS: - if not translations[language].get(string): - translation = tx.get_translation(string, language, None) - translations[language][string] = translation if translation else "" - file.write("---\n") - file.write(yaml.dump(translations)) - file.write("\n{{ patch('superset-extra-asset-translations')}}\n") +def extract_translations(root_path): + """ + This gathers all translatable text from the Superset assets. + An English locale file is created, which openedx-translations will send to + Transifex for translation. + """ + # The expectation is that this will end up at the site root, which should + # be cwd for make targets. This is a temporary file used only in the Github + # action in openedx-translations. + translation_file = "transifex_input.yaml" -def push_translations(root_path): - print("Publishing translation strings...") + print("Gathering text for translations...") + STRINGS = set(get_text_for_translations(root_path)) + translations = {'en': {}} - STRINGS = get_text_for_translations(root_path) + for string in STRINGS: + translations['en'][string] = string - source_strings = [] - for text in STRINGS: - source_string = SourceString( - text, - ) - source_strings.append(source_string) + print(f"Writing English strings to {translation_file}") + with open(translation_file, "w") as file: + file.write(yaml.dump(translations)) - response_code, response_content = tx.push_source_strings(source_strings, purge=True) - print(response_code, response_content) + print("Done compiling translations.") diff --git a/tutoraspects/templates/aspects/apps/superset/conf/README.md b/tutoraspects/templates/aspects/apps/superset/conf/README.md new file mode 100644 index 000000000..182ab3da5 --- /dev/null +++ b/tutoraspects/templates/aspects/apps/superset/conf/README.md @@ -0,0 +1,7 @@ +# Locale Files + +These files are managed via the OEP-58 process and should not be manually edited as they will be overwritten. The process is: + +1. A Github action on `openedx-translations` will pull this repo and run `make extract-translations` on this repo's Makefile. This pulls all translatable strings from the Superset assets. These are then uploaded to Transifex for translation. +2. As translations are updated in Transifex they will be pushed to the correct language's files in `openedx-translations`. +3. A Github action on this repo will run `make pull-translations` periodically. This will run the Atlas tool to synchronize the translation files in this directory with what is stored in `openedx-translations`. Then it will run a command to compile all of the separate translated files into one `locale.yaml` that is used to create the localized dashboard and chart strings during the Tutor init command. It will then automatically merge those changed files. diff --git a/tutoraspects/templates/aspects/apps/superset/conf/locale/en/locale.yaml b/tutoraspects/templates/aspects/apps/superset/conf/locale/en/locale.yaml new file mode 100644 index 000000000..e0743b8fe --- /dev/null +++ b/tutoraspects/templates/aspects/apps/superset/conf/locale/en/locale.yaml @@ -0,0 +1,225 @@ +en: + ? "# Instance information Aspects: " + : "# Instance information Aspects: " + ? '### Course Enrollment + + + + These charts can help you understand how many students are enrolled in your course + by showing how enrollments have changed over time and which enrollment modes students + are choosing. More details about each chart can be found by clicking the "3 dot" + menu at the top right of the chart and selecting "Show chart description". + + + You can filter these results by choosing selecting various options from the filters + on the left.' + : '### Course Enrollment + + + + These charts can help you understand how many students are enrolled in your course + by showing how enrollments have changed over time and which enrollment modes students + are choosing. More details about each chart can be found by clicking the "3 dot" + menu at the top right of the chart and selecting "Show chart description". + + + You can filter these results by choosing selecting various options from the filters + on the left.' + ? '### Problem Engagement + + + These charts can help you learn which problems your students have engaged with + or skipped, and the relative difficulty of problems across your course. More details + about each chart can be found by clicking the "3 dot" menu at the top right of + the chart and selecting "Show chart description". + + + **Select a course from the filters on the left to populate these charts.**' + : '### Problem Engagement + + + These charts can help you learn which problems your students have engaged with + or skipped, and the relative difficulty of problems across your course. More details + about each chart can be found by clicking the "3 dot" menu at the top right of + the chart and selecting "Show chart description". + + + **Select a course from the filters on the left to populate these charts.**' + ? '### Problem Performance + + + These charts help show the difficulty of your problems by showing you which answers + your students choose and which require many tries or hints to complete successfully. + + + **Select a problem from the filters on the left to populate the charts.**' + : '### Problem Performance + + + These charts help show the difficulty of your problems by showing you which answers + your students choose and which require many tries or hints to complete successfully. + + + **Select a problem from the filters on the left to populate the charts.**' + ? '### Video Engagement + + + These charts can help understand which videos are watched, and rewatched, most + often. It can also show how many users use the closed captions and transcript + features on your videos, and how often. + + + **Select a course from the filters on the left to populate these charts.**' + : '### Video Engagement + + + These charts can help understand which videos are watched, and rewatched, most + often. It can also show how many users use the closed captions and transcript + features on your videos, and how often. + + + **Select a course from the filters on the left to populate these charts.**' + ? '### Video Performance + + + This section allows you to see which parts of a particular video are most watched + or skipped. + + + **Select a video from the filters on the left to populate these charts.**' + : '### Video Performance + + + This section allows you to see which parts of a particular video are most watched + or skipped. + + + **Select a video from the filters on the left to populate these charts.**' + ? A count of the number of enrollments and un-enrollments per day. Learners can + enroll and unenroll multiple times, in this chart each individual enrollment and + unenrollment will be counted. + : A count of the number of enrollments and un-enrollments per day. Learners can + enroll and unenroll multiple times, in this chart each individual enrollment and + unenrollment will be counted. + Active Users Per Organization: Active Users Per Organization + Actor IDs over time: Actor IDs over time + Answers Chosen: Answers Chosen + ClickHouse: ClickHouse + Course Enrollment: Course Enrollment + Course Enrollments Over Time: Course Enrollments Over Time + Course Grade (out of 100%): Course Grade (out of 100%) + Course Grade Distribution: Course Grade Distribution + Courses: Courses + Courses Per Organization: Courses Per Organization + Currently Enrolled Learners Per Day: Currently Enrolled Learners Per Day + Distinct forum users: Distinct forum users + Distribution Of Attempts: Distribution Of Attempts + Distribution Of Hints Per Correct Answer: Distribution Of Hints Per Correct Answer + Distribution Of Problem Grades: Distribution Of Problem Grades + Distribution Of Responses: Distribution Of Responses + Enrollment Events Per Day: Enrollment Events Per Day + Enrollments: Enrollments + Enrollments By Enrollment Mode: Enrollments By Enrollment Mode + Enrollments By Type: Enrollments By Type + Event type: Event type + Events per course: Events per course + Forum Interaction: Forum Interaction + Hints / Answer Displayed Before Correct Answer Chosen: Hints / Answer Displayed + Before Correct Answer Chosen + Instance Health: Instance Health + Instructor Dashboard: Instructor Dashboard + Last Received Event: Last Received Event + Last course syncronized: Last course syncronized + Most Active Courses Per Day: Most Active Courses Per Day + Number Of Attempts To Find Correct Answer: Number Of Attempts To Find Correct Answer + Number Of Students: Number Of Students + Number Of Viewers / Views: Number Of Viewers / Views + Number of Users / Uses: Number of Users / Uses + Number of Views / Viewers: Number of Views / Viewers + Number of users: Number of users + Operator Dashboard: Operator Dashboard + Organizations: Organizations + Percentage Grade: Percentage Grade + Posts per user: Posts per user + Problem Engagement: Problem Engagement + Problem Performance: Problem Performance + Question Title: Question Title + Responses: Responses + Responses Per Problem: Responses Per Problem + Seconds Of Video: Seconds Of Video + Slowest ClickHouse Queries: Slowest ClickHouse Queries + Students: Students + ? The cumulative total of unique enrolled learners based on their enrollment state + at the end of each day. If a learner was enrolled previously, but has left the + course since, they are not counted as of the date they left. If they re-enroll + in the course they will be counted again. + : The cumulative total of unique enrolled learners based on their enrollment state + at the end of each day. If a learner was enrolled previously, but has left the + course since, they are not counted as of the date they left. If they re-enroll + in the course they will be counted again. + ? The current count of active enrollments by their most recent enrollment type, + so if a learner upgraded from Audit to Verified they will only be counted once + as Verified. Learners who have un-enrolled in the course are not counted. + : The current count of active enrollments by their most recent enrollment type, + so if a learner upgraded from Audit to Verified they will only be counted once + as Verified. Learners who have un-enrolled in the course are not counted. + The distribution of grades for a course, out of 100%. Grades are grouped in ranges of 10%.: The + distribution of grades for a course, out of 100%. Grades are grouped in ranges + of 10%. + ? The number of students who have responded to a question, and whether they have + ever answered correctly. Students can answer some questions multiple times, but + this chart counts each student only once per question. + : The number of students who have responded to a question, and whether they have + ever answered correctly. Students can answer some questions multiple times, but + this chart counts each student only once per question. + ? This chart displays counts of the number of times hints (including displaying + the answer itself) were displayed for each learner who eventually answered the + problem correctly. If the problem had no hint or answer display configured this + chart will group everyone into "0". + : This chart displays counts of the number of times hints (including displaying + the answer itself) were displayed for each learner who eventually answered the + problem correctly. If the problem had no hint or answer display configured this + chart will group everyone into "0". + ? This chart helps understand how many learners are using the video's transcripts + or closed captions. If the video has not transcripts or captions the chart will + be empty. + : This chart helps understand how many learners are using the video's transcripts + or closed captions. If the video has not transcripts or captions the chart will + be empty. + ? This chart shows how many unique learners have watched each video, and how many + repeat views each has gotten. If a video has never been played it will not appear + in this chart. + : This chart shows how many unique learners have watched each video, and how many + repeat views each has gotten. If a video has never been played it will not appear + in this chart. + ? This chart shows how often an answer or combination of answers (for multi-select) + is selected by learners. Some problems allow learners to submit a response more + than once, this chart will include all of the responses in that case. + : This chart shows how often an answer or combination of answers (for multi-select) + is selected by learners. Some problems allow learners to submit a response more + than once, this chart will include all of the responses in that case. + ? This chart shows the number of attempts that students needed to make before getting + the problem's answer correct. This only counts students who eventually answered + correctly. + : This chart shows the number of attempts that students needed to make before getting + the problem's answer correct. This only counts students who eventually answered + correctly. + ? 'This chart shows the number of students who scored within a certain percentage + of points for this problem. For problems that are pass/fail it will only show + the lowest and highest percentage ranges. ' + : 'This chart shows the number of students who scored within a certain percentage + of points for this problem. For problems that are pass/fail it will only show + the lowest and highest percentage ranges. ' + ? This chart shows which parts of a video are most watched, and which are most re-watched. + Each segment represents 5 seconds of the video. + : This chart shows which parts of a video are most watched, and which are most re-watched. + Each segment represents 5 seconds of the video. + Total Organizations: Total Organizations + Transcripts / Captions Per Video: Transcripts / Captions Per Video + Unique actors: Unique actors + Video Engagement: Video Engagement + Video Performance: Video Performance + Video Title: Video Title + Watched Video Segments: Watched Video Segments + Watches Per Video: Watches Per Video + xAPI Events Over Time: xAPI Events Over Time diff --git a/tutoraspects/templates/aspects/apps/superset/localization/locale.yaml b/tutoraspects/templates/aspects/apps/superset/localization/locale.yaml index 853c83559..8fa77986d 100644 --- a/tutoraspects/templates/aspects/apps/superset/localization/locale.yaml +++ b/tutoraspects/templates/aspects/apps/superset/localization/locale.yaml @@ -1,5 +1,11 @@ --- de: + ? "# Instance information Aspects: 0.49.0\n\n- **Vector Version**: timberio/vector:0.30.0-alpine\ + \ \n\n\n- **Clickhouse Version**: clickhouse/clickhouse-server:23.8 \n\n\n- **Ralph\ + \ Version**: fundocker/ralph:3.9.0 \n\n\n- **Superset Version**: edunext/aspects-superset:0.49.0\ + \ \n\n\n\n## Dependencies:\n\n- **Clickhouse Event Sink Version**: 0.2.2 \n\n\n\ + \n- **Event Routing Backends Version**: 6.2.0 \n\n\n" + : '' ? '### Course Enrollment @@ -152,6 +158,12 @@ de: Watches Per Video: '' xAPI Events Over Time: '' es: + ? "# Instance information Aspects: 0.49.0\n\n- **Vector Version**: timberio/vector:0.30.0-alpine\ + \ \n\n\n- **Clickhouse Version**: clickhouse/clickhouse-server:23.8 \n\n\n- **Ralph\ + \ Version**: fundocker/ralph:3.9.0 \n\n\n- **Superset Version**: edunext/aspects-superset:0.49.0\ + \ \n\n\n\n## Dependencies:\n\n- **Clickhouse Event Sink Version**: 0.2.2 \n\n\n\ + \n- **Event Routing Backends Version**: 6.2.0 \n\n\n" + : '' ? '### Course Enrollment @@ -209,7 +221,7 @@ es: unenrollment will be counted. : '' Active Users Per Organization: '' - Actor IDs over time: IDs de actores a lo largo del tiempo + Actor IDs over time: '' Answers Chosen: '' Coming Soon: '' Course Enrollment: '' @@ -217,7 +229,7 @@ es: Course Grade Distribution: '' Course Registrations Over Time: '' Course Success: '' - Courses: Cursos + Courses: '' Courses Per Organization: '' Currently Enrolled Learners Per Day: '' Distribution Of Attempts: '' @@ -228,13 +240,13 @@ es: Enrollments: '' Enrollments By Enrollment Mode: '' Enrollments By Type: '' - Event type: Tipo de evento - Events per course: Eventos por curso + Event type: '' + Events per course: '' Hints / Answer Displayed Before Correct Answer Chosen: '' Instance Health: '' Instructor Dashboard: '' Last Received Event: '' - Last course syncronized: "\xDAltimo curso sincronizado" + Last course syncronized: '' Learner Engagement: '' Most Active Courses Per Day: '' Number Of Attempts To Find Correct Answer: '' @@ -242,8 +254,8 @@ es: Number Of Viewers / Views: '' Number of Users / Uses: '' Number of Views / Viewers: '' - Operator Dashboard: Panel de Operador - Organizations: Organizaciones + Operator Dashboard: '' + Organizations: '' Percentage Grade: '' Problem Engagement: '' Problem Performance: '' @@ -296,7 +308,7 @@ es: : '' Total Organizations: '' Transcripts / Captions Per Video: '' - Unique actors: "Actores \xFAnicos" + Unique actors: '' Video Engagement: '' Video Performance: '' Video Title: '' @@ -304,6 +316,12 @@ es: Watches Per Video: '' xAPI Events Over Time: '' fr: + ? "# Instance information Aspects: 0.49.0\n\n- **Vector Version**: timberio/vector:0.30.0-alpine\ + \ \n\n\n- **Clickhouse Version**: clickhouse/clickhouse-server:23.8 \n\n\n- **Ralph\ + \ Version**: fundocker/ralph:3.9.0 \n\n\n- **Superset Version**: edunext/aspects-superset:0.49.0\ + \ \n\n\n\n## Dependencies:\n\n- **Clickhouse Event Sink Version**: 0.2.2 \n\n\n\ + \n- **Event Routing Backends Version**: 6.2.0 \n\n\n" + : '' ? '### Course Enrollment @@ -456,6 +474,12 @@ fr: Watches Per Video: '' xAPI Events Over Time: '' it: + ? "# Instance information Aspects: 0.49.0\n\n- **Vector Version**: timberio/vector:0.30.0-alpine\ + \ \n\n\n- **Clickhouse Version**: clickhouse/clickhouse-server:23.8 \n\n\n- **Ralph\ + \ Version**: fundocker/ralph:3.9.0 \n\n\n- **Superset Version**: edunext/aspects-superset:0.49.0\ + \ \n\n\n\n## Dependencies:\n\n- **Clickhouse Event Sink Version**: 0.2.2 \n\n\n\ + \n- **Event Routing Backends Version**: 6.2.0 \n\n\n" + : '' ? '### Course Enrollment @@ -608,6 +632,12 @@ it: Watches Per Video: '' xAPI Events Over Time: '' ja: + ? "# Instance information Aspects: 0.49.0\n\n- **Vector Version**: timberio/vector:0.30.0-alpine\ + \ \n\n\n- **Clickhouse Version**: clickhouse/clickhouse-server:23.8 \n\n\n- **Ralph\ + \ Version**: fundocker/ralph:3.9.0 \n\n\n- **Superset Version**: edunext/aspects-superset:0.49.0\ + \ \n\n\n\n## Dependencies:\n\n- **Clickhouse Event Sink Version**: 0.2.2 \n\n\n\ + \n- **Event Routing Backends Version**: 6.2.0 \n\n\n" + : '' ? '### Course Enrollment @@ -760,6 +790,12 @@ ja: Watches Per Video: '' xAPI Events Over Time: '' ko: + ? "# Instance information Aspects: 0.49.0\n\n- **Vector Version**: timberio/vector:0.30.0-alpine\ + \ \n\n\n- **Clickhouse Version**: clickhouse/clickhouse-server:23.8 \n\n\n- **Ralph\ + \ Version**: fundocker/ralph:3.9.0 \n\n\n- **Superset Version**: edunext/aspects-superset:0.49.0\ + \ \n\n\n\n## Dependencies:\n\n- **Clickhouse Event Sink Version**: 0.2.2 \n\n\n\ + \n- **Event Routing Backends Version**: 6.2.0 \n\n\n" + : '' ? '### Course Enrollment @@ -912,6 +948,12 @@ ko: Watches Per Video: '' xAPI Events Over Time: '' nl: + ? "# Instance information Aspects: 0.49.0\n\n- **Vector Version**: timberio/vector:0.30.0-alpine\ + \ \n\n\n- **Clickhouse Version**: clickhouse/clickhouse-server:23.8 \n\n\n- **Ralph\ + \ Version**: fundocker/ralph:3.9.0 \n\n\n- **Superset Version**: edunext/aspects-superset:0.49.0\ + \ \n\n\n\n## Dependencies:\n\n- **Clickhouse Event Sink Version**: 0.2.2 \n\n\n\ + \n- **Event Routing Backends Version**: 6.2.0 \n\n\n" + : '' ? '### Course Enrollment @@ -1064,6 +1106,12 @@ nl: Watches Per Video: '' xAPI Events Over Time: '' pt: + ? "# Instance information Aspects: 0.49.0\n\n- **Vector Version**: timberio/vector:0.30.0-alpine\ + \ \n\n\n- **Clickhouse Version**: clickhouse/clickhouse-server:23.8 \n\n\n- **Ralph\ + \ Version**: fundocker/ralph:3.9.0 \n\n\n- **Superset Version**: edunext/aspects-superset:0.49.0\ + \ \n\n\n\n## Dependencies:\n\n- **Clickhouse Event Sink Version**: 0.2.2 \n\n\n\ + \n- **Event Routing Backends Version**: 6.2.0 \n\n\n" + : '' ? '### Course Enrollment @@ -1216,6 +1264,12 @@ pt: Watches Per Video: '' xAPI Events Over Time: '' ru: + ? "# Instance information Aspects: 0.49.0\n\n- **Vector Version**: timberio/vector:0.30.0-alpine\ + \ \n\n\n- **Clickhouse Version**: clickhouse/clickhouse-server:23.8 \n\n\n- **Ralph\ + \ Version**: fundocker/ralph:3.9.0 \n\n\n- **Superset Version**: edunext/aspects-superset:0.49.0\ + \ \n\n\n\n## Dependencies:\n\n- **Clickhouse Event Sink Version**: 0.2.2 \n\n\n\ + \n- **Event Routing Backends Version**: 6.2.0 \n\n\n" + : '' ? '### Course Enrollment @@ -1368,6 +1422,12 @@ ru: Watches Per Video: '' xAPI Events Over Time: '' sk: + ? "# Instance information Aspects: 0.49.0\n\n- **Vector Version**: timberio/vector:0.30.0-alpine\ + \ \n\n\n- **Clickhouse Version**: clickhouse/clickhouse-server:23.8 \n\n\n- **Ralph\ + \ Version**: fundocker/ralph:3.9.0 \n\n\n- **Superset Version**: edunext/aspects-superset:0.49.0\ + \ \n\n\n\n## Dependencies:\n\n- **Clickhouse Event Sink Version**: 0.2.2 \n\n\n\ + \n- **Event Routing Backends Version**: 6.2.0 \n\n\n" + : '' ? '### Course Enrollment @@ -1520,6 +1580,12 @@ sk: Watches Per Video: '' xAPI Events Over Time: '' sl: + ? "# Instance information Aspects: 0.49.0\n\n- **Vector Version**: timberio/vector:0.30.0-alpine\ + \ \n\n\n- **Clickhouse Version**: clickhouse/clickhouse-server:23.8 \n\n\n- **Ralph\ + \ Version**: fundocker/ralph:3.9.0 \n\n\n- **Superset Version**: edunext/aspects-superset:0.49.0\ + \ \n\n\n\n## Dependencies:\n\n- **Clickhouse Event Sink Version**: 0.2.2 \n\n\n\ + \n- **Event Routing Backends Version**: 6.2.0 \n\n\n" + : '' ? '### Course Enrollment @@ -1672,6 +1738,12 @@ sl: Watches Per Video: '' xAPI Events Over Time: '' zh: + ? "# Instance information Aspects: 0.49.0\n\n- **Vector Version**: timberio/vector:0.30.0-alpine\ + \ \n\n\n- **Clickhouse Version**: clickhouse/clickhouse-server:23.8 \n\n\n- **Ralph\ + \ Version**: fundocker/ralph:3.9.0 \n\n\n- **Superset Version**: edunext/aspects-superset:0.49.0\ + \ \n\n\n\n## Dependencies:\n\n- **Clickhouse Event Sink Version**: 0.2.2 \n\n\n\ + \n- **Event Routing Backends Version**: 6.2.0 \n\n\n" + : '' ? '### Course Enrollment