diff --git a/src/e2e/java/teammates/e2e/cases/sql/BaseE2ETestCase.java b/src/e2e/java/teammates/e2e/cases/sql/BaseE2ETestCase.java index fbfd60ea84b..6878da94e44 100644 --- a/src/e2e/java/teammates/e2e/cases/sql/BaseE2ETestCase.java +++ b/src/e2e/java/teammates/e2e/cases/sql/BaseE2ETestCase.java @@ -22,12 +22,15 @@ import teammates.e2e.util.TestProperties; import teammates.storage.sqlentity.FeedbackQuestion; import teammates.storage.sqlentity.FeedbackResponse; +import teammates.storage.sqlentity.FeedbackSession; import teammates.storage.sqlentity.Student; import teammates.test.BaseTestCaseWithSqlDatabaseAccess; import teammates.test.FileHelper; import teammates.test.ThreadHelper; import teammates.ui.output.FeedbackQuestionData; import teammates.ui.output.FeedbackResponseData; +import teammates.ui.output.FeedbackSessionData; +import teammates.ui.output.FeedbackSessionPublishStatus; import teammates.ui.output.StudentData; /** @@ -266,6 +269,26 @@ protected StudentData getStudent(Student student) { return getStudent(student.getCourseId(), student.getEmail()); } + FeedbackSessionData getFeedbackSession(String courseId, String feedbackSessionName) { + return BACKDOOR.getFeedbackSessionData(courseId, feedbackSessionName); + } + + @Override + protected FeedbackSessionData getFeedbackSession(FeedbackSession feedbackSession) { + return getFeedbackSession(feedbackSession.getCourse().getId(), feedbackSession.getName()); + } + + /** + * Checks if the feedback session is published. + */ + protected boolean isFeedbackSessionPublished(FeedbackSessionPublishStatus status) { + return status == FeedbackSessionPublishStatus.PUBLISHED; + } + + FeedbackSessionData getSoftDeletedSession(String feedbackSessionName, String instructorId) { + return BACKDOOR.getSoftDeletedSessionData(feedbackSessionName, instructorId); + } + /** * Puts the documents in the database using BACKDOOR. * @param dataBundle the data to be put in the database diff --git a/src/e2e/java/teammates/e2e/cases/sql/InstructorHomePageE2ETest.java b/src/e2e/java/teammates/e2e/cases/sql/InstructorHomePageE2ETest.java new file mode 100644 index 00000000000..239de4ce25c --- /dev/null +++ b/src/e2e/java/teammates/e2e/cases/sql/InstructorHomePageE2ETest.java @@ -0,0 +1,261 @@ +package teammates.e2e.cases.sql; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import teammates.common.util.AppUrl; +import teammates.common.util.Const; +import teammates.e2e.pageobjects.InstructorHomePage; +import teammates.e2e.util.TestProperties; +import teammates.storage.sqlentity.Course; +import teammates.storage.sqlentity.FeedbackSession; +import teammates.storage.sqlentity.Instructor; +import teammates.storage.sqlentity.Student; +import teammates.test.ThreadHelper; +import teammates.ui.output.FeedbackSessionData; + +/** + * SUT: {@link Const.WebPageURIs#INSTRUCTOR_HOME_PAGE}. + */ +public class InstructorHomePageE2ETest extends BaseE2ETestCase { + private Instructor instructor; + private Student studentToEmail; + private Course course; + private Course otherCourse; + + private FeedbackSession feedbackSessionAwaiting; + private FeedbackSession feedbackSessionOpen; + private FeedbackSession feedbackSessionClosed; + private FeedbackSession feedbackSessionPublished; + private FeedbackSession otherCourseSession; + + private String fileName; + + @Override + protected void prepareTestData() { + testData = loadSqlDataBundle("/InstructorHomePageE2ETestSql.json"); + studentToEmail = testData.students.get("IHome.charlie.d.tmms@IHome.CS2104"); + studentToEmail.setEmail(TestProperties.TEST_EMAIL); + testData = removeAndRestoreDataBundle(testData); + putDocuments(testData); + + instructor = testData.instructors.get("IHome.instr.CS2104"); + course = testData.courses.get("IHome.CS2104"); + otherCourse = testData.courses.get("IHome.CS1101"); + + feedbackSessionAwaiting = testData.feedbackSessions.get("Second Feedback Session"); + feedbackSessionOpen = testData.feedbackSessions.get("First Feedback Session"); + feedbackSessionClosed = testData.feedbackSessions.get("Third Feedback Session"); + feedbackSessionPublished = testData.feedbackSessions.get("Fourth Feedback Session"); + otherCourseSession = testData.feedbackSessions.get("CS1101 Session"); + + fileName = "/" + feedbackSessionOpen.getCourse().getId() + "_" + feedbackSessionOpen.getName() + + "_result.csv"; + } + + @BeforeClass + public void classSetup() { + deleteDownloadsFile(fileName); + } + + @Test + @Override + public void testAll() { + AppUrl url = createFrontendUrl(Const.WebPageURIs.INSTRUCTOR_HOME_PAGE); + InstructorHomePage homePage = loginToPage(url, InstructorHomePage.class, instructor.getGoogleId()); + + ______TS("verify loaded data"); + homePage.sortCoursesById(); + int courseIndex = 1; + int otherCourseIndex = 0; + // by default, sessions are sorted by end date in descending order + FeedbackSession[] courseSessions = { feedbackSessionOpen, feedbackSessionAwaiting, + feedbackSessionClosed, feedbackSessionPublished }; + FeedbackSession[] otherCourseSessions = { otherCourseSession }; + // use course index instead of searching for course in table to test sorted order of courses + homePage.verifyCourseTabDetails(otherCourseIndex, otherCourse, otherCourseSessions); + homePage.verifyCourseTabDetails(courseIndex, course, courseSessions); + + ______TS("notification banner is visible"); + assertTrue(homePage.isBannerVisible()); + + ______TS("verify response rate"); + for (int i = 0; i < courseSessions.length; i++) { + homePage.verifyResponseRate(courseIndex, i, getExpectedResponseRate(courseSessions[i])); + } + + ______TS("copy session with modified session timings"); + int sessionIndex = 1; + String newName = "Copied Name"; + FeedbackSession copiedSession = feedbackSessionAwaiting.getCopy(); + copiedSession.setCourse(otherCourse); + copiedSession.setName(newName); + copiedSession.setCreatedAt(Instant.now()); + int startHour = ZonedDateTime.ofInstant(copiedSession.getStartTime(), + ZoneId.of(copiedSession.getCourse().getTimeZone())).getHour(); + copiedSession.setStartTime(ZonedDateTime.now(ZoneId.of(otherCourse.getTimeZone())).plus(Duration.ofDays(2)) + .withHour(startHour).truncatedTo(ChronoUnit.HOURS).toInstant()); + int endHour = ZonedDateTime.ofInstant(copiedSession.getEndTime(), ZoneId.of(copiedSession.getCourse().getTimeZone())) + .getHour(); + copiedSession.setEndTime(ZonedDateTime.now(ZoneId.of(otherCourse.getTimeZone())).plus(Duration.ofDays(7)) + .withHour(endHour).truncatedTo(ChronoUnit.HOURS).toInstant()); + copiedSession.setSessionVisibleFromTime(ZonedDateTime.now(ZoneId.of(otherCourse.getTimeZone())) + .minus(Duration.ofDays(28)).withHour(startHour).truncatedTo(ChronoUnit.HOURS).toInstant()); + copiedSession.setResultsVisibleFromTime(Const.TIME_REPRESENTS_LATER); + homePage.copySession(courseIndex, sessionIndex, otherCourse, newName); + + homePage.waitForConfirmationModalAndClickOk(); + homePage = getNewPageInstance(url, InstructorHomePage.class); + homePage.sortCoursesByName(); + // flip index after sorting + courseIndex = 0; + otherCourseIndex = 1; + FeedbackSession[] otherCourseSessionsWithCopy = { copiedSession, otherCourseSession }; + homePage.verifyCourseTabDetails(otherCourseIndex, otherCourse, otherCourseSessionsWithCopy); + verifyPresentInDatabase(copiedSession); + + ______TS("copy session with same session timings"); + sessionIndex = 0; + newName = "Copied Name 2"; + FeedbackSession copiedSession2 = copiedSession.getCopy(); + copiedSession2.setName(newName); + copiedSession2.setCreatedAt(Instant.now()); + homePage.copySession(otherCourseIndex, sessionIndex, otherCourse, newName); + + homePage.verifyStatusMessage("The feedback session has been copied. " + + "Please modify settings/questions as necessary."); + homePage = getNewPageInstance(url, InstructorHomePage.class); + homePage.sortCoursesByName(); + FeedbackSession[] otherCourseSessionsWithTwoCopies = { copiedSession, copiedSession2, otherCourseSession }; + homePage.verifyCourseTabDetails(otherCourseIndex, otherCourse, otherCourseSessionsWithTwoCopies); + verifyPresentInDatabase(copiedSession2); + + ______TS("publish results"); + sessionIndex = 0; + feedbackSessionOpen.setResultsVisibleFromTime(Const.TIME_REPRESENTS_NOW); + homePage.publishSessionResults(courseIndex, sessionIndex); + + homePage.verifyStatusMessage("The feedback session has been published. " + + "Please allow up to 1 hour for all the notification emails to be sent out."); + homePage.verifySessionDetails(courseIndex, sessionIndex, feedbackSessionOpen); + verifySessionPublishedState(feedbackSessionOpen, true); + verifyEmailSent(studentToEmail.getEmail(), "TEAMMATES: Feedback session results published" + + " [Course: " + course.getName() + "][Feedback Session: " + + feedbackSessionOpen.getName() + "]"); + + ______TS("send reminder email to selected student"); + homePage.sendReminderEmailToSelectedStudent(courseIndex, sessionIndex, studentToEmail); + + homePage.verifyStatusMessage("Reminder e-mails have been sent out to those students" + + " and instructors. Please allow up to 1 hour for all the notification emails to be sent out."); + verifyEmailSent(studentToEmail.getEmail(), "TEAMMATES: Feedback session reminder" + + " [Course: " + course.getName() + "][Feedback Session: " + + feedbackSessionOpen.getName() + "]"); + + ______TS("send reminder email to all student non-submitters"); + homePage.sendReminderEmailToNonSubmitters(courseIndex, sessionIndex); + + homePage.verifyStatusMessage("Reminder e-mails have been sent out to those students" + + " and instructors. Please allow up to 1 hour for all the notification emails to be sent out."); + verifyEmailSent(studentToEmail.getEmail(), "TEAMMATES: Feedback session reminder" + + " [Course: " + course.getName() + "][Feedback Session: " + + feedbackSessionOpen.getName() + "]"); + ______TS("resend results link"); + homePage.resendResultsLink(courseIndex, sessionIndex, studentToEmail); + + homePage.verifyStatusMessage("Session published notification emails have been resent" + + " to those students and instructors. Please allow up to 1 hour for all the notification emails to be" + + " sent out."); + verifyEmailSent(studentToEmail.getEmail(), "TEAMMATES: Feedback session results published" + + " [Course: " + course.getName() + "][Feedback Session: " + + feedbackSessionOpen.getName() + "]"); + + ______TS("unpublish results"); + feedbackSessionOpen.setResultsVisibleFromTime(Const.TIME_REPRESENTS_LATER); + homePage.unpublishSessionResults(courseIndex, sessionIndex); + + homePage.verifyStatusMessage("The feedback session has been unpublished."); + homePage.verifySessionDetails(courseIndex, sessionIndex, feedbackSessionOpen); + verifySessionPublishedState(feedbackSessionOpen, false); + verifyEmailSent(studentToEmail.getEmail(), "TEAMMATES: Feedback session results unpublished" + + " [Course: " + course.getName() + "][Feedback Session: " + + feedbackSessionOpen.getName() + "]"); + + ______TS("download results"); + homePage.downloadResults(courseIndex, sessionIndex); + List expectedContent = Arrays.asList("Course,tm.e2e.IHome.CS2104", + "Session Name,First Feedback Session", "Question 1,Rate 5 other students' products"); + verifyDownloadedFile(fileName, expectedContent); + + ______TS("soft delete session"); + sessionIndex = 1; + copiedSession.setDeletedAt(Instant.now()); + homePage.deleteSession(otherCourseIndex, sessionIndex); + + homePage.verifyStatusMessage("The feedback session has been deleted. " + + "You can restore it from the 'Sessions' tab."); + homePage.sortCoursesByName(); + otherCourseIndex = 1; + FeedbackSession[] otherCourseSessionsWithCopyTwo = { copiedSession, otherCourseSession }; + homePage.verifyCourseTabDetails(otherCourseIndex, otherCourse, otherCourseSessionsWithCopyTwo); + assertNotNull(getSoftDeletedSession(copiedSession2.getName(), + instructor.getGoogleId())); + + ______TS("delete course"); + otherCourseIndex = 1; + homePage.deleteCourse(otherCourseIndex); + + homePage.verifyStatusMessage("The course " + otherCourse.getId() + " has been deleted. " + + "You can restore it from the Recycle Bin manually."); + homePage.verifyNumCourses(1); + assertTrue(BACKDOOR.isCourseInRecycleBin(otherCourse.getId())); + } + + private String getExpectedResponseRate(FeedbackSession session) { + String sessionName = session.getName(); + boolean hasQuestion = testData.feedbackQuestions.values() + .stream() + .anyMatch(q -> q.getFeedbackSessionName().equals(sessionName)); + + if (!hasQuestion) { + return "0 / 0"; + } + + long numStudents = testData.students.values() + .stream() + .filter(s -> s.getCourse().getId().equals(session.getCourse().getId())) + .count(); + + Set uniqueGivers = new HashSet<>(); + testData.feedbackResponses.values() + .stream() + .filter(r -> r.getFeedbackQuestion().getFeedbackSessionName().equals(sessionName)) + .forEach(r -> uniqueGivers.add(r.getGiver())); + int numResponses = uniqueGivers.size(); + + return numResponses + " / " + numStudents; + } + + private void verifySessionPublishedState(FeedbackSession feedbackSession, boolean state) { + int retryLimit = 5; + FeedbackSessionData actual = getFeedbackSession(feedbackSession.getCourse().getId(), + feedbackSession.getName()); + while (isFeedbackSessionPublished(actual.getPublishStatus()) != state && retryLimit > 0) { + retryLimit--; + ThreadHelper.waitFor(1000); + actual = getFeedbackSession(feedbackSession.getCourse().getId(), + feedbackSession.getName()); + } + assertEquals(isFeedbackSessionPublished(actual.getPublishStatus()), state); + } +} diff --git a/src/e2e/java/teammates/e2e/pageobjects/InstructorHomePage.java b/src/e2e/java/teammates/e2e/pageobjects/InstructorHomePage.java index 21dbce2247a..ab9ee676def 100644 --- a/src/e2e/java/teammates/e2e/pageobjects/InstructorHomePage.java +++ b/src/e2e/java/teammates/e2e/pageobjects/InstructorHomePage.java @@ -12,6 +12,9 @@ import teammates.common.datatransfer.attributes.CourseAttributes; import teammates.common.datatransfer.attributes.FeedbackSessionAttributes; import teammates.common.datatransfer.attributes.StudentAttributes; +import teammates.storage.sqlentity.Course; +import teammates.storage.sqlentity.FeedbackSession; +import teammates.storage.sqlentity.Student; /** * Represents the instructor home page. @@ -38,12 +41,29 @@ public void verifyCourseTabDetails(int courseTabIndex, CourseAttributes course, verifyTableBodyValues(getSessionsTable(courseTabIndex), expectedValues); } + public void verifyCourseTabDetails(int courseTabIndex, Course course, FeedbackSession[] sessions) { + String expectedDetails = "[" + course.getId() + "]: " + course.getName(); + assertEquals(getCourseDetails(courseTabIndex), expectedDetails); + + String[][] expectedValues = new String[sessions.length][5]; + for (int i = 0; i < sessions.length; i++) { + expectedValues[i] = getExpectedSessionDetails(sessions[i]); + } + verifyTableBodyValues(getSessionsTable(courseTabIndex), expectedValues); + } + public void verifySessionDetails(int courseTabIndex, int sessionIndex, FeedbackSessionAttributes session) { String[] expectedValues = getExpectedSessionDetails(session); WebElement sessionRow = getSessionsTable(courseTabIndex).findElements(By.cssSelector("tbody tr")).get(sessionIndex); verifyTableRowValues(sessionRow, expectedValues); } + public void verifySessionDetails(int courseTabIndex, int sessionIndex, FeedbackSession session) { + String[] expectedValues = getExpectedSessionDetails(session); + WebElement sessionRow = getSessionsTable(courseTabIndex).findElements(By.cssSelector("tbody tr")).get(sessionIndex); + verifyTableRowValues(sessionRow, expectedValues); + } + public void verifyNumCourses(int expectedNum) { assertEquals(getNumCourses(), expectedNum); } @@ -59,6 +79,13 @@ public void copySession(int courseTabIndex, int sessionIndex, CourseAttributes c click(browser.driver.findElement(By.id("btn-confirm-copy-course"))); } + public void copySession(int courseTabIndex, int sessionIndex, Course copyToCourse, String newSessionName) { + WebElement copyFsModal = clickCopyButtonInTable(courseTabIndex, sessionIndex); + fillTextBox(copyFsModal.findElement(By.id("copy-session-name")), newSessionName); + selectCourseToCopyToInModal(copyFsModal, copyToCourse.getId()); + click(browser.driver.findElement(By.id("btn-confirm-copy-course"))); + } + public void publishSessionResults(int courseTabIndex, int sessionIndex) { WebElement courseTab = getCourseTab(courseTabIndex); click(courseTab.findElement(By.className("btn-results-" + sessionIndex))); @@ -85,6 +112,18 @@ public void sendReminderEmailToSelectedStudent(int courseTabIndex, int sessionIn click(courseTab.findElement(By.className("btn-remind-" + sessionIndex))); } + public void sendReminderEmailToSelectedStudent(int courseTabIndex, int sessionIndex, Student student) { + WebElement courseTab = getCourseTab(courseTabIndex); + click(courseTab.findElement(By.className("btn-remind-" + sessionIndex))); + List remindSelectedButtons = browser.driver.findElements( + By.className("btn-remind-selected-" + sessionIndex) + ); + click(remindSelectedButtons.get(remindSelectedButtons.size() - 1)); + selectStudentToEmail(student.getEmail()); + click(browser.driver.findElement(By.id("btn-confirm-send-reminder"))); + click(courseTab.findElement(By.className("btn-remind-" + sessionIndex))); + } + public void sendReminderEmailToNonSubmitters(int courseTabIndex, int sessionIndex) { WebElement courseTab = getCourseTab(courseTabIndex); click(courseTab.findElement(By.className("btn-remind-" + sessionIndex))); @@ -104,6 +143,14 @@ public void resendResultsLink(int courseTabIndex, int sessionIndex, StudentAttri click(browser.driver.findElement(By.id("btn-confirm-resend-results"))); } + public void resendResultsLink(int courseTabIndex, int sessionIndex, Student student) { + WebElement courseTab = getCourseTab(courseTabIndex); + click(courseTab.findElement(By.className("btn-results-" + sessionIndex))); + click(waitForElementPresence(By.className("btn-resend-" + sessionIndex))); + selectStudentToEmail(student.getEmail()); + click(browser.driver.findElement(By.id("btn-confirm-resend-results"))); + } + public void downloadResults(int courseTabIndex, int sessionIndex) { WebElement courseTab = getCourseTab(courseTabIndex); click(courseTab.findElement(By.className("btn-results-" + sessionIndex))); @@ -183,6 +230,23 @@ private String[] getExpectedSessionDetails(FeedbackSessionAttributes session) { return details; } + private String[] getExpectedSessionDetails(FeedbackSession session) { + String[] details = new String[5]; + details[0] = session.getName(); + details[1] = getDateString(session.getStartTime(), session.getCourse().getTimeZone()); + details[2] = getDateString(session.getEndTime(), session.getCourse().getTimeZone()); + + if (session.isClosed()) { + details[3] = "Closed"; + } else if (session.isVisible() && (session.isOpened() || session.isInGracePeriod())) { + details[3] = "Open"; + } else { + details[3] = "Awaiting"; + } + details[4] = session.isPublished() ? "Published" : "Not Published"; + return details; + } + private String getResponseRate(int courseTabIndex, int sessionIndex) { WebElement showButton = null; try { diff --git a/src/e2e/resources/data/InstructorHomePageE2ETestSql.json b/src/e2e/resources/data/InstructorHomePageE2ETestSql.json new file mode 100644 index 00000000000..ec5fa8858b7 --- /dev/null +++ b/src/e2e/resources/data/InstructorHomePageE2ETestSql.json @@ -0,0 +1,452 @@ +{ + "accounts": { + "IHome.instr": { + "id": "00000000-0000-4000-8000-000000000001", + "googleId": "tm.e2e.IHome.instructor.tmms", + "name": "Teammates Test", + "email": "IHome.instructor.tmms@gmail.tmt" + } + }, + "accountRequests": {}, + "courses": { + "IHome.CS2104": { + "id": "tm.e2e.IHome.CS2104", + "name": "Programming Language Concepts", + "timeZone": "Asia/Singapore", + "institute": "TEAMMATES Test Institute 1", + "createdAt": "2012-04-01T23:58:00Z" + }, + "IHome.CS1101": { + "id": "tm.e2e.IHome.CS1101", + "name": "Programming Methodology", + "timeZone": "Asia/Singapore", + "institute": "TEAMMATES Test Institute 1", + "createdAt": "2013-04-01T23:59:00Z" + } + }, + "sections": { + "tm.e2e.IHome.CS2104-None": { + "id": "00000000-0000-4000-8000-000000000201", + "course": { + "id": "tm.e2e.IHome.CS2104" + }, + "name": "None" + }, + "tm.e2e.IHome.CS1101-None": { + "id": "00000000-0000-4000-8000-000000000202", + "course": { + "id": "tm.e2e.IHome.CS1101" + }, + "name": "None" + } + }, + "teams": { + "tm.e2e.IHome.CS2104-None-Team1": { + "id": "00000000-0000-4000-8000-000000000301", + "section": { + "id": "00000000-0000-4000-8000-000000000201" + }, + "name": "Team 1" + }, + "tm.e2e.IHome.CS2104-None-Team2": { + "id": "00000000-0000-4000-8000-000000000302", + "section": { + "id": "00000000-0000-4000-8000-000000000201" + }, + "name": "Team 2" + }, + "tm.e2e.IHome.CS1101-None-Team1": { + "id": "00000000-0000-4000-8000-000000000303", + "section": { + "id": "00000000-0000-4000-8000-000000000202" + }, + "name": "Team 1" + }, + "tm.e2e.IHome.CS1101-None-Team2": { + "id": "00000000-0000-4000-8000-000000000304", + "section": { + "id": "00000000-0000-4000-8000-000000000202" + }, + "name": "Team 2" + } + }, + "deadlineExtensions": {}, + "instructors": { + "IHome.instr.CS2104": { + "isDisplayedToStudents": true, + "displayName": "Instructor", + "role": "INSTRUCTOR_PERMISSION_ROLE_COOWNER", + "privileges": { + "courseLevel": { + "canModifyCourse": true, + "canModifyInstructor": true, + "canModifySession": true, + "canModifyStudent": true, + "canViewStudentInSections": true, + "canViewSessionInSections": true, + "canSubmitSessionInSections": true, + "canModifySessionCommentsInSections": true + }, + "sectionLevel": {}, + "sessionLevel": {} + }, + "id": "00000000-0000-4000-8000-000000000501", + "courseId": "tm.e2e.IHome.CS2104", + "course": { + "id": "tm.e2e.IHome.CS2104" + }, + "account": { + "id": "00000000-0000-4000-8000-000000000001" + }, + "name": "Teammates Test", + "email": "IHome.instructor.tmms@gmail.tmt" + }, + "IHome.instr.CS1101": { + "isDisplayedToStudents": true, + "displayName": "Instructor", + "role": "INSTRUCTOR_PERMISSION_ROLE_COOWNER", + "privileges": { + "courseLevel": { + "canModifyCourse": true, + "canModifyInstructor": true, + "canModifySession": true, + "canModifyStudent": true, + "canViewStudentInSections": true, + "canViewSessionInSections": true, + "canSubmitSessionInSections": true, + "canModifySessionCommentsInSections": true + }, + "sectionLevel": {}, + "sessionLevel": {} + }, + "id": "00000000-0000-4000-8000-000000000502", + "courseId": "tm.e2e.IHome.CS1101", + "course": { + "id": "tm.e2e.IHome.CS1101" + }, + "account": { + "id": "00000000-0000-4000-8000-000000000001" + }, + "name": "Teammates Test", + "email": "IHome.instructor.tmms@gmail.tmt" + } + }, + "students": { + "IHome.alice.b.tmms@IHome.CS2104": { + "comments": "This student's name is Alice Betsy", + "id": "00000000-0000-4000-8000-000000000601", + "courseId": "tm.e2e.IHome.CS2104", + "course": { + "id": "tm.e2e.IHome.CS2104" + }, + "team": { + "id": "00000000-0000-4000-8000-000000000301" + }, + "name": "Alice Betsy", + "email": "IHome.alice.b.tmms@gmail.tmt" + }, + "IHome.benny.c.tmms@IHome.CS2104": { + "comments": "This student's name is Benny Charles", + "id": "00000000-0000-4000-8000-000000000602", + "courseId": "tm.e2e.IHome.CS2104", + "course": { + "id": "tm.e2e.IHome.CS2104" + }, + "team": { + "id": "00000000-0000-4000-8000-000000000301" + }, + "name": "Benny Charles", + "email": "IHome.benny.c.tmms@gmail.tmt" + }, + "IHome.charlie.d.tmms@IHome.CS2104": { + "comments": "This student's name is Charlie Davis", + "id": "00000000-0000-4000-8000-000000000603", + "courseId": "tm.e2e.IHome.CS2104", + "course": { + "id": "tm.e2e.IHome.CS2104" + }, + "team": { + "id": "00000000-0000-4000-8000-000000000302" + }, + "name": "Charlie Davis", + "email": "IHome.charlie.d.tmms@gmail.tmt" + }, + "IHome.danny.e.tmms@IHome.CS2104": { + "comments": "This student's name is Danny Engrid", + "id": "00000000-0000-4000-8000-000000000604", + "courseId": "tm.e2e.IHome.CS2104", + "course": { + "id": "tm.e2e.IHome.CS2104" + }, + "team": { + "id": "00000000-0000-4000-8000-000000000302" + }, + "name": "Danny Engrid", + "email": "IHome.danny.e.tmms@gmail.tmt" + }, + "IHome.alice.b.tmms@IHome.CS1101": { + "comments": "This student's name is Alice Betsy", + "id": "00000000-0000-4000-8000-000000000605", + "courseId": "tm.e2e.IHome.CS1101", + "course": { + "id": "tm.e2e.IHome.CS1101" + }, + "team": { + "id": "00000000-0000-4000-8000-000000000303" + }, + "name": "Alice Betsy", + "email": "IHome.alice.b.tmms@gmail.tmt" + }, + "IHome.benny.c.tmms@IHome.CS1101": { + "comments": "This student's name is Benny Charles", + "id": "00000000-0000-4000-8000-000000000606", + "courseId": "tm.e2e.IHome.CS1101", + "course": { + "id": "tm.e2e.IHome.CS1101" + }, + "team": { + "id": "00000000-0000-4000-8000-000000000303" + }, + "name": "Benny Charles", + "email": "IHome.benny.c.tmms@gmail.tmt" + }, + "IHome.charlie.d.tmms@IHome.CS1101": { + "comments": "This student's name is Charlie Davis", + "id": "00000000-0000-4000-8000-000000000607", + "courseId": "tm.e2e.IHome.CS1101", + "course": { + "id": "tm.e2e.IHome.CS1101" + }, + "team": { + "id": "00000000-0000-4000-8000-000000000304" + }, + "name": "Charlie Davis", + "email": "IHome.charlie.d.tmms@gmail.tmt" + }, + "IHome.danny.e.tmms@IHome.CS1101": { + "comments": "This student's name is Danny Engrid", + "id": "00000000-0000-4000-8000-000000000608", + "courseId": "tm.e2e.IHome.CS1101", + "course": { + "id": "tm.e2e.IHome.CS1101" + }, + "team": { + "id": "00000000-0000-4000-8000-000000000304" + }, + "name": "Danny Engrid", + "email": "IHome.danny.e.tmms@gmail.tmt" + } + }, + "feedbackSessions": { + "First Feedback Session": { + "id": "00000000-0000-4000-8000-000000000701", + "course": { + "id": "tm.e2e.IHome.CS2104" + }, + "name": "First Feedback Session", + "creatorEmail": "IHome.instructor.tmms@gmail.tmt", + "instructions": "Please please fill in the following questions.", + "startTime": "2012-04-01T04:00:00Z", + "endTime": "2027-04-30T16:00:00Z", + "sessionVisibleFromTime": "2012-03-28T16:00:00Z", + "resultsVisibleFromTime": "2027-05-01T16:00:00Z", + "gracePeriod": 10, + "isOpeningEmailEnabled": true, + "isClosingEmailEnabled": true, + "isPublishedEmailEnabled": true, + "isOpeningSoonEmailSent": false, + "isOpenEmailSent": false, + "isClosingSoonEmailSent": false, + "isClosedEmailSent": false, + "isPublishedEmailSent": false, + "createdAt": "2012-03-20T23:59:00Z" + }, + "Second Feedback Session": { + "id": "00000000-0000-4000-8000-000000000702", + "course": { + "id": "tm.e2e.IHome.CS2104" + }, + "name": "Second Feedback Session", + "creatorEmail": "IHome.instructor.tmms@gmail.tmt", + "instructions": "Please please fill in the following questions.", + "startTime": "2012-03-29T04:00:00Z", + "endTime": "2012-05-01T04:00:00Z", + "sessionVisibleFromTime": "2012-03-28T16:00:00Z", + "resultsVisibleFromTime": "2012-05-01T16:00:00Z", + "gracePeriod": 10, + "isOpeningEmailEnabled": true, + "isClosingEmailEnabled": true, + "isPublishedEmailEnabled": true, + "isOpeningSoonEmailSent": false, + "isOpenEmailSent": false, + "isClosingSoonEmailSent": false, + "isClosedEmailSent": false, + "isPublishedEmailSent": false, + "createdAt": "2012-03-20T23:59:00Z" + }, + "Third Feedback Session": { + "id": "00000000-0000-4000-8000-000000000703", + "course": { + "id": "tm.e2e.IHome.CS2104" + }, + "name": "Third Feedback Session", + "creatorEmail": "IHome.instructor.tmms@gmail.tmt", + "instructions": "Please please fill in the following questions.", + "startTime": "2012-04-10T16:00:00Z", + "endTime": "2012-04-30T16:00:00Z", + "sessionVisibleFromTime": "2012-03-28T16:00:00Z", + "resultsVisibleFromTime": "2027-05-01T16:00:00Z", + "gracePeriod": 10, + "isOpeningEmailEnabled": true, + "isClosingEmailEnabled": true, + "isPublishedEmailEnabled": true, + "isOpeningSoonEmailSent": false, + "isOpenEmailSent": false, + "isClosingSoonEmailSent": false, + "isClosedEmailSent": false, + "isPublishedEmailSent": false, + "createdAt": "2012-03-20T23:59:00Z" + }, + "Fourth Feedback Session": { + "id": "00000000-0000-4000-8000-000000000704", + "course": { + "id": "tm.e2e.IHome.CS2104" + }, + "name": "Fourth Feedback Session", + "creatorEmail": "IHome.instructor.tmms@gmail.tmt", + "instructions": "Please please fill in the following questions.", + "startTime": "2012-04-05T04:00:00Z", + "endTime": "2012-04-20T04:00:00Z", + "sessionVisibleFromTime": "2012-03-28T16:00:00Z", + "resultsVisibleFromTime": "2012-05-01T16:00:00Z", + "gracePeriod": 10, + "isOpeningEmailEnabled": true, + "isClosingEmailEnabled": true, + "isPublishedEmailEnabled": true, + "isOpeningSoonEmailSent": false, + "isOpenEmailSent": false, + "isClosingSoonEmailSent": false, + "isClosedEmailSent": false, + "isPublishedEmailSent": false, + "createdAt": "2012-03-20T23:59:00Z" + }, + "CS1101 Session": { + "id": "00000000-0000-4000-8000-000000000705", + "course": { + "id": "tm.e2e.IHome.CS1101" + }, + "name": "CS1101 Session", + "creatorEmail": "IHome.instructor.tmms@gmail.tmt", + "instructions": "Please please fill in the following questions.", + "startTime": "2012-04-05T04:00:00Z", + "endTime": "2012-04-20T04:00:00Z", + "sessionVisibleFromTime": "2012-03-28T16:00:00Z", + "resultsVisibleFromTime": "2012-05-01T16:00:00Z", + "gracePeriod": 10, + "isOpeningEmailEnabled": true, + "isClosingEmailEnabled": true, + "isPublishedEmailEnabled": true, + "isOpeningSoonEmailSent": false, + "isOpenEmailSent": false, + "isClosingSoonEmailSent": false, + "isClosedEmailSent": false, + "isPublishedEmailSent": false, + "createdAt": "2012-03-20T23:59:00Z" + } + }, + "feedbackQuestions": { + "IHome.CS2104:First Feedback Session:Q1": { + "questionDetails": { + "shouldAllowRichText": true, + "questionType": "TEXT", + "questionText": "Rate 5 other students' products" + }, + "id": "00000000-0000-4000-8000-000000000801", + "feedbackSession": { + "id": "00000000-0000-4000-8000-000000000701" + }, + "description": "Feedback text question.", + "questionNumber": 1, + "giverType": "STUDENTS", + "recipientType": "STUDENTS", + "numOfEntitiesToGiveFeedbackTo": 5, + "showResponsesTo": [ + "INSTRUCTORS", + "RECEIVER" + ], + "showGiverNameTo": [ + "INSTRUCTORS" + ], + "showRecipientNameTo": [ + "INSTRUCTORS", + "RECEIVER" + ] + }, + "IHome.CS2104:Third Feedback Session:Q1": { + "questionDetails": { + "shouldAllowRichText": true, + "questionType": "TEXT", + "questionText": "Rate 5 other students' products" + }, + "id": "00000000-0000-4000-8000-000000000802", + "feedbackSession": { + "id": "00000000-0000-4000-8000-000000000703" + }, + "description": "Feedback text question.", + "questionNumber": 1, + "giverType": "STUDENTS", + "recipientType": "STUDENTS", + "numOfEntitiesToGiveFeedbackTo": 5, + "showResponsesTo": [ + "INSTRUCTORS", + "RECEIVER" + ], + "showGiverNameTo": [ + "INSTRUCTORS" + ], + "showRecipientNameTo": [ + "INSTRUCTORS", + "RECEIVER" + ] + } + }, + "feedbackResponses": { + "response1": { + "answer": { + "answer": "Alice response to Danny.", + "questionType": "TEXT" + }, + "id": "00000000-0000-4000-8000-000000000901", + "giver": "IHome.alice.b.tmms@gmail.tmt", + "giverSection": { + "id": "00000000-0000-4000-8000-000000000201" + }, + "recipient": "IHome.danny.e.tmms@gmail.tmt", + "recipientSection": { + "id": "00000000-0000-4000-8000-000000000201" + }, + "feedbackQuestion": { + "id": "00000000-0000-4000-8000-000000000801", + "questionDetails": { + "shouldAllowRichText": true, + "questionType": "TEXT", + "questionText": "Rate 5 other students' products" + } + } + } + }, + "feedbackResponseComments": {}, + "notifications": { + "notification1": { + "id": "00000000-0000-4000-8000-000000000002", + "startTime": "2011-01-01T00:00:00Z", + "endTime": "2099-01-01T00:00:00Z", + "style": "DANGER", + "targetUser": "GENERAL", + "title": "A deprecation note", + "message": "

Deprecation happens in three minutes

", + "shown": false + } + }, + "readNotifications": {} +} diff --git a/src/e2e/resources/testng-e2e-sql.xml b/src/e2e/resources/testng-e2e-sql.xml index a78c2339825..149758c87e2 100644 --- a/src/e2e/resources/testng-e2e-sql.xml +++ b/src/e2e/resources/testng-e2e-sql.xml @@ -17,6 +17,7 @@ + diff --git a/src/main/java/teammates/sqllogic/api/Logic.java b/src/main/java/teammates/sqllogic/api/Logic.java index 42cbd8a6eb2..659f27750c5 100644 --- a/src/main/java/teammates/sqllogic/api/Logic.java +++ b/src/main/java/teammates/sqllogic/api/Logic.java @@ -676,15 +676,15 @@ public void deleteFeedbackSessionCascade(String feedbackSessionName, String cour } /** - * Soft-deletes a specific session to Recycle Bin. + * Soft-deletes a specific session to Recycle Bin. */ - public void moveFeedbackSessionToRecycleBin(String feedbackSessionName, String courseId) + public FeedbackSession moveFeedbackSessionToRecycleBin(String feedbackSessionName, String courseId) throws EntityDoesNotExistException { assert feedbackSessionName != null; assert courseId != null; - feedbackSessionsLogic.moveFeedbackSessionToRecycleBin(feedbackSessionName, courseId); + return feedbackSessionsLogic.moveFeedbackSessionToRecycleBin(feedbackSessionName, courseId); } /** diff --git a/src/main/java/teammates/sqllogic/core/FeedbackSessionsLogic.java b/src/main/java/teammates/sqllogic/core/FeedbackSessionsLogic.java index a707990d083..b634d441e6a 100644 --- a/src/main/java/teammates/sqllogic/core/FeedbackSessionsLogic.java +++ b/src/main/java/teammates/sqllogic/core/FeedbackSessionsLogic.java @@ -121,7 +121,7 @@ public List getFeedbackSessionsForCourseStartingAfter(String co * @return null if not found. */ public FeedbackSession getFeedbackSessionFromRecycleBin(String feedbackSessionName, String courseId) { - return fsDb.getSoftDeletedFeedbackSession(courseId, feedbackSessionName); + return fsDb.getSoftDeletedFeedbackSession(feedbackSessionName, courseId); } /** @@ -291,9 +291,9 @@ public void deleteFeedbackSessionCascade(String feedbackSessionName, String cour /** * Soft-deletes a specific feedback session to Recycle Bin. - * @return the time when the feedback session is moved to the recycle bin + * @return the feedback session */ - public Instant moveFeedbackSessionToRecycleBin(String feedbackSessionName, String courseId) + public FeedbackSession moveFeedbackSessionToRecycleBin(String feedbackSessionName, String courseId) throws EntityDoesNotExistException { return fsDb.softDeleteFeedbackSession(feedbackSessionName, courseId); diff --git a/src/main/java/teammates/storage/sqlapi/FeedbackSessionsDb.java b/src/main/java/teammates/storage/sqlapi/FeedbackSessionsDb.java index 1a666124c84..3303551e763 100644 --- a/src/main/java/teammates/storage/sqlapi/FeedbackSessionsDb.java +++ b/src/main/java/teammates/storage/sqlapi/FeedbackSessionsDb.java @@ -196,9 +196,9 @@ public void deleteFeedbackSession(FeedbackSession feedbackSession) { /** * Soft-deletes a specific feedback session by its name and course id. * - * @return Soft-deletion time of the feedback session. + * @return the feedback session. */ - public Instant softDeleteFeedbackSession(String feedbackSessionName, String courseId) + public FeedbackSession softDeleteFeedbackSession(String feedbackSessionName, String courseId) throws EntityDoesNotExistException { assert courseId != null; assert feedbackSessionName != null; @@ -212,7 +212,7 @@ public Instant softDeleteFeedbackSession(String feedbackSessionName, String cour feedbackSessionEntity.setDeletedAt(Instant.now()); merge(feedbackSessionEntity); - return feedbackSessionEntity.getDeletedAt(); + return feedbackSessionEntity; } /** diff --git a/src/main/java/teammates/storage/sqlentity/FeedbackSession.java b/src/main/java/teammates/storage/sqlentity/FeedbackSession.java index b82c45e01e5..78306541c18 100644 --- a/src/main/java/teammates/storage/sqlentity/FeedbackSession.java +++ b/src/main/java/teammates/storage/sqlentity/FeedbackSession.java @@ -140,7 +140,12 @@ public FeedbackSession getCopyForUser(String userEmail) { return copy; } - private FeedbackSession getCopy() { + /** + * Creates a copy of the feedback session. + * + * @return The copy of this object. + */ + public FeedbackSession getCopy() { FeedbackSession fs = new FeedbackSession( name, course, creatorEmail, instructions, startTime, endTime, sessionVisibleFromTime, resultsVisibleFromTime, @@ -507,7 +512,7 @@ public boolean isInGracePeriodGivenExtendedDeadline(Instant extendedDeadline) { } /** - * Returns {@code true} if the results of the feedback session is visible; {@code false} if not. + * Returns {@code true} if the results of the feedback session is published; {@code false} if not. * Does not care if the session has ended or not. */ public boolean isPublished() { diff --git a/src/main/java/teammates/ui/webapi/BinFeedbackSessionAction.java b/src/main/java/teammates/ui/webapi/BinFeedbackSessionAction.java index f88dd7da312..a26404b13f7 100644 --- a/src/main/java/teammates/ui/webapi/BinFeedbackSessionAction.java +++ b/src/main/java/teammates/ui/webapi/BinFeedbackSessionAction.java @@ -43,13 +43,11 @@ public JsonResult execute() { if (isCourseMigrated(courseId)) { try { - sqlLogic.moveFeedbackSessionToRecycleBin(feedbackSessionName, courseId); + FeedbackSession fs = sqlLogic.moveFeedbackSessionToRecycleBin(feedbackSessionName, courseId); + return new JsonResult(new FeedbackSessionData(fs)); } catch (EntityDoesNotExistException e) { throw new EntityNotFoundException(e); } - - FeedbackSession recycleBinFs = sqlLogic.getFeedbackSessionFromRecycleBin(feedbackSessionName, courseId); - return new JsonResult(new FeedbackSessionData(recycleBinFs)); } else { return oldFeedbackSession(courseId, feedbackSessionName); } diff --git a/src/main/java/teammates/ui/webapi/RestoreFeedbackSessionAction.java b/src/main/java/teammates/ui/webapi/RestoreFeedbackSessionAction.java index c93d6ba4b2c..33a19137d32 100644 --- a/src/main/java/teammates/ui/webapi/RestoreFeedbackSessionAction.java +++ b/src/main/java/teammates/ui/webapi/RestoreFeedbackSessionAction.java @@ -23,12 +23,23 @@ AuthType getMinAuthLevel() { void checkSpecificAccessControl() throws UnauthorizedAccessException { String courseId = getNonNullRequestParamValue(Const.ParamsNames.COURSE_ID); String feedbackSessionName = getNonNullRequestParamValue(Const.ParamsNames.FEEDBACK_SESSION_NAME); - FeedbackSessionAttributes feedbackSession = logic.getFeedbackSessionFromRecycleBin(feedbackSessionName, courseId); - gateKeeper.verifyAccessible( - logic.getInstructorForGoogleId(courseId, userInfo.getId()), - feedbackSession, - Const.InstructorPermissions.CAN_MODIFY_SESSION); + if (isCourseMigrated(courseId)) { + FeedbackSession feedbackSession = sqlLogic.getFeedbackSessionFromRecycleBin(feedbackSessionName, courseId); + + gateKeeper.verifyAccessible( + sqlLogic.getInstructorByGoogleId(courseId, userInfo.getId()), + feedbackSession, + Const.InstructorPermissions.CAN_MODIFY_SESSION); + } else { + FeedbackSessionAttributes feedbackSession = + logic.getFeedbackSessionFromRecycleBin(feedbackSessionName, courseId); + + gateKeeper.verifyAccessible( + logic.getInstructorForGoogleId(courseId, userInfo.getId()), + feedbackSession, + Const.InstructorPermissions.CAN_MODIFY_SESSION); + } } @Override diff --git a/src/test/java/teammates/test/AbstractBackDoor.java b/src/test/java/teammates/test/AbstractBackDoor.java index 48fb1eec380..84319580325 100644 --- a/src/test/java/teammates/test/AbstractBackDoor.java +++ b/src/test/java/teammates/test/AbstractBackDoor.java @@ -646,7 +646,7 @@ private Map convertDeadlinesToInstant(Map deadlin /** * Get soft deleted feedback session from database. */ - public FeedbackSessionAttributes getSoftDeletedSession(String feedbackSessionName, String instructorId) { + public FeedbackSessionData getSoftDeletedSessionData(String feedbackSessionName, String instructorId) { Map params = new HashMap<>(); params.put(Const.ParamsNames.ENTITY_TYPE, Const.EntityType.INSTRUCTOR); params.put(Const.ParamsNames.IS_IN_RECYCLE_BIN, "true"); @@ -657,12 +657,18 @@ public FeedbackSessionAttributes getSoftDeletedSession(String feedbackSessionNam } FeedbackSessionsData sessionsData = JsonUtils.fromJson(response.responseBody, FeedbackSessionsData.class); - FeedbackSessionData feedbackSession = sessionsData.getFeedbackSessions() + return sessionsData.getFeedbackSessions() .stream() .filter(fs -> fs.getFeedbackSessionName().equals(feedbackSessionName)) .findFirst() .orElse(null); + } + /** + * Get soft deleted feedback session from database. + */ + public FeedbackSessionAttributes getSoftDeletedSession(String feedbackSessionName, String instructorId) { + FeedbackSessionData feedbackSession = getSoftDeletedSessionData(feedbackSessionName, instructorId); if (feedbackSession == null) { return null; } diff --git a/src/test/java/teammates/test/BaseTestCaseWithSqlDatabaseAccess.java b/src/test/java/teammates/test/BaseTestCaseWithSqlDatabaseAccess.java index dddf3288a93..c5663428800 100644 --- a/src/test/java/teammates/test/BaseTestCaseWithSqlDatabaseAccess.java +++ b/src/test/java/teammates/test/BaseTestCaseWithSqlDatabaseAccess.java @@ -222,6 +222,8 @@ private ApiOutput getEntity(BaseEntity entity) { return getStudent((Student) entity); } else if (entity instanceof FeedbackQuestion) { return getFeedbackQuestion((FeedbackQuestion) entity); + } else if (entity instanceof FeedbackSession) { + return getFeedbackSession((FeedbackSession) entity); } else if (entity instanceof FeedbackResponse) { return getFeedbackResponse((FeedbackResponse) entity); } else { @@ -231,6 +233,8 @@ private ApiOutput getEntity(BaseEntity entity) { protected abstract FeedbackQuestionData getFeedbackQuestion(FeedbackQuestion fq); + protected abstract FeedbackSessionData getFeedbackSession(FeedbackSession fq); + protected abstract FeedbackResponseData getFeedbackResponse(FeedbackResponse fq); protected abstract StudentData getStudent(Student student);