diff options
author | Alexander Shubin <aleksandrs.subins@zabbix.com> | 2021-05-10 17:49:31 +0300 |
---|---|---|
committer | Alexander Shubin <aleksandrs.subins@zabbix.com> | 2021-05-10 17:49:31 +0300 |
commit | 6884c1c7a2e9debd41f149f168b87250a249c0c6 (patch) | |
tree | 4dafc24a5ee5c70d3dc808eede06d8bf09768c43 /ui | |
parent | 1ca32e7b027871a26432c1b9d2a5883740bd6231 (diff) | |
parent | afed3e3c3669bec757c1b5eb92968f87ea1c9278 (diff) |
A...I..... [ZBXNEXT-6411] updated to latest from master; resolved conflicts in:
# create/src/schema.tmpl
# src/libs/zbxdbupgrade/dbupgrade_5030.c
# templates/app/activemq_jmx/template_app_activemq_jmx.yaml
# templates/app/apache_agent/template_app_apache_agent.yaml
# templates/app/apache_http/template_app_apache_http.yaml
# templates/app/aranet/aranet_cloud.yaml
# templates/app/ceph_agent2/template_app_ceph_agent2.yaml
# templates/app/docker/template_app_docker.yaml
# templates/app/elasticsearch_http/template_app_elasticsearch_http.yaml
# templates/app/etcd_http/template_app_etcd_http.yaml
# templates/app/exchange/template_app_exchange.yaml
# templates/app/exchange_active/template_app_exchange_active.yaml
# templates/app/generic_java_jmx/template_app_generic_java_jmx.yaml
# templates/app/gitlab_http/template_app_gitlab_http.yaml
# templates/app/hadoop_http/template_app_hadoop_http.yaml
# templates/app/haproxy_agent/template_app_haproxy_agent.yaml
# templates/app/haproxy_http/template_app_haproxy_http.yaml
# templates/app/iis_agent/template_app_iis_agent.yaml
# templates/app/iis_agent_active/template_app_iis_agent_active.yaml
# templates/app/jenkins/template_app_jenkins.yaml
# templates/app/kafka_jmx/template_app_kafka_jmx.yaml
# templates/app/memcached/template_app_memcached.yaml
# templates/app/nginx_agent/template_app_nginx_agent.yaml
# templates/app/nginx_http/template_app_nginx_http.yaml
# templates/app/php-fpm_agent/template_app_php-fpm_agent.yaml
# templates/app/php-fpm_http/template_app_php-fpm_http.yaml
# templates/app/rabbitmq_agent/template_app_rabbitmq_agent.yaml
# templates/app/rabbitmq_http/template_app_rabbitmq_http.yaml
# templates/app/sharepoint_http/template_app_sharepoint_http.yaml
# templates/app/squid_snmp/template_app_squid_snmp.yaml
# templates/app/tomcat_jmx/template_app_tomcat_jmx.yaml
# templates/app/vault_http/template_app_vault.yaml
# templates/app/vmware/template_app_vmware.yaml
# templates/app/vmware_fqdn/template_app_vmware_fqdn.yaml
# templates/app/wildfly_domain_jmx/template_app_wildfly_domain_jmx.yaml
# templates/app/wildfly_server_jmx/template_app_wildfly_server_jmx.yaml
# templates/app/zookeeper_http/template_app_zookeeper_http.yaml
# templates/cctv/hikvision/template_cctv_hikvision_camera.yaml
# templates/classic/template_app_ftp_service.yaml
# templates/classic/template_app_http_service.yaml
# templates/classic/template_app_https_service.yaml
# templates/classic/template_app_imap_service.yaml
# templates/classic/template_app_ldap_service.yaml
# templates/classic/template_app_nntp_service.yaml
# templates/classic/template_app_ntp_service.yaml
# templates/classic/template_app_pop_service.yaml
# templates/classic/template_app_remote_zabbix_proxy.yaml
# templates/classic/template_app_remote_zabbix_server.yaml
# templates/classic/template_app_smtp_service.yaml
# templates/classic/template_app_ssh_service.yaml
# templates/classic/template_app_telnet_service.yaml
# templates/classic/template_app_zabbix_proxy.yaml
# templates/classic/template_app_zabbix_server.yaml
# templates/classic/template_os_aix.yaml
# templates/classic/template_os_freebsd.yaml
# templates/classic/template_os_hp-ux.yaml
# templates/classic/template_os_mac_os_x.yaml
# templates/classic/template_os_openbsd.yaml
# templates/classic/template_os_solaris.yaml
# templates/classic/template_server_intel_sr1530_ipmi.yaml
# templates/classic/template_server_intel_sr1630_ipmi.yaml
# templates/db/cassandra_jmx/template_db_cassandra_jmx.yaml
# templates/db/clickhouse_http/template_db_clickhouse_http.yaml
# templates/db/ignite_jmx/template_db_ignite_jmx.yaml
# templates/db/mongodb/template_db_mongodb.yaml
# templates/db/mongodb_cluster/template_db_mongodb_cluster.yaml
# templates/db/mssql_odbc/template_db_mssql_odbc.yaml
# templates/db/mysql_agent/template_db_mysql_agent.yaml
# templates/db/mysql_agent2/template_db_mysql_agent2.yaml
# templates/db/mysql_odbc/template_db_mysql_odbc.yaml
# templates/db/oracle_agent2/template_db_oracle_agent2.yaml
# templates/db/oracle_odbc/template_db_oracle_odbc.yaml
# templates/db/postgresql/template_db_postgresql.yaml
# templates/db/postgresql_agent2/template_db_postgresql_agent2.yaml
# templates/db/redis/template_db_redis.yaml
# templates/db/tidb_http/tidb_pd_http/template_db_tidb_pd_http.yaml
# templates/db/tidb_http/tidb_tidb_http/template_db_tidb_tidb_http.yaml
# templates/db/tidb_http/tidb_tikv_http/template_db_tidb_tikv_http.yaml
# templates/module/00icmp_ping/00template_module_icmp_ping.yaml
# templates/module/ether_like_snmp/template_module_ether_like_snmp.yaml
# templates/module/generic_snmp_snmp/template_module_generic_snmp_snmp.yaml
# templates/module/host_resources_snmp/template_module_host_resources_snmp.yaml
# templates/module/interfaces_simple_snmp/template_module_interfaces_simple_snmp.yaml
# templates/module/interfaces_snmp/template_module_interfaces_snmp.yaml
# templates/module/interfaces_win_snmp/template_module_interfaces_win_snmp.yaml
# templates/module/smart_agent2/template_module_smart_agent2.yaml
# templates/module/smart_agent2_active/template_module_smart_agent2_active.yaml
# templates/module/zabbix_agent/template_module_zabbix_agent.yaml
# templates/net/alcatel_timetra_snmp/template_net_alcatel_timetra_snmp.yaml
# templates/net/arista_snmp/template_net_arista_snmp.yaml
# templates/net/brocade_fc_sw_snmp/template_net_brocade_fc_sw_snmp.yaml
# templates/net/brocade_foundry_sw_snmp/template_net_brocade_foundry_sw_snmp.yaml
# templates/net/cisco_catalyst_3750/cisco_catalyst_3750_24fs_snmp/template_net_cisco_catalyst_3750_24fs_snmp.yaml
# templates/net/cisco_catalyst_3750/cisco_catalyst_3750_24ps_snmp/template_net_cisco_catalyst_3750_24ps_snmp.yaml
# templates/net/cisco_catalyst_3750/cisco_catalyst_3750_24ts_snmp/template_net_cisco_catalyst_3750_24ts_snmp.yaml
# templates/net/cisco_catalyst_3750/cisco_catalyst_3750_48ps_snmp/template_net_cisco_catalyst_3750_48ps_snmp.yaml
# templates/net/cisco_catalyst_3750/cisco_catalyst_3750_48ts_snmp/template_net_cisco_catalyst_3750_48ts_snmp.yaml
# templates/net/cisco_snmp/template_net_cisco_snmp.yaml
# templates/net/dell_force_s_series_snmp/template_net_dell_force_s_series_snmp.yaml
# templates/net/dlink_des7200_snmp/template_net_dlink_des7200_snmp.yaml
# templates/net/dlink_des_snmp/template_net_dlink_des_snmp.yaml
# templates/net/extreme_snmp/template_net_extreme_snmp.yaml
# templates/net/hp_hh3c_snmp/template_net_hp_hh3c_snmp.yaml
# templates/net/hp_hpn_snmp/template_net_hp_hpn_snmp.yaml
# templates/net/huawei_snmp/template_net_huawei_snmp.yaml
# templates/net/intel_qlogic_infiniband_snmp/template_net_intel_qlogic_infiniband_snmp.yaml
# templates/net/juniper_snmp/template_net_juniper_snmp.yaml
# templates/net/mellanox_snmp/template_net_mellanox_snmp.yaml
# templates/net/mikrotik_snmp/template_net_mikrotik_snmp.yaml
# templates/net/morningstar_snmp/prostar_mppt_snmp/prostar_mppt_snmp.yaml
# templates/net/morningstar_snmp/prostar_pwm_snmp/prostar_pwm_snmp.yaml
# templates/net/morningstar_snmp/sunsaver_mppt_snmp/sunsaver_mppt_snmp.yaml
# templates/net/morningstar_snmp/suresine_snmp/suresine_snmp.yaml
# templates/net/morningstar_snmp/tristar_mppt_600V_snmp/tristar_mppt_600V_snmp.yaml
# templates/net/morningstar_snmp/tristar_mppt_snmp/tristar_mppt_snmp.yaml
# templates/net/morningstar_snmp/tristar_pwm_snmp/tristar_pwm_snmp.yaml
# templates/net/netgear_snmp/template_net_netgear_snmp.yaml
# templates/net/qtech_snmp/template_net_qtech_snmp.yaml
# templates/net/tplink_snmp/template_net_tplink_snmp.yaml
# templates/net/ubiquiti_airos_snmp/template_net_ubiquiti_airos_snmp.yaml
# templates/os/linux/template_os_linux.yaml
# templates/os/linux_active/template_os_linux_active.yaml
# templates/os/linux_prom/template_os_linux_prom.yaml
# templates/os/linux_snmp_snmp/template_os_linux_snmp_snmp.yaml
# templates/os/windows_agent/template_os_windows_agent.yaml
# templates/os/windows_agent_active/template_os_windows_agent_active.yaml
# templates/power/apc/apc_ups_galaxy_3500_snmp/template_power_apc_ups_galaxy_3500_snmp.yaml
# templates/power/apc/apc_ups_smart_2200_rm_snmp/template_power_apc_ups_smart_2200_rm_snmp.yaml
# templates/power/apc/apc_ups_smart_3000_xlm_snmp/template_power_apc_ups_smart_3000_xlm_snmp.yaml
# templates/power/apc/apc_ups_smart_rt_1000_rm_xl_snmp/template_power_apc_ups_smart_rt_1000_rm_xl_snmp.yaml
# templates/power/apc/apc_ups_smart_rt_1000_xl_snmp/template_power_apc_ups_smart_rt_1000_xl_snmp.yaml
# templates/power/apc/apc_ups_smart_srt_5000_snmp/template_power_apc_ups_smart_srt_5000_snmp.yaml
# templates/power/apc/apc_ups_smart_srt_8000_snmp/template_power_apc_ups_smart_srt_8000_snmp.yaml
# templates/power/apc/apc_ups_snmp/template_power_apc_ups_snmp.yaml
# templates/power/apc/apc_ups_symmetra_lx_snmp/template_power_apc_ups_symmetra_lx_snmp.yaml
# templates/power/apc/apc_ups_symmetra_rm_snmp/template_power_apc_ups_symmetra_rm_snmp.yaml
# templates/power/apc/apc_ups_symmetra_rx_snmp/template_power_apc_ups_symmetra_rx_snmp.yaml
# templates/san/huawei_5300v5_snmp/template_san_huawei_5300v5_snmp.yaml
# templates/san/netapp_aff_a700_http/template_san_netapp_aff_a700_http.yaml
# templates/san/netapp_fas3220_snmp/template_san_netapp_fas3220_snmp.yaml
# templates/server/chassis_ipmi/template_server_chassis_ipmi.yaml
# templates/server/cisco_ucs_snmp/template_server_cisco_ucs_snmp.yaml
# templates/server/dell_idrac_snmp/template_server_dell_idrac_snmp.yaml
# templates/server/hp_ilo_snmp/template_server_hp_ilo_snmp.yaml
# templates/server/ibm_imm_snmp/template_server_ibm_imm_snmp.yaml
# templates/server/supermicro_aten_snmp/template_server_supermicro_aten_snmp.yaml
# templates/tel/asterisk_http/template_tel_asterisk_http.yaml
# ui/include/classes/api/services/CValueMap.php
# ui/include/classes/validators/CApiInputValidator.php
# ui/include/defines.inc.php
# ui/tests/unit/include/classes/validators/CApiInputValidatorTest.php
Diffstat (limited to 'ui')
54 files changed, 3244 insertions, 482 deletions
diff --git a/ui/app/controllers/CControllerPopupGeneric.php b/ui/app/controllers/CControllerPopupGeneric.php index e4d66845f14..619223b5025 100644 --- a/ui/app/controllers/CControllerPopupGeneric.php +++ b/ui/app/controllers/CControllerPopupGeneric.php @@ -1348,7 +1348,7 @@ class CControllerPopupGeneric extends CController { $db_valuemaps = API::ValueMap()->get([ 'output' => ['valuemapid', 'name', 'hostid'], - 'selectMappings' => ['value', 'newvalue'], + 'selectMappings' => ['type', 'value', 'newvalue'], 'hostids' => $hostids, 'limit' => $limit ]); @@ -1356,7 +1356,6 @@ class CControllerPopupGeneric extends CController { $disable_names = $this->getInput('disable_names', []); foreach ($db_valuemaps as $db_valuemap) { - order_result($db_valuemap['mappings'], 'value'); $valuemap = [ 'id' => $db_valuemap['valuemapid'], 'hostname' => $hosts[$db_valuemap['hostid']]['name'], diff --git a/ui/app/controllers/CControllerPopupItemTestGetValue.php b/ui/app/controllers/CControllerPopupItemTestGetValue.php index 1536ff035d2..3d697333407 100644 --- a/ui/app/controllers/CControllerPopupItemTestGetValue.php +++ b/ui/app/controllers/CControllerPopupItemTestGetValue.php @@ -147,7 +147,7 @@ class CControllerPopupItemTestGetValue extends CControllerPopupItemTest { unset($data['value_type']); } else { - $data['hostid'] = $this->getInput('hostid'); + $data['host']['hostid'] = $this->getInput('hostid'); } // Rename fields according protocol. diff --git a/ui/app/controllers/CControllerPopupItemTestSend.php b/ui/app/controllers/CControllerPopupItemTestSend.php index 074762e4db5..655cf7470a8 100644 --- a/ui/app/controllers/CControllerPopupItemTestSend.php +++ b/ui/app/controllers/CControllerPopupItemTestSend.php @@ -256,7 +256,7 @@ class CControllerPopupItemTestSend extends CControllerPopupItemTest { $valuemap = ($this->getInput('valuemapid', 0) != 0) ? API::ValueMap()->get([ 'output' => [], - 'selectMappings' => ['newvalue', 'value'], + 'selectMappings' => ['type', 'newvalue', 'value'], 'valuemapids' => $this->getInput('valuemapid') ])[0] : []; @@ -294,7 +294,7 @@ class CControllerPopupItemTestSend extends CControllerPopupItemTest { } if ($item_test_data['type'] == ITEM_TYPE_CALCULATED) { - $item_test_data['hostid'] = $this->getInput('hostid'); + $item_test_data['host']['hostid'] = $this->getInput('hostid'); } // Only non-empty fields need to be sent to server. @@ -422,7 +422,9 @@ class CControllerPopupItemTestSend extends CControllerPopupItemTest { ]; if ($valuemap) { - $output['mapped_value'] = CValueMapHelper::applyValueMap($result['result'], $valuemap); + $output['mapped_value'] = CValueMapHelper::applyValueMap($preproc_test_data['value_type'], + $result['result'], $valuemap + ); } } elseif (array_key_exists('error', $result)) { diff --git a/ui/app/controllers/CControllerPopupTriggerExpr.php b/ui/app/controllers/CControllerPopupTriggerExpr.php index aaaee743836..bf1cccd6d45 100644 --- a/ui/app/controllers/CControllerPopupTriggerExpr.php +++ b/ui/app/controllers/CControllerPopupTriggerExpr.php @@ -20,6 +20,7 @@ class CControllerPopupTriggerExpr extends CController { + private $metrics = []; private $param1SecCount = []; private $param1Period = []; @@ -30,7 +31,6 @@ class CControllerPopupTriggerExpr extends CController { private $param3SecVal = []; private $param_find = []; private $param3SecPercent = []; - private $paramSecIntCount = []; private $paramForecast = []; private $paramTimeleft = []; private $allowedTypesAny = []; @@ -39,7 +39,7 @@ class CControllerPopupTriggerExpr extends CController { private $allowedTypesLog = []; private $allowedTypesInt = []; private $functions = []; - private $operators = []; + private $operators = ['=', '<>', '>', '<', '>=', '<=']; private $period_optional = []; protected function init() { @@ -196,25 +196,6 @@ class CControllerPopupTriggerExpr extends CController { ] ]; - $this->paramSecIntCount = [ - 'last' => [ - 'C' => _('Last of').' (T)', - 'T' => T_ZBX_INT, - 'M' => $this->metrics, - 'A' => true - ], - 'shift' => [ - 'C' => _('Time shift'), - 'T' => T_ZBX_INT, - 'A' => false - ], - 'mask' => [ - 'C' => _('Mask'), - 'T' => T_ZBX_STR, - 'A' => true - ] - ]; - $this->paramForecast = [ 'last' => [ 'C' => _('Last of').' (T)', @@ -297,180 +278,767 @@ class CControllerPopupTriggerExpr extends CController { $this->functions = [ 'abs' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], 'description' => _('abs() - Absolute value'), 'allowed_types' => $this->allowedTypesAny, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators + ], + 'acos' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('acos() - The arccosine of a value as an angle, expressed in radians'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'ascii' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('ascii() - Returns the ASCII code of the leftmost character of the value'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesStr, + 'operators' => $this->operators + ], + 'asin' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('asin() - The arcsine of a value as an angle, expressed in radians'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'atan' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('atan() - The arctangent of a value as an angle, expressed in radians'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'atan2' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('atan2() - The arctangent of the ordinate (exprue) and abscissa coordinates specified as an angle, expressed in radians'), + 'params' => $this->param1SecCount + [ + 'abscissa' => [ + 'C' => _('Abscissa'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators ], 'avg' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE, ZBX_FUNCTION_TYPE_MATH], 'description' => _('avg() - Average value of a period T'), 'params' => $this->param1SecCount, 'allowed_types' => $this->allowedTypesNumeric, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators ], - 'change' => [ - 'description' => _('change() - Difference between last and previous value'), - 'allowed_types' => $this->allowedTypesAny, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'between' => [ + 'types' => [ZBX_FUNCTION_TYPE_OPERATOR], + 'description' => _('between() - Checks if a value belongs to the given range (1 - in range, 0 - otherwise)'), + 'params' => $this->param1SecCount + [ + 'min' => [ + 'C' => _('Min'), + 'T' => T_ZBX_STR, + 'A' => true + ], + 'max' => [ + 'C' => _('Max'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => ['=', '<>'] ], - 'count' => [ - 'description' => _('count() - Number of successfully retrieved values V (which fulfill operator O) for period T'), - 'params' => $this->param3SecVal, - 'allowed_types' => $this->allowedTypesAny, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'bitand' => [ + 'types' => [ZBX_FUNCTION_TYPE_BITWISE], + 'description' => _('bitand() - Bitwise AND'), + 'params' => $this->param1SecCount + [ + 'mask' => [ + 'C' => _('Mask'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesInt, + 'operators' => $this->operators ], - 'find' => [ - 'description' => _('find() - Check occurrence of pattern V (which fulfill operator O) for period T (1 - match, 0 - no match)'), - 'params' => $this->period_optional + $this->param_find, + 'bitlength' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('bitlength() - Returns the length in bits'), + 'params' => $this->param1SecCount, 'allowed_types' => $this->allowedTypesAny, - 'operators' => ['=', '<>'] + 'operators' => $this->operators ], - 'last' => [ - 'description' => _('last() - Last (most recent) T value'), + 'bitlshift' => [ + 'types' => [ZBX_FUNCTION_TYPE_BITWISE], + 'description' => _('bitlshift() - Bitwise shift left'), + 'params' => $this->param1SecCount + [ + 'bits' => [ + 'C' => _('Bits to shift'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesInt, + 'operators' => $this->operators + ], + 'bitnot' => [ + 'types' => [ZBX_FUNCTION_TYPE_BITWISE], + 'description' => _('bitnot() - Bitwise NOT'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesInt, + 'operators' => $this->operators + ], + 'bitor' => [ + 'types' => [ZBX_FUNCTION_TYPE_BITWISE], + 'description' => _('bitor() - Bitwise OR'), + 'params' => $this->param1SecCount + [ + 'mask' => [ + 'C' => _('Mask'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesInt, + 'operators' => $this->operators + ], + 'bitrshift' => [ + 'types' => [ZBX_FUNCTION_TYPE_BITWISE], + 'description' => _('bitrshift() - Bitwise shift right'), + 'params' => $this->param1SecCount + [ + 'bits' => [ + 'C' => _('Bits to shift'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesInt, + 'operators' => $this->operators + ], + 'bitxor' => [ + 'types' => [ZBX_FUNCTION_TYPE_BITWISE], + 'description' => _('bitxor() - Bitwise exclusive OR'), + 'params' => $this->param1SecCount + [ + 'mask' => [ + 'C' => _('Mask'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesInt, + 'operators' => $this->operators + ], + 'bytelength' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('bytelength() - Returns the length in bytes'), 'params' => $this->param1SecCount, 'allowed_types' => $this->allowedTypesAny, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators ], - 'length' => [ - 'description' => _('length() - Length of last (most recent) T value in characters'), - 'allowed_types' => $this->allowedTypesStr, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'cbrt' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('cbrt() - Cube root'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators ], - 'max' => [ - 'description' => _('max() - Maximum value for period T'), + 'ceil' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('ceil() - Rounds up to the nearest greater integer'), 'params' => $this->param1SecCount, 'allowed_types' => $this->allowedTypesNumeric, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators ], - 'min' => [ - 'description' => _('min() - Minimum value for period T'), + 'change' => [ + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], + 'description' => _('change() - Difference between last and previous value'), + 'allowed_types' => $this->allowedTypesAny, + 'operators' => $this->operators + ], + 'char' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('char() - Returns the character which represents the given ASCII code'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesInt, + 'operators' => $this->operators + ], + 'concat' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('concat() - Returns a string that is the result of concatenating value to string'), + 'params' => $this->param1SecCount + [ + 'string' => [ + 'C' => _('String'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesAny, + 'operators' => $this->operators + ], + 'cos' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('cos() - The cosine of a value, where the value is an angle expressed in radians'), 'params' => $this->param1SecCount, 'allowed_types' => $this->allowedTypesNumeric, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators ], - 'percentile' => [ - 'description' => _('percentile() - Percentile P of a period T'), - 'params' => $this->param3SecPercent, + 'cosh' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('cosh() - The hyperbolic cosine of a value'), + 'params' => $this->param1SecCount, 'allowed_types' => $this->allowedTypesNumeric, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators ], - 'sum' => [ - 'description' => _('sum() - Sum of values of a period T'), + 'cot' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('cot() - The cotangent of a value, where the value is an angle expressed in radians'), 'params' => $this->param1SecCount, 'allowed_types' => $this->allowedTypesNumeric, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators + ], + 'count' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE], + 'description' => _('count() - Number of successfully retrieved values V (which fulfill operator O) for period T'), + 'params' => $this->param3SecVal, + 'allowed_types' => $this->allowedTypesAny, + 'operators' => $this->operators + ], + 'countunique' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE], + 'description' => _('countunique() - The number of unique values'), + 'params' => $this->param3SecVal, + 'allowed_types' => $this->allowedTypesAny, + 'operators' => $this->operators ], 'date' => [ + 'types' => [ZBX_FUNCTION_TYPE_DATE_TIME], 'description' => _('date() - Current date'), 'allowed_types' => $this->allowedTypesAny, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators + ], + 'dayofmonth' => [ + 'types' => [ZBX_FUNCTION_TYPE_DATE_TIME], + 'description' => _('dayofmonth() - Day of month'), + 'allowed_types' => $this->allowedTypesAny, + 'operators' => $this->operators ], 'dayofweek' => [ + 'types' => [ZBX_FUNCTION_TYPE_DATE_TIME], 'description' => _('dayofweek() - Day of week'), 'allowed_types' => $this->allowedTypesAny, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators ], - 'dayofmonth' => [ - 'description' => _('dayofmonth() - Day of month'), + 'degrees' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('degrees() - Converts a value from radians to degrees'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'e' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _("e() - Returns Euler's number"), + 'allowed_types' => $this->allowedTypesAny + ], + 'exp' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _("exp() - Euler's number at a power of a value"), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'expm1' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _("expm1() - Euler's number at a power of a value minus 1"), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'find' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('find() - Check occurrence of pattern V (which fulfill operator O) for period T (1 - match, 0 - no match)'), + 'params' => $this->period_optional + $this->param_find, 'allowed_types' => $this->allowedTypesAny, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => ['=', '<>'] + ], + 'first' => [ + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], + 'description' => _('first() - The oldest value in the specified time interval'), + 'params' => $this->param1Sec + $this->period_optional, + 'allowed_types' => $this->allowedTypesAny, + 'operators' => $this->operators + ], + 'floor' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('floor() - Rounds down to the nearest smaller integer'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'forecast' => [ + 'types' => [ZBX_FUNCTION_TYPE_PREDICTION], + 'description' => _('forecast() - Forecast for next t seconds based on period T'), + 'params' => $this->paramForecast, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators ], 'fuzzytime' => [ + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], 'description' => _('fuzzytime() - Difference between item value (as timestamp) and Zabbix server timestamp is less than or equal to T seconds (1 - true, 0 - false)'), 'params' => $this->param1Sec, 'allowed_types' => $this->allowedTypesNumeric, 'operators' => ['=', '<>'] ], + 'in' => [ + 'types' => [ZBX_FUNCTION_TYPE_OPERATOR], + 'description' => _('in() - Checks if a value equals to one of the listed values (1 - equals, 0 - otherwise)'), + 'params' => $this->param1SecCount + [ + 'values' => [ + 'C' => _('Values'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesAny, + 'operators' => ['=', '<>'] + ], + 'insert' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('insert() - Inserts specified characters or spaces into a character string, beginning at a specified position in the string'), + 'params' => $this->param1SecCount + [ + 'start' => [ + 'C' => _('Start'), + 'T' => T_ZBX_STR, + 'A' => true + ], + 'length' => [ + 'C' => _('Length'), + 'T' => T_ZBX_STR, + 'A' => true + ], + 'replace' => [ + 'C' => _('Replacement'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesStr, + 'operators' => $this->operators + ], + 'kurtosis' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE], + 'description' => _('kurtosis() - Measures the "tailedness" of the probability distribution'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'last' => [ + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], + 'description' => _('last() - Last (most recent) T value'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesAny, + 'operators' => $this->operators + ], + 'left' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('left() - Returns the leftmost count characters'), + 'params' => $this->param1SecCount + [ + 'count' => [ + 'C' => _('Count'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesStr, + 'operators' => $this->operators + ], + 'length' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('length() - Length of last (most recent) T value in characters'), + 'allowed_types' => $this->allowedTypesStr, + 'operators' => $this->operators + ], + 'log' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('log() - Natural logarithm'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'log10' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('log10() - Decimal logarithm'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], 'logeventid' => [ + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], 'description' => _('logeventid() - Event ID of last log entry matching regular expression V for period T (1 - match, 0 - no match)'), 'params' => $this->period_optional + $this->param1Str, 'allowed_types' => $this->allowedTypesLog, 'operators' => ['=', '<>'] ], 'logseverity' => [ + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], 'description' => _('logseverity() - Log severity of the last log entry for period T'), 'params' => $this->period_optional, 'allowed_types' => $this->allowedTypesLog, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators ], 'logsource' => [ + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], 'description' => _('logsource() - Log source of the last log entry matching parameter V for period T (1 - match, 0 - no match)'), 'params' => $this->period_optional + $this->param1Str, 'allowed_types' => $this->allowedTypesLog, 'operators' => ['=', '<>'] ], - 'now' => [ - 'description' => _('now() - Number of seconds since the Epoch'), - 'allowed_types' => $this->allowedTypesAny, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'ltrim' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('ltrim() - Remove specified characters from the beginning of a string'), + 'params' => $this->param1SecCount + [ + 'chars' => [ + 'C' => _('Chars'), + 'T' => T_ZBX_STR, + 'A' => false + ] + ], + 'allowed_types' => $this->allowedTypesStr, + 'operators' => $this->operators ], - 'time' => [ - 'description' => _('time() - Current time'), - 'allowed_types' => $this->allowedTypesAny, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'mad' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE], + 'description' => _('mad() - Median absolute deviation'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'max' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE, ZBX_FUNCTION_TYPE_MATH], + 'description' => _('max() - Maximum value for period T'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'mid' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('mid() - Returns a substring beginning at the character position specified by start for N characters'), + 'params' => $this->param1SecCount + [ + 'start' => [ + 'C' => _('Start'), + 'T' => T_ZBX_STR, + 'A' => true + ], + 'length' => [ + 'C' => _('Length'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesStr, + 'operators' => $this->operators + ], + 'min' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE, ZBX_FUNCTION_TYPE_MATH], + 'description' => _('min() - Minimum value for period T'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'mod' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('mod() - Division remainder'), + 'params' => $this->param1SecCount + [ + 'denominator' => [ + 'C' => _('Division denominator'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators ], 'nodata' => [ + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], 'description' => _('nodata() - No data received during period of time T (1 - true, 0 - false), Mode (strict - ignore proxy time delay in sending data)'), 'params' => $this->param2SecMode, 'allowed_types' => $this->allowedTypesAny, 'operators' => ['=', '<>'] ], - 'bitand' => [ - 'description' => _('bitand() - Bitwise AND of last (most recent) T value and mask'), - 'params' => $this->paramSecIntCount, - 'allowed_types' => $this->allowedTypesInt, - 'operators' => ['=', '<>'] + 'now' => [ + 'types' => [ZBX_FUNCTION_TYPE_DATE_TIME], + 'description' => _('now() - Number of seconds since the Epoch'), + 'allowed_types' => $this->allowedTypesAny, + 'operators' => $this->operators ], - 'forecast' => [ - 'description' => _('forecast() - Forecast for next t seconds based on period T'), - 'params' => $this->paramForecast, + 'percentile' => [ + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], + 'description' => _('percentile() - Percentile P of a period T'), + 'params' => $this->param3SecPercent, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'pi' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('pi() - Returns the Pi constant'), + 'allowed_types' => $this->allowedTypesAny + ], + 'power' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('power() - The power of a base value to a power value'), + 'params' => $this->param1SecCount + [ + 'power' => [ + 'C' => _('Power value'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'radians' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('radians() - Converts a value from degrees to radians'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'rand' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('rand() - A random integer value'), + 'allowed_types' => $this->allowedTypesAny + ], + 'repeat' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('repeat() - Returns a string composed of value repeated count times'), + 'params' => $this->param1SecCount + [ + 'count' => [ + 'C' => _('Count'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesStr, + 'operators' => $this->operators + ], + 'replace' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('replace() - Search value for occurrences of pattern, and replace with replacement'), + 'params' => $this->param1SecCount + [ + 'pattern' => [ + 'C' => _('Pattern'), + 'T' => T_ZBX_STR, + 'A' => true + ], + 'replace' => [ + 'C' => _('Replacement'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesStr, + 'operators' => $this->operators + ], + 'right' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('right() - Returns the rightmost count characters'), + 'params' => $this->param1SecCount + [ + 'count' => [ + 'C' => _('Count'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesStr, + 'operators' => $this->operators + ], + 'round' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('round() - Rounds a value to decimal places'), + 'params' => $this->param1SecCount + [ + 'decimals' => [ + 'C' => _('Decimal places'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'rtrim' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('rtrim() - Removes specified characters from the end of a string'), + 'params' => $this->param1SecCount + [ + 'chars' => [ + 'C' => _('Chars'), + 'T' => T_ZBX_STR, + 'A' => false + ] + ], + 'allowed_types' => $this->allowedTypesStr, + 'operators' => $this->operators + ], + 'signum' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('signum() - Returns -1 if a value is negative, 0 if a value is zero, 1 if a value is positive'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'sin' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('sin() - The sine of a value, where the value is an angle expressed in radians'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'sinh' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('sinh() - The hyperbolic sine of a value'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'skewness' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE], + 'description' => _('skewness() - Measures the asymmetry of the probability distribution'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'sqrt' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('sqrt() - Square root of a value'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'stddevpop' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE], + 'description' => _('stddevpop() - Population standard deviation'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'stddevsamp' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE], + 'description' => _('stddevsamp() - Sample standard deviation'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'sum' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE, ZBX_FUNCTION_TYPE_MATH], + 'description' => _('sum() - Sum of values of a period T'), + 'params' => $this->param1SecCount, 'allowed_types' => $this->allowedTypesNumeric, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators + ], + 'sumofsquares' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE], + 'description' => _('sumofsquares() - The sum of squares'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'tan' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('tan() - The tangent of a value'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'time' => [ + 'types' => [ZBX_FUNCTION_TYPE_DATE_TIME], + 'description' => _('time() - Current time'), + 'allowed_types' => $this->allowedTypesAny, + 'operators' => $this->operators ], 'timeleft' => [ + 'types' => [ZBX_FUNCTION_TYPE_PREDICTION], 'description' => _('timeleft() - Time to reach threshold estimated based on period T'), 'params' => $this->paramTimeleft, 'allowed_types' => $this->allowedTypesNumeric, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators ], 'trendavg' => [ + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], 'description' => _('trendavg() - Average value of a period T with exact period shift'), 'params' => $this->param1Period, 'allowed_types' => $this->allowedTypesNumeric, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators ], 'trendcount' => [ - 'description' => _('trendcount() - Number of successfully retrieved values V (which fulfill operator O) for period T with exact period shift'), + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], + 'description' => _('trendcount() - Number of successfully retrieved values for period T'), 'params' => $this->param1Period, 'allowed_types' => $this->allowedTypesAny, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators ], 'trendmax' => [ + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], 'description' => _('trendmax() - Maximum value for period T with exact period shift'), 'params' => $this->param1Period, 'allowed_types' => $this->allowedTypesNumeric, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators ], 'trendmin' => [ + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], 'description' => _('trendmin() - Minimum value for period T with exact period shift'), 'params' => $this->param1Period, 'allowed_types' => $this->allowedTypesNumeric, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators ], 'trendsum' => [ + 'types' => [ZBX_FUNCTION_TYPE_HISTORY], 'description' => _('trendsum() - Sum of values of a period T with exact period shift'), 'params' => $this->param1Period, 'allowed_types' => $this->allowedTypesNumeric, - 'operators' => ['=', '<>', '>', '<', '>=', '<='] + 'operators' => $this->operators + ], + 'trim' => [ + 'types' => [ZBX_FUNCTION_TYPE_STRING], + 'description' => _('trim() - Remove specified characters from the beginning and the end of a string'), + 'params' => $this->param1SecCount + [ + 'chars' => [ + 'C' => _('Chars'), + 'T' => T_ZBX_STR, + 'A' => false + ] + ], + 'allowed_types' => $this->allowedTypesStr, + 'operators' => $this->operators + ], + 'truncate' => [ + 'types' => [ZBX_FUNCTION_TYPE_MATH], + 'description' => _('truncate() - Truncates a value to decimal places'), + 'params' => $this->param1SecCount + [ + 'decimals' => [ + 'C' => _('Decimal places'), + 'T' => T_ZBX_STR, + 'A' => true + ] + ], + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'varpop' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE], + 'description' => _('varpop() - Population variance'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators + ], + 'varsamp' => [ + 'types' => [ZBX_FUNCTION_TYPE_AGGREGATE], + 'description' => _('varsamp() - Sample variance'), + 'params' => $this->param1SecCount, + 'allowed_types' => $this->allowedTypesNumeric, + 'operators' => $this->operators ] ]; CArrayHelper::sort($this->functions, ['description']); - - foreach ($this->functions as $function) { - foreach ($function['operators'] as $operator) { - $this->operators[$operator] = true; - } - } } protected function checkInput() { @@ -481,7 +1049,7 @@ class CControllerPopupTriggerExpr extends CController { 'itemid' => 'db items.itemid', 'parent_discoveryid' => 'db items.itemid', 'function' => 'in '.implode(',', array_keys($this->functions)), - 'operator' => 'in '.implode(',', array_keys($this->operators)), + 'operator' => 'in '.implode(',', $this->operators), 'params' => '', 'paramtype' => 'in '.implode(',', [PARAM_TYPE_TIME, PARAM_TYPE_COUNTS]), 'value' => 'string|not_empty', @@ -517,7 +1085,7 @@ class CControllerPopupTriggerExpr extends CController { protected function doAction() { $expression_parser = new CExpressionParser(['lldmacros' => true]); - $expression_validator = new CExpressionValidator(); + $expression_validator = new CExpressionValidator(['partial' => true]); $itemid = $this->getInput('itemid', 0); $function = $this->getInput('function', 'last'); @@ -572,7 +1140,7 @@ class CControllerPopupTriggerExpr extends CController { if (array_key_exists($index, $tokens) && $tokens[$index]['type'] == CExpressionParserResult::TOKEN_TYPE_OPERATOR - && in_array($tokens[$index]['match'], ['=', '<>', '>', '<', '>=', '<='])) { + && in_array($tokens[$index]['match'], $this->operators)) { $operator = $tokens[$index]['match']; $index++; @@ -725,9 +1293,10 @@ class CControllerPopupTriggerExpr extends CController { 'params' => $params, 'paramtype' => $param_type, 'item_description' => $description, - 'item_required' => !in_array($function, getStandaloneFunctions()), + 'item_required' => !in_array($function, array_merge(getStandaloneFunctions(), getFunctionsConstants())), 'functions' => $this->functions, 'function' => $function, + 'function_type' => reset($this->functions[$function]['types']), 'operator' => $operator, 'item_key' => $item_key, 'itemValueType' => $item_value_type, @@ -748,6 +1317,7 @@ class CControllerPopupTriggerExpr extends CController { if ($data['selectedFunction'] === null) { $data['selectedFunction'] = 'last'; $data['function'] = 'last'; + $data['function_type'] = ZBX_FUNCTION_TYPE_HISTORY; } // Remove functions that not correspond to chosen item. @@ -755,9 +1325,10 @@ class CControllerPopupTriggerExpr extends CController { if ($data['itemValueType'] !== null && !array_key_exists($data['itemValueType'], $f['allowed_types'])) { unset($data['functions'][$id]); - // Take first available function from list and change to first available operator for that function. + // Take first available function from list. if ($id === $data['function']) { $data['function'] = key($data['functions']); + $data['function_type'] = reset($data['functions'][$data['function']]['types']); $data['operator'] = reset($data['functions'][$data['function']]['operators']); } } @@ -766,7 +1337,10 @@ class CControllerPopupTriggerExpr extends CController { // Create and validate trigger expression before inserting it into textarea field. if ($this->getInput('add', false)) { try { - if (in_array($function, getStandaloneFunctions())) { + if (in_array($function, getFunctionsConstants())) { + $data['expression'] = sprintf('%s()', $function); + } + elseif (in_array($function, getStandaloneFunctions())) { $data['expression'] = sprintf('%s()%s%s', $function, $operator, CExpressionParser::quoteString($data['value']) ); @@ -774,7 +1348,7 @@ class CControllerPopupTriggerExpr extends CController { elseif ($data['item_description']) { // Quote function string parameters. foreach ($data['params'] as $param_key => $param) { - if (!in_array($param_key, ['v', 'o', 'fit', 'mode', 'pattern']) + if (!in_array($param_key, ['v', 'o', 'chars', 'fit', 'mode', 'pattern', 'replace', 'string']) || !array_key_exists($param_key, $data['params']) || $data['params'][$param_key] === '') { continue; @@ -802,42 +1376,33 @@ class CControllerPopupTriggerExpr extends CController { } unset($data['params']['shift'], $data['params']['period_shift']); - $mask = ''; - if ($function === 'bitand' && array_key_exists('mask', $data['params'])) { - $mask = $data['params']['mask']; - unset($data['params']['mask']); - } - - $fn_params = rtrim(implode(',', $data['params']), ','); - - if ($function === 'abs') { - $data['expression'] = sprintf('abs(last(/%s/%s)%s)%s%s', - $item_host_data['host'], - $data['item_key'], - ($fn_params === '') ? '' : ','.$fn_params, - $operator, - CExpressionParser::quoteString($data['value']) - ); - } - elseif ($function === 'bitand') { - $data['expression'] = sprintf('bitand(last(/%s/%s%s)%s)%s%s', + // Functions where item is wrapped in last() like func(last(/host/item)). + $last_functions = [ + 'abs', 'acos', 'ascii', 'asin', 'atan', 'atan2', 'between', 'bitand', 'bitlength', 'bitlshift', + 'bitnot', 'bitor', 'bitrshift', 'bitxor', 'bytelength', 'cbrt', 'ceil', 'char', 'concat', 'cos', + 'cosh', 'cot', 'degrees', 'exp', 'expm1', 'floor', 'in', 'insert', 'left', 'log', 'log10', + 'ltrim', 'mid', 'mod', 'power', 'radians', 'repeat', 'replace', 'right', 'round', 'signum', + 'sin', 'sinh', 'sqrt', 'tan', 'trim', 'truncate' + ]; + + if (in_array($function, $last_functions)) { + $last_params = $data['params']['last']; + unset($data['params']['last']); + $fn_params = rtrim(implode(',', $data['params']), ','); + + $data['expression'] = sprintf('%s(last(/%s/%s%s)%s)%s%s', + $function, $item_host_data['host'], $data['item_key'], + ($last_params === '') ? '' : ','.$last_params, ($fn_params === '') ? '' : ','.$fn_params, - ($mask === '') ? '' : ','.$mask, - $operator, - CExpressionParser::quoteString($data['value']) - ); - } - elseif ($function === 'length') { - $data['expression'] = sprintf('length(last(/%s/%s))%s%s', - $item_host_data['host'], - $data['item_key'], $operator, CExpressionParser::quoteString($data['value']) ); } else { + $fn_params = rtrim(implode(',', $data['params']), ','); + $data['expression'] = sprintf('%s(/%s/%s%s)%s%s', $function, $item_host_data['host'], @@ -856,7 +1421,7 @@ class CControllerPopupTriggerExpr extends CController { // Parse and validate trigger expression. if ($expression_parser->parse($data['expression']) == CParser::PARSE_SUCCESS) { if (!$expression_validator->validate($expression_parser->getResult()->getTokens())) { - error($expression_validator->getError()); + error(_s('Invalid condition: %1$s.', $expression_validator->getError())); } } else { diff --git a/ui/app/controllers/CControllerPopupValueMapEdit.php b/ui/app/controllers/CControllerPopupValueMapEdit.php index 401b8b5fb93..9a2c89d2491 100644 --- a/ui/app/controllers/CControllerPopupValueMapEdit.php +++ b/ui/app/controllers/CControllerPopupValueMapEdit.php @@ -35,7 +35,7 @@ class CControllerPopupValueMapEdit extends CController { 'valuemap_names' => 'array' ]; - $ret = $this->validateInput($fields) && $this->validateValueMap(); + $ret = $this->validateInput($fields); if (!$ret) { $output = []; @@ -51,92 +51,13 @@ class CControllerPopupValueMapEdit extends CController { return $ret; } - /** - * Validate vlue map to be added. - * - * @return bool - */ - protected function validateValueMap(): bool { - if (!$this->hasInput('update')) { - return true; - } - - $name = $this->getInput('name', ''); - - if ($name === '') { - error(_s('Incorrect value for field "%1$s": %2$s.', _('Name'), _('cannot be empty'))); - return false; - } - - $valuemap_names = $this->getInput('valuemap_names', []); - - if (in_array($name, $valuemap_names)) { - error(_s('Incorrect value for field "%1$s": %2$s.', _('Name'), - _s('value %1$s already exists', '('.$name.')')) - ); - return false; - } - - $mappings = array_filter($this->getInput('mappings', []), function ($mapping) { - return ($mapping['value'] !== '' || $mapping['newvalue'] !== ''); - }); - - if (!$mappings) { - error(_s('Incorrect value for field "%1$s": %2$s.', _('Mappings'), _('cannot be empty'))); - return false; - } - - $values = []; - - foreach ($mappings as $mapping) { - if ($mapping['newvalue'] === '') { - error(_s('Incorrect value for field "%1$s": %2$s.', _('Mapped to'), _('cannot be empty'))); - return false; - } - - if (array_key_exists($mapping['value'], $values)) { - error(_s('Incorrect value for field "%1$s": %2$s.', _('Value'), - _s('value %1$s already exists', '('.$mapping['value'].')')) - ); - return false; - } - - $values[$mapping['value']] = 1; - } - - return true; - } - protected function checkPermissions() { return true; } protected function doAction() { - $this->setResponse($this->hasInput('update') ? $this->update() : $this->form()); - } - - /** - * Get response object with data to be returned as json data when added value map is valid. - * - * @return CControllerResponse - */ - protected function update(): CControllerResponse { - $data = []; - $this->getInputs($data, ['valuemapid', 'name', 'mappings', 'edit']); - order_result($data['mappings'], 'value'); - $data['mappings'] = array_values($data['mappings']); - - return (new CControllerResponseData(['main_block' => json_encode($data)]))->disableView(); - } - - /** - * Get response object with data required to render value map edit form. - * - * @return CControllerResponse - */ - protected function form(): CControllerResponse { $data = [ - 'action' => $this->getAction(), + 'action' => 'popup.valuemap.update', 'edit' => 0, 'mappings' => [], 'name' => '', @@ -146,9 +67,27 @@ class CControllerPopupValueMapEdit extends CController { $this->getInputs($data, array_keys($data)); if (!$data['mappings']) { - $data['mappings'][] = ['value' => '', 'newvalue' => '']; + $mappings = [['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '', 'newvalue' => '']]; + } + else { + $mappings = []; + $default = []; + + foreach ($data['mappings'] as $mapping) { + if ($mapping['type'] == VALUEMAP_MAPPING_TYPE_DEFAULT) { + $default = $mapping; + } + else { + $mappings[] = $mapping; + } + } + + if ($default) { + $mappings[] = $default; + } } + $data['mappings'] = $mappings; $data += [ 'title' => _('Value mapping'), 'user' => [ @@ -156,6 +95,6 @@ class CControllerPopupValueMapEdit extends CController { ] ]; - return new CControllerResponseData($data); + $this->setResponse(new CControllerResponseData($data)); } } diff --git a/ui/app/controllers/CControllerPopupValueMapUpdate.php b/ui/app/controllers/CControllerPopupValueMapUpdate.php new file mode 100644 index 00000000000..19631a8b8b9 --- /dev/null +++ b/ui/app/controllers/CControllerPopupValueMapUpdate.php @@ -0,0 +1,175 @@ +<?php +/* +** Zabbix +** Copyright (C) 2001-2020 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + + +class CControllerPopupValueMapUpdate extends CController { + + protected function checkInput() { + $fields = [ + 'edit' => 'in 1,0', + 'mappings' => 'array', + 'name' => 'string', + 'update' => 'in 1', + 'valuemapid' => 'id', + 'valuemap_names' => 'array' + ]; + + $ret = $this->validateInput($fields) && $this->validateValueMap(); + + if (!$ret) { + $output = []; + if (($messages = getMessages()) !== null) { + $output['errors'] = $messages->toString(); + } + + $this->setResponse( + (new CControllerResponseData(['main_block' => json_encode($output)]))->disableView() + ); + } + + return $ret; + } + + /** + * Validate vlue map to be added. + * + * @return bool + */ + protected function validateValueMap(): bool { + if (!$this->hasInput('update')) { + return true; + } + + $name = $this->getInput('name', ''); + + if ($name === '') { + error(_s('Incorrect value for field "%1$s": %2$s.', _('Name'), _('cannot be empty'))); + return false; + } + + $valuemap_names = $this->getInput('valuemap_names', []); + + if (in_array($name, $valuemap_names)) { + error(_s('Incorrect value for field "%1$s": %2$s.', _('Name'), + _s('value %1$s already exists', '('.$name.')')) + ); + return false; + } + + $type_uniq = array_fill_keys([VALUEMAP_MAPPING_TYPE_EQUAL, VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, + VALUEMAP_MAPPING_TYPE_LESS_EQUAL, VALUEMAP_MAPPING_TYPE_IN_RANGE, VALUEMAP_MAPPING_TYPE_REGEXP + ], [] + ); + $number_parser = new CNumberParser(); + $range_parser = new CRangesParser(['with_minus' => true, 'with_float' => true, 'with_suffix' => true]); + $mappings = []; + + foreach ($this->getInput('mappings', []) as $mapping) { + $mapping += ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '', 'newvalue' => '']; + $type = $mapping['type']; + $value = $mapping['value']; + + if ($type != VALUEMAP_MAPPING_TYPE_DEFAULT && $value === '' && $mapping['newvalue'] === '') { + continue; + } + + if ($mapping['newvalue'] === '') { + error(_s('Incorrect value for field "%1$s": %2$s.', _('Mapped to'), _('cannot be empty'))); + + return false; + } + elseif ($type == VALUEMAP_MAPPING_TYPE_REGEXP + && @preg_match('/'.str_replace('/', '\/', $value).'/', '') === false) { + error(_s('Incorrect value for field "%1$s": %2$s.', _('Value'), _('invalid regular expression'))); + + return false; + } + elseif ($type == VALUEMAP_MAPPING_TYPE_IN_RANGE && $range_parser->parse($value) != CParser::PARSE_SUCCESS) { + error(_s('Incorrect value for field "%1$s": %2$s.', _('Value'), + _('invalid range expression') + )); + + return false; + } + elseif ($type == VALUEMAP_MAPPING_TYPE_LESS_EQUAL || $type == VALUEMAP_MAPPING_TYPE_GREATER_EQUAL) { + if ($number_parser->parse($value) != CParser::PARSE_SUCCESS) { + error(_s('Incorrect value for field "%1$s": %2$s.', _('Value'), + _('a floating point value is expected') + )); + + return false; + } + + $value = (float) $number_parser->getMatch(); + $value = strval($value); + } + + if ($type != VALUEMAP_MAPPING_TYPE_DEFAULT && array_key_exists($value, $type_uniq[$type])) { + error(_s('Incorrect value for field "%1$s": %2$s.', _('Value'), + _s('value %1$s already exists', '('.$value.')')) + ); + + return false; + } + + $type_uniq[$type][$value] = true; + $mappings[] = $mapping; + } + + if (!$mappings) { + error(_s('Incorrect value for field "%1$s": %2$s.', _('Mappings'), _('cannot be empty'))); + return false; + } + + return true; + } + + protected function checkPermissions() { + return true; + } + + protected function doAction() { + $data = []; + $mappings = []; + $default = []; + $this->getInputs($data, ['valuemapid', 'name', 'mappings', 'edit']); + + foreach ($data['mappings'] as $mapping) { + if ($mapping['type'] != VALUEMAP_MAPPING_TYPE_DEFAULT && + $mapping['value'] === '' && $mapping['newvalue'] === '') { + continue; + } + elseif ($mapping['type'] == VALUEMAP_MAPPING_TYPE_DEFAULT) { + $default = $mapping; + + continue; + } + + $mappings[] = $mapping; + } + + if ($default) { + $mappings[] = $default; + } + + $data['mappings'] = $mappings; + $this->setResponse((new CControllerResponseData(['main_block' => json_encode($data)]))); + } +} diff --git a/ui/app/partials/js/configuration.valuemap.js.php b/ui/app/partials/js/configuration.valuemap.js.php index 4f4a1062c22..b1719df377f 100644 --- a/ui/app/partials/js/configuration.valuemap.js.php +++ b/ui/app/partials/js/configuration.valuemap.js.php @@ -105,25 +105,64 @@ var AddValueMap = class { createMappingCell() { let i = 0; - const cell = document.createElement('td'); - const hellip = document.createElement('span'); - hellip.innerHTML = '…'; + let cell = document.createElement('td'); + let hellip = document.createElement('span'); + let arrow_cell = document.createElement('div'); + let mappings_table = document.createElement('div'); + let value_cell, newvalue_cell; + hellip.innerHTML = '…'; + arrow_cell.textContent = '⇒'; + mappings_table.classList.add('mappings-table'); cell.classList.add('wordwrap'); - for (let value of this.data.mappings) { - if (i <= this.MAX_MAPPINGS) { - cell.append((i < this.MAX_MAPPINGS) - ? `${value.value} ⇒ ${value.newvalue}` - : hellip, - document.createElement('br') - ); - } - cell.appendChild(this.createHiddenInput(`[mappings][${i}][value]`, value.value)); - cell.appendChild(this.createHiddenInput(`[mappings][${i}][newvalue]`, value.newvalue)); + for (let mapping of this.data.mappings) { + mapping = {value: '', ...mapping}; + + cell.appendChild(this.createHiddenInput(`[mappings][${i}][type]`, mapping.type)); + cell.appendChild(this.createHiddenInput(`[mappings][${i}][value]`, mapping.value)); + cell.appendChild(this.createHiddenInput(`[mappings][${i}][newvalue]`, mapping.newvalue)); i++; } + for (let mapping of this.data.mappings.slice(0, this.MAX_MAPPINGS)) { + value_cell = document.createElement('div'); + newvalue_cell = document.createElement('div'); + newvalue_cell.textContent = mapping.newvalue; + + switch (parseInt(mapping.type, 10)) { + case <?= VALUEMAP_MAPPING_TYPE_EQUAL ?>: + value_cell.textContent = `=${mapping.value}`; + break; + + case <?= VALUEMAP_MAPPING_TYPE_GREATER_EQUAL ?>: + value_cell.textContent = `>=${mapping.value}`; + break; + + case <?= VALUEMAP_MAPPING_TYPE_LESS_EQUAL ?>: + value_cell.textContent = `<=${mapping.value}`; + break; + + case <?= VALUEMAP_MAPPING_TYPE_DEFAULT ?>: + value_cell = document.createElement('em'); + value_cell.textContent = <?= json_encode(_('default')) ?>; + break; + + default: + value_cell.textContent = mapping.value; + } + + mappings_table.append(value_cell); + mappings_table.append(arrow_cell.cloneNode(true)); + mappings_table.append(newvalue_cell); + } + + cell.append(mappings_table); + + if (this.data.mappings.length > this.MAX_MAPPINGS) { + cell.append(hellip); + } + return cell; } diff --git a/ui/app/views/js/popup.triggerexpr.js.php b/ui/app/views/js/popup.triggerexpr.js.php index c01cab3d6af..ffb6410b8ad 100644 --- a/ui/app/views/js/popup.triggerexpr.js.php +++ b/ui/app/views/js/popup.triggerexpr.js.php @@ -40,7 +40,12 @@ $(() => { } }; - $('#function').on('change', (e) => { - reloadPopup($(e.target).closest('form').get(0), 'popup.triggerexpr'); + $('#function-select').on('change', (e) => { + var form = e.target.closest('form'), + function_name_parts = form.elements.function_select.value.split('_'); + + form.elements.function.value = function_name_parts[1]; + + reloadPopup(form, 'popup.triggerexpr'); }); }); diff --git a/ui/app/views/js/popup.valuemap.edit.js.php b/ui/app/views/js/popup.valuemap.edit.js.php index 46047a57289..7fa3950cb14 100644 --- a/ui/app/views/js/popup.valuemap.edit.js.php +++ b/ui/app/views/js/popup.valuemap.edit.js.php @@ -24,7 +24,54 @@ */ ?> $(() => { - $('#mappings_table').dynamicRows({template: '#mapping-row-tmpl', rows: <?= json_encode($data['mappings']) ?>}); + let VALUEMAP_MAPPING_TYPE_DEFAULT = <?= VALUEMAP_MAPPING_TYPE_DEFAULT ?>; + let type_placeholder = <?= json_encode([ + VALUEMAP_MAPPING_TYPE_EQUAL => _('value'), + VALUEMAP_MAPPING_TYPE_GREATER_EQUAL => _('value'), + VALUEMAP_MAPPING_TYPE_LESS_EQUAL => _('value'), + VALUEMAP_MAPPING_TYPE_IN_RANGE => _('value'), + VALUEMAP_MAPPING_TYPE_REGEXP => _('regexp') + ]) ?>; + let table = document.querySelector('#mappings_table'); + let observer = new MutationObserver(mutationHandler); + + // Observe changes for form fields: type, value. + observer.observe(table, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['value'] + }); + updateOnTypeChange(); + + function updateOnTypeChange() { + let default_select = table.querySelector(`z-select[value="${VALUEMAP_MAPPING_TYPE_DEFAULT}"]`); + + table.querySelectorAll('tr').forEach((row) => { + let zselect = row.querySelector('z-select[name$="[type]"]'); + let input = row.querySelector('input[name$="[value]"]'); + + if (zselect) { + zselect.getOptionByValue(VALUEMAP_MAPPING_TYPE_DEFAULT).disabled = (default_select + && zselect !== default_select + ); + input.classList.toggle('visibility-hidden', (zselect === default_select)); + input.disabled = (zselect === default_select); + input.setAttribute('placeholder', type_placeholder[zselect.value]||''); + } + }); + } + + function mutationHandler(mutation_records, observer) { + let update = mutation_records.filter((mutation) => { + return (mutation.target.tagName === 'INPUT' && mutation.target.getAttribute('name').substr(-6) === '[type]') + || (mutation.target.tagName === 'TBODY' && mutation.removedNodes.length > 0); + }); + + if (update.length) { + updateOnTypeChange(); + } + } }); function submitValueMap(overlay) { diff --git a/ui/app/views/popup.generic.php b/ui/app/views/popup.generic.php index 7d308691736..a0ef5b316ef 100644 --- a/ui/app/views/popup.generic.php +++ b/ui/app/views/popup.generic.php @@ -594,18 +594,39 @@ switch ($data['popup_type']) { ->onClick(sprintf($inline_js, $valuemap['id'], $js_action_onclick)); } - $span = []; + $mappings_table = []; foreach (array_slice($valuemap['mappings'], 0, 3) as $mapping) { - $span[] = $mapping['value'].' ⇒ '.$mapping['newvalue']; - $span[] = BR(); - } + switch ($mapping['type']) { + case VALUEMAP_MAPPING_TYPE_EQUAL: + $value = '='.$mapping['value']; + break; + + case VALUEMAP_MAPPING_TYPE_GREATER_EQUAL: + $value = '>='.$mapping['value']; + break; + + case VALUEMAP_MAPPING_TYPE_LESS_EQUAL: + $value = '<='.$mapping['value']; + break; + + case VALUEMAP_MAPPING_TYPE_DEFAULT: + $value = new CTag('em', true, _('default')); + break; + + default: + $value = $mapping['value']; + } - if (count($valuemap['mappings']) > 3) { - $span[] = '…'; + $mappings_table[] = new CDiv($value); + $mappings_table[] = new CDiv('⇒'); + $mappings_table[] = new CDiv($mapping['newvalue']); } - $table->addRow([$check_box, $name, $span]); + $hellip = (count($valuemap['mappings']) > 3) ? '…' : null; + $table->addRow([$check_box, $name, [ + (new CDiv($mappings_table))->addClass(ZBX_STYLE_VALUEMAP_MAPPINGS_TABLE), $hellip + ]]); } break; } diff --git a/ui/app/views/popup.triggerexpr.php b/ui/app/views/popup.triggerexpr.php index c64ab723e08..84ce023c69b 100644 --- a/ui/app/views/popup.triggerexpr.php +++ b/ui/app/views/popup.triggerexpr.php @@ -32,6 +32,7 @@ $expression_form = (new CForm()) ->addVar('dstfld1', $data['dstfld1']) ->addItem((new CVar('hostid', $data['hostid']))->removeId()) ->addVar('groupid', $data['groupid']) + ->addVar('function', $data['function']) ->addItem((new CInput('submit', 'submit')) ->addStyle('display: none;') ->removeId() @@ -98,13 +99,34 @@ if ($data['item_required']) { $expression_form_list->addRow((new CLabel(_('Item'), 'item_description'))->setAsteriskMark(), $item); } -$function_select = (new CSelect('function')) +$function_select = (new CSelect('function_select')) ->setFocusableElementId('label-function') - ->setId('function') - ->setValue($data['function']); + ->setId('function-select') + ->setValue($data['function_type'].'_'.$data['function']); + +$function_types = [ + ZBX_FUNCTION_TYPE_AGGREGATE => _('Aggregate functions'), + ZBX_FUNCTION_TYPE_BITWISE => _('Bitwise functions'), + ZBX_FUNCTION_TYPE_DATE_TIME => _('Date and time functions'), + ZBX_FUNCTION_TYPE_HISTORY => _('History functions'), + ZBX_FUNCTION_TYPE_MATH => _('Mathematical functions'), + ZBX_FUNCTION_TYPE_OPERATOR => _('Operator functions'), + ZBX_FUNCTION_TYPE_PREDICTION => _('Prediction functions'), + ZBX_FUNCTION_TYPE_STRING => _('String functions') +]; + +$functions_by_group = []; +foreach ($data['functions'] as $id => $function) { + foreach ($function['types'] as $type) { + $functions_by_group[$function_types[$type]][$type.'_'.$id] = $function['description']; + } +} +ksort($functions_by_group); -foreach ($data['functions'] as $id => $f) { - $function_select->addOption(new CSelectOption($id, $f['description'])); +foreach ($functions_by_group as $group_name => $functions) { + $function_select->addOptionGroup( + (new CSelectOptionGroup($group_name))->addOptions(CSelect::createOptionsFromArray($functions)) + ); } $expression_form_list->addRow(new CLabel(_('Function'), $function_select->getFocusableElementId()), $function_select); @@ -112,6 +134,14 @@ $expression_form_list->addRow(new CLabel(_('Function'), $function_select->getFoc if (array_key_exists('params', $data['functions'][$data['selectedFunction']])) { $paramid = 0; + // Functions with optional #num and time shift parameters. + $count_functions = [ + 'acos', 'ascii', 'asin', 'atan', 'atan2', 'between', 'bitand', 'bitlength', 'bitlshift', 'bitnot', 'bitor', + 'bitrshift', 'bitxor', 'bytelength', 'cbrt', 'ceil', 'char', 'concat', 'cos', 'cosh', 'cot', 'degrees', 'exp', + 'expm1', 'floor', 'in', 'insert', 'last', 'left', 'log', 'log10', 'ltrim', 'mid', 'mod', 'power', 'radians', + 'repeat', 'replace', 'right', 'round', 'rtrim', 'signum', 'sin', 'sinh', 'sqrt', 'tan', 'trim', 'truncate' + ]; + foreach ($data['functions'][$data['selectedFunction']]['params'] as $param_name => $param_function) { if (array_key_exists($param_name, $data['params'])) { $param_value = $data['params'][$param_name]; @@ -127,7 +157,7 @@ if (array_key_exists('params', $data['functions'][$data['selectedFunction']])) { if (in_array($param_name, ['last'])) { if (array_key_exists('M', $param_function)) { - if (in_array($data['selectedFunction'], ['last', 'bitand', 'strlen'])) { + if (in_array($data['selectedFunction'], $count_functions)) { $param_type_element = $param_function['M'][PARAM_TYPE_COUNTS]; $label = $param_function['C']; $expression_form->addItem((new CVar('paramtype', PARAM_TYPE_COUNTS))->removeId()); @@ -181,20 +211,22 @@ else { $expression_form->addVar('paramtype', PARAM_TYPE_TIME); } -$expression_form_list->addRow( - (new CLabel(_('Result'), 'value'))->setAsteriskMark(), [ - (new CSelect('operator')) - ->setValue($data['operator']) - ->setFocusableElementId('value') - ->addOptions(CSelect::createOptionsFromArray(array_combine($data['functions'][$data['function']]['operators'], - $data['functions'][$data['function']]['operators'] - ))), - ' ', - (new CTextBox('value', $data['value'])) - ->setAriaRequired() - ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH) - ] -); +if (array_key_exists('operators', $data['functions'][$data['selectedFunction']])) { + $expression_form_list->addRow( + (new CLabel(_('Result'), 'value'))->setAsteriskMark(), [ + (new CSelect('operator')) + ->setValue($data['operator']) + ->setFocusableElementId('value') + ->addOptions(CSelect::createOptionsFromArray(array_combine($data['functions'][$data['function']]['operators'], + $data['functions'][$data['function']]['operators'] + ))), + ' ', + (new CTextBox('value', $data['value'])) + ->setAriaRequired() + ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH) + ] + ); +} $expression_form->addItem($expression_form_list); diff --git a/ui/app/views/popup.valuemap.edit.php b/ui/app/views/popup.valuemap.edit.php index cecf9375494..cf658b345b7 100644 --- a/ui/app/views/popup.valuemap.edit.php +++ b/ui/app/views/popup.valuemap.edit.php @@ -23,12 +23,12 @@ * @var CView $this */ $form = (new CForm()) - ->cleanItems() ->setId('valuemap-edit-form') ->setName('valuemap-edit-form') ->addVar('action', $data['action']) ->addVar('update', 1) ->addVar('source-name', $data['name']) + ->addItem(new CJsScript($this->readJsFile('../../../include/views/js/editabletable.js.php'))) ->addItem((new CInput('submit', 'submit')) ->addStyle('display: none;') ->removeId() @@ -48,19 +48,55 @@ foreach (array_values($data['valuemap_names']) as $index => $name) { $form_grid = (new CFormGrid())->addClass(CFormGrid::ZBX_STYLE_FORM_GRID_1_1); -$table = (new CTable()) - ->setId('mappings_table') - ->addClass(ZBX_STYLE_TABLE_FORMS) - ->setHeader([_('Value'), '', _('Mapped to'), _('Action')]) - ->addStyle('width: 100%;'); +$header_row = ['', _('Type'), _('Value'), '', _('Mapped to'), _('Action'), '']; +$mappings = (new CDiv([ + (new CTable()) + ->setId('mappings_table') + ->addClass(ZBX_STYLE_TABLE_FORMS) + ->setHeader($header_row) + ->addRow((new CRow)->setAttribute('data-insert-point', 'append')) + ->setFooter(new CRow( + (new CCol( + (new CButton(null, _('Add'))) + ->addClass(ZBX_STYLE_BTN_LINK) + ->setAttribute('data-row-action', 'add_row') + ))->setColSpan(count($header_row)) + )) +]))->setAttribute('data-sortable-pairs-table', '1'); -$table->addRow([ - (new CCol( - (new CButton(null, _('Add'))) +// Value map mapping template. +$mappings->addItem( + (new CTag('script', true)) + ->setAttribute('type', 'text/x-jquery-tmpl') + ->addItem((new CRow([ + (new CCol((new CDiv) + ->addClass(ZBX_STYLE_DRAG_ICON))) + ->addClass(ZBX_STYLE_TD_DRAG_ICON), + (new CSelect('mappings[#{index}][type]')) + ->setValue('#{type}') + ->addOptions(CSelect::createOptionsFromArray([ + VALUEMAP_MAPPING_TYPE_EQUAL => _('equals'), + VALUEMAP_MAPPING_TYPE_GREATER_EQUAL => _('is greater than or equals'), + VALUEMAP_MAPPING_TYPE_LESS_EQUAL => _('is less than or equals'), + VALUEMAP_MAPPING_TYPE_IN_RANGE => _('in range'), + VALUEMAP_MAPPING_TYPE_REGEXP => _('regexp'), + VALUEMAP_MAPPING_TYPE_DEFAULT => _('default') + ])), + (new CTextBox('mappings[#{index}][value]', '#{value}', false, DB::getFieldLength('valuemap_mapping', 'value'))) + ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH), + '⇒', + (new CTextBox('mappings[#{index}][newvalue]', '#{newvalue}', false, DB::getFieldLength('valuemap_mapping', 'newvalue'))) + ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH) + ->setAriaRequired(), + (new CButton('mappings[#{index}][remove]', _('Remove'))) ->addClass(ZBX_STYLE_BTN_LINK) - ->addClass('element-table-add') - ))->setColSpan(4) - ]); + ->setAttribute('data-row-action', 'remove_row') + ]))) +); + +$mappings_data = (new CTag('script', true))->setAttribute('type', 'text/json'); +$mappings_data->items = [json_encode($data['mappings'])]; +$mappings->addItem($mappings_data); $form_grid ->addItem([ @@ -75,7 +111,7 @@ $form_grid ->addItem([ (new CLabel(_('Mappings'), 'mappings'))->setAsteriskMark(), (new CFormField( - (new CDiv($table)) + (new CDiv($mappings)) ->addStyle('width: 100%;') ->addClass('table-forms-separator') ))->addClass(CFormField::ZBX_STYLE_FORM_FIELD_FLUID) @@ -83,24 +119,9 @@ $form_grid $form->addItem($form_grid); -// Value map mapping template. -$form->addItem((new CScriptTemplate('mapping-row-tmpl'))->addItem( - (new CRow([ - (new CTextBox('mappings[#{rowNum}][value]', '#{value}', false, DB::getFieldLength('valuemap_mapping', 'value'))) - ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH), - '⇒', - (new CTextBox('mappings[#{rowNum}][newvalue]', '#{newvalue}', false, DB::getFieldLength('valuemap_mapping', 'newvalue'))) - ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH) - ->setAriaRequired(), - (new CButton('mappings[#{rowNum}][remove]', _('Remove'))) - ->addClass(ZBX_STYLE_BTN_LINK) - ->addClass('element-table-remove') - ]))->addClass('form_row') -)); - $output = [ 'header' => $data['title'], - 'script_inline' => $this->readJsFile('popup.valuemap.edit.js.php', ['mappings' => $data['mappings']]), + 'script_inline' => $this->readJsFile('popup.valuemap.edit.js.php', []), 'body' => $form->toString(), 'buttons' => [ [ diff --git a/ui/assets/styles/blue-theme.css b/ui/assets/styles/blue-theme.css index 70bdc73a7a3..454f246990e 100644 --- a/ui/assets/styles/blue-theme.css +++ b/ui/assets/styles/blue-theme.css @@ -2896,6 +2896,15 @@ form.is-loading { padding-top: 5px; } .table-forms tfoot button, .table-forms .tfoot-buttons button { margin: 0 10px 0 0; } + .table-forms td { + position: relative; } + .table-forms td.td-drag-icon { + padding: 0 11px 0 0; + vertical-align: middle; } + .table-forms td .drag-icon { + position: absolute; + top: 5px; + margin-right: 5px; } .table-forms .table-forms-td-left { display: table-cell; padding: 5px 0; @@ -2919,12 +2928,7 @@ form.is-loading { padding: 5px 5px 5px 0; position: relative; } .table-forms .table-forms-td-right td.td-drag-icon { - padding: 0 11px 0 0; - vertical-align: middle; } - .table-forms .table-forms-td-right td .drag-icon { - position: absolute; - top: 5px; - margin-right: 5px; } + padding-right: 11px; } .table-forms .table-forms-td-right td.center { text-align: center; vertical-align: middle; } @@ -5350,6 +5354,9 @@ svg { .display-none, .table-forms li.display-none { display: none; } +.visibility-hidden { + visibility: hidden; } + .checkbox-list li { overflow: hidden; text-overflow: ellipsis; @@ -6358,6 +6365,15 @@ z-select.z-select-host-interface li[disabled] .description:not(:empty), .valuemap-list-table tbody tr:first-child td { border-top: 1px solid #ebeef0; } +.mappings-table { + display: grid; + grid-template-columns: auto auto minmax(auto, 100%); } + .mappings-table > div { + text-align: left; } + .mappings-table > div:nth-child(3n + 2) { + text-align: center; + padding: 0 10px; } + .valuemap-checkbox { margin-top: 10px; } diff --git a/ui/assets/styles/dark-theme.css b/ui/assets/styles/dark-theme.css index 2ac7097fa9e..e8660aafd2d 100644 --- a/ui/assets/styles/dark-theme.css +++ b/ui/assets/styles/dark-theme.css @@ -2907,6 +2907,15 @@ form.is-loading { padding-top: 5px; } .table-forms tfoot button, .table-forms .tfoot-buttons button { margin: 0 10px 0 0; } + .table-forms td { + position: relative; } + .table-forms td.td-drag-icon { + padding: 0 11px 0 0; + vertical-align: middle; } + .table-forms td .drag-icon { + position: absolute; + top: 5px; + margin-right: 5px; } .table-forms .table-forms-td-left { display: table-cell; padding: 5px 0; @@ -2930,12 +2939,7 @@ form.is-loading { padding: 5px 5px 5px 0; position: relative; } .table-forms .table-forms-td-right td.td-drag-icon { - padding: 0 11px 0 0; - vertical-align: middle; } - .table-forms .table-forms-td-right td .drag-icon { - position: absolute; - top: 5px; - margin-right: 5px; } + padding-right: 11px; } .table-forms .table-forms-td-right td.center { text-align: center; vertical-align: middle; } @@ -5361,6 +5365,9 @@ svg { .display-none, .table-forms li.display-none { display: none; } +.visibility-hidden { + visibility: hidden; } + .checkbox-list li { overflow: hidden; text-overflow: ellipsis; @@ -6369,6 +6376,15 @@ z-select.z-select-host-interface li[disabled] .description:not(:empty), .valuemap-list-table tbody tr:first-child td { border-top: 1px solid #383838; } +.mappings-table { + display: grid; + grid-template-columns: auto auto minmax(auto, 100%); } + .mappings-table > div { + text-align: left; } + .mappings-table > div:nth-child(3n + 2) { + text-align: center; + padding: 0 10px; } + .valuemap-checkbox { margin-top: 10px; } diff --git a/ui/assets/styles/hc-dark.css b/ui/assets/styles/hc-dark.css index e60dacd8873..ddc496a50dd 100644 --- a/ui/assets/styles/hc-dark.css +++ b/ui/assets/styles/hc-dark.css @@ -2860,6 +2860,15 @@ form.is-loading { padding-top: 5px; } .table-forms tfoot button, .table-forms .tfoot-buttons button { margin: 0 10px 0 0; } + .table-forms td { + position: relative; } + .table-forms td.td-drag-icon { + padding: 0 11px 0 0; + vertical-align: middle; } + .table-forms td .drag-icon { + position: absolute; + top: 5px; + margin-right: 5px; } .table-forms .table-forms-td-left { display: table-cell; padding: 5px 0; @@ -2883,12 +2892,7 @@ form.is-loading { padding: 5px 5px 5px 0; position: relative; } .table-forms .table-forms-td-right td.td-drag-icon { - padding: 0 11px 0 0; - vertical-align: middle; } - .table-forms .table-forms-td-right td .drag-icon { - position: absolute; - top: 5px; - margin-right: 5px; } + padding-right: 11px; } .table-forms .table-forms-td-right td.center { text-align: center; vertical-align: middle; } @@ -5305,6 +5309,9 @@ svg { .display-none, .table-forms li.display-none { display: none; } +.visibility-hidden { + visibility: hidden; } + .checkbox-list li { overflow: hidden; text-overflow: ellipsis; @@ -6313,6 +6320,15 @@ z-select.z-select-host-interface li[disabled] .description:not(:empty), .valuemap-list-table tbody tr:first-child td { border-top: 1px solid #333333; } +.mappings-table { + display: grid; + grid-template-columns: auto auto minmax(auto, 100%); } + .mappings-table > div { + text-align: left; } + .mappings-table > div:nth-child(3n + 2) { + text-align: center; + padding: 0 10px; } + .valuemap-checkbox { margin-top: 10px; } diff --git a/ui/assets/styles/hc-light.css b/ui/assets/styles/hc-light.css index 9675b39eefd..d54eefc5b70 100644 --- a/ui/assets/styles/hc-light.css +++ b/ui/assets/styles/hc-light.css @@ -2860,6 +2860,15 @@ form.is-loading { padding-top: 5px; } .table-forms tfoot button, .table-forms .tfoot-buttons button { margin: 0 10px 0 0; } + .table-forms td { + position: relative; } + .table-forms td.td-drag-icon { + padding: 0 11px 0 0; + vertical-align: middle; } + .table-forms td .drag-icon { + position: absolute; + top: 5px; + margin-right: 5px; } .table-forms .table-forms-td-left { display: table-cell; padding: 5px 0; @@ -2883,12 +2892,7 @@ form.is-loading { padding: 5px 5px 5px 0; position: relative; } .table-forms .table-forms-td-right td.td-drag-icon { - padding: 0 11px 0 0; - vertical-align: middle; } - .table-forms .table-forms-td-right td .drag-icon { - position: absolute; - top: 5px; - margin-right: 5px; } + padding-right: 11px; } .table-forms .table-forms-td-right td.center { text-align: center; vertical-align: middle; } @@ -5305,6 +5309,9 @@ svg { .display-none, .table-forms li.display-none { display: none; } +.visibility-hidden { + visibility: hidden; } + .checkbox-list li { overflow: hidden; text-overflow: ellipsis; @@ -6313,6 +6320,15 @@ z-select.z-select-host-interface li[disabled] .description:not(:empty), .valuemap-list-table tbody tr:first-child td { border-top: 1px solid #888888; } +.mappings-table { + display: grid; + grid-template-columns: auto auto minmax(auto, 100%); } + .mappings-table > div { + text-align: left; } + .mappings-table > div:nth-child(3n + 2) { + text-align: center; + padding: 0 10px; } + .valuemap-checkbox { margin-top: 10px; } diff --git a/ui/hosts.php b/ui/hosts.php index 049f8a835a0..eef5649dd55 100644 --- a/ui/hosts.php +++ b/ui/hosts.php @@ -842,13 +842,6 @@ if (hasRequest('form')) { // Valuemap order_result($dbHost['valuemaps'], 'name'); - - foreach ($dbHost['valuemaps'] as &$valuemap) { - order_result($valuemap['mappings'], 'value'); - $valuemap['mappings'] = array_values($valuemap['mappings']); - } - unset($valuemap); - $data['valuemaps'] = array_values($dbHost['valuemaps']); $groups = zbx_objectValues($dbHost['groups'], 'groupid'); diff --git a/ui/include/classes/api/services/CHostGeneral.php b/ui/include/classes/api/services/CHostGeneral.php index c6d52aada11..3f78404ab10 100644 --- a/ui/include/classes/api/services/CHostGeneral.php +++ b/ui/include/classes/api/services/CHostGeneral.php @@ -831,13 +831,15 @@ abstract class CHostGeneral extends CHostBase { if ($this->outputIsRequested('mappings', $options['selectValueMaps']) && $valuemaps) { $params = [ - 'output' => ['valuemapid', 'value', 'newvalue'], - 'filter' => ['valuemapid' => array_keys($valuemaps)] + 'output' => ['valuemapid', 'type', 'value', 'newvalue'], + 'filter' => ['valuemapid' => array_keys($valuemaps)], + 'sortfield' => ['sortorder'] ]; $query = DBselect(DB::makeSql('valuemap_mapping', $params)); while ($mapping = DBfetch($query)) { $valuemaps[$mapping['valuemapid']]['mappings'][] = [ + 'type' => $mapping['type'], 'value' => $mapping['value'], 'newvalue' => $mapping['newvalue'] ]; diff --git a/ui/include/classes/api/services/CItemGeneral.php b/ui/include/classes/api/services/CItemGeneral.php index a6415fab9c1..f3966f23417 100644 --- a/ui/include/classes/api/services/CItemGeneral.php +++ b/ui/include/classes/api/services/CItemGeneral.php @@ -2073,13 +2073,15 @@ abstract class CItemGeneral extends CApiService { if ($this->outputIsRequested('mappings', $options['selectValueMap']) && $valuemaps) { $params = [ - 'output' => ['valuemapid', 'value', 'newvalue'], - 'filter' => ['valuemapid' => array_keys($valuemaps)] + 'output' => ['valuemapid', 'type', 'value', 'newvalue'], + 'filter' => ['valuemapid' => array_keys($valuemaps)], + 'sortfield' => ['sortorder'] ]; $query = DBselect(DB::makeSql('valuemap_mapping', $params)); while ($mapping = DBfetch($query)) { $valuemaps[$mapping['valuemapid']]['mappings'][] = [ + 'type' => $mapping['type'], 'value' => $mapping['value'], 'newvalue' => $mapping['newvalue'] ]; diff --git a/ui/include/classes/api/services/CValueMap.php b/ui/include/classes/api/services/CValueMap.php index 3666d5881aa..891d85a86d6 100644 --- a/ui/include/classes/api/services/CValueMap.php +++ b/ui/include/classes/api/services/CValueMap.php @@ -61,7 +61,7 @@ class CValueMap extends CApiService { 'searchWildcardsEnabled' => ['type' => API_BOOLEAN, 'default' => false], // output 'output' => ['type' => API_OUTPUT, 'in' => implode(',', ['valuemapid', 'uuid', 'name', 'hostid']), 'default' => API_OUTPUT_EXTEND], - 'selectMappings' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', ['value', 'newvalue']), 'default' => null], + 'selectMappings' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', ['type', 'value', 'newvalue']), 'default' => null], 'countOutput' => ['type' => API_FLAG, 'default' => false], // sort and limit 'sortfield' => ['type' => API_STRINGS_UTF8, 'flags' => API_NORMALIZE, 'in' => implode(',', $this->sortColumns), 'uniq' => true, 'default' => []], @@ -139,12 +139,15 @@ class CValueMap extends CApiService { foreach ($valuemaps as $index => &$valuemap) { $valuemap['valuemapid'] = $valuemapids[$index]; + $sortorder = 0; foreach ($valuemap['mappings'] as $mapping) { $mappings[] = [ + 'type' => array_key_exists('type', $mapping) ? $mapping['type'] : VALUEMAP_MAPPING_TYPE_EQUAL, 'valuemapid' => $valuemap['valuemapid'], - 'value' => $mapping['value'], - 'newvalue' => $mapping['newvalue'] + 'value' => array_key_exists('value', $mapping) ? $mapping['value'] : '', + 'newvalue' => $mapping['newvalue'], + 'sortorder' => $sortorder++ ]; } } @@ -166,7 +169,7 @@ class CValueMap extends CApiService { $this->validateUpdate($valuemaps, $db_valuemaps); $upd_valuemaps = []; - $mappings = []; + $valuemaps_mappings = []; foreach ($valuemaps as $valuemap) { $valuemapid = $valuemap['valuemapid']; @@ -181,9 +184,17 @@ class CValueMap extends CApiService { } if (array_key_exists('mappings', $valuemap)) { - $mappings[$valuemapid] = []; + $valuemaps_mappings[$valuemapid] = []; + $sortorder = 0; + foreach ($valuemap['mappings'] as $mapping) { - $mappings[$valuemapid][$mapping['value']] = $mapping['newvalue']; + $mapping += ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '']; + $valuemaps_mappings[$valuemapid][] = [ + 'type' => $mapping['type'], + 'value' => $mapping['value'], + 'newvalue' => $mapping['newvalue'], + 'sortorder' => $sortorder++ + ]; } } } @@ -192,37 +203,57 @@ class CValueMap extends CApiService { DB::update('valuemap', $upd_valuemaps); } - if ($mappings) { + if ($valuemaps_mappings) { $db_mappings = DB::select('valuemap_mapping', [ - 'output' => ['valuemap_mappingid', 'valuemapid', 'value', 'newvalue'], - 'filter' => ['valuemapid' => array_keys($mappings)] + 'output' => ['valuemap_mappingid', 'valuemapid', 'type', 'value', 'newvalue', 'sortorder'], + 'filter' => ['valuemapid' => array_keys($valuemaps_mappings)] ]); + CArrayHelper::sort($db_mappings, [['field' => 'sortorder', 'order' => ZBX_SORT_UP]]); $ins_mapings = []; $upd_mapings = []; $del_mapingids = []; + $valuemapid_db_mappings = array_fill_keys(array_keys($valuemaps_mappings), []); foreach ($db_mappings as $db_mapping) { - $mapping = &$mappings[$db_mapping['valuemapid']]; + $valuemapid_db_mappings[$db_mapping['valuemapid']][] = $db_mapping; + } + + foreach ($valuemaps_mappings as $valuemapid => $mappings) { + $db_mappings = &$valuemapid_db_mappings[$valuemapid]; + + foreach ($mappings as $mapping) { + $exists = false; + + foreach ($db_mappings as $i => $db_mapping) { + if ($db_mapping['type'] == $mapping['type'] && $db_mapping['value'] == $mapping['value']) { + $exists = true; + break; + } + } + + if (!$exists) { + $ins_mapings[] = ['valuemapid' => $valuemapid] + $mapping; + continue; + } + + $update_fields = array_diff_assoc($mapping, $db_mapping); - if (array_key_exists($db_mapping['value'], $mapping)) { - if ($mapping[$db_mapping['value']] !== $db_mapping['newvalue']) { + if ($update_fields) { $upd_mapings[] = [ - 'values' => ['newvalue' => $mapping[$db_mapping['value']]], + 'values' => $update_fields, 'where' => ['valuemap_mappingid' => $db_mapping['valuemap_mappingid']] ]; } - unset($mapping[$db_mapping['value']]); - } - else { - $del_mapingids[] = $db_mapping['valuemap_mappingid']; + + unset($db_mappings[$i]); } } - unset($mapping); + unset($db_mappings); - foreach ($mappings as $valuemapid => $mapping) { - foreach ($mapping as $value => $newvalue) { - $ins_mapings[] = ['valuemapid' => $valuemapid, 'value' => $value, 'newvalue' => $newvalue]; + foreach ($valuemapid_db_mappings as $db_mappings) { + if ($db_mappings) { + $del_mapingids = array_merge($del_mapingids, array_column($db_mappings, 'valuemap_mappingid')); } } @@ -306,7 +337,6 @@ class CValueMap extends CApiService { } } - /** * @param array $valuemaps * @@ -317,8 +347,36 @@ class CValueMap extends CApiService { 'hostid' => ['type' => API_ID, 'flags' => API_REQUIRED | API_NOT_EMPTY], 'uuid' => ['type' => API_UUID], 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('valuemap', 'name')], - 'mappings' => ['type' => API_OBJECTS, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'uniq' => [['value']], 'fields' => [ - 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('valuemap_mapping', 'value')], + 'mappings' => ['type' => API_OBJECTS, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'fields' => [ + 'type' => ['type' => API_INT32, 'default' => VALUEMAP_MAPPING_TYPE_EQUAL, 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_EQUAL, VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, VALUEMAP_MAPPING_TYPE_LESS_EQUAL, VALUEMAP_MAPPING_TYPE_IN_RANGE, VALUEMAP_MAPPING_TYPE_REGEXP, VALUEMAP_MAPPING_TYPE_DEFAULT])], + 'value' => ['type' => API_MULTIPLE, 'rules' => [ + [ + 'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_EQUAL])], + 'type' => API_STRING_UTF8, + 'length' => DB::getFieldLength('valuemap_mapping', 'value') + ], + [ + 'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, VALUEMAP_MAPPING_TYPE_LESS_EQUAL])], + 'type' => API_FLOAT, + 'length' => DB::getFieldLength('valuemap_mapping', 'value') + ], + [ + 'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_IN_RANGE])], + 'type' => API_NUMERIC_RANGES, + 'flags' => API_NOT_EMPTY, + 'length' => DB::getFieldLength('valuemap_mapping', 'value') + ], + [ + 'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_REGEXP])], + 'type' => API_REGEX, + 'flags' => API_NOT_EMPTY, + 'length' => DB::getFieldLength('valuemap_mapping', 'value') + ], + [ + 'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_DEFAULT])], + 'type' => API_STRING_UTF8 + ] + ]], 'newvalue' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('valuemap_mapping', 'newvalue')] ]] ]]; @@ -326,7 +384,9 @@ class CValueMap extends CApiService { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } + $this->validateValuemapMappings($valuemaps); $hostids = []; + foreach ($valuemaps as $valuemap) { $hostids[$valuemap['hostid']] = true; } @@ -403,8 +463,36 @@ class CValueMap extends CApiService { $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['valuemapid']], 'fields' => [ 'valuemapid' => ['type' => API_ID, 'flags' => API_REQUIRED], 'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('valuemap', 'name')], - 'mappings' => ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY, 'uniq' => [['value']], 'fields' => [ - 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('valuemap_mapping', 'value')], + 'mappings' => ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY, 'fields' => [ + 'type' => ['type' => API_INT32, 'default' => VALUEMAP_MAPPING_TYPE_EQUAL, 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_EQUAL, VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, VALUEMAP_MAPPING_TYPE_LESS_EQUAL, VALUEMAP_MAPPING_TYPE_IN_RANGE, VALUEMAP_MAPPING_TYPE_REGEXP, VALUEMAP_MAPPING_TYPE_DEFAULT])], + 'value' => ['type' => API_MULTIPLE, 'rules' => [ + [ + 'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_EQUAL])], + 'type' => API_STRING_UTF8, + 'length' => DB::getFieldLength('valuemap_mapping', 'value') + ], + [ + 'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, VALUEMAP_MAPPING_TYPE_LESS_EQUAL])], + 'type' => API_FLOAT, + 'length' => DB::getFieldLength('valuemap_mapping', 'value') + ], + [ + 'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_IN_RANGE])], + 'type' => API_NUMERIC_RANGES, + 'flags' => API_NOT_EMPTY, + 'length' => DB::getFieldLength('valuemap_mapping', 'value') + ], + [ + 'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_REGEXP])], + 'type' => API_REGEX, + 'flags' => API_NOT_EMPTY, + 'length' => DB::getFieldLength('valuemap_mapping', 'value') + ], + [ + 'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_DEFAULT])], + 'type' => API_STRING_UTF8 + ] + ]], 'newvalue' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('valuemap_mapping', 'newvalue')] ]] ]]; @@ -412,6 +500,7 @@ class CValueMap extends CApiService { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } + $this->validateValuemapMappings($valuemaps); $db_valuemaps = $this->get([ 'output' => ['valuemapid', 'hostid', 'name'], 'valuemapids' => array_column($valuemaps, 'valuemapid'), @@ -449,6 +538,64 @@ class CValueMap extends CApiService { } } + /** + * Validate uniqueness of mapping value in value maps, type VALUEMAP_MAPPING_TYPE_DEFAULT can be defined only once + * per value map mappings. + * + * @param array $valuemaps Array of valuemaps + * + * @throws Exception when non uniquw + */ + protected function validateValuemapMappings(array $valuemaps) { + $i = 0; + $error = ''; + + foreach ($valuemaps as $valuemap) { + $i++; + + if (!array_key_exists('mappings', $valuemap)) { + continue; + } + + $type_uniq = array_fill_keys([VALUEMAP_MAPPING_TYPE_EQUAL, VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, + VALUEMAP_MAPPING_TYPE_LESS_EQUAL, VALUEMAP_MAPPING_TYPE_IN_RANGE, VALUEMAP_MAPPING_TYPE_REGEXP + ], [] + ); + $has_default = false; + + foreach (array_values($valuemap['mappings']) as $j => $mapping) { + $type = array_key_exists('type', $mapping) ? $mapping['type'] : VALUEMAP_MAPPING_TYPE_EQUAL; + $value = array_key_exists('value', $mapping) ? (string) $mapping['value'] : ''; + + if ($has_default && $type == VALUEMAP_MAPPING_TYPE_DEFAULT) { + $error = _s('value %1$s already exists', '(type)=('.VALUEMAP_MAPPING_TYPE_DEFAULT.')'); + } + elseif (!array_key_exists('value', $mapping) && $type != VALUEMAP_MAPPING_TYPE_DEFAULT) { + $error = _s('the parameter "%1$s" is missing', 'value'); + } + elseif ($value !== '' && $type == VALUEMAP_MAPPING_TYPE_DEFAULT) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + sprintf('/%1$s/mappings/%2$s/value', $i, $j + 1), + _('should be empty') + )); + } + elseif ($type != VALUEMAP_MAPPING_TYPE_DEFAULT && array_key_exists($value, $type_uniq[$type])) { + $error = _s('value %1$s already exists', '(value)=('.$value.')'); + } + + if ($error !== '') { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + sprintf('/%1$s/mappings/%2$s', $i, $j + 1), + $error + )); + } + + $has_default = ($has_default || $type == VALUEMAP_MAPPING_TYPE_DEFAULT); + $type_uniq[$type][$value] = true; + } + } + } + protected function addRelatedObjects(array $options, array $db_valuemaps) { $db_valuemaps = parent::addRelatedObjects($options, $db_valuemaps); @@ -474,13 +621,16 @@ class CValueMap extends CApiService { } else { $db_mappings = API::getApiService()->select('valuemap_mapping', [ - 'output' => $this->outputExtend($options['selectMappings'], ['valuemapid']), + 'output' => $this->outputExtend($options['selectMappings'], ['valuemapid', + 'valuemap_mappingid', 'sortorder' + ]), 'filter' => ['valuemapid' => array_keys($db_valuemaps)] ]); + CArrayHelper::sort($db_mappings, [['field' => 'sortorder', 'order' => ZBX_SORT_UP]]); foreach ($db_mappings as $db_mapping) { $valuemapid = $db_mapping['valuemapid']; - unset($db_mapping['valuemap_mappingid'], $db_mapping['valuemapid']); + unset($db_mapping['valuemap_mappingid'], $db_mapping['valuemapid'], $db_mapping['sortorder']); $db_valuemaps[$valuemapid]['mappings'][] = $db_mapping; } diff --git a/ui/include/classes/data/CHistFunctionData.php b/ui/include/classes/data/CHistFunctionData.php index 150c7f98e02..f4ed868fbb7 100644 --- a/ui/include/classes/data/CHistFunctionData.php +++ b/ui/include/classes/data/CHistFunctionData.php @@ -26,8 +26,9 @@ final class CHistFunctionData { public const PERIOD_MODE_DEFAULT = 0; public const PERIOD_MODE_SEC = 1; - public const PERIOD_MODE_NUM = 2; - public const PERIOD_MODE_TREND = 3; + public const PERIOD_MODE_SEC_ONLY = 2; + public const PERIOD_MODE_NUM_ONLY = 3; + public const PERIOD_MODE_TREND = 4; /** * Known history functions along with definition of parameters. @@ -41,7 +42,7 @@ final class CHistFunctionData { ], 'avg_foreach' => [ ['rules' => [['type' => 'query']]], - ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_SEC]]] + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_SEC_ONLY]]] ], 'count' => [ ['rules' => [['type' => 'query']]], @@ -51,9 +52,17 @@ final class CHistFunctionData { ], ['required' => false] ], + 'countunique' => [ + ['rules' => [['type' => 'query']]], + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_DEFAULT]]], + ['rules' => [['type' => 'regexp', 'pattern' => '/^(eq|ne|gt|ge|lt|le|like|bitand|regexp|iregexp)$/']], + 'required' => false + ], + ['required' => false] + ], 'count_foreach' => [ ['rules' => [['type' => 'query']]], - ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_SEC]]] + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_SEC_ONLY]]] ], 'change' => [ ['rules' => [['type' => 'query']]] @@ -66,6 +75,10 @@ final class CHistFunctionData { ], ['required' => false] ], + 'first' => [ + ['rules' => [['type' => 'query']]], + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_SEC]]] + ], 'forecast' => [ ['rules' => [['type' => 'query']]], ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_DEFAULT]]], @@ -79,35 +92,43 @@ final class CHistFunctionData { ['rules' => [['type' => 'query']]], ['rules' => [['type' => 'time', 'min' => 1]]] ], + 'kurtosis' => [ + ['rules' => [['type' => 'query']]], + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_DEFAULT]]] + ], 'last' => [ ['rules' => [['type' => 'query']]], - ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_NUM]], 'required' => false] + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_NUM_ONLY]], 'required' => false] ], 'last_foreach' => [ ['rules' => [['type' => 'query']]], - ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_SEC]], 'required' => false] + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_SEC_ONLY]], 'required' => false] ], 'logeventid' => [ ['rules' => [['type' => 'query']]], - ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_NUM]], 'required' => false], + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_NUM_ONLY]], 'required' => false], ['required' => false] ], 'logseverity' => [ ['rules' => [['type' => 'query']]], - ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_NUM]], 'required' => false] + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_NUM_ONLY]], 'required' => false] ], 'logsource' => [ ['rules' => [['type' => 'query']]], - ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_NUM]], 'required' => false], + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_NUM_ONLY]], 'required' => false], ['required' => false] ], + 'mad' => [ + ['rules' => [['type' => 'query']]], + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_DEFAULT]]] + ], 'max' => [ ['rules' => [['type' => 'query']]], ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_DEFAULT]]] ], 'max_foreach' => [ ['rules' => [['type' => 'query']]], - ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_SEC]]] + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_SEC_ONLY]]] ], 'min' => [ ['rules' => [['type' => 'query']]], @@ -115,7 +136,7 @@ final class CHistFunctionData { ], 'min_foreach' => [ ['rules' => [['type' => 'query']]], - ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_SEC]]] + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_SEC_ONLY]]] ], 'nodata' => [ ['rules' => [['type' => 'query']]], @@ -130,13 +151,38 @@ final class CHistFunctionData { ['type' => 'number', 'min' => 0, 'max' => 100] ]] ], + 'skewness' => [ + ['rules' => [['type' => 'query']]], + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_DEFAULT]]] + ], + + 'stddevpop' => [ + ['rules' => [['type' => 'query']]], + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_DEFAULT]]] + ], + 'stddevsamp' => [ + ['rules' => [['type' => 'query']]], + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_DEFAULT]]] + ], + 'sumofsquares' => [ + ['rules' => [['type' => 'query']]], + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_DEFAULT]]] + ], + 'varpop' => [ + ['rules' => [['type' => 'query']]], + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_DEFAULT]]] + ], + 'varsamp' => [ + ['rules' => [['type' => 'query']]], + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_DEFAULT]]] + ], 'sum' => [ ['rules' => [['type' => 'query']]], ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_DEFAULT]]] ], 'sum_foreach' => [ ['rules' => [['type' => 'query']]], - ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_SEC]]] + ['rules' => [['type' => 'period', 'mode' => self::PERIOD_MODE_SEC_ONLY]]] ], 'timeleft' => [ ['rules' => [['type' => 'query']]], @@ -185,22 +231,32 @@ final class CHistFunctionData { 'avg' => self::ITEM_VALUE_TYPES_NUM, 'avg_foreach' => self::ITEM_VALUE_TYPES_NUM, 'count' => self::ITEM_VALUE_TYPES_ALL, + 'countunique' => self::ITEM_VALUE_TYPES_ALL, 'count_foreach' => self::ITEM_VALUE_TYPES_ALL, 'change' => self::ITEM_VALUE_TYPES_ALL, 'find' => self::ITEM_VALUE_TYPES_ALL, + 'first' => self::ITEM_VALUE_TYPES_ALL, 'forecast' => self::ITEM_VALUE_TYPES_NUM, 'fuzzytime' => self::ITEM_VALUE_TYPES_NUM, + 'kurtosis' => self::ITEM_VALUE_TYPES_NUM, 'last' => self::ITEM_VALUE_TYPES_ALL, 'last_foreach' => self::ITEM_VALUE_TYPES_ALL, 'logeventid' => self::ITEM_VALUE_TYPES_LOG, 'logseverity' => self::ITEM_VALUE_TYPES_LOG, 'logsource' => self::ITEM_VALUE_TYPES_LOG, + 'mad' => self::ITEM_VALUE_TYPES_NUM, 'max' => self::ITEM_VALUE_TYPES_NUM, 'max_foreach' => self::ITEM_VALUE_TYPES_NUM, 'min' => self::ITEM_VALUE_TYPES_NUM, 'min_foreach' => self::ITEM_VALUE_TYPES_NUM, 'nodata' => self::ITEM_VALUE_TYPES_ALL, 'percentile' => self::ITEM_VALUE_TYPES_NUM, + 'skewness' => self::ITEM_VALUE_TYPES_NUM, + 'stddevpop' => self::ITEM_VALUE_TYPES_NUM, + 'stddevsamp' => self::ITEM_VALUE_TYPES_NUM, + 'sumofsquares' => self::ITEM_VALUE_TYPES_NUM, + 'varpop' => self::ITEM_VALUE_TYPES_NUM, + 'varsamp' => self::ITEM_VALUE_TYPES_NUM, 'sum' => self::ITEM_VALUE_TYPES_NUM, 'sum_foreach' => self::ITEM_VALUE_TYPES_NUM, 'timeleft' => self::ITEM_VALUE_TYPES_NUM, diff --git a/ui/include/classes/data/CMathFunctionData.php b/ui/include/classes/data/CMathFunctionData.php index 8d1b806c840..4130c4a42bb 100644 --- a/ui/include/classes/data/CMathFunctionData.php +++ b/ui/include/classes/data/CMathFunctionData.php @@ -25,23 +25,72 @@ final class CMathFunctionData { /** - * Known math functions along with number of required parameters (-1 for number of required parameters >= 1). + * Known math functions along with number or range of required parameters. * * @var array */ private const PARAMETERS = [ 'abs' => 1, - 'avg' => -1, + 'acos' => 1, + 'ascii' => 1, + 'asin' => 1, + 'atan' => 1, + 'atan2' => 2, + 'avg' => [1, null], + 'between' => 3, 'bitand' => 2, + 'bitlength' => 1, + 'bitlshift' => 2, + 'bitnot' => 1, + 'bitor' => 2, + 'bitrshift' => 2, + 'bitxor' => 2, + 'bytelength' => 1, + 'cbrt' => 1, + 'ceil' => 1, + 'char' => 1, + 'concat' => 2, + 'cos' => 1, + 'cosh' => 1, + 'cot' => 1, 'date' => 0, 'dayofmonth' => 0, 'dayofweek' => 0, + 'degrees' => 1, + 'e' => 0, + 'exp' => 1, + 'expm1' => 1, + 'floor' => 1, + 'in' => [2, null], + 'insert' => 4, + 'left' => 2, 'length' => 1, - 'max' => -1, - 'min' => -1, + 'log' => 1, + 'log10' => 1, + 'ltrim' => [1, 2], + 'max' => [1, null], + 'mid' => 3, + 'min' => [1, null], + 'mod' => 2, 'now' => 0, - 'sum' => -1, - 'time' => 0 + 'pi' => 0, + 'power' => 2, + 'radians' => 1, + 'rand' => 0, + 'repeat' => 2, + 'replace' => 3, + 'right' => 2, + 'rtrim' => [1, 2], + 'round' => 2, + 'sin' => 1, + 'sinh' => 1, + 'signum' => 1, + 'sqrt' => 1, + 'sum' => [1, null], + 'tan' => 1, + 'time' => 0, + 'trim' => [1, 2], + 'truncate' => 2 ]; /** @@ -56,7 +105,7 @@ final class CMathFunctionData { } /** - * Get known math functions along with number of required parameters (-1 for number of required parameters >= 1). + * Get known math functions along with number or range of required parameters. * * @return array */ diff --git a/ui/include/classes/export/CConfigurationExport.php b/ui/include/classes/export/CConfigurationExport.php index d1efffabefc..b340043ff5d 100644 --- a/ui/include/classes/export/CConfigurationExport.php +++ b/ui/include/classes/export/CConfigurationExport.php @@ -1118,6 +1118,22 @@ class CConfigurationExport { } /** + * Get value maps for export builder from database. + * + * @param array $valuemapids + * + * return array + */ + protected function gatherValueMaps(array $valuemapids) { + $this->data['valueMaps'] = API::ValueMap()->get([ + 'output' => ['valuemapid', 'name'], + 'selectMappings' => ['type', 'value', 'newvalue'], + 'valuemapids' => $valuemapids, + 'preservekeys' => true + ]); + } + + /** * Change map elements real database selement id and icons ids to unique field references. * * @param array $exportMaps diff --git a/ui/include/classes/export/CConfigurationExportBuilder.php b/ui/include/classes/export/CConfigurationExportBuilder.php index b436d2324ca..cf00a382a02 100644 --- a/ui/include/classes/export/CConfigurationExportBuilder.php +++ b/ui/include/classes/export/CConfigurationExportBuilder.php @@ -441,16 +441,22 @@ class CConfigurationExportBuilder { * @param array $valuemaps */ protected function formatValueMaps(array $valuemaps) { - $result = []; - CArrayHelper::sort($valuemaps, ['name']); - foreach ($valuemaps as $valuemap) { - CArrayHelper::sort($valuemap['mappings'], ['value']); - $result[] = $valuemap; + foreach ($valuemaps as &$valuemap) { + foreach ($valuemap['mappings'] as &$mapping) { + if ($mapping['type'] == VALUEMAP_MAPPING_TYPE_EQUAL) { + unset($mapping['type']); + } + elseif ($mapping['type'] == VALUEMAP_MAPPING_TYPE_DEFAULT) { + unset($mapping['value']); + } + } + unset($mapping); } + unset($valuemap); - return $result; + return $valuemaps; } /** diff --git a/ui/include/classes/helpers/CValueMapHelper.php b/ui/include/classes/helpers/CValueMapHelper.php index 5977634a412..9e981a1a44f 100644 --- a/ui/include/classes/helpers/CValueMapHelper.php +++ b/ui/include/classes/helpers/CValueMapHelper.php @@ -26,14 +26,15 @@ class CValueMapHelper { * If value map or mapping is not found, unchanged value is returned, * otherwise mapped value returned in format: "<mapped_value> (<initial_value>)". * + * @param int $value_type Item value type. * @param string $value Value that mapping should be applied to. * @param array $valuemap Valuemap array. * @param array $valuemap['mappings'] (optional) Valuemap mappings array. * * @return string */ - static public function applyValueMap(string $value, array $valuemap): string { - $newvalue = static::getMappedValue($value, $valuemap); + public static function applyValueMap($value_type, string $value, array $valuemap): string { + $newvalue = static::getMappedValue($value_type, $value, $valuemap); return ($newvalue !== false) ? $newvalue.' ('.$value.')' : $value; } @@ -41,19 +42,98 @@ class CValueMapHelper { /** * Get mapping for value. * + * @param int $value_type Item value type. * @param string $value Value that mapping should be applied to. * @param array $valuemap Valuemap array. - * @param array $valuemap['mappings'] (optional) Valuemap mappings array. + * @param array $valuemap['mappings'] Valuemap mappings array. * * @return string|bool If there is no mapping return false, return mapped value otherwise. */ - static public function getMappedValue(string $value, array $valuemap) { - if (!$valuemap) { - return false; + public static function getMappedValue($value_type, string $value, array $valuemap) { + $newvalue = false; + + if (array_key_exists('mappings', $valuemap)) { + foreach ($valuemap['mappings'] as $mapping) { + if ($mapping['type'] == VALUEMAP_MAPPING_TYPE_DEFAULT) { + $newvalue = $mapping['newvalue']; + } + elseif (static::matchMapping($value_type, $value, $mapping)) { + $newvalue = $mapping['newvalue']; + + break; + } + } } - $mappings = array_column($valuemap['mappings'], 'newvalue', 'value'); + return $newvalue; + } + + /** + * Check value match against single mapping. Return true on success, false otherwise. + * + * @param int $value_type Item value type. + * @param string $value Value to check against mapping. + * @param array $mapping Array of single mapping. + * @param string $mapping['type'] Type of mapping. + * @param string $mapping['value] Value of mapping. + */ + public static function matchMapping($value_type, string $value, array $mapping): bool { + $match_numeric = ($value_type == ITEM_VALUE_TYPE_FLOAT || $value_type == ITEM_VALUE_TYPE_UINT64); + $result = false; + + switch ($mapping['type']) { + case VALUEMAP_MAPPING_TYPE_EQUAL: + $result = $match_numeric + ? (floatval($value) == floatval($mapping['value'])) + : ($value === $mapping['value']); + + break; + + case VALUEMAP_MAPPING_TYPE_GREATER_EQUAL: + $result = ($match_numeric && floatval($value) >= floatval($mapping['value'])); + + break; + + case VALUEMAP_MAPPING_TYPE_LESS_EQUAL: + $result = ($match_numeric && floatval($value) <= floatval($mapping['value'])); + + break; + + case VALUEMAP_MAPPING_TYPE_IN_RANGE: + if (!$match_numeric) { + break; + } + + $ranges_parser = new CRangesParser(['with_minus' => true, 'with_float' => true, 'with_suffix' => true]); + + if ($ranges_parser->parse($mapping['value']) == CParser::PARSE_SUCCESS) { + $value = floatval($value); + + foreach ($ranges_parser->getRanges() as $ranges) { + if ($value == floatval($ranges[0]) + || (count($ranges) == 2 && $value >= floatval($ranges[0]) + && $value <= floatval($ranges[1]))) { + $result = true; + + break; + } + } + } + + break; + + case VALUEMAP_MAPPING_TYPE_REGEXP: + $result = (!$match_numeric + && @preg_match('/'.str_replace('/', '\/', $mapping['value']).'/', $value) == 1); + + break; + + case VALUEMAP_MAPPING_TYPE_DEFAULT: + $result = true; + + break; + } - return array_key_exists($value, $mappings) ? $mappings[$value] : false; + return $result; } } diff --git a/ui/include/classes/import/validators/C54XmlValidator.php b/ui/include/classes/import/validators/C54XmlValidator.php index 3ac4574bb18..ed04c539c54 100644 --- a/ui/include/classes/import/validators/C54XmlValidator.php +++ b/ui/include/classes/import/validators/C54XmlValidator.php @@ -383,6 +383,15 @@ class C54XmlValidator extends CXmlValidatorGeneral { CXmlConstantValue::DASHBOARD_WIDGET_FIELD_TYPE_GRAPH_PROTOTYPE => CXmlConstantName::DASHBOARD_WIDGET_FIELD_TYPE_GRAPH_PROTOTYPE ]; + private $VALUEMAP_MAPPING_TYPE = [ + CXmlConstantValue::MAPPING_EQUAL => CXmlConstantName::MAPPING_EQUAL, + CXmlConstantValue::MAPPING_GREATER_EQUAL => CXmlConstantName::MAPPING_GREATER_EQUAL, + CXmlConstantValue::MAPPING_LESS_EQUAL => CXmlConstantName::MAPPING_LESS_EQUAL, + CXmlConstantValue::MAPPING_IN_RANGE => CXmlConstantName::MAPPING_IN_RANGE, + CXmlConstantValue::MAPPING_REGEXP => CXmlConstantName::MAPPING_REGEXP, + CXmlConstantValue::MAPPING_DEFAULT => CXmlConstantName::MAPPING_DEFAULT + ]; + /** * Get validation rules schema. * @@ -1094,7 +1103,8 @@ class C54XmlValidator extends CXmlValidatorGeneral { 'name' => ['type' => XML_STRING | XML_REQUIRED], 'mappings' => ['type' => XML_INDEXED_ARRAY | XML_REQUIRED, 'prefix' => 'mapping', 'rules' => [ 'mapping' => ['type' => XML_ARRAY, 'rules' => [ - 'value' => ['type' => XML_STRING | XML_REQUIRED], + 'type' => ['type' => XML_STRING, 'default' => CXmlConstantValue::MAPPING_EQUAL, 'in' => $this->VALUEMAP_MAPPING_TYPE], + 'value' => ['type' => XML_STRING, 'default' => ''], 'newvalue' => ['type' => XML_STRING | XML_REQUIRED] ]] ]] @@ -1734,7 +1744,8 @@ class C54XmlValidator extends CXmlValidatorGeneral { 'name' => ['type' => XML_STRING | XML_REQUIRED], 'mappings' => ['type' => XML_INDEXED_ARRAY | XML_REQUIRED, 'prefix' => 'mapping', 'rules' => [ 'mapping' => ['type' => XML_ARRAY, 'rules' => [ - 'value' => ['type' => XML_STRING | XML_REQUIRED], + 'type' => ['type' => XML_STRING, 'default' => CXmlConstantValue::MAPPING_EQUAL, 'in' => $this->VALUEMAP_MAPPING_TYPE], + 'value' => ['type' => XML_STRING, 'default' => ''], 'newvalue' => ['type' => XML_STRING | XML_REQUIRED] ]] ]] diff --git a/ui/include/classes/macros/CMacrosResolverGeneral.php b/ui/include/classes/macros/CMacrosResolverGeneral.php index f2aefdc3600..2df1a33b7e7 100644 --- a/ui/include/classes/macros/CMacrosResolverGeneral.php +++ b/ui/include/classes/macros/CMacrosResolverGeneral.php @@ -388,13 +388,13 @@ class CMacrosResolverGeneral { $function_parameters = []; foreach ($function_parser->getParamsRaw()['parameters'] as $param_raw) { - switch ($param_raw->type) { + switch ($param_raw['type']) { case C10FunctionParser::PARAM_UNQUOTED: - $function_parameters[] = $param_raw->match; + $function_parameters[] = $param_raw['raw']; break; case C10FunctionParser::PARAM_QUOTED: - $function_parameters[] = C10FunctionParser::unquoteParam($param_raw->match); + $function_parameters[] = C10FunctionParser::unquoteParam($param_raw['raw']); break; } } @@ -748,8 +748,9 @@ class CMacrosResolverGeneral { } $options = [ - 'output' => ['valuemapid', 'value', 'newvalue'], - 'filter' => ['valuemapid' => array_keys($valuemapids)] + 'output' => ['valuemapid', 'type', 'value', 'newvalue'], + 'filter' => ['valuemapid' => array_keys($valuemapids)], + 'sortfield' => ['sortorder'] ]; $db_mappings = DBselect(DB::makeSql('valuemap_mapping', $options)); @@ -757,6 +758,7 @@ class CMacrosResolverGeneral { while ($db_mapping = DBfetch($db_mappings)) { $db_valuemaps[$db_mapping['valuemapid']]['mappings'][] = [ + 'type' => $db_mapping['type'], 'value' => $db_mapping['value'], 'newvalue' => $db_mapping['newvalue'] ]; diff --git a/ui/include/classes/mvc/CRouter.php b/ui/include/classes/mvc/CRouter.php index 86650769ebf..28bdd78c8c3 100644 --- a/ui/include/classes/mvc/CRouter.php +++ b/ui/include/classes/mvc/CRouter.php @@ -187,6 +187,7 @@ class CRouter { 'popup.massupdate.trigger' => ['CControllerPopupMassupdateTrigger', 'layout.json', 'popup.massupdate.trigger'], 'popup.massupdate.triggerprototype' => ['CControllerPopupMassupdateTrigger', 'layout.json', 'popup.massupdate.trigger'], 'popup.valuemap.edit' => ['CControllerPopupValueMapEdit', 'layout.json', 'popup.valuemap.edit'], + 'popup.valuemap.update' => ['CControllerPopupValueMapUpdate', 'layout.json', null], 'problem.view' => ['CControllerProblemView', 'layout.htmlpage', 'monitoring.problem.view'], 'problem.view.refresh' => ['CControllerProblemViewRefresh', 'layout.json', null], 'problem.view.csv' => ['CControllerProblemView', 'layout.csv', 'monitoring.problem.view'], diff --git a/ui/include/classes/parsers/CExpressionParser.php b/ui/include/classes/parsers/CExpressionParser.php index 0c291b7b35b..2415272e9a4 100644 --- a/ui/include/classes/parsers/CExpressionParser.php +++ b/ui/include/classes/parsers/CExpressionParser.php @@ -648,7 +648,7 @@ class CExpressionParser extends CParser { int $depth): bool { $p = $pos; - if (!preg_match('/^([a-z]+)\(/', substr($source, $p), $matches)) { + if (!preg_match('/^([a-z0-9]+)\(/', substr($source, $p), $matches)) { return false; } diff --git a/ui/include/classes/parsers/CNumberParser.php b/ui/include/classes/parsers/CNumberParser.php index 3132f715d58..1e83d03d0e4 100644 --- a/ui/include/classes/parsers/CNumberParser.php +++ b/ui/include/classes/parsers/CNumberParser.php @@ -31,6 +31,7 @@ class CNumberParser extends CParser { */ private $options = [ 'with_minus' => true, + 'with_float' => true, 'with_suffix' => false ]; @@ -84,22 +85,25 @@ class CNumberParser extends CParser { $fragment = substr($source, $pos); + $pattern = $this->options['with_float'] ? ZBX_PREG_NUMBER : ZBX_PREG_INT; $pattern = $this->options['with_suffix'] - ? '/^'.ZBX_PREG_NUMBER.'(?<suffix>['.self::$suffixes.'])?/' - : '/^'.ZBX_PREG_NUMBER.'/'; + ? '/^'.$pattern.'(?<suffix>['.self::$suffixes.'])?/' + : '/^'.$pattern.'/'; if (!preg_match($pattern, $fragment, $matches)) { return self::PARSE_FAIL; } - if ($matches['number'][0] === '-' && !$this->options['with_minus']) { + $number = $this->options['with_float'] ? $matches['number'] : $matches['int']; + + if ($number[0] === '-' && !$this->options['with_minus']) { return self::PARSE_FAIL; } $this->length = strlen($matches[0]); $this->match = $matches[0]; - $this->number = $matches['number']; + $this->number = $number; $this->suffix = array_key_exists('suffix', $matches) ? $matches['suffix'] : null; return ($pos + $this->length < strlen($source)) ? self::PARSE_SUCCESS_CONT : self::PARSE_SUCCESS; diff --git a/ui/include/classes/parsers/CRangeParser.php b/ui/include/classes/parsers/CRangeParser.php index b5b4a4cc24e..afd2aafd67d 100644 --- a/ui/include/classes/parsers/CRangeParser.php +++ b/ui/include/classes/parsers/CRangeParser.php @@ -46,7 +46,15 @@ class CRangeParser extends CParser { private $lld_macro_function_parser; /** - * Range. + * Number parser. + * + * @var CNumberParser + */ + private $number_parser; + + /** + * Array of range strings. Range value with suffix will be stored as string of calculated value, value "1K" + * will be stored as "1024". * * @var array */ @@ -55,23 +63,32 @@ class CRangeParser extends CParser { /** * Options to initialize other parsers. * + * usermacros Allow usermacros in ranges. + * lldmacros Allow lldmacros in ranges. + * with_minus Allow negative ranges. + * with_float Allow float number ranges. + * with_suffix Allow number ranges with suffix, supported suffixes see CNumberParser::$suffixes. + * * @var array */ private $options = [ 'usermacros' => false, - 'lldmacros' => false + 'lldmacros' => false, + 'with_minus' => false, + 'with_float' => false, + 'with_suffix' => false ]; /** * @param array $options An array of options to initialize other parsers. */ public function __construct($options = []) { - if (array_key_exists('usermacros', $options)) { - $this->options['usermacros'] = $options['usermacros']; - } - if (array_key_exists('lldmacros', $options)) { - $this->options['lldmacros'] = $options['lldmacros']; - } + $this->options = $options + $this->options; + $this->number_parser = new CNumberParser([ + 'with_minus' => $this->options['with_minus'], + 'with_float' => $this->options['with_float'], + 'with_suffix' => $this->options['with_suffix'] + ]); if ($this->options['usermacros']) { $this->user_macro_parser = new CUserMacroParser(); @@ -91,6 +108,8 @@ class CRangeParser extends CParser { * {$M}-{$M} * {#M}-{#M} * {$M}-{{#M}.regsub("^([0-9]+)", "{#M}: \1")} + * -200--10 + * -2.5--1.35 * * @param string $source Source string that needs to be parsed. * @param int $pos Position offset. @@ -196,21 +215,25 @@ class CRangeParser extends CParser { * @return bool|array Returns false if non-numeric character found else returns array of position and match. */ private function parseDigits($source, &$pos) { - if (!preg_match('/^([0-9]+)/', substr($source, $pos), $matches)) { + if ($this->number_parser->parse($source, $pos) == CParser::PARSE_FAIL) { return false; } - if ($matches[0] > ZBX_MAX_INT32) { + $value = $this->number_parser->calcValue(); + + if ($value > ZBX_MAX_INT32) { return false; } // Second value must be greater than or equal to first one. - if ($this->range && ctype_digit($this->range[0]) && $this->range[0] > $matches[0]) { + if ($this->range && is_numeric($this->range[0]) && $this->range[0] > $value) { return false; } - $pos += strlen($matches[0]); - $this->range[] = $matches[0]; + $pos += $this->number_parser->getLength(); + $this->range[] = ($this->number_parser->getSuffix() === null) + ? $this->number_parser->getMatch() + : strval($value); return true; } diff --git a/ui/include/classes/parsers/CRangesParser.php b/ui/include/classes/parsers/CRangesParser.php index f0053f7eb7f..6f3df36c16e 100644 --- a/ui/include/classes/parsers/CRangesParser.php +++ b/ui/include/classes/parsers/CRangesParser.php @@ -32,7 +32,8 @@ class CRangesParser extends CParser { private $range_parser; /** - * Array of status code ranges. + * Array of ranges strings. Range value with suffix will be stored as string of calculated value, value "1K" + * will be stored as "1024". * * @var array */ @@ -41,28 +42,29 @@ class CRangesParser extends CParser { /** * Options to initialize other parsers. * + * usermacros Allow usermacros in ranges. + * lldmacros Allow lldmacros in ranges. + * with_minus Allow negative ranges. + * with_float Allow float number ranges. + * with_suffix Allow number ranges with suffix, supported suffixes see CNumberParser::$suffixes. + * * @var array */ private $options = [ 'usermacros' => false, - 'lldmacros' => false + 'lldmacros' => false, + 'with_minus' => false, + 'with_float' => false, + 'with_suffix' => false ]; /** * @param array $options An array of options to initialize other parsers. */ public function __construct($options = []) { - if (array_key_exists('usermacros', $options)) { - $this->options['usermacros'] = $options['usermacros']; - } - if (array_key_exists('lldmacros', $options)) { - $this->options['lldmacros'] = $options['lldmacros']; - } + $this->options = $options + $this->options; - $this->range_parser = new CRangeParser([ - 'usermacros' => $this->options['usermacros'], - 'lldmacros' => $this->options['lldmacros'] - ]); + $this->range_parser = new CRangeParser($this->options); } /** diff --git a/ui/include/classes/screens/CScreenHistory.php b/ui/include/classes/screens/CScreenHistory.php index a81ccb8c284..56e0fa25251 100644 --- a/ui/include/classes/screens/CScreenHistory.php +++ b/ui/include/classes/screens/CScreenHistory.php @@ -424,7 +424,7 @@ class CScreenHistory extends CScreenBase { $value = formatFloat($value, null, ZBX_UNITS_ROUNDOFF_UNSUFFIXED); } - $value = CValueMapHelper::applyValueMap($value, $item['valuemap']); + $value = CValueMapHelper::applyValueMap($item['value_type'], $value, $item['valuemap']); $history_table->addRow([ (new CCol(zbx_date2str(DATE_TIME_FORMAT_SECONDS, $history_row['clock']))) @@ -508,7 +508,7 @@ class CScreenHistory extends CScreenBase { $value = formatFloat($value, null, ZBX_UNITS_ROUNDOFF_UNSUFFIXED); } - $value = CValueMapHelper::applyValueMap($value, $item['valuemap']); + $value = CValueMapHelper::applyValueMap($item['value_type'], $value, $item['valuemap']); $row[] = ($value === '') ? '' : new CPre($value); } diff --git a/ui/include/classes/validators/CApiInputValidator.php b/ui/include/classes/validators/CApiInputValidator.php index 7f32aeee294..417dd134634 100644 --- a/ui/include/classes/validators/CApiInputValidator.php +++ b/ui/include/classes/validators/CApiInputValidator.php @@ -196,6 +196,9 @@ class CApiInputValidator { case API_DATE: return self::validateDate($rule, $data, $path, $error); + case API_NUMERIC_RANGES: + return self::validateNumericRanges($rule, $data, $path, $error); + case API_UUID: return self::validateUuid($rule, $data, $path, $error); } @@ -254,6 +257,7 @@ class CApiInputValidator { case API_JSONRPC_PARAMS: case API_JSONRPC_ID: case API_DATE: + case API_NUMERIC_RANGES: case API_UUID: return true; @@ -2184,6 +2188,48 @@ class CApiInputValidator { } /** + * Validate numeric ranges. Multiple ranges separated by comma character. + * Example: + * 10-20,-20--10,-5-0,0.5-0.7,-20--10,-20.20--20.10 + * 30,-10,0.7,-0.5 + * + * @param array $rule + * @param int $rule['flags'] (optional) API_NOT_EMPTY + * @param int $rule['length'] (optional) + * @param mixed $data + * @param string $path + * @param string $error + * + * @return bool + */ + private static function validateNumericRanges($rule, &$data, $path, &$error) { + $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; + + if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { + return false; + } + + if (($flags & API_NOT_EMPTY) == 0 && $data === '') { + return true; + } + + if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); + return false; + } + + $parser = new CRangesParser(['with_minus' => true, 'with_float' => true, 'with_suffix' => true]); + + if ($parser->parse($data) != CParser::PARSE_SUCCESS) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid range expression')); + + return false; + } + + return true; + } + + /** * UUIDv4 validator. * * @param array $rule diff --git a/ui/include/classes/validators/CExpressionValidator.php b/ui/include/classes/validators/CExpressionValidator.php index c0adccdd33e..b8edd4b1ad2 100644 --- a/ui/include/classes/validators/CExpressionValidator.php +++ b/ui/include/classes/validators/CExpressionValidator.php @@ -29,11 +29,13 @@ class CExpressionValidator extends CValidator { * * Supported options: * 'calculated' => false Validate expression as part of calculated item formula. + * 'partial' => false Validate partial expression (relaxed requirements). * * @var array */ private $options = [ - 'calculated' => false + 'calculated' => false, + 'partial' => false ]; /** @@ -44,7 +46,7 @@ class CExpressionValidator extends CValidator { private $math_function_data; /** - * Known math functions along with number of required parameters. + * Known math functions along with number or range of required parameters. * * @var array */ @@ -90,7 +92,7 @@ class CExpressionValidator extends CValidator { } if (!$this->options['calculated']) { - if (!self::hasHistoryFunctions($tokens)) { + if (!$this->options['partial'] && !self::hasHistoryFunctions($tokens)) { $this->setError(_('trigger expression must contain at least one /host/key reference')); return false; @@ -188,7 +190,7 @@ class CExpressionValidator extends CValidator { } /** - * Check if history function tokens are contained within the hierarchy of given tokens. + * Check if there are history function tokens within the hierarchy of given tokens. * * @param array $tokens * diff --git a/ui/include/classes/validators/CHistFunctionValidator.php b/ui/include/classes/validators/CHistFunctionValidator.php index a16d7d7e492..3fee138cdf3 100644 --- a/ui/include/classes/validators/CHistFunctionValidator.php +++ b/ui/include/classes/validators/CHistFunctionValidator.php @@ -303,7 +303,8 @@ class CHistFunctionValidator extends CValidator { return false; case CHistFunctionData::PERIOD_MODE_SEC: - if ($time_shift !== '') { + case CHistFunctionData::PERIOD_MODE_SEC_ONLY: + if ($mode == CHistFunctionData::PERIOD_MODE_SEC_ONLY && $time_shift !== '') { return false; } @@ -315,7 +316,7 @@ class CHistFunctionValidator extends CValidator { return false; - case CHistFunctionData::PERIOD_MODE_NUM: + case CHistFunctionData::PERIOD_MODE_NUM_ONLY: if (preg_match('/^#(?<num>\d+)$/', $sec_num, $matches) == 1) { return ($matches['num'] > 0 && $matches['num'] <= ZBX_MAX_INT32); } diff --git a/ui/include/classes/validators/CMathFunctionValidator.php b/ui/include/classes/validators/CMathFunctionValidator.php index d0187358914..e11383dbfb5 100644 --- a/ui/include/classes/validators/CMathFunctionValidator.php +++ b/ui/include/classes/validators/CMathFunctionValidator.php @@ -60,13 +60,18 @@ class CMathFunctionValidator extends CValidator { $num_required_parameters = $this->options['parameters'][$token['data']['function']]; $num_parameters = count($token['data']['parameters']); - if (($num_required_parameters == -1 && $num_parameters == 0) - || ($num_required_parameters != -1 && $num_parameters != $num_required_parameters)) { - $this->setError(_s('invalid number of parameters in function "%1$s"', $token['data']['function'])); + if (is_array($num_required_parameters)) { + $is_valid = ($num_required_parameters[0] === null || $num_parameters >= $num_required_parameters[0]) + && ($num_required_parameters[1] === null || $num_parameters <= $num_required_parameters[1]); + } + else { + $is_valid = $num_parameters == $num_required_parameters; + } - return false; + if (!$is_valid) { + $this->setError(_s('invalid number of parameters in function "%1$s"', $token['data']['function'])); } - return true; + return $is_valid; } } diff --git a/ui/include/classes/xml/CXmlConstantName.php b/ui/include/classes/xml/CXmlConstantName.php index fba437fb042..cddd4402453 100644 --- a/ui/include/classes/xml/CXmlConstantName.php +++ b/ui/include/classes/xml/CXmlConstantName.php @@ -346,4 +346,12 @@ class CXmlConstantName { const DASHBOARD_WIDGET_FIELD_TYPE_ITEM_PROTOTYPE = 'ITEM_PROTOTYPE'; const DASHBOARD_WIDGET_FIELD_TYPE_GRAPH = 'GRAPH'; const DASHBOARD_WIDGET_FIELD_TYPE_GRAPH_PROTOTYPE = 'GRAPH_PROTOTYPE'; + + // Constants for value map mapping type. + const MAPPING_EQUAL = 'EQUAL'; + const MAPPING_GREATER_EQUAL = 'GREATER_OR_EQUAL'; + const MAPPING_LESS_EQUAL = 'LESS_OR_EQUAL'; + const MAPPING_IN_RANGE = 'IN_RANGE'; + const MAPPING_REGEXP = 'REGEXP'; + const MAPPING_DEFAULT = 'DEFAULT'; } diff --git a/ui/include/classes/xml/CXmlConstantValue.php b/ui/include/classes/xml/CXmlConstantValue.php index cb4485121ab..dce87e34d2c 100644 --- a/ui/include/classes/xml/CXmlConstantValue.php +++ b/ui/include/classes/xml/CXmlConstantValue.php @@ -366,4 +366,12 @@ class CXmlConstantValue { const DASHBOARD_WIDGET_FIELD_TYPE_ITEM_PROTOTYPE = ZBX_WIDGET_FIELD_TYPE_ITEM_PROTOTYPE; const DASHBOARD_WIDGET_FIELD_TYPE_GRAPH = ZBX_WIDGET_FIELD_TYPE_GRAPH; const DASHBOARD_WIDGET_FIELD_TYPE_GRAPH_PROTOTYPE = ZBX_WIDGET_FIELD_TYPE_GRAPH_PROTOTYPE; + + // Valuemap mapping type. + const MAPPING_EQUAL = VALUEMAP_MAPPING_TYPE_EQUAL; + const MAPPING_GREATER_EQUAL = VALUEMAP_MAPPING_TYPE_GREATER_EQUAL; + const MAPPING_LESS_EQUAL = VALUEMAP_MAPPING_TYPE_LESS_EQUAL; + const MAPPING_IN_RANGE = VALUEMAP_MAPPING_TYPE_IN_RANGE; + const MAPPING_REGEXP = VALUEMAP_MAPPING_TYPE_REGEXP; + const MAPPING_DEFAULT = VALUEMAP_MAPPING_TYPE_DEFAULT; } diff --git a/ui/include/defines.inc.php b/ui/include/defines.inc.php index 58cc6a34385..41b5570bd63 100644 --- a/ui/include/defines.inc.php +++ b/ui/include/defines.inc.php @@ -22,7 +22,7 @@ define('ZABBIX_VERSION', '5.4.0rc1'); define('ZABBIX_API_VERSION', '5.4.0'); define('ZABBIX_EXPORT_VERSION', '5.4'); -define('ZABBIX_DB_VERSION', 5030187); +define('ZABBIX_DB_VERSION', 5030201); define('ZABBIX_COPYRIGHT_FROM', '2001'); define('ZABBIX_COPYRIGHT_TO', '2021'); @@ -1174,6 +1174,15 @@ define('EXPRESSION_NOT_A_MACRO_ERROR', '#ERROR_MACRO#'); define('EXPRESSION_FUNCTION_UNKNOWN', '#ERROR_FUNCTION#'); define('EXPRESSION_UNSUPPORTED_VALUE_TYPE', '#ERROR_VALUE_TYPE#'); +define('ZBX_FUNCTION_TYPE_AGGREGATE', 0); +define('ZBX_FUNCTION_TYPE_BITWISE', 1); +define('ZBX_FUNCTION_TYPE_DATE_TIME', 2); +define('ZBX_FUNCTION_TYPE_HISTORY', 3); +define('ZBX_FUNCTION_TYPE_MATH', 4); +define('ZBX_FUNCTION_TYPE_OPERATOR', 5); +define('ZBX_FUNCTION_TYPE_PREDICTION', 6); +define('ZBX_FUNCTION_TYPE_STRING', 7); + /** * @deprecated use either a literal space " " or a non-breakable space " " instead */ @@ -1289,6 +1298,14 @@ define('IPMI_PRIVILEGE_OEM', 5); define('ZBX_HAVE_IPV6', true); define('ZBX_DISCOVERER_IPRANGE_LIMIT', 65536); +// Value map mappings type +define('VALUEMAP_MAPPING_TYPE_EQUAL', 0); +define('VALUEMAP_MAPPING_TYPE_GREATER_EQUAL', 1); +define('VALUEMAP_MAPPING_TYPE_LESS_EQUAL', 2); +define('VALUEMAP_MAPPING_TYPE_IN_RANGE', 3); +define('VALUEMAP_MAPPING_TYPE_REGEXP', 4); +define('VALUEMAP_MAPPING_TYPE_DEFAULT', 5); + define('ZBX_SOCKET_BYTES_LIMIT', ZBX_MEBIBYTE * 16); // socket response size limit // value is also used in servercheck.js file @@ -1358,7 +1375,8 @@ define('API_EVENT_NAME', 37); define('API_JSONRPC_PARAMS', 38); define('API_JSONRPC_ID', 39); define('API_DATE', 40); -define('API_UUID', 41); +define('API_NUMERIC_RANGES', 41); +define('API_UUID', 42); // flags define('API_REQUIRED', 0x0001); @@ -1861,6 +1879,7 @@ define('ZBX_STYLE_ROW', 'row'); define('ZBX_STYLE_INLINE_SR_ONLY', 'inline-sr-only'); define('ZBX_STYLE_VALUEMAP_LIST_TABLE', 'valuemap-list-table'); define('ZBX_STYLE_VALUEMAP_CHECKBOX', 'valuemap-checkbox'); +define('ZBX_STYLE_VALUEMAP_MAPPINGS_TABLE', 'mappings-table'); define('ZBX_STYLE_SEARCH', 'search'); define('ZBX_STYLE_FORM_SEARCH', 'form-search'); define('ZBX_STYLE_SECOND_COLUMN_LABEL', 'second-column-label'); diff --git a/ui/include/items.inc.php b/ui/include/items.inc.php index f7f362e0f40..3b5677c4349 100644 --- a/ui/include/items.inc.php +++ b/ui/include/items.inc.php @@ -1419,7 +1419,7 @@ function formatHistoryValue($value, array $item, $trim = true) { // apply value mapping switch ($item['value_type']) { case ITEM_VALUE_TYPE_STR: - $mapping = CValueMapHelper::getMappedValue($value, $item['valuemap']); + $mapping = CValueMapHelper::getMappedValue($item['value_type'], $value, $item['valuemap']); // break; is not missing here case ITEM_VALUE_TYPE_TEXT: @@ -1435,7 +1435,7 @@ function formatHistoryValue($value, array $item, $trim = true) { break; default: - $value = CValueMapHelper::applyValueMap($value, $item['valuemap']); + $value = CValueMapHelper::applyValueMap($item['value_type'], $value, $item['valuemap']); } return $value; diff --git a/ui/include/schema.inc.php b/ui/include/schema.inc.php index 9a834cb2fbe..7e475e91399 100644 --- a/ui/include/schema.inc.php +++ b/ui/include/schema.inc.php @@ -3371,6 +3371,18 @@ return [ 'type' => DB::FIELD_TYPE_CHAR, 'length' => 64, 'default' => '' + ], + 'type' => [ + 'null' => false, + 'type' => DB::FIELD_TYPE_INT, + 'length' => 10, + 'default' => '0' + ], + 'sortorder' => [ + 'null' => false, + 'type' => DB::FIELD_TYPE_INT, + 'length' => 10, + 'default' => '0' ] ] ], @@ -8056,8 +8068,13 @@ return [ ] ], 'trigger_queue' => [ - 'key' => '', + 'key' => 'trigger_queueid', 'fields' => [ + 'trigger_queueid' => [ + 'null' => false, + 'type' => DB::FIELD_TYPE_ID, + 'length' => 20 + ], 'objectid' => [ 'null' => false, 'type' => DB::FIELD_TYPE_ID, diff --git a/ui/include/triggers.inc.php b/ui/include/triggers.inc.php index e2126606294..6e43ca2391a 100644 --- a/ui/include/triggers.inc.php +++ b/ui/include/triggers.inc.php @@ -1820,19 +1820,28 @@ function get_item_function_info(string $expr) { $hist_functions = [ 'avg' => $rules['numeric_as_float'], 'count' => $rules['numeric_as_uint'] + $rules['string_as_uint'], + 'countunique' => $rules['numeric_as_uint'] + $rules['string_as_uint'], 'change' => $rules['numeric'] + $rules['string_as_0or1'], 'find' => $rules['numeric_as_0or1'] + $rules['string_as_0or1'], + 'first' => $rules['numeric'] + $rules['string'], 'forecast' => $rules['numeric_as_float'], 'fuzzytime' => $rules['numeric_as_0or1'], + 'kurtosis' => $rules['numeric_as_float'], 'last' => $rules['numeric'] + $rules['string'], - 'length' => $rules['numeric'] + $rules['string'], 'logeventid' => $rules['log_as_0or1'], 'logseverity' => $rules['log_as_uint'], 'logsource' => $rules['log_as_0or1'], + 'mad' => $rules['numeric_as_float'], 'max' => $rules['numeric'], 'min' => $rules['numeric'], 'nodata' => $rules['numeric_as_0or1'] + $rules['string_as_0or1'], 'percentile' => $rules['numeric'], + 'skewness' => $rules['numeric_as_float'], + 'stddevpop' => $rules['numeric_as_float'], + 'stddevsamp' => $rules['numeric_as_float'], + 'sumofsquares' => $rules['numeric_as_float'], + 'varpop' => $rules['numeric_as_float'], + 'varsamp' => $rules['numeric_as_float'], 'sum' => $rules['numeric'], 'timeleft' => $rules['numeric_as_float'], 'trendavg' => $rules['numeric'], @@ -1844,8 +1853,28 @@ function get_item_function_info(string $expr) { $math_functions = [ 'abs' => ['any' => $rule_float], + 'acos' => ['any' => $rule_float], + 'ascii' => ['any' => $rule_int], + 'asin' => ['any' => $rule_float], + 'atan' => ['any' => $rule_float], + 'atan2' => ['any' => $rule_float], 'avg' => ['any' => $rule_float], + 'between' => ['any' => $rule_0or1], 'bitand' => ['any' => $rule_int], + 'bitlength' => ['any' => $rule_int], + 'bitlshift' => ['any' => $rule_int], + 'bitnot' => ['any' => $rule_int], + 'bitor' => ['any' => $rule_int], + 'bitrshift' => ['any' => $rule_int], + 'bitxor' => ['any' => $rule_int], + 'bytelength' => ['any' => $rule_int], + 'cbrt' => ['any' => $rule_float], + 'ceil' => ['any' => $rule_int], + 'char' => ['any' => $rule_str], + 'concat' => ['any' => $rule_str], + 'cos' => ['any' => $rule_float], + 'cosh' => ['any' => $rule_float], + 'cot' => ['any' => $rule_float], 'date' => [ 'any' => ['value_type' => 'YYYYMMDD', 'values' => null] ], @@ -1855,14 +1884,43 @@ function get_item_function_info(string $expr) { 'dayofweek' => [ 'any' => ['value_type' => '1-7', 'values' => [1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7]] ], + 'degrees' => ['any' => $rule_float], + 'e' => ['any' => $rule_float], + 'exp' => ['any' => $rule_float], + 'expm1' => ['any' => $rule_float], + 'floor' => ['any' => $rule_int], + 'in' => ['any' => $rule_0or1], + 'insert' => ['any' => $rule_str], + 'left' => ['any' => $rule_str], 'length' => ['any' => $rule_int], + 'log' => ['any' => $rule_float], + 'log10' => ['any' => $rule_float], + 'ltrim' => ['any' => $rule_str], 'max' => ['any' => $rule_float], + 'mid' => ['any' => $rule_str], 'min' => ['any' => $rule_float], + 'mod' => ['any' => $rule_float], 'now' => ['any' => $rule_int], + 'pi' => ['any' => $rule_float], + 'power' => ['any' => $rule_float], + 'radians' => ['any' => $rule_float], + 'rand' => ['any' => $rule_int], + 'repeat' => ['any' => $rule_str], + 'replace' => ['any' => $rule_str], + 'right' => ['any' => $rule_str], + 'round' => ['any' => $rule_float], + 'rtrim' => ['any' => $rule_str], + 'sin' => ['any' => $rule_float], + 'sinh' => ['any' => $rule_float], + 'signum' => ['any' => $rule_int], + 'sqrt' => ['any' => $rule_float], 'sum' => ['any' => $rule_float], + 'tan' => ['any' => $rule_float], 'time' => [ 'any' => ['value_type' => 'HHMMSS', 'values' => null] - ] + ], + 'trim' => ['any' => $rule_str], + 'truncate' => ['any' => $rule_float] ]; $expression_parser = new CExpressionParser(['lldmacros' => true]); @@ -2540,3 +2598,12 @@ function makeTriggerDependencies(array $dependencies, $freeze_on_click = true) { function getStandaloneFunctions(): array { return ['date', 'dayofmonth', 'dayofweek', 'time', 'now']; } + +/** + * Returns a list of functions that return a constant or random number. + * + * @return array + */ +function getFunctionsConstants(): array { + return ['e', 'pi', 'rand']; +} diff --git a/ui/templates.php b/ui/templates.php index c9ba79196af..0d0f6089741 100644 --- a/ui/templates.php +++ b/ui/templates.php @@ -516,13 +516,6 @@ if (hasRequest('form')) { $data['tags'] = $data['dbTemplate']['tags']; $data['macros'] = $data['dbTemplate']['macros']; order_result($data['dbTemplate']['valuemaps'], 'name'); - - foreach ($data['dbTemplate']['valuemaps'] as &$valuemap) { - order_result($valuemap['mappings'], 'value'); - $valuemap['mappings'] = array_values($valuemap['mappings']); - } - unset($valuemap); - $data['valuemaps'] = array_values($data['dbTemplate']['valuemaps']); } } diff --git a/ui/tests/api_json/testValuemap.php b/ui/tests/api_json/testValuemap.php index 69c1a82555e..d8cf74bcd8a 100644 --- a/ui/tests/api_json/testValuemap.php +++ b/ui/tests/api_json/testValuemap.php @@ -252,11 +252,206 @@ class testValuemap extends CAPITest { ], 'expected_error' => 'Invalid parameter "/1/mappings/2": value (value)=(0) already exists.' ], + [ + 'valuemap' => [ + [ + 'hostid' => '50009', + 'name' => 'API value is required for type equal', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_EQUAL, + 'newvalue' => 'fail' + ] + ] + ] + ], + 'expected_error' => 'Invalid parameter "/1/mappings/1": the parameter "value" is missing.' + ], + [ + 'valuemap' => [ + [ + 'hostid' => '50009', + 'name' => 'API value is required for type greater or equal', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, + 'newvalue' => 'fail' + ] + ] + ] + ], + 'expected_error' => 'Invalid parameter "/1/mappings/1": the parameter "value" is missing.' + ], + [ + 'valuemap' => [ + [ + 'hostid' => '50009', + 'name' => 'API value is required for type less or equal', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_LESS_EQUAL, + 'newvalue' => 'fail' + ] + ] + ] + ], + 'expected_error' => 'Invalid parameter "/1/mappings/1": the parameter "value" is missing.' + ], + [ + 'valuemap' => [ + [ + 'hostid' => '50009', + 'name' => 'API value is required for type range', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_IN_RANGE, + 'newvalue' => 'fail' + ] + ] + ] + ], + 'expected_error' => 'Invalid parameter "/1/mappings/1": the parameter "value" is missing.' + ], + [ + 'valuemap' => [ + [ + 'hostid' => '50009', + 'name' => 'API value is required for type greater or equal', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_REGEXP, + 'newvalue' => 'fail' + ] + ] + ] + ], + 'expected_error' => 'Invalid parameter "/1/mappings/1": the parameter "value" is missing.' + ], + [ + 'valuemap' => [ + [ + 'hostid' => '50009', + 'name' => 'API value should be empty for type default', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_DEFAULT, + 'value' => 'fail', + 'newvalue' => 'fail' + ] + ] + ] + ], + 'expected_error' => 'Invalid parameter "/1/mappings/1/value": should be empty.' + ], + [ + 'valuemap' => [ + [ + 'hostid' => '50009', + 'name' => 'type and value combination should be unique', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_EQUAL, + 'value' => '10', + 'newvalue' => '1 fail' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_EQUAL, + 'value' => '10', + 'newvalue' => '2 fail' + ] + ] + ] + ], + 'expected_error' => 'Invalid parameter "/1/mappings/2": value (value)=(10) already exists.' + ], + [ + 'valuemap' => [ + [ + 'hostid' => '50009', + 'name' => 'API only one type default is allowed', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_DEFAULT, + 'newvalue' => '1 fail' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_DEFAULT, + 'value' => '', + 'newvalue' => '2 fail' + ] + ] + ] + ], + 'expected_error' => 'Invalid parameter "/1/mappings/2": value (type)=(5) already exists.' + ], + [ + 'valuemap' => [ + [ + 'hostid' => '50009', + 'name' => 'API float value with zero suffix check for geq mapping', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, + 'value' => '7', + 'newvalue' => '1 fail' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, + 'value' => '7.00', + 'newvalue' => '2 fail' + ] + ] + ] + ], + 'expected_error' => 'Invalid parameter "/1/mappings/2": value (value)=(7) already exists.' + ], // Successfully create. [ 'valuemap' => [ [ 'hostid' => '50009', + 'name' => 'API equal value uniqueness check as string for eq mapping', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_EQUAL, + 'value' => '5', + 'newvalue' => '5' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_EQUAL, + 'value' => '5.00', + 'newvalue' => '5.00' + ] + ] + ] + ], + 'expected_error' => null + ], + [ + 'valuemap' => [ + [ + 'hostid' => '50009', + 'name' => 'API float value uniqueness validation for geq mapping', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, + 'value' => '5', + 'newvalue' => '5' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, + 'value' => '5.001', + 'newvalue' => '5.001' + ] + ] + ] + ], + 'expected_error' => null + ], + [ + 'valuemap' => [ + [ + 'hostid' => '50009', 'name' => 'API value map created', 'mappings' => [ [ @@ -326,6 +521,72 @@ class testValuemap extends CAPITest { ] ], 'expected_error' => null + ], + [ + 'valuemap' => [ + [ + 'hostid' => '50009', + 'name' => 'API create value map with mapping types', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_DEFAULT, + 'newvalue' => 'default' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_REGEXP, + 'value' => '/[0-9]{3,}/', + 'newvalue' => 'regexp' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_IN_RANGE, + 'value' => '10-20,-20--10,20,0.5-0.7,-0.7--0.5', + 'newvalue' => 'range' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_LESS_EQUAL, + 'value' => '10', + 'newvalue' => '<=10' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, + 'value' => '10', + 'newvalue' => '>=10' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_EQUAL, + 'value' => '10', + 'newvalue' => '10' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_EQUAL, + 'value' => 'A', + 'newvalue' => 'A' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_EQUAL, + 'value' => '0.5', + 'newvalue' => '0.5' + ] + ] + ] + ], + 'expected_error' => null + ], + [ + 'valuemap' => [ + [ + 'hostid' => '50009', + 'name' => 'API mapping type default value can be empty when defined', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_DEFAULT, + 'value' => '', + 'newvalue' => 'default' + ] + ] + ] + ], + 'expected_error' => null ] ]; } @@ -343,6 +604,7 @@ class testValuemap extends CAPITest { $this->assertEquals($dbRow['name'], $valuemap[$key]['name']); foreach ($valuemap[$key]['mappings'] as $values) { + $values += ['value' => '']; $this->assertEquals(1, CDBHelper::getCount('select * from valuemap_mapping where valuemapid='.zbx_dbstr($id). ' and value='.zbx_dbstr($values['value']).' and newvalue='.zbx_dbstr($values['newvalue'])) ); @@ -611,6 +873,56 @@ class testValuemap extends CAPITest { ] ], 'expected_error' => null + ], + [ + 'valuemap' => [ + [ + 'valuemapid' => '18', + 'name' => 'API update valuemap one', + 'mappings' => [ + [ + 'type' => VALUEMAP_MAPPING_TYPE_DEFAULT, + 'newvalue' => 'default' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_REGEXP, + 'value' => '/[0-9]{3,}/', + 'newvalue' => 'regexp' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_IN_RANGE, + 'value' => '10-20,-20--10,20,0.5-0.7,-0.7--0.5', + 'newvalue' => 'range' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_LESS_EQUAL, + 'value' => '10.5', + 'newvalue' => '<=10.5' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, + 'value' => '20.5', + 'newvalue' => '>=20.5' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_EQUAL, + 'value' => 'A', + 'newvalue' => 'A' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_EQUAL, + 'value' => '10', + 'newvalue' => '10' + ], + [ + 'type' => VALUEMAP_MAPPING_TYPE_EQUAL, + 'value' => '0.5', + 'newvalue' => '0.5' + ] + ] + ] + ], + 'expected_error' => null ] ]; } @@ -628,11 +940,16 @@ class testValuemap extends CAPITest { $this->assertEquals($dbRow['name'], $valuemaps[$key]['name']); if (array_key_exists('mappings', $valuemaps[$key])){ - foreach ($valuemaps[$key]['mappings'] as $values) { - $this->assertEquals(1, CDBHelper::getCount('select * from valuemap_mapping where valuemapid='.zbx_dbstr($id). - ' and value='.zbx_dbstr($values['value']).' and newvalue='. - zbx_dbstr($values['newvalue'])) - ); + $db_mappings = CDBHelper::getAll( + 'SELECT type,value,newvalue'. + ' FROM valuemap_mapping'. + ' WHERE valuemapid='.zbx_dbstr($id). + ' ORDER BY sortorder ASC' + ); + + foreach ($valuemaps[$key]['mappings'] as $i => $mapping) { + $mapping = $mapping + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '']; + $this->assertEquals($mapping, $db_mappings[$i]); } } } diff --git a/ui/tests/integration/IntegrationTests.php b/ui/tests/integration/IntegrationTests.php index 42a9d3c1e43..62731bf3911 100644 --- a/ui/tests/integration/IntegrationTests.php +++ b/ui/tests/integration/IntegrationTests.php @@ -23,6 +23,7 @@ require_once dirname(__FILE__).'/testDiagnosticDataTask.php'; require_once dirname(__FILE__).'/testLowLevelDiscovery.php'; require_once dirname(__FILE__).'/testGoAgentDataCollection.php'; require_once dirname(__FILE__).'/testItemState.php'; +require_once dirname(__FILE__).'/testValuemaps.php'; class IntegrationTests { public static function suite() { @@ -32,6 +33,7 @@ class IntegrationTests { $suite->addTestSuite('testLowLevelDiscovery'); $suite->addTestSuite('testGoAgentDataCollection'); $suite->addTestSuite('testItemState'); + $suite->addTestSuite('testValuemaps'); return $suite; } diff --git a/ui/tests/integration/testValuemaps.php b/ui/tests/integration/testValuemaps.php new file mode 100644 index 00000000000..30003911070 --- /dev/null +++ b/ui/tests/integration/testValuemaps.php @@ -0,0 +1,304 @@ +<?php +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +require_once dirname(__FILE__).'/../include/CIntegrationTest.php'; +require_once dirname(__FILE__).'/../include/helpers/CDataHelper.php'; + +/** + * Test suite for value mapping. + * + * @required-components server + * @hosts test_valuemaps + * @backup history + */ +class testValuemaps extends CIntegrationTest { + const VALUEMAP_NAME = 'valuemap'; + const HOST_NAME = 'test_valuemaps'; + const ITEM_NAME = 'trap'; + + private static $hostid; + private static $itemid; + + /** + * @inheritdoc + */ + public function prepareData() { + // Create host HOST_NAME. + $response = $this->call('host.create', [ + 'host' => self::HOST_NAME, + 'interfaces' => [ + [ + 'type' => 1, + 'main' => 1, + 'useip' => 1, + 'ip' => '127.0.0.1', + 'dns' => '', + 'port' => $this->getConfigurationValue(self::COMPONENT_AGENT, 'ListenPort') + ] + ], + 'groups' => [ + [ + 'groupid' => 4 + ] + ] + ]); + + $this->assertArrayHasKey('hostids', $response['result']); + $this->assertArrayHasKey(0, $response['result']['hostids']); + self::$hostid = $response['result']['hostids'][0]; + // create trapper + $response = $this->call('item.create', [ + 'hostid' => self::$hostid, + 'name' => self::ITEM_NAME, + 'key_' => self::ITEM_NAME, + 'type' => ITEM_TYPE_TRAPPER, + 'value_type' => ITEM_VALUE_TYPE_FLOAT + ]); + $this->assertArrayHasKey('itemids', $response['result']); + $this->assertEquals(1, count($response['result']['itemids'])); + self::$itemid = $response['result']['itemids']; + + return true; + } + + /** + * Data provider (valuemaps). + * + * @return array + */ + public function getValuemaps() { + $valuemap_patterns = [ + 'exactMatch' => [ + 'name' => self::VALUEMAP_NAME, + 'hostid' => null, + 'mappings' => [ + [ + 'value' => '0', + 'newvalue' => 'Value 0' + ], + [ + 'value' => '1', + 'newvalue' => 'Value 1' + ] + ] + ], + 'rangeWithoutDefault' => [ + 'name' => self::VALUEMAP_NAME, + 'hostid' => null, + 'mappings' => [ + [ + 'value' => '0', + 'newvalue' => 'Value 0' + ], + [ + 'value' => '^1$', + 'newvalue' => 'Regexp 1', + 'type' => VALUEMAP_MAPPING_TYPE_REGEXP + ], + [ + 'value' => '3', + 'newvalue' => 'Value <= 3', + 'type' => VALUEMAP_MAPPING_TYPE_LESS_EQUAL + ], + [ + 'value' => '10', + 'newvalue' => 'Value >= 10', + 'type' => VALUEMAP_MAPPING_TYPE_GREATER_EQUAL + ], + [ + 'value' => '5-7,8', + 'newvalue' => 'Range 5-7,8', + 'type' => VALUEMAP_MAPPING_TYPE_IN_RANGE + ] + ] + ], + 'rangeWithDefault' => [ + 'name' => self::VALUEMAP_NAME, + 'hostid' => null, + 'mappings' => [ + [ + 'value' => '-1.2e-1K--3e1, -10, -7--5, -1-1, 5-7.8K', + 'newvalue' => 'Range', + 'type' => VALUEMAP_MAPPING_TYPE_IN_RANGE + ], + [ + 'newvalue' => 'Default', + 'type' => VALUEMAP_MAPPING_TYPE_DEFAULT + ] + ] + ] + ]; + + return [ + [ + 'inputData' => '1', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['exactMatch'], + 'outputData' => 'Value 1 (1)' + ], + [ + 'inputData' => '0', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['exactMatch'], + 'outputData' => 'Value 0 (0)' + ], + [ + 'inputData' => '2', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['exactMatch'], + 'outputData' => '2' + ], + [ + 'inputData' => '0', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['rangeWithoutDefault'], + 'outputData' => 'Value 0 (0)' + ], + [ + 'inputData' => '1', + 'inputType'=> ITEM_VALUE_TYPE_STR, + 'valuemap' => $valuemap_patterns['rangeWithoutDefault'], + 'outputData' => 'Regexp 1 (1)' + ], + [ + 'inputData' => '3', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['rangeWithoutDefault'], + 'outputData' => 'Value <= 3 (3)' + ], + [ + 'inputData' => '5', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['rangeWithoutDefault'], + 'outputData' => 'Range 5-7,8 (5)' + ], + [ + 'inputData' => '8', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['rangeWithoutDefault'], + 'outputData' => 'Range 5-7,8 (8)' + ], + [ + 'inputData' => '9', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['rangeWithoutDefault'], + 'outputData' => '9' + ], + [ + 'inputData' => '10', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['rangeWithoutDefault'], + 'outputData' => 'Value >= 10 (10)' + ], + [ + 'inputData' => '-123', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['rangeWithDefault'], + 'outputData' => 'Default (-123)' + ], + [ + 'inputData' => '-122.88', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['rangeWithDefault'], + 'outputData' => 'Range (-122.88)' + ], + [ + 'inputData' => '-30', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['rangeWithDefault'], + 'outputData' => 'Range (-30)' + ], + [ + 'inputData' => '0', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['rangeWithDefault'], + 'outputData' => 'Range (0)' + ], + [ + 'inputData' => '7987.2', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['rangeWithDefault'], + 'outputData' => 'Range (7987.2)' + ], + [ + 'inputData' => '7988', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['rangeWithDefault'], + 'outputData' => 'Default (7988)' + ], + [ + 'inputData' => '4', + 'inputType'=> ITEM_VALUE_TYPE_FLOAT, + 'valuemap' => $valuemap_patterns['rangeWithDefault'], + 'outputData' => 'Default (4)' + ] + ]; + } + + /** + * Test valuemaps cases. + * + * @dataProvider getValuemaps + */ + public function testValuemaps_checkProblemName($inputData, $inputType, $valuemap, $outputData) { + $valuemap['hostid'] = self::$hostid; + $response = $this->call('valuemap.create', $valuemap); + $this->assertArrayHasKey('valuemapids', $response['result']); + $this->assertEquals(1, count($response['result']['valuemapids'])); + $valuemapid = $response['result']['valuemapids']; + + $response = $this->call('item.update', [ + 'itemid' => self::$itemid[0], + 'valuemapid' => $valuemapid[0], + 'value_type' => $inputType + ]); + $this->assertArrayHasKey('itemids', $response['result']); + $this->assertEquals(1, count($response['result']['itemids'])); + $this->assertEquals(self::$itemid, $response['result']['itemids']); + + $response = $this->call('trigger.create', [ + 'description' => ' {ITEM.VALUE}', + 'expression' => 'last(/'.self::HOST_NAME.'/'.self::ITEM_NAME.')='.$inputData + ]); + $this->assertArrayHasKey('triggerids', $response['result']); + $this->assertEquals(1, count($response['result']['triggerids'])); + $triggerid = $response['result']['triggerids']; + + $this->reloadConfigurationCache(); + + $this->sendSenderValue(self::HOST_NAME, self::ITEM_NAME, $inputData); + + ['result' => $result] = $this->call('problem.get', [ + 'output' => ['name'], + 'objectids' => $triggerid + ]); + + $result = array_column($result, 'name'); + $this->assertEquals(' '.$outputData, $result[0]); + + $response = $this->call('trigger.delete', $triggerid); + $this->assertArrayHasKey('triggerids', $response['result']); + $this->assertEquals($triggerid, $response['result']['triggerids']); + + $response = $this->call('valuemap.delete', $valuemapid); + $this->assertArrayHasKey('valuemapids', $response['result']); + $this->assertEquals($valuemapid, $response['result']['valuemapids']); + } +} diff --git a/ui/tests/selenium/common/testFormValueMappings.php b/ui/tests/selenium/common/testFormValueMappings.php index b2379df8a0a..3af7ab9e1c2 100644 --- a/ui/tests/selenium/common/testFormValueMappings.php +++ b/ui/tests/selenium/common/testFormValueMappings.php @@ -47,18 +47,18 @@ class testFormValueMappings extends CWebTest { const EXISTING_VALUEMAPS = [ [ 'Name' => 'Valuemap for delete', - 'Value' => "four ⇒ 4\noneoneoneoneoneoneoneoneoneoneone ⇒ 11111111111\nthreethreethreethreethreethree". - "threethreethreethree ⇒ 3333333333\n…", + 'Value' => "=oneoneoneoneoneoneoneoneoneoneone\n⇒\n11111111111\n=two\n⇒\n2\n=threethreethreethreethreethree". + "threethreethreethree\n⇒\n3333333333\n…", 'Action' => 'Remove' ], [ 'Name' => 'Valuemap for update 1', - 'Value' => '⇒ reference newvalue', + 'Value' => "=\n⇒\nreference newvalue", 'Action' => 'Remove' ], [ 'Name' => 'Valuemap for update 2', - 'Value' => "⇒ no data\n1 ⇒ one\n2 ⇒ two\n…", + 'Value' => "=\n⇒\nno data\n=1\n⇒\none\n=2\n⇒\ntwo\n…", 'Action' => 'Remove' ] ]; @@ -89,7 +89,7 @@ class testFormValueMappings extends CWebTest { // Check mappings table layout. $mappings_table = $mapping_form->query('id:mappings_table')->asTable()->one(); - $this->assertEquals(['Value', '', 'Mapped to', 'Action'], $mappings_table->getHeadersText()); + $this->assertEquals(['', 'Type', 'Value', '', 'Mapped to', 'Action', ''], $mappings_table->getHeadersText()); $row = $mappings_table->getRow(0); foreach (['Value', 'Mapped to'] as $mapping_column) { $mapping_field = $row->getColumn($mapping_column)->query('xpath:.//input')->one(); @@ -387,11 +387,6 @@ class testFormValueMappings extends CWebTest { } unset($mapping); - // Sort reference mappings array by field "Value". - usort($mappings, function($a, $b) { - return $a['value'] <=> $b['value']; - }); - $mappings_table->checkValue($mappings); } @@ -522,7 +517,7 @@ class testFormValueMappings extends CWebTest { $reference_valuemaps = [ [ 'Name' => $valuemap['name'], - 'Value' => $valuemap['mappings']['value'].' ⇒ '.$valuemap['mappings']['newvalue'], + 'Value' => "=".$valuemap['mappings']['value']."\n⇒\n".$valuemap['mappings']['newvalue'], 'Action' => 'Remove' ] ]; diff --git a/ui/tests/selenium/testFormItem.php b/ui/tests/selenium/testFormItem.php index 2c10e93ce7f..04768acb1ba 100644 --- a/ui/tests/selenium/testFormItem.php +++ b/ui/tests/selenium/testFormItem.php @@ -751,22 +751,22 @@ class testFormItem extends CLegacyWebTest { $mappings = []; $i = 0; foreach ($db_mappings as $db_mapping) { - $mappings_text = $value_mapping->getColumn('Mapping')->getText(); - if ($db_mapping['name'] === $valuemap_name) { - $mappings_text = $value_mapping->getColumn('Mapping')->getText(); - $i++; - // Only the first three mappings are displayed in the form for each value mapping - if ($i < 4) { - $this->assertTrue(str_contains($mappings_text, $db_mapping['value'].' ⇒ '.$db_mapping['newvalue'])); + if ($i < 3) { + $mappings[] = '='.$db_mapping['value'].' ⇒ '.$db_mapping['newvalue']; + $i++; } else { - $this->assertTrue(str_contains($mappings_text, '…')); + $mappings[] = '…'; break; } } } + // Transform newlines in value map table text. + $source = $value_mapping->getColumn('Mapping')->getText(); + $text = rtrim(preg_replace("/(.*)\n⇒\n(.*)\n?/", "\\1 ⇒ \\2\n", $source), "\n"); + $this->assertEquals(implode("\n", $mappings), $text); } } else { diff --git a/ui/tests/selenium/testFormItemPrototype.php b/ui/tests/selenium/testFormItemPrototype.php index 77bb96c6f0c..48288de631d 100644 --- a/ui/tests/selenium/testFormItemPrototype.php +++ b/ui/tests/selenium/testFormItemPrototype.php @@ -951,22 +951,22 @@ class testFormItemPrototype extends CLegacyWebTest { $mappings = []; $i = 0; foreach ($db_mappings as $db_mapping) { - $mappings_text = $value_mapping->getColumn('Mapping')->getText(); - if ($db_mapping['name'] === $valuemap_name) { - $mappings_text = $value_mapping->getColumn('Mapping')->getText(); - $i++; - // Only the first three mappings are displayed in the form for each value mapping - if ($i < 4) { - $this->assertTrue(str_contains($mappings_text, $db_mapping['value'].' ⇒ '.$db_mapping['newvalue'])); + if ($i < 3) { + $mappings[] = '='.$db_mapping['value'].' ⇒ '.$db_mapping['newvalue']; + $i++; } else { - $this->assertTrue(str_contains($mappings_text, '…')); + $mappings[] = '…'; break; } } } + // Transform newlines in value map table text. + $source = $value_mapping->getColumn('Mapping')->getText(); + $text = rtrim(preg_replace("/(.*)\n⇒\n(.*)\n?/", "\\1 ⇒ \\2\n", $source), "\n"); + $this->assertEquals(implode("\n", $mappings), $text); } } else { diff --git a/ui/tests/unit/include/classes/helpers/CValueMapHelperTest.php b/ui/tests/unit/include/classes/helpers/CValueMapHelperTest.php new file mode 100644 index 00000000000..ac0d782e56a --- /dev/null +++ b/ui/tests/unit/include/classes/helpers/CValueMapHelperTest.php @@ -0,0 +1,261 @@ +<?php declare(strict_types=1); +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + + +use PHPUnit\Framework\TestCase; + +class CValueMapHelperTest extends TestCase { + + /** + * Data for testing single rule match. + */ + public function dataMatchMapping(): array { + return [ + // VALUEMAP_MAPPING_TYPE_EQUAL + [ + '.1', ITEM_VALUE_TYPE_FLOAT, + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '0.1', 'newvalue' => 'ok'], + true + ], + [ + '0.2', ITEM_VALUE_TYPE_FLOAT, + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '.2', 'newvalue' => 'ok'], + true + ], + [ + '2', ITEM_VALUE_TYPE_UINT64, + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '2', 'newvalue' => 'ok'], + true + ], + [ + '2', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '2', 'newvalue' => 'ok'], + true + ], + [ + 'match case sensitive', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => 'match case sensitive', 'newvalue' => 'ok'], + true + ], + [ + '1024', ITEM_VALUE_TYPE_UINT64, + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '1K', 'newvalue' => 'ok'], + false + ], + [ + '.1', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '0.1', 'newvalue' => 'ok'], + false + ], + [ + '0.2', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '.2', 'newvalue' => 'ok'], + false + ], + [ + 'match Case Sensitive', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => 'match case sensitive', 'newvalue' => 'ok'], + false + ], + [ + '11 ', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '11', 'newvalue' => 'ok'], + false + ], + [ + '2K', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '2000', 'newvalue' => 'ok'], + false + ], + // VALUEMAP_MAPPING_TYPE_GREATER_EQUAL + [ + '.1', ITEM_VALUE_TYPE_FLOAT, + ['type' => VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, 'value' => '0.1', 'newvalue' => 'ok'], + true + ], + [ + '3', ITEM_VALUE_TYPE_UINT64, + ['type' => VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, 'value' => '0', 'newvalue' => 'ok'], + true + ], + [ + '2048', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, 'value' => '1K', 'newvalue' => 'ok'], + false + ], + [ + '3', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, 'value' => '0', 'newvalue' => 'ok'], + false + ], + // VALUEMAP_MAPPING_TYPE_LESS_EQUAL + [ + '.1', ITEM_VALUE_TYPE_FLOAT, + ['type' => VALUEMAP_MAPPING_TYPE_LESS_EQUAL, 'value' => '10', 'newvalue' => 'ok'], + true + ], + [ + '4', ITEM_VALUE_TYPE_UINT64, + ['type' => VALUEMAP_MAPPING_TYPE_LESS_EQUAL, 'value' => '10', 'newvalue' => 'ok'], + true + ], + [ + '100', ITEM_VALUE_TYPE_UINT64, + ['type' => VALUEMAP_MAPPING_TYPE_LESS_EQUAL, 'value' => '1K', 'newvalue' => 'ok'], + false + ], + // VALUEMAP_MAPPING_TYPE_IN_RANGE + [ + '-5.2', ITEM_VALUE_TYPE_FLOAT, + ['type' => VALUEMAP_MAPPING_TYPE_IN_RANGE, 'value' => '-5.5--5', 'newvalue' => 'ok'], + true + ], + [ + '5', ITEM_VALUE_TYPE_UINT64, + ['type' => VALUEMAP_MAPPING_TYPE_IN_RANGE, 'value' => '1-10', 'newvalue' => 'ok'], + true + ], + [ + '5', ITEM_VALUE_TYPE_UINT64, + ['type' => VALUEMAP_MAPPING_TYPE_IN_RANGE, 'value' => '4-5', 'newvalue' => 'ok'], + true + ], + [ + '5', ITEM_VALUE_TYPE_UINT64, + ['type' => VALUEMAP_MAPPING_TYPE_IN_RANGE, 'value' => '0.5-5.5', 'newvalue' => 'ok'], + true + ], + [ + '5', ITEM_VALUE_TYPE_UINT64, + ['type' => VALUEMAP_MAPPING_TYPE_IN_RANGE, 'value' => '10-30,5', 'newvalue' => 'ok'], + true + ], + [ + '2048', ITEM_VALUE_TYPE_UINT64, + ['type' => VALUEMAP_MAPPING_TYPE_IN_RANGE, 'value' => '2K-2.5K', 'newvalue' => 'ok'], + true + ], + [ + '1K', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_IN_RANGE, 'value' => '1000-2000', 'newvalue' => 'ok'], + false + ], + // VALUEMAP_MAPPING_TYPE_REGEXP + [ + '12', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_REGEXP, 'value' => '\d{2}', 'newvalue' => 'ok'], + true + ], + [ + 'test two words', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_REGEXP, 'value' => 'test(\s\w+){2}', 'newvalue' => 'ok'], + true + ], + [ + 'test/ slash escaped', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_REGEXP, 'value' => 'test/(\s\w+){2}', 'newvalue' => 'ok'], + true + ], + [ + '12', ITEM_VALUE_TYPE_UINT64, + ['type' => VALUEMAP_MAPPING_TYPE_REGEXP, 'value' => '\d{2}', 'newvalue' => 'ok'], + false + ], + [ + 'test no modifiers', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_REGEXP, 'value' => '/test(\s\w+){2}/i', 'newvalue' => 'ok'], + false + ], + // VALUEMAP_MAPPING_TYPE_DEFAULT + [ + '12', ITEM_VALUE_TYPE_UINT64, + ['type' => VALUEMAP_MAPPING_TYPE_DEFAULT, 'value' => '', 'newvalue' => 'ok'], + true + ], + [ + '128K', ITEM_VALUE_TYPE_UINT64, + ['type' => VALUEMAP_MAPPING_TYPE_DEFAULT, 'value' => '', 'newvalue' => 'ok'], + true + ], + [ + 'any should match', ITEM_VALUE_TYPE_STR, + ['type' => VALUEMAP_MAPPING_TYPE_DEFAULT, 'value' => '', 'newvalue' => 'ok'], + true + ] + ]; + } + + /** + * @dataProvider dataMatchMapping + * + * @param string $value + * @param int $value_type + * @param array $mapping + * @param bool $expected + */ + public function testMatchMapping(string $value, int $value_type, array $mapping, bool $expected) { + $this->assertSame(CValueMapHelper::matchMapping($value_type, $value, $mapping), $expected); + } + + /** + * Data for testing multiple mappings. + */ + public function dataMatchMappingOrder(): array { + return [ + ['1', [], false], + [ + '1', + [ + ['type' => VALUEMAP_MAPPING_TYPE_DEFAULT, 'newvalue' => 'newvalue-1'], + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '10', 'newvalue' => 'newvalue-2'] + ], + 'newvalue-1' + ], + [ + '10', + [ + ['type' => VALUEMAP_MAPPING_TYPE_DEFAULT, 'newvalue' => 'newvalue-1'], + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '10', 'newvalue' => 'newvalue-2'] + ], + 'newvalue-2' + ], + [ + '10', + [ + ['type' => VALUEMAP_MAPPING_TYPE_DEFAULT, 'newvalue' => 'newvalue-1'], + ['type' => VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, 'value' => '10', 'newvalue' => 'newvalue-2'], + ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => '10', 'newvalue' => 'newvalue-3'] + ], + 'newvalue-2' + ] + ]; + } + + /** + * @dataProvider dataMatchMappingOrder + * + * @param string $value + * @param array $mapping + * @param string|bool $expected + */ + public function testMatchMappingOrder(string $value, array $mappings, $expected) { + $this->assertSame(CValueMapHelper::getMappedValue(ITEM_VALUE_TYPE_UINT64, $value, ['mappings' => $mappings]), $expected); + } +} diff --git a/ui/tests/unit/include/classes/parsers/CRangeParserTest.php b/ui/tests/unit/include/classes/parsers/CRangeParserTest.php index 2959290dfaa..dfdce85448e 100644 --- a/ui/tests/unit/include/classes/parsers/CRangeParserTest.php +++ b/ui/tests/unit/include/classes/parsers/CRangeParserTest.php @@ -27,6 +27,10 @@ class CRangeParserTest extends TestCase { * An array of time periods and parsed results. */ public static function dataProvider() { + $negative = ['with_minus' => true]; + $float = ['with_float' => true]; + $suffix= ['with_suffix' => true]; + return [ // success [ @@ -313,6 +317,102 @@ class CRangeParserTest extends TestCase { 'range' => ['{{#M}.regsub("^([0-9]+)", "{#M}: \1")}', '{$M}'] ] ], + [ + '-20--10', 0, $negative, + [ + 'rc' => CParser::PARSE_SUCCESS, + 'match' => '-20--10', + 'range' => ['-20', '-10'] + ] + ], + [ + ' -20 - -10 ', 0, $negative, + [ + 'rc' => CParser::PARSE_SUCCESS, + 'match' => ' -20 - -10 ', + 'range' => ['-20', '-10'] + ] + ], + [ + '20.0-30.0000', 0, $float, + [ + 'rc' => CParser::PARSE_SUCCESS, + 'match' => '20.0-30.0000', + 'range' => ['20.0', '30.0000'] + ] + ], + [ + ' 20.0 - 30.0000 ', 0, $float, + [ + 'rc' => CParser::PARSE_SUCCESS, + 'match' => ' 20.0 - 30.0000 ', + 'range' => ['20.0', '30.0000'] + ] + ], + [ + '-20.0--10.0', 0, $float + $negative, + [ + 'rc' => CParser::PARSE_SUCCESS, + 'match' => '-20.0--10.0', + 'range' => ['-20.0', '-10.0'] + ] + ], + [ + '-2.0K--1.0K', 0, $float + $negative + $suffix, + [ + 'rc' => CParser::PARSE_SUCCESS, + 'match' => '-2.0K--1.0K', + 'range' => [strval(ZBX_KIBIBYTE * -2), strval(ZBX_KIBIBYTE * -1)] + ] + ], + [ + '1h-1.5h', 0, $float + $suffix, + [ + 'rc' => CParser::PARSE_SUCCESS, + 'match' => '1h-1.5h', + 'range' => [strval(SEC_PER_HOUR), strval(SEC_PER_HOUR * 1.5)] + ] + ], + [ + '.5K-1K', 0, $float + $suffix, + [ + 'rc' => CParser::PARSE_SUCCESS, + 'match' => '.5K-1K', + 'range' => [strval(ZBX_KIBIBYTE * 0.5), strval(ZBX_KIBIBYTE * 1)] + ] + ], + [ + '.2-10', 0, $float, + [ + 'rc' => CParser::PARSE_SUCCESS, + 'match' => '.2-10', + 'range' => ['.2', '10'] + ] + ], + [ + '0.2-10', 0, $float, + [ + 'rc' => CParser::PARSE_SUCCESS, + 'match' => '0.2-10', + 'range' => ['0.2', '10'] + ] + ], + [ + '{$M}-10', 0, ['usermacros' => true] + $float, + [ + 'rc' => CParser::PARSE_SUCCESS, + 'match' => '{$M}-10', + 'range' => ['{$M}', '10'] + ] + ], + [ + '{#M}-10.0', 0, ['lldmacros' => true] + $float, + [ + 'rc' => CParser::PARSE_SUCCESS, + 'match' => '{#M}-10.0', + 'range' => ['{#M}', '10.0'] + ] + ], // partial success [ 'random text.....0....text', 16, [], @@ -603,6 +703,54 @@ class CRangeParserTest extends TestCase { 'range' => ['100'] ] ], + [ + '20-30.001', 0, [], + [ + 'rc' => CParser::PARSE_SUCCESS_CONT, + 'match' => '20-30', + 'range' => ['20', '30'] + ] + ], + [ + '20--30.001', 0, [], + [ + 'rc' => CParser::PARSE_SUCCESS_CONT, + 'match' => '20', + 'range' => ['20'] + ] + ], + [ + '10.00-.2', 0, $float, + [ + 'rc' => CParser::PARSE_SUCCESS_CONT, + 'match' => '10.00', + 'range' => ['10.00'] + ] + ], + [ + '10.00-', 0, [], + [ + 'rc' => CParser::PARSE_SUCCESS_CONT, + 'match' => '10', + 'range' => ['10'] + ] + ], + [ + '{$M}-10.0', 0, ['usermacros' => true], + [ + 'rc' => CParser::PARSE_SUCCESS_CONT, + 'match' => '{$M}-10', + 'range' => ['{$M}', '10'] + ] + ], + [ + '{#M}-10K', 0, ['lldmacros' => true], + [ + 'rc' => CParser::PARSE_SUCCESS_CONT, + 'match' => '{#M}-10', + 'range' => ['{#M}', '10'] + ] + ], // fail [ '', 0, [], @@ -749,6 +897,22 @@ class CRangeParserTest extends TestCase { 'match' => '', 'range' => [] ] + ], + [ + '-10.2--20.3', 0, [], + [ + 'rc' => CParser::PARSE_FAIL, + 'match' => '', + 'range' => [] + ] + ], + [ + '.2', 0, [], + [ + 'rc' => CParser::PARSE_FAIL, + 'match' => '', + 'range' => [] + ] ] ]; } diff --git a/ui/tests/unit/include/classes/validators/CApiInputValidatorTest.php b/ui/tests/unit/include/classes/validators/CApiInputValidatorTest.php index a4bc6e61301..fc6dacd80fc 100644 --- a/ui/tests/unit/include/classes/validators/CApiInputValidatorTest.php +++ b/ui/tests/unit/include/classes/validators/CApiInputValidatorTest.php @@ -3882,6 +3882,78 @@ class CApiInputValidatorTest extends TestCase { '2038-01-18' ], [ + ['type' => API_NUMERIC_RANGES], + null, + '/1/numeric_ranges', + 'Invalid parameter "/1/numeric_ranges": a character string is expected.' + ], + [ + ['type' => API_NUMERIC_RANGES], + '', + '/1/numeric_ranges', + '' + ], + [ + ['type' => API_NUMERIC_RANGES, 'flags' => API_NOT_EMPTY], + '', + '/1/numeric_ranges', + 'Invalid parameter "/1/numeric_ranges": cannot be empty.' + ], + [ + ['type' => API_NUMERIC_RANGES, 'length' => 5], + '12-15', + '/1/numeric_ranges', + '12-15' + ], + [ + ['type' => API_NUMERIC_RANGES, 'length' => 5], + '12-150', + '/1/numeric_ranges', + 'Invalid parameter "/1/numeric_ranges": value is too long.' + ], + [ + ['type' => API_NUMERIC_RANGES], + [], + '/1/numeric_ranges', + 'Invalid parameter "/1/numeric_ranges": a character string is expected.' + ], + [ + ['type' => API_NUMERIC_RANGES], + true, + '/1/numeric_ranges', + 'Invalid parameter "/1/numeric_ranges": a character string is expected.' + ], + [ + ['type' => API_NUMERIC_RANGES], + false, + '/1/numeric_ranges', + 'Invalid parameter "/1/numeric_ranges": a character string is expected.' + ], + [ + ['type' => API_NUMERIC_RANGES], + 'aaa', + '/1/numeric_ranges', + 'Invalid parameter "/1/numeric_ranges": invalid range expression.' + ], + [ + ['type' => API_NUMERIC_RANGES], + '123', + '/1/numeric_ranges', + '123' + ], + [ + ['type' => API_NUMERIC_RANGES], + '-5', + '/1/numeric_ranges', + '-5' + ], + [ + ['type' => API_NUMERIC_RANGES], + '20.0-30.0000', + '/1/numeric_ranges', + '20.0-30.0000' + ], + [ ['type' => API_UUID], null, '/uuid', diff --git a/ui/tests/unit/include/classes/validators/CHistFunctionValidatorTest.php b/ui/tests/unit/include/classes/validators/CHistFunctionValidatorTest.php index bc7c61fd059..0f4e6c92c91 100644 --- a/ui/tests/unit/include/classes/validators/CHistFunctionValidatorTest.php +++ b/ui/tests/unit/include/classes/validators/CHistFunctionValidatorTest.php @@ -60,7 +60,6 @@ class CHistFunctionValidatorTest extends TestCase { ['avg(/host/key, 596524h)', [], ['rc' => false, 'error' => 'invalid second parameter in function "avg"']], ['avg(/host/key, 24856d)', [], ['rc' => false, 'error' => 'invalid second parameter in function "avg"']], ['avg(/host/key, 3551w)', [], ['rc' => false, 'error' => 'invalid second parameter in function "avg"']], - ['avg(/host/key, #256,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "avg"']], ['min(/host/key)', [], ['rc' => false, 'error' => 'mandatory parameter is missing in function "min"']], @@ -111,6 +110,134 @@ class CHistFunctionValidatorTest extends TestCase { ['sum(/host/key, 2147483647)', [], ['rc' => true, 'error' => null]], ['sum(/host/key, #256,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "sum"']], + ['kurtosis(/host/key)', [], ['rc' => false, 'error' => 'mandatory parameter is missing in function "kurtosis"']], + ['kurtosis(/host/key,)', [], ['rc' => false, 'error' => 'invalid second parameter in function "kurtosis"']], + ['kurtosis(/host/key,0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "kurtosis"']], + ['kurtosis(/host/key,#0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "kurtosis"']], + ['kurtosis(/host/key,1)', [], ['rc' => true, 'error' => null]], + ['kurtosis(/host/key,#1)', [], ['rc' => true, 'error' => null]], + ['kurtosis(/host/key,1s)', [], ['rc' => true, 'error' => null]], + ['kurtosis(/host/key, 1m)', [], ['rc' => true, 'error' => null]], + ['kurtosis(/host/key, 1M)', [], ['rc' => false, 'error' => 'invalid second parameter in function "kurtosis"']], + ['kurtosis(/host/key, 1m:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['kurtosis(/host/key, #3:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['kurtosis(/host/key, #5:now/M)', [], ['rc' => true, 'error' => null]], + ['kurtosis(/host/key, #2147483647)', [], ['rc' => true, 'error' => null]], + ['kurtosis(/host/key, 2147483647)', [], ['rc' => true, 'error' => null]], + ['kurtosis(/host/key, #256,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "kurtosis"']], + + ['mad(/host/key)', [], ['rc' => false, 'error' => 'mandatory parameter is missing in function "mad"']], + ['mad(/host/key,)', [], ['rc' => false, 'error' => 'invalid second parameter in function "mad"']], + ['mad(/host/key,0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "mad"']], + ['mad(/host/key,#0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "mad"']], + ['mad(/host/key,1)', [], ['rc' => true, 'error' => null]], + ['mad(/host/key,#1)', [], ['rc' => true, 'error' => null]], + ['mad(/host/key,1s)', [], ['rc' => true, 'error' => null]], + ['mad(/host/key, 1m)', [], ['rc' => true, 'error' => null]], + ['mad(/host/key, 1M)', [], ['rc' => false, 'error' => 'invalid second parameter in function "mad"']], + ['mad(/host/key, 1m:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['mad(/host/key, #3:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['mad(/host/key, #5:now/M)', [], ['rc' => true, 'error' => null]], + ['mad(/host/key, #2147483647)', [], ['rc' => true, 'error' => null]], + ['mad(/host/key, 2147483647)', [], ['rc' => true, 'error' => null]], + ['mad(/host/key, #256,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "mad"']], + + ['skewness(/host/key)', [], ['rc' => false, 'error' => 'mandatory parameter is missing in function "skewness"']], + ['skewness(/host/key,)', [], ['rc' => false, 'error' => 'invalid second parameter in function "skewness"']], + ['skewness(/host/key,0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "skewness"']], + ['skewness(/host/key,#0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "skewness"']], + ['skewness(/host/key,1)', [], ['rc' => true, 'error' => null]], + ['skewness(/host/key,#1)', [], ['rc' => true, 'error' => null]], + ['skewness(/host/key,1s)', [], ['rc' => true, 'error' => null]], + ['skewness(/host/key, 1m)', [], ['rc' => true, 'error' => null]], + ['skewness(/host/key, 1M)', [], ['rc' => false, 'error' => 'invalid second parameter in function "skewness"']], + ['skewness(/host/key, 1m:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['skewness(/host/key, #3:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['skewness(/host/key, #5:now/M)', [], ['rc' => true, 'error' => null]], + ['skewness(/host/key, #2147483647)', [], ['rc' => true, 'error' => null]], + ['skewness(/host/key, 2147483647)', [], ['rc' => true, 'error' => null]], + ['skewness(/host/key, #256,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "skewness"']], + + ['stddevpop(/host/key)', [], ['rc' => false, 'error' => 'mandatory parameter is missing in function "stddevpop"']], + ['stddevpop(/host/key,)', [], ['rc' => false, 'error' => 'invalid second parameter in function "stddevpop"']], + ['stddevpop(/host/key,0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "stddevpop"']], + ['stddevpop(/host/key,#0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "stddevpop"']], + ['stddevpop(/host/key,1)', [], ['rc' => true, 'error' => null]], + ['stddevpop(/host/key,#1)', [], ['rc' => true, 'error' => null]], + ['stddevpop(/host/key,1s)', [], ['rc' => true, 'error' => null]], + ['stddevpop(/host/key, 1m)', [], ['rc' => true, 'error' => null]], + ['stddevpop(/host/key, 1M)', [], ['rc' => false, 'error' => 'invalid second parameter in function "stddevpop"']], + ['stddevpop(/host/key, 1m:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['stddevpop(/host/key, #3:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['stddevpop(/host/key, #5:now/M)', [], ['rc' => true, 'error' => null]], + ['stddevpop(/host/key, #2147483647)', [], ['rc' => true, 'error' => null]], + ['stddevpop(/host/key, 2147483647)', [], ['rc' => true, 'error' => null]], + ['stddevpop(/host/key, #256,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "stddevpop"']], + + ['stddevsamp(/host/key)', [], ['rc' => false, 'error' => 'mandatory parameter is missing in function "stddevsamp"']], + ['stddevsamp(/host/key,)', [], ['rc' => false, 'error' => 'invalid second parameter in function "stddevsamp"']], + ['stddevsamp(/host/key,0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "stddevsamp"']], + ['stddevsamp(/host/key,#0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "stddevsamp"']], + ['stddevsamp(/host/key,1)', [], ['rc' => true, 'error' => null]], + ['stddevsamp(/host/key,#1)', [], ['rc' => true, 'error' => null]], + ['stddevsamp(/host/key,1s)', [], ['rc' => true, 'error' => null]], + ['stddevsamp(/host/key, 1m)', [], ['rc' => true, 'error' => null]], + ['stddevsamp(/host/key, 1M)', [], ['rc' => false, 'error' => 'invalid second parameter in function "stddevsamp"']], + ['stddevsamp(/host/key, 1m:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['stddevsamp(/host/key, #3:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['stddevsamp(/host/key, #5:now/M)', [], ['rc' => true, 'error' => null]], + ['stddevsamp(/host/key, #2147483647)', [], ['rc' => true, 'error' => null]], + ['stddevsamp(/host/key, 2147483647)', [], ['rc' => true, 'error' => null]], + ['stddevsamp(/host/key, #256,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "stddevsamp"']], + + ['sumofsquares(/host/key)', [], ['rc' => false, 'error' => 'mandatory parameter is missing in function "sumofsquares"']], + ['sumofsquares(/host/key,)', [], ['rc' => false, 'error' => 'invalid second parameter in function "sumofsquares"']], + ['sumofsquares(/host/key,0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "sumofsquares"']], + ['sumofsquares(/host/key,#0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "sumofsquares"']], + ['sumofsquares(/host/key,1)', [], ['rc' => true, 'error' => null]], + ['sumofsquares(/host/key,#1)', [], ['rc' => true, 'error' => null]], + ['sumofsquares(/host/key,1s)', [], ['rc' => true, 'error' => null]], + ['sumofsquares(/host/key, 1m)', [], ['rc' => true, 'error' => null]], + ['sumofsquares(/host/key, 1M)', [], ['rc' => false, 'error' => 'invalid second parameter in function "sumofsquares"']], + ['sumofsquares(/host/key, 1m:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['sumofsquares(/host/key, #3:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['sumofsquares(/host/key, #5:now/M)', [], ['rc' => true, 'error' => null]], + ['sumofsquares(/host/key, #2147483647)', [], ['rc' => true, 'error' => null]], + ['sumofsquares(/host/key, 2147483647)', [], ['rc' => true, 'error' => null]], + ['sumofsquares(/host/key, #256,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "sumofsquares"']], + + ['varpop(/host/key)', [], ['rc' => false, 'error' => 'mandatory parameter is missing in function "varpop"']], + ['varpop(/host/key,)', [], ['rc' => false, 'error' => 'invalid second parameter in function "varpop"']], + ['varpop(/host/key,0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "varpop"']], + ['varpop(/host/key,#0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "varpop"']], + ['varpop(/host/key,1)', [], ['rc' => true, 'error' => null]], + ['varpop(/host/key,#1)', [], ['rc' => true, 'error' => null]], + ['varpop(/host/key,1s)', [], ['rc' => true, 'error' => null]], + ['varpop(/host/key, 1m)', [], ['rc' => true, 'error' => null]], + ['varpop(/host/key, 1M)', [], ['rc' => false, 'error' => 'invalid second parameter in function "varpop"']], + ['varpop(/host/key, 1m:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['varpop(/host/key, #3:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['varpop(/host/key, #5:now/M)', [], ['rc' => true, 'error' => null]], + ['varpop(/host/key, #2147483647)', [], ['rc' => true, 'error' => null]], + ['varpop(/host/key, 2147483647)', [], ['rc' => true, 'error' => null]], + ['varpop(/host/key, #256,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "varpop"']], + + ['varsamp(/host/key)', [], ['rc' => false, 'error' => 'mandatory parameter is missing in function "varsamp"']], + ['varsamp(/host/key,)', [], ['rc' => false, 'error' => 'invalid second parameter in function "varsamp"']], + ['varsamp(/host/key,0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "varsamp"']], + ['varsamp(/host/key,#0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "varsamp"']], + ['varsamp(/host/key,1)', [], ['rc' => true, 'error' => null]], + ['varsamp(/host/key,#1)', [], ['rc' => true, 'error' => null]], + ['varsamp(/host/key,1s)', [], ['rc' => true, 'error' => null]], + ['varsamp(/host/key, 1m)', [], ['rc' => true, 'error' => null]], + ['varsamp(/host/key, 1M)', [], ['rc' => false, 'error' => 'invalid second parameter in function "varsamp"']], + ['varsamp(/host/key, 1m:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['varsamp(/host/key, #3:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['varsamp(/host/key, #5:now/M)', [], ['rc' => true, 'error' => null]], + ['varsamp(/host/key, #2147483647)', [], ['rc' => true, 'error' => null]], + ['varsamp(/host/key, 2147483647)', [], ['rc' => true, 'error' => null]], + ['varsamp(/host/key, #256,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "varsamp"']], + ['change(/host/key)', [], ['rc' => true, 'error' => null]], ['change(/host/key,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "change"']], @@ -226,6 +353,38 @@ class CHistFunctionValidatorTest extends TestCase { ['count(/host/key, #256, "regexp", {#LLDMACRO})', [], ['rc' => true, 'error' => null]], ['count(/host/key, #256, "regexp",,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "count"']], + ['countunique(/host/key)', [], ['rc' => false, 'error' => 'mandatory parameter is missing in function "countunique"']], + ['countunique(/host/key,)', [], ['rc' => false, 'error' => 'invalid second parameter in function "countunique"']], + ['countunique(/host/key,0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "countunique"']], + ['countunique(/host/key,#0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "countunique"']], + ['countunique(/host/key,1)', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key,#1)', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key,1s)', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, 1m)', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, 1M)', [], ['rc' => false, 'error' => 'invalid second parameter in function "countunique"']], + ['countunique(/host/key, 1m:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #3:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #5:now/M)', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #2147483647)', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, 2147483647)', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256,)', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256, "str")', [], ['rc' => false, 'error' => 'invalid third parameter in function "countunique"']], + ['countunique(/host/key, #256, 10)', [], ['rc' => false, 'error' => 'invalid third parameter in function "countunique"']], + ['countunique(/host/key, #256, 10K)', [], ['rc' => false, 'error' => 'invalid third parameter in function "countunique"']], + ['countunique(/host/key, #256, "eq")', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256, "ne")', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256, "like")', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256, "bitand")', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256, "{$MACRO}{$LLDMACRO}")', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256, {$MACRO})', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256, {$LLDMACRO})', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256, "regexp",)', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256, "regexp", 100)', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256, "regexp", 1s)', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256, "regexp", {$MACRO})', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256, "regexp", {#LLDMACRO})', [], ['rc' => true, 'error' => null]], + ['countunique(/host/key, #256, "regexp",,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "countunique"']], + ['find(/host/key)', [], ['rc' => true, 'error' => null]], ['find(/host/key,)', [], ['rc' => true, 'error' => null]], ['find(/host/key,0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "find"']], @@ -258,6 +417,22 @@ class CHistFunctionValidatorTest extends TestCase { ['find(/host/key, #256, "regexp", {#LLDMACRO})', [], ['rc' => true, 'error' => null]], ['find(/host/key, #256, "regexp",,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "find"']], + ['first(/host/key)', [], ['rc' => false, 'error' => 'mandatory parameter is missing in function "first"']], + ['first(/host/key,)', [], ['rc' => false, 'error' => 'invalid second parameter in function "first"']], + ['first(/host/key,0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "first"']], + ['first(/host/key,#0)', [], ['rc' => false, 'error' => 'invalid second parameter in function "first"']], + ['first(/host/key,1)', [], ['rc' => true, 'error' => null]], + ['first(/host/key,#1)', [], ['rc' => false, 'error' => 'invalid second parameter in function "first"']], + ['first(/host/key,1s)', [], ['rc' => true, 'error' => null]], + ['first(/host/key, 1m)', [], ['rc' => true, 'error' => null]], + ['first(/host/key, 1M)', [], ['rc' => false, 'error' => 'invalid second parameter in function "first"']], + ['first(/host/key, 1m:now/h-1h)', [], ['rc' => true, 'error' => null]], + ['first(/host/key, #3:now/h-1h)', [], ['rc' => false, 'error' => 'invalid second parameter in function "first"']], + ['first(/host/key, #5:now/M)', [], ['rc' => false, 'error' => 'invalid second parameter in function "first"']], + ['first(/host/key, #2147483647)', [], ['rc' => false, 'error' => 'invalid second parameter in function "first"']], + ['first(/host/key, 2147483647)', [], ['rc' => true, 'error' => null]], + ['first(/host/key, 1,)', [], ['rc' => false, 'error' => 'invalid number of parameters in function "first"']], + ['forecast(/host/key)', [], ['rc' => false, 'error' => 'mandatory parameter is missing in function "forecast"']], ['forecast(/host/key,)', [], ['rc' => false, 'error' => 'invalid second parameter in function "forecast"']], ['forecast(/host/key,,10h)', [], ['rc' => false, 'error' => 'invalid second parameter in function "forecast"']], |