Merge pull request #206 from dod-ccpo/multiple-environments

Allow a user to add multiple environments when creating a project
This commit is contained in:
richard-dds 2018-08-23 16:15:00 -04:00 committed by GitHub
commit 8806705bc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 89 additions and 21 deletions

View File

@ -10,3 +10,9 @@ class Environments(object):
db.session.commit()
return environment
@classmethod
def create_many(cls, project, names):
for name in names:
environment = Environment(project=project, name=name)
db.session.add(environment)
db.session.commit()

View File

@ -1,11 +1,13 @@
from atst.database import db
from atst.models.project import Project
from atst.domain.environments import Environments
class Projects(object):
@classmethod
def create(cls, workspace, name, description):
def create(cls, workspace, name, description, environment_names):
project = Project(workspace=workspace, name=name, description=description)
Environments.create_many(project, environment_names)
db.session.add(project)
db.session.commit()

View File

@ -1,9 +1,26 @@
from flask_wtf import Form
from wtforms.fields import StringField, TextAreaField
from wtforms.fields import StringField, TextAreaField, FieldList
from wtforms.validators import Required
from atst.forms.validators import ListItemRequired
class NewProjectForm(Form):
name = StringField(label="Project Name")
description = TextAreaField(label="Description")
environment_name = StringField(label="Environment Name")
EMPTY_ENVIRONMENT_NAMES = ["", None]
name = StringField(label="Project Name", validators=[Required()])
description = TextAreaField(label="Description", validators=[Required()])
environment_names = FieldList(
StringField(label="Environment Name"),
validators=[ListItemRequired(message="Provide at least one environment name.")],
)
@property
def data(self):
_data = super(Form, self).data
_data["environment_names"] = [
n
for n in _data["environment_names"]
if n not in self.EMPTY_ENVIRONMENT_NAMES
]
return _data

View File

@ -51,3 +51,14 @@ def Alphabet(message="Please enter only letters."):
raise ValidationError(message)
return _alphabet
def ListItemRequired(message="Please provide at least one.", empty_values=("", None)):
def _list_item_required(form, field):
non_empty_values = [v for v in field.data if v not in empty_values]
if len(non_empty_values) == 0:
raise ValidationError(message)
return _list_item_required

View File

@ -9,7 +9,6 @@ from flask import (
from atst.domain.workspaces import Workspaces
from atst.domain.projects import Projects
from atst.domain.environments import Environments
from atst.forms.new_project import NewProjectForm
bp = Blueprint("workspaces", __name__)
@ -67,10 +66,16 @@ def update_project(workspace_id):
if form.validate():
project_data = form.data
project = Projects.create(
workspace, project_data["name"], project_data["description"]
Projects.create(
workspace,
project_data["name"],
project_data["description"],
project_data["environment_names"],
)
Environments.create(project, project_data["environment_name"])
return redirect(
url_for("workspaces.workspace_projects", workspace_id=workspace.id)
)
else:
return render_template(
"workspace_project_new.html", workspace=workspace, form=form
)

View File

@ -1,5 +1,7 @@
import textinput from '../text_input'
const createEnvironment = (name) => ({ name })
export default {
name: 'new-project',
@ -16,14 +18,16 @@ export default {
data: function () {
const {
name,
description,
environments = ['']
environment_names,
} = this.initialData
const environments = (
environment_names.length > 0
? environment_names
: [""]
).map(createEnvironment)
return {
name,
description,
environments,
}
},
@ -34,7 +38,7 @@ export default {
methods: {
addEnvironment: function (event) {
this.environments.push('')
this.environments.push(createEnvironment(""))
},
removeEnvironment: function (index) {

View File

@ -17,7 +17,8 @@
justify-content: space-between;
.icon-tooltip {
padding: 0.25rem 0.5rem;
margin: -$gap;
}
}

View File

@ -13,7 +13,7 @@
@include icon-link-vertical;
@include icon-link-color($color-red, $color-red-lightest);
margin-bottom: -$gap;
margin-bottom: 0;
margin-right: -$gap;
}
}

View File

@ -1,6 +1,7 @@
{% from "components/icon.html" import Icon %}
{% from "components/text_input.html" import TextInput %}
{% from "components/tooltip.html" import Tooltip %}
{% from "components/alert.html" import Alert %}
{% extends "base_workspace.html" %}
@ -24,6 +25,10 @@
</div>
</div>
{% if form.environment_names.errors %}
{{ Alert("Missing Environments", message="Provide at least one environment name.", level="error") }}
{% endif %}
<div class="block-list project-list-item">
<header class="block-list__header">
<h2 class="block-list__title">Project Environments</h2>
@ -34,10 +39,15 @@
</header>
<ul>
<li v-for="(_, i) in environments" class="block-list__item">
{{ TextInput(form.environment_name) }}
<span class="icon-link icon-link--danger icon-link--vertical" v-on:click="removeEnvironment(i)">{{ Icon('x') }} Remove</span>
<li v-for="(environment, i) in environments" class="block-list__item project-edit__env-list-item">
<div class="usa-input">
<label :for="'environment_names-' + i">Environment Name</label>
<input type="text" :name="'environment_names-' + i" :id="'environment_names-' + i" v-model="environment.name">
</div>
<button v-on:click="removeEnvironment(i)" v-if="environments.length > 1" type="button" class='project-edit__env-list-item__remover'>
{{ Icon('trash') }}
<span>Remove</span>
</button>
</li>
</ul>

View File

@ -0,0 +1,12 @@
from atst.domain.projects import Projects
from tests.factories import WorkspaceFactory
def test_create_project_with_multiple_environments():
workspace = WorkspaceFactory.create()
project = Projects.create(workspace, "My Test Project", "Test", ["dev", "prod"])
assert project.workspace == workspace
assert project.name == "My Test Project"
assert project.description == "Test"
assert sorted(e.name for e in project.environments) == ["dev", "prod"]