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

Feat partner link #108

Merged
merged 43 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
8b5ac4b
Docker: Dev setup: Use yarn instead of npm in tm-frontend
bshankar Jun 25, 2024
f2f694d
Docs: Fix Oauth 2 redirect URL
bshankar Jun 26, 2024
f42623f
Docs: Give tasking-manager.env as default ENV for compose
bshankar Jun 26, 2024
df55d00
Add migrations for linking partners with projects
bshankar Jun 27, 2024
a5f6e95
Fix: project_partners table doesn't need ended_on column
bshankar Jun 28, 2024
5dc454a
Add DTOs for project_partners
bshankar Jun 28, 2024
a66488d
Add models for project_partners
bshankar Jun 28, 2024
c32b58d
Remove update time range action (for now)
bshankar Jul 1, 2024
1f4250d
Update migrations for new project-partner spec
bshankar Jul 3, 2024
97b31ba
Update DTOs for new project-partner spec
bshankar Jul 3, 2024
f7e1032
Update postgis models for new project-partner spec
bshankar Jul 3, 2024
24646eb
Fix: Rename project-partners table -> project-partnerships
bshankar Jul 3, 2024
c4d8b28
Fix: Add ended_on column for partnerships
bshankar Jul 3, 2024
c232ecc
Fix: Remove all relationships
bshankar Jul 3, 2024
5477a2b
Fix: Add missing column ended_on for project partnerships
bshankar Jul 3, 2024
e4682cb
Implement API to retrieve partnership by ID
bshankar Jul 4, 2024
dad2320
Implement API to add partners to projects
bshankar Jul 4, 2024
01495cb
Fix: Only admins can link projects with partners
bshankar Jul 4, 2024
09060d7
Implement patch and delete of project partner links
bshankar Jul 4, 2024
7ba3ba4
Implement API to get partners associated with a project
bshankar Jul 5, 2024
61b58d2
ProjectPartnerActions rename: START -> CREATE, END -> DELETE
bshankar Jul 8, 2024
fd281ae
Fix: action column in project_partners_history is integer
bshankar Jul 8, 2024
022a277
Log changes to project partner associations in DB
bshankar Jul 8, 2024
61e8186
Validate the time range of a partnership: start <= end date
bshankar Jul 8, 2024
6fd9040
Fix: Docker dev setup: Frontend hot reloading
bshankar Jul 10, 2024
02badb8
chore: introduce partners section to link partners to projects
VinayakRugvedi Jul 12, 2024
0737818
chore: add circle exclamation and circle minus icons
VinayakRugvedi Jul 12, 2024
b91c6f7
chore: update messages for partners linking form, listing, remove, an…
VinayakRugvedi Jul 12, 2024
b455d14
feat: create partners input form to link a partner to the project
VinayakRugvedi Jul 12, 2024
7ef6fc2
feat: show listing of linked partners and integrate remove and update…
VinayakRugvedi Jul 12, 2024
1901678
Fix: Optimize docker caching of frontend build
bshankar Jul 12, 2024
960d6ee
Fix: docker-compose.override.sample.yml: DB uses 5433 port
bshankar Jul 12, 2024
21d79f0
docker-compose.override.sample.yml: Backend reload
bshankar Jul 12, 2024
a757656
Fix: docker-compose.override.sample.yml: Port 8000: frontend
bshankar Jul 12, 2024
ebeaae8
Fix: Linting errors and merge conflicts
bshankar Jul 12, 2024
6c41221
Fix project_partner_dto.py: line too long error
bshankar Jul 12, 2024
03c09b2
Fix: Allow managers of a project to edit partnerships
bshankar Jul 12, 2024
630581c
chore: add proptypes and minor styling updates
VinayakRugvedi Jul 15, 2024
36bb1d1
chore: move partners listing into a separate file
VinayakRugvedi Jul 15, 2024
7183fc4
chore: eslint fix for react-hooks/exhaustive-deps
VinayakRugvedi Jul 15, 2024
5994479
chore: add circleMinus and circleExclamation icons to common export
VinayakRugvedi Jul 16, 2024
a3034c7
Fix: Sync partnership migrations with develop branch
bshankar Jul 22, 2024
710b034
Fix: Sonarcloud error: yarn install --ignore-scripts
bshankar Jul 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ def add_api_endpoints(app):
)

from backend.api.projects.favorites import ProjectsFavoritesAPI
from backend.api.projects.partnerships import (
ProjectPartnershipsRestApi,
PartnersByProjectAPI,
)

# Tasks API import
from backend.api.tasks.resources import (
Expand Down Expand Up @@ -470,6 +474,25 @@ def add_api_endpoints(app):
ProjectsStatisticsQueriesPopularAPI, format_url("projects/queries/popular/")
)

api.add_resource(
ProjectPartnershipsRestApi,
format_url("projects/partnerships/<int:partnership_id>/"),
methods=["GET", "PATCH", "DELETE"],
)

api.add_resource(
ProjectPartnershipsRestApi,
format_url("projects/partnerships/"),
endpoint="create_partnership",
methods=["POST"],
)

api.add_resource(
PartnersByProjectAPI,
format_url("/projects/<int:project_id>/partners"),
methods=["GET"],
)

api.add_resource(
ProjectsTeamsAPI,
format_url("projects/<int:project_id>/teams/"),
Expand Down
294 changes: 294 additions & 0 deletions backend/api/projects/partnerships.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
from flask_restful import Resource, request
from backend.services.project_partnership_service import ProjectPartnershipService
from backend.services.users.authentication_service import token_auth
from backend.services.project_admin_service import ProjectAdminService
from backend.models.dtos.project_partner_dto import (
ProjectPartnershipDTO,
ProjectPartnershipUpdateDTO,
)
from backend.models.postgis.utils import timestamp


class ProjectPartnershipsRestApi(Resource):
def get(self, partnership_id: int):
"""
Retrieves a Partnership by id
---
tags:
- projects
- partners
- partnerships
produces:
- application/json
parameters:
- name: partnership_id
in: path
description: Unique partnership ID
required: true
type: integer
default: 1
responses:
200:
description: Partnership found
404:
description: Partnership not found
500:
description: Internal Server Error
"""

partnership_dto = ProjectPartnershipService.get_partnership_as_dto(
partnership_id
)
return partnership_dto.to_primitive(), 200

@token_auth.login_required
def post(self):
"""Assign a partner to a project
---
tags:
- projects
- partners
- partnerships
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- in: body
name: body
required: true
description: JSON object for creating a partnership
schema:
properties:
projectId:
required: true
type: int
description: Unique project ID
default: 1
partnerId:
required: true
type: int
description: Unique partner ID
default: 1
startedOn:
type: date
description: The timestamp when the partner is added to a project. Defaults to current time.
default: "2017-04-11T12:38:49"
endedOn:
type: date
description: The timestamp when the partner ended their work on a project.
default: "2018-04-11T12:38:49"
responses:
201:
description: Partner project association created
400:
description: Ivalid dates or started_on was after ended_on
401:
description: Forbidden, if user is not a manager of this project
403:
description: Forbidden, if user is not authenticated
404:
description: Not found
500:
description: Internal Server Error
"""
partnership_dto = ProjectPartnershipDTO(request.get_json())

if not ProjectAdminService.is_user_action_permitted_on_project(
token_auth.current_user(), partnership_dto.project_id
):
return {
"Error": "User is not a manager of the project",
"SubCode": "UserPermissionError",
}, 401

if partnership_dto.started_on is None:
partnership_dto.started_on = timestamp()

partnership_dto = ProjectPartnershipDTO(request.get_json())
partnership_id = ProjectPartnershipService.create_partnership(
partnership_dto.project_id,
partnership_dto.partner_id,
partnership_dto.started_on,
partnership_dto.ended_on,
)
return (
{
"Success": "Partner {} assigned to project {}".format(
partnership_dto.partner_id, partnership_dto.project_id
),
"partnershipId": partnership_id,
},
201,
)

@staticmethod
@token_auth.login_required
def patch(partnership_id: int):
"""Update the time range for a partner project link
---
tags:
- projects
- partners
- partnerships
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- name: partnership_id
in: path
description: Unique partnership ID
required: true
type: integer
default: 1
- in: body
name: body
required: true
description: JSON object for creating a partnership
schema:
properties:
startedOn:
type: date
description: The timestamp when the partner is added to a project. Defaults to current time.
default: "2017-04-11T12:38:49"
endedOn:
type: date
description: The timestamp when the partner ended their work on a project.
default: "2018-04-11T12:38:49"
responses:
201:
description: Partner project association created
400:
description: Ivalid dates or started_on was after ended_on
401:
description: Forbidden, if user is not a manager of this project
403:
description: Forbidden, if user is not authenticated
404:
description: Not found
500:
description: Internal Server Error
"""
partnership_updates = ProjectPartnershipUpdateDTO(request.get_json())
partnership_dto = ProjectPartnershipService.get_partnership_as_dto(
partnership_id
)

if not ProjectAdminService.is_user_action_permitted_on_project(
token_auth.current_user(), partnership_dto.project_id
):
return {
"Error": "User is not a manager of the project",
"SubCode": "UserPermissionError",
}, 401

partnership = ProjectPartnershipService.update_partnership_time_range(
partnership_id,
partnership_updates.started_on,
partnership_updates.ended_on,
)

return (
{
"Success": "Updated time range. startedOn: {}, endedOn: {}".format(
partnership.started_on, partnership.ended_on
),
"startedOn": f"{partnership.started_on}",
"endedOn": f"{partnership.ended_on}",
},
200,
)

@staticmethod
@token_auth.login_required
def delete(partnership_id: int):
"""Deletes a link between a project and a partner
---
tags:
- projects
- partners
- partnerships
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- name: partnership_id
in: path
description: Unique partnership ID
required: true
type: integer
default: 1
responses:
201:
description: Partner project association created
401:
description: Forbidden, if user is not a manager of this project
403:
description: Forbidden, if user is not authenticated
404:
description: Not found
500:
description: Internal Server Error
"""
partnership_dto = ProjectPartnershipService.get_partnership_as_dto(
partnership_id
)

if not ProjectAdminService.is_user_action_permitted_on_project(
token_auth.current_user(), partnership_dto.project_id
):
return {
"Error": "User is not a manager of the project",
"SubCode": "UserPermissionError",
}, 401

ProjectPartnershipService.delete_partnership(partnership_id)
return (
{
"Success": "Partnership ID {} deleted".format(partnership_id),
},
200,
)


class PartnersByProjectAPI(Resource):
@staticmethod
def get(project_id: int):
"""
Retrieves the list of partners associated with a project
---
tags:
- projects
- partners
- partnerships
produces:
- application/json
parameters:
- name: project_id
in: path
description: Unique project ID
required: true
type: integer
default: 1
responses:
200:
description: List (possibly empty) of partners associated with this project_id
500:
description: Internal Server Error
"""
partnerships = ProjectPartnershipService.get_partnerships_by_project(project_id)
return {"partnerships": partnerships}, 200
Loading
Loading