Prune Empty Drafts (#54)

* prune empty drafts after 72 hours

* add additional noops, update tests
This commit is contained in:
Danny Skubak 2019-11-05 14:38:34 -05:00 committed by Daniel Ternyak
parent 494303883a
commit c66be86c54
3 changed files with 163 additions and 4 deletions

View File

@ -14,7 +14,7 @@ from grant.milestone.models import Milestone
from grant.parser import body, query, paginated_fields
from grant.rfp.models import RFP
from grant.settings import PROPOSAL_STAKING_AMOUNT
from grant.task.jobs import ProposalDeadline
from grant.task.jobs import ProposalDeadline, PruneDraft
from grant.user.models import User
from grant.utils import pagination
from grant.utils.auth import (
@ -196,6 +196,9 @@ def make_proposal_draft(rfp_id):
rfp.proposals.append(proposal)
db.session.add(rfp)
task = PruneDraft(proposal)
task.make_task()
db.session.add(proposal)
db.session.commit()
return proposal_schema.dump(proposal), 201

View File

@ -2,7 +2,7 @@ from datetime import datetime, timedelta
from grant.extensions import db
from grant.email.send import send_email
from grant.utils.enums import ProposalStage, ContributionStatus
from grant.utils.enums import ProposalStage, ContributionStatus, ProposalStatus
from grant.utils.misc import make_url
from flask import current_app
@ -126,8 +126,53 @@ class ContributionExpired:
})
class PruneDraft:
JOB_TYPE = 4
PRUNE_TIME = 259200 # 72 hours in seconds
def __init__(self, proposal):
self.proposal = proposal
def blobify(self):
return {
"proposal_id": self.proposal.id,
}
def make_task(self):
from .models import Task
task = Task(
job_type=self.JOB_TYPE,
blob=self.blobify(),
execute_after=self.proposal.date_created + timedelta(seconds=self.PRUNE_TIME),
)
db.session.add(task)
db.session.commit()
@staticmethod
def process_task(task):
from grant.proposal.models import Proposal
proposal = Proposal.query.filter_by(id=task.blob["proposal_id"]).first()
# If it was deleted or moved out of a draft, noop out
if not proposal or proposal.status != ProposalStatus.DRAFT:
return
# If any of the proposal fields are filled, noop out
if proposal.title or proposal.brief or proposal.content or proposal.category or proposal.target != "0":
return
if proposal.payout_address or proposal.milestones:
return
# Otherwise, delete the empty proposal
db.session.delete(proposal)
db.session.commit()
JOBS = {
1: ProposalReminder.process_task,
2: ProposalDeadline.process_task,
3: ContributionExpired.process_task,
4: PruneDraft.process_task
}

View File

@ -1,6 +1,12 @@
from datetime import datetime
from datetime import datetime, timedelta
from grant.task.models import Task
from grant.task.models import Task, db
from grant.task.jobs import PruneDraft
from grant.milestone.models import Milestone
from grant.proposal.models import Proposal
from grant.utils.enums import ProposalStatus, Category
from mock import patch, Mock
from ..config import BaseProposalCreatorConfig
@ -22,3 +28,108 @@ class TestTaskAPI(BaseProposalCreatorConfig):
tasks = Task.query.filter(Task.execute_after <= datetime.now()).filter_by(completed=False).all()
self.assertEqual(tasks, [])
@patch('grant.task.views.datetime')
def test_proposal_pruning(self, mock_datetime):
self.login_default_user()
resp = self.app.post(
"/api/v1/proposals/drafts",
)
proposal_id = resp.json['proposalId']
# make sure proposal was created
proposal = Proposal.query.get(proposal_id)
self.assertIsNotNone(proposal)
# make sure the task was created
self.assertStatus(resp, 201)
tasks = Task.query.all()
self.assertEqual(len(tasks), 1)
task = tasks[0]
self.assertEqual(resp.json['proposalId'], task.blob['proposal_id'])
self.assertFalse(task.completed)
# mock time so task will run when called
after_time = datetime.now() + timedelta(seconds=PruneDraft.PRUNE_TIME + 100)
mock_datetime.now = Mock(return_value=after_time)
# run task
resp = self.app.get("/api/v1/task")
self.assert200(resp)
# make sure task ran successfully
tasks = Task.query.all()
self.assertEqual(len(tasks), 1)
task = tasks[0]
self.assertTrue(task.completed)
proposal = Proposal.query.get(proposal_id)
self.assertIsNone(proposal)
def test_proposal_pruning_noops(self):
# ensure all proposal noop states work as expected
def status(p):
p.status = ProposalStatus.LIVE
def title(p):
p.title = 'title'
def brief(p):
p.brief = 'brief'
def content(p):
p.content = 'content'
def category(p):
p.category = Category.DEV_TOOL
def target(p):
p.target = '100'
def payout_address(p):
p.payout_address = 'address'
def milestones(p):
milestones_data = [
{
"title": "All the money straightaway",
"content": "cool stuff with it",
"date_estimated": 1549505307,
"payout_percent": "100",
"immediate_payout": False
}
]
Milestone.make(milestones_data, p)
modifiers = [
status,
title,
brief,
content,
category,
target,
payout_address,
milestones
]
for modifier in modifiers:
proposal = Proposal.create(status=ProposalStatus.DRAFT)
proposal_id = proposal.id
modifier(proposal)
db.session.add(proposal)
db.session.commit()
blob = {
"proposal_id": proposal_id,
}
task = Task(
job_type=PruneDraft.JOB_TYPE,
blob=blob,
execute_after=datetime.now()
)
PruneDraft.process_task(task)
proposal = Proposal.query.get(proposal_id)
self.assertIsNotNone(proposal)