diff --git a/README.rst b/README.rst index 1e7442d90..223c4b980 100644 --- a/README.rst +++ b/README.rst @@ -2088,6 +2088,19 @@ In case you just want to change the colors used in a chart here's how to do it: } } +``OPENWISP_MONITORING_DEFAULT_CHART_TIME`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ++---------------------+---------------------------------------------+ +| **type**: | ``str`` | ++---------------------+---------------------------------------------+ +| **default**: | ``7d`` | ++---------------------+---------------------------------------------+ +| **possible values** | ``1d``, ``3d``, ``7d``, ``30d`` or ``365d`` | ++---------------------+---------------------------------------------+ + +Allows to set the default time period of the time series charts. + ``OPENWISP_MONITORING_AUTO_CLEAR_MANAGEMENT_IP`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/openwisp_monitoring/db/backends/influxdb/client.py b/openwisp_monitoring/db/backends/influxdb/client.py index 338258c9d..906769a00 100644 --- a/openwisp_monitoring/db/backends/influxdb/client.py +++ b/openwisp_monitoring/db/backends/influxdb/client.py @@ -256,7 +256,24 @@ def read(self, key, fields, tags, **kwargs): return list(self.query(q, precision='s').get_points()) def get_list_query(self, query, precision='s'): - return list(self.query(query, precision=precision).get_points()) + result = self.query(query, precision=precision) + if not len(result.keys()) or result.keys()[0][1] is None: + return list(result.get_points()) + # Handles query which contains "GROUP BY TAG" clause + result_points = {} + for (measurement, tag), group_points in result.items(): + tag_suffix = '_'.join(tag.values()) + for point in group_points: + values = {} + for key, value in point.items(): + if key != 'time': + values[tag_suffix] = value + values['time'] = point['time'] + try: + result_points[values['time']].update(values) + except KeyError: + result_points[values['time']] = values + return list(result_points.values()) @retry def get_list_retention_policies(self): @@ -329,7 +346,12 @@ def get_query( query = f'{query} LIMIT 1' return f"{query} tz('{timezone}')" - _group_by_regex = re.compile(r'GROUP BY time\(\w+\)', flags=re.IGNORECASE) + _group_by_time_tag_regex = re.compile( + r'GROUP BY ((time\(\w+\))(?:,\s+\w+)?)', flags=re.IGNORECASE + ) + _group_by_time_regex = re.compile(r'GROUP BY time\(\w+\)\s?', flags=re.IGNORECASE) + _time_regex = re.compile(r'time\(\w+\)\s?', flags=re.IGNORECASE) + _time_comma_regex = re.compile(r'time\(\w+\),\s?', flags=re.IGNORECASE) def _group_by(self, query, time, chart_type, group_map, strip=False): if not self.validate_query(query): @@ -343,7 +365,27 @@ def _group_by(self, query, time, chart_type, group_map, strip=False): if 'GROUP BY' not in query.upper(): query = f'{query} {group_by}' else: - query = re.sub(self._group_by_regex, group_by, query) + # The query could have GROUP BY clause for a TAG + if group_by: + # The query already contains "GROUP BY", therefore + # we remove it from the "group_by" to avoid duplicating + # "GROUP BY" + group_by = group_by.replace('GROUP BY ', '') + # We only need to substitute the time function. + # The resulting query would be "GROUP BY time(), " + query = re.sub(self._time_regex, group_by, query) + else: + # The query should not include the "GROUP by time()" + matches = re.search(self._group_by_time_tag_regex, query) + group_by_fields = matches.group(1) + if len(group_by_fields.split(',')) > 1: + # If the query has "GROUP BY time(), tag", + # then return "GROUP BY tag" + query = re.sub(self._time_comma_regex, '', query) + else: + # If the query has only has "GROUP BY time()", + # then remove the "GROUP BY" clause + query = re.sub(self._group_by_time_regex, '', query) return query _fields_regex = re.compile( diff --git a/openwisp_monitoring/db/backends/influxdb/tests.py b/openwisp_monitoring/db/backends/influxdb/tests.py index c87d09c59..508bd3bc8 100644 --- a/openwisp_monitoring/db/backends/influxdb/tests.py +++ b/openwisp_monitoring/db/backends/influxdb/tests.py @@ -190,6 +190,48 @@ def test_get_query_30d(self): self.assertIn(str(last30d)[0:10], q) self.assertIn('group by time(24h)', q.lower()) + def test_group_by_tags(self): + self.assertEqual( + timeseries_db._group_by( + 'SELECT COUNT(item) FROM measurement GROUP BY time(1d)', + time='30d', + chart_type='stackedbar+lines', + group_map={'30d': '30d'}, + strip=False, + ), + 'SELECT COUNT(item) FROM measurement GROUP BY time(30d)', + ) + self.assertEqual( + timeseries_db._group_by( + 'SELECT COUNT(item) FROM measurement GROUP BY time(1d)', + time='30d', + chart_type='stackedbar+lines', + group_map={'30d': '30d'}, + strip=True, + ), + 'SELECT COUNT(item) FROM measurement ', + ) + self.assertEqual( + timeseries_db._group_by( + 'SELECT COUNT(item) FROM measurement GROUP BY time(1d), tag', + time='30d', + chart_type='stackedbar+lines', + group_map={'30d': '30d'}, + strip=False, + ), + 'SELECT COUNT(item) FROM measurement GROUP BY time(30d), tag', + ) + self.assertEqual( + timeseries_db._group_by( + 'SELECT COUNT(item) FROM measurement GROUP BY time(1d), tag', + time='30d', + chart_type='stackedbar+lines', + group_map={'30d': '30d'}, + strip=True, + ), + 'SELECT COUNT(item) FROM measurement GROUP BY tag', + ) + def test_retention_policy(self): manage_short_retention_policy() manage_default_retention_policy() diff --git a/openwisp_monitoring/device/apps.py b/openwisp_monitoring/device/apps.py index 739709878..3fb18463e 100644 --- a/openwisp_monitoring/device/apps.py +++ b/openwisp_monitoring/device/apps.py @@ -415,8 +415,11 @@ def register_dashboard_items(self): 'monitoring/css/percircle.min.css', 'monitoring/css/chart.css', 'monitoring/css/dashboard-chart.css', + 'admin/css/vendor/select2/select2.min.css', + 'admin/css/autocomplete.css', ), 'js': ( + 'admin/js/vendor/select2/select2.full.min.js', 'monitoring/js/lib/moment.min.js', 'monitoring/js/lib/daterangepicker.min.js', 'monitoring/js/lib/percircle.min.js', diff --git a/openwisp_monitoring/device/static/monitoring/css/wifi-sessions.css b/openwisp_monitoring/device/static/monitoring/css/wifi-sessions.css index 42b06130c..d0c57ebae 100644 --- a/openwisp_monitoring/device/static/monitoring/css/wifi-sessions.css +++ b/openwisp_monitoring/device/static/monitoring/css/wifi-sessions.css @@ -1,10 +1,6 @@ td.original p { display: none; } -#inline-wifisession-quick-link-container { - text-align: center; - margin: 20px 0 40px; -} #wifisession_set-group { margin-bottom: 0px; } diff --git a/openwisp_monitoring/device/static/monitoring/js/wifi-session-inline.js b/openwisp_monitoring/device/static/monitoring/js/wifi-session-inline.js index af51281a4..add627ed5 100644 --- a/openwisp_monitoring/device/static/monitoring/js/wifi-session-inline.js +++ b/openwisp_monitoring/device/static/monitoring/js/wifi-session-inline.js @@ -27,7 +27,7 @@ if (typeof gettext === 'undefined') { wifiSessionLinkElement; wifiSessionUrl = `${wifiSessionUrl}?device=${getObjectIdFromUrl()}`; wifiSessionLinkElement = ` -