Repository navigation
feat: add overview export permissions and update related components #7854
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| from unittest.mock import MagicMock, patch | ||
|
|
||
| from django.test import TestCase | ||
|
|
||
| from common.exception.app_exception import AppApiException | ||
| from models_provider.models import Model, Status | ||
| from models_provider.serializers.model_serializer import ModelSerializer | ||
| from users.models import User | ||
|
|
||
| MODEL_NAME = "zws-LLM-dp001" | ||
|
|
||
|
|
||
| class ModelWorkspaceUniquenessTests(TestCase): | ||
| def setUp(self): | ||
| self.user = User.objects.create( | ||
| username="model-workspace-uniqueness-user", | ||
| nick_name="Model workspace uniqueness user", | ||
| password="password", | ||
| role="ADMIN", | ||
| ) | ||
| self.provider = MagicMock() | ||
| self.provider.get_model_type_list.return_value = [{"value": "LLM"}] | ||
|
|
||
| def create_model(self, workspace_id): | ||
| return Model.objects.create( | ||
| name=MODEL_NAME, | ||
| status=Status.SUCCESS, | ||
| model_type="LLM", | ||
| model_name="deepseek-chat", | ||
| user=self.user, | ||
| provider="test_provider", | ||
| credential="encrypted-credential", | ||
| model_params_form=[], | ||
| workspace_id=workspace_id, | ||
| ) | ||
|
|
||
| def serializer_data(self, workspace_id): | ||
| return { | ||
| "user_id": str(self.user.id), | ||
| "name": MODEL_NAME, | ||
| "provider": "test_provider", | ||
| "model_type": "LLM", | ||
| "model_name": "deepseek-chat", | ||
| "model_params_form": [], | ||
| "credential": {"api_key": "secret"}, | ||
| "workspace_id": workspace_id, | ||
| } | ||
|
|
||
| def test_create_same_name_in_other_workspace_uses_insert_workspace(self): | ||
| self.create_model("workspace-a") | ||
|
|
||
| with ( | ||
| patch( | ||
| "models_provider.serializers.model_serializer.ModelProvideConstants", {"test_provider": self.provider} | ||
| ), | ||
| patch("models_provider.serializers.model_serializer.UserResourcePermissionSerializer"), | ||
| ): | ||
| result = ModelSerializer.Create(data=self.serializer_data("workspace-a")).insert("workspace-b") | ||
|
|
||
| self.assertEqual("workspace-b", Model.objects.get(id=result["id"]).workspace_id) | ||
|
|
||
| def test_create_same_name_in_same_workspace_is_rejected(self): | ||
| self.create_model("workspace-a") | ||
|
|
||
| with ( | ||
| patch( | ||
| "models_provider.serializers.model_serializer.ModelProvideConstants", {"test_provider": self.provider} | ||
| ), | ||
| self.assertRaises(AppApiException), | ||
| ): | ||
| ModelSerializer.Create(data=self.serializer_data("workspace-a")).insert("workspace-a") | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Overall, the tests cover the intended behavior, but they are somewhat brittle and may pass/fail for the wrong reason. Key issues and suggestions:
Recommended shape: with (
patch("models_provider.serializers.model_serializer.ModelProvideConstants",
{"test_provider": self.provider}),
patch("models_provider.serializers.model_serializer.UserResourcePermissionSerializer") as permission_serializer,
):
permission_serializer.return_value.is_valid.return_value = True
permission_serializer.return_value.validated_data = {}
...Bottom line: the test intent is good, but the mocks are too broad, permission patching is inconsistent, and the rejection test should verify that the duplicate-name rule is the actual cause of failure. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| <script setup lang="ts"> | ||
| import { onMounted, ref } from 'vue' | ||
| import { computed, onMounted, ref } from 'vue' | ||
| import type { ApplicationRankingKind, HomeDateRange, HomeRankingRecord } from '@/api/types' | ||
| import perm from '@/permission' | ||
| import { isSystemResource } from '@/utils/resource-context' | ||
| import MkDateRange from '@/components/mk-date-range/index.vue' | ||
| import type { MkDateRangeValue } from '@/components/mk-date-range/types' | ||
| import { formatTokenNumber, numberFormat } from '@/utils/number' | ||
|
|
@@ -11,6 +13,12 @@ const props = defineProps<{ applicationId: string; initialKind: ApplicationRanki | |
| const emit = defineEmits<{ closed: [] }>() | ||
| const { resourceApi } = useApplicationDetailContext() | ||
|
|
||
| /* 概览排行榜导出需要独立的概览导出权限。 */ | ||
| const canExport = computed(() => { | ||
| if (isSystemResource()) return Boolean(perm.application.system.overviewExport()) | ||
| return Boolean(perm.application.workspace.overviewExport(props.applicationId)) | ||
| }) | ||
|
|
||
| const visible = ref(true) | ||
| const activeKind = ref(props.initialKind) | ||
| const loading = ref(false) | ||
|
|
@@ -104,7 +112,7 @@ onMounted(loadRanking) | |
| <MkSearchInput v-model="searchName" class="w-55!" @change="handleSearch" /> | ||
| </div> | ||
| <!-- 导出当前筛选下的完整用户排行 --> | ||
| <el-button plain :loading="exporting" :disabled="loading" @click="handleExport"> | ||
| <el-button v-if="canExport" plain :loading="exporting" :disabled="loading" @click="handleExport"> | ||
| <MkIcon name="icon_export_outlined" /> | ||
| <span>导出</span> | ||
| </el-button> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Review of the diff1. Correctness / Reactivity
2. Security
3. UX
4. Naming / maintainability
5. Redundant
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The patch is syntactically consistent with the existing
PermissionConstantsenum pattern. I do not see an immediate syntax error, but the following points should be verified:bit_indexuniqueness and bit widthbit_index=43andbit_index=32are not already used within the same permission group/sub-group.RESOURCE_APPLICATION_OVERVIEW_EXPORTwithbit_index=32. If permission masks are stored as 32-bit signed integers, bit index 32 (and 43) will overflow. Ensure the mask is stored asuint64/bigintor otherwise supports these indexes.Authorization scope / least privilege
APPLICATION_OVERVIEW_EXPORTgrants export toRoleConstants.USER. Confirm this is intended and that export does not expose sensitive workspace data to all users. If it does, consider restricting toWORKSPACE_MANAGEor a more specific role.scope=[WORKSPACE_RESOURCE, WORKSPACE]is correct. If export is workspace-level only, includingWORKSPACE_RESOURCEmay unintentionally allow resource-scoped access.PermissionMetaconsistencyis_ee, while the second setsis_ee=is_ee. Confirm this difference is intentional and matches neighboring permissions.resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANAGE]is required for the workspace export and that the constant name is correct.resource_permission_group_list.Naming and placement
BACKEND_API_ACCESSentries look consistent with the existing pattern.Tests / enforcement
bit_indexvalues,RESOURCE_APPLICATION_OVERVIEW_EXPORT.Overall: no definite bug from the diff alone, but the main risks are bit-index collision/overflow and overly broad export authorization for
RoleConstants.USER.