Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Agenda (Calendar) Location filter improvement [CPCN-935] #1158

Merged
merged 5 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions assets/agenda/components/AgendaFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const renderFilter = {
key="location"
activeFilter={props.activeFilter}
toggleFilter={props.toggleFilter}
locationEnabledOptions = {props.locationEnabledOptions}
/>
)
),
Expand Down Expand Up @@ -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<typeof mapStateToProps>;
Expand Down
49 changes: 29 additions & 20 deletions assets/agenda/components/LocationFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -315,9 +315,9 @@ export class LocationFilter extends React.Component<any, any> {
*/
renderRegionSearchResult(item: any, index: any) {
const {selectedIndex} = this.state;

if (item.type === LOCATION_TYPE.CITY) {
return (
const enabledOptions = get(this.props, 'locationEnabledOptions');
devketanpro marked this conversation as resolved.
Show resolved Hide resolved
if (item.type === LOCATION_TYPE.CITY && enabledOptions.city) {
devketanpro marked this conversation as resolved.
Show resolved Hide resolved
return(
<button
key={`city.${item.name}[${index}]`}
data-item-index={index}
Expand All @@ -336,7 +336,8 @@ export class LocationFilter extends React.Component<any, any> {
})}
</button>
);
} else if (item.type === LOCATION_TYPE.STATE) {
} else if (item.type === LOCATION_TYPE.STATE && enabledOptions.state) {
const stateLabel = getConfig('location_state_display_label');
devketanpro marked this conversation as resolved.
Show resolved Hide resolved
return (
<button
key={`state.${item.name}[${index}]`}
Expand All @@ -349,13 +350,14 @@ export class LocationFilter extends React.Component<any, any> {
)}
onClick={() => this.onChange(item)}
>
{gettext('{{ name }} (State, {{ country }})', {
{gettext('{{ name }} ({{ label }}, {{ country }})', {
name: item.name,
label: stateLabel,
country: item.country,
})}
</button>
);
} else if (item.type === LOCATION_TYPE.COUNTRY) {
} else if (item.type === LOCATION_TYPE.COUNTRY && enabledOptions.country) {
return (
<button
key={`country.${item.name}[${index}]`}
Expand All @@ -371,7 +373,7 @@ export class LocationFilter extends React.Component<any, any> {
{gettext('{{ name }} (Country)', {name: item.name})}
</button>
);
} else {
} else if (enabledOptions.place && !['city', 'state', 'country'].includes(item.type)) {
const results = this.state.results;

return (
Expand All @@ -389,6 +391,8 @@ export class LocationFilter extends React.Component<any, any> {
{item}
</button>
);
} else {
return null;
}
}

Expand Down Expand Up @@ -432,7 +436,7 @@ export class LocationFilter extends React.Component<any, any> {
render() {
const activeFilter = get(this.props, 'activeFilter.location') || {};
const isActive = activeFilter.type != null;

const isPlaceEnabled = get(this.props, 'locationEnabledOptions').place;
return (
<div
key="location"
Expand Down Expand Up @@ -522,17 +526,21 @@ export class LocationFilter extends React.Component<any, any> {
</button>
)}

<h6 className="dropdown-menu__section-heading">{gettext('Places')}</h6>
{this.state.results.places.length > 0 ? (
this.state.results.places.map(this.renderRegionSearchResult)
) : (
<button
key="empty-places"
className="dropdown-item disabled"
disabled={true}
>
{gettext('No places found')}
</button>
{isPlaceEnabled && (
<>
<h6 className="dropdown-menu__section-heading">{gettext('Places')}</h6>
{this.state.results.places.length > 0 ? (
this.state.results.places.map(this.renderRegionSearchResult)
) : (
<button
key="empty-places"
className="dropdown-item disabled"
disabled={true}
>
{gettext('No places found')}
</button>
)}
</>
)}
</React.Fragment>
)}
Expand All @@ -547,4 +555,5 @@ export class LocationFilter extends React.Component<any, any> {
LocationFilter.propTypes = {
activeFilter: PropTypes.object,
toggleFilter: PropTypes.func,
locationEnabledOptions: PropTypes.object,
};
3 changes: 2 additions & 1 deletion assets/agenda/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 || {},
};
}

Expand Down
1 change: 1 addition & 0 deletions assets/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ interface IClientConfig {
show_user_register?: boolean;
multimedia_website_search_url?: string;
show_default_time_frame_label?: boolean;
location_state_display_label?:boolean;
}

interface Window {
Expand Down
1 change: 1 addition & 0 deletions assets/interfaces/agenda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ export interface IAgendaState {
errors?: {[field: string]: Array<string>};
loadingAggregations?: boolean;
dateFilters?: IDateFilters;
locationsFiltersOptions?:{[key: string]: boolean};
devketanpro marked this conversation as resolved.
Show resolved Hide resolved
}

export type AgendaGetState = () => IAgendaState;
Expand Down
151 changes: 91 additions & 60 deletions newsroom/agenda/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", {}),
}


Expand Down Expand Up @@ -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

Expand All @@ -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": {
Expand All @@ -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,
)
13 changes: 13 additions & 0 deletions newsroom/web/default_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@
},
"agenda_sort_events_with_coverage_on_top": False,
"collapsed_search_by_default": False,
"location_state_display_label": "State",
}

# Enable rendering of the date in the base view
Expand Down Expand Up @@ -857,3 +858,15 @@
#: .. versionadded:: 2.8
#:
COVERAGE_REQUEST_EMAIL_CC_CURRENT_USER = False

#: Calendar Location Filter options config
#:
#: .. versionadded: 2.8
devketanpro marked this conversation as resolved.
Show resolved Hide resolved
#:

CALENDAR_LOCATIONS_FILTER_OPTIONS = {
"city": True,
"state": True,
"country": True,
"place": True,
}
Loading