We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Data Source Lifecycle Events
1. Add event when someone opens the new data source dialog.
2. Add event when someone edits a data source (on the backend).
3. Update Test Connection event to include result of test.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `redash/handlers/data_sources.py`
Content:
```
1 import logging
2 
3 from flask import make_response, request
4 from flask_restful import abort
5 from funcy import project
6 from six import text_type
7 from sqlalchemy.exc import IntegrityError
8 
9 from redash import models
10 from redash.handlers.base import BaseResource, get_object_or_404, require_fields
11 from redash.permissions import (require_access, require_admin,
12                                 require_permission, view_only)
13 from redash.query_runner import (get_configuration_schema_for_query_runner_type,
14                                  query_runners, NotSupported)
15 from redash.utils import filter_none
16 from redash.utils.configuration import ConfigurationContainer, ValidationError
17 
18 
19 class DataSourceTypeListResource(BaseResource):
20     @require_admin
21     def get(self):
22         return [q.to_dict() for q in sorted(query_runners.values(), key=lambda q: q.name())]
23 
24 
25 class DataSourceResource(BaseResource):
26     @require_admin
27     def get(self, data_source_id):
28         data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)
29         ds = data_source.to_dict(all=True)
30         self.record_event({
31             'action': 'view',
32             'object_id': data_source_id,
33             'object_type': 'datasource',
34         })
35         return ds
36 
37     @require_admin
38     def post(self, data_source_id):
39         data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)
40         req = request.get_json(True)
41 
42         schema = get_configuration_schema_for_query_runner_type(req['type'])
43         if schema is None:
44             abort(400)
45         try:
46             data_source.options.set_schema(schema)
47             data_source.options.update(filter_none(req['options']))
48         except ValidationError:
49             abort(400)
50 
51         data_source.type = req['type']
52         data_source.name = req['name']
53         models.db.session.add(data_source)
54 
55         try:
56             models.db.session.commit()
57         except IntegrityError as e:
58             if req['name'] in e.message:
59                 abort(400, message="Data source with the name {} already exists.".format(req['name']))
60 
61             abort(400)
62 
63         return data_source.to_dict(all=True)
64 
65     @require_admin
66     def delete(self, data_source_id):
67         data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)
68         data_source.delete()
69 
70         self.record_event({
71             'action': 'delete',
72             'object_id': data_source_id,
73             'object_type': 'datasource',
74         })
75 
76         return make_response('', 204)
77 
78 
79 class DataSourceListResource(BaseResource):
80     @require_permission('list_data_sources')
81     def get(self):
82         if self.current_user.has_permission('admin'):
83             data_sources = models.DataSource.all(self.current_org)
84         else:
85             data_sources = models.DataSource.all(self.current_org, group_ids=self.current_user.group_ids)
86 
87         response = {}
88         for ds in data_sources:
89             if ds.id in response:
90                 continue
91 
92             try:
93                 d = ds.to_dict()
94                 d['view_only'] = all(project(ds.groups, self.current_user.group_ids).values())
95                 response[ds.id] = d
96             except AttributeError:
97                 logging.exception("Error with DataSource#to_dict (data source id: %d)", ds.id)
98 
99         self.record_event({
100             'action': 'list',
101             'object_id': 'admin/data_sources',
102             'object_type': 'datasource',
103         })
104 
105         return sorted(response.values(), key=lambda d: d['name'].lower())
106 
107     @require_admin
108     def post(self):
109         req = request.get_json(True)
110         require_fields(req, ('options', 'name', 'type'))
111 
112         schema = get_configuration_schema_for_query_runner_type(req['type'])
113         if schema is None:
114             abort(400)
115 
116         config = ConfigurationContainer(filter_none(req['options']), schema)
117         # from IPython import embed
118         # embed()
119         if not config.is_valid():
120             abort(400)
121 
122         try:
123             datasource = models.DataSource.create_with_group(org=self.current_org,
124                                                              name=req['name'],
125                                                              type=req['type'],
126                                                              options=config)
127 
128             models.db.session.commit()
129         except IntegrityError as e:
130             if req['name'] in e.message:
131                 abort(400, message="Data source with the name {} already exists.".format(req['name']))
132 
133             abort(400)
134 
135         self.record_event({
136             'action': 'create',
137             'object_id': datasource.id,
138             'object_type': 'datasource'
139         })
140 
141         return datasource.to_dict(all=True)
142 
143 
144 class DataSourceSchemaResource(BaseResource):
145     def get(self, data_source_id):
146         data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)
147         require_access(data_source, self.current_user, view_only)
148         refresh = request.args.get('refresh') is not None
149 
150         response = {}
151 
152         try:
153             response['schema'] = data_source.get_schema(refresh)
154         except NotSupported:
155             response['error'] = {
156                 'code': 1,
157                 'message': 'Data source type does not support retrieving schema'
158             }
159         except Exception:
160             response['error'] = {
161                 'code': 2,
162                 'message': 'Error retrieving schema.'
163             }
164 
165         return response
166 
167 
168 class DataSourcePauseResource(BaseResource):
169     @require_admin
170     def post(self, data_source_id):
171         data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)
172         data = request.get_json(force=True, silent=True)
173         if data:
174             reason = data.get('reason')
175         else:
176             reason = request.args.get('reason')
177 
178         data_source.pause(reason)
179 
180         self.record_event({
181             'action': 'pause',
182             'object_id': data_source.id,
183             'object_type': 'datasource'
184         })
185         return data_source.to_dict()
186 
187     @require_admin
188     def delete(self, data_source_id):
189         data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)
190         data_source.resume()
191 
192         self.record_event({
193             'action': 'resume',
194             'object_id': data_source.id,
195             'object_type': 'datasource'
196         })
197         return data_source.to_dict()
198 
199 
200 class DataSourceTestResource(BaseResource):
201     @require_admin
202     def post(self, data_source_id):
203         data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)
204 
205         self.record_event({
206             'action': 'test',
207             'object_id': data_source_id,
208             'object_type': 'datasource',
209         })
210 
211         try:
212             data_source.query_runner.test_connection()
213         except Exception as e:
214             return {"message": text_type(e), "ok": False}
215         else:
216             return {"message": "success", "ok": True}
217
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/redash/handlers/data_sources.py b/redash/handlers/data_sources.py
--- a/redash/handlers/data_sources.py
+++ b/redash/handlers/data_sources.py
@@ -60,6 +60,12 @@
 
             abort(400)
 
+        self.record_event({
+            'action': 'edit',
+            'object_id': data_source.id,
+            'object_type': 'datasource',
+        })
+
         return data_source.to_dict(all=True)
 
     @require_admin
@@ -202,15 +208,18 @@
     def post(self, data_source_id):
         data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)
 
+        response = {}
+        try:
+            data_source.query_runner.test_connection()
+        except Exception as e:
+            response = {"message": text_type(e), "ok": False}
+        else:
+            response = {"message": "success", "ok": True}
+
         self.record_event({
             'action': 'test',
             'object_id': data_source_id,
             'object_type': 'datasource',
+            'result': response,
         })
-
-        try:
-            data_source.query_runner.test_connection()
-        except Exception as e:
-            return {"message": text_type(e), "ok": False}
-        else:
-            return {"message": "success", "ok": True}
+        return response

{"golden_diff": "diff --git a/redash/handlers/data_sources.py b/redash/handlers/data_sources.py\n--- a/redash/handlers/data_sources.py\n+++ b/redash/handlers/data_sources.py\n@@ -60,6 +60,12 @@\n \n             abort(400)\n \n+        self.record_event({\n+            'action': 'edit',\n+            'object_id': data_source.id,\n+            'object_type': 'datasource',\n+        })\n+\n         return data_source.to_dict(all=True)\n \n     @require_admin\n@@ -202,15 +208,18 @@\n     def post(self, data_source_id):\n         data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n \n+        response = {}\n+        try:\n+            data_source.query_runner.test_connection()\n+        except Exception as e:\n+            response = {\"message\": text_type(e), \"ok\": False}\n+        else:\n+            response = {\"message\": \"success\", \"ok\": True}\n+\n         self.record_event({\n             'action': 'test',\n             'object_id': data_source_id,\n             'object_type': 'datasource',\n+            'result': response,\n         })\n-\n-        try:\n-            data_source.query_runner.test_connection()\n-        except Exception as e:\n-            return {\"message\": text_type(e), \"ok\": False}\n-        else:\n-            return {\"message\": \"success\", \"ok\": True}\n+        return response\n", "issue": "Data Source Lifecycle Events\n1. Add event when someone opens the new data source dialog.\r\n2. Add event when someone edits a data source (on the backend).\r\n3. Update Test Connection event to include result of test.\n", "before_files": [{"content": "import logging\n\nfrom flask import make_response, request\nfrom flask_restful import abort\nfrom funcy import project\nfrom six import text_type\nfrom sqlalchemy.exc import IntegrityError\n\nfrom redash import models\nfrom redash.handlers.base import BaseResource, get_object_or_404, require_fields\nfrom redash.permissions import (require_access, require_admin,\n                                require_permission, view_only)\nfrom redash.query_runner import (get_configuration_schema_for_query_runner_type,\n                                 query_runners, NotSupported)\nfrom redash.utils import filter_none\nfrom redash.utils.configuration import ConfigurationContainer, ValidationError\n\n\nclass DataSourceTypeListResource(BaseResource):\n    @require_admin\n    def get(self):\n        return [q.to_dict() for q in sorted(query_runners.values(), key=lambda q: q.name())]\n\n\nclass DataSourceResource(BaseResource):\n    @require_admin\n    def get(self, data_source_id):\n        data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)\n        ds = data_source.to_dict(all=True)\n        self.record_event({\n            'action': 'view',\n            'object_id': data_source_id,\n            'object_type': 'datasource',\n        })\n        return ds\n\n    @require_admin\n    def post(self, data_source_id):\n        data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)\n        req = request.get_json(True)\n\n        schema = get_configuration_schema_for_query_runner_type(req['type'])\n        if schema is None:\n            abort(400)\n        try:\n            data_source.options.set_schema(schema)\n            data_source.options.update(filter_none(req['options']))\n        except ValidationError:\n            abort(400)\n\n        data_source.type = req['type']\n        data_source.name = req['name']\n        models.db.session.add(data_source)\n\n        try:\n            models.db.session.commit()\n        except IntegrityError as e:\n            if req['name'] in e.message:\n                abort(400, message=\"Data source with the name {} already exists.\".format(req['name']))\n\n            abort(400)\n\n        return data_source.to_dict(all=True)\n\n    @require_admin\n    def delete(self, data_source_id):\n        data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)\n        data_source.delete()\n\n        self.record_event({\n            'action': 'delete',\n            'object_id': data_source_id,\n            'object_type': 'datasource',\n        })\n\n        return make_response('', 204)\n\n\nclass DataSourceListResource(BaseResource):\n    @require_permission('list_data_sources')\n    def get(self):\n        if self.current_user.has_permission('admin'):\n            data_sources = models.DataSource.all(self.current_org)\n        else:\n            data_sources = models.DataSource.all(self.current_org, group_ids=self.current_user.group_ids)\n\n        response = {}\n        for ds in data_sources:\n            if ds.id in response:\n                continue\n\n            try:\n                d = ds.to_dict()\n                d['view_only'] = all(project(ds.groups, self.current_user.group_ids).values())\n                response[ds.id] = d\n            except AttributeError:\n                logging.exception(\"Error with DataSource#to_dict (data source id: %d)\", ds.id)\n\n        self.record_event({\n            'action': 'list',\n            'object_id': 'admin/data_sources',\n            'object_type': 'datasource',\n        })\n\n        return sorted(response.values(), key=lambda d: d['name'].lower())\n\n    @require_admin\n    def post(self):\n        req = request.get_json(True)\n        require_fields(req, ('options', 'name', 'type'))\n\n        schema = get_configuration_schema_for_query_runner_type(req['type'])\n        if schema is None:\n            abort(400)\n\n        config = ConfigurationContainer(filter_none(req['options']), schema)\n        # from IPython import embed\n        # embed()\n        if not config.is_valid():\n            abort(400)\n\n        try:\n            datasource = models.DataSource.create_with_group(org=self.current_org,\n                                                             name=req['name'],\n                                                             type=req['type'],\n                                                             options=config)\n\n            models.db.session.commit()\n        except IntegrityError as e:\n            if req['name'] in e.message:\n                abort(400, message=\"Data source with the name {} already exists.\".format(req['name']))\n\n            abort(400)\n\n        self.record_event({\n            'action': 'create',\n            'object_id': datasource.id,\n            'object_type': 'datasource'\n        })\n\n        return datasource.to_dict(all=True)\n\n\nclass DataSourceSchemaResource(BaseResource):\n    def get(self, data_source_id):\n        data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n        require_access(data_source, self.current_user, view_only)\n        refresh = request.args.get('refresh') is not None\n\n        response = {}\n\n        try:\n            response['schema'] = data_source.get_schema(refresh)\n        except NotSupported:\n            response['error'] = {\n                'code': 1,\n                'message': 'Data source type does not support retrieving schema'\n            }\n        except Exception:\n            response['error'] = {\n                'code': 2,\n                'message': 'Error retrieving schema.'\n            }\n\n        return response\n\n\nclass DataSourcePauseResource(BaseResource):\n    @require_admin\n    def post(self, data_source_id):\n        data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n        data = request.get_json(force=True, silent=True)\n        if data:\n            reason = data.get('reason')\n        else:\n            reason = request.args.get('reason')\n\n        data_source.pause(reason)\n\n        self.record_event({\n            'action': 'pause',\n            'object_id': data_source.id,\n            'object_type': 'datasource'\n        })\n        return data_source.to_dict()\n\n    @require_admin\n    def delete(self, data_source_id):\n        data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n        data_source.resume()\n\n        self.record_event({\n            'action': 'resume',\n            'object_id': data_source.id,\n            'object_type': 'datasource'\n        })\n        return data_source.to_dict()\n\n\nclass DataSourceTestResource(BaseResource):\n    @require_admin\n    def post(self, data_source_id):\n        data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n\n        self.record_event({\n            'action': 'test',\n            'object_id': data_source_id,\n            'object_type': 'datasource',\n        })\n\n        try:\n            data_source.query_runner.test_connection()\n        except Exception as e:\n            return {\"message\": text_type(e), \"ok\": False}\n        else:\n            return {\"message\": \"success\", \"ok\": True}\n", "path": "redash/handlers/data_sources.py"}], "after_files": [{"content": "import logging\n\nfrom flask import make_response, request\nfrom flask_restful import abort\nfrom funcy import project\nfrom six import text_type\nfrom sqlalchemy.exc import IntegrityError\n\nfrom redash import models\nfrom redash.handlers.base import BaseResource, get_object_or_404, require_fields\nfrom redash.permissions import (require_access, require_admin,\n                                require_permission, view_only)\nfrom redash.query_runner import (get_configuration_schema_for_query_runner_type,\n                                 query_runners, NotSupported)\nfrom redash.utils import filter_none\nfrom redash.utils.configuration import ConfigurationContainer, ValidationError\n\n\nclass DataSourceTypeListResource(BaseResource):\n    @require_admin\n    def get(self):\n        return [q.to_dict() for q in sorted(query_runners.values(), key=lambda q: q.name())]\n\n\nclass DataSourceResource(BaseResource):\n    @require_admin\n    def get(self, data_source_id):\n        data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)\n        ds = data_source.to_dict(all=True)\n        self.record_event({\n            'action': 'view',\n            'object_id': data_source_id,\n            'object_type': 'datasource',\n        })\n        return ds\n\n    @require_admin\n    def post(self, data_source_id):\n        data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)\n        req = request.get_json(True)\n\n        schema = get_configuration_schema_for_query_runner_type(req['type'])\n        if schema is None:\n            abort(400)\n        try:\n            data_source.options.set_schema(schema)\n            data_source.options.update(filter_none(req['options']))\n        except ValidationError:\n            abort(400)\n\n        data_source.type = req['type']\n        data_source.name = req['name']\n        models.db.session.add(data_source)\n\n        try:\n            models.db.session.commit()\n        except IntegrityError as e:\n            if req['name'] in e.message:\n                abort(400, message=\"Data source with the name {} already exists.\".format(req['name']))\n\n            abort(400)\n\n        self.record_event({\n            'action': 'edit',\n            'object_id': data_source.id,\n            'object_type': 'datasource',\n        })\n\n        return data_source.to_dict(all=True)\n\n    @require_admin\n    def delete(self, data_source_id):\n        data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)\n        data_source.delete()\n\n        self.record_event({\n            'action': 'delete',\n            'object_id': data_source_id,\n            'object_type': 'datasource',\n        })\n\n        return make_response('', 204)\n\n\nclass DataSourceListResource(BaseResource):\n    @require_permission('list_data_sources')\n    def get(self):\n        if self.current_user.has_permission('admin'):\n            data_sources = models.DataSource.all(self.current_org)\n        else:\n            data_sources = models.DataSource.all(self.current_org, group_ids=self.current_user.group_ids)\n\n        response = {}\n        for ds in data_sources:\n            if ds.id in response:\n                continue\n\n            try:\n                d = ds.to_dict()\n                d['view_only'] = all(project(ds.groups, self.current_user.group_ids).values())\n                response[ds.id] = d\n            except AttributeError:\n                logging.exception(\"Error with DataSource#to_dict (data source id: %d)\", ds.id)\n\n        self.record_event({\n            'action': 'list',\n            'object_id': 'admin/data_sources',\n            'object_type': 'datasource',\n        })\n\n        return sorted(response.values(), key=lambda d: d['name'].lower())\n\n    @require_admin\n    def post(self):\n        req = request.get_json(True)\n        require_fields(req, ('options', 'name', 'type'))\n\n        schema = get_configuration_schema_for_query_runner_type(req['type'])\n        if schema is None:\n            abort(400)\n\n        config = ConfigurationContainer(filter_none(req['options']), schema)\n        # from IPython import embed\n        # embed()\n        if not config.is_valid():\n            abort(400)\n\n        try:\n            datasource = models.DataSource.create_with_group(org=self.current_org,\n                                                             name=req['name'],\n                                                             type=req['type'],\n                                                             options=config)\n\n            models.db.session.commit()\n        except IntegrityError as e:\n            if req['name'] in e.message:\n                abort(400, message=\"Data source with the name {} already exists.\".format(req['name']))\n\n            abort(400)\n\n        self.record_event({\n            'action': 'create',\n            'object_id': datasource.id,\n            'object_type': 'datasource'\n        })\n\n        return datasource.to_dict(all=True)\n\n\nclass DataSourceSchemaResource(BaseResource):\n    def get(self, data_source_id):\n        data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n        require_access(data_source, self.current_user, view_only)\n        refresh = request.args.get('refresh') is not None\n\n        response = {}\n\n        try:\n            response['schema'] = data_source.get_schema(refresh)\n        except NotSupported:\n            response['error'] = {\n                'code': 1,\n                'message': 'Data source type does not support retrieving schema'\n            }\n        except Exception:\n            response['error'] = {\n                'code': 2,\n                'message': 'Error retrieving schema.'\n            }\n\n        return response\n\n\nclass DataSourcePauseResource(BaseResource):\n    @require_admin\n    def post(self, data_source_id):\n        data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n        data = request.get_json(force=True, silent=True)\n        if data:\n            reason = data.get('reason')\n        else:\n            reason = request.args.get('reason')\n\n        data_source.pause(reason)\n\n        self.record_event({\n            'action': 'pause',\n            'object_id': data_source.id,\n            'object_type': 'datasource'\n        })\n        return data_source.to_dict()\n\n    @require_admin\n    def delete(self, data_source_id):\n        data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n        data_source.resume()\n\n        self.record_event({\n            'action': 'resume',\n            'object_id': data_source.id,\n            'object_type': 'datasource'\n        })\n        return data_source.to_dict()\n\n\nclass DataSourceTestResource(BaseResource):\n    @require_admin\n    def post(self, data_source_id):\n        data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n\n        response = {}\n        try:\n            data_source.query_runner.test_connection()\n        except Exception as e:\n            response = {\"message\": text_type(e), \"ok\": False}\n        else:\n            response = {\"message\": \"success\", \"ok\": True}\n\n        self.record_event({\n            'action': 'test',\n            'object_id': data_source_id,\n            'object_type': 'datasource',\n            'result': response,\n        })\n        return response\n", "path": "redash/handlers/data_sources.py"}]}