source: cows_wps/trunk/cows_wps/controllers/jobviewer.py @ 7610

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/cows_wps/trunk/cows_wps/controllers/jobviewer.py@7610
Revision 7610, 9.4 KB checked in by astephen, 10 years ago (diff)

job viewer page added some extra info and a "stop polling" link.

Line 
1import os
2import re
3import glob
4import logging
5import urllib
6import xml.etree.ElementTree as ET
7from pygments import highlight
8from pygments.lexers import XmlLexer
9from pygments.formatters import HtmlFormatter
10
11from pylons import request, response, session, tmpl_context as c
12from pylons.controllers.util import abort, redirect_to
13from routes import url_for
14
15
16from cows.exceptions import *
17from cows.pylons.ows_controller import render_ows_exception
18
19from cows_wps.controllers import *
20from cows_wps import utils
21from cows_wps.utils.common import *
22from cows_wps.renderer.ui_renderer import *
23from cows_wps.model.managers import requestManager
24from cows_wps.process_handler.context.process_context import ProcessContext
25from cows_wps.renderer import xml_renderer
26
27from cows_wps.utils.parse_wps_config import wps_config_dict
28from cows_wps.utils.parse_capabilities_config import caps_config_dict
29
30
31from cows_wps.model.managers import requestManager
32
33log = logging.getLogger(__name__)
34
35
36class JobviewerController(BaseController):
37
38
39    def index(self):
40        status_url = str(request.params.getone("status_url"))
41        getter = urllib.urlopen(status_url)
42        job_xml = getter.read()
43        getter.close()
44 
45        job_as_table = self._xmlToTable(job_xml)
46        job_xml_encoded = self._htmlifyXML(job_xml)
47        job_as_plots = "There are no plots available in the outputs of this job."
48
49        # General info first...
50        resp = """Please note that offline jobs are scheduled and may take a long time to run. This page will continue to poll offline jobs when they are running unless you click the "stop polling" option when it is visible. You will receive an e-mail when your offline job has run so you do not need to stay on this page. Note that you can view all previous jobs on the <a href="/jobs">Jobs page</a>.<br><br>"""
51
52        resp += """<div id="view_options" style="background: #EEF4FF; border: 2px outset blue; padding: 5px;">
53                <a href="javascript:switchViewTo('table_view');">Normal View</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"""
54
55        poll_info_div = ""
56        if self.status == "complete":
57            resp += """<a href="javascript:switchViewTo('xml_view');">View as XML</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
58                <a href="javascript:switchViewTo('plots_view');">View Plots (if available)</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"""
59        elif self.status == "running":
60            poll_info_div = '<div id="poll_info" class="poller"></div>'
61
62        resp  += """
63</div>
64<div id="view_container" style="background: white; border: 2px outset blue; padding: 5px;">
65        <div id="view_contents">
66                <div id="table_view" style="visibility: visible; height: auto;">%s%s</div>""" % (poll_info_div, job_as_table)
67
68        if self.status == "complete":
69            resp += """
70                <div id="xml_view" style="visibility: hidden; height: 0px;">%s</div>
71                <div id="plots_view" style="visibility: hidden; height: 0px;">%s</div>
72</div>""" % (job_xml_encoded, job_as_plots)
73
74        elif self.status == "running":
75            jobviewer_url = url_for(controller='jobviewer', status_url = status_url)
76            base_url = "http://" + request.url[7:].split("/")[0]
77            full_url = base_url + jobviewer_url
78
79            resp += """<input type="hidden" id="poll_url" value="%s" />""" % status_url
80            resp += """<input type="hidden" id="jobviewer_url" value="%s" />""" % jobviewer_url
81            resp += """<script type="text/javascript">
82        function init() {
83                pollWPS();
84        }
85
86</script>"""
87
88        renderer = UIPageRenderer()
89        resp = renderer.render("WPS Job Information",
90                              [("Job: %s" % self.job_id, resp)])
91        return resp
92
93    def _resolveStatus(self, status_node):
94        snode = status_node.getchildren()[0].tag
95        mdict = {"Succeeded": "complete",
96                 "Failed": "failed",
97                 "Accepted": "running",
98                 "Started": "running"}
99
100        for k,v in mdict.items():
101            if snode.find(k) > -1:
102                return v
103        else:
104            raise Exception("Could not match status tag.")
105
106    def _xmlToTable(self, xml):
107        "Chops up xml to get table of outputs."
108        self.node = ET.fromstring(xml)
109        self.namespace_ows = "http://www.opengeospatial.net/ows"
110        self.namespace = "http://www.opengeospatial.net/wps"
111
112        self.status = self._resolveStatus(self.node.find("{" + self.namespace + "}Status")) 
113
114        job_details = self.node.find("{" + self.namespace +  "}ProcessOutputs").find("{" + self.namespace +  "}Output").find("{" + self.namespace +  "}ComplexValue").find("{" + self.namespace +  "}WPSResponseDetails").find("{" + self.namespace +  "}JobDetails")
115
116        file_set_list = job_details.find("{" + self.namespace +  "}FileSet").getchildren()
117
118        items = ["JobID", "JobCompletionTimeDate", "RequestDescription",
119                 "RequestType", "JobCapabilities", "JobDuration", "JobVolume"]
120        mapped_names = ["Job ID", "Completion Time", 
121                        "Request Description", "Request Type", "Job Capabilities"]
122
123        if self.status == "complete":
124            mapped_names.extend(["Job Duration", "Output Size"])
125        else:
126            mapped_names.extend(["Estimated Job Duration", "Estimated Output Size"])
127
128        resp = "<h2>DETAILS</h2>"
129
130        self.job_id = job_details.find("{" + self.namespace + "}JobID").text
131
132        for (i, item) in enumerate(items):
133            x = job_details.find("{" + self.namespace +  "}" + item).text
134            if type(x) != str: 
135                x = "Undefined"
136
137            x = x.strip()
138
139            if item == "JobCapabilities" and x.find("send_to_extract_weather_data") > -1:
140                file_url = file_set_list[0].find("{" + self.namespace +  "}FileURL").text
141                file_path = mapDownloadURLToFilePath(file_url)
142                if self.status == "complete":
143                    x = x.replace("send_to_extract_weather_data",
144                            '<a href="/submit/form?proc_id=ExtractUKStationData&StationsFile=%s">Use stations to extract UK weather data</a>' % file_path) 
145
146            elif item == "JobVolume":
147                x = self._toMB(x)
148
149            elif item == "JobDuration":
150                secs = int(float(x))
151                secs = secs or 1
152                ext = ""
153                if secs > 1: ext = "s"
154                x = "%d second%s" % (secs, ext)
155
156            elif item == "JobCompletionTimeDate":
157                x = x.split(".")[0]
158 
159            resp += "<b>%s</b> = %s<br>" % (mapped_names[i], x)
160
161        # Render output files if completed
162        if self.status == "complete":
163
164            resp += "<h2>OUTPUT FILES</h2>"
165
166            if len(file_set_list) > 0:
167                resp += "The following file outputs are available from your job.<br>"
168            else:
169                resp += 'There are no output files associated with your job. Please click the "View as XML" link above to view your output.<br>'
170
171            for fnode in file_set_list:
172
173                furl = fnode.find("{" + self.namespace +  "}FileURL").text
174                fname_html = self._prettyFileNameHTML(furl)
175 
176                resp += fname_html
177
178                size = fnode.find("{" + self.namespace +  "}FileSize").text
179                size = self._toMB(size) 
180                resp += size
181
182                # Check for nested file set
183                fcontents = fnode.find("{" + self.namespace + "}FileContents")
184
185                for nested_fnode in fcontents.getchildren():
186                    nfurl = nested_fnode.find("{" + self.namespace +  "}FileURL").text
187                    nfname_html = self._prettyFileNameHTML(nfurl, nested = True) 
188                    resp += nfname_html
189
190                    nsize = nested_fnode.find("{" + self.namespace +  "}FileSize").text
191                    nsize = self._toMB(nsize)
192                    resp += nsize
193
194        return resp
195
196
197    def _prettyFileNameHTML(self, furl, nested = False):
198        """
199        Returns pretty displayable HTML filename string. If nested is True then
200        don't display download option and nest slightly.
201        """
202        fname = furl.split("/")[-1]
203        len_fname = len(fname)
204
205        p = 70
206        prefix = ""
207        download = '[<a href="%s">Download</a>]' % furl
208        fname_html = "<b>%s</b>" % fname
209
210        if nested:
211            p = 90 
212            prefix = "&nbsp;*&nbsp;" 
213            download = ""
214            fname_html = fname
215
216        padding = p - len_fname
217        if padding < 0: padding = 0
218
219        return ('<br><kbd>%s%s %s</kbd>' + (padding * "&nbsp;")) % (prefix, fname_html, download)
220 
221   
222    def _toMB(self, bytes, float_format = "%.2f"):
223        "Returns MB string from string or int or float."
224        mb = float_format % (int(bytes) / (2**20))
225        if mb == "0.00": mb = "0.01"
226        mb += " MB"
227        return mb
228       
229
230    def _htmlifyXML(self, xml):
231        "Returns html string that will make XML look ok on HTML page."
232
233        html_xml = highlight(xml, XmlLexer(), HtmlFormatter())
234        return html_xml
235        xml_lines = html_xml.split("\n")
236        new_xml = []
237
238        for line in xml_lines:
239            if len(line) > 80:
240                while len(line) > 0:
241                    this_line = line[:80]
242                    line = line[80:]
243                    new_xml.append(line + "<br/>")
244            else:
245                new_xml.append(line)
246
247        clean_xml_string = "\n".join(new_xml)
248        return clean_xml_string
249                   
250
251
Note: See TracBrowser for help on using the repository browser.