Skip to content
Merged
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
32 changes: 0 additions & 32 deletions .github/workflows/fossa.yml

This file was deleted.

154 changes: 154 additions & 0 deletions escalated/migrations/0022_skills_management_routing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import django
import django.db.models.deletion
from django.conf import settings
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import migrations, models

from escalated.conf import get_table_name

# Django 5.1 renamed `check=` to `condition=` on CheckConstraint.
# Both 4.2 and 5.0 still use the old `check=` kwarg.
_check_kwargs = {"condition": models.Q(proficiency__gte=1, proficiency__lte=5)}
if django.VERSION < (5, 1):
_check_kwargs = {"check": models.Q(proficiency__gte=1, proficiency__lte=5)}


def backfill_agent_skill_proficiency(apps, schema_editor):
AgentSkill = apps.get_model("escalated", "AgentSkill")
AgentSkill.objects.filter(proficiency__lt=1).update(proficiency=3)
AgentSkill.objects.filter(proficiency__gt=5).update(proficiency=3)


def noop_reverse(apps, schema_editor):
pass


class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("escalated", "0021_backfill_contact_id_on_tickets"),
]

operations = [
migrations.AddField(
model_name="skill",
name="description",
field=models.TextField(blank=True, null=True),
),
migrations.CreateModel(
name="SkillRoutingTag",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"skill",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="skill_routing_tag_links",
to="escalated.skill",
),
),
(
"tag",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="skill_routing_tag_links",
to="escalated.tag",
),
),
],
options={
"db_table": get_table_name("skill_routing_tags"),
},
),
migrations.CreateModel(
name="SkillRoutingDepartment",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"skill",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="skill_routing_department_links",
to="escalated.skill",
),
),
(
"department",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="skill_routing_department_links",
to="escalated.department",
),
),
],
options={
"db_table": get_table_name("skill_routing_departments"),
},
),
migrations.AddField(
model_name="skill",
name="routing_departments",
field=models.ManyToManyField(
blank=True,
related_name="skill_routing_departments",
through="escalated.SkillRoutingDepartment",
to="escalated.department",
),
),
migrations.AddField(
model_name="skill",
name="routing_tags",
field=models.ManyToManyField(
blank=True,
related_name="skill_routing_tags",
through="escalated.SkillRoutingTag",
to="escalated.tag",
),
),
migrations.AddConstraint(
model_name="skillroutingdepartment",
constraint=models.UniqueConstraint(
fields=("skill", "department"),
name="escalated_skill_routing_dept_skill_dept_uniq",
),
),
migrations.AddConstraint(
model_name="skillroutingtag",
constraint=models.UniqueConstraint(
fields=("skill", "tag"),
name="escalated_skill_routing_tag_skill_tag_uniq",
),
),
migrations.AlterField(
model_name="agentskill",
name="proficiency",
field=models.PositiveIntegerField(
default=3,
validators=[MinValueValidator(1), MaxValueValidator(5)],
),
),
migrations.RunPython(backfill_agent_skill_proficiency, noop_reverse),
migrations.AddConstraint(
model_name="agentskill",
constraint=models.CheckConstraint(
**_check_kwargs,
name="escalated_agentskill_proficiency_1_5",
),
),
]
66 changes: 65 additions & 1 deletion escalated/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Q
from django.utils import timezone
Expand Down Expand Up @@ -1641,7 +1642,10 @@ class AgentSkill(models.Model):
on_delete=models.CASCADE,
related_name="agent_skills",
)
proficiency = models.PositiveIntegerField(default=1)
proficiency = models.PositiveIntegerField(
default=3,
validators=[MinValueValidator(1), MaxValueValidator(5)],
)

class Meta:
db_table = get_table_name("agent_skill")
Expand All @@ -1654,6 +1658,19 @@ def __str__(self):
class Skill(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
description = models.TextField(null=True, blank=True)
routing_tags = models.ManyToManyField(
"Tag",
through="SkillRoutingTag",
related_name="skill_routing_tags",
blank=True,
)
routing_departments = models.ManyToManyField(
"Department",
through="SkillRoutingDepartment",
related_name="skill_routing_departments",
blank=True,
)
agents = models.ManyToManyField(
settings.AUTH_USER_MODEL,
through="AgentSkill",
Expand All @@ -1677,6 +1694,53 @@ def save(self, *args, **kwargs):
super().save(*args, **kwargs)


class SkillRoutingTag(models.Model):
skill = models.ForeignKey(
"Skill",
on_delete=models.CASCADE,
related_name="skill_routing_tag_links",
)
tag = models.ForeignKey(
"Tag",
on_delete=models.CASCADE,
related_name="skill_routing_tag_links",
)

class Meta:
db_table = get_table_name("skill_routing_tags")
constraints = [
models.UniqueConstraint(fields=["skill", "tag"], name="escalated_skill_routing_tag_skill_tag_uniq"),
]

def __str__(self):
return f"SkillRoutingTag({self.skill_id}, tag={self.tag_id})"


class SkillRoutingDepartment(models.Model):
skill = models.ForeignKey(
"Skill",
on_delete=models.CASCADE,
related_name="skill_routing_department_links",
)
department = models.ForeignKey(
"Department",
on_delete=models.CASCADE,
related_name="skill_routing_department_links",
)

class Meta:
db_table = get_table_name("skill_routing_departments")
constraints = [
models.UniqueConstraint(
fields=["skill", "department"],
name="escalated_skill_routing_dept_skill_dept_uniq",
),
]

def __str__(self):
return f"SkillRoutingDepartment({self.skill_id}, dept={self.department_id})"


class AgentCapacity(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
Expand Down
34 changes: 34 additions & 0 deletions escalated/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,12 +673,46 @@ def serialize_list(profiles):


class SkillSerializer:
@staticmethod
def serialize_index_row(skill):
return {
"id": skill.pk,
"name": skill.name,
"agents_count": getattr(skill, "agents_count", skill.agents.count()),
"routing_tags_count": getattr(skill, "routing_tags_count", skill.routing_tags.count()),
"routing_departments_count": getattr(
skill,
"routing_departments_count",
skill.routing_departments.count(),
),
"updated_at": _format_dt(skill.updated_at),
}

@staticmethod
def serialize_index_list(skills):
return [SkillSerializer.serialize_index_row(s) for s in skills]

@staticmethod
def serialize_for_form(skill):
return {
"id": skill.pk,
"name": skill.name,
"description": skill.description,
"routing_tag_ids": list(skill.routing_tags.order_by("pk").values_list("pk", flat=True)),
"routing_department_ids": list(skill.routing_departments.order_by("pk").values_list("pk", flat=True)),
"agents": [
{"user_id": row.user_id, "proficiency": row.proficiency}
for row in skill.agent_skills.order_by("user_id", "pk")
],
}

@staticmethod
def serialize(skill):
data = {
"id": skill.pk,
"name": skill.name,
"slug": skill.slug,
"description": getattr(skill, "description", None),
"created_at": _format_dt(skill.created_at),
"updated_at": _format_dt(skill.updated_at),
}
Expand Down
Loading