From e022e5b53138ede006657f293774c908d0442967 Mon Sep 17 00:00:00 2001
From: Benjamin Renard <benjamin.renard@akka.eu>
Date: Fri, 9 Aug 2019 11:43:51 +0200
Subject: [PATCH] Filter parameter columns of a catalog (#7048)

---
 js/app/views/CatalogUI.js                  |  93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
 php/classes/CatalogCacheFilterObject.php   | 285 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 php/classes/CatalogCacheIntervalObject.php |   2 +-
 php/classes/CatalogCacheObject.php         |   5 ++---
 php/classes/TimeTableCacheFilterObject.php |  24 ++++++++++++++++--------
 php/classes/TimeTableCacheObject.php       |  14 +++++++-------
 6 files changed, 394 insertions(+), 29 deletions(-)
 create mode 100644 php/classes/CatalogCacheFilterObject.php

diff --git a/js/app/views/CatalogUI.js b/js/app/views/CatalogUI.js
index d64c96a..8d0d3a1 100644
--- a/js/app/views/CatalogUI.js
+++ b/js/app/views/CatalogUI.js
@@ -12,8 +12,13 @@ Ext.define('amdaUI.CatalogUI', {
 	alias: 'widget.panelCatalog',
 
 	requires: [
+    'Ext.ux.grid.menu.RangeMenu',
+    'Ext.ux.grid.FiltersFeature',
+		'Ext.ux.grid.filter.DateFilter',
+		'Ext.ux.grid.filter.NumericFilter',
+    'Ext.ux.grid.filter.StringFilter',
 		'Ext.grid.plugin.BufferedRenderer',
-                                        'amdaUI.StatisticalPlug'
+    'amdaUI.StatisticalPlug'
 	],
 
 	isCatalog : true,
@@ -178,6 +183,13 @@ Ext.define('amdaUI.CatalogUI', {
 
 		if (me.toReconfigure)
 		{
+      // clear filters
+      if (me.TTGrid.filters) {
+        me.TTGrid.getStore().clearFilter(true);
+        me.TTGrid.filters.clearFilters();
+        me.TTGrid.filters.destroy();
+      }
+
 			var fieldsConfig =  [
 				{
 					name : 'start',
@@ -277,7 +289,8 @@ Ext.define('amdaUI.CatalogUI', {
 						allowBlank:false,
 						hideTrigger: true,
 						format : 'Y-m-d\\TH:i:s'
-					}
+					},
+          filter: { type: 'date',  dateFormat: 'Y-m-d' }
 				},
 				{
 					xtype: 'datecolumn',
@@ -292,7 +305,8 @@ Ext.define('amdaUI.CatalogUI', {
 						allowBlank:false,
 						hideTrigger: true,
 						format : 'Y-m-d\\TH:i:s'
-					}
+					},
+          filter: { type: 'date',  dateFormat: 'Y-m-d' }
 				},
 				{
 					xtype: 'gridcolumn',
@@ -310,7 +324,8 @@ Ext.define('amdaUI.CatalogUI', {
 						beforeshow : function(){
 							updateDurationColumnsVisibility(this.ownerCt.getGridColumns(), amdaUI.CatalogUI.COL_TO_HIDE_DURATION+'1');
 						}
-					}
+					},
+          filter: { type: 'numeric'}
 				},
 				{
 					xtype: 'gridcolumn',
@@ -328,7 +343,8 @@ Ext.define('amdaUI.CatalogUI', {
 						beforeshow : function(){
 							updateDurationColumnsVisibility(this.ownerCt.getGridColumns(), amdaUI.CatalogUI.COL_TO_HIDE_DURATION+'2');
 						}
-					}
+					},
+          filter: { type: 'numeric'}
 				},
 				{
 					xtype: 'gridcolumn',
@@ -346,11 +362,12 @@ Ext.define('amdaUI.CatalogUI', {
 						beforeshow : function(){
 							updateDurationColumnsVisibility(this.ownerCt.getGridColumns(), amdaUI.CatalogUI.COL_TO_HIDE_DURATION+'3');
 						}
-					}
+					},
+          filter: { type: 'numeric'}
 				}
 			];
 
-			Ext.Array.each(result.parameters, function(obj, index) {
+      Ext.Array.each(result.parameters, function(obj, index) {
 				var field = {
 					name: obj.id
 				};
@@ -361,6 +378,17 @@ Ext.define('amdaUI.CatalogUI', {
 					menuDisabled: false
 				};
 				switch (obj.type) {
+          case 0: //double
+            field = Ext.apply({}, field, {
+              type: 'string'
+            });
+            column = Ext.apply({}, column, {
+              xtype: 'gridcolumn',
+              width : 50. *  parseInt(obj.size),
+              editor: 'textfield',
+              filter: { type: 'numeric'}
+            });
+            break;
 					case 1: //dateTime
 						field = Ext.apply({}, field, {
 							type : 'date',
@@ -382,9 +410,32 @@ Ext.define('amdaUI.CatalogUI', {
 								allowBlank:false,
 								hideTrigger: true,
 								format : 'Y-m-d\\TH:i:s'
-							}
+							},
+              filter: { type: 'date',  dateFormat: 'Y-m-d' }
 						});
 						break;
+          case 2: //string
+            field = Ext.apply({}, field, {
+              type: 'string'
+            });
+            column = Ext.apply({}, column, {
+              xtype: 'gridcolumn',
+              width : 50. *  parseInt(obj.size),
+              editor: 'textfield',
+              filter: { type: 'string'}
+          });
+            break;
+          case 3: //int
+            field = Ext.apply({}, field, {
+              type: 'string'
+            });
+            column = Ext.apply({}, column, {
+              xtype: 'gridcolumn',
+              width : 50. *  parseInt(obj.size),
+              editor: 'textfield',
+              filter: { type: 'numeric'}
+            });
+            break;
 					default:
 						field = Ext.apply({}, field, {
 							type: 'string'
@@ -392,7 +443,8 @@ Ext.define('amdaUI.CatalogUI', {
 						column = Ext.apply({}, column, {
 							xtype: 'gridcolumn',
 							width : 50. *  parseInt(obj.size),
-							editor: 'textfield'
+							editor: 'textfield',
+              filter: { type: 'string'}
 						});
 				}
 				fieldsConfig.push(field);
@@ -432,6 +484,9 @@ Ext.define('amdaUI.CatalogUI', {
 			});
 
 			me.TTGrid.reconfigure(store, columnsConfig);
+      if (me.TTGrid.filters) {
+        me.TTGrid.filters.bindStore(store);
+      }
 		}
 		me.TTGrid.getSelectionModel().deselectAll();
 	//
@@ -697,8 +752,18 @@ Ext.define('amdaUI.CatalogUI', {
 			}
 		});
 
+    var filters = {
+        ftype: 'filters',
+        encode: true, // json encode the filter query
+        local: false,
+        filters: [
+
+        ]
+    };
+
 		this.TTGrid =  Ext.create('Ext.grid.Panel', {
 			height: 530,
+      features: [filters],
 			columns: [ ],
 			frame: true,
 			columnLines: true,
@@ -754,7 +819,15 @@ Ext.define('amdaUI.CatalogUI', {
 							}, this);
 						}
 					}
-				}]
+				},'->',
+        {
+            text: 'Clear Filters',
+            scope: this,
+            handler: function () {
+              this.TTGrid.getStore().clearFilter(true);
+              this.TTGrid.filters.clearFilters();
+            }
+        }]
 			}]
 		});
 
diff --git a/php/classes/CatalogCacheFilterObject.php b/php/classes/CatalogCacheFilterObject.php
new file mode 100644
index 0000000..baa26b8
--- /dev/null
+++ b/php/classes/CatalogCacheFilterObject.php
@@ -0,0 +1,285 @@
+<?php
+
+class CatalogCacheFilterPartObject extends TimeTableCacheFilterPartObject
+{
+	public static $TYPE_PARAMETER = 6;
+
+	private $paramId = "";
+
+	public function getParamId() {
+		return $this->paramId;
+	}
+
+	public function isSame($part) {
+		$same = parent::isSame($part);
+		if ($this->type != self::$TYPE_PARAMETER) {
+			return $same;
+		}
+		return $same && ($this->paramId == $part->getParamId());
+	}
+
+	public function toFiltered($interval) {
+		if ($this->type != self::$TYPE_PARAMETER) {
+			return parent::toFiltered($interval);
+		}
+
+		$params = $interval->getParams();
+
+		// Retrieve data type
+		$data_type = -1;
+		foreach ($this->cacheObject->getParametersInfo() as $parameter) {
+			if ($parameter['id'] == $this->paramId) {
+				$data_type = $parameter['type'];
+				break;
+			}
+		}
+
+		if ($data_type < 0) {
+			return FALSE;
+		}
+		else if (!isset($params[$this->paramId])) {
+			return FALSE;
+		}
+
+		switch ($parameter['type']) {
+			case 0: //double
+			{
+				$param_value = floatval($params[$this->paramId]);
+				switch ($this->op) {
+					case self::$OPERATION_LT :
+						return ($param_value < $this->value);
+					case self::$OPERATION_GT :
+						return ($param_value > $this->value);
+					case self::$OPERATION_EQ :
+						return ($param_value != $this->value);
+					default :
+						return FALSE;
+				}
+				break;
+			}
+			case 1: //date (timestamp)
+			{
+				$param_value = intval($params[$this->paramId]);
+				switch ($this->op) {
+					case self::$OPERATION_LT :
+						return ($param_value < $this->value);
+					case self::$OPERATION_GT :
+						return ($param_value > $this->value);
+					case self::$OPERATION_EQ :
+						return (!(($param_value >= $this->value) && ($param_value <= $this->value+86400)));
+					default :
+						return FALSE;
+				}
+				break;
+			}
+			case 2: //string
+				$param_value = $params[$this->paramId];
+				if (empty($this->value))
+					return FALSE;
+				if (empty($param_value))
+					return TRUE;
+				return (strpos(strtolower($param_value), strtolower($this->value)) === FALSE);
+			case 3: //int
+			{
+				$param_value = intval($params[$this->paramId]);
+				switch ($this->op) {
+					case self::$OPERATION_LT :
+						return ($param_value < $this->value);
+					case self::$OPERATION_GT :
+						return ($param_value > $this->value);
+					case self::$OPERATION_EQ :
+						return ($param_value != $this->value);
+					default :
+						return FALSE;
+				}
+				break;
+			}
+			default:
+				return FALSE;
+		}
+
+		return FALSE;
+	}
+
+	public function loadFromObject($part_obj) {
+		parent::loadFromObject($part_obj);
+		if ($this->type == self::$TYPE_UNKNOWN) {
+			//Check if it's a catalog parameter
+			foreach ($this->cacheObject->getParametersInfo() as $parameter) {
+				if ($parameter['id'] == $part_obj->field) {
+					$this->type = self::$TYPE_PARAMETER;
+					$this->paramId = $parameter['id'];
+
+					switch ($parameter['type']) {
+						case 0: //double
+							$this->value = floatval($part_obj->value);
+							break;
+						case 1: //date (timestamp)
+							$this->value = TimeUtils::iso2stamp($part_obj->value);
+							break;
+						case 2: //string
+							$this->value = $part_obj->value;
+							break;
+						case 3: //int
+							$this->value = intval($part_obj->value);
+							break;
+						default:
+							$this->value = $part_obj->value;
+					}
+
+					break;
+				}
+			}
+		}
+	}
+
+	public function writeBin($handle) {
+		fwrite($handle,pack('L2',$this->type,$this->op));
+		if ($this->type == self::$TYPE_PARAMETER) {
+				//Param Id length
+			fwrite($handle,pack('L',strlen($this->paramId)));
+
+				//Param Id
+			for ($i = 0; $i < strlen($this->paramId); ++$i)
+				fwrite($handle,pack('C',ord($this->paramId[$i])));
+
+				// Retrieve data type
+			$data_type = -1;
+			foreach ($this->cacheObject->getParametersInfo() as $parameter) {
+				if ($parameter['id'] == $this->paramId) {
+					$data_type = $parameter['type'];
+					break;
+				}
+			}
+
+			switch ($data_type) {
+				case 0: //double
+					fwrite($handle,pack('f',floatval($this->value)));
+					break;
+				case 1: //date (timestamp)
+					fwrite($handle,pack('L',intval($this->value)));
+					break;
+				case 2: //string
+					fwrite($handle,pack('L',strlen($this->value)));
+					for ($i = 0; $i < strlen($this->value); ++$i)
+						fwrite($handle,pack('C',ord($this->value[$i])));
+					break;
+				case 3: //int
+					fwrite($handle,pack('L',intval($this->value)));
+					break;
+				default:
+					fwrite($handle,pack('L',strlen($this->value)));
+					for ($i = 0; $i < strlen($this->value); ++$i)
+						fwrite($handle,pack('C',ord($this->value[$i])));
+			}
+		}
+		else {
+			fwrite($handle,pack('f',$this->value));
+		}
+	}
+
+	public function loadBin($handle) {
+		$res = unpack('L2data',fread($handle,4*2));
+		$this->type = $res['data1'];
+		$this->op   = $res['data2'];
+
+		if ($this->type == self::$TYPE_PARAMETER) {
+				//Param Id length
+			if (!$res = unpack('Lidlength',fread($handle,4)))
+				return false;
+			$idlength = $res['idlength'];
+
+				//Param Id
+			$this->paramId = "";
+			for ($j = 0; $j < $idlength; ++$j)
+			{
+				if (!$res = unpack('Cid',fread($handle,1)))
+					return false;
+				$this->paramId .= chr($res['id']);
+			}
+
+				// Retrieve data type
+			$data_type = -1;
+			foreach ($this->cacheObject->getParametersInfo() as $parameter) {
+				if ($parameter['id'] == $this->paramId) {
+					$data_type = $parameter['type'];
+					break;
+				}
+			}
+
+			switch ($data_type) {
+				case 0: //double
+					$res = unpack('fvalue',fread($handle,4));
+					$this->value = $res['value'];
+					break;
+				case 1: //date (timestamp)
+					$res = unpack('Lvalue',fread($handle,4));
+					$this->value = $res['value'];
+					break;
+				case 2: //string
+					$res = unpack('Llength',fread($handle,4));
+					$length = $res['length'];
+					$this->value = "";
+					for ($j = 0; $j < $length; ++$j)
+					{
+							if (!$res = unpack('Cvalue',fread($handle,1)))
+								return false;
+							$this->value .= chr($res['value']);
+						}
+					break;
+				case 3: //int
+					$res = unpack('Lvalue',fread($handle,4));
+					$this->value = $res['value'];
+					break;
+				default:
+					$res = unpack('Llength',fread($handle,4));
+					$length = $res['length'];
+					$this->value = "";
+					for ($j = 0; $j < $length; ++$j)
+					{
+						if (!$res = unpack('Cvalue',fread($handle,1)))
+							return false;
+						$this->value .= chr($res['value']);
+					}
+					break;
+			}
+		}
+		else {
+			$res = unpack('fvalue',fread($handle,4));
+			$this->value = $res['value'];
+		}
+	}
+
+	public function dump() {
+		if ($this->type == self::$TYPE_PARAMETER) {
+			echo "   => ".get_class($this)." : type = parameter, id = ".$this->paramId;
+			echo ", operation = ";
+			switch ($this->op)
+			{
+				case self::$OPERATION_LT :
+					echo "lt";
+					break;
+				case self::$OPERATION_GT :
+					echo "gt";
+					break;
+				case self::$OPERATION_EQ :
+					echo "eq";
+					break;
+				default:
+					echo "unknown";
+			}
+			echo ", value = ".$this->value.PHP_EOL;
+			return;
+		}
+		parent::dump();
+	}
+}
+
+class CatalogCacheFilterObject extends TimeTableCacheFilterObject
+{
+	protected function createNewPart() {
+		return new CatalogCacheFilterPartObject($this->cacheObject);
+	}
+}
+
+ ?>
diff --git a/php/classes/CatalogCacheIntervalObject.php b/php/classes/CatalogCacheIntervalObject.php
index c9e2f27..2aa3197 100644
--- a/php/classes/CatalogCacheIntervalObject.php
+++ b/php/classes/CatalogCacheIntervalObject.php
@@ -104,7 +104,7 @@ class CatalogCacheIntervalObject extends TimeTableCacheIntervalObject
 						if (!$res = unpack('Lval',fread($handle,4))) {
 							break;
 						}
-						$val = $res['val'];
+						$val = TimeUtils::stamp2iso($res['val']);
 						break;
 					case 2: //string
 						if (!$res = unpack('Llength',fread($handle,4))) {
diff --git a/php/classes/CatalogCacheObject.php b/php/classes/CatalogCacheObject.php
index 4b63de4..5b8eb38 100644
--- a/php/classes/CatalogCacheObject.php
+++ b/php/classes/CatalogCacheObject.php
@@ -7,9 +7,8 @@ class CatalogCacheObject extends TimeTableCacheObject
 	public function reset() {
 		parent::reset();
 		$this->parameters = array();
-		//ToDo - Init sort and filters for Catalog
-		/*unset($this->filter);
-		$this->filter = new TimeTableCacheFilterObject();;*/
+		unset($this->filter);
+		$this->filter = new CatalogCacheFilterObject($this);
 		unset($this->sort);
 		$this->sort = new CatalogCacheSortObject($this);
 	}
diff --git a/php/classes/TimeTableCacheFilterObject.php b/php/classes/TimeTableCacheFilterObject.php
index 282b461..900ba69 100644
--- a/php/classes/TimeTableCacheFilterObject.php
+++ b/php/classes/TimeTableCacheFilterObject.php
@@ -14,14 +14,16 @@ class TimeTableCacheFilterPartObject
 	public static $OPERATION_GT        = 2;
 	public static $OPERATION_EQ        = 3;
 
+	protected $cacheObject = NULL;
 	protected $type;
 	protected $op;
 	protected $value;
 
-	function __construct() {
+	function __construct($cacheObject) {
 		$this->type  = self::$TYPE_UNKNOWN;
 		$this->op    = self::$OPERATION_UNKNOWN;
-		$this->value = 0.;
+		$this->value = 0;
+		$this->cacheObject = $cacheObject;
 	}
 
 	public function getType() {
@@ -156,7 +158,7 @@ class TimeTableCacheFilterPartObject
 	}
 
 	public function dump() {
-		echo "   => TimeTableCacheFilterPartObject : type = ";
+		echo "   => ".get_class($this)." : type = ";
 		switch ($this->type)
 		{
 			case self::$TYPE_START :
@@ -198,10 +200,15 @@ class TimeTableCacheFilterPartObject
 
 class TimeTableCacheFilterObject
 {
+	protected $cacheObject = NULL;
 	protected $parts = array();
 
-	function __construct() {
+	protected function createNewPart() {
+		return new TimeTableCacheFilterPartObject($this->cacheObject);
+	}
 
+	function __construct($cacheObject) {
+		$this->cacheObject = $cacheObject;
 	}
 
 	public function getParts() {
@@ -222,7 +229,7 @@ class TimeTableCacheFilterObject
 
 		foreach ($filter_obj as $filter_part)
 		{
-			$part = new TimeTableCacheFilterPartObject();
+			$part = $this->createNewPart();
 			$part->loadFromObject($filter_part);
 			array_push($this->parts, $part);
 		}
@@ -245,7 +252,8 @@ class TimeTableCacheFilterObject
 	}
 
 	public function isSameFromJSON($filter_json) {
-		$filter = new TimeTableCacheFilterObject();
+		$this_class = get_class();
+		$filter = new $this_class($this->cacheObject);
 		$filter->loadFromJSON($filter_json);
 		return $this->isSame($filter);
 	}
@@ -270,14 +278,14 @@ class TimeTableCacheFilterObject
 		$res = unpack('Lcount',fread($handle,4));
 		for ($i = 0; $i < $res['count']; ++$i)
 		{
-			$part = new TimeTableCacheFilterPartObject();
+			$part = $this->createNewPart();
 			$part->loadBin($handle);
 			array_push($this->parts, $part);
 		}
 	}
 
 	public function dump() {
-		echo " => TimeTableCacheFilterObject : number of parts = ".count($this->parts).PHP_EOL;
+		echo " => ".get_class($this)." : number of parts = ".count($this->parts).PHP_EOL;
 		foreach ($this->parts as $part)
 			$part->dump();
 	}
diff --git a/php/classes/TimeTableCacheObject.php b/php/classes/TimeTableCacheObject.php
index 21766a8..88f45b8 100644
--- a/php/classes/TimeTableCacheObject.php
+++ b/php/classes/TimeTableCacheObject.php
@@ -29,7 +29,7 @@ class TimeTableCacheObject
 		$this->intervals = array();
 		$this->indexes   = array();
 		unset($this->filter);
-		$this->filter = new TimeTableCacheFilterObject();;
+		$this->filter = new TimeTableCacheFilterObject($this);
 		unset($this->sort);
 		$this->sort = new TimeTableCacheSortObject($this);
 	}
@@ -401,15 +401,15 @@ class TimeTableCacheObject
 		//Modified
 		fwrite($handle,pack('L',$this->isModified));
 
+		//Additional info
+		$this->writeAdditionalHeaderBin($handle);
+
 		//Filter
 		$this->filter->writeBin($handle);
 
 		//Sort
 		$this->sort->writeBin($handle);
 
-		//Additional info
-		$this->writeAdditionalHeaderBin($handle);
-
 		//Intervals
 		fwrite($handle,pack('L2',count($this->intervals), $this->lastId));
 		foreach($this->intervals as $interval) {
@@ -451,15 +451,15 @@ class TimeTableCacheObject
 			return false;
 		$this->isModified = $res['modified'];
 
+		//Additional info
+		$this->loadAdditionalHeaderBin($handle);
+
 		//Filter
 		$this->filter->loadBin($handle);
 
 		//Sort
 		$this->sort->loadBin($handle);
 
-		//Additional info
-		$this->loadAdditionalHeaderBin($handle);
-
 		//Intervals
 		$res = unpack('L2data',fread($handle,2*4));
 		$nbIntervals  = $res['data1'];
--
libgit2 0.21.2