Skip to content

Commit

Permalink
Merge https://github.com/TEAMMATES/teammates into feedback-num-scale-…
Browse files Browse the repository at this point in the history
…qn-e2e
  • Loading branch information
marquestye committed Mar 26, 2024
2 parents aa60ac8 + a02f444 commit f96316d
Show file tree
Hide file tree
Showing 27 changed files with 1,626 additions and 44 deletions.
2 changes: 1 addition & 1 deletion docs/e2e-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Before running tests, modify `src/e2e/resources/test.properties` if necessary, e
<panel header="#### Using Chrome" no-close>

* You need to use chromedriver for testing with Chrome.
* Download the latest stable chromedriver from [here](https://sites.google.com/a/chromium.org/chromedriver/downloads).
* Download the latest stable chromedriver from [here](https://chromedriver.chromium.org/downloads).
The site will also inform the versions of Chrome that can be used with the driver.
* Specify the path to the chromedriver executable in `test.chromedriver.path` value in `test.properties`.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package teammates.client.scripts.sql;

// CHECKSTYLE.OFF:ImportOrder
import com.googlecode.objectify.cmd.Query;

import jakarta.persistence.criteria.CriteriaDelete;

import teammates.common.util.HibernateUtil;
import teammates.storage.sqlentity.AccountRequest;

// CHECKSTYLE.ON:ImportOrder

/**
* Data migration class for account request entity.
*/
Expand Down Expand Up @@ -33,7 +39,8 @@ protected boolean isPreview() {
*/
@Override
protected void setMigrationCriteria() {
// No migration criteria currently needed.
// Prepare clean db before migration
cleanAccountRequestInSql();
}

/**
Expand Down Expand Up @@ -73,4 +80,15 @@ protected void migrateEntity(teammates.storage.entity.AccountRequest oldEntity)

saveEntityDeferred(newEntity);
}

private void cleanAccountRequestInSql() {
HibernateUtil.beginTransaction();

CriteriaDelete<AccountRequest> cdAccountReq = HibernateUtil.getCriteriaBuilder()
.createCriteriaDelete(AccountRequest.class);
cdAccountReq.from(AccountRequest.class);
HibernateUtil.executeDelete(cdAccountReq);

HibernateUtil.commitTransaction();
}
}
2 changes: 1 addition & 1 deletion src/client/java/teammates/client/scripts/sql/SeedDb.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ protected void persistAdditionalData() {
String accountRequestEmail = String.format("Account Email %s", i);
String accountRequestInstitute = String.format("Account Institute %s", i);
AccountRequest accountRequest = AccountRequestAttributes
.builder(accountRequestName, accountRequestEmail, accountRequestInstitute)
.builder(accountRequestEmail, accountRequestInstitute, accountRequestName)
.withRegisteredAt(Instant.now()).build().toEntity();

String accountGoogleId = String.format("Account Google ID %s", i);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
package teammates.client.scripts.sql;

// CHECKSTYLE.OFF:ImportOrder
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
Expand All @@ -17,7 +13,6 @@

import teammates.common.util.HibernateUtil;
import teammates.storage.entity.Account;
import teammates.storage.sqlentity.ReadNotification;

/**
* Class for verifying account attributes.
Expand Down Expand Up @@ -87,18 +82,25 @@ public boolean equals(teammates.storage.sqlentity.Account sqlEntity, Account dat
return false;
}

Map<String, Instant> datastoreReadNotifications = datastoreEntity.getReadNotifications();
List<ReadNotification> sqlReadNotifications = sqlEntity.getReadNotifications();
return true;

List<Instant> datastoreEndTimes = new ArrayList<Instant>(datastoreReadNotifications.values());
Collections.sort(datastoreEndTimes);
// Not verifying read notification as current datastore implementation does not remove notifications
// that have been deleted from account entities. During migration, the notification will not be
// migrated since it is deleted and read notification will fail during migration (foreign key error)
// causing the verification to fail

List<Instant> sqlEndTimes = new ArrayList<>();
for (ReadNotification sqlReadNotification : sqlReadNotifications) {
sqlEndTimes.add(sqlReadNotification.getNotification().getEndTime());
}
Collections.sort(sqlEndTimes);
// Map<String, Instant> datastoreReadNotifications = datastoreEntity.getReadNotifications();
// List<ReadNotification> sqlReadNotifications = sqlEntity.getReadNotifications();

// List<Instant> datastoreEndTimes = new ArrayList<Instant>(datastoreReadNotifications.values());
// Collections.sort(datastoreEndTimes);

// List<Instant> sqlEndTimes = new ArrayList<>();
// for (ReadNotification sqlReadNotification : sqlReadNotifications) {
// sqlEndTimes.add(sqlReadNotification.getNotification().getEndTime());
// }
// Collections.sort(sqlEndTimes);

return datastoreEndTimes.equals(sqlEndTimes);
// return datastoreEndTimes.equals(sqlEndTimes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package teammates.client.scripts.testdataconversion;

import java.io.File;
import java.io.IOException;
import java.util.List;

import org.apache.commons.io.FilenameUtils;

import com.google.gson.JsonObject;

import teammates.common.datatransfer.DataBundle;
import teammates.common.datatransfer.SqlDataBundle;
import teammates.common.exception.InvalidParametersException;
import teammates.common.util.JsonUtils;
import teammates.storage.sqlentity.Account;
import teammates.storage.sqlentity.AccountRequest;
import teammates.storage.sqlentity.Course;
import teammates.storage.sqlentity.DeadlineExtension;
import teammates.storage.sqlentity.FeedbackQuestion;
import teammates.storage.sqlentity.FeedbackResponse;
import teammates.storage.sqlentity.FeedbackResponseComment;
import teammates.storage.sqlentity.FeedbackSession;
import teammates.storage.sqlentity.Instructor;
import teammates.storage.sqlentity.Notification;
import teammates.storage.sqlentity.ReadNotification;
import teammates.storage.sqlentity.Section;
import teammates.storage.sqlentity.Student;
import teammates.storage.sqlentity.Team;
import teammates.test.FileHelper;

/**
* Class to create JSON test data in SQL format from a noSQL JSON file.
* File can be run using the gradle execScript task and accepts a single argument which is the JSON path
* ./gradlew execScript -PuserScript="testdataconversion/ConvertDatastoreJsonToSqlJson" --args="JSON_FILE_PATH_HERE"
*/
public class ConvertDatastoreJsonToSqlJson {
private DataStoreToSqlConverter entityConverter;
private DataBundle dataStoreBundle;
private SqlDataBundle sqlDataBundle;

private String[] entitiesReferencedForeignKeys = new String[] {
"course",
"feedbackSession",
"section",
"account",
"giverSection",
"recipientSection",
"notification"};

protected ConvertDatastoreJsonToSqlJson(File inputFile) throws IOException {
this.entityConverter = new DataStoreToSqlConverter();

this.dataStoreBundle = loadDataBundle(inputFile.getCanonicalPath());
}

private String removeWhitespace(String string) {
return string.replaceAll("\\s", "");
}

private DataBundle loadDataBundle(String pathToJsonFile) throws IOException {
String jsonString = FileHelper.readFile(pathToJsonFile);
return JsonUtils.fromJson(jsonString, DataBundle.class);
}

private void saveFile(String filePath, String content) throws IOException {
FileHelper.saveFile(filePath, content);
System.out.println(filePath + " created!");
}

/**
* Amends foreign key references to only have ID field.
*/
private void removeForeignKeyData(JsonObject obj) {
for (String entityName : entitiesReferencedForeignKeys) {
if (obj.get(entityName) != null) {
JsonObject entity = obj.get(entityName).getAsJsonObject();
for (String field : entity.deepCopy().keySet()) {
if (!"id".equals(field)) {
entity.remove(field);
}
}
}
}
}

/**
* Read datstore json file and creates a SQL equivalent.
*/
private void createSqlJson(File outputFile) throws IOException, InvalidParametersException {
sqlDataBundle = new SqlDataBundle();

migrateIndepedentEntities();
migrateDependentEntities();

// Iterates through all entities in JSON file and removes foreign entitity data except its ID
JsonObject sqlJsonString = JsonUtils.toJsonObject(sqlDataBundle);
for (String entityCollectionName : sqlJsonString.keySet()) {
JsonObject entityCollection = sqlJsonString.get(entityCollectionName).getAsJsonObject();
for (String entityName : entityCollection.getAsJsonObject().keySet()) {
JsonObject entity = entityCollection.get(entityName).getAsJsonObject();
removeForeignKeyData(entity);
}
}

String jsonString = JsonUtils.toJson(sqlJsonString);
saveFile(outputFile.getCanonicalPath(), jsonString + System.lineSeparator());
}

/**
* Migrate entities with no foreign key reference.
* Entities are account requests, usage statistics, courses, accouns, notifications
*/
private void migrateIndepedentEntities() {
assert sqlDataBundle != null;

dataStoreBundle.accounts.forEach((k, datastoreAccount) -> {
Account sqlAccount = entityConverter.convert(datastoreAccount);
sqlDataBundle.accounts.put(k, sqlAccount);
});

dataStoreBundle.courses.forEach((k, datastoreCourse) -> {
Course sqlCourse = entityConverter.convert(datastoreCourse);
sqlDataBundle.courses.put(k, sqlCourse);
});

dataStoreBundle.accountRequests.forEach((k, accountRequest) -> {
AccountRequest sqlAccountRequest = entityConverter.convert(accountRequest);
sqlDataBundle.accountRequests.put(k, sqlAccountRequest);
});

dataStoreBundle.notifications.forEach((k, notification) -> {
Notification sqlNotification = entityConverter.convert(notification);
sqlDataBundle.notifications.put(k, sqlNotification);
});
}

/**
* Migrate entities which have dependence on each other or on the independent entities.
* The order which the entities were migrated was generated using a topological sort
* of its foreign key dependencies.
* Dependent entities: feedback sessions, sections, teams, users, students, instructors,
* deadline extensions, feedback questions, read notifications,
* feedback responses and feedback response comments.
*/
private void migrateDependentEntities() {

dataStoreBundle.feedbackSessions.forEach((k, feedbackSession) -> {
FeedbackSession sqlFeedbackSession = entityConverter.convert(feedbackSession);
sqlDataBundle.feedbackSessions.put(k, sqlFeedbackSession);
});

dataStoreBundle.students.forEach((k, student) -> {
String jsonKey = removeWhitespace(String.format("%s-%s",
student.getCourse(), student.getSection()));

if (!sqlDataBundle.sections.containsKey(jsonKey)) {
Section sqlSection = entityConverter.createSection(student);
sqlDataBundle.sections.put(jsonKey, sqlSection);
}
});

dataStoreBundle.students.forEach((k, student) -> {
String jsonKey = removeWhitespace(String.format("%s-%s-%s",
student.getCourse(), student.getSection(), student.getTeam()));

if (!sqlDataBundle.teams.containsKey(jsonKey)) {
Team sqlTeam = entityConverter.createTeam(student);
sqlDataBundle.teams.put(jsonKey, sqlTeam);
}
});

dataStoreBundle.instructors.forEach((k, instructor) -> {
Instructor sqlInstructor = entityConverter.convert(instructor);
sqlDataBundle.instructors.put(k, sqlInstructor);
});

dataStoreBundle.students.forEach((k, student) -> {
Student sqlStudent = entityConverter.convert(student);
sqlDataBundle.students.put(k, sqlStudent);
});

dataStoreBundle.deadlineExtensions.forEach((k, deadlineExtension) -> {
DeadlineExtension sqlDeadline = entityConverter.convert(deadlineExtension);
sqlDataBundle.deadlineExtensions.put(k, sqlDeadline);
});

dataStoreBundle.feedbackQuestions.forEach((k, feedbackQuestion) -> {
FeedbackQuestion sqlFeedbackQuestion = entityConverter.convert(feedbackQuestion);
sqlDataBundle.feedbackQuestions.put(k, sqlFeedbackQuestion);
});

dataStoreBundle.accounts.forEach((k, account) -> {
List<ReadNotification> sqlReadNotifications = entityConverter.createReadNotifications(account);
sqlReadNotifications.forEach(notif -> {
String jsonKey = removeWhitespace(String.format("%s-%s",
notif.getNotification().getTitle(), account.getEmail()));
sqlDataBundle.readNotifications.put(jsonKey, notif);
});
});

dataStoreBundle.feedbackResponses.forEach((k, feedbackResponse) -> {
FeedbackResponse sqlFeedbackResponse = entityConverter.convert(feedbackResponse);
sqlDataBundle.feedbackResponses.put(k, sqlFeedbackResponse);
});

dataStoreBundle.feedbackResponseComments.forEach((k, feedbackReponseComment) -> {
FeedbackResponseComment sqlFeedbackResponseComment = entityConverter.convert(feedbackReponseComment);
sqlDataBundle.feedbackResponseComments.put(k, sqlFeedbackResponseComment);
});
}

public static void main(String[] args) throws IOException, InvalidParametersException {
if (args.length > 0) {
File inputFile = new File(args[0]);
String fileExtension = FilenameUtils.getExtension(inputFile.getName());
if (!"json".equals(fileExtension)) {
throw new InvalidParametersException("The file provided is not a JSON file");
}

ConvertDatastoreJsonToSqlJson script = new ConvertDatastoreJsonToSqlJson(inputFile);
String outputFileName = FilenameUtils.getBaseName(inputFile.getName()) + "Sql.json";
File outputFile = new File(inputFile.getParent(), outputFileName);
script.createSqlJson(outputFile);
} else {
throw new InvalidParametersException("Required the path of the script to convert");
}
}
}
Loading

0 comments on commit f96316d

Please sign in to comment.