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

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

Fixes to bugs in responses when not logged in, and added datatypes as
list for GetWeatherStations?.

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        self.status = None 
46        # A by-product of the XML parsing is resolving the status
47        parsed_resp = self._xmlToTableAndResolveStatus(job_xml)
48        if self.status == "failed":
49            normal_view = """<div><b>Sorry, there was a problem running this job. The job failed with the following message:</b></div>
50    <div>%s</div>""" % (parsed_resp.lstrip("Exception=").lstrip())
51        else:
52            normal_view = parsed_resp
53            job_xml_encoded = self._htmlifyXML(job_xml)
54            job_as_plots = "There are no plots available in the outputs of this job."
55
56
57        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>"""
58
59        resp += """<div id="view_options" style="background: #EEF4FF; border: 2px outset blue; padding: 5px;">
60                <a href="javascript:switchViewTo('table_view');">Normal View</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"""
61
62        poll_info_div = ""
63        if self.status == "complete":
64            resp += """<a href="javascript:switchViewTo('xml_view');">View as XML</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
65                <a href="javascript:switchViewTo('plots_view');">View Plots (if available)</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"""
66        elif self.status == "running":
67            poll_info_div = '<div id="poll_info" class="poller"></div>'
68
69        resp  += """
70</div>
71<div id="view_container" style="background: white; border: 2px outset blue; padding: 5px;">
72        <div id="view_contents">
73                <div id="table_view" style="visibility: visible; height: auto;">%s%s</div>""" % (poll_info_div, normal_view)
74
75        if self.status == "complete":
76            resp += """
77                <div id="xml_view" style="visibility: hidden; height: 0px;">%s</div>
78                <div id="plots_view" style="visibility: hidden; height: 0px;">%s</div>
79</div>""" % (job_xml_encoded, job_as_plots)
80
81        elif self.status == "running":
82            jobviewer_url = url_for(controller='jobviewer', status_url = status_url)
83            base_url = "http://" + request.url[7:].split("/")[0]
84            full_url = base_url + jobviewer_url
85
86            resp += """<input type="hidden" id="poll_url" value="%s" />""" % status_url
87            resp += """<input type="hidden" id="jobviewer_url" value="%s" />""" % jobviewer_url
88            resp += """<script type="text/javascript">
89        function init() {
90                pollWPS();
91        }
92
93</script>"""
94
95        renderer = UIPageRenderer()
96        resp = renderer.render("WPS Job Information",
97                              [("Job: %s" % self.job_id, resp)])
98        return resp
99
100    def _resolveStatus(self, status_node):
101        snode = status_node.getchildren()[0].tag
102        mdict = {"Succeeded": "complete",
103                 "Failed": "failed",
104                 "Accepted": "running",
105                 "Started": "running"}
106
107        for k,v in mdict.items():
108            if snode.find(k) > -1:
109                return v
110        else:
111            raise Exception("Could not match status tag.")
112
113    def _xmlToTableAndResolveStatus(self, xml):
114        "Chops up xml to get table of outputs."
115        self.node = ET.fromstring(xml)
116        self.namespace_ows = "http://www.opengeospatial.net/ows"
117        self.namespace = "http://www.opengeospatial.net/wps"
118
119        self.status = self._resolveStatus(self.node.find("{" + self.namespace + "}Status")) 
120
121        # If failed then grab the Exception text
122        if self.status == "failed":
123            ons = self.namespace_ows
124            wns = self.namespace
125            # Set job id from status URL as not in exception reesopnse
126            self.job_id = self.node.get("statusLocation").split("/")[-1]
127
128            return self.node.find("{%s}Status/{%s}ProcessFailed/{%s}ExceptionReport/{%s}Exception/{%s}ExceptionText" % (wns, wns, ons, ons, ons)).text
129
130        job_details = self.node.find("{" + self.namespace +  "}ProcessOutputs").find("{" + self.namespace +  "}Output").find("{" + self.namespace +  "}ComplexValue").find("{" + self.namespace +  "}WPSResponseDetails").find("{" + self.namespace +  "}JobDetails")
131
132        file_set_list = job_details.find("{" + self.namespace +  "}FileSet").getchildren()
133
134        items = ["JobID", "JobCompletionTimeDate", "RequestDescription",
135                 "RequestType", "JobCapabilities", "JobDuration", "JobVolume"]
136        mapped_names = ["Job ID", "Completion Time", 
137                        "Request Description", "Request Type", "Job Capabilities"]
138
139        if self.status == "complete":
140            mapped_names.extend(["Job Duration", "Output Size"])
141        else:
142            mapped_names.extend(["Estimated Job Duration", "Estimated Output Size"])
143
144        resp = "<h2>DETAILS</h2>"
145
146        self.job_id = job_details.find("{" + self.namespace + "}JobID").text
147
148        for (i, item) in enumerate(items):
149            x = job_details.find("{" + self.namespace +  "}" + item).text
150            if type(x) != str: 
151                x = "Undefined"
152
153            x = x.strip()
154
155            if item == "JobCapabilities" and x.find("send_to_extract_weather_data") > -1:
156                file_url = file_set_list[0].find("{" + self.namespace +  "}FileURL").text
157                file_path = mapDownloadURLToFilePath(file_url)
158                if self.status == "complete":
159                    x = x.replace("send_to_extract_weather_data",
160                            '<a href="/submit/form?proc_id=ExtractUKStationData&StationsFile=%s">Use stations to extract UK weather data</a>' % file_path) 
161
162            elif item == "JobVolume":
163                x = self._toMB(x)
164
165            elif item == "JobDuration":
166                secs = int(float(x))
167                secs = secs or 1
168                ext = ""
169                if secs > 1: ext = "s"
170                x = "%d second%s" % (secs, ext)
171
172            elif item == "JobCompletionTimeDate":
173                x = x.split(".")[0]
174 
175            resp += "<b>%s</b> = %s<br>" % (mapped_names[i], x)
176
177        # Render output files if completed
178        if self.status == "complete":
179
180            resp += "<h2>OUTPUT FILES</h2>"
181
182            if len(file_set_list) > 0:
183                resp += "The following file outputs are available from your job.<br>"
184            else:
185                resp += 'There are no output files associated with your job. Please click the "View as XML" link above to view your output.<br>'
186
187            for fnode in file_set_list:
188
189                furl = fnode.find("{" + self.namespace +  "}FileURL").text
190                fname_html = self._prettyFileNameHTML(furl)
191 
192                resp += fname_html
193
194                size = fnode.find("{" + self.namespace +  "}FileSize").text
195                size = self._toMB(size) 
196                resp += size
197
198                # Check for nested file set
199                fcontents = fnode.find("{" + self.namespace + "}FileContents")
200
201                for nested_fnode in fcontents.getchildren():
202                    nfurl = nested_fnode.find("{" + self.namespace +  "}FileURL").text
203                    nfname_html = self._prettyFileNameHTML(nfurl, nested = True) 
204                    resp += nfname_html
205
206                    nsize = nested_fnode.find("{" + self.namespace +  "}FileSize").text
207                    nsize = self._toMB(nsize)
208                    resp += nsize
209
210        return resp
211
212
213    def _prettyFileNameHTML(self, furl, nested = False):
214        """
215        Returns pretty displayable HTML filename string. If nested is True then
216        don't display download option and nest slightly.
217        """
218        fname = furl.split("/")[-1]
219        len_fname = len(fname)
220
221        p = 70
222        prefix = ""
223        download = '[<a href="%s">Download</a>]' % furl
224        fname_html = "<b>%s</b>" % fname
225
226        if nested:
227            p = 90 
228            prefix = "&nbsp;*&nbsp;" 
229            download = ""
230            fname_html = fname
231
232        padding = p - len_fname
233        if padding < 0: padding = 0
234
235        return ('<br><kbd>%s%s %s</kbd>' + (padding * "&nbsp;")) % (prefix, fname_html, download)
236 
237   
238    def _toMB(self, bytes, float_format = "%.2f"):
239        "Returns MB string from string or int or float."
240        mb = float_format % (int(bytes) / (2**20))
241        if mb == "0.00": mb = "0.01"
242        mb += " MB"
243        return mb
244       
245
246    def _htmlifyXML(self, xml):
247        "Returns html string that will make XML look ok on HTML page."
248
249        html_xml = highlight(xml, XmlLexer(), HtmlFormatter())
250        return html_xml
251        xml_lines = html_xml.split("\n")
252        new_xml = []
253
254        for line in xml_lines:
255            if len(line) > 80:
256                while len(line) > 0:
257                    this_line = line[:80]
258                    line = line[80:]
259                    new_xml.append(line + "<br/>")
260            else:
261                new_xml.append(line)
262
263        clean_xml_string = "\n".join(new_xml)
264        return clean_xml_string
265                   
266
267
Note: See TracBrowser for help on using the repository browser.