Merge pull request #541 from dod-ccpo/task-order-status

Rework Task Order status
This commit is contained in:
patricksmithdds 2019-01-15 14:56:53 -05:00 committed by GitHub
commit a010487f34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 165 additions and 61 deletions

View File

@ -0,0 +1,28 @@
"""Remove status column from task order
Revision ID: da9d1c911a52
Revises: a6837632686c
Create Date: 2019-01-14 11:21:51.729134
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'da9d1c911a52'
down_revision = 'a6837632686c'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('task_orders', 'status')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('task_orders', sa.Column('status', sa.VARCHAR(length=7), autoincrement=False, nullable=True))
# ### end Alembic commands ###

View File

@ -1,14 +1,7 @@
from enum import Enum
from sqlalchemy import (
Column,
Enum as SQLAEnum,
Numeric,
String,
ForeignKey,
Date,
Integer,
)
import pendulum
from sqlalchemy import Column, Numeric, String, ForeignKey, Date, Integer
from sqlalchemy.types import ARRAY
from sqlalchemy.orm import relationship
@ -17,6 +10,8 @@ from atst.models import Base, types, mixins
class Status(Enum):
PENDING = "Pending"
ACTIVE = "Active"
EXPIRED = "Expired"
class TaskOrder(Base, mixins.TimestampsMixin):
@ -41,8 +36,6 @@ class TaskOrder(Base, mixins.TimestampsMixin):
so_id = Column(ForeignKey("users.id"))
security_officer = relationship("User", foreign_keys="TaskOrder.so_id")
status = Column(SQLAEnum(Status, native_enum=False))
scope = Column(String) # Cloud Project Scope
defense_component = Column(String) # Department of Defense Component
app_migration = Column(String) # App Migration
@ -79,10 +72,21 @@ class TaskOrder(Base, mixins.TimestampsMixin):
number = Column(String, unique=True) # Task Order Number
loa = Column(ARRAY(String)) # Line of Accounting (LOA)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if "status" not in kwargs:
self.status = Status.PENDING
@property
def is_submitted(self):
return self.number is not None
@property
def status(self):
if self.is_submitted:
now = pendulum.now().date()
if self.start_date > now:
return Status.PENDING
elif self.end_date < now:
return Status.EXPIRED
return Status.ACTIVE
else:
return Status.PENDING
@property
def budget(self):

View File

@ -1,6 +1,6 @@
#!/bin/bash
FILES_TO_FORMAT="atst/ tests/ app.py"
FILES_TO_FORMAT="atst/ tests/ app.py script/"
if [ "$1" == "check" ]; then
pipenv run black --check ${FILES_TO_FORMAT}

View File

@ -4,7 +4,8 @@ import csv
# Add root project dir to the python path
import os
import sys
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
sys.path.append(parent_dir)
from atst.app import make_app, make_config
@ -16,9 +17,10 @@ def get_pe_numbers(url):
t = response.read().decode("utf-8")
return list(csv.reader(t.split("\r\n")))
if __name__ == "__main__":
config = make_config()
url = config['PE_NUMBER_CSV_URL']
url = config["PE_NUMBER_CSV_URL"]
print("Fetching PE numbers from {}".format(url))
pe_numbers = get_pe_numbers(url)

View File

@ -41,19 +41,21 @@ dod_ids = [
"4567890123",
"5678901234",
"6789012345",
"2342342342", # Andy
"3453453453", # Sally
"4564564564", # Betty
"2342342342", # Andy
"3453453453", # Sally
"4564564564", # Betty
"6786786786",
]
def create_demo_portfolio(name, data):
try:
portfolio_owner = Users.get_by_dod_id("678678678") # Other
auditor = Users.get_by_dod_id("3453453453") # Sally
portfolio_owner = Users.get_by_dod_id("678678678") # Other
auditor = Users.get_by_dod_id("3453453453") # Sally
except NotFoundError:
print("Could not find demo users; will not create demo portfolio {}".format(name))
print(
"Could not find demo users; will not create demo portfolio {}".format(name)
)
return
request = RequestFactory.build(creator=portfolio_owner)
@ -64,10 +66,12 @@ def create_demo_portfolio(name, data):
approved_request = Requests.set_status(request, RequestStatus.APPROVED)
portfolio = Requests.approve_and_create_portfolio(request)
portfolios.update(portfolio, { "name": name })
portfolios.update(portfolio, {"name": name})
for mock_application in data["applications"]:
application = application(portfolio=portfolio, name=mock_application.name, description='')
application = application(
portfolio=portfolio, name=mock_application.name, description=""
)
env_names = [env.name for env in mock_application.environments]
envs = Environments.create_many(application, env_names)
db.session.add(application)
@ -153,5 +157,9 @@ if __name__ == "__main__":
app = make_app(config)
with app.app_context():
remove_sample_data()
create_demo_portfolio('Aardvark', MockReportingProvider.REPORT_FIXTURE_MAP["Aardvark"])
create_demo_portfolio('Beluga', MockReportingProvider.REPORT_FIXTURE_MAP["Beluga"])
create_demo_portfolio(
"Aardvark", MockReportingProvider.REPORT_FIXTURE_MAP["Aardvark"]
)
create_demo_portfolio(
"Beluga", MockReportingProvider.REPORT_FIXTURE_MAP["Beluga"]
)

View File

@ -14,10 +14,17 @@ from atst.domain.applications import Applications
from atst.domain.portfolio_roles import PortfolioRoles
from atst.models.invitation import Status as InvitationStatus
from atst.domain.exceptions import AlreadyExistsError
from tests.factories import RequestFactory, LegacyTaskOrderFactory, InvitationFactory
from tests.factories import (
InvitationFactory,
RequestFactory,
TaskOrderFactory,
random_future_date,
random_past_date,
random_task_order_number,
)
from atst.routes.dev import _DEV_USERS as DEV_USERS
portfolio_USERS = [
PORTFOLIO_USERS = [
{
"first_name": "Danny",
"last_name": "Knight",
@ -48,7 +55,7 @@ PORTFOLIO_INVITED_USERS = [
"email": "frederick@mil.gov",
"portfolio_role": "developer",
"dod_id": "0000000004",
"status": InvitationStatus.REJECTED_WRONG_USER
"status": InvitationStatus.REJECTED_WRONG_USER,
},
{
"first_name": "Gina",
@ -56,7 +63,7 @@ PORTFOLIO_INVITED_USERS = [
"email": "gina@mil.gov",
"portfolio_role": "developer",
"dod_id": "0000000005",
"status": InvitationStatus.REJECTED_EXPIRED
"status": InvitationStatus.REJECTED_EXPIRED,
},
{
"first_name": "Hector",
@ -64,7 +71,7 @@ PORTFOLIO_INVITED_USERS = [
"email": "hector@mil.gov",
"portfolio_role": "developer",
"dod_id": "0000000006",
"status": InvitationStatus.REVOKED
"status": InvitationStatus.REVOKED,
},
{
"first_name": "Isabella",
@ -72,7 +79,7 @@ PORTFOLIO_INVITED_USERS = [
"email": "isabella@mil.gov",
"portfolio_role": "developer",
"dod_id": "0000000007",
"status": InvitationStatus.PENDING
"status": InvitationStatus.PENDING,
},
]
@ -88,38 +95,45 @@ def seed_db():
users.append(user)
for user in users:
if Requests.get_many(creator=user):
continue
requests = []
for dollar_value in [1, 200, 3000, 40000, 500000, 1000000]:
request = RequestFactory.build(creator=user)
request.latest_revision.dollar_value = dollar_value
db.session.add(request)
db.session.commit()
Requests.submit(request)
requests.append(request)
request = requests[0]
request.legacy_task_order = LegacyTaskOrderFactory.build()
request = Requests.update(
request.id, {"financial_verification": RequestFactory.mock_financial_data()}
)
portfolio = Portfolios.create(
user, name="{}'s portfolio".format(user.first_name)
)
for portfolio_role in portfolio_USERS:
for portfolio_role in PORTFOLIO_USERS:
ws_role = Portfolios.create_member(user, portfolio, portfolio_role)
db.session.refresh(ws_role)
PortfolioRoles.enable(ws_role)
for portfolio_role in PORTFOLIO_INVITED_USERS:
ws_role = Portfolios.create_member(user, portfolio, portfolio_role)
invitation = InvitationFactory.build(portfolio_role=ws_role, status=portfolio_role["status"])
invitation = InvitationFactory.build(
portfolio_role=ws_role, status=portfolio_role["status"]
)
db.session.add(invitation)
[expired_start, expired_end] = sorted(
[
random_past_date(year_max=2, year_min=1),
random_past_date(year_max=1, year_min=1),
]
)
active_start = expired_end
active_end = random_future_date(year_min=1, year_max=1)
date_ranges = [(expired_start, expired_end), (active_start, active_end)]
for (start_date, end_date) in date_ranges:
task_order = TaskOrderFactory.build(
start_date=start_date,
end_date=end_date,
number=random_task_order_number(),
portfolio=portfolio,
)
db.session.add(task_order)
pending_task_order = TaskOrderFactory.build(
start_date=None, end_date=None, number=None, portfolio=portfolio
)
db.session.add(pending_task_order)
db.session.commit()
Applications.create(

View File

@ -56,6 +56,18 @@
}
}
.label--pending {
background-color: $color-gold;
}
.label--active {
background-color: $color-green;
}
.label--expired {
background-color: $color-red;
}
.task-order-heading {
align-items: center;
justify-content: space-between;

View File

@ -63,7 +63,7 @@
<div class="panel task-order-heading row">
<div class="panel__content task-order-heading__name row">
<h2>New Task Order</h2>
<span class="label label--{{ 'warning' if task_order.is_pending }}">{{ task_order.status.value }}</span>
<span class="label label--{{ task_order.status.value.lower() }}">{{ task_order.status.value }}</span>
</div>
<div class="task_order-heading__details row">
<div class="task-order-heading__value col">

View File

@ -1,3 +1,4 @@
import operator
import random
import string
import factory
@ -41,14 +42,26 @@ def random_phone_number():
return "".join(random.choices(string.digits, k=10))
def random_task_order_number():
return "-".join([str(random.randint(100, 999)) for _ in range(4)])
def random_past_date(year_min=1, year_max=5):
return _random_date(year_min, year_max, operator.sub)
def random_future_date(year_min=1, year_max=5):
return _random_date(year_min, year_max, operator.add)
def _random_date(year_min, year_max, operation):
if year_min == year_max:
inc = year_min
else:
inc = random.randrange(year_min, year_max)
return datetime.date(
datetime.date.today().year + inc,
operation(datetime.date.today().year, inc),
random.randrange(1, 12),
random.randrange(1, 28),
)

View File

@ -1,9 +1,32 @@
from atst.models.task_order import TaskOrder, Status
from tests.factories import random_future_date, random_past_date
def test_default_status():
class TestTaskOrderStatus:
def test_pending_status(self):
to = TaskOrder()
assert to.status == Status.PENDING
to = TaskOrder(number="42", start_date=random_future_date())
assert to.status == Status.PENDING
def test_active_status(self):
to = TaskOrder(
number="42", start_date=random_past_date(), end_date=random_future_date()
)
assert to.status == Status.ACTIVE
def test_expired_status(self):
to = TaskOrder(
number="42", start_date=random_past_date(), end_date=random_past_date()
)
assert to.status == Status.EXPIRED
def test_is_submitted():
to = TaskOrder()
assert to.status == Status.PENDING
assert not to.is_submitted
with_args = TaskOrder(number="42")
assert to.status == Status.PENDING
to = TaskOrder(number="42")
assert to.is_submitted