From a5a94d9af5b4c8f21b9537b9b445ca187f027899 Mon Sep 17 00:00:00 2001 From: djnunez-aot <103138766+djnunez-aot@users.noreply.github.com> Date: Thu, 13 Apr 2023 13:03:07 -0400 Subject: [PATCH] Engagement listing filtering (#1493) * setup new engagement listing fields * add back end filtering * name cleanup * update engagement listing tests * naming update * api lint * finish front end * allow for multiple fields * api lint * API Lint * flake 8 * partial matching * flake 8 * lint * more lint --- met-api/src/met_api/models/engagement.py | 31 ++- met-api/src/met_api/resources/engagement.py | 4 + .../EngagementTabsContext.tsx | 1 - .../AdvancedSearch/SearchComponent.tsx | 192 ++++++++++++++++-- .../AdvancedSearch/SearchComponentMobile.tsx | 175 +++++++++++++++- .../listing/AdvancedSearch/SearchTypes.ts | 5 + .../components/engagement/listing/index.tsx | 10 + .../src/services/engagementService/index.ts | 4 + .../engagement/EngagementListing.test.tsx | 12 +- 9 files changed, 410 insertions(+), 24 deletions(-) diff --git a/met-api/src/met_api/models/engagement.py b/met-api/src/met_api/models/engagement.py index fac0db77c..a2d4258d2 100644 --- a/met-api/src/met_api/models/engagement.py +++ b/met-api/src/met_api/models/engagement.py @@ -74,7 +74,7 @@ def get_engagements_paginated( query = cls._filter_by_engagement_status(query, search_options) - query = cls._filter_by_project_type(query, search_options.get('project_type')) + query = cls._filter_by_project_metadata(query, search_options) if assigned_engagements is not None: query = cls._filter_by_assigned_engagements(query, assigned_engagements) @@ -233,11 +233,32 @@ def _filter_by_search_text(query, search_options): return query @staticmethod - def _filter_by_project_type(query, project_type=None): - if project_type: - query = query.outerjoin(EngagementMetadataModel, EngagementMetadataModel.engagement_id == Engagement.id)\ - .filter(EngagementMetadataModel.project_metadata['type'].astext == project_type)\ + def _filter_by_project_metadata(query, search_options): + query = query.outerjoin(EngagementMetadataModel, EngagementMetadataModel.engagement_id == Engagement.id) + + if project_type := search_options.get('project_type'): + query = query.filter(EngagementMetadataModel.project_metadata['type'].astext.ilike(f'%{project_type}%'))\ .params(val=project_type) + + if project_name := search_options.get('project_name'): + query = query.filter(EngagementMetadataModel.project_metadata['project_name'] + .astext.ilike(f'%{project_name}%'))\ + .params(val=project_name) + + if project_id := search_options.get('project_id'): + query = query.filter(EngagementMetadataModel.project_id == project_id)\ + .params(val=project_id) + + if application_number := search_options.get('application_number'): + query = query.filter(EngagementMetadataModel.project_metadata['application_number'] + .astext.ilike(f'%{application_number}%'))\ + .params(val=application_number) + + if client_name := search_options.get('client_name'): + query = query.filter(EngagementMetadataModel.project_metadata['client_name'] + .astext.ilike(f'%{client_name}%'))\ + .params(val=client_name) + return query @staticmethod diff --git a/met-api/src/met_api/resources/engagement.py b/met-api/src/met_api/resources/engagement.py index 8b194baec..54d923fba 100644 --- a/met-api/src/met_api/resources/engagement.py +++ b/met-api/src/met_api/resources/engagement.py @@ -88,6 +88,10 @@ def get(): 'published_from_date': args.get('published_from_date', None, type=str), 'published_to_date': args.get('published_to_date', None, type=str), 'project_type': args.get('project_type', None, type=str), + 'project_id': args.get('project_id', None, type=str), + 'project_name': args.get('project_name', None, type=str), + 'application_number': args.get('application_number', None, type=str), + 'client_name': args.get('client_name', None, type=str), } engagement_records = EngagementService()\ diff --git a/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx b/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx index e4cd7b5bc..9dd66577e 100644 --- a/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx +++ b/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx @@ -25,7 +25,6 @@ const initialEngagementFormData = { end_date: '', description: '', content: '', - project_id: '', project_metadata: { project_name: '', diff --git a/met-web/src/components/engagement/listing/AdvancedSearch/SearchComponent.tsx b/met-web/src/components/engagement/listing/AdvancedSearch/SearchComponent.tsx index 400331f68..034532424 100644 --- a/met-web/src/components/engagement/listing/AdvancedSearch/SearchComponent.tsx +++ b/met-web/src/components/engagement/listing/AdvancedSearch/SearchComponent.tsx @@ -1,17 +1,30 @@ import React, { useState } from 'react'; -import { Grid, Stack, TextField, FormGroup, FormControlLabel, Checkbox } from '@mui/material'; +import { + Grid, + Stack, + TextField, + FormGroup, + FormControlLabel, + Checkbox, + Select, + SelectChangeEvent, + MenuItem, +} from '@mui/material'; import { MetParagraph, MetLabel } from 'components/common'; import { EngagementDisplayStatus } from 'constants/engagementStatus'; import { PrimaryButton, SecondaryButton } from '../../../common'; import dayjs from 'dayjs'; import { formatToUTC } from 'components/common/dateHelper'; import { SearchOptions } from './SearchTypes'; +import { AppConfig } from 'config'; interface filterParams { setFilterParams: (newsearchOptions: SearchOptions) => void; } const AdvancedSearch: React.FC = ({ setFilterParams }) => { + const { engagementProjectTypes } = AppConfig.constants; + const intitialStatusList = { [EngagementDisplayStatus[EngagementDisplayStatus.Draft]]: false, [EngagementDisplayStatus[EngagementDisplayStatus.Scheduled]]: false, @@ -22,8 +35,14 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { const [statusFilter, setStatusFilter] = useState(() => { return intitialStatusList; }); + const initialFilterParams = { status_list: [], + project_name: '', + project_id: '', + application_number: '', + client_name: '', + project_type: '', created_from_date: '', created_to_date: '', published_from_date: '', @@ -45,6 +64,14 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { ); }; + const [projectFilter, setProjectFilter] = useState({ + projectType: '', + projectId: '', + projectName: '', + clientName: '', + applicationNumber: '', + }); + const [dateFilter, setDateFilter] = useState({ createdFromDate: '', createdToDate: '', @@ -53,6 +80,8 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { }); const { createdFromDate, createdToDate, publishedFromDate, publishedToDate } = dateFilter; + const { projectType, projectId, projectName, clientName, applicationNumber } = projectFilter; + const handleDateFilterChange = (e: React.ChangeEvent) => { setDateFilter({ ...dateFilter, @@ -60,6 +89,15 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { }); }; + const handleProjectDataChange = ( + e: React.ChangeEvent | SelectChangeEvent, + ) => { + setProjectFilter({ + ...projectFilter, + [e.target.name]: e.target.value, + }); + }; + const handleSearch = () => { /* Database has the values in utc but the value we select from the calender is having only date without a time. @@ -75,6 +113,11 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { const fPublishedToDate = publishedToDate ? formatToUTC(dayjs(publishedToDate).endOf('day').format('YYYY-MM-DD HH:mm:ss')) : publishedToDate; + const fProjectType = projectType ? projectType : ''; + const fprojectId = projectId ? projectId : ''; + const fProjectName = projectName ? projectName : ''; + const fClientName = clientName ? clientName : ''; + const fAppNumber = applicationNumber ? applicationNumber : ''; setFilterParams({ status_list: selectedStatusList, @@ -82,6 +125,11 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { created_to_date: fCreatedToDate, published_from_date: fPublishedFromDate, published_to_date: fPublishedToDate, + project_type: fProjectType, + project_id: fprojectId, + project_name: fProjectName, + client_name: fClientName, + application_number: fAppNumber, }); }; @@ -89,6 +137,14 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { setStatusFilter(intitialStatusList); setSelectedStatusList([]); + setProjectFilter({ + projectType: '', + projectId: '', + projectName: '', + clientName: '', + applicationNumber: '', + }); + setDateFilter({ createdFromDate: '', createdToDate: '', @@ -101,8 +157,8 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { return ( <> - - + + Status @@ -171,7 +227,7 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { } label={ - Published - {EngagementDisplayStatus[EngagementDisplayStatus.Upcoming]} + {EngagementDisplayStatus[EngagementDisplayStatus.Upcoming]} } /> @@ -219,6 +275,124 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { + + Project Name + + + + + + + + Project # + + + + + + + + Application # + + + + + + + + + + Proponent + + + + + + + + Project Type + + + + + + Has comments that need review} + control={} + /> + + + Date Created - From @@ -260,7 +434,7 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { - + Date Created - To @@ -303,14 +477,6 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { - - - Has comments that need review} - control={} - /> - - handleResetSearchFilters()}> diff --git a/met-web/src/components/engagement/listing/AdvancedSearch/SearchComponentMobile.tsx b/met-web/src/components/engagement/listing/AdvancedSearch/SearchComponentMobile.tsx index c089bca66..fafba9f1f 100644 --- a/met-web/src/components/engagement/listing/AdvancedSearch/SearchComponentMobile.tsx +++ b/met-web/src/components/engagement/listing/AdvancedSearch/SearchComponentMobile.tsx @@ -1,17 +1,30 @@ import React, { useState } from 'react'; -import { Grid, Stack, TextField, FormGroup, FormControlLabel, Checkbox } from '@mui/material'; +import { + Grid, + Stack, + TextField, + FormGroup, + FormControlLabel, + Checkbox, + Select, + SelectChangeEvent, + MenuItem, +} from '@mui/material'; import { MetParagraph, MetLabel } from 'components/common'; import { EngagementDisplayStatus } from 'constants/engagementStatus'; import { PrimaryButton, SecondaryButton } from '../../../common'; import dayjs from 'dayjs'; import { formatToUTC } from 'components/common/dateHelper'; import { SearchOptions } from './SearchTypes'; +import { AppConfig } from 'config'; interface filterParams { setFilterParams: (newsearchOptions: SearchOptions) => void; } const AdvancedSearch: React.FC = ({ setFilterParams }) => { + const { engagementProjectTypes } = AppConfig.constants; + const intitialStatusList = { [EngagementDisplayStatus[EngagementDisplayStatus.Draft]]: false, [EngagementDisplayStatus[EngagementDisplayStatus.Scheduled]]: false, @@ -24,6 +37,11 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { }); const initialFilterParams = { status_list: [], + project_name: '', + project_id: '', + application_number: '', + client_name: '', + project_type: '', created_from_date: '', created_to_date: '', published_from_date: '', @@ -45,6 +63,14 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { ); }; + const [projectFilter, setProjectFilter] = useState({ + projectType: '', + projectId: '', + projectName: '', + clientName: '', + applicationNumber: '', + }); + const [dateFilter, setDateFilter] = useState({ createdFromDate: '', createdToDate: '', @@ -53,6 +79,8 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { }); const { createdFromDate, createdToDate, publishedFromDate, publishedToDate } = dateFilter; + const { projectType, projectId, projectName, clientName, applicationNumber } = projectFilter; + const handleDateFilterChange = (e: React.ChangeEvent) => { setDateFilter({ ...dateFilter, @@ -60,6 +88,15 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { }); }; + const handleProjectDataChange = ( + e: React.ChangeEvent | SelectChangeEvent, + ) => { + setProjectFilter({ + ...projectFilter, + [e.target.name]: e.target.value, + }); + }; + const handleSearch = () => { /* Database has the values in utc but the value we select from the calender is having only date without a time. @@ -75,6 +112,11 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { const fPublishedToDate = publishedToDate ? formatToUTC(dayjs(publishedToDate).endOf('day').format('YYYY-MM-DD HH:mm:ss')) : publishedToDate; + const fProjectType = projectType ? projectType : ''; + const fprojectId = projectId ? projectId : ''; + const fProjectName = projectName ? projectName : ''; + const fClientName = clientName ? clientName : ''; + const fAppNumber = applicationNumber ? applicationNumber : ''; setFilterParams({ status_list: selectedStatusList, @@ -82,6 +124,11 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { created_to_date: fCreatedToDate, published_from_date: fPublishedFromDate, published_to_date: fPublishedToDate, + project_type: fProjectType, + project_id: fprojectId, + project_name: fProjectName, + client_name: fClientName, + application_number: fAppNumber, }); }; @@ -89,6 +136,14 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { setStatusFilter(intitialStatusList); setSelectedStatusList([]); + setProjectFilter({ + projectType: '', + projectId: '', + projectName: '', + clientName: '', + applicationNumber: '', + }); + setDateFilter({ createdFromDate: '', createdToDate: '', @@ -219,7 +274,119 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { + + Project Name + + + + + + + + Project # + + + + + + + + Application # + + + + + + + + + Proponent + + + + + + + + Project Type + + + + + + + Date Created - From @@ -239,7 +406,7 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { /> - + Date Created - To @@ -262,7 +429,7 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { - + Date Published - From @@ -282,7 +449,7 @@ const AdvancedSearch: React.FC = ({ setFilterParams }) => { /> - + Date Published - To diff --git a/met-web/src/components/engagement/listing/AdvancedSearch/SearchTypes.ts b/met-web/src/components/engagement/listing/AdvancedSearch/SearchTypes.ts index 116508fa3..3f9c11b9a 100644 --- a/met-web/src/components/engagement/listing/AdvancedSearch/SearchTypes.ts +++ b/met-web/src/components/engagement/listing/AdvancedSearch/SearchTypes.ts @@ -1,5 +1,10 @@ export interface SearchOptions { status_list: number[]; + project_type: string; + project_id: string; + project_name: string; + client_name: string; + application_number: string; created_from_date: string; created_to_date: string; published_from_date: string; diff --git a/met-web/src/components/engagement/listing/index.tsx b/met-web/src/components/engagement/listing/index.tsx index 0c0fd6111..f8dd1a120 100644 --- a/met-web/src/components/engagement/listing/index.tsx +++ b/met-web/src/components/engagement/listing/index.tsx @@ -52,6 +52,11 @@ const EngagementListing = () => { const [searchOptions, setSearchOptions] = useState({ status_list: [], + project_name: '', + project_id: '', + application_number: '', + client_name: '', + project_type: '', created_from_date: '', created_to_date: '', published_from_date: '', @@ -84,6 +89,11 @@ const EngagementListing = () => { created_to_date: searchOptions.created_to_date, published_from_date: searchOptions.published_from_date, published_to_date: searchOptions.published_to_date, + project_type: searchOptions.project_type, + project_id: searchOptions.project_id, + project_name: searchOptions.project_name, + client_name: searchOptions.client_name, + application_number: searchOptions.application_number, }); setEngagements(response.items); setPageInfo({ diff --git a/met-web/src/services/engagementService/index.ts b/met-web/src/services/engagementService/index.ts index 6366f3b34..0d31453e1 100644 --- a/met-web/src/services/engagementService/index.ts +++ b/met-web/src/services/engagementService/index.ts @@ -27,6 +27,10 @@ interface GetEngagementsParams { published_to_date?: string; include_banner_url?: boolean; project_type?: string; + project_id?: string; + project_name?: string; + client_name?: string; + application_number?: string; } export const getEngagements = async (params: GetEngagementsParams = {}): Promise> => { const responseData = await http.GetRequest>(Endpoints.Engagement.GET_LIST, params); diff --git a/met-web/tests/unit/components/engagement/EngagementListing.test.tsx b/met-web/tests/unit/components/engagement/EngagementListing.test.tsx index 54f718053..21872d420 100644 --- a/met-web/tests/unit/components/engagement/EngagementListing.test.tsx +++ b/met-web/tests/unit/components/engagement/EngagementListing.test.tsx @@ -161,6 +161,11 @@ describe('Engagement form page tests', () => { sort_order: 'desc', search_text: 'Engagement One', engagement_status: [], + application_number: '', + client_name: '', + project_id: '', + project_name: '', + project_type: '', created_from_date: '', created_to_date: '', published_from_date: '', @@ -169,7 +174,7 @@ describe('Engagement form page tests', () => { }); }); - test('Advanced Search filter works and fetchs engagements with the selected status as a param', async () => { + test('Advanced Search filter works and fetches engagements with the selected status as a param', async () => { getEngagementMock.mockReturnValue( Promise.resolve({ items: [mockEngagementOne], @@ -198,6 +203,11 @@ describe('Engagement form page tests', () => { sort_order: 'desc', search_text: '', engagement_status: [1], + application_number: '', + client_name: '', + project_id: '', + project_name: '', + project_type: '', created_from_date: '', created_to_date: '', published_from_date: '',