Skip to content

Commit

Permalink
Merge pull request #6315 from hotosm/feature/liveMonitoringDQ
Browse files Browse the repository at this point in the history
Feature/live monitoring DQ
  • Loading branch information
ramyaragupathy authored Apr 9, 2024
2 parents e8f542f + 1282c82 commit 9afaa69
Show file tree
Hide file tree
Showing 14 changed files with 899 additions and 13 deletions.
2 changes: 2 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ TM_DEFAULT_LOCALE=en
# TM_SENTRY_BACKEND_DSN=https://foo.ingest.sentry.io/1234567
# TM_SENTRY_FRONTEND_DSN=https://bar.ingest.sentry.io/8901234

# Underpass API URL (for project live monitoring feature)
UNDERPASS_URL=https://underpass.hotosm.org

#EXPORT TOOL Integration with 0(Disable) and 1(Enable) and S3 URL for Export Tool
#EXPORT_TOOL_S3_URL=https://foorawdataapi.s3.amazonaws.com
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@formatjs/intl-relativetimeformat": "^11.2.4",
"@formatjs/macro": "^0.2.8",
"@hotosm/id": "^2.21.1",
"@hotosm/underpass-ui": "^0.0.4",
"@hotosm/iso-countries-languages": "^1.1.2",
"@mapbox/mapbox-gl-draw": "^1.4.1",
"@mapbox/mapbox-gl-geocoder": "^5.0.1",
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/api/projects.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import axios from 'axios';
import { subMonths, format } from 'date-fns';
import { useQuery } from '@tanstack/react-query';
import { useSelector } from 'react-redux';

import { remapParamsToAPI } from '../utils/remapParamsToAPI';
import api from './apiClient';
import { UNDERPASS_URL } from '../config';

export const useProjectsQuery = (fullProjectsQuery, action) => {
const token = useSelector((state) => state.auth.token);
Expand Down Expand Up @@ -187,6 +189,18 @@ export const submitValidationTask = (projectId, payload, token, locale) => {
);
};

export const useAvailableCountriesQuery = () => {
const fetchGeojsonData = () => {
return axios.get(`${UNDERPASS_URL}/availability`);
};

return useQuery({
queryKey: ['priority-geojson'],
queryFn: fetchGeojsonData,
select: (res) => res.data,
});
};

const backendToQueryConversion = {
difficulty: 'difficulty',
campaign: 'campaign',
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/footer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function Footer() {
'projects/:id/tasks',
'projects/:id/map',
'projects/:id/validate',
'projects/:id/live',
'manage/organisations/new/',
'manage/teams/new',
'manage/campaigns/new',
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/components/projectDetail/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import { PermissionBox } from './permissionBox';
import { CustomButton } from '../button';
import { ProjectInfoPanel } from './infoPanel';
import { OSMChaButton } from './osmchaButton';
import { LiveViewButton } from './liveViewButton';
import { useSetProjectPageTitleTag } from '../../hooks/UseMetaTags';
import useHasLiveMonitoringFeature from '../../hooks/UseHasLiveMonitoringFeature';
import { useProjectContributionsQuery, useProjectTimelineQuery } from '../../api/projects';
import { Alert } from '../alert';

Expand Down Expand Up @@ -144,6 +146,8 @@ export const ProjectDetail = (props) => {
props.project.projectId,
);

const hasLiveMonitoringFeature = useHasLiveMonitoringFeature();

const htmlDescription =
props.project.projectInfo && htmlFromMarkdown(props.project.projectInfo.description);
const h2Classes = 'pl4 f3 f2-ns fw5 mt2 mb3 mb4-ns ttu barlow-condensed blue-dark';
Expand Down Expand Up @@ -346,6 +350,15 @@ export const ProjectDetail = (props) => {
project={props.project}
className="bg-white blue-dark ba b--grey-light pa3"
/>

{/* show live view button only when the project has live monitoring feature */}
{hasLiveMonitoringFeature && (
<LiveViewButton
projectId={props.project.projectId}
className="bg-white blue-dark ba b--grey-light pa3"
/>
)}

<DownloadAOIButton
projectId={props.project.projectId}
className="bg-white blue-dark ba b--grey-light pa3"
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/components/projectDetail/liveViewButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';

import messages from './messages';
import { CustomButton } from '../button';

export const LiveViewButton = ({ projectId, className, compact = false }) => (
<Link to={`/projects/${projectId}/live`} className="pr2">
{
<CustomButton className={className}>
{compact ? (
<FormattedMessage {...messages.live} />
) : (
<FormattedMessage {...messages.liveMonitoring} />
)}
</CustomButton>
}
</Link>
);
8 changes: 8 additions & 0 deletions frontend/src/components/projectDetail/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,14 @@ export default defineMessages({
id: 'project.detail.sections.contributions.osmcha',
defaultMessage: 'Changesets in OSMCha',
},
live: {
id: 'project.detail.sections.contributions.live',
defaultMessage: 'Live',
},
liveMonitoring: {
id: 'project.detail.sections.contributions.liveMonitoring',
defaultMessage: 'Live monitoring',
},
changesets: {
id: 'project.detail.sections.contributions.changesets',
defaultMessage: 'Changesets',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ export const MAP_STYLE = MAPBOX_TOKEN
export const MAPBOX_RTL_PLUGIN_URL =
'https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.0/mapbox-gl-rtl-text.js';

export const UNDERPASS_URL = process.env.REACT_APP_UNDERPASS_URL || 'https://underpass.hotosm.org';

export const DROPZONE_SETTINGS = {
accept: {
'image/*': ['.jpeg', '.jpg', '.png', '.webp', '.gif'],
Expand Down
67 changes: 67 additions & 0 deletions frontend/src/config/underpass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { MAPBOX_TOKEN, UNDERPASS_URL } from '.';

export const underpassConfig = {
API_URL: UNDERPASS_URL,
MAPBOX_TOKEN,
// set default sources of Tasking Manager
sources: {
osm: {
type: 'raster',
tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: '&copy; OpenStreetMap Contributors',
maxzoom: 19,
},
Bing: {
type: 'raster',
tiles: ['https://ecn.t3.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=1'],
tileSize: 256,
attribution: '&copy; OpenStreetMap Contributors',
maxzoom: 18,
},
Mapbox: {
type: 'raster',
tiles: [
`https://api.mapbox.com/styles/v1/mapbox/satellite-v9/tiles/{z}/{x}/{y}?access_token=${MAPBOX_TOKEN}`,
],
tileSize: 512,
attribution: '&copy; OpenStreetMap Contributors &copy; Mapbox',
maxzoom: 19,
},
EsriWorldImagery: {
type: 'raster',
tiles: [
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
],
tileSize: 256,
attribution: '&copy; OpenStreetMap Contributors &copy; ESRI',
maxzoom: 18,
},
},
};

export const availableImageryOptions = [
{ label: 'OSM', value: 'osm' },
{ label: 'Bing', value: 'Bing' },
{ label: 'Mapbox Satellite', value: 'Mapbox' },
{ label: 'ESRI World Imagery', value: 'EsriWorldImagery' },
];

export const statusList = {
ALL: '',
UNSQUARED: 'badgeom',
OVERLAPPING: 'overlapping',
BADVALUE: 'badvalue',
};

export const mappingTypesTags = {
ROADS: 'highway',
BUILDINGS: 'building',
WATERWAYS: 'waterway',
};

export const mappingTypesFeatureTypes = {
ROADS: 'line',
BUILDINGS: 'polygon',
WATERWAYS: 'line',
};
42 changes: 42 additions & 0 deletions frontend/src/hooks/UseHasLiveMonitoringFeature.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';

import { useProjectQuery } from '../api/projects';
import { useAvailableCountriesQuery } from '../api/projects';

/**
* A React custom hook that checks if a project can view live monitoring feature
*
* Returns null if the status is unknown, else returns boolean
*
*/
export default function useHasLiveMonitoringFeature() {
const { id: projectId } = useParams();
const userDetails = useSelector((state) => state.auth.userDetails);
const [hasLiveMonitoringFeature, setHasLiveMonitoringFeature] = useState(null);

const { data: project } = useProjectQuery(projectId);
const { data: availableCountries } = useAvailableCountriesQuery();

useEffect(() => {
if (!availableCountries || !project || !userDetails) return;

// set hasLiveMonitoringFeature to false if project is not published
// or expert mode is not enabled
if (project.data.status !== 'PUBLISHED' || !userDetails.isExpert) {
setHasLiveMonitoringFeature(false);
return;
}

// check if the project has live monitoring feature enabled
// based on the country list provided by available.json
const isLiveMonitoringAvailableInCountry = project.data.countryTag.some((country) =>
availableCountries.countries.some((item) => country.toLowerCase() === item.toLowerCase()),
);

setHasLiveMonitoringFeature(isLiveMonitoringAvailableInCountry);
}, [availableCountries, project, userDetails]);

return hasLiveMonitoringFeature;
}
10 changes: 10 additions & 0 deletions frontend/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ export const router = createBrowserRouter(
}}
ErrorBoundary={FallbackComponent}
/>
<Route
path="projects/:id/live"
lazy={async () => {
const { ProjectLiveMonitoring } = await import(
'./views/projectLiveMonitoring' /* webpackChunkName: "projectLiveMonitoring" */
);
return { Component: ProjectLiveMonitoring };
}}
ErrorBoundary={FallbackComponent}
/>
<Route
path="projects/:id/stats"
lazy={async () => {
Expand Down
37 changes: 37 additions & 0 deletions frontend/src/views/projectLiveMonitoring.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@import '@hotosm/underpass-ui/dist/index.css';

.maplibregl-map {
height: calc(100vh - 6.05rem);
}

.top {
position: absolute;
top: 0.625rem;
left: 0.625rem;
z-index: 999;
}

svg.pl2 {
display: inherit;
height: 22px !important;
width: 19px !important;
margin-left: 0px;
}

.react-select__control {
border-radius: 0.25rem !important;
min-height: 36px !important;
width: 15.8rem;
}

.react-select__menu {
width: 15.8rem !important;
}

.title-text {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
Loading

0 comments on commit 9afaa69

Please sign in to comment.