update workflow integration phase, teams deep link, console page and tests, archive logcat console
This commit is contained in:
parent
b9f3adaadf
commit
b11182468e
7 changed files with 127 additions and 19 deletions
|
|
@ -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와 개발자의 반복 사용 흐름을 줄인다.
|
||||
- [스케치] 보안과 조직 배포
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
- 확인 필요: 없음
|
||||
|
|
@ -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 자체는 사용자 최종 확인 전까지 활성 경로에서 `[검토중]`으로 유지한다.
|
||||
|
|
|
|||
|
|
@ -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 설치 화면에 바로 진입한다.
|
||||
- [계획] 설치 세션 이력과 오류 리포트
|
||||
|
|
|
|||
|
|
@ -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 알림 템플릿 변경 가능 여부
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue