make the 'last_activity' field in /tickets show the actual last activity by making it a virtual field #46

Merged
j3d1 merged 1 commit from jedi/dev into live 2024-01-10 21:46:31 +00:00
7 changed files with 135 additions and 6 deletions

View file

@ -0,0 +1,19 @@
# Generated by Django 4.2.7 on 2024-01-10 19:04
from django.db import migrations, models
import files.models
class Migration(migrations.Migration):
dependencies = [
('files', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='file',
name='file',
field=models.FileField(upload_to=files.models.hash_upload),
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 4.2.7 on 2024-01-10 19:04
from django.db import migrations, models
import files.models
class Migration(migrations.Migration):
dependencies = [
('mail', '0003_emailattachment'),
]
operations = [
migrations.AlterField(
model_name='emailattachment',
name='file',
field=models.FileField(upload_to=files.models.hash_upload),
),
]

View file

@ -158,8 +158,8 @@ def receive_email(envelope, log=None):
log.warning("Header to does not match envelope to") log.warning("Header to does not match envelope to")
log.info(f"Header to: {header_to}, envelope to: {envelope.rcpt_tos[0]}") log.info(f"Header to: {header_to}, envelope to: {envelope.rcpt_tos[0]}")
recipient = envelope.rcpt_tos[0].lower() recipient = envelope.rcpt_tos[0].lower() if envelope.rcpt_tos else header_to.lower()
sender = envelope.mail_from sender = envelope.mail_from if envelope.mail_from else header_from
subject = parsed.get('Subject') subject = parsed.get('Subject')
subject = unescape_and_decode_quoted_printable(subject) subject = unescape_and_decode_quoted_printable(subject)
subject = unescape_and_decode_base64(subject) subject = unescape_and_decode_base64(subject)

View file

@ -13,11 +13,12 @@ from core.settings import MAIL_DOMAIN
from mail.models import Email from mail.models import Email
from mail.protocol import send_smtp, make_reply, collect_references from mail.protocol import send_smtp, make_reply, collect_references
from notify_sessions.models import SystemEvent from notify_sessions.models import SystemEvent
from tickets.models import IssueThread, Comment, STATE_CHOICES, StateChange from tickets.models import IssueThread, Comment, STATE_CHOICES
class IssueSerializer(serializers.ModelSerializer): class IssueSerializer(serializers.ModelSerializer):
timeline = serializers.SerializerMethodField() timeline = serializers.SerializerMethodField()
last_activity = serializers.SerializerMethodField()
class Meta: class Meta:
model = IssueThread model = IssueThread
@ -36,6 +37,18 @@ class IssueSerializer(serializers.ModelSerializer):
raise serializers.ValidationError('invalid state') raise serializers.ValidationError('invalid state')
return attrs return attrs
@staticmethod
def get_last_activity(self):
try:
last_state_change = self.state_changes.order_by('-timestamp').first().timestamp \
if self.state_changes.count() > 0 else None
last_comment = self.comments.order_by('-timestamp').first().timestamp if self.comments.count() > 0 else None
last_mail = self.emails.order_by('-timestamp').first().timestamp if self.emails.count() > 0 else None
args = [x for x in [last_state_change, last_comment, last_mail] if x is not None]
return max(args)
except AttributeError:
return None
@staticmethod @staticmethod
def get_timeline(obj): def get_timeline(obj):
timeline = [] timeline = []
@ -65,6 +78,9 @@ class IssueSerializer(serializers.ModelSerializer):
}) })
return sorted(timeline, key=lambda x: x['timestamp']) return sorted(timeline, key=lambda x: x['timestamp'])
def get_queryset(self):
return IssueThread.objects.all().order_by('-last_activity')
class IssueViewSet(viewsets.ModelViewSet): class IssueViewSet(viewsets.ModelViewSet):
serializer_class = IssueSerializer serializer_class = IssueSerializer

View file

@ -0,0 +1,17 @@
# Generated by Django 4.2.7 on 2024-01-10 19:04
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('tickets', '0004_remove_issuethread_state_alter_statechange_state'),
]
operations = [
migrations.RemoveField(
model_name='issuethread',
name='last_activity',
),
]

View file

@ -27,7 +27,6 @@ class IssueThread(SoftDeleteModel):
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
assigned_to = models.CharField(max_length=255, null=True) assigned_to = models.CharField(max_length=255, null=True)
last_activity = models.DateTimeField(auto_now=True)
manually_created = models.BooleanField(default=False) manually_created = models.BooleanField(default=False)
@property @property

View file

@ -59,7 +59,7 @@ class IssueApiTest(TestCase):
self.assertEqual(response.json()[0]['name'], "test issue") self.assertEqual(response.json()[0]['name'], "test issue")
self.assertEqual(response.json()[0]['state'], "pending_new") self.assertEqual(response.json()[0]['state'], "pending_new")
self.assertEqual(response.json()[0]['assigned_to'], None) self.assertEqual(response.json()[0]['assigned_to'], None)
self.assertEqual(response.json()[0]['last_activity'], issue.last_activity.strftime('%Y-%m-%dT%H:%M:%S.%fZ')) self.assertEqual(response.json()[0]['last_activity'], comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
self.assertEqual(len(response.json()[0]['timeline']), 4) self.assertEqual(len(response.json()[0]['timeline']), 4)
self.assertEqual(response.json()[0]['timeline'][0]['type'], 'state') self.assertEqual(response.json()[0]['timeline'][0]['type'], 'state')
self.assertEqual(response.json()[0]['timeline'][1]['type'], 'mail') self.assertEqual(response.json()[0]['timeline'][1]['type'], 'mail')
@ -85,6 +85,65 @@ class IssueApiTest(TestCase):
self.assertEqual(response.json()[0]['timeline'][3]['timestamp'], self.assertEqual(response.json()[0]['timeline'][3]['timestamp'],
comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ')) comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
def test_issues_incomplete_timeline(self):
now = datetime.now()
issue1 = IssueThread.objects.create(
name="test issue",
)
issue2 = IssueThread.objects.create(
name="test issue",
)
issue3 = IssueThread.objects.create(
name="test issue",
)
mail1 = Email.objects.create(
subject='test',
body='test',
sender='test',
recipient='test',
issue_thread=issue2,
timestamp=now + timedelta(seconds=2),
)
comment = Comment.objects.create(
issue_thread=issue3,
comment="test",
timestamp=now + timedelta(seconds=3),
)
response = self.client.get('/api/2/tickets/')
self.assertEqual(200, response.status_code)
self.assertEqual(3, len(response.json()))
self.assertEqual(issue1.id, response.json()[0]['id'])
self.assertEqual(issue2.id, response.json()[1]['id'])
self.assertEqual(issue3.id, response.json()[2]['id'])
self.assertEqual(issue1.state_changes.first().timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
response.json()[0]['last_activity'])
self.assertEqual(mail1.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
response.json()[1]['last_activity'])
self.assertEqual(comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
response.json()[2]['last_activity'])
self.assertEqual(1, len(response.json()[0]['timeline']))
self.assertEqual(2, len(response.json()[1]['timeline']))
self.assertEqual(2, len(response.json()[2]['timeline']))
self.assertEqual('state', response.json()[0]['timeline'][0]['type'])
self.assertEqual('state', response.json()[1]['timeline'][0]['type'])
self.assertEqual('mail', response.json()[1]['timeline'][1]['type'])
self.assertEqual('state', response.json()[2]['timeline'][0]['type'])
self.assertEqual('comment', response.json()[2]['timeline'][1]['type'])
self.assertEqual('pending_new', response.json()[0]['timeline'][0]['state'])
self.assertEqual('pending_new', response.json()[1]['timeline'][0]['state'])
self.assertEqual('test', response.json()[1]['timeline'][1]['sender'])
self.assertEqual('test', response.json()[1]['timeline'][1]['recipient'])
self.assertEqual('test', response.json()[1]['timeline'][1]['subject'])
self.assertEqual('test', response.json()[1]['timeline'][1]['body'])
self.assertEqual(mail1.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
response.json()[1]['timeline'][1]['timestamp'])
self.assertEqual('pending_new', response.json()[2]['timeline'][0]['state'])
self.assertEqual('test', response.json()[2]['timeline'][1]['comment'])
self.assertEqual(comment.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
response.json()[2]['timeline'][1]['timestamp'])
def test_manual_creation(self): def test_manual_creation(self):
response = self.client.post('/api/2/tickets/manual/', response = self.client.post('/api/2/tickets/manual/',
{'name': 'test issue', 'sender': 'test', 'recipient': 'test', 'body': 'test'}, {'name': 'test issue', 'sender': 'test', 'recipient': 'test', 'body': 'test'},