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

Case tiles for case detail #33990

Merged
merged 33 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3eb0f1e
Added case tile config UI to case detail tab
orangejenny Nov 8, 2023
7881c13
Updated module saving to save template for either short or long detail
orangejenny Nov 8, 2023
26ac3eb
Removed validation limiting tiles to short detail
orangejenny Nov 8, 2023
43a1eb5
Added tile to web apps UI
orangejenny Nov 13, 2023
6de67c6
Removed redundant code
orangejenny Dec 28, 2023
f44dc72
Added translation
orangejenny Dec 28, 2023
5ca01d7
Updated case tile mapping dropdown to not show for tabs
orangejenny Dec 28, 2023
8aa35f3
Removed tab properties from case tile preview
orangejenny Dec 28, 2023
f6639d8
Fixed display of tab rows while using custom tile
orangejenny Dec 28, 2023
3b10e9b
Added suite support for case tiles in nested details
orangejenny Dec 29, 2023
1c80e62
Moved short detail logic behind if statement now that CaseTileHelper …
orangejenny Dec 29, 2023
e018ce0
Added display of case tiles for nodeset tabs
orangejenny Jan 16, 2024
a9d5ec6
Added shading for tab regions of case tile preview
orangejenny Jan 15, 2024
8613fdb
Limited detail tiles to manual configuration
orangejenny Jan 15, 2024
26f81d8
Extracted initialization logic for views that list case tiles
orangejenny Jan 16, 2024
f05d05e
Updated tiles to be non-clickable when displayed in details
orangejenny Jan 16, 2024
bde5dd6
Added validation that each tile row uses only one tab
orangejenny Jan 16, 2024
bd929c3
Added basic tests
orangejenny Jan 17, 2024
3eaa0cc
Fixed lint errors
orangejenny Jan 18, 2024
3843ed5
Updated test to use modern app version
orangejenny Jan 18, 2024
70606f1
Updated tests to use AppFactory
orangejenny Jan 18, 2024
904070a
Updated js
orangejenny Jan 18, 2024
701e5cc
Inlined print template logic
orangejenny Jan 18, 2024
700b0af
Moved new functionality under CASE_LIST_TILE_CUSTOM flag
orangejenny Jan 18, 2024
d6536ae
Fixed JavaScript declarations
orangejenny Jan 18, 2024
f09df8e
Semicolon not comma
MartinRiese Jan 19, 2024
d3ddb34
Fixed js syntax
orangejenny Jan 23, 2024
39cf74a
Updated colors
orangejenny Jan 23, 2024
da4aac2
Merge branch 'jls/case-tile-for-case-detail' of github.com:dimagi/com…
orangejenny Jan 23, 2024
95217e2
Updated declarations
orangejenny Jan 23, 2024
3697206
Merge branch 'master' into jls/case-tile-for-case-detail
orangejenny Jan 24, 2024
1709452
Updated persistent tiles to be unclickable to screen readers
orangejenny Jan 29, 2024
c079a53
Revert "Updated persistent tiles to be unclickable to screen readers"
orangejenny Jan 31, 2024
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
30 changes: 23 additions & 7 deletions corehq/apps/app_manager/helpers/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,19 +642,35 @@ def validate_details_for_build(self):

for detail in [self.module.case_details.short, self.module.case_details.long]:
if detail.case_tile_template:
if not detail.display == "short":
errors.append({
'type': self.__invalid_tile_configuration_type,
'module': self.get_module_info(),
'reason': _('Case tiles may only be used for the case list (not the case details).')
})
if detail.display != "short":
if detail.case_tile_template != "custom":
errors.append({
'type': self.__invalid_tile_configuration_type,
'module': self.get_module_info(),
'reason': _('Case tiles on the case detail must be manually configured.'),
})

tab_spans = detail.get_tab_spans()
tile_rows = defaultdict(set) # tile row index => {tabs that appear in that row}
for index, span in enumerate(tab_spans):
for col in detail.columns[span[0]:span[1]]:
if col.grid_y is not None:
tile_rows[col.grid_y].add(index)
for row_index, tab_index_list in tile_rows.items():
if len(tab_index_list) > 1:
errors.append({
'type': self.__invalid_tile_configuration_type,
'module': self.get_module_info(),
'reason': _('Each row of the tile may contain fields only from a single tab. '
'Row #{} contains fields from multiple tabs.').format(row_index + 1),
})
col_by_tile_field = {c.case_tile_field: c for c in detail.columns}
for field in case_tile_template_config(detail.case_tile_template).fields:
if field not in col_by_tile_field:
errors.append({
'type': self.__invalid_tile_configuration_type,
'module': self.get_module_info(),
'reason': _('A case property must be assigned to the "{}" tile field.'.format(field))
'reason': _('A case property must be assigned to the "{}" tile field.').format(field)
})
self._validate_fields_with_format_duplicate('address', 'Address', detail.columns, errors)
self._validate_clickable_icons(detail.columns, errors)
Expand Down
36 changes: 18 additions & 18 deletions corehq/apps/app_manager/static/app_manager/js/details/column.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,23 @@ hqDefine("app_manager/js/details/column", function () {
});
self.original.late_flag = _.isNumber(self.original.late_flag) ? self.original.late_flag : 30;

// Set up tab defaults
const tabDefaults = {
isTab: false,
hasNodeset: false,
nodeset: "",
nodesetCaseType: "",
nodesetFilter: "",
relevant: "",
};
self.original = _.defaults(self.original, tabDefaults);
let screenHasChildCaseTypes = screen.childCaseTypes && screen.childCaseTypes.length;
if (!self.original.nodeset && !self.original.nodesetCaseType && screenHasChildCaseTypes) {
// If there's no nodeset but there are child case types, default to showing a case type
self.original.nodesetCaseType = screen.childCaseTypes[0];
}
_.extend(self, _.pick(self.original, _.keys(tabDefaults)));

self.original.case_tile_field = ko.utils.unwrapObservable(self.original.case_tile_field) || "";
self.case_tile_field = ko.observable(self.original.case_tile_field);

Expand Down Expand Up @@ -99,30 +116,13 @@ hqDefine("app_manager/js/details/column", function () {
return Number(self.tileColumnStart()) + Number(self.tileWidth());
});
self.showInTilePreview = ko.computed(function () {
return self.coordinatesVisible() && self.tileRowStart() && self.tileColumnStart() && self.tileWidth() && self.tileHeight();
return !self.isTab && self.coordinatesVisible() && self.tileRowStart() && self.tileColumnStart() && self.tileWidth() && self.tileHeight();
});
self.tileContent = ko.observable();
self.setTileContent = function () {
self.tileContent(self.header.val());
};

// Set up tab defaults
const tabDefaults = {
isTab: false,
hasNodeset: false,
nodeset: "",
nodesetCaseType: "",
nodesetFilter: "",
relevant: "",
};
self.original = _.defaults(self.original, tabDefaults);
let screenHasChildCaseTypes = screen.childCaseTypes && screen.childCaseTypes.length;
if (!self.original.nodeset && !self.original.nodesetCaseType && screenHasChildCaseTypes) {
// If there's no nodeset but there are child case types, default to showing a case type
self.original.nodesetCaseType = screen.childCaseTypes[0];
}
_.extend(self, _.pick(self.original, _.keys(tabDefaults)));

self.screen = screen;
self.lang = screen.lang;
self.model = uiElement.select([{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,11 @@
if (hqImport('hqwebapp/js/toggles').toggleEnabled('CASE_LIST_TILE_CUSTOM')) {
baseCaseTileTemplateOptions = baseCaseTileTemplateOptions.concat([["custom", gettext("Manually configure Case Tiles")]]);
}
if (self.columnKey === 'short') {
baseCaseTileTemplateOptions = baseCaseTileTemplateOptions.concat(options.caseTileTemplateOptions);
}

self.caseTileTemplateOptions = baseCaseTileTemplateOptions.concat(options.caseTileTemplateOptions);
self.caseTileTemplateOptions = baseCaseTileTemplateOptions;
self.caseTileTemplateOptions = self.caseTileTemplateOptions.map(function (templateOption) {
return {templateValue: templateOption[0], templateName: templateOption[1]};
});
Expand Down Expand Up @@ -150,6 +153,21 @@
});
};

// Given a column model, return a boolean indicating whether the column is on an odd
// or an even tab, for the sake of being able to differentiate in the case tile preview
// which rows go with which tab.
self.tabPolarity = function (column) {
var self = this,
orangejenny marked this conversation as resolved.
Show resolved Hide resolved
flag = false;
_.find(self.columns(), function (c, i) {

Check failure on line 162 in corehq/apps/app_manager/static/app_manager/js/details/screen.js

View workflow job for this annotation

GitHub Actions / Lint Javascript

'i' is defined but never used
orangejenny marked this conversation as resolved.
Show resolved Hide resolved
if (c.isTab) {
flag = !flag;
}
return c === column;
});
return flag;
};

self.adjustTileGridArea = function (activeColumnIndex, rowDelta, columnDelta, widthDelta, heightDelta) {
let matrix = self._buildMatrix();
matrix = self._adjustTileGridArea(matrix, activeColumnIndex, rowDelta, columnDelta, widthDelta, heightDelta);
Expand Down
55 changes: 28 additions & 27 deletions corehq/apps/app_manager/suite_xml/features/case_tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from corehq.apps.app_manager import id_strings
from corehq.apps.app_manager.exceptions import SuiteError
from corehq.apps.app_manager.suite_xml.xml_models import (
Detail, XPathVariable, Text, TileGroup, Style, EndpointAction
Detail, XPathVariable, TileGroup, Style, EndpointAction
)
from corehq.apps.app_manager.util import (
module_offers_search,
Expand Down Expand Up @@ -75,7 +75,7 @@ def __init__(self, app, module, detail, detail_id, detail_type, build_profile_id
self.detail_column_infos = detail_column_infos
self.entries_helper = entries_helper

def build_case_tile_detail(self):
def build_case_tile_detail(self, detail, start, end):
from corehq.apps.app_manager.suite_xml.sections.details import DetailContributor
"""
Return a Detail node from an apps.app_manager.models.Detail that is
Expand All @@ -87,10 +87,10 @@ def build_case_tile_detail(self):

if self.detail.case_tile_template == CUSTOM:
from corehq.apps.app_manager.detail_screen import get_column_generator
title = Text(locale_id=id_strings.detail_title_locale(self.detail_type))
detail = Detail(id=self.detail_id, title=title)

for column_info in self.detail_column_infos:
start = start or 0
end = end or len(self.detail_column_infos)
for column_info in self.detail_column_infos[start:end]:
# column_info is an instance of DetailColumnInfo named tuple.
style = None
if any(field is not None for field in [column_info.column.grid_x, column_info.column.grid_y,
Expand Down Expand Up @@ -133,29 +133,30 @@ def build_case_tile_detail(self):
self.app, self.module, detail.actions, self.build_profile_id, self.entries_helper)

# Add case search action if needed
if module_offers_search(self.module) and not module_uses_inline_search(self.module):
if (case_search_action := DetailContributor.get_case_search_action(
self.module,
self.build_profile_id,
self.detail_id
)) is not None:
detail.actions.append(case_search_action)

# Excludes legacy tile template to preserve behavior of existing apps using this template.
if self.detail.case_tile_template not in [CaseTileTemplates.PERSON_SIMPLE.value, CUSTOM]:
self._populate_sort_elements_in_detail(detail)

DetailContributor.add_no_items_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})",
header_rows=self.detail.case_tile_group.header_rows
)
if self.detail_type.endswith('short'):
if module_offers_search(self.module) and not module_uses_inline_search(self.module):
if (case_search_action := DetailContributor.get_case_search_action(
self.module,
self.build_profile_id,
self.detail_id
)) is not None:
detail.actions.append(case_search_action)

# Excludes legacy tile template to preserve behavior of existing apps using this template.
if self.detail.case_tile_template not in [CaseTileTemplates.PERSON_SIMPLE.value, CUSTOM]:
self._populate_sort_elements_in_detail(detail)

DetailContributor.add_no_items_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})",
header_rows=self.detail.case_tile_group.header_rows
)

if hasattr(self.module, 'lazy_load_case_list_fields') and self.module.lazy_load_case_list_fields:
detail.lazy_loading = self.module.lazy_load_case_list_fields

if (self.detail_type == 'case_short' or self.detail_type == 'search_short') \
and hasattr(self.module, 'lazy_load_case_list_fields') and self.module.lazy_load_case_list_fields:
detail.lazy_loading = self.module.lazy_load_case_list_fields
return detail

def _get_matched_detail_column(self, case_tile_field):
Expand Down
82 changes: 44 additions & 38 deletions corehq/apps/app_manager/suite_xml/sections/details.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,31 +116,23 @@ def get_section_elements(self):
) # list of DetailColumnInfo named tuples
if detail_column_infos:
detail_id = id_strings.detail(module, detail_type)
if detail.case_tile_template:
helper = CaseTileHelper(self.app, module, detail, detail_id, detail_type,
self.build_profile_id, detail_column_infos, self.entries_helper)

d = helper.build_case_tile_detail()
self._add_custom_variables(detail, d)
print_template_path = None
if detail.print_template:
print_template_path = detail.print_template['path']
orangejenny marked this conversation as resolved.
Show resolved Hide resolved
locale_id = id_strings.detail_title_locale(detail_type)
title = Text(locale_id=locale_id) if locale_id else Text()
d = self.build_detail(
module,
detail_type,
detail,
detail_column_infos,
title,
tabs=list(detail.get_tabs()),
id=detail_id,
print_template=print_template_path,
)
if d:
elements.append(d)
else:
print_template_path = None
if detail.print_template:
print_template_path = detail.print_template['path']
locale_id = id_strings.detail_title_locale(detail_type)
title = Text(locale_id=locale_id) if locale_id else Text()
d = self.build_detail(
module,
detail_type,
detail,
detail_column_infos,
tabs=list(detail.get_tabs()),
id=detail_id,
title=title,
print_template=print_template_path,
)
if d:
elements.append(d)

# add the persist case context if needed and if
# case tiles are present and have their own persistent block
Expand All @@ -159,8 +151,8 @@ def get_section_elements(self):

return elements

def build_detail(self, module, detail_type, detail, detail_column_infos, tabs=None, id=None,
title=None, nodeset=None, print_template=None, start=0, end=None, relevant=None):
def build_detail(self, module, detail_type, detail, detail_column_infos, title, tabs=None, id=None,
nodeset=None, print_template=None, start=0, end=None, relevant=None):
"""
Recursively builds the Detail object.
(Details can contain other details for each of their tabs)
Expand All @@ -185,7 +177,7 @@ def build_detail(self, module, detail_type, detail, detail_column_infos, tabs=No
detail_type,
detail,
detail_column_infos,
title=Text(locale_id=id_strings.detail_tab_title_locale(
Text(locale_id=id_strings.detail_tab_title_locale(
module, detail_type, tab
)),
nodeset=self._get_detail_tab_nodeset(module, detail, tab),
Expand Down Expand Up @@ -223,19 +215,33 @@ def build_detail(self, module, detail_type, detail, detail_column_infos, tabs=No
)
if variables:
d.variables.extend(variables)

# Add fields
if end is None:
end = len(detail_column_infos)
for column_info in detail_column_infos[start:end]:
# column_info is an instance of DetailColumnInfo named tuple.
fields = get_column_generator(
self.app, module, detail, parent_tab_nodeset=nodeset,
detail_type=detail_type, entries_helper=self.entries_helper,
*column_info
).fields
for field in fields:
d.fields.append(field)

# Add fields
if detail.case_tile_template:
detail_id = id_strings.detail(module, detail_type)
helper = CaseTileHelper(
self.app,
module,
detail,
detail_id,
detail_type,
self.build_profile_id,
detail_column_infos,
self.entries_helper,
)
d = helper.build_case_tile_detail(d, start, end)
else:
for column_info in detail_column_infos[start:end]:
# column_info is an instance of DetailColumnInfo named tuple.
fields = get_column_generator(
self.app, module, detail, parent_tab_nodeset=nodeset,
detail_type=detail_type, entries_helper=self.entries_helper,
*column_info
).fields
for field in fields:
d.fields.append(field)

# Add actions
if detail_type.endswith('short') and not module.put_in_root:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ <h4 class="panel-title panel-title-nolink">
</h4>
</div>
<div class="panel-body" data-bind="with: longScreen">
{% if request|toggle_enabled:'CASE_DETAIL_TILE' %}
{% include 'app_manager/partials/modules/case_tile_templates.html' %}
{% include 'app_manager/partials/modules/case_tile_preview.html' %}
{% endif %}

{% include 'app_manager/partials/modules/case_list_properties.html' %}
</div>
</div>
Expand Down
Loading
Loading