Skip to content

Commit

Permalink
Added endpoints for work status updates (#1177)
Browse files Browse the repository at this point in the history
  • Loading branch information
saravanpa-aot authored Nov 7, 2023
1 parent a1909d7 commit 1e382ba
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 5 deletions.
65 changes: 64 additions & 1 deletion epictrack-api/src/api/resources/work_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
from flask import jsonify
from flask_restx import Namespace, Resource, cors

from api.schemas import request as req
from api.schemas import response as res
from api.services import WorkIssuesService
from api.utils import auth
from api.utils import auth, profiletime
from api.utils.util import cors_preflight

API = Namespace("work-issues", description="Work Issues")
Expand All @@ -37,3 +38,65 @@ def get(work_id):
"""Return all active works."""
works = WorkIssuesService.find_all_work_issues(work_id)
return jsonify(res.WorkIssuesResponseSchema(many=True).dump(works)), HTTPStatus.OK

@staticmethod
@cors.crossdomain(origin="*")
@auth.require
@profiletime
def post(work_id):
"""Create new work status"""
request_dict = req.WorkIssuesParameterSchema().load(API.payload)
work_issues = WorkIssuesService.create_work_issue_and_updates(work_id, request_dict)
return res.WorkIssuesResponseSchema().dump(work_issues), HTTPStatus.CREATED


@API.route("/<int:issue_id>", methods=["PUT"])
class IssueUpdateEdits(Resource):
"""Endpoint resource to manage updates/edits for a specific issue and its description."""

@staticmethod
@cors.crossdomain(origin="*")
@auth.require
@profiletime
def put(work_id, issue_id):
"""Create a new update for the specified issue."""
request_dict = req.WorkIssuesUpdateSchema().load(API.payload)
work_issues = WorkIssuesService.edit_issue_update(work_id, issue_id, request_dict)
return res.WorkIssuesResponseSchema().dump(work_issues), HTTPStatus.CREATED


@API.route("/<int:issue_id>/issue_update", methods=["POST"])
class WorkIssueUpdate(Resource):
"""Endpoint resource to manage updates for a specific issue."""

@staticmethod
@cors.crossdomain(origin="*")
@auth.require
@profiletime
def post(work_id, issue_id):
"""Create a new update for the specified issue."""
description_data = API.payload.get('description_data', None)
if not description_data:
return jsonify({'error': 'description_data is required'}), HTTPStatus.BAD_REQUEST
work_issues = WorkIssuesService.add_work_issue_update(work_id, issue_id, description_data)
return res.WorkIssuesResponseSchema().dump(work_issues), HTTPStatus.CREATED


@cors_preflight("PATCH")
@API.route("/<int:issue_id>/approve", methods=["PATCH", "OPTIONS"])
class ApproveIssues(Resource):
"""Endpoint resource to manage approving of work status."""

@staticmethod
@cors.crossdomain(origin="*")
@auth.require
@profiletime
def patch(work_id, issue_id):
"""Approve a work status."""
existing_work_issues = WorkIssuesService.find_work_issue_by_id(work_id, issue_id)
if existing_work_issues is None:
return {"message": "Work issues not found"}, HTTPStatus.NOT_FOUND

approved_work_issues = WorkIssuesService.approve_work_issues(existing_work_issues)

return res.WorkIssuesResponseSchema().dump(approved_work_issues), HTTPStatus.OK
56 changes: 54 additions & 2 deletions epictrack-api/src/api/resources/work_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
from flask import jsonify
from flask_restx import Namespace, Resource, cors

from api.schemas import request as req
from api.schemas import response as res
from api.services import WorkStatusService
from api.utils import auth
from api.utils import auth, profiletime
from api.utils.util import cors_preflight

API = Namespace("work-statuses", description="Work Statuses")
Expand All @@ -35,5 +36,56 @@ class WorkStatus(Resource):
@auth.require
def get(work_id):
"""Return all active works."""
works = WorkStatusService.find_all_works_tatus(work_id)
works = WorkStatusService.find_all_work_status(work_id)
return jsonify(res.WorkStatusResponseSchema(many=True).dump(works)), HTTPStatus.OK

@staticmethod
@cors.crossdomain(origin="*")
@auth.require
@profiletime
def post(work_id):
"""Create new work status"""
request_dict = req.WorkStatusParameterSchema().load(API.payload)
work_status = WorkStatusService.create_work_status(work_id, request_dict)
return res.WorkStatusResponseSchema().dump(work_status), HTTPStatus.CREATED


@cors_preflight("GET, PUT")
@API.route("/<int:status_id>", methods=["GET", "PUT", "OPTIONS"])
class Status(Resource):
"""Endpoint resource to manage a work status."""

@staticmethod
@cors.crossdomain(origin="*")
@auth.require
@profiletime
def put(work_id, status_id):
"""Update work status"""
request_dict = req.WorkStatusParameterSchema().load(API.payload)
existing_work_status = WorkStatusService.find_work_status_by_id(work_id, status_id)
if existing_work_status is None:
return {"message": "Work status not found"}, HTTPStatus.NOT_FOUND

updated_work_status = WorkStatusService.update_work_status(existing_work_status, request_dict)

return res.WorkStatusResponseSchema().dump(updated_work_status), HTTPStatus.OK


@cors_preflight("PATCH")
@API.route("/<int:status_id>/approve", methods=["PATCH", "OPTIONS"])
class ApproveStatus(Resource):
"""Endpoint resource to manage approving of work status."""

@staticmethod
@cors.crossdomain(origin="*")
@auth.require
@profiletime
def patch(work_id, status_id):
"""Approve a work status."""
existing_work_status = WorkStatusService.find_work_status_by_id(work_id, status_id)
if existing_work_status is None:
return {"message": "Work status not found"}, HTTPStatus.NOT_FOUND

approved_work_status = WorkStatusService.approve_work_status(existing_work_status)

return res.WorkStatusResponseSchema().dump(approved_work_status), HTTPStatus.OK
3 changes: 2 additions & 1 deletion epictrack-api/src/api/schemas/request/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@
from .work_request import (
WorkBodyParameterSchema, WorkExistenceQueryParamSchema, WorkFirstNationImportBodyParamSchema,
WorkFirstNationNotesBodySchema, WorkIdPathParameterSchema, WorkIdPhaseIdPathParameterSchema,
WorkPlanDownloadQueryParamSchema, WorkTypeIdQueryParamSchema)
WorkPlanDownloadQueryParamSchema, WorkTypeIdQueryParamSchema, WorkStatusParameterSchema,
WorkIssuesParameterSchema, WorkIssuesUpdateSchema)
from .act_section_request import ActSectionQueryParameterSchema
80 changes: 80 additions & 0 deletions epictrack-api/src/api/schemas/request/work_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,83 @@ class WorkFirstNationImportBodyParamSchema(RequestBodyParameterSchema):
required=True,
validate=validate.Length(min=1),
)


class WorkStatusParameterSchema(RequestBodyParameterSchema):
"""Work status request body schema"""

description = fields.Str(
metadata={"description": "description of status"},
validate=validate.Length(max=500),
required=True,
)

notes = fields.Str(
metadata={"description": "Notes for the work status "},
allow_none=True
)

posted_date = fields.DateTime(
metadata={"description": "posted date for the work status"}, required=False
)


class WorkIssuesParameterSchema(RequestBodyParameterSchema):
"""Work status request body schema"""

title = fields.Str(
metadata={"description": "Title Of the issue"},
validate=validate.Length(max=50),
required=True,
)

is_active: bool = fields.Bool(
default=True,
description="Flag indicating whether the issue is active",
)

is_high_priority: bool = fields.Bool(
default=False,
description="Flag indicating whether the issue is of high priority",
)

start_date = fields.DateTime(
metadata={"description": "Start date for the issue"}, required=False
)

expected_resolution_date = fields.DateTime(
metadata={"description": "Expected Resolution date for the issue"}, required=False
)

updates = fields.List(
fields.Str,
metadata={"description": "List of updates for the issue"},
required=False,
)


class WorkIssuesUpdateSchema(WorkIssuesParameterSchema):
"""Work status update request body schema for PUT requests"""

title = fields.Str(
metadata={"description": "Title Of the issue"},
validate=validate.Length(max=50),
required=False,
)

updates = fields.List(
fields.Dict(
description=fields.Str(
metadata={"description": "Description of the update"},
required=True
),
id=fields.Int(
metadata={"description": "ID of the update"},
required=True
),
metadata={"description": "List of updates for the issue with IDs"},
required=True
),
metadata={"description": "List of updates for the issue with IDs"},
required=False,
)
92 changes: 92 additions & 0 deletions epictrack-api/src/api/services/work_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Service to manage Work status."""
from typing import Dict, List

from api.models import WorkIssueUpdates as WorkIssueUpdatesModel
from api.exceptions import ResourceNotFoundError
from api.models import WorkIssues as WorkIssuesModel
from api.utils import TokenInfo


class WorkIssuesService: # pylint: disable=too-many-public-methods
Expand All @@ -26,3 +30,91 @@ def find_all_work_issues(cls, work_id):
"""Find all issues related to a work"""
work_issues = WorkIssuesModel.find_by_params({"work_id": work_id})
return work_issues

@classmethod
def find_work_issue_by_id(cls, work_id, issue_id):
"""Find all status related to a work"""
results = WorkIssuesModel.find_by_params({"work_id": work_id, "id": issue_id})
return results[0] if results else None

@classmethod
def create_work_issue_and_updates(cls, work_id, issue_data: Dict):
"""Create a new work issue and its updates."""
updates = issue_data.pop('updates', [])

new_work_issue = WorkIssuesModel(**issue_data,
work_id=work_id
)
new_work_issue.save()

for update_description in updates:
new_update = WorkIssueUpdatesModel(description=update_description)
new_update.work_issue_id = new_work_issue.id
new_update.save()

return new_work_issue

@classmethod
def add_work_issue_update(cls, work_id, issue_id, description_data: List[str]):
"""Add a new description to the existing Issue."""
work_issues = WorkIssuesModel.find_by_params({"work_id": work_id,
"id": issue_id})

if not work_issues:
raise ResourceNotFoundError("Work Issues not found")

for description in description_data:
new_update = WorkIssueUpdatesModel(description=description)
new_update.work_issue_id = work_issues[0].id
new_update.save()

return WorkIssuesModel.find_by_id(issue_id)

@classmethod
def approve_work_issues(cls, work_issues):
"""Approve a work status."""
if work_issues.is_approved:
return work_issues

work_issues.is_approved = True
work_issues.approved_by = TokenInfo.get_username()

work_issues.save()

return work_issues

@classmethod
def edit_issue_update(cls, work_id, issue_id, issue_data):
"""Update an existing work issue, and save it only if there are changes."""
work_issue = WorkIssuesService.find_work_issue_by_id(work_id, issue_id)

if not work_issue:
raise ResourceNotFoundError("Work issue doesnt exist")

updates = issue_data.pop('updates', [])

# Create a flag to track changes on work_issues
has_changes_to_work_issue = False

for key, value in issue_data.items():
if getattr(work_issue, key) != value:
setattr(work_issue, key, value)
has_changes_to_work_issue = True

if updates:
for update_description in updates:
description_id = update_description.get('id')
if not description_id:
raise ResourceNotFoundError("Issue Description doesnt exist")
if (description_id := update_description.get('id')) is not None:
issue_update_model: WorkIssueUpdatesModel = WorkIssueUpdatesModel.find_by_id(description_id)
if not issue_update_model:
raise ResourceNotFoundError("Issue Description doesnt exist")
issue_update_model.description = update_description.get('description')
issue_update_model.flush()

if has_changes_to_work_issue:
work_issue.save() # Save the updated work issue only if there are changes
else:
issue_update_model.commit()
return work_issue
Loading

0 comments on commit 1e382ba

Please sign in to comment.