From bc42cca71a3e1edec8dfde6e7b01c77a84e637f3 Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Mon, 13 Aug 2018 21:25:31 -0400
Subject: [PATCH 01/18] Simplify requests_update
---
atst/routes/requests/requests_form.py | 43 ++++++++++++---------------
1 file changed, 19 insertions(+), 24 deletions(-)
diff --git a/atst/routes/requests/requests_form.py b/atst/routes/requests/requests_form.py
index c027890c..4839b059 100644
--- a/atst/routes/requests/requests_form.py
+++ b/atst/routes/requests/requests_form.py
@@ -64,35 +64,30 @@ def requests_update(screen=1, request_id=None):
existing_request=existing_request,
)
- rerender_args = dict(
- f=jedi_flow.form,
- data=post_data,
- screens=jedi_flow.screens,
- current=screen,
- next_screen=jedi_flow.next_screen,
- request_id=jedi_flow.request_id,
- )
+ has_next_screen = jedi_flow.next_screen <= len(jedi_flow.screens)
+ valid = jedi_flow.validate() and jedi_flow.validate_warnings()
- if jedi_flow.validate():
+ if valid:
jedi_flow.create_or_update_request()
- valid = jedi_flow.validate_warnings()
- if valid:
- if jedi_flow.next_screen > len(jedi_flow.screens):
- where = "/requests"
- else:
- where = url_for(
- "requests.requests_form_update",
- screen=jedi_flow.next_screen,
- request_id=jedi_flow.request_id,
- )
- return redirect(where)
- else:
- return render_template(
- "requests/screen-%d.html" % int(screen), **rerender_args
+ if has_next_screen:
+ where = url_for(
+ "requests.requests_form_update",
+ screen=jedi_flow.next_screen,
+ request_id=jedi_flow.request_id,
)
-
+ else:
+ where = "/requests"
+ return redirect(where)
else:
+ rerender_args = dict(
+ f=jedi_flow.form,
+ data=post_data,
+ screens=jedi_flow.screens,
+ current=screen,
+ next_screen=jedi_flow.next_screen,
+ request_id=jedi_flow.request_id,
+ )
return render_template("requests/screen-%d.html" % int(screen), **rerender_args)
From 9de9fb5c6a50675ab994711bd09f1abd1ef3f58e Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Mon, 13 Aug 2018 21:34:22 -0400
Subject: [PATCH 02/18] Remove unused fields from JEDIRequestFlow screens
---
atst/routes/requests/jedi_request_flow.py | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/atst/routes/requests/jedi_request_flow.py b/atst/routes/requests/jedi_request_flow.py
index 7c96d7d5..d69f1928 100644
--- a/atst/routes/requests/jedi_request_flow.py
+++ b/atst/routes/requests/jedi_request_flow.py
@@ -103,33 +103,21 @@ class JEDIRequestFlow(object):
"title": "Details of Use",
"section": "details_of_use",
"form": RequestForm,
- "subitems": [
- {
- "title": "Overall request details",
- "id": "overall-request-details",
- },
- {"title": "Cloud Resources", "id": "cloud-resources"},
- {"title": "Support Staff", "id": "support-staff"},
- ],
- "show": True,
},
{
"title": "Information About You",
"section": "information_about_you",
"form": OrgForm,
- "show": True,
},
{
"title": "Workspace Owner",
"section": "primary_poc",
"form": POCForm,
- "show": True,
},
{
"title": "Review & Submit",
"section": "review_submit",
"form": ReviewForm,
- "show": True,
},
]
From 5a2953ffc37fa63379cb556ed1477e75557577ee Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Tue, 14 Aug 2018 11:41:37 -0400
Subject: [PATCH 03/18] Split request update into new method for easier
extension
---
atst/routes/requests/jedi_request_flow.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/atst/routes/requests/jedi_request_flow.py b/atst/routes/requests/jedi_request_flow.py
index d69f1928..79506916 100644
--- a/atst/routes/requests/jedi_request_flow.py
+++ b/atst/routes/requests/jedi_request_flow.py
@@ -122,9 +122,13 @@ class JEDIRequestFlow(object):
]
def create_or_update_request(self):
- request_data = {self.form_section: self.form.data}
if self.request_id:
- Requests.update(self.request_id, request_data)
+ self.update_request(self.form_section, self.form.data)
else:
+ request_data = {self.form_section: self.form.data}
request = Requests.create(self.current_user, request_data)
self.request_id = request.id
+
+ def update_request(self, section, data):
+ request_data = {section: data}
+ Requests.update(self.request_id, request_data)
From c6618c503b9a2320ccb101e7948e7d3646fe7eca Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Tue, 14 Aug 2018 12:54:48 -0400
Subject: [PATCH 04/18] Create new template and form field
---
atst/forms/poc.py | 28 ++++++++++++---
atst/routes/requests/jedi_request_flow.py | 9 +++++
js/components/forms/poc.js | 44 +++++++++++++++++++++++
js/index.js | 2 ++
templates/requests/screen-3.html | 36 ++++++++++++-------
tests/factories.py | 1 +
tests/routes/test_request_new.py | 40 +++++++++++++++++++++
7 files changed, 142 insertions(+), 18 deletions(-)
create mode 100644 js/components/forms/poc.js
diff --git a/atst/forms/poc.py b/atst/forms/poc.py
index 3dcdc223..02ef76ab 100644
--- a/atst/forms/poc.py
+++ b/atst/forms/poc.py
@@ -1,16 +1,34 @@
-from wtforms.fields import StringField
+from wtforms.fields import StringField, RadioField
from wtforms.fields.html5 import EmailField
-from wtforms.validators import Required, Email, Length
+from wtforms.validators import Required, Email, Length, Optional
from .forms import ValidatedForm
from .validators import IsNumber
class POCForm(ValidatedForm):
- fname_poc = StringField("First Name", validators=[Required()])
- lname_poc = StringField("Last Name", validators=[Required()])
+ def validate(self, *args, **kwargs):
+ if self.am_poc.data == "yes":
+ self.fname_poc.validators.insert(0, Optional())
+ self.lname_poc.validators.insert(0, Optional())
+ self.email_poc.validators.insert(0, Optional())
+ self.dodid_poc.validators.insert(0, Optional())
- email_poc = EmailField("Email Address", validators=[Required(), Email()])
+ return super(POCForm, self).validate(*args, **kwargs)
+
+
+ am_poc = RadioField(
+ "I am the technical POC.",
+ choices=[("yes", "Yes"), ("no", "No")],
+ default="no",
+ validators=[Required()],
+ )
+
+ fname_poc = StringField("POC First Name", validators=[Required()])
+
+ lname_poc = StringField("POC Last Name", validators=[Required()])
+
+ email_poc = EmailField("POC Email Address", validators=[Required(), Email()])
dodid_poc = StringField(
"DOD ID", validators=[Required(), Length(min=10), IsNumber()]
diff --git a/atst/routes/requests/jedi_request_flow.py b/atst/routes/requests/jedi_request_flow.py
index 79506916..ce9b3d01 100644
--- a/atst/routes/requests/jedi_request_flow.py
+++ b/atst/routes/requests/jedi_request_flow.py
@@ -130,5 +130,14 @@ class JEDIRequestFlow(object):
self.request_id = request.id
def update_request(self, section, data):
+ if section == "primary_poc":
+ if data.get("am_poc") == "yes":
+ data = {
+ "dodid_poc": self.existing_request.creator.dod_id,
+ "fname_poc": self.existing_request.creator.first_name,
+ "lname_poc": self.existing_request.creator.last_name,
+ "email_poc": self.existing_request.creator.email
+ }
+
request_data = {section: data}
Requests.update(self.request_id, request_data)
diff --git a/js/components/forms/poc.js b/js/components/forms/poc.js
new file mode 100644
index 00000000..0be9fb1f
--- /dev/null
+++ b/js/components/forms/poc.js
@@ -0,0 +1,44 @@
+import optionsinput from '../options_input'
+import textinput from '../text_input'
+
+export default {
+ name: 'poc',
+
+ components: {
+ optionsinput,
+ textinput,
+ },
+
+ props: {
+ initialData: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+
+ data: function () {
+ return {
+ am_poc: "no"
+ }
+ },
+
+ mounted: function () {
+ this.$root.$on('field-change', this.handleFieldChange)
+ },
+
+ computed: {
+ amPOC: function () {
+ return this.am_poc === 'yes'
+ },
+ },
+
+ methods: {
+ handleFieldChange: function (event) {
+ const { value, name } = event
+ console.log(value, name)
+ if (typeof this[name] !== undefined) {
+ this[name] = value
+ }
+ },
+ }
+}
diff --git a/js/index.js b/js/index.js
index 5cf47a60..84b90c96 100644
--- a/js/index.js
+++ b/js/index.js
@@ -5,6 +5,7 @@ import VTooltip from 'v-tooltip'
import optionsinput from './components/options_input'
import textinput from './components/text_input'
import DetailsOfUse from './components/forms/details_of_use'
+import poc from './components/forms/poc'
Vue.use(VTooltip)
@@ -15,6 +16,7 @@ const app = new Vue({
optionsinput,
textinput,
DetailsOfUse,
+ poc,
},
methods: {
closeModal: function(name) {
diff --git a/templates/requests/screen-3.html b/templates/requests/screen-3.html
index 2f03c8bf..41c632dc 100644
--- a/templates/requests/screen-3.html
+++ b/templates/requests/screen-3.html
@@ -2,6 +2,7 @@
{% from "components/alert.html" import Alert %}
{% from "components/text_input.html" import TextInput %}
+{% from "components/options_input.html" import OptionsInput %}
{% block subtitle %}
Designate a Workspace Owner
@@ -16,20 +17,29 @@
) }}
{% endif %}
-The Workspace Owner is the primary point of contact and technical administrator of the JEDI Workspace and will have the following responsibilities:
-
- - Organize your cloud-hosted systems into projects and environments
- - Add users to this workspace and manage members
- - Manage access to the JEDI Cloud service provider’s portal
-
-
+
+
+
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:
+
+ - Create multiple application stacks and environments in the workspace to access the commercial cloud service provider portal
+ - Add and manage users in the workspace
+ - View the budget and billing history related to this workspace
+ - Manage access to the Cloud Service Provider's Console
+ - Transfer Workspace ownership to another person
+
+
This POC may be you.
+
-
This person must be a DoD employee (not a contractor).
-
The Workspace Owner may be you. You will be able to add other administrators later. This person will be invited via email once your request is approved.
+ {{ OptionsInput(f.am_poc) }}
-{{ TextInput(f.fname_poc,placeholder='First Name') }}
-{{ TextInput(f.lname_poc,placeholder='Last Name') }}
-{{ TextInput(f.email_poc,placeholder='jane@mail.mil', validation='email') }}
-{{ TextInput(f.dodid_poc,placeholder='10-digit number on the back of the CAC', validation='dodId') }}
+
+ {{ TextInput(f.fname_poc,placeholder='First Name') }}
+ {{ TextInput(f.lname_poc,placeholder='Last Name') }}
+ {{ TextInput(f.email_poc,placeholder='jane@mail.mil', validation='email') }}
+ {{ TextInput(f.dodid_poc,placeholder='10-digit number on the back of the CAC', validation='dodId') }}
+
+
+
{% endblock %}
diff --git a/tests/factories.py b/tests/factories.py
index 74124f95..41996d47 100644
--- a/tests/factories.py
+++ b/tests/factories.py
@@ -56,6 +56,7 @@ class RequestFactory(factory.alchemy.SQLAlchemyModelFactory):
def build_request_body(cls, user, dollar_value=1000000):
return {
"primary_poc": {
+ "am_poc": "no",
"dodid_poc": user.dod_id,
"email_poc": user.email,
"fname_poc": user.first_name,
diff --git a/tests/routes/test_request_new.py b/tests/routes/test_request_new.py
index fadf0ce9..217882c4 100644
--- a/tests/routes/test_request_new.py
+++ b/tests/routes/test_request_new.py
@@ -4,6 +4,7 @@ import urllib
from tests.mocks import MOCK_USER, MOCK_REQUEST
from tests.factories import RequestFactory, UserFactory
from atst.domain.roles import Roles
+from atst.domain.requests import Requests
ERROR_CLASS = "alert--error"
@@ -103,6 +104,45 @@ def test_non_creator_info_is_not_autopopulated(monkeypatch, client, user_session
assert not user.last_name in body
assert not user.email in body
+def test_am_poc_causes_poc_to_be_autopopulated(client, user_session):
+ creator = UserFactory.create()
+ user_session(creator)
+ request = RequestFactory.create(creator=creator, body={})
+ client.post(
+ "/requests/new/3/{}".format(request.id),
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
+ data="am_poc=yes",
+ )
+ request = Requests.get(request.id)
+ assert request.body["primary_poc"]["dodid_poc"] == creator.dod_id
+
+
+def test_not_am_poc_requires_poc_info_to_be_completed(client, user_session):
+ creator = UserFactory.create()
+ user_session(creator)
+ request = RequestFactory.create(creator=creator, body={})
+ response = client.post(
+ "/requests/new/3/{}".format(request.id),
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
+ data="am_poc=no",
+ )
+ assert ERROR_CLASS in response.data.decode()
+
+
+# def test_not_am_poc_allows_user_to_fill_in_poc_info(client, user_session):
+# creator = UserFactory.create()
+# user_session(creator)
+# request = RequestFactory.create(creator=creator, body={})
+# client.post(
+# "/requests/new/3/{}".format(request.id),
+# headers={"Content-Type": "application/x-www-form-urlencoded"},
+# data="am_poc=yes",
+# )
+
+# assert "Location" not in response.headers
+# request = Requests.get(request.id)
+
+
def test_can_review_data(user_session, client):
creator = UserFactory.create()
user_session(creator)
From ee207f163c23f74eaa7a28da2c33f886eb773cf7 Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Tue, 14 Aug 2018 15:29:55 -0400
Subject: [PATCH 05/18] Fix tests
---
atst/forms/poc.py | 2 ++
tests/routes/test_request_new.py | 23 +++++++++++------------
2 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/atst/forms/poc.py b/atst/forms/poc.py
index 02ef76ab..d55fce7b 100644
--- a/atst/forms/poc.py
+++ b/atst/forms/poc.py
@@ -9,6 +9,8 @@ class POCForm(ValidatedForm):
def validate(self, *args, **kwargs):
if self.am_poc.data == "yes":
+ # Prepend Optional validators so that the validation chain
+ # halts if no data exists.
self.fname_poc.validators.insert(0, Optional())
self.lname_poc.validators.insert(0, Optional())
self.email_poc.validators.insert(0, Optional())
diff --git a/tests/routes/test_request_new.py b/tests/routes/test_request_new.py
index 217882c4..3efc4360 100644
--- a/tests/routes/test_request_new.py
+++ b/tests/routes/test_request_new.py
@@ -125,22 +125,21 @@ def test_not_am_poc_requires_poc_info_to_be_completed(client, user_session):
"/requests/new/3/{}".format(request.id),
headers={"Content-Type": "application/x-www-form-urlencoded"},
data="am_poc=no",
+ follow_redirects=True
)
assert ERROR_CLASS in response.data.decode()
-# def test_not_am_poc_allows_user_to_fill_in_poc_info(client, user_session):
-# creator = UserFactory.create()
-# user_session(creator)
-# request = RequestFactory.create(creator=creator, body={})
-# client.post(
-# "/requests/new/3/{}".format(request.id),
-# headers={"Content-Type": "application/x-www-form-urlencoded"},
-# data="am_poc=yes",
-# )
-
-# assert "Location" not in response.headers
-# request = Requests.get(request.id)
+def test_not_am_poc_allows_user_to_fill_in_poc_info(client, user_session):
+ creator = UserFactory.create()
+ user_session(creator)
+ request = RequestFactory.create(creator=creator, body={})
+ response = client.post(
+ "/requests/new/3/{}".format(request.id),
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
+ data="am_poc=no&fname_poc=test&lname_poc=user&email_poc=test.user@mail.com&dodid_poc=1234567890",
+ )
+ assert ERROR_CLASS not in response.data.decode()
def test_can_review_data(user_session, client):
From d5d1265cd740c6d2d836378cdf508689b6ddc57d Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Tue, 14 Aug 2018 15:32:30 -0400
Subject: [PATCH 06/18] Fix linting errors
---
tests/routes/test_request_new.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/tests/routes/test_request_new.py b/tests/routes/test_request_new.py
index 3efc4360..e7a58da1 100644
--- a/tests/routes/test_request_new.py
+++ b/tests/routes/test_request_new.py
@@ -1,7 +1,4 @@
import re
-import pytest
-import urllib
-from tests.mocks import MOCK_USER, MOCK_REQUEST
from tests.factories import RequestFactory, UserFactory
from atst.domain.roles import Roles
from atst.domain.requests import Requests
From c639a82b82e8e71c18d3605074e4a974fe151ecc Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Tue, 14 Aug 2018 15:57:38 -0400
Subject: [PATCH 07/18] Hide fields on page load if necessary
---
js/components/forms/poc.js | 7 +++++--
templates/requests/screen-3.html | 2 +-
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/js/components/forms/poc.js b/js/components/forms/poc.js
index 0be9fb1f..da9b9c59 100644
--- a/js/components/forms/poc.js
+++ b/js/components/forms/poc.js
@@ -17,8 +17,12 @@ export default {
},
data: function () {
+ const {
+ am_poc = 'no'
+ } = this.initialData
+
return {
- am_poc: "no"
+ am_poc
}
},
@@ -35,7 +39,6 @@ export default {
methods: {
handleFieldChange: function (event) {
const { value, name } = event
- console.log(value, name)
if (typeof this[name] !== undefined) {
this[name] = value
}
diff --git a/templates/requests/screen-3.html b/templates/requests/screen-3.html
index 41c632dc..f54460e2 100644
--- a/templates/requests/screen-3.html
+++ b/templates/requests/screen-3.html
@@ -33,7 +33,7 @@
{{ OptionsInput(f.am_poc) }}
-
+
{{ TextInput(f.fname_poc,placeholder='First Name') }}
{{ TextInput(f.lname_poc,placeholder='Last Name') }}
{{ TextInput(f.email_poc,placeholder='jane@mail.mil', validation='email') }}
From 3b50b948a021e802e52c10a680ac6d6da22d425e Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Tue, 14 Aug 2018 15:58:05 -0400
Subject: [PATCH 08/18] Don't overwrite other POC form fields
---
atst/routes/requests/jedi_request_flow.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/atst/routes/requests/jedi_request_flow.py b/atst/routes/requests/jedi_request_flow.py
index ce9b3d01..b9160fb6 100644
--- a/atst/routes/requests/jedi_request_flow.py
+++ b/atst/routes/requests/jedi_request_flow.py
@@ -133,6 +133,7 @@ class JEDIRequestFlow(object):
if section == "primary_poc":
if data.get("am_poc") == "yes":
data = {
+ **data,
"dodid_poc": self.existing_request.creator.dod_id,
"fname_poc": self.existing_request.creator.first_name,
"lname_poc": self.existing_request.creator.last_name,
From ac4cf5279e24c4f484cd319cfc129e56153d79df Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Tue, 14 Aug 2018 15:58:18 -0400
Subject: [PATCH 09/18] Don't include csrf_token in form.data
---
atst/forms/forms.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/atst/forms/forms.py b/atst/forms/forms.py
index 2aaa4973..ce0ff791 100644
--- a/atst/forms/forms.py
+++ b/atst/forms/forms.py
@@ -6,3 +6,9 @@ class ValidatedForm(FlaskForm):
"""Performs any applicable extra validation. Must
return True if the form is valid or False otherwise."""
return True
+
+ @property
+ def data(self):
+ _data = super().data
+ _data.pop("csrf_token", None)
+ return _data
From 93ac342f755bf9d6c38919d59fcffcec2b2de80b Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Tue, 14 Aug 2018 16:30:11 -0400
Subject: [PATCH 10/18] Also autopopulate POC info for new requests
---
atst/routes/requests/jedi_request_flow.py | 33 +++++++++++------------
tests/routes/test_request_new.py | 14 ++++++++++
2 files changed, 30 insertions(+), 17 deletions(-)
diff --git a/atst/routes/requests/jedi_request_flow.py b/atst/routes/requests/jedi_request_flow.py
index b9160fb6..c0802a76 100644
--- a/atst/routes/requests/jedi_request_flow.py
+++ b/atst/routes/requests/jedi_request_flow.py
@@ -65,7 +65,7 @@ class JEDIRequestFlow(object):
return {
"fname_request": user.first_name,
"lname_request": user.last_name,
- "email_request": user.email
+ "email_request": user.email,
}
@property
@@ -80,7 +80,7 @@ class JEDIRequestFlow(object):
data = self.request.body
elif self.form_section == "information_about_you":
form_data = self.request.body.get(self.form_section, {})
- data = { **self.map_user_data(self.request.creator), **form_data }
+ data = {**self.map_user_data(self.request.creator), **form_data}
else:
data = self.request.body.get(self.form_section, {})
elif self.form_section == "information_about_you":
@@ -109,11 +109,7 @@ class JEDIRequestFlow(object):
"section": "information_about_you",
"form": OrgForm,
},
- {
- "title": "Workspace Owner",
- "section": "primary_poc",
- "form": POCForm,
- },
+ {"title": "Workspace Owner", "section": "primary_poc", "form": POCForm},
{
"title": "Review & Submit",
"section": "review_submit",
@@ -122,23 +118,26 @@ class JEDIRequestFlow(object):
]
def create_or_update_request(self):
+ request_data = self.map_request_data(self.form_section, self.form.data)
if self.request_id:
- self.update_request(self.form_section, self.form.data)
+ Requests.update(self.request_id, request_data)
else:
- request_data = {self.form_section: self.form.data}
request = Requests.create(self.current_user, request_data)
self.request_id = request.id
- def update_request(self, section, data):
+ def map_request_data(self, section, data):
+ user = (
+ self.existing_request.creator
+ if self.existing_request
+ else self.current_user
+ )
if section == "primary_poc":
if data.get("am_poc") == "yes":
data = {
**data,
- "dodid_poc": self.existing_request.creator.dod_id,
- "fname_poc": self.existing_request.creator.first_name,
- "lname_poc": self.existing_request.creator.last_name,
- "email_poc": self.existing_request.creator.email
+ "dodid_poc": user.dod_id,
+ "fname_poc": user.first_name,
+ "lname_poc": user.last_name,
+ "email_poc": user.email,
}
-
- request_data = {section: data}
- Requests.update(self.request_id, request_data)
+ return {section: data}
diff --git a/tests/routes/test_request_new.py b/tests/routes/test_request_new.py
index e7a58da1..f7ea2b9e 100644
--- a/tests/routes/test_request_new.py
+++ b/tests/routes/test_request_new.py
@@ -139,6 +139,20 @@ def test_not_am_poc_allows_user_to_fill_in_poc_info(client, user_session):
assert ERROR_CLASS not in response.data.decode()
+def test_not_am_poc_allows_user_to_fill_in_poc_info_for_new_request(client, user_session):
+ creator = UserFactory.create()
+ user_session(creator)
+ response = client.post(
+ "/requests/new/3",
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
+ data="am_poc=yes",
+ )
+ request_id = response.headers["Location"].split('/')[-1]
+ request = Requests.get(request_id)
+
+ assert request.body["primary_poc"]["dodid_poc"] == creator.dod_id
+
+
def test_can_review_data(user_session, client):
creator = UserFactory.create()
user_session(creator)
From 529ec532dc7b983982576b3448f430b1401375c6 Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Tue, 14 Aug 2018 16:35:30 -0400
Subject: [PATCH 11/18] Use python3 version of super()
---
atst/forms/poc.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/atst/forms/poc.py b/atst/forms/poc.py
index d55fce7b..358f9de2 100644
--- a/atst/forms/poc.py
+++ b/atst/forms/poc.py
@@ -16,7 +16,7 @@ class POCForm(ValidatedForm):
self.email_poc.validators.insert(0, Optional())
self.dodid_poc.validators.insert(0, Optional())
- return super(POCForm, self).validate(*args, **kwargs)
+ return super().validate(*args, **kwargs)
am_poc = RadioField(
From 4085c42c1cde60121f31a580eac334315672da1e Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Tue, 14 Aug 2018 17:26:21 -0400
Subject: [PATCH 12/18] WIP
---
atst/forms/poc.py | 7 ++---
templates/components/checkbox_input.html | 37 ++++++++++++++++++++++++
templates/requests/screen-3.html | 3 +-
3 files changed, 42 insertions(+), 5 deletions(-)
create mode 100644 templates/components/checkbox_input.html
diff --git a/atst/forms/poc.py b/atst/forms/poc.py
index 358f9de2..b036cdc2 100644
--- a/atst/forms/poc.py
+++ b/atst/forms/poc.py
@@ -1,4 +1,4 @@
-from wtforms.fields import StringField, RadioField
+from wtforms.fields import StringField, BooleanField
from wtforms.fields.html5 import EmailField
from wtforms.validators import Required, Email, Length, Optional
from .forms import ValidatedForm
@@ -19,11 +19,10 @@ class POCForm(ValidatedForm):
return super().validate(*args, **kwargs)
- am_poc = RadioField(
+ am_poc = BooleanField(
"I am the technical POC.",
- choices=[("yes", "Yes"), ("no", "No")],
- default="no",
validators=[Required()],
+ default=False
)
fname_poc = StringField("POC First Name", validators=[Required()])
diff --git a/templates/components/checkbox_input.html b/templates/components/checkbox_input.html
new file mode 100644
index 00000000..34c9a0da
--- /dev/null
+++ b/templates/components/checkbox_input.html
@@ -0,0 +1,37 @@
+{% from "components/icon.html" import Icon %}
+{% from "components/tooltip.html" import Tooltip %}
+
+{% macro OptionsInput(field, tooltip, inline=False) -%}
+
+
+
+
+
+{%- endmacro %}
diff --git a/templates/requests/screen-3.html b/templates/requests/screen-3.html
index f54460e2..1a469df6 100644
--- a/templates/requests/screen-3.html
+++ b/templates/requests/screen-3.html
@@ -31,7 +31,8 @@
This POC may be you.
- {{ OptionsInput(f.am_poc) }}
+ {{ f.am_poc() }}
+ {{ f.am_poc.label }}
{{ TextInput(f.fname_poc,placeholder='First Name') }}
From 932922cfe6d84e85545a4a808cae971afbc9c353 Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Tue, 14 Aug 2018 20:47:36 -0400
Subject: [PATCH 13/18] Use new checkboxinput for POC checkbox
---
atst/forms/poc.py | 2 +-
atst/routes/requests/jedi_request_flow.py | 2 +-
js/components/checkbox_input.js | 17 +++++++++++++
js/components/forms/poc.js | 10 +++-----
js/index.js | 2 ++
templates/components/checkbox_input.html | 29 ++++-------------------
templates/requests/screen-3.html | 7 +++---
7 files changed, 32 insertions(+), 37 deletions(-)
create mode 100644 js/components/checkbox_input.js
diff --git a/atst/forms/poc.py b/atst/forms/poc.py
index b036cdc2..31bb4c02 100644
--- a/atst/forms/poc.py
+++ b/atst/forms/poc.py
@@ -8,7 +8,7 @@ from .validators import IsNumber
class POCForm(ValidatedForm):
def validate(self, *args, **kwargs):
- if self.am_poc.data == "yes":
+ if self.am_poc.data:
# Prepend Optional validators so that the validation chain
# halts if no data exists.
self.fname_poc.validators.insert(0, Optional())
diff --git a/atst/routes/requests/jedi_request_flow.py b/atst/routes/requests/jedi_request_flow.py
index c0802a76..63910fa4 100644
--- a/atst/routes/requests/jedi_request_flow.py
+++ b/atst/routes/requests/jedi_request_flow.py
@@ -132,7 +132,7 @@ class JEDIRequestFlow(object):
else self.current_user
)
if section == "primary_poc":
- if data.get("am_poc") == "yes":
+ if data.get("am_poc"):
data = {
**data,
"dodid_poc": user.dod_id,
diff --git a/js/components/checkbox_input.js b/js/components/checkbox_input.js
new file mode 100644
index 00000000..73c52d4f
--- /dev/null
+++ b/js/components/checkbox_input.js
@@ -0,0 +1,17 @@
+export default {
+ name: 'checkboxinput',
+
+ props: {
+ name: String,
+ },
+
+ methods: {
+ onInput: function (e) {
+ console.log(e)
+ this.$root.$emit('field-change', {
+ value: e.target.checked,
+ name: this.name
+ })
+ }
+ }
+}
diff --git a/js/components/forms/poc.js b/js/components/forms/poc.js
index da9b9c59..255c1b04 100644
--- a/js/components/forms/poc.js
+++ b/js/components/forms/poc.js
@@ -1,5 +1,6 @@
import optionsinput from '../options_input'
import textinput from '../text_input'
+import checkboxinput from '../checkbox_input'
export default {
name: 'poc',
@@ -7,6 +8,7 @@ export default {
components: {
optionsinput,
textinput,
+ checkboxinput,
},
props: {
@@ -18,7 +20,7 @@ export default {
data: function () {
const {
- am_poc = 'no'
+ am_poc = false
} = this.initialData
return {
@@ -30,12 +32,6 @@ export default {
this.$root.$on('field-change', this.handleFieldChange)
},
- computed: {
- amPOC: function () {
- return this.am_poc === 'yes'
- },
- },
-
methods: {
handleFieldChange: function (event) {
const { value, name } = event
diff --git a/js/index.js b/js/index.js
index 84b90c96..afe6961d 100644
--- a/js/index.js
+++ b/js/index.js
@@ -4,6 +4,7 @@ import VTooltip from 'v-tooltip'
import optionsinput from './components/options_input'
import textinput from './components/text_input'
+import checkboxinput from './components/checkbox_input'
import DetailsOfUse from './components/forms/details_of_use'
import poc from './components/forms/poc'
@@ -15,6 +16,7 @@ const app = new Vue({
components: {
optionsinput,
textinput,
+ checkboxinput,
DetailsOfUse,
poc,
},
diff --git a/templates/components/checkbox_input.html b/templates/components/checkbox_input.html
index 34c9a0da..92538843 100644
--- a/templates/components/checkbox_input.html
+++ b/templates/components/checkbox_input.html
@@ -1,37 +1,18 @@
-{% from "components/icon.html" import Icon %}
-{% from "components/tooltip.html" import Tooltip %}
-{% macro OptionsInput(field, tooltip, inline=False) -%}
-
+{% macro CheckboxInput(field, inline=False) -%}
+
-
-
+
{%- endmacro %}
diff --git a/templates/requests/screen-3.html b/templates/requests/screen-3.html
index 1a469df6..0c86183c 100644
--- a/templates/requests/screen-3.html
+++ b/templates/requests/screen-3.html
@@ -2,7 +2,7 @@
{% from "components/alert.html" import Alert %}
{% from "components/text_input.html" import TextInput %}
-{% from "components/options_input.html" import OptionsInput %}
+{% from "components/checkbox_input.html" import CheckboxInput %}
{% block subtitle %}
Designate a Workspace Owner
@@ -31,10 +31,9 @@
This POC may be you.
- {{ f.am_poc() }}
- {{ f.am_poc.label }}
+ {{ CheckboxInput(f.am_poc) }}
-
+
{{ TextInput(f.fname_poc,placeholder='First Name') }}
{{ TextInput(f.lname_poc,placeholder='Last Name') }}
{{ TextInput(f.email_poc,placeholder='jane@mail.mil', validation='email') }}
From 90ada6992b30bda3045ceb93388b68490edae5b9 Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Tue, 14 Aug 2018 21:21:55 -0400
Subject: [PATCH 14/18] Always set POC to current user if checkbox is checked
---
atst/routes/requests/jedi_request_flow.py | 15 +++++----------
1 file changed, 5 insertions(+), 10 deletions(-)
diff --git a/atst/routes/requests/jedi_request_flow.py b/atst/routes/requests/jedi_request_flow.py
index 63910fa4..0b56743b 100644
--- a/atst/routes/requests/jedi_request_flow.py
+++ b/atst/routes/requests/jedi_request_flow.py
@@ -126,18 +126,13 @@ class JEDIRequestFlow(object):
self.request_id = request.id
def map_request_data(self, section, data):
- user = (
- self.existing_request.creator
- if self.existing_request
- else self.current_user
- )
if section == "primary_poc":
- if data.get("am_poc"):
+ if data.get("am_poc", False):
data = {
**data,
- "dodid_poc": user.dod_id,
- "fname_poc": user.first_name,
- "lname_poc": user.last_name,
- "email_poc": user.email,
+ "dodid_poc": self.current_user.dod_id,
+ "fname_poc": self.current_user.first_name,
+ "lname_poc": self.current_user.last_name,
+ "email_poc": self.current_user.email,
}
return {section: data}
From bcf74b09ebabc474443b018f367d217c34937c05 Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Tue, 14 Aug 2018 21:22:10 -0400
Subject: [PATCH 15/18] Expand BooleanField's default_values for stepthrough
test
---
atst/forms/poc.py | 4 ++--
js/components/checkbox_input.js | 1 -
tests/factories.py | 2 +-
3 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/atst/forms/poc.py b/atst/forms/poc.py
index 31bb4c02..ced1f9e7 100644
--- a/atst/forms/poc.py
+++ b/atst/forms/poc.py
@@ -21,8 +21,8 @@ class POCForm(ValidatedForm):
am_poc = BooleanField(
"I am the technical POC.",
- validators=[Required()],
- default=False
+ default=False,
+ false_values=(False, "false", "False", "no", "")
)
fname_poc = StringField("POC First Name", validators=[Required()])
diff --git a/js/components/checkbox_input.js b/js/components/checkbox_input.js
index 73c52d4f..6ed5e821 100644
--- a/js/components/checkbox_input.js
+++ b/js/components/checkbox_input.js
@@ -7,7 +7,6 @@ export default {
methods: {
onInput: function (e) {
- console.log(e)
this.$root.$emit('field-change', {
value: e.target.checked,
name: this.name
diff --git a/tests/factories.py b/tests/factories.py
index 41996d47..3073c4fb 100644
--- a/tests/factories.py
+++ b/tests/factories.py
@@ -56,7 +56,7 @@ class RequestFactory(factory.alchemy.SQLAlchemyModelFactory):
def build_request_body(cls, user, dollar_value=1000000):
return {
"primary_poc": {
- "am_poc": "no",
+ "am_poc": False,
"dodid_poc": user.dod_id,
"email_poc": user.email,
"fname_poc": user.first_name,
From a588591485e582a3df2924700373caa9ae83c260 Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Wed, 15 Aug 2018 10:34:35 -0400
Subject: [PATCH 16/18] urlencode form data for readability
---
tests/routes/test_request_new.py | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/tests/routes/test_request_new.py b/tests/routes/test_request_new.py
index f7ea2b9e..74a74dfe 100644
--- a/tests/routes/test_request_new.py
+++ b/tests/routes/test_request_new.py
@@ -2,6 +2,7 @@ import re
from tests.factories import RequestFactory, UserFactory
from atst.domain.roles import Roles
from atst.domain.requests import Requests
+from urllib.parse import urlencode
ERROR_CLASS = "alert--error"
@@ -131,10 +132,17 @@ def test_not_am_poc_allows_user_to_fill_in_poc_info(client, user_session):
creator = UserFactory.create()
user_session(creator)
request = RequestFactory.create(creator=creator, body={})
+ poc_input = {
+ "am_poc": "no",
+ "fname_poc": "test",
+ "lname_poc": "user",
+ "email_poc": "test.user@mail.com",
+ "dodid_poc": "1234567890",
+ }
response = client.post(
"/requests/new/3/{}".format(request.id),
headers={"Content-Type": "application/x-www-form-urlencoded"},
- data="am_poc=no&fname_poc=test&lname_poc=user&email_poc=test.user@mail.com&dodid_poc=1234567890",
+ data=urlencode(poc_input),
)
assert ERROR_CLASS not in response.data.decode()
From a9d3449548b1c5ed33c3eb8a2f80243e198f9727 Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Wed, 15 Aug 2018 10:35:06 -0400
Subject: [PATCH 17/18] Change inaccurate test name
---
tests/routes/test_request_new.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/routes/test_request_new.py b/tests/routes/test_request_new.py
index 74a74dfe..dd4f926c 100644
--- a/tests/routes/test_request_new.py
+++ b/tests/routes/test_request_new.py
@@ -147,7 +147,7 @@ def test_not_am_poc_allows_user_to_fill_in_poc_info(client, user_session):
assert ERROR_CLASS not in response.data.decode()
-def test_not_am_poc_allows_user_to_fill_in_poc_info_for_new_request(client, user_session):
+def test_poc_details_can_be_autopopulated_on_new_request(client, user_session):
creator = UserFactory.create()
user_session(creator)
response = client.post(
From e0ac7bfe5fcc33900abd2915dc0d1664e4acd37d Mon Sep 17 00:00:00 2001
From: richard-dds
Date: Wed, 15 Aug 2018 11:28:05 -0400
Subject: [PATCH 18/18] Update form fields and copy
---
atst/forms/poc.py | 8 ++++----
templates/requests/screen-3.html | 22 ++++++++++++----------
2 files changed, 16 insertions(+), 14 deletions(-)
diff --git a/atst/forms/poc.py b/atst/forms/poc.py
index ced1f9e7..827bf1ae 100644
--- a/atst/forms/poc.py
+++ b/atst/forms/poc.py
@@ -20,16 +20,16 @@ class POCForm(ValidatedForm):
am_poc = BooleanField(
- "I am the technical POC.",
+ "I am the Workspace Owner.",
default=False,
false_values=(False, "false", "False", "no", "")
)
- fname_poc = StringField("POC First Name", validators=[Required()])
+ fname_poc = StringField("First Name", validators=[Required()])
- lname_poc = StringField("POC Last Name", validators=[Required()])
+ lname_poc = StringField("Last Name", validators=[Required()])
- email_poc = EmailField("POC Email Address", validators=[Required(), Email()])
+ email_poc = EmailField("Email Address", validators=[Required(), Email()])
dodid_poc = StringField(
"DOD ID", validators=[Required(), Length(min=10), IsNumber()]
diff --git a/templates/requests/screen-3.html b/templates/requests/screen-3.html
index 0c86183c..79f4b3a0 100644
--- a/templates/requests/screen-3.html
+++ b/templates/requests/screen-3.html
@@ -19,18 +19,20 @@
-
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:
-
- - Create multiple application stacks and environments in the workspace to access the commercial cloud service provider portal
- - Add and manage users in the workspace
- - View the budget and billing history related to this workspace
- - Manage access to the Cloud Service Provider's Console
- - Transfer Workspace ownership to another person
-
-
This POC may be you.
+
+
The Workspace Owner is the primary point of contact and technical administrator of the JEDI Workspace and will have the
+ following responsibilities:
+
+ - Organize your cloud-hosted systems into projects and environments
+ - Add users to this workspace and manage members
+ - Manage access to the JEDI Cloud service provider’s portal
+
+
This person must be a DoD employee (not a contractor).
+
The Workspace Owner may be you. You will be able to add other administrators later. This person will be invited via email
+ once your request is approved.
+
{{ CheckboxInput(f.am_poc) }}