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:
commit
8806705bc3
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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) {
|
||||
|
@ -17,7 +17,8 @@
|
||||
justify-content: space-between;
|
||||
|
||||
.icon-tooltip {
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin: -$gap;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
12
tests/domain/test_projects.py
Normal file
12
tests/domain/test_projects.py
Normal 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"]
|
Loading…
x
Reference in New Issue
Block a user