Annotation of loncom/interface/statistics/lonsurveyreports.pm, revision 1.6
1.1 matthew 1: # The LearningOnline Network with CAPA
2: #
1.6 ! matthew 3: # $Id: lonsurveyreports.pm,v 1.5 2005/03/14 20:28:22 matthew Exp $
1.1 matthew 4: #
5: # Copyright Michigan State University Board of Trustees
6: #
7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
8: #
9: # LON-CAPA is free software; you can redistribute it and/or modify
10: # it under the terms of the GNU General Public License as published by
11: # the Free Software Foundation; either version 2 of the License, or
12: # (at your option) any later version.
13: #
14: # LON-CAPA is distributed in the hope that it will be useful,
15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17: # GNU General Public License for more details.
18: #
19: # You should have received a copy of the GNU General Public License
20: # along with LON-CAPA; if not, write to the Free Software
21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22: #
23: # /home/httpd/html/adm/gpl.txt
24: #
25: # http://www.lon-capa.org/
26: #
27: package Apache::lonsurveyreports;
28:
29: use strict;
30: use Apache::lonnet();
31: use Apache::loncommon();
32: use Apache::lonhtmlcommon();
33: use Apache::loncoursedata();
34: use Apache::lonstatistics;
35: use Apache::lonlocal;
36: use Apache::lonstathelpers;
1.4 matthew 37: use Spreadsheet::WriteExcel;
1.1 matthew 38: use HTML::Entities();
39: use Time::Local();
40:
41: my @SubmitButtons = ({ name => 'PrevProblem',
42: text => 'Previous Survey' },
43: { name => 'NextProblem',
44: text => 'Next Survey' },
45: { name => 'break'},
46: { name => 'SelectAnother',
47: text => 'Choose a different Survey Problem' },
48: { name => 'Generate',
49: text => 'Generate Report'},
50: );
51:
52: sub BuildSurveyReportsPage {
53: my ($r,$c)=@_;
54: #
55: my %Saveable_Parameters = ('Status' => 'scalar',
56: 'Section' => 'array',
57: 'NumPlots' => 'scalar',
58: );
59: &Apache::loncommon::store_course_settings('survey_reports',
60: \%Saveable_Parameters);
61: &Apache::loncommon::restore_course_settings('survey_resports',
62: \%Saveable_Parameters);
63: #
64: &Apache::lonstatistics::PrepareClasslist();
65: #
66: $r->print(&CreateInterface());
67: #
68: my @Students = @Apache::lonstatistics::Students;
69: #
70: if (@Students < 1) {
71: $r->print('<h2>There are no students in the sections selected</h2>');
72: }
73: #
74: my @CacheButtonHTML =
75: &Apache::lonstathelpers::manage_caches($r,'Statistics','stats_status');
76: $r->rflush();
77: #
78: if (exists($ENV{'form.problemchoice'}) &&
79: ! exists($ENV{'form.SelectAnother'})) {
80: foreach my $button (@SubmitButtons) {
81: if ($button->{'name'} eq 'break') {
82: $r->print("<br />\n");
83: } else {
84: $r->print('<input type="submit" name="'.$button->{'name'}.'" '.
85: 'value="'.&mt($button->{'text'}).'" />');
86: $r->print(' 'x5);
87: }
88: }
89: foreach my $html (@CacheButtonHTML) {
90: $r->print($html.(' 'x5));
91: }
92: #
93: $r->print('<hr />');
1.5 matthew 94: $r->print('<h4>'.
95: &Apache::lonstatistics::section_and_enrollment_description().
96: '</h4>');
1.1 matthew 97: $r->rflush();
98: #
99: # Determine which problem we are to analyze
100: my $current_problem = &Apache::lonstathelpers::get_target_from_id
101: ($ENV{'form.problemchoice'});
102: #
1.3 matthew 103: my ($navmap,$prev,$curr,$next) =
1.1 matthew 104: &Apache::lonstathelpers::get_prev_curr_next($current_problem,
105: '.',
106: 'part_survey',
107: );
108: if (exists($ENV{'form.PrevProblem'}) && defined($prev)) {
109: $current_problem = $prev;
110: } elsif (exists($ENV{'form.NextProblem'}) && defined($next)) {
111: $current_problem = $next;
112: } else {
113: $current_problem = $curr;
114: }
115: #
116: # Store the current problem choice and send it out in the form
117: $ENV{'form.problemchoice'} =
118: &Apache::lonstathelpers::make_target_id($current_problem);
119: $r->print('<input type="hidden" name="problemchoice" value="'.
120: $ENV{'form.problemchoice'}.'" />');
121: #
122: if (! defined($current_problem->{'resource'})) {
123: $r->print('resource is undefined');
124: } else {
125: my $resource = $current_problem->{'resource'};
1.3 matthew 126: $r->print('<h1>'.$resource->compTitle.'</h1>');
127: $r->print('<h3>'.$resource->src.'</h3>');
1.6 ! matthew 128: if ($ENV{'form.renderprob'} eq 'true') {
! 129: $r->print(&Apache::lonstathelpers::render_resource($resource));
! 130: }
1.1 matthew 131: $r->rflush();
132: my %Data = &Apache::lonstathelpers::get_problem_data
1.3 matthew 133: ($resource->src);
1.6 ! matthew 134: &Compile_Student_Answers($current_problem,\%Data,\@Students);
1.4 matthew 135: if ($ENV{'form.output'} eq 'HTML' ||
136: ! defined($ENV{'form.output'})) {
137: &make_HTML_report($r,$current_problem,\%Data,\@Students);
138: } elsif ($ENV{'form.output'} eq 'Excel') {
139: &make_Excel_report($r,$current_problem,\%Data,\@Students);
140: }
1.1 matthew 141: }
142: $r->print('<hr />');
143: } else {
144: $r->print('<input type="submit" name="Generate" value="'.
145: &mt('Generate Survey Report').'" />');
146: $r->print(' 'x5);
147: $r->print('<h3>'.&mt('Please select a Survey to analyze').'</h3>');
148: $r->print(&SurveyProblemSelector());
149: }
150: }
151:
152: ##########################################################
153: ##########################################################
154: ##
155: ## SurveyProblemSelector
156: ##
157: ##########################################################
158: ##########################################################
159: sub SurveyProblemSelector {
160: my $Str = '';
161: my @SurveyProblems;
1.3 matthew 162: my ($navmap,@sequences) =
163: &Apache::lonstatistics::selected_sequences_with_assessments('all');
164: foreach my $seq (@sequences) {
165: my @resources = &Apache::lonstathelpers::get_resources($navmap,$seq);
166: foreach my $res (@resources) {
167: foreach my $part (@{$res->parts}) {
168: if ($res->is_survey($part)) {
1.1 matthew 169: push(@SurveyProblems,{res=>$res,seq=>$seq,part=>$part});
170: last;
171: }
172: }
173: }
174: }
175: if (! scalar(@SurveyProblems)) {
176: $Str = '<h1>'.
177: &mt('There are no survey problems in this course').
178: '</h1>'.$/;
179: return $Str;
180: }
181: $Str .= '<table>'.$/;
182: $Str .= '<tr>'.'<td></td>'.
1.3 matthew 183: '<th>'.&mt('Survey').'</th>'.
1.1 matthew 184: '</tr>'.$/;
1.3 matthew 185: my $id;
1.1 matthew 186: foreach my $problem (@SurveyProblems) {
1.3 matthew 187: $id++;
1.1 matthew 188: my $value = &Apache::lonstathelpers::make_target_id
1.3 matthew 189: ({symb=>$problem->{'res'}->symb,
1.1 matthew 190: part=>$problem->{'part'},
191: respid=>undef,
192: resptype=>undef});
193: my $checked = '';
194: if ($ENV{'form.problemchoice'} eq $value) {
195: $checked = 'checked ';
196: }
1.3 matthew 197: my $link = $problem->{'res'}->src.
198: '?symb='.&Apache::lonnet::escape($problem->{'res'}->symb);
199: $Str .= '<tr><td>'.
200: '<input type="radio" name="problemchoice" id="'.$id.'" '.
1.1 matthew 201: 'value="'.$value.'" '.$checked.'/>'.'</td>'.
1.3 matthew 202: '<td><nobr>'.
203: '<label for="'.$id.'">'.$problem->{'res'}->compTitle.'('.$problem->{'seq'}->compTitle.')'.'</lablel>'.
204: (' 'x2).
205: qq{<a target="preview" href="$link">view</a>}.'</td></tr>'.$/;
1.1 matthew 206: }
207: $Str .= '</table>';
208: return $Str;
209: }
210:
211: #########################################################
212: #########################################################
213: ##
214: ## Compile Student Answers
215: ##
216: #########################################################
217: #########################################################
218: sub Compile_Student_Answers {
219: my ($problem,$ProblemData,$Students) = @_;
220: my $resource = $problem->{'resource'};
221: foreach my $student (@$Students) {
1.3 matthew 222: foreach my $partid (@{$resource->parts}) {
223: my @response_ids = $resource->responseIds($partid);
224: my @response_types = $resource->responseType($partid);
225: for (my $i=0;$i<=$#response_ids;$i++) {
226: my $respid = $response_ids[$i];
227: my $resptype = $response_types[$i];
1.1 matthew 228: my $results =
229: &Apache::loncoursedata::get_response_data_by_student
1.3 matthew 230: ($student,$resource->symb,$respid);
1.1 matthew 231: next if (! defined($results) || ref($results) ne 'ARRAY' ||
232: ref($results->[0]) ne 'ARRAY');
233: my $student_response =
234: $results->[0]->[&Apache::loncoursedata::RDs_submission()];
235: $problem->{'responsedata'}->{$partid}->{$respid}->{'_count'}++;
236: my $data = $problem->{'responsedata'}->{$partid}->{$respid};
1.3 matthew 237: if ($resptype =~ /^(option|match)$/) {
238: my @responses = split('&',$student_response);
239: foreach my $response (@responses) {
240: my ($foilid,$option) =
241: map {
242: &Apache::lonnet::unescape($_);
243: } split('=',$response);
244: $data->{'foil_count'}->{$foilid}++;
245: $data->{'foil_responses'}->{$foilid}->{$option}++;
246: }
247: } elsif ($resptype =~ /^(radiobutton)$/) {
248: my ($foil,$value) = map { &Apache::lonnet::unescape($_); } split('=',$student_response);
1.1 matthew 249: $value += 1; # explicitly increment it...
250: $data->{'foil_responses'}->{$foil}++;
251: $data->{'foil_values'}->{$value}++;
252: if (! exists($data->{'map'}->{$value})) {
253: $data->{'map'}->{$value} = $foil;
254: }
255: } else {
256: # Variable stuff (essays, raw numbers, strings) go here
257: push(@{$data->{'responses'}},$student_response);
258: }
259: }
260: }
261: }
262: return;
263: }
264:
265: #########################################################
266: #########################################################
267: ##
1.4 matthew 268: ## make_Excel_report
269: ##
270: #########################################################
271: #########################################################
272: sub make_Excel_report {
273: my ($r,$problem,$problem_data,$students) = @_;
274: my ($workbook,$filename,$format) = &Apache::loncommon::create_workbook($r);
275: if (! defined($workbook)) { return '';}
276: $r->print('<script>'.
277: 'window.document.Statistics.stats_status.value="'.
278: &mt('Building spreadsheet.').
279: '";</script>');
280: my $worksheet = $workbook->addworksheet('Survey Reports');
281: #
282: my $rows_output=0;
283: $worksheet->write($rows_output++,0,
284: $ENV{'course.'.$ENV{'request.course.id'}.'.description'},
285: $format->{'h1'});
286: $rows_output++;
287: #
288: my $resource = $problem->{'resource'};
289: $worksheet->write($rows_output++,0,$resource->compTitle,$format->{'h2'});
290: foreach my $partid (@{$resource->parts}) {
291: my @response_ids = $resource->responseIds($partid);
292: my @response_types = $resource->responseType($partid);
293: for (my $i=0;$i<=$#response_ids;$i++) {
294: my $respid = $response_ids[$i];
295: my $resptype = $response_types[$i];
296: my $data = $problem->{'responsedata'}->{$partid}->{$respid};
297: my $cols_output=0;
298: $worksheet->write($rows_output,$cols_output++,
299: $resource->part_display($partid),$format->{'h3'});
300: $worksheet->write($rows_output,$cols_output++,
301: 'Response '.$respid.', '.$resptype,
302: $format->{'h3'});
303: $rows_output++;
304: if (exists($data->{'responses'}) &&
305: ref($data->{'responses'}) eq 'ARRAY') {
306: my $warned_about_size = 0;
307: foreach my $data (@{$data->{'responses'}}) {
308: if (length($data) > 255 && ! $warned_about_size) {
309: $r->print('<p>'.
310: &mt('[_1]:[_2] responses to [_3] may be too long to fit Excel spreadsheet.',
311: $resource->compTitle,
312: $resource->part_display($partid),
313: $respid).
314: '</p>');
315: $r->rflush();
316: $warned_about_size=1;
317: }
318: $worksheet->write($rows_output++,0,$data);
319: }
320: } elsif (exists($data->{'foil_count'}) &&
321: exists($data->{'foil_responses'})) {
322: my $respdata = $problem_data->{$partid.'.'.$respid};
323: my @rowdata = ('Foil Name','Foil Text','Option',
324: 'Frequency');
325: $worksheet->write_row($rows_output++,0,
326: \@rowdata,$format->{'h4'});
327: #
328: my @foils = sort(keys(%{$data->{'foil_responses'}}));
329: foreach my $foilid (@foils) {
330: my $foil_count = $data->{'foil_count'}->{$foilid};
331: my $foiltext = $respdata->{'_Foils'}->{$foilid}->{'text'};
332: my $foilname = $respdata->{'_Foils'}->{$foilid}->{'name'};
333: $foiltext = &HTML::Entities::decode($foilname);
334: my $cols_output=0;
335: $worksheet->write($rows_output,$cols_output++,$foilname);
336: $worksheet->write($rows_output,$cols_output++,$foiltext);
337: my $option_start_col = $cols_output;
338: #
339: foreach my $option (sort(@{$respdata->{'_Options'}})){
340: $cols_output= $option_start_col;
341: $worksheet->write($rows_output,$cols_output++,
342: $option);
343: my $count=
344: $data->{'foil_responses'}->{$foilid}->{$option};
345: $worksheet->write($rows_output,$cols_output++,$count);
346: $rows_output++;
347: }
348: }
349: } elsif (exists($data->{'_count'}) &&
350: exists($data->{'foil_values'}) &&
351: exists($data->{'map'})) {
352: my $respdata = $problem_data->{$partid.'.'.$respid};
353: my @rowdata = ('Foil Name','Foil Text','Frequency');
354: $worksheet->write_row($rows_output++,0,
355: \@rowdata,$format->{'h4'});
356: foreach my $value (sort(keys(%{$data->{'foil_values'}}))) {
357: undef(@rowdata);
358: my $foilid = $data->{'map'}->{$value};
359: push(@rowdata,$respdata->{'_Foils'}->{$foilid}->{'name'});
360: push(@rowdata,$respdata->{'_Foils'}->{$foilid}->{'text'});
361: push(@rowdata,$data->{'foil_values'}->{$value});
362: $worksheet->write_row($rows_output++,0,\@rowdata);
363: }
364: }
365: $rows_output++;
366: } #response ids
367: } # partids
368: $workbook->close();
369: $r->print('<p><a href="'.$filename.'">'.
370: &mt('Your Excel spreadsheet.').
371: '</a></p>'."\n");
372: $r->print('<script>'.
373: 'window.document.Statistics.stats_status.value="'.
374: &mt('Done compiling spreadsheet. See link below to download.').
375: '";</script>');
376: $r->rflush();
377: return;
378: }
379:
380: #########################################################
381: #########################################################
382: ##
1.1 matthew 383: ## make_HTML_report
384: ##
385: #########################################################
386: #########################################################
387: sub make_HTML_report {
388: my ($r,$problem,$ProblemData,$Students) = @_;
389: my $resource = $problem->{'resource'};
1.3 matthew 390: foreach my $partid (@{$resource->parts}) {
391: my @response_ids = $resource->responseIds($partid);
392: my @response_types = $resource->responseType($partid);
393: for (my $i=0;$i<=$#response_ids;$i++) {
1.1 matthew 394: my $Str = '<table>'.$/;
1.3 matthew 395: my $respid = $response_ids[$i];
396: my $resptype = $response_types[$i];
1.1 matthew 397: my $data = $problem->{'responsedata'}->{$partid}->{$respid};
1.3 matthew 398: if (! defined($data) || ref($data) ne 'HASH') {
399: next;
400: }
1.1 matthew 401: # Debugging code
402: # $Str .= '<tr>'.
403: # '<td>'.$partid.'</td>'.
404: # '<td>'.$respid.'</td>'.
405: # '<td>'.$resptype.'</td>'.
406: # '</tr>'.$/;
407: $Str .= '<tr>'.
408: '<td><b>'.&mt('Total').'</b></td>'.
409: '<td>'.$data->{'_count'}.'</td>'.
1.2 matthew 410: '<td>'.&mt('Part [_1], Response [_2]',$partid,$respid).'</td>'.
1.3 matthew 411: '</tr>';
1.1 matthew 412: if (exists($data->{'responses'}) &&
413: ref($data->{'responses'}) eq 'ARRAY') {
414: &randomize_array($data->{'responses'});
415: foreach my $response (@{$data->{'responses'}}) {
416: $response =~ s/\\r\\n/\n/g;
417: $response =~ s/\\'/'/g;
418: $response =~ s/\\"/"/g;
419: $Str .= '<tr>'.
420: '<td colspan="3"><pre>'.
421: &HTML::Entities::encode($response,'<>&').
422: '</pre><hr /></td>'.
423: '</tr>'.$/;
424: }
1.3 matthew 425: } elsif (exists($data->{'foil_count'}) &&
426: exists($data->{'foil_responses'})) {
427: $Str.='<tr><td colspan="3">'.
428: '<table><tr>';
429: my $tmp = '<th>'.join('</th><th>',
430: (&mt('Foil Name'),
431: &mt('Foil Text'),
432: &mt('Option'),
433: &mt('Frequency'),
434: &mt('Percent'))).'</th></tr>';
435: my @foils = sort(keys(%{$data->{'foil_responses'}}));
436: foreach my $foilid (@foils) {
437: my $prob_data = $ProblemData->{$partid.'.'.$respid};
438: my $foil_count = $data->{'foil_count'}->{$foilid};
439: my $foiltext = $prob_data->{'_Foils'}->{$foilid}->{'text'};
440: my $foilname = $prob_data->{'_Foils'}->{$foilid}->{'name'};
441: my $rowspan = scalar(@{$prob_data->{'_Options'}});
442: my $preamble = '<tr>'.
443: '<td valign="top" rowspan="'.$rowspan.'">'.
444: $foilname.'</td>'.
445: '<td valign="top" rowspan="'.$rowspan.'">'.
446: $foiltext.'</td>';
447: foreach my $option (sort(@{$prob_data->{'_Options'}})){
448: my $count =
449: $data->{'foil_responses'}->{$foilid}->{$option};
450: $tmp .= $preamble.
451: '<td>'.$option.'</td>'.
452: '<td align="right">'.$count.'</td>'.
453: '<td align="right">'.
454: sprintf('%.2f',100*$count/$foil_count).'%'.
455: '</td></tr>'.$/;
456: $preamble = '<tr>';
457: }
458: }
459: $Str.=$tmp.'</table></td></tr>';
1.1 matthew 460: } elsif (exists($data->{'_count'}) &&
461: exists($data->{'foil_values'}) &&
462: exists($data->{'map'})) {
463: # This is an option or radiobutton survey response
464: my $total = $data->{'_count'};
465: my $sum = 0;
466: my $tmp;
467: foreach my $value (sort(keys(%{$data->{'foil_values'}}))) {
468: my $count = $data->{'foil_values'}->{$value};
469: my $foilid = $data->{'map'}->{$value};
470: my $foiltext = $ProblemData->{$partid.'.'.$respid}->{'_Foils'}->{$foilid}->{'text'};
471: my $foilname = $ProblemData->{$partid.'.'.$respid}->{'_Foils'}->{$foilid}->{'name'};
472: $tmp .= '<tr>'.
473: '<td>'.$foilname.'</td>'.
474: '<td>'.$foiltext.'</td>'.
475: '<td align="right">'.$count.'</td>'.
476: '<td align="right">'.
477: sprintf("%.2f",$count/$total*100).'%</td>'.
478: '</tr>'.$/;
479: }
480: $Str .= '<tr>'.
481: '<th>'.&mt('Foil Name').'</th>'.
482: '<th>'.&mt('Text').'</th>'.
483: '<th>'.&mt('Freq').'</th>'.
484: '<th>'.&mt('Percent').'</th>'.
485: '</tr>'.$/.
486: $tmp;
487: }
488: $Str.= '</table><hr />';
489: $r->print($Str);
490: $r->rflush();
491: }
492: }
493: return;
494: }
495:
496: sub randomize_array {
497: # Fisher Yates shuffle, lifted from p 121 of "The Perl Cookbook"
498: my ($array) = @_;
499: for (my $i=scalar(@$array);--$i;) {
500: my $j = int(rand($i+1));
501: next if ($i == $j);
502: @$array[$i,$j]=@$array[$j,$i];
503: }
504: }
505:
506: #########################################################
507: #########################################################
508: ##
509: ## Generic Interface Routines
510: ##
511: #########################################################
512: #########################################################
513: sub CreateInterface {
514: ##
515: ## Environment variable initialization
516: my $Str = '';
1.4 matthew 517: my $output_selector = '<select name="output" size="5">'.$/;
518: if (! exists($ENV{'form.output'})) {
519: $ENV{'form.output'} = 'HTML';
520: }
521: foreach my $output_format ( {name=>'HTML',text=>&mt("HTML") },
522: {name=>'Excel',text=>&mt("Excel") }) {
523: $output_selector.='<option value="'.$output_format->{'name'}.'"';
524: if ($ENV{'form.output'} eq $output_format->{'name'}) {
525: $output_selector.=' selected';
526: }
527: $output_selector.= '>'.$output_format->{'text'}.'</option>'.$/;
528: }
529: $output_selector .= '</select>'.$/;
1.1 matthew 530: $Str .= &Apache::lonhtmlcommon::breadcrumbs
531: (undef,'Student Submission Reports');
532: $Str .= '<p>';
533: $Str .= '<table cellspacing="5">'."\n";
534: $Str .= '<tr>';
1.6 ! matthew 535: $Str .= '<th>'.&mt('Sections').'</th>';
! 536: $Str .= '<th>'.&mt('Enrollment Status').'</th>';
! 537: $Str .= '<th>'.&mt('Output Format').'</th>';
! 538: $Str .= '<th>'.' '.'</th>';
1.1 matthew 539: $Str .= '</tr>'."\n";
540: #
541: $Str .= '<tr><td align="center">'."\n";
542: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
543: $Str .= '</td>';
544: #
545: $Str .= '<td align="center">';
546: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
547: $Str .= '</td>';
548: #
1.4 matthew 549: $Str .= '<td align="center">'.$output_selector.'</td>';
550: #
1.6 ! matthew 551: # Render problem checkbox
! 552: my $prob_checkbox = '<input type="checkbox" name="renderprob" ';
! 553: if (exists($ENV{'form.renderprob'}) && $ENV{'form.renderprob'} eq 'true') {
! 554: $prob_checkbox .= 'checked ';
! 555: }
! 556: $prob_checkbox .= 'value="true" />';
! 557: $Str .= '<td align="right" valign="top">'.
! 558: '<label><b>'.
! 559: &mt('Show problem [_1]',$prob_checkbox).'</b></label><br />'.
! 560: '<label><b>'.
! 561: '</td>';
! 562: #
1.1 matthew 563: $Str .= '</tr>'."\n";
564: $Str .= '</table>'."\n";
565: #
566: $Str .= '<nobr>'.&mt('Status: [_1]',
567: '<input type="text" '.
568: 'name="stats_status" size="60" value="" />').
569: '</nobr>'.'</p>';
570: ##
571: return $Str;
572: }
573:
574: 1;
575:
576: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>