Skip to content

Commit

Permalink
Merge pull request #6720 from hotosm/develop
Browse files Browse the repository at this point in the history
v4.8.4 on staging
  • Loading branch information
dakotabenjamin authored Jan 27, 2025
2 parents 6446849 + 2f67b8c commit dfd6d83
Show file tree
Hide file tree
Showing 19 changed files with 325 additions and 58 deletions.
39 changes: 26 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,36 @@ This is Free and Open Source Software. You are welcome to use the code and set u

## Product Roadmap

[Discussion](https://github.com/hotosm/tasking-manager/discussions/6688) | [Roadmap Kanban](https://github.com/orgs/hotosm/projects/41/views/1)


Status | Feature
-------|---------
✅ | Up-to-date OSM Statistics: Integrated with [ohsome Now](https://stats.now.ohsome.org/) for real-time data insights. Released in [v4.6.2](https://github.com/hotosm/tasking-manager/releases/tag/v4.6.2).
✅ | Downloadable OSM Exports: Export data directly from each project. Available in[ v4.7.0](https://github.com/hotosm/tasking-manager/releases/tag/v4.7.0).
✅ | Live Data Quality Monitoring: Monitor project data quality in real-time. Introduced in [v4.7.2](https://github.com/hotosm/tasking-manager/releases/tag/v4.7.2).
✅ Completed: Finished, available on [production instance](https://tasks.hotosm.org)

🔄 In Progress: Task or milestone is actively being worked on

📅 Planned: Task or milestone is scheduled for a future date



Status | Feature | Release
-------|---------|---------
✅ | Up-to-date OSM Statistics: Integrated with [ohsome Now](https://stats.now.ohsome.org/) for real-time data insights.| Released in [v4.6.2](https://github.com/hotosm/tasking-manager/releases/tag/v4.6.2).
✅ | Downloadable OSM Exports: Export data directly from each project. | Available in[ v4.7.0](https://github.com/hotosm/tasking-manager/releases/tag/v4.7.0).
✅ | Live Data Quality Monitoring: Monitor project data quality in real-time. | Introduced in [v4.7.2](https://github.com/hotosm/tasking-manager/releases/tag/v4.7.2).
✅ | Rapid Editor Upgrade: Enhanced mapping experience with the latest rapid editor updates.
✅ | Public-Facing Partner Pages: Create and display dedicated pages for partners running remote mapathons.
⚙️| Downloadable Project List View: Allow users to explore projects via a downloadable list. [View issue](https://github.com/hotosm/tasking-manager/issues/3394).
⚙️| MapSwipe Stats Integration: Display MapSwipe statistics on Partner Pages.
⚙️| iD Editor Latest Features: Integrate the newest features of the iD editor.
⚙️| FastAPI Migration: Improve performance and scalability of Tasking Manager to handle large scale validation and mapping efforts.
| | Latest Translations Update: Keep all content current with the latest translations.
| | OSM Practice Projects: Enable users to engage in OSM practice projects within Tasking Manager workflow.
| | Improved Project Sorting & Filtering: Enhance the user experience with better sorting and filtering options.
| | UI/UX Enhancements: Continuous improvements to the user interface and experience.
✅ | Downloadable Project List View: Allow users to explore projects via a downloadable list. [View issue](https://github.com/hotosm/tasking-manager/issues/3394).
✅ | MapSwipe Stats Integration: Display MapSwipe statistics on Partner Pages.
✅ | iD Editor Latest Features: Integrate the newest features of the iD editor.
🔄 | FastAPI Migration: Improve performance and scalability of Tasking Manager to handle large scale validation and mapping efforts.
🔄 | Super Mapper: Redefine Mapper Level Milestones
🔄 | OSM Practice Projects: Enable users to engage in OSM practice projects within Tasking Manager workflow.
📅 | Expanding Project Types beyond basemap features
📅 | AI Integration: task assignment, difficulty estimation, and validation
📅 | External tools Integration: MapSwipe, uMap, Maproulette
📅 | Latest Translations Update: Keep all content current with the latest translations.
📅 | Improved Project Sorting & Filtering: Enhance the user experience with better sorting and filtering options.
📅 | UI/UX Enhancements: Continuous improvements to the user interface and experience.



Expand Down
9 changes: 8 additions & 1 deletion backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,11 @@ def add_api_endpoints(app):
from backend.api.countries.resources import CountriesRestAPI

# Teams API endpoint
from backend.api.teams.resources import TeamsRestAPI, TeamsAllAPI
from backend.api.teams.resources import (
TeamsRestAPI,
TeamsAllAPI,
TeamsJoinRequestAPI,
)
from backend.api.teams.actions import (
TeamsActionsJoinAPI,
TeamsActionsAddAPI,
Expand Down Expand Up @@ -832,6 +836,9 @@ def add_api_endpoints(app):
format_url("teams/<int:team_id>/"),
methods=["GET", "DELETE", "PATCH"],
)
api.add_resource(
TeamsJoinRequestAPI, format_url("teams/join_requests/"), methods=["GET"]
)

# Teams actions endpoints
api.add_resource(
Expand Down
104 changes: 96 additions & 8 deletions backend/api/teams/resources.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from flask_restful import Resource, request, current_app
import csv
import io
from distutils.util import strtobool
from datetime import datetime
from flask_restful import Resource, current_app, request
from flask import Response
from schematics.exceptions import DataError

from backend.models.dtos.team_dto import (
NewTeamDTO,
UpdateTeamDTO,
TeamSearchDTO,
)
from backend.models.dtos.team_dto import NewTeamDTO, TeamSearchDTO, UpdateTeamDTO
from backend.models.postgis.team import Team, TeamMembers
from backend.models.postgis.user import User
from backend.services.organisation_service import OrganisationService
from backend.services.team_service import TeamService, TeamServiceError
from backend.services.users.authentication_service import token_auth
from backend.services.organisation_service import OrganisationService
from backend.services.users.user_service import UserService
from distutils.util import strtobool


class TeamsRestAPI(Resource):
Expand Down Expand Up @@ -368,3 +370,89 @@ def post(self):
return {"Error": error_msg, "SubCode": "CreateTeamNotPermitted"}, 403
except TeamServiceError as e:
return str(e), 400


class TeamsJoinRequestAPI(Resource):
# @tm.pm_only()
@token_auth.login_required
def get(self):
"""
Downloads join requests for a specific team as a CSV.
---
tags:
- teams
produces:
- text/csv
parameters:
- in: query
name: team_id
description: ID of the team to filter by
required: true
type: integer
default: null
responses:
200:
description: CSV file with inactive team members
400:
description: Missing or invalid parameters
401:
description: Unauthorized access
500:
description: Internal server error
"""
# Parse the team_id from query parameters
team_id = request.args.get("team_id", type=int)
if not team_id:
return {"message": "team_id is required"}, 400

# Query the database
try:
team_members = (
TeamMembers.query.join(User, TeamMembers.user_id == User.id)
.join(Team, TeamMembers.team_id == Team.id)
.filter(TeamMembers.team_id == team_id, ~TeamMembers.active)
.with_entities(
User.username.label("username"),
TeamMembers.joined_date.label("joined_date"),
Team.name.label("team_name"),
)
.all()
)

if not team_members:
return {
"message": "No inactive members found for the specified team"
}, 200

# Generate CSV in memory
csv_output = io.StringIO()
writer = csv.writer(csv_output)
writer.writerow(
["Username", "Date Joined (UTC)", "Team Name"]
) # CSV header

for member in team_members:
writer.writerow(
[
member.username,
member.joined_date.strftime("%Y-%m-%d %H:%M:%S")
if member.joined_date
else "N/A",
member.team_name,
]
)

# Prepare response
csv_output.seek(0)
return Response(
csv_output.getvalue(),
mimetype="text/csv",
headers={
"Content-Disposition": (
"attachment; filename=join_requests_"
f"{team_id}_{datetime.now().strftime('%Y%m%d')}.csv"
)
},
)
except Exception as e:
return {"message": f"Error occurred: {str(e)}"}, 500
2 changes: 2 additions & 0 deletions backend/models/dtos/team_dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
LongType,
ListType,
ModelType,
UTCDateTimeType,
)

from backend.models.dtos.stats_dto import Pagination
Expand Down Expand Up @@ -64,6 +65,7 @@ class TeamMembersDTO(Model):
default=False, serialized_name="joinRequestNotifications"
)
picture_url = StringType(serialized_name="pictureUrl")
joined_date = UTCDateTimeType(serialized_name="joinedDate")


class TeamProjectDTO(Model):
Expand Down
5 changes: 5 additions & 0 deletions backend/models/postgis/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
TeamRoles,
)
from backend.models.postgis.user import User
from backend.models.postgis.utils import timestamp


class TeamMembers(db.Model):
Expand All @@ -36,6 +37,7 @@ class TeamMembers(db.Model):
team = db.relationship(
"Team", backref=db.backref("members", cascade="all, delete-orphan")
)
joined_date = db.Column(db.DateTime, default=timestamp)

def create(self):
"""Creates and saves the current model to the DB"""
Expand Down Expand Up @@ -105,6 +107,7 @@ def create_from_dto(cls, new_team_dto: NewTeamDTO):
new_member.user_id = new_team_dto.creator
new_member.function = TeamMemberFunctions.MANAGER.value
new_member.active = True
new_member.joined_date = timestamp()

new_team.members.append(new_member)

Expand Down Expand Up @@ -222,6 +225,7 @@ def as_dto_team_member(self, member) -> TeamMembersDTO:
member_dto.picture_url = user.picture_url
member_dto.active = member.active
member_dto.join_request_notifications = member.join_request_notifications
member_dto.joined_date = member.joined_date
return member_dto

def as_dto_team_project(self, project) -> TeamProjectDTO:
Expand All @@ -242,6 +246,7 @@ def _get_team_members(self):
"pictureUrl": mem.member.picture_url,
"function": TeamMemberFunctions(mem.function).name,
"active": mem.active,
"joinedDate": mem.joined_date,
}
)

Expand Down
15 changes: 14 additions & 1 deletion backend/services/project_search_service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pandas as pd
from backend.models.postgis.user import User
from flask import current_app
import math
import geojson
Expand Down Expand Up @@ -92,14 +93,23 @@ def create_search_query(user=None, as_csv: bool = False):
Project.country,
Organisation.name.label("organisation_name"),
Organisation.logo.label("organisation_logo"),
User.name.label("author_name"),
User.username.label("author_username"),
Project.created.label("creation_date"),
func.coalesce(
func.sum(func.ST_Area(Project.geometry, True) / 1000000)
).label("total_area"),
)
.filter(Project.geometry is not None)
.outerjoin(Organisation, Organisation.id == Project.organisation_id)
.group_by(Organisation.id, Project.id, ProjectInfo.name)
.outerjoin(User, User.id == Project.author_id)
.group_by(
Organisation.id,
Project.id,
ProjectInfo.name,
User.username,
User.name,
)
)
else:
query = (
Expand Down Expand Up @@ -246,6 +256,7 @@ def search_projects_as_csv(search_dto: ProjectSearchDTO, user) -> str:
row["total_contributors"] = Project.get_project_total_contributions(
row["id"]
)
row["author"] = row["author_name"] or row["author_username"]

if is_user_admin:
partners_names = (
Expand All @@ -269,6 +280,8 @@ def search_projects_as_csv(search_dto: ProjectSearchDTO, user) -> str:
"tasks_validated",
"total_tasks",
"centroid",
"author_name",
"author_username",
]

colummns_to_rename = {
Expand Down
3 changes: 2 additions & 1 deletion example.env
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ OSM_REGISTER_URL=https://www.openstreetmap.org/user/new

# API base URL and token(used to retrieve user stats only) for ohsomeNow Stats
#
OHSOME_STATS_BASE_URL=https://stats.now.ohsome.org/api
OHSOME_STATS_BASE_URL=https://stats.now.ohsome.org
OHSOME_STATS_API_URL=https://stats.now.ohsome.org/api
OHSOME_STATS_TOKEN=testSuperSecretTestToken

# Secret (required)
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/api/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query';

import { fetchExternalJSONAPI } from '../network/genericJSONRequest';
import api from './apiClient';
import { OHSOME_STATS_BASE_URL, defaultChangesetComment } from '../config';
import { OHSOME_STATS_API_URL, defaultChangesetComment } from '../config';

const ohsomeProxyAPI = (url) => {
const token = localStorage.getItem('token');
Expand Down Expand Up @@ -39,7 +39,7 @@ export const useProjectStatisticsQuery = (projectId) => {

export const useOsmStatsQuery = () => {
const fetchOsmStats = ({ signal }) => {
return api().get(`${OHSOME_STATS_BASE_URL}/stats/${defaultChangesetComment}-%2A`, {
return api().get(`${OHSOME_STATS_API_URL}/stats/${defaultChangesetComment}-%2A`, {
signal,
});
};
Expand All @@ -54,7 +54,7 @@ export const useOsmStatsQuery = () => {

export const useOsmHashtagStatsQuery = (defaultComment) => {
const fetchOsmStats = ({ signal }) => {
return api().get(`${OHSOME_STATS_BASE_URL}/stats/${defaultComment[0].replace('#', '')}`, {
return api().get(`${OHSOME_STATS_API_URL}/stats/${defaultComment[0].replace('#', '')}`, {
signal,
});
};
Expand All @@ -71,7 +71,7 @@ export const useOsmHashtagStatsQuery = (defaultComment) => {
export const useUserOsmStatsQuery = (id) => {
const fetchUserOsmStats = () => {
return ohsomeProxyAPI(
`${OHSOME_STATS_BASE_URL}/topic/poi,highway,building,waterway/user?userId=${id}`,
`${OHSOME_STATS_API_URL}/topic/poi,highway,building,waterway/user?userId=${id}`,
);
};

Expand All @@ -87,7 +87,7 @@ export const useUserOsmStatsQuery = (id) => {

export const useOsmStatsMetadataQuery = () => {
const fetchOsmStatsMetadata = () => {
return fetchExternalJSONAPI(`${OHSOME_STATS_BASE_URL}/metadata`);
return fetchExternalJSONAPI(`${OHSOME_STATS_API_URL}/metadata`);
};

return useQuery({
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/partners/partnersActivity.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ReactPlaceholder from 'react-placeholder';

import PartnersProgresBar from './partnersProgresBar';
import messages from './messages';
import { OHSOME_STATS_BASE_URL } from '../../config';
import { OHSOME_STATS_API_URL } from '../../config';

export const Activity = ({ partner }) => {
const [data, setData] = useState(null);
Expand All @@ -22,7 +22,7 @@ export const Activity = ({ partner }) => {
?.map((tag) => tag.trim().replace('#', '').toLowerCase())
?.join(',');
const response = await fetch(
OHSOME_STATS_BASE_URL + `/stats/hashtags/${primaryHashtag},${secondaryHashtags}`,
OHSOME_STATS_API_URL + `/stats/hashtags/${primaryHashtag},${secondaryHashtags}`,
);

if (response.ok) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/partners/partnersProgresBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const ProgressBar = ({ className, firstBarValue, secondBarValue = 0, children, d
target={'_blank'}
rel="noreferrer"
className="white no-underline"
href={OHSOME_STATS_BASE_URL + '/dashboard#hashtags=' + data.primary}
href={OHSOME_STATS_BASE_URL + '/dashboard#hashtag=' + data.primary}
>
{'#' + data.primary}{' '}
</a>
Expand Down
Loading

0 comments on commit dfd6d83

Please sign in to comment.