diff --git a/Pipfile.lock b/Pipfile.lock index 7f424577..fd454f6f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -148,11 +148,11 @@ }, "black": { "hashes": [ - "sha256:22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44", - "sha256:4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191" + "sha256:479cc8b3455a75b5b289276b5f47fd2bb412f2f369292989c12b891264758b5c", + "sha256:fe3b7ac846f2a7c91d926782184826c57d2be283c57f0d6b37b85496eb5469ff" ], "index": "pypi", - "version": "==18.6b4" + "version": "==18.6b3" }, "click": { "hashes": [ diff --git a/atst/api_client.py b/atst/api_client.py index 7a8926b0..c41e0682 100644 --- a/atst/api_client.py +++ b/atst/api_client.py @@ -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 diff --git a/atst/forms/date.py b/atst/forms/date.py deleted file mode 100644 index 01d599f9..00000000 --- a/atst/forms/date.py +++ /dev/null @@ -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()]) diff --git a/atst/forms/fields.py b/atst/forms/fields.py new file mode 100644 index 00000000..3e9de703 --- /dev/null +++ b/atst/forms/fields.py @@ -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 = [] diff --git a/atst/forms/financial.py b/atst/forms/financial.py new file mode 100644 index 00000000..34b7a8fc --- /dev/null +++ b/atst/forms/financial.py @@ -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()], + ) diff --git a/atst/forms/funding.py b/atst/forms/funding.py deleted file mode 100644 index 104ff7cf..00000000 --- a/atst/forms/funding.py +++ /dev/null @@ -1,5 +0,0 @@ -from wtforms_tornado import Form - - -class FundingForm(Form): - pass diff --git a/atst/forms/org.py b/atst/forms/org.py new file mode 100644 index 00000000..98571176 --- /dev/null +++ b/atst/forms/org.py @@ -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()], + ) diff --git a/atst/forms/organization_info.py b/atst/forms/organization_info.py deleted file mode 100644 index 37e9ac99..00000000 --- a/atst/forms/organization_info.py +++ /dev/null @@ -1,5 +0,0 @@ -from wtforms_tornado import Form - - -class OrganizationInfoForm(Form): - pass diff --git a/atst/forms/poc.py b/atst/forms/poc.py new file mode 100644 index 00000000..4f2fe1cb --- /dev/null +++ b/atst/forms/poc.py @@ -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()] + ) diff --git a/atst/forms/readiness.py b/atst/forms/readiness.py deleted file mode 100644 index e12a61be..00000000 --- a/atst/forms/readiness.py +++ /dev/null @@ -1,5 +0,0 @@ -from wtforms_tornado import Form - - -class ReadinessForm(Form): - pass diff --git a/atst/forms/request.py b/atst/forms/request.py index d50b40ba..bbc1109a 100644 --- a/atst/forms/request.py +++ b/atst/forms/request.py @@ -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()], + ) diff --git a/atst/forms/review.py b/atst/forms/review.py index 6b183dac..b3cd2a21 100644 --- a/atst/forms/review.py +++ b/atst/forms/review.py @@ -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.") diff --git a/atst/forms/validators.py b/atst/forms/validators.py new file mode 100644 index 00000000..a7a047de --- /dev/null +++ b/atst/forms/validators.py @@ -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 diff --git a/atst/handlers/request_new.py b/atst/handlers/request_new.py index 9fbe6bda..fbf51f98 100644 --- a/atst/handlers/request_new.py +++ b/atst/handlers/request_new.py @@ -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( diff --git a/scss/base/_forms.scss b/scss/base/_forms.scss index 31f2f705..92f3acae 100644 --- a/scss/base/_forms.scss +++ b/scss/base/_forms.scss @@ -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; + } +} \ No newline at end of file diff --git a/scss/base/_typography.scss b/scss/base/_typography.scss index 943bd060..cdcd17e9 100644 --- a/scss/base/_typography.scss +++ b/scss/base/_typography.scss @@ -13,6 +13,6 @@ h1 { margin-top: 0.5em; } -label { - font-style: italic; +h2 { + margin-top: 0; } \ No newline at end of file diff --git a/templates/requests/screen-1.html.to b/templates/requests/screen-1.html.to index 5c9149fc..4d16ee9c 100644 --- a/templates/requests/screen-1.html.to +++ b/templates/requests/screen-1.html.to @@ -5,112 +5,141 @@ {% autoescape None %} {% if f.errors %} -There were some errors, see below. + There were some errors, see below. {% end %} -

Application Details

+ - - - +

Overall Request Details

+

Please help us understand the size and scope of your resource request.

+ +{{ 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 %} +
+ {{ e }} +
+{% end %} + +{{ f.num_applications.label }} +{{ f.num_applications(placeholder="Estimated number of applications") }} +{% for e in f.num_applications.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.date_start.label }} +{{ f.date_start }} +{% for e in f.date_start.errors %} +
+ {{ e }} +
+{% 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 %} +
+ {{ e }} +
+{% 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 %} +
+ {{ e }} +
+{% end %} + +

Cloud Resources

+

Please tell us about your expected cloud resource usage as best as you can. Please refer to the Cloud Service Provider as necessary.

+ +{{ f.total_cores.label }} +{{ f.total_cores(placeholder="Expected total cores", min="1", max="32") }} +{% for e in f.total_cores.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.total_ram.label }} +{{ f.total_ram(placeholder="Expected amount of RAM", min="1", max="32") }} +{% for e in f.total_ram.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.total_object_storage.label }} +{{ f.total_object_storage(placeholder="Expected total object storage") }} +{% for e in f.total_object_storage.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.total_database_storage.label }} +{{ f.total_database_storage(placeholder="Expected total database storage") }} +{% for e in f.total_database_storage.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.total_server_storage.label }} +{{ f.total_server_storage(placeholder="Expected total server storage") }} +{% for e in f.total_server_storage.errors %} +
+ {{ e }} +
+{% 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 }} - - NEW -
-
- -
-
- {{ f.date_start.month.label }} - {{ f.date_start.month(min="1", max="12") }} -
-
- {{ f.date_start.day.label }} - {{ f.date_start.day(min="1", max="31") }} -
-
- {{ f.date_start.year.label }} - {{ f.date_start.year(min="2000", max="2040") }} -
-
-
- - {{ f.period_of_performance.label }} - {{ f.period_of_performance }} - -
- -
- {{ f.classification_level.label }} - {{ f.classification_level(class_="usa-unstyled-list") }} -
- - {{ f.primary_service_branch.label }} - {{ f.primary_service_branch(placeholder="Add tags associated with service branches") }} - -
- -
- {{ f.cloud_model.label }} - {{ f.cloud_model(class_="usa-unstyled-list") }} -
+

Support Staff

+

We want to learn more about the people helping you with the cloud.

- - -

Computation

-

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.

- - {{ f.number_of_cores.label }} - {{ f.number_of_cores(placeholder="Total cores", min="1", max="32") }} - - - {{ f.total_ram.label }} - {{ f.total_ram(placeholder="Total RAM", min="1", max="32") }} - - {% for e in f.total_ram.errors %} +
+ {{ f.has_contractor_advisor.label }} + {{ f.has_contractor_advisor(class_="usa-unstyled-list") }} + {% for e in f.has_contractor_advisor.errors %}
{{ e }}
{% end %} +
+
+ {{ f.is_migrating_application.label }} + {{ f.is_migrating_application(class_="usa-unstyled-list") }} + {% for e in f.is_migrating_application.errors %} +
+ {{ e }} +
+ {% end %} +
- -

Storage

-

The particulars of your body copy will be determined by the topic of your page. Regardless of topic, it’s a good practice to follow the inverted pyramid structure when writing copy: Begin with the information that’s most important to your users and then present information of less importance.

+
+ {{ f.has_migration_office.label }} + {{ f.has_migration_office(class_="usa-unstyled-list") }} + {% for e in f.has_migration_office.errors %} +
+ {{ e }} +
+ {% end %} +
- {{ f.object_storage.label }} - {{ 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 %} +
+ {{ e }} +
+ {% end %} +
- {{ f.server_storage.label }} - {{ f.server_storage(placeholder="Total server storage") }} - - -

Estimated Application Storage

-

The particulars of your body copy will be determined by the topic of your page. Regardless of topic, it’s a good practice to follow the inverted pyramid structure when writing copy: Begin with the information that’s most important to your users and then present information of less importance.

- - {{ 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 %} diff --git a/templates/requests/screen-2.html.to b/templates/requests/screen-2.html.to index 1a4451bf..30f789bc 100644 --- a/templates/requests/screen-2.html.to +++ b/templates/requests/screen-2.html.to @@ -2,90 +2,79 @@ {% block form %} - -

Organizational Info

-

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.

- -

Information About You

- - - - - - - - - - - - - -
- - -
- -
- - -
- - - -
- - -
-
- - -
-
- - -
-
- - -
-
-
- - -

Information About Your Collaborators

-

Please designate a Contracting Officer that will help you complete this process.

- - - - - - - +{% autoescape None %} +{% if f.errors %} + There were some errors, see below. {% end %} + +

Information About You

+

Please tell us more about yourself.

+ +{{ f.fname_request.label }} +{{ f.fname_request(placeholder="Your first name") }} +{% for e in f.fname_request.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.lname_request.label }} +{{ f.lname_request(placeholder="Your last name") }} +{% for e in f.lname_request.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.email_request.label }} +{{ f.email_request(placeholder="jane@mail.mil") }} +{% for e in f.email_request.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.phone_number.label }} +{{ f.phone_number(placeholder="(123) 456-7890") }} +{% for e in f.phone_number.errors %} +
+ {{ e }} +
+{% 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 %} +
+ {{ e }} +
+{% end %} + +
+ {{ f.citizenship.label }} + {{ f.citizenship(class_="usa-unstyled-list") }} +{% for e in f.citizenship.errors %} +
+ {{ e }} +
+{% end %} +
+ +{{ f.designation.label }} +{{ f.designation }} +{% for e in f.designation.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.date_latest_training.label }} +{{ f.date_latest_training }} +{% for e in f.date_latest_training.errors %} +
+ {{ e }} +
+{% end %} + +{% end %} diff --git a/templates/requests/screen-3.html.to b/templates/requests/screen-3.html.to index aed60b90..c8ecdb99 100644 --- a/templates/requests/screen-3.html.to +++ b/templates/requests/screen-3.html.to @@ -1,7 +1,55 @@ {% extends '../requests_new.html.to' %} {% block form %} -

Funding/Contracting

- + +{% autoescape None %} +{% if f.errors %} + There were some errors, see below. {% end %} +

Primary Government/Military
Point of Contact (POC)

+

Please designate a Primary Point of Contact that will be responsible for owning the workspace in the JEDI Cloud.

+

The Point of Contact will become the primary owner of the workspace created to use the JEDI Cloud. As a workspace owner, this person will have the ability to: +

+ This POC may be you. +

+ +{{ f.fname_poc.label }} +{{ f.fname_poc(placeholder="First name") }} +{% for e in f.fname_poc.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.lname_poc.label }} +{{ f.lname_poc(placeholder="Last name") }} +{% for e in f.lname_poc.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.email_poc.label }} +{{ f.email_poc(placeholder="jane@mail.mil") }} +{% for e in f.email_poc.errors %} +
+ {{ e }} +
+{% 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 %} +
+ {{ e }} +
+{% end %} + +{% end %} diff --git a/templates/requests/screen-4.html.to b/templates/requests/screen-4.html.to index 1e864aee..97b89859 100644 --- a/templates/requests/screen-4.html.to +++ b/templates/requests/screen-4.html.to @@ -1,7 +1,123 @@ {% extends '../requests_new.html.to' %} {% block form %} -

Readiness Survey

- + +{% autoescape None %} +{% if f.errors %} +There were some errors, see below. {% end %} +

Review & Submit

+

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?

+ +

Details of Use Edit

+ +

Overall Request Details

+ + +{{ data.get('details_of_use', {}).get('dollar_value') }} + + +{{ data.get('details_of_use', {}).get('num_applications') }} + + +{{ data.get('details_of_use', {}).get('date_start') }} + + +{{ data.get('details_of_use', {}).get('app_description') }} + + +{{ data.get('details_of_use', {}).get('supported_organizations') }} + + +{{ data.get('details_of_use', {}).get('uii_ids') }} + + +{{ data.get('details_of_use', {}).get('pe_id') }} + +

Cloud Resources

+ + +{{ data.get('details_of_use', {}).get('total_cores') }} + + +{{ data.get('details_of_use', {}).get('total_ram') }} + + +{{ data.get('details_of_use', {}).get('total_object_storage') }} + + +{{ data.get('details_of_use', {}).get('total_server_storage') }} + +

Support Staff

+ + +{{ data.get('details_of_use', {}).get('has_contractor_advisor') }} + + +{{ data.get('details_of_use', {}).get('is_migrating_application') }} + + +{{ data.get('details_of_use', {}).get('supporting_organization') }} + + +{{ data.get('details_of_use', {}).get('has_migration_office') }} + + +{{ data.get('details_of_use', {}).get('supporting_organization') }} + +


+ +

Information About You Edit

+ + +{{ data.get('information_about_you', {}).get('fname_request') }} + + +{{ data.get('information_about_you', {}).get('lname_request') }} + + +{{ data.get('information_about_you', {}).get('email_request') }} + + +{{ data.get('information_about_you', {}).get('phone_number') }} + + +{{ data.get('information_about_you', {}).get('service_branch') }} + + +{{ data.get('information_about_you', {}).get('citizenship') }} + + +{{ data.get('information_about_you', {}).get('designation') }} + + +{{ data.get('information_about_you', {}).get('date_latest_training') }} + + +


+ +

Primary Government/Military Point of Contact (POC) Edit

+ + + + +{{ data.get('primary_poc', {}).get('fname_poc')}} + + +{{ data.get('primary_poc', {}).get('lname_poc')}} + + +{{ data.get('primary_poc', {}).get('email_poc')}} + + +{{ data.get('primary_poc', {}).get('dodid_poc')}} + + +

+ +{% end %} + +{% block next %} + +{% end %} diff --git a/templates/requests/screen-5.html.to b/templates/requests/screen-5.html.to index 8b3f7190..813cac4f 100644 --- a/templates/requests/screen-5.html.to +++ b/templates/requests/screen-5.html.to @@ -1,202 +1,176 @@ {% extends '../requests_new.html.to' %} {% block form %} -

Review & Submit

-

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!

- -

1. Details of Use Edit

- - -

Applications & Environments (2)

- - - - - - - - - - - - - - - - - - - - -
Application NameTotal UsersEstimated Cost
Code.mil235$1,000,000,000
Digital Dojo1,337$10,000
- -
Code.mil
- -
Application Details
- -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. - - -$1,000,000,000 - - -CSP Calculator - - -2020-03-02 - - -Lots of it - - -Secret - - -Army - - -IaaS and PaaS - -
Computation
- -32 - - -128GB - -
Storage
- -10TB - - -100TB - -
Estimated Application Storage
- -300 - - -1,000 - - -1,000 - - -3 - - -
Digital Dojo
- -
Application Details
- -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. - - -$10,000 - - -CSP Calculator - - -2020-03-02 - - -Lots of it - - -Secret - - -Army - - -IaaS and PaaS - -
Computation
- -32 - - -128GB - -
Storage
- -10TB - - -100TB - -
Estimated Application Storage
- -300 - - -1,000 - - -1,000 - - -3 - - -

2. Organizational Info Edit

- -

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.

-

Information About Requester

- - - -Friedrich Straat - - -fstraat@mail.gov - - -(123) 456-7890 - - -Army - - - -United States - - - -Military - - - -2018-04-12 - - -

Information About Collaborators

- - -Pietro Quirinis - - -quirinis@mail.gov - - - -

3. Funding & Contracting Edit

- -

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!

- - - - - - - -

+{% autoescape None %} +{% if f.errors %} + There were some errors, see below. +{% end %} + + +

Financial Verification

+

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.

+ +{{ f.task_order_id.label }} +{{ f.task_order_id(placeholder="Example: 1234567899C0001") }} +{% for e in f.task_order_id.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.uii_ids.label }} +{{ f.uii_ids(placeholder="Example: \nDI 0CVA5786950 \nUN1945326361234786950") }} +{% for e in f.uii_ids.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.pe_id.label }} +{{ f.pe_id(placeholder="Example: 0203752A") }} +{% for e in f.pe_id.errors %} +
+ {{ e }} +
+{% end %} + + +

Contracting Officer (KO) Information

+ +{{ f.fname_co.label }} +{{ f.fname_co(placeholder="Contracting Officer first name") }} +{% for e in f.fname_co.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.lname_co.label }} +{{ f.lname_co(placeholder="Contracting Officer last name") }} +{% for e in f.lname_co.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.email_co.label }} +{{ f.email_co(placeholder="jane@mail.mil") }} +{% for e in f.email_co.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.office_co.label }} +{{ f.office_co(placeholder="Example: WHS") }} +{% for e in f.office_co.errors %} +
+ {{ e }} +
+{% end %} + +

Contracting Officer Representative (COR) Information

+ +{{ f.fname_cor.label }} +{{ f.fname_cor(placeholder="Contracting Officer Representative first name") }} +{% for e in f.fname_cor.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.lname_cor.label }} +{{ f.lname_cor(placeholder="Contracting Officer Representative last name") }} +{% for e in f.lname_cor.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.email_cor.label }} +{{ f.email_cor(placeholder="jane@mail.mil") }} +{% for e in f.email_cor.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.office_cor.label }} +{{ f.office_cor(placeholder="Example: WHS") }} +{% for e in f.office_cor.errors %} +
+ {{ e }} +
+{% end %} + +

+↓ FIELDS NEEDED FOR MANUAL ENTRY OF TASK ORDER INFORMATION (only necessary if EDA info not available) + + +{{ f.funding_type.label }} +{{ f.funding_type }} +{% for e in f.funding_type.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.funding_type_other.label }} +{{ f.funding_type_other(placeholder="") }} +{% for e in f.funding_type_other.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.clin_0001.label }} +{{ f.clin_0001(placeholder="50,000") }} +{% for e in f.clin_0001.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.clin_0003.label }} +{{ f.clin_0003(placeholder="13,000") }} +{% for e in f.clin_0003.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.clin_1001.label }} +{{ f.clin_1001(placeholder="30,000") }} +{% for e in f.clin_1001.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.clin_1003.label }} +{{ f.clin_1003(placeholder="7,000") }} +{% for e in f.clin_1003.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.clin_2001.label }} +{{ f.clin_2001(placeholder="30,000") }} +{% for e in f.clin_2001.errors %} +
+ {{ e }} +
+{% end %} + +{{ f.clin_2003.label }} +{{ f.clin_2003(placeholder="7,000") }} +{% for e in f.clin_2003.errors %} +
+ {{ e }} +
+{% end %} {% end %} - -{% block next %} -Submit -{% end %} diff --git a/tests/forms/test_request.py b/tests/forms/test_request.py index 381dd810..2b51a38a 100644 --- a/tests/forms/test_request.py +++ b/tests/forms/test_request.py @@ -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