Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grafana #356

Merged
merged 13 commits into from Apr 10, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 15 additions & 6 deletions mercury/grafanaAPI/grafana_api.py
Expand Up @@ -57,6 +57,14 @@ def __init__(self, gf_config=None):
self.base_panel_width = 15
self.base_panel_height = 12

def create_safe_string(self, input):
"""
Reformats the input string to be lowercase and with spaces replaced by '-'.
:param input: string
:return: reformatted string
"""
return input.strip().lower().replace(" ", "-")

def generate_random_string(self, length):
"""
Generates a random string of letters of given length.
Expand Down Expand Up @@ -107,8 +115,10 @@ def get_dashboard_by_name(self, event_name):
"""
# If there are spaces in the name, the GF API will replace them with dashes
# to generate the "slug". A slug can be used to query the API.
formatted_event_name = self.create_safe_string(event_name)

endpoint = os.path.join(
self.hostname, "api/dashboards/db", event_name.lower().replace(" ", "-")
self.hostname, "api/dashboards/db", formatted_event_name
)
response = requests.get(url=endpoint, auth=("api_key", self.api_token))

Expand All @@ -118,9 +128,9 @@ def get_dashboard_by_name(self, event_name):
return None

def get_dashboard_url_by_name(self, name):
name = name.strip().lower().replace(" ", "-")
search_name = self.create_safe_string(name)

dashboard = self.get_dashboard_by_name(name)
dashboard = self.get_dashboard_by_name(search_name)
if dashboard:
endpoint = dashboard["meta"]["url"].strip("/")
url = os.path.join(self.hostname, endpoint)
Expand Down Expand Up @@ -270,9 +280,8 @@ def delete_dashboard(self, uid):
return False

def delete_dashboard_by_name(self, name):
endpoint = os.path.join(
self.hostname, "api/dashboards/db", name.lower().replace(" ", "-")
)
search_name = self.create_safe_string(name)
endpoint = os.path.join(self.hostname, "api/dashboards/db", search_name)
response = requests.get(url=endpoint, auth=("api_key", self.api_token))

dashboard = response.json().get("dashboard")
Expand Down
4 changes: 4 additions & 0 deletions mercury/templates/events.html
Expand Up @@ -124,6 +124,10 @@ <h1>All Events</h1>
{% else %}
<div>There are no events to show.</div>
{% endif %}

{% for message in messages %}
<p class="error"> {{ message }} </p>
{% endfor %}
</div>

<!--Create Events -->
Expand Down
40 changes: 40 additions & 0 deletions mercury/tests/test_event.py
Expand Up @@ -23,9 +23,34 @@ class TestEventView(TestCase):
"longitude": 200,
}

# Returns event
def create_venue_and_event(self, event_name):
venue = AGVenue.objects.create(
name=self.test_venue_data["name"],
description=self.test_venue_data["description"],
latitude=self.test_venue_data["latitude"],
longitude=self.test_venue_data["longitude"],
)
venue.save()

event = AGEvent.objects.create(
name=event_name,
date=self.test_event_data["date"],
description=self.test_event_data["description"],
venue_uuid=venue,
)
event.save()

return event

def setUp(self):
self.login_url = "mercury:EventAccess"
self.event_url = "mercury:events"
self.event_delete_url = "mercury:delete_event"

# Create random event name
self.event_name = "test"

test_code = EventCodeAccess(event_code=self.TESTCODE, enabled=True)
test_code.save()

Expand Down Expand Up @@ -139,3 +164,18 @@ def test_create_event_with_correct_parameters(self):
self.assertEqual(event.date, self.test_event_data["date"])
self.assertEqual(event.venue_uuid.uuid, venue.uuid)
self.assertEqual(event.description, self.test_event_data["description"])

def test_delete_event(self):
# Create an event
event = self.create_venue_and_event(self.event_name)

# Confirm that event was created
self.assertEquals(AGEvent.objects.all().count(), 1)

# Delete the event
self.client.post(
reverse(self.event_delete_url, kwargs={"event_uuid": event.uuid})
)

# Confirm that event was deleted
self.assertEquals(AGEvent.objects.all().count(), 0)
84 changes: 83 additions & 1 deletion mercury/tests/test_grafana.py
Expand Up @@ -72,6 +72,7 @@ def create_gfconfig(self):
config.save()
return config

# Returns event
def create_venue_and_event(self, event_name):
venue = AGVenue.objects.create(
name=self.test_venue_data["name"],
Expand All @@ -95,6 +96,7 @@ def setUp(self):
self.login_url = "mercury:EventAccess"
self.sensor_url = "mercury:sensor"
self.event_url = "mercury:events"
self.event_delete_url = "mercury:delete_event"
test_code = EventCodeAccess(event_code="testcode", enabled=True)
test_code.save()
# Login
Expand Down Expand Up @@ -327,8 +329,48 @@ def test_add_multiple_panels(self):
name = "".join([self.test_sensor_name, str(i)])
self.assertTrue(dashboard_info["dashboard"]["panels"][i]["title"] == name)

def test_add_panel_fail_no_dashboard_exists_for_event(self):
def test_add_sensor_creates_panel_in_dashboard(self):
# Create a dashboard, confirm it was created and retrieve its UID
dashboard = self.grafana.create_dashboard(self.event_name)
self.assertTrue(dashboard)

# Create an event
self.create_venue_and_event(self.event_name)

sensor_type = AGSensorType.objects.create(
name=self.test_sensor_type,
processing_formula=0,
format=self.test_sensor_format,
)
sensor_type.save()

# POST sensor data
self.client.post(
reverse(self.sensor_url),
data={
"submit_new_sensor": "",
"sensor-name": self.test_sensor_name,
"select-sensor-type": self.test_sensor_type,
},
)

# Fetch the dashboard again
dashboard = self.grafana.get_dashboard_by_name(dashboard["slug"])
self.assertTrue(dashboard)

# Confirm that a panel was added to the dashboard with the expected title
self.assertTrue(dashboard)
self.assertTrue(dashboard["dashboard"])
self.assertTrue(dashboard["dashboard"]["panels"])
self.assertTrue(len(dashboard["dashboard"]["panels"]) == 1)

# Note: converting test_sensor_name to lowercase because currently
# sensor names are automatically capitalized when they are created
self.assertEquals(
dashboard["dashboard"]["panels"][0]["title"], self.test_sensor_name.lower()
)

def test_add_panel_fail_no_dashboard_exists_for_event(self):
# Create an event
event = self.create_venue_and_event(self.event_name)

Expand Down Expand Up @@ -500,3 +542,43 @@ def test_create_event_creates_dashboard_with_panel(self):
self.assertTrue(
dashboard_info["dashboard"]["panels"][0]["title"] == sensor.name
)

def test_delete_event_deletes_grafana_dashboard(self):
self.grafana.create_postgres_datasource(self.datasource_name)

# Create a venue
venue = AGVenue.objects.create(
name=self.event_name,
description=self.test_venue_data["description"],
latitude=self.test_venue_data["latitude"],
longitude=self.test_venue_data["longitude"],
)
venue.save()

# Send a request to create an event (should trigger the creation of a
# grafana dashboard of the same name)
self.client.post(
reverse(self.event_url),
data={
"submit-event": "",
"name": self.event_name,
"date": self.test_event_data["date"],
"description": self.test_event_data["description"],
"venue_uuid": venue.uuid,
},
)

dashboard = self.grafana.get_dashboard_by_name(self.event_name)
self.assertTrue(dashboard)

# Retrieve event object
event = AGEvent.objects.all().first()

# Delete the event by posting to the delete view
self.client.post(
reverse(self.event_delete_url, kwargs={"event_uuid": event.uuid})
)
# Try and retrieve the dashboard
dashboard = self.grafana.get_dashboard_by_name(self.event_name)

self.assertFalse(dashboard)
2 changes: 1 addition & 1 deletion mercury/urls.py
Expand Up @@ -36,7 +36,7 @@
name="update_type",
),
path("events/", events.CreateEventsView.as_view(), name="events"),
path("events/delete/<uuid:event_uuid>", events.delete_event),
path("events/delete/<uuid:event_uuid>", events.delete_event, name="delete_event"),
path("events/update/<uuid:event_uuid>", events.update_event),
path("events/updatevenue/<uuid:venue_uuid>", events.update_venue),
path("events/export/<uuid:event_uuid>/csv", events.export_event),
Expand Down
20 changes: 20 additions & 0 deletions mercury/views/events.py
Expand Up @@ -46,7 +46,27 @@ def update_event(request, event_uuid=None):

def delete_event(request, event_uuid=None):
event_to_delete = AGEvent.objects.get(uuid=event_uuid)

# delete any dashboards that exist for this event
gfconfigs = GFConfig.objects.all()

# Add panel to each grafana instance
for gfconfig in gfconfigs:

# Grafana instance using current GFConfig
grafana = Grafana(gfconfig)

deleted = grafana.delete_dashboard_by_name(event_to_delete.name)

if not deleted:
messages.error(
request,
f"Failed to delete Event dashboard from Grafana instance: "
f"{gfconfig.gf_host}",
)

event_to_delete.delete()

return redirect("/events")


Expand Down
35 changes: 19 additions & 16 deletions mercury/views/sensor.py
Expand Up @@ -253,6 +253,9 @@ def post(self, request, *args, **kwargs):
sensor_types = (
AGSensorType.objects.all()
) # for when we return context later

sensors = AGSensor.objects.all()

if valid:
new_sensor = AGSensor.objects.create(
name=sensor_name, type_id=sensor_type
Expand All @@ -261,33 +264,33 @@ def post(self, request, *args, **kwargs):

# Add a Sensor panel to the Active Event

# Check that Grafana is already configured
# and that an Active Event exists

# Note: THIS IS A PLACEHOLDER - waiting to decide
# how to implement Current GFConfig
gf_configs = GFConfig.objects.filter(gf_current=True)
gfconfigs = GFConfig.objects.all()

# Note: THIS IS A PLACEHOLDER - waiting to decide
# how to implement Active Event
active_events = AGEvent.objects.all()

if len(gf_configs) > 0 and len(active_events) > 0:
gf_config = gf_configs.first()
# Only add panel to active event
if len(active_events) > 0:
active_event = active_events.first()

# Grafana instance using current GFConfig
grafana = Grafana(gf_config)
# Add panel to each grafana instance
for gfconfig in gfconfigs:

# Add the Sensor Panel to the Active Event's dashboard
try:
grafana.add_panel(new_sensor, active_event)
except ValueError as error:
messages.error(
request, f"Failed to add panel to active dashboard: {error}"
)
# Grafana instance using current GFConfig
grafana = Grafana(gfconfig)

# Add the Sensor Panel to the Active Event's dashboard
try:
grafana.add_panel(new_sensor, active_event)
except ValueError as error:
messages.error(
request,
f"Failed to add panel to active dashboard: {error}",
)

sensors = AGSensor.objects.all()
context = {"sensors": sensors, "sensor_types": sensor_types}
else:
sensors = AGSensor.objects.all()
Expand Down