From 113bbd0abddf2183e3a8771505cb1cca50f66a00 Mon Sep 17 00:00:00 2001 From: chyoo Date: Fri, 3 Jul 2026 10:12:04 +0900 Subject: [PATCH] improve app release build selection --- .../applications/ReleaseManagementPage.tsx | 97 ++++++++++++++++--- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/src/pages/applications/ReleaseManagementPage.tsx b/src/pages/applications/ReleaseManagementPage.tsx index c20b2db..3091d6f 100644 --- a/src/pages/applications/ReleaseManagementPage.tsx +++ b/src/pages/applications/ReleaseManagementPage.tsx @@ -42,6 +42,44 @@ function releaseArtifacts(release: AppRelease) { return release.artifact ? [release.artifact] : []; } +function buildValue(build: BuildJob) { + return build.image || build.artifact || ''; +} + +function buildTime(build: BuildJob) { + const value = new Date(build.finishedAt || build.createdAt).getTime(); + return Number.isNaN(value) ? 0 : value; +} + +function buildVersion(build: BuildJob) { + const artifact = build.artifact || ''; + const match = artifact.match(/\/apps\/[^/]+\/[^/]+\/([^/]+)\//); + if (match?.[1]) return match[1]; + const fileMatch = artifact.match(/[-_]v?(\d+\.\d+\.\d+(?:[-+][a-zA-Z0-9.-]+)?)/); + return fileMatch?.[1] ?? null; +} + +function usedReleaseValues(releases: AppRelease[]) { + return new Set(releases.flatMap(releaseArtifacts)); +} + +function latestBuildIdsByComponent(builds: BuildJob[]) { + const map = new Map(); + for (const build of builds) { + const current = map.get(build.componentId); + if (!current || buildTime(build) > buildTime(current)) map.set(build.componentId, build); + } + return [...map.values()].map((build) => build.id); +} + +function newestBuildVersion(builds: BuildJob[]) { + return builds + .slice() + .sort((a, b) => buildTime(b) - buildTime(a)) + .map(buildVersion) + .find((value): value is string => Boolean(value)) ?? ''; +} + function buildCount(release: AppRelease) { const imageTags = release.imageTags as ImageTags | null | undefined; const count = Object.keys(imageTags?.images ?? {}).length + Object.keys(imageTags?.artifacts ?? {}).length; @@ -81,10 +119,18 @@ export function ReleaseManagementPage() { const [loading, setLoading] = useState(true); const [page, setPage] = useState(1); const loadingVisible = useMinimumLoading(loading); + const usedValues = useMemo(() => usedReleaseValues(releases), [releases]); + const candidateBuilds = useMemo( + () => builds.filter((build) => { + const value = buildValue(build); + return !value || !usedValues.has(value); + }), + [builds, usedValues] + ); const candidateGroups = useMemo( - () => groupCandidateBuilds(builds).filter((group) => !selectedAppId || group.appId === selectedAppId), - [builds, selectedAppId] + () => groupCandidateBuilds(candidateBuilds).filter((group) => !selectedAppId || group.appId === selectedAppId), + [candidateBuilds, selectedAppId] ); const filteredReleases = useMemo( () => releases @@ -93,8 +139,10 @@ export function ReleaseManagementPage() { [releases, selectedAppId] ); const modalBuilds = useMemo( - () => builds.filter((build) => !selectedAppId || build.component.app.id === selectedAppId), - [builds, selectedAppId] + () => candidateBuilds + .filter((build) => !selectedAppId || build.component.app.id === selectedAppId) + .sort((a, b) => buildTime(b) - buildTime(a)), + [candidateBuilds, selectedAppId] ); const refresh = async () => { @@ -125,7 +173,9 @@ export function ReleaseManagementPage() { const openReleaseModal = (appId?: string) => { const nextAppId = appId || selectedAppId || candidateGroups[0]?.appId || ''; if (nextAppId && nextAppId !== selectedAppId) selectApp(nextAppId); - setSelectedBuildIds(builds.filter((build) => build.component.app.id === nextAppId).map((build) => build.id)); + const appBuilds = candidateBuilds.filter((build) => build.component.app.id === nextAppId); + setSelectedBuildIds(latestBuildIdsByComponent(appBuilds)); + setVersion((prev) => prev || newestBuildVersion(appBuilds)); setReleaseModalOpen(true); }; @@ -154,7 +204,7 @@ export function ReleaseManagementPage() { title="릴리즈 관리" description="브랜치, 커밋, 빌드 산출물을 묶어 환경별로 배포 가능한 릴리즈를 생성합니다." actions={ - @@ -255,22 +305,37 @@ export function ReleaseManagementPage() {
setVersion(event.target.value)} placeholder="버전 예: 0.1.1" /> setBranch(event.target.value)} placeholder="브랜치" />
- {modalBuilds.map((job) => ( - - ))} + {modalBuilds.length === 0 ? ( +
+ 등록 가능한 새 빌드 결과가 없습니다. +
+ ) : null} + {modalBuilds.map((job) => { + const artifactVersion = buildVersion(job); + return ( + + ); + })}