diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..cb06c8a56e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.* +*.yml +**/*.pyc +**/node_modules +**/npm-debug.log +logs/ +docs/ +scripts/ diff --git a/backend/__init__.py b/backend/__init__.py index 174c108bc0..40532a491b 100644 --- a/backend/__init__.py +++ b/backend/__init__.py @@ -49,9 +49,7 @@ def create_app(env=None): if EnvironmentConfig.SENTRY_BACKEND_DSN: sentry_init() - app = Flask( - __name__, - ) + app = Flask(__name__, template_folder="services/messaging/templates/") # Load configuration options from environment app.config.from_object("backend.config.EnvironmentConfig") diff --git a/backend/config.py b/backend/config.py index f6c6026597..12f335d21b 100644 --- a/backend/config.py +++ b/backend/config.py @@ -24,6 +24,10 @@ class EnvironmentConfig: API_VERSION = os.getenv("TM_APP_API_VERSION", "v2") ORG_CODE = os.getenv("TM_ORG_CODE", "") ORG_NAME = os.getenv("TM_ORG_NAME", "") + ORG_LOGO = os.getenv( + "TM_ORG_LOGO", + "https://cdn.hotosm.org/tasking-manager/uploads/1588741335578_hot-logo.png", + ) ENVIRONMENT = os.getenv("TM_ENVIRONMENT", "") # The default tag used in the OSM changeset comment DEFAULT_CHANGESET_COMMENT = os.getenv("TM_DEFAULT_CHANGESET_COMMENT", None) diff --git a/backend/services/messaging/chat_service.py b/backend/services/messaging/chat_service.py index d318d4f1fe..1d50dcf230 100644 --- a/backend/services/messaging/chat_service.py +++ b/backend/services/messaging/chat_service.py @@ -1,4 +1,7 @@ +import threading + from flask import current_app + from backend.models.dtos.message_dto import ChatMessageDTO, ProjectChatDTO from backend.models.postgis.project_chat import ProjectChat from backend.services.messaging.message_service import MessageService @@ -57,10 +60,11 @@ def post_message( if is_manager_permission or is_team_member or is_allowed_user: chat_message = ProjectChat.create_from_dto(chat_dto) - MessageService.send_message_after_chat( - chat_dto.user_id, chat_message.message, chat_dto.project_id - ) db.session.commit() + threading.Thread( + target=MessageService.send_message_after_chat, + args=(chat_dto.user_id, chat_message.message, chat_dto.project_id), + ).start() # Ensure we return latest messages after post return ProjectChat.get_messages(chat_dto.project_id, 1, 5) else: diff --git a/backend/services/messaging/message_service.py b/backend/services/messaging/message_service.py index 9b3fa6b7d7..f8a9a47b19 100644 --- a/backend/services/messaging/message_service.py +++ b/backend/services/messaging/message_service.py @@ -18,7 +18,7 @@ from backend.models.postgis.statuses import TeamRoles from backend.services.messaging.smtp_service import SMTPService from backend.services.messaging.template_service import ( - get_template, + get_txt_template, template_var_replacing, clean_html, ) @@ -41,7 +41,7 @@ class MessageService: def send_welcome_message(user: User): """ Sends welcome message to all new users at Sign up""" org_code = current_app.config["ORG_CODE"] - text_template = get_template("welcome_message_en.txt") + text_template = get_txt_template("welcome_message_en.txt") replace_list = [ ["[USERNAME]", user.username], ["[ORG_CODE]", org_code], @@ -68,7 +68,7 @@ def send_message_after_validation( return # No need to send a message to yourself user = UserService.get_user_by_id(mapped_by) - text_template = get_template( + text_template = get_txt_template( "invalidation_message_en.txt" if status == TaskStatus.INVALIDATED else "validation_message_en.txt" @@ -183,6 +183,7 @@ def _push_messages(messages): message["message"].project_id, clean_html(message["message"].subject), message["message"].message, + obj.message_type, ) if i + 1 % 10 == 0: @@ -370,64 +371,65 @@ def send_invite_to_join_team( @staticmethod def send_message_after_chat(chat_from: int, chat: str, project_id: int): """ Send alert to user if they were @'d in a chat message """ - current_app.logger.debug("Sending Message After Chat") - usernames = MessageService._parse_message_for_username(chat, project_id) - - if len(usernames) == 0: - return # Nobody @'d so return - - link = MessageService.get_project_link(project_id, include_chat_section=True) - - messages = [] - for username in usernames: - current_app.logger.debug(f"Searching for {username}") - try: - user = UserService.get_user_by_username(username) - except NotFound: - current_app.logger.error(f"Username {username} not found") - continue # If we can't find the user, keep going no need to fail - - message = Message() - message.message_type = MessageType.MENTION_NOTIFICATION.value - message.project_id = project_id - message.from_user_id = chat_from - message.to_user_id = user.id - message.subject = f"You were mentioned in {link} chat" - message.message = chat - messages.append(dict(message=message, user=user)) - - MessageService._push_messages(messages) - - query = ( - """ select user_id from project_favorites where project_id = :project_id""" - ) - result = db.engine.execute(text(query), project_id=project_id) - favorited_users = [r[0] for r in result] + # Because message-all run on background thread it needs it's own app context + app = create_app() + with app.app_context(): + usernames = MessageService._parse_message_for_username(chat, project_id) + if len(usernames) == 0: + return # Nobody @'d so return - if len(favorited_users) != 0: - project_link = MessageService.get_project_link( + link = MessageService.get_project_link( project_id, include_chat_section=True ) - # project_title = ProjectService.get_project_title(project_id) - messages = [] - for user_id in favorited_users: + messages = [] + for username in usernames: + current_app.logger.debug(f"Searching for {username}") try: - user = UserService.get_user_dto_by_id(user_id) + user = UserService.get_user_by_username(username) except NotFound: + current_app.logger.error(f"Username {username} not found") continue # If we can't find the user, keep going no need to fail message = Message() - message.message_type = MessageType.PROJECT_CHAT_NOTIFICATION.value + message.message_type = MessageType.MENTION_NOTIFICATION.value message.project_id = project_id + message.from_user_id = chat_from message.to_user_id = user.id - message.subject = f"{chat_from} left a comment in {project_link}" + message.subject = f"You were mentioned in {link} chat" message.message = chat messages.append(dict(message=message, user=user)) - # it's important to keep that line inside the if to avoid duplicated emails MessageService._push_messages(messages) + query = """ select user_id from project_favorites where project_id = :project_id""" + result = db.engine.execute(text(query), project_id=project_id) + favorited_users = [r[0] for r in result] + + if len(favorited_users) != 0: + project_link = MessageService.get_project_link( + project_id, include_chat_section=True + ) + # project_title = ProjectService.get_project_title(project_id) + messages = [] + for user_id in favorited_users: + + try: + user = UserService.get_user_dto_by_id(user_id) + except NotFound: + continue # If we can't find the user, keep going no need to fail + + message = Message() + message.message_type = MessageType.PROJECT_CHAT_NOTIFICATION.value + message.project_id = project_id + message.to_user_id = user.id + message.subject = f"{chat_from} left a comment in {project_link}" + message.message = chat + messages.append(dict(message=message, user=user)) + + # it's important to keep that line inside the if to avoid duplicated emails + MessageService._push_messages(messages) + @staticmethod def send_favorite_project_activities(user_id: int): current_app.logger.debug("Sending Favorite Project Activities") diff --git a/backend/services/messaging/smtp_service.py b/backend/services/messaging/smtp_service.py index 88162c2612..cf664f56a2 100644 --- a/backend/services/messaging/smtp_service.py +++ b/backend/services/messaging/smtp_service.py @@ -6,7 +6,6 @@ from flask import current_app from backend.services.messaging.template_service import ( get_template, - template_var_replacing, format_username_link, ) @@ -15,26 +14,18 @@ class SMTPService: @staticmethod def send_verification_email(to_address: str, username: str): """ Sends a verification email with a unique token so we can verify user owns this email address """ - org_code = current_app.config["ORG_CODE"] # TODO these could be localised if needed, in the future - html_template = get_template("email_verification_en.html") - text_template = get_template("email_verification_en.txt") - verification_url = SMTPService._generate_email_verification_url( to_address, username ) - replace_list = [ - ["[USERNAME]", username], - ["[VERIFICATION_LINK]", verification_url], - ["[ORG_CODE]", org_code], - ["[ORG_NAME]", current_app.config["ORG_NAME"]], - ] - html_template = template_var_replacing(html_template, replace_list) - text_template = template_var_replacing(text_template, replace_list) - - subject = "{} Tasking Manager - Email Verification".format(org_code) - SMTPService._send_message(to_address, subject, html_template, text_template) - + values = { + "USERNAME": username, + "VERIFICATION_LINK": verification_url, + } + html_template = get_template("email_verification_en.html", values) + + subject = "Confirm your email address" + SMTPService._send_message(to_address, subject, html_template) return True @staticmethod @@ -63,10 +54,10 @@ def send_email_alert( project_id: int, subject: str, content: str, + message_type: int, ): """Send an email to user to alert that they have a new message""" current_app.logger.debug(f"Test if email required {to_address}") - org_code = current_app.config["ORG_CODE"] from_user_link = f"{current_app.config['APP_BASE_URL']}/users/{from_username}" project_link = f"{current_app.config['APP_BASE_URL']}/projects/{project_id}" settings_url = "{}/settings#notifications".format( @@ -79,33 +70,25 @@ def send_email_alert( if message_id is not None: message_path = f"/message/{message_id}" - # TODO these could be localised if needed, in the future - html_template = get_template("message_alert_en.html") - text_template = get_template("message_alert_en.txt") inbox_url = f"{current_app.config['APP_BASE_URL']}/inbox{message_path}" - replace_list = [ - ["[FROM_USER_LINK]", from_user_link], - ["[FROM_USERNAME]", from_username], - ["[PROJECT_LINK]", project_link], - ["[PROJECT_ID]", str(project_id)], - ["[ORG_CODE]", org_code], - ["[PROFILE_LINK]", inbox_url], - ["[SETTINGS_LINK]", settings_url], - ] - html_replace_list = replace_list + [ - ["[CONTENT]", format_username_link(content)] - ] - html_template = template_var_replacing(html_template, html_replace_list) - replace_list += [["[CONTENT]", content]] - text_template = template_var_replacing(text_template, replace_list) - - SMTPService._send_message(to_address, subject, html_template, text_template) + values = { + "FROM_USER_LINK": from_user_link, + "FROM_USERNAME": from_username, + "PROJECT_LINK": project_link, + "PROJECT_ID": str(project_id) if project_id is not None else None, + "PROFILE_LINK": inbox_url, + "SETTINGS_LINK": settings_url, + "CONTENT": format_username_link(content), + "MESSAGE_TYPE": message_type, + } + html_template = get_template("message_alert_en.html", values) + SMTPService._send_message(to_address, subject, html_template) return True @staticmethod def _send_message( - to_address: str, subject: str, html_message: str, text_message: str + to_address: str, subject: str, html_message: str, text_message: str = None ): """ Helper sends SMTP message """ from_address = current_app.config["EMAIL_FROM_ADDRESS"] @@ -120,10 +103,11 @@ def _send_message( msg["To"] = to_address # Record the MIME types of both parts - text/plain and text/html. - part1 = MIMEText(text_message, "plain") part2 = MIMEText(html_message, "html") - msg.attach(part1) msg.attach(part2) + if text_message: + part1 = MIMEText(text_message, "plain") + msg.attach(part1) current_app.logger.debug(f"Sending email via SMTP {to_address}") if current_app.config["LOG_LEVEL"] == "DEBUG": diff --git a/backend/services/messaging/template_service.py b/backend/services/messaging/template_service.py index 635a370287..f02c00ed0d 100644 --- a/backend/services/messaging/template_service.py +++ b/backend/services/messaging/template_service.py @@ -1,10 +1,10 @@ import os import re -from flask import current_app +from flask import current_app, render_template -def get_template(template_name: str) -> str: +def get_txt_template(template_name: str): """ Helper function to read the template from disk and return as a string to be manipulated :param template_name: The template we want to load @@ -21,6 +21,24 @@ def get_template(template_name: str) -> str: raise ValueError("Unable open file {0}".format(template_location)) +def get_template(template_name: str, values: dict) -> str: + """ + Helper function to read a HTML template from disk and return it using flask's + render_template function + :param template_name: The template we want to load + :return: Template as a string + """ + try: + values["ORG_CODE"] = current_app.config["ORG_CODE"] + values["ORG_NAME"] = current_app.config["ORG_NAME"] + values["ORG_LOGO"] = current_app.config["ORG_LOGO"] + values["APP_BASE_URL"] = current_app.config["APP_BASE_URL"] + return render_template(template_name, values=values) + except (FileNotFoundError, TypeError): + current_app.logger.error("Unable open file {0}".format(template_name)) + raise ValueError("Unable open file {0}".format(template_name)) + + def template_var_replacing(content: str, replace_list: list) -> str: """Receives a content string and executes a replace operation to each item on the list. """ for term in replace_list: @@ -41,6 +59,6 @@ def format_username_link(content): username = name[2:-1] content = content.replace( name, - f'@{username}', + f'@{username}', ) return content diff --git a/backend/services/messaging/templates/base.html b/backend/services/messaging/templates/base.html new file mode 100644 index 0000000000..d3a428658b --- /dev/null +++ b/backend/services/messaging/templates/base.html @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + +
  +
+

+ {% if values['ORG_LOGO'] %} + + {% endif %} + {{values['ORG_CODE']}} Tasking Manager +

+ + + + +
+ + + + +
+

+ {% block content %}{% endblock %} +

+
+
+ +
+
 
+ + diff --git a/backend/services/messaging/templates/email_verification_en.html b/backend/services/messaging/templates/email_verification_en.html index 5f9a6e54a3..7ef961cb1e 100644 --- a/backend/services/messaging/templates/email_verification_en.html +++ b/backend/services/messaging/templates/email_verification_en.html @@ -1,13 +1,9 @@ - - - -

- Hi [USERNAME]

- Thank you for supplying your email address to the [ORG_CODE] Tasking Manager, please click the link below to verify you own this address

- Click here to verify your email address

+{% extends "base.html" %} +{% block content %} + Hi {{values['USERNAME']}},

+ Thank you for supplying your email address to the {{values['ORG_CODE']}} Tasking Manager, please click the link below to verify you own this address

+ Click here to verify your email address

Please ignore this email if you have received it by mistake.

Many thanks,
- [ORG_NAME]
-

- - + {{values['ORG_NAME']}}
+{% endblock %} diff --git a/backend/services/messaging/templates/email_verification_en.txt b/backend/services/messaging/templates/email_verification_en.txt deleted file mode 100644 index bb89336d41..0000000000 --- a/backend/services/messaging/templates/email_verification_en.txt +++ /dev/null @@ -1,10 +0,0 @@ -Hi [USERNAME] - -Thank you for supply your email address to the [ORG_CODE] Tasking Manager, please click the link below to verify you own this address - -[VERIFICATION_LINK] - -Please ignore this email if you have received it by mistake. - -Many thanks, -[ORG_NAME] diff --git a/backend/services/messaging/templates/message_alert_en.html b/backend/services/messaging/templates/message_alert_en.html index 9eb3bb1218..8c465bdb4f 100644 --- a/backend/services/messaging/templates/message_alert_en.html +++ b/backend/services/messaging/templates/message_alert_en.html @@ -1,12 +1,15 @@ - - - -

@[FROM_USERNAME] mentioned you in a comment on Project [PROJECT_ID]

-

- [CONTENT] -

- Access your inbox to read all your messages on the [ORG_CODE] Tasking Manager.

- You can opt-out of these emails by visiting your user settings page and adjusting your notification preferences.
-

- - +{% extends "base.html" %} +{% block content %} + {% if values['MESSAGE_TYPE'] in [3, 8, 9] %} +

@{{values['FROM_USERNAME']}} mentioned you in a comment on Project {{values['PROJECT_ID']}}:

+ {% endif %} + {% if values['MESSAGE_TYPE'] == 4 %} +

Task validated on Project {{values['PROJECT_ID']}}:

+ {% endif %} + {% if values['MESSAGE_TYPE'] == 5 %} +

Task on Project {{values['PROJECT_ID']}} needs more mapping:

+ {% endif %} +

+ {{ values['CONTENT']|safe }} +

+{% endblock %} diff --git a/backend/services/messaging/templates/message_alert_en.txt b/backend/services/messaging/templates/message_alert_en.txt deleted file mode 100644 index 0e839b0da5..0000000000 --- a/backend/services/messaging/templates/message_alert_en.txt +++ /dev/null @@ -1,6 +0,0 @@ -[FROM_USERNAME] mentioned you in a comment on Project [PROJECT_ID] - -[CONTENT] - -Access your inbox to read all your messages on the [ORG_CODE] Tasking Manager: [PROFILE_LINK] -You can opt-out of these emails by visiting your user settings page and adjusting your notification preferences: [SETTINGS_LINK] diff --git a/backend/services/users/user_service.py b/backend/services/users/user_service.py index 78a0f4773c..02d3d1358e 100644 --- a/backend/services/users/user_service.py +++ b/backend/services/users/user_service.py @@ -30,7 +30,7 @@ from backend.services.users.osm_service import OSMService, OSMServiceError from backend.services.messaging.smtp_service import SMTPService from backend.services.messaging.template_service import ( - get_template, + get_txt_template, template_var_replacing, ) @@ -742,7 +742,7 @@ def check_and_update_mapper_level(user_id: int): @staticmethod def notify_level_upgrade(user_id: int, username: str, level: str): - text_template = get_template("level_upgrade_message_en.txt") + text_template = get_txt_template("level_upgrade_message_en.txt") replace_list = [ ["[USERNAME]", username], ["[LEVEL]", level], diff --git a/docker-compose.yml b/docker-compose.yml index e261bc9510..e86cdc980d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,8 +17,7 @@ services: <<: *backend container_name: backend labels: - - traefik.http.routers.backend.rule=Host(`localhost`) - - traefik.http.routers.backend.rule=PathPrefix(`/api/`) + - traefik.http.routers.backend.rule=Host(`localhost`) && PathPrefix(`/api/`) - traefik.http.services.backend.loadbalancer.server.port=5000 migration: @@ -43,7 +42,7 @@ services: - tm-web traefik: - image: traefik + image: traefik:v2.3 ports: - "80:80" volumes: diff --git a/docs/assets/osm-oauth-settings.jpg b/docs/assets/osm-oauth-settings.jpg index 940bc0d0ce..b62bfe883f 100644 Binary files a/docs/assets/osm-oauth-settings.jpg and b/docs/assets/osm-oauth-settings.jpg differ diff --git a/docs/setup-development.md b/docs/setup-development.md index 3666a5a95a..821955e995 100644 --- a/docs/setup-development.md +++ b/docs/setup-development.md @@ -124,9 +124,9 @@ python3 manage.py db upgrade You can use [this script](../scripts/database/migration-from-tm2-postgres.sql) to migrate your data from the prior tasking manager version (v2) to the current one. Please see [this documentation page](../scripts/database/README.md) for important information about this process. -#### Set permissions to create a task +#### Set permissions to create projects -To be able to create a task and have full permissions as an admin inside TM, login to the TM with your OSM account to populate your user information in the database, then execute the following command on your terminal (with the OS user that is the owner of the database): +To be able to create projects and have full permissions as an admin user inside TM, login to the TM with your OSM account to populate your user information in the database, then execute the following command on your terminal (with the OS user that is the owner of the database): `psql -d -c "UPDATE users set role = 1 where username = ''"` diff --git a/docs/setup-docker.md b/docs/setup-docker.md index 9ee6ea9a47..c9ee684578 100644 --- a/docs/setup-docker.md +++ b/docs/setup-docker.md @@ -1,15 +1,43 @@ ## Installation with Docker -**Get the code** +### Get the code `git clone https://github.com/hotosm/tasking-manager.git`. -**Configure** +### Configure * Copy the example configuration file to start your own configuration: `cp example.env tasking-manager.env`. * Adjust the `tasking-manager.env` configuration file to fit your configuration. -**Connect with OpenStreetMap** +#### Public host + +By default, things are made to work from `localhost`. If you want to setup a public host, try setting the following: + +* `tasking-manager.env`: `TM_APP_BASE_URL=https://tasks.smartcitiestransport.com/` +* `docker-compose.override.yml`: + * `TM_APP_API_URL=https://tasks.smartcitiestransport.com/api` + * ```traefik.http.routers.frontend.rule=Host(`localhost`, `tasks.smartcitiestransport.com`)``` +* `docker-compose.yml`: + * ```traefik.http.routers.backend.rule=Host(`localhost`, `tasks.smartcitiestransport.com`) && PathPrefix(`/api/`)``` + * ```traefik.http.routers.frontend.rule=Host(`localhost`, `tasks.smartcitiestransport.com`)``` + +#### Persistent data + +If you may be removing and starting the postgresql container, you may want to setup a persistent volume: + +In `docker-compose.override.yml`: + +```yaml +services: + postgresql: + volumes: + - "postgresql-data:/var/lib/postgresql/data" + +volumes: + postgresql-data: +``` + +#### Connect with OpenStreetMap The Tasking Manager uses OpenStreetMap accounts for users to login. @@ -21,7 +49,7 @@ Afterwards copy the consumer key and secret from OpenStreetMap into your configu **NB**: if you've configured a custom OSM server, make sure that you create the user and oAuth client there. -**Run the Tasking Manager** +### Run the Tasking Manager The **easiest way** to run the Tasking Manager requires [Docker](https://docs.docker.com/get-started/) and [Docker Compose](https://docs.docker.com/compose/) to be installed on your system. Afterwards you'll just need: @@ -60,4 +88,4 @@ make down 2. When a new frontend module or backend requirement is included within their respective files, remember to rebuild dockerfile image, executing the following command ``` make build -``` \ No newline at end of file +``` diff --git a/example.env b/example.env index f2d638ecc5..4dfc61876f 100644 --- a/example.env +++ b/example.env @@ -13,6 +13,7 @@ TM_APP_API_VERSION=v2 # Information about the hosting organization TM_ORG_NAME="My organisation" TM_ORG_CODE=MYO +TM_ORG_LOGO=https://cdn.img.url/logo.png # Don't use http or https on the following var TM_ORG_URL=example.com TM_ORG_PRIVACY_POLICY_URL=example.com/privacy diff --git a/frontend/package.json b/frontend/package.json index b1f494b8df..be2ac6e2aa 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,8 +4,8 @@ "license": "BSD-2-Clause", "private": false, "dependencies": { - "@formatjs/intl-pluralrules": "^3.4.8", - "@formatjs/intl-relativetimeformat": "^7.2.8", + "@formatjs/intl-pluralrules": "^3.4.10", + "@formatjs/intl-relativetimeformat": "^7.2.10", "@formatjs/intl-utils": "^3.8.4", "@formatjs/macro": "^0.2.8", "@hotosm/id": "^2.18.5", @@ -16,8 +16,8 @@ "@mapbox/mapbox-gl-language": "^0.10.1", "@mapbox/togeojson": "^0.16.0", "@reach/router": "^1.3.4", - "@sentry/react": "^5.25.0", - "@sentry/tracing": "^5.25.0", + "@sentry/react": "^5.26.0", + "@sentry/tracing": "^5.26.0", "@turf/area": "^6.0.1", "@turf/bbox": "^6.0.1", "@turf/bbox-polygon": "^6.0.1", @@ -27,7 +27,7 @@ "@turf/transform-scale": "^5.1.5", "@webscopeio/react-textarea-autocomplete": "^4.7.2", "axios": "^0.20.0", - "chart.js": "^2.9.3", + "chart.js": "^2.9.4", "dompurify": "^2.1.1", "downshift-hooks": "^0.8.1", "final-form": "^4.20.1", @@ -40,12 +40,12 @@ "marked": "^1.2.0", "node-sass": "^4.14.1", "osmtogeojson": "^3.0.0-beta.3", - "query-string": "^6.13.5", - "react": "^16.13.1", + "query-string": "^6.13.6", + "react": "^16.14.0", "react-calendar-heatmap": "^1.8.1", "react-chartjs-2": "^2.10.0", "react-click-outside": "^3.0.1", - "react-datepicker": "^3.2.2", + "react-datepicker": "^3.3.0", "react-dom": "^16.13.1", "react-dropzone": "^11.2.0", "react-final-form": "^6.5.1", @@ -94,11 +94,11 @@ }, "devDependencies": { "@testing-library/jest-dom": "^5.11.4", - "@testing-library/react": "^11.0.4", + "@testing-library/react": "^11.1.0", "@testing-library/react-hooks": "^3.4.2", "combine-react-intl-messages": "^4.0.0", "jest-canvas-mock": "^2.3.0", - "msw": "^0.21.2", + "msw": "^0.21.3", "prettier": "^2.1.2", "react-test-renderer": "^16.13.1" }, diff --git a/frontend/src/assets/img/hot-logo-square.svg b/frontend/src/assets/img/hot-logo-square.svg index c9d9d84f85..3ffb66dbd5 100644 --- a/frontend/src/assets/img/hot-logo-square.svg +++ b/frontend/src/assets/img/hot-logo-square.svg @@ -17,7 +17,6 @@ height="194" viewBox="0 0 194 194" sodipodi:docname="hot-logo-square.svg" - inkscape:export-filename="/Users/thadkerosky/gitrepos/tm-redocker/frontend/src/assets/img/hot-system-avatar-square-opaque.png" inkscape:export-xdpi="219.78296" inkscape:export-ydpi="219.78296"> \ No newline at end of file + inkscape:connector-curvature="0" /> diff --git a/frontend/src/assets/img/hot-logo.png b/frontend/src/assets/img/hot-logo.png deleted file mode 100644 index 09259bba13..0000000000 Binary files a/frontend/src/assets/img/hot-logo.png and /dev/null differ diff --git a/frontend/src/assets/img/hot-logo.svg b/frontend/src/assets/img/hot-logo.svg deleted file mode 100644 index e017a731fc..0000000000 --- a/frontend/src/assets/img/hot-logo.svg +++ /dev/null @@ -1,257 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/frontend/src/assets/img/hot-tm-logo.svg b/frontend/src/assets/img/hot-tm-logo.svg deleted file mode 100644 index 45338ca84b..0000000000 --- a/frontend/src/assets/img/hot-tm-logo.svg +++ /dev/null @@ -1,116 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/frontend/src/assets/img/hot-system-avatar-square-opaque.png b/frontend/src/assets/img/logo-square.png similarity index 100% rename from frontend/src/assets/img/hot-system-avatar-square-opaque.png rename to frontend/src/assets/img/logo-square.png diff --git a/frontend/src/components/notifications/notificationCard.js b/frontend/src/components/notifications/notificationCard.js index 66a1fa852e..467d0e79c4 100644 --- a/frontend/src/components/notifications/notificationCard.js +++ b/frontend/src/components/notifications/notificationCard.js @@ -6,7 +6,7 @@ import DOMPurify from 'dompurify'; import { FormattedMessage } from 'react-intl'; import messages from './messages'; -import systemAvatar from '../../assets/img/hot-system-avatar-square-opaque.png'; +import systemAvatar from '../../assets/img/logo-square.png'; import { EyeIcon } from '../svgIcons'; import { CheckBox } from '../formInputs'; import { UserAvatar } from '../user/avatar'; diff --git a/frontend/src/components/projectStats/edits.js b/frontend/src/components/projectStats/edits.js index 802a713fc0..0d0e63f12f 100644 --- a/frontend/src/components/projectStats/edits.js +++ b/frontend/src/components/projectStats/edits.js @@ -55,7 +55,7 @@ export const EditsStats = (props) => { const { changesets, buildings, roads, edits } = props.data; return ( -
+

diff --git a/frontend/src/components/taskSelection/messages.js b/frontend/src/components/taskSelection/messages.js index 41abda3d9a..9692f56174 100644 --- a/frontend/src/components/taskSelection/messages.js +++ b/frontend/src/components/taskSelection/messages.js @@ -413,7 +413,7 @@ export default defineMessages({ }, taskActivity: { id: 'project.tasks.history.title', - defaultMessage: 'Task activity', + defaultMessage: 'Task {n}', }, taskSplitted: { id: 'project.tasks.history.splitted', diff --git a/frontend/src/components/taskSelection/taskActivity.js b/frontend/src/components/taskSelection/taskActivity.js index 120c5dce09..6dafb9adce 100644 --- a/frontend/src/components/taskSelection/taskActivity.js +++ b/frontend/src/components/taskSelection/taskActivity.js @@ -43,21 +43,19 @@ const PostComment = ({ projectId, taskId, setCommentPayload }) => { }; return ( - <> -
-
- -
-
- -
+
+
+ +
+
+
-
+
- +
); }; @@ -258,15 +256,14 @@ export const TaskActivity = ({

- +

- #{taskId} {project.projectInfo && project.projectInfo.name ? ( - `: ${project.projectInfo.name}` - ) : ( - , + #{project.projectId}: {project.projectInfo.name} + ) : ( + )}
@@ -294,7 +291,7 @@ export const TaskActivity = ({
-
+
{ - const { intl } = props; +const HeatmapLegend = () => { + const indexes = [30, 50, 70, 100]; + const legendFontStyle = 'ph2 f7 blue-grey ttc'; + + return ( +
+ + + +
+ {indexes.map((i) => ( +
+ ))} + + + +
+ ); +}; +export const ContributionTimeline = ({ userStats }) => { + const intl = useIntl(); const today = new Date(); const shiftDate = (date, numDays) => { @@ -15,32 +35,10 @@ const ContributionTimeline = props => { return newDate; }; - const countValues = props.userStats.contributionsByDay.map(r => { - return r.count; - }); + const countValues = userStats.contributionsByDay.map((r) => r.count); const maxValue = Math.max(...countValues); - const HeatmapLegend = () => { - const indexes = [30, 50, 70, 100]; - - const legendFontStyle = 'ph2 f7 blue-grey ttc'; - return ( -
- - - -
- {indexes.map(i => { - return
; - })} - - - -
- ); - }; - - const getHeatmapClass = v => { + const getHeatmapClass = (v) => { const rate = v.count / maxValue; if (0.0 <= rate && rate < 0.25) { @@ -66,15 +64,13 @@ const ContributionTimeline = props => { { - if (!value) { - return 'fill-tan'; - } + values={userStats.contributionsByDay} + classForValue={(value) => { + if (!value) return 'fill-tan'; return getHeatmapClass(value); }} showWeekdayLabels={true} - tooltipDataAttrs={value => { + tooltipDataAttrs={(value) => { let val = intl.formatMessage(messages.heatmapNoContribution); if (value.count !== null) { val = `${value.count} ${ @@ -95,5 +91,3 @@ const ContributionTimeline = props => {
); }; - -export default injectIntl(ContributionTimeline); diff --git a/frontend/src/components/userDetail/countriesMapped.js b/frontend/src/components/userDetail/countriesMapped.js index 62b3a1822e..c0dff57b86 100644 --- a/frontend/src/components/userDetail/countriesMapped.js +++ b/frontend/src/components/userDetail/countriesMapped.js @@ -19,12 +19,6 @@ try { const UserCountriesMap = ({ projects }) => { const locale = useSelector((state) => state.preferences['locale']); - const geojson = { - type: 'FeatureCollection', - features: projects.mappedProjects.map((f) => { - return { type: 'Feature', geometry: f.centroid, properties: { projectId: f.projectId } }; - }), - }; const [map, setMap] = useState(null); const mapRef = React.createRef(); @@ -49,11 +43,17 @@ const UserCountriesMap = ({ projects }) => { }, []); useLayoutEffect(() => { - if (map) { + if (map && projects.mappedProjects) { + const geojson = { + type: 'FeatureCollection', + features: projects.mappedProjects.map((f) => { + return { type: 'Feature', geometry: f.centroid, properties: { projectId: f.projectId } }; + }), + }; map.resize(); //https://docs.mapbox.com/help/troubleshooting/blank-tiles/ map.on('load', () => mapboxLayerDefn(map, geojson, (id) => navigate(`/projects/${id}/`))); } - }, [map, geojson]); + }, [map, projects.mappedProjects]); return
; }; diff --git a/frontend/src/components/userDetail/elementsMapped.js b/frontend/src/components/userDetail/elementsMapped.js index 7fd34fa15f..2b917f08b8 100644 --- a/frontend/src/components/userDetail/elementsMapped.js +++ b/frontend/src/components/userDetail/elementsMapped.js @@ -79,33 +79,38 @@ export const TaskStats = ({ userStats, username }) => {
-
+
-

+

}} />

-

{userStats.tasksMapped}

-

- -

+
-
- +
+

+ {userStats.tasksMapped} +

+

+ +

+
+
+

{userStats.tasksValidatedByOthers} - -

+

+

-
- +
+

{userStats.tasksInvalidatedByOthers} - -

+

+

@@ -118,35 +123,38 @@ export const TaskStats = ({ userStats, username }) => {
-
+
-

+

}} />

-

- {userStats.tasksValidated + userStats.tasksInvalidated || 0} -

-

- -

+
-
- +
+

+ {userStats.tasksValidated + userStats.tasksInvalidated || 0} +

+

+ +

+
+
+

{userStats.tasksValidated} - -

+

+

-
- +
+

{userStats.tasksInvalidated} - -

+

+

diff --git a/frontend/src/components/userDetail/tests/elementsMapped.test.js b/frontend/src/components/userDetail/tests/elementsMapped.test.js new file mode 100644 index 0000000000..358b9dc6c1 --- /dev/null +++ b/frontend/src/components/userDetail/tests/elementsMapped.test.js @@ -0,0 +1,59 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; + +import { ReduxIntlProviders } from '../../../utils/testWithIntl'; +import { TaskStats, ElementsMapped } from '../elementsMapped'; + +describe('ElementsMapped & TaskStats components', () => { + it('ElementsMapped component is rendered', () => { + const userStats = { + timeSpentMapping: 3000, + }; + const osmStats = { + total_building_count_add: 10, + total_road_km_add: 229.113, + total_poi_count_add: 15, + total_waterway_count_add: 20, + }; + const { getByText } = render( + + + , + ); + + expect(getByText('Time spent mapping')).toBeInTheDocument(); + expect(getByText('Buildings mapped')).toBeInTheDocument(); + expect(getByText('Km road mapped')).toBeInTheDocument(); + expect(getByText('Points of interests mapped')).toBeInTheDocument(); + expect(getByText('Waterways mapped')).toBeInTheDocument(); + //total road mapped + expect(getByText('229')).toBeInTheDocument(); + }); + + it ('TaskStats component is rendered', ()=> { + const userStats = { + tasksMapped: 9, + tasksValidatedByOthers: 8, + tasksInvalidatedByOthers: 0, + tasksValidated: 3, + tasksInvalidated: 2, + }; + const { getByText, getAllByText } = render( + + + , + ); + + expect(getByText('You mapped')).toBeInTheDocument(); + expect(getByText('You validated')).toBeInTheDocument(); + expect(getByText('Validated')).toBeInTheDocument(); + expect(getByText('Finished')).toBeInTheDocument(); + expect(getAllByText('tasks').length).toBe(2); + expect(getAllByText('Needed more mapping').length).toBe(2); + //mapped tasks + expect(getByText('9')).toBeInTheDocument(); + //validated tasks + expect(getByText('5')).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/locales/ar.json b/frontend/src/locales/ar.json index bccf9c9247..268c893197 100644 --- a/frontend/src/locales/ar.json +++ b/frontend/src/locales/ar.json @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "", "pages.learn.validate.video.title": "", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "", "pages.learn.validate.intro": "", "pages.learn.validate.description": "", diff --git a/frontend/src/locales/cs.json b/frontend/src/locales/cs.json index b3ebbd215c..11d80b87d0 100644 --- a/frontend/src/locales/cs.json +++ b/frontend/src/locales/cs.json @@ -198,7 +198,7 @@ "management.projects.clone.message": "Nový projekt bude kopií projektu #{id} ({name}).", "management.projects.create.clone": "Klonovat", "management.projects.create.area_size": "Velikost oblasti: {area} km{sq}", - "management.projects.create.area_error": "", + "management.projects.create.area_error": "Plocha projektu je vyšší než {n} čtverečních kilometrů.", "management.projects.create.tasks": "Počet úloh: {n}", "management.projects.create.steps.1": "Krok 1: definovat oblast", "management.projects.create.steps.2": "Krok 2: nastavit velikosti úloh", @@ -391,8 +391,8 @@ "projects.formInputs.priority_areas.options.rectangle": "Nakreslit obdélník", "projects.formInputs.priority_areas.action.clear": "Smazat vše", "projects.formInputs.name": "Název projektu", - "projects.formInputs.dueDate": "", - "projects.formInputs.dueDate.description": "", + "projects.formInputs.dueDate": "Datum ukončení", + "projects.formInputs.dueDate.description": "Definujte ideální datum pro dokončení projektu. Formát data je den / měsíc / rok.", "projects.formInputs.description": "Popis", "projects.formInputs.shortDescription": "Krátký popis", "projects.formInputs.instructions": "Podrobné instrukce", @@ -496,9 +496,9 @@ "project.stats.contributors.experience.6": "3 až 6 měsíců", "project.stats.contributors.experience.12": "6 až 12 měsíců", "project.stats.contributors.experience.year": "Více než 1 rok", - "project.stats.totalEdits": "", + "project.stats.totalEdits": "Celkový počet úprav mapy", "project.stats.changesets": "Sady změn", - "project.stats.edits": "", + "project.stats.edits": "Úpravy", "project.tasks.unsaved_map_changes.title": "Máte nějaké neuložené změny na mapě", "project.tasks.unsaved_map_changes.split": "Chcete-li úlohu rozdělit, uložte ji nebo zrušte", "project.tasks.unsaved_map_changes.unlock": "Chcete-li vybrat jinou úlohu, uložte ji nebo zrušte", @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Mapa úloh", "project.tasks.action.submit_task": "Odeslat úlohu", "project.tasks.action.submit_tasks": "Odeslat úlohy", - "project.tasks.history.title": "Aktivita v úloze", + "project.tasks.history.title": "", "project.tasks.history.splitted": "Úloha nedostupná", "project.tasks.history.splitted.description": "Úloha {id} byla rozdělena a její historie již není k dispozici", "project.tasks.activity.data.links": "Data úlohy", @@ -714,8 +714,8 @@ "user.gender.male": "Muž", "user.gender.preferNotToSay": "Nepřeji si uvést", "user.gender.selfDescribe": "Upřednostňuji vlastní popis:", - "user.slack": "", - "user.personalInfo.error": "", + "user.slack": "Uživatelské jméno na {org} Slack", + "user.personalInfo.error": "Zadejte pouze své uživatelské jméno, nikoli adresu URL.", "user.form.save": "Uložit", "user.settings.title": "Nastavení", "user.notifications.title": "Oznámení", @@ -816,8 +816,8 @@ "users.detail.heatmapLegendLess": "méně", "users.detail.delay_popup": "Tyto statistiky vyžadují náročné výpočty a změny se projevují se zpožděním asi jedné hodiny.", "users.detail.teams": "Týmy", - "error.page.title": "", - "error.page.description": "", + "error.page.title": "Vyskytla se chyba", + "error.page.description": "Něco nefungovalo dobře ...", "error.page.link": "Vrátit", "notFound.page.title": "Strana nenalezena", "notFound.project.title": "Projekt {id} nenalezen", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "Naučte se, jak mapovat budovy na OpenStreetMap.", "pages.learn.validate.video.title": "Naučit se validovat", "pages.learn.validate.video.description": "Naučte se, jak začít validovat projekty v Tasking Manageru.", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "Naučte se validovat", "pages.learn.validate.intro": "Validace je důležitou součástí procesu. Vyžaduje důvěru ve vaše mapovací schopnosti a ochotu pomoci koučovat a radit novějším mapovačům.", "pages.learn.validate.description": "Získání druhého, třetího nebo čtvrtého páru očí na mapované funkce je důležitým krokem k zajištění kvality dat přidávaných do OpenStreetMap a jejich použití po celém světě.", diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 68a28e2897..e45878c993 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Übersichtskarte", "project.tasks.action.submit_task": "Aufgabe abspeichern", "project.tasks.action.submit_tasks": "Aufgaben speichern", - "project.tasks.history.title": "Aufgabenaktivität", + "project.tasks.history.title": "", "project.tasks.history.splitted": "Aufgabe nicht verfügbar", "project.tasks.history.splitted.description": "", "project.tasks.activity.data.links": "Daten zur Aufgabe", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "Erfahren Sie, wie Sie Gebäude auf OpenStreetMap kartieren.", "pages.learn.validate.video.title": "Wie validiert man", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "Lernen Sie validieren", "pages.learn.validate.intro": "Die Validierung ist ein wichtiger Teil des Prozesses. Es erfordert Vertrauen in Ihre Mapping-Fähigkeiten sowie die Bereitschaft, neuere Mapper zu coachen und zu beraten.", "pages.learn.validate.description": "Um eine gute Qualität der Daten zu gewährleisten, die OpenStreetMap hinzugefügt und dann auf der ganzen Welt verwendet werden, ist es ist wichtiger Schritt die kartierten Objekte mit einem zweiten, dritten oder vierten Augenpaar zu verifizieren.", diff --git a/frontend/src/locales/el.json b/frontend/src/locales/el.json index 0ee4f760ae..104fc8385a 100644 --- a/frontend/src/locales/el.json +++ b/frontend/src/locales/el.json @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Χάρτης εργασιών", "project.tasks.action.submit_task": "Υποβάλετε εργασία", "project.tasks.action.submit_tasks": "Υποβάλετε εργασίες", - "project.tasks.history.title": "Δραστηριότητα εργασίας", + "project.tasks.history.title": "", "project.tasks.history.splitted": "Μη διαθέσιμη εργασία", "project.tasks.history.splitted.description": "Η εργασία {id} αποσχίστηκε και το ιστορικό της δεν είναι πλέον διαθέσιμο", "project.tasks.activity.data.links": "Δεδομένα εργασίας", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "Μάθετε πώς θα χαρτογραφήσετε κτίρια στο OpenStreetMap.", "pages.learn.validate.video.title": "Πώς θα επικυρώσετε", "pages.learn.validate.video.description": "Μάθετε πώς μπορείτε να ξεκινήσετε την επικύρωση έργων στον Διαχειριστή Εργασιών.", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "Μάθετε να επικυρώνετε", "pages.learn.validate.intro": "Η επικύρωση είναι ένα σημαντικό μέρος της διαδικασίας. Απαιτεί να έχετε εμπιστοσύνη στις ικανότητες χαρτογράφησης σας, καθώς και την προθυμία να βοηθήσετε στην συμβουλευτική προς τους νεότερους χαρτογράφους.", "pages.learn.validate.description": "Η συμβολή μιας δεύτερης, τρίτης ή τέταρτης άποψης στα χαρτογραφημένα χαρακτηριστικά, είναι ένα σημαντικό βήμα για τη διασφάλιση της ποιότητας των δεδομένων που προστίθενται στο OpenStreetMap και στη συνέχεια χρησιμοποιούνται σε όλο τον κόσμο.", diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index e34306fb03..9292621453 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Tasks map", "project.tasks.action.submit_task": "Submit task", "project.tasks.action.submit_tasks": "Submit tasks", - "project.tasks.history.title": "Task activity", + "project.tasks.history.title": "Task {n}", "project.tasks.history.splitted": "Task unavailable", "project.tasks.history.splitted.description": "The task {id} was split and its history is not available anymore", "project.tasks.activity.data.links": "Task data", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "Learn how to map buildings on OpenStreetMap.", "pages.learn.validate.video.title": "How to validate", "pages.learn.validate.video.description": "Learn how to start validating projects on Tasking Manager.", + "pages.learn.validate.training.video.title": "Validation training", + "pages.learn.validate.training.video.description": "Deep dive on validation with this training, which includes advanced concepts and the JOSM editor.", "pages.learn.validate_title": "Learn to validate", "pages.learn.validate.intro": "Validation is an important part of the process. It requires confidence in your mapping abilities as well as the willingness to help coach and advise newer mappers.", "pages.learn.validate.description": "Getting a second, third, or fourth pair of eyes on mapped features is an important step for ensuring the quality of the data being added to OpenStreetMap and then used around the world.", diff --git a/frontend/src/locales/es.json b/frontend/src/locales/es.json index 76ad492489..bce8728491 100644 --- a/frontend/src/locales/es.json +++ b/frontend/src/locales/es.json @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Mapa de tareas", "project.tasks.action.submit_task": "Enviar tarea", "project.tasks.action.submit_tasks": "Enviar tareas", - "project.tasks.history.title": "Actividad de la tarea", + "project.tasks.history.title": "Tarea {n}", "project.tasks.history.splitted": "Tarea no disponible", "project.tasks.history.splitted.description": "La tarea {id} fue dividida y su historial ya no está disponible", "project.tasks.activity.data.links": "Datos de la tarea", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "Aprende a mapear edificios en OpenStreetMap.", "pages.learn.validate.video.title": "Cómo validar", "pages.learn.validate.video.description": "Obtenga información sobre cómo empezar a validar proyectos en el Tasking Manager.", + "pages.learn.validate.training.video.title": "Capacitación en validación", + "pages.learn.validate.training.video.description": "Sumérgete en la validación con esta formación, que incluye conceptos avanzados y el editor JOSM.", "pages.learn.validate_title": "Aprende a validar", "pages.learn.validate.intro": "La validación es una parte importante del proceso. Requiere confianza en sus habilidades de mapeo, así como la voluntad de ayudar a entrenar y asesorar a los mapeadores más nuevos.", "pages.learn.validate.description": "Obtener más de una revisión de los elementos mapeados es un paso importante para garantizar la calidad de los datos que se añadirán a OpenStreetMap y serán usados en todo el mundo.", diff --git a/frontend/src/locales/fa_IR.json b/frontend/src/locales/fa_IR.json index 9b17127d5e..e5200d9d12 100644 --- a/frontend/src/locales/fa_IR.json +++ b/frontend/src/locales/fa_IR.json @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "", "pages.learn.validate.video.title": "", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "", "pages.learn.validate.intro": "", "pages.learn.validate.description": "", diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 39e4bd0e07..e20a71d676 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Carte des tâches", "project.tasks.action.submit_task": "Soumettre la tâche", "project.tasks.action.submit_tasks": "Soumettre les tâches", - "project.tasks.history.title": "Activité de la tâche", + "project.tasks.history.title": "", "project.tasks.history.splitted": "Tâche indisponible", "project.tasks.history.splitted.description": "La tâche {id} a été divisée et son historique n'est plus disponible", "project.tasks.activity.data.links": "données associées à la tâche", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "Apprendre à cartographier des bâtiments sur OpenStreetMap", "pages.learn.validate.video.title": "Comment valider", "pages.learn.validate.video.description": "Découvrez comment commencer à valider des projets sur le Gestionnaire des Tâches.", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "Apprendre à valider", "pages.learn.validate.intro": "La validation est une partie importante du processus. Elle exige une confiance dans vos capacités de cartographie ainsi que la volonté d'aider à encadrer et à conseiller les nouveaux cartographes.", "pages.learn.validate.description": "Obtenir une deuxième, troisième ou quatrième avis sur les éléments cartographiés est une étape importante pour garantir la qualité des données ajoutées à OpenStreetMap et utilisées ensuite dans le monde entier.", diff --git a/frontend/src/locales/he.json b/frontend/src/locales/he.json index 37eed83689..06e359448e 100644 --- a/frontend/src/locales/he.json +++ b/frontend/src/locales/he.json @@ -198,7 +198,7 @@ "management.projects.clone.message": "המיזם החדש יהיה כפיל של המיזם #{id} ({name}).", "management.projects.create.clone": "שכפול", "management.projects.create.area_size": "גודל השטח: {area} ק״מ{sq}", - "management.projects.create.area_error": "", + "management.projects.create.area_error": "שטח המיזם עולה על {n} קילומטרים רבועים.", "management.projects.create.tasks": "מספר משימות: {n}", "management.projects.create.steps.1": "שלב 1: להגדיר שטח", "management.projects.create.steps.2": "שלב 2: להגדיר גדלי משימות", @@ -391,8 +391,8 @@ "projects.formInputs.priority_areas.options.rectangle": "ציור ריבוע", "projects.formInputs.priority_areas.action.clear": "לפנות הכול", "projects.formInputs.name": "מתן שם למיזם", - "projects.formInputs.dueDate": "", - "projects.formInputs.dueDate.description": "", + "projects.formInputs.dueDate": "יעד לסיום", + "projects.formInputs.dueDate.description": "נא להגדיר את היעד הרצוי לסיום המיזם. תבנית התאריך היא: יום/חודש/שנה.", "projects.formInputs.description": "תיאור", "projects.formInputs.shortDescription": "תיאור קצר", "projects.formInputs.instructions": "הנחיות מפורטות", @@ -496,9 +496,9 @@ "project.stats.contributors.experience.6": "3 עד 6 חודשים", "project.stats.contributors.experience.12": "6 עד 12 חודשים", "project.stats.contributors.experience.year": "למעלה משנה", - "project.stats.totalEdits": "", + "project.stats.totalEdits": "סך כל העריכות במפה", "project.stats.changesets": "ערכות שינויים", - "project.stats.edits": "", + "project.stats.edits": "עריכות", "project.tasks.unsaved_map_changes.title": "ערכת שינויים במפה וטרם שמרת אותם", "project.tasks.unsaved_map_changes.split": "יש לשמור או לבטל זאת כדי שיתאפשר לך לפצל את המשימה", "project.tasks.unsaved_map_changes.unlock": "יש לשמור או לבטל זאת כדי שיתאפשר לך לבחור במשימה אחרת", @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "מפת משימות", "project.tasks.action.submit_task": "הגשת משימה", "project.tasks.action.submit_tasks": "הגשת משימות", - "project.tasks.history.title": "פעילות במשימה", + "project.tasks.history.title": "", "project.tasks.history.splitted": "משימה לא זמינה", "project.tasks.history.splitted.description": "המשימה {id} פוצלה וההיסטוריה שלה אינה זמינה עוד", "project.tasks.activity.data.links": "נתוני משימה", @@ -714,8 +714,8 @@ "user.gender.male": "גבר", "user.gender.preferNotToSay": "אעדיף שלא לציין", "user.gender.selfDescribe": "העדפתי היא הגדרה עצמית:", - "user.slack": "", - "user.personalInfo.error": "", + "user.slack": "שם משתמש ב־Slack של {org}", + "user.personalInfo.error": "יש להקליד רק את שם המשתמש שלך, לא את הכתובת.", "user.form.save": "שמירה", "user.settings.title": "הגדרות", "user.notifications.title": "התראות", @@ -816,9 +816,9 @@ "users.detail.heatmapLegendLess": "פחות", "users.detail.delay_popup": "סטטיסטיקה זו דורשת חישובים כבדים והשינויים מופיעים בעיכוב של שעה בערך.", "users.detail.teams": "צוותים", - "error.page.title": "", - "error.page.description": "", - "error.page.link": "", + "error.page.title": "אירעה שגיאה", + "error.page.description": "משהו לא עבד כמו שצריך…", + "error.page.link": "חזרה", "notFound.page.title": "העמוד לא נמצא", "notFound.project.title": "מיזם {id} לא נמצא", "notFound.lead": "נא לוודא שהכתובת נכונה ולדווח על השגיאה הזאת.", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "הסבר על מיפוי בניינים ב־OpenStreetMap.", "pages.learn.validate.video.title": "איך לאמת", "pages.learn.validate.video.description": "הסבר לימודי על כיצד להתחיל לאמת מיזמים ב־Tasking Manager.", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "ללמוד איך לאמת", "pages.learn.validate.intro": "אימות הוא חלק חשוב בתהליך. הוא דורש ביטחון ביכולות המיפוי שלך לצד הנכונות לסייע בחניכה וייעוץ לממפים חדשים.", "pages.learn.validate.description": "לקבל זוג עיניים נוסף, שלישי או רביעי שיסתכלו על מה שמופה זה שלב חשוב באבטחת איכות הנתונים שנוספים ל־OpenStreetMap ולאחר מכן משמשים אחרים בכל רחבי העולם.", diff --git a/frontend/src/locales/hu.json b/frontend/src/locales/hu.json index 5ee1dbd9d9..ea3ea14691 100644 --- a/frontend/src/locales/hu.json +++ b/frontend/src/locales/hu.json @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Feladatok térképe", "project.tasks.action.submit_task": "Feladat beküldése", "project.tasks.action.submit_tasks": "Feladatok beküldése", - "project.tasks.history.title": "Feladattevékenység", + "project.tasks.history.title": "", "project.tasks.history.splitted": "A feladat nem érhető el", "project.tasks.history.splitted.description": "", "project.tasks.activity.data.links": "Feladat adatai", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "Tanulja meg, hogyan kell épületeket térképezni az OpenStreetMapben.", "pages.learn.validate.video.title": "Hogyan érvényesítsünk?", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "Érvényesítés tanulása", "pages.learn.validate.intro": "A folyamatnak fontos része az érvényesítés. Magabiztosságot kíván a térképkészítői képességek terén, valamint hajlandóságot arra, hogy az újabb térképészeknek segítsen, őket kísérje és tanácsokat adjon nekik.", "pages.learn.validate.description": "Az OpenStreetMapre felrajzolt és az egész világon felhasznált adatok minőségének biztosításához fontos lépés, hogy a feltérképezett objektumot egy második, harmadik vagy negyedik szempár is lássa.", diff --git a/frontend/src/locales/id.json b/frontend/src/locales/id.json index a7a703f4c9..17eb50fb3e 100644 --- a/frontend/src/locales/id.json +++ b/frontend/src/locales/id.json @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "", "project.tasks.action.submit_task": "Kirim task", "project.tasks.action.submit_tasks": "", - "project.tasks.history.title": "Aktivitas task", + "project.tasks.history.title": "", "project.tasks.history.splitted": "", "project.tasks.history.splitted.description": "", "project.tasks.activity.data.links": "", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "", "pages.learn.validate.video.title": "Bagaimana caranya untuk validasi", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "", "pages.learn.validate.intro": "", "pages.learn.validate.description": "", diff --git a/frontend/src/locales/it.json b/frontend/src/locales/it.json index 37cf907148..4c1c5741da 100644 --- a/frontend/src/locales/it.json +++ b/frontend/src/locales/it.json @@ -481,7 +481,7 @@ "projects.stats.average_mapping_time": "", "projects.stats.average_validation_time": "Tempo medio di convalida", "projects.stats.time_finish_mapping": "", - "projects.stats.time_finish_validation": "", + "projects.stats.time_finish_validation": "Tempo di completamento della convalida", "project.stats.tasks.tatus": "Attività per stato", "project.stats.tasks.needs_mapping": "Attività da mappare", "project.stats.tasks.needs_validation": "Attività da convalidare", @@ -496,7 +496,7 @@ "project.stats.contributors.experience.6": "Da 3 a 6 mesi", "project.stats.contributors.experience.12": "Da 6 a 12 mesi", "project.stats.contributors.experience.year": "Più di 1 anno", - "project.stats.totalEdits": "", + "project.stats.totalEdits": "Totale modifiche alla mappa", "project.stats.changesets": "", "project.stats.edits": "Edizioni", "project.tasks.unsaved_map_changes.title": "Hai alcune modifiche alla mappa non salvate", @@ -551,7 +551,7 @@ "project.selectTask.footer.button.resumeMapping": "Riprendi la mappatura", "project.selectTask.footer.button.resumeValidation": "Riprendi la validazione", "project.tasks.list.lastUpdate": "Ultimo aggiornamento di {user}", - "project.tasks.list.details": "", + "project.tasks.list.details": "Vedere cronologia attività", "project.tasks.list.zoom": "", "project.tasks.list.copyLink": "", "project.tasks.list.linkCopied": "", @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Mappa dei task", "project.tasks.action.submit_task": "Invia task", "project.tasks.action.submit_tasks": "Invia task", - "project.tasks.history.title": "Attività dei task", + "project.tasks.history.title": "Attività {n}", "project.tasks.history.splitted": "Task non disponibile", "project.tasks.history.splitted.description": "", "project.tasks.activity.data.links": "Data del task", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "", "pages.learn.validate.video.title": "", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "Formazione sulla convalida", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "", "pages.learn.validate.intro": "", "pages.learn.validate.description": "", @@ -899,7 +901,7 @@ "pages.learn.validate.steps.identify.description": "", "pages.learn.validate.steps.build.title": "Costruisci le tue abilità", "pages.learn.validate.steps.build.description": "", - "pages.learn.validate.steps.collaborate.title": "", + "pages.learn.validate.steps.collaborate.title": "Collaborare come parte della community", "pages.learn.validate.steps.collaborate.description": "", "pages.learn.validate.note": "", "pages.learn.manage_title": "Imparare a gestire", diff --git a/frontend/src/locales/ja.json b/frontend/src/locales/ja.json index c4bdad62f6..f04001852f 100644 --- a/frontend/src/locales/ja.json +++ b/frontend/src/locales/ja.json @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "タスクマッピング済", "project.tasks.action.submit_task": "タスクを投稿", "project.tasks.action.submit_tasks": "タスクを投稿", - "project.tasks.history.title": "タスクの活動状況", + "project.tasks.history.title": "", "project.tasks.history.splitted": "タスク実施不可", "project.tasks.history.splitted.description": "", "project.tasks.activity.data.links": "データをタスクにする", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "OpenStreetMapでの建物マッピング方法", "pages.learn.validate.video.title": "検証方法", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "検証作業について", "pages.learn.validate.intro": "一連の作業プロセスにおいて、検証作業は重要な位置を占めます。検証作業には、マッピングする能力だけではなく、コーチングやアドバイスを通じて新しく参加するマッパーを支援する気持ちが必要となります。", "pages.learn.validate.description": "二度三度、そしてさらに多くの目でマッピングされた地物の確認を行うことは、世界中から利用されるOpenStreetMapのデータ品質を確認する上でたいへん重要です。", diff --git a/frontend/src/locales/mg.json b/frontend/src/locales/mg.json index f4234d2b62..b2602b17f0 100644 --- a/frontend/src/locales/mg.json +++ b/frontend/src/locales/mg.json @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "", "pages.learn.validate.video.title": "", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "", "pages.learn.validate.intro": "", "pages.learn.validate.description": "", diff --git a/frontend/src/locales/ml.json b/frontend/src/locales/ml.json index 2ed5c29539..01a578f1ae 100644 --- a/frontend/src/locales/ml.json +++ b/frontend/src/locales/ml.json @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "", "pages.learn.validate.video.title": "", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "", "pages.learn.validate.intro": "", "pages.learn.validate.description": "", diff --git a/frontend/src/locales/nl_NL.json b/frontend/src/locales/nl_NL.json index 667416ed64..329af41acd 100644 --- a/frontend/src/locales/nl_NL.json +++ b/frontend/src/locales/nl_NL.json @@ -198,7 +198,7 @@ "management.projects.clone.message": "Het nieuwe project zal een kloon zijn van het project #{id} ({name}).", "management.projects.create.clone": "Klonen", "management.projects.create.area_size": "Grootte gebied: {area} km{sq}", - "management.projects.create.area_error": "", + "management.projects.create.area_error": "Projectgebied is meer dan {n} vierkante kilometer.", "management.projects.create.tasks": "Aantal taken: {n}", "management.projects.create.steps.1": "Stap 1: gebied definiëren", "management.projects.create.steps.2": "Stap 2: grootte van taken instellen", @@ -391,8 +391,8 @@ "projects.formInputs.priority_areas.options.rectangle": "Rechthoek tekenen", "projects.formInputs.priority_areas.action.clear": "Alles leegmaken", "projects.formInputs.name": "Projectnaam", - "projects.formInputs.dueDate": "", - "projects.formInputs.dueDate.description": "", + "projects.formInputs.dueDate": "Datum te voltooien", + "projects.formInputs.dueDate.description": "Definieer de ideale datum waarop het project voltooid zou moeten zijn. De indeling voor de datum is dag/maand/jaar.", "projects.formInputs.description": "Omschrijving", "projects.formInputs.shortDescription": "Korte omschrijving", "projects.formInputs.instructions": "Gedetailleerde instructies", @@ -496,9 +496,9 @@ "project.stats.contributors.experience.6": "3 tot en met 6 maanden", "project.stats.contributors.experience.12": "6 tot en met 12 maanden", "project.stats.contributors.experience.year": "Meer dan 1 jaar", - "project.stats.totalEdits": "", + "project.stats.totalEdits": "Totaal aantal bewerkingen aan de kaart", "project.stats.changesets": "Wijzigingensets", - "project.stats.edits": "", + "project.stats.edits": "Bewerkingen", "project.tasks.unsaved_map_changes.title": "U heeft enkele niet opgeslagen wijzigingen voor de kaart", "project.tasks.unsaved_map_changes.split": "Sla ze op of maak ze ongedaan om de taak te kunnen splitsen", "project.tasks.unsaved_map_changes.unlock": "Sla ze op of maak ze ongedaan om een andere taak te kunnen selecteren", @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Kaart taken", "project.tasks.action.submit_task": "Taak indienen", "project.tasks.action.submit_tasks": "Taken indienen", - "project.tasks.history.title": "Activiteit taak", + "project.tasks.history.title": "", "project.tasks.history.splitted": "Taak niet beschikbaar", "project.tasks.history.splitted.description": "De taak {id} werd gesplitst en de geschiedenis ervan is niet meer beschikbaar", "project.tasks.activity.data.links": "Gegevens taak", @@ -714,8 +714,8 @@ "user.gender.male": "Mannelijk", "user.gender.preferNotToSay": "Voorkeur om niet te zeggen", "user.gender.selfDescribe": "Voorkeur om zelf te beschrijven", - "user.slack": "", - "user.personalInfo.error": "", + "user.slack": "Gebruikersnaam op {org} Slack", + "user.personalInfo.error": "Typ alleen uw gebruikersnaam, niet de URL.", "user.form.save": "Opslaan", "user.settings.title": "Instellingen", "user.notifications.title": "Notificaties", @@ -816,8 +816,8 @@ "users.detail.heatmapLegendLess": "minder", "users.detail.delay_popup": "Deze statistieken vereisen zware berekeningen en wijzigingen worden met een vertraging van ongeveer een uur weergegeven.", "users.detail.teams": "Teams", - "error.page.title": "", - "error.page.description": "", + "error.page.title": "Er is een fout opgetreden", + "error.page.description": "Er was iets dat niet zo goed werkte….", "error.page.link": "Terug", "notFound.page.title": "Pagina niet gevonden", "notFound.project.title": "Project {id} niet gevonden", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "Leren hoe gebouwen in kaart te brengen op OpenStreetMap.", "pages.learn.validate.video.title": "Hoe te valideren", "pages.learn.validate.video.description": "Leren hoe te beginnen met valideren van projecten in Tasking Manager.", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "Leren valideren", "pages.learn.validate.intro": "Valideren is een belangrijk deel van het proces. Het vereist vertrouwen in uw vaardigheden voor in kaart brengen, als ook de bereidheid om nieuwe mappers te coachen en te adviseren.", "pages.learn.validate.description": "Een tweede, derde of vierde paar ogen op in kaart gebrachte objecten is een belangrijke stap om te zorgen voor de kwaliteit van de aan OpenStreetMap toegevoegde gegevens, die dan over de gehele wereld worden gebruikt.", diff --git a/frontend/src/locales/pt.json b/frontend/src/locales/pt.json index 5b75ead6e3..3b639fea45 100644 --- a/frontend/src/locales/pt.json +++ b/frontend/src/locales/pt.json @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Mapa de tarefas", "project.tasks.action.submit_task": "Enviar tarefa", "project.tasks.action.submit_tasks": "Enviar tarefas", - "project.tasks.history.title": "Atividade da tarefa", + "project.tasks.history.title": "", "project.tasks.history.splitted": "Tarefa indisponível", "project.tasks.history.splitted.description": "A tarefa {id} foi dividida e o seu histórico já não se encontra disponível", "project.tasks.activity.data.links": "Dados da tarefa", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "Aprende a mapear edifícios no OpenStreetMap.", "pages.learn.validate.video.title": "Como validar", "pages.learn.validate.video.description": "Aprende a validar projetos no Gestor de Tarefas", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "Aprender a validar", "pages.learn.validate.intro": "A validação é uma parte importante do processo. Requer confiança nas tuas habilidades de mapeamento, bem como vontade de ajudar a treinar e aconselhar mapeadores mais inexperientes.", "pages.learn.validate.description": "Ter um segundo, terceiro ou quarto par de olhos sobre elementos mapeados é um passo importante para garantir a qualidade dos dados que estão a ser adicionados ao OpenStreetMap e que serão usados em todo o mundo.", diff --git a/frontend/src/locales/pt_BR.json b/frontend/src/locales/pt_BR.json index c997c577ae..7a503e052b 100644 --- a/frontend/src/locales/pt_BR.json +++ b/frontend/src/locales/pt_BR.json @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Mapa de tarefas", "project.tasks.action.submit_task": "Atualizar tarefa", "project.tasks.action.submit_tasks": "Enviar tarefas", - "project.tasks.history.title": "Atividade da tarefa", + "project.tasks.history.title": "Tarefa {n}", "project.tasks.history.splitted": "Tarefa indisponível", "project.tasks.history.splitted.description": "A tarefa {id} foi dividida e seu histórico não está mais disponível", "project.tasks.activity.data.links": "Dados da tarefa", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "Saiba como mapear edifícios no OpenStreetMap.", "pages.learn.validate.video.title": "Como validar", "pages.learn.validate.video.description": "Saiba como começar a validar projetos no Tasking Manager.", + "pages.learn.validate.training.video.title": "Treinamento de validação", + "pages.learn.validate.training.video.description": "Mergulhe fundo na área de validação com este treinamento, o qual inclui conceitos avançados e o editor JOSM.", "pages.learn.validate_title": "Aprenda a validar", "pages.learn.validate.intro": "A validação é uma parte importante do processo. Requer confiança em suas habilidades de mapeamento, bem como a disposição de ajudar a treinar e aconselhar novos mapeadores.", "pages.learn.validate.description": "Conferir duas, três ou quatro vezes os recursos mapeados é uma etapa importante para garantir a qualidade dos dados sendo adicionados ao OpenStreetMap e depois utilizados em todo o mundo.", diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json index d34908d3f2..f5245cd341 100644 --- a/frontend/src/locales/sv.json +++ b/frontend/src/locales/sv.json @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Karta över uppgifter", "project.tasks.action.submit_task": "Skicka uppgift", "project.tasks.action.submit_tasks": "Skicka uppgifter", - "project.tasks.history.title": "Uppgift aktivitet", + "project.tasks.history.title": "", "project.tasks.history.splitted": "", "project.tasks.history.splitted.description": "", "project.tasks.activity.data.links": "Uppgift data", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "", "pages.learn.validate.video.title": "", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "", "pages.learn.validate.intro": "", "pages.learn.validate.description": "", diff --git a/frontend/src/locales/sw.json b/frontend/src/locales/sw.json index 5ccff8ba57..d20568bb42 100644 --- a/frontend/src/locales/sw.json +++ b/frontend/src/locales/sw.json @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "", "project.tasks.action.submit_task": "Wasilisha kazi", "project.tasks.action.submit_tasks": "", - "project.tasks.history.title": "Utendaji kazi", + "project.tasks.history.title": "", "project.tasks.history.splitted": "", "project.tasks.history.splitted.description": "", "project.tasks.activity.data.links": "", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "", "pages.learn.validate.video.title": "Jinsi ya kuhalalisha", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "", "pages.learn.validate.intro": "", "pages.learn.validate.description": "", diff --git a/frontend/src/locales/tl.json b/frontend/src/locales/tl.json index 2ed5c29539..01a578f1ae 100644 --- a/frontend/src/locales/tl.json +++ b/frontend/src/locales/tl.json @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "", "pages.learn.validate.video.title": "", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "", "pages.learn.validate.intro": "", "pages.learn.validate.description": "", diff --git a/frontend/src/locales/tr.json b/frontend/src/locales/tr.json index cef49f41fc..591c5230ca 100644 --- a/frontend/src/locales/tr.json +++ b/frontend/src/locales/tr.json @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Görev haritası", "project.tasks.action.submit_task": "Görevi yükle", "project.tasks.action.submit_tasks": "Görevleri yükle", - "project.tasks.history.title": "Görev aktivitesi", + "project.tasks.history.title": "", "project.tasks.history.splitted": "Görev erişime kapalı", "project.tasks.history.splitted.description": "", "project.tasks.activity.data.links": "Görev verisi", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "", "pages.learn.validate.video.title": "Nasıl doğrulanıyor?", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "Doğrulamayı öğrenin", "pages.learn.validate.intro": "Doğrulama işlemi haritalama sürecinin önemli bir parçasıdır. Haritalama yeteneklerinize güven duymalı, ve başlangıç seviyesindeki haritacılara yardımcı olmaya gönüllü olmalısınız.", "pages.learn.validate.description": "Haritadaki detayların ikinci, üçüncü veya dördüncü bir çift göz tarafından incelenmesi, OpenStreetMap’e eklenen ve daha sonra tüm dünyada kullanılan verilerin kalitesinden emin olmak için önemli bir adımdır.", diff --git a/frontend/src/locales/uk.json b/frontend/src/locales/uk.json index 4f764bf7db..70724fa96a 100644 --- a/frontend/src/locales/uk.json +++ b/frontend/src/locales/uk.json @@ -198,7 +198,7 @@ "management.projects.clone.message": "Новий проєкт буде створено як копія проєкту #{id} ({name}).", "management.projects.create.clone": "Клонувати", "management.projects.create.area_size": "Територія: {area} км{sq}", - "management.projects.create.area_error": "", + "management.projects.create.area_error": "Територія проєкту більша за {n} км².", "management.projects.create.tasks": "Кількість завдань: {n}", "management.projects.create.steps.1": "Крок 1: Визначення території", "management.projects.create.steps.2": "Крок 2: Оберіть розмір завдання", @@ -391,8 +391,8 @@ "projects.formInputs.priority_areas.options.rectangle": "Накреслити прямокутник", "projects.formInputs.priority_areas.action.clear": "Очистити все", "projects.formInputs.name": "Назва проєкту", - "projects.formInputs.dueDate": "", - "projects.formInputs.dueDate.description": "", + "projects.formInputs.dueDate": "Дата закінчення", + "projects.formInputs.dueDate.description": "Визначте ідеальну дату завершення проєкту. Формат дати - день/місяць/рік.", "projects.formInputs.description": "Опис", "projects.formInputs.shortDescription": "Короткий опис", "projects.formInputs.instructions": "Докладні інструкції", @@ -496,9 +496,9 @@ "project.stats.contributors.experience.6": "від 3 до 6 місяців", "project.stats.contributors.experience.12": "від 6 до 12 місяців", "project.stats.contributors.experience.year": "Більше ніж 1 рік", - "project.stats.totalEdits": "", + "project.stats.totalEdits": "Всього внесено змін", "project.stats.changesets": "Набори змін", - "project.stats.edits": "", + "project.stats.edits": "Редагування", "project.tasks.unsaved_map_changes.title": "У вас є незбережені зміни в мапінгу", "project.tasks.unsaved_map_changes.split": "Збережіть або скасуйте їх для того щоб розділити завдання", "project.tasks.unsaved_map_changes.unlock": "Збережіть або скасуйте їх для того щоб обрати інше завдання", @@ -599,7 +599,7 @@ "project.tasks.action.tasks_map": "Мапа", "project.tasks.action.submit_task": "Надіслати", "project.tasks.action.submit_tasks": "Надіслати", - "project.tasks.history.title": "Історія завдання", + "project.tasks.history.title": "", "project.tasks.history.splitted": "Завдання недоступне", "project.tasks.history.splitted.description": "Завдання {id} було поділено на частини, історія його змін більше не доступна", "project.tasks.activity.data.links": "Дані завдання", @@ -714,8 +714,8 @@ "user.gender.male": "Чоловік", "user.gender.preferNotToSay": "Не бажаю зазначати", "user.gender.selfDescribe": "Вважаю що я:", - "user.slack": "", - "user.personalInfo.error": "", + "user.slack": "Логін користувача в {org} Slack", + "user.personalInfo.error": "Зазначте лише ваш логін, не URL.", "user.form.save": "Зберегти", "user.settings.title": "Налаштування", "user.notifications.title": "Повідомлення", @@ -816,8 +816,8 @@ "users.detail.heatmapLegendLess": "менше", "users.detail.delay_popup": "Підрахунок статистики вимагає великої кількості обчислень, тож статистика щодо змін буде десь через годину.", "users.detail.teams": "Команди", - "error.page.title": "", - "error.page.description": "", + "error.page.title": "Виникла помилка", + "error.page.description": "Схоже, щось не працює…", "error.page.link": "Повернутись", "notFound.page.title": "Сторінку не знайдено", "notFound.project.title": "Проєкт {id} не знайдено", @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "Дізнайтесь як мапити будинки в OpenStreetMap.", "pages.learn.validate.video.title": "Як перевіряти", "pages.learn.validate.video.description": "Дізнайтесь як розпочати перевіряти проєкти в Менеджері завдань.", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "Як перевіряти", "pages.learn.validate.intro": "Валідація – важлива складова процесу. Вона вимагає впевненості у ваших вміннях щодо мапінгу, а також готовності допомагати у навчанні та консультуванні нових маперів.", "pages.learn.validate.description": "Додаткова пара чи більше очей, що переглядають додані на мапу об'єкти є важливим елементом у перевірці якості даних, що додаються в OpenStreetMap та потім використовуються по всьому світу.", diff --git a/frontend/src/locales/zh_TW.json b/frontend/src/locales/zh_TW.json index 262261f2eb..06f2336d76 100644 --- a/frontend/src/locales/zh_TW.json +++ b/frontend/src/locales/zh_TW.json @@ -892,6 +892,8 @@ "pages.learn.videos.map_buildings.description": "", "pages.learn.validate.video.title": "", "pages.learn.validate.video.description": "", + "pages.learn.validate.training.video.title": "", + "pages.learn.validate.training.video.description": "", "pages.learn.validate_title": "學習如何審核", "pages.learn.validate.intro": "", "pages.learn.validate.description": "", diff --git a/frontend/src/utils/openEditor.js b/frontend/src/utils/openEditor.js index 34c3d9be25..7ef05b1519 100644 --- a/frontend/src/utils/openEditor.js +++ b/frontend/src/utils/openEditor.js @@ -66,9 +66,7 @@ export function getPotlatch2Url(centroid, zoomLevel) { } export function getIdUrl(project, centroid, zoomLevel, selectedTasks, customUrl) { - const base = customUrl - ? formatCustomUrl(customUrl) - : `${ID_EDITOR_URL}`; + const base = customUrl ? formatCustomUrl(customUrl) : `${ID_EDITOR_URL}`; let url = base + '#map=' + [zoomLevel, centroid[1], centroid[0]].join('/'); if (project.changesetComment) { url += '&comment=' + encodeURIComponent(project.changesetComment); @@ -83,6 +81,9 @@ export function getIdUrl(project, centroid, zoomLevel, selectedTasks, customUrl) if (project.projectId && selectedTasks) { url += '&gpx=' + encodeURIComponent(getTaskGpxUrl(project.projectId, selectedTasks).href); } + if (customUrl !== '?editor=ID' && project.idPresets) { + url += '&presets=' + encodeURIComponent(project.idPresets.join(',')); + } return url; } diff --git a/frontend/src/utils/tests/openEditor.test.js b/frontend/src/utils/tests/openEditor.test.js index d8501be592..10ffa293b3 100644 --- a/frontend/src/utils/tests/openEditor.test.js +++ b/frontend/src/utils/tests/openEditor.test.js @@ -43,7 +43,7 @@ describe('test if getIdUrl', () => { ); }); - it('with idPresets returns the correct formatted url', () => { + it('with idPresets returns the url param', () => { const testProject = { changesetComment: '#hotosm-project-5522 #osm_in #2018IndiaFloods #mmteamarm', projectId: 1234, @@ -51,6 +51,21 @@ describe('test if getIdUrl', () => { }; expect(getIdUrl(testProject, [120.25684, -9.663953], 18, [1])).toBe( 'https://www.openstreetmap.org/edit?editor=id&' + + '#map=18/-9.663953/120.25684' + + '&comment=%23hotosm-project-5522%20%23osm_in%20%232018IndiaFloods%20%23mmteamarm' + + '&gpx=http%3A%2F%2F127.0.0.1%3A5000%2Fapi%2Fv2%2Fprojects%2F1234%2Ftasks%2Fqueries%2Fgpx%2F%3Ftasks%3D1' + + '&presets=building%2Chighway%2Cnatural%2Fwater', + ); + }); + + it('with idPresets and internal iD does not return the url param', () => { + const testProject = { + changesetComment: '#hotosm-project-5522 #osm_in #2018IndiaFloods #mmteamarm', + projectId: 1234, + idPresets: ['building', 'highway', 'natural/water'], + }; + expect(getIdUrl(testProject, [120.25684, -9.663953], 18, [1], '?editor=ID')).toBe( + '?editor=ID' + '#map=18/-9.663953/120.25684' + '&comment=%23hotosm-project-5522%20%23osm_in%20%232018IndiaFloods%20%23mmteamarm' + '&gpx=http%3A%2F%2F127.0.0.1%3A5000%2Fapi%2Fv2%2Fprojects%2F1234%2Ftasks%2Fqueries%2Fgpx%2F%3Ftasks%3D1', diff --git a/frontend/src/views/learn.js b/frontend/src/views/learn.js index c453be95f1..3118cc980f 100644 --- a/frontend/src/views/learn.js +++ b/frontend/src/views/learn.js @@ -305,6 +305,10 @@ const LearnToValidate = ({ section }) => { message: 'learnValidateHowToVideo', youTubeId: 'frVwlJn4tdI', }, + { + message: 'learnValidateTrainingVideo', + youTubeId: 'YQ18XfRM6d4', + }, ]; return ( @@ -355,7 +359,7 @@ const LearnToMap = ({ section }) => { }, { message: 'learnMapBuildings', - youTubeId: 'lSLe6rjtgi0', + youTubeId: 'nswUcgMfKTM', }, { message: 'learnMapRoads', diff --git a/frontend/src/views/messages.js b/frontend/src/views/messages.js index 6d621d56cb..cf31491cf2 100644 --- a/frontend/src/views/messages.js +++ b/frontend/src/views/messages.js @@ -319,6 +319,15 @@ export default defineMessages({ id: 'pages.learn.validate.video.description', defaultMessage: 'Learn how to start validating projects on Tasking Manager.', }, + learnValidateTrainingVideoTitle: { + id: 'pages.learn.validate.training.video.title', + defaultMessage: 'Validation training', + }, + learnValidateTrainingVideoDescription: { + id: 'pages.learn.validate.training.video.description', + defaultMessage: + 'Deep dive on validation with this training, which includes advanced concepts and the JOSM editor.', + }, learnValidateTitle: { id: 'pages.learn.validate_title', defaultMessage: 'Learn to validate', diff --git a/frontend/src/views/projectStats.js b/frontend/src/views/projectStats.js index ef641ea8da..cd8f080cc4 100644 --- a/frontend/src/views/projectStats.js +++ b/frontend/src/views/projectStats.js @@ -31,7 +31,7 @@ export function ProjectStats({ id }: Object) { ); // To fix: set this URL with an ENV VAR later const [errorEdits, loadingEdits, edits] = useFetch( - `http://osm-stats-production-api.azurewebsites.net/stats/${ + `https://osm-stats-production-api.azurewebsites.net/stats/${ project && project.changesetComment && project.changesetComment.replace('#', '').split(' ')[0] }`, project && project.changesetComment !== undefined, @@ -77,24 +77,26 @@ export function ProjectStats({ id }: Object) {
-
+

-
- Loading...
}> - - - - -
-
- +
+
+ Loading...
}> + + + + +
+
+ +
diff --git a/frontend/src/views/userDetail.js b/frontend/src/views/userDetail.js index 6d8c24da16..52ad3ad09a 100644 --- a/frontend/src/views/userDetail.js +++ b/frontend/src/views/userDetail.js @@ -12,7 +12,7 @@ import { CountriesMapped } from '../components/userDetail/countriesMapped'; import { TopCauses } from '../components/userDetail/topCauses'; import { TopProjects } from '../components/userDetail/topProjects'; import { EditsByNumbers } from '../components/userDetail/editsByNumbers'; -import ContributionTimeline from '../components/userDetail/contributionTimeline'; +import { ContributionTimeline } from '../components/userDetail/contributionTimeline'; import { NotFound } from './notFound'; import { USER_STATS_API_URL } from '../config'; import { fetchExternalJSONAPI } from '../network/genericJSONRequest'; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 1ffdc1dd73..248410da18 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -995,20 +995,13 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.11.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw== dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839" - integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/template@^7.4.0", "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" @@ -1142,10 +1135,10 @@ resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.0.tgz#5b03ba4931436070ad926d1b2e89bf07edc5ea5b" integrity sha512-jc1bZHhIE1YI0HnZIZcdlKpF4wle2pkgQpzXHDoyy4bUqzBSvDqktnF26hOkyA04KD4wqd61gkuTvRrHMmroAg== -"@formatjs/ecma402-abstract@^1.2.3": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.3.tgz#ca94911dd8e9c89eeaabba0f00e2f692979fc27b" - integrity sha512-sEkxTZj7qa+Pi/c4ppE5mxZYJkqQO3GNZzJZjAxgsXip2ixO/TZn58qrxdQ0V8mXmf+5xf+AfveyPvv4yHaRtw== +"@formatjs/ecma402-abstract@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.5.tgz#5a61ac1990ff2df8d1348ab12e186c1ca2a2bd71" + integrity sha512-k0fqS3LBNOHueAoMdgig8Ni6TchsH+zbzWBzX2gTFm50X9mxHwnuXdCk0XLlCIbvgVVlzcO254Men/mHAheMbg== dependencies: tslib "^2.0.1" @@ -1163,12 +1156,20 @@ dependencies: "@formatjs/ecma402-abstract" "^1.2.0" -"@formatjs/intl-pluralrules@^3.4.8": - version "3.4.8" - resolved "https://registry.yarnpkg.com/@formatjs/intl-pluralrules/-/intl-pluralrules-3.4.8.tgz#8adea6bbae3a5109d6b53b0e791b696f1a4fad4d" - integrity sha512-u1upykr0awAEsYkfbg7KCsgKLZLWA+xMskUb2CVas3/RS+tD0BEgYSO+x3lGVI9BocX/iWGLsTSUYsU8G2jajQ== +"@formatjs/intl-pluralrules@^3.4.10": + version "3.4.10" + resolved "https://registry.yarnpkg.com/@formatjs/intl-pluralrules/-/intl-pluralrules-3.4.10.tgz#7ed3b03190971f21d482cb0e46791d90783a74d3" + integrity sha512-KcZZv38bu0pho9+9pMUOsCAi9/Kayh4+V5QZ/I9ps5OFSQlQaFMP5sX/zHBp41SsT6HxTfrPw5CHWpGrS75NQQ== + dependencies: + "@formatjs/ecma402-abstract" "^1.2.5" + tslib "^2.0.1" + +"@formatjs/intl-relativetimeformat@^7.2.10": + version "7.2.10" + resolved "https://registry.yarnpkg.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-7.2.10.tgz#51bf272cb96e3df91cd70cfae4ae8ae563849052" + integrity sha512-3eKfhK0X5O8JkJ9bSgkWCG9fXjFPQCxw18DWqhbZ5thTU2aKejpxBzwFYCo+o5TCIEmtn6G835nBqb2d197Gnw== dependencies: - "@formatjs/ecma402-abstract" "^1.2.3" + "@formatjs/ecma402-abstract" "^1.2.5" tslib "^2.0.1" "@formatjs/intl-relativetimeformat@^7.2.5": @@ -1178,14 +1179,6 @@ dependencies: "@formatjs/ecma402-abstract" "^1.2.0" -"@formatjs/intl-relativetimeformat@^7.2.8": - version "7.2.8" - resolved "https://registry.yarnpkg.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-7.2.8.tgz#a423ef9acd379980f58730b4757713156076de14" - integrity sha512-h6H5lcPn1LbVlAk62m7DYtY68aE6AiZVK1bLEo3HeWrMBFCskWAe9I/5kI+RjStdGZzo+CqBl+rSTcrSXjVj+g== - dependencies: - "@formatjs/ecma402-abstract" "^1.2.3" - tslib "^2.0.1" - "@formatjs/intl-unified-numberformat@^3.2.0": version "3.3.2" resolved "https://registry.yarnpkg.com/@formatjs/intl-unified-numberformat/-/intl-unified-numberformat-3.3.2.tgz#6994c768be082a6b5c3f1b73393d9f08ad82b1e2" @@ -1667,79 +1660,79 @@ prop-types "^15.6.1" react-lifecycles-compat "^3.0.4" -"@sentry/browser@5.25.0": - version "5.25.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.25.0.tgz#4e3d2132ba1f2e2b26f73c49cbb6977ee9c9fea9" - integrity sha512-QDVUbUuTu58xCdId0eUO4YzpvrPdoUw1ryVy/Yep9Es/HD0fiSyO1Js0eQVkV/EdXtyo2pomc1Bpy7dbn2EJ2w== +"@sentry/browser@5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.26.0.tgz#e90a197fb94c5f26c8e05d6a539c118f33c7d598" + integrity sha512-52kNVpy10Zd3gJRGFkhnOQvr80WJg7+XBqjMOE0//Akh4PfvEK3IqmAjVqysz6aHdruwTTivKF4ZoAxL/pA7Rg== dependencies: - "@sentry/core" "5.25.0" - "@sentry/types" "5.25.0" - "@sentry/utils" "5.25.0" + "@sentry/core" "5.26.0" + "@sentry/types" "5.26.0" + "@sentry/utils" "5.26.0" tslib "^1.9.3" -"@sentry/core@5.25.0": - version "5.25.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.25.0.tgz#525ad37f9e8a95603768e3b74b437d5235a51578" - integrity sha512-hY6Zmo7t/RV+oZuvXHP6nyAj/QnZr2jW0e7EbL5YKMV8q0vlnjcE0LgqFXme726OJemoLk67z+sQOJic/Ztehg== +"@sentry/core@5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.26.0.tgz#9b5fe4de8a869d733ebcc77f5ec9c619f8717a51" + integrity sha512-Ubrw7K52orTVsaxpz8Su40FPXugKipoQC+zPrXcH+JIMB+o18kutF81Ae4WzuUqLfP7YB91eAlRrP608zw0EXA== dependencies: - "@sentry/hub" "5.25.0" - "@sentry/minimal" "5.25.0" - "@sentry/types" "5.25.0" - "@sentry/utils" "5.25.0" + "@sentry/hub" "5.26.0" + "@sentry/minimal" "5.26.0" + "@sentry/types" "5.26.0" + "@sentry/utils" "5.26.0" tslib "^1.9.3" -"@sentry/hub@5.25.0": - version "5.25.0" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.25.0.tgz#6932535604cafaee1ac7f361b0e7c2ce8f7e7bc3" - integrity sha512-kOlOiJV8wMX50lYpzMlOXBoH7MNG0Ho4RTusdZnXZBaASq5/ljngDJkLr6uylNjceZQP21wzipCQajsJMYB7EQ== +"@sentry/hub@5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.26.0.tgz#b2bbd8128cd5915f2ee59cbc29fff30272d74ec5" + integrity sha512-lAYeWvvhGYS6eQ5d0VEojw0juxGc3v4aAu8VLvMKWcZ1jXD13Bhc46u9Nvf4qAY6BAQsJDQcpEZLpzJu1bk1Qw== dependencies: - "@sentry/types" "5.25.0" - "@sentry/utils" "5.25.0" + "@sentry/types" "5.26.0" + "@sentry/utils" "5.26.0" tslib "^1.9.3" -"@sentry/minimal@5.25.0": - version "5.25.0" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.25.0.tgz#447b5406b45c8c436c461abea4474d6a849ed975" - integrity sha512-9JFKuW7U+1vPO86k3+XRtJyooiVZsVOsFFO4GulBzepi3a0ckNyPgyjUY1saLH+cEHx18hu8fGgajvI8ANUF2g== +"@sentry/minimal@5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.26.0.tgz#851dea3644153ed3ac4837fa8ed5661d94e7a313" + integrity sha512-mdFo3FYaI1W3KEd8EHATYx8mDOZIxeoUhcBLlH7Iej6rKvdM7p8GoECrmHPU1l6sCCPtBuz66QT5YeXc7WILsA== dependencies: - "@sentry/hub" "5.25.0" - "@sentry/types" "5.25.0" + "@sentry/hub" "5.26.0" + "@sentry/types" "5.26.0" tslib "^1.9.3" -"@sentry/react@^5.25.0": - version "5.25.0" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.25.0.tgz#269f54db9d6f92410bee07117f8d8e03b219e068" - integrity sha512-lZwiFj+BQtmaj+Do9hcRSJcdrTisSGq2521/Xm9qGPbhsRW8uTHMJjkDgMHriYxxqXYOQrY9FisJwvkPpkroow== +"@sentry/react@^5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.26.0.tgz#0462430757ac0ab0c10de803f39fb41d5ced1caa" + integrity sha512-oC1wwfhckV8HHJTs4Zot5JIwEftcltPuC8cOedenDor5SKKbMeNufKw0ZgW82j9DSZMjh053LKkIZmO7zYf8eQ== dependencies: - "@sentry/browser" "5.25.0" - "@sentry/minimal" "5.25.0" - "@sentry/types" "5.25.0" - "@sentry/utils" "5.25.0" + "@sentry/browser" "5.26.0" + "@sentry/minimal" "5.26.0" + "@sentry/types" "5.26.0" + "@sentry/utils" "5.26.0" hoist-non-react-statics "^3.3.2" tslib "^1.9.3" -"@sentry/tracing@^5.25.0": - version "5.25.0" - resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.25.0.tgz#1cfbcf085a7a3b679f417058d09590298ddaa255" - integrity sha512-KcyHEGFpqSDubHrdWT/vF2hKkjw/ts6NpJ6tPDjBXUNz98BHdAyMKtLOFTCeJFply7/s5fyiAYu44M+M6IG3Bw== +"@sentry/tracing@^5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.26.0.tgz#33ee0426da14836e54e7b9a8838e4d7d0cb14b70" + integrity sha512-N9qWGmKrFJYKFTZBe8zVT3Qiju0+9bbNJuyun69T+fqP3PCDh+aRlRiP+OKTJyeCZjNG5HIvIlU8wTVUDoYfjQ== dependencies: - "@sentry/hub" "5.25.0" - "@sentry/minimal" "5.25.0" - "@sentry/types" "5.25.0" - "@sentry/utils" "5.25.0" + "@sentry/hub" "5.26.0" + "@sentry/minimal" "5.26.0" + "@sentry/types" "5.26.0" + "@sentry/utils" "5.26.0" tslib "^1.9.3" -"@sentry/types@5.25.0": - version "5.25.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.25.0.tgz#3bcf95e118d655d3f4e8bfa5f0be2e1fe4ea5307" - integrity sha512-8M4PREbcar+15wrtEqcwfcU33SS+2wBSIOd/NrJPXJPTYxi49VypCN1mZBDyWkaK+I+AuQwI3XlRPCfsId3D1A== +"@sentry/types@5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.26.0.tgz#b0cbacb0b24cd86620fb296b46cf7277bb004a3e" + integrity sha512-ugpa1ePOhK55pjsyutAsa2tiJVQEyGYCaOXzaheg/3+EvhMdoW+owiZ8wupfvPhtZFIU3+FPOVz0d5k9K5d1rw== -"@sentry/utils@5.25.0": - version "5.25.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.25.0.tgz#b132034be66d7381d30879d2a9e09216fed28342" - integrity sha512-Hz5spdIkMSRH5NR1YFOp5qbsY5Ud2lKhEQWlqxcVThMG5YNUc10aYv5ijL19v0YkrC2rqPjCRm7GrVtzOc7bXQ== +"@sentry/utils@5.26.0": + version "5.26.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.26.0.tgz#09a3d01d91747f38f796cafeb24f8fd86e4fa05f" + integrity sha512-F2gnHIAWbjiowcAgxz3VpKxY/NQ39NTujEd/NPnRTWlRynLFg3bAV+UvZFXljhYJeN3b/zRlScNDcpCWTrtZGw== dependencies: - "@sentry/types" "5.25.0" + "@sentry/types" "5.26.0" tslib "^1.9.3" "@svgr/babel-plugin-add-jsx-attribute@^4.2.0": @@ -1845,10 +1838,10 @@ "@svgr/plugin-svgo" "^4.3.1" loader-utils "^1.2.3" -"@testing-library/dom@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.24.2.tgz#6d2b7dd21efbd5358b98c2777fc47c252f3ae55e" - integrity sha512-ERxcZSoHx0EcN4HfshySEWmEf5Kkmgi+J7O79yCJ3xggzVlBJ2w/QjJUC+EBkJJ2OeSw48i3IoePN4w8JlVUIA== +"@testing-library/dom@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.26.0.tgz#da4d052dc426a4ccc916303369c6e7552126f680" + integrity sha512-fyKFrBbS1IigaE3FV21LyeC7kSGF84lqTlSYdKmGaHuK2eYQ/bXVPM5vAa2wx/AU1iPD6oQHsxy2QQ17q9AMCg== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.10.3" @@ -1856,6 +1849,7 @@ aria-query "^4.2.2" chalk "^4.1.0" dom-accessibility-api "^0.5.1" + lz-string "^1.4.4" pretty-format "^26.4.2" "@testing-library/jest-dom@^5.11.4": @@ -1880,13 +1874,13 @@ "@babel/runtime" "^7.5.4" "@types/testing-library__react-hooks" "^3.4.0" -"@testing-library/react@^11.0.4": - version "11.0.4" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.0.4.tgz#c84082bfe1593d8fcd475d46baee024452f31dee" - integrity sha512-U0fZO2zxm7M0CB5h1+lh31lbAwMSmDMEMGpMT3BUPJwIjDEKYWOV4dx7lb3x2Ue0Pyt77gmz/VropuJnSz/Iew== +"@testing-library/react@^11.1.0": + version "11.1.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.1.0.tgz#dfb4b3177d05a8ccf156b5fd14a5550e91d7ebe4" + integrity sha512-Nfz58jGzW0tgg3irmTB7sa02JLkLnCk+QN3XG6WiaGQYb0Qc4Ok00aujgjdxlIQWZHbb4Zj5ZOIeE9yKFSs4sA== dependencies: "@babel/runtime" "^7.11.2" - "@testing-library/dom" "^7.24.2" + "@testing-library/dom" "^7.26.0" "@turf/along@^6.0.1": version "6.0.1" @@ -3735,10 +3729,10 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -chart.js@^2.9.3: - version "2.9.3" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.3.tgz#ae3884114dafd381bc600f5b35a189138aac1ef7" - integrity sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw== +chart.js@^2.9.4: + version "2.9.4" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684" + integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A== dependencies: chartjs-color "^2.1.0" moment "^2.10.2" @@ -8115,6 +8109,11 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lz-string@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" + integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= + make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -8539,10 +8538,10 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -msw@^0.21.2: - version "0.21.2" - resolved "https://registry.yarnpkg.com/msw/-/msw-0.21.2.tgz#74ed10b8eb224325652a3c3812b5460dac297bd8" - integrity sha512-XOJehxtJThNFdMJdVjxDAbZ8KuC3UltOlO5nQDks0Q1yzSUqqKcVUjbKrH7T+K2hckBr0KEY2fwJHv21R4BV2A== +msw@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/msw/-/msw-0.21.3.tgz#d073842f9570a08f4041806a2c7303a9b8494602" + integrity sha512-voPc/EJsjarvi454vSEuozZQQqLG4AUHT6qQL5Ah47lq7sGCpc7icByeUlfvEj5+MvaugN0c7JwXyCa2rxu8cA== dependencies: "@open-draft/until" "^1.0.3" "@types/cookie" "^0.4.0" @@ -10408,10 +10407,10 @@ query-string@^4.1.0: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -query-string@^6.13.5: - version "6.13.5" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.5.tgz#99e95e2fb7021db90a6f373f990c0c814b3812d8" - integrity sha512-svk3xg9qHR39P3JlHuD7g3nRnyay5mHbrPctEBDUxUkHRifPHXJDhBUycdCC0NBjXoDf44Gb+IsOZL1Uwn8M/Q== +query-string@^6.13.6: + version "6.13.6" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.6.tgz#e5ac7c74f2a5da43fbca0b883b4f0bafba439966" + integrity sha512-/WWZ7d9na6s2wMEGdVCVgKWE9Rt7nYyNIf7k8xmHXcesPMlEzicWo3lbYwHyA4wBktI2KrXxxZeACLbE84hvSQ== dependencies: decode-uri-component "^0.2.0" split-on-first "^1.0.0" @@ -10528,10 +10527,10 @@ react-click-outside@^3.0.1: dependencies: hoist-non-react-statics "^2.1.1" -react-datepicker@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-3.2.2.tgz#cd5351ab1bba0b34412dd11db3169801f8f8b2ef" - integrity sha512-/3D6hfhXcCNCbO8LICuQeoNDItWFyitGo+aLcsi0tAyJLtCInamYRwPIXhsEF+N6/qWim1yNyr71mqjj4YEBmg== +react-datepicker@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-3.3.0.tgz#38dec531fd7c8e0de6860a55dfbc3a8ff803d43c" + integrity sha512-QnIlBxDSWEGBi2X5P1BqWzvfnPFRKhtrsgAcujUVwyWeID/VatFaAOEjEjfD1bXR9FuSYVLlLR3j/vbG19hWOA== dependencies: classnames "^2.2.6" date-fns "^2.0.1" @@ -10775,10 +10774,10 @@ react-transition-group@^4.3.0: loose-envify "^1.4.0" prop-types "^15.6.2" -react@^16.13.1, react@^16.9.0: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== +react@^16.14.0, react@^16.9.0: + version "16.14.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" + integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" diff --git a/requirements.txt b/requirements.txt index 078098ee70..2146f8e573 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,7 +34,7 @@ Mako==1.1.3 markdown==3.2.2 MarkupSafe==1.1.1 mccabe==0.6.1 -newrelic==5.20.0.149 +newrelic==5.20.1.150 nose==1.3.7 oauthlib==2.0.2 psycopg2==2.8.6 @@ -51,7 +51,7 @@ PyYAML==5.3.1 requests==2.24.0 requests-oauthlib==1.0.0 schematics==2.1.0 -sentry-sdk[flask]==0.18.0 +sentry-sdk[flask]==0.19.1 Shapely==1.7.1 six==1.15.0 SQLAlchemy==1.3.19 diff --git a/scripts/docker/Dockerfile.backend b/scripts/docker/Dockerfile.backend index 24e6c3ac51..79d902e9ba 100644 --- a/scripts/docker/Dockerfile.backend +++ b/scripts/docker/Dockerfile.backend @@ -1,25 +1,30 @@ -FROM alpine/git as base - -RUN mkdir -p /usr/src/app +FROM python:3.7-alpine as base WORKDIR /usr/src/app -ADD . /usr/src/app +## BUILD +FROM base as builder -FROM python:3.7-alpine +# Setup backend build dependencies +RUN apk update && apk add postgresql-dev gcc python3-dev musl-dev libffi-dev geos-dev proj-util proj-dev make -RUN mkdir -p /usr/src/app -WORKDIR /usr/src/app +# Setup backend Python dependencies +COPY requirements.txt . +RUN pip install --prefix=/install --no-warn-script-location -r requirements.txt -COPY --from=base /usr/src/app /usr/src/app +## DEPLOY +FROM base -## SETUP +# Setup backend runtime dependencies +RUN apk update && apk add postgresql-libs geos proj-util -# Setup backend dependencies -RUN apk update && apk add postgresql-dev gcc python3-dev musl-dev libffi-dev geos-dev proj-util proj-dev make -RUN pip install --no-cache-dir -r requirements.txt - -## INITIALIZATION +COPY --from=builder /install /usr/local +COPY . . +ENV TZ UTC # Fix timezone (do not change - see issue #3638) EXPOSE 5000 -CMD ["gunicorn", "-b", "0.0.0.0:5000", "--worker-class", "gevent", "--workers", "3", \ - "--threads", "3", "--timeout", "179", "manage:application", "&"] \ No newline at end of file + +ENV WORKERS 3 +ENV THREADS 3 +ENV TIMEOUT 179 + +CMD ["sh", "-c", "exec gunicorn -b 0.0.0.0:5000 --worker-class gevent --workers $WORKERS --threads $THREADS --timeout $TIMEOUT manage:application"] diff --git a/scripts/docker/Dockerfile.frontend b/scripts/docker/Dockerfile.frontend index 604f67a636..8da10d7d9b 100644 --- a/scripts/docker/Dockerfile.frontend +++ b/scripts/docker/Dockerfile.frontend @@ -1,16 +1,7 @@ -FROM alpine/git as base - -RUN mkdir -p /usr/src/app -WORKDIR /usr/src/app - -ADD . /usr/src/app - FROM tiangolo/node-frontend:10 as build -WORKDIR /usr/src/app - -COPY --from=base /usr/src/app/ /usr/src/app WORKDIR /usr/src/app/frontend +COPY frontend . ## SETUP RUN npm install @@ -25,4 +16,4 @@ COPY --from=build /usr/src/app/frontend/build /usr/share/nginx/html COPY --from=build /nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file +CMD ["nginx", "-g", "daemon off;"]