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,7 +130,7 @@ Ext.define('amdaDesktop.EpnTapModule', {
130 130
131 loadMask.show(); 131 loadMask.show();
132 this.servicesStore.each(function(record) { 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 Ext.Ajax.request({ 134 Ext.Ajax.request({
135 url: 'php/epntap.php', 135 url: 'php/epntap.php',
136 method: 'GET', 136 method: 'GET',
@@ -146,8 +146,18 @@ Ext.define('amdaDesktop.EpnTapModule', { @@ -146,8 +146,18 @@ Ext.define('amdaDesktop.EpnTapModule', {
146 'timeMax': timeMax 146 'timeMax': timeMax
147 }, 147 },
148 // timeout: 3000, 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 scope: this 161 scope: this
152 }); 162 });
153 }, this); 163 }, this);
@@ -156,25 +166,21 @@ Ext.define('amdaDesktop.EpnTapModule', { @@ -156,25 +166,21 @@ Ext.define('amdaDesktop.EpnTapModule', {
156 /** 166 /**
157 Update the nb_result field of the services store (see `EpnTapUI.servicesStore`), according to the field values in `serviceFilterPanel`. 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 loadMask.hide(); 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 record.set('info', ''); 181 record.set('info', '');
  182 + record.set('error', reason);
  183 + this.servicesStore.sort();
178 loadMask.hide(); 184 loadMask.hide();
179 }, 185 },
180 186
@@ -203,9 +209,11 @@ Ext.define('amdaDesktop.EpnTapModule', { @@ -203,9 +209,11 @@ Ext.define('amdaDesktop.EpnTapModule', {
203 // Ext.Ajax.abortAll(); 209 // Ext.Ajax.abortAll();
204 this.selectedService = record; 210 this.selectedService = record;
205 var nbRes = this.selectedService.get('nb_results'); 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 this.granulesStore.load({ 214 this.granulesStore.load({
208 params: this.getGranuleParams(), 215 params: this.getGranuleParams(),
  216 + // callback: function (records, operation) { console.log(Ext.decode(operation.response.responseText)); },
209 start: 0, 217 start: 0,
210 limit: this.granulesStore.pageSize, 218 limit: this.granulesStore.pageSize,
211 scope: this 219 scope: this
js/app/views/EpnTapUI.js
@@ -48,6 +48,8 @@ Ext.create('Ext.data.Store', { @@ -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 `targetNamesStore`: An ExtJS Store containing the list of the different target names defined on all granules, on 54 `targetNamesStore`: An ExtJS Store containing the list of the different target names defined on all granules, on
53 all available EPN-TAP services (defined in `generic_data/EpnTapData/metadata.json`, updated periodically with a cron 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,7 +67,12 @@ Ext.create('Ext.data.Store', {
65 proxy: { 67 proxy: {
66 type: 'ajax', 68 type: 'ajax',
67 url: 'php/epntap.php', 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,7 +147,7 @@ selected.
140 */ 147 */
141 Ext.create('Ext.data.Store', { 148 Ext.create('Ext.data.Store', {
142 id: 'granulesStore', 149 id: 'granulesStore',
143 - model: 'granulesModel', 150 + model: 'granulesModel', // Created dynamically
144 autoload: false, 151 autoload: false,
145 pageSize: 25, 152 pageSize: 25,
146 proxy: { 153 proxy: {
php/epntap.php
1 <?php 1 <?php
2 2
3 include(realpath(dirname(__FILE__) . "/config.php")); 3 include(realpath(dirname(__FILE__) . "/config.php"));
4 -// include(CLASSPATH . "EpnTapMgr.php");  
5 include(CLASSPATH . "VOTableMgr.php"); 4 include(CLASSPATH . "VOTableMgr.php");
6 5
7 $action = preg_replace("/[^a-zA-Z]+/", "", filter_var($_GET['action'], FILTER_SANITIZE_STRING)); 6 $action = preg_replace("/[^a-zA-Z]+/", "", filter_var($_GET['action'], FILTER_SANITIZE_STRING));
8 7
9 switch ($action) { 8 switch ($action) {
10 case 'resolver': 9 case 'resolver':
11 - $response = json_encode(resolver()); 10 + $response = resolver();
12 break; 11 break;
13 case 'getServices': 12 case 'getServices':
14 - $response = json_encode(getServices()); 13 + $response = getServices();
15 break; 14 break;
16 case 'getNbResults': 15 case 'getNbResults':
17 $response = getNbResults(); 16 $response = getNbResults();
18 break; 17 break;
19 case 'getGranules': 18 case 'getGranules':
20 - $response = json_encode(getGranules()); 19 + $response = getGranules();
21 break; 20 break;
22 default: 21 default:
23 - $response = 'unknown action'; 22 + $response = ['success' => false, 'msg' => 'Unknown action: ' . $action];
24 break; 23 break;
25 } 24 }
26 -// error_log('epntap response: ' . $response);  
27 -echo $response; 25 +echo json_encode($response);
28 26
29 function resolver() { 27 function resolver() {
30 $input = filter_var($_GET['input'], FILTER_SANITIZE_URL); 28 $input = filter_var($_GET['input'], FILTER_SANITIZE_URL);
31 $resolver_url = "http://voparis-registry.obspm.fr/ssodnet/1/autocomplete?q=%22$input%22"; 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 /* Return the list of available services by querying some usual registries. */ 76 /* Return the list of available services by querying some usual registries. */
@@ -48,29 +81,33 @@ function getServices() { @@ -48,29 +81,33 @@ function getServices() {
48 NATURAL JOIN rr.res_schema NATURAL JOIN rr.res_table NATURAL JOIN rr.interface NATURAL JOIN rr.res_detail NATURAL JOIN rr.capability 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 WHERE standard_id='ivo://ivoa.net/std/tap' AND intf_type='vs:paramhttp' AND detail_xpath='/capability/dataModel/@ivo-id' 82 WHERE standard_id='ivo://ivoa.net/std/tap' AND intf_type='vs:paramhttp' AND detail_xpath='/capability/dataModel/@ivo-id'
50 AND 1=ivo_nocasematch(detail_value, 'ivo://vopdc.obspm/std/EpnCore%') AND table_name LIKE '%.epn_core' ORDER BY short_name, table_name"; 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 $j-=1; 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 function getNbResults() { 113 function getNbResults() {
@@ -82,27 +119,33 @@ function getNbResults() { @@ -82,27 +119,33 @@ function getNbResults() {
82 $timeMax = filter_var($_GET['timeMax'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW); 119 $timeMax = filter_var($_GET['timeMax'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
83 120
84 $query = "SELECT COUNT(*) AS nb_rows FROM $tableName" . createFilter($targetName, $productTypes, $timeMin, $timeMax); 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 function getGranules() { 147 function getGranules() {
105 - // error_log('getGranules GET: ' . json_encode($_GET)); 148 + // TODO: simplify this
106 $url = filter_var($_GET['url'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW); 149 $url = filter_var($_GET['url'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
107 $tableName = filter_var($_GET['tableName'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW); 150 $tableName = filter_var($_GET['tableName'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
108 $targetName = filter_var($_GET['targetName'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW); 151 $targetName = filter_var($_GET['targetName'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
@@ -116,42 +159,38 @@ function getGranules() { @@ -116,42 +159,38 @@ function getGranules() {
116 $filter = createFilter($targetName, $productTypes, $timeMin, $timeMax); 159 $filter = createFilter($targetName, $productTypes, $timeMin, $timeMax);
117 $query = "SELECT TOP $limit * FROM $tableName $filter OFFSET $start"; 160 $query = "SELECT TOP $limit * FROM $tableName $filter OFFSET $start";
118 // error_log('getGranules query: ' . $query); 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 // ----- utils ----- 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 function createFilter($targetName, $productTypes, $timeMin, $timeMax) { 194 function createFilter($targetName, $productTypes, $timeMin, $timeMax) {
156 $filter = array(); 195 $filter = array();
157 if($targetName) { 196 if($targetName) {