diff --git a/api/controllers/console/tag/tags.py b/api/controllers/console/tag/tags.py index b9e876c906..346f572ccc 100644 --- a/api/controllers/console/tag/tags.py +++ b/api/controllers/console/tag/tags.py @@ -25,6 +25,10 @@ class TagBasePayload(BaseModel): type: TagType = Field(description="Tag type") +class TagUpdateRequestPayload(BaseModel): + name: str = Field(description="Tag name", min_length=1, max_length=50) + + class TagBindingPayload(BaseModel): tag_ids: list[str] = Field(description="Tag IDs to bind") target_id: str = Field(description="Target ID to bind tags to") @@ -68,6 +72,7 @@ class TagResponse(ResponseModel): register_schema_models( console_ns, TagBasePayload, + TagUpdateRequestPayload, TagBindingPayload, TagBindingRemovePayload, TagListQueryParam, @@ -118,7 +123,7 @@ class TagListApi(Resource): @console_ns.route("/tags/") class TagUpdateDeleteApi(Resource): - @console_ns.expect(console_ns.models[TagBasePayload.__name__]) + @console_ns.expect(console_ns.models[TagUpdateRequestPayload.__name__]) @setup_required @login_required @account_initialization_required @@ -129,8 +134,8 @@ class TagUpdateDeleteApi(Resource): if not (current_user.has_edit_permission or current_user.is_dataset_editor): raise Forbidden() - payload = TagBasePayload.model_validate(console_ns.payload or {}) - tag = TagService.update_tags(UpdateTagPayload(name=payload.name, type=payload.type), tag_id) + payload = TagUpdateRequestPayload.model_validate(console_ns.payload or {}) + tag = TagService.update_tags(UpdateTagPayload(name=payload.name), tag_id) binding_count = TagService.get_tag_binding_count(tag_id) diff --git a/api/controllers/service_api/dataset/dataset.py b/api/controllers/service_api/dataset/dataset.py index 9af66f1960..d85e46498d 100644 --- a/api/controllers/service_api/dataset/dataset.py +++ b/api/controllers/service_api/dataset/dataset.py @@ -31,7 +31,9 @@ from services.tag_service import ( TagBindingCreatePayload, TagBindingDeletePayload, TagService, - UpdateTagPayload, +) +from services.tag_service import ( + UpdateTagPayload as UpdateTagServicePayload, ) register_enum_models(service_api_ns, DatasetPermissionEnum) @@ -556,7 +558,7 @@ class DatasetTagsApi(DatasetApiResource): payload = TagUpdatePayload.model_validate(service_api_ns.payload or {}) tag_id = payload.tag_id - tag = TagService.update_tags(UpdateTagPayload(name=payload.name, type=TagType.KNOWLEDGE), tag_id) + tag = TagService.update_tags(UpdateTagServicePayload(name=payload.name), tag_id) binding_count = TagService.get_tag_binding_count(tag_id) diff --git a/api/openapi/markdown/console-swagger.md b/api/openapi/markdown/console-swagger.md index 8a7495cb8d..88410d6507 100644 --- a/api/openapi/markdown/console-swagger.md +++ b/api/openapi/markdown/console-swagger.md @@ -7515,7 +7515,7 @@ Remove one or more tag bindings from a target. | Name | Located in | Description | Required | Schema | | ---- | ---------- | ----------- | -------- | ------ | | tag_id | path | | Yes | string | -| payload | body | | Yes | [TagBasePayload](#tagbasepayload) | +| payload | body | | Yes | [TagUpdateRequestPayload](#tagupdaterequestpayload) | ##### Responses @@ -13456,6 +13456,12 @@ Tag type | ---- | ---- | ----------- | -------- | | TagType | string | Tag type | | +#### TagUpdateRequestPayload + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| name | string | Tag name | Yes | + #### TenantAccountRole | Name | Type | Description | Required | diff --git a/api/services/tag_service.py b/api/services/tag_service.py index 8043a99be1..09d49d8b3e 100644 --- a/api/services/tag_service.py +++ b/api/services/tag_service.py @@ -21,7 +21,6 @@ class SaveTagPayload(BaseModel): class UpdateTagPayload(BaseModel): name: str = Field(min_length=1, max_length=50) - type: TagType class TagBindingCreatePayload(BaseModel): diff --git a/api/tests/test_containers_integration_tests/services/test_tag_service.py b/api/tests/test_containers_integration_tests/services/test_tag_service.py index 583b6128e6..f088cc964d 100644 --- a/api/tests/test_containers_integration_tests/services/test_tag_service.py +++ b/api/tests/test_containers_integration_tests/services/test_tag_service.py @@ -759,7 +759,7 @@ class TestTagService: tag = TagService.save_tags(tag_args) # Update args - update_args = UpdateTagPayload(name="updated_name", type="knowledge") + update_args = UpdateTagPayload(name="updated_name") # Act: Execute the method under test result = TagService.update_tags(update_args, tag.id) @@ -799,7 +799,7 @@ class TestTagService: non_existent_tag_id = str(uuid.uuid4()) - update_args = UpdateTagPayload(name="updated_name", type="knowledge") + update_args = UpdateTagPayload(name="updated_name") # Act & Assert: Verify proper error handling with pytest.raises(NotFound) as exc_info: @@ -830,7 +830,7 @@ class TestTagService: tag2 = TagService.save_tags(tag2_args) # Try to update second tag with first tag's name - update_args = UpdateTagPayload(name="first_tag", type="app") + update_args = UpdateTagPayload(name="first_tag") # Act & Assert: Verify proper error handling with pytest.raises(ValueError) as exc_info: diff --git a/api/tests/unit_tests/controllers/console/tag/test_tags.py b/api/tests/unit_tests/controllers/console/tag/test_tags.py index 8b47da25fb..b44706c566 100644 --- a/api/tests/unit_tests/controllers/console/tag/test_tags.py +++ b/api/tests/unit_tests/controllers/console/tag/test_tags.py @@ -14,6 +14,7 @@ from controllers.console.tag.tags import ( TagUpdateDeleteApi, ) from models.enums import TagType +from services.tag_service import UpdateTagPayload def unwrap(func): @@ -147,7 +148,7 @@ class TestTagUpdateDeleteApi: api = TagUpdateDeleteApi() method = unwrap(api.patch) - payload = {"name": "updated", "type": "knowledge"} + payload = {"name": "updated"} with app.test_request_context("/", json=payload): with ( @@ -159,7 +160,7 @@ class TestTagUpdateDeleteApi: patch( "controllers.console.tag.tags.TagService.update_tags", return_value=tag, - ), + ) as update_tags_mock, patch( "controllers.console.tag.tags.TagService.get_tag_binding_count", return_value=3, @@ -168,6 +169,9 @@ class TestTagUpdateDeleteApi: result, status = method(api, "tag-1") assert status == 200 + update_payload, tag_id = update_tags_mock.call_args.args + assert update_payload == UpdateTagPayload(name="updated") + assert tag_id == "tag-1" assert result["binding_count"] == "3" def test_patch_forbidden(self, app: Flask, readonly_user, payload_patch): diff --git a/web/contract/console/tags.ts b/web/contract/console/tags.ts index 4a1d7c80d0..438da8a95e 100644 --- a/web/contract/console/tags.ts +++ b/web/contract/console/tags.ts @@ -48,7 +48,7 @@ export const tagUpdateContract = base name: string } }>()) - .output(type()) + .output(type()) export const tagDeleteContract = base .route({ diff --git a/web/features/tag-management/components/tag-item-editor.tsx b/web/features/tag-management/components/tag-item-editor.tsx index 0e47eafa60..09f8dbf45e 100644 --- a/web/features/tag-management/components/tag-item-editor.tsx +++ b/web/features/tag-management/components/tag-item-editor.tsx @@ -30,7 +30,6 @@ export const TagItemEditor = ({ tag, onTagsChange }: TagItemEditorProps) => { const updateTagMutation = useMutation(consoleQuery.tags.update.mutationOptions()) const deleteTagMutation = useMutation(consoleQuery.tags.delete.mutationOptions()) const [isEditing, setIsEditing] = useState(false) - const [name, setName] = useState(tag.name) const editTag = (tagId: string, name: string) => { if (name === tag.name) { setIsEditing(false) @@ -38,7 +37,6 @@ export const TagItemEditor = ({ tag, onTagsChange }: TagItemEditorProps) => { } if (!name) { toast.error('tag name is empty') - setName(tag.name) setIsEditing(false) return } @@ -53,13 +51,11 @@ export const TagItemEditor = ({ tag, onTagsChange }: TagItemEditorProps) => { }, { onSuccess: () => { toast.success(t('actionMsg.modifiedSuccessfully', { ns: 'common' })) - setName(name) setIsEditing(false) onTagsChange?.() }, onError: () => { toast.error(t('actionMsg.modifiedUnsuccessfully', { ns: 'common' })) - setName(tag.name) setIsEditing(false) }, }) @@ -123,7 +119,22 @@ export const TagItemEditor = ({ tag, onTagsChange }: TagItemEditorProps) => { )} - {isEditing && ( setName(e.target.value)} onKeyDown={e => e.key === 'Enter' && editTag(tag.id, name)} onBlur={() => editTag(tag.id, name)} />)} + {isEditing && ( + { + if (e.key !== 'Enter' || e.nativeEvent.isComposing) + return + + e.preventDefault() + e.currentTarget.blur() + }} + onBlur={e => editTag(tag.id, e.currentTarget.value)} + /> + )} !open && setShowRemoveModal(false)}> diff --git a/web/service/client.spec.ts b/web/service/client.spec.ts index 57ca8765e7..641cbda7c6 100644 --- a/web/service/client.spec.ts +++ b/web/service/client.spec.ts @@ -187,15 +187,20 @@ describe('consoleQuery tag mutation defaults', () => { queryClient.setQueryData(appListKey, [targetTag, otherTag]) queryClient.setQueryData(knowledgeListKey, [knowledgeTag]) + const updatedTag = createTag({ + ...targetTag, + name: 'After', + binding_count: 5, + }) const mutationOptions = consoleQuery.tags.update.mutationOptions() await mutationOptions.onSuccess?.( - undefined, + updatedTag, { params: { tagId: targetTag.id, }, body: { - name: 'After', + name: 'Ignored Client Name', }, }, undefined, @@ -203,10 +208,7 @@ describe('consoleQuery tag mutation defaults', () => { ) expect(queryClient.getQueryData(appListKey)).toEqual([ - { - ...targetTag, - name: 'After', - }, + updatedTag, otherTag, ]) expect(queryClient.getQueryData(knowledgeListKey)).toEqual([knowledgeTag]) diff --git a/web/service/client.ts b/web/service/client.ts index 1c33423295..984779e2b2 100644 --- a/web/service/client.ts +++ b/web/service/client.ts @@ -108,16 +108,13 @@ export const consoleQuery = createTanstackQueryUtils(consoleClient, { }, update: { mutationOptions: { - onSuccess: (_data, variables, _onMutateResult, context) => { + onSuccess: (updatedTag, variables, _onMutateResult, context) => { context.client.setQueriesData( { queryKey: consoleQuery.tags.list.key({ type: 'query' }), }, (oldTags: Tag[] | undefined) => oldTags?.map(tag => tag.id === variables.params.tagId - ? { - ...tag, - name: variables.body.name, - } + ? updatedTag : tag), ) },