Display app users view only table

This commit is contained in:
George Drummond 2019-04-08 13:53:17 -04:00
parent ec3d4f518f
commit 769867c6a9
No known key found for this signature in database
GPG Key ID: 296DD6077123BF17
8 changed files with 252 additions and 1 deletions

View File

@ -5,6 +5,13 @@ from atst.models import Base
from atst.models.types import Id from atst.models.types import Id
from atst.models import mixins from atst.models import mixins
from atst.models.application_role import (
ApplicationRole,
Status as ApplicationRoleStatuses,
)
from atst.database import db
class Application( class Application(
Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.DeletableMixin Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.DeletableMixin
@ -28,6 +35,15 @@ class Application(
def users(self): def users(self):
return set(role.user for role in self.roles) return set(role.user for role in self.roles)
@property
def members(self):
return (
db.session.query(ApplicationRole)
.filter(ApplicationRole.application_id == self.id)
.filter(ApplicationRole.status != ApplicationRoleStatuses.DISABLED)
.all()
)
@property @property
def num_users(self): def num_users(self):
return len(self.users) return len(self.users)

View File

@ -4,6 +4,7 @@ from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy.event import listen from sqlalchemy.event import listen
from atst.utils import first_or_none
from atst.models import Base, mixins from atst.models import Base, mixins
from atst.models.mixins.auditable import record_permission_sets_updates from atst.models.mixins.auditable import record_permission_sets_updates
from .types import Id from .types import Id
@ -59,6 +60,11 @@ class ApplicationRole(
def history(self): def history(self):
return self.get_changes() return self.get_changes()
def has_permission_set(self, perm_set_name):
return first_or_none(
lambda prms: prms.name == perm_set_name, self.permission_sets
)
Index( Index(
"application_role_user_application", "application_role_user_application",

View File

@ -9,6 +9,7 @@ from flask import (
from . import portfolios_bp from . import portfolios_bp
from atst.domain.environment_roles import EnvironmentRoles from atst.domain.environment_roles import EnvironmentRoles
from atst.domain.environments import Environments
from atst.domain.exceptions import UnauthorizedError from atst.domain.exceptions import UnauthorizedError
from atst.domain.applications import Applications from atst.domain.applications import Applications
from atst.domain.portfolios import Portfolios from atst.domain.portfolios import Portfolios
@ -16,6 +17,8 @@ from atst.forms.application import NewApplicationForm, ApplicationForm
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.models.permissions import Permissions from atst.models.permissions import Permissions
from atst.utils.flash import formatted_flash as flash from atst.utils.flash import formatted_flash as flash
from atst.domain.permission_sets import PermissionSets
from atst.utils.localization import translate
@portfolios_bp.route("/portfolios/<portfolio_id>/applications") @portfolios_bp.route("/portfolios/<portfolio_id>/applications")
@ -139,3 +142,44 @@ def delete_application(portfolio_id, application_id):
return redirect( return redirect(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio_id) url_for("portfolios.portfolio_applications", portfolio_id=portfolio_id)
) )
def permission_str(member, edit_perm_set):
if member.has_permission_set(edit_perm_set):
return translate("portfolios.members.permissions.edit_access")
else:
return translate("portfolios.members.permissions.view_only")
@portfolios_bp.route("/portfolios/<portfolio_id>/applications/<application_id>/team")
@user_can(Permissions.VIEW_APPLICATION, message="view portfolio applications")
def application_team(portfolio_id, application_id):
application = Applications.get(application_id)
portfolio = Portfolios.get(g.current_user, portfolio_id)
environment_users = {}
for member in application.members:
user_id = member.user.id
environment_users[user_id] = {
"permissions": {
"delete_access": permission_str(
member, PermissionSets.DELETE_APPLICATION_ENVIRONMENTS
),
"environment_management": permission_str(
member, PermissionSets.EDIT_APPLICATION_ENVIRONMENTS
),
"team_management": permission_str(
member, PermissionSets.EDIT_APPLICATION_TEAM
),
},
"environments": Environments.for_user(
user=member.user, application=application
),
}
return render_template(
"portfolios/applications/team.html",
application=application,
portfolio=portfolio,
environment_users=environment_users,
)

View File

@ -117,3 +117,28 @@
} }
} }
} }
#portfolio-members {
.accordion-table {
.accordion-table__head {
font-size: $small-font-size;
padding-left: $gap*3;
}
.accordion-table__item-content, .accordion-table__head {
display: flex;
& > span {
width: 75%;
&.icon-link {
width: 25%;
}
}
}
span {
display: flex;
width: 25%;
}
}
}

View File

@ -49,7 +49,9 @@
<div class='separator'></div> <div class='separator'></div>
{% endif %} {% endif %}
{% if user_can(permissions.VIEW_PORTFOLIO_USERS) %} {% if user_can(permissions.VIEW_PORTFOLIO_USERS) %}
<a class='icon-link'> <a
href="{{ url_for('portfolios.application_team', portfolio_id=portfolio.id, application_id=application.id) }}"
class='icon-link'>
<span>{{ "portfolios.applications.team_text" | translate }}</span> <span>{{ "portfolios.applications.team_text" | translate }}</span>
<span class='counter'>{{ application.num_users }}</span> <span class='counter'>{{ application.num_users }}</span>
</a> </a>

View File

@ -0,0 +1,104 @@
{% extends "portfolios/applications/base.html" %}
{% from "components/empty_state.html" import EmptyState %}
{% from "components/icon.html" import Icon %}
{% from "components/toggle_list.html" import ToggleList %}
{% set secondary_breadcrumb = 'portfolios.applications.team_settings.title' | translate({ "application_name": application.name }) %}
{% block application_content %}
{% if not application.members %}
{% set user_can_invite = user_can(permissions.CREATE_APPLICATION_MEMBER) %}
{{ EmptyState(
("portfolios.applications.team_settings.blank_slate.title" | translate),
action_label=("portfolios.applications.team_settings.blank_slate.action_label" | translate),
action_href='#' if user_can_invite else None,
sub_message=None if user_can_invite else ("portfolios.team_settings.blank_slate.sub_message" | translate),
icon='avatar'
) }}
{% else %}
<div class='subheading'>
{{ 'portfolios.applications.team_settings.subheading' | translate }}
</div>
<section class="member-list" id="portfolio-members">
<div class='responsive-table-wrapper panel'>
{% if g.matchesPath("portfolio-members") %}
{% include "fragments/flash.html" %}
{% endif %}
<form>
<header>
<div class="responsive-table-wrapper__header">
<div class="responsive-table-wrapper__title">
<div class="h3">
{{ "portfolios.applications.team_settings.section.title" | translate({ "application_name": application.name }) }}
</div>
</div>
<a class='icon-link'>
{{ Icon('info') }}
{{ "portfolios.admin.settings_info" | translate }}
</a>
</div>
</header>
<div class="accordion-table accordion-table-list">
<div class="accordion-table__head">
<span>
<span>
{{ "portfolios.applications.team_settings.user" | translate }}
</span>
<span>
{{ "portfolios.applications.team_settings.section.table.delete_access" | translate }}
</span>
<span>
{{ "portfolios.applications.team_settings.section.table.environment_management" | translate }}
</span>
<span>
{{ "portfolios.applications.team_settings.section.table.team_management" | translate }}
</span>
</span>
</div>
<ul class="accordion-table__items">
{% for member in application.members %}
{% set user = member.user %}
{% set user_info = environment_users[user.id] %}
{% set user_permissions = user_info["permissions"] %}
{% set user_row %}
<span>{{ user.full_name }}</span>
<span>{{ user_permissions["delete_access"] }}</span>
<span>{{ user_permissions["environment_management"] }}</span>
<span>{{ user_permissions["team_management"] }}</span>
{% endset %}
{% call ToggleList(
item_name=user_row,
item_type=("portfolios.applications.team_settings.environments" | translate),
length=(user_info["environments"] | length)
)
%}
<ul>
{% for environment in user_info["environments"] %}
<li class="accordion-table__item__expanded">
<div class="accordion-table__item-content">
{{ environment.name }}
</div>
</li>
{% endfor %}
</ul>
{% endcall %}
{% endfor %}
</ul>
</div>
<div class="members-table-footer">
<div class="action-group save">
</div>
</div>
</form>
</div>
</section>
{% endif %}
{% endblock %}

View File

@ -347,4 +347,40 @@ def test_edit_application_scope(client, user_session):
application_id=port1.applications[0].id, application_id=port1.applications[0].id,
) )
) )
assert response.status_code == 404
def test_application_team_with_permissions(client, user_session):
portfolio = PortfolioFactory.create()
application = ApplicationFactory.create(portfolio=portfolio)
user_session(portfolio.owner)
response = client.get(
url_for(
"portfolios.application_team",
portfolio_id=portfolio.id,
application_id=application.id,
)
)
assert response.status_code == 200
def test_application_team_without_permissions(client, user_session):
random_user = UserFactory.create()
portfolio = PortfolioFactory.create()
application = ApplicationFactory.create(portfolio=portfolio)
user_session(random_user)
response = client.get(
url_for(
"portfolios.application_team",
portfolio_id=portfolio.id,
application_id=application.id,
)
)
assert response.status_code == 404 assert response.status_code == 404

View File

@ -601,6 +601,24 @@ portfolios:
environments_description: Each environment created within an application is logically separated from one another for easier management and security. environments_description: Each environment created within an application is logically separated from one another for easier management and security.
update_button_text: Save update_button_text: Save
create_button_text: Create create_button_text: Create
team_settings:
title: '{application_name} Team Settings'
subheading: Team Settings
user: User
environments: Environments
blank_slate:
action_label: Invite a new team member
sub_message: Please contact your JEDI Cloud portfolio administrator to invite new members.
title: There are currently no team members for this application.
section:
title: '{application_name} Team'
members_count: 'Members ({members_count})'
table:
delete_access: Delete Access
environment_management: Environment Management
environments: Environments
name: Name
team_management: Team Management
team_management: team_management:
title: '{application_name} Team Management' title: '{application_name} Team Management'
subheading: Team Management subheading: Team Management