Skip to content

Commit 41b4339

Browse files
authored
feat(FR-2391): add multi-step async notification feature spec (#6193)
1 parent 640ef4d commit 41b4339

1 file changed

Lines changed: 145 additions & 0 deletions

File tree

  • .specs/FR-2391-multi-step-notification
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Multi-Step Async Notification 컴포넌트 및 Hook
2+
3+
> **Status**: Implemented
4+
> **Date**: 2026-03-26
5+
> **Implemented**: PR [#6234](https://github.com/lablup/backend.ai-webui/pull/6234)
6+
7+
## 개요
8+
9+
기존 notification 시스템 위에서 동작하는 재사용 가능한 multi-step 비동기 진행 표시 컴포넌트와 React hook. 각 단계(예: model definition 확인 → service definition 확인 → 결과 액션)가 step counter("Step 2/4") 형태로 notification에 표시되며, 단계별 설명과 액션 버튼을 제공한다. Hook이 Promise 체인과 SSE 기반 작업을 오케스트레이션하여 notification을 자동으로 업데이트한다.
10+
11+
## 문제 정의
12+
13+
현재 notification 시스템(`useBAINotification`)은 단일 작업 진행만 지원한다(하나의 `backgroundTask`, pending → resolved/rejected 라이프사이클). 다음과 같은 multi-step 비동기 작업을 표현할 표준화된 방법이 없다:
14+
15+
- 여러 순차 async 단계가 순서대로 완료되어야 하는 경우
16+
- 각 단계마다 고유한 상태, 설명, 액션 버튼이 필요한 경우
17+
- 특정 단계 실패 시 해당 단계만 retry하고 전체를 재시작하지 않아야 하는 경우
18+
- notification이 in-place 업데이트되어 현재 활성 단계와 완료된 단계를 반영해야 하는 경우
19+
20+
이 요구사항은 Model Store 개선 스펙(FR-2382)에서 식별되었으며, model folder 서비스 런칭과 Model Store "Clone & Start Service"에 단계별 notification flow가 필요하다. 다만, 컴포넌트와 hook은 애플리케이션 내 모든 multi-step async flow에서 재사용 가능하도록 범용적으로 설계한다.
21+
22+
## 사용자 스토리
23+
24+
- **개발자로서**, async step 목록을 넘기면 각 단계를 거치며 notification 상태 전환을 자동 관리해주는 hook이 필요하다. 이를 통해 multi-step flow마다 notification 상태를 수동으로 관리할 필요가 없다.
25+
- **개발자로서**, 단계별 설명, 성공/실패 메시지, 액션 버튼을 선언적으로 정의하고 싶다. 각 flow가 오케스트레이션 로직을 재구현하지 않고도 notification 내용을 커스터마이징할 수 있어야 한다.
26+
- **사용자로서**, multi-step 작업 중 현재 어느 단계가 실행 중인지(예: "Step 2/4") 확인하여 전체 진행 상황을 파악하고 싶다.
27+
- **사용자로서**, 단계 실패 시 명확한 에러 메시지와 retry 옵션을 보고, 전체 flow를 처음부터 재시작하지 않고 복구하고 싶다.
28+
- **개발자로서**, hook이 Promise 기반과 SSE 기반 async 작업을 모두 step 타입으로 지원하여, 다양한 백엔드 상호작용(API 호출, 스트리밍 이벤트)에 활용하고 싶다.
29+
30+
## 요구사항
31+
32+
### Must Have
33+
34+
- [x] step 정의 배열을 받아 각 단계를 오케스트레이션하는 React hook (예: `useMultiStepNotification`)
35+
- [x] 각 step 정의에 포함되는 항목:
36+
- notification에 표시할 label/description
37+
- async executor 함수 (Promise 반환 또는 SSE 스트림 구독)
38+
- 상태별 메시지 오버라이드 (pending, resolved, rejected) — 현재 `backgroundTask.onChange`와 동일한 패턴 (선택)
39+
- 상태별 액션 버튼 설정 (예: resolve 시 "서비스 상세 보기", reject 시 "폴더 열기") (선택)
40+
- 이전 단계 결과에 대한 데이터 의존성 여부를 나타내는 `dependsOn` 플래그/설정 (선택)
41+
- [x] **Eager execution with sequential display**: 이전 단계 결과에 의존하지 않는 단계(`dependsOn: false` 또는 데이터 의존성 없음)는 이전 단계와 병렬로 즉시 실행을 시작할 수 있다. 단, UI는 항상 순차적으로 진행을 표시한다(Step 1 → Step 2 → Step 3). notification의 step counter는 이전 단계가 모두 완료된 후에만 다음으로 넘어가며, 선형적인 사용자 경험을 유지하면서 총 대기 시간을 줄인다.
42+
- [x] 기존 `useBAINotification` 시스템과 호환되는 notification 상태 생성 (동일 notification list에 upsert, 동일 notification key 재사용)
43+
- [x] notification에 step counter 표시: 현재 단계 인덱스와 전체 수 (예: "Step 2/4: Checking service definition")
44+
- [x] 단계 resolve 시 hook이 자동으로 다음 단계로 진행하고 notification 업데이트
45+
- [x] 단계 reject 시 notification에 에러 표시 및 해당 단계에 대한 retry 액션 제공 (step 1부터 재시작하지 않음)
46+
- [x] retry 성공 후 다음 단계부터 정상 진행
47+
- [x] 모든 단계 성공 완료 시 설정 가능한 메시지와 액션이 포함된 최종 resolved 상태로 전환
48+
- [x] 명령적 제어 노출: `start()`, `retry()`, `cancel()`
49+
- [x] 취소 시 현재 단계 중단(가능한 경우) 및 cancelled 상태로 표시
50+
- [x] 두 가지 async 작업 타입 지원:
51+
- **Promise**: step executor가 `Promise<T>` 반환 — resolve/reject에 따라 notification 업데이트
52+
- **SSE**: step executor가 SSE 구독 설정(task ID) 반환 — 기존 SSE listener 인프라를 통해 notification 업데이트, 가능한 경우 진행률 표시
53+
- [x] 컴포넌트와 hook은 범용적으로 설계 (Model Store에 종속되지 않음), 모든 multi-step flow에서 사용 가능
54+
55+
### Nice to Have
56+
57+
- [x] 완료된 단계가 위로 스크롤되고 다음 단계가 나타나는 스크롤 애니메이션으로, 여러 단계가 진행되고 있다는 시각적 피드백 제공
58+
- [x] notification drawer에서 단계 타임라인/히스토리 보기 (완료된 모든 단계와 상태를 접힌 형태로 표시)
59+
- [x] 최종 상태별 auto-dismiss 설정 (예: 성공 시 N초 후 자동 닫기, 실패 시 유지)
60+
61+
## 인수 조건
62+
63+
### Hook API
64+
65+
- [x] `useMultiStepNotification`이 설정 객체를 받음: notification 메타데이터(message, key), step 정의 배열, 최종 상태 설정(선택)
66+
- [x] `start()` 호출 시 첫 단계부터 실행 시작 및 notification 열림
67+
- [x] 각 단계 전환 시 notification을 in-place 업데이트 (동일 key)하여 새로운 step counter와 설명 표시
68+
- [x] step counter 형식: "{current}/{total}: {step label}" (예: "1/3: Checking model definition")
69+
- [x] notification 아이콘이 현재 전체 상태를 반영: 실행 중 pending(시계), 전체 완료 시 success(체크), 단계 실패 시 error(X)
70+
71+
### Promise Steps
72+
73+
- [x] Promise executor가 있는 단계는 promise 시작 시 notification을 "pending" 상태로 업데이트
74+
- [x] promise resolve 시 단계를 resolved로 표시하고 다음 단계 자동 시작
75+
- [x] promise reject 시 단계를 rejected로 표시하고 에러 메시지 표시 및 retry 버튼 표시
76+
- [x] resolved promise의 반환 데이터는 다음 단계가 의존성을 선언한 경우 해당 executor의 input으로 전달 (의존 단계 간 data chaining)
77+
- [x] eager-executed 단계(의존성 없음)의 executor는 이전 단계의 input을 받지 않고 즉시 시작
78+
79+
### SSE Steps
80+
81+
- [x] SSE executor가 있는 단계는 기존 `listenToBackgroundTask` 인프라에 task ID를 전달
82+
- [x] SSE 이벤트의 진행률을 notification에 표시 (`BAINotificationBackgroundProgress` 재사용)
83+
- [x] SSE 완료(onDone) 시 단계를 resolved로 표시하고 다음 단계로 진행
84+
- [x] SSE 실패(onTaskFailed/onFailed) 시 단계를 rejected로 표시하고 에러 상세 표시
85+
86+
### 에러 처리 및 Retry
87+
88+
- [x] 단계 실패 시 rejected promise 또는 SSE failure 이벤트의 에러 메시지를 notification에 표시
89+
- [x] 단계 실패 시 notification에 "Retry" 액션 버튼 표시
90+
- [x] "Retry" 클릭 시 실패한 단계만 재실행 (이전 단계는 재실행하지 않음)
91+
- [x] retry 성공 시 다음 단계로 정상 진행
92+
- [x] 첫 버전에서는 retry 횟수 제한 없음
93+
94+
### Eager Execution
95+
96+
- [x] 이전 단계에 대한 데이터 의존성이 없는 단계는 step 배열 내 위치와 무관하게 `start()` 호출 시 즉시 실행 시작
97+
- [x] UI step counter는 단계 1부터 N-1까지 모두 완료된 후에만 단계 N으로 진행 (순차 표시 보장)
98+
- [x] 앞 단계가 실패했을 때 이미 실행 중인 뒤 eager 단계는 계속 진행하되 결과를 보유 — 실패한 단계의 retry 시 이미 완료된 eager 단계를 재실행하지 않음
99+
- [x] eager 단계가 표시 순서상 차례보다 먼저 완료되면, counter가 해당 단계에 도달했을 때 즉시 완료 상태로 표시 (인위적 지연 없음)
100+
101+
### 취소
102+
103+
- [x] `cancel()` 호출 시 현재 실행을 중단하고 notification을 cancelled 상태로 업데이트
104+
- [x] cancelled 상태는 에러와 구분되는 별도 설명 메시지 표시
105+
106+
### 기존 시스템과의 통합
107+
108+
- [x] multi-step notification이 기존 notification drawer(`WEBUINotificationDrawer`)에 표시됨
109+
- [x] multi-step notification popup(우하단)이 기존 notification과 동일한 시각적 컨테이너 사용
110+
- [x] hook이 내부적으로 `useSetBAINotification().upsertNotification`을 호출 — 기존 notification 시스템을 우회하지 않음
111+
- [x] Desktop notification(활성화된 경우)은 최종 상태(전체 완료 또는 실패)에서만 발생, 중간 단계에서는 발생하지 않음
112+
113+
## 범위 외
114+
115+
- Background task polling을 async 작업 타입으로 지원 (첫 버전에서는 Promise와 SSE만 지원)
116+
- 비순차적 표시를 동반한 완전 독립 병렬 단계 (eager execution은 지원하되, UI 표시 순서는 항상 순차적)
117+
- hook 내 단계 의존성 그래프 또는 조건 분기 (호출자가 자체 step executor 로직에서 분기 처리)
118+
- 페이지 새로고침 시 notification 상태 유지 (현재 동작과 동일하게 ephemeral)
119+
- notification 위치 커스터마이징 (현재와 동일하게 항상 우하단)
120+
- 기존 `useBAINotification` hook 인터페이스 변경 (새 hook은 기존 위에 구축)
121+
122+
## 기술 컨텍스트
123+
124+
### 기존 Notification 인프라
125+
126+
현재 notification 시스템의 구성요소:
127+
128+
- **`useBAINotification` hook** (`react/src/hooks/useBAINotification.tsx`): Jotai atom(`notificationListState`)으로 `NotificationState` 객체를 관리. 단일 작업 라이프사이클(pending/resolved/rejected)의 `backgroundTask`, `listenToBackgroundTask`를 통한 SSE, Promise 추적을 지원.
129+
- **`BAIGeneralNotificationItem`** (`react/src/components/BAIGeneralNotificationItem.tsx`): 아이콘, 메시지, 설명, 액션 링크, 선택적 progress bar가 포함된 개별 notification 항목 렌더링.
130+
- **`BAINotificationBackgroundProgress`** (`react/src/components/BAINotificationBackgroundProgress.tsx`): 백그라운드 작업의 진행률 progress bar 렌더링.
131+
- **Ant Design `App.notification`**: 기반 notification popup 시스템.
132+
133+
새 hook은 이 인프라를 대체하지 않고 조합하여 사용해야 한다. `upsertNotification`을 호출하여 notification 상태를 업데이트하고, 기존 SSE listener 패턴을 재사용한다.
134+
135+
### 대상 유스케이스 (FR-2382 기반)
136+
137+
1. **Model folder 서비스 런칭**: model definition 확인 → service definition 확인 → 결과 액션 (3단계)
138+
2. **Model Store clone & start**: clone → model definition 확인 → service definition 확인 → 결과 액션 (4단계)
139+
3. **HuggingFace 모델 import**: 다운로드 → definition 생성 → 완료 (3단계, 향후)
140+
141+
## 관련 이슈
142+
143+
- FR-2391: Multi-step async notification 컴포넌트 및 hook 스펙 정의 (본 스펙의 소스 이슈)
144+
- FR-1927: Model Store 개선 (상위 Epic)
145+
- FR-2382: Model Store 개선 기능 스펙 (본 스펙을 소비하는 스펙)

0 commit comments

Comments
 (0)