From e435f17078295e98185a4fd115aff840e9ad26dc Mon Sep 17 00:00:00 2001 From: Nicolas <25302138+NicolasCwy@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:51:17 +0800 Subject: [PATCH] [#12920] Create script to migrate noSQL test data to SQL schema format (#12922) * Add classes to migrate test json data * Add toposort script * Add function to remove foreign key data * Cleanup * WIP * Simplify keys for students and instructors * Fix lint issues * Output SQL JSON in same folder as JSON * Change output file name * Fix bug: wrong jsonkey used * Fix lint error * Make section and team name unique * Set read notification key to be unique * Delete python file --- .../ConvertDatastoreJsonToSqlJson.java | 228 ++++++++++ .../DataStoreToSqlConverter.java | 392 ++++++++++++++++++ .../testdataconversion/UuidGenerator.java | 31 ++ .../testdataconversion/package-info.java | 4 + .../java/teammates/common/util/JsonUtils.java | 7 + 5 files changed, 662 insertions(+) create mode 100644 src/client/java/teammates/client/scripts/testdataconversion/ConvertDatastoreJsonToSqlJson.java create mode 100644 src/client/java/teammates/client/scripts/testdataconversion/DataStoreToSqlConverter.java create mode 100644 src/client/java/teammates/client/scripts/testdataconversion/UuidGenerator.java create mode 100644 src/client/java/teammates/client/scripts/testdataconversion/package-info.java diff --git a/src/client/java/teammates/client/scripts/testdataconversion/ConvertDatastoreJsonToSqlJson.java b/src/client/java/teammates/client/scripts/testdataconversion/ConvertDatastoreJsonToSqlJson.java new file mode 100644 index 00000000000..e705008232c --- /dev/null +++ b/src/client/java/teammates/client/scripts/testdataconversion/ConvertDatastoreJsonToSqlJson.java @@ -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 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"); + } + } +} diff --git a/src/client/java/teammates/client/scripts/testdataconversion/DataStoreToSqlConverter.java b/src/client/java/teammates/client/scripts/testdataconversion/DataStoreToSqlConverter.java new file mode 100644 index 00000000000..69acaad7975 --- /dev/null +++ b/src/client/java/teammates/client/scripts/testdataconversion/DataStoreToSqlConverter.java @@ -0,0 +1,392 @@ +package teammates.client.scripts.testdataconversion; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import teammates.common.datatransfer.InstructorPermissionRole; +import teammates.common.datatransfer.attributes.AccountAttributes; +import teammates.common.datatransfer.attributes.AccountRequestAttributes; +import teammates.common.datatransfer.attributes.CourseAttributes; +import teammates.common.datatransfer.attributes.DeadlineExtensionAttributes; +import teammates.common.datatransfer.attributes.FeedbackQuestionAttributes; +import teammates.common.datatransfer.attributes.FeedbackResponseAttributes; +import teammates.common.datatransfer.attributes.FeedbackResponseCommentAttributes; +import teammates.common.datatransfer.attributes.FeedbackSessionAttributes; +import teammates.common.datatransfer.attributes.InstructorAttributes; +import teammates.common.datatransfer.attributes.NotificationAttributes; +import teammates.common.datatransfer.attributes.StudentAttributes; +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; + +/** + * Helper class to convert entities from its noSQL to SQL format. + */ +public class DataStoreToSqlConverter { + private String uuidPrefix = "00000000-0000-4000-8000-"; + private int initialAccountNumber = 1; + private int initialAccountRequestNumber = 101; + private int initialSectionNumber = 201; + private int initialTeamNumber = 301; + private int initialDeadlineExtensionNumber = 401; + private int initialInstructorNumber = 501; + private int initialStudentNumber = 601; + private int intitialFeedbackSessionNumber = 701; + private int initialFeedbackQuestionNumber = 801; + private int intialFeedbackResponseNumber = 901; + private int initialNotificationNumber = 1101; + private int initialReadNotificationNumber = 1201; + + private UuidGenerator accountUuidGenerator = new UuidGenerator(initialAccountNumber, uuidPrefix); + private UuidGenerator accounRequestUuidGenerator = new UuidGenerator(initialAccountRequestNumber, uuidPrefix); + private UuidGenerator sectionUuidGenerator = new UuidGenerator(initialSectionNumber, uuidPrefix); + private UuidGenerator teamUuidGenerator = new UuidGenerator(initialTeamNumber, uuidPrefix); + private UuidGenerator deadlineExtensionUuidGenerator = new UuidGenerator(initialDeadlineExtensionNumber, uuidPrefix); + private UuidGenerator instructorUuidGenerator = new UuidGenerator(initialInstructorNumber, uuidPrefix); + private UuidGenerator studentUuidGenerator = new UuidGenerator(initialStudentNumber, uuidPrefix); + private UuidGenerator feedbackSessionUuidGenerator = new UuidGenerator(intitialFeedbackSessionNumber, uuidPrefix); + private UuidGenerator feedbackQuestionUuidGenerator = new UuidGenerator(initialFeedbackQuestionNumber, uuidPrefix); + private UuidGenerator feedbackResponseUuidGenerator = new UuidGenerator(intialFeedbackResponseNumber, uuidPrefix); + private UuidGenerator notificationUuidGenerator = new UuidGenerator(initialNotificationNumber, uuidPrefix); + private UuidGenerator readNotificationUuidGenerator = new UuidGenerator(initialReadNotificationNumber, uuidPrefix); + + private long initialFeedbackResponseCommentId; + + // Maps google id to account + private Map accounts = new HashMap<>(); + // Maps old id to courses + private Map courses = new HashMap<>(); + + // Maps question id to question + private Map feedbackQuestions = new HashMap<>(); + + // Map course%feedbackSession to feedbackSection + private Map feedbackSessions = new HashMap<>(); + + // Maps notification id to notification + private Map notifications = new HashMap<>(); + + // Map course%section to section + private Map sections = new HashMap<>(); + + private long getNextFeedbackResponseCommentId() { + long nextId = initialFeedbackResponseCommentId; + initialFeedbackResponseCommentId += 1; + return nextId; + } + + private String generateSectionKey(StudentAttributes student) { + return String.format("%s-%s", student.getCourse(), student.getSection()); + } + + private String generateSectionKey(String courseId, String sectionName) { + return String.format("%s-%s", courseId, sectionName); + } + + private String generatefeedbackSessionKey(FeedbackSessionAttributes feedbackSession) { + return String.format("%s-%s", feedbackSession.getCourseId(), feedbackSession.getFeedbackSessionName()); + } + + private String generatefeedbackSessionKey(String courseId, String feedbackSessionName) { + return String.format("%s-%s", courseId, feedbackSessionName); + } + + /** + * Converts Account from its noSQL to SQL entity. + */ + protected Account convert(AccountAttributes accAttr) { + Account sqlAccount = new Account(accAttr.getGoogleId(), + accAttr.getName(), + accAttr.getEmail()); + + UUID uuid = accountUuidGenerator.generateUuid(); + sqlAccount.setId(uuid); + + accounts.put(accAttr.getGoogleId(), sqlAccount); + + return sqlAccount; + } + + /** + * Converts Account Request from its noSQL to SQL entity. + */ + protected AccountRequest convert(AccountRequestAttributes accReqAttr) { + AccountRequest sqlAccountRequest = new AccountRequest(accReqAttr.getEmail(), + accReqAttr.getName(), + accReqAttr.getInstitute()); + + sqlAccountRequest.setCreatedAt(accReqAttr.getCreatedAt()); + sqlAccountRequest.setRegisteredAt(accReqAttr.getRegisteredAt()); + sqlAccountRequest.setRegistrationKey(accReqAttr.getRegistrationKey()); + + UUID uuid = accounRequestUuidGenerator.generateUuid(); + sqlAccountRequest.setId(uuid); + + return sqlAccountRequest; + } + + /** + * Converts Course from its noSQL to SQL entity. + */ + protected Course convert(CourseAttributes courseAttr) { + Course sqlCourse = new Course(courseAttr.getId(), + courseAttr.getName(), + courseAttr.getTimeZone(), + courseAttr.getInstitute()); + + sqlCourse.setDeletedAt(courseAttr.getDeletedAt()); + sqlCourse.setCreatedAt(courseAttr.getCreatedAt()); + + courses.put(courseAttr.getId(), sqlCourse); + + return sqlCourse; + } + + /** + * Converts Notification from its noSQL to SQL entity. + */ + protected Notification convert(NotificationAttributes notifAttr) { + Notification sqlNotification = new Notification(notifAttr.getStartTime(), + notifAttr.getEndTime(), + notifAttr.getStyle(), + notifAttr.getTargetUser(), + notifAttr.getTitle(), + notifAttr.getMessage()); + + sqlNotification.setCreatedAt(notifAttr.getCreatedAt()); + + if (notifAttr.isShown()) { + sqlNotification.setShown(); + } + + UUID uuid = notificationUuidGenerator.generateUuid(); + sqlNotification.setId(uuid); + + notifications.put(notifAttr.getNotificationId(), sqlNotification); + + return sqlNotification; + } + + /** + * Converts Feedback Session from its noSQL to SQL entity. + */ + protected FeedbackSession convert(FeedbackSessionAttributes fsAttr) { + Duration gracePeriod = Duration.ofMinutes(fsAttr.getGracePeriodMinutes()); + Course sqlCourse = courses.get(fsAttr.getCourseId()); + FeedbackSession sqlFs = new FeedbackSession( + fsAttr.getFeedbackSessionName(), + sqlCourse, + fsAttr.getCreatorEmail(), + fsAttr.getInstructions(), + fsAttr.getStartTime(), + fsAttr.getEndTime(), + fsAttr.getSessionVisibleFromTime(), + fsAttr.getResultsVisibleFromTime(), + gracePeriod, + fsAttr.isOpeningEmailEnabled(), + fsAttr.isClosingEmailEnabled(), + fsAttr.isPublishedEmailEnabled()); + + sqlFs.setCreatedAt(fsAttr.getCreatedTime()); + sqlFs.setDeletedAt(fsAttr.getDeletedTime()); + sqlFs.setId(feedbackSessionUuidGenerator.generateUuid()); + + feedbackSessions.put(generatefeedbackSessionKey(fsAttr), sqlFs); + + return sqlFs; + } + + /** + * Converts Instructor from its noSQL to SQL entity. + */ + protected Instructor convert(InstructorAttributes instructor) { + Course sqlCourse = courses.get(instructor.getCourseId()); + Account sqlAccount = accounts.get(instructor.getGoogleId()); + + InstructorPermissionRole role = InstructorPermissionRole.getEnum(instructor.getRole()); + + Instructor sqlInstructor = new Instructor(sqlCourse, + instructor.getName(), + instructor.getEmail(), + instructor.isDisplayedToStudents(), + instructor.getDisplayedName(), + role, + instructor.getPrivileges()); + sqlInstructor.setId(instructorUuidGenerator.generateUuid()); + sqlInstructor.setAccount(sqlAccount); + + return sqlInstructor; + } + + /** + * Converts Student from its noSQL to SQL entity. + */ + protected Student convert(StudentAttributes student) { + Course sqlCourse = courses.get(student.getCourse()); + Account sqlAccount = accounts.get(student.getGoogleId()); + + Student sqlStudent = new Student(sqlCourse, + student.getName(), + student.getEmail(), + student.getComments()); + + sqlStudent.setId(studentUuidGenerator.generateUuid()); + sqlStudent.setAccount(sqlAccount); + + return sqlStudent; + } + + /** + * Converts Deadline Extension from its noSQL to SQL entity. + */ + protected DeadlineExtension convert(DeadlineExtensionAttributes deadlineExtension) { + FeedbackSession sqlFeedbackSession = feedbackSessions.get( + generatefeedbackSessionKey(deadlineExtension.getCourseId(), deadlineExtension.getFeedbackSessionName())); + + // User is not included since DataBundleLogic.java does not read users from this attribute + DeadlineExtension sqlDE = new DeadlineExtension(null, + sqlFeedbackSession, + deadlineExtension.getEndTime()); + + sqlDE.setClosingSoonEmailSent(deadlineExtension.getSentClosingEmail()); + sqlDE.setCreatedAt(deadlineExtension.getCreatedAt()); + sqlDE.setId(deadlineExtensionUuidGenerator.generateUuid()); + + return sqlDE; + } + + /** + * Converts Feedback Question from its noSQL to SQL entity. + */ + protected FeedbackQuestion convert(FeedbackQuestionAttributes feedbackQuestion) { + FeedbackSession sqlFeedbackSession = feedbackSessions.get( + generatefeedbackSessionKey(feedbackQuestion.getCourseId(), feedbackQuestion.getFeedbackSessionName())); + + FeedbackQuestion sqlFq = FeedbackQuestion.makeQuestion(sqlFeedbackSession, + feedbackQuestion.getQuestionNumber(), + feedbackQuestion.getQuestionDescription(), + feedbackQuestion.getGiverType(), + feedbackQuestion.getRecipientType(), + feedbackQuestion.getNumberOfEntitiesToGiveFeedbackTo(), + feedbackQuestion.getShowResponsesTo(), + feedbackQuestion.getShowGiverNameTo(), + feedbackQuestion.getShowRecipientNameTo(), + feedbackQuestion.getQuestionDetails()); + + sqlFq.setCreatedAt(feedbackQuestion.getCreatedAt()); + sqlFq.setId(feedbackQuestionUuidGenerator.generateUuid()); + + return sqlFq; + } + + /** + * Converts Feedback Response from its noSQL to SQL entity. + */ + protected FeedbackResponse convert(FeedbackResponseAttributes feedbackResponse) { + FeedbackQuestion sqlFeedbackQuestion = feedbackQuestions.get(feedbackResponse.getFeedbackQuestionId()); + + Section sqlGiverSection = sections.get(generateSectionKey(feedbackResponse.getCourseId(), + feedbackResponse.getGiverSection())); + + Section sqlReceiverSection = sections.get(generateSectionKey(feedbackResponse.getCourseId(), + feedbackResponse.getRecipientSection())); + + FeedbackResponse sqlFeedbackResponse = FeedbackResponse.makeResponse( + sqlFeedbackQuestion, + feedbackResponse.getGiver(), + sqlGiverSection, + feedbackResponse.getRecipient(), + sqlReceiverSection, + feedbackResponse.getResponseDetails()); + + sqlFeedbackResponse.setId(feedbackResponseUuidGenerator.generateUuid()); + sqlFeedbackResponse.setCreatedAt(feedbackResponse.getCreatedAt()); + + return sqlFeedbackResponse; + } + + /** + * Converts Feedback Response Comment from its noSQL to SQL entity. + */ + protected FeedbackResponseComment convert(FeedbackResponseCommentAttributes feedbackReponseComment) { + Section sqlGiverSection = sections.get(generateSectionKey(feedbackReponseComment.getCourseId(), + feedbackReponseComment.getGiverSection())); + + Section sqlReceiverSection = sections.get(generateSectionKey(feedbackReponseComment.getCourseId(), + feedbackReponseComment.getReceiverSection())); + + FeedbackResponseComment sqlFrc = new FeedbackResponseComment(null, + feedbackReponseComment.getCommentGiver(), + feedbackReponseComment.getCommentGiverType(), + sqlGiverSection, + sqlReceiverSection, + feedbackReponseComment.getCommentText(), + feedbackReponseComment.isVisibilityFollowingFeedbackQuestion(), + feedbackReponseComment.isCommentFromFeedbackParticipant(), + feedbackReponseComment.getShowCommentTo(), + feedbackReponseComment.getShowGiverNameTo(), + feedbackReponseComment.getLastEditorEmail()); + + sqlFrc.setId(getNextFeedbackResponseCommentId()); + sqlFrc.setCreatedAt(feedbackReponseComment.getCreatedAt()); + + return sqlFrc; + } + + /** + * Creates SQL Section from noSQL Student attribute. + */ + protected Section createSection(StudentAttributes student) { + Course sqlCourse = courses.get(student.getCourse()); + Section sqlSection = new Section(sqlCourse, student.getSection()); + + sqlSection.setId(sectionUuidGenerator.generateUuid()); + + sections.put(generateSectionKey(student), sqlSection); + + return sqlSection; + } + + /** + * Creates SQL Team from noSQL Student attribute. + */ + protected Team createTeam(StudentAttributes student) { + Section sqlSection = sections.get(generateSectionKey(student)); + Team sqlTeam = new Team(sqlSection, student.getTeam()); + sqlTeam.setId(teamUuidGenerator.generateUuid()); + + return sqlTeam; + } + + /** + * Creates SQL Read Notifications from Account attributes. + */ + protected List createReadNotifications(AccountAttributes account) { + List sqlReadNotifications = new ArrayList<>(); + Account sqlAccount = accounts.get(account.getGoogleId()); + + account.getReadNotifications().forEach((notifId, endTime) -> { + Notification sqlNotification = notifications.get(notifId); + ReadNotification sqlReadNotification = new ReadNotification(sqlAccount, sqlNotification); + sqlReadNotification.setId(readNotificationUuidGenerator.generateUuid()); + sqlReadNotifications.add(sqlReadNotification); + }); + + return sqlReadNotifications; + } +} diff --git a/src/client/java/teammates/client/scripts/testdataconversion/UuidGenerator.java b/src/client/java/teammates/client/scripts/testdataconversion/UuidGenerator.java new file mode 100644 index 00000000000..feb83abd66b --- /dev/null +++ b/src/client/java/teammates/client/scripts/testdataconversion/UuidGenerator.java @@ -0,0 +1,31 @@ +package teammates.client.scripts.testdataconversion; + +import java.util.UUID; + +/** + * Generator that counts up to generate an ID for an entity. + */ +public class UuidGenerator { + int currId; + String uuidPrefix; + + protected UuidGenerator(int startId, String uuidPrefix) { + this.currId = startId; + this.uuidPrefix = uuidPrefix; + } + + private String leftPad(int digits, String string, Character paddingChar) { + return String.format("%" + digits + "s", string).replace(' ', paddingChar); + } + + /** + * Generates an ID for the test entity. + * This does not guarantee uniqueness between entities and is merely a counter + */ + protected UUID generateUuid() { + String trailingUuid = leftPad(12, Integer.toString(this.currId), '0'); + UUID uuid = UUID.fromString(uuidPrefix + trailingUuid); + this.currId += 1; + return uuid; + } +} diff --git a/src/client/java/teammates/client/scripts/testdataconversion/package-info.java b/src/client/java/teammates/client/scripts/testdataconversion/package-info.java new file mode 100644 index 00000000000..181de6513fe --- /dev/null +++ b/src/client/java/teammates/client/scripts/testdataconversion/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes to migrate noSQL test data. + */ +package teammates.client.scripts.testdataconversion; diff --git a/src/main/java/teammates/common/util/JsonUtils.java b/src/main/java/teammates/common/util/JsonUtils.java index d921ef06324..7f121801e05 100644 --- a/src/main/java/teammates/common/util/JsonUtils.java +++ b/src/main/java/teammates/common/util/JsonUtils.java @@ -82,6 +82,13 @@ private static Gson getGsonInstance(boolean prettyPrint) { return builder.create(); } + /** + * This creates a Gson object that can be reformatted to modify JSON output. + */ + public static JsonObject toJsonObject(Object src) { + return (JsonObject) getGsonInstance(true).toJsonTree(src); + } + /** * Serializes and pretty-prints the specified object into its equivalent JSON string. *