Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/ay/async-geo-case-reassignment-t…
Browse files Browse the repository at this point in the history
…est' into autostaging
  • Loading branch information
kaapstorm committed Aug 26, 2024
2 parents 1df4e67 + b72b872 commit 3ce7809
Show file tree
Hide file tree
Showing 11 changed files with 522 additions and 21 deletions.
4 changes: 2 additions & 2 deletions corehq/apps/geospatial/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ class CaseManagementMap(BaseCaseMapReport):
name = gettext_noop("Case Management Map")
slug = "case_management_map"

base_template = "geospatial/map_visualization_base.html"
report_template_path = "map_visualization.html"
base_template = "geospatial/case_management_base.html"
report_template_path = "case_management.html"

def default_report_url(self):
return reverse('geospatial_default', args=[self.request.project.name])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

hqDefine("geospatial/js/geospatial_map", [
hqDefine("geospatial/js/case_management", [
"jquery",
"hqwebapp/js/initial_page_data",
"knockout",
Expand Down Expand Up @@ -275,7 +275,7 @@ hqDefine("geospatial/js/geospatial_map", [
// disbursement if this is the case
const hasValidData = selectedCases.length && selectedUsers.length;
if (!hasValidData) {
const errorMessage = gettext("Please ensure that the filtered area includes both cases" +
const errorMessage = gettext("Please ensure that the filtered area includes both cases " +
"and mobile workers before attempting to run disbursement.");
disbursementRunner.disbursementErrorMessage(errorMessage);
} else {
Expand Down Expand Up @@ -431,7 +431,7 @@ hqDefine("geospatial/js/geospatial_map", [
}

function initAssignmentReview() {
const $manageAssignmentModal = $("#review-assignments");
const $manageAssignmentModal = $("#assignments-results");
if ($manageAssignmentModal.length) {
assignmentManagerModel = models.AssignmentManager(mapModel, disbursementRunner);
$manageAssignmentModal.koApplyBindings(assignmentManagerModel);
Expand Down
37 changes: 37 additions & 0 deletions corehq/apps/geospatial/static/geospatial/js/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ hqDefine('geospatial/js/models', [
const DEFAULT_CENTER_COORD = [-20.0, -0.0];
const DISBURSEMENT_LAYER_PREFIX = 'route-';
const saveGeoPolygonUrl = initialPageData.reverse('geo_polygons');
const reassignCasesUrl = initialPageData.reverse('reassign_cases');
const unexpectedErrorMessage = "Oops! Something went wrong! Please report an issue if the problem persists.";

var MissingGPSModel = function () {
Expand Down Expand Up @@ -1059,6 +1060,42 @@ hqDefine('geospatial/js/models', [
utils.downloadCsv(casesToExport, headers, cols, 'Case Assignment Export');
};

self.acceptAssignments = function () {
let caseIdToOwnerId = {};
for (const caseItem of self.mapModel.caseMapItems()) {
const caseData = self.mapModel.caseGroupsIndex[caseItem.itemId];
if ('assignedUserId' in caseData && caseData.assignedUserId) {
caseIdToOwnerId[caseData.item.itemId] = caseData.assignedUserId;
}
}
let requestData = {
'case_id_to_owner_id': caseIdToOwnerId,
'include_related_cases': self.includeRelatedCases(),
};

$.ajax({
type: 'post',
url: reassignCasesUrl,
dataType: 'json',
data: JSON.stringify(requestData),
contentType: "application/json; charset=utf-8",
success: function (response) {
if (!response.success) {
return alertUser.alert_user(response.message, 'danger');
}
alertUser.alert_user(response.message, 'success', false, true);
},
error: function (response) {
const responseText = response.responseText;
if (responseText) {
alertUser.alert_user(responseText, 'danger');
} else {
alertUser.alert_user(gettext(unexpectedErrorMessage), 'danger');
}
},
});
};

return self;
};

Expand Down
24 changes: 24 additions & 0 deletions corehq/apps/geospatial/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from celery import states
from celery.result import AsyncResult

from corehq.apps.celery import task
from corehq.apps.geospatial.utils import update_cases_owner


@task(queue="background_queue", ignore_result=False)
def geo_cases_reassignment_update_owners(domain, case_id_to_owner_id):
from time import sleep
sleep(30)
update_cases_owner(domain, case_id_to_owner_id)


def is_task_invoked_and_not_completed(task_id):
"""Returns True is a task is invoked and is executing or waiting to be executed.
NOTE: Only works for tasks that store results i.e. ignore_result must be False.
NOTE: Celery states.PENDING is a bit ambiguous as it could mean two things one, task was never invoked and two
task was invoked but still waiting to picked up.
However, in HQ we set state to `SENT` as soon as it is invoked.
See 'casexml.apps.phone.tasks.update_celery_state'
"""
result = AsyncResult(task_id)
return result.state not in [states.PENDING, states.SUCCESS, states.FAILURE, states.REVOKED]
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,13 @@ <h4 class="text-center">
</div>

<div id="case-buttons">
<div id="review-assignments" class="pull-left">
<div id="assignments-results" class="pull-left">
<button class="btn btn-default" data-toggle="modal" data-target="#review-assignment-results" data-bind="click: loadCaseData, enable: canOpenModal">
{% trans 'Review Results' %}
</button>
<button id="accept-assignment-results" class="btn btn-default" data-bind="click: acceptAssignments, enable: canOpenModal">
{% trans 'Accept Results' %}
</button>
{% include 'geospatial/partials/review_assignment_modal.html' %}
</div>
<div id="user-modals" class="pull-right">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

{% block js %}{{ block.super }}
{% compress js %}
<script src="{% static 'geospatial/js/geospatial_map.js' %}"></script>
<script src="{% static 'geospatial/js/case_management.js' %}"></script>
{% endcompress %}
{% endblock %}

Expand All @@ -21,4 +21,5 @@
{% registerurl 'get_users_with_gps' domain %}
{% registerurl 'edit_commcare_user' domain '---' %}
{% registerurl 'location_search' domain %}
{% registerurl 'reassign_cases' domain %}
{% endblock %}
56 changes: 51 additions & 5 deletions corehq/apps/geospatial/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@
from corehq.apps.domain.shortcuts import create_domain
from corehq.apps.es import case_search_adapter
from corehq.apps.es.tests.utils import es_test
from corehq.form_processor.models import CommCareCase
from corehq.apps.users.models import CommCareUser
from corehq.form_processor.tests.utils import create_case
from corehq.apps.geospatial.const import GPS_POINT_CASE_PROPERTY
from corehq.apps.geospatial.models import GeoConfig
from corehq.apps.geospatial.utils import (
create_case_with_gps_property,
get_geo_case_property,
get_geo_user_property,
set_case_gps_property,
set_user_gps_property,
create_case_with_gps_property,
update_cases_owner,
)
from corehq.apps.geospatial.const import GPS_POINT_CASE_PROPERTY
from corehq.apps.users.models import CommCareUser
from corehq.form_processor.models import CommCareCase
from corehq.form_processor.tests.utils import create_case


class TestGetGeoProperty(TestCase):
Expand Down Expand Up @@ -116,3 +117,48 @@ def test_set_user_gps_property(self):
set_user_gps_property(self.DOMAIN, submit_data)
user = CommCareUser.get_by_user_id(self.user.user_id, self.DOMAIN)
self.assertEqual(user.get_user_data(self.DOMAIN)[GPS_POINT_CASE_PROPERTY], '1.23 4.56 0.0 0.0')


class TestUpdateCasesOwner(TestCase):
domain = 'test-domain'

def setUp(self):
super().setUp()
self.user_a = CommCareUser.create(self.domain, 'User_A', '1234', None, None)
self.case_1 = create_case(self.domain, case_id=uuid4().hex, save=True, owner_id=self.user_a.user_id)
self.case_2 = create_case(self.domain, case_id=uuid4().hex, save=True, owner_id=self.user_a.user_id)

self.user_b = CommCareUser.create(self.domain, 'User_B', '1234', None, None)
self.case_3 = create_case(self.domain, case_id=uuid4().hex, save=True, owner_id=self.user_b.user_id)
self.case_4 = create_case(self.domain, case_id=uuid4().hex, save=True, owner_id=self.user_b.user_id)

def tearDown(self):
self.user_a.delete(self.domain, None)
self.user_b.delete(self.domain, None)
CommCareCase.objects.hard_delete_cases(
self.domain,
[self.case_1.case_id, self.case_2.case_id, self.case_3.case_id, self.case_4.case_id]
)
super().tearDown()

def _refresh_cases(self):
self.case_1.refresh_from_db()
self.case_2.refresh_from_db()
self.case_3.refresh_from_db()
self.case_4.refresh_from_db()

def test_update_cases_owner(self):
case_id_to_owner_id = {
self.case_1.case_id: self.user_b.user_id,
self.case_2.case_id: self.user_b.user_id,
self.case_3.case_id: self.user_a.user_id,
self.case_4.case_id: self.user_a.user_id,
}

update_cases_owner(self.domain, case_id_to_owner_id, chunk_size=2)

self._refresh_cases()
assert self.case_1.owner_id == self.user_b.user_id
assert self.case_2.owner_id == self.user_b.user_id
assert self.case_3.owner_id == self.user_a.user_id
assert self.case_4.owner_id == self.user_a.user_id
Loading

0 comments on commit 3ce7809

Please sign in to comment.