diff --git a/mc_backend/mcserver/app/__init__.py b/mc_backend/mcserver/app/__init__.py index ee7b812ed74d091ce2ebe9e166353c35baf351e8..f69176db30c50b372968a3236ba0da299c575f19 100644 --- a/mc_backend/mcserver/app/__init__.py +++ b/mc_backend/mcserver/app/__init__.py @@ -48,7 +48,9 @@ def create_app(cfg: Type[Config] = Config) -> Flask: # use local postgres database for migrations if len(sys.argv) > 2 and sys.argv[2] == Config.FLASK_MIGRATE: cfg.SQLALCHEMY_DATABASE_URI = Config.DATABASE_URL_LOCAL - Config.GRAPH_DATABASE_DIR = os.path.join(Config.GRAPH_DATABASE_DIR_BASE, str(uuid.uuid4())) + guid: str = str(uuid.uuid4()) + Config.GRAPH_DATABASE_DIR = os.path.join(Config.GRAPH_DATABASE_DIR_BASE, guid) + Config.GRAPHANNIS_LOG_PATH = os.path.join(Config.LOGS_DIRECTORY, f"graphannis_{guid}.log") Config.CORPUS_STORAGE_MANAGER = CorpusStorageManager(Config.GRAPH_DATABASE_DIR) app: Flask = init_app_common(cfg=cfg) from mcserver.app.services import bp as services_bp diff --git a/mc_backend/mcserver/app/logs/.gitignore b/mc_backend/mcserver/app/logs/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..67c04e47cd6c931505eefb86429e6f00dafa5a6a --- /dev/null +++ b/mc_backend/mcserver/app/logs/.gitignore @@ -0,0 +1,2 @@ +*.* +!.gitignore diff --git a/mc_backend/mcserver/app/services/corpusService.py b/mc_backend/mcserver/app/services/corpusService.py index c1030bd3ff76f6cc2f5fdbf6456ce1d61be8e1b2..264c2c379148579cdf1f75476cecf2ef00e5cbfd 100644 --- a/mc_backend/mcserver/app/services/corpusService.py +++ b/mc_backend/mcserver/app/services/corpusService.py @@ -305,8 +305,7 @@ class CorpusService: def init_graphannis_logging() -> None: """Initializes logging for the graphannis backend.""" err = ffi.new("AnnisErrorList **") - CAPI.annis_init_logging(os.path.join(os.getcwd(), Config.GRAPHANNIS_LOG_PATH).encode("utf-8"), CAPI.Info, - err) # Debug + CAPI.annis_init_logging(Config.GRAPHANNIS_LOG_PATH.encode("utf-8"), CAPI.Info, err) # Debug consume_errors(err) @staticmethod diff --git a/mc_backend/mcserver/config.py b/mc_backend/mcserver/config.py index f582e1855f5da750e7d5889d741b7f00a0923d61..bbc034557ca64069e90a5f6652ff24a9210be16f 100644 --- a/mc_backend/mcserver/config.py +++ b/mc_backend/mcserver/config.py @@ -22,17 +22,18 @@ class Config(object): GRAPH_DATABASE_DIR_BASE = os.path.join(os.sep, "tmp", "graphannis-data") GRAPH_DATABASE_DIR = GRAPH_DATABASE_DIR_BASE MC_SERVER_DIRECTORY = CURRENT_WORKING_DIRECTORY if \ - os.path.split(CURRENT_WORKING_DIRECTORY)[-1] == "mcserver" else os.path.join(CURRENT_WORKING_DIRECTORY, - "mcserver") + os.path.split(CURRENT_WORKING_DIRECTORY)[-1] == "mcserver" else os.path.join( + CURRENT_WORKING_DIRECTORY, "mcserver") SERVER_URI_BASE = "/mc/api/v1.0/" # dirty hack to get the app working either with the Gunicorn/Flask CLI or the PyCharm debugger - MC_SERVER_APP_DIRECTORY = os.path.join(MC_SERVER_DIRECTORY, "app") if os.path.split(MC_SERVER_DIRECTORY)[ - -1] == "mcserver" else MC_SERVER_DIRECTORY + MC_SERVER_APP_DIRECTORY = os.path.join(MC_SERVER_DIRECTORY, "app") if os.path.split( + MC_SERVER_DIRECTORY)[-1] == "mcserver" else MC_SERVER_DIRECTORY IS_DOCKER = os.environ.get("IS_THIS_A_DOCKER_CONTAINER", False) MC_FRONTEND_DIRECTORY = os.path.join(Path(MC_SERVER_DIRECTORY).parent.parent, "mc_frontend") MC_FRONTEND_SRC_DIRECTORY = os.path.join(MC_FRONTEND_DIRECTORY, "src") ASSETS_DIRECTORY = os.path.join(MC_SERVER_APP_DIRECTORY, "assets") FILES_DIRECTORY = os.path.join(MC_SERVER_APP_DIRECTORY, "files") + LOGS_DIRECTORY = os.path.join(MC_SERVER_APP_DIRECTORY, "logs") TMP_DIRECTORY = os.path.join(FILES_DIRECTORY, "tmp") TREEBANKS_PATH = os.path.join(ASSETS_DIRECTORY, "treebanks") TREEBANKS_PROIEL_PATH = os.path.join(TREEBANKS_PATH, "proiel") @@ -95,7 +96,7 @@ class Config(object): FAVICON_FILE_NAME = "favicon.ico" FLASK_MIGRATE = "migrate" GRAPHANNIS_DEPENDENCY_LINK = "dep" - GRAPHANNIS_LOG_PATH = os.path.join(os.getcwd(), "graphannis.log") + GRAPHANNIS_LOG_PATH = os.path.join(LOGS_DIRECTORY, "graphannis.log") H5P_DIRECTORY = "/home/mc/h5p" if IS_DOCKER else os.path.join(MC_FRONTEND_SRC_DIRECTORY, "assets", "h5p") # Windows: use 127.0.0.1 as host IP fallback HOST_IP_FALLBACK = "0.0.0.0" diff --git a/mc_backend/tests.py b/mc_backend/tests.py index bbbdf171b82075227d3f0391445da9e24544379e..95240c25a75aa859f9928fd6b77283569087ef80 100644 --- a/mc_backend/tests.py +++ b/mc_backend/tests.py @@ -523,9 +523,8 @@ class McTestCase(unittest.TestCase): def test_app_init(self): """Creates an MCserver app in testing mode.""" CorpusService.init_graphannis_logging() - log_path: str = os.path.join(os.getcwd(), Config.GRAPHANNIS_LOG_PATH) - self.assertTrue(os.path.exists(log_path)) - os.remove(log_path) + self.assertTrue(os.path.exists(Config.GRAPHANNIS_LOG_PATH)) + os.remove(Config.GRAPHANNIS_LOG_PATH) with patch.object(sys, 'argv', Mocks.test_args): app: Flask = mcserver.get_app() self.assertIsInstance(app, Flask) diff --git a/mc_frontend/src/app/exercise-list/exercise-list.page.spec.ts b/mc_frontend/src/app/exercise-list/exercise-list.page.spec.ts index b4526d7d8d6ec643d2d3fa1c1c8b7a11846ee281..0464e4448c028b9546964a865c336302e22b4939 100644 --- a/mc_frontend/src/app/exercise-list/exercise-list.page.spec.ts +++ b/mc_frontend/src/app/exercise-list/exercise-list.page.spec.ts @@ -107,7 +107,7 @@ describe('ExerciseListPage', () => { it('should show an exercise', (done) => { const requestSpy: Spy = spyOn(exerciseListPage.helperService, 'makeGetRequest').and.callFake(() => Promise.reject()); spyOn(exerciseListPage.helperService, 'goToPage').and.returnValue(Promise.resolve(true)); - exerciseListPage.showExercise(new ExerciseMC()).then(() => { + exerciseListPage.showExercise(new ExerciseMC({exercise_type: MoodleExerciseType.cloze.toString()})).then(() => { requestSpy.and.returnValue(Promise.resolve({})); exerciseListPage.showExercise(new ExerciseMC({exercise_type: MoodleExerciseType.markWords.toString()})).then(() => { expect(exerciseListPage.corpusService.exercise.type).toBe(ExerciseType.markWords); diff --git a/mc_frontend/src/app/exercise-list/exercise-list.page.ts b/mc_frontend/src/app/exercise-list/exercise-list.page.ts index 6287ce0f6f5bc061d295eed113251a94e50bd0af..4f041f3614b42329777ffe42ea2b6cdf5b506ff2 100644 --- a/mc_frontend/src/app/exercise-list/exercise-list.page.ts +++ b/mc_frontend/src/app/exercise-list/exercise-list.page.ts @@ -20,6 +20,7 @@ import {UpdateInfo} from '../models/updateInfo'; import {take} from 'rxjs/operators'; import {ApplicationState} from '../models/applicationState'; import {AnnisResponse, VocabularyMC} from '../../../openapi'; +import {ExerciseParams} from '../models/exerciseParams'; @Component({ selector: 'app-exercise-list', @@ -102,8 +103,8 @@ export class ExerciseListPage implements OnInit { this.helperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((exercises: ExerciseMC[]) => { updateInfo.exerciseList = new Date().getTime(); this.storage.set(configMC.localStorageKeyUpdateInfo, JSON.stringify(updateInfo)).then(); - const newExercise: ExerciseMC[] = exercises.length ? exercises : state.exerciseList; - state.exerciseList = this.availableExercises = this.exercises = newExercise; + const newExercises: ExerciseMC[] = exercises.length ? exercises : state.exerciseList; + state.exerciseList = this.availableExercises = this.exercises = newExercises; this.helperService.saveApplicationState(state).then(); this.processExercises(); return resolve(); @@ -146,19 +147,13 @@ export class ExerciseListPage implements OnInit { showExercise(exercise: ExerciseMC): Promise<void> { return new Promise<void>((resolve) => { - const url: string = configMC.backendBaseUrl + configMC.backendApiExercisePath; - const params: HttpParams = new HttpParams().set('eid', exercise.eid); - this.helperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((ar: AnnisResponse) => { - // save this exercise only locally in the CorpusService (not as MostRecentSetup in the HelperService) because - // users just want to have a quick look at it - this.corpusService.annisResponse = ar; - const met: MoodleExerciseType = MoodleExerciseType[exercise.exercise_type]; - this.corpusService.exercise.type = ExerciseType[met.toString()]; - this.helperService.goToPage(this.navCtrl, configMC.pageUrlPreview).then(); - return resolve(); - }, () => { - return resolve(); - }); + this.corpusService.previewAnnisResponse = { + exercise_id: exercise.eid, graph_data: {nodes: [], links: []}, solutions: [] + }; + const met: MoodleExerciseType = MoodleExerciseType[exercise.exercise_type]; + this.corpusService.exercise.type = ExerciseType[met.toString()]; + this.helperService.goToPage(this.navCtrl, configMC.pageUrlPreview).then(); + return resolve(); }); } diff --git a/mc_frontend/src/app/exercise.service.spec.ts b/mc_frontend/src/app/exercise.service.spec.ts index 1f2b6ec0750cd2fda8f05b25f34521f193c447c4..5711b5a9791e3da93483a03b3a85911a8c0e9b84 100644 --- a/mc_frontend/src/app/exercise.service.spec.ts +++ b/mc_frontend/src/app/exercise.service.spec.ts @@ -81,17 +81,17 @@ describe('ExerciseService', () => { expect(document.createElement).toHaveBeenCalledTimes(1); }); - it('should download a H5P exercise', (done) => { + it('should download an H5P exercise', (done) => { const postSpy: Spy = spyOn(exerciseService.helperService, 'makePostRequest').and.callFake(() => Promise.resolve(new Blob())); exerciseService.currentExerciseParams = {fileID: 'abc'}; - const localSpy: Spy = spyOn(exerciseService, 'downloadLocalH5PExercise').and.returnValue(Promise.resolve()); + spyOn(exerciseService, 'downloadLocalH5PExercise').and.returnValue(Promise.resolve()); exerciseService.downloadH5Pexercise().then(async () => { expect(postSpy).toHaveBeenCalledTimes(0); exerciseService.currentExerciseParams = {}; const downloadSpy: Spy = spyOn(exerciseService, 'downloadBlobAsFile'); exerciseService.corpusService.previewAnnisResponse = {exercise_id: ''}; exerciseService.corpusService.exercise.type = ExerciseType.markWords; - exerciseService.currentExerciseParams = {language: LanguageShortcut.English}; + exerciseService.currentExerciseParams = {language: LanguageShortcut.English, type: ExerciseTypePath.MarkWords}; exerciseService.downloadH5Pexercise().then(() => { expect(downloadSpy).toHaveBeenCalledTimes(1); exerciseService.corpusService.currentSolutions = @@ -168,7 +168,7 @@ describe('ExerciseService', () => { it('should get solution indices', () => { const solution: Solution = {target: {sentence_id: 1, token_id: 1}}; exerciseService.corpusService.previewAnnisResponse = {solutions: [solution]}; - const result: string = exerciseService.getSolutionIndices([solution]); + const result: string = exerciseService.getSolutionIndicesString([solution]); expect(result).toContain('0'); }); diff --git a/mc_frontend/src/app/exercise.service.ts b/mc_frontend/src/app/exercise.service.ts index 0c54ea7084e89f6fe75c1dcccb3cdb8ac305dbb5..07eff75d4b7f98519fd245b3b92668f29d4c73fb 100644 --- a/mc_frontend/src/app/exercise.service.ts +++ b/mc_frontend/src/app/exercise.service.ts @@ -59,6 +59,7 @@ export class ExerciseService { public embedSizeInputString = '.h5p-embed-size'; public embedTextAreaString = '.h5p-embed-code-container'; public excludeOOV = false; + public exerciseTypeToPathMap: { [exerciseType: string]: string }; public h5pContainerString = '.h5p-container'; public h5pIframeString = '.h5p-iframe'; public h5pPathLocal = 'assets/h5p'; @@ -77,6 +78,7 @@ export class ExerciseService { public corpusService: CorpusService, public storage: Storage, public translateService: TranslateService) { + this.initExerciseTypeToPathMap(); } areSolutionElementsEqual(se1: SolutionElement, se2: SolutionElement): boolean { @@ -122,8 +124,8 @@ export class ExerciseService { } return new Promise<void>((resolve, reject) => { const url = `${configMC.backendBaseUrl}${configMC.backendApiH5pPath}`; - const indices: number[] = this.corpusService.currentSolutions ? this.corpusService.currentSolutions.map( - x => this.corpusService.previewAnnisResponse.solutions.indexOf(x)) : []; + const indices: number[] = this.corpusService.currentSolutions ? + this.getSolutionIndices(this.corpusService.currentSolutions) : []; const h5pForm: H5PForm = { eid: this.corpusService.previewAnnisResponse.exercise_id, exercise_type_path: this.currentExerciseParams.type as ExerciseTypePath, @@ -131,7 +133,9 @@ export class ExerciseService { solution_indices: indices }; const formData: FormData = new FormData(); - Object.keys(h5pForm).forEach((key: string) => formData.append(key, h5pForm[key])); + Object.keys(h5pForm).forEach((key: string) => { + formData.append(key, h5pForm[key].toString()); + }); const options = {responseType: 'blob' as const}; const errorMsg: string = HelperService.generalErrorAlertMessage; this.helperService.makePostRequest(this.http, this.toastCtrl, url, formData, errorMsg, options) @@ -195,15 +199,26 @@ export class ExerciseService { return new JSZip(); } - getSolutionIndices(solutions: Solution[]): string { - const indices: string[] = - solutions.map((x: Solution) => this.corpusService.previewAnnisResponse.solutions.findIndex((y: Solution) => { - return this.areSolutionElementsEqual(x.target, y.target) && - this.areSolutionElementsEqual(x.value, y.value); - }).toString()); + getSolutionIndices(solutions: Solution[]): number[] { + return solutions.map((x: Solution) => this.corpusService.previewAnnisResponse.solutions.findIndex((y: Solution) => { + return this.areSolutionElementsEqual(x.target, y.target) && + this.areSolutionElementsEqual(x.value, y.value); + })); + } + + getSolutionIndicesString(solutions: Solution[]): string { + const indices: number[] = this.getSolutionIndices(solutions); return '&solution_indices=' + indices.join(','); } + initExerciseTypeToPathMap() { + this.exerciseTypeToPathMap = {}; + const paths: ExerciseTypePath[] = [ExerciseTypePath.DragText, ExerciseTypePath.MarkWords, ExerciseTypePath.DragText]; + [ExerciseType.cloze, ExerciseType.markWords, ExerciseType.matching].forEach((key: ExerciseType, idx: number) => { + this.exerciseTypeToPathMap[key] = paths[idx]; + }); + } + initH5P(exerciseTypePath: ExerciseTypePath | string, url: string, showActions: boolean = true): Promise<void> { return new Promise((resolve) => { this.setH5Purl(url); @@ -244,6 +259,8 @@ export class ExerciseService { this.helperService.saveApplicationState(as).then(); const met: MoodleExerciseType = MoodleExerciseType[ar.exercise_type]; this.corpusService.exercise.type = ExerciseType[met.toString()]; + this.corpusService.currentSolutions = ar.solutions; + this.currentExerciseParams.type = this.exerciseTypeToPathMap[this.corpusService.exercise.type]; this.loadH5P(this.corpusService.previewAnnisResponse.exercise_id).then(() => { return resolve(); }); @@ -281,7 +298,8 @@ export class ExerciseService { } loadH5P(eid: string): Promise<void> { - const solutionIndicesString: string = this.excludeOOV ? this.getSolutionIndices(this.corpusService.currentSolutions) : ''; + const solutionIndicesString: string = this.excludeOOV ? + this.getSolutionIndicesString(this.corpusService.currentSolutions) : ''; // 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}${solutionIndicesString}`; diff --git a/mc_frontend/src/app/preview/preview.page.ts b/mc_frontend/src/app/preview/preview.page.ts index a6d3f0b2745ab4102adee538183d3d1e88a7afed..b945521ddfe4afcfef897cba88dfda770401ab86 100644 --- a/mc_frontend/src/app/preview/preview.page.ts +++ b/mc_frontend/src/app/preview/preview.page.ts @@ -109,8 +109,7 @@ export class PreviewPage implements AfterContentInit, OnDestroy, OnInit { this.urlBase = configMC.backendBaseUrl + configMC.backendApiFilePath + '?id=' + fileId + fileTypeBase; this.solutionIndicesString = ''; if (this.exerciseService.excludeOOV) { - this.solutionIndicesString = this.exerciseService.getSolutionIndices( - this.corpusService.currentSolutions); + this.solutionIndicesString = this.exerciseService.getSolutionIndicesString(this.corpusService.currentSolutions); } } }