From a79b0198406e7a5a4b2265ff7d2ccf28a1c7c7a0 Mon Sep 17 00:00:00 2001
From: Nathanael Jourdane <nathanael.jourdane@irap.omp.eu>
Date: Wed, 23 Nov 2016 12:04:44 +0100
Subject: [PATCH] Add EPN-TAP module (draft).

---
 js/app/AmdaApp.js                    |   7 +++++++
 js/app/controllers/EpnTapModule.js   |  85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 js/app/views/EpnTapUI.js             | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 js/resources/images/64x64/epntap.png | Bin 0 -> 3284 bytes
 php/classes/AmdaAction.php           |   5 ++++-
 php/classes/EpnTapMgr.php            | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 php/config.php                       |   6 +++++-
 7 files changed, 444 insertions(+), 2 deletions(-)
 create mode 100644 js/app/controllers/EpnTapModule.js
 create mode 100644 js/app/views/EpnTapUI.js
 create mode 100644 js/resources/images/64x64/epntap.png
 create mode 100644 php/classes/EpnTapMgr.php

diff --git a/js/app/AmdaApp.js b/js/app/AmdaApp.js
index 7ea6130..3b8c401 100755
--- a/js/app/AmdaApp.js
+++ b/js/app/AmdaApp.js
@@ -97,6 +97,13 @@ Ext.define('amdaApp.AmdaApp', {
     		source      : 'amdaDesktop.InteropModule',
     		useLauncher : true
     	},
+      epntap : {
+    		id          : 'epntap-win',
+    		icon        : 'icon-epntap',
+    		title       : 'EPN-TAP data',
+    		source      : 'amdaDesktop.EpnTapModule',
+    		useLauncher : true
+    	},
     	info : {
     		id          : 'info-win',
     		icon        : 'icon-information',
diff --git a/js/app/controllers/EpnTapModule.js b/js/app/controllers/EpnTapModule.js
new file mode 100644
index 0000000..8bc58a5
--- /dev/null
+++ b/js/app/controllers/EpnTapModule.js
@@ -0,0 +1,85 @@
+/**
+ * Project  : AMDA-NG
+ * Name	 : EpnTapModule.js
+ * @class   amdaDesktop.EpnTapModule
+ * @extends amdaDesktop.AmdaModule
+ * @brief   EpnTap Module controller definition
+ * @author  Nathanael Jourdane
+ */
+
+function onWindowLoaded() {
+}
+
+function onSearchBtnClicked() {
+	var targetName = Ext.getCmp('targetNameCB').value;
+	var productType = Ext.getCmp('productTypeCB').value;
+	var startTime = Ext.getCmp('startTimeDF').rawValue;
+	var stopTime = Ext.getCmp('stopTimeDF').rawValue;
+
+	for(let service of Ext.getCmp('servicesPanel').getStore().getRange()) {
+		var filter = Array(service.data.table_name, service.data.access_url, targetName, productType, startTime, stopTime);
+		AmdaAction.epnTapMgr('getServiceNbResults', filter, function(epnTapServices) {
+			service.set('nb_responses', epnTapServices);
+			// console.log(epnTapServices[1]);
+		});
+	}
+}
+
+// var grid = Ext.getCmp('servicesPanel');
+// var selection = grid.getSelectionModel();
+// access_url = [];
+// table_name = [];
+// for(i=0 ; i<grid.store.getCount() ; i++) {
+// 	if(selection.isSelected(i)) {
+// 		table_name.push(grid.store.getAt(i).data.table_name);
+// 		access_url.push(grid.store.getAt(i).data.access_url);
+// 	}
+//
+// }
+
+function onServiceSelected(service) {
+	var filter = Array(service['table_name'], service['access_url'], Ext.getCmp('targetNameCB').value,
+			Ext.getCmp('productTypeCB').value, Ext.getCmp('startTimeDF').rawValue, Ext.getCmp('stopTimeDF').rawValue);
+
+	AmdaAction.epnTapMgr('getGranules', filter, function(granules) {
+		console.log(granules);
+	});
+}
+
+function onGranuleSelected(granule) {
+	console.log('selected granule: ' + granule.target_name);
+}
+
+Ext.define('amdaDesktop.EpnTapModule', {
+
+	extend: 'amdaDesktop.AmdaModule',
+
+	requires: ['amdaUI.EpnTapUI'],
+	contentId : 'EpnTapUI',
+
+	/** The alias name of the module view. */
+	uiType: 'panelEpnTap',
+
+	/** The text displayed on the *help button* tooltip. */
+	helpTitle: 'Help on EPN-TAP Module',
+
+	/** The name of the documentation file related to the module. */
+	helpFile : 'epnTapHelp',
+
+	width : 600,
+	height: 550,
+
+	/** @class Module initialisation. */
+	init: function() {
+		this.launcher = {
+			text: this.title,
+			iconCls: this.icon,
+			handler: this.createWindow,
+			scope: this
+		};
+		this.onWindowLoaded = onWindowLoaded;
+		this.onSearchBtnClicked = onSearchBtnClicked;
+		this.onGranuleSelected = onGranuleSelected;
+		this.onServiceSelected = onServiceSelected;
+	}
+});
diff --git a/js/app/views/EpnTapUI.js b/js/app/views/EpnTapUI.js
new file mode 100644
index 0000000..4623846
--- /dev/null
+++ b/js/app/views/EpnTapUI.js
@@ -0,0 +1,243 @@
+/**
+ * Project: AMDA-NG
+ * Name: EpnTapUI.js
+ * @class amdaUI.EpnTapUI
+ * @extends Ext.tab.Panel
+ * @brief client for EPN-TAP services (View)
+ * @author Nathanael JOURDANE
+ * 24/10/2016: file creation
+ */
+
+Ext.create('Ext.data.Store', {
+	storeId:'services_store',
+	fields: ['short_name', 'res_title', 'ivoid', 'access_url', 'table_name', 'content_type',
+		'res_description', 'creator seq', 'content_level', 'reference_url', 'created', 'updated', 'nb_responses'],
+	proxy: {
+		type: 'ajax',
+		url: 'generic_data/EpnTapData/EpnTapServices.json',
+		reader: {type: 'json'}
+	},
+	autoLoad: true
+});
+
+Ext.create('Ext.data.Store', {
+	storeId:'granules_store',
+	fields:['type', 'target_name', 'time_min', 'time_max'],
+	data: {'items': [
+		{'type': 'sp', 'target_name': 'mars', 'time_min': '01/01/2012', 'time_max': '02/01/2012'},
+		{'type': 'sp', 'target_name': 'mars', 'time_min': '01/01/2012', 'time_max': '02/01/2012'},
+		{'type': 'im', 'target_name': 'earth', 'time_min': '01/01/2012', 'time_max': '02/01/2012'},
+		{'type': 'im', 'target_name': 'earth', 'time_min': '01/01/2012', 'time_max': '02/01/2012'}
+	]},
+	proxy: {
+		type: 'memory',
+		reader: { type: 'json', root: 'items' }
+	}
+});
+
+Ext.create('Ext.data.Store', {
+	storeId:'productTypes_store',
+	fields: ['id', 'name'],
+	proxy: {
+		type: 'ajax',
+		url : 'generic_data/EpnTapData/EpnTapProductTypes.json',
+		reader: {type: 'json'}
+	},
+	autoLoad: true
+});
+
+Ext.create('Ext.data.Store', {
+	storeId:'targetClasses_store',
+	fields: ['id', 'name'],
+	proxy: {
+		type: 'ajax',
+		url : 'generic_data/EpnTapData/EpnTapTargetClasses.json',
+		reader: {
+			type: 'json'
+		}
+	},
+	autoLoad: true
+});
+
+Ext.create('Ext.data.Store', {
+	storeId: 'targetNames_store',
+	fields: ['n'],
+	proxy: {
+		type: 'ajax',
+		url : 'generic_data/EpnTapData/EpnTapTargetNames.json',
+		reader: {type: 'json'}
+	},
+	autoLoad: true
+});
+
+var serviceFilterPanel = {
+	id: 'serviceFilterPanel',
+	xtype: 'panel',
+	region : 'north',
+	layout: { type: 'hbox', pack: 'start', align: 'stretch' },
+	defaults: { margin: 5 },
+	items: [{ // Left part
+		xtype : 'container',
+		layout: 'form',
+		flex: 2,
+		items: [{
+			id: 'productTypeCB',
+			xtype: 'combobox',
+			fieldLabel: 'Product type',
+			store: Ext.data.StoreManager.lookup('productTypes_store'),
+			queryMode: 'local',
+			displayField: 'name',
+			valueField: 'id',
+			name: 'productType',
+			editable: false,
+			allowBlank: false
+		}, {
+			id: 'targetClassCB',
+			xtype: 'combobox',
+			fieldLabel: 'Target class',
+			store: Ext.data.StoreManager.lookup('targetClasses_store'),
+			queryMode: 'local',
+			displayField: 'name',
+			valueField: 'id',
+			name: 'targetClass',
+			editable: false,
+			allowBlank: false
+		}, {
+			id: 'targetNameCB',
+			xtype: 'combobox',
+			fieldLabel: 'Target name',
+			store: Ext.data.StoreManager.lookup('targetNames_store'),
+			queryMode: 'local',
+			displayField: 'n',
+			valueField: 'n',
+			name: 'targetName',
+			allowBlank: false,
+			triggerAction: 'all',
+			typeAhead: true,
+			mode: 'remote',
+			minChars: 2,
+			forceSelection: true
+			// listeners: {
+			// 	scope: window,
+			// 	'change': function(combobox, newValue, oldValue, eOpts) {
+			// 		if(newValue && newValue.length >= 3) {
+			// 			myDesktopApp.getLoadedModule(myDesktopApp.dynamicModules.epntap.id).onTargetNameUpdate(newValue);
+			// 		}
+			// 	}
+			// }
+		}]
+	}, { // Right part
+		xtype : 'container',
+		layout: 'form',
+		flex: 2,
+		items: [{
+			id: 'startTimeDF',
+			xtype: 'datefield',
+			fieldLabel: 'Start time',
+			name: 'start_time',
+			allowBlank:false
+		}, {
+			id: 'stopTimeDF',
+			xtype: 'datefield',
+			fieldLabel: 'Stop time',
+			name: 'stop_time',
+			allowBlank:false
+		}, {
+			id: 'searchServicesBtn',
+			xtype: 'button',
+			text: 'Search services',
+			handler: function() {
+				myDesktopApp.getLoadedModule(myDesktopApp.dynamicModules.epntap.id).onSearchBtnClicked();
+			}
+		}]
+	}]
+}
+
+var servicesPanel = {
+	id: 'servicesPanel',
+	xtype : 'grid',
+	title: 'Services',
+	multiSelect: true,
+	store: Ext.data.StoreManager.lookup('services_store'),
+	flex: 1,
+	columns: [
+		{ text: 'Service',  dataIndex: 'short_name', flex: 3 },
+		{ text: 'Results', dataIndex: 'nb_responses', flex: 1}
+	],
+	listeners: {
+		'cellclick': function(grid, td, cellIndex, record, tr, rowIndex, e, eOpts) {
+			myDesktopApp.getLoadedModule(myDesktopApp.dynamicModules.epntap.id).onServiceSelected(record.data);
+		}
+	}
+}
+
+var granulesPanel = {
+	id: 'granulesPanel',
+	xtype : 'grid',
+	title: 'Granules',
+	store: Ext.data.StoreManager.lookup('granulesStore'),
+	flex: 3,
+	columns: [
+		{ text: 'Type',  dataIndex: 'type', flex: 1 },
+		{ text: 'Target', dataIndex: 'target_name', flex: 1 },
+		{ text: 'Time min', dataIndex: 'time_min', flex: 2 },
+		{ text: 'Time max', dataIndex: 'time_max', flex: 2 }
+	],
+	listeners: {
+		'cellclick': function(grid, td, cellIndex, record, tr, rowIndex, e, eOpts) {
+			myDesktopApp.getLoadedModule(myDesktopApp.dynamicModules.epntap.id).onGranuleSelected(record.data);
+		}
+	}
+}
+
+var mainPanel = {
+	id: 'mainPanel',
+	xtype: 'panel',
+	region: 'center',
+	height: 350,
+	layout: { type: 'hbox', pack: 'start', align: 'stretch' },
+	items: [ servicesPanel, granulesPanel ],
+	listeners: {
+		afterrender: function() {
+			myDesktopApp.getLoadedModule(myDesktopApp.dynamicModules.epntap.id).onWindowLoaded();
+		}
+	}
+}
+
+var infoPanel = {
+	id: 'infoPanel',
+	xtype : 'panel',
+	region: 'south',
+	title: 'Information',
+	collapsible: true,
+	flex: 0,
+	height: 100,
+	autoHide: false,
+	bodyStyle: 'padding: 5px',
+	iconCls: 'icon-information',
+	loader: { autoLoad: true, url: helpDir + 'epnTapHOWTO' }
+}
+
+Ext.define('amdaUI.EpnTapUI', {
+	extend: 'Ext.container.Container',
+	alias: 'widget.panelEpnTap',
+
+	constructor: function(config) {
+		this.init(config);
+		this.callParent(arguments);
+	},
+
+	init : function(config) {
+		var myConf = {
+			width: 600,
+			height: 550,
+			layout: 'border',
+			items: [
+				serviceFilterPanel,
+				mainPanel,
+				infoPanel
+			]
+		};
+		Ext.apply(this, Ext.apply(arguments, myConf));
+	}
+});
diff --git a/js/resources/images/64x64/epntap.png b/js/resources/images/64x64/epntap.png
new file mode 100644
index 0000000..c749a1e
Binary files /dev/null and b/js/resources/images/64x64/epntap.png differ
diff --git a/php/classes/AmdaAction.php b/php/classes/AmdaAction.php
index 297cd5a..fd8ac52 100644
--- a/php/classes/AmdaAction.php
+++ b/php/classes/AmdaAction.php
@@ -1334,7 +1334,10 @@ class AmdaAction {
         	$alreadyUsed = $mgr->isNameAlreadyUsed($obj->type, $obj->name);
         	return array('success' => true, 'alreadyUsed' => $alreadyUsed);
         }
-        
+
+		public function epnTapMgr($function_name, $args) {
+			return (new EpnTapMgr)->call($function_name, $args);
+		}
 }
 ?>
 
diff --git a/php/classes/EpnTapMgr.php b/php/classes/EpnTapMgr.php
new file mode 100644
index 0000000..ef03603
--- /dev/null
+++ b/php/classes/EpnTapMgr.php
@@ -0,0 +1,100 @@
+<?php
+/**  @class EpnTapMgr
+*    @brief Manager to communicates with EPN-TAP services.
+*/
+
+class EpnTapMgr {
+
+	public function call($function_name, $args) {
+		switch($function_name) {
+			case 'getServiceNbResults':
+				if(count($args) == 6)
+					return $this->getServiceNbResults($args[0], $args[1], $args[2], $args[3], $args[4], $args[5]);
+				break;
+			case 'getGranules':
+				if(count($args) == 6)
+					return $this->getGranules($args[0], $args[1], $args[2], $args[3], $args[4], $args[5]);
+				break;
+			default:
+				return array('success' => false, 'message' => $function_name.' is an unknown function.');
+				break;
+		}
+	return array('success' => false, 'message' => 'The function do not have the required number of arguments');
+	}
+
+	// dc.zah.uni-heidelberg.de/tap/sync?FORMAT=votable&LANG=ADQL&QUERY=SELECT DISTINCT table_name WHERE standard_id='ivo://ivoa.net/std/tap' AND intf_type='vs:paramhttp' AND detail_xpath='/capability/dataModel/@ivo-id' FROM&REQUEST=doQuery
+	// select distinct target_name from xxx.epn_core
+
+	private function date2JD($date) {
+		list($month, $day, $year) = split('[/.-]', $date);
+
+		if($month == 1 || $month == 2) {
+			$yearp = $year - 1;
+			$monthp = $month + 12;
+		} else {
+			$yearp = $year;
+			$monthp = $month;
+		}
+
+		# this checks where we are in relation to October 15, 1582, the beginning
+		# of the Gregorian calendar.
+		if (($year < 1582) ||
+				($year == 1582 && $month < 10) ||
+				($year == 1582 && $month == 10 && $day < 15))
+			$j_day = 0;
+		else
+			$j_day = 2 - (int)($yearp / 100.0) + (int)((int)($yearp / 100.0) / 4.0);
+
+		$j_day += $yearp < 0 ? (int)((365.25 * $yearp) - 0.75) : (int)(365.25 * $yearp);
+		$j_day += (int)(30.6001 * ($monthp + 1)) + $day + 1720994.5;
+
+		return $j_day;
+	}
+
+	private function getServiceNbResults($table_name, $access_url, $target_name, $dataproduct_type, $time_min, $time_max) {
+		$filter = array();
+		if($target_name)
+			array_push($filter, "target_name = '$target_name'");
+		if($dataproduct_type)
+			array_push($filter, "dataproduct_type = '$dataproduct_type'");
+		if($time_min)
+			array_push($filter, "time_min >= " . $this->date2JD($time_min));
+		if($time_max)
+			array_push($filter, "time_max >= " . $this->date2JD($time_max));
+
+		$query = "SELECT COUNT(granule_uid) AS nb_results FROM $table_name"
+				. ($filter.length > 0 ? ' WHERE ' . join(' AND ', $filter) : '');
+
+		$votMgr = new VOTableMgr;
+		$params = 'FORMAT=votable&LANG=ADQL&REQUEST=doQuery';
+		$url = $access_url . '/sync?' . $params . '&QUERY=' . urlencode($query);
+		$votMgr->load($url);
+		return $votMgr->isValidSchema() ? $votMgr->getSimpleInteger() : 0;
+	}
+
+	static function getGranules($table_name, $access_url) {
+		$filter = array();
+		if($target_name)
+			array_push($filter, "target_name = '$target_name'");
+		if($dataproduct_type)
+			array_push($filter, "dataproduct_type = '$dataproduct_type'");
+		if($time_min)
+			array_push($filter, "time_min >= " . $this->date2JD($time_min));
+		if($time_max)
+			array_push($filter, "time_max >= " . $this->date2JD($time_max));
+
+		$query = "SELECT dataproduct_type, target_name, time_min, time_max FROM $table_name"
+				. ($filter.length > 0 ? ' WHERE ' . join(' AND ', $filter) : '');
+
+		$votMgr = new VOTableMgr;
+		$params = 'FORMAT=votable&LANG=ADQL&REQUEST=doQuery';
+		$url = $access_url . '/sync?' . $params . '&QUERY=' . urlencode($query);
+		$votMgr->load($url);
+		if($votMgr->isValidSchema()) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+}
+?>
diff --git a/php/config.php b/php/config.php
index 0e5417b..87e1aa1 100644
--- a/php/config.php
+++ b/php/config.php
@@ -137,6 +137,9 @@ define('PREDEFINED',BASE_PATH.'amda_plus/predefined/');
 define('PRO',BASE_PATH.'amda_plus/pro/');
 define('specialGrpsXml',SpecialSettingsDir.'Groups.xml');
 
+// EPN-TAP data
+define('EpnTapDataPath', DATAPATH.'EpnTapData/');
+
 //Help info dirs
 define('HELPPATH',  BASE_PATH."help/");
 define('targetsSimu',HELPPATH.'simu/TargetsSimu.xml');
@@ -380,7 +383,8 @@ $API = array(
         ),
         'isSharedObjectNameAlreadyUsed' => array(
         	'len'=>1
-        )
+        ),
+		'epnTapMgr' => array('len'=>2)
       )
     ) 
 );
--
libgit2 0.21.2