Merge pull request #799 from dod-ccpo/edit-env-form-view-part-2
Edit env form view part 2
This commit is contained in:
commit
4c2b6c331b
@ -98,7 +98,7 @@ class Environments(object):
|
||||
environment = Environments.get(environment_id)
|
||||
|
||||
for member in team_roles:
|
||||
new_role = member["role"]
|
||||
new_role = member["role_name"]
|
||||
user = Users.get(member["user_id"])
|
||||
Environments.update_env_role(
|
||||
environment=environment, user=user, new_role=new_role
|
||||
@ -113,6 +113,15 @@ class Environments(object):
|
||||
environment=environment, user=member, new_role=new_role
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_members_by_role(cls, env, role):
|
||||
return (
|
||||
db.session.query(EnvironmentRole)
|
||||
.filter(EnvironmentRole.environment_id == env.id)
|
||||
.filter(EnvironmentRole.role == role)
|
||||
.all()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def revoke_access(cls, environment, target_user):
|
||||
EnvironmentRoles.delete(environment.id, target_user.id)
|
||||
|
@ -1,16 +1,32 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms.fields import StringField, HiddenField, RadioField, FieldList, FormField
|
||||
from wtforms.fields import FieldList, FormField, HiddenField, RadioField, StringField
|
||||
|
||||
from .forms import BaseForm
|
||||
from .data import ENV_ROLES
|
||||
|
||||
|
||||
class EnvMemberRoleForm(FlaskForm):
|
||||
name = StringField()
|
||||
class MemberForm(FlaskForm):
|
||||
user_id = HiddenField()
|
||||
role = RadioField(choices=ENV_ROLES, coerce=BaseForm.remove_empty_string)
|
||||
user_name = StringField()
|
||||
role_name = RadioField(choices=ENV_ROLES, default="no_access")
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
_data = super().data
|
||||
if "role_name" in _data and _data["role_name"] == "no_access":
|
||||
_data["role_name"] = None
|
||||
return _data
|
||||
|
||||
|
||||
class EnvironmentRolesForm(BaseForm):
|
||||
team_roles = FieldList(FormField(EnvMemberRoleForm))
|
||||
class RoleForm(FlaskForm):
|
||||
role = HiddenField()
|
||||
members = FieldList(FormField(MemberForm))
|
||||
|
||||
|
||||
class EnvironmentRolesForm(FlaskForm):
|
||||
team_roles = FieldList(FormField(RoleForm))
|
||||
env_id = HiddenField()
|
||||
|
||||
|
||||
class AppEnvRolesForm(BaseForm):
|
||||
envs = FieldList(FormField(EnvironmentRolesForm))
|
||||
|
@ -217,4 +217,6 @@ REQUIRED_DISTRIBUTIONS = [
|
||||
("other", "Other as necessary"),
|
||||
]
|
||||
|
||||
ENV_ROLES = [(role.value, role.value) for role in CSPRole] + [(None, "No access")]
|
||||
ENV_ROLES = [(role.value, role.value) for role in CSPRole] + [
|
||||
("no_access", "No access")
|
||||
]
|
||||
|
@ -21,7 +21,7 @@ class Environment(
|
||||
|
||||
@property
|
||||
def users(self):
|
||||
return [r.user for r in self.roles]
|
||||
return {r.user for r in self.roles}
|
||||
|
||||
@property
|
||||
def num_users(self):
|
||||
|
@ -1,10 +1,9 @@
|
||||
from flask import redirect, render_template, request as http_request, url_for
|
||||
|
||||
from . import applications_bp
|
||||
from atst.domain.environment_roles import EnvironmentRoles
|
||||
from atst.domain.environments import Environments
|
||||
from atst.domain.applications import Applications
|
||||
from atst.forms.app_settings import EnvironmentRolesForm
|
||||
from atst.forms.app_settings import AppEnvRolesForm
|
||||
from atst.forms.application import ApplicationForm, EditEnvironmentForm
|
||||
from atst.domain.authz.decorator import user_can_access_decorator as user_can
|
||||
from atst.models.environment_role import CSPRole
|
||||
@ -20,37 +19,59 @@ def get_environments_obj_for_app(application):
|
||||
"id": env.id,
|
||||
"name": env.name,
|
||||
"edit_form": EditEnvironmentForm(obj=env),
|
||||
"members_form": EnvironmentRolesForm(data=data_for_env_members_form(env)),
|
||||
"members": sort_env_users_by_role(env),
|
||||
"member_count": len(env.users),
|
||||
"members": [user.full_name for user in env.users],
|
||||
}
|
||||
environments_obj.append(env_data)
|
||||
|
||||
return environments_obj
|
||||
|
||||
|
||||
def sort_env_users_by_role(env):
|
||||
users_dict = {"no_access": []}
|
||||
for role in CSPRole:
|
||||
users_dict[role.value] = []
|
||||
def serialize_members(member_list, role):
|
||||
serialized_list = []
|
||||
|
||||
for user in env.application.users:
|
||||
if user in env.users:
|
||||
role = EnvironmentRoles.get(user.id, env.id)
|
||||
users_dict[role.displayname].append(
|
||||
{"name": user.full_name, "user_id": user.id}
|
||||
for member in member_list:
|
||||
serialized_list.append(
|
||||
{
|
||||
"user_id": str(member.user_id),
|
||||
"user_name": member.user.full_name,
|
||||
"role_name": role,
|
||||
}
|
||||
)
|
||||
else:
|
||||
users_dict["no_access"].append({"name": user.full_name, "user_id": user.id})
|
||||
|
||||
return users_dict
|
||||
return serialized_list
|
||||
|
||||
|
||||
def data_for_env_members_form(environment):
|
||||
data = {"env_id": environment.id, "team_roles": []}
|
||||
for user in environment.users:
|
||||
env_role = EnvironmentRoles.get(user.id, environment.id)
|
||||
data["team_roles"].append(
|
||||
{"name": user.full_name, "user_id": user.id, "role": env_role.displayname}
|
||||
def sort_env_users_by_role(env):
|
||||
users_list = []
|
||||
no_access_users = env.application.users - env.users
|
||||
no_access_list = [
|
||||
{"user_id": str(user.id), "user_name": user.full_name, "role_name": "no_access"}
|
||||
for user in no_access_users
|
||||
]
|
||||
users_list.append({"role": "no_access", "members": no_access_list})
|
||||
|
||||
for role in CSPRole:
|
||||
users_list.append(
|
||||
{
|
||||
"role": role.value,
|
||||
"members": serialize_members(
|
||||
Environments.get_members_by_role(env, role.value), role.value
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return users_list
|
||||
|
||||
|
||||
def data_for_app_env_roles_form(application):
|
||||
data = {"envs": []}
|
||||
for environment in application.environments:
|
||||
data["envs"].append(
|
||||
{
|
||||
"env_id": environment.id,
|
||||
"team_roles": sort_env_users_by_role(environment),
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
@ -67,15 +88,19 @@ def check_users_are_in_application(user_ids, application):
|
||||
@applications_bp.route("/applications/<application_id>/settings")
|
||||
@user_can(Permissions.VIEW_APPLICATION, message="view application edit form")
|
||||
def settings(application_id):
|
||||
# refactor like portfolio admin render function
|
||||
application = Applications.get(application_id)
|
||||
form = ApplicationForm(name=application.name, description=application.description)
|
||||
environments_obj = get_environments_obj_for_app(application=application)
|
||||
members_form = AppEnvRolesForm(data=data_for_app_env_roles_form(application))
|
||||
|
||||
return render_template(
|
||||
"portfolios/applications/settings.html",
|
||||
application=application,
|
||||
form=form,
|
||||
environments_obj=get_environments_obj_for_app(application=application),
|
||||
environments_obj=environments_obj,
|
||||
members_form=members_form,
|
||||
active_toggler=http_request.args.get("active_toggler"),
|
||||
active_toggler_section=http_request.args.get("active_toggler_section"),
|
||||
)
|
||||
|
||||
|
||||
@ -98,6 +123,8 @@ def update_environment(environment_id):
|
||||
application_id=application.id,
|
||||
fragment="application-environments",
|
||||
_anchor="application-environments",
|
||||
active_toggler=environment.id,
|
||||
active_toggler_section="edit",
|
||||
)
|
||||
)
|
||||
else:
|
||||
@ -109,6 +136,11 @@ def update_environment(environment_id):
|
||||
name=application.name, description=application.description
|
||||
),
|
||||
environments_obj=get_environments_obj_for_app(application=application),
|
||||
members_form=AppEnvRolesForm(
|
||||
data=data_for_app_env_roles_form(application)
|
||||
),
|
||||
active_toggler=environment.id,
|
||||
active_toggler_section="edit",
|
||||
),
|
||||
400,
|
||||
)
|
||||
@ -143,13 +175,17 @@ def update(application_id):
|
||||
def update_env_roles(environment_id):
|
||||
environment = Environments.get(environment_id)
|
||||
application = environment.application
|
||||
env_roles_form = EnvironmentRolesForm(http_request.form)
|
||||
|
||||
if env_roles_form.validate():
|
||||
form = AppEnvRolesForm(formdata=http_request.form)
|
||||
|
||||
if form.validate():
|
||||
env_data = []
|
||||
try:
|
||||
user_ids = [user["user_id"] for user in env_roles_form.data["team_roles"]]
|
||||
for env in form.envs.data:
|
||||
if env["env_id"] == str(environment.id):
|
||||
for role in env["team_roles"]:
|
||||
user_ids = [user["user_id"] for user in role["members"]]
|
||||
check_users_are_in_application(user_ids, application)
|
||||
env_data = env_data + role["members"]
|
||||
except NotFoundError as err:
|
||||
app.logger.warning(
|
||||
"User {} requested environment role change for unauthorized user {} in application {}.".format(
|
||||
@ -159,22 +195,36 @@ def update_env_roles(environment_id):
|
||||
)
|
||||
|
||||
raise (err)
|
||||
env_data = env_roles_form.data
|
||||
|
||||
Environments.update_env_roles_by_environment(
|
||||
environment_id=environment_id, team_roles=env_data["team_roles"]
|
||||
environment_id=environment_id, team_roles=env_data
|
||||
)
|
||||
|
||||
flash("application_environment_members_updated")
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
"applications.settings",
|
||||
application_id=application.id,
|
||||
fragment="application-environments",
|
||||
_anchor="application-environments",
|
||||
active_toggler=environment.id,
|
||||
active_toggler_section="members",
|
||||
)
|
||||
)
|
||||
return redirect(url_for("applications.settings", application_id=application.id))
|
||||
else:
|
||||
# TODO: Create a better pattern to handle when a form doesn't validate
|
||||
# if a user is submitting the data via the web page then they
|
||||
# should never have any form validation errors
|
||||
return render_template(
|
||||
return (
|
||||
render_template(
|
||||
"portfolios/applications/settings.html",
|
||||
application=application,
|
||||
form=ApplicationForm(
|
||||
name=application.name, description=application.description
|
||||
),
|
||||
environments_obj=get_environments_obj_for_app(application=application),
|
||||
active_toggler=environment.id,
|
||||
active_toggler_section="edit",
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
|
||||
|
@ -7,6 +7,11 @@ MESSAGES = {
|
||||
"message_template": 'The environment "{{ environment_name }}" has been deleted',
|
||||
"category": "success",
|
||||
},
|
||||
"application_environment_members_updated": {
|
||||
"title_template": "Application environment members updated",
|
||||
"message_template": "Application environment members have been updated",
|
||||
"category": "success",
|
||||
},
|
||||
"application_environments_updated": {
|
||||
"title_template": "Application environments updated",
|
||||
"message_template": "Application environments have been updated",
|
||||
|
94
js/components/__tests__/edit_environment_role.test.js
Normal file
94
js/components/__tests__/edit_environment_role.test.js
Normal file
@ -0,0 +1,94 @@
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import EditEnvironmentRole from '../forms/edit_environment_role'
|
||||
|
||||
describe('EditEnvironmentRole', () => {
|
||||
var initialRoleCategories, wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
initialRoleCategories = [
|
||||
{
|
||||
role: 'no_access',
|
||||
members: [
|
||||
{ role_name: null, user_id: '123' },
|
||||
{ role_name: null, user_id: '456' },
|
||||
],
|
||||
},
|
||||
{
|
||||
role: 'Basic Access',
|
||||
members: [{ role_name: 'Basic Access', user_id: '789' }],
|
||||
},
|
||||
{
|
||||
role: 'Network Admin',
|
||||
members: [],
|
||||
},
|
||||
{
|
||||
role: 'Business Read-only',
|
||||
members: [
|
||||
{ role_name: 'Business Read-only', user_id: '012' },
|
||||
{ role_name: 'Business Read-only', user_id: '345' },
|
||||
],
|
||||
},
|
||||
{
|
||||
role: 'Technical Read-only',
|
||||
members: [{ role_name: 'Technical Read-only', user_id: '678' }],
|
||||
},
|
||||
]
|
||||
|
||||
wrapper = shallowMount(EditEnvironmentRole, {
|
||||
propsData: { initialRoleCategories },
|
||||
})
|
||||
})
|
||||
|
||||
it('removes null roles to no_access', () => {
|
||||
let roles = wrapper.vm.sanitizeValues([
|
||||
{ role: 'no_access', members: [{ role_name: null }] },
|
||||
])
|
||||
expect(roles).toEqual([
|
||||
{ role: 'no_access', members: [{ role_name: 'no_access' }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('gets the data for a user', () => {
|
||||
let member_data = wrapper.vm.getUserInfo('678')
|
||||
|
||||
expect(member_data).toEqual({
|
||||
role_name: 'Technical Read-only',
|
||||
user_id: '678',
|
||||
})
|
||||
})
|
||||
|
||||
it('removes a user from role', () => {
|
||||
let techRole = wrapper.vm.roleCategories.find(role => {
|
||||
return role.role === 'Technical Read-only'
|
||||
})
|
||||
|
||||
expect(techRole.members.length).toEqual(1)
|
||||
wrapper.vm.removeUser('678')
|
||||
expect(techRole.members.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('adds user to a role', () => {
|
||||
let techRole = wrapper.vm.roleCategories.find(role => {
|
||||
return role.role === 'Technical Read-only'
|
||||
})
|
||||
|
||||
expect(techRole.members.length).toEqual(1)
|
||||
wrapper.vm.addUser({ user_id: '901' }, 'Technical Read-only')
|
||||
expect(techRole.members.length).toEqual(2)
|
||||
})
|
||||
|
||||
it('updates users role', () => {
|
||||
let techRole = wrapper.vm.roleCategories.find(role => {
|
||||
return role.role === 'Technical Read-only'
|
||||
})
|
||||
let businessRole = wrapper.vm.roleCategories.find(role => {
|
||||
return role.role === 'Business Read-only'
|
||||
})
|
||||
|
||||
expect(techRole.members.length).toEqual(1)
|
||||
expect(businessRole.members.length).toEqual(2)
|
||||
wrapper.vm.updateRoles('678', 'Business Read-only')
|
||||
expect(techRole.members.length).toEqual(0)
|
||||
expect(businessRole.members.length).toEqual(3)
|
||||
})
|
||||
})
|
@ -1,63 +1,96 @@
|
||||
import FormMixin from '../../mixins/form'
|
||||
import textinput from '../text_input'
|
||||
import Selector from '../selector'
|
||||
import Modal from '../../mixins/modal'
|
||||
import toggler from '../toggler'
|
||||
|
||||
// Note: If refactoring consider using nested vue components as suggested by Dan:
|
||||
// https://github.com/dod-ccpo/atst/pull/799/files#r282240663
|
||||
// May also want to reconsider the data structure by storing the roles and members separately
|
||||
|
||||
export default {
|
||||
name: 'edit-environment-role',
|
||||
|
||||
mixins: [FormMixin, Modal],
|
||||
|
||||
components: {
|
||||
toggler,
|
||||
Modal,
|
||||
Selector,
|
||||
textinput,
|
||||
},
|
||||
mixins: [FormMixin],
|
||||
|
||||
props: {
|
||||
choices: Array,
|
||||
initialData: String,
|
||||
applicationId: String,
|
||||
initialRoleCategories: Array,
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
new_role: this.initialData,
|
||||
selectedSection: null,
|
||||
roleCategories: this.sanitizeValues(this.initialRoleCategories),
|
||||
}
|
||||
},
|
||||
|
||||
mounted: function() {
|
||||
this.$root.$on('revoke-' + this.applicationId, this.revoke)
|
||||
},
|
||||
|
||||
methods: {
|
||||
change: function(e) {
|
||||
this.new_role = e.target.value
|
||||
sanitizeValues: function(roles) {
|
||||
roles.forEach(role => {
|
||||
role.members.forEach(member => {
|
||||
if (member.role_name === null) {
|
||||
member.role_name = 'no_access'
|
||||
}
|
||||
})
|
||||
})
|
||||
return roles
|
||||
},
|
||||
cancel: function() {
|
||||
this.new_role = this.initialData
|
||||
|
||||
checkNoAccess: function(role) {
|
||||
return role === 'no_access'
|
||||
},
|
||||
revoke: function() {
|
||||
this.new_role = ''
|
||||
|
||||
toggleSection: function(sectionName) {
|
||||
if (this.selectedSection === sectionName) {
|
||||
this.selectedSection = null
|
||||
} else {
|
||||
this.selectedSection = sectionName
|
||||
}
|
||||
},
|
||||
|
||||
onInput: function(e) {
|
||||
this.changed = true
|
||||
this.updateRoles(e.target.attributes['user-id'].value, e.target.value)
|
||||
this.showError = false
|
||||
this.showValid = true
|
||||
},
|
||||
|
||||
getUserInfo: function(userId) {
|
||||
for (let role of this.roleCategories) {
|
||||
for (let member of role.members) {
|
||||
if (member.user_id === userId) {
|
||||
return member
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
removeUser: function(userId) {
|
||||
for (let role of this.roleCategories) {
|
||||
role.members = role.members.filter(member => {
|
||||
return member.user_id !== userId
|
||||
})
|
||||
if (!role.members) {
|
||||
role.members = []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addUser: function(userInfo, newRole) {
|
||||
this.roleCategories.forEach(role => {
|
||||
if (role.role === newRole) {
|
||||
userInfo.role_name = newRole
|
||||
role.members.push(userInfo)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
updateRoles: function(userId, newRole) {
|
||||
var userInfo = this.getUserInfo(userId)
|
||||
this.removeUser(userId)
|
||||
this.addUser(userInfo, newRole)
|
||||
this.toggleSection()
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
displayName: function() {
|
||||
const newRole = this.newRole
|
||||
for (var arr in this.choices) {
|
||||
if (this.choices[arr][0] == newRole) {
|
||||
return this.choices[arr][1].name
|
||||
}
|
||||
}
|
||||
},
|
||||
label_class: function() {
|
||||
return this.newRole === '' ? 'label' : 'label label--success'
|
||||
},
|
||||
newRole: function() {
|
||||
return this.new_role
|
||||
},
|
||||
render: function(createElement) {
|
||||
return createElement('p', 'Please implement inline-template')
|
||||
},
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import editEnvironmentRole from './forms/edit_environment_role'
|
||||
import FormMixin from '../mixins/form'
|
||||
import optionsinput from './options_input'
|
||||
import textinput from './text_input'
|
||||
@ -7,7 +8,16 @@ export default {
|
||||
|
||||
mixins: [FormMixin],
|
||||
|
||||
props: {
|
||||
initialSelectedSection: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
editEnvironmentRole,
|
||||
optionsinput,
|
||||
textinput,
|
||||
optionsinput,
|
||||
},
|
||||
|
@ -25,11 +25,14 @@
|
||||
.app-team-settings-link {
|
||||
font-size: $small-font-size;
|
||||
font-weight: $font-normal;
|
||||
padding-left: $gap * 2;
|
||||
}
|
||||
|
||||
.environment-roles {
|
||||
padding: 0 ($gap * 3) ($gap * 3);
|
||||
}
|
||||
|
||||
.environment-role {
|
||||
padding: $gap * 3;
|
||||
padding: ($gap * 2) 0;
|
||||
|
||||
h4 {
|
||||
margin-bottom: $gap / 4;
|
||||
@ -50,16 +53,43 @@
|
||||
margin: $gap;
|
||||
white-space: nowrap;
|
||||
width: 20rem;
|
||||
position: relative;
|
||||
height: 3.6rem;
|
||||
|
||||
&.unassigned {
|
||||
border: solid 1px $color-gray-light;
|
||||
}
|
||||
|
||||
.icon-link {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.environment-role__user-field {
|
||||
position: absolute;
|
||||
background-color: $color-white;
|
||||
margin-top: $gap * 2;
|
||||
padding: $gap;
|
||||
left: -0.1rem;
|
||||
border: solid 1px $color-gray-light;
|
||||
width: 20rem;
|
||||
z-index: 3;
|
||||
|
||||
.usa-input {
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
background-color: $color-white;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.environment-role__no-user {
|
||||
margin: $gap;
|
||||
padding: ($gap / 2) $gap;
|
||||
font-weight: $font-normal;
|
||||
height: 3.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,3 +124,19 @@
|
||||
font-weight: $font-normal;
|
||||
color: $color-gray-medium;
|
||||
}
|
||||
|
||||
.application-list-item {
|
||||
.usa-button-primary {
|
||||
width: $search-button-width * 2;
|
||||
}
|
||||
|
||||
.action-group-cancel {
|
||||
position: relative;
|
||||
|
||||
.action-group-cancel__action {
|
||||
position: absolute;
|
||||
right: $search-button-width * 2 + $gap * 2;
|
||||
top: -($gap * 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,8 @@
|
||||
</span>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro ToggleSection(section_name) %}
|
||||
<div v-show="selectedSection === '{{ section_name }}'">
|
||||
{% macro ToggleSection(section_name, classes="") %}
|
||||
<div v-show="selectedSection === '{{ section_name }}'" class='{{ classes }}'>
|
||||
{{ caller() }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
@ -0,0 +1,97 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/save_button.html" import SaveButton %}
|
||||
|
||||
|
||||
{% for env_form in members_form.envs %}
|
||||
{% if env_form.env_id.data == env['id'] %}
|
||||
<div class='app-team-settings-link'>
|
||||
{{ 'fragments.edit_environment_team_form.add_new_member_text' | translate }}
|
||||
<a href='{{ url_for("applications.team", application_id=application.id) }}'>
|
||||
{{ 'fragments.edit_environment_team_form.add_new_member_link' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
<form
|
||||
action="{{ url_for('applications.update_env_roles', environment_id=env['id']) }}"
|
||||
method="post">
|
||||
{{ members_form.csrf_token }}
|
||||
{{ env_form.env_id() }}
|
||||
<edit-environment-role
|
||||
inline-template
|
||||
v-bind:initial-role-categories='{{ env_form.team_roles.data | tojson }}'>
|
||||
<div>
|
||||
<div v-for='(roleCategory, roleindex) in roleCategories' class='environment-role'>
|
||||
<h4 v-if='checkNoAccess(roleCategory.role)'>
|
||||
{{ 'fragments.edit_environment_team_form.unassigned_title' | translate }}
|
||||
</h4>
|
||||
<h4 v-else v-html='roleCategory.role'></h4>
|
||||
<ul class='environment-role__users'>
|
||||
<div
|
||||
v-if="roleCategory.members && !roleCategory.members.length"
|
||||
class='environment-role__no-user'>
|
||||
{{ 'fragments.edit_environment_team_form.no_members' | translate }}
|
||||
</div>
|
||||
<li
|
||||
v-for='(member, memberindex) in roleCategory.members'
|
||||
class="environment-role__user"
|
||||
v-bind:class="{'unassigned': checkNoAccess(member.role_name)}">
|
||||
<span v-html='member.user_name'>
|
||||
</span>
|
||||
<span v-on:click="toggleSection(member.user_id)" class="icon-link right">
|
||||
{{ Icon('edit', classes="icon--medium") }}
|
||||
</span>
|
||||
<div
|
||||
v-show="selectedSection === member.user_id"
|
||||
class='environment-role__user-field'>
|
||||
<div class="usa-input">
|
||||
<fieldset
|
||||
data-ally-disabled="true"
|
||||
class="usa-input__choices"
|
||||
v-on:change="onInput">
|
||||
<ul
|
||||
v-for='(roleCategory, roleinputindex) in roleCategories'
|
||||
v-bind:id="'envs-{{ loop.index0 }}-team_roles-' + roleindex + '-members-' + memberindex + '-role_name'">
|
||||
<li>
|
||||
<input
|
||||
v-bind:checked="member.role_name === roleCategory.role"
|
||||
v-bind:name="'envs-{{ loop.index0 }}-team_roles-' + roleindex + '-members-' + memberindex + '-role_name'"
|
||||
v-bind:id="'envs-{{ loop.index0 }}-team_roles-' + roleindex + '-members-' + memberindex + '-role_name-' + roleinputindex"
|
||||
type="radio"
|
||||
v-bind:user-id='member.user_id'
|
||||
v-bind:value='roleCategory.role'>
|
||||
<label
|
||||
v-bind:for="'envs-{{ loop.index0 }}-team_roles-' + roleindex + '-members-' + memberindex + '-role_name-' + roleinputindex">
|
||||
<span v-if='checkNoAccess(roleCategory.role)'>
|
||||
{{ 'fragments.edit_environment_team_form.no_access' | translate }}
|
||||
</span>
|
||||
<span v-else v-html='roleCategory.role'></span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
v-bind:id="'envs-{{ loop.index0 }}-team_roles-' + roleindex + '-members-' + memberindex + '-user_id'"
|
||||
v-bind:name="'envs-{{ loop.index0 }}-team_roles-' + roleindex + '-members-' + memberindex + '-user_id'"
|
||||
type="hidden"
|
||||
v-bind:value='member.user_id'>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class='action-group'>
|
||||
{{
|
||||
SaveButton(
|
||||
text=("portfolios.applications.update_button_text" | translate)
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</edit-environment-role>
|
||||
<div class='action-group-cancel'>
|
||||
<a class='action-group-cancel__action icon-link icon-link--default' v-on:click="toggleSection('members')">
|
||||
{{ "common.cancel" | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endfor %}
|
@ -1,31 +1,11 @@
|
||||
{% from "components/delete_confirmation.html" import DeleteConfirmation %}
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/modal.html" import Modal %}
|
||||
{% from "components/options_input.html" import OptionsInput %}
|
||||
{% from "components/save_button.html" import SaveButton %}
|
||||
{% from "components/text_input.html" import TextInput %}
|
||||
{% from "components/toggle_list.html" import ToggleButton, ToggleSection %}
|
||||
|
||||
{% macro RolePanel(users=[], role='no_access') %}
|
||||
{% if role == 'no_access' %}
|
||||
{% set role = 'Unassigned (No Access)' %}
|
||||
{% set unassigned = True %}
|
||||
{% endif %}
|
||||
|
||||
<div class='environment-role'>
|
||||
<h4>{{ role }}</h4>
|
||||
<ul class='environment-role__users'>
|
||||
{% for user in users %}
|
||||
<li class="environment-role__user {{ 'unassigned' if unassigned }}">
|
||||
{{ user.name }}{{ Icon('edit', classes="icon--medium right") }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% if users == [] %}
|
||||
<div class='environment-role__no-user'>Currently no members are in this role</div>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
<div class="application-list-item application-list">
|
||||
<header>
|
||||
@ -50,13 +30,10 @@
|
||||
|
||||
<ul class="accordion-table__items">
|
||||
{% for env in environments_obj %}
|
||||
{% set edit_form = env['edit_form'] %}
|
||||
{% set member_count = env['members_form'].data['team_roles'] | length %}
|
||||
{% set members_by_role = env['members'] %}
|
||||
{% set unassigned = members_by_role['no_access'] %}
|
||||
{% set delete_environment_modal_id = "delete_modal_environment{}".format(env['id']) %}
|
||||
{% set edit_form = env['edit_form'] %}
|
||||
|
||||
<toggler inline-template {% if edit_form.errors %}initial-selected-section="edit"{% endif %}>
|
||||
<toggler inline-template {% if active_toggler == (env['id'] | safe) %}initial-selected-section="{{ active_toggler_section }}"{% endif %}>
|
||||
<li class="accordion-table__item">
|
||||
<div class="accordion-table__item-content row">
|
||||
<div class="col col--grow">
|
||||
@ -84,11 +61,11 @@
|
||||
</div>
|
||||
<div class="col col--grow icon-link icon-link--large accordion-table__item__toggler">
|
||||
{% set open_members_button %}
|
||||
{{ "common.members" | translate }} ({{ member_count }}) {{ Icon('caret_down') }}
|
||||
{{ "common.members" | translate }} ({{ env['member_count'] }}) {{ Icon('caret_down') }}
|
||||
{% endset %}
|
||||
|
||||
{% set close_members_button %}
|
||||
{{ "common.members" | translate }} ({{ member_count }}) {{ Icon('caret_up') }}
|
||||
{{ "common.members" | translate }} ({{ env['member_count'] }}) {{ Icon('caret_up') }}
|
||||
{% endset %}
|
||||
|
||||
{{
|
||||
@ -101,11 +78,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% call ToggleSection(section_name="members") %}
|
||||
<div class='app-team-settings-link'>Need to add someone new to the team? <a href='{{ url_for("applications.team", application_id=application.id) }}'>Jump to Team Settings</a></div>
|
||||
{% for role, members in members_by_role.items() %}
|
||||
{{ RolePanel(users=members, role=role) }}
|
||||
{% endfor %}
|
||||
{% call ToggleSection(section_name="members", classes="environment-roles") %}
|
||||
{% include 'fragments/applications/edit_environment_team_form.html' %}
|
||||
{% endcall %}
|
||||
|
||||
{% call ToggleSection(section_name="edit") %}
|
||||
@ -152,3 +126,11 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel__footer">
|
||||
<div class="action-group">
|
||||
<a class='icon-link'>
|
||||
{{ "portfolios.applications.add_environment" | translate }}
|
||||
{{ Icon('plus') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -26,11 +26,11 @@
|
||||
|
||||
<span class="icon-link icon-link--large accordion-table__item__toggler">
|
||||
{% set open_members_button %}
|
||||
{{ "common.members" | translate }} ({{ env['members'] | length }}) {{ Icon('caret_down') }}
|
||||
{{ "common.members" | translate }} ({{ env['member_count'] }}) {{ Icon('caret_down') }}
|
||||
{% endset %}
|
||||
|
||||
{% set close_members_button %}
|
||||
{{ "common.members" | translate }} ({{ env['members'] | length }}) {{ Icon('caret_up') }}
|
||||
{{ "common.members" | translate }} ({{ env['member_count'] }}) {{ Icon('caret_up') }}
|
||||
{% endset %}
|
||||
|
||||
{{
|
||||
@ -47,7 +47,7 @@
|
||||
<ul>
|
||||
{% for member in env['members'] %}
|
||||
<li class="accordion-table__item__expanded">
|
||||
<div class="accordion-table__item__expanded_first">{{ member.name }}</div>
|
||||
<div class="accordion-table__item__expanded_first">{{ member }}</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
@ -68,15 +68,7 @@
|
||||
|
||||
{% if user_can(permissions.EDIT_APPLICATION) %}
|
||||
{% include "fragments/applications/edit_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') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% elif user_can(permissions.VIEW_ENVIRONMENT) %}
|
||||
{% include "fragments/applications/read_only_environments.html" %}
|
||||
{% endif %}
|
||||
|
@ -79,18 +79,18 @@ def test_update_env_roles_by_environment():
|
||||
team_roles = [
|
||||
{
|
||||
"user_id": env_role_1.user.id,
|
||||
"name": env_role_1.user.full_name,
|
||||
"role": CSPRole.BUSINESS_READ.value,
|
||||
"user_name": env_role_1.user.full_name,
|
||||
"role_name": CSPRole.BUSINESS_READ.value,
|
||||
},
|
||||
{
|
||||
"user_id": env_role_2.user.id,
|
||||
"name": env_role_2.user.full_name,
|
||||
"role": CSPRole.NETWORK_ADMIN.value,
|
||||
"user_name": env_role_2.user.full_name,
|
||||
"role_name": CSPRole.NETWORK_ADMIN.value,
|
||||
},
|
||||
{
|
||||
"user_id": env_role_3.user.id,
|
||||
"name": env_role_3.user.full_name,
|
||||
"role": None,
|
||||
"user_name": env_role_3.user.full_name,
|
||||
"role_name": None,
|
||||
},
|
||||
]
|
||||
|
||||
@ -136,6 +136,36 @@ def test_update_env_roles_by_member():
|
||||
assert not EnvironmentRoles.get(user.id, testing.id)
|
||||
|
||||
|
||||
def test_get_members_by_role(db):
|
||||
environment = EnvironmentFactory.create()
|
||||
env_role_1 = EnvironmentRoleFactory.create(
|
||||
environment=environment, role=CSPRole.BASIC_ACCESS.value
|
||||
)
|
||||
env_role_2 = EnvironmentRoleFactory.create(
|
||||
environment=environment, role=CSPRole.TECHNICAL_READ.value
|
||||
)
|
||||
env_role_3 = EnvironmentRoleFactory.create(
|
||||
environment=environment, role=CSPRole.TECHNICAL_READ.value
|
||||
)
|
||||
rando_env = EnvironmentFactory.create()
|
||||
rando_env_role = EnvironmentRoleFactory.create(
|
||||
environment=rando_env, role=CSPRole.BASIC_ACCESS.value
|
||||
)
|
||||
|
||||
basic_access_members = Environments.get_members_by_role(
|
||||
environment, CSPRole.BASIC_ACCESS.value
|
||||
)
|
||||
technical_read_members = Environments.get_members_by_role(
|
||||
environment, CSPRole.TECHNICAL_READ.value
|
||||
)
|
||||
assert basic_access_members == [env_role_1]
|
||||
assert rando_env_role not in basic_access_members
|
||||
assert technical_read_members == [env_role_2, env_role_3]
|
||||
assert (
|
||||
Environments.get_members_by_role(environment, CSPRole.BUSINESS_READ.value) == []
|
||||
)
|
||||
|
||||
|
||||
def test_get_scoped_environments(db):
|
||||
developer = UserFactory.create()
|
||||
portfolio = PortfolioFactory.create(
|
||||
|
@ -22,7 +22,7 @@ from atst.domain.exceptions import NotFoundError
|
||||
from atst.models.environment_role import CSPRole
|
||||
from atst.models.portfolio_role import Status as PortfolioRoleStatus
|
||||
from atst.forms.application import EditEnvironmentForm
|
||||
from atst.forms.app_settings import EnvironmentRolesForm
|
||||
from atst.forms.app_settings import AppEnvRolesForm
|
||||
|
||||
from tests.utils import captured_templates
|
||||
|
||||
@ -48,6 +48,8 @@ def test_updating_application_environments_success(client, user_session):
|
||||
_external=True,
|
||||
fragment="application-environments",
|
||||
_anchor="application-environments",
|
||||
active_toggler=environment.id,
|
||||
active_toggler_section="edit",
|
||||
)
|
||||
assert environment.name == "new name a"
|
||||
|
||||
@ -116,23 +118,88 @@ def test_edit_application_environments_obj(app, client, user_session):
|
||||
assert response.status_code == 200
|
||||
_, context = templates[0]
|
||||
|
||||
assert isinstance(context["members_form"], AppEnvRolesForm)
|
||||
env_obj = context["environments_obj"][0]
|
||||
assert env_obj["name"] == env.name
|
||||
assert env_obj["id"] == env.id
|
||||
assert isinstance(env_obj["edit_form"], EditEnvironmentForm)
|
||||
assert isinstance(env_obj["members_form"], EnvironmentRolesForm)
|
||||
assert env_obj["members"] == {
|
||||
"no_access": [
|
||||
{"name": app_role.user.full_name, "user_id": app_role.user_id}
|
||||
assert (
|
||||
env_obj["members"].sort()
|
||||
== [env_role1.user.full_name, env_role2.user.full_name].sort()
|
||||
)
|
||||
|
||||
|
||||
def test_data_for_app_env_roles_form(app, client, user_session):
|
||||
portfolio = PortfolioFactory.create()
|
||||
application = Applications.create(
|
||||
portfolio,
|
||||
"Snazzy Application",
|
||||
"A new application for me and my friends",
|
||||
{"env"},
|
||||
)
|
||||
env = application.environments[0]
|
||||
app_role = ApplicationRoleFactory.create(application=application)
|
||||
env_role1 = EnvironmentRoleFactory.create(
|
||||
environment=env, role=CSPRole.BASIC_ACCESS.value
|
||||
)
|
||||
ApplicationRoleFactory.create(application=application, user=env_role1.user)
|
||||
env_role2 = EnvironmentRoleFactory.create(
|
||||
environment=env, role=CSPRole.NETWORK_ADMIN.value
|
||||
)
|
||||
ApplicationRoleFactory.create(application=application, user=env_role2.user)
|
||||
|
||||
user_session(portfolio.owner)
|
||||
|
||||
with captured_templates(app) as templates:
|
||||
response = app.test_client().get(
|
||||
url_for("applications.settings", application_id=application.id)
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
_, context = templates[0]
|
||||
|
||||
members_form = context["members_form"]
|
||||
assert isinstance(members_form, AppEnvRolesForm)
|
||||
assert members_form.data == {
|
||||
"envs": [
|
||||
{
|
||||
"env_id": env.id,
|
||||
"team_roles": [
|
||||
{
|
||||
"role": "no_access",
|
||||
"members": [
|
||||
{
|
||||
"user_id": str(app_role.user_id),
|
||||
"user_name": app_role.user.full_name,
|
||||
"role_name": None,
|
||||
}
|
||||
],
|
||||
CSPRole.BASIC_ACCESS.value: [
|
||||
{"name": env_role1.user.full_name, "user_id": env_role1.user_id}
|
||||
},
|
||||
{
|
||||
"role": CSPRole.BASIC_ACCESS.value,
|
||||
"members": [
|
||||
{
|
||||
"user_id": str(env_role1.user_id),
|
||||
"user_name": env_role1.user.full_name,
|
||||
"role_name": CSPRole.BASIC_ACCESS.value,
|
||||
}
|
||||
],
|
||||
CSPRole.NETWORK_ADMIN.value: [
|
||||
{"name": env_role2.user.full_name, "user_id": env_role2.user_id}
|
||||
},
|
||||
{
|
||||
"role": CSPRole.NETWORK_ADMIN.value,
|
||||
"members": [
|
||||
{
|
||||
"user_id": str(env_role2.user_id),
|
||||
"user_name": env_role2.user.full_name,
|
||||
"role_name": CSPRole.NETWORK_ADMIN.value,
|
||||
}
|
||||
],
|
||||
CSPRole.BUSINESS_READ.value: [],
|
||||
CSPRole.TECHNICAL_READ.value: [],
|
||||
},
|
||||
{"role": CSPRole.BUSINESS_READ.value, "members": []},
|
||||
{"role": CSPRole.TECHNICAL_READ.value, "members": []},
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@ -234,19 +301,15 @@ def test_update_team_env_roles(client, user_session):
|
||||
|
||||
app_role = ApplicationRoleFactory.create(application=application)
|
||||
form_data = {
|
||||
"env_id": environment.id,
|
||||
"team_roles-0-user_id": env_role_1.user.id,
|
||||
"team_roles-0-name": env_role_1.user.full_name,
|
||||
"team_roles-0-role": CSPRole.NETWORK_ADMIN.value,
|
||||
"team_roles-1-user_id": env_role_2.user.id,
|
||||
"team_roles-1-name": env_role_2.user.full_name,
|
||||
"team_roles-1-role": CSPRole.BASIC_ACCESS.value,
|
||||
"team_roles-2-user_id": env_role_3.user.id,
|
||||
"team_roles-2-name": env_role_3.user.full_name,
|
||||
"team_roles-2-role": "",
|
||||
"team_roles-3-user_id": app_role.user.id,
|
||||
"team_roles-3-name": app_role.user.full_name,
|
||||
"team_roles-3-role": CSPRole.TECHNICAL_READ.value,
|
||||
"envs-0-env_id": environment.id,
|
||||
"envs-0-team_roles-0-members-0-user_id": app_role.user.id,
|
||||
"envs-0-team_roles-0-members-0-role_name": CSPRole.TECHNICAL_READ.value,
|
||||
"envs-0-team_roles-1-members-0-user_id": env_role_1.user.id,
|
||||
"envs-0-team_roles-1-members-0-role_name": CSPRole.NETWORK_ADMIN.value,
|
||||
"envs-0-team_roles-1-members-1-user_id": env_role_2.user.id,
|
||||
"envs-0-team_roles-1-members-1-role_name": CSPRole.BASIC_ACCESS.value,
|
||||
"envs-0-team_roles-1-members-2-user_id": env_role_3.user.id,
|
||||
"envs-0-team_roles-1-members-2-role_name": "no_access",
|
||||
}
|
||||
|
||||
user_session(application.portfolio.owner)
|
||||
|
@ -333,6 +333,11 @@ fragments:
|
||||
new_application_title: Add a new application
|
||||
edit_environment_team_form:
|
||||
delete_environment_title: Are you sure you want to delete this environment?
|
||||
add_new_member_text: Need to add someone new to the team?
|
||||
add_new_member_link: Jump to Team Settings
|
||||
unassigned_title: Unassigned (No Access)
|
||||
no_members: Currently no members are in this role
|
||||
no_access: No Access
|
||||
edit_user_form:
|
||||
date_last_training_tooltip: When was the last time you completed the IA training? <br> Information Assurance (IA) training is an important step in cyber awareness.
|
||||
save_details_button: Save
|
||||
|
Loading…
x
Reference in New Issue
Block a user