Merge pull request #159 from dod-ccpo/details-of-use-form
Details of use form
This commit is contained in:
commit
f8fe6fa6f5
@ -1,11 +1,38 @@
|
||||
from wtforms.fields.html5 import IntegerField
|
||||
from wtforms.fields import RadioField, TextAreaField, SelectField
|
||||
from wtforms.validators import Optional, Required
|
||||
|
||||
from .fields import DateField
|
||||
from .forms import ValidatedForm
|
||||
from atst.domain.requests import Requests
|
||||
|
||||
|
||||
class RequestForm(ValidatedForm):
|
||||
|
||||
def validate(self, *args, **kwargs):
|
||||
if self.jedi_migration.data == 'no':
|
||||
self.rationalization_software_systems.validators.append(Optional())
|
||||
self.technical_support_team.validators.append(Optional())
|
||||
self.organization_providing_assistance.validators.append(Optional())
|
||||
self.engineering_assessment.validators.append(Optional())
|
||||
self.data_transfers.validators.append(Optional())
|
||||
self.expected_completion_date.validators.append(Optional())
|
||||
elif self.jedi_migration.data == 'yes':
|
||||
if self.technical_support_team.data == 'no':
|
||||
self.organization_providing_assistance.validators.append(Optional())
|
||||
self.cloud_native.validators.append(Optional())
|
||||
|
||||
try:
|
||||
annual_spend = int(self.estimated_monthly_spend.data or 0) * 12
|
||||
except ValueError:
|
||||
annual_spend = 0
|
||||
|
||||
if annual_spend > Requests.AUTO_APPROVE_THRESHOLD:
|
||||
self.number_user_sessions.validators.append(Required())
|
||||
self.average_daily_traffic.validators.append(Required())
|
||||
|
||||
return super(RequestForm, self).validate(*args, **kwargs)
|
||||
|
||||
# Details of Use: General
|
||||
dod_component = SelectField(
|
||||
"DoD Component",
|
||||
@ -37,16 +64,19 @@ class RequestForm(ValidatedForm):
|
||||
jedi_migration = RadioField(
|
||||
"Are you using the JEDI Cloud to migrate existing systems?",
|
||||
choices=[("yes", "Yes"), ("no", "No")],
|
||||
default="",
|
||||
)
|
||||
|
||||
rationalization_software_systems = RadioField(
|
||||
"Have you completed a “rationalization” of your software systems to move to the cloud?",
|
||||
choices=[("yes", "Yes"), ("no", "No"), ("in_progress", "In Progress")],
|
||||
default="",
|
||||
)
|
||||
|
||||
technical_support_team = RadioField(
|
||||
"Are you working with a technical support team experienced in cloud migrations?",
|
||||
choices=[("yes", "Yes"), ("no", "No")],
|
||||
default="",
|
||||
)
|
||||
|
||||
organization_providing_assistance = RadioField( # this needs to be updated to use checkboxes instead of radio
|
||||
@ -56,11 +86,13 @@ class RequestForm(ValidatedForm):
|
||||
("contractor", "Contractor"),
|
||||
("other_dod_organization", "Other DoD organization"),
|
||||
],
|
||||
default="",
|
||||
)
|
||||
|
||||
engineering_assessment = RadioField(
|
||||
"Have you completed an engineering assessment of your software systems for cloud readiness?",
|
||||
choices=[("yes", "Yes"), ("no", "No"), ("in_progress", "In Progress")],
|
||||
default="",
|
||||
)
|
||||
|
||||
data_transfers = SelectField(
|
||||
@ -94,6 +126,7 @@ class RequestForm(ValidatedForm):
|
||||
cloud_native = RadioField(
|
||||
"Are your software systems being developed cloud native?",
|
||||
choices=[("yes", "Yes"), ("no", "No")],
|
||||
default="",
|
||||
)
|
||||
|
||||
# Details of Use: Financial Usage
|
||||
|
71
js/components/forms/details_of_use.js
Normal file
71
js/components/forms/details_of_use.js
Normal file
@ -0,0 +1,71 @@
|
||||
import createNumberMask from 'text-mask-addons/dist/createNumberMask'
|
||||
import { conformToMask } from 'vue-text-mask'
|
||||
|
||||
import textinput from '../text_input'
|
||||
import optionsinput from '../options_input'
|
||||
|
||||
export default {
|
||||
name: 'details-of-use',
|
||||
|
||||
components: {
|
||||
textinput,
|
||||
optionsinput,
|
||||
},
|
||||
|
||||
props: {
|
||||
initialData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
|
||||
data: function () {
|
||||
const {
|
||||
estimated_monthly_spend = 0,
|
||||
jedi_migration = '',
|
||||
technical_support_team = ''
|
||||
} = this.initialData
|
||||
|
||||
return {
|
||||
estimated_monthly_spend,
|
||||
jedi_migration,
|
||||
technical_support_team
|
||||
}
|
||||
},
|
||||
|
||||
mounted: function () {
|
||||
this.$root.$on('field-change', this.handleFieldChange)
|
||||
},
|
||||
|
||||
computed: {
|
||||
annualSpend: function () {
|
||||
const monthlySpend = this.estimated_monthly_spend || 0
|
||||
return monthlySpend * 12
|
||||
},
|
||||
annualSpendStr: function () {
|
||||
return this.formatDollars(this.annualSpend)
|
||||
},
|
||||
jediMigrationOptionSelected: function () {
|
||||
return this.jedi_migration !== ''
|
||||
},
|
||||
isJediMigration: function () {
|
||||
return this.jedi_migration === 'yes'
|
||||
},
|
||||
hasTechnicalSupportTeam: function () {
|
||||
return this.technical_support_team === 'yes'
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
formatDollars: function (intValue) {
|
||||
const mask = createNumberMask({ prefix: '$', allowDecimal: true })
|
||||
return conformToMask(intValue.toString(), mask).conformedValue
|
||||
},
|
||||
handleFieldChange: function (event) {
|
||||
const { value, name } = event
|
||||
if (typeof this[name] !== undefined) {
|
||||
this[name] = value
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
16
js/components/options_input.js
Normal file
16
js/components/options_input.js
Normal file
@ -0,0 +1,16 @@
|
||||
export default {
|
||||
name: 'optionsinput',
|
||||
|
||||
props: {
|
||||
name: String
|
||||
},
|
||||
|
||||
methods: {
|
||||
onInput: function (e) {
|
||||
this.$root.$emit('field-change', {
|
||||
value: e.target.value,
|
||||
name: this.name
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -89,8 +89,8 @@ export default {
|
||||
this.showValid = valid
|
||||
|
||||
// Emit a change event
|
||||
this.$emit('fieldChange', {
|
||||
value,
|
||||
this.$root.$emit('field-change', {
|
||||
value: this._rawValue(value),
|
||||
valid,
|
||||
name: this.name
|
||||
})
|
||||
|
@ -1,12 +1,16 @@
|
||||
import classes from '../styles/atat.scss'
|
||||
import Vue from 'vue/dist/vue'
|
||||
|
||||
import optionsinput from './components/options_input'
|
||||
import textinput from './components/text_input'
|
||||
import DetailsOfUse from './components/forms/details_of_use'
|
||||
|
||||
const app = new Vue({
|
||||
el: '#app-root',
|
||||
components: {
|
||||
textinput
|
||||
optionsinput,
|
||||
textinput,
|
||||
DetailsOfUse,
|
||||
},
|
||||
methods: {
|
||||
closeModal: function(name) {
|
||||
@ -31,5 +35,6 @@ const app = new Vue({
|
||||
const modal = modalOpen.getAttribute("data-modal");
|
||||
this.modals[modal] = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
delimiters: ['!{', '}']
|
||||
})
|
||||
|
@ -1,32 +1,33 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
|
||||
{% macro OptionsInput(field, inline=False) -%}
|
||||
<div class='usa-input {% if field.errors %}usa-input--error{% endif %}'>
|
||||
<optionsinput name='{{ field.name }}' inline-template key='{{ field.name }}'>
|
||||
<div class='usa-input {% if field.errors %}usa-input--error{% endif %}'>
|
||||
|
||||
<fieldset class="usa-input__choices {% if inline %}usa-input__choices--inline{% endif %}">
|
||||
<legend>
|
||||
{{ field.label | striptags}}
|
||||
<fieldset v-on:change="onInput" class="usa-input__choices {% if inline %}usa-input__choices--inline{% endif %}">
|
||||
<legend>
|
||||
{{ field.label | striptags}}
|
||||
|
||||
{% if field.description %}
|
||||
<span class='usa-input__help'>{{ field.description | safe }}</span>
|
||||
{% endif %}
|
||||
{% if field.description %}
|
||||
<span class='usa-input__help'>{{ field.description | safe }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if field.errors %}
|
||||
{{ Icon('alert') }}
|
||||
{% endif %}
|
||||
</legend>
|
||||
|
||||
{{ field() }}
|
||||
|
||||
{% if field.errors %}
|
||||
{{ Icon('alert') }}
|
||||
{% for error in field.errors %}
|
||||
<span class='usa-input__message'>{{ error }}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</legend>
|
||||
|
||||
{{ field() }}
|
||||
|
||||
{% if field.errors %}
|
||||
{% for error in field.errors %}
|
||||
<span class='usa-input__message'>{{ error }}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
</optionsinput>
|
||||
|
||||
{%- endmacro %}
|
||||
|
@ -6,6 +6,7 @@
|
||||
validation='{{ validation }}'
|
||||
{% if field.data %}initial-value='{{ field.data }}'{% endif %}
|
||||
{% if field.errors %}v-bind:initial-errors='{{ field.errors }}'{% endif %}
|
||||
key='{{ field.name }}'
|
||||
inline-template>
|
||||
|
||||
<div
|
||||
|
@ -17,32 +17,46 @@
|
||||
) }}
|
||||
{% endif %}
|
||||
|
||||
<details-of-use inline-template v-bind:initial-data='{{ f.data|tojson }}'>
|
||||
<div>
|
||||
|
||||
<p>We’d like to know a little about how you plan to use JEDI Cloud services to process your request. Please answer the following questions to the best of your ability. Note that the CCPO does not directly help with migrating systems to JEDI Cloud. These questions are for learning about your cloud readiness and financial usage of the JEDI Cloud; your estimates will not be used for any department level reporting.</p>
|
||||
<p><em>All fields are required, unless specified optional.</em></p>
|
||||
<p>We’d like to know a little about how you plan to use JEDI Cloud services to process your request. Please answer the following questions to the best of your ability. Note that the CCPO does not directly help with migrating systems to JEDI Cloud. These questions are for learning about your cloud readiness and financial usage of the JEDI Cloud; your estimates will not be used for any department level reporting.</p>
|
||||
<p><em>All fields are required, unless specified optional.</em></p>
|
||||
|
||||
<h2>General</h2>
|
||||
{{ OptionsInput(f.dod_component) }}
|
||||
{{ TextInput(f.jedi_usage, paragraph=True, placeholder="e.g. We are migrating XYZ application to the cloud so that...") }}
|
||||
<h2>General</h2>
|
||||
{{ OptionsInput(f.dod_component) }}
|
||||
{{ TextInput(f.jedi_usage, paragraph=True, placeholder="e.g. We are migrating XYZ application to the cloud so that...") }}
|
||||
|
||||
<h2>Cloud Readiness</h2>
|
||||
{{ TextInput(f.num_software_systems, validation='integer') }}
|
||||
{{ OptionsInput(f.jedi_migration) }}
|
||||
{{ OptionsInput(f.rationalization_software_systems) }}
|
||||
{{ OptionsInput(f.technical_support_team) }}
|
||||
{{ OptionsInput(f.organization_providing_assistance) }}
|
||||
{{ OptionsInput(f.engineering_assessment) }}
|
||||
{{ OptionsInput(f.data_transfers) }}
|
||||
{{ OptionsInput(f.expected_completion_date) }}
|
||||
{{ OptionsInput(f.cloud_native) }}
|
||||
<h2>Cloud Readiness</h2>
|
||||
{{ TextInput(f.num_software_systems, validation='integer') }}
|
||||
{{ OptionsInput(f.jedi_migration) }}
|
||||
<template v-if="jediMigrationOptionSelected">
|
||||
<template v-if='isJediMigration' v-cloak>
|
||||
{{ OptionsInput(f.rationalization_software_systems) }}
|
||||
{{ OptionsInput(f.technical_support_team) }}
|
||||
<template v-if="hasTechnicalSupportTeam">
|
||||
{{ OptionsInput(f.organization_providing_assistance) }}
|
||||
</template>
|
||||
{{ OptionsInput(f.engineering_assessment) }}
|
||||
{{ OptionsInput(f.data_transfers) }}
|
||||
{{ OptionsInput(f.expected_completion_date) }}
|
||||
</template>
|
||||
<template v-else-if='!isJediMigration' v-cloak>
|
||||
{{ OptionsInput(f.cloud_native) }}
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<h2>Financial Usage</h2>
|
||||
{{ TextInput(f.estimated_monthly_spend, validation='dollars') }}
|
||||
<p>So this means you are spending approximately <b>$X</b> annually</p>
|
||||
{{ TextInput(f.dollar_value, validation='dollars') }}
|
||||
{{ TextInput(f.number_user_sessions, validation='integer') }}
|
||||
{{ TextInput(f.average_daily_traffic, placeholder='Gigabytes per day', validation='gigabytes') }}
|
||||
{{ TextInput(f.start_date, validation='date', placeholder='MM / DD / YYYY') }}
|
||||
<h2>Financial Usage</h2>
|
||||
{{ TextInput(f.estimated_monthly_spend, validation='dollars') }}
|
||||
<p>So this means you are spending approximately <b>!{ annualSpendStr }</b> annually</p>
|
||||
{{ TextInput(f.dollar_value, validation='dollars') }}
|
||||
<template v-if="annualSpend > 1000000">
|
||||
{{ TextInput(f.number_user_sessions, validation='integer') }}
|
||||
{{ TextInput(f.average_daily_traffic, placeholder='Gigabytes per day', validation='gigabytes') }}
|
||||
</template>
|
||||
{{ TextInput(f.start_date, validation='date', placeholder='MM / DD / YYYY') }}
|
||||
|
||||
</div>
|
||||
</details-of-use>
|
||||
|
||||
{% endblock %}
|
||||
|
85
tests/forms/test_request.py
Normal file
85
tests/forms/test_request.py
Normal file
@ -0,0 +1,85 @@
|
||||
import pytest
|
||||
|
||||
from atst.forms.request import RequestForm
|
||||
|
||||
|
||||
class TestRequestForm:
|
||||
|
||||
form_data = {
|
||||
'dod_component': 'us_air_force',
|
||||
'jedi_usage': 'cloud-ify all the things',
|
||||
'num_software_systems': '12',
|
||||
'estimated_monthly_spend': '1000000',
|
||||
'dollar_value': '42',
|
||||
'number_user_sessions': '6',
|
||||
'average_daily_traffic': '0',
|
||||
'start_date': '12/12/2012',
|
||||
}
|
||||
migration_data = {
|
||||
'jedi_migration': 'yes',
|
||||
'rationalization_software_systems': 'yes',
|
||||
'technical_support_team': 'yes',
|
||||
'organization_providing_assistance': 'in_house_staff',
|
||||
'engineering_assessment': 'yes',
|
||||
'data_transfers': 'less_than_100gb',
|
||||
'expected_completion_date': 'less_than_1_month'
|
||||
}
|
||||
|
||||
def test_require_cloud_native_when_not_migrating(self):
|
||||
extra_data = { 'jedi_migration': 'no' }
|
||||
request_form = RequestForm(data={ **self.form_data, **extra_data })
|
||||
assert not request_form.validate()
|
||||
assert request_form.errors == { 'cloud_native': ['Not a valid choice'] }
|
||||
|
||||
def test_require_migration_questions_when_migrating(self):
|
||||
extra_data = { 'jedi_migration': 'yes' }
|
||||
request_form = RequestForm(data={ **self.form_data, **extra_data })
|
||||
assert not request_form.validate()
|
||||
assert request_form.errors == {
|
||||
'rationalization_software_systems': ['Not a valid choice'],
|
||||
'technical_support_team': ['Not a valid choice'],
|
||||
'organization_providing_assistance': ['Not a valid choice'],
|
||||
'engineering_assessment': ['Not a valid choice'],
|
||||
'data_transfers': ['Not a valid choice'],
|
||||
'expected_completion_date': ['Not a valid choice']
|
||||
}
|
||||
|
||||
def test_require_organization_when_technical_support_team(self):
|
||||
data = { **self.form_data, **self.migration_data }
|
||||
del data['organization_providing_assistance']
|
||||
|
||||
request_form = RequestForm(data=data)
|
||||
assert not request_form.validate()
|
||||
assert request_form.errors == {
|
||||
'organization_providing_assistance': ['Not a valid choice'],
|
||||
}
|
||||
|
||||
def test_valid_form_data(self):
|
||||
data = { **self.form_data, **self.migration_data }
|
||||
data['technical_support_team'] = 'no'
|
||||
del data['organization_providing_assistance']
|
||||
|
||||
request_form = RequestForm(data=data)
|
||||
assert request_form.validate()
|
||||
|
||||
def test_sessions_required_for_large_projects(self):
|
||||
data = { **self.form_data, **self.migration_data }
|
||||
data['estimated_monthly_spend'] = '9999999'
|
||||
del data['number_user_sessions']
|
||||
del data['average_daily_traffic']
|
||||
|
||||
request_form = RequestForm(data=data)
|
||||
assert not request_form.validate()
|
||||
assert request_form.errors == {
|
||||
'number_user_sessions': ['This field is required.'],
|
||||
'average_daily_traffic': ['This field is required.'],
|
||||
}
|
||||
|
||||
def test_sessions_not_required_invalid_monthly_spend(self):
|
||||
data = { **self.form_data, **self.migration_data }
|
||||
data['estimated_monthly_spend'] = 'foo'
|
||||
del data['number_user_sessions']
|
||||
del data['average_daily_traffic']
|
||||
|
||||
request_form = RequestForm(data=data)
|
||||
assert request_form.validate()
|
Loading…
x
Reference in New Issue
Block a user