Merge pull request #38 from dod-ccpo/requests-persistence
Hook ATST up to the requests-queue
This commit is contained in:
commit
390df83390
@ -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
|
||||
|
14
atst/app.py
14
atst/app.py
@ -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"),
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
self.redirect(where)
|
||||
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
|
||||
|
@ -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>
|
||||
|
@ -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 %}
|
@ -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, 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.</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, 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.</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 %}
|
||||
|
@ -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 %}
|
||||
|
@ -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
|
||||
|
@ -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'),
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user