Merge pull request #302 from iflytek/fix/rerelease-precheck-warnings

fix(rerelease): support precheck warning confirmation flow
This commit is contained in:
dongmucat 2026-04-14 20:28:00 +08:00 committed by GitHub
commit 2cfd4a730b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 257 additions and 19 deletions

View file

@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotBlank;
public record SkillVersionRereleaseRequest(
@NotBlank(message = "{validation.required}")
String targetVersion
String targetVersion,
boolean confirmWarnings
) {
}

View file

@ -154,7 +154,8 @@ public class SkillLifecycleAppService {
skillVersion.getVersion(),
targetVersion,
userId,
normalizeRoles(userNamespaceRoles)
normalizeRoles(userNamespaceRoles),
request.confirmWarnings()
);
auditLogService.record(
userId,

View file

@ -212,7 +212,8 @@ class SkillLifecycleControllerTest {
eq("1.2.3"),
eq("1.2.4"),
eq("usr_1"),
anyMap()))
anyMap(),
eq(false)))
.willReturn(new SkillPublishService.PublishResult(1L, "demo-skill", newVersion));
mockMvc.perform(post("/api/web/skills/global/demo-skill/versions/1.2.3/rerelease")
@ -279,7 +280,8 @@ class SkillLifecycleControllerTest {
eq("1.2.3"),
eq("1.2.4"),
eq("usr_1"),
anyMap()))
anyMap(),
eq(false)))
.willReturn(new SkillPublishService.PublishResult(1L, "demo-skill", newVersion));
mockMvc.perform(post("/api/web/skills/global/demo-skill/versions/1.2.3/rerelease")
@ -299,7 +301,44 @@ class SkillLifecycleControllerTest {
eq("1.2.3"),
eq("1.2.4"),
eq("usr_1"),
anyMap());
anyMap(),
eq(false));
}
@Test
void rereleaseVersion_passesConfirmWarningsToService() throws Exception {
Namespace namespace = new Namespace("global", "Global", "owner");
setNamespaceId(namespace, 1L);
Skill skill = new Skill(1L, "demo-skill", "owner", SkillVisibility.PUBLIC);
setSkillId(skill, 1L);
SkillVersion newVersion = new SkillVersion(1L, "1.2.4", "owner");
setSkillVersionId(newVersion, 3L);
newVersion.setStatus(SkillVersionStatus.PUBLISHED);
given(namespaceRepository.findBySlug("global")).willReturn(java.util.Optional.of(namespace));
given(skillSlugResolutionService.resolve(1L, "demo-skill", "usr_1", SkillSlugResolutionService.Preference.CURRENT_USER))
.willReturn(skill);
SkillVersion sourceVersion = new SkillVersion(1L, "1.2.3", "owner");
setSkillVersionId(sourceVersion, 2L);
sourceVersion.setStatus(SkillVersionStatus.PUBLISHED);
given(skillVersionRepository.findBySkillIdAndVersion(1L, "1.2.3")).willReturn(java.util.Optional.of(sourceVersion));
given(skillPublishService.rereleasePublishedVersion(
eq(1L), eq("1.2.3"), eq("1.2.4"), eq("usr_1"), anyMap(), eq(true)))
.willReturn(new SkillPublishService.PublishResult(1L, "demo-skill", newVersion));
mockMvc.perform(post("/api/web/skills/global/demo-skill/versions/1.2.3/rerelease")
.requestAttr("userId", "usr_1")
.requestAttr("userNsRoles", java.util.Map.of(1L, NamespaceRole.ADMIN))
.contentType(MediaType.APPLICATION_JSON)
.content("{\"targetVersion\":\"1.2.4\",\"confirmWarnings\":true}")
.with(user("usr_1"))
.with(csrf()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(0))
.andExpect(jsonPath("$.data.action").value("RERELEASE_VERSION"));
verify(skillPublishService).rereleasePublishedVersion(
eq(1L), eq("1.2.3"), eq("1.2.4"), eq("usr_1"), anyMap(), eq(true));
}
private Skill skillWithStatus(Skill skill, com.iflytek.skillhub.domain.skill.SkillStatus status) {

View file

@ -156,7 +156,8 @@ public class SkillPublishService {
String sourceVersion,
String targetVersion,
String publisherId,
Map<Long, NamespaceRole> userNamespaceRoles) {
Map<Long, NamespaceRole> userNamespaceRoles,
boolean confirmWarnings) {
Skill skill = skillRepository.findById(skillId)
.orElseThrow(() -> new DomainBadRequestException("error.skill.notFound", skillId));
assertCanManageLifecycle(skill, publisherId, userNamespaceRoles);
@ -181,7 +182,7 @@ public class SkillPublishService {
publisherId,
skill.getVisibility(),
Set.of(),
false, // confirmWarnings=false: no warnings to confirm for rerelease
confirmWarnings, // confirmWarnings: honour caller's choice for rerelease
false, // forceAutoPublish=false: respect visibility rules
true
);

View file

@ -855,7 +855,8 @@ class SkillPublishServiceTest {
"1.2.3",
"1.2.4",
publisherId,
Map.of(skill.getNamespaceId(), com.iflytek.skillhub.domain.namespace.NamespaceRole.OWNER)
Map.of(skill.getNamespaceId(), com.iflytek.skillhub.domain.namespace.NamespaceRole.OWNER),
false
);
assertEquals("1.2.4", result.version().getVersion());
@ -892,7 +893,8 @@ class SkillPublishServiceTest {
"1.2.3",
"1.2.4",
publisherId,
Map.of(skill.getNamespaceId(), com.iflytek.skillhub.domain.namespace.NamespaceRole.OWNER)
Map.of(skill.getNamespaceId(), com.iflytek.skillhub.domain.namespace.NamespaceRole.OWNER),
false
));
}
@ -953,7 +955,8 @@ class SkillPublishServiceTest {
"1.2.3",
"1.2.4",
publisherId,
Map.of(skill.getNamespaceId(), com.iflytek.skillhub.domain.namespace.NamespaceRole.OWNER)
Map.of(skill.getNamespaceId(), com.iflytek.skillhub.domain.namespace.NamespaceRole.OWNER),
false
);
assertEquals("1.2.4", result.version().getVersion());
@ -966,6 +969,100 @@ class SkillPublishServiceTest {
assertEquals(30L, skill.getLatestVersionId());
}
@Test
void testRereleasePublishedVersion_ShouldRequireConfirmationWhenWarningsExist() throws Exception {
String publisherId = "user-100";
Skill skill = new Skill(1L, "demo-skill", publisherId, SkillVisibility.PUBLIC);
setId(skill, 11L);
skill.setDisplayName("Demo Skill");
skill.setSummary("Original summary");
Namespace namespace = new Namespace("global", "Global", "owner");
setId(namespace, 1L);
SkillVersion sourceVersion = new SkillVersion(skill.getId(), "1.2.3", publisherId);
setId(sourceVersion, 21L);
sourceVersion.setStatus(SkillVersionStatus.PUBLISHED);
sourceVersion.setPublishedAt(Instant.parse("2026-03-15T10:00:00Z"));
String sourceSkillMd = "---\nname: Demo Skill\ndescription: Original summary\nversion: 1.2.3\n---\nHello world";
SkillFile skillMdFile = new SkillFile(sourceVersion.getId(), "SKILL.md", (long) sourceSkillMd.getBytes(StandardCharsets.UTF_8).length, "text/markdown", "hash1", "skills/11/21/SKILL.md");
SkillMetadata rereleaseMetadata = new SkillMetadata(
"Demo Skill", "Original summary", "1.2.4", "Hello world",
Map.of("name", "Demo Skill", "description", "Original summary", "version", "1.2.4"));
when(skillRepository.findById(skill.getId())).thenReturn(Optional.of(skill));
when(namespaceRepository.findById(skill.getNamespaceId())).thenReturn(Optional.of(namespace));
when(namespaceRepository.findBySlug("global")).thenReturn(Optional.of(namespace));
when(skillVersionRepository.findBySkillIdAndVersion(skill.getId(), "1.2.3")).thenReturn(Optional.of(sourceVersion));
when(skillVersionRepository.findBySkillIdAndVersion(skill.getId(), "1.2.4")).thenReturn(Optional.empty());
when(skillFileRepository.findByVersionId(sourceVersion.getId())).thenReturn(List.of(skillMdFile));
when(objectStorageService.getObject(skillMdFile.getStorageKey())).thenReturn(new java.io.ByteArrayInputStream(sourceSkillMd.getBytes(StandardCharsets.UTF_8)));
when(skillPackageValidator.validate(anyList())).thenReturn(ValidationResult.pass());
when(skillMetadataParser.parse(anyString())).thenReturn(rereleaseMetadata);
when(prePublishValidator.validate(any())).thenReturn(ValidationResult.warn(List.of(
"SKILL.md line 5 contains a value that looks like a secret or token.")));
DomainBadRequestException exception = assertThrows(DomainBadRequestException.class, () -> service.rereleasePublishedVersion(
skill.getId(), "1.2.3", "1.2.4", publisherId,
Map.of(skill.getNamespaceId(), com.iflytek.skillhub.domain.namespace.NamespaceRole.OWNER),
false
));
assertEquals("error.skill.publish.precheck.confirmRequired", exception.messageCode());
assertTrue(String.valueOf(exception.messageArgs()[0]).contains("looks like a secret or token"));
verify(skillVersionRepository, never()).save(any(SkillVersion.class));
}
@Test
void testRereleasePublishedVersion_ShouldSucceedWhenWarningsConfirmed() throws Exception {
String publisherId = "user-100";
Skill skill = new Skill(1L, "demo-skill", publisherId, SkillVisibility.PUBLIC);
setId(skill, 11L);
skill.setDisplayName("Demo Skill");
skill.setSummary("Original summary");
Namespace namespace = new Namespace("global", "Global", "owner");
setId(namespace, 1L);
SkillVersion sourceVersion = new SkillVersion(skill.getId(), "1.2.3", publisherId);
setId(sourceVersion, 21L);
sourceVersion.setStatus(SkillVersionStatus.PUBLISHED);
sourceVersion.setPublishedAt(Instant.parse("2026-03-15T10:00:00Z"));
String sourceSkillMd = "---\nname: Demo Skill\ndescription: Original summary\nversion: 1.2.3\n---\nHello world";
SkillFile skillMdFile = new SkillFile(sourceVersion.getId(), "SKILL.md", (long) sourceSkillMd.getBytes(StandardCharsets.UTF_8).length, "text/markdown", "hash1", "skills/11/21/SKILL.md");
SkillMetadata rereleaseMetadata = new SkillMetadata(
"Demo Skill", "Original summary", "1.2.4", "Hello world",
Map.of("name", "Demo Skill", "description", "Original summary", "version", "1.2.4"));
when(skillRepository.findById(skill.getId())).thenReturn(Optional.of(skill));
when(namespaceRepository.findById(skill.getNamespaceId())).thenReturn(Optional.of(namespace));
when(namespaceRepository.findBySlug("global")).thenReturn(Optional.of(namespace));
when(skillVersionRepository.findBySkillIdAndVersion(skill.getId(), "1.2.3")).thenReturn(Optional.of(sourceVersion));
when(skillVersionRepository.findBySkillIdAndVersion(skill.getId(), "1.2.4")).thenReturn(Optional.empty());
when(skillFileRepository.findByVersionId(sourceVersion.getId())).thenReturn(List.of(skillMdFile));
when(objectStorageService.getObject(skillMdFile.getStorageKey())).thenReturn(new java.io.ByteArrayInputStream(sourceSkillMd.getBytes(StandardCharsets.UTF_8)));
when(skillPackageValidator.validate(anyList())).thenReturn(ValidationResult.pass());
when(skillMetadataParser.parse(anyString())).thenReturn(rereleaseMetadata);
when(prePublishValidator.validate(any())).thenReturn(ValidationResult.warn(List.of(
"SKILL.md line 5 contains a value that looks like a secret or token.")));
when(skillVersionRepository.save(any(SkillVersion.class))).thenAnswer(invocation -> {
SkillVersion saved = invocation.getArgument(0);
if (saved.getId() == null) { setId(saved, 30L); }
return saved;
});
when(skillRepository.save(any())).thenReturn(skill);
SkillPublishService.PublishResult result = service.rereleasePublishedVersion(
skill.getId(), "1.2.3", "1.2.4", publisherId,
Map.of(skill.getNamespaceId(), com.iflytek.skillhub.domain.namespace.NamespaceRole.OWNER),
true // confirmWarnings = true should bypass warning and succeed
);
assertEquals("1.2.4", result.version().getVersion());
assertEquals(SkillVersionStatus.PENDING_REVIEW, result.version().getStatus());
verify(skillVersionRepository, atLeastOnce()).save(any(SkillVersion.class));
}
@Test
void testPublishFromEntries_ShouldRejectWhenOtherOwnerHasPublishedSkill() throws Exception {
String namespaceSlug = "test-ns";
@ -999,6 +1096,39 @@ class SkillPublishServiceTest {
));
}
@Test
void testPublishFromEntries_ShouldRejectWithPrivateConflictWhenOtherOwnerHasPrivatePublishedSkill() throws Exception {
String namespaceSlug = "test-ns";
String publisherId = "user-200";
String skillMdContent = "---\nname: test-skill\ndescription: Test\nversion: 1.0.0\n---\nBody";
PackageEntry skillMd = new PackageEntry("SKILL.md", skillMdContent.getBytes(), skillMdContent.length(), "text/markdown");
List<PackageEntry> entries = List.of(skillMd);
Namespace namespace = new Namespace(namespaceSlug, "Test NS", "user-1");
setId(namespace, 1L);
NamespaceMember member = mock(NamespaceMember.class);
SkillMetadata metadata = new SkillMetadata("test-skill", "Test", "1.0.0", "Body", Map.of());
Skill existingSkill = new Skill(1L, "test-skill", "user-100", SkillVisibility.PRIVATE);
setId(existingSkill, 1L);
SkillVersion publishedVersion = new SkillVersion(1L, "0.1.0", "user-100");
publishedVersion.setStatus(SkillVersionStatus.PUBLISHED);
when(namespaceRepository.findBySlug(namespaceSlug)).thenReturn(Optional.of(namespace));
when(namespaceMemberRepository.findByNamespaceIdAndUserId(any(), eq(publisherId))).thenReturn(Optional.of(member));
when(skillPackageValidator.validate(entries)).thenReturn(ValidationResult.pass());
when(skillMetadataParser.parse(skillMdContent)).thenReturn(metadata);
when(prePublishValidator.validate(any())).thenReturn(ValidationResult.pass());
when(skillRepository.findByNamespaceIdAndSlug(any(), eq("test-skill"))).thenReturn(List.of(existingSkill));
when(skillVersionRepository.findBySkillIdAndStatus(1L, SkillVersionStatus.PUBLISHED)).thenReturn(List.of(publishedVersion));
DomainBadRequestException ex = assertThrows(DomainBadRequestException.class, () -> service.publishFromEntries(
namespaceSlug, entries, publisherId, SkillVisibility.PRIVATE, Set.of()
));
assertEquals("error.skill.publish.nameConflict.private", ex.messageCode());
}
@Test
void testPublishFromEntries_ShouldAllowWhenOtherOwnerHasNonPublishedSkill() throws Exception {
String namespaceSlug = "test-ns";

View file

@ -488,14 +488,14 @@ export const skillLifecycleApi = {
})
},
async rereleaseVersion(namespace: string, slug: string, version: string, targetVersion: string): Promise<void> {
async rereleaseVersion(namespace: string, slug: string, version: string, targetVersion: string, confirmWarnings = false): Promise<void> {
const cleanNamespace = namespace.startsWith('@') ? namespace.slice(1) : namespace
await fetchJson<void>(`${WEB_API_PREFIX}/skills/${cleanNamespace}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/rerelease`, {
method: 'POST',
headers: await ensureCsrfHeaders({
'Content-Type': 'application/json',
}),
body: JSON.stringify({ targetVersion }),
body: JSON.stringify({ targetVersion, confirmWarnings }),
})
},

View file

@ -3237,6 +3237,7 @@ export interface components {
};
SkillVersionRereleaseRequest: {
targetVersion: string;
confirmWarnings?: boolean;
};
SkillReportSubmitRequest: {
reason?: string;

View file

@ -911,6 +911,9 @@
"rereleaseSuccessTitle": "Version re-released",
"rereleaseSuccessDescription": "Created v{{target}} from v{{source}}.",
"rereleaseErrorTitle": "Failed to re-release version",
"rereleaseWarningTitle": "Pre-publish warning",
"rereleaseWarningDescription": "We found the following risk reminders. If you understand them and still want to proceed, you can continue re-releasing.",
"rereleaseWarningConfirm": "Continue re-releasing",
"yankVersion": "Yank Current Version",
"promoteToGlobal": "Promote to Global",
"promotionSectionTitle": "Promote to Global",

View file

@ -912,6 +912,9 @@
"rereleaseSuccessTitle": "版本已重新发布",
"rereleaseSuccessDescription": "已基于 v{{source}} 创建新版本 v{{target}}。",
"rereleaseErrorTitle": "重新发布版本失败",
"rereleaseWarningTitle": "发布前风险提醒",
"rereleaseWarningDescription": "检测到以下风险项。若你确认这些内容可以接受,仍可继续重新发布。",
"rereleaseWarningConfirm": "继续重新发布",
"yankVersion": "撤回当前版本",
"promoteToGlobal": "申请提升到全局",
"promotionSectionTitle": "提升到全局",

View file

@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useParams, useNavigate, useRouterState, useSearch } from '@tanstack/react-router'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { ArrowLeft, ArrowUpCircle, ChevronDown, ChevronUp, Clock, Folder, RefreshCw, ShieldCheck, Terminal, User } from 'lucide-react'
import { ArrowLeft, ArrowUpCircle, ChevronDown, ChevronUp, Clock, Folder, Globe, Lock, RefreshCw, ShieldCheck, Terminal, User, Users } from 'lucide-react'
import { MarkdownRenderer } from '@/features/skill/markdown-renderer'
import { FileTree } from '@/features/skill/file-tree'
import { FilePreviewDialog } from '@/features/skill/file-preview-dialog'
@ -16,6 +16,7 @@ import {
shouldCollapseOverview,
} from '@/features/skill/overview-collapse'
import { resolveSkillActionErrorTitle } from '@/features/skill/skill-action-error'
import { isPrecheckConfirmationMessage, extractPrecheckWarnings } from '@/features/publish/publish-error-utils'
import { clearDeletedSkillQueries, isDeleteSlugConfirmationValid, resolveDeletedSkillReturnTo } from '@/features/skill/skill-delete-flow'
import { isSkillDetailQueriesEnabled } from './skill-detail-query'
import { RatingInput } from '@/features/social/rating-input'
@ -116,6 +117,8 @@ export function SkillDetailPage() {
const [withdrawVersionTarget, setWithdrawVersionTarget] = useState<string | null>(null)
const [rereleaseTarget, setRereleaseTarget] = useState<string | null>(null)
const [targetVersionInput, setTargetVersionInput] = useState('')
const [rereleaseWarnings, setRereleaseWarnings] = useState<string[]>([])
const [rereleaseWarningDialogOpen, setRereleaseWarningDialogOpen] = useState(false)
const [diffSourceVersion, setDiffSourceVersion] = useState<string | null>(null)
const [confirmPublishTarget, setConfirmPublishTarget] = useState<string | null>(null)
const [submitReviewTarget, setSubmitReviewTarget] = useState<string | null>(null)
@ -575,7 +578,7 @@ export function SkillDetailPage() {
setTargetVersionInput(suggestNextVersion(version))
}
const handleRereleaseVersion = async () => {
const handleRereleaseVersion = async (confirmWarnings = false) => {
if (!rereleaseTarget || !targetVersionInput.trim()) {
return
}
@ -585,6 +588,7 @@ export function SkillDetailPage() {
slug,
version: rereleaseTarget,
targetVersion: targetVersionInput.trim(),
confirmWarnings,
})
toast.success(
t('skillDetail.rereleaseSuccessTitle'),
@ -592,7 +596,15 @@ export function SkillDetailPage() {
)
setRereleaseTarget(null)
setTargetVersionInput('')
setRereleaseWarnings([])
setRereleaseWarningDialogOpen(false)
} catch (error) {
if (error instanceof ApiError && isPrecheckConfirmationMessage(error.serverMessage)) {
const warnings = extractPrecheckWarnings(error.serverMessage)
setRereleaseWarnings(warnings)
setRereleaseWarningDialogOpen(true)
return
}
toast.error(t('skillDetail.rereleaseErrorTitle'), error instanceof Error ? error.message : '')
throw error
}
@ -710,10 +722,31 @@ export function SkillDetailPage() {
<div className="flex items-center gap-3 mb-1">
<NamespaceBadge type="GLOBAL" name={namespace} />
{skill.status && (
<span className="badge-soft badge-soft-blue">
<span className={cn(
'badge-soft',
skill.status === 'ACTIVE' && 'badge-soft-green',
skill.status === 'ARCHIVED' && 'bg-secondary text-muted-foreground',
skill.status === 'HIDDEN' && 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400',
!['ACTIVE', 'ARCHIVED', 'HIDDEN'].includes(skill.status) && 'badge-soft-blue',
)}>
{resolveSkillStatusLabel(skill.status)}
</span>
)}
{skill.visibility && (
<span className={cn(
'badge-soft inline-flex items-center gap-1',
skill.visibility === 'PUBLIC' && 'badge-soft-green',
skill.visibility === 'PRIVATE' && 'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300',
skill.visibility === 'NAMESPACE_ONLY' && 'badge-soft-blue',
)}>
{skill.visibility === 'PUBLIC' && <Globe className="h-3 w-3" />}
{skill.visibility === 'PRIVATE' && <Lock className="h-3 w-3" />}
{skill.visibility === 'NAMESPACE_ONLY' && <Users className="h-3 w-3" />}
{skill.visibility === 'PUBLIC' && t('publish.visibilityOptions.public')}
{skill.visibility === 'PRIVATE' && t('publish.visibilityOptions.private')}
{skill.visibility === 'NAMESPACE_ONLY' && t('publish.visibilityOptions.namespaceOnly')}
</span>
)}
{isReviewFlowPending && (
<span className="badge-soft" style={{ background: '#fef3c7', color: '#92400e' }}>
{t('skillDetail.versionStatusPendingReview')}
@ -957,7 +990,7 @@ export function SkillDetailPage() {
))}
</div>
) : (
<div className="text-muted-foreground text-center py-8">{t('skillDetail.noVersions')}</div>
<Card className="p-8 text-muted-foreground text-center">{t('skillDetail.noVersions')}</Card>
)}
</Card>
</TabsContent>
@ -1396,6 +1429,8 @@ export function SkillDetailPage() {
if (!open) {
setRereleaseTarget(null)
setTargetVersionInput('')
setRereleaseWarnings([])
setRereleaseWarningDialogOpen(false)
}
}}
>
@ -1422,13 +1457,34 @@ export function SkillDetailPage() {
<Button variant="outline" onClick={() => setRereleaseTarget(null)}>
{t('dialog.cancel')}
</Button>
<Button onClick={handleRereleaseVersion} disabled={rereleaseVersionMutation.isPending || !targetVersionInput.trim()}>
<Button onClick={() => handleRereleaseVersion()} disabled={rereleaseVersionMutation.isPending || !targetVersionInput.trim()}>
{rereleaseVersionMutation.isPending ? t('skillDetail.processing') : t('skillDetail.rereleaseVersion')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<ConfirmDialog
open={rereleaseWarningDialogOpen}
onOpenChange={setRereleaseWarningDialogOpen}
title={t('skillDetail.rereleaseWarningTitle')}
description={
<div className="space-y-2">
<p>{t('skillDetail.rereleaseWarningDescription')}</p>
<ul className="list-disc space-y-1 pl-5 text-sm">
{rereleaseWarnings.map((warning, index) => (
<li key={index}>{warning}</li>
))}
</ul>
</div>
}
confirmText={t('skillDetail.rereleaseWarningConfirm')}
onConfirm={() => {
setRereleaseWarningDialogOpen(false)
handleRereleaseVersion(true)
}}
/>
<ConfirmDialog
open={!!confirmPublishTarget}
onOpenChange={(open) => {

View file

@ -206,8 +206,11 @@ export function useRereleaseSkillVersion() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ namespace, slug, version, targetVersion }: { namespace: string; slug: string; version: string; targetVersion: string }) =>
skillLifecycleApi.rereleaseVersion(namespace, slug, version, targetVersion),
mutationFn: ({ namespace, slug, version, targetVersion, confirmWarnings }: { namespace: string; slug: string; version: string; targetVersion: string; confirmWarnings?: boolean }) =>
skillLifecycleApi.rereleaseVersion(namespace, slug, version, targetVersion, confirmWarnings),
meta: {
skipGlobalErrorHandler: true,
},
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({ queryKey: ['skills', 'my'] })
queryClient.invalidateQueries({ queryKey: ['skills', variables.namespace, variables.slug] })