fix: allow tag rename without type payload (#36182)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
yyh 2026-05-15 09:29:17 +08:00 committed by GitHub
parent 0e16d36edb
commit 194b54bae4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 55 additions and 29 deletions

View File

@ -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/<uuid:tag_id>")
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)

View File

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

View File

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

View File

@ -21,7 +21,6 @@ class SaveTagPayload(BaseModel):
class UpdateTagPayload(BaseModel):
name: str = Field(min_length=1, max_length=50)
type: TagType
class TagBindingCreatePayload(BaseModel):

View File

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

View File

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

View File

@ -48,7 +48,7 @@ export const tagUpdateContract = base
name: string
}
}>())
.output(type<unknown>())
.output(type<Tag>())
export const tagDeleteContract = base
.route({

View File

@ -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) => {
</button>
</>
)}
{isEditing && (<input aria-label={`${t('operation.rename', { ns: 'common' })} ${tag.name}`} className="shrink-0 appearance-none caret-primary-600 outline-hidden placeholder:text-text-quaternary" autoFocus value={name} onChange={e => setName(e.target.value)} onKeyDown={e => e.key === 'Enter' && editTag(tag.id, name)} onBlur={() => editTag(tag.id, name)} />)}
{isEditing && (
<input
aria-label={`${t('operation.rename', { ns: 'common' })} ${tag.name}`}
className="shrink-0 appearance-none caret-primary-600 outline-hidden placeholder:text-text-quaternary"
autoFocus
defaultValue={tag.name}
onKeyDown={(e) => {
if (e.key !== 'Enter' || e.nativeEvent.isComposing)
return
e.preventDefault()
e.currentTarget.blur()
}}
onBlur={e => editTag(tag.id, e.currentTarget.value)}
/>
)}
</div>
<AlertDialog open={showRemoveModal} onOpenChange={open => !open && setShowRemoveModal(false)}>
<AlertDialogContent>

View File

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

View File

@ -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),
)
},