Skip to content

Commit

Permalink
Merge branch 'master' into bmb/datetimepicker
Browse files Browse the repository at this point in the history
  • Loading branch information
orangejenny committed Jan 25, 2024
2 parents 3c349d5 + decdc13 commit 9e8a8aa
Show file tree
Hide file tree
Showing 87 changed files with 2,519 additions and 1,250 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
/corehq/apps/callcenter/ @snopoke
/corehq/apps/change_feed/ @dannyroberts
/corehq/apps/commtrack/ @esoergel
/corehq/apps/data_dictionary/ @esoergel @orangejenny
/corehq/apps/data_dictionary/ @esoergel @orangejenny @zandre-eng
/corehq/apps/domain/decorators.py @esoergel
/corehq/apps/domain/auth.py @esoergel
/corehq/apps/es/ @esoergel @amitphulera
Expand Down
6 changes: 6 additions & 0 deletions corehq/apps/app_manager/app_strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ def _create_module_details_app_strings(module, langs):
clean_trans(module.case_details.short.no_items_text, langs)
)

if module.get_app().supports_select_text and hasattr(module, 'case_details'):
yield (
id_strings.select_text_detail(module),
clean_trans(module.case_details.short.select_text, langs)
)

for detail_type, detail, _ in module.get_details():
for column in detail.get_columns():
yield (
Expand Down
7 changes: 7 additions & 0 deletions corehq/apps/app_manager/feature_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,13 @@ def supports_empty_case_list_text(self):
and self._require_minimum_version('2.54')
)

@property
def supports_select_text(self):
"""
Ability to configure select button text through bulk translations
"""
return self._require_minimum_version('2.54')

@property
def supports_menu_instances(self):
return self._require_minimum_version('2.54')
Expand Down
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
5 changes: 5 additions & 0 deletions corehq/apps/app_manager/id_strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,11 @@ def no_items_text_detail(module):
return detail(module, 'no_items_text')


@pattern('m%d_select_text')
def select_text_detail(module):
return detail(module, 'select_text')


def fixture_detail(module):
return detail(module, 'fixture_select')

Expand Down
3 changes: 3 additions & 0 deletions corehq/apps/app_manager/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2024,6 +2024,8 @@ class Detail(IndexedSchema, CaseListLookupMixin):
#Only applies to 'short' details
no_items_text = LabelProperty(default={'en': 'List is empty.'})

select_text = LabelProperty(default={'en': 'Continue'})

def get_instance_name(self, module):
value_is_the_default = self.instance_name == 'casedb'
if value_is_the_default:
Expand Down Expand Up @@ -2188,6 +2190,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
2 changes: 2 additions & 0 deletions corehq/apps/app_manager/suite_xml/features/case_tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ def build_case_tile_detail(self):

DetailContributor.add_no_items_text_to_detail(detail, self.app, self.detail_type, self.module)

DetailContributor.add_select_text_to_detail(detail, self.app, self.detail_type, self.module)

if self.module.has_grouped_tiles():
detail.tile_group = TileGroup(
function=f"string(./index/{self.detail.case_tile_group.index_identifier})",
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
7 changes: 7 additions & 0 deletions corehq/apps/app_manager/suite_xml/sections/details.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ def build_detail(self, module, detail_type, detail, detail_column_infos, tabs=No
id
)) is not None:
d.actions.append(case_search_action)
# Add select text
self.add_select_text_to_detail(d, self.app, detail_type, module)

try:
if not self.app.enable_multi_sort:
Expand Down Expand Up @@ -553,6 +555,11 @@ def add_no_items_text_to_detail(detail, app, detail_type, module):
if detail_type.endswith('short') and app.supports_empty_case_list_text:
detail.no_items_text = Text(locale_id=id_strings.no_items_text_detail(module))

@staticmethod
def add_select_text_to_detail(detail, app, detail_type, module):
if detail_type.endswith('short') and app.supports_select_text:
detail.select_text = Text(locale_id=id_strings.select_text_detail(module))


class DetailsHelper(object):

Expand Down
2 changes: 2 additions & 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 Expand Up @@ -933,6 +934,7 @@ class Detail(OrderedXmlObject, IdNode):
fields = NodeListField('field', Field)
actions = NodeListField('action', Action)
details = NodeListField('detail', "self")
select_text = NodeField('select_text/text', Text)
_variables = NodeField('variables', DetailVariableList)
relevant = StringField('@relevant')
tile_group = NodeField('group', TileGroup)
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
6 changes: 5 additions & 1 deletion corehq/apps/app_manager/tests/app_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,10 @@ def form_opens_case(form, case_type=None, is_subcase=False, parent_tag=None, is_
if not parent_tag:
parent_tag = form.actions.load_update_cases[-1].case_tag

action.case_indices = [CaseIndex(tag=parent_tag, relationship='extension' if is_extension else 'child')]
action.case_indices = [CaseIndex(
tag=parent_tag,
relationship='extension' if is_extension else 'child'
)]

form.actions.open_cases.append(action)

Expand Down Expand Up @@ -234,6 +237,7 @@ def case_claim_app_factory(cls):
case_module.search_config.description = {
'en': 'More information',
}

case_module.search_config.properties = [CaseSearchProperty(
name='name',
label={'en': 'Name of Mother'}
Expand Down
18 changes: 18 additions & 0 deletions corehq/apps/app_manager/tests/test_app_strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,21 @@ def test_form_submit_label(self):
self.assertEqual(default_strings['forms.m0f0.submit_label'], form.submit_label['en'])
self.assertEqual(default_strings['forms.m0f0.submit_notification_label'],
form.submit_notification_label['en'])

def test_select_text_app_strings(self):
factory = AppFactory(build_version='2.54.0')
factory.app.langs = ['en', 'fra']
factory.app.build_profiles = OrderedDict({
'en': BuildProfile(langs=['en'], name='en-profile'),
'fra': BuildProfile(langs=['fra'], name='fra-profile'),
})
module, form = factory.new_basic_module('my_module', 'cases')
module.case_details.short.select_text = {'en': 'Continue with case', 'fra': 'Continuer avec le cas'}

app = Application.wrap(factory.app.to_json())

en_app_strings = self._generate_app_strings(app, 'default', build_profile_id='en')
self.assertEqual(en_app_strings['m0_select_text'], 'Continue with case')

es_app_strings = self._generate_app_strings(app, 'fra', build_profile_id='fra')
self.assertEqual(es_app_strings['m0_select_text'], 'Continuer avec le cas')
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])
Loading

0 comments on commit 9e8a8aa

Please sign in to comment.