Merge pull request #38 from dod-ccpo/requests-persistence

Hook ATST up to the requests-queue
This commit is contained in:
richard-dds 2018-06-19 16:14:31 -04:00 committed by GitHub
commit 390df83390
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 163 additions and 54 deletions

View File

@ -22,6 +22,10 @@ class ApiClient(object):
def post(self, path, **kwargs):
return (yield self.make_request('POST', self.base_url + path, **kwargs))
@tornado.gen.coroutine
def patch(self, path, **kwargs):
return (yield self.make_request('PATCH', self.base_url + path, **kwargs))
@tornado.gen.coroutine
def delete(self, path, **kwargs):
return (yield self.make_request('DELETE', self.base_url + path, **kwargs))
@ -39,11 +43,13 @@ class ApiClient(object):
if not 'validate_cert' in kwargs:
kwargs['validate_cert'] = self.validate_cert
response = yield self.client.fetch(url, method=method, **kwargs)
response = yield self.client.fetch(
url, method=method, **kwargs)
return self.adapt_response(response)
def adapt_response(self, response):
if response.headers['Content-Type'] == 'application/json':
if 'application/json' in response.headers['Content-Type']:
json = loads(response.body)
setattr(response, 'json', json)
setattr(response, 'ok', 200 <= response.code < 300)
return response

View File

@ -34,7 +34,11 @@ def make_app(config, deps, **kwargs):
{"page": "workspaces", "authz_client": deps["authz_client"]},
name="workspaces",
),
url(r"/requests", Request, {"page": "requests"}, name="requests"),
url(
r"/requests",
Request,
{"page": "requests", 'requests_client': deps['requests_client']},
name="requests"),
url(
r"/requests/new",
RequestNew,
@ -44,7 +48,13 @@ def make_app(config, deps, **kwargs):
r"/requests/new/([0-9])",
RequestNew,
{"page": "requests_new", "requests_client": deps["requests_client"]},
name="request_form",
name="request_form_new",
),
url(
r"/requests/new/([0-9])/(\S+)",
RequestNew,
{"page": "requests_new", "requests_client": deps["requests_client"]},
name="request_form_update",
),
url(r"/users", MainHandler, {"page": "users"}, name="users"),
url(r"/reports", MainHandler, {"page": "reports"}, name="reports"),

View File

@ -1,5 +1,5 @@
from wtforms.fields.html5 import IntegerField
from wtforms.fields import RadioField, StringField, SelectField, TextAreaField
from wtforms.fields import RadioField, StringField, SelectField, TextAreaField, FormField
from wtforms.validators import Required, ValidationError
from wtforms_tornado import Form
from .date import DateForm
@ -14,7 +14,7 @@ class RequestForm(Form):
('B','Option B'),
('C','Option C') ])
# no way to apply a label to a whole nested form like this
date_start = DateForm()
date_start = FormField(DateForm)
period_of_performance = SelectField('Desired period of performance', validators=[Required()],
choices=[('','- Select -'),
('value1','30 days'),
@ -31,6 +31,12 @@ class RequestForm(Form):
('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):

View File

@ -26,7 +26,7 @@ class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
if self.get_secure_cookie('atst'):
return True
return '9cb348f0-8102-4962-88c4-dac8180c904c'
else:
return False

View File

@ -28,10 +28,27 @@ mock_requests = [
},
]
def map_request(request):
return {
'order_id': request['id'],
'is_new': False,
'status': 'Pending',
'app_count': 1,
'is_new': False,
'date': '',
'full_name': 'Richard Howard'
}
class Request(BaseHandler):
def initialize(self, page):
def initialize(self, page, requests_client):
self.page = page
self.requests_client = requests_client
@tornado.web.authenticated
@tornado.gen.coroutine
def get(self):
self.render('requests.html.to', page = self.page, requests = mock_requests )
response = yield self.requests_client.get(
'/users/{}/requests'.format(self.get_current_user()))
requests = response.json['requests']
mapped_requests = [map_request(request) for request in requests]
self.render('requests.html.to', page=self.page, requests=mapped_requests)

View File

@ -6,6 +6,7 @@ from atst.forms.funding import FundingForm
from atst.forms.readiness import ReadinessForm
from atst.forms.review import ReviewForm
import tornado.httputil
from tornado.httpclient import HTTPError
class RequestNew(BaseHandler):
@ -45,26 +46,62 @@ class RequestNew(BaseHandler):
self.requests_client = requests_client
@tornado.web.authenticated
def post(self, screen = 1):
@tornado.gen.coroutine
def post(self, screen=1, request_id=None):
self.check_xsrf_cookie()
screen = int(screen)
form = self.screens[ screen - 1 ]['form'](self.request.arguments)
if form.validate():
where = self.application.default_router.reverse_url('request_form', str(screen + 1))
response = yield self.create_or_update_request(form.data, request_id)
if response.ok:
where = self.application.default_router.reverse_url(
'request_form_update', str(screen + 1), request_id or response.json['id'])
self.redirect(where)
else:
self.set_status(response.code)
else:
self.show_form(screen, form)
@tornado.web.authenticated
def get(self, screen = 1):
self.show_form(screen=screen)
@tornado.gen.coroutine
def get(self, screen=1, request_id=None):
form = None
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)
def show_form(self, screen = 1, form = None):
self.show_form(screen=screen, form=form, request_id=request_id)
def show_form(self, screen=1, form=None, request_id=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,
page = self.page,
screens = self.screens,
current = int(screen),
next_screen = int(screen) + 1 )
self.render('requests/screen-%d.html.to' % int(screen),
f=form,
page=self.page,
screens=self.screens,
current=int(screen),
next_screen=int(screen) + 1,
request_id=request_id)
@tornado.gen.coroutine
def get_request(self, request_id):
request = yield self.requests_client.get(
'/users/{}/requests/{}'.format(self.get_current_user(), request_id),
raise_error=False)
return request
@tornado.gen.coroutine
def create_or_update_request(self, form_data, request_id=None):
request_data = {
'creator_id': self.get_current_user(),
'request': form_data
}
if request_id:
response = yield self.requests_client.patch(
'/requests/{}'.format(request_id), json=request_data)
else:
response = yield self.requests_client.post(
'/requests', json=request_data)
return response

View File

@ -20,7 +20,7 @@
<tbody>
{% for r in requests %}
<tr>
<th scope="row"><a href="#">#{{ r['order_id'] }}</a>
<th scope="row"><a href="{{ reverse_url('request_form_update', 1, r['order_id']) }}">{{ r['order_id'] }}</a>
{% if r['is_new'] %}<span class="usa-label">New</span></th>{% end %}
<td>{{ r['date'] }}</td>
<td>{{ r['full_name'] }}</td>

View File

@ -5,8 +5,8 @@
<p class="usa-font-lead">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Natus error omnis a, tenetur similique quo officiis voluptates eum recusandae dolorem minus dignissimos, magni consequatur, maxime debitis reprehenderit sint non iusto?</p>
<a class='usa-button usa-button-secondary' href='{{ reverse_url('request_form',next_screen) }}'>New Application</a>
<a class='usa-button usa-button-secondary' href='{{ reverse_url('request_form',next_screen) }}'>Existing Application</a>
<a class='usa-button usa-button-secondary' href='{{ reverse_url('request_form',next_screen) }}'>Sandbox Environment</a>
<a class='usa-button usa-button-secondary' href='{{ reverse_url('request_form_new',next_screen) }}'>New Application</a>
<a class='usa-button usa-button-secondary' href='{{ reverse_url('request_form_new',next_screen) }}'>Existing Application</a>
<a class='usa-button usa-button-secondary' href='{{ reverse_url('request_form_new',next_screen) }}'>Sandbox Environment</a>
{% end %}

View File

@ -92,29 +92,25 @@
<h4 id="application-storage">Storage</h4>
<p>The particulars of your body copy will be determined by the topic of your page. Regardless of topic, its a good practice to follow the inverted pyramid structure when writing copy: Begin with the information thats most important to your users and then present information of less importance.</p>
<label for="">Object storage</label>
<input id="" name="" type="text" placeholder="Total object storage">
<label for="">Server storage</label>
<input id="" name="" type="text" placeholder="Total server storage">
{{ f.object_storage.label }}
{{ f.object_storage(placeholder="Total object storage") }}
{{ 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>
<label for="">Expected active users</label>
<input id="" name="" type="text" placeholder="Total active users">
{{ f.total_active_users.label }}
{{ f.total_active_users(placeholder="Total active users") }}
<label for="">Expected peak concurrent users</label>
<input id="" name="" type="text" placeholder="Total peak users">
<label for="">Expected requests per minute</label>
<input id="" name="" type="text" placeholder="Total requests">
<label for="">Number of application environments</label>
<input id="" name="" type="text" placeholder="Total number of environments">
{{ f.total_peak_users.label }}
{{ f.total_peak_users(placeholder="Total peak users") }}
{{ f.total_requests.label }}
{{ f.total_requests(placeholder="Total requests") }}
{{ f.total_environments.label }}
{{ f.total_environments(placeholder="Total number of environments") }}
{% end %}

View File

@ -2,7 +2,7 @@
{% for i,s in enumerate(screens) %}
<li>
{% if i+1==current %}
<a class="usa-current" href="{{ reverse_url('request_form',i+1) }}">
<a class="usa-current" href="{{ reverse_url('request_form_update', i+1, request_id) if request_id else reverse_url('request_form_new',i+1) }}">
{{ i+1 }}. {{ s['title'] }}
</a>
{% if s.get('subitems') %}
@ -14,7 +14,7 @@
{% end %}
{% else %}
<a href='{{ reverse_url('request_form',i+1) }}'>
<a href="{{ reverse_url('request_form_update', i+1, request_id) if request_id else reverse_url('request_form_new',i+1) }}">
{{ i+1 }}. {{ s['title'] }}
</a>
{% end %}

View File

@ -14,7 +14,12 @@
<main class="main-content usa-width-two-thirds">
<form method='POST' action='{{ reverse_url('request_form', current) }}'>
{% if request_id %}
<form method='POST' action="{{ reverse_url('request_form_update', current, request_id) }}">
{% else %}
<form method='POST' action="{{ reverse_url('request_form_new', current) }}">
{% end %}
{% module xsrf_form_html() %}
{% block form %}
form goes here

View File

@ -1,14 +1,14 @@
import pytest
from atst.app import make_app, make_deps, make_config
from tests.mocks import MockApiClient
from tests.mocks import MockApiClient, MockRequestsClient
@pytest.fixture
def app():
TEST_DEPS = {
'authz_client': MockApiClient('authz'),
'requests_client': MockApiClient('requests'),
'requests_client': MockRequestsClient('requests'),
'authnid_client': MockApiClient('authnid'),
}

View File

@ -1,9 +1,9 @@
import re
import pytest
ERROR_CLASS = 'usa-input-error-message'
@pytest.mark.gen_test
def test_submit_invalid_request_form(monkeypatch, http_client, base_url):
monkeypatch.setattr('atst.handlers.request_new.RequestNew.get_current_user', lambda s: True)
@ -18,11 +18,13 @@ def test_submit_invalid_request_form(monkeypatch, http_client, base_url):
assert response.effective_url == base_url + '/requests/new'
assert re.search(ERROR_CLASS, response.body.decode())
@pytest.mark.gen_test
def test_submit_valid_request_form(monkeypatch, http_client, base_url):
monkeypatch.setattr('atst.handlers.request_new.RequestNew.get_current_user', lambda s: True)
monkeypatch.setattr('atst.handlers.request_new.RequestNew.check_xsrf_cookie', lambda s: True)
monkeypatch.setattr('atst.forms.request.RequestForm.validate', lambda s: True)
# this just needs to send a known invalid form value
response = yield http_client.fetch(
base_url + "/requests/new",
@ -30,4 +32,4 @@ def test_submit_valid_request_form(monkeypatch, http_client, base_url):
headers={"Content-Type": "application/x-www-form-urlencoded"},
body="meaning=42",
)
assert response.effective_url == base_url + '/requests/new/2'
assert '/requests/new/2' in response.effective_url

View File

@ -1,8 +1,10 @@
import tornado.gen
from tornado.httpclient import HTTPRequest, HTTPResponse
from atst.api_client import ApiClient
class MockApiClient(object):
class MockApiClient(ApiClient):
def __init__(self, service):
self.service = service
@ -26,7 +28,35 @@ class MockApiClient(object):
def delete(self, path, **kwargs):
return self._get_response('DELETE', path)
def _get_response(self, verb, path):
response = HTTPResponse(request=HTTPRequest(path, verb), code=200)
setattr(response, 'json', {})
def _get_response(self, verb, path, code=200, json=None):
response = HTTPResponse(
request=HTTPRequest(path, verb),
code=code,
headers={'Content-Type': 'application/json'})
setattr(response, 'ok', 200 <= code < 300)
if json:
setattr(response, 'json', json)
return response
class MockRequestsClient(MockApiClient):
@tornado.gen.coroutine
def get(self, path, **kwargs):
json = {
'id': '66b8ef71-86d3-48ef-abc2-51bfa1732b6b',
'creator': '49903ae7-da4a-49bf-a6dc-9dff5d004238',
'body': {}
}
return self._get_response('GET', path, 200, json=json)
@tornado.gen.coroutine
def post(self, path, **kwargs):
json = {
'id': '66b8ef71-86d3-48ef-abc2-51bfa1732b6b',
'creator': '49903ae7-da4a-49bf-a6dc-9dff5d004238',
'body': {}
}
return self._get_response('POST', path, 202, json=json)