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

Icf cpheapm61 customizable mgmt dashboard #1112

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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 client/hawc_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .interactive import InteractiveHawcClient
from .invitro import InvitroClient
from .literature import LiteratureClient
from .mgmt import MgmtClient
from .riskofbias import RiskOfBiasClient
from .session import HawcSession
from .study import StudyClient
Expand Down Expand Up @@ -45,6 +46,7 @@ def __init__(self, root_url: str = "https://hawcproject.org"):
self.epiv2 = EpiV2Client(self.session)
self.invitro = InvitroClient(self.session)
self.lit = LiteratureClient(self.session)
self.mgmt = MgmtClient(self.session)
self.riskofbias = RiskOfBiasClient(self.session)
self.summary = SummaryClient(self.session)
self.study = StudyClient(self.session)
Expand Down
38 changes: 38 additions & 0 deletions client/hawc_client/mgmt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import pandas as pd

from .client import BaseClient


class MgmtClient(BaseClient):
"""
Client class for task management requests
"""

def tasks(self, assessment_id: int) -> list[dict]:
"""
Retrieves all tasks for the given assessment

Args:
Assessment (int): Assessment Id

Returns:
pd.DataFrame: Assessment task information
"""
url = f"{self.session.root_url}/mgmt/api/assessment/{assessment_id}/export"
response_json = self.session.post(url, assessment_id).json()
return pd.DataFrame(response_json)

def time_spent(self, assessment_id: int):
"""
Retrieves time spent for the given assessment

Args:
Assessment (int): Assessment Id

Returns:
pd.DataFrame: Assessment time spent
"""

url = f"{self.session.root_url}/mgmt/api/assessment/{assessment_id}/time-spent"
response_json = self.session.post(url, assessment_id).json()
return pd.DataFrame(response_json)
5 changes: 5 additions & 0 deletions hawc/apps/mgmt/actions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .mgmt_approach import clone_approach

__all__ = [
"clone_approach",
]
54 changes: 54 additions & 0 deletions hawc/apps/mgmt/actions/mgmt_approach.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from textwrap import dedent

from django.db import transaction

from ...assessment.models import Assessment, Log
from ..models import Task


@transaction.atomic
def clone_approach(
dest_assessment: Assessment, src_assessment: Assessment, user_id: int | None = None
):
"""
Clone approach from one assessment to another.
"""

# log about changes being made
tasks = Task.objects.filter(study__assessment=dest_assessment)
task_types = dest_assessment.task_types.all()
task_statuses = dest_assessment.task_statuses.all()
task_triggers = dest_assessment.task_triggers.all()

log_message = dedent(
f"""\
Cloning Task setup approach: {src_assessment.id} -> {dest_assessment.id}
Deleting {tasks.count()} TaskType objects
Deleting {task_types.count()} TaskType objects
Deleting {task_statuses.count()} TaskStatus objects
Deleting {task_triggers.count()} TaskTrigger objects
"""
)
Log.objects.create(assessment=dest_assessment, user_id=user_id, message=log_message)

# delete existing data (recursively deletes task triggers, types, statueses, etc)
tasks.delete()
task_triggers.delete()
task_types.delete()
task_statuses.delete()

# copy task setup to new assessment (new tasks should be auto-created by trigger)
for status in src_assessment.task_statuses.all():
status.id = None
status.assessment = dest_assessment
status.save()

for type in src_assessment.task_types.all():
type.id = None
type.assessment = dest_assessment
type.save()

for trigger in src_assessment.task_triggers.all():
trigger.id = None
trigger.assessment = dest_assessment
trigger.save()
48 changes: 46 additions & 2 deletions hawc/apps/mgmt/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,53 @@ class TaskAdmin(admin.ModelAdmin):
"type",
"owner",
"status",
"open",
"notes",
"due_date",
"started",
"completed",
)
list_select_related = ("study", "owner")
list_select_related = ("study", "owner", "type", "status")


@admin.register(models.TaskType)
class TaskTypeAdmin(admin.ModelAdmin):
search_fields = ("assessment__name", "assessment__id", "name")
list_display = (
"assessment",
"name",
"order",
"description",
"created",
"last_updated",
)
list_select_related = ("assessment",)


@admin.register(models.TaskStatus)
class TaskStatusAdmin(admin.ModelAdmin):
search_fields = ("assessment__name", "assessment__id", "name")
list_display = (
"assessment",
"name",
"value",
"description",
"order",
"color",
"terminal_status",
"created",
"last_updated",
)


@admin.register(models.TaskTrigger)
class TaskTriggerAdmin(admin.ModelAdmin):
search_fields = ("task_type__name", "current_status__name", "event")
list_display = (
"task_type",
"current_status",
"next_status",
"event",
"created",
"last_updated",
)
list_select_related = ("task_type", "current_status", "next_status")
2 changes: 1 addition & 1 deletion hawc/apps/mgmt/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def tasks(self, request, pk):
assessment: Assessment = self.get_object()
qs = (
models.Task.objects.get_qs(assessment)
.select_related("study", "owner")
.select_related("study", "owner", "type", "status")
.order_by("study_id", "type", "id")
)
exporter = exports.TaskExporter.flat_export(qs, filename=f"{assessment}-task")
Expand Down
3 changes: 3 additions & 0 deletions hawc/apps/mgmt/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
class MgmtConfig(AppConfig):
name = "hawc.apps.mgmt"
verbose_name = "Project management"

def ready(self):
from . import signals # noqa: F401
23 changes: 20 additions & 3 deletions hawc/apps/mgmt/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,30 @@ class TaskStatus(models.IntegerChoices):

@classmethod
def extra_choices(cls):
return [(990, "Active"), (995, "Inactive"), *cls.choices]
return [(990, "Active"), (995, "Inactive")]

@classmethod
def filter_extra(cls, value: int) -> models.Q:
if value == 990:
return models.Q(status__in=[10, 20])
return models.Q(status__terminal_status=False)
elif value == 995:
return models.Q(status__in=[30, 40])
return models.Q(status__terminal_status=True)
else:
return models.Q(status=value)

@classmethod
def status_colors(cls, value: int):
colors = {
10: "#CFCFCF",
20: "#FFCC00",
30: "#00CC00",
40: "#CC3333",
}
return colors.get(value)


class StartTaskTriggerEvent(models.IntegerChoices):
STUDY_CREATION = 10, "Create Study"
DATA_EXTRACTION = 20, "Data Extraction"
MODIFY_ROB = 30, "Modify Study Evaluation"
COMPLETE_ROB = 40, "Complete Study Evaluation"
13 changes: 2 additions & 11 deletions hawc/apps/mgmt/exports.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from ..common.exports import Exporter, ModelExport
from ..common.models import sql_display
from ..study.exports import StudyExport
from . import constants


class TaskExport(ModelExport):
Expand All @@ -11,21 +9,14 @@ def get_value_map(self):
"owner_id": "owner",
"owner_email": "owner__email",
"type": "type",
"type_display": "type_display",
"type_display": "type__name",
"status": "status",
"status_display": "status_display",
"open": "open",
"status_display": "status__name",
"due_date": "due_date",
"started": "started",
"completed": "completed",
}

def get_annotation_map(self, query_prefix):
return {
"type_display": sql_display(query_prefix + "type", constants.TaskType),
"status_display": sql_display(query_prefix + "status", constants.TaskStatus),
}


class TaskExporter(Exporter):
def build_modules(self) -> list[ModelExport]:
Expand Down
21 changes: 18 additions & 3 deletions hawc/apps/mgmt/filterset.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,21 @@ class TaskFilterSet(BaseFilterSet):
help_text="Data type for full-text extraction",
empty_label="- Data Type -",
)
type = df.ChoiceFilter(
type = df.ModelChoiceFilter(
queryset=models.TaskType.objects.none(),
method="filter_type",
empty_label="- Type -",
choices=constants.TaskType.choices,
)
owner = df.ModelChoiceFilter(
label="Assigned user",
queryset=HAWCUser.objects.none(),
help_text="Includes all tasks for a study where a user has at least one assignment",
empty_label="- User -",
)
# The status search filter is now dynamic. To continue displaying the 'active' and 'inactive'
# search options, it can't use the ModelChoiceFilter that 'type' now uses.
status = df.ChoiceFilter(
empty_label="- Status -",
choices=constants.TaskStatus.extra_choices(),
method="filter_status",
)
order_by = TaskOrderingFilter(
Expand All @@ -75,10 +77,23 @@ def filter_search(self, queryset, name, value):
def filter_data_type(self, queryset, name, value):
return queryset.filter(**{f"study__{value}": True})

def filter_type(self, queryset, name, value):
return queryset.filter(type=value)

def filter_status(self, queryset, name, value):
return queryset.filter(constants.TaskStatus.filter_extra(int(value)))

def get_status_choices(self, assessment):
model_choices = models.TaskStatus.objects.filter(assessment=assessment).values_list(
"id", "name"
)
extra_choices = constants.TaskStatus.extra_choices()
return list(model_choices) + extra_choices

def create_form(self):
self.filters["status"].extra.update({"choices": self.get_status_choices(self.assessment)})

form = super().create_form()
form.fields["type"].queryset = models.TaskType.objects.filter(assessment=self.assessment)
form.fields["owner"].queryset = self.assessment.pms_and_team_users()
return form
Loading
Loading