update workflow integration phase, teams deep link, console page and tests, archive logcat console

This commit is contained in:
toki 2026-06-14 12:42:25 +09:00
parent b9f3adaadf
commit b11182468e
7 changed files with 127 additions and 19 deletions

View file

@ -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와 개발자의 반복 사용 흐름을 줄인다.
- [스케치] 보안과 조직 배포

View file

@ -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`.
- 확인 필요: 없음

View file

@ -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 자체는 사용자 최종 확인 전까지 활성 경로에서 `[검토중]`으로 유지한다.

View file

@ -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 설치 화면에 바로 진입한다.
- [계획] 설치 세션 이력과 오류 리포트

View file

@ -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 알림 템플릿 변경 가능 여부

View file

@ -69,6 +69,8 @@ class _ConsolePageState extends State<ConsolePage> {
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,

View file

@ -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<AdbLogcatSession>();
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);