From 7b3af8a56cb53fa5b444107aa8b94fa67fc76bf3 Mon Sep 17 00:00:00 2001 From: Salabh A N Date: Thu, 7 Dec 2023 11:51:38 +0530 Subject: [PATCH] #929 & #1410 #1410 - Refactor positions - Added new resource for handling Position related API endpoints - Updated `StaffForm` to use new position API #929 - Create Work - Project list order - Updated project list API to return `ListType` response based on query params - Projects are now sorted in `WorkForm` component, using natural sort algorithm --- epictrack-api/src/api/__init__.py | 6 ++- epictrack-api/src/api/resources/__init__.py | 2 + epictrack-api/src/api/resources/position.py | 43 +++++++++++++++++++ epictrack-api/src/api/resources/project.py | 14 +++--- .../src/api/schemas/response/__init__.py | 1 + .../schemas/response/list_type_response.py | 26 +++++++++++ epictrack-api/src/api/services/__init__.py | 9 ++-- epictrack-api/src/api/services/position.py | 24 +++++++++++ .../src/components/staff/StaffForm.tsx | 5 ++- .../src/components/work/WorkForm.tsx | 7 ++- epictrack-web/src/constants/api-endpoint.ts | 3 ++ .../src/services/positionService/index.ts | 10 +++++ .../services/projectService/projectService.ts | 4 +- 13 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 epictrack-api/src/api/resources/position.py create mode 100644 epictrack-api/src/api/schemas/response/list_type_response.py create mode 100644 epictrack-api/src/api/services/position.py create mode 100644 epictrack-web/src/services/positionService/index.ts diff --git a/epictrack-api/src/api/__init__.py b/epictrack-api/src/api/__init__.py index 609e2e6e3..26a67e92d 100644 --- a/epictrack-api/src/api/__init__.py +++ b/epictrack-api/src/api/__init__.py @@ -20,8 +20,7 @@ import os from http import HTTPStatus -from flask import Flask -from flask import current_app +from flask import Flask, current_app from flask_restx import abort from marshmallow import ValidationError @@ -79,6 +78,9 @@ def handle_error(err): return err.message, HTTPStatus.FORBIDDEN if isinstance(err, UnprocessableEntityError): return err.message, HTTPStatus.UNPROCESSABLE_ENTITY + if run_mode == "development": + # To get stacktrace in local development for internal server errors + raise err return 'Internal server error', HTTPStatus.INTERNAL_SERVER_ERROR register_shellcontext(app) diff --git a/epictrack-api/src/api/resources/__init__.py b/epictrack-api/src/api/resources/__init__.py index 49d92a64a..2c099d8e9 100644 --- a/epictrack-api/src/api/resources/__init__.py +++ b/epictrack-api/src/api/resources/__init__.py @@ -37,6 +37,7 @@ from .outcome import API as OUTCOME_API from .outcome_configuration import API as OUTCOME_CONFIGURATION_API from .phase import API as PHASE_API +from .position import API as POSITION_API from .project import API as PROJECTS_API from .proponent import API as PROPONENT_API from .reminder_configuration import API as REMINDER_CONFIGURATION_API @@ -110,3 +111,4 @@ API.add_namespace(WORK_STATUS_API, path='/work//statuses') API.add_namespace(WORK_ISSUES_API, path='/work//issues') API.add_namespace(SPECIAL_FIELD_API, path='/special-fields') +API.add_namespace(POSITION_API, path='/positions') diff --git a/epictrack-api/src/api/resources/position.py b/epictrack-api/src/api/resources/position.py new file mode 100644 index 000000000..edb19b636 --- /dev/null +++ b/epictrack-api/src/api/resources/position.py @@ -0,0 +1,43 @@ +# Copyright © 2019 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Resource for Position endpoints.""" +from http import HTTPStatus + +from flask import jsonify +from flask_restx import Namespace, Resource, cors + +from api.schemas import response as res +from api.services.position import PositionService +from api.utils import auth, constants, profiletime +from api.utils.caching import AppCache +from api.utils.util import cors_preflight + + +API = Namespace('positions', description='Positions') + + +@cors_preflight('GET') +@API.route('', methods=['GET', 'OPTIONS']) +class Postions(Resource): + """Endpoints for the Positions""" + + @staticmethod + @cors.crossdomain(origin='*') + @auth.require + @AppCache.cache.cached(timeout=constants.CACHE_DAY_TIMEOUT, query_string=True) + @profiletime + def get(): + """Return all positions.""" + positions = PositionService.find_all() + return jsonify(res.ListTypeResponseSchema(many=True).dump(positions)), HTTPStatus.OK diff --git a/epictrack-api/src/api/resources/project.py b/epictrack-api/src/api/resources/project.py index 37049b830..d3fa824b2 100644 --- a/epictrack-api/src/api/resources/project.py +++ b/epictrack-api/src/api/resources/project.py @@ -40,10 +40,12 @@ class Projects(Resource): def get(): """Return all projects.""" projects = ProjectService.find_all() - return ( - jsonify(res.ProjectResponseSchema(many=True).dump(projects)), - HTTPStatus.OK, - ) + return_type = request.args.get("return_type", None) + if return_type == "list_type": + schema = res.ListTypeResponseSchema(many=True) + else: + schema = res.ProjectResponseSchema(many=True) + return jsonify(schema.dump(projects)), HTTPStatus.OK @staticmethod @cors.crossdomain(origin="*") @@ -201,5 +203,7 @@ class ProjectAbbreviation(Resource): def post(): """Create new project abbreviation""" request_json = req.ProjectAbbreviationParameterSchema().load(API.payload) - project_abbreviation = ProjectService.create_project_abbreviation(request_json.get("name")) + project_abbreviation = ProjectService.create_project_abbreviation( + request_json.get("name") + ) return project_abbreviation, HTTPStatus.CREATED diff --git a/epictrack-api/src/api/schemas/response/__init__.py b/epictrack-api/src/api/schemas/response/__init__.py index 7b06aaa0f..0e90f805e 100644 --- a/epictrack-api/src/api/schemas/response/__init__.py +++ b/epictrack-api/src/api/schemas/response/__init__.py @@ -18,6 +18,7 @@ from .event_response import EventDateChangePosibilityCheckResponseSchema, EventResponseSchema from .event_template_response import EventTemplateResponseSchema from .indigenous_nation_response import IndigenousResponseNationSchema, WorkIndigenousNationResponseSchema +from .list_type_response import ListTypeResponseSchema from .outcome_configuration_response import OutcomeConfigurationResponseSchema from .outcome_template_response import OutcomeTemplateResponseSchema from .phase_response import PhaseResponseSchema diff --git a/epictrack-api/src/api/schemas/response/list_type_response.py b/epictrack-api/src/api/schemas/response/list_type_response.py new file mode 100644 index 000000000..36cceeb5d --- /dev/null +++ b/epictrack-api/src/api/schemas/response/list_type_response.py @@ -0,0 +1,26 @@ +# Copyright © 2019 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""List type response schema""" +from flask_marshmallow import Schema +from marshmallow import fields + + +class ListTypeResponseSchema(Schema): + """List type response schema""" + + id = fields.Int( + metadata={"description": "The id of the item"}, + ) + + name = fields.Str(metadata={"description": "Name of the item."}) diff --git a/epictrack-api/src/api/services/__init__.py b/epictrack-api/src/api/services/__init__.py index 1b736c033..dd08e0d22 100644 --- a/epictrack-api/src/api/services/__init__.py +++ b/epictrack-api/src/api/services/__init__.py @@ -13,7 +13,10 @@ # limitations under the License. """Exposes all of the Services used in the API.""" +from .act_section import ActSectionService +from .action_template import ActionTemplateService from .code import CodeService +from .event import EventService from .event_configuration import EventConfigurationService from .event_template import EventTemplateService from .indigenous_nation import IndigenousNationService @@ -21,6 +24,7 @@ from .lookups import LookupService from .outcome_template import OutcomeTemplateService from .phaseservice import PhaseService +from .position import PositionService from .project import ProjectService from .proponent import ProponentService from .reminder_configuration import ReminderConfigurationService @@ -32,9 +36,6 @@ from .task_template import TaskTemplateService from .user import UserService from .work import WorkService +from .work_issues import WorkIssuesService from .work_phase import WorkPhaseService -from .action_template import ActionTemplateService -from .act_section import ActSectionService from .work_status import WorkStatusService -from .work_issues import WorkIssuesService -from .event import EventService diff --git a/epictrack-api/src/api/services/position.py b/epictrack-api/src/api/services/position.py new file mode 100644 index 000000000..3eff297c5 --- /dev/null +++ b/epictrack-api/src/api/services/position.py @@ -0,0 +1,24 @@ +# Copyright © 2019 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Service to manage Position.""" +from api.models import Position + + +class PositionService: # pylint: disable=too-few-public-methods + """Service to manage Position related operations.""" + + @classmethod + def find_all(cls): + """Return all active positions""" + return Position.find_all() diff --git a/epictrack-web/src/components/staff/StaffForm.tsx b/epictrack-web/src/components/staff/StaffForm.tsx index 9b9a41852..d28af8122 100644 --- a/epictrack-web/src/components/staff/StaffForm.tsx +++ b/epictrack-web/src/components/staff/StaffForm.tsx @@ -13,6 +13,7 @@ import staffService from "../../services/staffService/staffService"; import ControlledTextField from "../shared/controlledInputComponents/ControlledTextField"; import ControlledSwitch from "../shared/controlledInputComponents/ControlledSwitch"; import { ControlledMaskTextField } from "../shared/maskTextField"; +import positionService from "../../services/positionService"; const schema = yup.object().shape({ email: yup .string() @@ -71,9 +72,9 @@ export default function StaffForm({ ...props }) { }, [ctx.item]); const getPositions = async () => { - const positionResult = await codeService.getCodes("positions"); + const positionResult = await positionService.getAll(); if (positionResult.status === 200) { - setPositions((positionResult.data as never)["codes"]); + setPositions(positionResult.data as ListType[]); } }; React.useEffect(() => { diff --git a/epictrack-web/src/components/work/WorkForm.tsx b/epictrack-web/src/components/work/WorkForm.tsx index 77b6fd010..74d4b62bc 100644 --- a/epictrack-web/src/components/work/WorkForm.tsx +++ b/epictrack-web/src/components/work/WorkForm.tsx @@ -26,6 +26,7 @@ import LockClosed from "../../assets/images/lock-closed.svg"; import projectService from "../../services/projectService/projectService"; import ControlledDatePicker from "../shared/controlledInputComponents/ControlledDatePicker"; import { MIN_WORK_START_DATE } from "./constant"; +import { sort } from "../../utils"; const schema = yup.object().shape({ ea_act_id: yup.number().required("EA Act is required"), @@ -146,9 +147,11 @@ export default function WorkForm({ ...props }) { }; const getProjects = async () => { - const projectResult = await projectService.getAll(); + const projectResult = await projectService.getAll("list_type"); if (projectResult.status === 200) { - setProjects(projectResult.data as ListType[]); + let projects = projectResult.data as ListType[]; + projects = sort(projects, "name"); + setProjects(projects); } }; diff --git a/epictrack-web/src/constants/api-endpoint.ts b/epictrack-web/src/constants/api-endpoint.ts index e5288414b..919e2be12 100644 --- a/epictrack-web/src/constants/api-endpoint.ts +++ b/epictrack-web/src/constants/api-endpoint.ts @@ -83,5 +83,8 @@ const Endpoints = { Workplan: { GET_ALL: "/works/dashboard", }, + Position: { + GET_ALL: "positions", + }, }; export default Endpoints; diff --git a/epictrack-web/src/services/positionService/index.ts b/epictrack-web/src/services/positionService/index.ts new file mode 100644 index 000000000..06d47e3c8 --- /dev/null +++ b/epictrack-web/src/services/positionService/index.ts @@ -0,0 +1,10 @@ +import http from "../../apiManager/http-request-handler"; +import Endpoints from "../../constants/api-endpoint"; + +class PositionService { + async getAll() { + return await http.GetRequest(Endpoints.Position.GET_ALL); + } +} + +export default new PositionService(); diff --git a/epictrack-web/src/services/projectService/projectService.ts b/epictrack-web/src/services/projectService/projectService.ts index 45b2f8efe..6adbf8a80 100644 --- a/epictrack-web/src/services/projectService/projectService.ts +++ b/epictrack-web/src/services/projectService/projectService.ts @@ -4,8 +4,8 @@ import ServiceBase from "../common/serviceBase"; import { MasterBase } from "../../models/type"; class ProjectService implements ServiceBase { - async getAll() { - return await http.GetRequest(Endpoints.Projects.PROJECTS); + async getAll(return_type?: string) { + return await http.GetRequest(Endpoints.Projects.PROJECTS, { return_type }); } async getById(id: string) {