Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
docs
.github
*.ipynb
Dockerfile
.dockerignore
96 changes: 96 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Docker

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

on:
push:
branches: [ "master" ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: [ "master" ]

env:
# Use docker.io for Docker Hub if empty
REGISTRY: docker.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}


jobs:
build:

runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

# Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0
with:
cosign-release: 'v2.2.4'

# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0

# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

# Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
# transparency data even for private images, pass --force to cosign below.
# https://github.com/sigstore/cosign
- name: Sign the published Docker image
if: ${{ github.event_name != 'pull_request' }}
env:
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
TAGS: ${{ steps.meta.outputs.tags }}
DIGEST: ${{ steps.build-and-push.outputs.digest }}
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
.idea/
.idea/
.venv/
__pycache__
client_secrets.json
*.json
27 changes: 27 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use an official Python runtime as a parent image
FROM python:3.9-slim

# Install ca-certificates and update them
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates && \
update-ca-certificates && \
apt-get clean && rm -rf /var/lib/apt/lists/*

# Set the working directory
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# Make port 8050 available to the world outside this container
EXPOSE 8050

# Define environment variable
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Run app.py when the container launches
CMD ["python", "TMonSimvaApp.py"]
102 changes: 102 additions & 0 deletions LoadProcessStatements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from datetime import datetime
import json
import pandas as pd
import sys
import base64
from dataclasses import dataclass
import traceback

@dataclass
class Progress:
"""Fools load_from_string, keeps track of progress."""
value: float

#
# fileBrowserAndUploadButtonToLoadProcessStatements.ipynb
#

def is_json_and_not_list(str):
try:
return not isinstance(json.loads(str), list)
except Exception as e:
return False

def log(target, o_str):
if type(target) is None:
pass
elif type(target) is list:
target.append(o_str)
else:
with target.output:
print(o_str)

#
# Loads either JSON-statement-per-line or JSON-array-of-statements from a str
# Updates progress in progress by calling progress.value from 0.0 to 1.0 (=finished)
# err_output & info_output can be either
# None (= no output),
# a list (= ouput strings get appended), or
# an object o where with o.output: print() is valid
#
# Callback to update progress periodically
def load_from_string(str, xapiData, info_output, err_output):
progress = Progress(0)
total=0
count=0
try:
start_time = datetime.now()
if is_json_and_not_list(str.partition('\n')[0]):
total=len(str.splitlines())
log(info_output, f"... 1st line is valid JSON; interpreting as one-statement-per-line ({total} statement(s))")
# 1st line is well-formed json, and not json list of statements; assume 1-statement-per-line
statements=str.splitlines()
for statement in statements:
s=json.loads(statement)
progress.value=count/total
count+=1
processxapisgdata(s, xapiData)
else:
log(info_output, "... interpreting as statement-array")
# attempt to process all of it as a single JSON document
statements = json.loads(str)
total = len(statements)
log(info_output, f"... parsed correctly as json array, total statements = {total}")
for s in statements:
progress.value=count/total
count+=1
processxapisgdata(s, xapiData)
except Exception as e:
log(err_output,
f"ERROR loading at line/statement {count}/{total}: {e}\n"
f"Full error:\n~~~\n{''.join(traceback.format_exception(e))}\n~~~\n"
f"File must contain EITHER 1 statement (in JSON) per line, or be a well-formed JSON of statements. Please select another file.")
return e
progress.value=1.0
log(info_output, f"... processed {count}/{total} statement(s) in {datetime.now() - start_time}. Displaying visualizations ...")

def load_players_info_from_file(file, xapiData, out, err):
log(out, f"{file}")
with open(file, encoding="utf-8") as f:
str = f.read()
load_from_string(str, xapiData, out, err)
print(f"Info log ({len(out)} lines):\n" + "\n".join(out))
if len(err) > 0:
print(f"ERRORS FOUND ({len(err)} lines):\n" + "\n".join(err))
sys.exit(-1)

def load_players_info_from_uploaded_content(filecontent, filename, xapiData, out, err):
log(out, f"{filename}")
content_type, content_string = filecontent.split(',')
decoded = base64.b64decode(content_string).decode('utf-8')
load_from_string(decoded, xapiData, out, err)

def load_players_info_from_content(filecontent, filename, xapiData, out, err):
log(out, f"{filename}")
decoded = filecontent.decode('utf-8')
load_from_string(decoded, xapiData, out, err)

def processxapisgdata(statement, xapiData):
# Extract the `id` from the first category object
if "context" in statement and "contextActivities" in statement["context"] and "category" in statement["context"]["contextActivities"] and len(statement["context"]["contextActivities"]["category"]) > 0:
statement["context"]["contextActivities"]["category"] = statement["context"]["contextActivities"]["category"][0]["id"]
xapiData.append(statement)
Loading