mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-06 02:48:28 +00:00
feat(web): add skill version management UI
This commit is contained in:
parent
4ad97aa55a
commit
3446740b5f
6 changed files with 361 additions and 1 deletions
|
|
@ -447,6 +447,17 @@ export const skillLifecycleApi = {
|
|||
headers: await ensureCsrfHeaders(),
|
||||
})
|
||||
},
|
||||
|
||||
async rereleaseVersion(namespace: string, slug: string, version: string, targetVersion: string): Promise<void> {
|
||||
const cleanNamespace = namespace.startsWith('@') ? namespace.slice(1) : namespace
|
||||
await fetchJson<void>(`${WEB_API_PREFIX}/skills/${cleanNamespace}/${slug}/versions/${encodeURIComponent(version)}/rerelease`, {
|
||||
method: 'POST',
|
||||
headers: await ensureCsrfHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: JSON.stringify({ targetVersion }),
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
export const tokenApi = {
|
||||
|
|
|
|||
|
|
@ -145,6 +145,18 @@ export interface SkillVersion {
|
|||
publishedAt: string
|
||||
}
|
||||
|
||||
export interface SkillVersionDetail {
|
||||
id: number
|
||||
version: string
|
||||
status: string
|
||||
changelog?: string
|
||||
fileCount: number
|
||||
totalSize: number
|
||||
publishedAt: string
|
||||
parsedMetadataJson?: string
|
||||
manifestJson?: string
|
||||
}
|
||||
|
||||
export interface SkillFile {
|
||||
id: number
|
||||
filePath: string
|
||||
|
|
|
|||
|
|
@ -429,6 +429,31 @@
|
|||
"deleteVersionSuccessTitle": "Version deleted",
|
||||
"deleteVersionSuccessDescription": "Version {{version}} has been deleted.",
|
||||
"deleteVersionErrorTitle": "Failed to delete version",
|
||||
"currentVersion": "Current",
|
||||
"compareVersions": "Compare",
|
||||
"compareDialogTitle": "Version comparison",
|
||||
"compareDialogDescription": "Compare v{{source}} with v{{target}}.",
|
||||
"compareSourceLabel": "Selected version",
|
||||
"compareTargetLabel": "Compared with",
|
||||
"versionCompareUnavailableTitle": "Not enough versions to compare",
|
||||
"versionCompareUnavailableDescription": "Publish at least two versions before using version comparison.",
|
||||
"metadataChanges": "Metadata changes",
|
||||
"noMetadataChanges": "No metadata changes",
|
||||
"readmeChange": "README change",
|
||||
"readmeChanged": "README content changed",
|
||||
"readmeUnchanged": "README content unchanged",
|
||||
"fileChanges": "File changes",
|
||||
"filesAdded": "Added",
|
||||
"filesRemoved": "Removed",
|
||||
"filesChanged": "Changed",
|
||||
"rereleaseVersion": "Re-release",
|
||||
"rereleaseDialogTitle": "Re-release from version",
|
||||
"rereleaseDialogDescription": "Create a new published version based on v{{version}}.",
|
||||
"rereleaseSourceVersion": "Source version",
|
||||
"rereleaseTargetVersion": "New version",
|
||||
"rereleaseSuccessTitle": "Version re-released",
|
||||
"rereleaseSuccessDescription": "Created v{{target}} from v{{source}}.",
|
||||
"rereleaseErrorTitle": "Failed to re-release version",
|
||||
"yankVersion": "Yank Current Version",
|
||||
"reportSkill": "Report Skill",
|
||||
"reportDialogTitle": "Report skill",
|
||||
|
|
|
|||
|
|
@ -429,6 +429,31 @@
|
|||
"deleteVersionSuccessTitle": "版本已删除",
|
||||
"deleteVersionSuccessDescription": "版本 {{version}} 已删除。",
|
||||
"deleteVersionErrorTitle": "删除版本失败",
|
||||
"currentVersion": "当前版本",
|
||||
"compareVersions": "对比版本",
|
||||
"compareDialogTitle": "版本对比",
|
||||
"compareDialogDescription": "对比 v{{source}} 与 v{{target}}。",
|
||||
"compareSourceLabel": "选中版本",
|
||||
"compareTargetLabel": "对比版本",
|
||||
"versionCompareUnavailableTitle": "暂无可对比版本",
|
||||
"versionCompareUnavailableDescription": "至少发布两个版本后才能进行版本对比。",
|
||||
"metadataChanges": "元数据变化",
|
||||
"noMetadataChanges": "元数据没有变化",
|
||||
"readmeChange": "README 变化",
|
||||
"readmeChanged": "README 内容已变化",
|
||||
"readmeUnchanged": "README 内容未变化",
|
||||
"fileChanges": "文件变化",
|
||||
"filesAdded": "新增",
|
||||
"filesRemoved": "删除",
|
||||
"filesChanged": "修改",
|
||||
"rereleaseVersion": "重新发布",
|
||||
"rereleaseDialogTitle": "基于旧版本重新发布",
|
||||
"rereleaseDialogDescription": "基于 v{{version}} 创建一个新的已发布版本。",
|
||||
"rereleaseSourceVersion": "来源版本",
|
||||
"rereleaseTargetVersion": "新版本号",
|
||||
"rereleaseSuccessTitle": "版本已重新发布",
|
||||
"rereleaseSuccessDescription": "已基于 v{{source}} 创建新版本 v{{target}}。",
|
||||
"rereleaseErrorTitle": "重新发布版本失败",
|
||||
"yankVersion": "撤回当前版本",
|
||||
"reportSkill": "举报技能",
|
||||
"reportDialogTitle": "举报技能",
|
||||
|
|
|
|||
|
|
@ -25,14 +25,37 @@ import { toast } from '@/shared/lib/toast'
|
|||
import {
|
||||
useSkillDetail,
|
||||
useSkillVersions,
|
||||
useSkillVersionDetail,
|
||||
useSkillFiles,
|
||||
useSkillReadme,
|
||||
useArchiveSkill,
|
||||
useDeleteSkillVersion,
|
||||
useRereleaseSkillVersion,
|
||||
useUnarchiveSkill,
|
||||
useWithdrawSkillReview,
|
||||
} from '@/shared/hooks/use-skill-queries'
|
||||
|
||||
function suggestNextVersion(version: string) {
|
||||
const semverMatch = version.match(/^(\d+)\.(\d+)\.(\d+)$/)
|
||||
if (semverMatch) {
|
||||
const [, major, minor, patch] = semverMatch
|
||||
return `${major}.${minor}.${Number.parseInt(patch, 10) + 1}`
|
||||
}
|
||||
return `${version}.1`
|
||||
}
|
||||
|
||||
function parseMetadataJson(parsed?: string) {
|
||||
if (!parsed) {
|
||||
return {}
|
||||
}
|
||||
try {
|
||||
const value = JSON.parse(parsed)
|
||||
return typeof value === 'object' && value !== null ? value : {}
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export function SkillDetailPage() {
|
||||
const { t, i18n } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
|
|
@ -45,6 +68,10 @@ export function SkillDetailPage() {
|
|||
const [unarchiveConfirmOpen, setUnarchiveConfirmOpen] = useState(false)
|
||||
const [deleteVersionTarget, setDeleteVersionTarget] = useState<string | null>(null)
|
||||
const [withdrawVersionTarget, setWithdrawVersionTarget] = useState<string | null>(null)
|
||||
const [rereleaseTarget, setRereleaseTarget] = useState<string | null>(null)
|
||||
const [targetVersionInput, setTargetVersionInput] = useState('')
|
||||
const [diffSourceVersion, setDiffSourceVersion] = useState<string | null>(null)
|
||||
const [diffCompareVersion, setDiffCompareVersion] = useState<string | null>(null)
|
||||
const { namespace, slug } = useParams({ from: '/space/$namespace/$slug' })
|
||||
const { user, hasRole } = useAuth()
|
||||
|
||||
|
|
@ -53,6 +80,12 @@ export function SkillDetailPage() {
|
|||
const latestVersion = versions?.[0]
|
||||
const { data: files } = useSkillFiles(namespace, slug, latestVersion?.version)
|
||||
const { data: readme } = useSkillReadme(namespace, slug, latestVersion?.version)
|
||||
const { data: diffSourceDetail } = useSkillVersionDetail(namespace, slug, diffSourceVersion ?? undefined)
|
||||
const { data: diffCompareDetail } = useSkillVersionDetail(namespace, slug, diffCompareVersion ?? undefined)
|
||||
const { data: diffSourceFiles } = useSkillFiles(namespace, slug, diffSourceVersion ?? undefined)
|
||||
const { data: diffCompareFiles } = useSkillFiles(namespace, slug, diffCompareVersion ?? undefined)
|
||||
const { data: diffSourceReadme } = useSkillReadme(namespace, slug, diffSourceVersion ?? undefined)
|
||||
const { data: diffCompareReadme } = useSkillReadme(namespace, slug, diffCompareVersion ?? undefined)
|
||||
const governanceVisible = hasRole('SKILL_ADMIN') || hasRole('SUPER_ADMIN')
|
||||
|
||||
const refreshSkill = () => {
|
||||
|
|
@ -79,6 +112,7 @@ export function SkillDetailPage() {
|
|||
const unarchiveMutation = useUnarchiveSkill()
|
||||
const deleteVersionMutation = useDeleteSkillVersion()
|
||||
const withdrawReviewMutation = useWithdrawSkillReview()
|
||||
const rereleaseVersionMutation = useRereleaseSkillVersion()
|
||||
const reportMutation = useSubmitSkillReport(namespace, slug)
|
||||
|
||||
const handleDownload = () => {
|
||||
|
|
@ -154,6 +188,43 @@ export function SkillDetailPage() {
|
|||
|
||||
const canDeleteVersion = (status?: string) => status === 'DRAFT' || status === 'REJECTED'
|
||||
const canWithdrawVersion = (status?: string) => status === 'PENDING_REVIEW'
|
||||
const canRereleaseVersion = (status?: string) => status === 'PUBLISHED'
|
||||
|
||||
const metadataDiffEntries = (() => {
|
||||
const source = parseMetadataJson(diffSourceDetail?.parsedMetadataJson)
|
||||
const compare = parseMetadataJson(diffCompareDetail?.parsedMetadataJson)
|
||||
const keys = Array.from(new Set([...Object.keys(source), ...Object.keys(compare)])).sort()
|
||||
return keys
|
||||
.filter((key) => JSON.stringify(source[key]) !== JSON.stringify(compare[key]))
|
||||
.map((key) => ({
|
||||
key,
|
||||
source: source[key],
|
||||
target: compare[key],
|
||||
}))
|
||||
})()
|
||||
|
||||
const fileDiffSummary = (() => {
|
||||
const sourceMap = new Map((diffSourceFiles ?? []).map((file) => [file.filePath, file.sha256]))
|
||||
const compareMap = new Map((diffCompareFiles ?? []).map((file) => [file.filePath, file.sha256]))
|
||||
const added: string[] = []
|
||||
const removed: string[] = []
|
||||
const changed: string[] = []
|
||||
|
||||
for (const [path, hash] of sourceMap.entries()) {
|
||||
if (!compareMap.has(path)) {
|
||||
removed.push(path)
|
||||
} else if (compareMap.get(path) !== hash) {
|
||||
changed.push(path)
|
||||
}
|
||||
}
|
||||
for (const path of compareMap.keys()) {
|
||||
if (!sourceMap.has(path)) {
|
||||
added.push(path)
|
||||
}
|
||||
}
|
||||
|
||||
return { added, removed, changed }
|
||||
})()
|
||||
|
||||
const handleArchive = async () => {
|
||||
try {
|
||||
|
|
@ -218,6 +289,45 @@ export function SkillDetailPage() {
|
|||
}
|
||||
}
|
||||
|
||||
const handleOpenRerelease = (version: string) => {
|
||||
setRereleaseTarget(version)
|
||||
setTargetVersionInput(suggestNextVersion(version))
|
||||
}
|
||||
|
||||
const handleRereleaseVersion = async () => {
|
||||
if (!rereleaseTarget || !targetVersionInput.trim()) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await rereleaseVersionMutation.mutateAsync({
|
||||
namespace,
|
||||
slug,
|
||||
version: rereleaseTarget,
|
||||
targetVersion: targetVersionInput.trim(),
|
||||
})
|
||||
toast.success(
|
||||
t('skillDetail.rereleaseSuccessTitle'),
|
||||
t('skillDetail.rereleaseSuccessDescription', { source: rereleaseTarget, target: targetVersionInput.trim() }),
|
||||
)
|
||||
setRereleaseTarget(null)
|
||||
setTargetVersionInput('')
|
||||
} catch (error) {
|
||||
toast.error(t('skillDetail.rereleaseErrorTitle'), error instanceof Error ? error.message : '')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpenDiff = (version: string) => {
|
||||
const publishedVersions = versions?.filter((item) => item.status === 'PUBLISHED') ?? []
|
||||
const compareVersion = publishedVersions.find((item) => item.version !== version)?.version ?? null
|
||||
if (!compareVersion) {
|
||||
toast.error(t('skillDetail.versionCompareUnavailableTitle'), t('skillDetail.versionCompareUnavailableDescription'))
|
||||
return
|
||||
}
|
||||
setDiffSourceVersion(version)
|
||||
setDiffCompareVersion(compareVersion)
|
||||
}
|
||||
|
||||
if (isLoadingSkill) {
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-up">
|
||||
|
|
@ -331,11 +441,34 @@ export function SkillDetailPage() {
|
|||
{version.status}
|
||||
</span>
|
||||
)}
|
||||
{skill.latestVersion === version.version && (
|
||||
<span className="rounded-full bg-primary px-2.5 py-0.5 text-xs text-primary-foreground">
|
||||
{t('skillDetail.currentVersion')}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{formatLocalDateTime(version.publishedAt, i18n.language)}
|
||||
</span>
|
||||
{skill.canManageLifecycle && canRereleaseVersion(version.status) && (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleOpenDiff(version.version)}
|
||||
>
|
||||
{t('skillDetail.compareVersions')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleOpenRerelease(version.version)}
|
||||
>
|
||||
{t('skillDetail.rereleaseVersion')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{skill.canManageLifecycle && canDeleteVersion(version.status) && (
|
||||
<Button
|
||||
size="sm"
|
||||
|
|
@ -567,6 +700,132 @@ export function SkillDetailPage() {
|
|||
confirmText={t('skillDetail.withdrawReview')}
|
||||
onConfirm={handleWithdrawVersion}
|
||||
/>
|
||||
|
||||
<Dialog
|
||||
open={!!rereleaseTarget}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setRereleaseTarget(null)
|
||||
setTargetVersionInput('')
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('skillDetail.rereleaseDialogTitle')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{rereleaseTarget ? t('skillDetail.rereleaseDialogDescription', { version: rereleaseTarget }) : ''}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm text-muted-foreground">{t('skillDetail.rereleaseSourceVersion')}</div>
|
||||
<div className="rounded-lg border border-border/60 bg-secondary/30 px-3 py-2 font-mono text-sm text-foreground">
|
||||
{rereleaseTarget ? `v${rereleaseTarget}` : '—'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm text-muted-foreground">{t('skillDetail.rereleaseTargetVersion')}</div>
|
||||
<Input value={targetVersionInput} onChange={(event) => setTargetVersionInput(event.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setRereleaseTarget(null)}>
|
||||
{t('dialog.cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleRereleaseVersion} disabled={rereleaseVersionMutation.isPending || !targetVersionInput.trim()}>
|
||||
{rereleaseVersionMutation.isPending ? t('skillDetail.processing') : t('skillDetail.rereleaseVersion')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
open={!!diffSourceVersion && !!diffCompareVersion}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setDiffSourceVersion(null)
|
||||
setDiffCompareVersion(null)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('skillDetail.compareDialogTitle')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{diffSourceVersion && diffCompareVersion
|
||||
? t('skillDetail.compareDialogDescription', { source: diffSourceVersion, target: diffCompareVersion })
|
||||
: ''}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-5">
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div className="rounded-lg border border-border/60 p-3">
|
||||
<div className="text-muted-foreground">{t('skillDetail.compareSourceLabel')}</div>
|
||||
<div className="mt-1 font-mono text-foreground">v{diffSourceVersion}</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border/60 p-3">
|
||||
<div className="text-muted-foreground">{t('skillDetail.compareTargetLabel')}</div>
|
||||
<div className="mt-1 font-mono text-foreground">v{diffCompareVersion}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-semibold text-foreground">{t('skillDetail.metadataChanges')}</div>
|
||||
{metadataDiffEntries.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{metadataDiffEntries.map((entry) => (
|
||||
<div key={entry.key} className="rounded-lg border border-border/60 p-3 text-sm">
|
||||
<div className="font-medium text-foreground">{entry.key}</div>
|
||||
<div className="mt-1 text-muted-foreground">
|
||||
{String(entry.source ?? '—')} → {String(entry.target ?? '—')}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-muted-foreground">{t('skillDetail.noMetadataChanges')}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-semibold text-foreground">{t('skillDetail.readmeChange')}</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{diffSourceReadme !== diffCompareReadme ? t('skillDetail.readmeChanged') : t('skillDetail.readmeUnchanged')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-semibold text-foreground">{t('skillDetail.fileChanges')}</div>
|
||||
<div className="grid grid-cols-3 gap-3 text-sm">
|
||||
<div className="rounded-lg border border-border/60 p-3">
|
||||
<div className="text-muted-foreground">{t('skillDetail.filesAdded')}</div>
|
||||
<div className="mt-1 font-semibold text-foreground">{fileDiffSummary.added.length}</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border/60 p-3">
|
||||
<div className="text-muted-foreground">{t('skillDetail.filesRemoved')}</div>
|
||||
<div className="mt-1 font-semibold text-foreground">{fileDiffSummary.removed.length}</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border/60 p-3">
|
||||
<div className="text-muted-foreground">{t('skillDetail.filesChanged')}</div>
|
||||
<div className="mt-1 font-semibold text-foreground">{fileDiffSummary.changed.length}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setDiffSourceVersion(null)
|
||||
setDiffCompareVersion(null)
|
||||
}}
|
||||
>
|
||||
{t('dialog.close')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import type { SkillSummary, SkillDetail, SkillVersion, SkillFile, SearchParams, PagedResponse, PublishResult, Namespace, NamespaceMember } from '@/api/types'
|
||||
import type { SkillSummary, SkillDetail, SkillVersion, SkillVersionDetail, SkillFile, SearchParams, PagedResponse, PublishResult, Namespace, NamespaceMember } from '@/api/types'
|
||||
import { fetchJson, fetchText, getCsrfHeaders, meApi, skillLifecycleApi, WEB_API_PREFIX } from '@/api/client'
|
||||
|
||||
const PUBLISH_REQUEST_TIMEOUT_MS = 60_000
|
||||
|
|
@ -34,6 +34,11 @@ async function getSkillFiles(namespace: string, slug: string, version: string):
|
|||
return fetchJson<SkillFile[]>(`${WEB_API_PREFIX}/skills/${cleanNamespace}/${slug}/versions/${version}/files`)
|
||||
}
|
||||
|
||||
async function getSkillVersionDetail(namespace: string, slug: string, version: string): Promise<SkillVersionDetail> {
|
||||
const cleanNamespace = namespace.startsWith('@') ? namespace.slice(1) : namespace
|
||||
return fetchJson<SkillVersionDetail>(`${WEB_API_PREFIX}/skills/${cleanNamespace}/${slug}/versions/${version}`)
|
||||
}
|
||||
|
||||
async function getSkillReadme(namespace: string, slug: string, version: string): Promise<string> {
|
||||
const cleanNamespace = namespace.startsWith('@') ? namespace.slice(1) : namespace
|
||||
try {
|
||||
|
|
@ -122,6 +127,14 @@ export function useSkillReadme(namespace: string, slug: string, version?: string
|
|||
})
|
||||
}
|
||||
|
||||
export function useSkillVersionDetail(namespace: string, slug: string, version?: string) {
|
||||
return useQuery({
|
||||
queryKey: ['skills', namespace, slug, 'versions', version, 'detail'],
|
||||
queryFn: () => getSkillVersionDetail(namespace, slug, version!),
|
||||
enabled: !!namespace && !!slug && !!version,
|
||||
})
|
||||
}
|
||||
|
||||
export function useMySkills() {
|
||||
return useQuery({
|
||||
queryKey: ['skills', 'my'],
|
||||
|
|
@ -233,3 +246,18 @@ export function useWithdrawSkillReview() {
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
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),
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['skills', 'my'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['skills', variables.namespace, variables.slug] })
|
||||
queryClient.invalidateQueries({ queryKey: ['skills', variables.namespace, variables.slug, 'versions'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['skills'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue