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": { "black": {
"hashes": [ "hashes": [
"sha256:22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44", "sha256:479cc8b3455a75b5b289276b5f47fd2bb412f2f369292989c12b891264758b5c",
"sha256:4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191" "sha256:fe3b7ac846f2a7c91d926782184826c57d2be283c57f0d6b37b85496eb5469ff"
], ],
"index": "pypi", "index": "pypi",
"version": "==18.6b4" "version": "==18.6b3"
}, },
"click": { "click": {
"hashes": [ "hashes": [

View File

@ -1,6 +1,6 @@
import tornado.gen import tornado.gen
from tornado.httpclient import AsyncHTTPClient from tornado.httpclient import AsyncHTTPClient
from json import dumps, loads from json import dumps, loads, decoder
class ApiClient(object): class ApiClient(object):
@ -49,7 +49,12 @@ class ApiClient(object):
def adapt_response(self, response): def adapt_response(self, response):
if "application/json" in response.headers["Content-Type"]: if "application/json" in response.headers["Content-Type"]:
json = loads(response.body) try:
setattr(response, "json", json) 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) setattr(response, "ok", 200 <= response.code < 300)
return response 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.html5 import IntegerField
from wtforms.fields import ( from wtforms.fields import RadioField, StringField, TextAreaField
RadioField, from wtforms.validators import NumberRange, InputRequired
StringField,
SelectField,
TextAreaField,
FormField,
)
from wtforms.validators import Required, ValidationError
from wtforms_tornado import Form from wtforms_tornado import Form
from .date import DateForm from .fields import DateField, NewlineListField
from .validators import DateRange
import pendulum
class RequestForm(Form): 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. # Details of Use: Overall Request Details
def validate_total_ram(self, field): dollar_value = IntegerField(
if (field.data % 2) != 0: "What is the total estimated dollar value of the cloud resources you are requesting using the JEDI CSP Calculator? ",
raise ValidationError("RAM must be in increments of 2.") 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 from wtforms_tornado import Form
class ReviewForm(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 import tornado
from atst.handler import BaseHandler from atst.handler import BaseHandler
from atst.forms.request import RequestForm from atst.forms.request import RequestForm
from atst.forms.organization_info import OrganizationInfoForm from atst.forms.org import OrgForm
from atst.forms.funding import FundingForm from atst.forms.poc import POCForm
from atst.forms.readiness import ReadinessForm
from atst.forms.review import ReviewForm from atst.forms.review import ReviewForm
from atst.forms.financial import FinancialForm
import tornado.httputil import tornado.httputil
@ -12,18 +12,30 @@ class RequestNew(BaseHandler):
screens = [ screens = [
{ {
"title": "Details of Use", "title": "Details of Use",
"section": "details_of_use",
"form": RequestForm, "form": RequestForm,
"subitems": [ "subitems": [
{"title": "Application Details", "id": "application-details"}, {"title": "Overall request details", "id": "overall-request-details"},
{"title": "Computation", "id": "computation"}, {"title": "Cloud Resources", "id": "cloud-resources"},
{"title": "Storage", "id": "storage"}, {"title": "Support Staff", "id": "support-staff"},
{"title": "Usage", "id": "usage"},
], ],
}, },
{"title": "Organizational Info", "form": OrganizationInfoForm}, {
{"title": "Funding/Contracting", "form": FundingForm}, "title": "Information About You",
{"title": "Readiness Survey", "form": ReadinessForm}, "section": "information_about_you",
{"title": "Review & Submit", "form": ReviewForm}, "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): def initialize(self, page, requests_client):
@ -35,9 +47,14 @@ class RequestNew(BaseHandler):
def post(self, screen=1, request_id=None): def post(self, screen=1, request_id=None):
self.check_xsrf_cookie() self.check_xsrf_cookie()
screen = int(screen) 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(): 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: if response.ok:
where = self.application.default_router.reverse_url( where = self.application.default_router.reverse_url(
"request_form_update", "request_form_update",
@ -54,20 +71,29 @@ class RequestNew(BaseHandler):
@tornado.gen.coroutine @tornado.gen.coroutine
def get(self, screen=1, request_id=None): def get(self, screen=1, request_id=None):
form = None form = None
form_data = None
is_review_section = screen == 4
if request_id: if request_id:
request = yield self.get_request(request_id) request = yield self.get_request(request_id)
if request.ok: if request.ok:
form_data = request.json["body"] if request else {} if is_review_section:
form = self.screens[int(screen) - 1]["form"](data=form_data) 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: if not form:
form = self.screens[int(screen) - 1]["form"](self.request.arguments) form = self.screens[int(screen) - 1]["form"](self.request.arguments)
self.render( self.render(
"requests/screen-%d.html.to" % int(screen), "requests/screen-%d.html.to" % int(screen),
f=form, f=form,
data=data,
page=self.page, page=self.page,
screens=self.screens, screens=self.screens,
current=int(screen), current=int(screen),
@ -78,16 +104,16 @@ class RequestNew(BaseHandler):
@tornado.gen.coroutine @tornado.gen.coroutine
def get_request(self, request_id): def get_request(self, request_id):
request = yield self.requests_client.get( 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, raise_error=False,
) )
return request return request
@tornado.gen.coroutine @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 = { request_data = {
"creator_id": self.get_current_user()["id"], "creator_id": self.get_current_user()["id"],
"request": form_data, "request": {form_section: form_data},
} }
if request_id: if request_id:
response = yield self.requests_client.patch( response = yield self.requests_client.patch(

View File

@ -2,11 +2,23 @@ from {
margin-bottom: 3rem; margin-bottom: 3rem;
} }
select {
border-radius: 0;
-webkit-appearance: none;
}
.usa-date-input label { .usa-date-input label {
margin-top: 0; margin-top: 0;
} }
.input-label { .input-label {
margin-top: 1rem; 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; margin-top: 0.5em;
} }
label { h2 {
font-style: italic; margin-top: 0;
} }

View File

@ -5,112 +5,141 @@
{% autoescape None %} {% autoescape None %}
{% if f.errors %} {% 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 %} {% end %}
<h3 id="application-details">Application Details</h3> <!-- DETAILS OF USE -->
<button class="usa-button-secondary usa-button-active">New Application</button> <h3 id="overall-request-details">Overall Request Details</h3>
<button class="usa-button-secondary" disabled>Existing Application</button> <p>Please help us understand the size and scope of your resource request.</p>
<button class="usa-button-secondary" disabled>Sandbox Application</button>
{{ 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 }} <h3 id="support-staff">Support Staff</h3>
{{ f.application_name(placeholder="What is the application name?") }} <p>We want to learn more about the people helping you with the cloud.</p>
{{ 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>
<fieldset class="usa-fieldset-inputs">
<!-- Computation --> {{ f.has_contractor_advisor.label }}
<h4 id="application-details">Computation</h4> {{ f.has_contractor_advisor(class_="usa-unstyled-list") }}
<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> {% for e in f.has_contractor_advisor.errors %}
{{ 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 %}
<div class="usa-input-error-message"> <div class="usa-input-error-message">
{{ e }} {{ e }}
</div> </div>
{% end %} {% 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 --> <fieldset class="usa-fieldset-inputs">
<h4 id="application-storage">Storage</h4> {{ f.has_migration_office.label }}
<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.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 }} <fieldset class="usa-fieldset-inputs">
{{ f.object_storage(placeholder="Total object storage") }} {{ 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 %} {% end %}

View File

@ -2,90 +2,79 @@
{% block form %} {% block form %}
{% autoescape None %}
<h2>Organizational Info</h2> {% if f.errors %}
<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> <b class="usa-input-error-message">There were some errors, see below.</b>
<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">
{% end %} {% 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' %} {% extends '../requests_new.html.to' %}
{% block form %} {% 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 %} {% 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' %} {% extends '../requests_new.html.to' %}
{% block form %} {% block form %}
<h2>Readiness Survey</h2>
<input type='textbox'> {% autoescape None %}
{% if f.errors %}
<b>There were some errors, see below.</b>
{% end %} {% 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' %} {% extends '../requests_new.html.to' %}
{% block form %} {% 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>
{% autoescape None %}
<h3>1. Details of Use <a href="" class="usa-button usa-button-secondary">Edit</a></h3> {% if f.errors %}
<b class="usa-input-error-message">There were some errors, see below.</b>
{% end %}
<h4>Applications &amp; Environments (2)</h4>
<table class="usa-table-borderless" width="100%">
<thead> <h2 id="financial-verification">Financial Verification</h2>
<tr> <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>
<th scope="col">Application Name</th>
<th scope="col">Total Users</th> {{ f.task_order_id.label }}
<th scope="col">Estimated Cost</th> {{ f.task_order_id(placeholder="Example: 1234567899C0001") }}
</tr> {% for e in f.task_order_id.errors %}
</thead> <div class="usa-input-error-message">
<tbody> {{ e }}
<tr> </div>
<td><a href="#app-code-mil">Code.mil</a></td> {% end %}
<td>235</td>
<td>$1,000,000,000</td> {{ f.uii_ids.label }}
</tr> {{ f.uii_ids(placeholder="Example: \nDI 0CVA5786950 \nUN1945326361234786950") }}
<tr> {% for e in f.uii_ids.errors %}
<td><a href="#app-digital-dojo">Digital Dojo</a></td> <div class="usa-input-error-message">
<td>1,337</td> {{ e }}
<td>$10,000</td> </div>
</tr> {% end %}
</tbody>
</table> {{ f.pe_id.label }}
{{ f.pe_id(placeholder="Example: 0203752A") }}
<h5 id="app-code-mil">Code.mil</h5> {% for e in f.pe_id.errors %}
<div class="usa-input-error-message">
<h6>Application Details</h6> {{ e }}
<label>Application description</label> </div>
<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> {% end %}
<label>Estimated dollar value of use</label>
<span>$1,000,000,000</span> <h3>Contracting Officer (KO) Information</h3>
<label>How did you arrive at this estimate?</label> {{ f.fname_co.label }}
<span>CSP Calculator</span> {{ f.fname_co(placeholder="Contracting Officer first name") }}
{% for e in f.fname_co.errors %}
<label>Expected start date</label> <div class="usa-input-error-message">
<span>2020-03-02</span> {{ e }}
</div>
<label>Desired period of performance</label> {% end %}
<span>Lots of it</span>
{{ f.lname_co.label }}
<label>Classification level</label> {{ f.lname_co(placeholder="Contracting Officer last name") }}
<span>Secret</span> {% for e in f.lname_co.errors %}
<div class="usa-input-error-message">
<label>Primary service branch usage</label> {{ e }}
<span>Army</span> </div>
{% end %}
<label>Cloud model service</label>
<span>IaaS and PaaS</span> {{ f.email_co.label }}
{{ f.email_co(placeholder="jane@mail.mil") }}
<h6>Computation</h6> {% for e in f.email_co.errors %}
<label>Number of cores</label> <div class="usa-input-error-message">
<span>32</span> {{ e }}
</div>
<label>Total RAM</label> {% end %}
<span>128GB</span>
{{ f.office_co.label }}
<h6>Storage</h6> {{ f.office_co(placeholder="Example: WHS") }}
<label>Object storage</label> {% for e in f.office_co.errors %}
<span>10TB</span> <div class="usa-input-error-message">
{{ e }}
<label>Server storage</label> </div>
<span>100TB</span> {% end %}
<h6>Estimated Application Storage</h6> <h3>Contracting Officer Representative (COR) Information</h3>
<label>Expected active users</label>
<span>300</span> {{ f.fname_cor.label }}
{{ f.fname_cor(placeholder="Contracting Officer Representative first name") }}
<label>Expected peak concurrent users</label> {% for e in f.fname_cor.errors %}
<span>1,000</span> <div class="usa-input-error-message">
{{ e }}
<label>Expected requests per minute</label> </div>
<span>1,000</span> {% end %}
<label>Number of application environments</label> {{ f.lname_cor.label }}
<span>3</span> {{ f.lname_cor(placeholder="Contracting Officer Representative last name") }}
{% for e in f.lname_cor.errors %}
<div class="usa-input-error-message">
<h5 id="app-digital-dojo">Digital Dojo</h5> {{ e }}
</div>
<h6>Application Details</h6> {% end %}
<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> {{ f.email_cor.label }}
{{ f.email_cor(placeholder="jane@mail.mil") }}
<label>Estimated dollar value of use</label> {% for e in f.email_cor.errors %}
<span>$10,000</span> <div class="usa-input-error-message">
{{ e }}
<label>How did you arrive at this estimate?</label> </div>
<span>CSP Calculator</span> {% end %}
<label>Expected start date</label> {{ f.office_cor.label }}
<span>2020-03-02</span> {{ f.office_cor(placeholder="Example: WHS") }}
{% for e in f.office_cor.errors %}
<label>Desired period of performance</label> <div class="usa-input-error-message">
<span>Lots of it</span> {{ e }}
</div>
<label>Classification level</label> {% end %}
<span>Secret</span>
<br><hr>
<label>Primary service branch usage</label> <em>&darr; FIELDS NEEDED FOR MANUAL ENTRY OF TASK ORDER INFORMATION (only necessary if EDA info not available)</em>
<span>Army</span>
<label>Cloud model service</label> {{ f.funding_type.label }}
<span>IaaS and PaaS</span> {{ f.funding_type }}
{% for e in f.funding_type.errors %}
<h6>Computation</h6> <div class="usa-input-error-message">
<label>Number of cores</label> {{ e }}
<span>32</span> </div>
{% end %}
<label>Total RAM</label>
<span>128GB</span> {{ f.funding_type_other.label }}
{{ f.funding_type_other(placeholder="") }}
<h6>Storage</h6> {% for e in f.funding_type_other.errors %}
<label>Object storage</label> <div class="usa-input-error-message">
<span>10TB</span> {{ e }}
</div>
<label>Server storage</label> {% end %}
<span>100TB</span>
{{ f.clin_0001.label }}
<h6>Estimated Application Storage</h6> {{ f.clin_0001(placeholder="50,000") }}
<label>Expected active users</label> {% for e in f.clin_0001.errors %}
<span>300</span> <div class="usa-input-error-message">
{{ e }}
<label>Expected peak concurrent users</label> </div>
<span>1,000</span> {% end %}
<label>Expected requests per minute</label> {{ f.clin_0003.label }}
<span>1,000</span> {{ f.clin_0003(placeholder="13,000") }}
{% for e in f.clin_0003.errors %}
<label>Number of application environments</label> <div class="usa-input-error-message">
<span>3</span> {{ e }}
</div>
{% end %}
<h3>2. Organizational Info <a href="" class="usa-button usa-button-secondary">Edit</a></h3>
{{ f.clin_1001.label }}
<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> {{ f.clin_1001(placeholder="30,000") }}
<h4>Information About Requester</h4> {% for e in f.clin_1001.errors %}
<div class="usa-input-error-message">
{{ e }}
<label>Name</label> </div>
<span>Friedrich Straat</span> {% end %}
<label>Email</label> {{ f.clin_1003.label }}
<span>fstraat@mail.gov</span> {{ f.clin_1003(placeholder="7,000") }}
{% for e in f.clin_1003.errors %}
<label>Phone Number</label> <div class="usa-input-error-message">
<span>(123) 456-7890</span> {{ e }}
</div>
<label>Office Symbol / Department</label> {% end %}
<span>Army</span>
{{ f.clin_2001.label }}
{{ f.clin_2001(placeholder="30,000") }}
<label>Citizenship</label> {% for e in f.clin_2001.errors %}
<span>United States</span> <div class="usa-input-error-message">
{{ e }}
</div>
<label>Designation of Person</label> {% end %}
<span>Military</span>
{{ f.clin_2003.label }}
{{ f.clin_2003(placeholder="7,000") }}
<label>Latest IA Training completion date</label> {% for e in f.clin_2003.errors %}
<span>2018-04-12</span> <div class="usa-input-error-message">
{{ e }}
</div>
<h4>Information About Collaborators</h4> {% end %}
<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>
{% 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(): def test_form_has_expected_fields():
label = form.application_name.label label = form.dollar_value.label
assert label.text == "Application name" assert "estimated dollar value" in label.text
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)