Skip to content

Commit 535fd18

Browse files
authored
Merge pull request #232 from caktus/develop
Prepare production release v1.11.0 #227
2 parents e6fdb38 + 0ea9a73 commit 535fd18

File tree

10 files changed

+90
-36
lines changed

10 files changed

+90
-36
lines changed

Dockerfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:16-bullseye-slim as static_files
1+
FROM node:18.17.0-bullseye-slim as static_files
22

33
WORKDIR /code
44
ENV PATH /code/node_modules/.bin:$PATH
@@ -148,7 +148,7 @@ RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/
148148
&& curl https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor | tee /etc/apt/trusted.gpg.d/docker.gpg >/dev/null \
149149
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/trusted.gpg.d/docker.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
150150
# nodejs
151-
&& sh -c 'echo "deb https://deb.nodesource.com/node_16.x $(lsb_release -cs) main" > /etc/apt/sources.list.d/nodesource.list' \
151+
&& sh -c 'echo "deb https://deb.nodesource.com/node_18.x $(lsb_release -cs) main" > /etc/apt/sources.list.d/nodesource.list' \
152152
&& wget --quiet -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
153153
# PostgreSQL
154154
&& sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' \

frontend/src/Components/Charts/TrafficStops/TrafficStops.js

+11-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef } from 'react';
22
import TrafficStopsStyled, {
33
GroupedStopsContainer,
44
LineWrapper,
5+
PieStopsContainer,
56
PieWrapper,
67
StopGroupsContainer,
78
SwitchContainer,
@@ -704,6 +705,7 @@ function TrafficStops(props) {
704705
title="Stop Purposes By Group"
705706
maintainAspectRatio={false}
706707
displayStopPurposeTooltips
708+
showLegendOnBottom={false}
707709
/>
708710
</StopGroupsContainer>
709711
</LineWrapper>
@@ -763,6 +765,7 @@ function TrafficStops(props) {
763765
maintainAspectRatio={false}
764766
displayLegend={false}
765767
yAxisMax={stopsGroupedByPurposeData.max_step_size}
768+
redraw
766769
/>
767770
</GroupedStopsContainer>
768771
<GroupedStopsContainer visible={visibleStopsGroupedByPurpose[1].visible}>
@@ -773,6 +776,7 @@ function TrafficStops(props) {
773776
displayLegend={false}
774777
yAxisMax={stopsGroupedByPurposeData.max_step_size}
775778
yAxisShowLabels={!visibleStopsGroupedByPurpose[0].visible}
779+
redraw
776780
/>
777781
</GroupedStopsContainer>
778782
<GroupedStopsContainer visible={visibleStopsGroupedByPurpose[2].visible}>
@@ -785,6 +789,7 @@ function TrafficStops(props) {
785789
yAxisShowLabels={
786790
!visibleStopsGroupedByPurpose[0].visible && !visibleStopsGroupedByPurpose[1].visible
787791
}
792+
redraw
788793
/>
789794
</GroupedStopsContainer>
790795
</LineWrapper>
@@ -797,30 +802,30 @@ function TrafficStops(props) {
797802
/>
798803
)}
799804
<PieWrapper visible={checked === true}>
800-
<GroupedStopsContainer visible={visibleStopsGroupedByPurpose[0].visible}>
805+
<PieStopsContainer visible={visibleStopsGroupedByPurpose[0].visible}>
801806
<PieChart
802807
data={stopsGroupedByPurposePieData.safety}
803808
title="Safety Violation"
804809
maintainAspectRatio={false}
805810
displayLegend={false}
806811
/>
807-
</GroupedStopsContainer>
808-
<GroupedStopsContainer visible={visibleStopsGroupedByPurpose[1].visible}>
812+
</PieStopsContainer>
813+
<PieStopsContainer visible={visibleStopsGroupedByPurpose[1].visible}>
809814
<PieChart
810815
data={stopsGroupedByPurposePieData.regulatory}
811816
title="Regulatory/Equipment"
812817
maintainAspectRatio={false}
813818
displayLegend={false}
814819
/>
815-
</GroupedStopsContainer>
816-
<GroupedStopsContainer visible={visibleStopsGroupedByPurpose[2].visible}>
820+
</PieStopsContainer>
821+
<PieStopsContainer visible={visibleStopsGroupedByPurpose[2].visible}>
817822
<PieChart
818823
data={stopsGroupedByPurposePieData.other}
819824
title="Other"
820825
maintainAspectRatio={false}
821826
displayLegend={false}
822827
/>
823-
</GroupedStopsContainer>
828+
</PieStopsContainer>
824829
</PieWrapper>
825830

826831
<Legend

frontend/src/Components/Charts/TrafficStops/TrafficStops.styled.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ export const LineWrapper = styled.div`
2020
export const PieWrapper = styled.div`
2121
display: ${(props) => (props.visible ? 'flex' : 'none')};
2222
flex-direction: row;
23-
flex-wrap: wrap;
2423
gap: 10px;
2524
justify-content: space-evenly;
25+
26+
@media (${smallerThanTabletLandscape}) {
27+
flex-direction: column;
28+
}
2629
`;
2730

2831
export const StopGroupsContainer = styled.div`
@@ -31,7 +34,16 @@ export const StopGroupsContainer = styled.div`
3134
`;
3235

3336
export const GroupedStopsContainer = styled.div`
34-
width: 30%;
37+
width: 80%;
38+
height: 500px;
39+
@media (${smallerThanTabletLandscape}) {
40+
width: 100%;
41+
}
42+
display: ${(props) => (props.visible ? 'block' : 'none')};
43+
`;
44+
45+
export const PieStopsContainer = styled.div`
46+
width: 33%;
3547
height: 500px;
3648
@media (${smallerThanTabletLandscape}) {
3749
width: 100%;

frontend/src/Components/Elements/Table/TableModal.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -531,17 +531,17 @@ function TableModal({ chartState, dataSet, columns, isOpen, closeModal }) {
531531
const subheadingForDataset = (ds) => {
532532
const message = 'The following data correspond to the number of times each race was';
533533
if (ds === CONTRABAND_HIT_RATE) {
534-
return `${message} found with contraband during a stop totalled by year.`;
534+
return `${message} found with contraband during a stop totaled by year.`;
535535
}
536536
if (ds === LIKELIHOOD_OF_SEARCH) {
537537
if (consolidateYears) {
538-
return `${message} searched during a stop totalled by year.`;
538+
return `${message} searched during a stop totaled by year.`;
539539
}
540540
return `${message} searched during a specific stop reason.`;
541541
}
542542
if (ds === STOPS_BY_REASON) {
543543
if (consolidateYears) {
544-
return `${message} stopped totalled by year.`;
544+
return `${message} stopped totaled by year.`;
545545
}
546546
return `${message} stopped for a specific reason.`;
547547
}

frontend/src/Components/NewCharts/LineChart.js

+24-12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Line } from 'react-chartjs-2';
33
import { tooltipLanguage } from '../../util/tooltipLanguage';
44
import { usePopper } from 'react-popper';
55
import styled from 'styled-components';
6+
import DataLoading from '../Charts/ChartPrimitives/DataLoading';
67

78
export const Tooltip = styled.div`
89
background: #333;
@@ -21,13 +22,14 @@ export const Tooltip = styled.div`
2122
export default function LineChart({
2223
data,
2324
title,
24-
maintainAspectRatio = true,
25+
maintainAspectRatio = false,
2526
displayTitle = true,
2627
displayLegend = true,
2728
yAxisMax = null,
2829
yAxisShowLabels = true,
2930
displayStopPurposeTooltips = false,
3031
showLegendOnBottom = true,
32+
redraw = false,
3133
}) {
3234
const options = {
3335
responsive: true,
@@ -47,8 +49,10 @@ export default function LineChart({
4749
}
4850
},
4951
onLeave() {
50-
setTooltipText('');
51-
hideTooltip();
52+
if (displayStopPurposeTooltips) {
53+
setTooltipText('');
54+
hideTooltip();
55+
}
5256
},
5357
},
5458
tooltip: {
@@ -94,17 +98,25 @@ export default function LineChart({
9498
popperElement.removeAttribute('data-show');
9599
};
96100

101+
if (!data.datasets.length) {
102+
return <DataLoading />;
103+
}
104+
97105
return (
98106
<>
99-
<div ref={setReferenceElement} />
100-
<Tooltip
101-
ref={setPopperElement}
102-
style={{ ...styles.popper, width: '300px' }}
103-
{...attributes.popper}
104-
>
105-
{tooltipText}
106-
</Tooltip>
107-
<Line options={options} data={data} />
107+
{displayStopPurposeTooltips && (
108+
<>
109+
<div ref={setReferenceElement} />
110+
<Tooltip
111+
ref={setPopperElement}
112+
style={{ ...styles.popper, width: '300px' }}
113+
{...attributes.popper}
114+
>
115+
{tooltipText}
116+
</Tooltip>
117+
</>
118+
)}
119+
<Line options={options} data={data} redraw={redraw} datasetIdKey={title} />
108120
</>
109121
);
110122
}

frontend/src/Components/NewCharts/PieChart.js

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import { Pie } from 'react-chartjs-2';
3+
import DataLoading from '../Charts/ChartPrimitives/DataLoading';
34

45
export default function PieChart({
56
data,
@@ -31,6 +32,9 @@ export default function PieChart({
3132
return `${context.parsed}%`;
3233
},
3334
},
35+
titleColor: '#000',
36+
bodyColor: '#000',
37+
backgroundColor: 'rgba(255, 255, 255, 1.0)',
3438
},
3539
title: {
3640
display: displayTitle,
@@ -57,20 +61,20 @@ export default function PieChart({
5761
}
5862
const text = `${chart.data.labels[index]}: ${chart.data.datasets[0].data[index]}%`;
5963
const textWidth = ctx.measureText(text).width;
60-
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
64+
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
6165
const height = y - 15 - offsetHeight;
6266
ctx.fillRect(x - (textWidth + 10) / 2, height, textWidth + 10, 20);
6367

64-
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
68+
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
6569
ctx.beginPath();
6670
ctx.moveTo(x, y + 15 - offsetHeight);
6771
ctx.lineTo(x - 10, y - 5 - offsetHeight);
6872
ctx.lineTo(x + 10, y - 5 - offsetHeight);
6973
ctx.fill();
7074
ctx.restore();
7175

72-
ctx.font = '12px Arial';
73-
ctx.fillStyle = 'white';
76+
ctx.font = '14px Arial';
77+
ctx.fillStyle = 'black';
7478
ctx.fillText(text, x - textWidth / 2, y - offsetHeight);
7579
ctx.restore();
7680
}
@@ -98,6 +102,10 @@ export default function PieChart({
98102

99103
const noData = data.datasets[0].data.every((v) => parseInt(v, 10) === 0);
100104

105+
if (!data.datasets.length) {
106+
return <DataLoading />;
107+
}
108+
101109
return (
102110
<>
103111
{noData && <div style={{ textAlign: 'center' }}>No Data Found</div>}

frontend/src/util/tooltipLanguage.js

+2-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nc/prime_cache.py

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
"nc:agency-api-searches-by-type",
1919
"nc:agency-api-contraband-hit-rate",
2020
"nc:agency-api-use-of-force",
21+
"nc:stops-by-count",
22+
"nc:stop-purpose-groups",
23+
"nc:stops-grouped-by-purpose",
2124
)
2225
DEFAULT_CUTOFF_SECS = 4
2326

nc/urls.py

+3
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@
1818
path(
1919
"api/agency/<agency_id>/stops-by-count/",
2020
views.AgencyTrafficStopsByCountView.as_view(),
21+
name="stops-by-count",
2122
),
2223
path(
2324
"api/agency/<agency_id>/stop-purpose-groups/",
2425
views.AgencyStopPurposeGroupView.as_view(),
26+
name="stop-purpose-groups",
2527
),
2628
path(
2729
"api/agency/<agency_id>/stops-grouped-by-purpose/",
2830
views.AgencyStopGroupByPurposeView.as_view(),
31+
name="stops-grouped-by-purpose",
2932
),
3033
]

nc/views.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ def get(self, request, agency_id):
452452

453453
qs = qs.values(*qs_values).annotate(count=Sum("count")).order_by(date_precision)
454454
if qs.count() == 0:
455-
return Response(data=[], status=200)
455+
return Response(data={"labels": [], "datasets": []}, status=200)
456456
df = pd.DataFrame(qs)
457457
unique_x_range = df[date_precision].unique()
458458
pivot_df = df.pivot(index=date_precision, columns=qs_df_cols, values="count").fillna(
@@ -464,6 +464,12 @@ def get(self, request, agency_id):
464464

465465

466466
class AgencyStopPurposeGroupView(APIView):
467+
def get_values(self, df, stop_purpose, years_len):
468+
if stop_purpose and stop_purpose in df:
469+
return list(df[stop_purpose].values)
470+
else:
471+
return [0] * years_len
472+
467473
def get(self, request, agency_id):
468474
qs = StopSummary.objects.all()
469475
agency_id = int(agency_id)
@@ -480,30 +486,34 @@ def get(self, request, agency_id):
480486
.annotate(count=Sum("count"))
481487
.order_by("year")
482488
)
489+
if qs.count() == 0:
490+
return Response(data={"labels": [], "datasets": []}, status=200)
491+
483492
df = pd.DataFrame(qs)
484493
unique_years = df.year.unique()
485494
pivot_df = df.pivot(index="year", columns="stop_purpose_group", values="count").fillna(
486495
value=0
487496
)
488497
df = pd.DataFrame(pivot_df)
498+
years_len = len(unique_years)
489499
data = {
490500
"labels": unique_years,
491501
"datasets": [
492502
{
493503
"label": StopPurposeGroup.SAFETY_VIOLATION,
494-
"data": list(df[StopPurposeGroup.SAFETY_VIOLATION].values),
504+
"data": self.get_values(df, StopPurposeGroup.SAFETY_VIOLATION, years_len),
495505
"borderColor": "#7F428A",
496506
"backgroundColor": "#CFA9D6",
497507
},
498508
{
499509
"label": StopPurposeGroup.REGULATORY_EQUIPMENT,
500-
"data": list(df[StopPurposeGroup.REGULATORY_EQUIPMENT].values),
510+
"data": self.get_values(df, StopPurposeGroup.REGULATORY_EQUIPMENT, years_len),
501511
"borderColor": "#b36800",
502512
"backgroundColor": "#ffa500",
503513
},
504514
{
505515
"label": StopPurposeGroup.OTHER,
506-
"data": list(df[StopPurposeGroup.OTHER].values),
516+
"data": self.get_values(df, StopPurposeGroup.OTHER, years_len),
507517
"borderColor": "#1B4D3E",
508518
"backgroundColor": "#ACE1AF",
509519
},
@@ -575,6 +585,8 @@ def get(self, request, agency_id):
575585
.annotate(count=Sum("count"))
576586
.order_by("year")
577587
)
588+
if qs.count() == 0:
589+
return Response(data={"labels": [], "datasets": []}, status=200)
578590
df = pd.DataFrame(qs)
579591
unique_years = df.year.unique()
580592
pivot_table = pd.pivot_table(

0 commit comments

Comments
 (0)