Skip to content

Commit 2c2e5d7

Browse files
authored
Merge branch 'develop' into CU-86897a79g-redis-monitoring
2 parents dad651c + 6699867 commit 2c2e5d7

File tree

20 files changed

+224
-181
lines changed

20 files changed

+224
-181
lines changed

.github/workflows/deploy.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
echo "ENV_URL=https://nccopwatch.org/" >> $GITHUB_ENV
2525
- uses: actions/setup-python@v4
2626
with:
27-
python-version: '3.9'
27+
python-version: '3.10'
2828
cache: 'pip'
2929
cache-dependency-path: 'requirements/*/*.txt'
3030
- name: Install dependencies

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
- uses: actions/checkout@v3
2929
- uses: actions/setup-python@v4
3030
with:
31-
python-version: '3.9'
31+
python-version: '3.10'
3232
cache: 'pip'
3333
cache-dependency-path: 'requirements/*/*.txt'
3434
- name: Install dependencies

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ repos:
33
rev: 22.3.0
44
hooks:
55
- id: black
6-
language_version: python3.8
6+
language_version: python3.10
77
exclude: migrations
88
- repo: https://github.com/PyCQA/flake8
99
rev: 4.0.1

Dockerfile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ RUN npm install --silent
88
COPY frontend/ /code/
99
RUN npm run build
1010

11-
FROM python:3.8-slim-bullseye as base
11+
FROM python:3.10-slim-bullseye as base
1212

1313
# Create a group and user to run our app
1414
ARG APP_USER=appuser
@@ -93,7 +93,7 @@ ENTRYPOINT ["/code/docker-entrypoint.sh"]
9393
# Start uWSGI
9494
CMD ["newrelic-admin", "run-program", "uwsgi", "--single-interpreter", "--enable-threads", "--show-config"]
9595

96-
FROM python:3.8-slim-bullseye AS dev
96+
FROM python:3.10-slim-bullseye AS dev
9797

9898
ARG USERNAME=appuser
9999
ARG USER_UID=1000
@@ -113,8 +113,8 @@ RUN groupadd --gid $USER_GID $USERNAME \
113113
# openssh-client -- for git over SSH
114114
# sudo -- to run commands as superuser
115115
# vim -- enhanced vi editor for commits
116-
ENV KUBE_CLIENT_VERSION="v1.25.10"
117-
ENV HELM_VERSION="3.12.0"
116+
ENV KUBE_CLIENT_VERSION="v1.29.4"
117+
ENV HELM_VERSION="3.14.4"
118118
RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \
119119
--mount=type=cache,mode=0755,target=/root/.cache/pip \
120120
set -ex \

deploy/group_vars/all.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ k8s_install_descheduler: yes
6464
# You must set the k8s_descheduler_chart_version to match the Kubernetes
6565
# node version (0.23.x -> K8s 1.23.x); see:
6666
# https://github.com/kubernetes-sigs/descheduler#compatibility-matrix
67-
k8s_descheduler_chart_version: v0.25.2
67+
k8s_descheduler_chart_version: v0.29.0
6868
# See values.yaml for options:
6969
# https://github.com/kubernetes-sigs/descheduler/blob/master/charts/descheduler/values.yaml#L63
7070
k8s_descheduler_release_values:
@@ -94,9 +94,9 @@ k8s_iam_users: [copelco]
9494
# Pin ingress-nginx and cert-manager to current versions so future upgrades of this
9595
# role will not upgrade these charts without your intervention:
9696
# https://github.com/kubernetes/ingress-nginx/releases
97-
k8s_ingress_nginx_chart_version: "4.4.2"
97+
k8s_ingress_nginx_chart_version: "4.9.1"
9898
# https://github.com/jetstack/cert-manager/releases
99-
k8s_cert_manager_chart_version: "v1.11.1"
99+
k8s_cert_manager_chart_version: "v1.14.3"
100100
# AWS only:
101101
# Use the newer load balancer type (NLB). DO NOT edit k8s_aws_load_balancer_type after
102102
# creating your Service.
@@ -108,7 +108,7 @@ k8s_aws_load_balancer_type: nlb
108108

109109
# New Relic Account: forwardjustice-team@caktusgroup.com
110110
# https://github.com/newrelic/helm-charts/releases
111-
k8s_newrelic_chart_version: "5.0.4"
111+
k8s_newrelic_chart_version: "5.0.68"
112112
k8s_newrelic_logging_enabled: true
113113
k8s_newrelic_license_key: !vault |
114114
$ANSIBLE_VAULT;1.1;AES256

deploy/requirements.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22

33
- src: https://github.com/caktus/ansible-role-django-k8s
44
name: caktus.django-k8s
5-
version: v1.6.0
5+
version: v1.9.0
66

77
- src: https://github.com/caktus/ansible-role-aws-web-stacks
88
name: caktus.aws-web-stacks
99
version: ''
1010

1111
- src: https://github.com/caktus/ansible-role-k8s-web-cluster
1212
name: caktus.k8s-web-cluster
13-
version: v1.5.0
13+
version: v1.6.0
1414

1515
- src: https://github.com/caktus/ansible-role-k8s-hosting-services
1616
name: caktus.k8s-hosting-services
17-
version: v0.11.0
17+
version: v0.12.0

docs/dev-setup.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Below you will find basic setup and deployment instructions for the NC Traffic
55
Stops project. To begin you should have the following applications installed on
66
your local development system:
77

8-
- Python 3.8
8+
- Python 3.10
99
- NodeJS >= 12.6.0
1010
- `pip >= 8 or so <http://www.pip-installer.org/>`_
1111
- Postgres >= 12
@@ -85,8 +85,8 @@ To use ``psql`` locally, make sure you have the following env variables loaded
8585
To setup your local environment you should create a virtualenv and install the
8686
necessary requirements::
8787

88-
$ which python3.8 # make sure you have Python 3.8 installed
89-
$ mkvirtualenv --python=`which python3.8` traffic-stops
88+
$ which python3.11 # make sure you have Python 3.11 installed
89+
$ mkvirtualenv --python=`which python3.11` traffic-stops
9090
(traffic-stops)$ pip install -U pip
9191
(traffic-stops)$ make setup
9292

docs/hosting-services.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ The services configured for this project are:
44
* PostgreSQL database backups to S3 (within Caktus AWS account)
55
* Currently, this is only `traffic_stops`, which contains users, census data, etc.
66
* `traffic_stops_nc` is not backed up since the entire dataset is re-imported daily.
7-
* Papertrail logging (to Caktus account)
7+
* New Relic logging (to Caktus account)
88
* New Relic Infrastructure monitoring (Account: `admin+newrelic@caktusgroup.com`)
99

1010

nc/tests/api/test_arrests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test_sort_by_stop_purpose(self):
4646
self.assertEqual(sort_by_stop_purpose(df)["stop_purpose"].tolist(), StopPurpose.values)
4747

4848

49-
@pytest.mark.django_db
49+
@pytest.mark.django_db(databases=["traffic_stops_nc"])
5050
class TestArrests:
5151
def test_arrest_contraband_missing_race(self, client, durham):
5252
"""A single stop will result no data for other races"""

nc/tests/api/test_basic_search.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ def test_no_agency(client, search_url):
1616
assert response.status_code == status.HTTP_400_BAD_REQUEST
1717

1818

19+
@pytest.mark.django_db(databases=["traffic_stops_nc"])
1920
def test_agency_success(client, search_url, durham):
2021
response = client.get(search_url, data={"agency": durham.pk}, format="json")
2122
assert response.status_code == status.HTTP_200_OK
2223

2324

25+
@pytest.mark.django_db(databases=["traffic_stops_nc"])
2426
def test_response_person_fields(client, search_url, durham):
2527
person = factories.PersonFactory(stop__agency=durham)
2628
response = client.get(search_url, data={"agency": durham.pk}, format="json")
@@ -42,6 +44,7 @@ def test_response_person_fields(client, search_url, durham):
4244
assert result == expected
4345

4446

47+
@pytest.mark.django_db(databases=["traffic_stops_nc"])
4548
@pytest.mark.parametrize("race", RACE_VALUES)
4649
def test_race_filtering(client, search_url, durham, race):
4750
other_races = RACE_VALUES - set(race)

nc/tests/api/test_timezones.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def august_person(durham):
2323
return factories.PersonFactory(stop__agency=durham, stop__date=stop_date)
2424

2525

26+
@pytest.mark.django_db(databases=["traffic_stops_nc"])
2627
def test_stop_date_after_august_excludes_july_stop(client, search_url, durham, july_person):
2728
response = client.get(
2829
search_url,
@@ -33,6 +34,7 @@ def test_stop_date_after_august_excludes_july_stop(client, search_url, durham, j
3334
assert july_person.stop.stop_id not in stop_ids
3435

3536

37+
@pytest.mark.django_db(databases=["traffic_stops_nc"])
3638
def test_stop_date_after_august_includes_august_stop(client, search_url, durham, august_person):
3739
response = client.get(
3840
search_url,
@@ -44,6 +46,7 @@ def test_stop_date_after_august_includes_august_stop(client, search_url, durham,
4446
assert august_person.stop.date == response.data["results"][0]["date"]
4547

4648

49+
@pytest.mark.django_db(databases=["traffic_stops_nc"])
4750
def test_stop_date_after_july_includes_both(client, search_url, durham, july_person, august_person):
4851
response = client.get(
4952
search_url,

nc/tests/test_api.py

Lines changed: 51 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ def test_list_agencies(self):
2626
agency = factories.AgencyFactory()
2727
url = reverse("nc:agency-api-list")
2828
response = self.client.get(url, format="json")
29+
response_data = response.json()
2930
self.assertEqual(response.status_code, status.HTTP_200_OK)
3031
# Other Agencies may have been left around from other tests
31-
self.assertIn((agency.pk, agency.name), [(a["id"], a["name"]) for a in response.data])
32+
self.assertIn((agency.pk, agency.name), [(a["id"], a["name"]) for a in response_data])
3233

3334
def test_agency_census_data(self):
3435
"""
@@ -39,19 +40,21 @@ def test_agency_census_data(self):
3940
agency = factories.AgencyFactory(census_profile_id=census_profile.id)
4041
url = reverse("nc:agency-api-detail", args=[agency.pk])
4142
response = self.client.get(url, format="json")
43+
response_data = response.json()
4244
self.assertEqual(response.status_code, status.HTTP_200_OK)
43-
self.assertIn("census_profile", response.data)
45+
self.assertIn("census_profile", response_data)
4446
# CensusProfile tests check census data in more detail
4547
for attr in ("hispanic", "non_hispanic", "total"):
46-
self.assertEqual(response.data["census_profile"][attr], getattr(census_profile, attr))
48+
self.assertEqual(response_data["census_profile"][attr], getattr(census_profile, attr))
4749

4850
def test_stops_api(self):
4951
"""Test Agency stops API endpoint with no stops"""
5052
agency = factories.AgencyFactory()
5153
url = reverse("nc:agency-api-stops", args=[agency.pk])
5254
response = self.client.get(url, format="json")
55+
response_data = response.json()
5356
self.assertEqual(response.status_code, status.HTTP_200_OK)
54-
self.assertEqual(len(response.data), 0)
57+
self.assertEqual(len(response_data), 0)
5558

5659
def test_stops_count(self):
5760
"""Test Agency stop counts"""
@@ -73,18 +76,19 @@ def test_stops_count(self):
7376
factories.PersonFactory(race="I", stop__agency=agency, ethnicity="H", stop__year=2012)
7477

7578
url = reverse("nc:agency-api-stops", args=[agency.pk])
76-
response = self.client.get(url, format="json")
77-
self.assertEqual(len(response.data), 2)
79+
response = self.client.get(url, format="application/json")
80+
response_data = response.json()
81+
self.assertEqual(len(response_data), 2)
7882
self.assertEqual(response.status_code, status.HTTP_200_OK)
79-
self.assertEqual(response.data[0]["year"], 2010)
80-
self.assertEqual(response.data[0]["black"], 2)
81-
self.assertEqual(response.data[0]["white"], 1)
82-
self.assertEqual(response.data[0]["asian"], 0)
83-
self.assertEqual(response.data[0]["hispanic"], 3)
84-
self.assertEqual(response.data[1]["year"], 2012)
85-
self.assertEqual(response.data[1]["black"], 0)
86-
self.assertEqual(response.data[1]["white"], 1)
87-
self.assertEqual(response.data[1]["hispanic"], 4)
83+
self.assertEqual(response_data[0]["year"], 2010)
84+
self.assertEqual(response_data[0]["black"], 2)
85+
self.assertEqual(response_data[0]["white"], 1)
86+
self.assertEqual(response_data[0]["asian"], 0)
87+
self.assertEqual(response_data[0]["hispanic"], 3)
88+
self.assertEqual(response_data[1]["year"], 2012)
89+
self.assertEqual(response_data[1]["black"], 0)
90+
self.assertEqual(response_data[1]["white"], 1)
91+
self.assertEqual(response_data[1]["hispanic"], 4)
8892

8993
def test_grouping_by_year(self):
9094
"""
@@ -111,12 +115,13 @@ def test_grouping_by_year(self):
111115
)
112116
url = reverse("nc:agency-api-stops", args=[agency.pk])
113117
response = self.client.get(url, format="json")
118+
response_data = response.json()
114119
self.assertEqual(response.status_code, status.HTTP_200_OK)
115-
self.assertEqual(len(response.data), 2)
116-
self.assertEqual(response.data[0]["year"], year)
117-
self.assertEqual(response.data[0][race_label], 1)
118-
self.assertEqual(response.data[1]["year"], year + 1)
119-
self.assertEqual(response.data[1]["hispanic"], 1)
120+
self.assertEqual(len(response_data), 2)
121+
self.assertEqual(response_data[0]["year"], year)
122+
self.assertEqual(response_data[0][race_label], 1)
123+
self.assertEqual(response_data[1]["year"], year + 1)
124+
self.assertEqual(response_data[1]["hispanic"], 1)
120125

121126
def test_officer_stops_count(self):
122127
"""Test officer (within an agency) stop counts"""
@@ -131,12 +136,12 @@ def test_officer_stops_count(self):
131136
url = reverse("nc:agency-api-stops", args=[agency.pk])
132137
url = "{}?officer={}".format(url, p1.stop.officer_id)
133138
response = self.client.get(url, format="json")
139+
response_data = response.json()
134140
self.assertEqual(response.status_code, status.HTTP_200_OK)
135-
self.assertEqual(len(response.data), 2)
136-
self.assertEqual(response.data[0]["year"], p1.stop.date.year)
137-
self.assertEqual(response.data[0][GROUPS[p1.race]], 1)
138-
self.assertEqual(response.data[1]["year"], p2.stop.date.year)
139-
self.assertEqual(response.data[1]["hispanic"], 2)
141+
self.assertEqual(len(response_data), 2)
142+
self.assertEqual(response_data[0]["year"], p1.stop.date.year)
143+
self.assertEqual(response_data[1]["year"], p2.stop.date.year)
144+
self.assertEqual(response_data[1]["hispanic"], 2)
140145

141146
def test_stops_by_reason(self):
142147
"""Test Agency stops_by_reason API endpoint"""
@@ -189,10 +194,11 @@ def test_stops_by_reason(self):
189194
factories.SearchFactory(stop=p5.stop)
190195

191196
response = self.client.get(url, format="json")
197+
response_data = response.json()
192198
self.assertEqual(response.status_code, status.HTTP_200_OK)
193-
self.assertEqual(len(response.data.keys()), 2)
199+
self.assertEqual(len(response_data.keys()), 2)
194200

195-
searches = response.data["searches"]
201+
searches = response_data["searches"]
196202
self.assertEqual(searches[0]["year"], 2010)
197203
self.assertEqual(searches[0]["black"], 0)
198204
self.assertEqual(searches[0]["hispanic"], 3)
@@ -201,7 +207,7 @@ def test_stops_by_reason(self):
201207
self.assertEqual(searches[1]["black"], 1)
202208
self.assertEqual(searches[1]["purpose"], purpose_label)
203209

204-
stops = response.data["stops"]
210+
stops = response_data["stops"]
205211
self.assertEqual(stops[0]["year"], 2010)
206212
self.assertEqual(stops[0]["black"], 1)
207213
self.assertEqual(stops[0]["hispanic"], 3)
@@ -227,16 +233,17 @@ def test_searches(self):
227233
factories.SearchFactory(person=p5, stop=p5.stop)
228234
url = reverse("nc:agency-api-searches", args=[agency.pk])
229235
response = self.client.get(url, format="json")
236+
response_data = response.json()
230237
self.assertEqual(response.status_code, status.HTTP_200_OK)
231-
self.assertEqual(len(response.data), 2)
238+
self.assertEqual(len(response_data), 2)
232239
# Everyone got searched, so the expected racial data for 2015 are: 1 black,
233240
# and for 2016 are: 1 native american, 3 hispanic
234-
self.assertEqual(response.data[0]["year"], s1.stop.date.year)
235-
self.assertEqual(response.data[0]["black"], 1)
236-
self.assertEqual(response.data[1]["year"], s2.stop.date.year)
237-
self.assertEqual(response.data[1]["black"], 0)
238-
self.assertEqual(response.data[1]["native_american"], 1)
239-
self.assertEqual(response.data[1]["hispanic"], 3)
241+
self.assertEqual(response_data[0]["year"], s1.stop.date.year)
242+
self.assertEqual(response_data[0]["black"], 1)
243+
self.assertEqual(response_data[1]["year"], s2.stop.date.year)
244+
self.assertEqual(response_data[1]["black"], 0)
245+
self.assertEqual(response_data[1]["native_american"], 1)
246+
self.assertEqual(response_data[1]["hispanic"], 3)
240247

241248
def test_searches_by_reason(self):
242249
agency = factories.AgencyFactory()
@@ -259,18 +266,18 @@ def test_searches_by_reason(self):
259266
factories.SearchFactory(person=p5, stop=p5.stop, type=type_code)
260267

261268
response = self.client.get(url, format="json")
269+
response_data = response.json()
262270
self.assertEqual(response.status_code, status.HTTP_200_OK)
263271
# Two years = two items
264-
self.assertEqual(len(response.data), 2)
272+
self.assertEqual(len(response_data), 2)
265273

266-
searches = response.data
267-
self.assertEqual(searches[0]["year"], 2015)
268-
self.assertEqual(searches[0]["black"], 1)
269-
self.assertEqual(searches[0]["search_type"], type_label)
270-
self.assertEqual(searches[1]["year"], 2016)
271-
self.assertEqual(searches[1]["hispanic"], 3)
272-
self.assertEqual(searches[1]["native_american"], 1)
273-
self.assertEqual(searches[1]["search_type"], type_label)
274+
self.assertEqual(response_data[0]["year"], 2015)
275+
self.assertEqual(response_data[0]["black"], 1)
276+
self.assertEqual(response_data[0]["search_type"], type_label)
277+
self.assertEqual(response_data[1]["year"], 2016)
278+
self.assertEqual(response_data[1]["hispanic"], 3)
279+
self.assertEqual(response_data[1]["native_american"], 1)
280+
self.assertEqual(response_data[1]["search_type"], type_label)
274281

275282
def test_use_of_force(self):
276283
pass

0 commit comments

Comments
 (0)