We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Stop pulling data from RedShift 
Starting in version 72 (Jan 2020), Firefox Telemetry uses BigQuery instead of RedShift. 

We currently pull data from both data sources for frequency capping and performance reports. 

In about a year from now the usage of pre-72 versions will be limited and we will be able to remove the RedShift queries from the codebase.

- [x] Stop pulling for Freq Capped Jobs
- [x] Stop pulling Daily Data

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `snippets/base/etl.py`
Content:
```
1 import collections
2 import json
3 
4 from urllib.parse import urlencode
5 
6 from django.conf import settings
7 from django.db.transaction import atomic
8 from redash_dynamic_query import RedashDynamicQuery
9 
10 from snippets.base.models import CHANNELS, DailyImpressions, JobDailyPerformance, Job
11 
12 
13 REDASH_QUERY_IDS = {
14     'redshift-job': 68135,
15     'bq-job': 68136,
16     'redshift-impressions': 68345,
17     'bq-impressions': 68341,
18 }
19 
20 redash = RedashDynamicQuery(
21     endpoint=settings.REDASH_ENDPOINT,
22     apikey=settings.REDASH_API_KEY,
23     max_wait=settings.REDASH_MAX_WAIT)
24 
25 
26 def redash_source_url(query_id_or_name, **params):
27     query_id = REDASH_QUERY_IDS.get(query_id_or_name, query_id_or_name)
28     url = f'{settings.REDASH_ENDPOINT}/queries/{query_id}/source'
29     if params:
30         url += '?' + urlencode({f'p_{key}_{query_id}': value
31                                 for key, value in params.items()})
32     return url
33 
34 
35 def redash_rows(query_name, date):
36     query_id = REDASH_QUERY_IDS[query_name]
37     bind_data = {'date': str(date)}
38     result = redash.query(query_id, bind_data)
39     return result['query_result']['data']['rows']
40 
41 
42 def prosses_rows(rows, key='message_id'):
43     job_ids = [str(x) for x in Job.objects.all().values_list('id', flat=True)]
44     new_rows = []
45     for row in sorted(rows, key=lambda x: x[key]):
46         # Remove rows with invalid Job IDs
47         if row['message_id'] not in job_ids:
48             continue
49 
50         # Redash uses {} instead of null
51         if row['event_context'] == '{}':
52             row['event_context'] = ''
53 
54         # Sometimes data in Telemetry populate `event_context`, some
55         # other times it uses `additional_properties['value']` to
56         # place the event context. Extract information from both
57         # places to identify the event.
58         properties = json.loads(row.get('additional_properties', '{}'))
59         event = row['event_context'] or properties.get('value', '') or row['event']
60 
61         if event in ['CLICK_BUTTON', 'CLICK']:
62             event = 'click'
63         elif event == 'IMPRESSION':
64             event = 'impression'
65         elif event == 'BLOCK':
66             event = 'block'
67         elif event == 'DISMISS':
68             event = 'dismiss'
69         elif event == 'scene1-button-learn-more':
70             event = 'go_to_scene2'
71         elif event in ['subscribe-success',
72                        'subscribe-error',
73                        'conversion-subscribe-activation']:
74             event = event.replace('-', '_')
75         else:
76             # Ignore invalid event
77             continue
78 
79         row['event'] = event
80 
81         # Normalize channel name, based on what kind of snippets they get.
82         channel = row['channel']
83         if not channel:
84             channel = 'release'
85         row['channel'] = next(
86             (item for item in CHANNELS if
87              channel.startswith(item)), 'release'
88         )
89 
90         # Normalize country
91         country_code = row['country_code']
92         if country_code in ['ERROR', None]:
93             row['country_code'] = 'XX'
94 
95         # Not needed anymore
96         row.pop('event_context', None)
97         row.pop('additional_properties', None)
98 
99         new_rows.append(row)
100 
101     # Aggregate counts of same events for the global count.
102     processed = collections.defaultdict(dict)
103     for row in new_rows:
104         event = row['event']
105         processed[row[key]][event] = processed[row[key]].get(event, 0) + row['counts']
106 
107         detail = [{
108             'event': row['event'],
109             'channel': row['channel'],
110             'country': row['country_code'],
111             'counts': row['counts'],
112         }]
113 
114         if not processed[row[key]].get('details'):
115             processed[row[key]]['details'] = detail
116         else:
117             for drow in processed[row[key]]['details']:
118                 if ((drow['event'] == row['event'] and
119                      drow['channel'] == row['channel'] and
120                      drow['country'] == row['country_code'])):
121                     drow['counts'] += row['counts']
122                     break
123             else:
124                 processed[row[key]]['details'] += detail
125 
126     # Last pass for multi-scene snippets: Click events here refer to
127     #  clicks of secondary links listed on the template that go to
128     #  terms of services or additional information and are displayed
129     #  in the small text below the input element. These do not count
130     #  clicking on `Learn more` (i.e. going from scene 1 to scene 2)
131     #  or the main Call To Action. The later is measured in
132     #  `conversion_subscribe_activation` and this is the value which
133     #  is important to us and thus we rename this to `clicks`.
134     for k, v in processed.items():
135         if 'conversion_subscribe_activation' in v:
136             processed[k]['other_click'] = processed[k].get('click', 0)
137             processed[k]['click'] = processed[k].pop('conversion_subscribe_activation')
138             for row in processed[k]['details']:
139                 if row['event'] == 'click':
140                     row['event'] = 'other_click'
141                 elif row['event'] == 'conversion_subscribe_activation':
142                     row['event'] = 'click'
143 
144     return processed
145 
146 
147 def update_job_metrics(date):
148     rows = []
149     for query in ['redshift-job', 'bq-job']:
150         rows += redash_rows(query, date)
151 
152     processed = prosses_rows(rows, key='message_id')
153     with atomic():
154         JobDailyPerformance.objects.filter(date=date).delete()
155         for job, data in processed.items():
156             JobDailyPerformance.objects.create(
157                 date=date,
158                 job=Job.objects.get(id=job),
159                 **data
160             )
161     return len(processed) > 0
162 
163 
164 def update_impressions(date):
165     rows = []
166 
167     for query in ['redshift-impressions', 'bq-impressions']:
168         rows += redash_rows(query, date)
169 
170     details = []
171     for row in rows:
172         # Normalize channel name, based on what kind of snippets they get.
173         channel = row['channel']
174         if not channel:
175             channel = 'release'
176         channel = next(
177             (item for item in CHANNELS if
178              channel.startswith(item)), 'release'
179         )
180 
181         # Aggregate counts of the same duration and the same channel.
182         for item in details:
183             if (item['channel'] == channel and item['duration'] == row['duration']):
184                 item['counts'] += row['counts']
185                 break
186         else:
187             details.append({
188                 'channel': channel,
189                 'duration': row['duration'],
190                 'counts': row['counts'],
191             })
192 
193     with atomic():
194         DailyImpressions.objects.filter(date=date).delete()
195         DailyImpressions.objects.create(
196             date=date,
197             details=details
198         )
199 
200     return len(details)
201
```
--- 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/snippets/base/etl.py b/snippets/base/etl.py
--- a/snippets/base/etl.py
+++ b/snippets/base/etl.py
@@ -11,10 +11,12 @@
 
 
 REDASH_QUERY_IDS = {
-    'redshift-job': 68135,
     'bq-job': 68136,
-    'redshift-impressions': 68345,
     'bq-impressions': 68341,
+
+    # Not currently used but kept here for reference.
+    'redshift-job': 68135,
+    'redshift-impressions': 68345,
 }
 
 redash = RedashDynamicQuery(
@@ -145,10 +147,7 @@
 
 
 def update_job_metrics(date):
-    rows = []
-    for query in ['redshift-job', 'bq-job']:
-        rows += redash_rows(query, date)
-
+    rows = redash_rows('bq-job', date)
     processed = prosses_rows(rows, key='message_id')
     with atomic():
         JobDailyPerformance.objects.filter(date=date).delete()
@@ -162,11 +161,7 @@
 
 
 def update_impressions(date):
-    rows = []
-
-    for query in ['redshift-impressions', 'bq-impressions']:
-        rows += redash_rows(query, date)
-
+    rows = redash_rows('bq-impressions', date)
     details = []
     for row in rows:
         # Normalize channel name, based on what kind of snippets they get.

{"golden_diff": "diff --git a/snippets/base/etl.py b/snippets/base/etl.py\n--- a/snippets/base/etl.py\n+++ b/snippets/base/etl.py\n@@ -11,10 +11,12 @@\n \n \n REDASH_QUERY_IDS = {\n-    'redshift-job': 68135,\n     'bq-job': 68136,\n-    'redshift-impressions': 68345,\n     'bq-impressions': 68341,\n+\n+    # Not currently used but kept here for reference.\n+    'redshift-job': 68135,\n+    'redshift-impressions': 68345,\n }\n \n redash = RedashDynamicQuery(\n@@ -145,10 +147,7 @@\n \n \n def update_job_metrics(date):\n-    rows = []\n-    for query in ['redshift-job', 'bq-job']:\n-        rows += redash_rows(query, date)\n-\n+    rows = redash_rows('bq-job', date)\n     processed = prosses_rows(rows, key='message_id')\n     with atomic():\n         JobDailyPerformance.objects.filter(date=date).delete()\n@@ -162,11 +161,7 @@\n \n \n def update_impressions(date):\n-    rows = []\n-\n-    for query in ['redshift-impressions', 'bq-impressions']:\n-        rows += redash_rows(query, date)\n-\n+    rows = redash_rows('bq-impressions', date)\n     details = []\n     for row in rows:\n         # Normalize channel name, based on what kind of snippets they get.\n", "issue": "Stop pulling data from RedShift \nStarting in version 72 (Jan 2020), Firefox Telemetry uses BigQuery instead of RedShift. \r\n\r\nWe currently pull data from both data sources for frequency capping and performance reports. \r\n\r\nIn about a year from now the usage of pre-72 versions will be limited and we will be able to remove the RedShift queries from the codebase.\r\n\r\n- [x] Stop pulling for Freq Capped Jobs\r\n- [x] Stop pulling Daily Data\n", "before_files": [{"content": "import collections\nimport json\n\nfrom urllib.parse import urlencode\n\nfrom django.conf import settings\nfrom django.db.transaction import atomic\nfrom redash_dynamic_query import RedashDynamicQuery\n\nfrom snippets.base.models import CHANNELS, DailyImpressions, JobDailyPerformance, Job\n\n\nREDASH_QUERY_IDS = {\n    'redshift-job': 68135,\n    'bq-job': 68136,\n    'redshift-impressions': 68345,\n    'bq-impressions': 68341,\n}\n\nredash = RedashDynamicQuery(\n    endpoint=settings.REDASH_ENDPOINT,\n    apikey=settings.REDASH_API_KEY,\n    max_wait=settings.REDASH_MAX_WAIT)\n\n\ndef redash_source_url(query_id_or_name, **params):\n    query_id = REDASH_QUERY_IDS.get(query_id_or_name, query_id_or_name)\n    url = f'{settings.REDASH_ENDPOINT}/queries/{query_id}/source'\n    if params:\n        url += '?' + urlencode({f'p_{key}_{query_id}': value\n                                for key, value in params.items()})\n    return url\n\n\ndef redash_rows(query_name, date):\n    query_id = REDASH_QUERY_IDS[query_name]\n    bind_data = {'date': str(date)}\n    result = redash.query(query_id, bind_data)\n    return result['query_result']['data']['rows']\n\n\ndef prosses_rows(rows, key='message_id'):\n    job_ids = [str(x) for x in Job.objects.all().values_list('id', flat=True)]\n    new_rows = []\n    for row in sorted(rows, key=lambda x: x[key]):\n        # Remove rows with invalid Job IDs\n        if row['message_id'] not in job_ids:\n            continue\n\n        # Redash uses {} instead of null\n        if row['event_context'] == '{}':\n            row['event_context'] = ''\n\n        # Sometimes data in Telemetry populate `event_context`, some\n        # other times it uses `additional_properties['value']` to\n        # place the event context. Extract information from both\n        # places to identify the event.\n        properties = json.loads(row.get('additional_properties', '{}'))\n        event = row['event_context'] or properties.get('value', '') or row['event']\n\n        if event in ['CLICK_BUTTON', 'CLICK']:\n            event = 'click'\n        elif event == 'IMPRESSION':\n            event = 'impression'\n        elif event == 'BLOCK':\n            event = 'block'\n        elif event == 'DISMISS':\n            event = 'dismiss'\n        elif event == 'scene1-button-learn-more':\n            event = 'go_to_scene2'\n        elif event in ['subscribe-success',\n                       'subscribe-error',\n                       'conversion-subscribe-activation']:\n            event = event.replace('-', '_')\n        else:\n            # Ignore invalid event\n            continue\n\n        row['event'] = event\n\n        # Normalize channel name, based on what kind of snippets they get.\n        channel = row['channel']\n        if not channel:\n            channel = 'release'\n        row['channel'] = next(\n            (item for item in CHANNELS if\n             channel.startswith(item)), 'release'\n        )\n\n        # Normalize country\n        country_code = row['country_code']\n        if country_code in ['ERROR', None]:\n            row['country_code'] = 'XX'\n\n        # Not needed anymore\n        row.pop('event_context', None)\n        row.pop('additional_properties', None)\n\n        new_rows.append(row)\n\n    # Aggregate counts of same events for the global count.\n    processed = collections.defaultdict(dict)\n    for row in new_rows:\n        event = row['event']\n        processed[row[key]][event] = processed[row[key]].get(event, 0) + row['counts']\n\n        detail = [{\n            'event': row['event'],\n            'channel': row['channel'],\n            'country': row['country_code'],\n            'counts': row['counts'],\n        }]\n\n        if not processed[row[key]].get('details'):\n            processed[row[key]]['details'] = detail\n        else:\n            for drow in processed[row[key]]['details']:\n                if ((drow['event'] == row['event'] and\n                     drow['channel'] == row['channel'] and\n                     drow['country'] == row['country_code'])):\n                    drow['counts'] += row['counts']\n                    break\n            else:\n                processed[row[key]]['details'] += detail\n\n    # Last pass for multi-scene snippets: Click events here refer to\n    #  clicks of secondary links listed on the template that go to\n    #  terms of services or additional information and are displayed\n    #  in the small text below the input element. These do not count\n    #  clicking on `Learn more` (i.e. going from scene 1 to scene 2)\n    #  or the main Call To Action. The later is measured in\n    #  `conversion_subscribe_activation` and this is the value which\n    #  is important to us and thus we rename this to `clicks`.\n    for k, v in processed.items():\n        if 'conversion_subscribe_activation' in v:\n            processed[k]['other_click'] = processed[k].get('click', 0)\n            processed[k]['click'] = processed[k].pop('conversion_subscribe_activation')\n            for row in processed[k]['details']:\n                if row['event'] == 'click':\n                    row['event'] = 'other_click'\n                elif row['event'] == 'conversion_subscribe_activation':\n                    row['event'] = 'click'\n\n    return processed\n\n\ndef update_job_metrics(date):\n    rows = []\n    for query in ['redshift-job', 'bq-job']:\n        rows += redash_rows(query, date)\n\n    processed = prosses_rows(rows, key='message_id')\n    with atomic():\n        JobDailyPerformance.objects.filter(date=date).delete()\n        for job, data in processed.items():\n            JobDailyPerformance.objects.create(\n                date=date,\n                job=Job.objects.get(id=job),\n                **data\n            )\n    return len(processed) > 0\n\n\ndef update_impressions(date):\n    rows = []\n\n    for query in ['redshift-impressions', 'bq-impressions']:\n        rows += redash_rows(query, date)\n\n    details = []\n    for row in rows:\n        # Normalize channel name, based on what kind of snippets they get.\n        channel = row['channel']\n        if not channel:\n            channel = 'release'\n        channel = next(\n            (item for item in CHANNELS if\n             channel.startswith(item)), 'release'\n        )\n\n        # Aggregate counts of the same duration and the same channel.\n        for item in details:\n            if (item['channel'] == channel and item['duration'] == row['duration']):\n                item['counts'] += row['counts']\n                break\n        else:\n            details.append({\n                'channel': channel,\n                'duration': row['duration'],\n                'counts': row['counts'],\n            })\n\n    with atomic():\n        DailyImpressions.objects.filter(date=date).delete()\n        DailyImpressions.objects.create(\n            date=date,\n            details=details\n        )\n\n    return len(details)\n", "path": "snippets/base/etl.py"}], "after_files": [{"content": "import collections\nimport json\n\nfrom urllib.parse import urlencode\n\nfrom django.conf import settings\nfrom django.db.transaction import atomic\nfrom redash_dynamic_query import RedashDynamicQuery\n\nfrom snippets.base.models import CHANNELS, DailyImpressions, JobDailyPerformance, Job\n\n\nREDASH_QUERY_IDS = {\n    'bq-job': 68136,\n    'bq-impressions': 68341,\n\n    # Not currently used but kept here for reference.\n    'redshift-job': 68135,\n    'redshift-impressions': 68345,\n}\n\nredash = RedashDynamicQuery(\n    endpoint=settings.REDASH_ENDPOINT,\n    apikey=settings.REDASH_API_KEY,\n    max_wait=settings.REDASH_MAX_WAIT)\n\n\ndef redash_source_url(query_id_or_name, **params):\n    query_id = REDASH_QUERY_IDS.get(query_id_or_name, query_id_or_name)\n    url = f'{settings.REDASH_ENDPOINT}/queries/{query_id}/source'\n    if params:\n        url += '?' + urlencode({f'p_{key}_{query_id}': value\n                                for key, value in params.items()})\n    return url\n\n\ndef redash_rows(query_name, date):\n    query_id = REDASH_QUERY_IDS[query_name]\n    bind_data = {'date': str(date)}\n    result = redash.query(query_id, bind_data)\n    return result['query_result']['data']['rows']\n\n\ndef prosses_rows(rows, key='message_id'):\n    job_ids = [str(x) for x in Job.objects.all().values_list('id', flat=True)]\n    new_rows = []\n    for row in sorted(rows, key=lambda x: x[key]):\n        # Remove rows with invalid Job IDs\n        if row['message_id'] not in job_ids:\n            continue\n\n        # Redash uses {} instead of null\n        if row['event_context'] == '{}':\n            row['event_context'] = ''\n\n        # Sometimes data in Telemetry populate `event_context`, some\n        # other times it uses `additional_properties['value']` to\n        # place the event context. Extract information from both\n        # places to identify the event.\n        properties = json.loads(row.get('additional_properties', '{}'))\n        event = row['event_context'] or properties.get('value', '') or row['event']\n\n        if event in ['CLICK_BUTTON', 'CLICK']:\n            event = 'click'\n        elif event == 'IMPRESSION':\n            event = 'impression'\n        elif event == 'BLOCK':\n            event = 'block'\n        elif event == 'DISMISS':\n            event = 'dismiss'\n        elif event == 'scene1-button-learn-more':\n            event = 'go_to_scene2'\n        elif event in ['subscribe-success',\n                       'subscribe-error',\n                       'conversion-subscribe-activation']:\n            event = event.replace('-', '_')\n        else:\n            # Ignore invalid event\n            continue\n\n        row['event'] = event\n\n        # Normalize channel name, based on what kind of snippets they get.\n        channel = row['channel']\n        if not channel:\n            channel = 'release'\n        row['channel'] = next(\n            (item for item in CHANNELS if\n             channel.startswith(item)), 'release'\n        )\n\n        # Normalize country\n        country_code = row['country_code']\n        if country_code in ['ERROR', None]:\n            row['country_code'] = 'XX'\n\n        # Not needed anymore\n        row.pop('event_context', None)\n        row.pop('additional_properties', None)\n\n        new_rows.append(row)\n\n    # Aggregate counts of same events for the global count.\n    processed = collections.defaultdict(dict)\n    for row in new_rows:\n        event = row['event']\n        processed[row[key]][event] = processed[row[key]].get(event, 0) + row['counts']\n\n        detail = [{\n            'event': row['event'],\n            'channel': row['channel'],\n            'country': row['country_code'],\n            'counts': row['counts'],\n        }]\n\n        if not processed[row[key]].get('details'):\n            processed[row[key]]['details'] = detail\n        else:\n            for drow in processed[row[key]]['details']:\n                if ((drow['event'] == row['event'] and\n                     drow['channel'] == row['channel'] and\n                     drow['country'] == row['country_code'])):\n                    drow['counts'] += row['counts']\n                    break\n            else:\n                processed[row[key]]['details'] += detail\n\n    # Last pass for multi-scene snippets: Click events here refer to\n    #  clicks of secondary links listed on the template that go to\n    #  terms of services or additional information and are displayed\n    #  in the small text below the input element. These do not count\n    #  clicking on `Learn more` (i.e. going from scene 1 to scene 2)\n    #  or the main Call To Action. The later is measured in\n    #  `conversion_subscribe_activation` and this is the value which\n    #  is important to us and thus we rename this to `clicks`.\n    for k, v in processed.items():\n        if 'conversion_subscribe_activation' in v:\n            processed[k]['other_click'] = processed[k].get('click', 0)\n            processed[k]['click'] = processed[k].pop('conversion_subscribe_activation')\n            for row in processed[k]['details']:\n                if row['event'] == 'click':\n                    row['event'] = 'other_click'\n                elif row['event'] == 'conversion_subscribe_activation':\n                    row['event'] = 'click'\n\n    return processed\n\n\ndef update_job_metrics(date):\n    rows = redash_rows('bq-job', date)\n    processed = prosses_rows(rows, key='message_id')\n    with atomic():\n        JobDailyPerformance.objects.filter(date=date).delete()\n        for job, data in processed.items():\n            JobDailyPerformance.objects.create(\n                date=date,\n                job=Job.objects.get(id=job),\n                **data\n            )\n    return len(processed) > 0\n\n\ndef update_impressions(date):\n    rows = redash_rows('bq-impressions', date)\n    details = []\n    for row in rows:\n        # Normalize channel name, based on what kind of snippets they get.\n        channel = row['channel']\n        if not channel:\n            channel = 'release'\n        channel = next(\n            (item for item in CHANNELS if\n             channel.startswith(item)), 'release'\n        )\n\n        # Aggregate counts of the same duration and the same channel.\n        for item in details:\n            if (item['channel'] == channel and item['duration'] == row['duration']):\n                item['counts'] += row['counts']\n                break\n        else:\n            details.append({\n                'channel': channel,\n                'duration': row['duration'],\n                'counts': row['counts'],\n            })\n\n    with atomic():\n        DailyImpressions.objects.filter(date=date).delete()\n        DailyImpressions.objects.create(\n            date=date,\n            details=details\n        )\n\n    return len(details)\n", "path": "snippets/base/etl.py"}]}