Commit 1b7ffc26 authored by Konstantin Schulz's avatar Konstantin Schulz

the backend now serves a exercise that is always available (for testing and monitoring)

parent ed685f27
Pipeline #13324 passed with stages
in 2 minutes and 41 seconds
......@@ -28,6 +28,8 @@ def adjust_solutions(exercise_data: ExerciseData, exercise_type: str, solutions:
def get(eid: str) -> Union[Response, ConnexionResponse]:
""" The GET method for the Exercise REST API.
It retrieves an exercise from the database that has the specified exercise ID. """
exercise: TExercise = DatabaseService.query(Exercise, filter_by=dict(eid=eid), first=True)
if not exercise:
return connexion.problem(404, Config.ERROR_TITLE_NOT_FOUND, Config.ERROR_MESSAGE_EXERCISE_NOT_FOUND)
......@@ -114,6 +116,8 @@ def map_exercise_data_to_database(exercise_data: ExerciseData, exercise_type: st
def post(exercise_data: dict) -> Union[Response, ConnexionResponse]:
""" The POST method for the Exercise REST API.
It creates a new exercise from the given data and stores it in the database. """
ef: ExerciseForm = ExerciseForm.from_dict(exercise_data)
ef.urn = ef.urn if ef.urn else ""
exercise_type: ExerciseType = ExerciseType(ef.type)
......
......@@ -3,7 +3,6 @@ from typing import List, Set
import conllu
from conllu import TokenList
from mcserver.app import db
from mcserver.app.models import Language, VocabularyCorpus, ResourceType
from mcserver.app.services import NetworkService, FileService, DatabaseService
from mcserver.models_auto import Exercise, UpdateInfo
......@@ -15,7 +14,7 @@ def get(lang: str, frequency_upper_bound: int, last_update_time: int, vocabulary
vocabulary_set: Set[str]
ui_exercises: UpdateInfo = DatabaseService.query(
UpdateInfo, filter_by=dict(resource_type=ResourceType.exercise_list.name), first=True)
if ui_exercises.last_modified_time < last_update_time / 1000:
if ui_exercises and ui_exercises.last_modified_time < last_update_time / 1000:
return NetworkService.make_json_response([])
try:
vc: VocabularyCorpus = VocabularyCorpus[vocabulary]
......
......@@ -11,6 +11,7 @@ from mcserver.app import db
from mcserver.app.models import Language, ExerciseType, Solution, MimeType, FileType
from mcserver.app.services import TextService, NetworkService, DatabaseService
from mcserver.models_auto import Exercise
from mocks import Mocks
from openapi.openapi_server.models import H5PForm
......@@ -28,6 +29,8 @@ def get(eid: str, lang: str, solution_indices: List[int]) -> Union[Response, Con
""" The GET method for the H5P REST API. It provides JSON templates for client-side H5P exercise layouts. """
language: Language = determine_language(lang)
exercise: Exercise = DatabaseService.query(Exercise, filter_by=dict(eid=eid), first=True)
if eid == Config.EXERCISE_ID_TEST:
exercise = Mocks.exercise
if not exercise:
return connexion.problem(404, Config.ERROR_TITLE_NOT_FOUND, Config.ERROR_MESSAGE_EXERCISE_NOT_FOUND)
text_field_content: str = get_text_field_content(exercise, solution_indices)
......
......@@ -92,6 +92,7 @@ class Config(object):
ERROR_TITLE_NOT_FOUND = "Not found"
ERROR_TITLE_SERVICE_UNAVAILABLE = "Service Unavailable"
ERROR_TITLE_UNPROCESSABLE_ENTITY = "Unprocessable Entity"
EXERCISE_ID_TEST = "test_exercise"
FAVICON_FILE_NAME = "favicon.ico"
FLASK_MIGRATE = "migrate"
GRAPHANNIS_DEPENDENCY_LINK = "dep"
......
......@@ -20,7 +20,7 @@ from mcserver import Config, TestingConfig
from mcserver.app import db, shutdown_session
from mcserver.app.models import Phenomenon, PartOfSpeech, CitationLevel, ExerciseData, GraphData, \
LinkMC, NodeMC, Language, Dependency, Case, AnnisResponse, Solution, TextPart, Citation, ExerciseMC, CorpusMC, \
SolutionElement, ReferenceableText
SolutionElement, ReferenceableText, ExerciseType
from mcserver.app.services import AnnotationService, CustomCorpusService, TextService, DatabaseService
from mcserver.models_auto import Corpus, Exercise, UpdateInfo
......@@ -680,10 +680,14 @@ class Mocks:
cts_passage_xml_2_levels: str = '<GetPassage xmlns:tei="http://www.tei-c.org/ns/1.0" xmlns="http://chs.harvard.edu/xmlns/cts"><request><requestName>GetPassage</requestName><requestUrn>urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1-1.2</requestUrn></request><reply><urn>urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1-1.2</urn><passage><TEI xmlns="http://www.tei-c.org/ns/1.0"><text><body><div type="edition" xml:lang="lat" n="urn:cts:latinLit:phi0448.phi001.perseus-lat2"><div n="1" type="textpart" subtype="book"><div type="textpart" subtype="section" n="1"><p>Gallia est omnis divisa in partes tres, quarum unam incolunt Belgae, aliam Aquitani, tertiam qui ipsorum lingua Celtae, nostra Galli appellantur.</p></div></div></div></body></text></TEI></passage></reply></GetPassage>'
cts_reff_xml: str = '<GetValidReff xmlns:tei="http://www.tei-c.org/ns/1.0" xmlns="http://chs.harvard.edu/xmlns/cts"><request><requestName>GetValidReff</requestName><requestUrn>urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1</requestUrn><requestLevel>3</requestLevel></request><reply><reff><urn>urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1.1</urn><urn>urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1.2</urn><urn>urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1.3</urn><urn>urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1.4</urn><urn>urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1.5</urn><urn>urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1.6</urn><urn>urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1.7</urn></reff></reply></GetValidReff>'
exercise: Exercise = ExerciseMC.from_dict(
eid="test", last_access_time=datetime.utcnow().timestamp(), exercise_type='ddwtos',
search_values=f'["{Phenomenon.FEATS}={Case.accusative.name}", "{Phenomenon.DEPENDENCY}={Dependency.object.name}", "{Phenomenon.LEMMA}=bellum", "{Phenomenon.DEPENDENCY}={Dependency.root.name}"]',
language=Language.English.value,
conll="# newdoc id = /var/folders/30/yqnv6lz56r14dqhpw18knn2r0000gp/T/tmp7qn86au9\n# newpar\n# sent_id = 1\n# text = Caesar fortis est.\n1\tCaesar\tCaeso\tVERB\tC1|grn1|casA|gen1|stAN\tCase=Nom|Degree=Pos|Gender=Masc|Number=Sing\t2\tcsubj\t_\t_\n2\tfortis\tfortis\tADJ\tC1|grn1|casA|gen1|stAN\tCase=Nom|Degree=Pos|Gender=Masc|Number=Sing\t0\troot\t_\t_\n3\test\tsum\tAUX\tN3|modA|tem1|gen6|stAV\tMood=Ind|Number=Sing|Person=3|Tense=Pres|VerbForm=Fin|Voice=Act\t2\tcop\t_\tSpaceAfter=No\n4\t.\t.\tPUNCT\tPunc\t_\t2\tpunct\t_\t_\n\n# sent_id = 2\n# text = Galli moriuntur.\n1\tGalli\tGallus\tPRON\tF1|grn1|casJ|gen1|stPD\tCase=Nom|Degree=Pos|Gender=Masc|Number=Plur|PronType=Dem\t2\tnsubj:pass\t_\t_\n2\tmoriuntur\tmorior\tVERB\tL3|modJ|tem1|gen9|stAV\tMood=Ind|Number=Plur|Person=3|Tense=Pres|VerbForm=Fin|Voice=Pass\t0\troot\t_\tSpaceAfter=No\n3\t.\t.\tPUNCT\tPunc\t_\t2\tpunct\t_\tSpacesAfter=\\n\n\n",
eid="test", exercise_type=ExerciseType.cloze.value, exercise_type_translation="Cloze",
instructions="Assign the words from the pool to the correct gaps!",
language=Language.English.value,
last_access_time=datetime.utcnow().timestamp(),
search_values=f'["{Phenomenon.FEATS}={Case.accusative.name}", "{Phenomenon.DEPENDENCY}=' +
f'{Dependency.object.name}", "{Phenomenon.LEMMA}=bellum", "{Phenomenon.DEPENDENCY}=' +
f'{Dependency.root.name}"]',
solutions=json.dumps([
Solution(target=SolutionElement(
sentence_id=1, token_id=1, content="praecepturus",
......
......@@ -181,8 +181,8 @@ class McTestCase(unittest.TestCase):
def test_api_exercise_get(self):
""" Retrieves an existing exercise by its exercise ID. """
db.session.query(Exercise).delete()
response: Response = Mocks.app_dict[self.class_name].client.get(Config.SERVER_URI_EXERCISE,
query_string=dict(eid=""))
response: Response = Mocks.app_dict[self.class_name].client.get(
Config.SERVER_URI_EXERCISE, query_string=dict(eid=""))
self.assertEqual(response.status_code, 404)
old_urn: str = Mocks.exercise.urn
Mocks.exercise.urn = ""
......@@ -197,8 +197,7 @@ class McTestCase(unittest.TestCase):
DatabaseService.commit()
response = Mocks.app_dict[self.class_name].client.get(Config.SERVER_URI_EXERCISE,
query_string=dict(eid=Mocks.exercise.eid))
graph_dict: dict = json.loads(response.get_data(as_text=True))
ar: AnnisResponse = AnnisResponse.from_dict(graph_dict)
ar: AnnisResponse = AnnisResponse.from_dict(json.loads(response.get_data(as_text=True)))
self.assertEqual(len(ar.graph_data.nodes), 52)
db.session.query(Exercise).delete()
session.make_transient(Mocks.exercise)
......@@ -322,8 +321,11 @@ class McTestCase(unittest.TestCase):
def test_api_h5p_get(self):
""" Requests a H5P JSON file for a given exercise. """
response: Response = Mocks.app_dict[self.class_name].client.get(
TestingConfig.SERVER_URI_H5P, query_string=dict(eid=Config.EXERCISE_ID_TEST, lang=Language.English.value))
self.assertIn(Mocks.h5p_json_cloze[1:-1], response.get_data(as_text=True))
args: dict = dict(eid=Mocks.exercise.eid, lang=Language.English.value, solution_indices=[0])
response: Response = Mocks.app_dict[self.class_name].client.get(TestingConfig.SERVER_URI_H5P, query_string=args)
response = Mocks.app_dict[self.class_name].client.get(TestingConfig.SERVER_URI_H5P, query_string=args)
self.assertEqual(response.status_code, 404)
db.session.add(Mocks.exercise)
DatabaseService.commit()
......@@ -356,7 +358,7 @@ class McTestCase(unittest.TestCase):
DatabaseService.commit()
response = Mocks.app_dict[self.class_name].client.post(
TestingConfig.SERVER_URI_H5P, headers=Mocks.headers_form_data, data=hf.to_dict())
self.assertEqual(len(response.get_data()), 1940089)
self.assertEqual(len(response.get_data()), 1940145)
with patch.object(mcserver.app.api.h5pAPI, "get_text_field_content", return_value=""):
response = Mocks.app_dict[self.class_name].client.post(
TestingConfig.SERVER_URI_H5P, headers=Mocks.headers_form_data, data=hf.to_dict())
......@@ -1010,10 +1012,12 @@ class CommonTestCase(unittest.TestCase):
Mocks.exercise.exercise_type = ExerciseType.matching.value
solutions: List[Solution] = [Solution.from_dict(x) for x in json.loads(Mocks.exercise.solutions)]
result: str = FileService.get_pdf_html_string(Mocks.exercise, Mocks.annotations, FileType.PDF, solutions)
self.assertEqual(result, '<br><p>: </p><p><table><tr><td>praecepturus</td><td>Caesar</td></tr></table></p>')
expected_result: str = '<br><p>Cloze: Assign the words from the pool to the correct gaps!</p><p><table><tr><td>praecepturus</td><td>Caesar</td></tr></table></p>'
self.assertEqual(result, expected_result)
Mocks.exercise.exercise_type = ExerciseType.markWords.value
result = FileService.get_pdf_html_string(Mocks.exercise, Mocks.annotations, FileType.PDF, solutions)
self.assertEqual(result, '<p>: </p><p>Caesar et Galli fortes sunt.</p><br><br>')
expected_result = '<p>Cloze: Assign the words from the pool to the correct gaps!</p><p>Caesar et Galli fortes sunt.</p><br><br>'
self.assertEqual(result, expected_result)
Mocks.exercise.exercise_type = ExerciseType.cloze.value
def test_get_raw_text(self):
......@@ -1129,13 +1133,13 @@ class CommonTestCase(unittest.TestCase):
file_path: str = os.path.join(Config.TMP_DIRECTORY, "make_docx_file.docx")
solutions: List[Solution] = [Solution.from_dict(x) for x in json.loads(Mocks.exercise.solutions)]
FileService.make_docx_file(Mocks.exercise, file_path, Mocks.annotations, FileType.DOCX, solutions)
self.assertEqual(os.path.getsize(file_path), 36611)
self.assertEqual(os.path.getsize(file_path), 36647)
Mocks.exercise.exercise_type = ExerciseType.markWords.value
FileService.make_docx_file(Mocks.exercise, file_path, Mocks.annotations, FileType.DOCX, solutions)
self.assertEqual(os.path.getsize(file_path), 36599)
self.assertEqual(os.path.getsize(file_path), 36637)
Mocks.exercise.exercise_type = ExerciseType.matching.value
FileService.make_docx_file(Mocks.exercise, file_path, Mocks.annotations, FileType.DOCX, solutions)
self.assertEqual(os.path.getsize(file_path), 36714)
self.assertEqual(os.path.getsize(file_path), 36757)
Mocks.exercise.exercise_type = ExerciseType.cloze.value
os.remove(file_path)
......
......@@ -513,7 +513,7 @@ export class CorpusService {
this.currentCorpusCache = state.mostRecentSetup.currentCorpus;
this.currentTextRangeCache = state.mostRecentSetup.currentTextRange;
this.isTextRangeCorrect = true;
if (this.annisResponse && this.annisResponse.graph_data.nodes.length) {
if (this.annisResponse && this.annisResponse.graph_data && this.annisResponse.graph_data.nodes.length) {
this.processAnnisResponse(this.annisResponse, false);
return resolve();
} else if (this.currentText) {
......@@ -521,6 +521,7 @@ export class CorpusService {
return resolve();
} else {
const saveToCache: boolean = !state.mostRecentSetup.annisResponse ||
!state.mostRecentSetup.annisResponse.graph_data ||
!state.mostRecentSetup.annisResponse.graph_data.nodes.length;
this.getText(saveToCache).then(() => {
return resolve();
......
......@@ -2,7 +2,6 @@ import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {ExerciseService} from '../exercise.service';
import {ExerciseParams} from '../models/exerciseParams';
import {rejects} from 'assert';
@Component({
selector: 'app-embed',
......
......@@ -205,7 +205,7 @@ export class ExerciseService {
// this will be called via GET request from the h5p standalone javascript library
const url: string = `${configMC.backendBaseUrl}${configMC.backendApiH5pPath}` +
`?eid=${eid}&lang=${this.translateService.currentLang}`;
this.storage.set(configMC.localStorageKeyH5P, url).then();
this.setH5Purl(url);
const exerciseTypePath: string = this.corpusService.exercise.type === ExerciseType.markWords ?
ExerciseTypePath.MarkWords : ExerciseTypePath.DragText;
return this.initH5P(exerciseTypePath);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment