From d785e9c3eb268f6fa1005ad9f996b915ea3b39ec Mon Sep 17 00:00:00 2001 From: devketanpro Date: Tue, 12 Nov 2024 16:34:36 +0530 Subject: [PATCH 1/5] Agenda (Calendar) Location filter improvement [CPCN-935] --- assets/agenda/components/LocationFilter.tsx | 47 ++++++++++++--------- assets/globals.d.ts | 2 + newsroom/web/default_settings.py | 7 +++ 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/assets/agenda/components/LocationFilter.tsx b/assets/agenda/components/LocationFilter.tsx index 2dbf25529..2c329ef4d 100644 --- a/assets/agenda/components/LocationFilter.tsx +++ b/assets/agenda/components/LocationFilter.tsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import {get, debounce} from 'lodash'; -import {gettext} from 'utils'; +import {gettext, getConfig} from 'utils'; import server from 'server'; import {KEYS} from 'common'; @@ -315,9 +315,9 @@ export class LocationFilter extends React.Component { */ renderRegionSearchResult(item: any, index: any) { const {selectedIndex} = this.state; - - if (item.type === LOCATION_TYPE.CITY) { - return ( + const enabledOptions = getConfig('calendar_location_filter_options'); + if (item.type === LOCATION_TYPE.CITY && enabledOptions.city) { + return( ); - } else if (item.type === LOCATION_TYPE.STATE) { + } else if (item.type === LOCATION_TYPE.STATE && enabledOptions.state) { + const stateLabel = getConfig('location_state_display_label'); return ( ); - } else if (item.type === LOCATION_TYPE.COUNTRY) { + } else if (item.type === LOCATION_TYPE.COUNTRY && enabledOptions.country) { return ( ); - } else { + } else if (enabledOptions.places && !['city', 'state', 'country'].includes(item.type)) { const results = this.state.results; return ( @@ -389,6 +391,8 @@ export class LocationFilter extends React.Component { {item} ); + } else { + return null; } } @@ -432,6 +436,7 @@ export class LocationFilter extends React.Component { render() { const activeFilter = get(this.props, 'activeFilter.location') || {}; const isActive = activeFilter.type != null; + const isPlaceEnabled = getConfig('calendar_location_filter_options').places; return (
{ )} -
{gettext('Places')}
- {this.state.results.places.length > 0 ? ( - this.state.results.places.map(this.renderRegionSearchResult) - ) : ( - + {isPlaceEnabled && ( + <> +
{gettext('Places')}
+ {this.state.results.places.length > 0 ? ( + this.state.results.places.map(this.renderRegionSearchResult) + ) : ( + + )} + )} )} diff --git a/assets/globals.d.ts b/assets/globals.d.ts index 2015be61b..e0a299c7d 100644 --- a/assets/globals.d.ts +++ b/assets/globals.d.ts @@ -65,6 +65,8 @@ interface IClientConfig { show_user_register?: boolean; multimedia_website_search_url?: string; show_default_time_frame_label?: boolean; + calendar_location_filter_options?:{city: boolean, state: boolean, country: boolean, place:boolean}; + location_state_display_label?:boolean; } interface Window { diff --git a/newsroom/web/default_settings.py b/newsroom/web/default_settings.py index 9092c011a..46b11e3d1 100644 --- a/newsroom/web/default_settings.py +++ b/newsroom/web/default_settings.py @@ -399,6 +399,13 @@ }, "agenda_sort_events_with_coverage_on_top": False, "collapsed_search_by_default": False, + "calendar_location_filter_options": { + "city": True, + "state": True, + "country": True, + "place": True, + }, + "location_state_display_label": "State", } # Enable rendering of the date in the base view From 88ba23b157d54947ec6d007d73949f61db8fb825 Mon Sep 17 00:00:00 2001 From: devketanpro Date: Wed, 13 Nov 2024 12:11:31 +0530 Subject: [PATCH 2/5] use server config and optimize location search aggs based on configuration --- assets/agenda/components/AgendaFilters.tsx | 2 + assets/agenda/components/LocationFilter.tsx | 8 +- assets/agenda/reducers.ts | 3 +- assets/globals.d.ts | 1 - assets/interfaces/agenda.ts | 1 + newsroom/agenda/views.py | 151 ++++++++++++-------- newsroom/web/default_settings.py | 18 ++- 7 files changed, 112 insertions(+), 72 deletions(-) diff --git a/assets/agenda/components/AgendaFilters.tsx b/assets/agenda/components/AgendaFilters.tsx index 8ec98d689..73db0d52b 100644 --- a/assets/agenda/components/AgendaFilters.tsx +++ b/assets/agenda/components/AgendaFilters.tsx @@ -48,6 +48,7 @@ const renderFilter = { key="location" activeFilter={props.activeFilter} toggleFilter={props.toggleFilter} + locationEnabledOptions = {props.locationEnabledOptions} /> ) ), @@ -154,6 +155,7 @@ const mapStateToProps = (state: IAgendaState) => ({ locators: state.locators?.items || [], itemTypeFilterConfig: state.uiConfig.subnav?.item_type || {}, subnavConfig: state, + locationEnabledOptions: state.locationsFiltersOptions || {}, }); type StateProps = ReturnType; diff --git a/assets/agenda/components/LocationFilter.tsx b/assets/agenda/components/LocationFilter.tsx index 2c329ef4d..d6743e74c 100644 --- a/assets/agenda/components/LocationFilter.tsx +++ b/assets/agenda/components/LocationFilter.tsx @@ -315,7 +315,7 @@ export class LocationFilter extends React.Component { */ renderRegionSearchResult(item: any, index: any) { const {selectedIndex} = this.state; - const enabledOptions = getConfig('calendar_location_filter_options'); + const enabledOptions = get(this.props, 'locationEnabledOptions'); if (item.type === LOCATION_TYPE.CITY && enabledOptions.city) { return( ); - } else if (enabledOptions.places && !['city', 'state', 'country'].includes(item.type)) { + } else if (enabledOptions.place && !['city', 'state', 'country'].includes(item.type)) { const results = this.state.results; return ( @@ -436,8 +436,7 @@ export class LocationFilter extends React.Component { render() { const activeFilter = get(this.props, 'activeFilter.location') || {}; const isActive = activeFilter.type != null; - const isPlaceEnabled = getConfig('calendar_location_filter_options').places; - + const isPlaceEnabled = get(this.props, 'locationEnabledOptions').place; return (
{ LocationFilter.propTypes = { activeFilter: PropTypes.object, toggleFilter: PropTypes.func, + locationEnabledOptions: PropTypes.object, }; diff --git a/assets/agenda/reducers.ts b/assets/agenda/reducers.ts index 685c7e43e..f4a484434 100644 --- a/assets/agenda/reducers.ts +++ b/assets/agenda/reducers.ts @@ -336,7 +336,8 @@ export default function agendaReducer(state: IAgendaState = initialState, action hasAgendaFeaturedItems: action.agendaData.has_agenda_featured_items || false, userFolders: action.agendaData.user_folders, companyFolders: action.agendaData.company_folders, - dateFilters: action.agendaData.date_filters || [] + dateFilters: action.agendaData.date_filters || [], + locationsFiltersOptions: action.agendaData.location_filters_options || {}, }; } diff --git a/assets/globals.d.ts b/assets/globals.d.ts index e0a299c7d..29aac2e8d 100644 --- a/assets/globals.d.ts +++ b/assets/globals.d.ts @@ -65,7 +65,6 @@ interface IClientConfig { show_user_register?: boolean; multimedia_website_search_url?: string; show_default_time_frame_label?: boolean; - calendar_location_filter_options?:{city: boolean, state: boolean, country: boolean, place:boolean}; location_state_display_label?:boolean; } diff --git a/assets/interfaces/agenda.ts b/assets/interfaces/agenda.ts index b5eb5cabc..3439fedac 100644 --- a/assets/interfaces/agenda.ts +++ b/assets/interfaces/agenda.ts @@ -314,6 +314,7 @@ export interface IAgendaState { errors?: {[field: string]: Array}; loadingAggregations?: boolean; dateFilters?: IDateFilters; + locationsFiltersOptions?:{[key: string]: boolean}; } export type AgendaGetState = () => IAgendaState; diff --git a/newsroom/agenda/views.py b/newsroom/agenda/views.py index fa92ba063..2b6f2a954 100644 --- a/newsroom/agenda/views.py +++ b/newsroom/agenda/views.py @@ -143,6 +143,7 @@ def get_view_data() -> Dict: "user_folders": get_user_folders(user, "agenda") if user else [], "company_folders": get_company_folders(company, "agenda") if company else [], "date_filters": app.config.get("AGENDA_TIME_FILTERS", []), + "location_filters_options": app.config.get("CALENDAR_LOCATIONS_FILTER_OPTIONS", {}), } @@ -315,6 +316,7 @@ def related_wire_items(wire_id): @blueprint.route("/agenda/search_locations") @login_required def search_locations(): + location_filter_options = app.config.get("CALENDAR_LOCATIONS_FILTER_OPTIONS", {}) query = request.args.get("q") or "" apply_filters = len(query) > 0 @@ -341,37 +343,44 @@ def gen_agg_terms(field: str): "size": 1000, } - es_query = { - "size": 0, - "aggs": { - "city_search_country": { - "terms": gen_agg_terms("address.country"), - "aggs": { - "city_search_state": { - "terms": gen_agg_terms("address.state"), - "aggs": { - "cities": { - "terms": gen_agg_terms("address.city"), - }, + # Start with an empty aggregation structure + es_query = {"size": 0, "aggs": {}} + + # Conditionally add aggregations based on configuration + if location_filter_options.get("city", True): + es_query["aggs"]["city_search_country"] = { + "terms": gen_agg_terms("address.country"), + "aggs": { + "city_search_state": { + "terms": gen_agg_terms("address.state"), + "aggs": { + "cities": { + "terms": gen_agg_terms("address.city"), }, }, }, }, - "state_search_country": { - "terms": gen_agg_terms("address.country"), - "aggs": { - "states": { - "terms": gen_agg_terms("address.state"), - }, + } + + if location_filter_options.get("state", True): + es_query["aggs"]["state_search_country"] = { + "terms": gen_agg_terms("address.country"), + "aggs": { + "states": { + "terms": gen_agg_terms("address.state"), }, }, - "countries": { - "terms": gen_agg_terms("address.country"), - }, - "places": {"terms": gen_agg_terms("name")}, - }, - } + } + if location_filter_options.get("country", True): + es_query["aggs"]["countries"] = { + "terms": gen_agg_terms("address.country"), + } + + if location_filter_options.get("place", True): + es_query["aggs"]["places"] = {"terms": gen_agg_terms("name")} + + # Add a query filter if one is provided if apply_filters: es_query["query"] = { "bool": { @@ -391,62 +400,84 @@ def gen_agg_terms(field: str): }, } - es_query["aggs"]["city_search"] = { - "filter": gen_agg_filter("address.city"), - "aggs": {"city_search_country": es_query["aggs"].pop("city_search_country")}, - } - es_query["aggs"]["state_search"] = { - "filter": gen_agg_filter("address.state"), - "aggs": {"state_search_country": es_query["aggs"].pop("state_search_country")}, - } - es_query["aggs"]["country_search"] = { - "filter": gen_agg_filter("address.country"), - "aggs": {"countries": es_query["aggs"].pop("countries")}, - } - es_query["aggs"]["place_search"] = { - "filter": gen_agg_filter("name"), - "aggs": {"places": es_query["aggs"].pop("places")}, - } + # Conditionally add filtered aggregations based on enabled options + if location_filter_options.get("city", True): + es_query["aggs"]["city_search"] = { + "filter": gen_agg_filter("address.city"), + "aggs": {"city_search_country": es_query["aggs"].pop("city_search_country")}, + } + + if location_filter_options.get("state", True): + es_query["aggs"]["state_search"] = { + "filter": gen_agg_filter("address.state"), + "aggs": {"state_search_country": es_query["aggs"].pop("state_search_country")}, + } + + if location_filter_options.get("country", True): + es_query["aggs"]["country_search"] = { + "filter": gen_agg_filter("address.country"), + "aggs": {"countries": es_query["aggs"].pop("countries")}, + } + + if location_filter_options.get("place", True): + es_query["aggs"]["place_search"] = { + "filter": gen_agg_filter("name"), + "aggs": {"places": es_query["aggs"].pop("places")}, + } + # Execute the query req = ParsedRequest() req.args = {"source": json.dumps(es_query)} service = get_resource_service("agenda") cursor = service.internal_get(req, {}) aggs = cursor.hits.get("aggregations") or {} + # Process results based on the aggregations enabled in config regions = [] - for country_bucket in (aggs.get("city_search_country") or aggs["city_search"]["city_search_country"])["buckets"]: - country_name = country_bucket["key"] - for state_bucket in country_bucket["city_search_state"]["buckets"]: - state_name = state_bucket["key"] - for city_bucket in state_bucket["cities"]["buckets"]: + + if location_filter_options.get("city", True): + for country_bucket in (aggs.get("city_search_country") or aggs["city_search"]["city_search_country"])[ + "buckets" + ]: + country_name = country_bucket["key"] + for state_bucket in country_bucket["city_search_state"]["buckets"]: + state_name = state_bucket["key"] + for city_bucket in state_bucket["cities"]["buckets"]: + regions.append( + {"name": city_bucket["key"], "country": country_name, "state": state_name, "type": "city"} + ) + + if location_filter_options.get("state", True): + for country_bucket in (aggs.get("state_search_country") or aggs["state_search"]["state_search_country"])[ + "buckets" + ]: + country_name = country_bucket["key"] + for state_bucket in country_bucket["states"]["buckets"]: regions.append( - {"name": city_bucket["key"], "country": country_name, "state": state_name, "type": "city"} + { + "name": state_bucket["key"], + "country": country_name, + "type": "state", + } ) - for country_bucket in (aggs.get("state_search_country") or aggs["state_search"]["state_search_country"])["buckets"]: - country_name = country_bucket["key"] - for state_bucket in country_bucket["states"]["buckets"]: + if location_filter_options.get("country", True): + for country_bucket in (aggs.get("countries") or aggs["country_search"]["countries"])["buckets"]: regions.append( { - "name": state_bucket["key"], - "country": country_name, - "type": "state", + "name": country_bucket["key"], + "type": "country", } ) - for country_bucket in (aggs.get("countries") or aggs["country_search"]["countries"])["buckets"]: - regions.append( - { - "name": country_bucket["key"], - "type": "country", - } - ) + places = [] + if location_filter_options.get("place", True): + places = [bucket["key"] for bucket in (aggs.get("places") or aggs["place_search"]["places"])["buckets"]] return ( { "regions": regions, - "places": [bucket["key"] for bucket in (aggs.get("places") or aggs["place_search"]["places"])["buckets"]], + "places": places, }, 200, ) diff --git a/newsroom/web/default_settings.py b/newsroom/web/default_settings.py index 46b11e3d1..224a9f6b2 100644 --- a/newsroom/web/default_settings.py +++ b/newsroom/web/default_settings.py @@ -399,12 +399,6 @@ }, "agenda_sort_events_with_coverage_on_top": False, "collapsed_search_by_default": False, - "calendar_location_filter_options": { - "city": True, - "state": True, - "country": True, - "place": True, - }, "location_state_display_label": "State", } @@ -864,3 +858,15 @@ #: .. versionadded:: 2.8 #: COVERAGE_REQUEST_EMAIL_CC_CURRENT_USER = False + +#: Calendar Location Filter options config +#: +#: .. versionadded: 2.8 +#: + +CALENDAR_LOCATIONS_FILTER_OPTIONS = { + "city": True, + "state": True, + "country": True, + "place": True, +} From 749ba6fee1aa555baac80eef8ad96a9ece375237 Mon Sep 17 00:00:00 2001 From: devketanpro Date: Wed, 13 Nov 2024 15:22:12 +0530 Subject: [PATCH 3/5] address comment --- assets/agenda/components/LocationFilter.tsx | 17 ++++++----------- assets/globals.d.ts | 1 - newsroom/web/default_settings.py | 3 +-- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/assets/agenda/components/LocationFilter.tsx b/assets/agenda/components/LocationFilter.tsx index d6743e74c..fba69f2d5 100644 --- a/assets/agenda/components/LocationFilter.tsx +++ b/assets/agenda/components/LocationFilter.tsx @@ -315,8 +315,7 @@ export class LocationFilter extends React.Component { */ renderRegionSearchResult(item: any, index: any) { const {selectedIndex} = this.state; - const enabledOptions = get(this.props, 'locationEnabledOptions'); - if (item.type === LOCATION_TYPE.CITY && enabledOptions.city) { + if (item.type === LOCATION_TYPE.CITY) { return( ); - } else if (item.type === LOCATION_TYPE.STATE && enabledOptions.state) { - const stateLabel = getConfig('location_state_display_label'); + } else if (item.type === LOCATION_TYPE.STATE) { return ( ); - } else if (item.type === LOCATION_TYPE.COUNTRY && enabledOptions.country) { + } else if (item.type === LOCATION_TYPE.COUNTRY) { return ( ); - } else if (enabledOptions.place && !['city', 'state', 'country'].includes(item.type)) { + } else { const results = this.state.results; return ( @@ -391,8 +388,6 @@ export class LocationFilter extends React.Component { {item} ); - } else { - return null; } } @@ -436,7 +431,7 @@ export class LocationFilter extends React.Component { render() { const activeFilter = get(this.props, 'activeFilter.location') || {}; const isActive = activeFilter.type != null; - const isPlaceEnabled = get(this.props, 'locationEnabledOptions').place; + const isPlaceEnabled = this.props.locationEnabledOptions?.place; return (
Date: Wed, 13 Nov 2024 15:25:49 +0530 Subject: [PATCH 4/5] remove unused imports --- assets/agenda/components/LocationFilter.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/agenda/components/LocationFilter.tsx b/assets/agenda/components/LocationFilter.tsx index fba69f2d5..15dbdf084 100644 --- a/assets/agenda/components/LocationFilter.tsx +++ b/assets/agenda/components/LocationFilter.tsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import {get, debounce} from 'lodash'; -import {gettext, getConfig} from 'utils'; +import {gettext} from 'utils'; import server from 'server'; import {KEYS} from 'common'; @@ -315,8 +315,9 @@ export class LocationFilter extends React.Component { */ renderRegionSearchResult(item: any, index: any) { const {selectedIndex} = this.state; + if (item.type === LOCATION_TYPE.CITY) { - return( + return (