Skip to content

Commit

Permalink
CLS2-1031 Add EYB Lead to Company Activity and Opensearch (#5828)
Browse files Browse the repository at this point in the history
* Add EYB Lead to Company Activity

* Add EYB Lead / Company Activity to Opensearch
  • Loading branch information
samuele-mattiuzzo authored Nov 27, 2024
1 parent 8ea5471 commit 91c9bf7
Show file tree
Hide file tree
Showing 18 changed files with 335 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
GreatExportEnquiryFactory,
InvestmentProjectFactory,
OrderFactory,
EYBLeadFactory,
]


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.16 on 2024-11-27 15:47

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('investment_lead', '0009_alter_eyblead_marketing_hashed_uuid'),
('company_activity', '0017_ingestedfile_file_created_and_more'),
]

operations = [
migrations.AddField(
model_name='companyactivity',
name='eyb_lead',
field=models.ForeignKey(blank=True, help_text='If related to an EYB lead, must not have relations to any other activity (referral, event etc)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='activity', to='investment_lead.eyblead', unique=True),
),
migrations.AlterField(
model_name='companyactivity',
name='activity_source',
field=models.CharField(choices=[('interaction', 'interaction'), ('referral', 'referral'), ('event', 'event'), ('investment', 'investment'), ('order', 'order'), ('great_export_enquiry', 'great_export_enquiry'), ('eyb_lead', 'eyb_lead')], help_text='The type of company activity, such as an interaction, event, referral etc.', max_length=255),
),
]
14 changes: 14 additions & 0 deletions datahub/company_activity/models/company_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class ActivitySource(models.TextChoices):
investment = ('investment', 'investment')
order = ('order', 'order')
great_export_enquiry = ('great_export_enquiry', 'great_export_enquiry')
eyb_lead = ('eyb_lead', 'eyb_lead')

id = models.UUIDField(primary_key=True, default=uuid.uuid4)
company = models.ForeignKey(
Expand Down Expand Up @@ -114,6 +115,19 @@ class ActivitySource(models.TextChoices):
),
)

eyb_lead = models.ForeignKey(
'investment_lead.EYBLead',
unique=True,
null=True,
blank=True,
related_name='activity',
on_delete=models.CASCADE,
help_text=(
'If related to an EYB lead, must not have relations to any other activity '
'(referral, event etc)'
),
)

def __str__(self):
"""Readable name for CompanyActivity"""
return f'Activity from "{self.activity_source}" for company: {self.company.name}'
41 changes: 36 additions & 5 deletions datahub/company_activity/tasks/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from datahub.core.queues.scheduler import LONG_RUNNING_QUEUE
from datahub.interaction.models import Interaction
from datahub.investment.project.models import InvestmentProject
from datahub.investment_lead.models import EYBLead
from datahub.omis.order.models import Order


Expand All @@ -15,7 +16,7 @@

def relate_company_activity_to_interactions(batch_size=500):
"""
Grabs all interactions so they can be related to in the
Grabs all interactions so they can be related to the
`CompanyActivity` model with bulk_create. Excludes any
interactions already associated in the CompanyActivity model.
Expand Down Expand Up @@ -46,7 +47,7 @@ def relate_company_activity_to_interactions(batch_size=500):

def relate_company_activity_to_referrals(batch_size=500):
"""
Grabs all referrals so they can be related to in the
Grabs all referrals so they can be related to the
`CompanyActivity` model with a bulk_create. Excludes any
referrals already associated in the CompanyActivity model.
Expand Down Expand Up @@ -76,7 +77,7 @@ def relate_company_activity_to_referrals(batch_size=500):

def relate_company_activity_to_investment_projects(batch_size=500):
"""
Grabs all investment projects so they can be related to in the
Grabs all investment projects so they can be related to the
`CompanyActivity` model with a bulk_create. Excludes any
investment projects already associated in the CompanyActivity model.
"""
Expand Down Expand Up @@ -106,7 +107,7 @@ def relate_company_activity_to_investment_projects(batch_size=500):

def relate_company_activity_to_orders(batch_size=500):
"""
Grabs all omis orders so they can be related to in the
Grabs all omis orders so they can be related to the
`CompanyActivity` model with a bulk_create. Excludes any
order projects already associated in the CompanyActivity model.
"""
Expand Down Expand Up @@ -136,7 +137,7 @@ def relate_company_activity_to_orders(batch_size=500):

def relate_company_activity_to_great(batch_size=500):
"""
Grabs all great export enquiry so they can be related to in the
Grabs all great export enquiry so they can be related to the
`CompanyActivity` model with a bulk_create. Excludes any
great export enquiry already associated in the CompanyActivity model.
"""
Expand Down Expand Up @@ -164,6 +165,36 @@ def relate_company_activity_to_great(batch_size=500):
bulk_create_activity(objs, batch_size)


def relate_company_activity_to_eyb_lead(batch_size=500):
"""
Grabs all EYB leads so they can be related to the
`CompanyActivity` model with a bulk_create. Excludes any
EYB leads already associated in the CompanyActivity model.
"""
activity = set(
CompanyActivity.objects.filter(
eyb_lead__isnull=False,
).values_list('eyb_lead', flat=True),
)

eyb_leads = EYBLead.objects.filter(
company__isnull=False,
).values('id', 'created_on', 'company_id')

objs = [
CompanyActivity(
eyb_lead_id=eyb_lead['id'],
date=eyb_lead['created_on'],
company_id=eyb_lead['company_id'],
activity_source=CompanyActivity.ActivitySource.eyb_lead,
)
for eyb_lead in eyb_leads
if eyb_lead['id'] not in activity
]

bulk_create_activity(objs, batch_size)


def schedule_sync_data_to_company_activity(relate_function):
"""
Schedules a task for the given function.
Expand Down
23 changes: 23 additions & 0 deletions datahub/company_activity/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from datahub.company_referral.test.factories import CompanyReferralFactory
from datahub.interaction.test.factories import CompanyInteractionFactory
from datahub.investment.project.test.factories import InvestmentProjectFactory
from datahub.investment_lead.test.factories import EYBLeadFactory
from datahub.metadata.test.factories import CountryFactory, SectorFactory
from datahub.omis.order.test.factories import OrderFactory

Expand Down Expand Up @@ -205,3 +206,25 @@ def _create(cls, model_class, *args, **kwargs):
"""
obj = model_class(*args, **kwargs)
return CompanyActivity.objects.get(great_export_enquiry_id=obj.great_export_enquiry_id)


class CompanyActivityEYBLeadFactory(CompanyActivityBaseFactory):
"""
CompanyActivity factory with an EYB lead.
"""

activity_source = CompanyActivity.ActivitySource.eyb_lead
eyb_lead = factory.SubFactory(EYBLeadFactory)

class Meta:
model = 'company_activity.CompanyActivity'

@classmethod
def _create(cls, model_class, *args, **kwargs):
"""
Overwrite the _create to prevent the CompanyActivity from saving to the database.
This is due to the EYB lead already creating the CompanyActivity inside its
model save.
"""
obj = model_class(*args, **kwargs)
return CompanyActivity.objects.get(eyb_lead_id=obj.eyb_lead_id)
76 changes: 76 additions & 0 deletions datahub/company_activity/tests/test_tasks/test_eyb_lead_tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from unittest import mock

import pytest

from datahub.company_activity.models import CompanyActivity
from datahub.company_activity.tasks.sync import (
relate_company_activity_to_eyb_lead,
schedule_sync_data_to_company_activity,
)
from datahub.investment_lead.test.factories import EYBLeadFactory


@pytest.mark.django_db
class TestCompanyActivityEYBLeadTasks:
"""
Tests for the schedule_sync_data_to_company_activity task.
"""

def test_eyb_leads_are_copied_to_company_activity(self):
"""
Test that eyb leads are added to the CompanyActivity model.
"""
eyb_leads = EYBLeadFactory.create_batch(5)

# Remove the created CompanyActivities added by the eyb lead `save` method
# to mimic already existing data in staging and prod database.
CompanyActivity.objects.all().delete()
assert CompanyActivity.objects.count() == 0

# Check the "existing" eyb leads are added to the company activity model
schedule_sync_data_to_company_activity(relate_company_activity_to_eyb_lead)
assert CompanyActivity.objects.count() == len(eyb_leads)

company_activity = CompanyActivity.objects.get(eyb_lead=eyb_leads[0])
assert company_activity.date == eyb_leads[0].created_on
assert company_activity.activity_source == CompanyActivity.ActivitySource.eyb_lead
assert company_activity.eyb_lead_id == eyb_leads[0].id

@mock.patch('datahub.company_activity.models.CompanyActivity.objects.bulk_create')
def test_eyb_leads_are_bulk_created_in_batches(self, mocked_bulk_create, caplog):
"""
Test that eyb leads are bulk created in batches.
"""
caplog.set_level('INFO')
batch_size = 5

EYBLeadFactory.create_batch(10)

# Delete any activity created through the investments save method.
CompanyActivity.objects.all().delete()
assert CompanyActivity.objects.count() == 0

# Ensure eyb leads are bulk_created
relate_company_activity_to_eyb_lead(batch_size)
assert mocked_bulk_create.call_count == 2

assert (
f'Creating in batches of: {batch_size} CompanyActivities. 10 remaining.' in caplog.text
)
assert (
f'Creating in batches of: {batch_size} CompanyActivities. 5 remaining.' in caplog.text
)
assert 'Finished bulk creating CompanyActivities.' in caplog.text

def test_eyb_leads_with_a_company_activity_are_not_added_again(self):
"""
Test that eyb leads which are already part of the `CompanyActivity` model
are not added again.
"""
EYBLeadFactory.create_batch(4)

assert CompanyActivity.objects.count() == 4

# Check count remains unchanged.
schedule_sync_data_to_company_activity(relate_company_activity_to_eyb_lead)
assert CompanyActivity.objects.count() == 4
4 changes: 2 additions & 2 deletions datahub/company_activity/tests/test_tasks/test_order_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
@pytest.mark.django_db
class TestCompanyActivityOrderTasks:
"""
Tests for the schedule_sync_investments_to_company_activity task.
Tests for the schedule_sync_data_to_company_activity task.
"""

def test_orders_are_copied_to_company_activity(self):
"""
Test that investments are added to the CompanyActivity model.
Test that omis orders are added to the CompanyActivity model.
"""
orders = OrderFactory.create_batch(5)

Expand Down
17 changes: 16 additions & 1 deletion datahub/investment_lead/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
MaxLengthValidator,
MinLengthValidator,
)
from django.db import models
from django.db import models, transaction
from mptt.fields import TreeForeignKey

from datahub.company_activity.models import CompanyActivity
from datahub.core import reversion
from datahub.core.models import ArchivableModel

Expand Down Expand Up @@ -221,3 +222,17 @@ def name(self):
if self.company:
return f'EYB Lead ({shortened_pk}...) for {self.company.name}'
return f'EYB Lead ({shortened_pk}...)'

def save(self, *args, **kwargs):
"""
Creates a CompanyActivity when a EYB Lead is saved
"""
with transaction.atomic():
super().save(*args, **kwargs)
if not self.company:
return
CompanyActivity.objects.update_or_create(
eyb_lead_id=self.id,
activity_source=CompanyActivity.ActivitySource.eyb_lead,
defaults={'date': self.created_on, 'company_id': self.company_id},
)
20 changes: 20 additions & 0 deletions datahub/investment_lead/test/test_models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import pytest

from datahub.company_activity.models import CompanyActivity
from datahub.investment_lead.test.factories import EYBLeadFactory


@pytest.mark.django_db
class TestEYBLead:
"""Tests EYB Lead model"""

def test_save_without_company_no_company_activity(self):
assert not CompanyActivity.objects.all().exists()
EYBLeadFactory(company=None)
assert not CompanyActivity.objects.all().exists()

def test_save_with_company_creates_company_activity(self):
assert not CompanyActivity.objects.all().exists()

eyb_lead = EYBLeadFactory()

assert CompanyActivity.objects.all().count() == 1

company_activity = CompanyActivity.objects.get(eyb_lead=eyb_lead.id)
assert company_activity.company_id == eyb_lead.company.id
assert company_activity.date == eyb_lead.created_on
assert company_activity.activity_source == CompanyActivity.ActivitySource.eyb_lead

def test_str(self, eyb_lead_instance_from_db):
"""Test the human friendly string representation of the object"""
assert str(eyb_lead_instance_from_db) == eyb_lead_instance_from_db.name
1 change: 1 addition & 0 deletions datahub/search/company_activity/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class CompanyActivitySearchApp(SearchApp):
'order__created_by',
'great_export_enquiry',
'great_export_enquiry__contact',
'eyb_lead',
).prefetch_related(
'interaction__contacts',
Prefetch(
Expand Down
13 changes: 13 additions & 0 deletions datahub/search/company_activity/dict_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,16 @@ def activity_great_dict(obj):
'meta_subject': obj.meta_subject,
'data_enquiry': obj.data_enquiry,
}


def activity_eyb_lead_dict(obj):
"""Creates a dictionary for an eyb lead."""
if obj is None:
return None

return {
'id': str(obj.id),
'created_on': obj.created_on,
'company_name': obj.company_name,
'duns_number': obj.duns_number,
}
11 changes: 11 additions & 0 deletions datahub/search/company_activity/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,14 @@ def activity_great_field():
'data_enquiry': Text(index=False),
},
)


def activity_eyb_lead_field():
return Object(
properties={
'id': Keyword(),
'created_on': Date(),
'company_name': Text(index=False),
'duns_number': Text(index=False),
},
)
Loading

0 comments on commit 91c9bf7

Please sign in to comment.