Merge pull request #1107 from dod-ccpo/multistep-app-provisioning-design-tweaks

Multistep app provisioning design tweaks
This commit is contained in:
graham-dds 2019-10-08 13:34:25 -04:00 committed by GitHub
commit 0870959477
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 277 additions and 248 deletions

View File

@ -0,0 +1,31 @@
"""Make application description optional
Revision ID: f50596c5ffbb
Revises: e3d93f9caba7
Create Date: 2019-10-08 09:41:11.835664
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "f50596c5ffbb" # pragma: allowlist secret
down_revision = "e3d93f9caba7" # pragma: allowlist secret
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column(
"applications", "description", existing_type=sa.VARCHAR(), nullable=True
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column(
"applications", "description", existing_type=sa.VARCHAR(), nullable=False
)
# ### end Alembic commands ###

View File

@ -1,6 +1,6 @@
from .forms import BaseForm
from wtforms.fields import StringField, TextAreaField, FieldList
from wtforms.validators import Required
from wtforms.validators import Required, Optional
from atst.forms.validators import ListItemRequired, ListItemsUnique
from atst.utils.localization import translate
@ -16,7 +16,9 @@ class NameAndDescriptionForm(BaseForm):
label=translate("forms.application.name_label"), validators=[Required()]
)
description = TextAreaField(
label=translate("forms.application.description_label"), validators=[Required()]
label=translate("forms.application.description_label"),
validators=[Optional()],
filters=[lambda x: x or None],
)

View File

@ -15,7 +15,7 @@ class Application(
id = Id()
name = Column(String, nullable=False)
description = Column(String, nullable=False)
description = Column(String)
portfolio_id = Column(ForeignKey("portfolios.id"), nullable=False)
portfolio = relationship("Portfolio")

View File

@ -37,11 +37,9 @@ def render_new_application_form(
@applications_bp.route("/portfolios/<portfolio_id>/applications/new")
@applications_bp.route(
"/portfolios/<portfolio_id>/applications/<application_id>/step_1"
)
@applications_bp.route("/applications/<application_id>/new/step_1")
@user_can(Permissions.CREATE_APPLICATION, message="view create new application form")
def view_new_application_step_1(portfolio_id, application_id=None):
def view_new_application_step_1(portfolio_id=None, application_id=None):
return render_new_application_form(
"applications/new/step_1.html",
NameAndDescriptionForm,
@ -56,13 +54,12 @@ def view_new_application_step_1(portfolio_id, application_id=None):
methods=["POST"],
)
@applications_bp.route(
"/portfolios/<portfolio_id>/applications/<application_id>/step_1",
"/applications/<application_id>/new/step_1",
endpoint="update_new_application_step_1",
methods=["POST"],
)
@user_can(Permissions.CREATE_APPLICATION, message="view create new application form")
def create_or_update_new_application_step_1(portfolio_id, application_id=None):
portfolio = Portfolios.get_for_update(portfolio_id)
def create_or_update_new_application_step_1(portfolio_id=None, application_id=None):
form = get_new_application_form(
{**http_request.form}, NameAndDescriptionForm, application_id
)
@ -72,12 +69,14 @@ def create_or_update_new_application_step_1(portfolio_id, application_id=None):
if application_id:
application = Applications.get(application_id)
application = Applications.update(application, form.data)
flash("application_updated", application_name=application.name)
else:
portfolio = Portfolios.get_for_update(portfolio_id)
application = Applications.create(g.current_user, portfolio, **form.data)
flash("application_created", application_name=application.name)
return redirect(
url_for(
"applications.update_new_application_step_2",
portfolio_id=portfolio_id,
application_id=application.id,
)
)
@ -94,11 +93,9 @@ def create_or_update_new_application_step_1(portfolio_id, application_id=None):
)
@applications_bp.route(
"/portfolios/<portfolio_id>/applications/<application_id>/step_2"
)
@applications_bp.route("/applications/<application_id>/new/step_2")
@user_can(Permissions.CREATE_APPLICATION, message="view create new application form")
def view_new_application_step_2(portfolio_id, application_id):
def view_new_application_step_2(application_id):
application = Applications.get(application_id)
render_args = {
"form": EnvironmentsForm(
@ -114,11 +111,9 @@ def view_new_application_step_2(portfolio_id, application_id):
return render_template("applications/new/step_2.html", **render_args)
@applications_bp.route(
"/portfolios/<portfolio_id>/applications/<application_id>/step_2", methods=["POST"]
)
@applications_bp.route("/applications/<application_id>/new/step_2", methods=["POST"])
@user_can(Permissions.CREATE_APPLICATION, message="view create new application form")
def update_new_application_step_2(portfolio_id, application_id):
def update_new_application_step_2(application_id):
form = get_new_application_form(
{**http_request.form}, EnvironmentsForm, application_id
)
@ -137,15 +132,14 @@ def update_new_application_step_2(portfolio_id, application_id):
render_new_application_form(
"applications/new/step_2.html",
EnvironmentsForm,
portfolio_id,
application_id,
form,
application_id=application_id,
form=form,
),
400,
)
@applications_bp.route("/applications/<application_id>/step_3")
@applications_bp.route("/applications/<application_id>/new/step_3")
@user_can(Permissions.CREATE_APPLICATION, message="view create new application form")
def view_new_application_step_3(application_id):
application = Applications.get(application_id)
@ -161,7 +155,7 @@ def view_new_application_step_3(application_id):
)
@applications_bp.route("/applications/<application_id>/step_3", methods=["POST"])
@applications_bp.route("/applications/<application_id>/new/step_3", methods=["POST"])
@user_can(Permissions.CREATE_APPLICATION, message="view create new application form")
def update_new_application_step_3(application_id):

View File

@ -14,6 +14,13 @@ MESSAGES = {
""",
"category": "success",
},
"application_updated": {
"title_template": translate("flash.success"),
"message_template": """
{{ "flash.application.updated" | translate({"application_name": application_name}) }}
""",
"category": "success",
},
"application_deleted": {
"title_template": translate("flash.success"),
"message_template": """

View File

@ -138,85 +138,22 @@
color: $color-gray;
}
.responsive-table-wrapper {
padding-bottom: $gap * 3;
margin-bottom: 0;
}
table {
margin: 0;
width: 100%;
min-width: 100%;
margin-top: 0;
}
thead {
th:first-child {
padding-left: 3 * $gap;
}
tr:first-child {
padding: 0 2 * $gap 0 5 * $gap;
}
td {
font-weight: bold;
font-size: 1.4rem;
border-top: 0;
}
}
th {
background-color: $color-gray-lightest;
padding: $gap 2 * $gap;
border-top: none;
border-bottom: none;
color: $color-gray;
}
td:first-child {
padding: 2 * $gap 2 * $gap 2 * $gap 5 * $gap;
}
tbody {
td {
border-bottom: 1px solid $color-gray-lightest;
font-size: 1.6rem;
border-top: 0;
padding: 3 * $gap 2 * $gap;
.usa-button-disabled {
color: $color-gray-medium;
background-color: $color-gray-lightest;
box-shadow: inset 0 0 0 1px $color-gray-medium;
}
button {
padding: 0;
margin: 0;
font-size: 1.5rem;
width: 11rem;
height: 3rem;
}
}
.name {
font-weight: bold;
.you {
font-size: 1.2rem;
}
}
.usa-input.usa-input--success {
margin: 0;
}
select {
border: none;
}
}
.add-member-link {
text-align: right;
}
.usa-button-primary .usa-button {
padding: 2 * $gap;
float: right;
}
a.create-member {
display: inherit;
margin-left: auto;
margin-right: auto;
max-width: 50%;
margin-top: 3rem;
}
input.usa-button.usa-button-primary {

View File

@ -2,6 +2,11 @@
margin-left: -$gap * 4;
margin-right: -$gap * 5;
z-index: 10;
background-color: $color-gray-lightest;
border-top: 1px solid $color-gray-lighter;
border-bottom: 1px solid $color-gray-lighter;
padding: 0 $gap * 5 0 $gap * 5;
box-shadow: $box-shadow;
@include media($medium-screen) {
margin-right: -$gap * 5;
@ -16,12 +21,6 @@
display: flex;
align-items: center;
background-color: $color-gray-lightest;
border-top: 1px solid $color-gray-lighter;
border-bottom: 1px solid $color-gray-lighter;
padding: 0 $gap * 5 0 $gap * 5;
box-shadow: $box-shadow;
.usa-button {
margin: $gap $gap * 1.5 $gap 0;
width: 20rem;
@ -32,6 +31,12 @@
&-text {
flex-grow: 1;
display: flex;
align-items: baseline;
}
&-context {
color: $color-gray;
margin-left: $gap;
}
&-buttons {

View File

@ -120,9 +120,10 @@
@include panel-actions;
}
hr {
hr,
&__break {
border: 0;
border-bottom: 1px dashed $color-gray-light;
border-bottom: 1px solid $color-gray-light;
margin: ($gap * 4) ($site-margins * -4);
}
}

View File

@ -66,7 +66,7 @@ table.atat-table {
display: table-cell;
white-space: nowrap;
border-bottom-style: dashed;
border-bottom-style: solid;
border-top: none;
&:last-child {
@ -88,18 +88,14 @@ table.atat-table {
th,
td {
@include block-list-header;
display: table-cell;
white-space: nowrap;
color: black;
}
}
}
@at-root .panel #{&} {
tr:last-child td {
border-bottom: 0;
}
&:last-child {
margin-bottom: 0;
}
@ -138,10 +134,6 @@ table.atat-table {
}
@at-root .panel #{&} {
tr:last-child td {
border-bottom: 0;
}
&:last-child {
margin-bottom: 0;
}

View File

@ -7,14 +7,14 @@
{% set secondary_breadcrumb = 'portfolios.applications.new_application_title' | translate %}
{% if application_id %}
{% set action = url_for('applications.update_new_application_step_1', portfolio_id=portfolio.id, application_id=application_id) %}
{% set action = url_for('applications.update_new_application_step_1', application_id=application_id) %}
{% else %}
{% set action = url_for('applications.create_new_application_step_1', portfolio_id=portfolio.id, application_id=application_id) %}
{% endif %}
{% block portfolio_header %}
{% include "portfolios/header.html" %}
{{ StickyCTA(text="Name and Describe New Application") }}
{{ StickyCTA(text=('portfolios.applications.new.step_1_header' | translate | safe), context="Step 1 of 3") }}
{% endblock %}
{% block application_content %}
@ -23,30 +23,32 @@
<application-name-and-description inline-template v-bind:initial-data='{{ form.data|tojson }}'>
<form method="POST" action="{{ action }}" v-on:submit="handleSubmit">
<div class="panel">
<div class="panel__content">
{{ form.csrf_token }}
<p>
{{ "fragments.edit_application_form.explain" | translate }}
</p>
<div class="form-row">
<div class="form-col form-col--two-thirds">
{{ TextInput(form.name, optional=False) }}
{{ ('portfolios.applications.new.step_1_form_help_text.description' | translate | safe) }}
</div>
</div>
<hr class="panel__break">
<div class="form-row">
<div class="form-col form-col--two-thirds">
{{ TextInput(form.description, paragraph=True, optional=False) }}
{{ TextInput(form.description, paragraph=True, optional=True) }}
{{ ('portfolios.applications.new.step_1_form_help_text.description' | translate | safe) }}
</div>
</div>
</div>
</div>
<span class="action-group">
{% block next_button %}
{{ SaveButton(text=('portfolios.applications.new.step_1_button_text' | translate)) }}
{% endblock %}
</span>
<span class="action-group">
{% block next_button %}
{{ SaveButton(text=('portfolios.applications.new.step_1_button_text' | translate)) }}
{% endblock %}
<a class="usa-button usa-button-secondary" href="{{ url_for('applications.portfolio_applications', portfolio_id=portfolio.id) }}">
Cancel
</a>
</span>
</form>
</application-name-and-description>

View File

@ -8,68 +8,74 @@
{% set secondary_breadcrumb = 'portfolios.applications.new_application_title' | translate %}
{% block portfolio_header %}
{{ StickyCTA(text=application.name) }}
{% include "portfolios/header.html" %}
{{ StickyCTA(text=('portfolios.applications.new.step_2_header' | translate({"application_name": application.name}) ), context="Step 2 of 3") }}
{% endblock %}
{% block application_content %}
{% set modalName = "newApplicationConfirmation" %}
{% include "fragments/flash.html" %}
<application-environments inline-template v-bind:initial-data='{{ form.data|tojson }}'>
<form method="POST" action="{{ url_for('applications.update_new_application_step_2', portfolio_id=portfolio.id, application_id=application.id) }}" v-on:submit="handleSubmit">
<div class="panel">
<div class="panel__content">
{{ form.csrf_token }}
<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 class="panel__content">
<p>
{{ 'portfolios.applications.new.step_2_description' | translate }}
</p>
<hr class="panel__break">
<application-environments inline-template v-bind:initial-data='{{ form.data|tojson }}'>
<form method="POST" action="{{ url_for('applications.update_new_application_step_2', portfolio_id=portfolio.id, application_id=application.id) }}" v-on:submit="handleSubmit">
<div class="subheading">{{ 'portfolios.applications.environments_heading' | translate }}</div>
<div class="panel">
<div class="panel__content">
{{ form.csrf_token }}
<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>
<div class="application-list-item">
<header>
<h2 class="block-list__title">{{ 'portfolios.applications.environments_heading' | translate }}</h2>
<p>
{{ 'portfolios.applications.environments_description' | translate }}
</p>
</header>
<div class="application-list-item">
<ul>
<li v-for="(environment, i) in environments" class="application-edit__env-list-item">
<div class="usa-input">
<label :for="'environment_names-' + i">Environment Name</label>
<input type="text" :id="'environment_names-' + i" v-model="environment.name" @input="onInput" placeholder="e.g. Development, Staging, Production"/> <input type="hidden" :name="'environment_names-' + i" v-model="environment.name"/>
</div>
<div class="application-edit__env-list-item-block">
<button v-on:click="removeEnvironment(i)" v-if="environments.length > 1" type="button" class="application-edit__env-list-item__remover">
{{ Icon('trash') }}
<span>Remove</span>
</button>
</div>
</li>
</ul>
<ul>
<li v-for="(environment, i) in environments" class="application-edit__env-list-item">
<div class="usa-input">
<label :for="'environment_names-' + i">Environment Name</label>
<input type="text" :id="'environment_names-' + i" v-model="environment.name" @input="onInput" placeholder="e.g. Development, Staging, Production"/> <input type="hidden" :name="'environment_names-' + i" v-model="environment.name"/>
</div>
<div class="application-edit__env-list-item-block">
<button v-on:click="removeEnvironment(i)" v-if="environments.length > 1" type="button" class="application-edit__env-list-item__remover">
{{ Icon('trash') }}
<span>Remove</span>
</button>
</div>
</li>
</ul>
<div class="block-list__footer">
<button
v-on:click="addEnvironment"
class="icon-link"
tabindex="0"
type="button">
{{ 'portfolios.applications.add_another_environment' | translate }}
{{ Icon("plus") }}
</button>
</div>
<div class="block-list__footer">
<button
v-on:click="addEnvironment"
class="icon-link"
tabindex="0"
type="button">
{{ 'portfolios.applications.add_another_environment' | translate }}
{{ Icon("plus") }}
</button>
</div>
</div>
</div>
</div>
<span class="action-group">
{% block next_button %}
{{ SaveButton(text=('portfolios.applications.new.step_2_button_text' | translate)) }}
{% endblock %}
</span>
</form>
</application-environments>
<span class="action-group">
{% block next_button %}
{{ SaveButton(text=('portfolios.applications.new.step_2_button_text' | translate)) }}
{% endblock %}
<a class="usa-button usa-button-secondary" href="{{ url_for('applications.view_new_application_step_1', application_id=application.id) }}">
Previous
</a>
<a href="{{ url_for('applications.portfolio_applications', portfolio_id=portfolio.id) }}">
Cancel
</a>
</span>
</form>
</application-environments>
</div>
{% endblock %}

View File

@ -6,21 +6,36 @@
{% block portfolio_header %}
{% include "portfolios/header.html" %}
{{ StickyCTA(text=application.name) }}
{{ StickyCTA(text=('portfolios.applications.new.step_3_header' | translate({"application_name": application.name}) ), context="Step 3 of 3") }}
{% endblock %}
{% block application_content %}
{{ MemberManagementTemplate(
application,
members,
new_member_form,
"applications.update_new_application_step_3",
user_can(permissions.CREATE_APPLICATION_MEMBER)) }}
{% include "fragments/flash.html" %}
<div class="panel__content">
<p>
{{ ('portfolios.applications.new.step_3_description' | translate) }}
</p>
<hr class="panel__break">
<span class="action-group">
<a class="usa-button" href="{{ url_for('applications.settings', application_id=application_id) }}">
Return to Application Settings
</a>
</span>
{{ MemberManagementTemplate(
application,
members,
new_member_form,
"applications.update_new_application_step_3",
user_can(permissions.CREATE_APPLICATION_MEMBER)) }}
<span class="action-group">
<a class="usa-button" href="{{ url_for('applications.settings', application_id=application_id) }}">
Return to Application Settings
</a>
<a class="usa-button usa-button-secondary" href="{{ url_for('applications.view_new_application_step_2', application_id=application.id) }}">
Previous
</a>
<a href="{{ url_for('applications.portfolio_applications', portfolio_id=portfolio.id) }}">
Cancel
</a>
</span>
</div>
{% endblock %}

View File

@ -32,7 +32,7 @@
<div class="form-row">
<div class="form-col form-col--two-thirds">
{{ TextInput(application_form.name, optional=False) }}
{{ TextInput(application_form.description, paragraph=True, optional=False) }}
{{ TextInput(application_form.description, paragraph=True, optional=True) }}
</div>
</div>
</div>
@ -81,14 +81,9 @@
"applications.create_member",
user_can(permissions.CREATE_APPLICATION_MEMBER)) }}
<div class='subheading'>
<div class="subheading">
{{ 'common.resource_names.environments' | translate }}
{% if user_can(permissions.CREATE_ENVIRONMENT) %}
{% include "applications/fragments/add_new_environment.html" %}
{% endif %}
</div>
<div class="panel">
{% if g.matchesPath("application-environments") %}
{% include "fragments/flash.html" %}
@ -178,6 +173,11 @@
</ul>
</div>
</div>
{% if user_can(permissions.CREATE_ENVIRONMENT) %}
<div class='subheading'>
{% include "applications/fragments/add_new_environment.html" %}
</div>
{% endif %}
</div>
<hr>

View File

@ -1,23 +1,26 @@
{% from 'components/icon.html' import Icon %}
{% macro StickyCTA(text, return_link_url=None, return_link_text=None) -%}
<div class="sticky-cta" v-sticky='{ "stickyBitStickyOffset": 76 }'>
<div class="sticky-cta-container">
<div class="sticky-cta-text">
{% if return_link_url and return_link_text %}
<div class="sticky-cta-return-link">
<a href="{{ return_link_url }}">
{{ Icon('caret_left', classes="icon--tiny icon--blue") }} {{ return_link_text}}
</a>
</div>
{% endif %}
<h3>{{ text }}</h3>
</div>
{% if caller %}
<div class="sticky-cta-buttons">
{{ caller() }}
</div>
{% endif %}
</div>
{% macro StickyCTA(text, context=None, return_link_url=None, return_link_text=None) -%}
<div class="sticky-cta" v-sticky='{ "stickyBitStickyOffset": 76 }'>
{% if return_link_url and return_link_text %}
<div class="sticky-cta-return-link">
<a href="{{ return_link_url }}">
{{ Icon('caret_left', classes="icon--tiny icon--blue") }} {{ return_link_text}}
</a>
</div>
{% endif %}
<div class="sticky-cta-container">
<div class="sticky-cta-text">
<h3>{{ text }}</h3>
{% if context %}
<span class="sticky-cta-context"> {{ context }} </span>
{% endif %}
</div>
{% if caller %}
<div class="sticky-cta-buttons">
{{ caller() }}
</div>
{% endif %}
</div>
</div>
{%- endmacro %}

View File

@ -15,10 +15,16 @@
) %}
{% include "fragments/flash.html" %}
{% if g.matchesPath("application-members") %}
{% include "fragments/flash.html" %}
{% endif %}
<div class="subheading">
{{ 'portfolios.applications.settings.team_members' | translate }}
</div>
<div class="panel">
{% if not application.members %}
<div class='empty-state'>
<div class='empty-state panel__content'>
<p class='empty-state__message'>{{ ("portfolios.applications.team_settings.blank_slate.title" | translate) }}</p>
{{ Icon('avatar') }}
@ -45,21 +51,9 @@
</div>
{% else %}
<div class='subheading'>
{{ 'portfolios.applications.settings.team_members' | translate }}
{% set new_member_modal_name = "add-app-mem" %}
{% set new_member_modal_name = "add-app-mem" %}
{% if user_can_create_app_member %}
<a class="icon-link modal-link icon-link__add" v-on:click="openModal('{{ new_member_modal_name }}')">
{{ Icon("plus") }}
{{ "portfolios.applications.add_member" | translate }}
</a>
{% endif %}
</div>
<section class="member-list application-list" id="application-members">
<div class='responsive-table-wrapper panel'>
{% for member in members %}
{% for member in members %}
{% set modal_name = "edit_member-{}".format(loop.index) %}
{% call Modal(modal_name) %}
<div class="modal__form--header">
@ -78,11 +72,15 @@
</base-form>
{% endcall %}
{% endfor %}
<table>
<section class="member-list application-list" id="application-members">
<div class='responsive-table-wrapper'>
<table class="atat-table">
<thead>
<tr>
<th>Member</th>
<th>Project Permissions</th>
<th>Application Permissions</th>
<th>Environment Access</th>
<th></th>
</tr>
@ -92,7 +90,7 @@
{% set modal_name = "edit_member-{}".format(loop.index) %}
<tr>
<td>
{{ member.user_name }}
<strong>{{ member.user_name }}</strong>
<a class="icon-link" v-on:click="openModal('{{ modal_name }}')">
{{ Icon('edit') }}
</a>
@ -123,6 +121,11 @@
{% endfor %}
</tbody>
</table>
{% if user_can_create_app_member %}
<a class="usa-button usa-button-secondary create-member" v-on:click="openModal('{{ new_member_modal_name }}')">
{{ "portfolios.applications.add_member" | translate }}
</a>
{% endif %}
</div>
{% if user_can_create_app_member %}
@ -139,5 +142,6 @@
{% endif %}
</section>
{% endif %}
</div>
{% endmacro %}

View File

@ -107,6 +107,7 @@ email:
flash:
application:
created: 'You have successfully created the {application_name} application.'
updated: 'You have successfully updated the {application_name} application.'
deleted: 'You have successfully deleted the {application_name} application. To view the retained activity log, visit the portfolio administration page.'
delete_member_success: 'You have successfully deleted {member_name} from the portfolio.'
deleted_member: Portfolio member deleted
@ -308,8 +309,38 @@ portfolios:
add_another_environment: Add another environment
app_settings_text: App settings
new:
step_1_header: Name and Describe New Application
step_1_button_text: "Save and Add Environments"
step_1_form_help_text:
name: |
<div style="margin-top: -3rem;">
<p>
The name of your application should be intuitive and easily recognizable for all of your team members.
</p>
<p>
<strong>Writer's Block? A naming example includes:</strong>
<ul>
<li>Army Security Infrastructure Application</li>
</ul>
</p>
</div>
description: |
<div style="margin-top: -3rem;">
<p>
Add a brief one to two sentence description of your application. You should be able to reference your TO Description of Work.
</p>
<p>
<strong>Writer's Block? A naming example includes:</strong>
<ul>
<li>Build security applications for FOB Clark</li>
</ul>
</p>
</div>
step_2_header: Add Environments to {application_name}
step_2_description: "Production, Staging, Testing, and Development environments are included by default. However, you can add, edit, and delete environments based on the needs of your Application."
step_2_button_text: "Save and Add Members"
step_3_header: Invite Members to {application_name}
step_3_description: "To proceed, you will need each member's email address and DOD ID. Within this section, you will also assign application-level permissions and environment-level roles for each member."
step_3_button_text: Save Application
create_new_env: Create a new environment.
create_new_env_info: Creating an environment gives you access to the Cloud Service Provider. This environment will function within the constraints of the task order, and any costs will be billed against the portfolio.
@ -333,8 +364,7 @@ portfolios:
delete:
button: Delete environment
edit_name: Edit name
environments_description: Each environment created within an application is logically separated from one another for easier management and security.
environments_heading: Application environments
environments_heading: Application Environments
existing_application_title: '{application_name} Application Settings'
member_count: '{count} members'
new_application_title: New Application