Skip to content

Commit

Permalink
fix(backend): parsing of hashtags (allow separation by comma, space, …
Browse files Browse the repository at this point in the history
…or semicolon) (#1607)

* fix(frontend,backend): fix parsing of hashtags into array, allow comma separated

* fix(backend): project update endpoint update all fields

* test(backend): fix tests using hashtags list --> string
  • Loading branch information
spwoodcock authored Jul 1, 2024
1 parent d2790de commit d42de85
Show file tree
Hide file tree
Showing 8 changed files with 39 additions and 39 deletions.
24 changes: 10 additions & 14 deletions src/backend/app/projects/project_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ async def partial_update_project_info(
return await convert_to_app_project(db_project)


async def update_project_info(
async def update_project_with_project_info(
db: Session,
project_metadata: project_schemas.ProjectUpdate,
db_project: db_models.DbProject,
Expand All @@ -278,24 +278,20 @@ async def update_project_info(
detail="No project info passed in",
)

# Project meta information
project_info = project_metadata.project_info

# Update author of the project
db_project.author_id = db_user.id
db_project.project_name_prefix = project_metadata.project_name_prefix
for key, value in project_metadata.model_dump(
exclude=["project_info", "outline_geojson"]
).items():
setattr(db_project, key, value)

# get project info
db_project_info = await get_project_info_by_id(db, db_project.id)

# Update projects meta information (name, descriptions)
if db_project and db_project_info:
db_project_info.name = project_info.name
db_project_info.short_description = project_info.short_description
db_project_info.description = project_info.description
# Update project's meta information (name, descriptions)
if db_project_info:
for key, value in project_info.model_dump(exclude_unset=True).items():
setattr(db_project_info, key, value)

db.commit()
db.refresh(db_project)
db.refresh(db_project_info)

return await convert_to_app_project(db_project)

Expand Down
2 changes: 1 addition & 1 deletion src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ async def update_project(
Raises:
- HTTPException with 404 status code if project not found
"""
project = await project_crud.update_project_info(
project = await project_crud.update_project_with_project_info(
db, project_info, project_user_dict["project"], project_user_dict["user"]
)
if not project:
Expand Down
23 changes: 18 additions & 5 deletions src/backend/app/projects/project_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class ProjectIn(BaseModel):
xform_category: str
custom_tms_url: Optional[str] = None
organisation_id: Optional[int] = None
hashtags: Optional[List[str]] = None
hashtags: Optional[str] = None
task_split_type: Optional[TaskSplitType] = None
task_split_dimension: Optional[int] = None
task_num_buildings: Optional[int] = None
Expand Down Expand Up @@ -177,11 +177,24 @@ def project_name_prefix(self) -> str:

@field_validator("hashtags", mode="after")
@classmethod
def prepend_hash_to_tags(cls, hashtags: List[str]) -> Optional[List[str]]:
"""Add '#' to hashtag if missing. Also added default '#FMTM'."""
def validate_hashtags(cls, hashtags: Optional[str]) -> List[str]:
"""Validate hashtags.
- Receives a string and parsed as a list of tags.
- Commas or semicolons are replaced with spaces before splitting.
- Add '#' to hashtag if missing.
- Also add default '#FMTM' tag.
"""
if hashtags is None:
return ["#FMTM"]

hashtags = hashtags.replace(",", " ").replace(";", " ")
hashtags_list = hashtags.split()

# Add '#' to hashtag strings if missing
hashtags_with_hash = [
f"#{hashtag}" if hashtag and not hashtag.startswith("#") else hashtag
for hashtag in hashtags
for hashtag in hashtags_list
]

if "#FMTM" not in hashtags_with_hash:
Expand Down Expand Up @@ -233,7 +246,7 @@ def project_name_prefix(self) -> str:
return self.name.replace(" ", "_").lower()


class ProjectUpdate(ProjectIn):
class ProjectUpdate(ProjectUpload):
"""Update project."""

pass
Expand Down
2 changes: 1 addition & 1 deletion src/backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ async def project(db, admin_user, organisation):
odk_central_url=os.getenv("ODK_CENTRAL_URL"),
odk_central_user=os.getenv("ODK_CENTRAL_USER"),
odk_central_password=os.getenv("ODK_CENTRAL_PASSWD"),
hashtags=["hot-fmtm"],
hashtags="hashtag1 hashtag2",
outline_geojson=Polygon(
type="Polygon",
coordinates=[
Expand Down
9 changes: 6 additions & 3 deletions src/backend/tests/test_projects_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ async def test_create_project(client, admin_user, organisation):
"description": "test",
},
"xform_category": "buildings",
"hashtags": ["#FMTM"],
"hashtags": "#FMTM",
"outline_geojson": {
"coordinates": [
[
Expand Down Expand Up @@ -283,8 +283,8 @@ async def test_update_project(client, admin_user, project):
"short_description": "updated short description",
"description": "updated description",
},
"xform_category": "buildings",
"hashtags": ["#FMTM"],
"xform_category": "healthcare",
"hashtags": "#FMTM anothertag",
"outline_geojson": {
"coordinates": [
[
Expand Down Expand Up @@ -320,6 +320,9 @@ async def test_update_project(client, admin_user, project):
== updated_project_data["project_info"]["description"]
)

assert response_data["xform_category"] == "healthcare"
assert response_data["hashtags"] == ["#FMTM", "#anothertag"]


async def test_project_summaries(client, project):
"""Test read project summaries."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,6 @@ const ProjectDetailsForm = ({ flag }) => {
};
}, []);

// Checks if hashtag value starts with hotosm-fmtm'
const handleHashtagOnChange = (e) => {
let enteredText = e.target.value;
handleCustomChange('hashtags', enteredText);
};

const handleInputChanges = (e) => {
handleChange(e);
dispatch(CreateProjectActions.SetIsUnsavedChanges(true));
Expand Down Expand Up @@ -240,7 +234,7 @@ const ProjectDetailsForm = ({ flag }) => {
label="Hashtags"
value={values?.hashtags}
onChange={(e) => {
handleHashtagOnChange(e);
handleCustomChange('hashtags', e.target.value);
}}
fieldType="text"
errorMsg={errors.hashtag}
Expand Down
8 changes: 1 addition & 7 deletions src/frontend/src/components/createnewproject/SplitTasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,6 @@ const SplitTasks = ({ flag, geojsonFile, setGeojsonFile, customDataExtractUpload
dispatch(CreateProjectActions.SetIsUnsavedChanges(false));

dispatch(CreateProjectActions.SetIndividualProjectDetailsData(formValues));
const hashtags = projectDetails.hashtags;
const arrayHashtag = hashtags
?.split('#')
.map((item) => item.trim())
.filter(Boolean);

// Project POST data
let projectData = {
project_info: {
Expand All @@ -116,7 +110,7 @@ const SplitTasks = ({ flag, geojsonFile, setGeojsonFile, customDataExtractUpload
task_split_type: splitTasksSelection,
form_ways: projectDetails.formWays,
// "uploaded_form": projectDetails.uploaded_form,
hashtags: arrayHashtag,
hashtags: projectDetails.hashtags,
data_extract_url: projectDetails.data_extract_url,
custom_tms_url: projectDetails.custom_tms_url,
};
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/store/types/ICreateProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ type EditProjectResponseTypes = {
outline_geojson: GeoJSONFeatureTypes;
tasks: ProjectTaskTypes[];
xform_category: string;
hashtags: string[];
hashtags: string;
};
export type EditProjectDetailsTypes = {
name: string;
Expand Down

0 comments on commit d42de85

Please sign in to comment.