Commit a8f31b84 authored by Konstantin Schulz's avatar Konstantin Schulz

fixed duplicate API calls when showing previews for exercises from the exercise list page

parent e5237035
Pipeline #15869 passed with stages
in 2 minutes and 49 seconds
......@@ -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
......
......@@ -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
......
......@@ -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"
......
......@@ -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)
......
......@@ -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);
......
......@@ -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();
});
}
......
......@@ -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');
});
......
......@@ -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}`;
......
......@@ -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);
}
}
}
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