Merge pull request #751 from dod-ccpo/view-app-envs
View Application Environments Table
This commit is contained in:
commit
bf4eb23557
@ -64,11 +64,16 @@ def edit_application(portfolio_id, application_id):
|
||||
application = Applications.get(application_id)
|
||||
form = ApplicationForm(name=application.name, description=application.description)
|
||||
|
||||
environments_obj = {}
|
||||
for env in application.environments:
|
||||
environments_obj[env.name] = [user.full_name for user in env.users]
|
||||
|
||||
return render_template(
|
||||
"portfolios/applications/edit.html",
|
||||
portfolio=portfolio,
|
||||
application=application,
|
||||
form=form,
|
||||
environments_obj=environments_obj,
|
||||
)
|
||||
|
||||
|
||||
|
34
js/components/tables/application_environments.js
Normal file
34
js/components/tables/application_environments.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { set } from 'vue/dist/vue'
|
||||
|
||||
export default {
|
||||
name: 'environments-table',
|
||||
|
||||
props: {
|
||||
environments: Object,
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
environmentsState: this.environments,
|
||||
}
|
||||
},
|
||||
|
||||
created: function() {
|
||||
Object.keys(this.environments).forEach(environment => {
|
||||
set(this.environmentsState[environment], 'isVisible', false)
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle: function(e, environmentName) {
|
||||
this.environmentsState = Object.assign(this.environmentsState, {
|
||||
[environmentName]: Object.assign(
|
||||
this.environmentsState[environmentName],
|
||||
{
|
||||
isVisible: !this.environmentsState[environmentName].isVisible,
|
||||
}
|
||||
),
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
@ -25,6 +25,7 @@ import Modal from './mixins/modal'
|
||||
import selector from './components/selector'
|
||||
import BudgetChart from './components/charts/budget_chart'
|
||||
import SpendTable from './components/tables/spend_table'
|
||||
import EnvironmentsTable from './components/tables/application_environments'
|
||||
import TaskOrderList from './components/tables/task_order_list.js'
|
||||
import MembersList from './components/members_list'
|
||||
import LocalDatetime from './components/local_datetime'
|
||||
@ -56,6 +57,7 @@ const app = new Vue({
|
||||
selector,
|
||||
BudgetChart,
|
||||
SpendTable,
|
||||
EnvironmentsTable,
|
||||
TaskOrderList,
|
||||
MembersList,
|
||||
LocalDatetime,
|
||||
|
@ -12,7 +12,7 @@
|
||||
@import 'elements/buttons';
|
||||
@import 'elements/panels';
|
||||
@import 'elements/block_lists';
|
||||
@import 'elements/accordians';
|
||||
@import 'elements/accordions';
|
||||
@import 'elements/tables';
|
||||
@import 'elements/sidenav';
|
||||
@import 'elements/action_group';
|
||||
@ -23,6 +23,7 @@
|
||||
@import 'elements/graphs';
|
||||
@import 'elements/menu';
|
||||
|
||||
@import 'components/accordion_table';
|
||||
@import 'components/topbar';
|
||||
@import 'components/top_message';
|
||||
@import 'components/global_layout';
|
||||
|
66
styles/components/_accordion_table.scss
Normal file
66
styles/components/_accordion_table.scss
Normal file
@ -0,0 +1,66 @@
|
||||
.accordion-table {
|
||||
|
||||
table {
|
||||
thead th {
|
||||
text-transform: uppercase;
|
||||
border-bottom: 1px solid $color-gray-lightest;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
th, td {
|
||||
white-space: nowrap;
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.accordion-table__items {
|
||||
.accordion-table__item__toggler {
|
||||
@include icon-link-color($color-blue, $color-gray-lightest);
|
||||
float: right;
|
||||
margin-left: -$gap;
|
||||
color: $color-blue;
|
||||
|
||||
.icon {
|
||||
@include icon-size(12);
|
||||
margin-right: $gap;
|
||||
}
|
||||
|
||||
.open-indicator {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 5 * $gap;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
|
||||
border-bottom: 10px solid $color-blue-light;
|
||||
}
|
||||
}
|
||||
|
||||
th, td, tr {
|
||||
border-bottom: 1px dashed $color-gray-lightest;
|
||||
}
|
||||
|
||||
th[scope=rowgroup] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.accordion-table__item__expanded {
|
||||
margin-left: 2 * $gap;
|
||||
|
||||
th, td {
|
||||
.icon-link {
|
||||
font-weight: $font-normal;
|
||||
font-size: $base-font-size;
|
||||
}
|
||||
|
||||
border-bottom: 1px dashed $color-white;
|
||||
background-color: $color-gray-lightest;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -171,26 +171,6 @@
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.member-list-header {
|
||||
margin: 2 * $gap 5 * $gap;
|
||||
padding: inherit;
|
||||
overflow: auto;
|
||||
|
||||
.left {
|
||||
float: left;
|
||||
padding-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.icon-link {
|
||||
float: right;
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.subheading {
|
||||
font-size: 1.4rem;
|
||||
color: $color-gray;
|
||||
@ -326,15 +306,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.members-table-footer {
|
||||
float: right;
|
||||
padding: 3 * $gap 0;
|
||||
|
||||
.action-group.save {
|
||||
padding-right: 3 * $gap;
|
||||
}
|
||||
}
|
||||
|
||||
a.modal-link.icon-link {
|
||||
float: right;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
.accordian {
|
||||
.accordion {
|
||||
@include block-list;
|
||||
|
||||
box-shadow: 0 4px 10px 0 rgba(193,193,193,0.5);
|
||||
@ -21,7 +21,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.accordian__header {
|
||||
.accordion__header {
|
||||
@include block-list-header;
|
||||
border-top: 3px solid $color-blue;
|
||||
border-bottom: none;
|
||||
@ -32,7 +32,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.accordian__title {
|
||||
.accordion__title {
|
||||
@include block-list__title;
|
||||
color: $color-blue;
|
||||
@include h3;
|
||||
@ -45,14 +45,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.accordian__description {
|
||||
.accordion__description {
|
||||
@include block-list__description;
|
||||
font-style: italic;
|
||||
font-size: $small-font-size;
|
||||
color: $color-gray;
|
||||
}
|
||||
|
||||
.accordian__actions {
|
||||
.accordion__actions {
|
||||
margin-top: $gap;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -76,14 +76,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.accordian__item {
|
||||
.accordion__item {
|
||||
@include block-list-item;
|
||||
|
||||
opacity: 0.75;
|
||||
background-color: $color-blue-light;
|
||||
border-bottom: 1px solid rgba($color-gray-light, 0.5);
|
||||
|
||||
&.accordian__item--selectable {
|
||||
&.accordion__item--selectable {
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
@ -117,7 +117,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.accordian__footer {
|
||||
.accordion__footer {
|
||||
@include block-list__footer;
|
||||
border-top: 0;
|
||||
}
|
@ -107,6 +107,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.panel__footer {
|
||||
padding: 3 * $gap;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px dashed $color-gray-light;
|
||||
|
@ -102,6 +102,25 @@
|
||||
overflow-x: auto;
|
||||
@include panel-margin;
|
||||
|
||||
.responsive-table-wrapper__header {
|
||||
@include panel-base;
|
||||
@include panel-theme-default;
|
||||
border-top: none;
|
||||
border-bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: $gap * 2;
|
||||
|
||||
.responsive-table-wrapper__title {
|
||||
@include h4;
|
||||
font-size: $lead-font-size;
|
||||
flex: 2;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@ -24,7 +24,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
header.accordian__header {
|
||||
padding: 1.6rem;
|
||||
.list-header {
|
||||
margin: 2 * $gap 5 * $gap;
|
||||
padding: inherit;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.icon-link {
|
||||
.icon--info {
|
||||
bottom: -1px;
|
||||
left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
|
||||
thead {
|
||||
td {
|
||||
font-weight: bold;
|
||||
font-size: 1.4rem;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
th {
|
||||
font-weight: bold;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
td {
|
||||
font-size: 1.6rem;
|
||||
border-bottom: 1px solid $color-gray-lightest;
|
||||
border-top: 0;
|
||||
padding: 3 * $gap 2 * $gap;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -201,143 +201,60 @@
|
||||
}
|
||||
}
|
||||
|
||||
.spend-table__month-select {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.spend-table {
|
||||
box-shadow: 0 6px 18px 0 rgba(144,164,183,0.3);
|
||||
|
||||
.spend-table__header {
|
||||
@include panel-base;
|
||||
@include panel-theme-default;
|
||||
border-top: none;
|
||||
border-bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: $gap * 2;
|
||||
|
||||
.spend-table__title {
|
||||
@include h4;
|
||||
font-size: $lead-font-size;
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.spend-table__month-select {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
table {
|
||||
.spend-table__portfolio {
|
||||
th, td {
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid $color-gray-lightest;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
thead th {
|
||||
text-transform: uppercase;
|
||||
border-bottom: 1px solid $color-gray-lightest;
|
||||
border-top: none;
|
||||
th, td {
|
||||
&.previous-month {
|
||||
color: $color-gray;
|
||||
}
|
||||
|
||||
th, td {
|
||||
white-space: nowrap;
|
||||
&.meter-cell {
|
||||
padding-left: 0;
|
||||
position: relative;
|
||||
min-width: 4rem;
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
@include media($medium-screen) {
|
||||
min-width: 12rem;
|
||||
}
|
||||
|
||||
&.previous-month {
|
||||
color: $color-gray;
|
||||
}
|
||||
|
||||
&.meter-cell {
|
||||
padding-left: 0;
|
||||
position: relative;
|
||||
min-width: 4rem;
|
||||
meter {
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
background: $color-white;
|
||||
display: none;
|
||||
|
||||
@include media($medium-screen) {
|
||||
min-width: 12rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
meter {
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
&::-webkit-meter-bar {
|
||||
background: $color-white;
|
||||
display: none;
|
||||
|
||||
@include media($medium-screen) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&::-webkit-meter-bar {
|
||||
background: $color-white;
|
||||
}
|
||||
}
|
||||
|
||||
.spend-table__meter-value {
|
||||
@include h5;
|
||||
|
||||
@include media($medium-screen) {
|
||||
display: block;
|
||||
color: $color-white;
|
||||
background-color: rgba($color-blue, 0.65);
|
||||
border-radius: $gap/2;
|
||||
position: absolute;
|
||||
top: 2.3rem;
|
||||
left: $gap / 2;
|
||||
padding: 0 ($gap / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spend-table__portfolio {
|
||||
th, td {
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid $color-gray-lightest;
|
||||
}
|
||||
}
|
||||
.spend-table__meter-value {
|
||||
@include h5;
|
||||
|
||||
.spend-table__application {
|
||||
.spend-table__application__toggler {
|
||||
@include icon-link-color($color-blue, $color-gray-lightest);
|
||||
margin-left: -$gap;
|
||||
color: $color-blue;
|
||||
|
||||
.icon {
|
||||
@include icon-size(12);
|
||||
margin-right: $gap;
|
||||
}
|
||||
|
||||
.open-indicator {
|
||||
@include media($medium-screen) {
|
||||
display: block;
|
||||
color: $color-white;
|
||||
background-color: rgba($color-blue, 0.65);
|
||||
border-radius: $gap/2;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 5 * $gap;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
|
||||
border-bottom: 10px solid $color-blue-light;
|
||||
}
|
||||
}
|
||||
|
||||
th, td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
th[scope=rowgroup] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.spend-table__application__env {
|
||||
margin-left: 2 * $gap;
|
||||
|
||||
th, td {
|
||||
.icon-link {
|
||||
font-weight: $font-normal;
|
||||
font-size: $base-font-size;
|
||||
}
|
||||
|
||||
border-bottom: 1px dashed $color-white;
|
||||
background-color: $color-blue-light;
|
||||
top: 2.3rem;
|
||||
left: $gap / 2;
|
||||
padding: 0 ($gap / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
{% from "components/alert.html" import Alert %}
|
||||
|
||||
<section class="member-list" id="portfolio-members">
|
||||
<div class='responsive-table-wrapper panel'>
|
||||
<div class='responsive-table-wrapper panel accordion-table'>
|
||||
{% if g.matchesPath("portfolio-members") %}
|
||||
{% include "fragments/flash.html" %}
|
||||
{% endif %}
|
||||
@ -14,53 +14,62 @@
|
||||
<form method='POST' id="member-perms" action='{{ url_for("portfolios.edit_portfolio_members", portfolio_id=portfolio.id) }}' autocomplete="off" enctype="multipart/form-data">
|
||||
{{ member_perms_form.csrf_token }}
|
||||
|
||||
<div class='member-list-header'>
|
||||
<div class='left'>
|
||||
<div class='h3'>{{ "portfolios.admin.portfolio_members_title" | translate }}</div>
|
||||
<div class='subheading'>
|
||||
{{ "portfolios.admin.portfolio_members_subheading" | translate }}
|
||||
<div class='application-list-item'>
|
||||
<header>
|
||||
<div class='responsive-table-wrapper__header'>
|
||||
<div class='responsive-table-wrapper__title'>
|
||||
<div class='h3'>{{ "portfolios.admin.portfolio_members_title" | translate }}</div>
|
||||
<div class='subheading'>
|
||||
{{ "portfolios.admin.portfolio_members_subheading" | translate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class='icon-link'>
|
||||
{{ Icon('info') }}
|
||||
{{ "portfolios.admin.settings_info" | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{% if not portfolio.members %}
|
||||
<p>{{ "portfolios.admin.no_members" | translate }}</p>
|
||||
{% else %}
|
||||
<table>
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{{ "portfolios.members.permissions.name" | translate }}</td>
|
||||
<td>{{ "portfolios.members.permissions.app_mgmt" | translate }}</td>
|
||||
<td>{{ "portfolios.members.permissions.funding" | translate }}</td>
|
||||
<td>{{ "portfolios.members.permissions.reporting" | translate }}</td>
|
||||
<td>{{ "portfolios.members.permissions.portfolio_mgmt" | translate }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) %}
|
||||
{% include "fragments/admin/members_edit.html" %}
|
||||
{% elif user_can(permissions.VIEW_PORTFOLIO_USERS) %}
|
||||
{% include "fragments/admin/members_view.html" %}
|
||||
{% endif %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
<a class='icon-link'>
|
||||
{{ Icon('info') }}
|
||||
{{ "portfolios.admin.settings_info" | translate }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if not portfolio.members %}
|
||||
<p>{{ "portfolios.admin.no_members" | translate }}</p>
|
||||
{% else %}
|
||||
<table>
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{{ "portfolios.members.permissions.name" | translate }}</td>
|
||||
<td>{{ "portfolios.members.permissions.app_mgmt" | translate }}</td>
|
||||
<td>{{ "portfolios.members.permissions.funding" | translate }}</td>
|
||||
<td>{{ "portfolios.members.permissions.reporting" | translate }}</td>
|
||||
<td>{{ "portfolios.members.permissions.portfolio_mgmt" | translate }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) %}
|
||||
{% include "fragments/admin/members_edit.html" %}
|
||||
{% elif user_can(permissions.VIEW_PORTFOLIO_USERS) %}
|
||||
{% include "fragments/admin/members_view.html" %}
|
||||
{% endif %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
<div class="members-table-footer">
|
||||
<div class="panel__footer">
|
||||
<div class="action-group save">
|
||||
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) %}
|
||||
{{ SaveButton(text=('common.save' | translate), element="input", form="member-perms") }}
|
||||
{% endif %}
|
||||
|
||||
{% if user_can(permissions.CREATE_PORTFOLIO_USERS) %}
|
||||
{% include "fragments/admin/add_new_portfolio_member.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</form>
|
||||
</base-form>
|
||||
|
||||
@ -78,26 +87,21 @@
|
||||
)
|
||||
}}
|
||||
|
||||
<div class="action-group">
|
||||
<form method="POST" action="{{ url_for('portfolios.remove_member', portfolio_id=portfolio.id, user_id=member.user_id) }}">
|
||||
{{ member_perms_form.csrf_token }}
|
||||
<button class="usa-button usa-button-danger">
|
||||
{{ "portfolios.members.archive_button" | translate }}
|
||||
</button>
|
||||
</form>
|
||||
<a v-on:click="closeModal('{{ modal_id }}')" class="action-group__action icon-link icon-link--default">{{ "common.cancel" | translate }}</a>
|
||||
<div class="panel__footer">
|
||||
<div class="action-group">
|
||||
<form method="POST" action="{{ url_for('portfolios.remove_member', portfolio_id=portfolio.id, user_id=member.user_id) }}">
|
||||
{{ member_perms_form.csrf_token }}
|
||||
<button class="usa-button usa-button-danger">
|
||||
{{ "portfolios.members.archive_button" | translate }}
|
||||
</button>
|
||||
</form>
|
||||
<a v-on:click="closeModal('{{ modal_id }}')" class="action-group__action icon-link icon-link--default">{{ "common.cancel" | translate }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="members-table-footer">
|
||||
<div class="action-group">
|
||||
{% if user_can(permissions.CREATE_PORTFOLIO_USERS) %}
|
||||
{% include "fragments/admin/add_new_portfolio_member.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
50
templates/fragments/applications/environments.html
Normal file
50
templates/fragments/applications/environments.html
Normal file
@ -0,0 +1,50 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
|
||||
<div class="application-list-item">
|
||||
<header>
|
||||
<div class="responsive-table-wrapper__header">
|
||||
<div class='responsive-table-wrapper__title'>
|
||||
<div class='h3'>{{ 'portfolios.applications.environments_heading' | translate }}</div>
|
||||
</div>
|
||||
<a class='icon-link'>
|
||||
{{ Icon('info') }}
|
||||
{{ "portfolios.admin.settings_info" | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<environments-table
|
||||
v-cloak
|
||||
v-bind:environments='{{ environments_obj }}'
|
||||
inline-template>
|
||||
<table>
|
||||
<thead>
|
||||
<th scope='col'>{{ "portfolios.applications.environments.name" | translate }}</th>
|
||||
<th scope='col' class='table-cell--align-right'>{{ "portfolios.applications.environments.members" | translate }}</th>
|
||||
</thead>
|
||||
|
||||
<tbody v-for='(members_list, name) in environments' class='accordion-table__items'>
|
||||
<tr>
|
||||
<th scope='rowgroup' v-on:click="toggle($event, name)" v-html='name'></th>
|
||||
|
||||
<template v-if="environmentsState[name].isVisible">
|
||||
<td v-on:click="toggle($event, name)" class='icon-link icon-link--large accordion-table__item__toggler'>Hide Members (<span v-html='members_list.length'></span>){{ Icon('caret_up') }}</td>
|
||||
</template>
|
||||
<template v-else>
|
||||
<td v-on:click="toggle($event, name)" class='icon-link icon-link--large accordion-table__item__toggler'>Show Members (<span v-html='members_list.length'></span>){{ Icon('caret_down') }}</td>
|
||||
</template>
|
||||
</tr>
|
||||
|
||||
<tr scope='rowgroup' v-for='member in members_list' v-show='environmentsState[name].isVisible' class='accordion-table__item__expanded'>
|
||||
<td>
|
||||
<div>
|
||||
<span v-html='member'></span>
|
||||
</div>
|
||||
</td>
|
||||
<td class='table-cell--expand'></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</environments-table>
|
||||
</div>
|
@ -1,6 +1,7 @@
|
||||
{% extends "portfolios/applications/base.html" %}
|
||||
|
||||
{% from "components/text_input.html" import TextInput %}
|
||||
{% from "components/icon.html" import Icon %}
|
||||
|
||||
{% set secondary_breadcrumb = 'portfolios.applications.existing_application_title' | translate({ "application_name": application.name }) %}
|
||||
|
||||
@ -12,33 +13,28 @@
|
||||
<div class="panel">
|
||||
<div class="panel__content">
|
||||
|
||||
{% include "fragments/edit_application_form.html" %}
|
||||
|
||||
<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>
|
||||
|
||||
<ul>
|
||||
{% for environment in application.environments %}
|
||||
<li class="application-edit__env-list-item">
|
||||
<div class="usa-input input--disabled">
|
||||
<label>Environment Name</label>
|
||||
<input type="text" disabled value="{{ environment.name }}" readonly />
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% include "fragments/applications/edit_application_form.html" %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-group">
|
||||
<button class="usa-button usa-button-primary" tabindex="0" type="submit">{{ 'portfolios.applications.update_button_text' | translate }}</button>
|
||||
<div class="panel__footer">
|
||||
<div class="action-group">
|
||||
<button class="usa-button usa-button-primary" tabindex="0" type="submit">{{ 'portfolios.applications.update_button_text' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="accordion-table responsive-table-wrapper panel">
|
||||
{% include "fragments/applications/environments.html" %}
|
||||
<div class="panel__footer">
|
||||
<div class="action-group">
|
||||
<button class="usa-button usa-button-primary" tabindex="0" type="submit">{{ 'portfolios.applications.update_button_text' | translate }}</button>
|
||||
<a class='icon-link'>
|
||||
{{ "portfolios.applications.add_environment" | translate }}
|
||||
{{ Icon('plus-circle-solid') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
@ -34,13 +34,13 @@
|
||||
|
||||
<div class='application-list'>
|
||||
{% for application in portfolio.applications|sort(attribute='name') %}
|
||||
<div is='toggler' v-cloak class='accordian application-list-item'>
|
||||
<div is='toggler' v-cloak class='accordion application-list-item'>
|
||||
<template slot-scope='props'>
|
||||
<header class='accordian__header row'>
|
||||
<header class='accordion__header row'>
|
||||
<div class='col col-grow'>
|
||||
<h3 class='icon-link accordian__title' v-on:click="props.toggle">{{ application.name }}</h3>
|
||||
<span class='accordian__description'>{{ application.description }}</span>
|
||||
<div class='accordian__actions'>
|
||||
<h3 class='icon-link accordion__title' v-on:click="props.toggle">{{ application.name }}</h3>
|
||||
<span class='accordion__description'>{{ application.description }}</span>
|
||||
<div class='accordion__actions'>
|
||||
{% if user_can(permissions.EDIT_APPLICATION) %}
|
||||
<a class='icon-link' href='{{ url_for("portfolios.edit_application", portfolio_id=portfolio.id, application_id=application.id) }}'>
|
||||
<span>{{ "portfolios.applications.app_settings_text" | translate }}</span>
|
||||
@ -68,7 +68,7 @@
|
||||
</header>
|
||||
<ul v-if="props.isVisible">
|
||||
{% for environment in application.environments %}
|
||||
<li class='accordian__item application-list-item__environment'>
|
||||
<li class='accordion__item application-list-item__environment'>
|
||||
<div class='application-list-item__environment__name'>
|
||||
<span>{{ environment.name }}</span>
|
||||
</div>
|
||||
|
@ -37,7 +37,7 @@
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{% include "fragments/edit_application_form.html" %}
|
||||
{% include "fragments/applications/edit_application_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">
|
||||
|
@ -346,9 +346,9 @@
|
||||
</div>
|
||||
</budget-chart>
|
||||
|
||||
<div class='spend-table responsive-table-wrapper'>
|
||||
<div class='spend-table__header'>
|
||||
<h2 class='spend-table__title'>Total spent per month</h2>
|
||||
<div class='accordion-table responsive-table-wrapper'>
|
||||
<div class='responsive-table-wrapper__header'>
|
||||
<h2 class='responsive-table-wrapper__title'>Total spent per month</h2>
|
||||
|
||||
<select name='month' id='month' onchange='location = this.value' class='spend-table__month-select'>
|
||||
{% for m in cumulative_budget["months"] %}
|
||||
@ -402,10 +402,10 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody v-for='(application, name) in applicationsState' class='spend-table__application'>
|
||||
<tbody v-for='(application, name) in applicationsState' class='accordion-table__items'>
|
||||
<tr>
|
||||
<th scope='rowgroup'>
|
||||
<button v-on:click='toggle($event, name)' class='icon-link icon-link--large spend-table__application__toggler'>
|
||||
<button v-on:click='toggle($event, name)' class='icon-link icon-link--large accordion-table__item__toggler'>
|
||||
<template v-if='application.isVisible'>{{ Icon('caret_down') }}<div class='open-indicator'></div></template>
|
||||
<template v-else>{{ Icon('caret_right') }}</template>
|
||||
<span v-html='name'></span>
|
||||
@ -433,9 +433,9 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr v-for='(environment, envName) in environments[name]' v-show='application.isVisible' class='spend-table__application__env'>
|
||||
<tr v-for='(environment, envName) in environments[name]' v-show='application.isVisible' class='accordion-table__item__expanded'>
|
||||
<th scope='rowgroup'>
|
||||
<div class='icon-link spend-table__application__env'>
|
||||
<div class='icon-link accordion-table__item__expanded'>
|
||||
<span v-html='envName'></span>
|
||||
</div>
|
||||
</th>
|
||||
|
@ -13,6 +13,8 @@ from atst.domain.applications import Applications
|
||||
from atst.domain.portfolios import Portfolios
|
||||
from atst.models.portfolio_role import Status as PortfolioRoleStatus
|
||||
|
||||
from tests.utils import captured_templates
|
||||
|
||||
|
||||
def test_user_with_permission_has_budget_report_link(client, user_session):
|
||||
portfolio = PortfolioFactory.create()
|
||||
@ -105,6 +107,41 @@ def test_view_edit_application(client, user_session):
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_edit_application_environments_obj(app, client, user_session):
|
||||
portfolio = PortfolioFactory.create()
|
||||
application = Applications.create(
|
||||
portfolio,
|
||||
"Snazzy Application",
|
||||
"A new application for me and my friends",
|
||||
{"env1", "env2"},
|
||||
)
|
||||
user1 = UserFactory.create()
|
||||
user2 = UserFactory.create()
|
||||
env1 = application.environments[0]
|
||||
env2 = application.environments[1]
|
||||
EnvironmentRoleFactory.create(environment=env1, user=user1)
|
||||
EnvironmentRoleFactory.create(environment=env1, user=user2)
|
||||
EnvironmentRoleFactory.create(environment=env2, user=user1)
|
||||
|
||||
user_session(portfolio.owner)
|
||||
|
||||
with captured_templates(app) as templates:
|
||||
response = app.test_client().get(
|
||||
url_for(
|
||||
"portfolios.edit_application",
|
||||
portfolio_id=portfolio.id,
|
||||
application_id=application.id,
|
||||
)
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
_, context = templates[0]
|
||||
assert context["environments_obj"] == {
|
||||
env1.name: [user1.full_name, user2.full_name],
|
||||
env2.name: [user1.full_name],
|
||||
}
|
||||
|
||||
|
||||
def test_user_with_permission_can_update_application(client, user_session):
|
||||
owner = UserFactory.create()
|
||||
portfolio = PortfolioFactory.create(
|
||||
|
@ -580,13 +580,17 @@ portfolios:
|
||||
existing_application_title: '{application_name} Application Settings'
|
||||
new_application_title: New Application
|
||||
settings_heading: Application Settings
|
||||
environments_heading: Environments
|
||||
environments_heading: Application Environments
|
||||
environments_description: Each environment created within an application is logically separated from one another for easier management and security.
|
||||
update_button_text: Save
|
||||
create_button_text: Create
|
||||
team_management:
|
||||
title: '{application_name} Team Management'
|
||||
subheading: Team Management
|
||||
environments:
|
||||
name: Name
|
||||
members: Members
|
||||
add_environment: Add New Environment
|
||||
admin:
|
||||
portfolio_members_title: Portfolio members
|
||||
portfolio_members_subheading: These members have different levels of access to the portfolio.
|
||||
|
Loading…
x
Reference in New Issue
Block a user