From ecea33ffa9dc70aec10db7dd60c517a9ee8ff5b2 Mon Sep 17 00:00:00 2001
From: Patrick Jentsch <p.jentsch@uni-bielefeld.de>
Date: Mon, 9 Jan 2023 10:03:52 +0100
Subject: [PATCH] Make Utils functions more stable

---
 app/static/js/Utils.js | 147 +++++++++++++++++++++++++++--------------
 1 file changed, 97 insertions(+), 50 deletions(-)

diff --git a/app/static/js/Utils.js b/app/static/js/Utils.js
index e7b4aa1b..2b81a8ed 100644
--- a/app/static/js/Utils.js
+++ b/app/static/js/Utils.js
@@ -5,10 +5,10 @@ class Utils {
     return tmpElement.firstChild;
   }
 
-  static generateElementId(prefix='') {
+  static generateElementId(prefix='', suffix='') {
     for (let i = 0; true; i++) {
-      if (document.querySelector(`#${prefix}${i}`) !== null) {continue;}
-      return `${prefix}${i}`;
+      if (document.querySelector(`#${prefix}${i}${suffix}`) !== null) {continue;}
+      return `${prefix}${i}${suffix}`;
     }
   }
 
@@ -21,15 +21,11 @@ class Utils {
     if (objects.length === 0) {
       return mergedObject;
     }
+    if (!Utils.isObject(objects[0])) {throw 'Cannot merge non-object';}
     if (objects.length === 1) {
-      if (!Utils.isObject(objects[0])) {
-        throw 'Cannot merge non-object';
-      }
       return Utils.mergeObjectsDeep(mergedObject, objects[0]);
     }
-    if (!Utils.isObject(objects[0]) || !Utils.isObject(objects[1])) {
-      throw 'Cannot merge non-object';
-    }
+    if (!Utils.isObject(objects[1])) {throw 'Cannot merge non-object';}
     for (let key in objects[0]) {
       if (objects[0].hasOwnProperty(key)) {
         if (objects[1].hasOwnProperty(key)) {
@@ -58,15 +54,20 @@ class Utils {
 
   static buildCorpusRequest(userId, corpusId) {
     return new Promise((resolve, reject) => {
-      let corpus = app.data.users[userId].corpora[corpusId];
+      let corpus;
+      try {
+        corpus = app.data.users[userId].corpora[corpusId];
+      } catch (error) {
+        corpus = {};
+      }
 
-      fetch(`/corpora/${corpus.id}/build`, {method: 'POST', headers: {Accept: 'application/json'}})
+      fetch(`/corpora/${corpusId}/build`, {method: 'POST', headers: {Accept: 'application/json'}})
         .then(
           (response) => {
             if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
             if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
             if (response.status === 409) {app.flash('Conflict', 'error'); reject(response);}
-            app.flash(`Corpus "${corpus.title}" marked for building`, 'corpus');
+            app.flash(`Corpus "${corpus?.title}" marked for building`, 'corpus');
             resolve(response);
           },
           (response) => {
@@ -79,14 +80,19 @@ class Utils {
 
   static deleteCorpusRequest(userId, corpusId) {
     return new Promise((resolve, reject) => {
-      let corpus = app.data.users[userId].corpora[corpusId];
+      let corpus;
+      try {
+        corpus = app.data.users[userId].corpora[corpusId];
+      } catch (error) {
+        corpus = {};
+      }
 
       let modalElement = Utils.elementFromString(
         `
           <div class="modal">
             <div class="modal-content">
               <h4>Confirm Corpus deletion</h4>
-              <p>Do you really want to delete the Corpus <b>${corpus.title}</b>? All files will be permanently deleted!</p>
+              <p>Do you really want to delete the Corpus <b>${corpus?.title}</b>? All files will be permanently deleted!</p>
             </div>
             <div class="modal-footer">
               <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
@@ -109,13 +115,13 @@ class Utils {
       
       let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
       confirmElement.addEventListener('click', (event) => {
-        let corpusTitle = corpus.title;
-        fetch(`/corpora/${corpus.id}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
+        let corpusTitle = corpus?.title;
+        fetch(`/corpora/${corpusId}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
           .then(
             (response) => {
               if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
               if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
-              app.flash(`Corpus "${corpus.title}" marked for deletion`, 'corpus');
+              app.flash(`Corpus "${corpusTitle}" marked for deletion`, 'corpus');
               resolve(response);
             },
             (response) => {
@@ -130,15 +136,19 @@ class Utils {
 
   static deleteCorpusFileRequest(userId, corpusId, corpusFileId) {
     return new Promise((resolve, reject) => {
-      let corpus = app.data.users[userId].corpora[corpusId];
-      let corpusFile = corpus.files[corpusFileId];
+      let corpusFile;
+      try {
+        corpusFile = app.data.users[userId].corpora[corpusId].files[corpusFileId];
+      } catch (error) {
+        corpusFile = {};
+      }
 
       let modalElement = Utils.elementFromString(
         `
           <div class="modal">
             <div class="modal-content">
               <h4>Confirm Corpus File deletion</h4>
-              <p>Do you really want to delete the Corpus File <b>${corpusFile.title}</b>? All files will be permanently deleted!</p>
+              <p>Do you really want to delete the Corpus File <b>${corpusFile?.title}</b>? All files will be permanently deleted!</p>
             </div>
             <div class="modal-footer">
               <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
@@ -161,7 +171,7 @@ class Utils {
 
       let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
       confirmElement.addEventListener('click', (event) => {
-        let corpusFileTitle = corpusFile.title;
+        let corpusFileTitle = corpusFile?.title;
         fetch(`/corpora/${corpusId}/files/${corpusFileId}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
           .then(
             (response) => {
@@ -182,13 +192,19 @@ class Utils {
 
   static deleteSpaCyNLPPipelineModelRequest(userId, spaCyNLPPipelineModelId) {
     return new Promise((resolve, reject) => {
-      let spaCyNLPPipelineModel = app.data.users[userId].spacy_nlp_pipeline_models[spaCyNLPPipelineModelId];
+      let spaCyNLPPipelineModel;
+      try {
+        spaCyNLPPipelineModel = app.data.users[userId].spacy_nlp_pipeline_models[spaCyNLPPipelineModelId];
+      } catch (error) {
+        spaCyNLPPipelineModel = {};
+      }
+
       let modalElement = Utils.elementFromString(
         `
           <div class="modal">
             <div class="modal-content">
               <h4>Confirm SpaCy NLP Pipeline Model deletion</h4>
-              <p>Do you really want to delete the SpaCy NLP Pipeline Model <b>${spaCyNLPPipelineModel.title}</b>? All files will be permanently deleted!</p>
+              <p>Do you really want to delete the SpaCy NLP Pipeline Model <b>${spaCyNLPPipelineModel?.title}</b>? All files will be permanently deleted!</p>
             </div>
             <div class="modal-footer">
               <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
@@ -210,7 +226,7 @@ class Utils {
       );
       let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
       confirmElement.addEventListener('click', (event) => {
-        let spaCyNLPPipelineModelTitle = spaCyNLPPipelineModel.title;
+        let spaCyNLPPipelineModelTitle = spaCyNLPPipelineModel?.title;
         fetch(`/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModelId}`, {method: 'DELETE'})
           .then(
             (response) => {
@@ -231,13 +247,19 @@ class Utils {
 
   static deleteTesseractOCRPipelineModelRequest(userId, tesseractOCRPipelineModelId) {
     return new Promise((resolve, reject) => {
-      let tesseractOCRPipelineModel = app.data.users[userId].tesseract_ocr_pipeline_models[tesseractOCRPipelineModelId];
+      let tesseractOCRPipelineModel;
+      try {
+        tesseractOCRPipelineModel = app.data.users[userId].tesseract_ocr_pipeline_models[tesseractOCRPipelineModelId];
+      } catch (error) {
+        tesseractOCRPipelineModel = {};
+      }
+
       let modalElement = Utils.elementFromString(
         `
           <div class="modal">
             <div class="modal-content">
               <h4>Confirm Tesseract OCR Pipeline Model deletion</h4>
-              <p>Do you really want to delete the Tesseract OCR Pipeline Model <b>${tesseractOCRPipelineModel.title}</b>? All files will be permanently deleted!</p>
+              <p>Do you really want to delete the Tesseract OCR Pipeline Model <b>${tesseractOCRPipelineModel?.title}</b>? All files will be permanently deleted!</p>
             </div>
             <div class="modal-footer">
               <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
@@ -259,7 +281,7 @@ class Utils {
       );
       let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
       confirmElement.addEventListener('click', (event) => {
-        let tesseractOCRPipelineModelTitle = tesseractOCRPipelineModel.title;
+        let tesseractOCRPipelineModelTitle = tesseractOCRPipelineModel?.title;
         fetch(`/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}`, {method: 'DELETE'})
           .then(
             (response) => {
@@ -328,14 +350,19 @@ class Utils {
 
   static deleteJobRequest(userId, jobId) {
     return new Promise((resolve, reject) => {
-      let job = app.data.users[userId].jobs[jobId];
+      let job;
+      try {
+        job = app.data.users[userId].jobs[jobId];
+      } catch (error) {
+        job = {};
+      }
 
       let modalElement = Utils.elementFromString(
         `
           <div class="modal">
             <div class="modal-content">
               <h4>Confirm Job deletion</h4>
-              <p>Do you really want to delete the Job <b>${job.title}</b>? All files will be permanently deleted!</p>
+              <p>Do you really want to delete the Job <b>${job?.title}</b>? All files will be permanently deleted!</p>
             </div>
             <div class="modal-footer">
               <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
@@ -358,8 +385,8 @@ class Utils {
 
       let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
       confirmElement.addEventListener('click', (event) => {
-        let jobTitle = job.title;
-        fetch(`/jobs/${job.id}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
+        let jobTitle = job?.title;
+        fetch(`/jobs/${jobId}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
           .then(
             (response) => {
               if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
@@ -379,9 +406,7 @@ class Utils {
 
   static getJobLogRequest(userId, jobId) {
     return new Promise((resolve, reject) => {
-      let job = app.data.users[userId].jobs[jobId];
-
-      fetch(`/jobs/${job.id}/log`, {method: 'GET', headers: {Accept: 'application/json, text/plain'}})
+      fetch(`/jobs/${jobId}/log`, {method: 'GET', headers: {Accept: 'application/json, text/plain'}})
         .then(
           (response) => {
             if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
@@ -427,14 +452,19 @@ class Utils {
 
   static restartJobRequest(userId, jobId) {
     return new Promise((resolve, reject) => {
-      let job = app.data.users[userId].jobs[jobId];
+      let job;
+      try {
+        job = app.data.users[userId].jobs[jobId];
+      } catch (error) {
+        job = {};
+      }
 
       let modalElement = Utils.elementFromString(
         `
           <div class="modal">
             <div class="modal-content">
               <h4>Confirm Job restart</h4>
-              <p>Do you really want to restart the Job <b>${job.title}</b>? All Job Results will be permanently deleted.</p>
+              <p>Do you really want to restart the Job <b>${job?.title}</b>? All Job Results will be permanently deleted.</p>
             </div>
             <div class="modal-footer">
               <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
@@ -457,8 +487,8 @@ class Utils {
 
       let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
       confirmElement.addEventListener('click', (event) => {
-        let jobTitle = job.title;
-        fetch(`/jobs/${job.id}/restart`, {method: 'POST', headers: {Accept: 'application/json'}})
+        let jobTitle = job?.title;
+        fetch(`/jobs/${jobId}/restart`, {method: 'POST', headers: {Accept: 'application/json'}})
           .then(
             (response) => {
               if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
@@ -479,14 +509,19 @@ class Utils {
 
   static deleteUserRequest(userId) {
     return new Promise((resolve, reject) => {
-      let user = app.data.users[userId];
+      let user;
+      try {
+        user = app.data.users[userId];
+      } catch (error) {
+        user = {};
+      }
 
       let modalElement = Utils.elementFromString(
         `
           <div class="modal">
             <div class="modal-content">
               <h4>Confirm User deletion</h4>
-              <p>Do you really want to delete the User <b>${user.username}</b>? All files will be permanently deleted!</p>
+              <p>Do you really want to delete the User <b>${user?.username}</b>? All files will be permanently deleted!</p>
             </div>
             <div class="modal-footer">
               <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
@@ -509,8 +544,8 @@ class Utils {
 
       let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
       confirmElement.addEventListener('click', (event) => {
-        let userName = user.username;
-        fetch(`/users/${user.id}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
+        let userName = user?.username;
+        fetch(`/users/${userId}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
           .then(
             (response) => {
               if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
@@ -530,14 +565,20 @@ class Utils {
 
   static shareTesseractOCRPipelineModelRequest(userId, tesseractOCRPipelineModelId, is_public) {
     return new Promise((resolve, reject) => {
-      let tesseractOCRPipelineModel = app.data.users[userId].tesseract_ocr_pipeline_models[tesseractOCRPipelineModelId];
+      let tesseractOCRPipelineModel;
+      try {
+        tesseractOCRPipelineModel = app.data.users[userId].tesseract_ocr_pipeline_models[tesseractOCRPipelineModelId];
+      } catch (error) {
+        tesseractOCRPipelineModel = {};
+      }
+    
       let msg = '';
       if (is_public) {
-        msg = `Model "${tesseractOCRPipelineModel.title}" is now public`;
+        msg = `Model "${tesseractOCRPipelineModel?.title}" is now public`;
       } else {
-        msg = `Model "${tesseractOCRPipelineModel.title}" is now private`;
+        msg = `Model "${tesseractOCRPipelineModel?.title}" is now private`;
       }
-      fetch(`/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModel.id}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}})
+      fetch(`/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}})
       .then(
         (response) => {
           if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
@@ -554,14 +595,20 @@ class Utils {
 
   static shareSpaCyNLPPipelineModelRequest(userId, spaCyNLPPipelineModelId, is_public) {
     return new Promise((resolve, reject) => {
-      let spaCyNLPPipelineModel = app.data.users[userId].spacy_nlp_pipeline_models[spaCyNLPPipelineModelId];
+      let spaCyNLPPipelineModel;
+      try {
+        spaCyNLPPipelineModel = app.data.users[userId].spacy_nlp_pipeline_models[spaCyNLPPipelineModelId];
+      } catch (error) {
+        spaCyNLPPipelineModel = {};
+      }
+
       let msg = '';
       if (is_public) {
-        msg = `Model "${spaCyNLPPipelineModel.title}" is now public`;
+        msg = `Model "${spaCyNLPPipelineModel?.title}" is now public`;
       } else {
-        msg = `Model "${spaCyNLPPipelineModel.title}" is now private`;
+        msg = `Model "${spaCyNLPPipelineModel?.title}" is now private`;
       }
-      fetch(`/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModel.id}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}})
+      fetch(`/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModelId}/toggle-public-status`, {method: 'POST', headers: {Accept: 'application/json'}})
       .then(
         (response) => {
           if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
-- 
GitLab