Merge pull request #1229 from dod-ccpo/to-blank-state
Styling tweaks to application and TO index pages
This commit is contained in:
commit
95697db8f5
@ -3,7 +3,7 @@
|
||||
"files": "^.secrets.baseline$|^.*pgsslrootcert.yml$",
|
||||
"lines": null
|
||||
},
|
||||
"generated_at": "2019-11-26T21:33:43Z",
|
||||
"generated_at": "2019-12-03T19:44:47Z",
|
||||
"plugins_used": [
|
||||
{
|
||||
"base64_limit": 4.5,
|
||||
@ -170,7 +170,7 @@
|
||||
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
|
||||
"is_secret": false,
|
||||
"is_verified": false,
|
||||
"line_number": 657,
|
||||
"line_number": 656,
|
||||
"type": "Hex High Entropy String"
|
||||
}
|
||||
]
|
||||
|
@ -11,16 +11,6 @@ from atst.domain.authz.decorator import user_can_access_decorator as user_can
|
||||
from atst.utils.flash import formatted_flash as flash
|
||||
|
||||
|
||||
@portfolios_bp.route("/portfolios")
|
||||
def portfolios():
|
||||
portfolios = Portfolios.for_user(g.current_user)
|
||||
|
||||
if portfolios:
|
||||
return render_template("portfolios/index.html", page=5, portfolios=portfolios)
|
||||
else:
|
||||
return render_template("portfolios/blank_slate.html")
|
||||
|
||||
|
||||
@portfolios_bp.route("/portfolios/new")
|
||||
def new_portfolio():
|
||||
form = PortfolioCreationForm()
|
||||
|
@ -1,28 +1,20 @@
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 5rem ($gap * 2) 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: $gap * 3;
|
||||
max-width: 100%;
|
||||
background-color: $color-gray-lightest;
|
||||
margin-top: $gap * 5;
|
||||
|
||||
> .icon {
|
||||
@include icon-size(50);
|
||||
@include icon-color($color-gray-light);
|
||||
hr {
|
||||
margin-left: -$gap * 3;
|
||||
margin-right: -$gap * 3;
|
||||
}
|
||||
|
||||
&__message {
|
||||
font-weight: $font-bold;
|
||||
}
|
||||
&__footer {
|
||||
text-align: center;
|
||||
|
||||
&__sub-message {
|
||||
@include h4;
|
||||
|
||||
color: $color-gray;
|
||||
max-width: 100%;
|
||||
|
||||
@include media($large-screen) {
|
||||
@include h3;
|
||||
a.usa-button {
|
||||
width: 60%;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -383,6 +383,8 @@
|
||||
}
|
||||
|
||||
.portfolio-applications {
|
||||
margin-top: $gap * 5;
|
||||
|
||||
&__header {
|
||||
&--title {
|
||||
@include subheading;
|
||||
|
@ -1,7 +1,7 @@
|
||||
.sticky-cta {
|
||||
margin-left: -$gap * 4;
|
||||
margin-left: -$gap * 5;
|
||||
margin-right: -$gap * 5;
|
||||
z-index: 10;
|
||||
z-index: 1;
|
||||
background-color: $color-gray-lightest;
|
||||
border-top: 1px solid $color-gray-lighter;
|
||||
border-bottom: 1px solid $color-gray-lighter;
|
||||
|
@ -1,5 +1,6 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/empty_state.html" import EmptyState %}
|
||||
{% from "components/sticky_cta.html" import StickyCTA %}
|
||||
|
||||
{% extends "portfolios/base.html" %}
|
||||
|
||||
@ -7,29 +8,26 @@
|
||||
|
||||
{% block portfolio_content %}
|
||||
|
||||
{% call StickyCTA(text="common.applications"|translate) %}
|
||||
{% if can_create_applications and portfolio.applications %}
|
||||
<a href="{{ url_for("applications.view_new_application_step_1", portfolio_id=portfolio.id) }}" class="usa-button usa-button-primary">
|
||||
{{ "portfolios.applications.create_button"|translate }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endcall %}
|
||||
|
||||
<div class='portfolio-applications'>
|
||||
{% include "fragments/flash.html" %}
|
||||
<div class='portfolio-applications__header row'>
|
||||
<div class='portfolio-applications__header--title col col--grow'>Applications</div>
|
||||
<div class='portfolio-applications__header--actions col'>
|
||||
{% if can_create_applications %}
|
||||
<a class='icon-link' href='{{ url_for('applications.view_new_application_step_1', portfolio_id=portfolio.id) }}'>
|
||||
{{ 'portfolios.applications.add_application_text' | translate }}
|
||||
{{ Icon("plus", classes="sidenav__link-icon") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not portfolio.applications %}
|
||||
|
||||
{{ EmptyState(
|
||||
'This portfolio doesn’t have any applications',
|
||||
action_label='Add a new application' if can_create_applications else None,
|
||||
action_href=url_for('applications.create_new_application_step_1', portfolio_id=portfolio.id) if can_create_applications else None,
|
||||
icon='cloud',
|
||||
sub_message=None if can_create_applications else 'Please contact your JEDI Cloud portfolio administrator to set up a new application.',
|
||||
add_perms=can_create_applications
|
||||
header="portfolios.applications.empty_state.header"|translate,
|
||||
message="portfolios.applications.empty_state.message"|translate,
|
||||
button_text="portfolios.applications.empty_state.button_text"|translate,
|
||||
button_link=url_for("applications.view_new_application_step_1", portfolio_id=portfolio.id),
|
||||
view_only_text="portfolios.applications.empty_state.view_only_text"|translate,
|
||||
user_can_create=can_create_applications,
|
||||
) }}
|
||||
|
||||
{% else %}
|
||||
|
@ -1,20 +1,14 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
|
||||
{% macro EmptyState(message, action_label, action_href, icon=None, sub_message=None, add_perms=True) -%}
|
||||
<div class='empty-state'>
|
||||
<p class='empty-state__message'>{{ message }}</p>
|
||||
|
||||
{% if icon %}
|
||||
{{ Icon(icon) }}
|
||||
{% endif %}
|
||||
|
||||
{% if sub_message %}
|
||||
<p class='empty-state__sub-message'>{{ sub_message }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if add_perms and (action_href and action_label) %}
|
||||
<a href='{{ action_href }}' class='usa-button usa-button-big'>{{ action_label }}</a>
|
||||
{% endif %}
|
||||
|
||||
{% macro EmptyState(header, message, button_text, button_link, view_only_text, user_can_create=True) %}
|
||||
<div class="empty-state">
|
||||
<h3>{{ header }}</h3>
|
||||
<p>{{ message }}</p>
|
||||
<hr>
|
||||
<div class="empty-state__footer">
|
||||
{% if user_can_create %}
|
||||
<a href="{{ button_link }}" class="usa-button usa-button-primary">{{ button_text }}</a>
|
||||
{% else %}
|
||||
<p>{{ view_only_text }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
{% endmacro %}
|
||||
|
@ -1,15 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% from "components/empty_state.html" import EmptyState %}
|
||||
{% from "components/tooltip.html" import Tooltip %}
|
||||
|
||||
{% block content %}
|
||||
{{
|
||||
EmptyState(
|
||||
action_href="#",
|
||||
action_label=("portfolios.index.empty.start_button" | translate),
|
||||
icon="cloud",
|
||||
message=("portfolios.index.empty.title" | translate),
|
||||
)
|
||||
}}
|
||||
{% endblock %}
|
@ -15,13 +15,14 @@
|
||||
%}
|
||||
|
||||
{{ EmptyState(
|
||||
('portfolios.reports.empty_state.message' | translate),
|
||||
action_label= ('portfolios.reports.empty_state.action_label' | translate) if can_create_applications else None,
|
||||
action_href=url_for('applications.create_new_application_step_1', portfolio_id=portfolio.id) if can_create_applications else None,
|
||||
icon='chart',
|
||||
sub_message=message,
|
||||
add_perms=can_create_applications
|
||||
header='portfolios.reports.empty_state.message' | translate,
|
||||
message=message,
|
||||
button_text="portfolios.applications.empty_state.button_text"|translate,
|
||||
button_link=url_for("applications.view_new_application_step_1", portfolio_id=portfolio.id),
|
||||
view_only_text="portfolios.applications.empty_state.view_only_text"|translate,
|
||||
user_can_create=can_create_applications,
|
||||
) }}
|
||||
|
||||
{% else %}
|
||||
<spend-table
|
||||
v-bind:applications='{{ monthly_totals['applications'] | tojson }}'
|
||||
|
@ -93,9 +93,11 @@
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% call StickyCTA(text="Funding") %}
|
||||
{% if user_can(permissions.CREATE_TASK_ORDER) %}
|
||||
<a href="{{ url_for("task_orders.form_step_one_add_pdf", portfolio_id=portfolio.id) }}" class="usa-button usa-button-primary" type="submit">Start a new task order</a>
|
||||
{% call StickyCTA(text="common.task_orders"|translate) %}
|
||||
{% if user_can(permissions.CREATE_TASK_ORDER) and task_orders %}
|
||||
<a href="{{ url_for("task_orders.form_step_one_add_pdf", portfolio_id=portfolio.id) }}" class="usa-button usa-button-primary">
|
||||
{{ "task_orders.add_new_button" | translate }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endcall %}
|
||||
|
||||
@ -107,11 +109,12 @@
|
||||
{{ TaskOrderList(task_orders) }}
|
||||
{% else %}
|
||||
{{ EmptyState(
|
||||
'This portfolio doesn’t have any active or pending task orders.',
|
||||
action_label='Add a New Task Order',
|
||||
action_href=url_for('task_orders.form_step_one_add_pdf', portfolio_id=portfolio.id),
|
||||
icon='cloud',
|
||||
add_perms=user_can(permissions.CREATE_TASK_ORDER)
|
||||
header="task_orders.empty_state.header"|translate,
|
||||
message="task_orders.empty_state.message"|translate,
|
||||
button_link=url_for('task_orders.form_step_one_add_pdf', portfolio_id=portfolio.id),
|
||||
button_text="task_orders.empty_state.button_text"|translate,
|
||||
view_only_text="task_orders.empty_state.view_only_text"|translate,
|
||||
user_can_create=user_can(permissions.CREATE_TASK_ORDER),
|
||||
) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -65,32 +65,6 @@ def test_create_portfolio_failure(client, user_session):
|
||||
assert len(PortfoliosQuery.get_all()) == original_portfolio_count
|
||||
|
||||
|
||||
def test_portfolio_index_with_existing_portfolios(client, user_session):
|
||||
portfolio = PortfolioFactory.create()
|
||||
user_session(portfolio.owner)
|
||||
|
||||
response = client.get(url_for("portfolios.portfolios"))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert portfolio.name.encode("utf8") in response.data
|
||||
assert (
|
||||
translate("portfolios.index.empty.start_button").encode("utf8")
|
||||
not in response.data
|
||||
)
|
||||
|
||||
|
||||
def test_portfolio_index_without_existing_portfolios(client, user_session):
|
||||
user = UserFactory.create()
|
||||
user_session(user)
|
||||
|
||||
response = client.get(url_for("portfolios.portfolios"))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert (
|
||||
translate("portfolios.index.empty.start_button").encode("utf8") in response.data
|
||||
)
|
||||
|
||||
|
||||
def test_portfolio_reports(client, user_session):
|
||||
portfolio = PortfolioFactory.create(
|
||||
applications=[
|
||||
|
@ -26,7 +26,6 @@ _NO_ACCESS_CHECK_REQUIRED = _NO_LOGIN_REQUIRED + [
|
||||
"portfolios.accept_invitation", # available to all users; access control is built into invitation logic
|
||||
"portfolios.create_portfolio", # create a portfolio
|
||||
"portfolios.new_portfolio", # all users can create a portfolio
|
||||
"portfolios.portfolios", # the portfolios list is scoped to the user separately
|
||||
"task_orders.get_started", # all users can start a new TO
|
||||
"users.update_user", # available to all users
|
||||
"users.user", # available to all users
|
||||
|
@ -105,13 +105,6 @@ def test_protected_routes_redirect_to_login(client, app):
|
||||
assert server_name in resp.headers["Location"]
|
||||
|
||||
|
||||
def test_get_protected_route_encodes_redirect(client):
|
||||
portfolio_index = url_for("portfolios.portfolios")
|
||||
response = client.get(portfolio_index)
|
||||
redirect = url_for("atst.root", next=portfolio_index)
|
||||
assert redirect in response.headers["Location"]
|
||||
|
||||
|
||||
def test_unprotected_routes_set_user_if_logged_in(client, app, user_session):
|
||||
user = UserFactory.create()
|
||||
|
||||
|
@ -44,6 +44,7 @@ ccpo:
|
||||
alert_message: "Confirm removing CCPO superuser access from {user_name}"
|
||||
remove_button: Remove Access
|
||||
common:
|
||||
applications: Applications
|
||||
cancel: Cancel
|
||||
close: Close
|
||||
confirm: Confirm
|
||||
@ -65,6 +66,7 @@ common:
|
||||
response_label: Response required
|
||||
save: Save
|
||||
save_changes: Save Changes
|
||||
task_orders: Task Orders
|
||||
undo: Undo
|
||||
view: View
|
||||
resource_names:
|
||||
@ -308,6 +310,12 @@ portfolios:
|
||||
add_member: Add Team Member
|
||||
add_another_environment: Add another environment
|
||||
app_settings_text: App settings
|
||||
create_button: Create Application
|
||||
empty_state:
|
||||
header: You don't have any Applications yet
|
||||
message: You can manage multiple Applications within a single Portfolio as long as the funding sources are the same.
|
||||
button_text: Create Your First Application
|
||||
view_only_text: Contact your portfolio administrator to add an application.
|
||||
new:
|
||||
step_1_header: Name and Describe New Application
|
||||
step_1_button_text: "Next: Add Environments"
|
||||
@ -455,6 +463,7 @@ portfolios:
|
||||
action_label: 'Add a new application'
|
||||
|
||||
task_orders:
|
||||
add_new_button: Add New Task Order
|
||||
review:
|
||||
pdf_title: Approved Task Order
|
||||
review_your_funding: Review your funding
|
||||
@ -507,6 +516,11 @@ task_orders:
|
||||
alert_message: All task orders require a Contracting Officer signature.
|
||||
next_button: 'Confirm & Submit'
|
||||
sticky_header_text: 'Add Task Order (step {step} of 5)'
|
||||
empty_state:
|
||||
header: Add approved task orders
|
||||
message: Upload your approved Task Order here. You are required to confirm you have the appropriate signature. You will have the ability to add additional approved Task Orders with more funding to this Portfolio in the future.
|
||||
button_text: Add Task Order
|
||||
view_only_text: Contact your portfolio administrator to add a Task Order.
|
||||
new:
|
||||
form_help_text: Before you can begin work in the cloud, you'll need to complete the information below and upload your approved task order for reference by the CCPO.
|
||||
app_info:
|
||||
|
Loading…
x
Reference in New Issue
Block a user