Commit 50dd7220400406a831c1c0e846c6727321cc8b23

Authored by Nathanael Jourdane
1 parent 07382ff2

epntap.php works now as an API; improve error handling.

js/app/controllers/EpnTapModule.js
... ... @@ -130,7 +130,7 @@ Ext.define('amdaDesktop.EpnTapModule', {
130 130  
131 131 loadMask.show();
132 132 this.servicesStore.each(function(record) {
133   - // TODO: use store.load() method instead.
  133 + // TODO: use store.load() method instead and add 'success' and 'enable' columns in the store
134 134 Ext.Ajax.request({
135 135 url: 'php/epntap.php',
136 136 method: 'GET',
... ... @@ -146,8 +146,18 @@ Ext.define('amdaDesktop.EpnTapModule', {
146 146 'timeMax': timeMax
147 147 },
148 148 // timeout: 3000,
149   - success: this.updateNbResults,
150   - failure: this.updateNbResultsFail,
  149 + success: function(response) {
  150 + var record = this.servicesStore.getById(response.request.options.params['serviceId']);
  151 + var responseObj = Ext.decode(response.responseText);
  152 + if(responseObj['success']) {
  153 + this.updateNbResults(responseObj, record);
  154 + } else {
  155 + this.updateNbResultsFail(response, responseObj['msg']);
  156 + }
  157 + },
  158 + failure: function(response) {
  159 + this.updateNbResultsFail(response, response.statusText);
  160 + },
151 161 scope: this
152 162 });
153 163 }, this);
... ... @@ -156,25 +166,21 @@ Ext.define('amdaDesktop.EpnTapModule', {
156 166 /**
157 167 Update the nb_result field of the services store (see `EpnTapUI.servicesStore`), according to the field values in `serviceFilterPanel`.
158 168 */
159   - updateNbResults: function(response) {
160   - if(response.status !== 200 || isNaN(response.responseText)) {
161   - this.updateNbResultsFail(response);
162   - } else {
163   - var record = this.servicesStore.getById(response.request.options.params['serviceId']);
164   - var nbRes = Number(response.responseText);
165   - record.set('nb_results', response.responseText);
166   - record.set('error', '');
167   - record.set('info', 'The service returned ' + (nbRes == 0 ? 'any' : nbRes) + ' result' + (nbRes > 1 ? 's' : '') + ' for the given query.');
168   - this.servicesStore.sort();
169   - }
  169 + updateNbResults: function(responseObj, record) {
  170 + record.set('nb_results', responseObj['data']);
  171 + record.set('info', responseObj['msg']);
  172 + record.set('error', '');
  173 + this.servicesStore.sort();
170 174 loadMask.hide();
171 175 },
172 176  
173   - updateNbResultsFail: function(response) {
174   - var record = this.servicesStore.getById(response.request.options.params['serviceId']);
175   - var reason = response.status === 200 ? response.responseText : response.statusText;
176   - record.set('error', reason);
  177 + updateNbResultsFail: function(response, reason) {
  178 + var serviceId = response.request.options.params['serviceId'];
  179 + var record = this.servicesStore.getById(serviceId);
  180 + console.log('Can not get nb results for service ' + serviceId + ': ' + reason, response);
177 181 record.set('info', '');
  182 + record.set('error', reason);
  183 + this.servicesStore.sort();
178 184 loadMask.hide();
179 185 },
180 186  
... ... @@ -203,9 +209,11 @@ Ext.define('amdaDesktop.EpnTapModule', {
203 209 // Ext.Ajax.abortAll();
204 210 this.selectedService = record;
205 211 var nbRes = this.selectedService.get('nb_results');
206   - if(nbRes > 0 && !isNaN(nbRes)) {
  212 +
  213 + if(nbRes > 0 && !isNaN(nbRes)) { // TODO replace !isNaN(nbRes) by this.selectedService.get('success')
207 214 this.granulesStore.load({
208 215 params: this.getGranuleParams(),
  216 + // callback: function (records, operation) { console.log(Ext.decode(operation.response.responseText)); },
209 217 start: 0,
210 218 limit: this.granulesStore.pageSize,
211 219 scope: this
... ...
js/app/views/EpnTapUI.js
... ... @@ -48,6 +48,8 @@ Ext.create('Ext.data.Store', {
48 48 ]
49 49 });
50 50  
  51 +// TODO: Create a generic epntap stop from which all other stores inherits.
  52 +
51 53 /**
52 54 `targetNamesStore`: An ExtJS Store containing the list of the different target names defined on all granules, on
53 55 all available EPN-TAP services (defined in `generic_data/EpnTapData/metadata.json`, updated periodically with a cron
... ... @@ -65,7 +67,12 @@ Ext.create('Ext.data.Store', {
65 67 proxy: {
66 68 type: 'ajax',
67 69 url: 'php/epntap.php',
68   - extraParams : { action: 'resolver' }
  70 + extraParams: { action: 'resolver' }
  71 + // listeners: {
  72 + // exception: function(proxy, response, operation) {
  73 + // console.log('Error ', response); //TODO: Use ExtJs alert instead
  74 + // }
  75 + // }
69 76 }
70 77 });
71 78  
... ... @@ -140,7 +147,7 @@ selected.
140 147 */
141 148 Ext.create('Ext.data.Store', {
142 149 id: 'granulesStore',
143   - model: 'granulesModel',
  150 + model: 'granulesModel', // Created dynamically
144 151 autoload: false,
145 152 pageSize: 25,
146 153 proxy: {
... ...
php/epntap.php
1 1 <?php
2 2  
3 3 include(realpath(dirname(__FILE__) . "/config.php"));
4   -// include(CLASSPATH . "EpnTapMgr.php");
5 4 include(CLASSPATH . "VOTableMgr.php");
6 5  
7 6 $action = preg_replace("/[^a-zA-Z]+/", "", filter_var($_GET['action'], FILTER_SANITIZE_STRING));
8 7  
9 8 switch ($action) {
10 9 case 'resolver':
11   - $response = json_encode(resolver());
  10 + $response = resolver();
12 11 break;
13 12 case 'getServices':
14   - $response = json_encode(getServices());
  13 + $response = getServices();
15 14 break;
16 15 case 'getNbResults':
17 16 $response = getNbResults();
18 17 break;
19 18 case 'getGranules':
20   - $response = json_encode(getGranules());
  19 + $response = getGranules();
21 20 break;
22 21 default:
23   - $response = 'unknown action';
  22 + $response = ['success' => false, 'msg' => 'Unknown action: ' . $action];
24 23 break;
25 24 }
26   -// error_log('epntap response: ' . $response);
27   -echo $response;
  25 +echo json_encode($response);
28 26  
29 27 function resolver() {
30 28 $input = filter_var($_GET['input'], FILTER_SANITIZE_URL);
31 29 $resolver_url = "http://voparis-registry.obspm.fr/ssodnet/1/autocomplete?q=%22$input%22";
32   - $result = json_decode(file_get_contents($resolver_url), true);
33 30  
34   - $targets = array();
35   - foreach($result['hits'] as $e) {
36   - $aliases = '<li>' . join('</li><li>', $e['aliases']) . '</li>';
37   - $target = array('name' => $e['name'], 'type' => $e['type'], 'parent' => $e['parent'], 'aliases' => $aliases);
38   - array_push($targets, $target);
  31 + $response = ['success' => true, 'metaData' => ['root' => 'data', 'messageProperty' => 'msg']];
  32 + try {
  33 + $content = file_get_contents($resolver_url);
  34 + } catch (Exception $e) {
  35 + error_log('Resolver access error: ' . $e);
  36 + $response['success'] = false;
  37 + $response['msg'] = "Resolver unreachable on $resolver_url.";
39 38 }
40   - return $targets;
  39 + try {
  40 + $result = json_decode($content, true);
  41 + $targets = array();
  42 + foreach($result['hits'] as $e) {
  43 + $aliases = '<li>' . join('</li><li>', $e['aliases']) . '</li>';
  44 + $target = array('name' => $e['name'], 'type' => $e['type'], 'parent' => $e['parent'], 'aliases' => $aliases);
  45 + array_push($targets, $target);
  46 + }
  47 + $response['data'] = $targets;
  48 + } catch (Exception $e) {
  49 + error_log('Resolver type error: ' . $e);
  50 + $response['success'] = false;
  51 + $response['msg'] = 'The resolver returned a bad result.';
  52 + }
  53 + return $response;
  54 +}
  55 +
  56 +function request($access_url, $query) {
  57 + $votMgr = new VOTableMgr;
  58 + $params = 'FORMAT=votable&LANG=ADQL&REQUEST=doQuery';
  59 + $url = $access_url . '/sync?' . $params . '&QUERY=' . urlencode(preg_replace('/\s+/', ' ', $query)); // remove also multiple whitespaces
  60 +
  61 + $votMgr->load($url);
  62 + $data = $votMgr->parseStream();
  63 + $error = $votMgr->getVotableError();
  64 +
  65 + $response = ['query' => $query, 'metaData' => ['root' => 'data', 'messageProperty' => 'msg']];
  66 + if($error) {
  67 + $response['success'] = false;
  68 + $response['msg'] = $error;
  69 + } else {
  70 + $response['success'] = true;
  71 + $response['data'] = $data;
  72 + }
  73 + return $response;
41 74 }
42 75  
43 76 /* Return the list of available services by querying some usual registries. */
... ... @@ -48,29 +81,33 @@ function getServices() {
48 81 NATURAL JOIN rr.res_schema NATURAL JOIN rr.res_table NATURAL JOIN rr.interface NATURAL JOIN rr.res_detail NATURAL JOIN rr.capability
49 82 WHERE standard_id='ivo://ivoa.net/std/tap' AND intf_type='vs:paramhttp' AND detail_xpath='/capability/dataModel/@ivo-id'
50 83 AND 1=ivo_nocasematch(detail_value, 'ivo://vopdc.obspm/std/EpnCore%') AND table_name LIKE '%.epn_core' ORDER BY short_name, table_name";
51   - // error_log('getServices query: ' . $query);
52   -
53   - for($i=0; $i<count($registriesURL); $i++) {
54   - $services = request($registriesURL[$i], $query);
55   - if(! array_key_exists("error", $services)) {
56   - for($j=0; $j<count($services); $j++) {
57   - $services[$j]['id'] = generateServiceId($services[$j]);
58   - $services[$j]['nb_results'] = -1;
59   - $services[$j]['info'] = 'Please make a query first.';
60   - if($services[$j]['id'] == 'cdpp/amda/amdadb') {
61   - array_splice($services, $j, 1);
  84 +
  85 + $regNumber = 0;
  86 + for(; $regNumber<count($registriesURL) ; $regNumber++) {
  87 + $response = request($registriesURL[$regNumber], $query);
  88 + if($response['success']) {
  89 + // Add several other parameters and remove AMDA
  90 + for($j=0 ; $j<count($response['data']) ; $j++) {
  91 + $response['data'][$j]['id'] = generateServiceId($response['data'][$j]);
  92 + $response['data'][$j]['nb_results'] = -1;
  93 + $response['data'][$j]['info'] = 'Please make a query.';
  94 + if($response['data'][$j]['id'] == 'cdpp/amda/amdadb') {
  95 + array_splice($response['data'], $j, 1);
62 96 $j-=1;
63 97 }
64 98 }
65   - return $services;
66   - } else {
67   - error_log('getServices error: ' . $services['error']);
68   - if($i === count($registriesURL)-1) {
69   - error_log("Can not access any of these registries : " . implode(', ', $registriesURL) . ", check the internet connexion.");
70   - return;
  99 + if(isset($lastErrorMesage)) {
  100 + $response['msg'] = $lastErrorMesage;
71 101 }
  102 + break;
  103 + } else {
  104 + $lastErrorMesage = 'Last tried registry (' . $registriesURL[$regNumber] . ') returned this error: ' . $response['msg'] . '.';
72 105 }
73 106 }
  107 + if(!$response['success']) {
  108 + $response['msg'] = 'Can not access any of these registries: ' . implode(', ', $registriesURL) . ', last error message is ' . $lastErrorMesage;
  109 + }
  110 + return $response;
74 111 }
75 112  
76 113 function getNbResults() {
... ... @@ -82,27 +119,33 @@ function getNbResults() {
82 119 $timeMax = filter_var($_GET['timeMax'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
83 120  
84 121 $query = "SELECT COUNT(*) AS nb_rows FROM $tableName" . createFilter($targetName, $productTypes, $timeMin, $timeMax);
85   - // error_log('getNbResults query: ' . $query);
86   - $result = request($url, $query);
87   - if(count($result) < 1) {
88   - return 'Too few returned raws.';
89   - } else if(count($result) > 1) {
90   - return 'Too many returned raws.';
91   - } else if(!array_key_exists(0, $result)) {
92   - return 'cant find raw item 0';
93   - } else if(is_null($result[0])) {
94   - return 'The returned raw is null.';
95   - } else if(!array_key_exists("nb_rows", $result[0])) {
96   - return 'cant find nb_rows.';
97   - } else if(!is_numeric($result[0]['nb_rows'])) {
98   - return 'The returned value is not a number.';
99   - } else {
100   - return (int)($result[0]['nb_rows']);
  122 + $response = request($url, $query);
  123 + if($response['success']) {
  124 + $response['success'] = false;
  125 + $response['msg'] = 'The service returned a bad value, can not get the number of results.';
  126 + if(count($response['data']) < 1) {
  127 + error_log('getNbResults error: Too few returned raws.');
  128 + } else if(count($response['data']) > 1) {
  129 + error_log('getNbResults error: Too many returned raws.');
  130 + } else if(!array_key_exists(0, $response['data'])) {
  131 + error_log('getNbResults error: cant find raw item 0');
  132 + } else if(is_null($response['data'][0])) {
  133 + error_log('getNbResults error: The returned raw is null.');
  134 + } else if(!array_key_exists("nb_rows", $response['data'][0])) {
  135 + error_log('getNbResults error: cant find nb_rows.');
  136 + } else if(!is_numeric($response['data'][0]['nb_rows'])) {
  137 + error_log('getNbResults error: The returned value is not a number.');
  138 + } else {
  139 + $response['success'] = true;
  140 + $response['data'] = (int)($response['data'][0]['nb_rows']);
  141 + $response['msg'] = 'The service returned ' . ($response['data'] == 0 ? 'no' : $response['data']) . ' result' . ($response['data'] > 1 ? 's' : '') . ' for the given query.';
  142 + }
101 143 }
  144 + return $response;
102 145 }
103 146  
104 147 function getGranules() {
105   - // error_log('getGranules GET: ' . json_encode($_GET));
  148 + // TODO: simplify this
106 149 $url = filter_var($_GET['url'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
107 150 $tableName = filter_var($_GET['tableName'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
108 151 $targetName = filter_var($_GET['targetName'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
... ... @@ -116,42 +159,38 @@ function getGranules() {
116 159 $filter = createFilter($targetName, $productTypes, $timeMin, $timeMax);
117 160 $query = "SELECT TOP $limit * FROM $tableName $filter OFFSET $start";
118 161 // error_log('getGranules query: ' . $query);
119   - $rows = request($url, $query);
120   -
121   - $visibleColumns = ['granule_uid', 'dataproduct_type', 'time_min', 'time_max', 'access_estsize', 'thumbnail_url', 'access_url'];
122   - $names = ['dataproduct_type' => 'Type', 'access_estsize' => 'Size'];
123   - $renderers = ['dataproduct_type' => 'type', 'time_min' => 'date', 'time_max' => 'date', 'access_estsize' => 'size', 'thumbnail_url' => 'img', 'access_url' => 'link', 'access_format' => 'format'];
124   - $flexs = ['granule_uid' => 2];
125   -
126   - $fields = array();
127   - $columns = array();
128   - foreach($rows[0] as $key => $value) {
129   - $fields[] = ['name' => $key, 'type' => 'string'];
130   - $columns[] = [
131   - 'dataIndex' => $key,
132   - 'text' => array_key_exists($key, $names) ? $names[$key] : ucfirst(str_replace('_', ' ', $key)),
133   - 'flex' => array_key_exists($key, $flexs) ? $flexs[$key] : 1,
134   - 'hidden' => !in_array($key, $visibleColumns),
135   - 'renderer' => array_key_exists($key, $renderers) ? $renderers[$key] : 'text'
136   - ];
137   - }
  162 + $response = request($url, $query);
  163 + if($response['success']) {
  164 + $visibleColumns = ['granule_uid', 'dataproduct_type', 'time_min', 'time_max', 'access_estsize', 'thumbnail_url', 'access_url']; // rest are hidden
  165 + $names = ['dataproduct_type' => 'Type', 'access_estsize' => 'Size']; // default: pretty printed key name
  166 + $renderers = ['dataproduct_type' => 'type', 'time_min' => 'date', 'time_max' => 'date', 'access_estsize' => 'size', 'thumbnail_url' => 'img', 'access_url' => 'link', 'access_format' => 'format']; // default: text
  167 + $flexs = ['granule_uid' => 2]; // default: 1
  168 + // $types = ['boolean' => , 'integer']; // TODO see http://php.net/manual/fr/function.gettype.php
  169 +
  170 + $fields = array();
  171 + $columns = array();
  172 + foreach($response['data'][0] as $key => $value) {
  173 + error_log('Granule ' . $key . ' is ' . gettype($value));
  174 + $fields[] = ['name' => $key, 'type' => 'string'];
  175 + $columns[] = [
  176 + 'dataIndex' => $key,
  177 + 'text' => array_key_exists($key, $names) ? $names[$key] : ucfirst(str_replace('_', ' ', $key)),
  178 + 'flex' => array_key_exists($key, $flexs) ? $flexs[$key] : 1,
  179 + 'hidden' => !in_array($key, $visibleColumns),
  180 + // 'type' => array_key_exists(gettype(), $types),
  181 + 'renderer' => array_key_exists($key, $renderers) ? $renderers[$key] : 'text'
  182 + ];
  183 + }
138 184  
139   - $metadata = ['fields' => $fields, 'columns' => $columns, 'root' => 'data'];
140   - return ['data' => $rows, 'total' => $nbRes, 'metaData' => $metadata];
  185 + $response['total'] = $nbRes;
  186 + $response['metaData']['fields'] = $fields;
  187 + $response['metaData']['columns'] = $columns;
  188 + }
  189 + return $response;
141 190 }
142 191  
143 192 // ----- utils -----
144 193  
145   -function request($access_url, $query) {
146   - $votMgr = new VOTableMgr;
147   - $params = 'FORMAT=votable&LANG=ADQL&REQUEST=doQuery';
148   - $url = $access_url . '/sync?' . $params . '&QUERY=' . urlencode(preg_replace('/\s+/', ' ', $query)); // remove also multiple whitespaces
149   -
150   - $votMgr->load($url);
151   - $result = $votMgr->parseStream();
152   - return $votMgr->getVotableError() ? array('error' => $votMgr->getVotableError()) : $result;
153   -}
154   -
155 194 function createFilter($targetName, $productTypes, $timeMin, $timeMax) {
156 195 $filter = array();
157 196 if($targetName) {
... ...