Annotation of loncom/interface/statistics/lonsurveyreports.pm, revision 1.5
1.1 matthew 1: # The LearningOnline Network with CAPA
2: #
1.5 ! matthew 3: # $Id: lonsurveyreports.pm,v 1.4 2005/03/10 00:23:15 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.1 matthew 128: $r->print(&Apache::lonstathelpers::render_resource($resource));
129: $r->rflush();
130: my %Data = &Apache::lonstathelpers::get_problem_data
1.3 matthew 131: ($resource->src);
1.4 matthew 132: if ($ENV{'form.output'} eq 'HTML' ||
133: ! defined($ENV{'form.output'})) {
134: &make_HTML_report($r,$current_problem,\%Data,\@Students);
135: } elsif ($ENV{'form.output'} eq 'Excel') {
136: &make_Excel_report($r,$current_problem,\%Data,\@Students);
137: }
1.1 matthew 138: }
139: $r->print('<hr />');
140: } else {
141: $r->print('<input type="submit" name="Generate" value="'.
142: &mt('Generate Survey Report').'" />');
143: $r->print(' 'x5);
144: $r->print('<h3>'.&mt('Please select a Survey to analyze').'</h3>');
145: $r->print(&SurveyProblemSelector());
146: }
147: }
148:
149: ##########################################################
150: ##########################################################
151: ##
152: ## SurveyProblemSelector
153: ##
154: ##########################################################
155: ##########################################################
156: sub SurveyProblemSelector {
157: my $Str = '';
158: my @SurveyProblems;
1.3 matthew 159: my ($navmap,@sequences) =
160: &Apache::lonstatistics::selected_sequences_with_assessments('all');
161: foreach my $seq (@sequences) {
162: my @resources = &Apache::lonstathelpers::get_resources($navmap,$seq);
163: foreach my $res (@resources) {
164: foreach my $part (@{$res->parts}) {
165: if ($res->is_survey($part)) {
1.1 matthew 166: push(@SurveyProblems,{res=>$res,seq=>$seq,part=>$part});
167: last;
168: }
169: }
170: }
171: }
172: if (! scalar(@SurveyProblems)) {
173: $Str = '<h1>'.
174: &mt('There are no survey problems in this course').
175: '</h1>'.$/;
176: return $Str;
177: }
178: $Str .= '<table>'.$/;
179: $Str .= '<tr>'.'<td></td>'.
1.3 matthew 180: '<th>'.&mt('Survey').'</th>'.
1.1 matthew 181: '</tr>'.$/;
1.3 matthew 182: my $id;
1.1 matthew 183: foreach my $problem (@SurveyProblems) {
1.3 matthew 184: $id++;
1.1 matthew 185: my $value = &Apache::lonstathelpers::make_target_id
1.3 matthew 186: ({symb=>$problem->{'res'}->symb,
1.1 matthew 187: part=>$problem->{'part'},
188: respid=>undef,
189: resptype=>undef});
190: my $checked = '';
191: if ($ENV{'form.problemchoice'} eq $value) {
192: $checked = 'checked ';
193: }
1.3 matthew 194: my $link = $problem->{'res'}->src.
195: '?symb='.&Apache::lonnet::escape($problem->{'res'}->symb);
196: $Str .= '<tr><td>'.
197: '<input type="radio" name="problemchoice" id="'.$id.'" '.
1.1 matthew 198: 'value="'.$value.'" '.$checked.'/>'.'</td>'.
1.3 matthew 199: '<td><nobr>'.
200: '<label for="'.$id.'">'.$problem->{'res'}->compTitle.'('.$problem->{'seq'}->compTitle.')'.'</lablel>'.
201: (' 'x2).
202: qq{<a target="preview" href="$link">view</a>}.'</td></tr>'.$/;
1.1 matthew 203: }
204: $Str .= '</table>';
205: return $Str;
206: }
207:
208: #########################################################
209: #########################################################
210: ##
211: ## Compile Student Answers
212: ##
213: #########################################################
214: #########################################################
215: sub Compile_Student_Answers {
216: my ($problem,$ProblemData,$Students) = @_;
217: my $resource = $problem->{'resource'};
218: foreach my $student (@$Students) {
1.3 matthew 219: foreach my $partid (@{$resource->parts}) {
220: my @response_ids = $resource->responseIds($partid);
221: my @response_types = $resource->responseType($partid);
222: for (my $i=0;$i<=$#response_ids;$i++) {
223: my $respid = $response_ids[$i];
224: my $resptype = $response_types[$i];
1.1 matthew 225: my $results =
226: &Apache::loncoursedata::get_response_data_by_student
1.3 matthew 227: ($student,$resource->symb,$respid);
1.1 matthew 228: next if (! defined($results) || ref($results) ne 'ARRAY' ||
229: ref($results->[0]) ne 'ARRAY');
230: my $student_response =
231: $results->[0]->[&Apache::loncoursedata::RDs_submission()];
232: $problem->{'responsedata'}->{$partid}->{$respid}->{'_count'}++;
233: my $data = $problem->{'responsedata'}->{$partid}->{$respid};
1.3 matthew 234: if ($resptype =~ /^(option|match)$/) {
235: my @responses = split('&',$student_response);
236: foreach my $response (@responses) {
237: my ($foilid,$option) =
238: map {
239: &Apache::lonnet::unescape($_);
240: } split('=',$response);
241: $data->{'foil_count'}->{$foilid}++;
242: $data->{'foil_responses'}->{$foilid}->{$option}++;
243: }
244: } elsif ($resptype =~ /^(radiobutton)$/) {
245: my ($foil,$value) = map { &Apache::lonnet::unescape($_); } split('=',$student_response);
1.1 matthew 246: $value += 1; # explicitly increment it...
247: $data->{'foil_responses'}->{$foil}++;
248: $data->{'foil_values'}->{$value}++;
249: if (! exists($data->{'map'}->{$value})) {
250: $data->{'map'}->{$value} = $foil;
251: }
252: } else {
253: # Variable stuff (essays, raw numbers, strings) go here
254: push(@{$data->{'responses'}},$student_response);
255: }
256: }
257: }
258: }
259: return;
260: }
261:
262: #########################################################
263: #########################################################
264: ##
1.4 matthew 265: ## make_Excel_report
266: ##
267: #########################################################
268: #########################################################
269: sub make_Excel_report {
270: my ($r,$problem,$problem_data,$students) = @_;
271: &Compile_Student_Answers($problem,$problem_data,$students);
272: my ($workbook,$filename,$format) = &Apache::loncommon::create_workbook($r);
273: if (! defined($workbook)) { return '';}
274: $r->print('<script>'.
275: 'window.document.Statistics.stats_status.value="'.
276: &mt('Building spreadsheet.').
277: '";</script>');
278: my $worksheet = $workbook->addworksheet('Survey Reports');
279: #
280: my $rows_output=0;
281: $worksheet->write($rows_output++,0,
282: $ENV{'course.'.$ENV{'request.course.id'}.'.description'},
283: $format->{'h1'});
284: $rows_output++;
285: #
286: my $resource = $problem->{'resource'};
287: $worksheet->write($rows_output++,0,$resource->compTitle,$format->{'h2'});
288: foreach my $partid (@{$resource->parts}) {
289: my @response_ids = $resource->responseIds($partid);
290: my @response_types = $resource->responseType($partid);
291: for (my $i=0;$i<=$#response_ids;$i++) {
292: my $respid = $response_ids[$i];
293: my $resptype = $response_types[$i];
294: my $data = $problem->{'responsedata'}->{$partid}->{$respid};
295: my $cols_output=0;
296: $worksheet->write($rows_output,$cols_output++,
297: $resource->part_display($partid),$format->{'h3'});
298: $worksheet->write($rows_output,$cols_output++,
299: 'Response '.$respid.', '.$resptype,
300: $format->{'h3'});
301: $rows_output++;
302: if (exists($data->{'responses'}) &&
303: ref($data->{'responses'}) eq 'ARRAY') {
304: my $warned_about_size = 0;
305: foreach my $data (@{$data->{'responses'}}) {
306: if (length($data) > 255 && ! $warned_about_size) {
307: $r->print('<p>'.
308: &mt('[_1]:[_2] responses to [_3] may be too long to fit Excel spreadsheet.',
309: $resource->compTitle,
310: $resource->part_display($partid),
311: $respid).
312: '</p>');
313: $r->rflush();
314: $warned_about_size=1;
315: }
316: $worksheet->write($rows_output++,0,$data);
317: }
318: } elsif (exists($data->{'foil_count'}) &&
319: exists($data->{'foil_responses'})) {
320: my $respdata = $problem_data->{$partid.'.'.$respid};
321: my @rowdata = ('Foil Name','Foil Text','Option',
322: 'Frequency');
323: $worksheet->write_row($rows_output++,0,
324: \@rowdata,$format->{'h4'});
325: #
326: my @foils = sort(keys(%{$data->{'foil_responses'}}));
327: foreach my $foilid (@foils) {
328: my $foil_count = $data->{'foil_count'}->{$foilid};
329: my $foiltext = $respdata->{'_Foils'}->{$foilid}->{'text'};
330: my $foilname = $respdata->{'_Foils'}->{$foilid}->{'name'};
331: $foiltext = &HTML::Entities::decode($foilname);
332: my $cols_output=0;
333: $worksheet->write($rows_output,$cols_output++,$foilname);
334: $worksheet->write($rows_output,$cols_output++,$foiltext);
335: my $option_start_col = $cols_output;
336: #
337: foreach my $option (sort(@{$respdata->{'_Options'}})){
338: $cols_output= $option_start_col;
339: $worksheet->write($rows_output,$cols_output++,
340: $option);
341: my $count=
342: $data->{'foil_responses'}->{$foilid}->{$option};
343: $worksheet->write($rows_output,$cols_output++,$count);
344: $rows_output++;
345: }
346: }
347: } elsif (exists($data->{'_count'}) &&
348: exists($data->{'foil_values'}) &&
349: exists($data->{'map'})) {
350: my $respdata = $problem_data->{$partid.'.'.$respid};
351: my @rowdata = ('Foil Name','Foil Text','Frequency');
352: $worksheet->write_row($rows_output++,0,
353: \@rowdata,$format->{'h4'});
354: foreach my $value (sort(keys(%{$data->{'foil_values'}}))) {
355: undef(@rowdata);
356: my $foilid = $data->{'map'}->{$value};
357: push(@rowdata,$respdata->{'_Foils'}->{$foilid}->{'name'});
358: push(@rowdata,$respdata->{'_Foils'}->{$foilid}->{'text'});
359: push(@rowdata,$data->{'foil_values'}->{$value});
360: $worksheet->write_row($rows_output++,0,\@rowdata);
361: }
362: }
363: $rows_output++;
364: } #response ids
365: } # partids
366: $workbook->close();
367: $r->print('<p><a href="'.$filename.'">'.
368: &mt('Your Excel spreadsheet.').
369: '</a></p>'."\n");
370: $r->print('<script>'.
371: 'window.document.Statistics.stats_status.value="'.
372: &mt('Done compiling spreadsheet. See link below to download.').
373: '";</script>');
374: $r->rflush();
375: return;
376: }
377:
378: #########################################################
379: #########################################################
380: ##
1.1 matthew 381: ## make_HTML_report
382: ##
383: #########################################################
384: #########################################################
385: sub make_HTML_report {
386: my ($r,$problem,$ProblemData,$Students) = @_;
387: &Compile_Student_Answers($problem,$ProblemData,$Students);
388: # &output_hash('',$ProblemData);
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>';
535: $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
536: $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
1.4 matthew 537: $Str .= '<td align="center"><b>'.&mt('Output Format').'</b></td>';
1.1 matthew 538: $Str .= '</tr>'."\n";
539: #
540: $Str .= '<tr><td align="center">'."\n";
541: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
542: $Str .= '</td>';
543: #
544: $Str .= '<td align="center">';
545: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
546: $Str .= '</td>';
547: #
1.4 matthew 548: $Str .= '<td align="center">'.$output_selector.'</td>';
549: #
1.1 matthew 550: $Str .= '</tr>'."\n";
551: $Str .= '</table>'."\n";
552: #
553: $Str .= '<nobr>'.&mt('Status: [_1]',
554: '<input type="text" '.
555: 'name="stats_status" size="60" value="" />').
556: '</nobr>'.'</p>';
557: ##
558: return $Str;
559: }
560:
561: 1;
562:
563: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>