make the 'last_activity' field in /tickets show the actual last activity by making it a virtual field #46
7 changed files with 135 additions and 6 deletions
19
core/files/migrations/0002_alter_file_file.py
Normal file
19
core/files/migrations/0002_alter_file_file.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
19
core/mail/migrations/0004_alter_emailattachment_file.py
Normal file
19
core/mail/migrations/0004_alter_emailattachment_file.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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)
|
||||||
|
@ -188,7 +188,7 @@ def receive_email(envelope, log=None):
|
||||||
in_reply_to=header_message_id, event=target_event, issue_thread=active_issue_thread)
|
in_reply_to=header_message_id, event=target_event, issue_thread=active_issue_thread)
|
||||||
reply = make_reply(reply_email, references)
|
reply = make_reply(reply_email, references)
|
||||||
else:
|
else:
|
||||||
#change state if not new
|
# change state if not new
|
||||||
if active_issue_thread.state != 'pending_new':
|
if active_issue_thread.state != 'pending_new':
|
||||||
active_issue_thread.state = 'pending_open'
|
active_issue_thread.state = 'pending_open'
|
||||||
active_issue_thread.save()
|
active_issue_thread.save()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
),
|
||||||
|
]
|
|
@ -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
|
||||||
|
|
|
@ -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'},
|
||||||
|
|
Loading…
Reference in a new issue