Merge pull request #398 from dod-ccpo/rename-project-#158473663
Rename project #158473663
This commit is contained in:
commit
d3cb7e3dd1
@ -64,3 +64,15 @@ class Projects(object):
|
|||||||
raise NotFoundError("projects")
|
raise NotFoundError("projects")
|
||||||
|
|
||||||
return projects
|
return projects
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls, user, workspace, project, new_data):
|
||||||
|
if "name" in new_data:
|
||||||
|
project.name = new_data["name"]
|
||||||
|
if "description" in new_data:
|
||||||
|
project.description = new_data["description"]
|
||||||
|
|
||||||
|
db.session.add(project)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return project
|
||||||
|
@ -4,12 +4,14 @@ from wtforms.validators import Required
|
|||||||
from atst.forms.validators import ListItemRequired, ListItemsUnique
|
from atst.forms.validators import ListItemRequired, ListItemsUnique
|
||||||
|
|
||||||
|
|
||||||
class NewProjectForm(FlaskForm):
|
class ProjectForm(FlaskForm):
|
||||||
|
|
||||||
EMPTY_ENVIRONMENT_NAMES = ["", None]
|
|
||||||
|
|
||||||
name = StringField(label="Project Name", validators=[Required()])
|
name = StringField(label="Project Name", validators=[Required()])
|
||||||
description = TextAreaField(label="Description", validators=[Required()])
|
description = TextAreaField(label="Description", validators=[Required()])
|
||||||
|
|
||||||
|
|
||||||
|
class NewProjectForm(ProjectForm):
|
||||||
|
EMPTY_ENVIRONMENT_NAMES = ["", None]
|
||||||
|
|
||||||
environment_names = FieldList(
|
environment_names = FieldList(
|
||||||
StringField(label="Environment Name"),
|
StringField(label="Environment Name"),
|
||||||
validators=[
|
validators=[
|
@ -17,7 +17,7 @@ from atst.domain.workspaces import Workspaces
|
|||||||
from atst.domain.workspace_users import WorkspaceUsers
|
from atst.domain.workspace_users import WorkspaceUsers
|
||||||
from atst.domain.environments import Environments
|
from atst.domain.environments import Environments
|
||||||
from atst.domain.environment_roles import EnvironmentRoles
|
from atst.domain.environment_roles import EnvironmentRoles
|
||||||
from atst.forms.new_project import NewProjectForm
|
from atst.forms.project import NewProjectForm, ProjectForm
|
||||||
from atst.forms.new_member import NewMemberForm
|
from atst.forms.new_member import NewMemberForm
|
||||||
from atst.forms.edit_member import EditMemberForm
|
from atst.forms.edit_member import EditMemberForm
|
||||||
from atst.forms.workspace import WorkspaceForm
|
from atst.forms.workspace import WorkspaceForm
|
||||||
@ -181,17 +181,34 @@ def create_project(workspace_id):
|
|||||||
def edit_project(workspace_id, project_id):
|
def edit_project(workspace_id, project_id):
|
||||||
workspace = Workspaces.get_for_update_projects(g.current_user, workspace_id)
|
workspace = Workspaces.get_for_update_projects(g.current_user, workspace_id)
|
||||||
project = Projects.get(g.current_user, workspace, project_id)
|
project = Projects.get(g.current_user, workspace, project_id)
|
||||||
form = NewProjectForm(
|
form = ProjectForm(name=project.name, description=project.description)
|
||||||
name=project.name,
|
|
||||||
environment_names=[env.name for env in project.environments],
|
|
||||||
description=project.description,
|
|
||||||
)
|
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"workspaces/projects/edit.html", workspace=workspace, project=project, form=form
|
"workspaces/projects/edit.html", workspace=workspace, project=project, form=form
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/workspaces/<workspace_id>/projects/<project_id>/edit", methods=["POST"])
|
||||||
|
def update_project(workspace_id, project_id):
|
||||||
|
workspace = Workspaces.get_for_update_projects(g.current_user, workspace_id)
|
||||||
|
project = Projects.get(g.current_user, workspace, project_id)
|
||||||
|
form = ProjectForm(http_request.form)
|
||||||
|
if form.validate():
|
||||||
|
project_data = form.data
|
||||||
|
Projects.update(g.current_user, workspace, project, project_data)
|
||||||
|
|
||||||
|
return redirect(
|
||||||
|
url_for("workspaces.workspace_projects", workspace_id=workspace.id)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return render_template(
|
||||||
|
"workspaces/projects/edit.html",
|
||||||
|
workspace=workspace,
|
||||||
|
project=project,
|
||||||
|
form=form,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/workspaces/<workspace_id>/members/new")
|
@bp.route("/workspaces/<workspace_id>/members/new")
|
||||||
def new_member(workspace_id):
|
def new_member(workspace_id):
|
||||||
workspace = Workspaces.get(g.current_user, workspace_id)
|
workspace = Workspaces.get(g.current_user, workspace_id)
|
||||||
|
@ -133,6 +133,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input:read-only {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
.usa-input__choices { // checkbox & radio sets
|
.usa-input__choices { // checkbox & radio sets
|
||||||
legend {
|
legend {
|
||||||
padding: 0 0 $gap 0;
|
padding: 0 0 $gap 0;
|
||||||
|
@ -1,85 +1,19 @@
|
|||||||
{% from "components/icon.html" import Icon %}
|
|
||||||
{% from "components/modal.html" import Modal %}
|
|
||||||
{% from "components/text_input.html" import TextInput %}
|
{% from "components/text_input.html" import TextInput %}
|
||||||
{% from "components/alert.html" import Alert %}
|
{% from "components/alert.html" import Alert %}
|
||||||
|
|
||||||
<new-project inline-template v-bind:initial-data='{{ form.data|tojson }}' modal-name='{{ modalName }}'>
|
{% set title_text = 'Edit {} project'.format(project.name) if project else 'Add a new project' %}
|
||||||
{% set new_project = project is not defined %}
|
|
||||||
{% set form_action = url_for('workspaces.create_project', workspace_id=workspace.id) if new_project else url_for('workspaces.edit_project', workspace_id=workspace.id, project_id=project.id) %}
|
|
||||||
{% set action_text = 'Create' if new_project else 'Update' %}
|
|
||||||
{% set title_text = 'Add a new project' if new_project else 'Edit {} project'.format(project.name) %}
|
|
||||||
|
|
||||||
<form method="POST" action="{{ form_action }}" v-on:submit="handleSubmit">
|
{{ form.csrf_token }}
|
||||||
{% call Modal(name=modalName, dismissable=False) %}
|
<div class="panel">
|
||||||
<h1>{{ action_text }} project !{ name }</h1>
|
<div class="panel__heading panel__heading--grow">
|
||||||
|
<h1>{{ title_text }}</h1>
|
||||||
<p>
|
</div>
|
||||||
When you click <em>{{ action_text }} Project</em>, the environments
|
|
||||||
<span v-for="(environment, index) in environments">
|
|
||||||
<strong>!{environment.name}</strong><template v-if="index < (environments.length - 1)">, </template>
|
|
||||||
</span>
|
|
||||||
will be created as individual cloud resource groups under <strong>!{ name }</strong> project.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class='action-group'>
|
|
||||||
<button autofocus type='submit' class='action-group__action usa-button' tabindex='0'>{{ action_text }} Project</button>
|
|
||||||
<button type='button' v-on:click="handleCancelSubmit" class='icon-link action-group__action' tabindex='0'>Cancel</button>
|
|
||||||
</div>
|
|
||||||
{% endcall %}
|
|
||||||
|
|
||||||
{{ form.csrf_token }}
|
|
||||||
<div class="panel">
|
|
||||||
<div class="panel__heading panel__heading--grow">
|
|
||||||
<h1>{{ title_text }}</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel__content">
|
|
||||||
<p>
|
|
||||||
AT-AT allows you to organize your workspace into multiple projects, each of which may have environments.
|
|
||||||
</p>
|
|
||||||
{{ TextInput(form.name) }}
|
|
||||||
{{ TextInput(form.description, paragraph=True) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div> {# this extra div prevents this bug: https://www.pivotaltracker.com/story/show/160768940 #}
|
|
||||||
<div v-cloak v-for="title in errors" :key="title">
|
|
||||||
{{ Alert(message=None, level="error", vue_template=True) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="block-list project-list-item">
|
|
||||||
<header class="block-list__header block-list__header--grow">
|
|
||||||
<h2 class="block-list__title">Project Environments</h2>
|
|
||||||
<p>
|
|
||||||
Each environment created within a project is an enclave of cloud resources that is logically separated from each other for increased security.
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<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" placeholder="e.g. Development, Staging, Production">
|
|
||||||
</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>
|
|
||||||
|
|
||||||
<div class="block-list__footer">
|
|
||||||
<button v-on:click="addEnvironment" class="icon-link" tabindex="0" type="button">Add another environment</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="action-group">
|
|
||||||
<button class="usa-button usa-button-primary" tabindex="0" type="submit">{{ action_text }} Project</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</new-project>
|
|
||||||
|
|
||||||
|
<div class="panel__content">
|
||||||
|
<p>
|
||||||
|
AT-AT allows you to organize your workspace into multiple projects, each of which may have environments.
|
||||||
|
</p>
|
||||||
|
{{ TextInput(form.name) }}
|
||||||
|
{{ TextInput(form.description, paragraph=True) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@ -1,15 +1,36 @@
|
|||||||
{% extends "workspaces/base.html" %}
|
{% extends "workspaces/base.html" %}
|
||||||
|
|
||||||
{% from "components/alert.html" import Alert %}
|
{% from "components/text_input.html" import TextInput %}
|
||||||
{% from "components/icon.html" import Icon %}
|
|
||||||
|
|
||||||
{% block workspace_content %}
|
{% block workspace_content %}
|
||||||
|
|
||||||
{% if g.dev %}
|
<form method="POST" action="{{ url_for('workspaces.edit_project', workspace_id=workspace.id, project_id=project.id) }}">
|
||||||
{{ Alert("In Progress", message="This page is a work in progress. You won't be able to edit environments on this project just yet.", level="warning") }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% set modalName = "updateProjectConfirmation" %}
|
{% include "fragments/edit_project_form.html" %}
|
||||||
{% include "fragments/edit_project_form.html" %}
|
|
||||||
|
<div class="block-list project-list-item">
|
||||||
|
<header class="block-list__header block-list__header--grow">
|
||||||
|
<h2 class="block-list__title">Project Environments</h2>
|
||||||
|
<p>
|
||||||
|
Each environment created within a project is an enclave of cloud resources that is logically separated from each other for increased security.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for environment in project.environments %}
|
||||||
|
<li class="block-list__item project-edit__env-list-item">
|
||||||
|
<div class="usa-input">
|
||||||
|
<label>Environment Name</label>
|
||||||
|
<input type="text" value="{{ environment.name }}" readonly />
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-group">
|
||||||
|
<button class="usa-button usa-button-primary" tabindex="0" type="submit">Update Project</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,19 +1,80 @@
|
|||||||
{% extends "workspaces/base.html" %}
|
{% extends "workspaces/base.html" %}
|
||||||
|
|
||||||
{% from "components/alert.html" import Alert %}
|
{% from "components/alert.html" import Alert %}
|
||||||
|
{% from "components/icon.html" import Icon %}
|
||||||
|
{% from "components/modal.html" import Modal %}
|
||||||
|
{% from "components/text_input.html" import TextInput %}
|
||||||
|
|
||||||
{% block workspace_content %}
|
{% block workspace_content %}
|
||||||
|
|
||||||
{% set modalName = "newProjectConfirmation" %}
|
{% set modalName = "newProjectConfirmation" %}
|
||||||
{% if request.args.get("newWorkspace") %}
|
{% if request.args.get("newWorkspace") %}
|
||||||
{{ Alert('Workspace created!',
|
{{ Alert('Workspace created!',
|
||||||
message="\
|
message="\
|
||||||
<p>You are now ready to create projects and environments within the JEDI Cloud.</p>
|
<p>You are now ready to create projects and environments within the JEDI Cloud.</p>
|
||||||
",
|
",
|
||||||
level='success'
|
level='success'
|
||||||
) }}
|
) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% include "fragments/edit_project_form.html" %}
|
<new-project inline-template v-bind:initial-data='{{ form.data|tojson }}' modal-name='{{ modalName }}'>
|
||||||
|
<form method="POST" action="{{ url_for('workspaces.create_project', workspace_id=workspace.id) }}" v-on:submit="handleSubmit">
|
||||||
|
|
||||||
|
{% call Modal(name=modalName, dismissable=False) %}
|
||||||
|
<h1>Create project !{ name }</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
When you click <em>Create Project</em>, the environments
|
||||||
|
<span v-for="(environment, index) in environments">
|
||||||
|
<strong>!{environment.name}</strong><template v-if="index < (environments.length - 1)">, </template>
|
||||||
|
</span>
|
||||||
|
will be created as individual cloud resource groups under <strong>!{ name }</strong> project.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class='action-group'>
|
||||||
|
<button autofocus type='submit' class='action-group__action usa-button' tabindex='0'>Create Project</button>
|
||||||
|
<button type='button' v-on:click="handleCancelSubmit" class='icon-link action-group__action' tabindex='0'>Cancel</button>
|
||||||
|
</div>
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
{% include "fragments/edit_project_form.html" %}
|
||||||
|
|
||||||
|
<div> {# this extra div prevents this bug: https://www.pivotaltracker.com/story/show/160768940 #}
|
||||||
|
<div v-cloak v-for="title in errors" :key="title">
|
||||||
|
{{ Alert(message=None, level="error", vue_template=True) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block-list project-list-item">
|
||||||
|
<header class="block-list__header block-list__header--grow">
|
||||||
|
<h2 class="block-list__title">Project Environments</h2>
|
||||||
|
<p>
|
||||||
|
Each environment created within a project is an enclave of cloud resources that is logically separated from each other for increased security.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<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" placeholder="e.g. Development, Staging, Production">
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<div class="block-list__footer">
|
||||||
|
<button v-on:click="addEnvironment" class="icon-link" tabindex="0" type="button">Add another environment</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-group">
|
||||||
|
<button class="usa-button usa-button-primary" tabindex="0" type="submit">Create Project</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</new-project>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -24,3 +24,34 @@ def test_workspace_owner_can_view_environments():
|
|||||||
project = Projects.get(owner, workspace, workspace.projects[0].id)
|
project = Projects.get(owner, workspace, workspace.projects[0].id)
|
||||||
|
|
||||||
assert len(project.environments) == 2
|
assert len(project.environments) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_only_update_name_and_description():
|
||||||
|
owner = UserFactory.create()
|
||||||
|
workspace = WorkspaceFactory.create(
|
||||||
|
owner=owner,
|
||||||
|
projects=[
|
||||||
|
{
|
||||||
|
"name": "Project 1",
|
||||||
|
"description": "a project",
|
||||||
|
"environments": [{"name": "dev"}],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
project = Projects.get(owner, workspace, workspace.projects[0].id)
|
||||||
|
env_name = project.environments[0].name
|
||||||
|
Projects.update(
|
||||||
|
owner,
|
||||||
|
workspace,
|
||||||
|
project,
|
||||||
|
{
|
||||||
|
"name": "New Name",
|
||||||
|
"description": "a new project",
|
||||||
|
"environment_name": "prod",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert project.name == "New Name"
|
||||||
|
assert project.description == "a new project"
|
||||||
|
assert len(project.environments) == 1
|
||||||
|
assert project.environments[0].name == env_name
|
||||||
|
@ -115,6 +115,66 @@ def test_view_edit_project(client, user_session):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_with_permission_can_update_project(client, user_session):
|
||||||
|
owner = UserFactory.create()
|
||||||
|
workspace = WorkspaceFactory.create(
|
||||||
|
owner=owner,
|
||||||
|
projects=[
|
||||||
|
{
|
||||||
|
"name": "Awesome Project",
|
||||||
|
"description": "It's really awesome!",
|
||||||
|
"environments": [{"name": "dev"}, {"name": "prod"}],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
project = workspace.projects[0]
|
||||||
|
user_session(owner)
|
||||||
|
response = client.post(
|
||||||
|
url_for(
|
||||||
|
"workspaces.update_project",
|
||||||
|
workspace_id=workspace.id,
|
||||||
|
project_id=project.id,
|
||||||
|
),
|
||||||
|
data={"name": "Really Cool Project", "description": "A very cool project."},
|
||||||
|
follow_redirects=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert project.name == "Really Cool Project"
|
||||||
|
assert project.description == "A very cool project."
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_without_permission_cannot_update_project(client, user_session):
|
||||||
|
dev = UserFactory.create()
|
||||||
|
owner = UserFactory.create()
|
||||||
|
workspace = WorkspaceFactory.create(
|
||||||
|
owner=owner,
|
||||||
|
members=[{"user": dev, "role_name": "developer"}],
|
||||||
|
projects=[
|
||||||
|
{
|
||||||
|
"name": "Great Project",
|
||||||
|
"description": "Cool stuff happening here!",
|
||||||
|
"environments": [{"name": "dev"}, {"name": "prod"}],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
project = workspace.projects[0]
|
||||||
|
user_session(dev)
|
||||||
|
response = client.post(
|
||||||
|
url_for(
|
||||||
|
"workspaces.update_project",
|
||||||
|
workspace_id=workspace.id,
|
||||||
|
project_id=project.id,
|
||||||
|
),
|
||||||
|
data={"name": "New Name", "description": "A new description."},
|
||||||
|
follow_redirects=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert project.name == "Great Project"
|
||||||
|
assert project.description == "Cool stuff happening here!"
|
||||||
|
|
||||||
|
|
||||||
def test_create_member(client, user_session):
|
def test_create_member(client, user_session):
|
||||||
owner = UserFactory.create()
|
owner = UserFactory.create()
|
||||||
user = UserFactory.create()
|
user = UserFactory.create()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user