Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
This project adheres to `Semantic Versioning <http://semver.org/>`_.
Please note that the changes before version 1.10.0 have not been documented.

v5.0.2
----------
**Bug Fix Release**

Fixed
^^^^^
- **Build**: Fixed broken 5.0.0 release - Angular frontend is now properly built before publishing to PyPI
- **Deprecation**: Replaced deprecated ``datetime.utcnow()`` with ``datetime.now(timezone.utc)`` throughout codebase
- **Deprecation**: Replaced deprecated ``datetime.utcfromtimestamp()`` with ``datetime.fromtimestamp(..., tz=timezone.utc)``
- **Deprecation**: Replaced ``datetime.UTC`` with ``datetime.timezone.utc`` for Python 3.11+ compatibility
- **Timezone**: Fixed timezone-aware datetime handling in reporting and database operations
- **Tests**: Updated test fixtures to properly handle timezone-aware datetimes

Contributors
^^^^^^^^^^^^
Special thanks to: Alex Knop (@aknopper) for identifying and fixing the deprecated datetime usage

v5.0.0
----------
**Major Release - Exception Monitoring & Stability Improvements**
Expand Down
4 changes: 2 additions & 2 deletions flask_monitoringdashboard/controllers/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ def get_endpoint_overview(session):
:param session: session for the database
:return: A list of properties for each endpoint that is found in the database
"""
week_ago = datetime.datetime.now(datetime.UTC) - datetime.timedelta(days=7)
now_local = to_local_datetime(datetime.datetime.now(datetime.UTC))
week_ago = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=7)
now_local = to_local_datetime(datetime.datetime.now(datetime.timezone.utc))
today_local = now_local.replace(hour=0, minute=0, second=0, microsecond=0)
today_utc = to_utc_datetime(today_local)

Expand Down
4 changes: 2 additions & 2 deletions flask_monitoringdashboard/core/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ def update_last_requested_cache(endpoint_name):
Use this instead of updating the last requested to the database.
"""
global memory_cache
memory_cache.get(endpoint_name).set_last_requested(datetime.datetime.now(datetime.UTC))
memory_cache.get(endpoint_name).set_last_requested(datetime.datetime.now(datetime.timezone.utc))


def update_duration_cache(endpoint_name, duration):
"""
Use this together with adding a request to the database.
"""
global memory_cache
memory_cache.get(endpoint_name).set_last_requested(datetime.datetime.now(datetime.UTC))
memory_cache.get(endpoint_name).set_last_requested(datetime.datetime.now(datetime.timezone.utc))
memory_cache.get(endpoint_name).set_duration(duration)


Expand Down
4 changes: 2 additions & 2 deletions flask_monitoringdashboard/core/database_pruning.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from sqlalchemy.orm import Session
from flask_monitoringdashboard.core.custom_graph import scheduler
from flask_monitoringdashboard.database import (
Expand All @@ -23,7 +23,7 @@
def prune_database_older_than_weeks(weeks_to_keep, delete_custom_graph_data):
"""Prune the database of Request and optionally CustomGraph data older than the specified number of weeks"""
with session_scope() as session:
date_to_delete_from = datetime.utcnow() - timedelta(weeks=weeks_to_keep)
date_to_delete_from = datetime.now(timezone.utc) - timedelta(weeks=weeks_to_keep)

# Prune Request table and related Outlier entries
requests_to_delete = (
Expand Down
2 changes: 1 addition & 1 deletion flask_monitoringdashboard/core/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def initialize_telemetry_session(session):
else:
telemetry_user = session.query(TelemetryUser).one()
telemetry_user.times_initialized += 1
telemetry_user.last_initialized = datetime.datetime.now(datetime.UTC)
telemetry_user.last_initialized = datetime.datetime.now(datetime.timezone.utc)
session.commit()

# reset telemetry if declined in previous session
Expand Down
10 changes: 5 additions & 5 deletions flask_monitoringdashboard/database/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class TelemetryUser(Base):
times_initialized = Column(Integer, default=1)
"""For checking the amount of times the app was initialized"""

last_initialized = Column(DateTime, default=datetime.datetime.utcnow)
last_initialized = Column(DateTime, default=datetime.datetime.now(datetime.timezone.utc))
"""Check when was the last time user accessed FMD"""

monitoring_consent = Column(Integer, default=1)
Expand All @@ -93,7 +93,7 @@ class Endpoint(Base):
monitor_level = Column(Integer, default=config.monitor_level)
"""0 - disabled, 1 - performance, 2 - outliers, 3 - profiler + outliers"""

time_added = Column(DateTime, default=datetime.datetime.utcnow)
time_added = Column(DateTime, default=datetime.datetime.now(datetime.timezone.utc))
"""Time when the endpoint was added."""

version_added = Column(String(100), default=config.version)
Expand All @@ -118,7 +118,7 @@ class Request(Base):
duration = Column(Float, nullable=False)
"""Processing time of the request in milliseconds."""

time_requested = Column(DateTime, default=datetime.datetime.utcnow)
time_requested = Column(DateTime, default=datetime.datetime.now(datetime.timezone.utc))
"""Moment when the request was handled."""

version_requested = Column(String(100), default=config.version)
Expand Down Expand Up @@ -225,7 +225,7 @@ class CustomGraph(Base):
title = Column(String(250), nullable=False, unique=True)
"""Title of this graph."""

time_added = Column(DateTime, default=datetime.datetime.utcnow)
time_added = Column(DateTime, default=datetime.datetime.now(datetime.timezone.utc))
"""When the graph was first added to the dashboard."""

version_added = Column(String(100), default=config.version)
Expand All @@ -244,7 +244,7 @@ class CustomGraphData(Base):
graph = relationship(CustomGraph, backref="data")
"""Graph for which the data is collected."""

time = Column(DateTime, default=datetime.datetime.utcnow)
time = Column(DateTime, default=datetime.datetime.now(datetime.timezone.utc))
"""Moment when the data is collected."""

value = Column(Float)
Expand Down
2 changes: 1 addition & 1 deletion flask_monitoringdashboard/database/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def update_last_requested(session, endpoint_name, timestamp=None):
:param endpoint_name: name of the endpoint
:param timestamp: optional timestamp. If not given, timestamp is current time
"""
ts = timestamp if timestamp else datetime.datetime.now(datetime.UTC)
ts = timestamp if timestamp else datetime.datetime.now(datetime.timezone.utc)
session.query(Endpoint).filter(Endpoint.name == endpoint_name).update(
{Endpoint.last_requested: ts}
)
Expand Down
58 changes: 32 additions & 26 deletions flask_monitoringdashboard/views/reporting.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime
from datetime import datetime, timezone

from flask import request, jsonify
from sqlalchemy import and_
Expand All @@ -8,18 +8,21 @@
from flask_monitoringdashboard.core.auth import secure
from flask_monitoringdashboard.core.date_interval import DateInterval
from flask_monitoringdashboard.core.telemetry import post_to_back_if_telemetry_enabled
from flask_monitoringdashboard.core.reporting.questions.median_latency import \
MedianLatency
from flask_monitoringdashboard.core.reporting.questions.median_latency import (
MedianLatency,
)
from flask_monitoringdashboard.core.reporting.questions.status_code_distribution import (
StatusCodeDistribution,
)
from flask_monitoringdashboard.database import session_scope
from flask_monitoringdashboard.database.endpoint import get_endpoints
from flask_monitoringdashboard.database.request import create_time_based_sample_criterion
from flask_monitoringdashboard.database.request import (
create_time_based_sample_criterion,
)


def get_date(p):
return datetime.utcfromtimestamp(int(request.args.get(p)))
return datetime.fromtimestamp(int(request.args.get(p)), tz=timezone.utc)


def make_endpoint_summary(endpoint, requests_criterion, baseline_requests_criterion):
Expand All @@ -33,13 +36,14 @@ def make_endpoint_summary(endpoint, requests_criterion, baseline_requests_criter
)

for question in questions:
answer = question.get_answer(endpoint, requests_criterion,
baseline_requests_criterion)
answer = question.get_answer(
endpoint, requests_criterion, baseline_requests_criterion
)

if answer.is_significant():
summary['has_anything_significant'] = True
summary["has_anything_significant"] = True

summary['answers'].append(answer.serialize())
summary["answers"].append(answer.serialize())

return summary

Expand All @@ -49,52 +53,54 @@ def make_endpoint_summaries(requests_criterion, baseline_requests_criterion):

with session_scope() as db_session:
for endpoint in get_endpoints(db_session):
endpoint_summary = make_endpoint_summary(endpoint, requests_criterion,
baseline_requests_criterion)
endpoint_summary = make_endpoint_summary(
endpoint, requests_criterion, baseline_requests_criterion
)
endpoint_summaries.append(endpoint_summary)

return dict(summaries=endpoint_summaries)


@blueprint.route('/api/reporting/make_report/intervals', methods=['POST'])
@blueprint.route("/api/reporting/make_report/intervals", methods=["POST"])
@secure
def make_report_intervals():
post_to_back_if_telemetry_enabled(**{'name': 'reporting/make_reports/intervals'})
post_to_back_if_telemetry_enabled(**{"name": "reporting/make_reports/intervals"})
arguments = request.json

try:
interval = DateInterval(
datetime.fromtimestamp(int(arguments['interval']['from'])),
datetime.fromtimestamp(int(arguments['interval']['to'])),
datetime.fromtimestamp(int(arguments["interval"]["from"]), tz=timezone.utc),
datetime.fromtimestamp(int(arguments["interval"]["to"]), tz=timezone.utc),
)

baseline_interval = DateInterval(
datetime.fromtimestamp(int(arguments['baseline_interval']['from'])),
datetime.fromtimestamp(int(arguments['baseline_interval']['to'])),
datetime.fromtimestamp(int(arguments["baseline_interval"]["from"]), tz=timezone.utc),
datetime.fromtimestamp(int(arguments["baseline_interval"]["to"]), tz=timezone.utc),
)

except Exception:
return 'Invalid payload', 422
return "Invalid payload", 422

baseline_requests_criterion = create_time_based_sample_criterion(
baseline_interval.start_date(),
baseline_interval.end_date())
requests_criterion = create_time_based_sample_criterion(interval.start_date(),
interval.end_date())
baseline_interval.start_date(), baseline_interval.end_date()
)
requests_criterion = create_time_based_sample_criterion(
interval.start_date(), interval.end_date()
)

summaries = make_endpoint_summaries(requests_criterion, baseline_requests_criterion)

return jsonify(summaries)


@blueprint.route('/api/reporting/make_report/commits', methods=['POST'])
@blueprint.route("/api/reporting/make_report/commits", methods=["POST"])
@secure
def make_report_commits():
post_to_back_if_telemetry_enabled(**{'name': 'reporting/make_reports/commits'})
post_to_back_if_telemetry_enabled(**{"name": "reporting/make_reports/commits"})
arguments = request.json

baseline_commit_version = arguments['baseline_commit_version']
commit_version = arguments['commit_version']
baseline_commit_version = arguments["baseline_commit_version"]
commit_version = arguments["commit_version"]

if None in [baseline_commit_version, commit_version]:
return dict(message="Please select two commits"), 422
Expand Down
4 changes: 2 additions & 2 deletions tests/api/test_custom.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone

from flask_monitoringdashboard.database import CustomGraph

Expand All @@ -17,7 +17,7 @@ def test_custom_graphs(dashboard_user, custom_graph, session):


def test_custom_graph_data(dashboard_user, custom_graph, custom_graph_data):
today = datetime.utcnow()
today = datetime.now(timezone.utc)
yesterday = today - timedelta(days=1)
response = dashboard_user.get('dashboard/api/custom_graph/{id}/{start}/{end}'.format(
id=custom_graph.graph_id,
Expand Down
16 changes: 8 additions & 8 deletions tests/api/test_reporting.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import sys
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
import pytest

from flask_monitoringdashboard.database import Endpoint
Expand All @@ -11,24 +11,24 @@ def test_make_report_get(dashboard_user):
assert not response.is_json


@pytest.mark.parametrize('request_1__time_requested', [datetime.utcnow() - timedelta(hours=6)])
@pytest.mark.parametrize('request_1__time_requested', [datetime.now(timezone.utc) - timedelta(hours=6)])
@pytest.mark.parametrize('request_1__duration', [5000])
@pytest.mark.parametrize('request_1__status_code', [500])
@pytest.mark.parametrize('request_2__time_requested', [datetime.utcnow() - timedelta(days=1, hours=6)])
@pytest.mark.parametrize('request_2__time_requested', [datetime.now(timezone.utc) - timedelta(days=1, hours=6)])
@pytest.mark.parametrize('request_2__duration', [100])
@pytest.mark.skipif(sys.version_info < (3, ), reason="For some reason, this doesn't work in python 2.7.")
def test_make_report_post_not_significant(dashboard_user, endpoint, request_1, request_2, session):
epoch = datetime(1970, 1, 1)
epoch = datetime(1970, 1, 1, tzinfo=timezone.utc)
response = dashboard_user.post(
'dashboard/api/reporting/make_report/intervals',
json={
'interval': {
'from': (datetime.utcnow() - timedelta(days=1) - epoch).total_seconds(),
'to': (datetime.utcnow() - epoch).total_seconds(),
'from': (datetime.now(timezone.utc) - timedelta(days=1) - epoch).total_seconds(),
'to': (datetime.now(timezone.utc) - epoch).total_seconds(),
},
'baseline_interval': {
'from': (datetime.utcnow() - timedelta(days=2) - epoch).total_seconds(),
'to': (datetime.utcnow() - timedelta(days=1) - epoch).total_seconds(),
'from': (datetime.now(timezone.utc) - timedelta(days=2) - epoch).total_seconds(),
'to': (datetime.now(timezone.utc) - timedelta(days=1) - epoch).total_seconds(),
},
},
)
Expand Down
12 changes: 6 additions & 6 deletions tests/fixtures/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import uuid
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from random import random

import factory
Expand Down Expand Up @@ -58,9 +58,9 @@ class Meta:

name = factory.LazyFunction(lambda: str(uuid.uuid4()))
monitor_level = 1
time_added = factory.LazyFunction(lambda: datetime.utcnow() - timedelta(days=1))
time_added = factory.LazyFunction(lambda: datetime.now(timezone.utc) - timedelta(days=1))
version_added = "1.0"
last_requested = factory.LazyFunction(datetime.utcnow)
last_requested = factory.LazyFunction(lambda: datetime.now(timezone.utc))


class RequestFactory(ModelFactory):
Expand All @@ -69,7 +69,7 @@ class Meta:

endpoint = factory.SubFactory(EndpointFactory)
duration = factory.LazyFunction(lambda: random() * 5000)
time_requested = factory.LazyFunction(datetime.utcnow)
time_requested = factory.LazyFunction(lambda: datetime.now(timezone.utc))
version_requested = factory.LazyFunction(lambda: str(uuid.uuid4()))
group_by = None
ip = factory.Faker("ipv4_private")
Expand Down Expand Up @@ -115,7 +115,7 @@ class Meta:
model = CustomGraph

title = factory.Faker("name")
time_added = factory.LazyFunction(datetime.utcnow)
time_added = factory.LazyFunction(lambda: datetime.now(timezone.utc))
version_added = factory.LazyFunction(lambda: str(uuid.uuid4()))


Expand All @@ -124,7 +124,7 @@ class Meta:
model = CustomGraphData

graph = factory.SubFactory(CustomGraphFactory)
time = factory.LazyFunction(datetime.utcnow)
time = factory.LazyFunction(lambda: datetime.now(timezone.utc))
value = factory.LazyFunction(random)


Expand Down
10 changes: 7 additions & 3 deletions tests/unit/core/test_timezone.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@


def test_timezone():
dt = datetime.datetime.now()
assert to_local_datetime(to_utc_datetime(dt)) == dt
assert to_utc_datetime(to_local_datetime(dt)) == dt
dt = datetime.datetime.now(datetime.timezone.utc)
# Convert to local and back to UTC should give the same result
local_dt = to_local_datetime(dt)
assert to_utc_datetime(local_dt) == dt
# Convert to UTC and back to local should give the same result
utc_dt = to_utc_datetime(local_dt)
assert to_local_datetime(utc_dt) == local_dt


def test_timezone_none():
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/database/test_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from __future__ import division # can be removed once we leave python 2.7

import time
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone

import pytest

Expand All @@ -18,7 +18,7 @@


def test_get_latencies_sample(session, request_1, endpoint):
interval = DateInterval(datetime.utcnow() - timedelta(days=1), datetime.utcnow())
interval = DateInterval(datetime.now(timezone.utc) - timedelta(days=1), datetime.now(timezone.utc))
requests_criterion = create_time_based_sample_criterion(interval.start_date(),
interval.end_date())
data = get_latencies_sample(session, endpoint.id, requests_criterion, sample_size=500)
Expand Down