Skip to content

Commit

Permalink
feat: GTFS Schedule Analytics UI (#653)
Browse files Browse the repository at this point in the history
  • Loading branch information
cka-y authored Aug 29, 2024
1 parent 6301db1 commit d4c9b1a
Show file tree
Hide file tree
Showing 24 changed files with 2,464 additions and 178 deletions.
15 changes: 11 additions & 4 deletions web-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@fortawesome/free-regular-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@mui/icons-material": "^5.14.9",
"@mui/material": "^5.14.9",
"@mui/icons-material": "^5.16.4",
"@mui/material": "^5.16.4",
"@mui/x-date-pickers": "^7.11.0",
"@mui/x-tree-view": "^6.17.0",
"@reduxjs/toolkit": "^1.9.6",
"@turf/center": "^6.5.0",
"@types/i18next": "^13.0.0",
"@types/leaflet": "^1.9.12",
"axios": "^1.7.2",
"country-code-emoji": "^2.3.0",
"date-fns": "^2.30.0",
"date-fns-tz": "^2.0.0",
Expand All @@ -26,6 +28,9 @@
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.5.2",
"leaflet": "^1.9.4",
"material-react-table": "^2.13.0",
"mui-datatables": "^4.3.0",
"mui-nested-menu": "^3.4.0",
"openapi-fetch": "^0.9.3",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0",
Expand All @@ -38,6 +43,7 @@
"react-redux": "^8.1.3",
"react-router-dom": "^6.16.0",
"react-scripts": "5.0.1",
"recharts": "^2.12.7",
"redux-persist": "^6.0.0",
"redux-saga": "^1.2.3",
"typeface-muli": "^1.1.13",
Expand Down Expand Up @@ -95,6 +101,7 @@
"@types/cypress": "^1.1.3",
"@types/jest": "^29.5.12",
"@types/material-ui": "^0.21.12",
"@types/mui-datatables": "^4.3.12",
"@types/node": "^20.8.10",
"@types/react": "^18.2.25",
"@types/react-dom": "^18.2.7",
Expand Down
22 changes: 13 additions & 9 deletions web-app/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import i18n from '../i18n';
import { Suspense, useEffect, useState } from 'react';
import { I18nextProvider } from 'react-i18next';
import { app } from '../firebase';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';

function App(): React.ReactElement {
require('typeface-muli'); // Load font
Expand All @@ -33,15 +35,17 @@ function App(): React.ReactElement {
<RemoteConfigProvider>
<I18nextProvider i18n={i18n}>
<Suspense>
<div id='app-main-container'>
<AppSpinner>
<BrowserRouter>
<Header />
{isAppReady ? <AppRouter /> : null}
</BrowserRouter>
</AppSpinner>
<Footer />
</div>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<div id='app-main-container'>
<AppSpinner>
<BrowserRouter>
<Header />
{isAppReady ? <AppRouter /> : null}
</BrowserRouter>
</AppSpinner>
<Footer />
</div>
</LocalizationProvider>
</Suspense>
</I18nextProvider>
</RemoteConfigProvider>
Expand Down
128 changes: 124 additions & 4 deletions web-app/src/app/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,18 @@ import {
import type NavigationItem from '../interface/Navigation';
import { useNavigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { selectIsAuthenticated } from '../store/selectors';
import { selectIsAuthenticated, selectUserEmail } from '../store/selectors';
import LogoutConfirmModal from './LogoutConfirmModal';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import { TreeView } from '@mui/x-tree-view/TreeView';
import { TreeItem } from '@mui/x-tree-view/TreeItem';
import { OpenInNew } from '@mui/icons-material';
import { BikeScooterOutlined, OpenInNew } from '@mui/icons-material';
import '../styles/Header.css';
import { useRemoteConfig } from '../context/RemoteConfigProvider';
import i18n from '../../i18n';
import { NestedMenuItem } from 'mui-nested-menu';
import DirectionsBusIcon from '@mui/icons-material/DirectionsBus';

const drawerWidth = 240;
const websiteTile = 'Mobility Database';
Expand Down Expand Up @@ -95,6 +97,38 @@ const DrawerContent: React.FC<{
</ListItem>
))}
<Divider sx={{ mt: 2, mb: 2 }} />
<TreeView
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
sx={{ textAlign: 'left' }}
>
<TreeItem nodeId='1' label='GTFS Metrics' sx={{ color: '#3959fa' }}>
<TreeItem
nodeId='2'
label='Feeds'
sx={{ color: '#7c7c7c', cursor: 'pointer' }}
onClick={() => {
onNavigationClick('/metrics/gtfs/feeds');
}}
/>
<TreeItem
nodeId='3'
label='Notices'
sx={{ color: '#7c7c7c', cursor: 'pointer' }}
onClick={() => {
onNavigationClick('/metrics/gtfs/notices');
}}
/>
<TreeItem
nodeId='4'
label='Features'
sx={{ color: '#7c7c7c', cursor: 'pointer' }}
onClick={() => {
onNavigationClick('/metrics/gtfs/features');
}}
/>
</TreeItem>
</TreeView>
{isAuthenticated ? (
<TreeView
defaultCollapseIcon={<ExpandMoreIcon />}
Expand Down Expand Up @@ -166,6 +200,7 @@ export default function DrawerAppBar(): React.ReactElement {

const navigateTo = useNavigate();
const isAuthenticated = useSelector(selectIsAuthenticated);
const userEmail = useSelector(selectUserEmail);

const handleDrawerToggle = (): void => {
setMobileOpen((prevState) => !prevState);
Expand Down Expand Up @@ -199,7 +234,7 @@ export default function DrawerAppBar(): React.ReactElement {
setAnchorEl(null);
};

const handleMenuItemClick = (item: NavigationItem): void => {
const handleMenuItemClick = (item: NavigationItem | string): void => {
handleMenuClose();
handleNavigation(item);
};
Expand Down Expand Up @@ -260,20 +295,104 @@ export default function DrawerAppBar(): React.ReactElement {
{item.title}
</Button>
))}
{/* Allow users with mobilitydata.org email to access metrics */}
{config.enableMetrics ||
(userEmail?.endsWith('mobilitydata.org') === true && (
<>
<Button
aria-controls='analytics-menu'
aria-haspopup='true'
endIcon={<ArrowDropDownIcon />}
onClick={handleMenuOpen}
sx={{ color: 'black' }}
id='analytics-button-menu'
>
Metrics
</Button>
<Menu
id='analytics-menu'
anchorEl={anchorEl}
open={
anchorEl !== null &&
anchorEl.id === 'analytics-button-menu'
}
onClose={handleMenuClose}
>
<NestedMenuItem
label='GTFS'
parentMenuOpen={Boolean(anchorEl)}
leftIcon={<DirectionsBusIcon />}
>
<MenuItem
onClick={() => {
handleMenuItemClick('/metrics/gtfs/feeds');
}}
>
Feeds
</MenuItem>
<MenuItem
onClick={() => {
handleMenuItemClick('/metrics/gtfs/notices');
}}
>
Notices
</MenuItem>
<MenuItem
onClick={() => {
handleMenuItemClick('/metrics/gtfs/features');
}}
>
Features
</MenuItem>
</NestedMenuItem>
<NestedMenuItem
label='GBFS'
parentMenuOpen={Boolean(anchorEl)}
leftIcon={<BikeScooterOutlined />}
>
<MenuItem
onClick={() => {
handleMenuItemClick('/metrics/gbfs/feeds');
}}
>
Feeds
</MenuItem>
<MenuItem
onClick={() => {
handleMenuItemClick('/metrics/gbfs/notices');
}}
>
Notices
</MenuItem>
<MenuItem
onClick={() => {
handleMenuItemClick('/metrics/gbfs/versions');
}}
>
Versions
</MenuItem>
</NestedMenuItem>
</Menu>
</>
))}

{isAuthenticated ? (
<>
<Button
aria-controls='account-menu'
aria-haspopup='true'
onClick={handleMenuOpen}
endIcon={<ArrowDropDownIcon />}
id='account-button-menu'
>
Account
</Button>
<Menu
id='account-menu'
anchorEl={anchorEl}
open={Boolean(anchorEl)}
open={
anchorEl !== null && anchorEl.id === 'account-button-menu'
}
onClose={handleMenuClose}
>
<MenuItem
Expand Down Expand Up @@ -304,6 +423,7 @@ export default function DrawerAppBar(): React.ReactElement {
onChange={(lang) => {
void i18n.changeLanguage(lang.target.value);
}}
variant='standard'
>
<MenuItem value={'en'}>EN</MenuItem>
<MenuItem value={'fr'}>FR</MenuItem>
Expand Down
2 changes: 2 additions & 0 deletions web-app/src/app/constants/Navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const MOBILITY_DATA_LINKS = {
github: 'https://github.com/MobilityData/mobility-database-catalogs',
};

export const WEB_VALIDATOR_LINK = 'https://gtfs-validator.mobilitydata.org';

export function buildNavigationItems(
featureFlags: RemoteConfigValues,
): NavigationItem[] {
Expand Down
11 changes: 11 additions & 0 deletions web-app/src/app/interface/RemoteConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ export interface RemoteConfigValues extends FirebaseDefaultConfig {
* false: renders the legacy feed submission page based in the Contribute.tsx
*/
enableFeedSubmissionStepper: boolean;
/** Enable Metrics view
* Values:
* true: renders the metrics view
* false: hides the metrics view
*/
enableMetrics: boolean;
/** GTFS metrics' bucket endpoint */
gtfsMetricsBucketEndpoint: string;
}

// Add default values for remote config here
Expand All @@ -21,6 +29,9 @@ export const defaultRemoteConfigValues: RemoteConfigValues = {
enableFeedsPage: false,
enableLanguageToggle: false,
enableFeedSubmissionStepper: false,
enableMetrics: false,
gtfsMetricsBucketEndpoint:
'https://storage.googleapis.com/mobilitydata-gtfs-analytics-dev',
};

remoteConfig.defaultConfig = defaultRemoteConfigValues;
9 changes: 9 additions & 0 deletions web-app/src/app/router/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import { logout } from '../store/profile-reducer';
import FeedSubmission from '../screens/FeedSubmission';
import FeedSubmissionFAQ from '../screens/FeedSubmissionFAQ';
import FeedSubmitted from '../screens/FeedSubmitted';
import GTFSFeedAnalytics from '../screens/Analytics/GTFSFeedAnalytics';
import GTFSNoticeAnalytics from '../screens/Analytics/GTFSNoticeAnalytics';
import GTFSFeatureAnalytics from '../screens/Analytics/GTFSFeatureAnalytics';

export const AppRouter: React.FC = () => {
const navigateTo = useNavigate();
Expand Down Expand Up @@ -89,6 +92,12 @@ export const AppRouter: React.FC = () => {
<Route path='contribute-faq' element={<FeedSubmissionFAQ />} />
<Route path='privacy-policy' element={<PrivacyPolicy />} />
<Route path='terms-and-conditions' element={<TermsAndConditions />} />
<Route path='metrics/gtfs'>
<Route index element={<GTFSFeedAnalytics />} />
<Route path='feeds/*' element={<GTFSFeedAnalytics />} />
<Route path='notices/*' element={<GTFSNoticeAnalytics />} />
<Route path='features/*' element={<GTFSFeatureAnalytics />} />
</Route>
</Routes>
);
};
Expand Down
Loading

0 comments on commit d4c9b1a

Please sign in to comment.