Skip to content

Commit 31000a6

Browse files
authored
Merge branch 'main' into 1082-sort-feeds-page-by-created_at
2 parents 8d6bfb9 + 8ca3a3b commit 31000a6

File tree

13 files changed

+471
-243
lines changed

13 files changed

+471
-243
lines changed

functions-python/gbfs_validator/src/gbfs_data_processor.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@
3030
from shared.database.database import with_db_session
3131
from shared.helpers.utils import create_http_task
3232

33+
FEATURE_ENDPOINTS = [
34+
"manifest",
35+
"gbfs_versions",
36+
"vehicle_types",
37+
"station_status",
38+
"vehicle_status",
39+
"system_regions",
40+
"system_pricing_plans",
41+
"system_alerts",
42+
"geofencing_zones",
43+
]
44+
3345

3446
class GBFSDataProcessor:
3547
def __init__(self, stable_id: str, feed_id: str):
@@ -298,7 +310,9 @@ def update_or_create_gbfs_endpoint(
298310
)
299311

300312
gbfs_endpoint_orm.url = endpoint.url # Update the URL
301-
gbfs_endpoint_orm.is_feature = endpoint.name in features
313+
gbfs_endpoint_orm.is_feature = (
314+
endpoint.name in features and endpoint.name in FEATURE_ENDPOINTS
315+
)
302316
return gbfs_endpoint_orm
303317

304318
def validate_gbfs_feed_versions(self) -> None:

liquibase/changelog.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,5 @@
5656
<include file="changes/feat_1124.sql" relativeToChangelogFile="true"/>
5757
<!-- Materialized view updated. Added versions of GBFS feeds-->
5858
<include file="changes/feat_1118.sql" relativeToChangelogFile="true"/>
59+
<include file="changes/feat_1125.sql" relativeToChangelogFile="true"/>
5960
</databaseChangeLog>

liquibase/changes/feat_1125.sql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-- Update gbfsendpoint table to set is_feature = false for specific endpoints
2+
UPDATE gbfsendpoint
3+
SET is_feature = CASE
4+
WHEN name IN (
5+
'manifest',
6+
'gbfs_versions',
7+
'vehicle_types',
8+
'station_status',
9+
'vehicle_status',
10+
'system_regions',
11+
'system_pricing_plans',
12+
'system_alerts',
13+
'geofencing_zones'
14+
) THEN true
15+
ELSE false
16+
END;

web-app/public/locales/en/common.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
},
2121
"loading": "Loading...",
2222
"download": "Download",
23+
"updated": "Updated",
2324
"errors": {
2425
"generic": "We are unable to complete your request at the moment."
2526
},

web-app/public/locales/en/feeds.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"detailedCoveredAreaView": "Detailed",
6868
"detailedCoveredAreaViewTooltip": "View the detailed covered area of the feed",
6969
"unableToGenerateBoundingBox": "Unable to generate bounding box from the feed's stops.txt file.",
70+
"unableToGetGbfsMap": "Error fetching GBFS Map",
7071
"areYouOfficialProducer": "Are you the official producer or transit agency responsible for this data ?",
7172
"isOfficialSource": "Is this an official data source?",
7273
"isOfficialSourceDetails": "Select \"Yes\" if the inputted feed is the official source of information by the transit provider and should be used to display to riders.",

web-app/src/app/components/ContentBox.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const ContentBox = (
3131
...props.sx,
3232
}}
3333
>
34-
{props.title.trim() !== '' && (
34+
{(props.title.trim() !== '' || props.action != null) && (
3535
<Typography
3636
variant='h5'
3737
sx={{
@@ -42,7 +42,7 @@ export const ContentBox = (
4242
mb: 1,
4343
}}
4444
>
45-
{props.title}
45+
<span>{props.title}</span>
4646
{props.action != null && props.action}
4747
</Typography>
4848
)}

web-app/src/app/components/CoveredAreaMap.tsx

Lines changed: 131 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,28 @@ import {
66
Tooltip,
77
Skeleton,
88
Button,
9+
Typography,
910
} from '@mui/material';
1011
import MapIcon from '@mui/icons-material/Map';
1112
import TravelExploreIcon from '@mui/icons-material/TravelExplore';
1213
import { ContentBox } from './ContentBox';
1314
import { WarningContentBox } from './WarningContentBox';
1415
import { mapBoxPositionStyle } from '../screens/Feed/Feed.styles';
15-
import { type GeoJSONData, MapGeoJSON } from './MapGeoJSON';
16+
import {
17+
type GeoJSONData,
18+
type GeoJSONDataGBFS,
19+
MapGeoJSON,
20+
} from './MapGeoJSON';
1621
import { Map } from './Map';
1722
import { useTranslation } from 'react-i18next';
1823
import type { LatLngExpression } from 'leaflet';
1924
import { useTheme } from '@mui/material/styles';
2025
import { type GBFSFeedType, type AllFeedType } from '../services/feeds/utils';
2126
import { OpenInNew } from '@mui/icons-material';
27+
import { computeBoundingBox } from '../screens/Feed/Feed.functions';
28+
import { displayFormattedDate } from '../utils/date';
29+
import { useSelector } from 'react-redux';
30+
import { selectLatestGbfsVersion } from '../store/feed-selectors';
2231

2332
interface CoveredAreaMapProps {
2433
boundingBox?: LatLngExpression[];
@@ -28,7 +37,7 @@ interface CoveredAreaMapProps {
2837

2938
export const fetchGeoJson = async (
3039
latestDatasetUrl: string,
31-
): Promise<GeoJSONData> => {
40+
): Promise<GeoJSONData | GeoJSONDataGBFS> => {
3241
const geoJsonUrl = latestDatasetUrl
3342
.split('/')
3443
.slice(0, -2)
@@ -49,36 +58,57 @@ const CoveredAreaMap: React.FC<CoveredAreaMapProps> = ({
4958
const { t } = useTranslation('feeds');
5059
const theme = useTheme();
5160

52-
const [geoJsonData, setGeoJsonData] = useState<GeoJSONData | null>(null);
61+
const [geoJsonData, setGeoJsonData] = useState<
62+
GeoJSONData | GeoJSONDataGBFS | null
63+
>(null);
5364
const [geoJsonError, setGeoJsonError] = useState(false);
5465
const [geoJsonLoading, setGeoJsonLoading] = useState(false);
5566
const [view, setView] = useState<
5667
'boundingBoxView' | 'detailedCoveredAreaView'
5768
>('detailedCoveredAreaView');
69+
const latestGbfsVersion = useSelector(selectLatestGbfsVersion);
70+
71+
const getAndSetGeoJsonData = (urlToExtract: string): void => {
72+
setGeoJsonLoading(true);
73+
fetchGeoJson(urlToExtract)
74+
.then((data) => {
75+
setGeoJsonData(data);
76+
setGeoJsonError(false);
77+
setView('detailedCoveredAreaView');
78+
})
79+
.catch(() => {
80+
setGeoJsonError(true);
81+
setView('boundingBoxView');
82+
})
83+
.finally(() => {
84+
setGeoJsonLoading(false);
85+
});
86+
};
5887

5988
useEffect(() => {
60-
if (latestDataset?.hosted_url !== undefined && boundingBox != undefined) {
61-
setGeoJsonLoading(true);
62-
fetchGeoJson(latestDataset.hosted_url)
63-
.then((data) => {
64-
setGeoJsonData(data);
65-
setGeoJsonError(false);
66-
setView('detailedCoveredAreaView');
67-
})
68-
.catch(() => {
69-
setGeoJsonError(true);
70-
setView('boundingBoxView');
71-
})
72-
.finally(() => {
73-
setGeoJsonLoading(false);
74-
});
75-
} else {
76-
// No dataset, fallback to bounding box
77-
setGeoJsonData(null);
78-
setGeoJsonError(true);
79-
setView('boundingBoxView');
89+
if (feed?.data_type === 'gbfs') {
90+
const latestGbfsVersionReportUrl =
91+
latestGbfsVersion?.latest_validation_report?.report_summary_url;
92+
if (latestGbfsVersionReportUrl === undefined) {
93+
setGeoJsonData(null);
94+
setGeoJsonError(true);
95+
return;
96+
}
97+
getAndSetGeoJsonData(latestGbfsVersionReportUrl);
98+
return;
8099
}
81-
}, [latestDataset]);
100+
if (
101+
feed?.data_type === 'gtfs' &&
102+
latestDataset?.hosted_url != undefined &&
103+
boundingBox != undefined
104+
) {
105+
getAndSetGeoJsonData(latestDataset.hosted_url);
106+
return;
107+
}
108+
setGeoJsonData(null);
109+
setGeoJsonError(true);
110+
setView('boundingBoxView');
111+
}, [latestDataset, feed]);
82112

83113
const handleViewChange = (
84114
_: React.MouseEvent<HTMLElement>,
@@ -87,11 +117,47 @@ const CoveredAreaMap: React.FC<CoveredAreaMapProps> = ({
87117
if (newView !== null) setView(newView);
88118
};
89119

90-
const getGbfsLatestVersionVisualizationUrl = (feed: GBFSFeedType): string => {
91-
// TODO: Redo logic when versions all have the auto discovery
92-
return `https://gbfs-validator.mobilitydata.org/visualization?url=${feed?.source_info?.producer_url}`;
120+
const getGbfsLatestVersionVisualizationUrl = (
121+
feed: GBFSFeedType,
122+
): string | undefined => {
123+
const latestAutodiscoveryUrl = latestGbfsVersion?.endpoints?.find(
124+
(endpoint) => endpoint.name === 'gbfs',
125+
)?.url;
126+
if (latestAutodiscoveryUrl != undefined) {
127+
return `https://gbfs-validator.mobilitydata.org/visualization?url=${latestAutodiscoveryUrl}`;
128+
}
129+
return undefined;
93130
};
94131

132+
const renderMap = (): JSX.Element => {
133+
const displayBoundingBoxMap =
134+
view === 'boundingBoxView' && feed?.data_type === 'gtfs';
135+
let gbfsBoundingBox: LatLngExpression[] = [];
136+
if (feed?.data_type === 'gbfs') {
137+
gbfsBoundingBox = computeBoundingBox(geoJsonData) ?? [];
138+
if (gbfsBoundingBox.length === 0) {
139+
setGeoJsonError(true);
140+
}
141+
}
142+
return (
143+
<>
144+
{displayBoundingBoxMap ? (
145+
<Map polygon={boundingBox ?? []} />
146+
) : (
147+
<MapGeoJSON
148+
geoJSONData={geoJsonData}
149+
polygon={boundingBox ?? gbfsBoundingBox}
150+
displayMapDetails={feed?.data_type === 'gtfs'}
151+
/>
152+
)}
153+
</>
154+
);
155+
};
156+
157+
const mapDisplayError = boundingBox == undefined && geoJsonError;
158+
const latestAutodiscoveryUrl = getGbfsLatestVersionVisualizationUrl(
159+
feed as GBFSFeedType,
160+
);
95161
return (
96162
<ContentBox
97163
sx={{
@@ -102,22 +168,39 @@ const CoveredAreaMap: React.FC<CoveredAreaMapProps> = ({
102168
xs: '100%',
103169
md: '70vh',
104170
},
171+
minHeight: '50vh',
105172
}}
106-
title={t('coveredAreaTitle') + ' - ' + t(view)}
173+
title={mapDisplayError ? '' : t('coveredAreaTitle') + ' - ' + t(view)}
107174
width={{ xs: '100%' }}
108175
outlineColor={theme.palette.primary.dark}
109176
padding={2}
110177
action={
111178
<>
112179
{feed?.data_type === 'gbfs' ? (
113-
<Button
114-
href={getGbfsLatestVersionVisualizationUrl(feed as GBFSFeedType)}
115-
target='_blank'
116-
rel='noreferrer'
117-
endIcon={<OpenInNew />}
118-
>
119-
{t('viewRealtimeVisualization')}
120-
</Button>
180+
<Box sx={{ textAlign: 'right' }}>
181+
{latestAutodiscoveryUrl != undefined && (
182+
<Button
183+
href={latestAutodiscoveryUrl}
184+
target='_blank'
185+
rel='noreferrer'
186+
endIcon={<OpenInNew />}
187+
>
188+
{t('viewRealtimeVisualization')}
189+
</Button>
190+
)}
191+
{(geoJsonData as GeoJSONDataGBFS)?.extracted_at != undefined && (
192+
<Typography
193+
variant='caption'
194+
color='text.secondary'
195+
sx={{ display: 'block', px: 1 }}
196+
>
197+
{t('common:updated')}:{' '}
198+
{displayFormattedDate(
199+
(geoJsonData as GeoJSONDataGBFS).extracted_at,
200+
)}
201+
</Typography>
202+
)}
203+
</Box>
121204
) : (
122205
<ToggleButtonGroup
123206
value={view}
@@ -150,13 +233,19 @@ const CoveredAreaMap: React.FC<CoveredAreaMapProps> = ({
150233
</>
151234
}
152235
>
153-
{boundingBox === undefined && view === 'boundingBoxView' && (
154-
<WarningContentBox>
155-
{t('unableToGenerateBoundingBox')}
156-
</WarningContentBox>
236+
{feed?.data_type === 'gtfs' &&
237+
boundingBox === undefined &&
238+
view === 'boundingBoxView' && (
239+
<WarningContentBox>
240+
{t('unableToGenerateBoundingBox')}
241+
</WarningContentBox>
242+
)}
243+
244+
{feed?.data_type === 'gbfs' && geoJsonError && (
245+
<WarningContentBox>{t('unableToGetGbfsMap')}</WarningContentBox>
157246
)}
158247

159-
{boundingBox !== undefined && (
248+
{(boundingBox != undefined || !geoJsonError) && (
160249
<Box sx={mapBoxPositionStyle}>
161250
{geoJsonLoading ? (
162251
<Skeleton
@@ -165,10 +254,8 @@ const CoveredAreaMap: React.FC<CoveredAreaMapProps> = ({
165254
height='100%'
166255
animation='wave'
167256
/>
168-
) : view === 'boundingBoxView' ? (
169-
<Map polygon={boundingBox} />
170257
) : (
171-
<MapGeoJSON geoJSONData={geoJsonData} polygon={boundingBox} />
258+
<>{geoJsonData !== null && <>{renderMap()}</>}</>
172259
)}
173260
</Box>
174261
)}

0 commit comments

Comments
 (0)