Skip to content

Commit

Permalink
Merge pull request #2357 from zetkin/undocumented/logging-to-place
Browse files Browse the repository at this point in the history
Logging visits to places/areas instead of households
  • Loading branch information
ziggabyte authored Dec 5, 2024
2 parents 845f9a9 + 2959af2 commit 6eb0d5c
Show file tree
Hide file tree
Showing 36 changed files with 1,646 additions and 386 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
question: m.question,
})),
organization: { id: assignmentModel.orgId },
reporting_level: assignmentModel.reporting_level || 'household',
start_date: assignmentModel.start_date,
title: assignmentModel.title,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { AreaModel } from 'features/areas/models';
import {
CanvassAssignmentModel,
PlaceModel,
PlaceVisitModel,
PlaceVisitModelType,
} from 'features/canvassAssignments/models';
import {
ZetkinAssignmentAreaStatsItem,
Expand Down Expand Up @@ -88,6 +90,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
organization: {
id: assignmentModel.orgId,
},
reporting_level: assignmentModel.reporting_level || 'household',
start_date: assignmentModel.start_date,
title: assignmentModel.title,
},
Expand Down Expand Up @@ -181,6 +184,19 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {

const statsByAreaId: Record<string, ZetkinAssignmentAreaStatsItem> = {};

const allPlaceVisits = await PlaceVisitModel.find({
canvassAssId: params.canvassAssId,
});

const visitsByPlaceId: Record<string, PlaceVisitModelType[]> = {};
allPlaceVisits.forEach((visit) => {
if (!visitsByPlaceId[visit.placeId]) {
visitsByPlaceId[visit.placeId] = [];
}

visitsByPlaceId[visit.placeId].push(visit);
});

uniqueAreas.forEach((area) => {
statsByAreaId[area.id] = {
areaId: area.id,
Expand Down Expand Up @@ -234,6 +250,25 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
}
});
});

const placeVisits = visitsByPlaceId[place.id] || [];
placeVisits.forEach((visit) => {
const numHouseholds = Math.max(
...visit.responses.map((response) =>
response.responseCounts.reduce((sum, count) => sum + count, 0)
)
);

const successfulResponse = visit.responses.find(
(response) => response.metricId == idOfMetricThatDefinesDone
);
const numSuccessful = successfulResponse?.responseCounts[0] || 0;

statsByAreaId[area.id].num_successful_visited_households +=
numSuccessful;
statsByAreaId[area.id].num_visited_households += numHouseholds;
statsByAreaId[area.id].num_visited_places += 1;
});
}
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
question: metric.question,
})),
organization: { id: orgId },
reporting_level: canvassAssignmentModel.reporting_level || 'household',
start_date: canvassAssignmentModel.start_date,
title: canvassAssignmentModel.title,
};
Expand All @@ -66,7 +67,13 @@ export async function PATCH(request: NextRequest, { params }: RouteMeta) {
await mongoose.connect(process.env.MONGODB_URL || '');

const payload = await request.json();
const { metrics: newMetrics, title, start_date, end_date } = payload;
const {
metrics: newMetrics,
title,
start_date,
end_date,
reporting_level,
} = payload;

if (newMetrics) {
// Find existing metrics to remove
Expand Down Expand Up @@ -121,7 +128,10 @@ export async function PATCH(request: NextRequest, { params }: RouteMeta) {
}
}
type UpdateFieldsType = Partial<
Pick<ZetkinCanvassAssignment, 'title' | 'start_date' | 'end_date'>
Pick<
ZetkinCanvassAssignment,
'title' | 'start_date' | 'end_date' | 'reporting_level'
>
>;

const updateFields: UpdateFieldsType = {};
Expand All @@ -134,6 +144,10 @@ export async function PATCH(request: NextRequest, { params }: RouteMeta) {
updateFields.start_date = start_date;
}

if (reporting_level) {
updateFields.reporting_level = reporting_level;
}

if (Object.prototype.hasOwnProperty.call(payload, 'end_date')) {
updateFields.end_date = end_date;
}
Expand Down Expand Up @@ -165,6 +179,7 @@ export async function PATCH(request: NextRequest, { params }: RouteMeta) {
question: metric.question,
})),
organization: { id: orgId },
reporting_level: model.reporting_level || 'household',
start_date: model.start_date,
title: model.title,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
organization: {
id: model.orgId,
},
reporting_level: model.reporting_level || 'household',
start_date: model.start_date,
title: model.title,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
organization: {
id: assignmentModel.orgId,
},
reporting_level: assignmentModel.reporting_level || 'household',
start_date: assignmentModel.start_date,
title: assignmentModel.title,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import mongoose from 'mongoose';
import { NextRequest, NextResponse } from 'next/server';

import {
CanvassAssignmentModel,
PlaceVisitModel,
} from 'features/canvassAssignments/models';
import asCanvasserAuthorized from 'features/canvassAssignments/utils/asCanvasserAuthorized';

type RouteMeta = {
params: {
canvassAssId: string;
orgId: string;
};
};

export async function GET(request: NextRequest, { params }: RouteMeta) {
const canvassAssId = params.canvassAssId;
return asCanvasserAuthorized(
{
orgId: params.orgId,
request: request,
},
async ({ orgId }) => {
await mongoose.connect(process.env.MONGODB_URL || '');

const assignmentModel = await CanvassAssignmentModel.find({
_id: canvassAssId,
orgId: orgId,
});

if (!assignmentModel) {
return NextResponse.json({ error: {} }, { status: 404 });
}

const visitModels = await PlaceVisitModel.find({
canvassAssId: canvassAssId,
});

return NextResponse.json({
data: visitModels.map((model) => ({
canvassAssId: model.canvassAssId,
id: model._id.toString(),
personId: model.personId,
placeId: model.placeId,
responses: model.responses,
timestamp: model.timestamp,
})),
});
}
);
}

export async function POST(request: NextRequest, { params }: RouteMeta) {
const canvassAssId = params.canvassAssId;
return asCanvasserAuthorized(
{
orgId: params.orgId,
request: request,
},
async ({ orgId, personId }) => {
await mongoose.connect(process.env.MONGODB_URL || '');

const assignmentModel = await CanvassAssignmentModel.find({
_id: canvassAssId,
orgId: orgId,
});

if (!assignmentModel) {
return NextResponse.json({ error: {} }, { status: 404 });
}

const payload = await request.json();

const visit = new PlaceVisitModel({
canvassAssId,
personId: personId,
placeId: payload.placeId,
responses: payload.responses,
timestamp: new Date().toISOString(),
});

await visit.save();

return NextResponse.json({
data: {
canvassAssId: visit.canvassAssId,
id: visit._id.toString(),
personId: visit.personId,
placeId: visit.placeId,
responses: visit.responses,
timestamp: visit.timestamp,
},
});
}
);
}
2 changes: 2 additions & 0 deletions src/app/beta/orgs/[orgId]/canvassassignments/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export async function POST(request: NextRequest, { params }: RouteMeta) {
campId: payload.campaign_id,
metrics: payload.metrics || [],
orgId: orgId,
reporting_level: payload.reporting_level || 'household',
title: payload.title,
});

Expand All @@ -81,6 +82,7 @@ export async function POST(request: NextRequest, { params }: RouteMeta) {
question: metric.question,
})),
organization: { id: orgId },
reporting_level: model.reporting_level || 'household',
start_date: model.start_date,
title: model.title,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function POST(request: NextRequest, { params }: RouteMeta) {
orgId: params.orgId,
request: request,
},
async ({ orgId }) => {
async ({ orgId, personId }) => {
await mongoose.connect(process.env.MONGODB_URL || '');

const payload = await request.json();
Expand All @@ -33,6 +33,7 @@ export async function POST(request: NextRequest, { params }: RouteMeta) {
id: new mongoose.Types.ObjectId().toString(),
missionAccomplished: payload.missionAccomplished,
noteToOfficial: payload.noteToOfficial,
personId: personId,
responses: payload.responses || [],
timestamp: payload.timestamp,
},
Expand Down
1 change: 1 addition & 0 deletions src/app/beta/users/me/canvassassignments/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export async function GET(request: NextRequest) {
organization: {
id: assignment.orgId,
},
reporting_level: assignment.reporting_level || 'household',
start_date: assignment.start_date,
title: assignment.title,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { ZetkinCanvassAssignment } from '../types';
import MapControls from './MapControls';
import objToLatLng from 'features/areas/utils/objToLatLng';
import CanvassAssignmentMapOverlays from './CanvassAssignmentMapOverlays';
import useAllPlaceVisits from '../hooks/useAllPlaceVisits';

const useStyles = makeStyles(() => ({
'@keyframes ghostMarkerBounce': {
Expand Down Expand Up @@ -71,6 +72,10 @@ const CanvassAssignmentMap: FC<CanvassAssignmentMapProps> = ({
const classes = useStyles();
const places = usePlaces(assignment.organization.id).data || [];
const createPlace = useCreatePlace(assignment.organization.id);
const placeVisitList = useAllPlaceVisits(
assignment.organization.id,
assignment.id
);

const [selectedPlaceId, setSelectedPlaceId] = useState<string | null>(null);
const [isCreating, setIsCreating] = useState(false);
Expand Down Expand Up @@ -261,7 +266,12 @@ const CanvassAssignmentMap: FC<CanvassAssignmentMapProps> = ({
))}
</FeatureGroup>
{places.map((place) => {
const state = getVisitState(place.households, assignment.id);
const householdState = getVisitState(place.households, assignment.id);
const visited = placeVisitList.data?.some(
(visit) => visit.placeId == place.id
);

const state = visited ? 'all' : householdState;

const selected = place.id == selectedPlaceId;
const key = `marker-${place.id}-${selected.toString()}`;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Box, Button, IconButton } from '@mui/material';
import { Box, Button } from '@mui/material';
import { FC, useEffect, useState } from 'react';
import { makeStyles } from '@mui/styles';
import { KeyboardArrowUp } from '@mui/icons-material';

import { ZetkinCanvassAssignment, ZetkinPlace } from '../types';
import PlaceDialog from './PlaceDialog';
import { CreatePlaceCard } from './CreatePlaceCard';
import PageBaseHeader from './PlaceDialog/pages/PageBaseHeader';
import ContractedHeader from './PlaceDialog/ContractedHeader';

type Props = {
assignment: ZetkinCanvassAssignment;
Expand Down Expand Up @@ -58,11 +57,6 @@ const CanvassAssignmentMapOverlays: FC<Props> = ({
}
}, [selectedPlace]);

const numVisitedHouseholds =
selectedPlace?.households.filter((household) =>
household.visits.some((visit) => visit.canvassAssId == assignment.id)
).length ?? 0;

return (
<>
{!selectedPlace && !isCreating && (
Expand All @@ -78,7 +72,7 @@ const CanvassAssignmentMapOverlays: FC<Props> = ({
bottom: 0,
boxShadow: theme.shadows[20],
left: 0,
position: 'fixed',
position: 'absolute',
right: 0,
top: drawerTop,
transition: 'top 0.2s',
Expand All @@ -87,15 +81,7 @@ const CanvassAssignmentMapOverlays: FC<Props> = ({
>
{showViewPlaceButton && (
<Box onClick={() => setExpanded(true)} p={2}>
<PageBaseHeader
iconButtons={
<IconButton onClick={() => setExpanded(true)}>
<KeyboardArrowUp />
</IconButton>
}
subtitle={`${numVisitedHouseholds} / ${selectedPlace.households.length} households visited`}
title={selectedPlace.title || 'Untitled place'}
/>
<ContractedHeader assignment={assignment} place={selectedPlace} />
</Box>
)}
{selectedPlace && expanded && (
Expand Down
Loading

0 comments on commit 6eb0d5c

Please sign in to comment.