Skip to content

Commit

Permalink
Merge pull request #675 from dimagi/smh/versioning-details-view
Browse files Browse the repository at this point in the history
Experiment versioning UI: version details view
  • Loading branch information
SmittieC authored Sep 19, 2024
2 parents a8485ff + 11abeb5 commit e90138f
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 1 deletion.
44 changes: 44 additions & 0 deletions apps/experiments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import uuid
from datetime import datetime
from functools import cached_property
from typing import TypedDict
from uuid import uuid4

import markdown
Expand Down Expand Up @@ -29,6 +30,13 @@
log = logging.getLogger(__name__)


class ExperimentDetail(TypedDict):
"""Represents a specific detail about an experiment. The label is the user friendly name"""

label: str
value: str | int


class PromptObjectManager(AuditingManager):
pass

Expand Down Expand Up @@ -691,6 +699,42 @@ def is_public(self) -> bool:
def is_participant_allowed(self, identifier: str):
return identifier in self.participant_allowlist or self.team.members.filter(email=identifier).exists()

def version_details_for_display(self) -> list[ExperimentDetail]:
"""
Returns a list of dictionaries, each representing a specific detail of this the current experiment.
Each dictionary should have a `name` and `value` key.
"""

def name_or_none(attr_name):
return getattr(self, attr_name) or ""

def yes_no(condition):
return "Yes" if condition else "No"

return [
ExperimentDetail(label="Description", value=self.description),
ExperimentDetail(label="Version", value=self.version_number),
ExperimentDetail(label="Version Description", value=self.version_description),
ExperimentDetail(label="LLM Model", value=self.llm),
ExperimentDetail(label="LLM Provider", value=self.llm_provider.name),
ExperimentDetail(label="Assistant", value=name_or_none("assistant")),
ExperimentDetail(label="Pipeline", value=name_or_none("pipeline")),
ExperimentDetail(label="Temperature", value=self.temperature),
ExperimentDetail(label="Source Material", value=name_or_none("source_material")),
ExperimentDetail(label="Pre-Survey", value=name_or_none("pre_survey")),
ExperimentDetail(label="Post-Survey", value=name_or_none("post_survey")),
ExperimentDetail(
label="Safety Violation Notification Emails",
value=", ".join(self.safety_violation_notification_emails),
),
ExperimentDetail(label="Max Token Limit", value=self.max_token_limit),
ExperimentDetail(label="Voice Response Behaviour", value=self.get_voice_response_behaviour_display),
ExperimentDetail(label="Trace Provider", value=name_or_none("trace_provider")),
ExperimentDetail(label="Consent Form", value=name_or_none("consent_form")),
ExperimentDetail(label="Conversational Consent Enabled", value=yes_no(self.conversational_consent_enabled)),
ExperimentDetail(label="Echo Transcript", value=yes_no(self.echo_transcript)),
]


class ExperimentRouteType(models.TextChoices):
PROCESSOR = "processor"
Expand Down
4 changes: 4 additions & 0 deletions apps/experiments/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ class ExperimentVersionsTable(tables.Table):
{% endif %}""",
verbose_name="Default Version",
)
details = columns.TemplateColumn(
template_name="experiments/components/experiment_version_details_button.html",
verbose_name="",
)

class Meta:
model = Experiment
Expand Down
10 changes: 10 additions & 0 deletions apps/experiments/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@
path("e/<int:experiment_id>/", views.single_experiment_home, name="single_experiment_home"),
path("e/<int:experiment_id>/sessions-table/", views.ExperimentSessionsTableView.as_view(), name="sessions-list"),
path("e/<int:experiment_id>/versions/", views.ExperimentVersionsTableView.as_view(), name="versions-list"),
path(
"e/<int:experiment_id>/versions/details/<int:version_number>/",
views.experiment_version_details,
name="experiment-version-details",
),
path(
"e/<int:experiment_id>/versions/set_default/<int:version_number>/",
views.set_default_experiment,
name="set-default-experiment",
),
path("e/<int:experiment_id>/versions/create", views.CreateExperimentVersion.as_view(), name="create_version"),
path("e/<int:pk>/edit/", views.EditExperiment.as_view(), name="edit"),
path("e/<int:pk>/delete/", views.delete_experiment, name="delete"),
Expand Down
2 changes: 2 additions & 0 deletions apps/experiments/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@
experiment_session_details_view,
experiment_session_message,
experiment_session_pagination_view,
experiment_version_details,
experiments_home,
get_message_response,
poll_messages,
send_invitation,
set_default_experiment,
single_experiment_home,
start_authed_web_session,
start_session_from_invite,
Expand Down
36 changes: 35 additions & 1 deletion apps/experiments/views/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from django.views.decorators.http import require_POST
from django.views.generic import CreateView, UpdateView
from django_tables2 import SingleTableView
from field_audit.models import AuditAction
from waffle import flag_is_active

from apps.annotations.models import Tag
Expand Down Expand Up @@ -144,7 +145,7 @@ class ExperimentVersionsTableView(SingleTableView, PermissionRequiredMixin):
model = Experiment
paginate_by = 25
table_class = ExperimentVersionsTable
template_name = "table/single_table.html"
template_name = "experiments/experiment_version_table.html"
permission_required = "experiments.view_experiment"

def get_queryset(self):
Expand Down Expand Up @@ -1200,3 +1201,36 @@ def download_file(request, team_slug: str, session_id: int, pk: int):
return FileResponse(file, as_attachment=True, filename=resource.file.name)
except FileNotFoundError:
raise Http404()


@require_POST
@transaction.atomic
@login_and_team_required
def set_default_experiment(request, team_slug: str, experiment_id: int, version_number: int):
experiment = get_object_or_404(
Experiment, working_version_id=experiment_id, version_number=version_number, team=request.team
)
Experiment.objects.exclude(version_number=version_number).filter(
team__slug=team_slug, working_version_id=experiment_id
).update(is_default_version=False, audit_action=AuditAction.AUDIT)
experiment.is_default_version = True
experiment.save()

url = (
reverse(
"experiments:single_experiment_home",
kwargs={"team_slug": request.team.slug, "experiment_id": experiment_id},
)
+ "#versions"
)
return redirect(url)


@login_and_team_required
def experiment_version_details(request, team_slug: str, experiment_id: int, version_number: int):
experiment_version = get_object_or_404(
Experiment, working_version_id=experiment_id, version_number=version_number, team=request.team
)
return render(
request, "experiments/components/experiment_version_details_content.html", {"experiment": experiment_version}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<button class="btn btn-sm btn-outline btn-primary"
hx-get="{% url 'experiments:experiment-version-details' team_slug=team.slug experiment_id=record.working_version_id version_number=record.version_number %}"
hx-target="#modal-content"
hx-trigger="click"
onclick="document.querySelector('#versionDetailsModal').showModal();">
View Details
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<div class="modal-header">
<div class="modal-title" id="detailsModalLabel">
<h3 class="text-base text-xl font-bold">Details for {{ experiment }}</h3>
</div>
</div>

<div class="modal-body">
<div class="my-4 space-y-2">
{% for field in experiment.version_details_for_display %}
<div class="grid grid-cols-3 gap-2">
<dt class="text-sm font-medium leading-6">{{ field.label }}</dt>
<dd class="text-sm col-span-2">{{ field.value }}</dd>
</div>
{% endfor %}
</div>
</div>

<div class="modal-footer">
{% url 'experiments:set-default-experiment' team_slug=team.slug experiment_id=experiment.working_version_id version_number=experiment.version_number as form_action_url%}
<form method="post" action="{{ form_action_url }}">
{% csrf_token %}
<button type="submit" class="btn btn-primary" {% if experiment.is_default_version %}disabled{% endif %}>
Set as Default
</button>
</form>
</div>
15 changes: 15 additions & 0 deletions templates/experiments/experiment_version_table.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% extends "table/single_table.html" %}

{% block modal %}
<dialog id="versionDetailsModal" class="modal">
<div class="modal-box">
<form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost" aria-label="Close"></button>
</form>
<div id="modal-content"></div>
</div>
<form method="dialog" class="modal-backdrop">
<button>Close</button>
</form>
</dialog>
{% endblock %}
2 changes: 2 additions & 0 deletions templates/table/single_table.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{% load static %}
{% load render_table from django_tables2 %}
{% render_table table "table/tailwind_js_pagination.html" %}
{% block modal %}
{% endblock %}
<script>
function initializeRowClickHandlers() {
document.querySelectorAll('tr[data-redirect-url]:not([data-redirect-url=""])').forEach(function (element) {
Expand Down

0 comments on commit e90138f

Please sign in to comment.