From b11182468eb4fd649b017b99ef172b63bb5efee8 Mon Sep 17 00:00:00 2001 From: toki Date: Sun, 14 Jun 2026 12:42:25 +0900 Subject: [PATCH] update workflow integration phase, teams deep link, console page and tests, archive logcat console --- agent-roadmap/ROADMAP.md | 4 +- .../usable-mvp/milestones/logcat-console.md | 21 +++-- agent-roadmap/phase/usable-mvp/PHASE.md | 16 +++- .../phase/workflow-integration/PHASE.md | 4 +- .../milestones/teams-deep-link.md | 4 +- lib/src/features/console/console_page.dart | 8 +- test/console_page_test.dart | 89 +++++++++++++++++++ 7 files changed, 127 insertions(+), 19 deletions(-) rename agent-roadmap/{ => archive}/phase/usable-mvp/milestones/logcat-console.md (67%) diff --git a/agent-roadmap/ROADMAP.md b/agent-roadmap/ROADMAP.md index 84cabfa..59d3a17 100644 --- a/agent-roadmap/ROADMAP.md +++ b/agent-roadmap/ROADMAP.md @@ -11,10 +11,10 @@ AppSok은 Jenkins Android artifact를 네트워크 제약이 있는 Android 기 검토중 또는 진행중 Phase는 계획 Phase보다 위에 두어, 아래로 갈수록 미래 작업에 가까워지게 정렬한다. 스케치 Phase는 아직 구현 가능한 계획이 아니므로 계획 Phase보다 아래에 둔다. -- [진행중] 사용 가능한 MVP +- [검토중] 사용 가능한 MVP - 경로: `agent-roadmap/phase/usable-mvp/PHASE.md` - 요약: Jenkins build 조회, 사용자별 credential, APK 다운로드, USB 설치, logcat 확인까지 하루 업무에 쓸 수 있는 최소 흐름을 만든다. -- [계획] 업무 흐름 통합 +- [진행중] 업무 흐름 통합 - 경로: `agent-roadmap/phase/workflow-integration/PHASE.md` - 요약: Teams 메시지/deep link, 설치 이력, 오류 리포트로 QA와 개발자의 반복 사용 흐름을 줄인다. - [스케치] 보안과 조직 배포 diff --git a/agent-roadmap/phase/usable-mvp/milestones/logcat-console.md b/agent-roadmap/archive/phase/usable-mvp/milestones/logcat-console.md similarity index 67% rename from agent-roadmap/phase/usable-mvp/milestones/logcat-console.md rename to agent-roadmap/archive/phase/usable-mvp/milestones/logcat-console.md index a1ce0c8..324467b 100644 --- a/agent-roadmap/phase/usable-mvp/milestones/logcat-console.md +++ b/agent-roadmap/archive/phase/usable-mvp/milestones/logcat-console.md @@ -11,7 +11,7 @@ ## 상태 -[진행중] +[완료] ## 승격 조건 @@ -47,17 +47,19 @@ - [x] [query-filter] package/tag/text query 필터를 제공한다. - [x] [level-filter] ALL/DEBUG/ERROR 같은 level filter를 제공한다. - [x] [clear-pause] clear와 pause/resume 버튼을 제공한다. -- [ ] [export] 현재 로그를 파일 저장 또는 clipboard 복사할 수 있는 방향을 정리한다. +- [x] [export] 현재 로그를 파일 저장 또는 clipboard 복사할 수 있는 방향을 정리한다. ## 완료 리뷰 -- 상태: 없음 -- 요청일: 없음 -- 완료 근거: 모든 기능 Task와 Task 안에 명시된 검증 충족 후 기록한다. +- 상태: 승인됨 +- 요청일: 2026-06-14 +- 완료 근거: logcat stream, lifecycle cleanup, level color, auto-scroll, query/level/clear-pause, export Task가 모두 완료 근거와 함께 체크되었다. +- 완료 근거: `04+03_export_tools` PASS complete log에서 `export` Roadmap Completion과 remote Mac runner `flutter analyze`, `flutter test`, `flutter test test/console_page_test.dart` PASS가 확인되었다. +- 완료 근거: 완료 전 코드 레벨 검토에서 brief-format live log level filter와 pending session sample-mode 전환 이슈를 보강했고, remote Mac runner 검증이 통과했다. - 리뷰 필요: - - [ ] 사용자가 완료 결과를 확인했다 - - [ ] archive 이동을 승인했다 -- 리뷰 코멘트: 없음 + - [x] 사용자가 완료 결과를 확인했다 + - [x] archive 이동을 승인했다 +- 리뷰 코멘트: 사용자 요청에 따라 코드 레벨 검토 후 남은 큰 이슈가 없어 완료 및 archive로 전환한다. ## 범위 제외 @@ -76,5 +78,6 @@ - `02+01_console_stream`: PASS 완료. archive: `agent-task/archive/2026/06/m-logcat-console/02+01_console_stream/complete.log`. Roadmap Completion: `stream`, `lifecycle`. - `03+02_auto_scroll`: PASS 완료. archive: `agent-task/archive/2026/06/m-logcat-console/03+02_auto_scroll/complete.log`. Roadmap Completion: `auto-scroll`. - `filter-small`: 직접 완료. query/level/clear-pause 연결 및 widget test 추가. Evidence: remote runner `flutter analyze`, `flutter test`. - - `04+03_export_tools`: 활성 plan. Roadmap Targets: `export`. 선행 `03+02_auto_scroll` 완료 조건 충족. + - `04+03_export_tools`: PASS 완료. archive: `agent-task/archive/2026/06/m-logcat-console/04+03_export_tools/complete.log`. Roadmap Completion: `export`. + - 완료 전 코드 리뷰: brief-format live log level filter와 pending session sample-mode 전환을 보강했다. Evidence: remote runner `dart format`, `flutter analyze`, `flutter test test/console_page_test.dart`, `flutter test`, `adb devices -l`, `flutter devices`. - 확인 필요: 없음 diff --git a/agent-roadmap/phase/usable-mvp/PHASE.md b/agent-roadmap/phase/usable-mvp/PHASE.md index 2815d2e..2c8cb34 100644 --- a/agent-roadmap/phase/usable-mvp/PHASE.md +++ b/agent-roadmap/phase/usable-mvp/PHASE.md @@ -2,7 +2,7 @@ ## 상태 -[진행중] +[검토중] ## 목표 @@ -26,8 +26,8 @@ Jenkins에서 Android artifact를 찾고, Mac에 내려받고, USB로 연결된 - [완료] Jenkins 로그인과 credential 수명주기 - 경로: `agent-roadmap/archive/phase/usable-mvp/milestones/jenkins-credential.md` - 요약: 사용자별 Jenkins API token 또는 WebView 기반 token 자동화 흐름을 안전하게 저장/검증한다. -- [진행중] ADB 콘솔과 logcat - - 경로: `agent-roadmap/phase/usable-mvp/milestones/logcat-console.md` +- [완료] ADB 콘솔과 logcat + - 경로: `agent-roadmap/archive/phase/usable-mvp/milestones/logcat-console.md` - 요약: 설치 후 바로 문제를 볼 수 있도록 logcat stream, filter, pause/clear 기능을 제공한다. ## Phase 경계 @@ -35,3 +35,13 @@ Jenkins에서 Android artifact를 찾고, Mac에 내려받고, USB로 연결된 - 이 Phase는 단일 APK 설치를 기준으로 한다. split APK/APKS/AAB는 후속 Phase 후보로 남긴다. - Jenkins 인증은 사용자별 권한을 전제로 하며, 앱 번들에 공용 token을 넣지 않는다. - Teams deep link와 조직 배포 정책은 MVP 이후 Phase에서 다룬다. + +## 완료 리뷰 + +- 상태: 요청됨 +- 요청일: 2026-06-14 +- 완료 근거: 하위 Milestone이 모두 `[완료]`로 정리되었고 마지막 `ADB 콘솔과 logcat`도 코드 레벨 검토와 remote Mac runner 검증을 통과했다. +- 리뷰 필요: + - [ ] 사용자가 Phase 완료 결과를 확인했다 + - [ ] Phase archive 이동을 승인했다 +- 리뷰 코멘트: Phase 자체는 사용자 최종 확인 전까지 활성 경로에서 `[검토중]`으로 유지한다. diff --git a/agent-roadmap/phase/workflow-integration/PHASE.md b/agent-roadmap/phase/workflow-integration/PHASE.md index b8379c9..701dcdf 100644 --- a/agent-roadmap/phase/workflow-integration/PHASE.md +++ b/agent-roadmap/phase/workflow-integration/PHASE.md @@ -2,7 +2,7 @@ ## 상태 -[계획] +[진행중] ## 목표 @@ -14,7 +14,7 @@ MVP 설치 흐름을 Teams/Jenkins 알림과 연결하고, 반복 설치와 실 완료, 검토중, 진행중, 계획, 스케치 순서로 두어 아래로 갈수록 미래 작업에 가까워지게 정렬한다. 스케치 Milestone은 아직 구현 가능한 계획이 아니므로 계획 Milestone보다 아래에 둔다. -- [계획] Teams deep link 설치 진입 +- [진행중] Teams deep link 설치 진입 - 경로: `agent-roadmap/phase/workflow-integration/milestones/teams-deep-link.md` - 요약: Teams/Jenkins 메시지의 `appsok://` 링크로 특정 job/build/artifact 설치 화면에 바로 진입한다. - [계획] 설치 세션 이력과 오류 리포트 diff --git a/agent-roadmap/phase/workflow-integration/milestones/teams-deep-link.md b/agent-roadmap/phase/workflow-integration/milestones/teams-deep-link.md index e5992b3..bd1afe8 100644 --- a/agent-roadmap/phase/workflow-integration/milestones/teams-deep-link.md +++ b/agent-roadmap/phase/workflow-integration/milestones/teams-deep-link.md @@ -11,7 +11,7 @@ Teams/Jenkins 알림에 포함된 `appsok://` 링크를 클릭하면 AppSok이 ## 상태 -[계획] +[진행중] ## 승격 조건 @@ -74,4 +74,6 @@ Teams/Jenkins가 만들고 AppSok이 해석할 URL 형식을 정한다. - 표준선(선택): deep link는 식별자만 전달하고 실제 권한 확인은 Jenkins credential로 다시 수행한다. - 선행 작업: Artifact 탐색과 다운로드 - 후속 작업: 설치 세션 이력과 오류 리포트 +- 작업 현황: + - 2026-06-14: `ADB 콘솔과 logcat` 완료 후 다음 활성 Milestone으로 지정했다. - 확인 필요: Teams payload 형식, Jenkins 알림 템플릿 변경 가능 여부 diff --git a/lib/src/features/console/console_page.dart b/lib/src/features/console/console_page.dart index 1b2979b..3e5d855 100644 --- a/lib/src/features/console/console_page.dart +++ b/lib/src/features/console/console_page.dart @@ -69,6 +69,8 @@ class _ConsolePageState extends State { final starter = widget.logcatStarter; final device = widget.device; if (starter == null || device == null) { + _isLoading = false; + _errorMsg = null; return; } @@ -375,9 +377,11 @@ enum _LogcatLevel { error; static _LogcatLevel? fromLine(String line) { - final match = RegExp(r'\s([DIWEF])\s').firstMatch(line); + final level = + RegExp(r'\s([DIWEF])\s').firstMatch(line)?.group(1) ?? + RegExp(r'(?:^|\s)([DIWEF])\/').firstMatch(line)?.group(1); - return switch (match?.group(1)) { + return switch (level) { 'D' => _LogcatLevel.debug, 'I' => _LogcatLevel.info, 'W' => _LogcatLevel.warn, diff --git a/test/console_page_test.dart b/test/console_page_test.dart index cd66e81..c21ae3a 100644 --- a/test/console_page_test.dart +++ b/test/console_page_test.dart @@ -123,6 +123,48 @@ void main() { expect(find.text(errorLine), findsNothing); }); + testWidgets('filters live brief-format rows by log level segment', ( + tester, + ) async { + final fakeProcess = _FakeProcess(); + final session = AdbLogcatSession(fakeProcess); + const device = AdbDevice( + serial: 'R5CT90A1B2C', + state: 'device', + model: 'SM_S918N', + product: 'dm3q', + ); + + await tester.pumpWidget( + MaterialApp( + theme: AppTheme.light(), + home: Scaffold( + body: ConsolePage( + device: device, + logcatStarter: ({serial}) async => session, + ), + ), + ), + ); + await tester.pump(); + + fakeProcess.emitStdout('I/Keep: keep this line\n'); + fakeProcess.emitStdout('E/Crash: crash this line\n'); + await tester.pump(Duration.zero); + + await tester.tap(find.text('INFO')); + await tester.pump(); + + expect(find.text('I/Keep: keep this line'), findsOneWidget); + expect(find.text('E/Crash: crash this line'), findsNothing); + + await tester.tap(find.text('ERROR')); + await tester.pump(); + + expect(find.text('I/Keep: keep this line'), findsNothing); + expect(find.text('E/Crash: crash this line'), findsOneWidget); + }); + testWidgets('appends live stream lines and stops session on dispose', ( tester, ) async { @@ -255,6 +297,53 @@ void main() { expect(fakeProcessA.killCalled, isTrue); }); + testWidgets( + 'clears loading state when pending live session becomes sample mode', + (tester) async { + final completer = Completer(); + final fakeProcess = _FakeProcess(); + final session = AdbLogcatSession(fakeProcess); + const device = AdbDevice( + serial: 'device-pending', + state: 'device', + model: 'Pixel_8', + product: 'shiba', + ); + + await tester.pumpWidget( + MaterialApp( + theme: AppTheme.light(), + home: Scaffold( + body: ConsolePage( + device: device, + logcatStarter: ({serial}) => completer.future, + ), + ), + ), + ); + await tester.pump(); + + expect(find.byType(CircularProgressIndicator), findsOneWidget); + + await tester.pumpWidget( + MaterialApp( + theme: AppTheme.light(), + home: const Scaffold(body: ConsolePage()), + ), + ); + await tester.pump(); + + expect(find.byType(CircularProgressIndicator), findsNothing); + expect(find.text('device 미선택'), findsOneWidget); + expect(find.textContaining('ActivityTaskManager'), findsOneWidget); + + completer.complete(session); + await tester.pump(); + + expect(fakeProcess.killCalled, isTrue); + }, + ); + testWidgets('auto-scrolls to the newest log while running', (tester) async { final fakeProcess = _FakeProcess(); final session = AdbLogcatSession(fakeProcess);