Skip to content

Commit

Permalink
V4.1/Accessibility alerts (#578)
Browse files Browse the repository at this point in the history
* Fetch facilities by line

* Fix errors

* Add types

* Rename

* Another rename

* Render accessibility alerts

* Improve alert rendering

* Lint

* Bold station names

* Moving Alerts to Overview

* Revert "Moving Alerts to Overview"

This reverts commit aec3030.

* Try Again

* Update mbta_v3.py

* Comments

* Removing Alerts Page

---------

Co-authored-by: Kevin Moses <[email protected]>
Co-authored-by: Austin Houck <[email protected]>
Co-authored-by: Austin Houck <[email protected]>
  • Loading branch information
4 people authored Sep 8, 2023
1 parent 3460d3e commit d742188
Show file tree
Hide file tree
Showing 31 changed files with 381 additions and 115 deletions.
80 changes: 44 additions & 36 deletions common/api/alerts.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,82 @@
import type { AlertsResponse, OldAlert } from '../types/alerts';
import type { LineShort } from '../types/lines';
import { APP_DATA_BASE_PATH } from '../utils/constants';
import { getStationKeysFromStations } from '../utils/stations';
import { apiFetch } from './utils/fetch';

const alertsAPIConfig = {
activity: 'BOARD,EXIT,RIDE',
};

const accessibilityAlertsAPIConfig = {
activity: 'USING_ESCALATOR,USING_WHEELCHAIR',
};

export const fetchAlerts = async (
route: LineShort,
line: LineShort,
busRoute?: string
): Promise<AlertsResponse[]> => {
if (route === 'Bus' && busRoute) {
if (line === 'Bus' && busRoute) {
return fetchAlertsForBus(busRoute);
}
return fetchAlertsForLine(route);
return fetchAlertsForLine(line);
};

const fetchAlertsForLine = async (route: LineShort): Promise<AlertsResponse[]> => {
const url = new URL(`${APP_DATA_BASE_PATH}/api/alerts`, window.location.origin);
const fetchAlertsForLine = async (line: LineShort): Promise<AlertsResponse[]> => {
const options = { ...alertsAPIConfig };
if (route === 'Green') {
if (line === 'Green') {
// route_type 0 is light rail (green line & Mattapan)
options['route_type'] = '0';
} else {
options['route_type'] = '1';
options['route'] = route;
options['route'] = line;
}
Object.entries(options).forEach(([key, value]) => {
url.searchParams.append(key, value.toString());

return await apiFetch({
path: '/api/alerts',
options,
errorMessage: `Failed to fetch alerts for line ${line}`,
});
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error('Failed to fetch alerts');
}
return await response.json();
};

const fetchAlertsForBus = async (busRoute: string): Promise<AlertsResponse[]> => {
const url = new URL(`${APP_DATA_BASE_PATH}/api/alerts`, window.location.origin);
const options = { ...alertsAPIConfig };
const options = { ...alertsAPIConfig, route: busRoute };
options['route_type'] = '3';
options['route'] = busRoute;
Object.entries(options).forEach(([key, value]) => {
url.searchParams.append(key, value.toString());

return await apiFetch({
path: '/api/alerts',
options,
errorMessage: `Failed to fetch alerts for bus route ${busRoute}`,
});
};

export const fetchAccessibilityAlertsForLine = async (
line: LineShort
): Promise<AlertsResponse[]> => {
const stationKeys = getStationKeysFromStations(line);
const options = { ...accessibilityAlertsAPIConfig, stop: stationKeys.join(',') };

return await apiFetch({
path: '/api/alerts',
options,
errorMessage: 'Failed to fetch accessibility alerts',
});
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error('Failed to fetch alerts');
}
return await response.json();
};

export const fetchHistoricalAlerts = async (
date: string | undefined,
route: LineShort,
line: LineShort,
busRoute?: string
): Promise<OldAlert[]> => {
const url = new URL(`${APP_DATA_BASE_PATH}/api/alerts/${date}`, window.location.origin);
const options = { route: '' };
if (route === 'Bus' && busRoute) {
if (line === 'Bus' && busRoute) {
options['route'] = busRoute;
} else {
options['route'] = route;
options['route'] = line;
}
Object.entries(options).forEach(([key, value]) => {
url.searchParams.append(key, value.toString());

return await apiFetch({
path: `/api/alerts/${date}`,
options,
errorMessage: 'Failed to fetch historical alerts',
});
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error('Failed to fetch alerts');
}
return await response.json();
};
17 changes: 17 additions & 0 deletions common/api/facilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { FacilitiesResponse } from '../types/facilities';
import type { LineShort } from '../types/lines';
import { getStationKeysFromStations } from '../utils/stations';
import { apiFetch } from './utils/fetch';

export const fetchAllElevatorsAndEscalators = async (
line: LineShort
): Promise<FacilitiesResponse> => {
const stationKeys = getStationKeysFromStations(line);
const options = { type: 'ESCALATOR,ELEVATOR', stop: stationKeys.join(',') };

return await apiFetch({
path: '/api/facilities',
options,
errorMessage: 'Failed to fetch elevators and escalators',
});
};
18 changes: 12 additions & 6 deletions common/api/hooks/alerts.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type { UseQueryResult } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import { fetchAlerts, fetchHistoricalAlerts } from '../alerts';
import { fetchAlerts, fetchHistoricalAlerts, fetchAccessibilityAlertsForLine } from '../alerts';
import type { LineShort } from '../../types/lines';
import { FIVE_MINUTES, ONE_MINUTE } from '../../constants/time';
import type { AlertsResponse } from '../../types/alerts';

export const useHistoricalAlertsData = (
date: string | undefined,
route: LineShort,
line: LineShort,
busRoute?: string
) => {
return useQuery(
['alerts', date, route, busRoute],
() => fetchHistoricalAlerts(date, route, busRoute),
['alerts', date, line, busRoute],
() => fetchHistoricalAlerts(date, line, busRoute),
{
staleTime: FIVE_MINUTES,
enabled: date !== undefined,
Expand All @@ -21,10 +21,16 @@ export const useHistoricalAlertsData = (
};

export const useAlertsData = (
route: LineShort,
line: LineShort,
busRoute?: string
): UseQueryResult<AlertsResponse[]> => {
return useQuery(['alerts', route, busRoute], () => fetchAlerts(route, busRoute), {
return useQuery(['alerts', line, busRoute], () => fetchAlerts(line, busRoute), {
staleTime: ONE_MINUTE,
});
};

export const useAccessibilityAlertsData = (line: LineShort) => {
return useQuery(['accessibilityAlerts', line], () => fetchAccessibilityAlertsForLine(line), {
staleTime: ONE_MINUTE,
});
};
7 changes: 7 additions & 0 deletions common/api/hooks/facilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useQuery } from '@tanstack/react-query';
import { fetchAllElevatorsAndEscalators } from '../facilities';
import type { LineShort } from '../../types/lines';

export const useElevatorsAndEscalators = (line: LineShort) => {

Check warning on line 5 in common/api/hooks/facilities.ts

View workflow job for this annotation

GitHub Actions / frontend (18, 3.10)

exported declaration 'useElevatorsAndEscalators' not used within other modules
return useQuery(['elevAndEsc', line], () => fetchAllElevatorsAndEscalators(line));
};
4 changes: 2 additions & 2 deletions common/api/hooks/ridership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type { FetchRidershipOptions } from '../../types/api';
import { fetchLandingRidership, fetchRidership } from '../ridership';
import { ONE_HOUR } from '../../constants/time';

export const useRidershipData = (params: FetchRidershipOptions, enabled?: boolean) => {
return useQuery(['trips', params], () => fetchRidership(params), {
export const useRidershipData = (options: FetchRidershipOptions, enabled?: boolean) => {
return useQuery(['trips', options], () => fetchRidership(options), {
enabled: enabled,
staleTime: ONE_HOUR,
});
Expand Down
4 changes: 2 additions & 2 deletions common/api/hooks/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type { FetchScheduledServiceOptions } from '../../types/api';
import { ONE_HOUR } from '../../constants/time';
import { fetchScheduledService } from '../service';

export const useScheduledService = (params: FetchScheduledServiceOptions, enabled?: boolean) => {
return useQuery(['scheduledservice', params], () => fetchScheduledService(params), {
export const useScheduledService = (options: FetchScheduledServiceOptions, enabled?: boolean) => {
return useQuery(['scheduledservice', options], () => fetchScheduledService(options), {
enabled: enabled,
staleTime: ONE_HOUR,
});
Expand Down
4 changes: 2 additions & 2 deletions common/api/hooks/speed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { fetchSpeeds } from '../speed';
import type { FetchSpeedsOptions } from '../../types/api';
import { FIVE_MINUTES } from '../../constants/time';

export const useSpeedData = (params: FetchSpeedsOptions, enabled?: boolean) => {
return useQuery(['speed', params], () => fetchSpeeds(params), {
export const useSpeedData = (options: FetchSpeedsOptions, enabled?: boolean) => {
return useQuery(['speed', options], () => fetchSpeeds(options), {
enabled: enabled,
staleTime: FIVE_MINUTES,
});
Expand Down
4 changes: 2 additions & 2 deletions common/api/hooks/tripmetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { FIVE_MINUTES } from '../../constants/time';
import { fetchActualTripsByLine, fetchLandingTripMetrics } from '../tripmetrics';

export const useDeliveredTripMetrics = (
params: FetchDeliveredTripMetricsOptions,
options: FetchDeliveredTripMetricsOptions,
enabled?: boolean
) => {
return useQuery(['actualTrips', params], () => fetchActualTripsByLine(params), {
return useQuery(['actualTrips', options], () => fetchActualTripsByLine(options), {
enabled: enabled,
staleTime: FIVE_MINUTES,
});
Expand Down
17 changes: 8 additions & 9 deletions common/api/ridership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@ import { FetchRidershipParams } from '../types/api';
import type { FetchRidershipOptions } from '../types/api';
import type { RidershipCount } from '../types/dataPoints';
import { type Line } from '../types/lines';
import { APP_DATA_BASE_PATH } from '../utils/constants';
import { apiFetch } from './utils/fetch';

export const fetchRidership = async (
params: FetchRidershipOptions
options: FetchRidershipOptions
): Promise<RidershipCount[] | undefined> => {
if (!params[FetchRidershipParams.lineId]) return undefined;
const url = new URL(`${APP_DATA_BASE_PATH}/api/ridership`, window.location.origin);
Object.keys(params).forEach((paramKey) => {
url.searchParams.append(paramKey, params[paramKey]);
if (!options[FetchRidershipParams.lineId]) return undefined;

return await apiFetch({
path: '/api/ridership',
options,
errorMessage: 'Failed to fetch ridership counts',
});
const response = await fetch(url.toString());
if (!response.ok) throw new Error('Failed to fetch ridership counts');
return await response.json();
};

export const fetchLandingRidership = async (): Promise<{ [key in Line]: RidershipCount[] }> => {
Expand Down
18 changes: 8 additions & 10 deletions common/api/service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import type { FetchScheduledServiceOptions } from '../types/api';
import { FetchScheduledServiceParams } from '../types/api';
import type { ScheduledService } from '../types/dataPoints';
import { APP_DATA_BASE_PATH } from '../utils/constants';
import { apiFetch } from './utils/fetch';

export const fetchScheduledService = async (
params: FetchScheduledServiceOptions
options: FetchScheduledServiceOptions
): Promise<ScheduledService | undefined> => {
if (!params[FetchScheduledServiceParams.routeId]) return undefined;
const url = new URL(`${APP_DATA_BASE_PATH}/api/scheduledservice`, window.location.origin);
Object.keys(params).forEach((paramKey) => {
url.searchParams.append(paramKey, params[paramKey]);
});
const response = await fetch(url.toString());
if (!response.ok) throw new Error('Failed to fetch trip counts');
if (!options[FetchScheduledServiceParams.routeId]) return undefined;

return await response.json();
return await apiFetch({
path: '/api/scheduledservice',
options,
errorMessage: 'Failed to fetch trip counts',
});
};
18 changes: 9 additions & 9 deletions common/api/slowzones.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import type {
SpeedRestriction,
} from '../../common/types/dataPoints';
import type { FetchSpeedRestrictionsOptions, FetchSpeedRestrictionsResponse } from '../types/api';
import { APP_DATA_BASE_PATH } from '../utils/constants';
import { getGtfsRailLineId } from '../utils/lines';
import { apiFetch } from './utils/fetch';

export const fetchDelayTotals = (): Promise<DayDelayTotals[]> => {
const url = new URL(`/static/slowzones/delay_totals.json`, window.location.origin);
Expand All @@ -21,18 +21,18 @@ export const fetchSpeedRestrictions = async (
options: FetchSpeedRestrictionsOptions
): Promise<SpeedRestriction[]> => {
const { lineId, date: requestedDate } = options;
const params = new URLSearchParams({ line_id: getGtfsRailLineId(lineId), date: requestedDate });
const speedRestrictionsUrl = new URL(
`${APP_DATA_BASE_PATH}/api/speed_restrictions?${params.toString()}`,
window.location.origin
);
const today = new Date();
const response = await fetch(speedRestrictionsUrl.toString());

const {
available,
date: resolvedDate,
zones,
}: FetchSpeedRestrictionsResponse = await response.json();
}: FetchSpeedRestrictionsResponse = await apiFetch({
path: `/api/speed_restrictions`,
options: { line_id: getGtfsRailLineId(lineId), date: requestedDate },
errorMessage: 'Failed to fetch speed restrictions',
});

const today = new Date();
if (available) {
return zones.map((zone) => ({
...zone,
Expand Down
18 changes: 8 additions & 10 deletions common/api/speed.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import type { FetchSpeedsOptions as FetchSpeedsOptions } from '../types/api';
import { FetchSpeedsParams as FetchSpeedsParams } from '../types/api';
import type { SpeedDataPoint } from '../types/dataPoints';
import { APP_DATA_BASE_PATH } from '../utils/constants';
import { apiFetch } from './utils/fetch';

export const fetchSpeeds = async (params: FetchSpeedsOptions): Promise<SpeedDataPoint[]> => {
if (!params[FetchSpeedsParams.line]) return [];
const url = new URL(`${APP_DATA_BASE_PATH}/api/speed`, window.location.origin);
Object.keys(params).forEach((paramKey) => {
url.searchParams.append(paramKey, params[paramKey]);
});
const response = await fetch(url.toString());
if (!response.ok) throw new Error('Failed to fetch traversal times');
export const fetchSpeeds = async (options: FetchSpeedsOptions): Promise<SpeedDataPoint[]> => {
if (!options[FetchSpeedsParams.line]) return [];

return await response.json();
return await apiFetch({
path: '/api/speed',
options,
errorMessage: 'Failed to fetch traversal times',
});
};
17 changes: 8 additions & 9 deletions common/api/tripmetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@ import type { FetchDeliveredTripMetricsOptions } from '../types/api';
import { FetchDeliveredTripMetricsParams } from '../types/api';
import type { DeliveredTripMetrics } from '../types/dataPoints';
import type { Line } from '../types/lines';
import { APP_DATA_BASE_PATH } from '../utils/constants';
import { apiFetch } from './utils/fetch';

export const fetchActualTripsByLine = async (
params: FetchDeliveredTripMetricsOptions
options: FetchDeliveredTripMetricsOptions
): Promise<DeliveredTripMetrics[]> => {
if (!params[FetchDeliveredTripMetricsParams.line]) return [];
const url = new URL(`${APP_DATA_BASE_PATH}/api/tripmetrics`, window.location.origin);
Object.keys(params).forEach((paramKey) => {
url.searchParams.append(paramKey, params[paramKey]);
if (!options[FetchDeliveredTripMetricsParams.line]) return [];

return await apiFetch({
path: '/api/tripmetrics',
options,
errorMessage: 'Failed to fetch trip metrics',
});
const response = await fetch(url.toString());
if (!response.ok) throw new Error('Failed to fetch trip metrics');
return await response.json();
};

export const fetchLandingTripMetrics = (): Promise<{ [key in Line]: DeliveredTripMetrics[] }> => {
Expand Down
11 changes: 11 additions & 0 deletions common/api/utils/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { APP_DATA_BASE_PATH } from '../../utils/constants';

export const apiFetch = async ({ path, options, errorMessage }) => {
const url = new URL(`${APP_DATA_BASE_PATH}${path}`, window.location.origin);
Object.entries(options).forEach(([key, value]: [string, any]) => {

Check warning on line 5 in common/api/utils/fetch.ts

View workflow job for this annotation

GitHub Actions / frontend (18, 3.10)

Unexpected any. Specify a different type
url.searchParams.append(key, value.toString());
});
const response = await fetch(url.toString());
if (!response.ok) throw new Error(errorMessage);
return await response.json();
};
1 change: 1 addition & 0 deletions common/types/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface FormattedAlert {
stops: string[];
routes?: string[];
header: string;
description?: string;
}

export interface AlertsResponse {
Expand Down
Loading

0 comments on commit d742188

Please sign in to comment.