Added role members to view
This commit is contained in:
parent
3b2d4674f6
commit
a4f0fb3ea6
2 changed files with 143 additions and 1 deletions
|
|
@ -10,7 +10,7 @@ from rest_framework.response import Response
|
||||||
from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN
|
from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN
|
||||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||||
|
|
||||||
from apps.accounts.models import Organization, Role
|
from apps.accounts.models import Organization, Role, User
|
||||||
from apps.accounts.permissions import CanManageOrganization, can_manage_organization
|
from apps.accounts.permissions import CanManageOrganization, can_manage_organization
|
||||||
from apps.onboarding.models import AgentConfig, AgentInteractionLog, OnboardingFlow, OnboardingSession
|
from apps.onboarding.models import AgentConfig, AgentInteractionLog, OnboardingFlow, OnboardingSession
|
||||||
from apps.onboarding.serializers import AgentConfigSerializer, AgentInteractionLogSerializer, OnboardingFlowSerializer, OnboardingSessionSerializer
|
from apps.onboarding.serializers import AgentConfigSerializer, AgentInteractionLogSerializer, OnboardingFlowSerializer, OnboardingSessionSerializer
|
||||||
|
|
@ -309,12 +309,115 @@ class OnboardingSessionViewSet(ModelViewSet):
|
||||||
if role_uuid:
|
if role_uuid:
|
||||||
queryset = queryset.filter(role__uuid=role_uuid)
|
queryset = queryset.filter(role__uuid=role_uuid)
|
||||||
|
|
||||||
|
user_uuid = self.request.query_params.get('user_uuid')
|
||||||
|
if user_uuid in (None, ''):
|
||||||
|
user_uuid = self.request.data.get('user_uuid')
|
||||||
|
if user_uuid:
|
||||||
|
if not user.is_manager and str(user.uuid) != str(user_uuid):
|
||||||
|
raise PermissionDenied('You can only view your own progress sessions.')
|
||||||
|
queryset = queryset.filter(user__uuid=user_uuid)
|
||||||
|
|
||||||
|
flow_uuid = self.request.query_params.get('flow_uuid')
|
||||||
|
if flow_uuid in (None, ''):
|
||||||
|
flow_uuid = self.request.data.get('flow_uuid')
|
||||||
|
if flow_uuid:
|
||||||
|
queryset = queryset.filter(state__flow_uuid=str(flow_uuid))
|
||||||
|
|
||||||
status_value = self.request.query_params.get('status')
|
status_value = self.request.query_params.get('status')
|
||||||
if status_value:
|
if status_value:
|
||||||
queryset = queryset.filter(status=status_value)
|
queryset = queryset.filter(status=status_value)
|
||||||
|
|
||||||
return queryset.order_by('-created_at')
|
return queryset.order_by('-created_at')
|
||||||
|
|
||||||
|
@action(detail=False, methods=['get'], url_path='progress-overview')
|
||||||
|
def progress_overview(self, request):
|
||||||
|
user = request.user
|
||||||
|
|
||||||
|
role_uuid = request.query_params.get('role_uuid')
|
||||||
|
|
||||||
|
if user.is_manager:
|
||||||
|
roles_qs = Role.objects.filter(
|
||||||
|
Q(organization__owner=user) | Q(organization__members=user)
|
||||||
|
).distinct()
|
||||||
|
else:
|
||||||
|
roles_qs = Role.objects.filter(members=user)
|
||||||
|
|
||||||
|
if role_uuid:
|
||||||
|
roles_qs = roles_qs.filter(uuid=role_uuid)
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
|
||||||
|
for role in roles_qs.order_by('name'):
|
||||||
|
flows = list(OnboardingFlow.objects.filter(role=role).order_by('-updated_at'))
|
||||||
|
if not flows:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if user.is_manager:
|
||||||
|
learners = list(role.members.all().order_by('first_name', 'last_name', 'email_address'))
|
||||||
|
else:
|
||||||
|
learners = [user] if role.members.filter(id=user.id).exists() else []
|
||||||
|
|
||||||
|
if not learners:
|
||||||
|
continue
|
||||||
|
|
||||||
|
role_sessions = list(
|
||||||
|
OnboardingSession.objects.filter(role=role, user__in=learners)
|
||||||
|
.select_related('user')
|
||||||
|
.order_by('-updated_at')
|
||||||
|
)
|
||||||
|
|
||||||
|
latest_by_user_flow = {}
|
||||||
|
for session in role_sessions:
|
||||||
|
state = session.state if isinstance(session.state, dict) else {}
|
||||||
|
session_flow_uuid = str(state.get('flow_uuid') or '')
|
||||||
|
if not session_flow_uuid:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = (session.user_id, session_flow_uuid)
|
||||||
|
if key not in latest_by_user_flow:
|
||||||
|
latest_by_user_flow[key] = session
|
||||||
|
|
||||||
|
for flow in flows:
|
||||||
|
flow_uuid_str = str(flow.uuid)
|
||||||
|
for learner in learners:
|
||||||
|
latest_session = latest_by_user_flow.get((learner.id, flow_uuid_str))
|
||||||
|
|
||||||
|
latest_status = latest_session.status if latest_session else 'not_started'
|
||||||
|
state = latest_session.state if latest_session and isinstance(latest_session.state, dict) else {}
|
||||||
|
progress = state.get('progress_percentage', state.get('progress', 0))
|
||||||
|
|
||||||
|
rows.append({
|
||||||
|
'role': {
|
||||||
|
'uuid': str(role.uuid),
|
||||||
|
'name': role.name,
|
||||||
|
},
|
||||||
|
'user': {
|
||||||
|
'uuid': str(learner.uuid),
|
||||||
|
'name': str(getattr(learner, 'full_name', '')).strip() or learner.email_address,
|
||||||
|
'email': learner.email_address,
|
||||||
|
},
|
||||||
|
'flow': {
|
||||||
|
'uuid': flow_uuid_str,
|
||||||
|
'title': flow.title,
|
||||||
|
'is_active': flow.is_active,
|
||||||
|
},
|
||||||
|
'latest_status': latest_status,
|
||||||
|
'progress': int(progress) if isinstance(progress, (int, float)) else 0,
|
||||||
|
'is_completed': latest_status == 'completed',
|
||||||
|
'latest_session_uuid': str(latest_session.uuid) if latest_session else None,
|
||||||
|
'updated_at': latest_session.updated_at.isoformat() if latest_session and latest_session.updated_at else None,
|
||||||
|
})
|
||||||
|
|
||||||
|
rows.sort(
|
||||||
|
key=lambda row: (
|
||||||
|
str(row.get('role', {}).get('name', '')),
|
||||||
|
str(row.get('user', {}).get('name', '')),
|
||||||
|
str(row.get('flow', {}).get('title', '')),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(rows, status=HTTP_200_OK)
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
return Response(
|
return Response(
|
||||||
{'error': 'Use onboarding-flow/<uuid>/start-session/ to create a session.'},
|
{'error': 'Use onboarding-flow/<uuid>/start-session/ to create a session.'},
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,9 @@ const loading = ref(false)
|
||||||
const creatingRole = ref(false)
|
const creatingRole = ref(false)
|
||||||
const deletingRoleUuid = ref<string | null>(null)
|
const deletingRoleUuid = ref<string | null>(null)
|
||||||
const roleModalVisible = ref(false)
|
const roleModalVisible = ref(false)
|
||||||
|
const roleMembersModalVisible = ref(false)
|
||||||
|
const selectedRoleForMembers = ref<Role | null>(null)
|
||||||
|
const selectedRoleMembers = ref<User[]>([])
|
||||||
const createRoleForm = ref({
|
const createRoleForm = ref({
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
|
@ -186,6 +189,12 @@ const deleteRole = async (role: Role) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openRoleMembersModal = (role: Role) => {
|
||||||
|
selectedRoleForMembers.value = role
|
||||||
|
selectedRoleMembers.value = Array.isArray(role.members) ? role.members : []
|
||||||
|
roleMembersModalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
const createInvite = async () => {
|
const createInvite = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.post<InviteToken>(
|
const response = await apiClient.post<InviteToken>(
|
||||||
|
|
@ -482,6 +491,9 @@ onMounted(async () => {
|
||||||
/>
|
/>
|
||||||
<Space>
|
<Space>
|
||||||
<Tag>{{ item.member_count }} members</Tag>
|
<Tag>{{ item.member_count }} members</Tag>
|
||||||
|
<Button size="small" @click="openRoleMembersModal(item)">
|
||||||
|
View Members
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
danger
|
danger
|
||||||
size="small"
|
size="small"
|
||||||
|
|
@ -548,6 +560,33 @@ onMounted(async () => {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
v-model:open="roleMembersModalVisible"
|
||||||
|
:title="`Members in ${selectedRoleForMembers?.name || 'Role'}`"
|
||||||
|
:footer="null"
|
||||||
|
>
|
||||||
|
<List
|
||||||
|
v-if="selectedRoleMembers.length > 0"
|
||||||
|
:data-source="selectedRoleMembers"
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
<template #renderItem="{ item }">
|
||||||
|
<List.Item>
|
||||||
|
<List.Item.Meta
|
||||||
|
:title="`${item.first_name} ${item.last_name}`"
|
||||||
|
:description="item.email_address"
|
||||||
|
/>
|
||||||
|
<Tag :color="item.is_manager ? 'purple' : 'default'">
|
||||||
|
{{ item.is_manager ? 'Manager' : 'Member' }}
|
||||||
|
</Tag>
|
||||||
|
</List.Item>
|
||||||
|
</template>
|
||||||
|
</List>
|
||||||
|
<Typography.Paragraph v-else type="secondary">
|
||||||
|
No members assigned to this role yet.
|
||||||
|
</Typography.Paragraph>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue