Rename project #158473663
This commit is contained in:
leigh-mil 2018-10-23 16:59:11 -04:00 committed by GitHub
commit d3cb7e3dd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 249 additions and 107 deletions

View File

@ -64,3 +64,15 @@ class Projects(object):
raise NotFoundError("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

View File

@ -4,12 +4,14 @@ from wtforms.validators import Required
from atst.forms.validators import ListItemRequired, ListItemsUnique
class NewProjectForm(FlaskForm):
EMPTY_ENVIRONMENT_NAMES = ["", None]
class ProjectForm(FlaskForm):
name = StringField(label="Project Name", validators=[Required()])
description = TextAreaField(label="Description", validators=[Required()])
class NewProjectForm(ProjectForm):
EMPTY_ENVIRONMENT_NAMES = ["", None]
environment_names = FieldList(
StringField(label="Environment Name"),
validators=[

View File

@ -17,7 +17,7 @@ from atst.domain.workspaces import Workspaces
from atst.domain.workspace_users import WorkspaceUsers
from atst.domain.environments import Environments
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.edit_member import EditMemberForm
from atst.forms.workspace import WorkspaceForm
@ -181,17 +181,34 @@ def create_project(workspace_id):
def edit_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 = NewProjectForm(
name=project.name,
environment_names=[env.name for env in project.environments],
description=project.description,
)
form = ProjectForm(name=project.name, description=project.description)
return render_template(
"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")
def new_member(workspace_id):
workspace = Workspaces.get(g.current_user, workspace_id)

View File

@ -133,6 +133,10 @@
}
}
input:read-only {
color: grey;
}
.usa-input__choices { // checkbox & radio sets
legend {
padding: 0 0 $gap 0;

View File

@ -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/alert.html" import Alert %}
<new-project inline-template v-bind:initial-data='{{ form.data|tojson }}' modal-name='{{ modalName }}'>
{% 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) %}
{% set title_text = 'Edit {} project'.format(project.name) if project else 'Add a new project' %}
<form method="POST" action="{{ form_action }}" v-on:submit="handleSubmit">
{% call Modal(name=modalName, dismissable=False) %}
<h1>{{ action_text }} project !{ name }</h1>
<p>
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>
{{ 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>

View File

@ -1,15 +1,36 @@
{% extends "workspaces/base.html" %}
{% from "components/alert.html" import Alert %}
{% from "components/icon.html" import Icon %}
{% from "components/text_input.html" import TextInput %}
{% block workspace_content %}
{% if g.dev %}
{{ 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 %}
<form method="POST" action="{{ url_for('workspaces.edit_project', workspace_id=workspace.id, project_id=project.id) }}">
{% 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 %}

View File

@ -1,19 +1,80 @@
{% extends "workspaces/base.html" %}
{% 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 %}
{% set modalName = "newProjectConfirmation" %}
{% if request.args.get("newWorkspace") %}
{{ Alert('Workspace created!',
message="\
<p>You are now ready to create projects and environments within the JEDI Cloud.</p>
",
level='success'
) }}
{% endif %}
{% set modalName = "newProjectConfirmation" %}
{% if request.args.get("newWorkspace") %}
{{ Alert('Workspace created!',
message="\
<p>You are now ready to create projects and environments within the JEDI Cloud.</p>
",
level='success'
) }}
{% 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 %}

View File

@ -24,3 +24,34 @@ def test_workspace_owner_can_view_environments():
project = Projects.get(owner, workspace, workspace.projects[0].id)
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

View File

@ -115,6 +115,66 @@ def test_view_edit_project(client, user_session):
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):
owner = UserFactory.create()
user = UserFactory.create()