示例#1
0
    def setUp(self):
        # api keys with admin and viewer level permissions
        self.ADMIN = Grafana.create_api_key(HOST, "admin", "Admin")
        self.VIEWER = Grafana.create_api_key(HOST, "viewer", "Viewer")

        self.login_url = "mercury:EventAccess"
        self.sensor_url = "mercury:sensor"
        self.event_url = "mercury:events"
        self.config_url = "mercury:gfconfig"
        self.config_update_url = "mercury:gfconfig_update"
        self.config_delete_url = "mercury:gfconfig_delete"
        self.config_update_dashboard_url = "mercury:gfconfig_update_dashboard"
        self.config_reset_dashboard_url = "mercury:gfconfig_reset_dashboard"
        self.config_delete_dashboard_url = "mercury:gfconfig_delete_dashboard"
        self.config_add_dashboard_url = "mercury:gfconfig_create_dashboard"
        test_code = EventCodeAccess(event_code="testcode", enabled=True)
        test_code.save()
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        self.gfconfig = GFConfig.objects.create(gf_name="Test",
                                                gf_host=HOST,
                                                gf_token=self.ADMIN,
                                                gf_current=True)
        self.gfconfig.save()
        # Create fresh grafana object
        self.grafana = Grafana(self.gfconfig)

        # Create random name to be used for event and datasource
        self.event_name = self.grafana.generate_random_string(10)
        self.datasource_name = self.grafana.generate_random_string(10)

        # Clear existing dashboard and datasource
        self.grafana.delete_all_dashboards()
        self.grafana.delete_all_datasources()
示例#2
0
    def setUp(self):
        # api keys with admin and viewer level permissions
        self.ADMIN = Grafana.create_api_key(HOST, "admin", "Admin")
        self.VIEWER = Grafana.create_api_key(HOST, "viewer", "Viewer")

        self.login_url = "mercury:EventAccess"
        self.sensor_url = "mercury:sensor"
        self.event_url = "mercury:events"
        self.event_delete_url = "mercury:delete_event"
        self.event_update_url = "mercury:update_event"
        self.update_sensor_url = "mercury:update_sensor"
        self.delete_sensor_url = "mercury:delete_sensor"
        self.update_sensor_type_url = "mercury:update_type"
        test_code = EventCodeAccess(event_code="testcode", enabled=True)
        test_code.save()
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        # Create fresh grafana object
        self.config = self.create_gfconfig()
        self.grafana = Grafana(self.config)

        # Create random name to be used for event and datasource
        self.event_name = self.grafana.generate_random_string(10)
        self.updated_event_name = self.event_name + " Day Two"
        self.datasource_name = self.grafana.generate_random_string(10)

        # Clear existing dashboard and datasource
        self.grafana.delete_all_dashboards()
        self.grafana.delete_all_datasources()
示例#3
0
class TestGrafana(TestCase):
    TESTCODE = "testcode"

    title = "Bar"

    test_sensor_name = "Wind Sensor"
    test_sensor_name_update = "Another Name"
    test_sensor_type = "Dual wind"
    test_sensor_format = {
        "left_gust": {"unit": "km/h", "format": "float"},
        "right_gust": {"unit": "km/h", "format": "float"},
    }
    test_sensor_graph_type = "graph"

    field_name_1 = "test-field-1"
    field_name_2 = "test-field-2"
    data_type_1 = "float"
    data_type_2 = "float"
    unit_1 = "test-unit-1"
    unit_2 = "test-unit-2"

    test_sensor = {
        "name": test_sensor_name,
        "processing formula": 0,
        "field-names": [field_name_1, field_name_2],
        "data-types": [data_type_1, data_type_2],
        "units": [unit_1, unit_2],
    }

    test_map = {
        "name": "foo",
        "processing formula": 0,
        "field-names": ["latitude", "longitude"],
        "data-types": ["float", "float"],
        "units": ["", ""],
        "graph_type": "map",
    }

    test_gauge = {
        "name": "foo",
        "processing formula": 0,
        "field-names": ["temperature"],
        "data-types": ["float"],
        "units": [""],
        "graph_type": "gauge",
    }

    test_sensor_format_update = {
        "fuel reading": {"unit": "m", "format": "float"},
    }

    test_event_data = {
        "name": "Sunny Day Test Drive",
        "date": datetime.datetime(2020, 2, 2, 20, 21, 22),
        "description": "A very progressive test run at \
                    Sunnyside Daycare's Butterfly Room.",
        "location": "New York, NY",
    }

    test_event_data_update = {
        "name": "Another Day Test Drive",
        "date": datetime.datetime(2020, 3, 3, 20, 21, 22),
        "description": "A very modern test run at \
                        my backyard.",
        "location": "Buffalo, NY",
    }

    test_venue_data = {
        "name": "Venue 1",
        "description": "foo",
        "latitude": 100,
        "longitude": 200,
    }

    def create_gfconfig(self):
        config = GFConfig.objects.create(
            gf_host=HOST,
            gf_token=self.ADMIN,
            gf_db_host=DB_HOSTNAME,
            gf_db_name=DB_NAME,
            gf_db_username=DB_USERNAME,
            gf_db_pw=DB_PASSWORD,
            gf_current=True,
        )
        config.save()
        return config

    # 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):
        # api keys with admin and viewer level permissions
        self.ADMIN = Grafana.create_api_key(HOST, "admin", "Admin")
        self.VIEWER = Grafana.create_api_key(HOST, "viewer", "Viewer")

        self.login_url = "mercury:EventAccess"
        self.sensor_url = "mercury:sensor"
        self.event_url = "mercury:events"
        self.event_delete_url = "mercury:delete_event"
        self.event_update_url = "mercury:update_event"
        self.update_sensor_url = "mercury:update_sensor"
        self.delete_sensor_url = "mercury:delete_sensor"
        self.update_sensor_type_url = "mercury:update_type"
        test_code = EventCodeAccess(event_code="testcode", enabled=True)
        test_code.save()
        # Login
        self._get_with_event_code(self.sensor_url, self.TESTCODE)

        # Create fresh grafana object
        self.config = self.create_gfconfig()
        self.grafana = Grafana(self.config)

        # Create random name to be used for event and datasource
        self.event_name = self.grafana.generate_random_string(10)
        self.updated_event_name = self.event_name + " Day Two"
        self.datasource_name = self.grafana.generate_random_string(10)

        # Clear existing dashboard and datasource
        self.grafana.delete_all_dashboards()
        self.grafana.delete_all_datasources()

    def tearDown(self):
        # Create fresh grafana instance (in case test invalidated any tokens, etc.)
        self.grafana = Grafana(self.config)

        # Clear all of the created dashboards
        self.grafana.delete_all_dashboards()
        self.grafana.delete_all_datasources()

    def test_create_api_key(self):
        key = self.grafana.create_api_key(HOST, "admin2", "Admin")
        self.assertTrue(key is not None and len(key) > 0)
        self.assertTrue(self.grafana.validate_credentials())

    def _get_with_event_code(self, url, event_code):
        self.client.get(reverse(self.login_url))
        self.client.post(reverse(self.login_url), data={"eventcode": event_code})
        response = self.client.get(reverse(url))
        session = self.client.session
        return response, session

    def test_get_dashboard_with_uid_success(self):
        # Should return True if create_dashboard was successful
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

        # Query new dashboard
        uid = dashboard["uid"]
        fetched_dashboard = self.grafana.get_dashboard_with_uid(uid)

        # Confirm params of new dashboard (slug, uid, title)
        self.assertTrue(fetched_dashboard)
        self.assertTrue(fetched_dashboard["meta"])
        self.assertTrue(fetched_dashboard["meta"]["slug"], self.event_name.lower())
        self.assertTrue(fetched_dashboard["dashboard"])
        self.assertTrue(fetched_dashboard["dashboard"]["uid"], uid)
        self.assertTrue(fetched_dashboard["dashboard"]["title"], self.event_name)

    def test_get_dashboard_url_success(self):
        self.grafana.create_dashboard(self.event_name)
        self.assertTrue(self.grafana.get_dashboard_url_by_name(self.event_name))

    def test_get_dashboard_url_none(self):
        self.assertIsNone(
            self.grafana.get_dashboard_url_by_name(self.event_name + "foo")
        )

    def test_get_dashboard_with_uid_fail(self):
        # Create a random UID to search for
        uid = self.grafana.generate_random_string(10)

        # Try and fetch random UID (should return None)
        fetched_dashboard = self.grafana.get_dashboard_with_uid(uid)
        self.assertFalse(fetched_dashboard)

    def test_create_grafana_dashboard_success(self):
        # Should return an object with dashboard credentials
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

    def test_create_grafana_dashboard_verify_new_dashboard_contents(self):
        # Should return an object with dashboard credentials
        dashboard = self.grafana.create_dashboard(self.event_name)

        # Confirm that an object was returned
        self.assertTrue(dashboard)
        # Check for success status message
        self.assertEquals(dashboard["status"], "success")
        # Check that dashboard has correct name
        self.assertEquals(dashboard["slug"], self.event_name.lower())

        # Retrieve UID for new dashboard, use to query new dashboard
        uid = dashboard["uid"]

        # Confirm new dashboard can be queried from the API
        endpoint = os.path.join(self.grafana.endpoints["dashboard_uid"], uid)
        headers = {"Content-Type": "application/json"}
        response = requests.get(
            url=endpoint, headers=headers, auth=("api_key", self.grafana.api_token)
        )

        # Confirm uid and title are as expected
        self.assertEquals(response.json()["dashboard"]["uid"], uid)
        self.assertEquals(response.json()["dashboard"]["title"], self.event_name)

    def test_create_grafana_dashboard_fail_authorization(self):
        self.grafana.api_token = "abcde"  # invalid API token

        expected_message = "Invalid API key"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_dashboard(self.event_name)

    def test_create_grafana_dashboard_fail_permissions(self):
        self.grafana.api_token = self.VIEWER  # API token with viewer permissions

        expected_message = "Access denied - check API permissions"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_dashboard(self.event_name)

    def test_create_grafana_dashboard_fail_empty_title(self):
        expected_message = "Dashboard title cannot be empty"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_dashboard("")

    def test_validate_credentials_success(self):
        # should return True if credentials are valid
        self.assertTrue(self.grafana.validate_credentials())

    def test_update_dashboard_title_success(self):
        event = self.create_venue_and_event(self.event_name)
        self.grafana.create_dashboard(self.event_name)
        new_name = event.name + " foo"
        self.assertTrue(self.grafana.update_dashboard_title(event, new_name))

    def test_update_dashboard_title_fail_same_name(self):
        event = self.create_venue_and_event(self.event_name)
        self.grafana.create_dashboard(self.event_name)
        self.assertFalse(self.grafana.update_dashboard_title(event, event.name))

    def test_update_dashboard_title_fail_no_dashboard(self):
        event = self.create_venue_and_event(self.event_name)
        self.assertFalse(self.grafana.update_dashboard_title(event, event.name))

    def test_validate_credentials_fail_authorization(self):
        self.grafana.api_token = "abcde"  # invalid API token

        expected_message = "Grafana API validation failed: Invalid API key"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.validate_credentials()

    def test_validate_credentials_fail_permissions(self):
        self.grafana.api_token = self.VIEWER  # API token with viewer permissions

        expected_message = (
            "Grafana API validation failed: Access denied - " "check API permissions"
        )
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.validate_credentials()

    def test_create_grafana_dashboard_fail_duplicate_title(self):
        # Should return a JSON object with a message that the dashboard already exists
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

        expected_message = "Dashboard with the same name already exists"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_dashboard(self.event_name)

    def test_delete_grafana_dashboard(self):
        # Create a dashboard
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

        # Delete the dashboard, should return True if deleted
        deleted_dashboard = self.grafana.delete_dashboard(dashboard["uid"])
        self.assertTrue(deleted_dashboard)

        # Confirm that dashboard was deleted by querying it
        endpoint = os.path.join(
            self.grafana.endpoints["dashboard_uid"], dashboard["uid"]
        )
        headers = {"Content-Type": "application/json"}
        response = requests.get(
            url=endpoint, headers=headers, auth=("api_key", self.grafana.api_token)
        )
        self.assertTrue(response.json()["message"])
        self.assertEquals(response.json()["message"], "Dashboard not found")

    def test_delete_grafana_dashboard_fail(self):
        # Generate a random uid
        uid = self.grafana.generate_random_string(10)

        # Attempt to delete dashboard that doesn't exist, should return False
        deleted_dashboard = self.grafana.delete_dashboard(uid)
        self.assertFalse(deleted_dashboard)

    def test_add_panel(self):
        # Create a dashboard, confirm it was created and retrieve its UID
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)
        uid = dashboard["uid"]

        # Create an event and venue
        event = self.create_venue_and_event(self.event_name)

        # Create a sensor type and sensor
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()
        sensor = AGSensor.objects.create(
            name=self.test_sensor_name, type_id=sensor_type
        )
        sensor.save()

        # Add a panel to the dashboard
        self.grafana.add_panel(sensor, event)

        # Retrieve the current dashboard
        dashboard_info = self.grafana.get_dashboard_with_uid(uid)

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

    def test_add_multiple_panels(self):
        # Create a dashboard, confirm it was created and retrieve its UID
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)
        uid = dashboard["uid"]

        # Create an event and venue
        event = self.create_venue_and_event(self.event_name)

        # Create a sensor type
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()

        # Create 10 sensors, invoking add_panel for each new sensor
        for i in range(10):
            sensor = AGSensor.objects.create(
                name="".join([self.test_sensor_name, str(i)]), type_id=sensor_type
            )
            sensor.save()

            self.grafana.add_panel(sensor, event)

        # Query dashboard to confirm 10 panels were added
        dashboard_info = self.grafana.get_dashboard_with_uid(uid)
        self.assertTrue(dashboard_info)
        self.assertTrue(dashboard_info["dashboard"])
        self.assertTrue(dashboard_info["dashboard"]["panels"])
        self.assertTrue(len(dashboard_info["dashboard"]["panels"]) == 10)

        # Confirm correct title for each panel
        for i in range(10):
            name = "".join([self.test_sensor_name, str(i)])
            self.assertTrue(dashboard_info["dashboard"]["panels"][i]["title"] == name)

    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
        event = self.create_venue_and_event(self.event_name)

        # Make the event the active event
        AGActiveEvent.objects.create(agevent=event).save()

        # Create a sensor through the UI
        self.client.post(
            reverse(self.sensor_url),
            data={
                "submit_new_sensor": "",
                "sensor-name": self.test_sensor["name"],
                "field-names": self.test_sensor["field-names"],
                "data-types": self.test_sensor["data-types"],
                "units": self.test_sensor["units"],
                "sensor-graph-type": self.test_sensor_graph_type,
            },
        )

        self.assertEquals(AGSensor.objects.count(), 1)

        # Fetch the dashboard again
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)
        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_update_sensor_name_updates_panel_title(self):
        # Create a dashboard
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

        # Create an event, create a sensor
        event = self.create_venue_and_event(self.event_name)

        # Create AGSensorType object for foreign key reference
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor["name"].lower(),
            processing_formula=0,
            format={
                self.field_name_1: {"data_type": self.data_type_1, "unit": self.unit_1},
                self.field_name_2: {"data_type": self.data_type_2, "unit": self.unit_2},
            },
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()

        # Create AG Sensor Object
        sensor = AGSensor.objects.create(
            name=self.test_sensor["name"].lower(), type_id=sensor_type
        )
        sensor.save()

        # Add the sensor panel to the dashboard
        self.grafana.add_panel(sensor, event)

        updated_test_sensor_name = "foo"

        # Post Edited Name
        self.client.post(
            reverse(self.sensor_url),
            data={
                "edit_sensor": "",
                "sensor-name": self.test_sensor["name"].lower(),
                "sensor-name-updated": updated_test_sensor_name,
                "field-names": self.test_sensor["field-names"],
                "data-types": self.test_sensor["data-types"],
                "units": self.test_sensor["units"],
                "sensor-graph-type": self.test_sensor_graph_type,
            },
        )

        # Check that AGSensor object has new name
        sensor = AGSensor.objects.all()[0]
        self.assertEqual(sensor.name, updated_test_sensor_name)

        # Check that sensor name was updated
        sensor = AGSensor.objects.all()[0]
        self.assertEquals(sensor.name, updated_test_sensor_name)

        # Confirm that Grafana panel title was updated
        # Confirm that a panel was added to the dashboard with the expected title
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)

        self.assertTrue(dashboard)
        self.assertTrue(dashboard["dashboard"])
        self.assertTrue(dashboard["dashboard"]["panels"])
        self.assertTrue(len(dashboard["dashboard"]["panels"]) == 1)

        self.assertEquals(
            dashboard["dashboard"]["panels"][0]["title"], updated_test_sensor_name,
        )

    def test_update_sensor_type_updates_panel_query(self):
        # Create a dashboard, confirm it was created
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

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

        # Create sensor and sensor type
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_name.lower(),
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()

        sensor = AGSensor.objects.create(
            name=self.test_sensor_name.lower(), type_id=sensor_type
        )
        sensor.save()

        # Create grafana sensor panel
        self.grafana.add_panel(sensor, event)

        # New fields
        field_names_updated = ["foo"]
        data_types_updated = ["float"]
        units_updated = ["km/h"]

        # Post edited sensor type
        self.client.post(
            reverse(self.sensor_url),
            data={
                "edit_sensor": "",
                "sensor-name": self.test_sensor["name"].lower(),
                "sensor-name-updated": self.test_sensor["name"].lower(),
                "field-names": field_names_updated,
                "data-types": data_types_updated,
                "units": units_updated,
                "sensor-graph-type": self.test_sensor_graph_type,
            },
        )

        # Confirm that a new Grafana panel was created with a query containing all of
        # the current field names (not checking the full syntax of the query, just that
        # the new field name is in the query)
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)

        self.assertIn(
            field_names_updated[0],
            dashboard["dashboard"]["panels"][0]["targets"][0]["rawSql"],
        )

    def test_update_sensor_name_and_type_updates_panel_title_and_query(self):
        # Create a dashboard, confirm it was created
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

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

        # Create sensor and sensor type
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_name.lower(),
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()

        sensor = AGSensor.objects.create(
            name=self.test_sensor_name.lower(), type_id=sensor_type
        )
        sensor.save()

        # Create grafana sensor panel
        self.grafana.add_panel(sensor, event)

        # New fields
        updated_sensor_name = "bar"
        field_names_updated = ["foo"]
        data_types_updated = ["float"]
        units_updated = ["km/h"]

        # Post edited sensor type
        self.client.post(
            reverse(self.sensor_url),
            data={
                "edit_sensor": "",
                "sensor-name": self.test_sensor["name"].lower(),
                "sensor-name-updated": updated_sensor_name,
                "field-names": field_names_updated,
                "data-types": data_types_updated,
                "units": units_updated,
                "sensor-graph-type": self.test_sensor_graph_type,
            },
        )

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

        # Confirm title and query
        self.assertEquals(
            dashboard["dashboard"]["panels"][0]["title"], updated_sensor_name,
        )
        self.assertIn(
            field_names_updated[0],
            dashboard["dashboard"]["panels"][0]["targets"][0]["rawSql"],
        )

    def test_delete_sensor_deletes_panel_in_dashboard(self):
        # Create a dashboard, confirm it was created
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

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

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

        sensor = AGSensor.objects.create(
            name=self.test_sensor_name, type_id=sensor_type
        )
        sensor.save()

        self.grafana.add_panel(sensor, event)

        # Delete sensor
        self.client.post(
            reverse(self.delete_sensor_url, kwargs={"sensor_name": sensor.name}),
            follow=True,
        )

        # Confirm sensor was deleted
        self.assertEquals(AGSensor.objects.count(), 0)

        # Confirm that sensor was deleted from Grafana
        # 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.assertEquals(len(dashboard["dashboard"]["panels"]), 0)

    def test_delete_sensor_fails_sensor_not_found(self):
        # Create a dashboard, confirm it was created
        dashboard = self.grafana.create_dashboard(self.event_name)
        self.assertTrue(dashboard)

        sensor_name = "foo"

        # Delete sensor
        response = self.client.post(
            reverse(self.delete_sensor_url, kwargs={"sensor_name": sensor_name}),
            follow=True,
        )

        messages = list(response.context["messages"])
        self.assertEqual(len(messages), 1)
        self.assertEqual(str(messages[0]), "foo not found")

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

        # Create a sensor type and sensor
        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()
        sensor = AGSensor.objects.create(
            name=self.test_sensor_name, type_id=sensor_type
        )
        sensor.save()

        # Check for expected ValueError and message
        expected_message = "Dashboard not found for this event."
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.add_panel(sensor, event)

    def test_create_postgres_datasource(self):
        # Create datasource
        self.grafana.create_postgres_datasource(self.datasource_name)

        # Query new datasource
        endpoint = os.path.join(
            self.grafana.endpoints["datasource_name"], self.datasource_name
        )
        headers = {"Content-Type": "application/json"}
        response = requests.get(
            url=endpoint, headers=headers, auth=("api_key", self.grafana.api_token)
        )

        # Confirm datasource exists with expected name
        self.assertEquals(response.json()["name"], self.datasource_name)

    def test_create_datasource_fail_authorization(self):
        self.grafana.api_token = "abcde"  # invalid API token

        expected_message = "Invalid API key"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_postgres_datasource(self.datasource_name)

    def test_create_datasource_fail_permissions(self):
        self.grafana.api_token = self.VIEWER  # API token with viewer permissions

        expected_message = "Access denied - check API permissions"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_postgres_datasource(self.datasource_name)

    def test_create_datasource_fail_empty_string(self):
        expected_message = "Response contains no message"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_postgres_datasource("")

    def test_delete_postgres_datasource(self):
        # create the datasource
        self.grafana.create_postgres_datasource(self.datasource_name)

        # deleted should be True if delete_datasource_by_name returns true
        deleted = self.grafana.delete_datasource_by_name(self.datasource_name)
        self.assertTrue(deleted)

        # confirm that the datasource was actually deleted by querying it
        endpoint = os.path.join(
            self.grafana.endpoints["datasource_name"], self.datasource_name
        )
        headers = {"Content-Type": "application/json"}
        response = requests.get(
            url=endpoint, headers=headers, auth=("api_key", self.grafana.api_token)
        )

        self.assertTrue(response.json()["message"])
        self.assertEquals(response.json()["message"], "Data source not found")

    def test_create_event_creates_dashboard_no_panels(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,
            },
        )

        # 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.
        endpoint = os.path.join(
            self.grafana.hostname,
            "api/dashboards/db",
            self.event_name.lower().replace(" ", "-"),
        )

        response = requests.get(url=endpoint, auth=("api_key", self.grafana.api_token))

        # Confirm that a dashboard was created with the expected name
        self.assertEquals(response.status_code, 200)
        self.assertEquals(response.json()["dashboard"]["title"], self.event_name)

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

        # Create a venue
        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()

        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()
        sensor = AGSensor.objects.create(
            name=self.test_sensor_name, type_id=sensor_type
        )
        sensor.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,
            },
        )

        # 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.
        endpoint = os.path.join(
            self.grafana.hostname,
            "api/dashboards/db",
            self.event_name.lower().replace(" ", "-"),
        )

        response = requests.get(url=endpoint, auth=("api_key", self.grafana.api_token))

        # Confirm that a dashboard was created with the expected name
        self.assertEquals(response.status_code, 200)
        self.assertEquals(response.json()["dashboard"]["title"], self.event_name)

        # Confirm that a the new dashboard can be queried and has panel with expected
        # title
        dashboard_info = self.grafana.get_dashboard_with_uid(
            response.json()["dashboard"]["uid"]
        )
        self.assertTrue(dashboard_info)
        self.assertTrue(dashboard_info["dashboard"])
        self.assertTrue(dashboard_info["dashboard"]["panels"])
        self.assertTrue(len(dashboard_info["dashboard"]["panels"]) == 1)
        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)

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

        event = self.create_venue_and_event(self.event_name)

        updated_event_name = self.updated_event_name

        venue = AGVenue.objects.first()

        # 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)

        # Update the event name
        self.client.post(
            reverse(self.event_update_url, kwargs={"event_uuid": event.uuid}),
            data={
                "name": updated_event_name,
                "description": self.test_event_data["description"],
                "venue_uuid": venue.uuid,
            },
        )

        # Confirm that a dashboard exists with the new event name
        dashboard = self.grafana.get_dashboard_by_name(updated_event_name)
        self.assertTrue(dashboard)
        self.assertEquals(dashboard["dashboard"]["title"], updated_event_name)

    def test_get_all_dashboards(self):
        self.grafana.create_dashboard("hello")
        print(self.grafana.get_all_dashboards())

    def test_create_map_sensor(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
        event = self.create_venue_and_event(self.event_name)

        # Make the event the active event
        AGActiveEvent.objects.create(agevent=event).save()

        # Create a sensor through the UI
        self.client.post(
            reverse(self.sensor_url),
            data={
                "submit_new_sensor": "",
                "sensor-name": self.test_map["name"],
                "field-names": self.test_map["field-names"],
                "data-types": self.test_map["data-types"],
                "units": self.test_map["units"],
                "sensor-graph-type": self.test_map["graph_type"],
            },
        )

        self.assertEquals(AGSensor.objects.count(), 1)

        # Fetch the dashboard again
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)
        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_map["name"].lower(),
        )

        # Check that the sensor panel type is a TrackMap panel
        self.assertEquals(
            dashboard["dashboard"]["panels"][0]["type"], "pr0ps-trackmap-panel",
        )

    def test_create_gauge_sensor(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
        event = self.create_venue_and_event(self.event_name)

        # Make the event the active event
        AGActiveEvent.objects.create(agevent=event).save()

        # Create a sensor through the UI
        self.client.post(
            reverse(self.sensor_url),
            data={
                "submit_new_sensor": "",
                "sensor-name": self.test_gauge["name"],
                "field-names": self.test_gauge["field-names"],
                "data-types": self.test_gauge["data-types"],
                "units": self.test_gauge["units"],
                "sensor-graph-type": self.test_gauge["graph_type"],
            },
        )

        self.assertEquals(AGSensor.objects.count(), 1)

        # Fetch the dashboard again
        dashboard = self.grafana.get_dashboard_by_name(self.event_name)
        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_gauge["name"].lower(),
        )

        # Check that the sensor panel type is a TrackMap panel
        self.assertEquals(
            dashboard["dashboard"]["panels"][0]["type"], "gauge",
        )

    def test_create_map_sensor_fails_insufficient_fields(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
        event = self.create_venue_and_event(self.event_name)

        # Make the event the active event
        AGActiveEvent.objects.create(agevent=event).save()

        bad_map = self.test_map.copy()

        # this map has only 1 field, should be rejected
        bad_map["field-names"] = ["temp"]
        bad_map["data-types"] = ["float"]
        bad_map["units"] = [""]

        # Create a sensor through the UI
        response = self.client.post(
            reverse(self.sensor_url),
            data={
                "submit_new_sensor": "",
                "sensor-name": bad_map["name"],
                "field-names": bad_map["field-names"],
                "data-types": bad_map["data-types"],
                "units": bad_map["units"],
                "sensor-graph-type": bad_map["graph_type"],
            },
        )

        self.assertEquals(AGSensor.objects.count(), 0)

        expected_message = (
            "Map panels must have exactly 2 fields for latitude and "
            "longitude GPS coordinates. Update the sensor fields or "
            "change the Graph Type."
        )

        messages = list(response.context["messages"])
        self.assertEqual(len(messages), 1)
        self.assertEqual(str(messages[0]), expected_message)

    def test_create_gauge_sensor_fails_too_many_fields(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
        event = self.create_venue_and_event(self.event_name)

        # Make the event the active event
        AGActiveEvent.objects.create(agevent=event).save()

        bad_gauge = self.test_gauge.copy()

        # this map has only 1 field, should be rejected
        bad_gauge["field-names"] = ["x", "y", "z"]
        bad_gauge["data-types"] = ["float", "float", "float"]
        bad_gauge["units"] = ["", "", ""]

        # Create a sensor through the UI
        response = self.client.post(
            reverse(self.sensor_url),
            data={
                "submit_new_sensor": "",
                "sensor-name": bad_gauge["name"],
                "field-names": bad_gauge["field-names"],
                "data-types": bad_gauge["data-types"],
                "units": bad_gauge["units"],
                "sensor-graph-type": bad_gauge["graph_type"],
            },
        )

        self.assertEquals(AGSensor.objects.count(), 0)

        expected_message = (
            "Gauge panels must have exactly 1 field. "
            "Update the sensor fields or change the Graph Type."
        )

        messages = list(response.context["messages"])
        self.assertEqual(len(messages), 1)
        self.assertEqual(str(messages[0]), expected_message)

    def test_create_dashboard_update_dict_invalid_dashboard(self):
        dashboard_info = dict()
        panels = []

        expected_message = "Dashboard is missing expected fields. dashboard_info:"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.create_dashboard_update_dict(dashboard_info, panels)

    def test_create_panel_dict_no_fields(self):
        event = self.create_venue_and_event(self.event_name)
        self.grafana.create_dashboard(event.name)

        sensor_type = AGSensorType.objects.create(
            name=self.test_sensor_type,
            processing_formula=0,
            format=self.test_sensor_format,
            graph_type=self.test_sensor_graph_type,
        )
        sensor_type.save()
        sensor = AGSensor.objects.create(
            name=self.test_sensor_name, type_id=sensor_type
        )
        sensor.save()

        sensor_type.format = ""

        expected_message = "Sensor field names are missing"
        with self.assertRaisesMessage(ValueError, expected_message):
            self.grafana.add_panel(sensor, event)
    def post(self, request, *args, **kwargs):
        if "submit" not in request.POST:
            return

        DB = settings.DATABASES
        gf_host = request.POST.get("gf_host")
        gf_name = request.POST.get("gf_name")
        gf_username = request.POST.get("gf_username")
        gf_password = request.POST.get("gf_password")
        gf_token = request.POST.get("gf_token")

        # check whether gf_host already in use
        existing_host = GFConfig.objects.filter(gf_host=gf_host)
        if "update-config" not in request.POST and existing_host:
            messages.error(
                request,
                "Hostname {} already in use".format(gf_host),
            )
            return redirect("/gfconfig")

        # a test is calling this with known gf_token
        if not gf_token:
            auth_url = make_auth_url(gf_host, gf_username, gf_password)
            try:
                gf_token = Grafana.create_api_key(auth_url,
                                                  "mercury-auto-admin",
                                                  "Admin")
            except ValueError as error:
                messages.error(
                    request,
                    "Failed to create API token: {}".format(error),
                )
                return redirect("/gfconfig")

            # the user is submitting an update form
            if "update-config" in request.POST:
                existing_host.update(
                    gf_username=gf_username,
                    gf_password=gf_password,
                    gf_token=gf_token,
                )
                messages.success(request,
                                 "Updated Grafana host: {}".format(gf_host))
                return redirect("/gfconfig")

        # new gfconfig record
        config_data = GFConfig(
            gf_name=gf_name,
            gf_host=gf_host,
            gf_username=gf_username,
            gf_password=gf_password,
            gf_token=gf_token,
            gf_db_host=DB["default"]["HOST"] + ":" +
            str(DB["default"]["PORT"]),
            gf_db_name=DB["default"]["NAME"],
            gf_db_username=DB["default"]["USER"],
            gf_db_pw=DB["default"]["PASSWORD"],
        )

        # Create Grafana instance with host and token
        grafana = Grafana(config_data)

        try:
            grafana.validate_credentials()
            grafana.create_postgres_datasource()
        except ValueError as error:
            messages.error(request, error)
        try:

            config_data.gf_current = True  # Deprecated
            # Only save the config if credentials were validated
            config_data.save()

            # If any events exist, add a dashboard for each event
            # If any sensors exist, add them to each event dashboard
            events = AGEvent.objects.all()
            sensors = AGSensor.objects.all()
            for event in events:
                grafana.create_dashboard(event.name)
                for sensor in sensors:
                    grafana.add_panel(sensor, event)

        except ValueError as error:
            messages.error(request, f"Grafana initial set up failed: {error}")

        messages.success(request, "Created Grafana Host: {}".format(gf_name))
        return redirect("/gfconfig")
    def post(self, request, *args, **kwargs):
        if "submit" not in request.POST:
            return

        DB = settings.DATABASES
        gf_host = request.POST.get("gf_host")
        gf_name = request.POST.get("gf_name")
        gf_username = request.POST.get("gf_username")
        gf_password = request.POST.get("gf_password")
        gf_token = request.POST.get("gf_token")

        # check whether gf_host already in use
        existing_host = GFConfig.objects.filter(gf_host=gf_host)
        if "update-config" not in request.POST and existing_host:
            messages.error(
                request, "Hostname {} already in use".format(gf_host),
            )
            return redirect("/gfconfig")

        configure_grafana_github_url = os.path.join(
            GITHUB_DOCS_ROOT, CONFIGURE_GRAFANA_HELP_DOC
        )

        # user providing username/pasword, generate API key automatically
        if not gf_token:
            auth_url = make_auth_url(gf_host, gf_username, gf_password)
            try:
                gf_token = Grafana.create_api_key(
                    auth_url, "mercury-auto-admin", "Admin"
                )
            except ValueError as error:
                messages.error(
                    request,
                    mark_safe(
                        "Failed to create API token: {}. If this "
                        "problem persists, please provide "
                        "an admin API key directly with the 'Use API "
                        "Key' option in the `Add Grafana Host` form. "
                        'See <a target="_blank" '
                        'href="{}">Configure Grafana: How to Create an '
                        "API Token </a> to learn how to create an "
                        "API "
                        "key.".format(
                            error,
                            configure_grafana_github_url
                            + "#c-how-to-create-an-api-token",
                        )
                    ),
                )
                return redirect("/gfconfig")

        # the user is submitting an update form with username/password
        if "update-config" in request.POST and gf_username and gf_password:
            existing_host.update(
                gf_username=gf_username, gf_password=gf_password, gf_token=gf_token
            )
            messages.success(request, "Updated Grafana host: {}".format(gf_host))
            return redirect("/gfconfig")

        # new gfconfig record
        config_data = GFConfig(
            gf_name=gf_name,
            gf_host=gf_host,
            gf_username=gf_username,
            gf_password=gf_password,
            gf_token=gf_token,
            gf_db_host=DB["default"]["HOST"] + ":" + str(DB["default"]["PORT"]),
            gf_db_name=DB["default"]["NAME"],
            gf_db_username=DB["default"]["USER"],
            gf_db_pw=DB["default"]["PASSWORD"],
        )

        # Create Grafana instance with host and token
        grafana = Grafana(config_data)

        try:
            grafana.validate_credentials()
        except ValueError as error:
            messages.error(request, error)
            return redirect("/gfconfig")

        # the user is submitting an update form with a validated API key
        if "update-config" in request.POST:
            existing_host.update(gf_token=gf_token)
            messages.success(request, "Updated Grafana host: {}".format(gf_host))
            return redirect("/gfconfig")

        try:
            grafana.create_postgres_datasource()
        except ValueError as error:
            messages.error(request, error)

        try:
            config_data.gf_current = True
            config_data.save()

            # If any events exist, add a dashboard for each event
            # If any sensors exist, add them to each event dashboard
            events = AGEvent.objects.all()
            sensors = AGSensor.objects.all()
            for event in events:
                grafana.create_dashboard(event.name)
                for sensor in sensors:
                    grafana.add_panel(sensor, event)

        except ValueError as error:
            messages.warning(request, f"Warning: {error}")

        messages.success(request, "Created Grafana Host: {}".format(gf_name))
        return redirect("/gfconfig")