Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v4.7.4 release on staging #6452

Merged
merged 11 commits into from
Jun 4, 2024
Merged
102 changes: 102 additions & 0 deletions .github/workflows/ecs-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Deploy to Amazon ECS

on:
push:
branches:
- tasking-manager-fastapi

env:
REGISTRY: ghcr.io
AWS_REGION: us-east-1
ECS_CLUSTER: tasking-manager
ECS_SERVICE: tasking-manager-fastAPI
CONTAINER_NAME: backend
IMAGE_NAME: hotosm/tasking-manager-backend # was ${{ github.repository }}

jobs:
build-push-image:
name: Build Images
runs-on: ubuntu-latest
environment: production

permissions:
contents: read
packages: write

outputs:
imageid: steps.build-push-image.imageid

steps:
- name: Setup QEMU
uses: docker/setup-qemu-action@v3

- name: Setup Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set container image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

tags: |
type=ref,event=branch

- name: Build and push container image
id: build-push-image
uses: docker/build-push-action@v5
with:
context: "{{defaultContext}}"
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}

deploy:
name: Deploy
runs-on: ubuntu-latest
environment: production

permissions:
contents: read
id-token: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: us-east-1
role-to-assume: arn:aws:iam::670261699094:role/Github-AWS-OIDC
role-session-name: gh-ci-ecs-deploy

- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition tasking-manager --query taskDefinition > task-definition.json

- name: Task definition rendition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ needs.build-push-image.outputs.imageid }}

- name: Deploy task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true



77 changes: 72 additions & 5 deletions backend/models/postgis/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,82 @@ def delete(self):

@staticmethod
def get_open_for_task(project_id, task_id, local_session=None):
"""
Retrieve the open TaskInvalidationHistory entry for the given project and task.

This method also handles a suspected concurrency issue by managing cases where multiple entries
are created when only one should exist. If multiple entries are found, it
recursively handles and closes duplicate entries to ensure only a single entry
remains open.

Args:
project_id (int): The ID of the project.
task_id (int): The ID of the task.
local_session (Session, optional): The SQLAlchemy session to use for the query.
If not provided, a default session is used.

Returns:
TaskInvalidationHistory or None: The open TaskInvalidationHistory entry, or
None if no open entry is found.

Raises:
None: This method handles the MultipleResultsFound exception internally.
"""
try:
if local_session:
return (
local_session.query(TaskInvalidationHistory)
.filter_by(task_id=task_id, project_id=project_id, is_closed=False)
.one_or_none()
)
return TaskInvalidationHistory.query.filter_by(
task_id=task_id, project_id=project_id, is_closed=False
).one_or_none()

except MultipleResultsFound:
TaskInvalidationHistory.close_duplicate_invalidation_history_rows(
project_id, task_id, local_session
)

return TaskInvalidationHistory.get_open_for_task(
project_id, task_id, local_session
)

@staticmethod
def close_duplicate_invalidation_history_rows(
project_id: int, task_id: int, local_session=None
):
"""
Closes duplicate TaskInvalidationHistory entries except for the latest one for the given project and task.

Args:
project_id (int): The ID of the project.
task_id (int): The ID of the task.
local_session (Session, optional): The SQLAlchemy session to use for the query.
If not provided, a default session is used.
"""
if local_session:
return (
oldest_dupe = (
local_session.query(TaskInvalidationHistory)
.filter_by(task_id=task_id, project_id=project_id, is_closed=False)
.one_or_none()
.order_by(TaskInvalidationHistory.id.asc())
.first()
)
return TaskInvalidationHistory.query.filter_by(
task_id=task_id, project_id=project_id, is_closed=False
).one_or_none()
else:
oldest_dupe = (
TaskInvalidationHistory.query.filter_by(
task_id=task_id, project_id=project_id, is_closed=False
)
.order_by(TaskInvalidationHistory.id.asc())
.first()
)

if oldest_dupe:
oldest_dupe.is_closed = True
if local_session:
local_session.commit()
else:
db.session.commit()

@staticmethod
def close_all_for_task(project_id, task_id, local_session=None):
Expand Down
1 change: 1 addition & 0 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ TM_ORG_GITHUB=https://github.com/hotosm
# Information about the OSM server - Customize your server here
# By default, it's the public OpenStreetMap.org server
OSM_SERVER_URL=https://www.openstreetmap.org
OSM_SERVER_API_URL=https://api.openstreetmap.org
OSM_NOMINATIM_SERVER_URL=https://nominatim.openstreetmap.org
OSM_REGISTER_URL=https://www.openstreetmap.org/user/new

Expand Down
1 change: 1 addition & 0 deletions frontend/.env.expand
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ REACT_APP_OSM_CLIENT_ID=$TM_CLIENT_ID
REACT_APP_OSM_CLIENT_SECRET=$TM_CLIENT_SECRET
REACT_APP_OSM_REDIRECT_URI=$TM_REDIRECT_URI
REACT_APP_OSM_SERVER_URL=$OSM_SERVER_URL
REACT_APP_OSM_SERVER_API_URL=$OSM_SERVER_API_URL
REACT_APP_TM_ORG_NAME=$TM_ORG_NAME
REACT_APP_OSM_REGISTER_URL=$OSM_REGISTER_URL
REACT_APP_ID_EDITOR_URL=$ID_EDITOR_URL
Expand Down
4 changes: 2 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
"private": false,
"dependencies": {
"@hotosm/id": "^2.21.1",
"@hotosm/underpass-ui": "^0.0.4",
"@hotosm/iso-countries-languages": "^1.1.2",
"@hotosm/underpass-ui": "^0.0.4",
"@mapbox/mapbox-gl-draw": "^1.4.3",
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
"@mapbox/mapbox-gl-language": "^0.10.1",
"@placemarkio/geo-viewport": "^1.0.2",
"@rapideditor/rapid": "^2.1.1",
"@rapideditor/rapid": "^2.3.1",
"@sentry/react": "^7.102.0",
"@tanstack/react-query": "^4.29.7",
"@tanstack/react-query-devtools": "^4.29.7",
Expand Down
16 changes: 9 additions & 7 deletions frontend/src/components/projectCreate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,15 @@ const ProjectCreate = () => {

const handleCreate = useCallback(
(cloneProjectData) => {
if (!metadata.projectName.trim()) {
setErr({ error: true, message: intl.formatMessage(messages.noProjectName) });
throw new Error('Missing project name.');
}
if (!/^[a-zA-Z]/.test(metadata.projectName)) {
setErr({ error: true, message: intl.formatMessage(messages.projectNameValidationError) });
throw new Error('Project name validation error.');
if (!cloneProjectData.name) {
if (!metadata.projectName.trim()) {
setErr({ error: true, message: intl.formatMessage(messages.noProjectName) });
throw new Error('Missing project name.');
}
if (!/^[a-zA-Z]/.test(metadata.projectName)) {
setErr({ error: true, message: intl.formatMessage(messages.projectNameValidationError) });
throw new Error('Project name validation error.');
}
}
if (!metadata.geom) {
setErr({ error: true, message: intl.formatMessage(messages.noGeometry) });
Expand Down
32 changes: 21 additions & 11 deletions frontend/src/components/rapidEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { useDispatch, useSelector } from 'react-redux';

import PropTypes from 'prop-types';

import { OSM_CLIENT_ID, OSM_CLIENT_SECRET, OSM_REDIRECT_URI, OSM_SERVER_URL } from '../config';
import {
OSM_CLIENT_ID,
OSM_CLIENT_SECRET,
OSM_REDIRECT_URI,
OSM_SERVER_API_URL,
OSM_SERVER_URL,
} from '../config';
import { types } from '../store/actions/editor';

// We import from a CDN using a SEMVER minor version range
Expand Down Expand Up @@ -190,6 +196,7 @@ function RapidEditor({
context.apiConnections = [
{
url: OSM_SERVER_URL,
apiUrl: OSM_SERVER_API_URL,
client_id: OSM_CLIENT_ID,
client_secret: OSM_CLIENT_SECRET,
redirect_uri: OSM_REDIRECT_URI,
Expand Down Expand Up @@ -223,7 +230,7 @@ function RapidEditor({

useEffect(() => {
const containerRoot = document.getElementById('rapid-container-root');
const editListener = () => updateDisableState(setDisable, context.systems.edits);
const editListener = () => updateDisableState(setDisable, context.systems.editor);
if (context && dom) {
containerRoot.appendChild(dom);
// init the ui or restart if it was loaded previously
Expand All @@ -239,35 +246,38 @@ function RapidEditor({

/* Perform tasks after Rapid has started up */
promise.then(() => {
/* Keep track of edits */
const editSystem = context.systems.edits;
if (context?.systems?.editor) {
/* Keep track of edits */
const editSystem = context.systems.editor;

editSystem.on('change', editListener);
editSystem.on('reset', editListener);
editSystem.on('stablechange', editListener);
editSystem.on('reset', editListener);
}
});
}
return () => {
if (containerRoot?.childNodes && dom in containerRoot.childNodes) {
document.getElementById('rapid-container-root')?.removeChild(dom);
}
if (context?.systems?.edits) {
const editSystem = context.systems.edits;
editSystem.off('change', editListener);
if (context?.systems?.editor) {
const editSystem = context.systems.editor;
editSystem.off('stablechange', editListener);
editSystem.off('reset', editListener);
}
};
}, [dom, context, setDisable]);

useEffect(() => {
if (context) {
return () => context.save();
if (context?.systems?.editor) {
return () => context.systems.editor.saveBackup();
}
}, [context]);

useEffect(() => {
if (context && session) {
context.preauth = {
url: OSM_SERVER_URL,
apiUrl: OSM_SERVER_API_URL,
client_id: OSM_CLIENT_ID,
client_secret: OSM_CLIENT_SECRET,
redirect_uri: OSM_REDIRECT_URI,
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export const SENTRY_FRONTEND_DSN = process.env.REACT_APP_SENTRY_FRONTEND_DSN;
// OSM API and Editor URLs
export const OSM_SERVER_URL =
process.env.REACT_APP_OSM_SERVER_URL || 'https://www.openstreetmap.org';
export const OSM_SERVER_API_URL =
process.env.REACT_APP_OSM_SERVER_API_URL || 'https://api.openstreetmap.org';
export const ID_EDITOR_URL =
process.env.REACT_APP_ID_EDITOR_URL || 'https://www.openstreetmap.org/edit?editor=id&';
export const POTLATCH2_EDITOR_URL =
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/network/genericJSONRequest.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { handleErrors } from '../utils/promise';
import { API_URL } from '../config';


/**
* Fetch data from an external JSON API
* @param {string} url The url to fetch from
Expand All @@ -10,7 +9,7 @@ import { API_URL } from '../config';
*/
export function fetchExternalJSONAPI(url, init = {}): Promise<*> {
if (!init.headers) {
init.headers = {'Content-Type': 'application/json'};
init.headers = { 'Content-Type': 'application/json' };
}
init.headers['Content-Type'] = 'application/json';

Expand Down
Loading
Loading