Annotation of loncom/interface/statistics/lonsurveyreports.pm, revision 1.4
1.1 matthew 1: # The LearningOnline Network with CAPA
2: #
1.4 ! matthew 3: # $Id: lonsurveyreports.pm,v 1.3 2005/03/01 22:04:55 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 />');
94: $r->rflush();
95: #
96: # Determine which problem we are to analyze
97: my $current_problem = &Apache::lonstathelpers::get_target_from_id
98: ($ENV{'form.problemchoice'});
99: #
1.3 matthew 100: my ($navmap,$prev,$curr,$next) =
1.1 matthew 101: &Apache::lonstathelpers::get_prev_curr_next($current_problem,
102: '.',
103: 'part_survey',
104: );
105: if (exists($ENV{'form.PrevProblem'}) && defined($prev)) {
106: $current_problem = $prev;
107: } elsif (exists($ENV{'form.NextProblem'}) && defined($next)) {
108: $current_problem = $next;
109: } else {
110: $current_problem = $curr;
111: }
112: #
113: # Store the current problem choice and send it out in the form
114: $ENV{'form.problemchoice'} =
115: &Apache::lonstathelpers::make_target_id($current_problem);
116: $r->print('<input type="hidden" name="problemchoice" value="'.
117: $ENV{'form.problemchoice'}.'" />');
118: #
119: if (! defined($current_problem->{'resource'})) {
120: $r->print('resource is undefined');
121: } else {
122: my $resource = $current_problem->{'resource'};
1.3 matthew 123: $r->print('<h1>'.$resource->compTitle.'</h1>');
124: $r->print('<h3>'.$resource->src.'</h3>');
1.1 matthew 125: $r->print(&Apache::lonstathelpers::render_resource($resource));
126: $r->rflush();
127: my %Data = &Apache::lonstathelpers::get_problem_data
1.3 matthew 128: ($resource->src);
1.4 ! matthew 129: if ($ENV{'form.output'} eq 'HTML' ||
! 130: ! defined($ENV{'form.output'})) {
! 131: &make_HTML_report($r,$current_problem,\%Data,\@Students);
! 132: } elsif ($ENV{'form.output'} eq 'Excel') {
! 133: &make_Excel_report($r,$current_problem,\%Data,\@Students);
! 134: }
1.1 matthew 135: }
136: $r->print('<hr />');
137: } else {
138: $r->print('<input type="submit" name="Generate" value="'.
139: &mt('Generate Survey Report').'" />');
140: $r->print(' 'x5);
141: $r->print('<h3>'.&mt('Please select a Survey to analyze').'</h3>');
142: $r->print(&SurveyProblemSelector());
143: }
144: }
145:
146: ##########################################################
147: ##########################################################
148: ##
149: ## SurveyProblemSelector
150: ##
151: ##########################################################
152: ##########################################################
153: sub SurveyProblemSelector {
154: my $Str = '';
155: my @SurveyProblems;
1.3 matthew 156: my ($navmap,@sequences) =
157: &Apache::lonstatistics::selected_sequences_with_assessments('all');
158: foreach my $seq (@sequences) {
159: my @resources = &Apache::lonstathelpers::get_resources($navmap,$seq);
160: foreach my $res (@resources) {
161: foreach my $part (@{$res->parts}) {
162: if ($res->is_survey($part)) {
1.1 matthew 163: push(@SurveyProblems,{res=>$res,seq=>$seq,part=>$part});
164: last;
165: }
166: }
167: }
168: }
169: if (! scalar(@SurveyProblems)) {
170: $Str = '<h1>'.
171: &mt('There are no survey problems in this course').
172: '</h1>'.$/;
173: return $Str;
174: }
175: $Str .= '<table>'.$/;
176: $Str .= '<tr>'.'<td></td>'.
1.3 matthew 177: '<th>'.&mt('Survey').'</th>'.
1.1 matthew 178: '</tr>'.$/;
1.3 matthew 179: my $id;
1.1 matthew 180: foreach my $problem (@SurveyProblems) {
1.3 matthew 181: $id++;
1.1 matthew 182: my $value = &Apache::lonstathelpers::make_target_id
1.3 matthew 183: ({symb=>$problem->{'res'}->symb,
1.1 matthew 184: part=>$problem->{'part'},
185: respid=>undef,
186: resptype=>undef});
187: my $checked = '';
188: if ($ENV{'form.problemchoice'} eq $value) {
189: $checked = 'checked ';
190: }
1.3 matthew 191: my $link = $problem->{'res'}->src.
192: '?symb='.&Apache::lonnet::escape($problem->{'res'}->symb);
193: $Str .= '<tr><td>'.
194: '<input type="radio" name="problemchoice" id="'.$id.'" '.
1.1 matthew 195: 'value="'.$value.'" '.$checked.'/>'.'</td>'.
1.3 matthew 196: '<td><nobr>'.
197: '<label for="'.$id.'">'.$problem->{'res'}->compTitle.'('.$problem->{'seq'}->compTitle.')'.'</lablel>'.
198: (' 'x2).
199: qq{<a target="preview" href="$link">view</a>}.'</td></tr>'.$/;
1.1 matthew 200: }
201: $Str .= '</table>';
202: return $Str;
203: }
204:
205: #########################################################
206: #########################################################
207: ##
208: ## Compile Student Answers
209: ##
210: #########################################################
211: #########################################################
212: sub Compile_Student_Answers {
213: my ($problem,$ProblemData,$Students) = @_;
214: my $resource = $problem->{'resource'};
215: foreach my $student (@$Students) {
1.3 matthew 216: foreach my $partid (@{$resource->parts}) {
217: my @response_ids = $resource->responseIds($partid);
218: my @response_types = $resource->responseType($partid);
219: for (my $i=0;$i<=$#response_ids;$i++) {
220: my $respid = $response_ids[$i];
221: my $resptype = $response_types[$i];
1.1 matthew 222: my $results =
223: &Apache::loncoursedata::get_response_data_by_student
1.3 matthew 224: ($student,$resource->symb,$respid);
1.1 matthew 225: next if (! defined($results) || ref($results) ne 'ARRAY' ||
226: ref($results->[0]) ne 'ARRAY');
227: my $student_response =
228: $results->[0]->[&Apache::loncoursedata::RDs_submission()];
229: $problem->{'responsedata'}->{$partid}->{$respid}->{'_count'}++;
230: my $data = $problem->{'responsedata'}->{$partid}->{$respid};
1.3 matthew 231: if ($resptype =~ /^(option|match)$/) {
232: my @responses = split('&',$student_response);
233: foreach my $response (@responses) {
234: my ($foilid,$option) =
235: map {
236: &Apache::lonnet::unescape($_);
237: } split('=',$response);
238: $data->{'foil_count'}->{$foilid}++;
239: $data->{'foil_responses'}->{$foilid}->{$option}++;
240: }
241: } elsif ($resptype =~ /^(radiobutton)$/) {
242: my ($foil,$value) = map { &Apache::lonnet::unescape($_); } split('=',$student_response);
1.1 matthew 243: $value += 1; # explicitly increment it...
244: $data->{'foil_responses'}->{$foil}++;
245: $data->{'foil_values'}->{$value}++;
246: if (! exists($data->{'map'}->{$value})) {
247: $data->{'map'}->{$value} = $foil;
248: }
249: } else {
250: # Variable stuff (essays, raw numbers, strings) go here
251: push(@{$data->{'responses'}},$student_response);
252: }
253: }
254: }
255: }
256: return;
257: }
258:
259: #########################################################
260: #########################################################
261: ##
1.4 ! matthew 262: ## make_Excel_report
! 263: ##
! 264: #########################################################
! 265: #########################################################
! 266: sub make_Excel_report {
! 267: my ($r,$problem,$problem_data,$students) = @_;
! 268: &Compile_Student_Answers($problem,$problem_data,$students);
! 269: my ($workbook,$filename,$format) = &Apache::loncommon::create_workbook($r);
! 270: if (! defined($workbook)) { return '';}
! 271: $r->print('<script>'.
! 272: 'window.document.Statistics.stats_status.value="'.
! 273: &mt('Building spreadsheet.').
! 274: '";</script>');
! 275: my $worksheet = $workbook->addworksheet('Survey Reports');
! 276: #
! 277: my $rows_output=0;
! 278: $worksheet->write($rows_output++,0,
! 279: $ENV{'course.'.$ENV{'request.course.id'}.'.description'},
! 280: $format->{'h1'});
! 281: $rows_output++;
! 282: #
! 283: my $resource = $problem->{'resource'};
! 284: $worksheet->write($rows_output++,0,$resource->compTitle,$format->{'h2'});
! 285: foreach my $partid (@{$resource->parts}) {
! 286: my @response_ids = $resource->responseIds($partid);
! 287: my @response_types = $resource->responseType($partid);
! 288: for (my $i=0;$i<=$#response_ids;$i++) {
! 289: my $respid = $response_ids[$i];
! 290: my $resptype = $response_types[$i];
! 291: my $data = $problem->{'responsedata'}->{$partid}->{$respid};
! 292: my $cols_output=0;
! 293: $worksheet->write($rows_output,$cols_output++,
! 294: $resource->part_display($partid),$format->{'h3'});
! 295: $worksheet->write($rows_output,$cols_output++,
! 296: 'Response '.$respid.', '.$resptype,
! 297: $format->{'h3'});
! 298: $rows_output++;
! 299: if (exists($data->{'responses'}) &&
! 300: ref($data->{'responses'}) eq 'ARRAY') {
! 301: my $warned_about_size = 0;
! 302: foreach my $data (@{$data->{'responses'}}) {
! 303: if (length($data) > 255 && ! $warned_about_size) {
! 304: $r->print('<p>'.
! 305: &mt('[_1]:[_2] responses to [_3] may be too long to fit Excel spreadsheet.',
! 306: $resource->compTitle,
! 307: $resource->part_display($partid),
! 308: $respid).
! 309: '</p>');
! 310: $r->rflush();
! 311: $warned_about_size=1;
! 312: }
! 313: $worksheet->write($rows_output++,0,$data);
! 314: }
! 315: } elsif (exists($data->{'foil_count'}) &&
! 316: exists($data->{'foil_responses'})) {
! 317: my $respdata = $problem_data->{$partid.'.'.$respid};
! 318: my @rowdata = ('Foil Name','Foil Text','Option',
! 319: 'Frequency');
! 320: $worksheet->write_row($rows_output++,0,
! 321: \@rowdata,$format->{'h4'});
! 322: #
! 323: my @foils = sort(keys(%{$data->{'foil_responses'}}));
! 324: foreach my $foilid (@foils) {
! 325: my $foil_count = $data->{'foil_count'}->{$foilid};
! 326: my $foiltext = $respdata->{'_Foils'}->{$foilid}->{'text'};
! 327: my $foilname = $respdata->{'_Foils'}->{$foilid}->{'name'};
! 328: $foiltext = &HTML::Entities::decode($foilname);
! 329: my $cols_output=0;
! 330: $worksheet->write($rows_output,$cols_output++,$foilname);
! 331: $worksheet->write($rows_output,$cols_output++,$foiltext);
! 332: my $option_start_col = $cols_output;
! 333: #
! 334: foreach my $option (sort(@{$respdata->{'_Options'}})){
! 335: $cols_output= $option_start_col;
! 336: $worksheet->write($rows_output,$cols_output++,
! 337: $option);
! 338: my $count=
! 339: $data->{'foil_responses'}->{$foilid}->{$option};
! 340: $worksheet->write($rows_output,$cols_output++,$count);
! 341: $rows_output++;
! 342: }
! 343: }
! 344: } elsif (exists($data->{'_count'}) &&
! 345: exists($data->{'foil_values'}) &&
! 346: exists($data->{'map'})) {
! 347: my $respdata = $problem_data->{$partid.'.'.$respid};
! 348: my @rowdata = ('Foil Name','Foil Text','Frequency');
! 349: $worksheet->write_row($rows_output++,0,
! 350: \@rowdata,$format->{'h4'});
! 351: foreach my $value (sort(keys(%{$data->{'foil_values'}}))) {
! 352: undef(@rowdata);
! 353: my $foilid = $data->{'map'}->{$value};
! 354: push(@rowdata,$respdata->{'_Foils'}->{$foilid}->{'name'});
! 355: push(@rowdata,$respdata->{'_Foils'}->{$foilid}->{'text'});
! 356: push(@rowdata,$data->{'foil_values'}->{$value});
! 357: $worksheet->write_row($rows_output++,0,\@rowdata);
! 358: }
! 359: }
! 360: $rows_output++;
! 361: } #response ids
! 362: } # partids
! 363: $workbook->close();
! 364: $r->print('<p><a href="'.$filename.'">'.
! 365: &mt('Your Excel spreadsheet.').
! 366: '</a></p>'."\n");
! 367: $r->print('<script>'.
! 368: 'window.document.Statistics.stats_status.value="'.
! 369: &mt('Done compiling spreadsheet. See link below to download.').
! 370: '";</script>');
! 371: $r->rflush();
! 372: return;
! 373: }
! 374:
! 375: #########################################################
! 376: #########################################################
! 377: ##
1.1 matthew 378: ## make_HTML_report
379: ##
380: #########################################################
381: #########################################################
382: sub make_HTML_report {
383: my ($r,$problem,$ProblemData,$Students) = @_;
384: &Compile_Student_Answers($problem,$ProblemData,$Students);
385: # &output_hash('',$ProblemData);
386: my $resource = $problem->{'resource'};
1.3 matthew 387: foreach my $partid (@{$resource->parts}) {
388: my @response_ids = $resource->responseIds($partid);
389: my @response_types = $resource->responseType($partid);
390: for (my $i=0;$i<=$#response_ids;$i++) {
1.1 matthew 391: my $Str = '<table>'.$/;
1.3 matthew 392: my $respid = $response_ids[$i];
393: my $resptype = $response_types[$i];
1.1 matthew 394: my $data = $problem->{'responsedata'}->{$partid}->{$respid};
1.3 matthew 395: if (! defined($data) || ref($data) ne 'HASH') {
396: next;
397: }
1.1 matthew 398: # Debugging code
399: # $Str .= '<tr>'.
400: # '<td>'.$partid.'</td>'.
401: # '<td>'.$respid.'</td>'.
402: # '<td>'.$resptype.'</td>'.
403: # '</tr>'.$/;
404: $Str .= '<tr>'.
405: '<td><b>'.&mt('Total').'</b></td>'.
406: '<td>'.$data->{'_count'}.'</td>'.
1.2 matthew 407: '<td>'.&mt('Part [_1], Response [_2]',$partid,$respid).'</td>'.
1.3 matthew 408: '</tr>';
1.1 matthew 409: if (exists($data->{'responses'}) &&
410: ref($data->{'responses'}) eq 'ARRAY') {
411: &randomize_array($data->{'responses'});
412: foreach my $response (@{$data->{'responses'}}) {
413: $response =~ s/\\r\\n/\n/g;
414: $response =~ s/\\'/'/g;
415: $response =~ s/\\"/"/g;
416: $Str .= '<tr>'.
417: '<td colspan="3"><pre>'.
418: &HTML::Entities::encode($response,'<>&').
419: '</pre><hr /></td>'.
420: '</tr>'.$/;
421: }
1.3 matthew 422: } elsif (exists($data->{'foil_count'}) &&
423: exists($data->{'foil_responses'})) {
424: $Str.='<tr><td colspan="3">'.
425: '<table><tr>';
426: my $tmp = '<th>'.join('</th><th>',
427: (&mt('Foil Name'),
428: &mt('Foil Text'),
429: &mt('Option'),
430: &mt('Frequency'),
431: &mt('Percent'))).'</th></tr>';
432: my @foils = sort(keys(%{$data->{'foil_responses'}}));
433: foreach my $foilid (@foils) {
434: my $prob_data = $ProblemData->{$partid.'.'.$respid};
435: my $foil_count = $data->{'foil_count'}->{$foilid};
436: my $foiltext = $prob_data->{'_Foils'}->{$foilid}->{'text'};
437: my $foilname = $prob_data->{'_Foils'}->{$foilid}->{'name'};
438: my $rowspan = scalar(@{$prob_data->{'_Options'}});
439: my $preamble = '<tr>'.
440: '<td valign="top" rowspan="'.$rowspan.'">'.
441: $foilname.'</td>'.
442: '<td valign="top" rowspan="'.$rowspan.'">'.
443: $foiltext.'</td>';
444: foreach my $option (sort(@{$prob_data->{'_Options'}})){
445: my $count =
446: $data->{'foil_responses'}->{$foilid}->{$option};
447: $tmp .= $preamble.
448: '<td>'.$option.'</td>'.
449: '<td align="right">'.$count.'</td>'.
450: '<td align="right">'.
451: sprintf('%.2f',100*$count/$foil_count).'%'.
452: '</td></tr>'.$/;
453: $preamble = '<tr>';
454: }
455: }
456: $Str.=$tmp.'</table></td></tr>';
1.1 matthew 457: } elsif (exists($data->{'_count'}) &&
458: exists($data->{'foil_values'}) &&
459: exists($data->{'map'})) {
460: # This is an option or radiobutton survey response
461: my $total = $data->{'_count'};
462: my $sum = 0;
463: my $tmp;
464: foreach my $value (sort(keys(%{$data->{'foil_values'}}))) {
465: my $count = $data->{'foil_values'}->{$value};
466: my $foilid = $data->{'map'}->{$value};
467: my $foiltext = $ProblemData->{$partid.'.'.$respid}->{'_Foils'}->{$foilid}->{'text'};
468: my $foilname = $ProblemData->{$partid.'.'.$respid}->{'_Foils'}->{$foilid}->{'name'};
469: $tmp .= '<tr>'.
470: '<td>'.$foilname.'</td>'.
471: '<td>'.$foiltext.'</td>'.
472: '<td align="right">'.$count.'</td>'.
473: '<td align="right">'.
474: sprintf("%.2f",$count/$total*100).'%</td>'.
475: '</tr>'.$/;
476: }
477: $Str .= '<tr>'.
478: '<th>'.&mt('Foil Name').'</th>'.
479: '<th>'.&mt('Text').'</th>'.
480: '<th>'.&mt('Freq').'</th>'.
481: '<th>'.&mt('Percent').'</th>'.
482: '</tr>'.$/.
483: $tmp;
484: }
485: $Str.= '</table><hr />';
486: $r->print($Str);
487: $r->rflush();
488: }
489: }
490: return;
491: }
492:
493: sub randomize_array {
494: # Fisher Yates shuffle, lifted from p 121 of "The Perl Cookbook"
495: my ($array) = @_;
496: for (my $i=scalar(@$array);--$i;) {
497: my $j = int(rand($i+1));
498: next if ($i == $j);
499: @$array[$i,$j]=@$array[$j,$i];
500: }
501: }
502:
503: #########################################################
504: #########################################################
505: ##
506: ## Generic Interface Routines
507: ##
508: #########################################################
509: #########################################################
510: sub CreateInterface {
511: ##
512: ## Environment variable initialization
513: my $Str = '';
1.4 ! matthew 514: my $output_selector = '<select name="output" size="5">'.$/;
! 515: if (! exists($ENV{'form.output'})) {
! 516: $ENV{'form.output'} = 'HTML';
! 517: }
! 518: foreach my $output_format ( {name=>'HTML',text=>&mt("HTML") },
! 519: {name=>'Excel',text=>&mt("Excel") }) {
! 520: $output_selector.='<option value="'.$output_format->{'name'}.'"';
! 521: if ($ENV{'form.output'} eq $output_format->{'name'}) {
! 522: $output_selector.=' selected';
! 523: }
! 524: $output_selector.= '>'.$output_format->{'text'}.'</option>'.$/;
! 525: }
! 526: $output_selector .= '</select>'.$/;
1.1 matthew 527: $Str .= &Apache::lonhtmlcommon::breadcrumbs
528: (undef,'Student Submission Reports');
529: $Str .= '<p>';
530: $Str .= '<table cellspacing="5">'."\n";
531: $Str .= '<tr>';
532: $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
533: $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
1.4 ! matthew 534: $Str .= '<td align="center"><b>'.&mt('Output Format').'</b></td>';
1.1 matthew 535: $Str .= '</tr>'."\n";
536: #
537: $Str .= '<tr><td align="center">'."\n";
538: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
539: $Str .= '</td>';
540: #
541: $Str .= '<td align="center">';
542: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
543: $Str .= '</td>';
544: #
1.4 ! matthew 545: $Str .= '<td align="center">'.$output_selector.'</td>';
! 546: #
1.1 matthew 547: $Str .= '</tr>'."\n";
548: $Str .= '</table>'."\n";
549: #
550: $Str .= '<nobr>'.&mt('Status: [_1]',
551: '<input type="text" '.
552: 'name="stats_status" size="60" value="" />').
553: '</nobr>'.'</p>';
554: ##
555: return $Str;
556: }
557:
558: 1;
559:
560: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>