Teammates: Instructor: view results: fix IndexOutOfBoundsException

Created on 8 Feb 2018  路  18Comments  路  Source: TEAMMATES/teammates

I'm getting this error when I try to view results of an ongoing session.

teammates.common.util.Logger severe: Unexpected exception caught by ControllerServlet :  (Logger.java:52)
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(ArrayList.java:654)
    at java.util.ArrayList.get(ArrayList.java:430)
    at teammates.common.datatransfer.questions.FeedbackMsqQuestionDetails.getQuestionAdditionalInfoHtml(FeedbackMsqQuestionDetails.java:460)
    at teammates.ui.pagedata.InstructorFeedbackResultsPageData.buildQuestionTableAndResponseRows(InstructorFeedbackResultsPageData.java:924)
    at teammates.ui.pagedata.InstructorFeedbackResultsPageData.buildQuestionTableWithoutResponseRows(InstructorFeedbackResultsPageData.java:865)
    at teammates.ui.pagedata.InstructorFeedbackResultsPageData.lambda$initForViewByQuestion$0(InstructorFeedbackResultsPageData.java:120)
    at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
    at teammates.ui.pagedata.InstructorFeedbackResultsPageData.initForViewByQuestion(InstructorFeedbackResultsPageData.java:117)
    at teammates.ui.controller.InstructorFeedbackResultsPageAction.execute(InstructorFeedbackResultsPageAction.java:137)
    at teammates.ui.controller.Action.executeAndPostProcess(Action.java:474)
    at teammates.ui.controller.ControllerServlet.doPost(ControllerServlet.java:76)
    at teammates.ui.controller.ControllerServlet.doGet(ControllerServlet.java:51)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:848)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1772)
    at com.googlecode.objectify.ObjectifyFilter.doFilter(ObjectifyFilter.java:48)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1759)
    at com.google.apphosting.utils.servlet.ParseBlobUploadFilter.doFilter(ParseBlobUploadFilter.java:125)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1759)
    at com.google.apphosting.runtime.jetty9.SaveSessionFilter.doFilter(SaveSessionFilter.java:37)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1759)
    at com.google.apphosting.utils.servlet.JdbcMySqlConnectionCleanupFilter.doFilter(JdbcMySqlConnectionCleanupFilter.java:60)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1759)
    at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:48)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1759)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:582)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:524)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:226)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1180)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:512)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1112)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at com.google.apphosting.runtime.jetty9.AppVersionHandlerMap.handle(AppVersionHandlerMap.java:297)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
    at org.eclipse.jetty.server.Server.handle(Server.java:534)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:320)
    at com.google.apphosting.runtime.jetty9.RpcConnection.handle(RpcConnection.java:202)
    at com.google.apphosting.runtime.jetty9.RpcConnector.serviceRequest(RpcConnector.java:81)
    at com.google.apphosting.runtime.jetty9.JettyServletEngineAdapter.serviceRequest(JettyServletEngineAdapter.java:108)
    at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.dispatchServletRequest(JavaRuntime.java:680)
    at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.dispatchRequest(JavaRuntime.java:642)
    at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.run(JavaRuntime.java:612)
    at com.google.apphosting.runtime.JavaRuntime$NullSandboxRequestRunnable.run(JavaRuntime.java:806)
    at com.google.apphosting.runtime.ThreadGroupPool$PoolEntry.run(ThreadGroupPool.java:274)
    at java.lang.Thread.run(Thread.java:745)
p.Urgent

Most helpful comment

Here's what I found:

The offending line for the Exception comes from the method getQuestionAdditionalInfoHtml which is used to generate the additional information for the questions like this:
image

Inside the FeedbackMsqQuestionDetails class, there are 2 attributes:

private int numOfMsqChoices;
private List<String> msqChoices;

When the MSQ's choices are generated from the instructors' input, both fields are assigned accordingly in this method: https://github.com/TEAMMATES/teammates/blob/cd9a5365cfed580bae5d1bf15f2994a921e31f03/src/main/java/teammates/common/datatransfer/questions/FeedbackMsqQuestionDetails.java#L113-L118

However when MSQ's choices are generated from say students in the class, or teams, etc.,
the method below assigns a value for numOfMsqChoices but not for msqChoices.
https://github.com/TEAMMATES/teammates/blob/cd9a5365cfed580bae5d1bf15f2994a921e31f03/src/main/java/teammates/common/datatransfer/questions/FeedbackMsqQuestionDetails.java#L120-L134

Hence when the instructor wants to view the result of a session which contains MSQ questions with generated options(2nd scenario above), the above lines will trigger the problem in the execution of the lines below, as @whipermr5 has identified and discussed in the comments in this thread.
https://github.com/TEAMMATES/teammates/blob/cd9a5365cfed580bae5d1bf15f2994a921e31f03/src/main/java/teammates/common/datatransfer/questions/FeedbackMsqQuestionDetails.java#L482-L487

In fact, for a question that has generated options, this additional information should not be generated, and the UI below should just be shown.
image

This is why MCQ questions do not face such Exceptions because in MCQ, numOfMcqChoices is not assigned and that code block in the MCQ class will not be executed when the question generates options from students, or teams, etc.
https://github.com/TEAMMATES/teammates/blob/cd9a5365cfed580bae5d1bf15f2994a921e31f03/src/main/java/teammates/common/datatransfer/questions/FeedbackMcqQuestionDetails.java#L357-L366

This is also why MSQ with choices that the instructor created manually does not cause the exception, as there is no discrepancy between numOfMsqChoices and msqChoices. The UI displays correct as shown in the first screenshot in this comment. The Exception is due to the program trying to display all the choices of students, or teams, etc., which should not be displayed anyway.

TLDR:
The fix should not cause other problems because generating the responses are done in FeedbackMsqResponseDetails. The cause for the problem is because of generating the additional information for display for the questions.

All 18 comments

I have several multiple-select, multiple-answers questions. In one, the options are generated based on the students in the class. In others, options are fixed.

Need to fix quite soon as I need to access results of that session.

Seems like there's a discrepancy between numOfMsqChoices and msqChoices. Both fields are deserialised from the question metadata stored in the datastore and can possibly be inconsistent. Would it be possible to view the FeedbackQuestion entity of the corresponding question in the Google Cloud console? I'm interested to see what is stored in the questionText field that contains the metadata. It may be inconsistent, e.g. this existing test data is malformed (it's an MSQ question with msqChoices but numOfMcqChoices instead of numOfMsqChoices in the metadata):
https://github.com/TEAMMATES/teammates/blob/85faa7829f4edf19383b033e2d59825652b4b835/src/test/resources/data/FeedbackSessionQuestionTypeTest.json#L711

This issue might even be related to the issue of response deletion (both may be due to inconsistencies in the view of questions stored in the datastore).

Meanwhile, I've updated log-response-deletion with a hotfix. It has passed the Travis build.

The hot fix seem to have fixed the issue. I can see the results now. In any case, we might need a test to cover this case.

In this method, is there any reason why numOfMsqChoices is explicitly set instead of being taken from the size of msqChoices?

And does anyone know how to insert embedded code snippets into comments?

In this method, is there any reason why numOfMsqChoices is explicitly set instead of being taken from the size of msqChoices?

Yes; that's what I was wondering too. The problematic question had an empty msqChoices (the options are generated based on students in the team) but numOfMsqChoices was set to 25.

And does anyone know how to insert embedded code snippets into comments?

https://help.github.com/articles/creating-a-permanent-link-to-a-code-snippet/

I think it's safe to use msqChoices.size here instead of numOfMsqChoices, which was only needed in extractQuestionDetails to iterate through msqChoices. Then again, what could trigger a request with discrepancy between numOfMsqChoices and msqChoices?

Likely a bug in the code related to generating options based on students in the team.

In this method
https://github.com/TEAMMATES/teammates/blob/a1e79ad5effda8542d619c7e0b88ecc3cdd694d6/src/main/java/teammates/common/datatransfer/questions/FeedbackMsqQuestionDetails.java#L119-L128
I'm quite baffled as msqChoices is set to empty then not populated back (not in generateOptionList as well) while numOfMsqChoices is set to the size of the generated list for courseId. Am I missing something here?

I believe you have found the bug! This was done in #7509 (specifically, this commit). @VamsiSangam was this intentional? This leads to a non-zero numOfMsqChoices being stored in the datastore even though msqChoices is stored as empty.

@whipermr5 Anyone working on this issue ?

@Shashwat-Garg Don't think so.

I'll try to do what's left. :)
By the way, @whipermr5 what's left to be done in this ?
Can you please point to some things ?

@Shashwat-Garg Investigate why the current code was written this way https://github.com/TEAMMATES/teammates/issues/8415#issuecomment-364382607, and whether this solution https://github.com/TEAMMATES/teammates/issues/8415#issuecomment-364362101 has any side effects.

Here's what I found:

The offending line for the Exception comes from the method getQuestionAdditionalInfoHtml which is used to generate the additional information for the questions like this:
image

Inside the FeedbackMsqQuestionDetails class, there are 2 attributes:

private int numOfMsqChoices;
private List<String> msqChoices;

When the MSQ's choices are generated from the instructors' input, both fields are assigned accordingly in this method: https://github.com/TEAMMATES/teammates/blob/cd9a5365cfed580bae5d1bf15f2994a921e31f03/src/main/java/teammates/common/datatransfer/questions/FeedbackMsqQuestionDetails.java#L113-L118

However when MSQ's choices are generated from say students in the class, or teams, etc.,
the method below assigns a value for numOfMsqChoices but not for msqChoices.
https://github.com/TEAMMATES/teammates/blob/cd9a5365cfed580bae5d1bf15f2994a921e31f03/src/main/java/teammates/common/datatransfer/questions/FeedbackMsqQuestionDetails.java#L120-L134

Hence when the instructor wants to view the result of a session which contains MSQ questions with generated options(2nd scenario above), the above lines will trigger the problem in the execution of the lines below, as @whipermr5 has identified and discussed in the comments in this thread.
https://github.com/TEAMMATES/teammates/blob/cd9a5365cfed580bae5d1bf15f2994a921e31f03/src/main/java/teammates/common/datatransfer/questions/FeedbackMsqQuestionDetails.java#L482-L487

In fact, for a question that has generated options, this additional information should not be generated, and the UI below should just be shown.
image

This is why MCQ questions do not face such Exceptions because in MCQ, numOfMcqChoices is not assigned and that code block in the MCQ class will not be executed when the question generates options from students, or teams, etc.
https://github.com/TEAMMATES/teammates/blob/cd9a5365cfed580bae5d1bf15f2994a921e31f03/src/main/java/teammates/common/datatransfer/questions/FeedbackMcqQuestionDetails.java#L357-L366

This is also why MSQ with choices that the instructor created manually does not cause the exception, as there is no discrepancy between numOfMsqChoices and msqChoices. The UI displays correct as shown in the first screenshot in this comment. The Exception is due to the program trying to display all the choices of students, or teams, etc., which should not be displayed anyway.

TLDR:
The fix should not cause other problems because generating the responses are done in FeedbackMsqResponseDetails. The cause for the problem is because of generating the additional information for display for the questions.

Is there even a use case for storing numOfMsqChoices when we can just read msqChoices.size() all the time?

I believe you have found the bug! This was done in #7509 (specifically, this commit). @VamsiSangam was this intentional? This leads to a non-zero numOfMsqChoices being stored in the datastore even though msqChoices is stored as empty.

Sorry for the late reply. As far as I remember, when the MSQ/MCQ question has its options generated from students/teams etc., we don't store the options in the FeedbackMsqQuestionDetails class object. Precisely speaking, we don't store it in private List<String> msqChoices; member variable. Whenever we want to generate the HTML for the question, we directly call private List<String> generateOptionList(String courseId). msqChoices will be non-empty when the instructor has given custom options as input (not generated from students/teams etc.)

But I wanted numOfMsqChoices to have the correct value since I needed it for my min/max selectable choices feature. So, in the code mentioned, the msqChoices List was empty but numOfMsqChoices was not.

Is there even a use case for storing numOfMsqChoices when we can just read msqChoices.size() all the time?

As explained above, I believe they differ in the case where options are generated.

I may be wrong though, my knowledge of Teammates is pretty outdated 馃槄

@VamsiSangam Thanks for the reply! We've moved the discussion to #8714. Am I right in saying that numOfMsqChoices is used only for validation? I feel it should not be persisted to the datastore if that is so.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

whipermr5 picture whipermr5  路  3Comments

wkurniawan07 picture wkurniawan07  路  4Comments

YongJieYongJie picture YongJieYongJie  路  3Comments

keoren3 picture keoren3  路  3Comments

wkurniawan07 picture wkurniawan07  路  3Comments