Skip to content

Commit

Permalink
Merge pull request #33983 from dimagi/jt/case_search_on_clear
Browse files Browse the repository at this point in the history
Case Search on Clear
  • Loading branch information
stephherbers committed Jan 24, 2024
2 parents 33229e9 + d93f4bf commit 3a03969
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 46 deletions.
5 changes: 5 additions & 0 deletions corehq/apps/app_manager/helpers/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,11 @@ def validate_search_config(self):
"module": self.get_module_info(),
"property": prop.name,
}
if search_config.search_on_clear and self.module.is_auto_select():
yield {
"type": "search on clear with auto select",
"module": self.get_module_info(),
}

def validate_case_list_field_actions(self):
if hasattr(self.module, 'case_details'):
Expand Down
1 change: 1 addition & 0 deletions corehq/apps/app_manager/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2188,6 +2188,7 @@ class CaseSearch(DocumentSchema):
description = LabelProperty(default={})
include_all_related_cases = BooleanProperty(default=False)
dynamic_search = BooleanProperty(default=False)
search_on_clear = BooleanProperty(default=False)

# case property referencing another case's ID
custom_related_case_property = StringProperty(exclude_if_none=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ hqDefine("app_manager/js/details/case_claim", function () {
'title_label', 'description', 'search_button_display_condition', 'search_label', 'search_filter',
'additional_relevant', 'data_registry', 'data_registry_workflow', 'additional_registry_cases',
'custom_related_case_property', 'inline_search', 'instance_name', 'include_all_related_cases',
'search_on_clear',
];
var searchConfigModel = function (options, lang, searchFilterObservable, saveButton) {
hqImport("hqwebapp/js/assert_properties").assertRequired(options, searchConfigKeys);
Expand Down
28 changes: 16 additions & 12 deletions corehq/apps/app_manager/suite_xml/post_process/remote_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,19 +207,23 @@ def build_session(self):
)

def build_remote_request_queries(self):
kwargs = {
"url": absolute_reverse('app_aware_remote_search', args=[self.app.domain, self.app._id]),
"storage_instance": self.storage_instance,
"template": 'case',
"title": self.build_title() if self.app.enable_case_search_title_translation else None,
"description": self.build_description() if self.module.search_config.description != {} else None,
"data": self._remote_request_query_datums,
"prompts": self.build_query_prompts(),
"prompt_groups": self.build_query_prompt_groups(),
"default_search": self.module.search_config.default_search,
"dynamic_search": self.app.split_screen_dynamic_search and not self.module.is_auto_select()
}
if self.module.search_config.search_on_clear and toggles.SPLIT_SCREEN_CASE_SEARCH.enabled(self.app.domain):
kwargs["search_on_clear"] = (self.module.search_config.search_on_clear
and not self.module.is_auto_select())
return [
RemoteRequestQuery(
url=absolute_reverse('app_aware_remote_search', args=[self.app.domain, self.app._id]),
storage_instance=self.storage_instance,
template='case',
title=self.build_title() if self.app.enable_case_search_title_translation else None,
description=self.build_description() if self.module.search_config.description != {} else None,
data=self._remote_request_query_datums,
prompts=self.build_query_prompts(),
prompt_groups=self.build_query_prompt_groups(),
default_search=self.module.search_config.default_search,
dynamic_search=self.app.split_screen_dynamic_search and not self.module.is_auto_select(),
)
RemoteRequestQuery(**kwargs)
]

def build_remote_request_datums(self):
Expand Down
1 change: 1 addition & 0 deletions corehq/apps/app_manager/suite_xml/xml_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ class RemoteRequestQuery(OrderedXmlObject, XmlObject):
prompt_groups = NodeListField('group', QueryPromptGroup)
default_search = BooleanField("@default_search")
dynamic_search = BooleanField("@dynamic_search")
search_on_clear = BooleanField("@search_on_clear", required=False)

@property
def id(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ <h4 class="alert-heading">
{% blocktrans with module_name=error.module.name|trans:langs %}
The case list in <a href="{{ module_url }}">{{ module_name }}</a> can not use the same "search input instance name" as its Parent Select Menu.
{% endblocktrans %}
{% case "search on clear with auto select" %}
{% blocktrans with module_name=error.module.name|trans:langs %}
The case list in <a href="{{ module_url }}">{{ module_name }}</a> uses both multi-select auto select and "Clearing search terms resets search results".
These two features are not compatible.
{% endblocktrans %}
{% case "inline search to display only forms" %}
{% blocktrans with module_name=error.module.name|trans:langs %}
<a href="{{ module_url }}">{{ module_name }}</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -466,11 +466,23 @@ <h4 class="panel-title panel-title-nolink">{% trans "Search and Claim Options" %
</div>
</div>
{% endif %}
{% if request|toggle_enabled:'SPLIT_SCREEN_CASE_SEARCH' and not app.split_screen_dynamic_search%}
<div class="form-group">
<label class="control-label {% css_label_class %}">
{% trans "Clearing search terms resets search results" %}
</label>
<div class="{% css_field_class %} checkbox">
<label>
<input type="checkbox" data-bind="checked: search_on_clear" />
</label>
</div>
</div>
{% endif %}
<span class="hide" id="searchFilterXpathErrorHtml">
{% blocktrans %}
This is not a valid xpath expression. Check to make sure your parentheses match and you are referencing case properties correctly.
{% endblocktrans %}
</span>
</span>
</div>
</div>
</div>
Expand Down
13 changes: 13 additions & 0 deletions corehq/apps/app_manager/tests/test_build_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,3 +418,16 @@ def test_form_link_validation_mismatched_shadow_module(self, *args):
'module': {'id': 0, 'name': {'en': 'm0 module'}},
'form': {'id': 0, 'name': {'en': 'm0 form 0'}},
}, errors)

@patch('corehq.apps.app_manager.models.ModuleBase.is_auto_select', return_value=True)
def test_search_on_clear_with_auto_select(self, *args):
factory = AppFactory()
module = factory.new_basic_module('basic', 'person', with_form=False)
module.search_config = CaseSearch(
search_on_clear=True,
)
errors = factory.app.validate_app()
self.assertIn({
'type': 'search on clear with auto select',
'module': {'id': 0, 'unique_id': 'basic_module', 'name': {'en': 'basic module'}},
}, errors)
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ def setUp(self):
self.module.search_config = CaseSearch(
properties=[
CaseSearchProperty(name='name', label={'en': 'Name'}),
]
],
search_on_clear=True,
)

self.module.assign_references()
# wrap to have assign_references called
self.app = Application.wrap(self.app.to_json())
Expand All @@ -78,3 +80,17 @@ def test_dynamic_search_suite_disable_with_auto_select(self, mock):
suite = parse_normalize(suite, to_string=False)
self.assertEqual(True, self.module.is_auto_select())
self.assertEqual("false", suite.xpath("./remote-request[1]/session/query/@dynamic_search")[0])

@flag_enabled('SPLIT_SCREEN_CASE_SEARCH')
def test_search_on_clear(self):
suite = self.app.create_suite()
suite = parse_normalize(suite, to_string=False)
self.assertEqual("true", suite.xpath("./remote-request[1]/session/query/@search_on_clear")[0])

@patch('corehq.apps.app_manager.models.ModuleBase.is_auto_select', return_value=True)
@flag_enabled('SPLIT_SCREEN_CASE_SEARCH')
def test_search_on_clear_disable_with_auto_select(self, mock):
suite = self.app.create_suite()
suite = parse_normalize(suite, to_string=False)
self.assertEqual(True, self.module.is_auto_select())
self.assertEqual("false", suite.xpath("./remote-request[1]/session/query/@search_on_clear")[0])
2 changes: 2 additions & 0 deletions corehq/apps/app_manager/views/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ def _get_shared_module_view_context(request, app, module, case_property_builder,
'instance_name': module.search_config.instance_name or "",
'include_all_related_cases': module.search_config.include_all_related_cases,
'dynamic_search': app.split_screen_dynamic_search,
'search_on_clear': module.search_config.search_on_clear,
},
},
}
Expand Down Expand Up @@ -1395,6 +1396,7 @@ def _check_xpath(xpath, location):
instance_name=instance_name,
include_all_related_cases=search_properties.get('include_all_related_cases', False),
dynamic_search=app.split_screen_dynamic_search and not module.is_auto_select(),
search_on_clear=search_properties.get('search_on_clear', False),
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ hqDefine("cloudcare/js/formplayer/menus/collections", function () {

queryProperties: [
'groupHeaders',
'searchOnClear',
],

parse: function (response) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,26 +106,44 @@ hqDefine("cloudcare/js/formplayer/menus/controller", function () {
var showMenu = function (menuResponse) {
var menuListView = menusUtils.getMenuView(menuResponse);
var appPreview = FormplayerFrontend.currentUser.displayOptions.singleAppMode;
var queryResponse = menuResponse.queryResponse;
var sidebarEnabled = !appPreview && menusUtils.isSidebarEnabled(menuResponse);
if (sidebarEnabled && menuResponse.type === constants.QUERY) {
var menuData = menusUtils.getMenuData(menuResponse);
menuData["triggerEmptyCaseList"] = true;
menuData["sidebarEnabled"] = true;
menuData["description"] = menuResponse.description;

var caseListView = menusUtils.getCaseListView(menuResponse);
FormplayerFrontend.regions.getRegion('main').show(caseListView(menuData));
} else if (menuListView) {
if (menuListView && !sidebarEnabled) {
FormplayerFrontend.regions.getRegion('main').show(menuListView);
}
if (sidebarEnabled) {
showSplitScreenQuery(menuResponse, menuListView);
} else {
FormplayerFrontend.regions.getRegion('sidebar').empty();
}
if (menuResponse.persistentCaseTile && !appPreview) {
showPersistentCaseTile(menuResponse.persistentCaseTile);
} else {
FormplayerFrontend.regions.getRegion('persistentCaseTile').empty();
}

if (sidebarEnabled && menuResponse.type === constants.ENTITIES && queryResponse) {
if (menuResponse.breadcrumbs) {
menusUtils.showBreadcrumbs(menuResponse.breadcrumbs);
if (!appPreview) {
let isFormEntry = !menuResponse.queryKey;
if (isFormEntry) {
menusUtils.showMenuDropdown(menuResponse.langs, initialPageData.get('lang_code_name_mapping'));
}
if (menuResponse.type === constants.ENTITIES) {
menusUtils.showMenuDropdown();
}
}
} else {
FormplayerFrontend.regions.getRegion('breadcrumb').empty();
}
if (menuResponse.appVersion) {
FormplayerFrontend.trigger('setVersionInfo', menuResponse.appVersion);
}
};

var showSplitScreenQuery = function (menuResponse, menuListView) {
var menuData = menusUtils.getMenuData(menuResponse);
var queryResponse = menuResponse.queryResponse;
if (menuResponse.type === constants.ENTITIES && queryResponse) {
var queryCollection = new Collection(queryResponse.displays);
FormplayerFrontend.regions.getRegion('sidebar').show(
queryView.queryListView({
Expand All @@ -136,9 +154,11 @@ hqDefine("cloudcare/js/formplayer/menus/controller", function () {
sidebarEnabled: true,
disableDynamicSearch: !sessionStorage.submitPerformed,
groupHeaders: queryResponse.groupHeaders,
searchOnClear: queryResponse.searchOnClear,
}).render()
);
} else if (sidebarEnabled && menuResponse.type === constants.QUERY) {
FormplayerFrontend.regions.getRegion('main').show(menuListView);
} else if (menuResponse.type === constants.QUERY) {
FormplayerFrontend.regions.getRegion('sidebar').show(
queryView.queryListView({
collection: menuResponse,
Expand All @@ -148,28 +168,16 @@ hqDefine("cloudcare/js/formplayer/menus/controller", function () {
sidebarEnabled: true,
disableDynamicSearch: true,
groupHeaders: menuResponse.groupHeaders,
searchOnClear: menuResponse.searchOnClear,
}).render()
);
} else {
FormplayerFrontend.regions.getRegion('sidebar').empty();
}

if (menuResponse.breadcrumbs) {
menusUtils.showBreadcrumbs(menuResponse.breadcrumbs);
if (!appPreview) {
let isFormEntry = !menuResponse.queryKey;
if (isFormEntry) {
menusUtils.showMenuDropdown(menuResponse.langs, initialPageData.get('lang_code_name_mapping'));
}
if (menuResponse.type === constants.ENTITIES) {
menusUtils.showMenuDropdown();
}
}
} else {
FormplayerFrontend.regions.getRegion('breadcrumb').empty();
}
if (menuResponse.appVersion) {
FormplayerFrontend.trigger('setVersionInfo', menuResponse.appVersion);
menuData["triggerEmptyCaseList"] = true;
menuData["sidebarEnabled"] = true;
menuData["description"] = menuResponse.description;

var caseListView = menusUtils.getCaseListView(menuResponse);
FormplayerFrontend.regions.getRegion('main').show(caseListView(menuData));
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,7 @@ hqDefine("cloudcare/js/formplayer/menus/views/query", function () {

this.dynamicSearchEnabled = !(options.disableDynamicSearch || this.smallScreenEnabled) &&
(toggles.toggleEnabled('DYNAMICALLY_UPDATE_SEARCH_RESULTS') && this.options.sidebarEnabled);
this.searchOnClear = (options.searchOnClear && !this.smallScreenEnabled);

if (Object.keys(options.groupHeaders).length > 0) {
const groupedCollection = groupDisplays(options.collection, options.groupHeaders);
Expand Down Expand Up @@ -707,7 +708,7 @@ hqDefine("cloudcare/js/formplayer/menus/views/query", function () {
childView.clear();
});
self.setStickyQueryInputs();
if (self.dynamicSearchEnabled) {
if (self.dynamicSearchEnabled || this.searchOnClear) {
self.updateSearchResults();
}
},
Expand Down

0 comments on commit 3a03969

Please sign in to comment.