Skip to content

Commit

Permalink
feat: added query user leader information by bk_username (#2031)
Browse files Browse the repository at this point in the history
  • Loading branch information
rolin999 authored Jan 10, 2025
1 parent 523d255 commit 6faf444
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 10 deletions.
8 changes: 8 additions & 0 deletions src/bk-user/bkuser/apis/open_v3/serializers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,11 @@ class TenantUserDepartmentListOutputSLZ(serializers.Serializer):
id = serializers.IntegerField(help_text="部门 ID")
name = serializers.CharField(help_text="部门名称")
ancestors = serializers.ListField(help_text="祖先部门列表", required=False, child=AncestorSLZ(), allow_empty=True)


class TenantUserLeaderListOutputSLZ(serializers.Serializer):
bk_username = serializers.CharField(help_text="蓝鲸用户唯一标识", source="id")
display_name = serializers.SerializerMethodField(help_text="用户展示名称")

def get_display_name(self, obj: TenantUser) -> str:
return TenantUserHandler.generate_tenant_user_display_name(obj)
5 changes: 5 additions & 0 deletions src/bk-user/bkuser/apis/open_v3/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
views.TenantUserDepartmentListApi.as_view(),
name="open_v3.tenant_user.department.list",
),
path(
"users/<str:id>/leaders/",
views.TenantUserLeaderListApi.as_view(),
name="open_v3.tenant_user.leaders.list",
),
]
),
),
Expand Down
15 changes: 13 additions & 2 deletions src/bk-user/bkuser/apis/open_v3/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@
# to the current version of the project delivered to anyone in the future.

from .tenant import TenantListApi
from .user import TenantUserDepartmentListApi, TenantUserDisplayNameListApi, TenantUserRetrieveApi
from .user import (
TenantUserDepartmentListApi,
TenantUserDisplayNameListApi,
TenantUserLeaderListApi,
TenantUserRetrieveApi,
)

__all__ = ["TenantListApi", "TenantUserDisplayNameListApi", "TenantUserRetrieveApi", "TenantUserDepartmentListApi"]
__all__ = [
"TenantListApi",
"TenantUserDisplayNameListApi",
"TenantUserRetrieveApi",
"TenantUserDepartmentListApi",
"TenantUserLeaderListApi",
]
33 changes: 33 additions & 0 deletions src/bk-user/bkuser/apis/open_v3/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import logging
from typing import Dict, List

from django.db.models import QuerySet
from drf_yasg.utils import swagger_auto_schema
from rest_framework import generics, status
from rest_framework.generics import get_object_or_404
Expand All @@ -28,12 +29,14 @@
TenantUserDepartmentListOutputSLZ,
TenantUserDisplayNameListInputSLZ,
TenantUserDisplayNameListOutputSLZ,
TenantUserLeaderListOutputSLZ,
TenantUserRetrieveOutputSLZ,
)
from bkuser.apps.data_source.models import (
DataSourceDepartment,
DataSourceDepartmentRelation,
DataSourceDepartmentUserRelation,
DataSourceUserLeaderRelation,
)
from bkuser.apps.tenant.models import TenantDepartment, TenantUser

Expand Down Expand Up @@ -187,3 +190,33 @@ def _get_dept_ancestors(dept_id: int) -> List[int]:
return []
# 返回的祖先部门默认以降序排列,从根祖先部门 -> 父部门
return list(relation.get_ancestors().values_list("department_id", flat=True))


class TenantUserLeaderListApi(OpenApiCommonMixin, generics.ListAPIView):
"""
根据用户 bk_username 获取用户 Leader 列表信息
"""

pagination_class = None

serializer_class = TenantUserLeaderListOutputSLZ

def get_queryset(self) -> QuerySet[TenantUser]:
tenant_user = get_object_or_404(TenantUser.objects.all(), id=self.kwargs["id"])

leader_ids = list(
DataSourceUserLeaderRelation.objects.filter(user=tenant_user.data_source_user).values_list(
"leader_id", flat=True
)
)

return TenantUser.objects.filter(data_source_user_id__in=leader_ids, tenant_id=tenant_user.tenant_id)

@swagger_auto_schema(
tags=["open_v3.user"],
operation_id="list_user_leader",
operation_description="查询用户 Leader 列表",
responses={status.HTTP_200_OK: TenantUserLeaderListOutputSLZ(many=True)},
)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
53 changes: 53 additions & 0 deletions src/bk-user/support-files/apidocs/en/list_user_leader.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
### Description

Query user's list of leaders

### Parameters

| Name | Type | Required | Description |
|----------------|---------|----------|------------------------------------------------|
| bk_username | string | Yes | Blueking user's unique identifier |

### Request Example

```
// URL Path Parameter
/api/v3/open/tenant/users/mzmwjffhhbjg4fxz/leaders/
```

### Response Example for Status Code 200

```json5
{
"data": [
{
"bk_username": "q9k6bhqks0ckl5ew",
"display_name": "张三"
},
{
"bk_username": "er0ugcammqwf1q5w",
"display_name": "李四"
}
]
}
```

### Response Parameters Description

| Name | Type | Description |
|--------------|--------|-----------------------------------|
| bk_username | string | Blueking user's unique identifier |
| display_name | string | User's display_name |


# Response Example for Non-200 Status Code

```json5
// status_code = 404
{
"error": {
"code": "NOT_FOUND",
"message": "Object not found"
}
}
```
53 changes: 53 additions & 0 deletions src/bk-user/support-files/apidocs/zh/list_user_leader.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
### 描述

查询用户 Leader 列表

### 输入参数

| 参数名称 | 参数类型 | 必选 | 描述 |
|----------------|---------|----|--------------------|
| bk_username | string || 蓝鲸用户唯一标识 |

### 请求示例

```
// URL Path 参数
/api/v3/open/tenant/users/mzmwjffhhbjg4fxz/leaders/
```

### 状态码 200 的响应示例

```json5
{
"data": [
{
"bk_username": "q9k6bhqks0ckl5ew",
"display_name": "张三"
},
{
"bk_username": "er0ugcammqwf1q5w",
"display_name": "李四"
}
]
}
```

### 响应参数说明

| 参数名称 | 参数类型 | 描述 |
|--------------|--------|----------|
| bk_username | string | 蓝鲸用户唯一标识 |
| display_name | string | 用户展示名称 |


### 状态码非 200 的响应示例

```json5
// status_code = 404
{
"error": {
"code": "NOT_FOUND",
"message": "对象未找到"
}
}
```
25 changes: 25 additions & 0 deletions src/bk-user/support-files/resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,28 @@ paths:
appVerifiedRequired: true
resourcePermissionRequired: true
descriptionEn: query user's list of departments

/api/v3/open/tenant/users/{bk_username}/leaders/:
get:
operationId: list_user_leader
description: 查询用户 Leader 列表
tags: []
responses:
default:
description: ''
x-bk-apigateway-resource:
isPublic: true
allowApplyPermission: false
matchSubpath: false
backend:
name: default
method: get
path: /api/v3/open/tenant/users/{bk_username}/leaders/
matchSubpath: false
timeout: 0
pluginConfigs: []
authConfig:
userVerifiedRequired: false
appVerifiedRequired: true
resourcePermissionRequired: true
descriptionEn: Query user's list of leaders
46 changes: 38 additions & 8 deletions src/bk-user/tests/apis/open_v3/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
@pytest.mark.usefixtures("_init_tenant_users_depts")
class TestTenantUserDisplayNameListApi:
def test_standard(self, api_client):
zhangsan_id = TenantUser.objects.get(data_source_user__code="zhangsan").id
lisi_id = TenantUser.objects.get(data_source_user__code="lisi").id
zhangsan_id = TenantUser.objects.get(data_source_user__username="zhangsan").id
lisi_id = TenantUser.objects.get(data_source_user__username="lisi").id
resp = api_client.get(
reverse("open_v3.tenant_user.display_name.list"), data={"bk_usernames": ",".join([zhangsan_id, lisi_id])}
)
Expand All @@ -38,7 +38,7 @@ def test_standard(self, api_client):
assert {t["display_name"] for t in resp.data} == {"张三", "李四"}

def test_with_invalid_bk_usernames(self, api_client):
zhangsan_id = TenantUser.objects.get(data_source_user__code="zhangsan").id
zhangsan_id = TenantUser.objects.get(data_source_user__username="zhangsan").id
resp = api_client.get(
reverse("open_v3.tenant_user.display_name.list"), data={"bk_usernames": ",".join([zhangsan_id, "invalid"])}
)
Expand Down Expand Up @@ -67,7 +67,7 @@ def test_with_invalid_length(self, api_client):
@pytest.mark.usefixtures("_init_tenant_users_depts")
class TestTenantUserRetrieveApi:
def test_standard(self, api_client, random_tenant):
zhangsan = TenantUser.objects.get(data_source_user__code="zhangsan")
zhangsan = TenantUser.objects.get(data_source_user__username="zhangsan")
resp = api_client.get(reverse("open_v3.tenant_user.retrieve", kwargs={"id": zhangsan.id}))
assert resp.status_code == status.HTTP_200_OK
assert resp.data["bk_username"] == zhangsan.id
Expand All @@ -85,7 +85,7 @@ def test_tenant_not_found(self, api_client):
class TestTenantUserDepartmentListApi:
def test_with_not_ancestors(self, api_client):
# with_ancestors = False
zhangsan = TenantUser.objects.get(data_source_user__code="zhangsan")
zhangsan = TenantUser.objects.get(data_source_user__username="zhangsan")
company = TenantDepartment.objects.get(data_source_department__name="公司")
resp = api_client.get(reverse("open_v3.tenant_user.department.list", kwargs={"id": zhangsan.id}))
assert resp.status_code == status.HTTP_200_OK
Expand All @@ -94,7 +94,7 @@ def test_with_not_ancestors(self, api_client):
assert "ancestors" not in resp.data[0]

def test_with_no_ancestors(self, api_client):
zhangsan = TenantUser.objects.get(data_source_user__code="zhangsan")
zhangsan = TenantUser.objects.get(data_source_user__username="zhangsan")
company = TenantDepartment.objects.get(data_source_department__name="公司")
resp = api_client.get(
reverse("open_v3.tenant_user.department.list", kwargs={"id": zhangsan.id}), data={"with_ancestors": True}
Expand All @@ -105,7 +105,7 @@ def test_with_no_ancestors(self, api_client):
assert resp.data[0]["ancestors"] == []

def test_with_ancestors(self, api_client):
lisi = TenantUser.objects.get(data_source_user__code="lisi")
lisi = TenantUser.objects.get(data_source_user__username="lisi")
company = TenantDepartment.objects.get(data_source_department__name="公司")
dept_a = TenantDepartment.objects.get(data_source_department__name="部门A")
dept_aa = TenantDepartment.objects.get(data_source_department__name="中心AA")
Expand All @@ -123,9 +123,39 @@ def test_with_invalid_user(self, api_client):
assert resp.status_code == status.HTTP_404_NOT_FOUND

def test_with_no_department(self, api_client):
freedom = TenantUser.objects.get(data_source_user__code="freedom")
freedom = TenantUser.objects.get(data_source_user__username="freedom")
resp = api_client.get(
reverse("open_v3.tenant_user.department.list", kwargs={"id": freedom.id}), data={"with_ancestors": True}
)
assert resp.status_code == status.HTTP_200_OK
assert len(resp.data) == 0


@pytest.mark.usefixtures("_init_tenant_users_depts")
class TestTenantUserLeaderListApi:
def test_with_single_leader(self, api_client):
lisi = TenantUser.objects.get(data_source_user__username="lisi")
zhangsan = TenantUser.objects.get(data_source_user__username="zhangsan")
resp = api_client.get(reverse("open_v3.tenant_user.leaders.list", kwargs={"id": lisi.id}))
assert resp.status_code == status.HTTP_200_OK
assert resp.data[0]["bk_username"] == zhangsan.id
assert resp.data[0]["display_name"] == "张三"

def test_with_multiple_leader(self, api_client):
lisi = TenantUser.objects.get(data_source_user__username="lisi")
wangwu = TenantUser.objects.get(data_source_user__username="wangwu")
maiba = TenantUser.objects.get(data_source_user__username="maiba")
resp = api_client.get(reverse("open_v3.tenant_user.leaders.list", kwargs={"id": maiba.id}))
assert resp.status_code == status.HTTP_200_OK
assert {t["bk_username"] for t in resp.data} == {wangwu.id, lisi.id}
assert {t["display_name"] for t in resp.data} == {"王五", "李四"}

def test_with_no_leader(self, api_client):
zhangsan = TenantUser.objects.get(data_source_user__username="zhangsan")
resp = api_client.get(reverse("open_v3.tenant_user.leaders.list", kwargs={"id": zhangsan.id}))
assert resp.status_code == status.HTTP_200_OK
assert len(resp.data) == 0

def test_with_invalid_user(self, api_client):
resp = api_client.get(reverse("open_v3.tenant_user.leaders.list", kwargs={"id": "a1e5b2f6c3g7d4h8"}))
assert resp.status_code == status.HTTP_404_NOT_FOUND

0 comments on commit 6faf444

Please sign in to comment.