Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion back/admin/appointments/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pytest_factoryboy import register

from admin.appointments.models import Appointment
from misc.mixins import DepartmentsPostGenerationMixin
from misc.factories import DepartmentsPostGenerationMixin


@register
Expand Down
2 changes: 1 addition & 1 deletion back/admin/badges/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pytest_factoryboy import register

from admin.badges.models import Badge
from misc.mixins import DepartmentsPostGenerationMixin
from misc.factories import DepartmentsPostGenerationMixin


@register
Expand Down
2 changes: 1 addition & 1 deletion back/admin/hardware/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pytest_factoryboy import register

from admin.hardware.models import Hardware
from misc.mixins import DepartmentsPostGenerationMixin
from misc.factories import DepartmentsPostGenerationMixin


@register
Expand Down
3 changes: 2 additions & 1 deletion back/admin/integrations/builder_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,8 @@ def post(self, *args, **kwargs):
elif test_type == "execute":
result = integration.execute(user)
elif test_type == "revoke":
result = integration.revoke_user(user)
revoke_result = integration.revoke_user(user)
result = (revoke_result.success, revoke_result.message)

tracker = IntegrationTracker.objects.filter(
integration=integration, for_user=user
Expand Down
14 changes: 10 additions & 4 deletions back/admin/integrations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
WebhookManifestSerializer,
)
from admin.integrations.utils import get_value_from_notation
from admin.people.revoke_result import RevokeResult
from misc.fernet_fields import EncryptedTextField
from misc.fields import EncryptedJSONField
from organization.models import FilteredForManagerQuerySet, Notification
Expand Down Expand Up @@ -218,6 +219,9 @@ class ManifestType(models.IntegerChoices):
bot_token = EncryptedTextField(max_length=10000, default="", blank=True)
bot_id = models.CharField(max_length=100, default="")

def __str__(self):
return self.name

@property
def skip_user_provisioning(self):
return self.manifest_type == Integration.ManifestType.MANUAL_USER_PROVISIONING
Expand Down Expand Up @@ -562,14 +566,16 @@ def needs_user_info(self, user):
def revoke_user(self, user):
if self.skip_user_provisioning:
# should never be triggered
return False, "Cannot revoke manual integration"
return RevokeResult(
result=False, message="Cannot revoke manual integration"
)

self.new_hire = user
self.has_user_context = True

# Renew token if necessary
if not self.renew_key():
return False, "Couldn't renew key"
return RevokeResult(result=False, message="Couldn't renew key")

revoke_manifest = self.manifest.get("revoke", [])

Expand All @@ -585,9 +591,9 @@ def revoke_user(self, user):
success, response = self.run_request(item)

if not success or not self.tracker.steps.last().found_expected:
return False, self.clean_response(response)
return RevokeResult(result=False, message=self.clean_response(response))

return True, ""
return RevokeResult(result=True, message="")

def renew_key(self):
# Oauth2 refreshing access token if needed
Expand Down
16 changes: 8 additions & 8 deletions back/admin/integrations/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,25 +513,25 @@ def test_integration_revoke_user(
"admin.integrations.models.Integration.run_request",
Mock(return_value=(True, Mock())),
):
success, error = integration.revoke_user(new_hire)
assert success
assert error == ""
result = integration.revoke_user(new_hire)
assert result.success
assert result.message == ""

# Revoke user unsuccessfully
with patch(
"admin.integrations.models.Integration.run_request",
Mock(return_value=(False, "Something went wrong")),
):
success, error = integration.revoke_user(new_hire)
assert not success
result = integration.revoke_user(new_hire)
assert not result.success
assert "Something went wrong"

# try the same with a manual integration, this doesn't work as it can't actually
# revoke a user
manual_integration = manual_user_provision_integration_factory()
success, error = manual_integration.revoke_user(new_hire)
assert not success
assert error == "Cannot revoke manual integration"
result = manual_integration.revoke_user(new_hire)
assert not result.success
assert result.message == "Cannot revoke manual integration"


@pytest.mark.django_db
Expand Down
2 changes: 1 addition & 1 deletion back/admin/introductions/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pytest_factoryboy import register

from admin.introductions.models import Introduction
from misc.mixins import DepartmentsPostGenerationMixin
from misc.factories import DepartmentsPostGenerationMixin
from users.factories import EmployeeFactory


Expand Down
50 changes: 49 additions & 1 deletion back/admin/people/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
from django.utils.translation import gettext_lazy as _

from admin.integrations.models import Integration
from admin.sequences.models import Sequence
from admin.sequences.models import IntegrationConfig, Sequence
from admin.sequences.selectors import get_onboarding_sequences_for_user
from admin.templates.forms import (
MultiSelectField,
UploadField,
)
from misc.mixins import FilterDepartmentsFieldByUserMixin
from organization.models import Organization
from users.models import User


class NewHireAddForm(forms.ModelForm):
Expand Down Expand Up @@ -436,3 +437,50 @@ def __init__(self, *args, **kwargs):
class Meta:
model = get_user_model()
fields = ("role",)


class AddUsersToSequenceChoiceForm(forms.Form):
users = forms.ModelMultipleChoiceField(
label=_("Select the users you want to add this sequence to"),
widget=forms.CheckboxSelectMultiple,
queryset=User.objects.none(),
required=False,
)

def __init__(self, *args, **kwargs):
users = kwargs.pop("users")
super().__init__(*args, **kwargs)
self.fields["users"].queryset = users


class AddSequencesToUser(forms.Form):
start_day = forms.DateField(
label=_("Date when they will start in this new role"),
widget=forms.DateInput(attrs={"type": "date"}, format=("%Y-%m-%d")),
)
sequences = forms.ModelMultipleChoiceField(
label=_("Select the sequences you want to add to this user"),
widget=forms.CheckboxSelectMultiple(attrs={"checked": "checked"}),
queryset=Sequence.objects.none(),
required=False,
)

def __init__(self, *args, **kwargs):
sequence_pks = kwargs.pop("sequence_pks")
super().__init__(*args, **kwargs)
self.fields["sequences"].queryset = Sequence.objects.filter(pk__in=sequence_pks)


class ItemsToBeRemovedForm(forms.Form):
integrations = forms.ModelMultipleChoiceField(
label=_("Select the integrations you want to remove from this user"),
widget=forms.CheckboxSelectMultiple(attrs={"checked": ""}),
queryset=IntegrationConfig.objects.none(),
required=False,
)

def __init__(self, *args, **kwargs):
# naming 'items' as it will likely be expanded to other types later
items = kwargs.pop("items")
super().__init__(*args, **kwargs)
self.fields["integrations"].queryset = items
7 changes: 7 additions & 0 deletions back/admin/people/revoke_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from dataclasses import dataclass


@dataclass(frozen=True, slots=True)
class RevokeResult:
success: bool
message: str
50 changes: 50 additions & 0 deletions back/admin/people/templates/_departments_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{% load i18n %}

<div id="departmentslist" hx-swap-oob="true">
{% for department in departments %}
<div class="card mb-4 ignore-styling {% if not is_users_page %}drop{% endif %}" {% if not is_users_page %}data-method="POST" data-url="{% url 'people:toggle_seq_department' department.id %}"{% endif %}>
<div class="card-header">
<div class="col-11">
<h3 class="card-title">{{ department }}</h3>
</div>
<div class="col-1 text-end">
<a class="btn btn-azure btn-sm" href="{% url "people:department_update" department.pk %}">
{% include "_edit_icon.html" %}
</a>
</div>
</div>
<div class="card-body">
{% if not is_users_page %}
{# sequences can be assigned to departments only, instead of roles #}
{% include "_departments_sequences_list.html" with sequences=department.sequences.all %}
{% endif %}
{% for role in department.roles.all %}
<div class="drop mb-2" data-method="post" data-url="{% if is_users_page %}{% url "people:toggle_user_to_role" role.pk %}{% else %}{% url "people:toggle_seq_role" role.pk %}{% endif %}">
<h3 class="card-title mb-2">{{ role }}</h3>
{% if is_users_page %}
{% include "_departments_users_list.html" with users=role.users.all %}
{% if not role.users.all|length %}
{% trans "No users have been added to this role yet." %}
{% endif %}
{% else %}
{% include "_departments_sequences_list.html" with sequences=role.sequences.all %}
{% if not role.sequences.all|length %}
{% trans "No sequences have been added to this role yet." %}
{% endif %}
{% endif %}
</div>
{% empty %}
{% trans "No roles have been added to this department yet." %}
<br/>
<a class="btn btn-primary btn-sm mt-2" href="{% url "people:department_role_create" department.id %}">{% trans "Add role" %}</a>
{% endfor %}
</div>
</div>
{% empty %}
<div class="card mb-4">
<div class="card-header">
{% trans "There are no departments yet." %}
</div>
</div>
{% endfor %}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{% load i18n %}
{% load crispy_forms_tags %}
{% include "_departments_list.html" %}

<div class="modal-dialog modal-dialog-centered">
{% if form.integrations.field.choices %}
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{% trans "Remove additional items from user" %}</h5>
</div>
<div class="modal-body">
<p>{% trans "If you would like to remove any items from the user, then please select/deselect them here" %}</p>
<form hx-post="{{modal_url}}" hx-target="#department-modals">
{{ form|crispy }}
<button class="btn btn-primary">{% trans "Submit" %}</button>
</form>

</div>
</div>
{% endif %}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{% load i18n %}
{% load crispy_forms_tags %}
{% include "_departments_list.html" %}

<div class="modal-dialog modal-dialog-centered">
{% if form.users.field.choices %}
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{% trans "Add sequence to users" %}</h5>
</div>
<div class="modal-body">
<p>{% trans "If you would like to apply this sequence to any current users directly, then feel free to do so here" %}</p>
<form hx-post="{{modal_url}}">
{{ form|crispy }}
<button class="btn btn-primary">{% trans "Submit" %}</button>
</form>

</div>
</div>
{% endif %}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{% load i18n %}
{% load crispy_forms_tags %}
{% include "_departments_list.html" %}

<div class="modal-dialog modal-dialog-centered">
{% if form.sequences.field.choices %}
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{% trans "Add sequences to user" %}</h5>
</div>
<div class="modal-body">
<p>{% trans "The sequences within this role/department are added by default, feel free to unselect some if they are not applicable" %}</p>
<form hx-post="{{modal_url}}">
{{ form|crispy }}
<button class="btn btn-primary">{% trans "Submit" %}</button>
</form>
</div>
</div>
{% endif %}
</div>
20 changes: 20 additions & 0 deletions back/admin/people/templates/_departments_sequences_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{% load i18n %}
{% if sequences %}
<ul class="list-group list-group-flush">
{% for seq in sequences %}
<li class="list-group-item p-1">
{{ seq.name }}
<form hx-delete="
{% if role %}
{% url 'people:toggle_seq_role' role.pk %}?item={{seq.pk}}
{% else %}
{% url 'people:toggle_seq_department' department.pk %}?item={{seq.pk}}
{% endif %}
" hx-target="#departmentslist" class="d-inline">
{% csrf_token %}
<button class="btn btn-danger btn-sm inline">{% trans "remove" %}</button>
</form>
</li>
{% endfor %}
</ul>
{% endif %}
19 changes: 19 additions & 0 deletions back/admin/people/templates/_departments_users_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% load i18n %}
{% if users %}
<ul class="list-group list-group-flush">
{% for user in users %}
<li class="list-group-item p-1">
{% if user.profile_image is not None %}
<span class="avatar me-2 avatar-xs" style="background-image: url({{ user.profile_image.get_url }})"></span>
{% else %}
<span class="avatar me-2 avatar-xs">{{ user.initials }}</span>
{% endif %}
{{ user.name }}
<form hx-delete="{% url 'people:toggle_user_to_role' role.pk %}?item={{user.pk}}" hx-target="#department-modals" class="d-inline">
{% csrf_token %}
<button class="btn btn-danger btn-sm inline">{% trans "remove" %}</button>
</form>
</li>
{% endfor %}
</ul>
{% endif %}
19 changes: 19 additions & 0 deletions back/admin/people/templates/_integration_revoke_results.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% load i18n %}
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{% trans "Remove additional items from user" %}</h5>
</div>
<div class="modal-body">
{% if results|length > 0 %}
<p>{% trans "Revoked items:" %}</p>
{% for result in results %}
<p>{{ integration_name }}: {% if result.success %}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-check"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l5 5l10 -10" /></svg>{% else %}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-x"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M18 6l-12 12" /><path d="M6 6l12 12" /></svg> - {{ result.message }}{% endif %}</p>
{% endfor %}
{% else %}
<p>{% trans "No items were revoked." %}</p>
{% endif %}
<a href="{{access_url}}" class="btn btn-primary">{% trans "See active integrations" %}</a>
</div>
</div>
</div>
2 changes: 1 addition & 1 deletion back/admin/people/templates/department_create.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<h3 class="card-title">{% translate "New department" %}</h3>
</div>
<div class="card-body">
<form action="." method="post">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary">{% trans "Create" %}</button>
Expand Down
Loading