Merge remote-tracking branch 'origin/dev-1.2' into development
This commit is contained in:
		
						commit
						03300923c0
					
				
					 8 changed files with 461 additions and 153 deletions
				
			
		|  | @ -61,7 +61,7 @@ public final class LmsSetup implements GrantEntity, Activatable { | ||||||
|         /** The Moodle binding features only the course access API so far */ |         /** The Moodle binding features only the course access API so far */ | ||||||
|         MOODLE(Features.COURSE_API /* , Features.SEB_RESTRICTION */), |         MOODLE(Features.COURSE_API /* , Features.SEB_RESTRICTION */), | ||||||
|         /** The Ans Delft binding is on the way */ |         /** The Ans Delft binding is on the way */ | ||||||
|         ANS_DELFT(), |         ANS_DELFT(Features.COURSE_API, Features.SEB_RESTRICTION), | ||||||
|         /** The OpenOLAT binding is on the way */ |         /** The OpenOLAT binding is on the way */ | ||||||
|         OPEN_OLAT(Features.COURSE_API, Features.SEB_RESTRICTION); |         OPEN_OLAT(Features.COURSE_API, Features.SEB_RESTRICTION); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -357,7 +357,7 @@ public class LmsSetupForm implements TemplateComposer { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             return this.resourceService.lmsTypeResources().get(0)._1; |             return LmsType.MOCKUP.name(); | ||||||
|         } catch (final Exception e) { |         } catch (final Exception e) { | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -293,14 +293,22 @@ public final class ViewContext { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void setValuesToInputFields(final Collection<ConfigurationValue> values) { |     void setValuesToInputFields(final Collection<ConfigurationValue> values) { | ||||||
|         this.inputFieldMapping |         try { | ||||||
|                 .values() |             this.inputFieldMapping | ||||||
|                 .forEach(field -> { |                     .values() | ||||||
|                     final ConfigurationValue initValue = field.initValue(values); |                     .forEach(field -> { | ||||||
|                     if (initValue != null) { |                         try { | ||||||
|                         this.valueChangeListener.notifyGUI(this, field.getAttribute(), initValue); |                             final ConfigurationValue initValue = field.initValue(values); | ||||||
|                     } |                             if (initValue != null) { | ||||||
|                 }); |                                 this.valueChangeListener.notifyGUI(this, field.getAttribute(), initValue); | ||||||
|  |                             } | ||||||
|  |                         } catch (final Exception e) { | ||||||
|  |                             log.error("Failed to initialize SEB setting: {}", field.getAttribute(), e); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |         } catch (final Exception e) { | ||||||
|  |             log.error("Unexpected error while initialize SEB settings: ", e); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** Removes all registered InputFields with the given attribute ids |     /** Removes all registered InputFields with the given attribute ids | ||||||
|  |  | ||||||
|  | @ -9,20 +9,33 @@ | ||||||
| package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.ans; | package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.ans; | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import java.util.Arrays; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
|  | import java.util.HashMap; | ||||||
| import java.util.HashSet; | import java.util.HashSet; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Locale; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Optional; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| import java.util.function.Supplier; | import java.util.function.Supplier; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
|  | import java.util.stream.Stream; | ||||||
| 
 | 
 | ||||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||||
| import org.joda.time.DateTime; | import org.joda.time.DateTime; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
| import org.springframework.cache.CacheManager; | import org.springframework.cache.CacheManager; | ||||||
|  | import org.springframework.core.ParameterizedTypeReference; | ||||||
| import org.springframework.core.env.Environment; | import org.springframework.core.env.Environment; | ||||||
|  | import org.springframework.http.HttpEntity; | ||||||
|  | import org.springframework.http.HttpHeaders; | ||||||
|  | import org.springframework.http.HttpMethod; | ||||||
|  | import org.springframework.http.ResponseEntity; | ||||||
| import org.springframework.http.client.ClientHttpRequestFactory; | import org.springframework.http.client.ClientHttpRequestFactory; | ||||||
| import org.springframework.security.oauth2.client.OAuth2RestTemplate; |  | ||||||
| import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; | import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; | ||||||
|  | import org.springframework.web.client.RestTemplate; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService; | import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.APIMessage; | import ch.ethz.seb.sebserver.gbl.api.APIMessage; | ||||||
|  | @ -36,7 +49,6 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; | import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction; | import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; | import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features; |  | ||||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; | import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; | import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails; | import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails; | ||||||
|  | @ -47,20 +59,22 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; | import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; | import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCachedCourseAccess; | import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCachedCourseAccess; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.ans.AnsLmsData.AccessibilitySettingsData; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.ans.AnsLmsData.AssignmentData; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.ans.AnsLmsData.UserData; | ||||||
| 
 | 
 | ||||||
| public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements LmsAPITemplate { | public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements LmsAPITemplate { | ||||||
| 
 | 
 | ||||||
|     // TODO add needed dependencies here |     private static final Logger log = LoggerFactory.getLogger(AnsLmsAPITemplate.class); | ||||||
| 
 | 
 | ||||||
|     private final ClientHttpRequestFactoryService clientHttpRequestFactoryService; |     private final ClientHttpRequestFactoryService clientHttpRequestFactoryService; | ||||||
|     private final ClientCredentialService clientCredentialService; |     private final ClientCredentialService clientCredentialService; | ||||||
|     private final APITemplateDataSupplier apiTemplateDataSupplier; |     private final APITemplateDataSupplier apiTemplateDataSupplier; | ||||||
|     private final Long lmsSetupId; |     private final Long lmsSetupId; | ||||||
| 
 | 
 | ||||||
|  |     private AnsPersonalRestTemplate cachedRestTemplate; | ||||||
|  | 
 | ||||||
|     protected AnsLmsAPITemplate( |     protected AnsLmsAPITemplate( | ||||||
| 
 |  | ||||||
|             // TODO if you need more dependencies inject them here and set the reference |  | ||||||
| 
 |  | ||||||
|             final ClientHttpRequestFactoryService clientHttpRequestFactoryService, |             final ClientHttpRequestFactoryService clientHttpRequestFactoryService, | ||||||
|             final ClientCredentialService clientCredentialService, |             final ClientCredentialService clientCredentialService, | ||||||
|             final APITemplateDataSupplier apiTemplateDataSupplier, |             final APITemplateDataSupplier apiTemplateDataSupplier, | ||||||
|  | @ -97,28 +111,19 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms | ||||||
|         if (testLmsSetupSettings.hasAnyError()) { |         if (testLmsSetupSettings.hasAnyError()) { | ||||||
|             return testLmsSetupSettings; |             return testLmsSetupSettings; | ||||||
|         } |         } | ||||||
| 
 |         try { | ||||||
|         // TODO check if the course API of the remote LMS is available |             this.getRestTemplate().get(); | ||||||
|         // if not, create corresponding LmsSetupTestResult error |         } catch (final RuntimeException e) { | ||||||
|  |             log.error("Failed to access Ans course API: ", e); | ||||||
|  |             return LmsSetupTestResult.ofQuizAccessAPIError(LmsType.ANS_DELFT, e.getMessage()); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         return LmsSetupTestResult.ofOkay(LmsType.ANS_DELFT); |         return LmsSetupTestResult.ofOkay(LmsType.ANS_DELFT); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public LmsSetupTestResult testCourseRestrictionAPI() { |     public LmsSetupTestResult testCourseRestrictionAPI() { | ||||||
|         final LmsSetupTestResult testLmsSetupSettings = testLmsSetupSettings(); |         return testCourseAccessAPI(); | ||||||
|         if (testLmsSetupSettings.hasAnyError()) { |  | ||||||
|             return testLmsSetupSettings; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (LmsType.ANS_DELFT.features.contains(Features.SEB_RESTRICTION)) { |  | ||||||
| 
 |  | ||||||
|             // TODO check if the course API of the remote LMS is available |  | ||||||
|             // if not, create corresponding LmsSetupTestResult error |  | ||||||
| 
 |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return LmsSetupTestResult.ofOkay(LmsType.ANS_DELFT); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private LmsSetupTestResult testLmsSetupSettings() { |     private LmsSetupTestResult testLmsSetupSettings() { | ||||||
|  | @ -203,58 +208,126 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms | ||||||
|         return super.protectedQuizRequest(id); |         return super.protectedQuizRequest(id); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private List<QuizData> collectAllQuizzes(final AnsPersonalRestTemplate restTemplate) { | ||||||
|  |         final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); | ||||||
|  |         final List<QuizData> quizDatas = getAssignments(restTemplate) | ||||||
|  |                 .stream() | ||||||
|  |                 .map(a -> quizDataFromAnsData(lmsSetup, a)) | ||||||
|  |                 .collect(Collectors.toList()); | ||||||
|  |         quizDatas.forEach(q -> super.putToCache(q)); | ||||||
|  |         return quizDatas; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private QuizData getQuizByAssignmentId(final RestTemplate restTemplate, final String id) { | ||||||
|  |         final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); | ||||||
|  |         final AssignmentData a = getAssignmentById(restTemplate, id); | ||||||
|  |         return quizDataFromAnsData(lmsSetup, a); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private QuizData quizDataFromAnsData(final LmsSetup lmsSetup, final AssignmentData a) { | ||||||
|  |         // In Ans, one assignment can have multiple timeslots, but the SEB restriciton | ||||||
|  |         // is done at the assignment level, so timeslots don't really matter. | ||||||
|  |         // An assignment's start_at and end_at dates indicate when the first timeslot starts | ||||||
|  |         // and the last timeslot ends. If these are null, there are no timeslots, yet. | ||||||
|  |         // In that case, we create a placeholder timeslot to display in SEB server. | ||||||
|  |         if (a.start_at == null) { | ||||||
|  |             a.start_at = java.time.Instant.now().plus(365, java.time.temporal.ChronoUnit.DAYS).toString(); | ||||||
|  |             a.end_at = java.time.Instant.now().plus(366, java.time.temporal.ChronoUnit.DAYS).toString(); | ||||||
|  |         } | ||||||
|  |         final DateTime startTime = new DateTime(a.start_at); | ||||||
|  |         final DateTime endTime = new DateTime(a.end_at); | ||||||
|  |         final Map<String, String> attrs = new HashMap<>(); | ||||||
|  |         attrs.put("assignment_id", String.valueOf(a.id)); | ||||||
|  |         return new QuizData( | ||||||
|  |                 String.valueOf(a.id), | ||||||
|  |                 lmsSetup.getInstitutionId(), | ||||||
|  |                 lmsSetup.id, | ||||||
|  |                 lmsSetup.getLmsType(), | ||||||
|  |                 String.format("%s", a.name), | ||||||
|  |                 String.format(""), | ||||||
|  |                 startTime, | ||||||
|  |                 endTime, | ||||||
|  |                 a.start_url, | ||||||
|  |                 attrs); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private List<AssignmentData> getAssignments(final RestTemplate restTemplate) { | ||||||
|  |         // NOTE: at the moment, seb_server_enabled cannot be set inside the Ans GUI, | ||||||
|  |         // only via the API, so we need to list all assignments. Maybe in the future, | ||||||
|  |         // we can only list those for which seb server has been enabled in Ans (like in OLAT): | ||||||
|  |         //final String url = "/api/v2/search/assignments?query=seb_server_enabled:true"; | ||||||
|  |         final String url = "/api/v2/search/assignments"; | ||||||
|  |         return this.apiGetList(restTemplate, url, new ParameterizedTypeReference<List<AssignmentData>>() { | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private AssignmentData getAssignmentById(final RestTemplate restTemplate, final String id) { | ||||||
|  |         final String url = String.format("/api/v2/assignments/%s", id); | ||||||
|  |         return this.apiGet(restTemplate, url, AssignmentData.class); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private List<QuizData> getQuizzesByIds(final RestTemplate restTemplate, final Set<String> ids) { | ||||||
|  |         final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); | ||||||
|  |         return ids.stream().map(id -> { | ||||||
|  |             final String url = String.format("/api/v2/assignments/%s", id); | ||||||
|  |             return this.apiGet(restTemplate, url, AssignmentData.class); | ||||||
|  |         }).map(a -> { | ||||||
|  |             final QuizData quizData = quizDataFromAnsData(lmsSetup, a); | ||||||
|  |             super.putToCache(quizData); | ||||||
|  |             return quizData; | ||||||
|  |         }) | ||||||
|  |                 .collect(Collectors.toList()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap) { |     protected Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap) { | ||||||
| 
 |         // We cannot filter by from-date or partial names using the Ans search API. | ||||||
|         @SuppressWarnings("unused") |         // Only exact matches are permitted. So we're not implementing filtering | ||||||
|         final String quizName = filterMap.getString(QuizData.FILTER_ATTR_QUIZ_NAME); |         // on the API level and always retrieve all assignments and let SEB server | ||||||
|         @SuppressWarnings("unused") |         // do the filtering. | ||||||
|         final DateTime quizFromTime = (filterMap != null) ? filterMap.getQuizFromTime() : null; |  | ||||||
| 
 |  | ||||||
|         return () -> { |         return () -> { | ||||||
| 
 |             final List<QuizData> res = getRestTemplate() | ||||||
|             // TODO get all course / quiz data from remote LMS that matches the filter criteria. |                     .map(this::collectAllQuizzes) | ||||||
|             //      put loaded QuizData to the cache: super.putToCache(quizDataCollection); |                     .getOrThrow(); | ||||||
|             //      before returning it. |             super.putToCache(res); | ||||||
| 
 |             return res; | ||||||
|             throw new RuntimeException("TODO"); |  | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected Supplier<Collection<QuizData>> quizzesSupplier(final Set<String> ids) { |     protected Supplier<Collection<QuizData>> quizzesSupplier(final Set<String> ids) { | ||||||
|         return () -> { |         return () -> getRestTemplate() | ||||||
| 
 |                 .map(t -> this.getQuizzesByIds(t, ids)) | ||||||
|             // TODO get all quiz / course data for specified identifiers from remote LMS |                 .getOrThrow(); | ||||||
|             //      and put it to the cache: super.putToCache(quizDataCollection); |  | ||||||
|             //      before returning it. |  | ||||||
| 
 |  | ||||||
|             throw new RuntimeException("TODO"); |  | ||||||
|         }; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected Supplier<QuizData> quizSupplier(final String id) { |     protected Supplier<QuizData> quizSupplier(final String id) { | ||||||
|         return () -> { |         return () -> getRestTemplate() | ||||||
|  |                 .map(t -> this.getQuizByAssignmentId(t, id)) | ||||||
|  |                 .getOrThrow(); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|             // TODO get the specified quiz / course data for specified identifier from remote LMS |     private ExamineeAccountDetails getExamineeById(final RestTemplate restTemplate, final String id) { | ||||||
|             //      and put it to the cache: super.putToCache(quizDataCollection); |         final String url = String.format("/api/v2/users/%s", id); | ||||||
|             //      before returning it. |         final UserData u = this.apiGet(restTemplate, url, UserData.class); | ||||||
| 
 |         final Map<String, String> attrs = new HashMap<>(); | ||||||
|             throw new RuntimeException("TODO"); |         attrs.put("role", u.role); | ||||||
|         }; |         attrs.put("affiliation", u.affiliation); | ||||||
|  |         attrs.put("active", u.active ? "yes" : "no"); | ||||||
|  |         return new ExamineeAccountDetails( | ||||||
|  |                 String.valueOf(u.id), | ||||||
|  |                 u.last_name + ", " + u.first_name, | ||||||
|  |                 u.external_id, | ||||||
|  |                 u.email_address, | ||||||
|  |                 attrs); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId) { |     protected Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String id) { | ||||||
| 
 |         return () -> getRestTemplate() | ||||||
|         return () -> { |                 .map(t -> this.getExamineeById(t, id)) | ||||||
| 
 |                 .getOrThrow(); | ||||||
|             // TODO get the examinee's account details by the given examineeSessionId from remote LMS. |  | ||||||
|             //      Currently only the name is needed to display on monitoring view. |  | ||||||
| 
 |  | ||||||
|             throw new RuntimeException("TODO"); |  | ||||||
|         }; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  | @ -266,79 +339,169 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Result<SEBRestriction> getSEBClientRestriction(final Exam exam) { |     public Result<SEBRestriction> getSEBClientRestriction(final Exam exam) { | ||||||
|  |         return getRestTemplate() | ||||||
|  |                 .map(t -> this.getRestrictionForAssignmentId(t, exam.externalId)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private SEBRestriction getRestrictionForAssignmentId(final RestTemplate restTemplate, final String id) { | ||||||
|  |         final String url = String.format("/api/v2/assignments/%s", id); | ||||||
|  |         final AssignmentData assignment = this.apiGet(restTemplate, url, AssignmentData.class); | ||||||
|  |         final AccessibilitySettingsData ts = assignment.accessibility_settings; | ||||||
|  |         return new SEBRestriction(Long.valueOf(id), ts.config_keys, null, new HashMap<String, String>()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private SEBRestriction setRestrictionForAssignmentId(final RestTemplate restTemplate, final String id, | ||||||
|  |             final SEBRestriction restriction) { | ||||||
|  |         final String url = String.format("/api/v2/assignments/%s", id); | ||||||
|  |         final AssignmentData assignment = getAssignmentById(restTemplate, id); | ||||||
|  |         assignment.accessibility_settings.config_keys = new ArrayList<>(restriction.configKeys); | ||||||
|  |         assignment.accessibility_settings.seb_server_enabled = true; | ||||||
|         @SuppressWarnings("unused") |         @SuppressWarnings("unused") | ||||||
|         final String quizId = exam.externalId; |         final AssignmentData r = | ||||||
|  |                 this.apiPatch(restTemplate, url, assignment, AssignmentData.class, AssignmentData.class); | ||||||
|  |         final AccessibilitySettingsData ts = assignment.accessibility_settings; | ||||||
|  |         return new SEBRestriction(Long.valueOf(id), ts.config_keys, null, new HashMap<String, String>()); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         return Result.tryCatch(() -> { |     private SEBRestriction deleteRestrictionForAssignmentId(final RestTemplate restTemplate, final String id) { | ||||||
| 
 |         final String url = String.format("/api/v2/assignments/%s", id); | ||||||
|             // TODO get the SEB client restrictions that are currently set on the remote LMS for |         final AssignmentData assignment = getAssignmentById(restTemplate, id); | ||||||
|             //      the given quiz / course derived from the given exam |         assignment.accessibility_settings.config_keys = null; | ||||||
| 
 |         assignment.accessibility_settings.seb_server_enabled = false; | ||||||
|             throw new RuntimeException("TODO"); |         @SuppressWarnings("unused") | ||||||
| 
 |         final AssignmentData r = | ||||||
|         }); |                 this.apiPatch(restTemplate, url, assignment, AssignmentData.class, AssignmentData.class); | ||||||
|  |         final AccessibilitySettingsData ts = assignment.accessibility_settings; | ||||||
|  |         return new SEBRestriction(Long.valueOf(id), ts.config_keys, null, new HashMap<String, String>()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Result<SEBRestriction> applySEBClientRestriction( |     public Result<SEBRestriction> applySEBClientRestriction( | ||||||
|             final String externalExamId, |             final String externalExamId, | ||||||
|             final SEBRestriction sebRestrictionData) { |             final SEBRestriction sebRestrictionData) { | ||||||
| 
 |         return getRestTemplate() | ||||||
|         return Result.tryCatch(() -> { |                 .map(t -> this.setRestrictionForAssignmentId(t, externalExamId, sebRestrictionData)); | ||||||
| 
 |  | ||||||
|             // TODO apply the given sebRestrictionData settings as current SEB client restriction setting |  | ||||||
|             //      to the remote LMS for the given quiz / course. |  | ||||||
|             //      Mainly SEBRestriction.configKeys and SEBRestriction.browserExamKeys |  | ||||||
| 
 |  | ||||||
|             throw new RuntimeException("TODO"); |  | ||||||
| 
 |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Result<Exam> releaseSEBClientRestriction(final Exam exam) { |     public Result<Exam> releaseSEBClientRestriction(final Exam exam) { | ||||||
|         @SuppressWarnings("unused") |         return getRestTemplate() | ||||||
|         final String quizId = exam.externalId; |                 .map(t -> this.deleteRestrictionForAssignmentId(t, exam.externalId)) | ||||||
|  |                 .map(x -> exam); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|  |     private enum LinkRel { | ||||||
|  |         FIRST, LAST, PREV, NEXT | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private class PageLink { | ||||||
|  |         public final String link; | ||||||
|  |         public final LinkRel rel; | ||||||
|  | 
 | ||||||
|  |         public PageLink(final String l, final LinkRel r) { | ||||||
|  |             this.link = l; | ||||||
|  |             this.rel = r; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private List<PageLink> parseLinks(final String header) { | ||||||
|  |         // Extracts the individual links from a header that looks like this: | ||||||
|  |         // <https://staging.ans.app/api/v2/search/assignments?query=seb_server_enabled%3Atrue&page=1&items=20>; rel="first",<https://staging.ans.app/api/v2/search/assignments?query=seb_server_enabled%3Atrue&page=1&items=20>; rel="last" | ||||||
|  |         final Stream<String> links = Arrays.stream(header.split(",")); | ||||||
|  |         return links | ||||||
|  |                 .map(s -> { | ||||||
|  |                     final String[] pair = s.split(";"); | ||||||
|  |                     final String link = pair[0].trim().substring(1).replaceFirst(".$", ""); // remove < > | ||||||
|  |                     final String relName = pair[1].trim().substring(5).replaceFirst(".$", ""); // remove rel=" " | ||||||
|  |                     return new PageLink(link, LinkRel.valueOf(relName.toUpperCase(Locale.ROOT))); | ||||||
|  |                 }) | ||||||
|  |                 .collect(Collectors.toList()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Optional<PageLink> getNextLink(final List<PageLink> links) { | ||||||
|  |         return links.stream().filter(l -> l.rel == LinkRel.NEXT).findFirst(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private <T> List<T> apiGetList(final RestTemplate restTemplate, final String url, | ||||||
|  |             final ParameterizedTypeReference<List<T>> type) { | ||||||
|  |         final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); | ||||||
|  |         return apiGetListPages(restTemplate, lmsSetup.lmsApiUrl + url, type); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private <T> List<T> apiGetListPages(final RestTemplate restTemplate, final String link, | ||||||
|  |             final ParameterizedTypeReference<List<T>> type) { | ||||||
|  |         // unlike the other api methods, this one takes an explicit link | ||||||
|  |         // instead of prepending lmsSetup.lmsApiUrl. This is done because Ans | ||||||
|  |         // provides absolute links for pagination. This method calls itself | ||||||
|  |         // recursively to retrieve multiple pages. | ||||||
|  |         final HttpHeaders requestHeaders = new HttpHeaders(); | ||||||
|  |         requestHeaders.set("accept", "application/json"); | ||||||
|  |         final ResponseEntity<List<T>> response = restTemplate.exchange( | ||||||
|  |                 link, | ||||||
|  |                 HttpMethod.GET, | ||||||
|  |                 new HttpEntity<>(requestHeaders), | ||||||
|  |                 type); | ||||||
|  |         final List<T> page = response.getBody(); | ||||||
|  |         final HttpHeaders responseHeaders = response.getHeaders(); | ||||||
|  |         final List<PageLink> links = parseLinks(responseHeaders.getFirst("link")); | ||||||
|  |         final List<T> nextPage = getNextLink(links).map(l -> { | ||||||
|  |             return apiGetListPages(restTemplate, l.link, type); | ||||||
|  |         }).orElse(new ArrayList<T>()); | ||||||
|  |         page.addAll(nextPage); | ||||||
|  |         return page; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private <T> T apiGet(final RestTemplate restTemplate, final String url, final Class<T> type) { | ||||||
|  |         final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); | ||||||
|  |         final HttpHeaders requestHeaders = new HttpHeaders(); | ||||||
|  |         requestHeaders.set("accept", "application/json"); | ||||||
|  |         final ResponseEntity<T> res = restTemplate.exchange( | ||||||
|  |                 lmsSetup.lmsApiUrl + url, | ||||||
|  |                 HttpMethod.GET, | ||||||
|  |                 new HttpEntity<>(requestHeaders), | ||||||
|  |                 type); | ||||||
|  |         return res.getBody(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private <P, R> R apiPatch(final RestTemplate restTemplate, final String url, final P patch, | ||||||
|  |             final Class<P> patchType, final Class<R> responseType) { | ||||||
|  |         final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); | ||||||
|  |         final HttpHeaders requestHeaders = new HttpHeaders(); | ||||||
|  |         requestHeaders.set("content-type", "application/json"); | ||||||
|  |         final HttpEntity<P> requestEntity = new HttpEntity<>(patch, requestHeaders); | ||||||
|  |         final ResponseEntity<R> res = restTemplate.exchange( | ||||||
|  |                 lmsSetup.lmsApiUrl + url, | ||||||
|  |                 HttpMethod.PATCH, | ||||||
|  |                 requestEntity, | ||||||
|  |                 responseType); | ||||||
|  |         return res.getBody(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Result<AnsPersonalRestTemplate> getRestTemplate() { | ||||||
|         return Result.tryCatch(() -> { |         return Result.tryCatch(() -> { | ||||||
|  |             if (this.cachedRestTemplate != null) { | ||||||
|  |                 return this.cachedRestTemplate; | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             // TODO Release respectively delete all SEB client restrictions for the given |             final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials(); | ||||||
|             //      course / quize on the remote LMS. |             final ProxyData proxyData = this.apiTemplateDataSupplier.getProxyData(); | ||||||
|  |             final CharSequence plainClientSecret = this.clientCredentialService | ||||||
|  |                     .getPlainClientSecret(credentials) | ||||||
|  |                     .getOrThrow(); | ||||||
| 
 | 
 | ||||||
|             throw new RuntimeException("TODO"); |             final ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); | ||||||
|  |             details.setClientSecret(plainClientSecret.toString()); | ||||||
| 
 | 
 | ||||||
|  |             final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService | ||||||
|  |                     .getClientHttpRequestFactory(proxyData) | ||||||
|  |                     .getOrThrow(); | ||||||
|  | 
 | ||||||
|  |             final AnsPersonalRestTemplate template = new AnsPersonalRestTemplate(details); | ||||||
|  |             template.setRequestFactory(clientHttpRequestFactory); | ||||||
|  | 
 | ||||||
|  |             this.cachedRestTemplate = template; | ||||||
|  |             return this.cachedRestTemplate; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // TODO: This is an example of how to create a RestTemplate for the service to access the LMS API |  | ||||||
|     //       The example deals with a Http based API that is secured by an OAuth2 client-credential flow. |  | ||||||
|     //       You might need some different template, then you have to adapt this code |  | ||||||
|     //       To your needs. |  | ||||||
|     @SuppressWarnings("unused") |  | ||||||
|     private OAuth2RestTemplate createRestTemplate(final String accessTokenRequestPath) { |  | ||||||
| 
 |  | ||||||
|         final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); |  | ||||||
|         final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials(); |  | ||||||
|         final ProxyData proxyData = this.apiTemplateDataSupplier.getProxyData(); |  | ||||||
| 
 |  | ||||||
|         final CharSequence plainClientId = credentials.clientId; |  | ||||||
|         final CharSequence plainClientSecret = this.clientCredentialService |  | ||||||
|                 .getPlainClientSecret(credentials) |  | ||||||
|                 .getOrThrow(); |  | ||||||
| 
 |  | ||||||
|         final ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); |  | ||||||
|         details.setAccessTokenUri(lmsSetup.lmsApiUrl + accessTokenRequestPath); |  | ||||||
|         details.setClientId(plainClientId.toString()); |  | ||||||
|         details.setClientSecret(plainClientSecret.toString()); |  | ||||||
| 
 |  | ||||||
|         final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService |  | ||||||
|                 .getClientHttpRequestFactory(proxyData) |  | ||||||
|                 .getOrThrow(); |  | ||||||
| 
 |  | ||||||
|         final OAuth2RestTemplate template = new OAuth2RestTemplate(details); |  | ||||||
|         template.setRequestFactory(clientHttpRequestFactory); |  | ||||||
| 
 |  | ||||||
|         return template; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,105 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) | ||||||
|  |  * | ||||||
|  |  * This Source Code Form is subject to the terms of the Mozilla Public | ||||||
|  |  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||||
|  |  * file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.ans; | ||||||
|  | 
 | ||||||
|  | import java.util.List; | ||||||
|  | 
 | ||||||
|  | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||||||
|  | 
 | ||||||
|  | public final class AnsLmsData { | ||||||
|  | 
 | ||||||
|  |     @JsonIgnoreProperties(ignoreUnknown = true) | ||||||
|  |     static final class AccessibilitySettingsData { | ||||||
|  |         /* Ans API example: see nested in AssignmentData */ | ||||||
|  |         public boolean seb_server_enabled; | ||||||
|  |         public List<String> config_keys; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @JsonIgnoreProperties(ignoreUnknown = true) | ||||||
|  |     static final class AssignmentData { | ||||||
|  |         /* | ||||||
|  |          * Ans API example: | ||||||
|  |          * { | ||||||
|  |          * "id": 78711, | ||||||
|  |          * "course_id": 44412, | ||||||
|  |          * "name": "Digital test demo", | ||||||
|  |          * "summative": false, | ||||||
|  |          * "assignment_type": "Quiz", | ||||||
|  |          * "start_at": "2021-08-18T09:00:00.000+02:00", | ||||||
|  |          * "end_at": "2021-08-18T12:00:00.000+02:00", | ||||||
|  |          * "created_at": "2021-06-21T12:24:28.538+02:00", | ||||||
|  |          * "updated_at": "2021-08-17T03:41:56.747+02:00", | ||||||
|  |          * "trashed": false, | ||||||
|  |          * "start_url": "https://staging.ans.app/digital_test/assignments/78805/results/new", | ||||||
|  |          * "accessibility_settings": { | ||||||
|  |          * "attempts": 1, | ||||||
|  |          * "restricted_access_to_other_pages": false, | ||||||
|  |          * "notes": false, | ||||||
|  |          * "spellchecker": false, | ||||||
|  |          * "feedback": false, | ||||||
|  |          * "forced_test_navigation": false, | ||||||
|  |          * "cannot_reopen_question_groups": false, | ||||||
|  |          * "seb_server_enabled": true, | ||||||
|  |          * "config_keys": [ | ||||||
|  |          * "9dd14ac828617116a1230c71b9a1aa9e06f43b32d9fa7db67f4fa113a6896e83e" | ||||||
|  |          * ] | ||||||
|  |          * }, | ||||||
|  |          * "grades_settings": { | ||||||
|  |          * "grade_calculation": "formula", | ||||||
|  |          * "grade_formula": "1 + 9 * points / total", | ||||||
|  |          * "rounding": "decimal", | ||||||
|  |          * "grade_lower_bound": true, | ||||||
|  |          * "grade_lower_limit": "1.0", | ||||||
|  |          * "grade_upper_bound": true, | ||||||
|  |          * "grade_upper_limit": "10.0", | ||||||
|  |          * "guess_correction": false, | ||||||
|  |          * "passed_grade": "5.5" | ||||||
|  |          * } | ||||||
|  |          * } | ||||||
|  |          */ | ||||||
|  |         public long id; | ||||||
|  |         public long course_id; | ||||||
|  |         public String name; | ||||||
|  |         public String external_id; | ||||||
|  |         public String start_at; | ||||||
|  |         public String end_at; | ||||||
|  |         public String start_url; | ||||||
|  |         public AccessibilitySettingsData accessibility_settings; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @JsonIgnoreProperties(ignoreUnknown = true) | ||||||
|  |     static final class UserData { | ||||||
|  |         /* | ||||||
|  |          * Ans API example: | ||||||
|  |          * { | ||||||
|  |          * "id": 726404, | ||||||
|  |          * "student_number": null, | ||||||
|  |          * "first_name": "John", | ||||||
|  |          * "middle_name": null, | ||||||
|  |          * "last_name": "Doe", | ||||||
|  |          * "external_id": null, | ||||||
|  |          * "created_at": "2021-06-21T12:07:11.668+02:00", | ||||||
|  |          * "updated_at": "2021-07-26T20:16:01.638+02:00", | ||||||
|  |          * "active": true, | ||||||
|  |          * "email_address": "person@example.org", | ||||||
|  |          * "affiliation": "employee", | ||||||
|  |          * "role": "owner" | ||||||
|  |          * } | ||||||
|  |          */ | ||||||
|  |         public long id; | ||||||
|  |         public String first_name; | ||||||
|  |         public String last_name; | ||||||
|  |         public String email_address; | ||||||
|  |         public String external_id; | ||||||
|  |         public String role; | ||||||
|  |         public String affiliation; | ||||||
|  |         public boolean active; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,42 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) | ||||||
|  |  * | ||||||
|  |  * This Source Code Form is subject to the terms of the Mozilla Public | ||||||
|  |  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||||
|  |  * file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.ans; | ||||||
|  | 
 | ||||||
|  | import java.io.IOException; | ||||||
|  | 
 | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | import org.springframework.http.HttpRequest; | ||||||
|  | import org.springframework.http.client.ClientHttpRequestExecution; | ||||||
|  | import org.springframework.http.client.ClientHttpRequestInterceptor; | ||||||
|  | import org.springframework.http.client.ClientHttpResponse; | ||||||
|  | import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; | ||||||
|  | import org.springframework.web.client.RestTemplate; | ||||||
|  | 
 | ||||||
|  | public class AnsPersonalRestTemplate extends RestTemplate { | ||||||
|  |     private static final Logger log = LoggerFactory.getLogger(AnsPersonalRestTemplate.class); | ||||||
|  |     public String token; | ||||||
|  | 
 | ||||||
|  |     public AnsPersonalRestTemplate(final ClientCredentialsResourceDetails details) { | ||||||
|  |         super(); | ||||||
|  |         this.token = details.getClientSecret(); | ||||||
|  |         this.getInterceptors().add(new ClientHttpRequestInterceptor() { | ||||||
|  |             @Override | ||||||
|  |             public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, | ||||||
|  |                     final ClientHttpRequestExecution execution) throws IOException { | ||||||
|  | 
 | ||||||
|  |                 request.getHeaders().set("Authorization", "Bearer " + AnsPersonalRestTemplate.this.token); | ||||||
|  |                 //log.debug("Matching curl: curl -X GET {} -H  'accept: application/json' -H  'Authorization: Bearer {}'", request.getURI(), token); | ||||||
|  |                 final ClientHttpResponse response = execution.execute(request, body); | ||||||
|  |                 log.debug("Response Headers      : {}", response.getHeaders()); | ||||||
|  |                 return response; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,17 +1,17 @@ | ||||||
| <html> | <html> | ||||||
|     <head> |     <head> | ||||||
|         <meta charset='utf-8' /> |         <meta charset='utf-8' /> | ||||||
|         <link type='text/css' rel='stylesheet' href='https://source.zoom.us/1.9.0/css/bootstrap.css' /> |         <link type='text/css' rel='stylesheet' href='https://source.zoom.us/1.9.1/css/bootstrap.css' /> | ||||||
|         <link type='text/css' rel='stylesheet' href='https://source.zoom.us/1.9.0/css/react-select.css' /> |         <link type='text/css' rel='stylesheet' href='https://source.zoom.us/1.9.1/css/react-select.css' /> | ||||||
|     </head> |     </head> | ||||||
|     <body> |     <body> | ||||||
|         <script src='https://source.zoom.us/1.9.0/lib/vendor/react.min.js'></script> |         <script src='https://source.zoom.us/1.9.1/lib/vendor/react.min.js'></script> | ||||||
|         <script src='https://source.zoom.us/1.9.0/lib/vendor/react-dom.min.js'></script> |         <script src='https://source.zoom.us/1.9.1/lib/vendor/react-dom.min.js'></script> | ||||||
|         <script src='https://source.zoom.us/1.9.0/lib/vendor/redux.min.js'></script> |         <script src='https://source.zoom.us/1.9.1/lib/vendor/redux.min.js'></script> | ||||||
|         <script src='https://source.zoom.us/1.9.0/lib/vendor/redux-thunk.min.js'></script> |         <script src='https://source.zoom.us/1.9.1/lib/vendor/redux-thunk.min.js'></script> | ||||||
|         <script src='https://source.zoom.us/1.9.0/lib/vendor/jquery.min.js'></script> |         <script src='https://source.zoom.us/1.9.1/lib/vendor/jquery.min.js'></script> | ||||||
|         <script src='https://source.zoom.us/1.9.0/lib/vendor/lodash.min.js'></script> |         <script src='https://source.zoom.us/1.9.1/lib/vendor/lodash.min.js'></script> | ||||||
|         <script src='https://source.zoom.us/zoom-meeting-1.9.0.min.js'></script> |         <script src='https://source.zoom.us/zoom-meeting-1.9.1.min.js'></script> | ||||||
|         <script src='https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9/crypto-js.min.js'></script> |         <script src='https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9/crypto-js.min.js'></script> | ||||||
|         <script type='text/javascript'> |         <script type='text/javascript'> | ||||||
| 
 | 
 | ||||||
|  | @ -19,7 +19,7 @@ | ||||||
|             console.log(JSON.stringify(ZoomMtg.checkSystemRequirements())); |             console.log(JSON.stringify(ZoomMtg.checkSystemRequirements())); | ||||||
| 
 | 
 | ||||||
|             console.log('Initializing Zoom...'); |             console.log('Initializing Zoom...'); | ||||||
|             ZoomMtg.setZoomJSLib('https://source.zoom.us/1.9.0/lib', '/av'); |             ZoomMtg.setZoomJSLib('https://source.zoom.us/1.9.1/lib', '/av'); | ||||||
|             ZoomMtg.preLoadWasm(); |             ZoomMtg.preLoadWasm(); | ||||||
|             ZoomMtg.prepareJssdk(); |             ZoomMtg.prepareJssdk(); | ||||||
| 
 | 
 | ||||||
|  | @ -89,11 +89,6 @@ | ||||||
|                         passWord: config.passWord, |                         passWord: config.passWord, | ||||||
|                         success(res) { |                         success(res) { | ||||||
|                              console.log('JOIN SUCCESS') |                              console.log('JOIN SUCCESS') | ||||||
|                              ZoomMtg.getAttendeeslist({ |  | ||||||
|                                 success: function (res) { |  | ||||||
|                                         console.log(res, "get getAttendeeslist"); |  | ||||||
|                                    } |  | ||||||
|                                 }); |  | ||||||
|                         }, |                         }, | ||||||
|                         error(res) { |                         error(res) { | ||||||
|                             console.warn('JOIN ERROR') |                             console.warn('JOIN ERROR') | ||||||
|  |  | ||||||
|  | @ -70,17 +70,17 @@ public class ZoomWindowScriptResolverTest { | ||||||
|                 "<html>\r\n" |                 "<html>\r\n" | ||||||
|                         + "    <head>\r\n" |                         + "    <head>\r\n" | ||||||
|                         + "        <meta charset='utf-8' />\r\n" |                         + "        <meta charset='utf-8' />\r\n" | ||||||
|                         + "        <link type='text/css' rel='stylesheet' href='https://source.zoom.us/1.9.0/css/bootstrap.css' />\r\n" |                         + "        <link type='text/css' rel='stylesheet' href='https://source.zoom.us/1.9.1/css/bootstrap.css' />\r\n" | ||||||
|                         + "        <link type='text/css' rel='stylesheet' href='https://source.zoom.us/1.9.0/css/react-select.css' />\r\n" |                         + "        <link type='text/css' rel='stylesheet' href='https://source.zoom.us/1.9.1/css/react-select.css' />\r\n" | ||||||
|                         + "    </head>\r\n" |                         + "    </head>\r\n" | ||||||
|                         + "    <body>\r\n" |                         + "    <body>\r\n" | ||||||
|                         + "        <script src='https://source.zoom.us/1.9.0/lib/vendor/react.min.js'></script>\r\n" |                         + "        <script src='https://source.zoom.us/1.9.1/lib/vendor/react.min.js'></script>\r\n" | ||||||
|                         + "        <script src='https://source.zoom.us/1.9.0/lib/vendor/react-dom.min.js'></script>\r\n" |                         + "        <script src='https://source.zoom.us/1.9.1/lib/vendor/react-dom.min.js'></script>\r\n" | ||||||
|                         + "        <script src='https://source.zoom.us/1.9.0/lib/vendor/redux.min.js'></script>\r\n" |                         + "        <script src='https://source.zoom.us/1.9.1/lib/vendor/redux.min.js'></script>\r\n" | ||||||
|                         + "        <script src='https://source.zoom.us/1.9.0/lib/vendor/redux-thunk.min.js'></script>\r\n" |                         + "        <script src='https://source.zoom.us/1.9.1/lib/vendor/redux-thunk.min.js'></script>\r\n" | ||||||
|                         + "        <script src='https://source.zoom.us/1.9.0/lib/vendor/jquery.min.js'></script>\r\n" |                         + "        <script src='https://source.zoom.us/1.9.1/lib/vendor/jquery.min.js'></script>\r\n" | ||||||
|                         + "        <script src='https://source.zoom.us/1.9.0/lib/vendor/lodash.min.js'></script>\r\n" |                         + "        <script src='https://source.zoom.us/1.9.1/lib/vendor/lodash.min.js'></script>\r\n" | ||||||
|                         + "        <script src='https://source.zoom.us/zoom-meeting-1.9.0.min.js'></script>\r\n" |                         + "        <script src='https://source.zoom.us/zoom-meeting-1.9.1.min.js'></script>\r\n" | ||||||
|                         + "        <script src='https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9/crypto-js.min.js'></script>\r\n" |                         + "        <script src='https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9/crypto-js.min.js'></script>\r\n" | ||||||
|                         + "        <script type='text/javascript'>\r\n" |                         + "        <script type='text/javascript'>\r\n" | ||||||
|                         + "\r\n" |                         + "\r\n" | ||||||
|  | @ -88,7 +88,7 @@ public class ZoomWindowScriptResolverTest { | ||||||
|                         + "            console.log(JSON.stringify(ZoomMtg.checkSystemRequirements()));\r\n" |                         + "            console.log(JSON.stringify(ZoomMtg.checkSystemRequirements()));\r\n" | ||||||
|                         + "\r\n" |                         + "\r\n" | ||||||
|                         + "            console.log('Initializing Zoom...');\r\n" |                         + "            console.log('Initializing Zoom...');\r\n" | ||||||
|                         + "            ZoomMtg.setZoomJSLib('https://source.zoom.us/1.9.0/lib', '/av');\r\n" |                         + "            ZoomMtg.setZoomJSLib('https://source.zoom.us/1.9.1/lib', '/av');\r\n" | ||||||
|                         + "            ZoomMtg.preLoadWasm();\r\n" |                         + "            ZoomMtg.preLoadWasm();\r\n" | ||||||
|                         + "            ZoomMtg.prepareJssdk();\r\n" |                         + "            ZoomMtg.prepareJssdk();\r\n" | ||||||
|                         + "\r\n" |                         + "\r\n" | ||||||
|  | @ -158,11 +158,6 @@ public class ZoomWindowScriptResolverTest { | ||||||
|                         + "                        passWord: config.passWord,\r\n" |                         + "                        passWord: config.passWord,\r\n" | ||||||
|                         + "                        success(res) {\r\n" |                         + "                        success(res) {\r\n" | ||||||
|                         + "                             console.log('JOIN SUCCESS')\r\n" |                         + "                             console.log('JOIN SUCCESS')\r\n" | ||||||
|                         + "                             ZoomMtg.getAttendeeslist({\r\n" |  | ||||||
|                         + "                                success: function (res) {\r\n" |  | ||||||
|                         + "                                        console.log(res, \"get getAttendeeslist\");\r\n" |  | ||||||
|                         + "                                   }\r\n" |  | ||||||
|                         + "                                });\r\n" |  | ||||||
|                         + "                        },\r\n" |                         + "                        },\r\n" | ||||||
|                         + "                        error(res) {\r\n" |                         + "                        error(res) {\r\n" | ||||||
|                         + "                            console.warn('JOIN ERROR')\r\n" |                         + "                            console.warn('JOIN ERROR')\r\n" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti