Merge pull request #1232 from dod-ccpo/to-index-page-redesign
TO and App index pages redesign (Part 1)
This commit is contained in:
commit
2af99da9cf
@ -3,9 +3,10 @@ from flask import current_app as app
|
|||||||
|
|
||||||
from atst.database import db
|
from atst.database import db
|
||||||
from atst.models import (
|
from atst.models import (
|
||||||
EnvironmentRole,
|
|
||||||
ApplicationRole,
|
|
||||||
Environment,
|
Environment,
|
||||||
|
EnvironmentRole,
|
||||||
|
Application,
|
||||||
|
ApplicationRole,
|
||||||
ApplicationRoleStatus,
|
ApplicationRoleStatus,
|
||||||
)
|
)
|
||||||
from atst.domain.exceptions import NotFoundError
|
from atst.domain.exceptions import NotFoundError
|
||||||
@ -126,3 +127,15 @@ class EnvironmentRoles(object):
|
|||||||
.one_or_none()
|
.one_or_none()
|
||||||
)
|
)
|
||||||
return existing_env_role
|
return existing_env_role
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def for_user(cls, user_id, portfolio_id):
|
||||||
|
return (
|
||||||
|
db.session.query(EnvironmentRole)
|
||||||
|
.join(ApplicationRole)
|
||||||
|
.join(Application)
|
||||||
|
.filter(Application.portfolio_id == portfolio_id)
|
||||||
|
.filter(ApplicationRole.application_id == Application.id)
|
||||||
|
.filter(ApplicationRole.user_id == user_id)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
from flask import render_template
|
from flask import render_template, g
|
||||||
|
|
||||||
from .blueprint import applications_bp
|
from .blueprint import applications_bp
|
||||||
from atst.domain.authz.decorator import user_can_access_decorator as user_can
|
from atst.domain.authz.decorator import user_can_access_decorator as user_can
|
||||||
|
from atst.domain.environment_roles import EnvironmentRoles
|
||||||
from atst.models.permissions import Permissions
|
from atst.models.permissions import Permissions
|
||||||
|
|
||||||
|
|
||||||
@ -23,4 +24,11 @@ def has_portfolio_applications(_user, portfolio=None, **_kwargs):
|
|||||||
message="view portfolio applications",
|
message="view portfolio applications",
|
||||||
)
|
)
|
||||||
def portfolio_applications(portfolio_id):
|
def portfolio_applications(portfolio_id):
|
||||||
return render_template("applications/index.html")
|
user_env_roles = EnvironmentRoles.for_user(g.current_user.id, portfolio_id)
|
||||||
|
environment_access = {
|
||||||
|
env_role.environment_id: env_role.role for env_role in user_env_roles
|
||||||
|
}
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"applications/index.html", environment_access=environment_access
|
||||||
|
)
|
||||||
|
@ -40,8 +40,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.col--grow {
|
&.col--grow {
|
||||||
flex: 1;
|
flex: 1 auto;
|
||||||
flex-grow: 1;
|
|
||||||
padding-right: $spacing-small;
|
padding-right: $spacing-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ $footer-height: 5rem;
|
|||||||
$usa-banner-height: 2.8rem;
|
$usa-banner-height: 2.8rem;
|
||||||
$sidenav-expanded-width: 25rem;
|
$sidenav-expanded-width: 25rem;
|
||||||
$sidenav-collapsed-width: 10rem;
|
$sidenav-collapsed-width: 10rem;
|
||||||
|
$max-panel-width: 80rem;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* USWDS Variables
|
* USWDS Variables
|
||||||
|
@ -1,148 +1,50 @@
|
|||||||
.triangle-box {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.triangle-up {
|
|
||||||
$triangle-size: $gap * 1.5;
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-left: $triangle-size solid transparent;
|
|
||||||
border-right: $triangle-size solid transparent;
|
|
||||||
border-bottom: $triangle-size solid $color-blue-light;
|
|
||||||
bottom: -4px;
|
|
||||||
right: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.accordion {
|
.accordion {
|
||||||
@include block-list;
|
@include shadow-panel;
|
||||||
|
margin: $gap * 3 0;
|
||||||
box-shadow: 0 4px 10px 0 rgba(193, 193, 193, 0.5);
|
max-width: $max-panel-width;
|
||||||
margin-bottom: 6 * $gap;
|
|
||||||
|
|
||||||
.icon-link {
|
|
||||||
margin: (-$gap) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-link,
|
|
||||||
.label {
|
|
||||||
&:first-child {
|
|
||||||
margin-left: -$gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right: -$gap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
@include block-list-header;
|
padding: $gap * 2 $gap * 3;
|
||||||
|
background-color: $color-white;
|
||||||
|
|
||||||
border-top: 3px solid $color-blue;
|
&-text {
|
||||||
border-bottom: none;
|
|
||||||
box-shadow: 0 2px 4px 0 rgba(216, 218, 222, 0.58);
|
|
||||||
|
|
||||||
&.row {
|
|
||||||
background: $color-white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
@include block-list__title;
|
|
||||||
|
|
||||||
color: $color-blue;
|
|
||||||
|
|
||||||
@include h3;
|
|
||||||
|
|
||||||
&.icon-link {
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: block;
|
|
||||||
padding: 0 $gap;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__description {
|
&__button {
|
||||||
@include block-list__description;
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
font-style: italic;
|
&__content {
|
||||||
font-size: $small-font-size;
|
padding: 0 ($gap * 3) $gap;
|
||||||
|
|
||||||
|
&--list-item {
|
||||||
|
border-bottom: 1px solid $color-gray-lightest;
|
||||||
|
padding: $gap 0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col {
|
||||||
|
padding-right: $gap * 2;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: $gap * 2 0 $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1rem;
|
||||||
color: $color-gray;
|
color: $color-gray;
|
||||||
}
|
margin: 0;
|
||||||
|
|
||||||
&__actions {
|
|
||||||
margin-top: $gap;
|
|
||||||
margin-bottom: $gap * 0.5;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
.icon-link {
|
|
||||||
font-size: $small-font-size;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__footer {
|
|
||||||
@include block-list__footer;
|
|
||||||
|
|
||||||
border-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__item {
|
|
||||||
@include block-list-item;
|
|
||||||
|
|
||||||
opacity: 0.75;
|
|
||||||
background-color: $color-blue-light;
|
|
||||||
border-bottom: 1px solid rgba($color-gray-light, 0.5);
|
|
||||||
|
|
||||||
&--selectable {
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
|
|
||||||
@include ie-only {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
> label {
|
|
||||||
@include block-list-selectable-label;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> label {
|
|
||||||
@include block-list-selectable-label;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked {
|
|
||||||
+ label {
|
|
||||||
color: $color-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include ie-only {
|
|
||||||
dl {
|
|
||||||
width: 100%;
|
|
||||||
padding-left: $gap * 4;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter {
|
|
||||||
background-color: $color-cool-blue-light;
|
|
||||||
color: $color-white;
|
|
||||||
border-radius: 2px;
|
|
||||||
padding: ($gap / 2) $gap;
|
|
||||||
margin-left: $gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.separator {
|
|
||||||
border: 1px solid $color-gray-medium;
|
|
||||||
opacity: 0.75;
|
|
||||||
margin: 0 (0.5 * $gap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -90,4 +90,8 @@
|
|||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--primary {
|
||||||
|
@include icon-color($color-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,3 @@
|
|||||||
.task-order-list {
|
|
||||||
margin-top: 6 * $gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-order-card {
|
|
||||||
&__buttons .usa-button {
|
|
||||||
min-width: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__buttons .usa-button-secondary {
|
|
||||||
min-width: 14rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-size: $small-font-size;
|
|
||||||
margin-right: 2 * $gap;
|
|
||||||
min-width: 7rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-order {
|
.task-order {
|
||||||
margin-top: $gap * 4;
|
margin-top: $gap * 4;
|
||||||
width: 900px;
|
width: 900px;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/accordion.html" import Accordion %}
|
||||||
{% from "components/empty_state.html" import EmptyState %}
|
{% from "components/empty_state.html" import EmptyState %}
|
||||||
{% from "components/sticky_cta.html" import StickyCTA %}
|
{% from "components/sticky_cta.html" import StickyCTA %}
|
||||||
|
{% from "components/icon.html" import Icon %}
|
||||||
|
|
||||||
{% extends "portfolios/base.html" %}
|
{% extends "portfolios/base.html" %}
|
||||||
|
|
||||||
@ -31,59 +32,51 @@
|
|||||||
) }}
|
) }}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
<div class="usa-accordion">
|
||||||
<div class='application-list'>
|
|
||||||
{% for application in portfolio.applications|sort(attribute='name') %}
|
{% for application in portfolio.applications|sort(attribute='name') %}
|
||||||
{% set section_name = "application-{}".format(application.id) %}
|
{% set section_name = "application-{}".format(application.id) %}
|
||||||
|
{% set title = "Environments ({})".format(application.environments|length) %}
|
||||||
<toggler inline-template>
|
<div class="accordion">
|
||||||
<div class='accordion application-list-item'>
|
<div class="accordion__header">
|
||||||
<header class='accordion__header row'>
|
<h3 class="accordion__header-text">
|
||||||
<div class='col col-grow'>
|
<a href='{{ url_for("applications.settings", application_id=application.id) }}'>
|
||||||
<h3 class='icon-link accordion__title' v-on:click="toggleSection('{{ section_name }}')">{{ application.name }}</h3>
|
{{ application.name }} {{ Icon("caret_right", classes="icon--tiny icon--primary") }}
|
||||||
<p class='accordion__description'>
|
</a>
|
||||||
|
</h3>
|
||||||
|
<p class="accordion__header-text">
|
||||||
{{ application.description }}
|
{{ application.description }}
|
||||||
</p>
|
</p>
|
||||||
<div class='accordion__actions'>
|
|
||||||
<a class='icon-link' href='{{ url_for("applications.settings", application_id=application.id) }}'>
|
|
||||||
<span>{{ "portfolios.applications.app_settings_text" | translate }}</span>
|
|
||||||
</a>
|
|
||||||
<div class='separator'></div>
|
|
||||||
{% set has_environments = 0 < (application.environments|length) %}
|
|
||||||
<a class='icon-link triangle-box' v-on:click="toggleSection('{{ section_name }}')" disabled="{{ not has_environments }}">
|
|
||||||
<span>Environments ({{ application.environments|length }})</span>
|
|
||||||
{% if has_environments %}
|
|
||||||
<span v-if="selectedSection === '{{ section_name }}'">
|
|
||||||
{{ Icon('caret_up') }}
|
|
||||||
</span>
|
|
||||||
<span v-else>
|
|
||||||
{{ Icon('caret_down') }}
|
|
||||||
</span>
|
|
||||||
<div class="triangle-up" v-if="selectedSection === '{{ section_name }}'"></div>
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% call Accordion(
|
||||||
</header>
|
title=title,
|
||||||
<ul v-show="selectedSection === '{{ section_name }}'">
|
id=section_name,
|
||||||
|
heading_tag="h4"
|
||||||
|
) %}
|
||||||
{% for environment in application.environments %}
|
{% for environment in application.environments %}
|
||||||
<li class='accordion__item application-list-item__environment'>
|
{% set env_access = environment_access[environment.id] %}
|
||||||
<div class='application-list-item__environment__name'>
|
<div class="accordion__content--list-item">
|
||||||
<span>{{ environment.displayname }}</span>
|
<div class="row">
|
||||||
</div>
|
<div class="col col--grow">
|
||||||
{% if g.current_user in environment.users %}
|
{% if env_access %}
|
||||||
<a href='{{ url_for("applications.access_environment", environment_id=environment.id)}}' target='_blank' rel='noopener noreferrer' class='application-list-item__environment__csp_link icon-link'>
|
<a href='{{ url_for("applications.access_environment", environment_id=environment.id)}}' target='_blank' rel='noopener noreferrer'>
|
||||||
<span>{{ "portfolios.applications.csp_console_text" | translate }}</span>
|
{{ environment.displayname }} {{ Icon('link', classes='icon--medium icon--primary') }}
|
||||||
</a>
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{{ environment.displayname }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</toggler>
|
{% if env_access %}
|
||||||
|
<div class="col">
|
||||||
|
{{ env_access }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endcall %}
|
||||||
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
content_classes="") %}
|
content_classes="") %}
|
||||||
<accordion v-cloak inline-template>
|
<accordion v-cloak inline-template>
|
||||||
<{{wrapper_tag}} class="{{ wrapper_classes }}">
|
<{{wrapper_tag}} class="{{ wrapper_classes }}">
|
||||||
<{{heading_tag}} class="{{ heading_classes }}">
|
<{{heading_tag}} class="accordion__button {{ heading_classes }}">
|
||||||
<button
|
<button
|
||||||
v-on:click="toggle($event)"
|
v-on:click="toggle($event)"
|
||||||
class="usa-accordion-button"
|
class="usa-accordion-button"
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</{{heading_tag}}>
|
</{{heading_tag}}>
|
||||||
<{{content_tag}}
|
<{{content_tag}}
|
||||||
id="{{ id }}"
|
id="{{ id }}"
|
||||||
class="usa-accordion-content {{ content_classes }}"
|
class="usa-accordion-content accordion__content {{ content_classes }}"
|
||||||
v-bind:aria-hidden="isVisible ? 'false' : 'true'">
|
v-bind:aria-hidden="isVisible ? 'false' : 'true'">
|
||||||
{{ caller() }}
|
{{ caller() }}
|
||||||
</{{content_tag}}>
|
</{{content_tag}}>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{% from "components/accordion.html" import Accordion %}
|
||||||
{% from "components/empty_state.html" import EmptyState %}
|
{% from "components/empty_state.html" import EmptyState %}
|
||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
{% from "components/sticky_cta.html" import StickyCTA %}
|
{% from "components/sticky_cta.html" import StickyCTA %}
|
||||||
@ -6,89 +7,45 @@
|
|||||||
|
|
||||||
{% block portfolio_content %}
|
{% block portfolio_content %}
|
||||||
|
|
||||||
{% macro TaskOrderButton(task_order, route, text="Edit", secondary=False) %}
|
|
||||||
<a href="{{ url_for(route, task_order_id=task_order.id) }}" class="usa-button {{ 'usa-button-secondary' if secondary else '' }}">
|
|
||||||
{{ text }}
|
|
||||||
</a>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro TaskOrderDateTime(dt, className="") %}
|
{% macro TaskOrderDateTime(dt, className="") %}
|
||||||
<local-datetime timestamp="{{ dt }}" format="MMMM D, YYYY" class="{{ className }}"></local-datetime>
|
<local-datetime timestamp="{{ dt }}" format="MMMM D, YYYY" class="{{ className }}"></local-datetime>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro TaskOrderDate(task_order) %}
|
|
||||||
<span class="datetime">
|
|
||||||
<!-- Draft: {Begins, Began} start_date -->
|
|
||||||
<!-- Everything else: {Starts, Started} start_date | {Ends, Ended} end_date -->
|
|
||||||
|
|
||||||
{% if task_order.is_draft %}
|
{% macro TaskOrderList(task_orders, status) %}
|
||||||
{% if task_order.has_begun %}
|
{% set status = "All Task Orders" %}
|
||||||
Started on
|
<div class="accordion usa-accordion">
|
||||||
{% else %}
|
{% call Accordion(title=status, id=status, heading_tag="h4") %}
|
||||||
Starts on
|
|
||||||
{% endif %}
|
|
||||||
{{ TaskOrderDateTime(task_order.time_created) }}
|
|
||||||
{% else %}
|
|
||||||
{% if task_order.has_begun %}
|
|
||||||
Began
|
|
||||||
{% else %}
|
|
||||||
Begins
|
|
||||||
{% endif %}
|
|
||||||
{{ TaskOrderDateTime(task_order.start_date) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if not task_order.is_draft %}
|
|
||||||
|
|
|
||||||
|
|
||||||
{% if task_order.has_ended %}
|
|
||||||
Ended
|
|
||||||
{% else %}
|
|
||||||
Ends
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{{ TaskOrderDateTime(task_order.end_date) }}
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro TaskOrderActions(task_order) %}
|
|
||||||
<div class="task-order-card__buttons">
|
|
||||||
{% if task_order.is_draft and user_can(permissions.EDIT_TASK_ORDER_DETAILS) %}
|
|
||||||
{{ TaskOrderButton(task_order, "task_orders.edit")}}
|
|
||||||
{% elif task_order.is_expired %}
|
|
||||||
{{ TaskOrderButton(task_order, "task_orders.review_task_order", text="View") }}
|
|
||||||
{% elif task_order.is_unsigned %}
|
|
||||||
{% if user_can(permissions.EDIT_TASK_ORDER_DETAILS) %}
|
|
||||||
{{ TaskOrderButton(task_order, "task_orders.form_step_four_review", text="Sign", secondary=True) }}
|
|
||||||
{% endif %}
|
|
||||||
{{ TaskOrderButton(task_order, "task_orders.review_task_order", text="View") }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro TaskOrderList(task_orders, label='success') %}
|
|
||||||
<div class="task-order-list">
|
|
||||||
{% for task_order in task_orders %}
|
{% for task_order in task_orders %}
|
||||||
<div class="card task-order-card">
|
<div class="accordion__content--list-item">
|
||||||
<div class="card__status">
|
<h4><a href="{{ url_for('task_orders.review_task_order', task_order_id=task_order.id) }}">Task Order #{{ task_order.number }} {{ Icon("caret_right", classes="icon--tiny icon--primary" ) }}</a></h4>
|
||||||
<span class='label label--{{ label_colors[task_order.status] }}'>{{ task_order.display_status }}</span>
|
<div class="row">
|
||||||
{{ TaskOrderDate(task_order) }}
|
<div class="col col--grow">
|
||||||
<span class="card__status-spacer"></span>
|
<h5>
|
||||||
<span class="card__button">
|
Current Period of Performance
|
||||||
{{ TaskOrderActions(task_order) }}
|
</h5>
|
||||||
</span>
|
<p>
|
||||||
|
{{ task_order.start_date | formattedDate(formatter="%b %d, %Y") }}
|
||||||
|
-
|
||||||
|
{{ task_order.end_date | formattedDate(formatter="%b %d, %Y") }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card__header">
|
<div class="col col--grow">
|
||||||
<h3>Task Order #{{ task_order.number }}</h3>
|
<h5>Total Value</h5>
|
||||||
|
<p>{{ task_order.total_contract_amount | dollars }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card__body">
|
<div class="col col--grow">
|
||||||
<b>Total amount: </b>{{ task_order.total_contract_amount | dollars }}
|
<h5>Total Obligated</h5>
|
||||||
|
<p>{{ task_order.total_obligated_funds | dollars }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="col col--grow">
|
||||||
|
<h5>Total Expended</h5>
|
||||||
|
<p>$0</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card__body">
|
|
||||||
<b>Obligated amount: </b>{{ task_order.total_obligated_funds | dollars }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endcall %}
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
@ -136,3 +136,28 @@ def test_get_for_update(application_role, environment):
|
|||||||
assert role.application_role == application_role
|
assert role.application_role == application_role
|
||||||
assert role.environment == environment
|
assert role.environment == environment
|
||||||
assert role.deleted
|
assert role.deleted
|
||||||
|
|
||||||
|
|
||||||
|
def test_for_user(application_role):
|
||||||
|
portfolio = application_role.application.portfolio
|
||||||
|
user = application_role.user
|
||||||
|
# create roles for 2 environments associated with application_role fixture
|
||||||
|
env_role_1 = EnvironmentRoleFactory.create(application_role=application_role)
|
||||||
|
env_role_2 = EnvironmentRoleFactory.create(application_role=application_role)
|
||||||
|
|
||||||
|
# create role for environment in a different app in same portfolio
|
||||||
|
application = ApplicationFactory.create(portfolio=portfolio)
|
||||||
|
env_role_3 = EnvironmentRoleFactory.create(
|
||||||
|
application_role=ApplicationRoleFactory.create(
|
||||||
|
application=application, user=user
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# create role for environment for random user in app2
|
||||||
|
rando_app_role = ApplicationRoleFactory.create(application=application)
|
||||||
|
rando_env_role = EnvironmentRoleFactory.create(application_role=rando_app_role)
|
||||||
|
|
||||||
|
env_roles = EnvironmentRoles.for_user(user.id, portfolio.id)
|
||||||
|
assert len(env_roles) == 3
|
||||||
|
assert env_roles == [env_role_1, env_role_2, env_role_3]
|
||||||
|
assert not rando_env_role in env_roles
|
||||||
|
Loading…
x
Reference in New Issue
Block a user