Update request form (#45)

* Implement OrganizationInfo form, add it to the template

* Format request_new

* Update "Details of Use" section

* Refactor request_new

* Added some new fields, but form is still WIP

* Add details of use fields

* Add org info fields

* Add some comments

* Add Financial Verification and more Details of Use fields

* Update some textarea fields to single text field

* WIP

* Implement OrganizationInfo form, add it to the template

* Format request_new

* Update "Details of Use" section

* Refactor request_new

* Added some new fields, but form is still WIP

* Add details of use fields

* Add org info fields

* Add some comments

* Add Financial Verification and more Details of Use fields

* Update some textarea fields to single text field

* Format

* Update fields with the correct fieldtypes

* Begin updating sidenav changes

* Split form into each section

* adjust and skip some outdated form validation tests

* break request form into multiple form objects

* need to send user ID to requests-queue

* use DateForm for start date in request

* alter request_new handler to pass raw form data to template

* change review form

* Add KO and COR section titles

* Update date input class name

* Add a special case for the summary form. We should refactor this

* Add read-only fields for review and submit section

* Add minimum number validators to request form

* Fix formatting

* Use html5 datepicker for dates

* Fix request form validators

* Finish org info form

* Finish POC form

* Finish financial verification form

* Move PE and UII to financial form

* Un-skip form validation test
This commit is contained in:
luisgov 2018-07-03 10:10:44 -04:00 committed by richard-dds
parent 863d323319
commit 9d781577c4
22 changed files with 890 additions and 505 deletions

6
Pipfile.lock generated
View File

@ -148,11 +148,11 @@
},
"black": {
"hashes": [
"sha256:22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44",
"sha256:4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191"
"sha256:479cc8b3455a75b5b289276b5f47fd2bb412f2f369292989c12b891264758b5c",
"sha256:fe3b7ac846f2a7c91d926782184826c57d2be283c57f0d6b37b85496eb5469ff"
],
"index": "pypi",
"version": "==18.6b4"
"version": "==18.6b3"
},
"click": {
"hashes": [

View File

@ -1,6 +1,6 @@
import tornado.gen
from tornado.httpclient import AsyncHTTPClient
from json import dumps, loads
from json import dumps, loads, decoder
class ApiClient(object):
@ -49,7 +49,12 @@ class ApiClient(object):
def adapt_response(self, response):
if "application/json" in response.headers["Content-Type"]:
json = loads(response.body)
setattr(response, "json", json)
try:
json = loads(response.body)
setattr(response, "json", json)
except decoder.JSONDecodeError:
setattr(response, "json", {})
else:
setattr(response, "json", {})
setattr(response, "ok", 200 <= response.code < 300)
return response

View File

@ -1,9 +0,0 @@
from wtforms.fields.html5 import IntegerField
from wtforms.validators import Required, ValidationError
from wtforms_tornado import Form
class DateForm(Form):
month = IntegerField("Month", validators=[Required()])
day = IntegerField("Day", validators=[Required()])
year = IntegerField("Year", validators=[Required()])

34
atst/forms/fields.py Normal file
View File

@ -0,0 +1,34 @@
from wtforms.fields.html5 import DateField
from wtforms.fields import Field
from wtforms.widgets import TextArea
import pendulum
class DateField(DateField):
def _value(self):
if self.data:
return pendulum.parse(self.data).date()
else:
return None
def process_formdata(self, values):
if values:
self.data = values[0]
else:
self.data = []
class NewlineListField(Field):
widget = TextArea()
def _value(self):
if self.data:
return "\n".join(self.data)
else:
return ""
def process_formdata(self, valuelist):
if valuelist:
self.data = [l.strip() for l in valuelist[0].split("\n")]
else:
self.data = []

87
atst/forms/financial.py Normal file
View File

@ -0,0 +1,87 @@
from wtforms.fields.html5 import EmailField
from wtforms.fields import StringField, SelectField
from wtforms.validators import Required, Email
from wtforms_tornado import Form
from .fields import NewlineListField
class FinancialForm(Form):
task_order_id = StringField(
"Task Order Number associated with this request.", validators=[Required()]
)
uii_ids = NewlineListField(
"Please enter the Unique Item Identifier (UII)s related to your application(s) if you already have them."
)
pe_id = NewlineListField(
"Please provide the Program Element (PE) Numbers related to your request"
)
fname_co = StringField("Contracting Officer First Name", validators=[Required()])
lname_co = StringField("Contracting Officer Last Name", validators=[Required()])
email_co = EmailField("Contracting Officer Email", validators=[Required(), Email()])
office_co = StringField("Contracting Office Office", validators=[Required()])
fname_cor = StringField(
"Contracting Officer Representative (COR) First Name", validators=[Required()]
)
lname_cor = StringField(
"Contracting Officer Representative (COR) Last Name", validators=[Required()]
)
email_cor = EmailField(
"Contracting Officer Representative (COR) Email",
validators=[Required(), Email()],
)
office_cor = StringField(
"Contracting Officer Representative (COR) Office", validators=[Required()]
)
funding_type = SelectField(
validators=[Required()],
choices=[
("", "- Select -"),
("RDTE", "Research, Development, Testing & Evaluation (RDT&E)"),
("OM", "Operations & Maintenance (O&M)"),
("PROC", "Procurement (PROC)"),
("OTHER", "Other"),
],
)
funding_type_other = StringField(
"If other, please specify", validators=[Required()]
)
clin_0001 = StringField(
"CLIN 0001 - Unclassified IaaS and PaaS Amount", validators=[Required()]
)
clin_0003 = StringField(
"CLIN 0003 - Unclassified Cloud Support Package", validators=[Required()]
)
clin_1001 = StringField(
"CLIN 1001 - Unclassified IaaS and PaaS Amount OPTION PERIOD 1",
validators=[Required()],
)
clin_1003 = StringField(
"CLIN 1003 - Unclassified Cloud Support Package OPTION PERIOD 1",
validators=[Required()],
)
clin_2001 = StringField(
"CLIN 2001 - Unclassified IaaS and PaaS Amount OPTION PERIOD 2",
validators=[Required()],
)
clin_2003 = StringField(
"CLIN 2003 - Unclassified Cloud Support Package OPTION PERIOD 2",
validators=[Required()],
)

View File

@ -1,5 +0,0 @@
from wtforms_tornado import Form
class FundingForm(Form):
pass

34
atst/forms/org.py Normal file
View File

@ -0,0 +1,34 @@
from wtforms.fields.html5 import EmailField, TelField
from wtforms.fields import RadioField, StringField
from wtforms.validators import Required, Length, Email
from wtforms_tornado import Form
from .fields import DateField
class OrgForm(Form):
fname_request = StringField("First Name", validators=[Required()])
lname_request = StringField("Last Name", validators=[Required()])
email_request = EmailField(
"Email (associated with your CAC)", validators=[Required(), Email()]
)
phone_number = TelField("Phone Number", validators=[Required(), Length(min=7)])
service_branch = StringField("Service Branch or Agency", validators=[Required()])
citizenship = RadioField(
choices=[
("United States", "United States"),
("Foreign National", "Foreign National"),
("Other", "Other"),
],
validators=[Required()],
)
designation = StringField("Designation of Person", validators=[Required()])
date_latest_training = DateField(
"Latest Information Assurance (IA) Training completion date.",
validators=[Required()],
)

View File

@ -1,5 +0,0 @@
from wtforms_tornado import Form
class OrganizationInfoForm(Form):
pass

17
atst/forms/poc.py Normal file
View File

@ -0,0 +1,17 @@
from wtforms.fields import StringField
from wtforms.validators import Required, Email, Length
from wtforms_tornado import Form
from .validators import IsNumber
class POCForm(Form):
fname_poc = StringField("POC First Name", validators=[Required()])
lname_poc = StringField("POC Last Name", validators=[Required()])
email_poc = StringField(
"POC Email (associated with CAC)", validators=[Required(), Email()]
)
dodid_poc = StringField(
"DOD ID", validators=[Required(), Length(min=10), IsNumber()]
)

View File

@ -1,5 +0,0 @@
from wtforms_tornado import Form
class ReadinessForm(Form):
pass

View File

@ -1,73 +1,87 @@
from wtforms.fields.html5 import IntegerField
from wtforms.fields import (
RadioField,
StringField,
SelectField,
TextAreaField,
FormField,
)
from wtforms.validators import Required, ValidationError
from wtforms.fields import RadioField, StringField, TextAreaField
from wtforms.validators import NumberRange, InputRequired
from wtforms_tornado import Form
from .date import DateForm
from .fields import DateField, NewlineListField
from .validators import DateRange
import pendulum
class RequestForm(Form):
application_name = StringField("Application name", validators=[Required()])
application_description = TextAreaField(
"Application description", validators=[Required()]
)
dollar_value = IntegerField(
"Estimated dollar value of use", validators=[Required()]
)
input_estimate = SelectField(
"How did you arrive at this estimate?",
validators=[Required()],
choices=[
("", "- Select -"),
("calculator", "CSP usage calculator"),
("B", "Option B"),
("C", "Option C"),
],
)
# no way to apply a label to a whole nested form like this
date_start = FormField(DateForm)
period_of_performance = SelectField(
"Desired period of performance",
validators=[Required()],
choices=[
("", "- Select -"),
("value1", "30 days"),
("value2", "60 days"),
("value3", "90 days"),
],
)
classification_level = RadioField(
"Classification level",
validators=[Required()],
choices=[
("unclassified", "Unclassified"),
("secret", "Secret"),
("top-secret", "Top Secret"),
],
)
primary_service_branch = StringField(
"Primary service branch usage", validators=[Required()]
)
cloud_model = RadioField(
"Cloud model service",
validators=[Required()],
choices=[("iaas", "IaaS"), ("paas", "PaaS"), ("both", "Both")],
)
number_of_cores = IntegerField("Number of cores", validators=[Required()])
total_ram = IntegerField("Total RAM", validators=[Required()])
object_storage = IntegerField("Total object storage", validators=[Required()])
server_storage = IntegerField("Total server storage", validators=[Required()])
total_active_users = IntegerField("Total active users", validators=[Required()])
total_peak_users = IntegerField("Total peak users", validators=[Required()])
total_requests = IntegerField("Total requests", validators=[Required()])
total_environments = IntegerField("Total environments", validators=[Required()])
# this is just an example validation; obviously this is wrong.
def validate_total_ram(self, field):
if (field.data % 2) != 0:
raise ValidationError("RAM must be in increments of 2.")
# Details of Use: Overall Request Details
dollar_value = IntegerField(
"What is the total estimated dollar value of the cloud resources you are requesting using the JEDI CSP Calculator? ",
validators=[InputRequired(), NumberRange(min=1)],
)
num_applications = IntegerField(
"Please estimate the number of applications that might be supported by this request",
validators=[InputRequired(), NumberRange(min=1)],
)
date_start = DateField(
"Date you expect to start accessing this cloud resource.",
validators=[
InputRequired(),
DateRange(
lower_bound=pendulum.duration(days=0),
message="Must be no earlier than today.",
),
],
)
app_description = TextAreaField(
"Please briefly describe how your team is expecting to use the JEDI Cloud"
)
supported_organizations = StringField(
"What organizations are supported by these applications?",
validators=[InputRequired()],
)
# Details of Use: Cloud Resources
total_cores = IntegerField(
"Total Number of vCPU cores", validators=[InputRequired(), NumberRange(min=0)]
)
total_ram = IntegerField(
"Total RAM", validators=[InputRequired(), NumberRange(min=0)]
)
total_object_storage = IntegerField(
"Total object storage", validators=[InputRequired(), NumberRange(min=0)]
)
total_database_storage = IntegerField(
"Total database storage", validators=[InputRequired(), NumberRange(min=0)]
)
total_server_storage = IntegerField(
"Total server storage", validators=[InputRequired(), NumberRange(min=0)]
)
# Details of Use: Support Staff
has_contractor_advisor = RadioField(
"Do you have a contractor to advise and assist you with using cloud services?",
choices=[("yes", "Yes"), ("no", "No")],
validators=[InputRequired()],
)
is_migrating_application = RadioField(
"Are you using the JEDI Cloud to migrate existing applications?",
choices=[("yes", "Yes"), ("no", "No")],
validators=[InputRequired()],
)
supporting_organization = TextAreaField(
"Please describe the organizations that are supporting you, include both government and contractor resources",
validators=[InputRequired()],
)
has_migration_office = RadioField(
"Do you have a migration office that you're working with to migrate to the cloud?",
choices=[("yes", "Yes"), ("no", "No")],
validators=[InputRequired()],
)
supporting_organization = StringField(
"Please describe the organizations that are supporting you, include both government and contractor resources.",
validators=[InputRequired()],
)

View File

@ -1,5 +1,6 @@
from wtforms.fields import BooleanField
from wtforms_tornado import Form
class ReviewForm(Form):
pass
reviewed = BooleanField("I have reviewed this data and it is correct.")

29
atst/forms/validators.py Normal file
View File

@ -0,0 +1,29 @@
from wtforms.validators import ValidationError
import pendulum
def DateRange(lower_bound=None, upper_bound=None, message=None):
def _date_range(form, field):
now = pendulum.now().date()
if lower_bound is not None:
date = pendulum.parse(field.data).date()
if (now - lower_bound) > date:
raise ValidationError(message)
if upper_bound is not None:
date = pendulum.parse(field.data).date()
if (now + upper_bound) < date:
raise ValidationError(message)
return _date_range
def IsNumber(message="Please enter a valid number."):
def _is_number(form, field):
try:
int(field.data)
except ValueError:
raise ValidationError(message)
return _is_number

View File

@ -1,10 +1,10 @@
import tornado
from atst.handler import BaseHandler
from atst.forms.request import RequestForm
from atst.forms.organization_info import OrganizationInfoForm
from atst.forms.funding import FundingForm
from atst.forms.readiness import ReadinessForm
from atst.forms.org import OrgForm
from atst.forms.poc import POCForm
from atst.forms.review import ReviewForm
from atst.forms.financial import FinancialForm
import tornado.httputil
@ -12,18 +12,30 @@ class RequestNew(BaseHandler):
screens = [
{
"title": "Details of Use",
"section": "details_of_use",
"form": RequestForm,
"subitems": [
{"title": "Application Details", "id": "application-details"},
{"title": "Computation", "id": "computation"},
{"title": "Storage", "id": "storage"},
{"title": "Usage", "id": "usage"},
{"title": "Overall request details", "id": "overall-request-details"},
{"title": "Cloud Resources", "id": "cloud-resources"},
{"title": "Support Staff", "id": "support-staff"},
],
},
{"title": "Organizational Info", "form": OrganizationInfoForm},
{"title": "Funding/Contracting", "form": FundingForm},
{"title": "Readiness Survey", "form": ReadinessForm},
{"title": "Review & Submit", "form": ReviewForm},
{
"title": "Information About You",
"section": "information_about_you",
"form": OrgForm,
},
{
"title": "Primary Point of Contact",
"section": "primary_poc",
"form": POCForm,
},
{"title": "Review & Submit", "section": "review_submit", "form": ReviewForm},
{
"title": "Financial Verification",
"section": "financial_verification",
"form": FinancialForm,
},
]
def initialize(self, page, requests_client):
@ -35,9 +47,14 @@ class RequestNew(BaseHandler):
def post(self, screen=1, request_id=None):
self.check_xsrf_cookie()
screen = int(screen)
form = self.screens[screen - 1]["form"](self.request.arguments)
form_metadata = self.screens[screen - 1]
form_section = form_metadata["section"]
form = form_metadata["form"](self.request.arguments)
if form.validate():
response = yield self.create_or_update_request(form.data, request_id)
response = yield self.create_or_update_request(
form_section, form.data, request_id
)
if response.ok:
where = self.application.default_router.reverse_url(
"request_form_update",
@ -54,20 +71,29 @@ class RequestNew(BaseHandler):
@tornado.gen.coroutine
def get(self, screen=1, request_id=None):
form = None
form_data = None
is_review_section = screen == 4
if request_id:
request = yield self.get_request(request_id)
if request.ok:
form_data = request.json["body"] if request else {}
form = self.screens[int(screen) - 1]["form"](data=form_data)
if is_review_section:
form_data = request.json["body"]
else:
form_metadata = self.screens[int(screen) - 1]
section = form_metadata["section"]
form_data = request.json["body"].get(section, request.json["body"])
form = form_metadata["form"](data=form_data)
self.show_form(screen=screen, form=form, request_id=request_id)
self.show_form(screen=screen, form=form, request_id=request_id, data=form_data)
def show_form(self, screen=1, form=None, request_id=None):
def show_form(self, screen=1, form=None, request_id=None, data=None):
if not form:
form = self.screens[int(screen) - 1]["form"](self.request.arguments)
self.render(
"requests/screen-%d.html.to" % int(screen),
f=form,
data=data,
page=self.page,
screens=self.screens,
current=int(screen),
@ -78,16 +104,16 @@ class RequestNew(BaseHandler):
@tornado.gen.coroutine
def get_request(self, request_id):
request = yield self.requests_client.get(
"/users/{}/requests/{}".format(self.get_current_user(), request_id),
"/users/{}/requests/{}".format(self.get_current_user()["id"], request_id),
raise_error=False,
)
return request
@tornado.gen.coroutine
def create_or_update_request(self, form_data, request_id=None):
def create_or_update_request(self, form_section, form_data, request_id=None):
request_data = {
"creator_id": self.get_current_user()["id"],
"request": form_data,
"request": {form_section: form_data},
}
if request_id:
response = yield self.requests_client.patch(

View File

@ -2,11 +2,23 @@ from {
margin-bottom: 3rem;
}
select {
border-radius: 0;
-webkit-appearance: none;
}
.usa-date-input label {
margin-top: 0;
}
.input-label {
margin-top: 1rem;
}
.usa-fieldset-inputs {
margin-top: 2.25rem;
label:first-child {
padding-bottom: 0.5rem;
}
}

View File

@ -13,6 +13,6 @@ h1 {
margin-top: 0.5em;
}
label {
font-style: italic;
h2 {
margin-top: 0;
}

View File

@ -5,112 +5,141 @@
{% autoescape None %}
{% if f.errors %}
<b>There were some errors, see below.</b>
<b class="usa-input-error-message">There were some errors, see below.</b>
{% end %}
<h3 id="application-details">Application Details</h3>
<!-- DETAILS OF USE -->
<button class="usa-button-secondary usa-button-active">New Application</button>
<button class="usa-button-secondary" disabled>Existing Application</button>
<button class="usa-button-secondary" disabled>Sandbox Application</button>
<h3 id="overall-request-details">Overall Request Details</h3>
<p>Please help us understand the size and scope of your resource request.</p>
{{ f.dollar_value.label }}
{{ f.dollar_value(placeholder="Total dollar amount to be associated with the Task Order")}}
{% for e in f.dollar_value.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.num_applications.label }}
{{ f.num_applications(placeholder="Estimated number of applications") }}
{% for e in f.num_applications.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.date_start.label }}
{{ f.date_start }}
{% for e in f.date_start.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.supported_organizations.label }}
{{ f.supported_organizations(placeholder="Add tags associated with DoD components or other entities") }}
{% for e in f.supported_organizations.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.app_description.label }}
{{ f.app_description(placeholder="Example: My organization is supporting the migration of XYZ system to the cloud due to XX policy memo. I am planning to use a sandbox environment to do testing.") }}
{% for e in f.app_description.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
<h3 id="cloud-resources">Cloud Resources</h3>
<p>Please tell us about your expected cloud resource usage as best as you can. Please refer to the <a href="">Cloud Service Provider</a> as necessary.</p>
{{ f.total_cores.label }}
{{ f.total_cores(placeholder="Expected total cores", min="1", max="32") }}
{% for e in f.total_cores.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.total_ram.label }}
{{ f.total_ram(placeholder="Expected amount of RAM", min="1", max="32") }}
{% for e in f.total_ram.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.total_object_storage.label }}
{{ f.total_object_storage(placeholder="Expected total object storage") }}
{% for e in f.total_object_storage.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.total_database_storage.label }}
{{ f.total_database_storage(placeholder="Expected total database storage") }}
{% for e in f.total_database_storage.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.total_server_storage.label }}
{{ f.total_server_storage(placeholder="Expected total server storage") }}
{% for e in f.total_server_storage.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.application_name.label }}
{{ f.application_name(placeholder="What is the application name?") }}
{{ f.application_description.label }}
{{ f.application_description(placeholder="Describe the application") }}
{{ f.dollar_value.label }}
{{ f.dollar_value(placeholder="$") }}
{{ f.input_estimate.label }}
{{ f.input_estimate }}
<b>NEW</b>
<hr>
<fieldset>
<label for="input-start-date">Expected start date</label>
<div class="usa-date-of-birth">
<div class="usa-form-group usa-form-group-month">
{{ f.date_start.month.label }}
{{ f.date_start.month(min="1", max="12") }}
</div>
<div class="usa-form-group usa-form-group-day">
{{ f.date_start.day.label }}
{{ f.date_start.day(min="1", max="31") }}
</div>
<div class="usa-form-group usa-form-group-year">
{{ f.date_start.year.label }}
{{ f.date_start.year(min="2000", max="2040") }}
</div>
</div>
</fieldset>
{{ f.period_of_performance.label }}
{{ f.period_of_performance }}
<br>
<fieldset class="usa-fieldset-inputs usa-sans">
{{ f.classification_level.label }}
{{ f.classification_level(class_="usa-unstyled-list") }}
</fieldset>
{{ f.primary_service_branch.label }}
{{ f.primary_service_branch(placeholder="Add tags associated with service branches") }}
<br>
<fieldset class="usa-fieldset-inputs usa-sans">
{{ f.cloud_model.label }}
{{ f.cloud_model(class_="usa-unstyled-list") }}
</fieldset>
<h3 id="support-staff">Support Staff</h3>
<p>We want to learn more about the people helping you with the cloud.</p>
<!-- Computation -->
<h4 id="application-details">Computation</h4>
<p>These headings introduce, respectively, sections and subsections within your body copy. As you create these headings, follow the same guidelines that you use when writing section headings: Be succinct, descriptive, and precise.</p>
{{ f.number_of_cores.label }}
{{ f.number_of_cores(placeholder="Total cores", min="1", max="32") }}
<!-- example field with custom validation -->
{{ f.total_ram.label }}
{{ f.total_ram(placeholder="Total RAM", min="1", max="32") }}
<!-- example validation errors -->
{% for e in f.total_ram.errors %}
<fieldset class="usa-fieldset-inputs">
{{ f.has_contractor_advisor.label }}
{{ f.has_contractor_advisor(class_="usa-unstyled-list") }}
{% for e in f.has_contractor_advisor.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
</fieldset>
<fieldset class="usa-fieldset-inputs">
{{ f.is_migrating_application.label }}
{{ f.is_migrating_application(class_="usa-unstyled-list") }}
{% for e in f.is_migrating_application.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
</fieldset>
<!-- Storage -->
<h4 id="application-storage">Storage</h4>
<p>The particulars of your body copy will be determined by the topic of your page. Regardless of topic, its a good practice to follow the inverted pyramid structure when writing copy: Begin with the information thats most important to your users and then present information of less importance.</p>
<fieldset class="usa-fieldset-inputs">
{{ f.has_migration_office.label }}
{{ f.has_migration_office(class_="usa-unstyled-list") }}
{% for e in f.has_migration_office.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
</fieldset>
{{ f.object_storage.label }}
{{ f.object_storage(placeholder="Total object storage") }}
<fieldset class="usa-fieldset-inputs">
{{ f.supporting_organization.label }}
{{ f.supporting_organization(placeholder="Example: AF CCE/HNI, Navy SPAWAR, MITRE", class_="usa-unstyled-list") }}
{% for e in f.supporting_organization.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
</fieldset>
{{ f.server_storage.label }}
{{ f.server_storage(placeholder="Total server storage") }}
<!-- Application Usage -->
<h4 id="application-usage">Estimated Application Storage</h4>
<p>The particulars of your body copy will be determined by the topic of your page. Regardless of topic, its a good practice to follow the inverted pyramid structure when writing copy: Begin with the information thats most important to your users and then present information of less importance.</p>
{{ f.total_active_users.label }}
{{ f.total_active_users(placeholder="Total active users") }}
{{ f.total_peak_users.label }}
{{ f.total_peak_users(placeholder="Total peak users") }}
{{ f.total_requests.label }}
{{ f.total_requests(placeholder="Total requests") }}
{{ f.total_environments.label }}
{{ f.total_environments(placeholder="Total number of environments") }}
{% end %}

View File

@ -2,90 +2,79 @@
{% block form %}
<h2>Organizational Info</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloremque placeat distinctio accusamus quo temporibus facilis, dicta delectus asperiores. Nihil aut quod quibusdam id fugit, officia dolorum laudantium! Quidem tempora, aliquam.</p>
<h3 id="information-about-you">Information About You</h3>
<label for="">Name</label>
<input id="" name="" type="text">
<label for="">Email</label>
<input id="" name="" type="email" placeholder="i.e. name@mail.gov">
<label for="">Phone Number</label>
<input id="" name="" type="text" placeholder="i.e. (123) 456-7890">
<label for="">Office Symbol / Department</label>
<input id="" name="" type="text" placeholder="i.e. Army PEO-EIS">
<fieldset>
<label for="">Citizenship</label>
<ul class="usa-unstyled-list">
<li>
<input id="" type="radio" name="" value"">
<label class="input-label">United States</label>
</li>
<li>
<input id="" type="radio" name="" value"">
<label class="input-label">Foreign National</label>
</li>
<li>
<input id="" type="radio" name="" value"">
<label class="input-label">Other</label>
</li>
</ul>
</fieldset>
<fieldset>
<label for="">Designation of Person</label>
<ul class="usa-unstyled-list">
<li>
<input id="" type="radio" name="" value"">
<label class="input-label">Military</label>
</li>
<li>
<input id="" type="radio" name="" value"">
<label class="input-label">Civilian</label>
</li>
<li>
<input id="" type="radio" name="" value"">
<label class="input-label">Contractor</label>
</li>
</ul>
</fieldset>
<fieldset>
<label for="">Latest IA Training completion date</label>
<div class="usa-date-input">
<div class="usa-form-group usa-form-group-month">
<label for="">Month</label>
<input class="usa-input-inline" id="" name="" type="number" min="1" max="12" value="">
</div>
<div class="usa-form-group usa-form-group-day">
<label for="">Day</label>
<input class="usa-input-inline" id="" name="" type="number" min="1" max="31" value="">
</div>
<div class="usa-form-group usa-form-group-year">
<label for="">Year</label>
<input class="usa-input-inline" id="" name="" type="number" min="1900" max="2000" value="">
</div>
</div>
</fieldset>
<h3 id="information-about-your-collaborators">Information About Your Collaborators</h3>
<p>Please designate a Contracting Officer that will help you complete this process.</p>
<label for="">Name</label>
<input id="" name="" type="text">
<label for="">Email</label>
<input id="" name="" type="text" placeholder="i.e. name@mail.gov">
{% autoescape None %}
{% if f.errors %}
<b class="usa-input-error-message">There were some errors, see below.</b>
{% end %}
<h2 id="Information About You">Information About You</h2>
<p>Please tell us more about yourself.</p>
{{ f.fname_request.label }}
{{ f.fname_request(placeholder="Your first name") }}
{% for e in f.fname_request.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.lname_request.label }}
{{ f.lname_request(placeholder="Your last name") }}
{% for e in f.lname_request.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.email_request.label }}
{{ f.email_request(placeholder="jane@mail.mil") }}
{% for e in f.email_request.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.phone_number.label }}
{{ f.phone_number(placeholder="(123) 456-7890") }}
{% for e in f.phone_number.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.service_branch.label }}
{{ f.service_branch(placeholder="i.e. US Air Force, US Army, US Navy, Marine Corps, Defense Media Agency") }}
{% for e in f.service_branch.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
<fieldset class="usa-fieldset-inputs">
{{ f.citizenship.label }}
{{ f.citizenship(class_="usa-unstyled-list") }}
{% for e in f.citizenship.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
</fieldset>
{{ f.designation.label }}
{{ f.designation }}
{% for e in f.designation.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.date_latest_training.label }}
{{ f.date_latest_training }}
{% for e in f.date_latest_training.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{% end %}

View File

@ -1,7 +1,55 @@
{% extends '../requests_new.html.to' %}
{% block form %}
<h2>Funding/Contracting</h2>
<input type='textbox'>
{% autoescape None %}
{% if f.errors %}
<b class="usa-input-error-message">There were some errors, see below.</b>
{% end %}
<h2 id="primary-poc">Primary Government/Military <br> Point of Contact (POC)</h2>
<p>Please designate a Primary Point of Contact that will be responsible for owning the workspace in the JEDI Cloud.</p>
<p>The Point of Contact will become the primary owner of the <em>workspace</em> created to use the JEDI Cloud. As a workspace owner, this person will have the ability to:
<ul>
<li>Create multiple application stacks and environments in the workspace to access the commercial cloud service provider portal</li>
<li>Add and manage users in the workspace</li>
<li>View the budget and billing history related to this workspace</li>
<li>Manage access to the Cloud Service Provider's Console</li>
<li>Transfer Workspace ownership to another person</li>
</ul>
<em>This POC may be you.</em>
</p>
{{ f.fname_poc.label }}
{{ f.fname_poc(placeholder="First name") }}
{% for e in f.fname_poc.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.lname_poc.label }}
{{ f.lname_poc(placeholder="Last name") }}
{% for e in f.lname_poc.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.email_poc.label }}
{{ f.email_poc(placeholder="jane@mail.mil") }}
{% for e in f.email_poc.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.dodid_poc.label }}
{{ f.dodid_poc(placeholder="10-digit number on the back of the CAC") }}
{% for e in f.dodid_poc.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{% end %}

View File

@ -1,7 +1,123 @@
{% extends '../requests_new.html.to' %}
{% block form %}
<h2>Readiness Survey</h2>
<input type='textbox'>
{% autoescape None %}
{% if f.errors %}
<b>There were some errors, see below.</b>
{% end %}
<h2 id="review-submit">Review &amp; Submit</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Autem ullam veritatis fuga non repellendus repellat dolorum labore nulla iure aspernatur ipsam recusandae saepe harum iste, dolorem adipisci dolores eum, doloribus?</p>
<h3>Details of Use <a href="">Edit</a></h3>
<h4>Overall Request Details</h4>
<label>What is the total estimated dollar value of the cloud resources you are requesting using the JEDI CSP Calculator? </label>
<b>{{ data.get('details_of_use', {}).get('dollar_value') }}</b>
<label>Please estimate the number of applications that might be supported by this request</label>
<b>{{ data.get('details_of_use', {}).get('num_applications') }}</b>
<label>Start Date</label>
<b>{{ data.get('details_of_use', {}).get('date_start') }}</b>
<label>Please briefly describe how your team is expecting to use the JEDI Cloud</label>
<b>{{ data.get('details_of_use', {}).get('app_description') }}</b>
<label>What organizations are supported by these applications?</label>
<b>{{ data.get('details_of_use', {}).get('supported_organizations') }}</b>
<label>Please enter the Unique Item Identifier (UII)s related to your application(s) if you already have them.</label>
<b>{{ data.get('details_of_use', {}).get('uii_ids') }}</b>
<label>Please provide the Program Element (PE) Numbers related to your request</label>
<b>{{ data.get('details_of_use', {}).get('pe_id') }}</b>
<h4>Cloud Resources</h4>
<label>Total Number of vCPU cores</label>
<b>{{ data.get('details_of_use', {}).get('total_cores') }}</b>
<label>Total RAM</label>
<b>{{ data.get('details_of_use', {}).get('total_ram') }}</b>
<label>Total object storage</label>
<b>{{ data.get('details_of_use', {}).get('total_object_storage') }}</b>
<label>Total server storage</label>
<b>{{ data.get('details_of_use', {}).get('total_server_storage') }}</b>
<h4>Support Staff</h4>
<label>Do you have a contractor to advise and assist you with using cloud services?</label>
<b>{{ data.get('details_of_use', {}).get('has_contractor_advisor') }}</b>
<label>Are you using the JEDI Cloud to migrate existing applications?</label>
<b>{{ data.get('details_of_use', {}).get('is_migrating_application') }}</b>
<label>Please describe the organizations that are supporting you, include both government and contractor resources</label>
<b>{{ data.get('details_of_use', {}).get('supporting_organization') }}</b>
<label>Do you have a migration office that you're working with to migrate to the cloud?</label>
<b>{{ data.get('details_of_use', {}).get('has_migration_office') }}</b>
<label>Please describe the organizations that are supporting you, include both government and contractor resources.</label>
<b>{{ data.get('details_of_use', {}).get('supporting_organization') }}</b>
<br><br><hr>
<h3>Information About You <a href="">Edit</a></h3>
<label>First Name</label>
<b>{{ data.get('information_about_you', {}).get('fname_request') }}</b>
<label>Last Name</label>
<b>{{ data.get('information_about_you', {}).get('lname_request') }}</b>
<label>Email (associated with your CAC)</label>
<b>{{ data.get('information_about_you', {}).get('email_request') }}</b>
<label>Phone Number</label>
<b>{{ data.get('information_about_you', {}).get('phone_number') }}</b>
<label>Service Branch or Agency</label>
<b>{{ data.get('information_about_you', {}).get('service_branch') }}</b>
<label>Citizenship</label>
<b>{{ data.get('information_about_you', {}).get('citizenship') }}</b>
<label>Designation of Person</label>
<b>{{ data.get('information_about_you', {}).get('designation') }}</b>
<label>Latest Information Assurance (IA) Training completion date</label>
<b>{{ data.get('information_about_you', {}).get('date_latest_training') }}</b>
<br><br><hr>
<h3>Primary Government/Military Point of Contact (POC) <a href="">Edit</a></h3>
<label>POC First Name</label>
<b>{{ data.get('primary_poc', {}).get('fname_poc')}}</b>
<label>POC Last Name</label>
<b>{{ data.get('primary_poc', {}).get('lname_poc')}}</b>
<label>POC Email (associated with CAC)</label>
<b>{{ data.get('primary_poc', {}).get('email_poc')}} </b>
<label>DOD ID</label>
<b>{{ data.get('primary_poc', {}).get('dodid_poc')}}</b>
<br><br>
{% end %}
{% block next %}
<input type='submit' class='usa-button usa-button-primary' value='Submit' />
{% end %}

View File

@ -1,202 +1,176 @@
{% extends '../requests_new.html.to' %}
{% block form %}
<h2>Review &amp; Submit</h2>
<p class="usa-font-lead">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Beatae placeat maiores illo totam consequuntur ipsum quo animi earum voluptatem, velit minus, perferendis aperiam, tenetur alias nemo ratione accusantium, ullam at!</p>
<h3>1. Details of Use <a href="" class="usa-button usa-button-secondary">Edit</a></h3>
<h4>Applications &amp; Environments (2)</h4>
<table class="usa-table-borderless" width="100%">
<thead>
<tr>
<th scope="col">Application Name</th>
<th scope="col">Total Users</th>
<th scope="col">Estimated Cost</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#app-code-mil">Code.mil</a></td>
<td>235</td>
<td>$1,000,000,000</td>
</tr>
<tr>
<td><a href="#app-digital-dojo">Digital Dojo</a></td>
<td>1,337</td>
<td>$10,000</td>
</tr>
</tbody>
</table>
<h5 id="app-code-mil">Code.mil</h5>
<h6>Application Details</h6>
<label>Application description</label>
<span>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolore tempora provident labore, deleniti facilis dolorum impedit repellat, et, quisquam modi eos libero nulla ut excepturi omnis. At magnam assumenda vero.</span>
<label>Estimated dollar value of use</label>
<span>$1,000,000,000</span>
<label>How did you arrive at this estimate?</label>
<span>CSP Calculator</span>
<label>Expected start date</label>
<span>2020-03-02</span>
<label>Desired period of performance</label>
<span>Lots of it</span>
<label>Classification level</label>
<span>Secret</span>
<label>Primary service branch usage</label>
<span>Army</span>
<label>Cloud model service</label>
<span>IaaS and PaaS</span>
<h6>Computation</h6>
<label>Number of cores</label>
<span>32</span>
<label>Total RAM</label>
<span>128GB</span>
<h6>Storage</h6>
<label>Object storage</label>
<span>10TB</span>
<label>Server storage</label>
<span>100TB</span>
<h6>Estimated Application Storage</h6>
<label>Expected active users</label>
<span>300</span>
<label>Expected peak concurrent users</label>
<span>1,000</span>
<label>Expected requests per minute</label>
<span>1,000</span>
<label>Number of application environments</label>
<span>3</span>
<h5 id="app-digital-dojo">Digital Dojo</h5>
<h6>Application Details</h6>
<label>Application description</label>
<span>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolore tempora provident labore, deleniti facilis dolorum impedit repellat, et, quisquam modi eos libero nulla ut excepturi omnis. At magnam assumenda vero.</span>
<label>Estimated dollar value of use</label>
<span>$10,000</span>
<label>How did you arrive at this estimate?</label>
<span>CSP Calculator</span>
<label>Expected start date</label>
<span>2020-03-02</span>
<label>Desired period of performance</label>
<span>Lots of it</span>
<label>Classification level</label>
<span>Secret</span>
<label>Primary service branch usage</label>
<span>Army</span>
<label>Cloud model service</label>
<span>IaaS and PaaS</span>
<h6>Computation</h6>
<label>Number of cores</label>
<span>32</span>
<label>Total RAM</label>
<span>128GB</span>
<h6>Storage</h6>
<label>Object storage</label>
<span>10TB</span>
<label>Server storage</label>
<span>100TB</span>
<h6>Estimated Application Storage</h6>
<label>Expected active users</label>
<span>300</span>
<label>Expected peak concurrent users</label>
<span>1,000</span>
<label>Expected requests per minute</label>
<span>1,000</span>
<label>Number of application environments</label>
<span>3</span>
<h3>2. Organizational Info <a href="" class="usa-button usa-button-secondary">Edit</a></h3>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint qui rem molestiae officia vitae quas error ut, est commodi quo! Error itaque earum, facere quod dolore qui beatae repudiandae accusantium.</p>
<h4>Information About Requester</h4>
<label>Name</label>
<span>Friedrich Straat</span>
<label>Email</label>
<span>fstraat@mail.gov</span>
<label>Phone Number</label>
<span>(123) 456-7890</span>
<label>Office Symbol / Department</label>
<span>Army</span>
<label>Citizenship</label>
<span>United States</span>
<label>Designation of Person</label>
<span>Military</span>
<label>Latest IA Training completion date</label>
<span>2018-04-12</span>
<h4>Information About Collaborators</h4>
<label>Name</label>
<span>Pietro Quirinis</span>
<label>Email</label>
<span>quirinis@mail.gov</span>
<h3>3. Funding &amp; Contracting <a href="" class="usa-button usa-button-secondary">Edit</a></h3>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Esse inventore non illo quibusdam tempora vero temporibus reprehenderit sapiente cumque enim quaerat fuga praesentium nemo vel, expedita numquam sequi sed iusto!</p>
<br><br>
{% autoescape None %}
{% if f.errors %}
<b class="usa-input-error-message">There were some errors, see below.</b>
{% end %}
<h2 id="financial-verification">Financial Verification</h2>
<p>In order to get you access to the JEDI Cloud, we will need you to enter the details below that will help us verify and account for your Task Order.</p>
{{ f.task_order_id.label }}
{{ f.task_order_id(placeholder="Example: 1234567899C0001") }}
{% for e in f.task_order_id.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.uii_ids.label }}
{{ f.uii_ids(placeholder="Example: \nDI 0CVA5786950 \nUN1945326361234786950") }}
{% for e in f.uii_ids.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.pe_id.label }}
{{ f.pe_id(placeholder="Example: 0203752A") }}
{% for e in f.pe_id.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
<h3>Contracting Officer (KO) Information</h3>
{{ f.fname_co.label }}
{{ f.fname_co(placeholder="Contracting Officer first name") }}
{% for e in f.fname_co.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.lname_co.label }}
{{ f.lname_co(placeholder="Contracting Officer last name") }}
{% for e in f.lname_co.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.email_co.label }}
{{ f.email_co(placeholder="jane@mail.mil") }}
{% for e in f.email_co.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.office_co.label }}
{{ f.office_co(placeholder="Example: WHS") }}
{% for e in f.office_co.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
<h3>Contracting Officer Representative (COR) Information</h3>
{{ f.fname_cor.label }}
{{ f.fname_cor(placeholder="Contracting Officer Representative first name") }}
{% for e in f.fname_cor.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.lname_cor.label }}
{{ f.lname_cor(placeholder="Contracting Officer Representative last name") }}
{% for e in f.lname_cor.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.email_cor.label }}
{{ f.email_cor(placeholder="jane@mail.mil") }}
{% for e in f.email_cor.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.office_cor.label }}
{{ f.office_cor(placeholder="Example: WHS") }}
{% for e in f.office_cor.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
<br><hr>
<em>&darr; FIELDS NEEDED FOR MANUAL ENTRY OF TASK ORDER INFORMATION (only necessary if EDA info not available)</em>
{{ f.funding_type.label }}
{{ f.funding_type }}
{% for e in f.funding_type.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.funding_type_other.label }}
{{ f.funding_type_other(placeholder="") }}
{% for e in f.funding_type_other.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.clin_0001.label }}
{{ f.clin_0001(placeholder="50,000") }}
{% for e in f.clin_0001.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.clin_0003.label }}
{{ f.clin_0003(placeholder="13,000") }}
{% for e in f.clin_0003.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.clin_1001.label }}
{{ f.clin_1001(placeholder="30,000") }}
{% for e in f.clin_1001.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.clin_1003.label }}
{{ f.clin_1003(placeholder="7,000") }}
{% for e in f.clin_1003.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.clin_2001.label }}
{{ f.clin_2001(placeholder="30,000") }}
{% for e in f.clin_2001.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{{ f.clin_2003.label }}
{{ f.clin_2003(placeholder="7,000") }}
{% for e in f.clin_2003.errors %}
<div class="usa-input-error-message">
{{ e }}
</div>
{% end %}
{% end %}
{% block next %}
<a class='usa-button'>Submit</a>
{% end %}

View File

@ -6,11 +6,5 @@ form = RequestForm()
def test_form_has_expected_fields():
label = form.application_name.label
assert label.text == "Application name"
def test_form_can_validate_total_ram():
form.application_name.data = 5
with pytest.raises(wtforms.validators.ValidationError):
form.validate_total_ram(form.application_name)
label = form.dollar_value.label
assert "estimated dollar value" in label.text