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()
|
db.session.commit()
|
||||||
return environment
|
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.database import db
|
||||||
from atst.models.project import Project
|
from atst.models.project import Project
|
||||||
|
from atst.domain.environments import Environments
|
||||||
|
|
||||||
|
|
||||||
class Projects(object):
|
class Projects(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, workspace, name, description):
|
def create(cls, workspace, name, description, environment_names):
|
||||||
project = Project(workspace=workspace, name=name, description=description)
|
project = Project(workspace=workspace, name=name, description=description)
|
||||||
|
Environments.create_many(project, environment_names)
|
||||||
|
|
||||||
db.session.add(project)
|
db.session.add(project)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -1,9 +1,26 @@
|
|||||||
from flask_wtf import Form
|
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):
|
class NewProjectForm(Form):
|
||||||
|
|
||||||
name = StringField(label="Project Name")
|
EMPTY_ENVIRONMENT_NAMES = ["", None]
|
||||||
description = TextAreaField(label="Description")
|
|
||||||
environment_name = StringField(label="Environment Name")
|
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)
|
raise ValidationError(message)
|
||||||
|
|
||||||
return _alphabet
|
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.workspaces import Workspaces
|
||||||
from atst.domain.projects import Projects
|
from atst.domain.projects import Projects
|
||||||
from atst.domain.environments import Environments
|
|
||||||
from atst.forms.new_project import NewProjectForm
|
from atst.forms.new_project import NewProjectForm
|
||||||
|
|
||||||
bp = Blueprint("workspaces", __name__)
|
bp = Blueprint("workspaces", __name__)
|
||||||
@ -67,10 +66,16 @@ def update_project(workspace_id):
|
|||||||
|
|
||||||
if form.validate():
|
if form.validate():
|
||||||
project_data = form.data
|
project_data = form.data
|
||||||
project = Projects.create(
|
Projects.create(
|
||||||
workspace, project_data["name"], project_data["description"]
|
workspace,
|
||||||
|
project_data["name"],
|
||||||
|
project_data["description"],
|
||||||
|
project_data["environment_names"],
|
||||||
)
|
)
|
||||||
Environments.create(project, project_data["environment_name"])
|
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for("workspaces.workspace_projects", workspace_id=workspace.id)
|
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'
|
import textinput from '../text_input'
|
||||||
|
|
||||||
|
const createEnvironment = (name) => ({ name })
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'new-project',
|
name: 'new-project',
|
||||||
|
|
||||||
@ -16,14 +18,16 @@ export default {
|
|||||||
|
|
||||||
data: function () {
|
data: function () {
|
||||||
const {
|
const {
|
||||||
name,
|
environment_names,
|
||||||
description,
|
|
||||||
environments = ['']
|
|
||||||
} = this.initialData
|
} = this.initialData
|
||||||
|
|
||||||
|
const environments = (
|
||||||
|
environment_names.length > 0
|
||||||
|
? environment_names
|
||||||
|
: [""]
|
||||||
|
).map(createEnvironment)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
|
||||||
description,
|
|
||||||
environments,
|
environments,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -34,7 +38,7 @@ export default {
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
addEnvironment: function (event) {
|
addEnvironment: function (event) {
|
||||||
this.environments.push('')
|
this.environments.push(createEnvironment(""))
|
||||||
},
|
},
|
||||||
|
|
||||||
removeEnvironment: function (index) {
|
removeEnvironment: function (index) {
|
||||||
|
@ -17,7 +17,8 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.icon-tooltip {
|
.icon-tooltip {
|
||||||
padding: 0.25rem 0.5rem;
|
margin: -$gap;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
@include icon-link-vertical;
|
@include icon-link-vertical;
|
||||||
@include icon-link-color($color-red, $color-red-lightest);
|
@include icon-link-color($color-red, $color-red-lightest);
|
||||||
|
|
||||||
margin-bottom: -$gap;
|
margin-bottom: 0;
|
||||||
margin-right: -$gap;
|
margin-right: -$gap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
{% from "components/text_input.html" import TextInput %}
|
{% from "components/text_input.html" import TextInput %}
|
||||||
{% from "components/tooltip.html" import Tooltip %}
|
{% from "components/tooltip.html" import Tooltip %}
|
||||||
|
{% from "components/alert.html" import Alert %}
|
||||||
|
|
||||||
{% extends "base_workspace.html" %}
|
{% extends "base_workspace.html" %}
|
||||||
|
|
||||||
@ -24,6 +25,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="block-list project-list-item">
|
||||||
<header class="block-list__header">
|
<header class="block-list__header">
|
||||||
<h2 class="block-list__title">Project Environments</h2>
|
<h2 class="block-list__title">Project Environments</h2>
|
||||||
@ -34,10 +39,15 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(_, i) in environments" class="block-list__item">
|
<li v-for="(environment, i) in environments" class="block-list__item project-edit__env-list-item">
|
||||||
{{ TextInput(form.environment_name) }}
|
<div class="usa-input">
|
||||||
<span class="icon-link icon-link--danger icon-link--vertical" v-on:click="removeEnvironment(i)">{{ Icon('x') }} Remove</span>
|
<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>
|
</li>
|
||||||
</ul>
|
</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