Skip to content

Commit

Permalink
Merge pull request breatheco-de#1392 from jefer94/feat/add-warnings-t…
Browse files Browse the repository at this point in the history
…o-bills

Feat/add-warnings-to-bills
  • Loading branch information
alesanchezr authored Jun 18, 2024
2 parents 752775f + 29ecff5 commit 7f037b8
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 93 deletions.
109 changes: 45 additions & 64 deletions breathecode/provisioning/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ def add_codespaces_activity(context: ActivityContext, field: dict, position: int
academies = random.choices(academies, k=1)

errors = []
ignores = []
warnings = []
logs = {}
provisioning_bills = {}
provisioning_vendor = None
Expand Down Expand Up @@ -354,28 +354,10 @@ def add_codespaces_activity(context: ActivityContext, field: dict, position: int
provisioning_bills[academy.id] = provisioning_bill

date = datetime.strptime(field['Date'], '%Y-%m-%d')
for academy_id in logs.keys():
for log in logs[academy_id]:
if (log['storage_action'] == 'DELETE' and log['storage_status'] == 'SYNCHED'
and log['starting_at'] <= pytz.utc.localize(date) <= log['ending_at']):
provisioning_bills.pop(academy_id, None)
ignores.append(f'User {field["Username"]} was deleted from the academy during this event at {date}')

if not provisioning_bills:
for academy_id in logs.keys():
cohort_user = CohortUser.objects.filter(
Q(cohort__ending_date__lte=date) | Q(cohort__never_ends=True),
cohort__kickoff_date__gte=date,
cohort__academy__id=academy_id,
user__credentialsgithub__username=field['Username']).order_by('-created_at').first()

if cohort_user:
errors.append('We found activity from this user while he was studying at one of your cohort '
f'{cohort_user.cohort.slug}')

if not_found:
errors.append(f'We could not find enough information about {field["Username"]}, mark this user user as '
'deleted if you don\'t recognize it')
warnings.append(f'We could not find enough information about {field["Username"]}, mark this user user as '
'deleted if you don\'t recognize it')

if not (kind := context['provisioning_activity_kinds'].get((field['Product'], field['SKU']), None)):
kind, _ = ProvisioningConsumptionKind.objects.get_or_create(
Expand Down Expand Up @@ -415,19 +397,22 @@ def add_codespaces_activity(context: ActivityContext, field: dict, position: int
csv_row=position,
)

if errors and not (len(errors) == 1 and not_found):
last_status_list = [x for x in pa.status_text.split(', ') if x]
if errors:
pa.status = 'ERROR'
pa.status_text = pa.status_text + (', ' if pa.status_text else '') + ', '.join(errors + ignores)
pa.status_text = ', '.join(last_status_list + errors + warnings)

elif warnings:
if pa.status != 'ERROR':
pa.status = 'WARNING'

elif pa.status != 'ERROR' and ignores and not provisioning_bills:
pa.status = 'IGNORED'
pa.status_text = pa.status_text + (', ' if pa.status_text else '') + ', '.join(ignores)
pa.status_text = ', '.join(last_status_list + warnings)

else:
pa.status = 'PERSISTED'
pa.status_text = pa.status_text + (', ' if pa.status_text else '') + ', '.join(errors + ignores)
pa.status_text = ', '.join(last_status_list + errors + warnings)

pa.status_text = ', '.join(sorted(set(pa.status_text.split(', '))))
pa.status_text = ', '.join([x for x in sorted(set(pa.status_text.split(', '))) if x])
pa.status_text = pa.status_text[:255]
pa.save()

Expand Down Expand Up @@ -469,13 +454,14 @@ def add_gitpod_activity(context: ActivityContext, field: dict, position: int):
academies = list(context['academies'])

errors = []
warnings = []
if not academies:
errors.append(f'We could not find enough information about {field["userName"]}, mark this user user as '
'deleted if you don\'t recognize it')
warnings.append(f'We could not find enough information about {field["userName"]}, mark this user user as '
'deleted if you don\'t recognize it')

pattern = r'^https://github\.com/[^/]+/([^/]+)/?'
if not (result := re.findall(pattern, field['contextURL'])):
errors.append(f'Invalid repository URL {field["contextURL"]}')
warnings.append(f'Invalid repository URL {field["contextURL"]}')
slug = 'unknown'

else:
Expand Down Expand Up @@ -556,9 +542,22 @@ def add_gitpod_activity(context: ActivityContext, field: dict, position: int):
if pa.status == 'PENDING':
pa.status = 'PERSISTED' if not errors else 'ERROR'

pa.status_text = pa.status_text + (', ' if pa.status_text else '') + ', '.join(errors)
last_status_list = [x for x in pa.status_text.split(', ') if x]
if errors:
pa.status = 'ERROR'
pa.status_text = ', '.join(last_status_list + errors + warnings)

elif warnings:
if pa.status != 'ERROR':
pa.status = 'WARNING'

pa.status_text = ', '.join(last_status_list + warnings)

pa.status_text = ', '.join(sorted(set(pa.status_text.split(', '))))
else:
pa.status = 'PERSISTED'
pa.status_text = ', '.join(last_status_list + errors + warnings)

pa.status_text = ', '.join([x for x in sorted(set(pa.status_text.split(', '))) if x])
pa.status_text = pa.status_text[:255]
pa.save()

Expand All @@ -572,7 +571,7 @@ def add_gitpod_activity(context: ActivityContext, field: dict, position: int):

def add_rigobot_activity(context: ActivityContext, field: dict, position: int) -> None:
errors = []
ignores = []
warnings = []

if field['organization'] != '4Geeks':
return
Expand Down Expand Up @@ -657,31 +656,11 @@ def add_rigobot_activity(context: ActivityContext, field: dict, position: int) -
context['provisioning_bills'][academy.id] = provisioning_bill
provisioning_bills[academy.id] = provisioning_bill

for academy_id in logs.keys():
for log in logs[academy_id]:
if (log['storage_action'] == 'DELETE' and log['storage_status'] == 'SYNCHED'
and log['starting_at'] <= pytz.utc.localize(date) <= log['ending_at']):
provisioning_bills.pop(academy_id, None)
ignores.append(
f'User {field["github_username"]} was deleted from the academy during this event at {date}')

# disabled because rigobot doesn't have the organization configured yet.
# if not provisioning_bills:
# for academy_id in logs.keys():
# cohort_user = CohortUser.objects.filter(
# Q(cohort__ending_date__lte=date) | Q(cohort__never_ends=True),
# cohort__kickoff_date__gte=date,
# cohort__academy__id=academy_id,
# user__credentialsgithub__username=field['github_username']).order_by('-created_at').first()

# if cohort_user:
# errors.append('We found activity from this user while he was studying at one of your cohort '
# f'{cohort_user.cohort.slug}')

# not implemented yet
if not_found:
errors.append(f'We could not find enough information about {field["github_username"]}, mark this user user as '
'deleted if you don\'t recognize it')
warnings.append(
f'We could not find enough information about {field["github_username"]}, mark this user user as '
'deleted if you don\'t recognize it')

s_slug = f'{field["purpose_slug"] or "no-provided"}--{field["pricing_type"].lower()}--{field["model"].lower()}'
s_name = f'{field["purpose"]} (type: {field["pricing_type"]}, model: {field["model"]})'
Expand Down Expand Up @@ -723,20 +702,22 @@ def add_rigobot_activity(context: ActivityContext, field: dict, position: int) -
csv_row=position,
)

# if errors and not (len(errors) == 1 and not_found):
last_status_list = [x for x in pa.status_text.split(', ') if x]
if errors:
pa.status = 'ERROR'
pa.status_text = pa.status_text + (', ' if pa.status_text else '') + ', '.join(errors + ignores)
pa.status_text = ', '.join(last_status_list + errors + warnings)

elif warnings:
if pa.status != 'ERROR':
pa.status = 'WARNING'

elif pa.status != 'ERROR' and ignores and not provisioning_bills:
pa.status = 'IGNORED'
pa.status_text = pa.status_text + (', ' if pa.status_text else '') + ', '.join(ignores)
pa.status_text = ', '.join(last_status_list + warnings)

else:
pa.status = 'PERSISTED'
pa.status_text = pa.status_text + (', ' if pa.status_text else '') + ', '.join(errors + ignores)
pa.status_text = ', '.join(last_status_list + errors + warnings)

pa.status_text = ', '.join(sorted(set(pa.status_text.split(', '))))
pa.status_text = ', '.join([x for x in sorted(set(pa.status_text.split(', '))) if x])
pa.status_text = pa.status_text[:255]
pa.save()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 5.0.6 on 2024-06-14 00:14

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('provisioning', '0016_alter_provisioningconsumptionevent_repository_url_and_more'),
]

operations = [
migrations.AlterField(
model_name='provisioninguserconsumption',
name='status',
field=models.CharField(choices=[('PENDING', 'Pending'), ('PERSISTED', 'Persisted'), ('IGNORED', 'Ignored'),
('WARNING', 'Warning'), ('ERROR', 'Error')],
default='PENDING',
max_length=20),
),
]
2 changes: 2 additions & 0 deletions breathecode/provisioning/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,12 @@ def __str__(self):

PENDING = 'PENDING'
PERSISTED = 'PERSISTED'
WARNING = 'WARNING'
ACTIVITY_STATUS = (
(PENDING, 'Pending'),
(PERSISTED, 'Persisted'),
(IGNORED, 'Ignored'),
(WARNING, 'Warning'),
(ERROR, 'Error'),
)

Expand Down
5 changes: 4 additions & 1 deletion breathecode/provisioning/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def calculate_bill_amounts(hash: str, *, force: bool = False, **_: Any):

for bill in bills:
amount = 0
for activity in ProvisioningUserConsumption.objects.filter(bills=bill, status='PERSISTED'):
for activity in ProvisioningUserConsumption.objects.filter(bills=bill, status__in=['PERSISTED', 'WARNING']):
consumption_amount = 0
consumption_quantity = 0
for item in activity.events.all():
Expand Down Expand Up @@ -242,6 +242,9 @@ def upload(hash: str, *, page: int = 0, force: bool = False, task_manager_id: in
elif not ProvisioningUserConsumption.objects.filter(hash=hash, status='ERROR').exists():
calculate_bill_amounts.delay(hash)

elif ProvisioningUserConsumption.objects.filter(hash=hash, status='ERROR').exists():
ProvisioningBill.objects.filter(hash=hash).update(status='ERROR')


@task(priority=TaskPriority.BACKGROUND.value)
def archive_provisioning_bill(bill_id: int, **_: Any):
Expand Down
31 changes: 15 additions & 16 deletions breathecode/provisioning/templates/provisioning_invoice.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
{% load math %}

{% block head %}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
{% endblock %}

{% block content %}
Expand Down Expand Up @@ -121,7 +120,6 @@
.alert td {
width: 100%;
}

</style>
</head>

Expand Down Expand Up @@ -160,7 +158,7 @@ <h5>
Created At: {{ bill.created_at }}<br>
<div class="text-end">
{% if bill.stripe_url %}
<a class="btn btn-primary my-3 w-50" type="button" href="{{bill.stripe_url}}">Pay</a>
<a class="btn btn-primary my-3 w-50" type="button" href="{{bill.stripe_url}}">Pay</a>
{% endif %}
</div>
</div>
Expand Down Expand Up @@ -189,20 +187,20 @@ <h5>
<td colspan="2">
{{ consumption.kind.product_name }} ({{ consumption.kind.sku }})
{% if consumption.status_text %}
<small> - </small>
<a onclick="alert('{{consumption.status_text}}')" type="button" class="btn-link">
see error
</a>
<small> - </small>
<a onclick="alert('{{consumption.status_text}}')" type="button" class="btn-link">
show errors
</a>
{% endif %}
</td>


<td class="price">
<span style="color: #608062;">
{% if consumption.amount.is_integer %}
{{ consumption.amount|floatformat:0 }}
{{ consumption.amount|floatformat:0 }}
{% else %}
{{ consumption.amount|floatformat:2 }}
{{ consumption.amount|floatformat:2 }}
{% endif %}
</span>
</td>
Expand All @@ -222,14 +220,15 @@ <h5>

</table>

{% if page < pages %}
<button class="btn btn-primary mx-auto" >
{% if page < pages %} <button class="btn btn-primary mx-auto">
<a class="page-link" href="{{ url }}&page={{ page|add:1 }}">Load more</a>
</button>
{% endif %}
</button>
{% endif %}
</div>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
crossorigin="anonymous"></script>

</body>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
"""
Test /answer/:id
"""
from datetime import datetime, timedelta
import logging
import math
import os
import random
import re
from datetime import datetime, timedelta
from unittest.mock import MagicMock, PropertyMock, call, patch

import pandas as pd
from django.utils import timezone
from faker import Faker
import pandas as pd
from pytz import UTC
from breathecode.provisioning.tasks import calculate_bill_amounts
import logging
from unittest.mock import PropertyMock, patch, MagicMock, call

from breathecode.payments.services.stripe import Stripe
from breathecode.provisioning.tasks import calculate_bill_amounts

from ..mixins import ProvisioningTestCase

Expand Down Expand Up @@ -252,7 +254,7 @@ def test_bill_exists_and_activities__gitpod(self):
} for n in range(2)]

provisioning_user_consumptions = [{
'status': 'PERSISTED',
'status': random.choice(['PERSISTED', 'WARNING']),
} for _ in range(2)]

amount = sum([
Expand Down Expand Up @@ -333,7 +335,7 @@ def test_bill_exists_and_activities__codespaces(self):
} for n in range(2)]

provisioning_user_consumptions = [{
'status': 'PERSISTED',
'status': random.choice(['PERSISTED', 'WARNING']),
} for _ in range(2)]

amount = sum([
Expand Down
Loading

0 comments on commit 7f037b8

Please sign in to comment.