Skip to content

Commit

Permalink
[Enhancement] "On hold" instead of "Need info" state
Browse files Browse the repository at this point in the history
  • Loading branch information
elias-boulharts committed Dec 11, 2023
1 parent d0ce224 commit 3d0a4a7
Show file tree
Hide file tree
Showing 30 changed files with 188 additions and 125 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Enhancement

- Add a transition in FSM to switch from "NEED INFO" to "ACCEPTED"
- Add a transition in FSM to switch from "ON HOLD" to "ACCEPTED"
- Add field "enabled" in ApprovalWorkflow
- Remove "operations" field in ApprovalWorkflow form when editing
- Add the number of items displayed at the end of lists
Expand Down
8 changes: 4 additions & 4 deletions Squest/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ def home(request):

if request.user.has_perm('service_catalog.list_request'):
context['total_request'] = sum([x["count"] for x in all_requests if x["state"] == RequestState.SUBMITTED])
context['total_request_need_info'] = sum(
[x["count"] for x in all_requests if x["state"] == RequestState.NEED_INFO])
context['total_request_on_hold'] = sum(
[x["count"] for x in all_requests if x["state"] == RequestState.ON_HOLD])

if request.user.has_perm('service_catalog.list_instance'):
context['total_instance'] = sum([x["count"] for x in all_instances if x["state"] == InstanceState.AVAILABLE])
Expand Down Expand Up @@ -77,8 +77,8 @@ def home(request):
x["state"] == RequestState.FAILED and x[
"instance__service"] == service.id])

service_dict["need_info_requests"] = sum([x["count"] for x in all_requests if
x["state"] == RequestState.NEED_INFO and x[
service_dict["hold_requests"] = sum([x["count"] for x in all_requests if
x["state"] == RequestState.ON_HOLD and x[
"instance__service"] == service.id])

service_dict["opened_supports"] = sum([x["count"] for x in all_supports if
Expand Down
6 changes: 3 additions & 3 deletions docs/administration/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ squest_request_per_state_total{state="ACCEPTED"} 4.0
squest_request_per_state_total{state="CANCELED"} 3.0
squest_request_per_state_total{state="COMPLETE"} 5.0
squest_request_per_state_total{state="FAILED"} 4.0
squest_request_per_state_total{state="NEED_INFO"} 2.0
squest_request_per_state_total{state="ON_HOLD"} 2.0
squest_request_per_state_total{state="PROCESSING"} 3.0
squest_request_per_state_total{state="REJECTED"} 5.0
squest_request_per_state_total{state="SUBMITTED"} 4.00
Expand Down Expand Up @@ -111,7 +111,7 @@ E.g:
squest_request_total{service="VMWare",state="COMPLETE"} 3.0
squest_request_total{service="VMWare",state="PROCESSING"} 2.0
squest_request_total{service="VMWare",state="ACCEPTED"} 2.0
squest_request_total{service="VMWare",state="NEED_INFO"} 1.0
squest_request_total{service="VMWare",state="ON_HOLD"} 1.0
squest_request_total{service="VMWare",state="REJECTED"} 4.0
squest_request_total{service="VMWare",state="SUBMITTED"} 1.0
squest_request_total{service="VMWare",state="FAILED"} 1.0
Expand All @@ -125,7 +125,7 @@ squest_request_total{service="Kubernetes",state="SUBMITTED"} 1.0
squest_request_total{service="Kubernetes",state="COMPLETE"} 1.0
squest_request_total{service="Kubernetes",state="CANCELED"} 1.0
squest_request_total{service="Kubernetes",state="PROCESSING"} 1.0
squest_request_total{service="Kubernetes",state="NEED_INFO"} 1.0
squest_request_total{service="Kubernetes",state="ON_HOLD"} 1.0
```

### squest_support_total
Expand Down
19 changes: 11 additions & 8 deletions docs/dev/request-state-machine.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,32 @@ graph TB
style auto_accept fill:#80CBC4
instance_pending([instance pending])
submitted --> instance_pending
submitted --> |re-submit|submitted
instance_pending --> auto_accept
accepted[ACCEPTED]
auto_accept -->|Yes| accepted
admin_action_1{admin action}
style admin_action_1 fill:#80DEEA
auto_accept -->|No| admin_action_1
need_info[NEED_INFO]
admin_action_1 -->|need_info| need_info
on_hold[ON_HOLD]
admin_action_1 -->|on_hold| on_hold
admin_action_1 -->|cancel| canceled
admin_action_1 -->|reject| rejected
admin_action_1 -->|accept| accepted
rejected[REJECTED]
need_info -->|reject| rejected
need_info -->|Submit| submitted
on_hold -->|reject| rejected
canceled[CANCELED]
need_info --> |cancel|canceled
on_hold --> |cancel|canceled
on_hold --> |accept|accepted
rejected --> |cancel|canceled
submitted --> |cancel|canceled
submitted -->|reject| rejected
canceled --> |delete| deleted
deleted((Deleted))
auto_pocess{auto process?}
style auto_pocess fill:#80CBC4
accepted --> auto_pocess
accepted -->|reject| rejected
accepted -->|review| accepted
accepted -->|cancel| canceled
auto_pocess --> |Yes| operation_type
admin_action_2{admin action}
auto_pocess --> |No| admin_action_2
Expand All @@ -60,7 +63,7 @@ graph TB
processing_ok --> |Yes| complete
processing_ok --> |No| failed
failed --> |retry| processing
failed --> |cancel| accepted
failed --> |review| accepted
archived[ARCHIVED]
complete -->|archive| archived
archived -->|unarchive| complete
Expand Down
27 changes: 16 additions & 11 deletions docs/manual/administration/rbac.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

Role-based access control (RBAC), is a mechanism that restricts Squest access.
It involves setting **permissions** to enable access to authorized users. Permissions are then grouped
into **Roles** and given to a scope which can be a _team_ or and _organizations_ or _global_. **RBAC** is the link between a role, a scope and a user.
into **Roles** and given to a scope which can be a _team_ or and _organizations_ or _global_. **RBAC** is the link
between a role, a scope and a user.

The Squest RBAC system enable an administrator to grant users or groups the ability to perform an action
on arbitrary subsets of objects in Squest.
Expand All @@ -13,12 +14,13 @@ on arbitrary subsets of objects in Squest.

Permission in Squest represent a relationship with following components:

- **Name:** A short description of the permission.
- **Name:** A short description of the permission.
- **Codename:** A unique identifier for the permission with camel case format.
- **Content type:** A Squest object (E.g: Request, Instance)

For example, a permission named "Can request a day2 operation on instance" attached to the content type "instance".
This permission is required, like the name is suggesting, to create a request for a day 2 operation on an existing instance.
This permission is required, like the name is suggesting, to create a request for a day 2 operation on an existing
instance.

All objects have **generic** CRUD (Create, Retrieve/List, Update, Delete) permissions by default:

Expand All @@ -35,7 +37,7 @@ All objects have **generic** CRUD (Create, Retrieve/List, Update, Delete) permis
**Specific** Squest permissions:

| Short description | Codename | Object |
| ----------------------------------------------- | --------------------------- | ------------ |
|-------------------------------------------------|-----------------------------|--------------|
| Can add users in global scope | add_users_globalscope | globalscope |
| Can delete users in global scope | delete_users_globalscope | globalscope |
| Can view users in global scope | view_users_globalscope | globalscope |
Expand All @@ -57,7 +59,7 @@ All objects have **generic** CRUD (Create, Retrieve/List, Update, Delete) permis
| Can accept request | accept_request | request |
| Can archive request | archive_request | request |
| Can cancel request | cancel_request | request |
| Can ask info request | need_info_request | request |
| Can ask info request | hold_request | request |
| Can process request | process_request | request |
| Can reject request | reject_request | request |
| Can re-submit request | re_submit_request | request |
Expand All @@ -74,7 +76,8 @@ All objects have **generic** CRUD (Create, Retrieve/List, Update, Delete) permis

## Global permissions

Global permissions are permissions granted to all logged Squest user. Permissions are purely additive (there are no "deny" rules).
Global permissions are permissions granted to all logged Squest user. Permissions are purely additive (there are no "
deny" rules).

!!!warning

Expand All @@ -86,12 +89,14 @@ Owner permissions are permissions granted to the owner of an Instance, Request o

Are considered as owner:

* `requester` for Instance
* `user` for Request
* `opened_by` for Support
* `requester` for Instance
* `user` for Request
* `opened_by` for Support

Configuring "view_instance" permissions within the Owner Permissions will grant users the ability to see all instances for which they are the requester.
Adding "view_support" permission will grant users the ability to see all supports related to their instances and supports they opened.
Configuring "view_instance" permissions within the Owner Permissions will grant users the ability to see all instances
for which they are the requester.
Adding "view_support" permission will grant users the ability to see all supports related to their instances and
supports they opened.

!!!warning

Expand Down
2 changes: 1 addition & 1 deletion profiles/templatetags/squest_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def generate_sidebar(user):
'permission_required': 'service_catalog.list_request',
'active': [
"request_list", "request_create", "request_edit", "request_delete",
"request_details", "request_cancel", "request_need_info", "request_re_submit", "request_reject",
"request_details", "request_cancel", "request_on_hold", "request_re_submit", "request_reject",
"request_accept", "request_process", "request_archive", "request_unarchive", "request_approve",
"request_bulk_delete", "request_archived_list", "requestmessage_edit", "requestmessage_create"
]
Expand Down
4 changes: 2 additions & 2 deletions service_catalog/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
name='api_request_archive'),
path('request/<int:pk>/cancel/', RequestStateMachine.as_view({'post': 'cancel'}),
name='api_request_cancel'),
path('request/<int:pk>/need-info/', RequestStateMachine.as_view({'post': 'need_info'}),
name='api_request_need_info'),
path('request/<int:pk>/hold/', RequestStateMachine.as_view({'post': 'hold'}),
name='api_request_on_hold'),
path('request/<int:pk>/process/', RequestStateMachine.as_view({'post': 'process'}),
name='api_request_process'),
path('request/<int:pk>/re-submit/', RequestStateMachine.as_view({'post': 're_submit'}),
Expand Down
10 changes: 5 additions & 5 deletions service_catalog/api/views/request_state_machine_api_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,22 +100,22 @@ def re_submit(self, request, pk=None):

@swagger_auto_schema(request_body=MessageSerializer, responses={200: AdminRequestSerializer()})
@action(detail=True)
def need_info(self, request, pk=None):
def hold(self, request, pk=None):
"""
Ask for more info : change the state of the request to 'NEED_INFO'.
Ask for more info : change the state of the request to 'ON_HOLD'.
"""
target_request = get_object_or_404(Request, id=pk)
if not request.user.has_perm('service_catalog.need_info_request', target_request):
if not request.user.has_perm('service_catalog.hold_request', target_request):
raise PermissionDenied
if not can_proceed(target_request.need_info):
if not can_proceed(target_request.on_hold):
raise PermissionDenied
data = deepcopy(request.data)
data['sender'] = request.user.id
data['request'] = target_request.id
message = RequestMessageSerializer(data=data)
if message.is_valid():
message.save()
target_request.need_info()
target_request.on_hold()
target_request.save()
send_mail_request_update(target_request, user_applied_state=request.user, message=message)
return Response(AdminRequestSerializer(target_request).data, status=status.HTTP_200_OK)
Expand Down
2 changes: 1 addition & 1 deletion service_catalog/management/commands/insert_testing_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def handle(self, *args, **options):
type=OperationType.DELETE,
service=service,
job_template=job_templates.get(name="Demo Job Template"))
states = [RequestState.SUBMITTED, RequestState.FAILED, RequestState.ACCEPTED, RequestState.NEED_INFO,
states = [RequestState.SUBMITTED, RequestState.FAILED, RequestState.ACCEPTED, RequestState.ON_HOLD,
RequestState.REJECTED, RequestState.CANCELED, RequestState.PROCESSING, RequestState.COMPLETE]
for i in range(random.randint(1, 3)):
for username in users:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 4.2.6 on 2023-12-11 12:53

from django.db import migrations, models
import django_fsm


class Migration(migrations.Migration):

dependencies = [
('service_catalog', '0038_alter_towersurveyfield_unique_together_and_more'),
]

operations = [
migrations.AlterModelOptions(
name='request',
options={'default_permissions': ('add', 'change', 'delete', 'view', 'list'), 'ordering': ['-last_updated'], 'permissions': [('accept_request', 'Can accept request'), ('cancel_request', 'Can cancel request'), ('reject_request', 'Can reject request'), ('archive_request', 'Can archive request'), ('unarchive_request', 'Can unarchive request'), ('re_submit_request', 'Can re-submit request'), ('process_request', 'Can process request'), ('hold_request', 'Can hold request'), ('view_admin_survey', 'Can view admin survey'), ('list_approvers', 'Can view who can accept')]},
),
migrations.AlterField(
model_name='request',
name='state',
field=django_fsm.FSMIntegerField(choices=[(1, 'SUBMITTED'), (2, 'ON_HOLD'), (3, 'REJECTED'), (4, 'CANCELED'), (5, 'ACCEPTED'), (6, 'PROCESSING'), (7, 'COMPLETE'), (8, 'FAILED'), (9, 'ARCHIVED')], default=1),
),
migrations.AlterField(
model_name='requesthook',
name='state',
field=models.IntegerField(choices=[(1, 'SUBMITTED'), (2, 'ON_HOLD'), (3, 'REJECTED'), (4, 'CANCELED'), (5, 'ACCEPTED'), (6, 'PROCESSING'), (7, 'COMPLETE'), (8, 'FAILED'), (9, 'ARCHIVED')]),
),
]
12 changes: 6 additions & 6 deletions service_catalog/models/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class Meta:
("unarchive_request", "Can unarchive request"),
("re_submit_request", "Can re-submit request"),
("process_request", "Can process request"),
("need_info_request", "Can ask info request"),
("hold_request", "Can hold request"),
("view_admin_survey", "Can view admin survey"),
("list_approvers", "Can view who can accept"),
]
Expand Down Expand Up @@ -158,8 +158,8 @@ def can_process(self):
def tower_job_url(self):
return f"{self.operation.job_template.tower_server.url}/#/jobs/playbook/{self.tower_job_id}/output"

@transition(field=state, source=RequestState.SUBMITTED, target=RequestState.NEED_INFO)
def need_info(self):
@transition(field=state, source=RequestState.SUBMITTED, target=RequestState.ON_HOLD)
def on_hold(self):
pass

@transition(field=state, source=[RequestState.SUBMITTED], target=RequestState.SUBMITTED)
Expand All @@ -168,15 +168,15 @@ def re_submit(self, save=True):
if save:
self.save()
@transition(field=state, source=[RequestState.SUBMITTED,
RequestState.NEED_INFO,
RequestState.ON_HOLD,
RequestState.REJECTED,
RequestState.ACCEPTED], target=RequestState.CANCELED)
def cancel(self):
if self.instance.state == InstanceState.PENDING:
self.instance.abort()
self.instance.save()

@transition(field=state, source=[RequestState.SUBMITTED, RequestState.ACCEPTED, RequestState.NEED_INFO],
@transition(field=state, source=[RequestState.SUBMITTED, RequestState.ACCEPTED, RequestState.ON_HOLD],
target=RequestState.REJECTED)
def reject(self, user):
if self.instance.state == InstanceState.PENDING:
Expand All @@ -186,7 +186,7 @@ def reject(self, user):
return True

@transition(field=state,
source=[RequestState.ACCEPTED, RequestState.SUBMITTED, RequestState.FAILED, RequestState.NEED_INFO],
source=[RequestState.ACCEPTED, RequestState.SUBMITTED, RequestState.FAILED, RequestState.ON_HOLD],
target=RequestState.ACCEPTED)
def accept(self, user, save=True):
self.accepted_by = user
Expand Down
2 changes: 1 addition & 1 deletion service_catalog/models/request_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

class RequestState(IntegerChoices):
SUBMITTED = 1, "SUBMITTED"
NEED_INFO = 2, "NEED_INFO"
ON_HOLD = 2, "ON_HOLD"
REJECTED = 3, "REJECTED"
CANCELED = 4, "CANCELED"
ACCEPTED = 5, "ACCEPTED"
Expand Down
2 changes: 1 addition & 1 deletion service_catalog/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

# Request State Machine
path('request/<int:pk>/cancel/', views.request_cancel, name='request_cancel'),
path('request/<int:pk>/need-info/', views.request_need_info, name='request_need_info'),
path('request/<int:pk>/hold/', views.request_hold, name='request_on_hold'),
path('request/<int:pk>/re-submit/', views.RequestReSubmitView.as_view(), name='request_re_submit'),
path('request/<int:pk>/reject/', views.request_reject, name='request_reject'),
path('request/<int:pk>/accept/', views.request_accept, name='request_accept'),
Expand Down
2 changes: 1 addition & 1 deletion service_catalog/views/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

map_dict_request_state = {
RequestState.ACCEPTED: "primary",
RequestState.NEED_INFO: "warning",
RequestState.ON_HOLD: "warning",
RequestState.SUBMITTED: "info",
RequestState.REJECTED: "dark",
RequestState.PROCESSING: "orange",
Expand Down
6 changes: 3 additions & 3 deletions service_catalog/views/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ def can_proceed_request_action(args):
target_request = Request.objects.get(id=args.split(',')[1])
if target_action == "cancel":
return can_proceed(target_request.cancel)
elif target_action == "need_info":
return can_proceed(target_request.need_info)
elif target_action == "on_hold":
return can_proceed(target_request.on_hold)
elif target_action == "reject":
return can_proceed(target_request.reject)
elif target_action == "accept":
Expand Down Expand Up @@ -154,7 +154,7 @@ def squest_date_format(date_to_format):

@register.filter(name="map_color_next_state")
def map_color_next_state(current_state, next_state):
if current_state == "NEED_INFO":
if current_state == "ON_HOLD":
return "warning"
if current_state == next_state:
return "primary"
Expand Down
10 changes: 5 additions & 5 deletions service_catalog/views/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,18 +173,18 @@ def requestmessage_edit(request, request_id, pk):


@login_required
def request_need_info(request, pk):
def request_hold(request, pk):
target_request = get_object_or_404(Request, id=pk)
if not request.user.has_perm('service_catalog.need_info_request', target_request):
if not request.user.has_perm('service_catalog.hold_request', target_request):
raise PermissionDenied
if not can_proceed(target_request.need_info):
if not can_proceed(target_request.on_hold):
raise PermissionDenied
if request.method == "POST":
form = RequestMessageForm(request.POST or None, request.FILES or None, sender=request.user,
target_request=target_request)
if form.is_valid():
message = form.save(send_notification=False)
target_request.need_info()
target_request.on_hold()
target_request.save()
send_mail_request_update(target_request, user_applied_state=request.user, message=message)
return redirect(target_request.get_absolute_url())
Expand All @@ -199,7 +199,7 @@ def request_need_info(request, pk):
'target_request': target_request,
'breadcrumbs': breadcrumbs
}
return render(request, "service_catalog/admin/request/request-need-info.html", context)
return render(request, "service_catalog/admin/request/request-hold.html", context)



Expand Down
Loading

0 comments on commit 3d0a4a7

Please sign in to comment.