source: branches/blt4/gui/scripts/bugreport.tcl @ 1970

Last change on this file since 1970 was 1933, checked in by gah, 14 years ago
File size: 24.4 KB
Line 
1# ----------------------------------------------------------------------
2#  UTILITY: bugreport
3#
4#  This redefines the usual Tcl bgerror command to install a nicer
5#  looking bug handler.  Bug reports can be submitted back to a
6#  HUBzero-based site as support tickets.  Additional information
7#  can be obtained by defining procedures as bugreport::instrumented
8#  proc (captures entrance/exit from proc) and by calling
9#  bugreport::remark with extra info along the way.
10# ======================================================================
11#  AUTHOR:  Michael McLennan, Purdue University
12#  Copyright (c) 2004-2006  Purdue Research Foundation
13#
14#  See the file "license.terms" for information on usage and
15#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
16# ======================================================================
17option add *BugReport*Label.font {Helvetica -12} startupFile
18option add *BugReport*banner*foreground white startupFile
19option add *BugReport*banner*background #a9a9a9 startupFile
20option add *BugReport*banner*highlightBackground #a9a9a9 startupFile
21option add *BugReport*banner.title.font {Helvetica -18 bold} startupFile
22option add *BugReport*xmit*wrapLength 3i startupFile
23option add *BugReport*expl.width 50 startupFile
24option add *BugReport*expl.font {Helvetica -12} startupFile
25option add *BugReport*expl.boldFont {Helvetica -12 bold} startupFile
26option add *BugReport*comments.l.font {Helvetica -12 italic} startupFile
27option add *BugReport*comments.info.text.font {Helvetica -12} startupFile
28option add *BugReport*details*font {Courier -12} startupFile
29
30namespace eval Rappture::bugreport {
31    # details from the current trouble report, which user may decide to submit
32    variable details
33
34    # status from bugreport::instrumented/remark in case a bug occurs
35    variable extraStack ""
36    variable extraInfo ""
37
38    # assume that if there's a problem launching a job, we should know it
39    variable reportJobFailures 1
40
41    # submit these kinds of tickets by default
42    variable settings
43    set settings(user) $::tcl_platform(user)
44    set settings(type) "automatic"
45    set settings(group) ""
46    set settings(category) "Rappture"
47}
48
49# ----------------------------------------------------------------------
50# USAGE: install
51#
52# Called once in the main program to install this bug reporting
53# facility.  Any unexpected errors after this call will be handled
54# by this mechanism.
55# ----------------------------------------------------------------------
56proc Rappture::bugreport::install {} {
57    ::proc ::bgerror {err} { ::Rappture::bugreport::activate $err }
58}
59
60# ----------------------------------------------------------------------
61# USAGE: activate <error>
62#
63# Used internally to pop up the bug handler whenver a bug is
64# encountered.  Tells the user that there is a bug and logs the
65# problem, so it can be fixed.
66# ----------------------------------------------------------------------
67proc Rappture::bugreport::activate {err} {
68    global env errorInfo
69    variable details
70    variable settings
71
72    if {"@SHOWDETAILS" == $err} {
73        pack propagate .bugreport yes
74        pack forget .bugreport.expl
75        pack forget .bugreport.xmit
76        pack forget .bugreport.done
77        pack forget .bugreport.cntls.show
78        pack .bugreport.cntls -after .bugreport.banner -side bottom -fill x
79        pack .bugreport.details -after .bugreport.banner \
80            -expand yes -fill both -padx 8 -pady 8
81        pack .bugreport.comments -after .bugreport.details \
82            -expand yes -fill both -padx 8 -pady {0 8}
83
84        update idletasks
85        set w [winfo reqwidth .bugreport]
86        set h [winfo reqheight .bugreport]
87        set x [expr {([winfo screenwidth .bugreport]-$w)/2}]
88        if {$x < 0} {set x "+0"} else {set x "+$x"}
89        set y [expr {([winfo screenheight .bugreport]-$h)/2}]
90        if {$y < 0} {set y "-0"} else {set y "+$y"}
91
92        wm geometry .bugreport $x$y
93        raise .bugreport
94        return
95    }
96
97    # gather details so we can submit trouble reports later
98    # do this now, before we do anything with "catch" down below
99    # that might mask the errorInfo
100    register $err
101
102    pack propagate .bugreport yes
103    pack forget .bugreport.details
104    pack forget .bugreport.xmit
105    pack forget .bugreport.done
106    pack .bugreport.cntls.show -side right
107    pack .bugreport.cntls -after .bugreport.banner -side bottom -fill x
108    pack .bugreport.expl -after .bugreport.banner \
109        -expand yes -fill both -padx 8 -pady 8
110    pack .bugreport.comments -after .bugreport.expl \
111        -expand yes -fill both -padx 8 -pady {0 8}
112
113    .bugreport.expl configure -state normal
114    .bugreport.expl delete 1.0 end
115
116    set url [Rappture::Tool::resources -huburl]
117    if {"" != $url} {
118        .bugreport.expl insert end "Something went wrong with this tool.  Help us understand what happened by submitting a trouble report, so we can fix the problem.  If you continue having trouble with this tool, please close it and restart."
119        .bugreport.cntls.send configure -state normal
120        focus .bugreport.cntls.send
121    } else {
122        .bugreport.expl insert end "Something went wrong with this tool.  We would ask you to submit a trouble report about the error, but we can't tell what hub it should be submitted to.  If you continue having trouble with this tool, please close it and restart."
123        pack forget .bugreport.comments
124        .bugreport.cntls.send configure -state disabled
125        focus .bugreport.cntls.ok
126    }
127    fixTextHeight .bugreport.expl
128    .bugreport.expl configure -state disabled
129
130    .bugreport.details.info.text configure -state normal
131    .bugreport.details.info.text delete 1.0 end
132    .bugreport.details.info.text insert end "    USER: $settings(user)\n"
133    .bugreport.details.info.text insert end "HOSTNAME: $details(hostname)\n"
134    .bugreport.details.info.text insert end "PLATFORM: $details(platform)\n"
135    .bugreport.details.info.text insert end "CATEGORY: $details(category)\n"
136    .bugreport.details.info.text insert end "    TOOL: $details(referrer)\n"
137    .bugreport.details.info.text insert end " SESSION: $details(session)\n"
138    .bugreport.details.info.text insert end " SUMMARY: $details(summary)\n"
139    .bugreport.details.info.text insert end "---------\n"
140    .bugreport.details.info.text insert end $details(stackTrace)
141    .bugreport.details.info.text configure -state disabled
142
143    set w [winfo reqwidth .bugreport]
144    set h [winfo reqheight .bugreport]
145    set x [expr {([winfo screenwidth .bugreport]-$w)/2}]
146    if {$x < 0} {set x 0}
147    set y [expr {([winfo screenheight .bugreport]-$h)/2}]
148    if {$y < 0} {set y 0}
149
150    wm geometry .bugreport +$x+$y
151    wm deiconify .bugreport
152    raise .bugreport
153
154    catch {grab set .bugreport}
155    update
156}
157
158# ----------------------------------------------------------------------
159# USAGE: deactivate
160#
161# Used internally to take down the bug handler dialog.
162# ----------------------------------------------------------------------
163proc Rappture::bugreport::deactivate {} {
164    grab release .bugreport
165    wm withdraw .bugreport
166
167    # reset the grab in case it's hosed
168    Rappture::grab::reset
169}
170
171# ----------------------------------------------------------------------
172# USAGE: instrumented <what> <name> <arglist> <body>
173#
174# Used instead of the usual Tcl "proc" or itcl::body to define a
175# procedure that will automatically register information about its
176# execution in the bugreport mechanism.  The <what> parameter should
177# be either "proc" or "itcl::body" or something like that.  When the
178# procedure starts, it pushes its call information onto the stack,
179# then invokes the procedure body, then adds information about the
180# return code.
181# ----------------------------------------------------------------------
182proc Rappture::bugreport::instrumented {what name arglist body} {
183    set avals ""
184    foreach term $arglist {
185        set aname [lindex $term 0]
186        append avals "\$$aname "
187    }
188    uplevel [list $what $name $arglist [format {
189        Rappture::bugreport::remark -enter "PROC %s: %s"
190        set __status [catch {%s} __result]
191        Rappture::bugreport::remark -leave "PROC %s: code($__status) => $__result"
192        switch -- $__status {
193            0 - 2 {
194                return $__result
195            }
196            3 {
197                set __result "invoked \"break\" outside of a loop"
198            }
199            4 {
200                set __result "invoked \"continue\" outside of a loop"
201            }
202        }
203        error $__result $::errorInfo
204    } $name $avals $body $name]]
205}
206
207# ----------------------------------------------------------------------
208# USAGE: remark ?-enter|-leave? <message>
209#
210# Adds the <message> to the current "extraInfo" being kept about the
211# program.  This adds useful debugging info to the report that gets
212# sent back when an unexpected error is trapped.  The -enter and -leave
213# options are used when a bugreport::instrumented proc starts/exits to
214# change the indent level for future messages.
215# ----------------------------------------------------------------------
216proc Rappture::bugreport::remark {args} {
217    variable extraStack
218    variable extraInfo
219
220    if {[llength $args] > 1} {
221        set option [lindex $args 0]
222        set args [lrange $args 1 end]
223        switch -- $option {
224            -enter {
225                if {[llength $args] != 1} {
226                    error "wrong # args: should be \"remark -enter message\""
227                }
228                set mesg [lindex $args 0]
229                if {[llength $extraStack] == 0} {
230                    set extraInfo ""
231                }
232                append extraInfo [remark -indent ">> $mesg"]
233                set extraStack [linsert $extraStack 0 $mesg]
234                return
235            }
236            -leave {
237                if {[llength $args] != 1} {
238                    error "wrong # args: should be \"remark -leave message\""
239                }
240                set mesg [lindex $args 0]
241                set extraStack [lrange $extraStack 1 end]
242                append extraInfo [remark -indent "<< $mesg"]
243                return
244            }
245            -indent {
246                if {[llength $args] != 1} {
247                    error "wrong # args: should be \"remark -indent message\""
248                }
249            }
250            default {
251                error "bad option \"$option\": should be -enter, -leave, -indent"
252            }
253        }
254    }
255    set mesg [lindex $args 0]
256    set nlevel [llength $extraStack]
257    set indent [string repeat { } [expr {2*$nlevel}]]
258    foreach line [split $mesg \n] {
259        append extraInfo "$indent$line\n"
260        set prefix "   "
261    }
262}
263
264# ----------------------------------------------------------------------
265# USAGE: submit
266#
267# Takes details currently stored in the panel and registers them
268# as a support ticket on the hosting hub site.  Pops up a panel
269# during the process and informs the user of the result.
270# ----------------------------------------------------------------------
271proc Rappture::bugreport::submit {} {
272    set info [.bugreport.details.info.text get 1.0 end]
273
274    pack propagate .bugreport no
275    pack forget .bugreport.details
276    pack forget .bugreport.expl
277    pack forget .bugreport.comments
278    pack forget .bugreport.cntls
279    pack .bugreport.xmit -after .bugreport.banner -padx 8 -pady 8
280    .bugreport.xmit.title configure -text "Sending trouble report to [Rappture::Tool::resources -hubname]..."
281
282    # send off the trouble report...
283    .bugreport.xmit.icon start
284    set status [catch send result]
285    .bugreport.xmit.icon stop
286
287    pack propagate .bugreport yes
288    pack forget .bugreport.xmit
289    pack .bugreport.expl -after .bugreport.banner -padx 8 -pady 8
290    .bugreport.expl configure -state normal
291    .bugreport.expl delete 1.0 end
292
293    # handle the result
294    if {$status != 0} {
295        # add error to the details field, so we can see it with magic clicks
296        .bugreport.details.info.text configure -state normal
297        .bugreport.details.info.text insert 1.0 "Ticket submission failed:\n$result\n-----\n"
298        .bugreport.details.info.text configure -state disabled
299
300        .bugreport.expl insert end "Oops! Ticket submission failed:\n$result\n\nIf you want to report the original problem, you can file your own trouble report by going to the web site and clicking on the \"Help\" or \"Support\" link on the main navigation bar.  If you continue having trouble with this tool, please close it and restart."
301    } elseif {[regexp {Ticket #([0-9]*) +\((.*?)\) +([0-9]+) +times} $result match ticket extra times]} {
302        .bugreport.expl insert end "This problem has been reported as " "" "Ticket #$ticket" bold " in our system." ""
303        if {[string is integer $times] && $times > 1} {
304            .bugreport.expl insert end "  This particular problem has been reported $times times."
305        }
306        .bugreport.expl insert end "\n\nIf you continue having trouble with this tool, please close it and restart.  Thanks for reporting the problem and helping us improve things!"
307    } else {
308        .bugreport.expl insert end "This problem has been reported.  Here is the response from the hub, which may contain information about your ticket:\n" "" $result bold "\n\nIf you continue having trouble with this tool, please close it and restart.  Thanks for reporting the problem and helping us improve things!" ""
309    }
310    fixTextHeight .bugreport.expl
311    .bugreport.expl configure -state disabled
312    pack .bugreport.done -side bottom -padx 8 -pady 8
313    focus .bugreport.done
314}
315
316# ----------------------------------------------------------------------
317# USAGE: register <err>
318#
319# Low-level function used to capture information about a bug report
320# prior to calling "send", which actually sends the ticket.  We usually
321# let the user preview the information and decide whether or not to
322# send the ticket.
323# ----------------------------------------------------------------------
324proc Rappture::bugreport::register {err} {
325    global errorInfo tcl_platform
326    variable details
327    variable settings
328    variable extraInfo
329
330    #
331    # Figure out exactly what we'll send if the bug report is
332    # submitted, so we can show the user.
333    #
334    set stackTrace "$err\n---------\n$errorInfo\n---------\n$extraInfo"
335    if {![regexp {^([^\n]+)\n} $stackTrace match summary]} {
336        if {[string length $stackTrace] == 0} {
337            set summary "Unexpected error from Rappture"
338        } else {
339            set summary $stackTrace
340        }
341    }
342    if {[string length $summary] > 200} {
343        set summary "[string range $summary 0 200]..."
344    }
345    if {[string match {Problem launching job*} $summary]} {
346        append summary " (in tool \"[Rappture::Tool::resources -appname]\")"
347        set category "Tools"
348    } else {
349        set category $settings(category)
350    }
351
352    # make sure that the stack trace isn't too long
353    set toolong 20000
354    if {[string length $stackTrace] > $toolong} {
355        #
356        # If this came from "Problem launching job", then it will have
357        # a "== RAPPTURE INPUT ==" part somewhere in the middle.  Try
358        # to show the first part, this middle part, and the very last
359        # part, cutting out whatever we have to in the middle.
360        #
361        if {[regexp -indices {\n== RAPPTURE INPUT ==\n} $stackTrace match]} {
362            foreach {smid0 smid1} $match break
363            set quarter [expr {$toolong/4}]
364            set s0 $quarter
365            set smid0 [expr {$smid0-$quarter}]
366            set smid1 [expr {$smid1+$quarter}]
367            set s1 [expr {[string length $stackTrace]-$quarter}]
368
369            if {$smid0 < $s0} {
370                # first part is short -- truncate last part
371                set stackTrace "[string range $stackTrace 0 $smid1]\n...\n[string range $stackTrace [expr {[string length $stackTrace]-($toolong-$smid1)}] end]"
372            } elseif {$smid1 > $s1} {
373                # last part is short -- truncate first part
374                set tailsize [expr {[string length $stackTrace]-$smid0}]
375                set stackTrace "[string range $stackTrace 0 [expr {$toolong-$tailsize}]]\n...\n[string range $stackTrace $smid0 end]"
376            } else {
377                # rappture input line is right about in the middle
378                set stackTrace "[string range $stackTrace 0 $s0]\n...\n[string range $stackTrace $smid0 $smid1]\n...\n[string range $stackTrace $s1 end]"
379            }
380        } else {
381            # no Rappture input -- just show first part and last part
382            set half [expr {$toolong/2}]
383            set stackTrace "[string range $stackTrace 0 $half]\n...\n[string range $stackTrace [expr {[string length $stackTrace]-$half}] end]"
384        }
385    }
386
387    set details(summary) $summary
388    set details(category) $category
389    set details(stackTrace) $stackTrace
390    set details(hostname) [info hostname]
391    set details(session) [Rappture::Tool::resources -session]
392    set details(referrer) [Rappture::Tool::resources -appname]
393    set details(platform) [array get tcl_platform]
394}
395
396# ----------------------------------------------------------------------
397# USAGE: send
398#
399# Low-level function used to send bug reports back to the hub site.
400# Error details gathered by a previous call to "register" are sent
401# along as a support ticket.  Returns a string of the following form,
402# representing details about the new or existing ticket:
403#   Ticket #XX (XXXXXX) XX times
404# ----------------------------------------------------------------------
405proc Rappture::bugreport::send {} {
406    variable details
407    variable settings
408
409    package require http
410    package require tls
411    http::register https 443 ::tls::socket
412
413    set report $details(stackTrace)
414    set cmts [string trim [.bugreport.comments.info.text get 1.0 end]]
415    if {[string length $cmts] > 0} {
416        set report "$cmts\n[string repeat = 72]\n$report"
417    }
418
419    set query [http::formatQuery \
420        option com_support \
421        task create \
422        no_html 1 \
423        report $report \
424        sesstoken $details(session) \
425        hostname $details(hostname) \
426        os $details(platform) \
427        category $details(category) \
428        summary $details(summary) \
429        referrer $details(referrer) \
430        login $settings(user) \
431        group $settings(group) \
432        type $settings(type) \
433    ]
434    set url [Rappture::Tool::resources -huburl]
435    if { $url == "" } {
436        set url "http://hubzero.org"
437    }
438    if {[string index $url end] == "/"} {
439        append url "index.php"
440    } else {
441        append url "/index.php"
442    }
443
444    set token [http::geturl $url -query $query -timeout 60000]
445
446    if {[http::ncode $token] != 200} {
447        error [http::code $token]
448    }
449    upvar #0 $token rval
450    set info $rval(body)
451    http::cleanup $token
452
453    if {[regexp {Ticket #[0-9]* +\(.*?\) +[0-9]+ +times} $info match]} {
454        return $match
455    }
456    error "Report received, but ticket may not have been filed.  Here's the result...\n$info"
457}
458
459# ----------------------------------------------------------------------
460# USAGE: fixTextHeight <widget>
461#
462# Used internally to adjust the height of a text widget so it is just
463# tall enough to show the info within it.
464# ----------------------------------------------------------------------
465proc Rappture::bugreport::fixTextHeight {widget} {
466    #
467    # HACK ALERT!  In Tk8.5, we can count display lines directly.
468    #   But for earlier versions, we have to cook up something
469    #   similar.
470    #
471    if {[catch {$widget count -displaylines 1.0 end} h] == 0 && $h > 0} {
472        $widget configure -height $h
473    } else {
474        for {set h 1} {$h < 15} {incr h} {
475            $widget configure -height $h
476            $widget see 1.0
477            update idletasks
478            if {"" != [$widget bbox end-1char]} {
479                break
480            }
481        }
482    }
483}
484
485# ----------------------------------------------------------------------
486# USAGE: shouldReport jobfailures <boolean>
487# USAGE: shouldReport for ?oops|jobs?
488#
489# Used internally to determine whether or not this system should
490# automatically report errors back to the hosting hub.  Returns 1
491# if the tool should, and 0 otherwise.  The decision is made based
492# on whether this is a current tool in production, whether it is
493# being tested in a workspace, and whether the tool commonly generates
494# problems (by pilot error in its input deck).
495# ----------------------------------------------------------------------
496proc Rappture::bugreport::shouldReport {option value} {
497    global env
498
499    switch -- $option {
500        jobfailures {
501            variable reportJobFailures
502            if {![string is boolean $value]} {
503                error "bad value \"$value\": should be boolean"
504            }
505            set reportJobFailures $value
506        }
507        for {
508            # is it being run within a workspace?
509            set appname [Rappture::Tool::resources -appname]
510            if {[string match {[Ww]orkspace*} $appname]} {
511                return 0
512            }
513
514            # if this is a problem launching a job and the tool
515            # expects this, then don't bother with automatic reports.
516            variable reportJobFailures
517            if {"jobs" == $value && !$reportJobFailures} {
518                return 0
519            }
520
521            # this is a real problem -- report it!
522            return 1
523        }
524        default {
525            error "bad option \"$option\": should be jobfailures or for"
526        }
527    }
528}
529
530# ----------------------------------------------------------------------
531# Build the bug reporting dialog
532# ----------------------------------------------------------------------
533toplevel .bugreport -class BugReport -borderwidth 1 -relief solid
534wm overrideredirect .bugreport 1
535wm withdraw .bugreport
536
537frame .bugreport.banner -background #a9a9a9
538pack .bugreport.banner -side top -fill x
539label .bugreport.banner.icon -image [Rappture::icon alert]
540pack .bugreport.banner.icon -side left -padx 2 -pady 2
541label .bugreport.banner.title -text "Oops! Internal Error"
542pack .bugreport.banner.title -side left -padx {0 8} -pady 2
543
544# add these frustration bindings in case the "Dismiss" button is off screen
545bind .bugreport.banner.icon <Double-ButtonPress-1> \
546    Rappture::bugreport::deactivate
547bind .bugreport.banner.title <Double-ButtonPress-1> \
548    Rappture::bugreport::deactivate
549
550set bg [.bugreport cget -background]
551text .bugreport.expl -borderwidth 0 -highlightthickness 0 -background $bg \
552    -height 3 -wrap word
553.bugreport.expl tag configure bold \
554    -font [option get .bugreport.expl boldFont Font]
555#
556# HACK ALERT!  We have problems with fixTextHeight working correctly
557#   on Windows for Tk8.4 and earlier.  To make it work properly, we
558#   add the binding below.  At some point, we'll ditch 8.4 and we can
559#   use the new "count -displaylines" option in Tk8.5.
560#
561bind .bugreport.expl <Map> {Rappture::bugreport::fixTextHeight %W}
562
563frame .bugreport.comments
564label .bugreport.comments.l -text "What were you doing just before this error?" -anchor w
565pack .bugreport.comments.l -side top -anchor w
566Rappture::Scroller .bugreport.comments.info -xscrollmode none -yscrollmode auto
567text .bugreport.comments.info.text -width 30 -height 3 -wrap word
568.bugreport.comments.info contents .bugreport.comments.info.text
569bind .bugreport.comments.info.text <ButtonPress> {focus %W}
570pack .bugreport.comments.info -expand yes -fill both
571
572frame .bugreport.cntls
573pack .bugreport.cntls -side bottom -fill x
574button .bugreport.cntls.ok -text "Ignore" -command {
575    Rappture::bugreport::deactivate
576}
577pack .bugreport.cntls.ok -side left -padx {4 20} -pady 8
578button .bugreport.cntls.send -text "Send Trouble Report" -command {
579    Rappture::bugreport::submit
580}
581pack .bugreport.cntls.send -side right -padx 4 -pady 8
582
583button .bugreport.cntls.show -text "Show Details..." \
584    -command {Rappture::bugreport::activate @SHOWDETAILS}
585pack .bugreport.cntls.show -side right
586
587
588frame .bugreport.details
589Rappture::Scroller .bugreport.details.info -xscrollmode auto -yscrollmode auto
590text .bugreport.details.info.text -width 50 -height 15 -wrap none
591.bugreport.details.info contents .bugreport.details.info.text
592pack .bugreport.details.info -expand yes -fill both
593
594frame .bugreport.xmit
595Rappture::Animicon .bugreport.xmit.icon -images {
596    circle-ball1 circle-ball2 circle-ball3 circle-ball4
597    circle-ball5 circle-ball6 circle-ball7 circle-ball8
598}
599pack .bugreport.xmit.icon -side left
600label .bugreport.xmit.title -anchor w
601pack .bugreport.xmit.title -side left -expand yes -fill x
602
603button .bugreport.done -text "Done" \
604    -command Rappture::bugreport::deactivate
605
606# this binding keeps the bugreport window on top
607bind BugReportOnTop <ButtonPress> {
608    wm deiconify %W
609    raise %W
610}
611set btags [bindtags .bugreport]
612bindtags .bugreport [linsert $btags 0 BugReportOnTop]
Note: See TracBrowser for help on using the repository browser.