Skip to content

Commit

Permalink
Merge pull request #6021 from hotosm/enhancement/5999-ohsomeNow-stats
Browse files Browse the repository at this point in the history
Restructured API endpoints for ohsomeNow stats
  • Loading branch information
ramyaragupathy authored Oct 10, 2023
2 parents 669ca25 + 5c7f928 commit a858e01
Show file tree
Hide file tree
Showing 31 changed files with 2,043 additions and 1,966 deletions.
6 changes: 3 additions & 3 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ OSM_REGISTER_URL=https://www.openstreetmap.org/user/new
# It's not required to set this tag. Case it isn't set, an image will be used as background.
# TM_HOMEPAGE_VIDEO_URL=

# Endpoint for the missing maps stats
# API base URL and token(used to retrieve user stats only) for ohsomeNow Stats
#
TM_USER_STATS_API_URL=https://osm-stats-production-api.azurewebsites.net/users/
TM_HOMEPAGE_STATS_API_URL=https://osmstats-api.hotosm.org/wildcard?key=hotosm-project-*
OHSOME_STATS_BASE_URL=https://stats.now.ohsome.org/api
OHSOME_STATS_TOKEN=testSuperSecretTestToken

# Secret (required)
#
Expand Down
4 changes: 2 additions & 2 deletions frontend/.env.expand
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ REACT_APP_MAPBOX_TOKEN=$TM_MAPBOX_TOKEN
REACT_APP_ENABLE_SERVICEWORKER=$TM_ENABLE_SERVICEWORKER
REACT_APP_MAX_FILESIZE=$TM_IMPORT_MAX_FILESIZE
REACT_APP_MAX_AOI_AREA=$TM_MAX_AOI_AREA
REACT_APP_USER_STATS_API_URL=$TM_USER_STATS_API_URL
REACT_APP_HOMEPAGE_STATS_API_URL=$TM_HOMEPAGE_STATS_API_URL
REACT_APP_OHSOME_STATS_BASE_URL=$OHSOME_STATS_BASE_URL
REACT_APP_OHSOME_STATS_TOKEN=$OHSOME_STATS_TOKEN
REACT_APP_OSM_CLIENT_ID=$TM_CLIENT_ID
REACT_APP_OSM_CLIENT_SECRET=$TM_CLIENT_SECRET
REACT_APP_OSM_REDIRECT_URI=$TM_REDIRECT_URI
Expand Down
19 changes: 7 additions & 12 deletions frontend/src/api/stats.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useQuery } from '@tanstack/react-query';

import api from './apiClient';
import { HOMEPAGE_STATS_API_URL } from '../config';
import { OHSOME_STATS_BASE_URL } from '../config';

export const useSystemStatisticsQuery = () => {
const fetchSystemStats = ({ signal }) => {
Expand Down Expand Up @@ -33,7 +33,7 @@ export const useProjectStatisticsQuery = (projectId) => {

export const useOsmStatsQuery = () => {
const fetchOsmStats = ({ signal }) => {
return api().get(HOMEPAGE_STATS_API_URL, {
return api().get(`${OHSOME_STATS_BASE_URL}/stats/hotosm-project-%2A`, {
signal,
});
};
Expand All @@ -42,27 +42,22 @@ export const useOsmStatsQuery = () => {
queryKey: ['osm-stats'],
queryFn: fetchOsmStats,
useErrorBoundary: true,
select: (data) => data.data.result
});
};

export const useOsmHashtagStatsQuery = (defaultComment) => {
const fetchOsmStats = ({ signal }) => {
return api().get(
`https://osm-stats-production-api.azurewebsites.net/stats/${defaultComment[0].replace(
'#',
'',
)}`,
{
signal,
},
);
return api().get(`${OHSOME_STATS_BASE_URL}/stats/${defaultComment[0].replace('#', '')}`, {
signal,
});
};

return useQuery({
queryKey: ['osm-hashtag-stats'],
queryFn: fetchOsmStats,
useErrorBoundary: true,
enabled: Boolean(defaultComment?.[0]),
select: (data) => data.data,
select: (data) => data.data.result,
});
};
6 changes: 3 additions & 3 deletions frontend/src/components/homepage/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ export const StatsSection = () => {
<div className="pt5 pb2 ph6-l ph4 flex justify-around flex-wrap flex-nowrap-ns stats-container">
<StatsColumn
label={messages.buildingsStats}
value={hasStatsLoaded ? osmStatsData?.data.building_count_add : undefined}
value={hasStatsLoaded ? osmStatsData?.buildings : undefined}
/>
<StatsColumn
label={messages.roadsStats}
value={hasStatsLoaded ? osmStatsData?.data.road_km_add : undefined}
value={hasStatsLoaded ? osmStatsData?.roads : undefined}
/>
<StatsColumn
label={messages.editsStats}
value={hasStatsLoaded ? osmStatsData?.data.edits : undefined}
value={hasStatsLoaded ? osmStatsData?.edits : undefined}
/>
<StatsColumn
label={messages.communityStats}
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/components/homepage/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,9 @@
padding-bottom: 3.345rem;
}
}

.info-ohsome-tooltip {
display: flex;
justify-content: flex-end;
padding-right: 0.875rem;
}
21 changes: 9 additions & 12 deletions frontend/src/components/projectStats/edits.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
import React from 'react';
import ReactTooltip from 'react-tooltip';
import { FormattedMessage, useIntl } from 'react-intl';
import { FormattedMessage } from 'react-intl';

import projectMessages from './messages';
import userDetailMessages from '../userDetail/messages';
import { MappingIcon, HomeIcon, RoadIcon, EditIcon, InfoIcon } from '../svgIcons';
import { MappingIcon, HomeIcon, RoadIcon, EditIcon } from '../svgIcons';
import { StatsCard } from '../statsCard';
import StatsTimestamp from '../statsTimestamp';

export const EditsStats = ({ data }) => {
const intl = useIntl();
const { changesets, buildings, roads, edits } = data;

const iconClass = 'h-50 w-50';
const iconStyle = { height: '45px' };

return (
<div className="cf w-100 pb4 ph2 ph4-ns blue-dark">
<h3 className="barlow-condensed ttu f3">
<FormattedMessage {...projectMessages.edits} />
<InfoIcon
data-tip={intl.formatMessage(projectMessages.editsStats)}
className="blue-grey h1 w1 v-mid pb1 ml2"
/>
</h3>
<ReactTooltip place="top" className="mw6" effect="solid" />
<div className="flex items-center">
<h3 className="barlow-condensed ttu f3">
<FormattedMessage {...projectMessages.edits} />
</h3>
<StatsTimestamp messageType="project" />
</div>
<div className="db pb2 project-edit-stats">
<StatsCard
field={'changesets'}
Expand Down
4 changes: 0 additions & 4 deletions frontend/src/components/projectStats/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,4 @@ export default defineMessages({
id: 'project.stats.edits',
defaultMessage: 'Edits',
},
editsStats: {
id: 'project.stats.edits.info',
defaultMessage: 'These stats are retrieved using the default changeset comment of the project',
},
});
45 changes: 45 additions & 0 deletions frontend/src/components/statsTimestamp/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useState, useEffect } from 'react';
import { useIntl } from 'react-intl';
import ReactTooltip from 'react-tooltip';

import { fetchExternalJSONAPI } from '../../network/genericJSONRequest';
import { OHSOME_STATS_BASE_URL } from '../../config';
import { InfoIcon } from '../svgIcons';
import messages from './messages';

function StatsTimestamp({ messageType }) {
const intl = useIntl();
const [lastUpdated, setLastUpdated] = useState(null);

useEffect(() => {
fetchExternalJSONAPI(`${OHSOME_STATS_BASE_URL}/metadata`)
.then((res) => {
setLastUpdated(res.result.max_timestamp);
})
.catch((error) => console.error(error));
}, []);

const dateOptions = {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: 'numeric',
minute: 'numeric',
};

return (
<div>
<InfoIcon
className="blue-grey h1 w1 v-mid ml2 pointer"
data-tip={intl.formatMessage(messages[messageType], {
formattedDate: intl.formatDate(lastUpdated, dateOptions),
timeZone: intl.timeZone,
})}
data-for="ohsome-timestamp"
/>
<ReactTooltip id="ohsome-timestamp" place="top" className="mw6" effect="solid" />
</div>
);
}

export default StatsTimestamp;
14 changes: 14 additions & 0 deletions frontend/src/components/statsTimestamp/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineMessages } from 'react-intl';

export default defineMessages({
generic: {
id: 'stats.ohsome.timestamp.generic',
defaultMessage:
'These statistics come from ohsomeNow Stats and were last updated at {formattedDate} ({timeZone}). Missing fields will be made available soon!',
},
project: {
id: 'stats.ohsome.timestamp.project',
defaultMessage:
'These stats were retrieved using the default changeset comment of the project and were last updated at {formattedDate} ({timeZone}).',
},
});
75 changes: 44 additions & 31 deletions frontend/src/components/teamsAndOrgs/featureStats.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@ import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { FormattedMessage } from 'react-intl';

import messages from './messages';
import userDetailMessages from '../userDetail/messages';
import { HOMEPAGE_STATS_API_URL } from '../../config';
import { OHSOME_STATS_BASE_URL } from '../../config';
import { RoadIcon, HomeIcon, WavesIcon, MarkerIcon } from '../svgIcons';
import { StatsCard } from '../statsCard';
import StatsTimestamp from '../statsTimestamp';

export const FeatureStats = () => {
const [stats, setStats] = useState({ edits: 0, buildings: 0, roads: 0, pois: 0, waterways: 0 });
const getStats = async () => {
try {
const response = await axios.get(HOMEPAGE_STATS_API_URL);
const response = await axios.get(
`${OHSOME_STATS_BASE_URL}/stats/hotosm-project-%2A`,
);
const { edits, buildings, roads } = response.data.result;
setStats({
edits: response.data.edits,
buildings: response.data.building_count_add,
roads: response.data.road_km_add,
edits,
buildings,
roads,
pois: response.data.poi_count_add,
waterways: response.data.waterway_km_add,
});
Expand All @@ -32,31 +37,39 @@ export const FeatureStats = () => {
const iconStyle = { height: '45px' };

return (
<div className="w-100 cf">
<StatsCard
icon={<HomeIcon className={iconClass} style={iconStyle} />}
description={<FormattedMessage {...userDetailMessages.buildingsMapped} />}
value={stats.buildings || 0}
className={'w-25-l w-50-m w-100 mv1'}
/>
<StatsCard
icon={<RoadIcon className={iconClass} style={iconStyle} />}
description={<FormattedMessage {...userDetailMessages.roadMapped} />}
value={stats.roads || 0}
className={'w-25-l w-50-m w-100 mv1'}
/>
<StatsCard
icon={<MarkerIcon className={iconClass} style={iconStyle} />}
description={<FormattedMessage {...userDetailMessages.poiMapped} />}
value={stats.pois || 0}
className={'w-25-l w-50-m w-100 mv1'}
/>
<StatsCard
icon={<WavesIcon className={iconClass} style={iconStyle} />}
description={<FormattedMessage {...userDetailMessages.waterwaysMapped} />}
value={stats.waterways || 0}
className={'w-25-l w-50-m w-100 mv1'}
/>
</div>
<>
<div className="flex items-center">
<h4 className="f3 fw6 ttu barlow-condensed blue-dark">
<FormattedMessage {...messages.totalFeatures} />
</h4>
<StatsTimestamp messageType="generic" />
</div>
<div className="w-100 cf">
<StatsCard
icon={<HomeIcon className={iconClass} style={iconStyle} />}
description={<FormattedMessage {...userDetailMessages.buildingsMapped} />}
value={stats.buildings || 0}
className={'w-25-l w-50-m w-100 mv1'}
/>
<StatsCard
icon={<RoadIcon className={iconClass} style={iconStyle} />}
description={<FormattedMessage {...userDetailMessages.roadMapped} />}
value={stats.roads || 0}
className={'w-25-l w-50-m w-100 mv1'}
/>
<StatsCard
icon={<MarkerIcon className={iconClass} style={iconStyle} />}
description={<FormattedMessage {...userDetailMessages.poiMapped} />}
value={stats.pois || 0}
className={'w-25-l w-50-m w-100 mv1'}
/>
<StatsCard
icon={<WavesIcon className={iconClass} style={iconStyle} />}
description={<FormattedMessage {...userDetailMessages.waterwaysMapped} />}
value={stats.waterways || 0}
className={'w-25-l w-50-m w-100 mv1'}
/>
</div>
</>
);
};
4 changes: 4 additions & 0 deletions frontend/src/components/teamsAndOrgs/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -553,4 +553,8 @@ export default defineMessages({
id: 'management.stats.overview',
defaultMessage: 'Overview',
},
totalFeatures: {
id: 'management.stats.features',
defaultMessage: 'Total features',
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ test('FeatureStats renders the correct values and labels', async () => {
expect(screen.getByText('Km waterways mapped')).toBeInTheDocument();
await waitFor(() => expect(screen.getByText('2,380,562')).toBeInTheDocument());
expect(screen.getByText('101,367,027')).toBeInTheDocument();
expect(screen.getByText('183,011')).toBeInTheDocument();
expect(screen.getByText('350,906')).toBeInTheDocument();
// Uncomment the following when POIs and waterways become available
// expect(screen.getByText('183,011')).toBeInTheDocument();
// expect(screen.getByText('350,906')).toBeInTheDocument();
});
4 changes: 2 additions & 2 deletions frontend/src/components/userDetail/editsByNumbers.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ const EditsByNumbers = ({ osmStats }) => {
let reference = [
{
label: intl.formatMessage(typesMessages.buildings),
field: 'total_building_count_add',
field: 'buildings',
backgroundColor: CHART_COLOURS.red,
borderColor: CHART_COLOURS.white,
},
{
label: intl.formatMessage(typesMessages.roads),
field: 'total_road_km_add',
field: 'roads',
backgroundColor: CHART_COLOURS.green,
borderColor: CHART_COLOURS.white,
},
Expand Down
18 changes: 4 additions & 14 deletions frontend/src/components/userDetail/elementsMapped.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import humanizeDuration from 'humanize-duration';
import ReactTooltip from 'react-tooltip';
import { FormattedMessage } from 'react-intl';

import messages from './messages';
Expand All @@ -10,11 +9,11 @@ import {
HomeIcon,
WavesIcon,
MarkerIcon,
QuestionCircleIcon,
MappedIcon,
ValidatedIcon,
} from '../svgIcons';
import { StatsCard } from '../statsCard';
import StatsTimestamp from '../statsTimestamp';

export const TaskStats = ({ userStats, username }) => {
const {
Expand Down Expand Up @@ -137,12 +136,12 @@ export const ElementsMapped = ({ userStats, osmStats }) => {
<StatsCard
icon={<HomeIcon className={iconClass} style={iconStyle} />}
description={<FormattedMessage {...messages.buildingsMapped} />}
value={osmStats.total_building_count_add || 0}
value={osmStats.buildings || 0}
/>
<StatsCard
icon={<RoadIcon className={iconClass} style={iconStyle} />}
description={<FormattedMessage {...messages.roadMapped} />}
value={osmStats.total_road_km_add || 0}
value={osmStats.roads || 0}
/>
<StatsCard
icon={<MarkerIcon className={iconClass} style={iconStyle} />}
Expand All @@ -156,16 +155,7 @@ export const ElementsMapped = ({ userStats, osmStats }) => {
/>
</div>
<div className="cf w-100 relative tr pt3 pr3">
<FormattedMessage {...messages.delayPopup}>
{(msg) => (
<QuestionCircleIcon
className="pointer dib v-mid pl2 pb1 blue-light"
height="1.25rem"
data-tip={msg}
/>
)}
</FormattedMessage>
<ReactTooltip />
<StatsTimestamp messageType="generic" />
</div>
</div>
);
Expand Down
Loading

0 comments on commit a858e01

Please sign in to comment.