Added role members to view

This commit is contained in:
Viswamedha Nalabotu 2026-03-10 19:38:47 +00:00
parent 3b2d4674f6
commit a4f0fb3ea6
2 changed files with 143 additions and 1 deletions

View file

@ -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.'},

View file

@ -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>