Skip to content

Commit

Permalink
[MDS-6095] Add BE Pagination/Filters to Mine Reports Table (#3234)
Browse files Browse the repository at this point in the history
* Applied backend pagination to mine reports.  Created shared report pagination helper.  Converted various js files to tsx.

* update tests and address PR comments.

* update pytest for mine reports to include pagination/filtering

* replace antd compatible form with regular

* added tests for global reports resource
  • Loading branch information
matbusby-fw authored Sep 6, 2024
1 parent aafa470 commit 1fb870d
Show file tree
Hide file tree
Showing 30 changed files with 1,492 additions and 891 deletions.
6 changes: 2 additions & 4 deletions services/common/src/constants/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,8 @@ export const REPORTS = (params = {}) => `/mines/reports?${queryString.stringify(
export const REPORT_SUBMISSIONS = (params?) =>
`/mines/reports/submissions?${queryString.stringify(params)}`;
export const MINE_REPORT_DEFINITIONS = () => `/mines/reports/definitions`;
export const MINE_REPORTS = (mineGuid, reportsType?) =>
`/mines/${mineGuid}/reports?${queryString.stringify({
mine_reports_type: reportsType,
})}`;
export const MINE_REPORTS = (mineGuid, params?) =>
`/mines/${mineGuid}/reports?${queryString.stringify(params)}`;
export const MINE_REPORT = (mineGuid, mineReportGuid) =>
`/mines/${mineGuid}/reports/${mineReportGuid}`;
export const MINE_REPORT_SUBMISSIONS = (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
export interface MineReportParams {
id?: string;
report_name: string;
report_type: string;
compliance_year: string;
due_date_start: string;
due_date_end: string;
received_date_start: string;
received_date_end: string;
received_only: string;
requested_by: string;
status: string[];
sort_field: string;
sort_dir: string;
mine_reports_type: string;
report_name?: string;
report_type?: string;
compliance_year?: string;
due_date_start?: string;
due_date_end?: string;
received_date_start?: string;
received_date_end?: string;
received_only?: string;
requested_by?: string;
status?: string[];
sort_field?: string;
sort_dir?: string;
mine_reports_type?: string;
permit_guid?: string;
page?: string;
per_page?: string;
search?: string;
}
11 changes: 9 additions & 2 deletions services/common/src/redux/actionCreators/reportActionCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,20 @@ export const createMineReport = (mineGuid, payload) => (dispatch) => {

export const fetchMineReports = (
mineGuid,
reportsType = Strings.MINE_REPORTS_TYPE.codeRequiredReports
reportsType = Strings.MINE_REPORTS_TYPE.codeRequiredReports,
params = {}
) => (dispatch) => {
dispatch(mineReportActions.clearMineReports());
dispatch(request(reducerTypes.GET_MINE_REPORTS));
dispatch(showLoading());
return CustomAxios()
.get(`${ENVIRONMENT.apiUrl}${API.MINE_REPORTS(mineGuid, reportsType)}`, createRequestHeader())
.get(
`${ENVIRONMENT.apiUrl}${API.MINE_REPORTS(mineGuid, {
...params,
mine_reports_type: reportsType,
})}`,
createRequestHeader()
)
.then((response) => {
dispatch(success(reducerTypes.GET_MINE_REPORTS));
dispatch(mineReportActions.storeMineReports(response.data));
Expand Down
1 change: 1 addition & 0 deletions services/common/src/redux/reducers/reportReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const reportReducer = (state = initialState, action) => {
return {
...state,
mineReports: action.payload.records,
reportsPageData: action.payload,
mineReportGuid: "",
};
case actionTypes.CLEAR_MINE_REPORTS:
Expand Down
4 changes: 4 additions & 0 deletions services/common/src/tests/mocks/dataMocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6102,6 +6102,10 @@ export const MINE_REPORTS = [

export const MINE_REPORT_RESPONSE = {
records: MINE_REPORTS,
current_page: 1,
items_per_page: 25,
total: 25,
total_pages: 1,
};

export const REPORTS_PAGE_DATA = {
Expand Down
17 changes: 1 addition & 16 deletions services/core-api/app/api/mines/reports/models/mine_report.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import uuid

from flask import current_app
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.schema import FetchedValue
Expand Down Expand Up @@ -355,22 +356,6 @@ def find_by_mine_guid_and_category(cls, _id, category):
except ValueError:
return None

@classmethod
def find_by_mine_guid_and_report_type(cls,
_id,
reports_type=MINE_REPORT_TYPE['CODE REQUIRED REPORTS']):
try:
uuid.UUID(_id, version=4)
reports = cls.query.filter_by(mine_guid=_id).filter_by(deleted_ind=False).order_by(
cls.due_date.asc())
if reports_type == MINE_REPORT_TYPE['PERMIT REQUIRED REPORTS']:
reports = reports.filter(MineReport.permit_condition_category_code.isnot(None))
elif reports_type == MINE_REPORT_TYPE['CODE REQUIRED REPORTS']:
reports = reports.filter(MineReport.permit_condition_category_code.is_(None))
return reports.all()
except ValueError:
return None

@validates('mine_report_definition_id')
def validate_mine_report_definition_id(self, key, mine_report_definition_id):
if mine_report_definition_id and self.permit_condition_category_code:
Expand Down
144 changes: 144 additions & 0 deletions services/core-api/app/api/mines/reports/report_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# report_helpers.py
from sqlalchemy import asc, desc
from sqlalchemy_filters import apply_sort, apply_pagination, apply_filters

from app.api.mines.mine.models.mine import Mine
from app.api.mines.reports.models.mine_report import MineReport
from app.api.mines.reports.models.mine_report_category import MineReportCategory
from app.api.mines.reports.models.mine_report_category_xref import MineReportCategoryXref
from app.api.mines.reports.models.mine_report_definition import MineReportDefinition


class ReportFilterHelper:
@classmethod
def build_filter(cls, model, field, op, argfield):
return {'model': model, 'field': field, 'op': op, 'value': argfield}

@staticmethod
def apply_filters_and_pagination(query, args):
sort_models = {
"mine_report_id": 'MineReport',
"mine_report_category": 'MineReportCategoryXref',
"report_name": 'MineReportDefinition',
"due_date": 'MineReport',
"received_date": 'MineReport',
"submission_year": 'MineReport',
"mine_report_status_code": 'MineReportSubmissionStatusCode',
"created_by_idir": 'MineReport',
"mine_name": 'Mine',
}

sort_field = {
"mine_report_id": 'mine_report_id',
"mine_report_category": 'mine_report_category',
"report_name": 'report_name',
"due_date": 'due_date',
"received_date": 'received_date',
"submission_year": 'submission_year',
"mine_report_status_code": 'mine_report_status_description',
"created_by_idir": 'created_by_idir',
"mine_name": 'mine_name',
}

conditions = []

if args["search_terms"] or args["major"] or args["region"] or (
args["sort_field"] and sort_models[args['sort_field']] == 'Mine'):
query = query.join(Mine)

if args["report_type"] or args["report_name"] or (args['sort_field'] and sort_models[
args['sort_field']] in ['MineReportCategoryXref', 'MineReportDefinition']):
query = query.join(
MineReportDefinition, MineReport.mine_report_definition_id ==
MineReportDefinition.mine_report_definition_id)
query = query.join(
MineReportCategoryXref, MineReportDefinition.mine_report_definition_id ==
MineReportCategoryXref.mine_report_definition_id)
query = query.join(
MineReportCategory, MineReportCategoryXref.mine_report_category ==
MineReportCategory.mine_report_category)

if args["major"]:
conditions.append(ReportFilterHelper.build_filter('Mine', 'major_mine_ind', '==', args["major"]))

if args["region"]:
conditions.append(ReportFilterHelper.build_filter('Mine', 'mine_region', 'in', args["region"]))

if args["report_type"]:
conditions.append(
ReportFilterHelper.build_filter('MineReportCategoryXref', 'mine_report_category', 'in',
args["report_type"]))

if args["report_name"]:
conditions.append(
ReportFilterHelper.build_filter('MineReportDefinition', 'mine_report_definition_guid', 'in',
args["report_name"]))

if args["status"]:
query = query.filter(MineReport.mine_report_status_code.in_(args["status"]))

if args["compliance_year"]:
conditions.append(
ReportFilterHelper.build_filter('MineReport', 'submission_year', '==', args["compliance_year"]))

if args["due_date_before"]:
conditions.append(
ReportFilterHelper.build_filter('MineReport', 'due_date', '<=', args["due_date_before"]))

if args["due_date_after"]:
conditions.append(
ReportFilterHelper.build_filter('MineReport', 'due_date', '>=', args["due_date_after"]))

if args["received_date_before"]:
conditions.append(
ReportFilterHelper.build_filter('MineReport', 'received_date', '<=',
args["received_date_before"]))

if args["received_date_after"]:
conditions.append(
ReportFilterHelper.build_filter('MineReport', 'received_date', '>=',
args["received_date_after"]))

if args["received_only"]:
query = query.filter(MineReport.received_date.isnot(None))

if args["requested_by"]:
conditions.append(
ReportFilterHelper.build_filter('MineReport', 'created_by_idir', 'ilike',
'%{}%'.format(args["requested_by"])))

if args["search_terms"]:
search_conditions = [
ReportFilterHelper.build_filter('Mine', 'mine_name', 'ilike',
'%{}%'.format(args["search_terms"])),
ReportFilterHelper.build_filter('Mine', 'mine_no', 'ilike', '%{}%'.format(args["search_terms"])),
]
conditions.append({'or': search_conditions})

filtered_query = apply_filters(query, conditions)

if args['sort_field'] == 'mine_report_status_code':
if args['sort_dir'] == 'asc':
filtered_query = filtered_query.order_by(
asc(MineReport.mine_report_status_description))
else:
filtered_query = filtered_query.order_by(
desc(MineReport.mine_report_status_description))

else:
if args['sort_field'] and args['sort_dir']:
sort_criteria = [{
'model': sort_models[args['sort_field']],
'field': sort_field[args['sort_field']],
'direction': args['sort_dir']
}]
else:
sort_criteria = [{
'model': 'MineReport',
'field': 'received_date',
'direction': 'desc'
}]

filtered_query = apply_sort(filtered_query, sort_criteria)

return apply_pagination(filtered_query, args["page_number"], args["page_size"])
69 changes: 64 additions & 5 deletions services/core-api/app/api/mines/reports/resources/mine_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from datetime import datetime
from werkzeug.exceptions import BadRequest, NotFound, InternalServerError

from app.api.constants import MINE_REPORT_TYPE
from app.api.mines.reports.models.mine_report_contact import MineReportContact
from app.api.mines.reports.report_helpers import ReportFilterHelper
from app.extensions import api
from app.api.utils.resources_mixins import UserMixin
from app.api.utils.access_decorators import requires_any_of, requires_role_edit_report, EDIT_REPORT, MINESPACE_PROPONENT, VIEW_ALL, is_minespace_user
Expand All @@ -21,10 +23,11 @@
from app.api.mines.documents.models.mine_document import MineDocument
from app.api.mines.permits.permit_conditions.models.permit_condition_category import PermitConditionCategory
from app.api.utils.custom_reqparser import CustomReqparser
from app.api.mines.response_models import MINE_REPORT_MODEL
from app.api.mines.response_models import MINE_REPORT_MODEL, PAGINATED_REPORT_LIST
from app.api.mines.exceptions.mine_exceptions import MineException


PAGE_DEFAULT = 1
PER_PAGE_DEFAULT = 10

class MineReportListResource(Resource, UserMixin):
parser = CustomReqparser()
Expand All @@ -49,17 +52,73 @@ class MineReportListResource(Resource, UserMixin):
parser.add_argument('submitter_email', type=str, location='json')
parser.add_argument('mine_report_contacts', type=list, location='json')

@api.marshal_with(MINE_REPORT_MODEL, envelope='records', code=200)
@api.doc(description='returns the reports for a given mine.')
@api.marshal_with(PAGINATED_REPORT_LIST, code=200)
@api.doc(description='returns the reports for a given mine.',
params={
'page': f'The page number of paginated records to return. Default: {PAGE_DEFAULT}',
'per_page': f'The number of records to return per page. Default: {PER_PAGE_DEFAULT}',
'sort_field': 'The field the returned results will be ordered by',
'sort_dir': 'The direction by which the sort field is ordered',
'search': 'A substring to match in a mine name, mine number, or permit number',
'report_type': 'The report categories',
'report_name': 'The descriptive names of the report',
'due_date_after': 'Reports with a due date only after this date',
'due_date_before': 'Reports with a due date only before this date',
'received_date_after': 'Reports with a received date only after this date',
'received_date_before': 'Reports with a received date only before this date',
'received_only': 'Whether or not to only show reports that have a set received date',
'compliance_year': 'The compliance year/period of the report',
'requested_by': 'A substring to match in the name of the user who requested the report',
'major': 'Whether or not the report is for a major or regional mine',
'region': 'Regions the mines associated with the report are located in',
})
@requires_any_of([VIEW_ALL, MINESPACE_PROPONENT])
def get(self, mine_guid):
args = {
"page_number": request.args.get('page', PAGE_DEFAULT, type=int),
"page_size": request.args.get('per_page', PER_PAGE_DEFAULT, type=int),
'sort_field': request.args.get('sort_field', type=str),
'sort_dir': request.args.get('sort_dir', type=str),
'search_terms': request.args.get('search', type=str),
'report_type': request.args.getlist('report_type', type=str),
'report_name': request.args.getlist('report_name', type=str),
'due_date_after': request.args.get('due_date_start', type=str),
'due_date_before': request.args.get('due_date_end', type=str),
'received_date_after': request.args.get('received_date_start', type=str),
'received_date_before': request.args.get('received_date_end', type=str),
'received_only': request.args.get('received_only', type=str) == "true",
'compliance_year': request.args.get('compliance_year', type=str),
'requested_by': request.args.get('requested_by', type=str),
'status': request.args.getlist('status', type=str),
'major': request.args.get('major', type=str),
'region': request.args.getlist('region', type=str),
}

mrd_category = request.args.get('mine_report_definition_category')
if mrd_category:
return MineReport.find_by_mine_guid_and_category(mine_guid, mrd_category)

reports_type = request.args.get('mine_reports_type', None)

return MineReport.find_by_mine_guid_and_report_type(mine_guid, reports_type)
query = MineReport.query.filter_by(mine_guid=mine_guid, deleted_ind=False).order_by(MineReport.due_date.asc())

if reports_type == MINE_REPORT_TYPE['PERMIT REQUIRED REPORTS']:
query = query.filter(MineReport.permit_condition_category_code.isnot(None))
elif reports_type == MINE_REPORT_TYPE['CODE REQUIRED REPORTS']:
query = query.filter(MineReport.permit_condition_category_code.is_(None))

records, pagination_details = ReportFilterHelper.apply_filters_and_pagination(query, args)

if not records:
raise BadRequest('Unable to fetch reports')

return {
'records': records.all(),
'current_page': pagination_details.page_number,
'total_pages': pagination_details.num_pages,
'items_per_page': pagination_details.page_size,
'total': pagination_details.total_results,
}

@api.doc(description='creates a new report for the mine')
@api.marshal_with(MINE_REPORT_MODEL, code=201)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from sqlalchemy_filters import apply_sort, apply_pagination, apply_filters
from werkzeug.exceptions import BadRequest
from sqlalchemy import asc, desc, func, or_

from app.api.mines.reports.report_helpers import ReportFilterHelper
from app.extensions import api
from app.api.utils.access_decorators import requires_any_of, VIEW_ALL
from app.api.utils.resources_mixins import UserMixin
Expand Down Expand Up @@ -63,7 +65,9 @@ def get(self):
'region': request.args.getlist('region', type=str),
}

records, pagination_details = self._apply_filters_and_pagination(args)
query = MineReport.query.filter_by(deleted_ind=False)

records, pagination_details = ReportFilterHelper.apply_filters_and_pagination(query, args)
if not records:
raise BadRequest('Unable to fetch reports.')
return {
Expand Down
Loading

0 comments on commit 1fb870d

Please sign in to comment.