Value Stream Analytics 개발 가이드라인
GitLab v19.1GitLab에서 value stream analytics(VSA)를 구성하는 방법에 대한 정보는 analytics 문서를 참조하세요. Value Stream Analytics는 두 개의 타임스탬프 칼럼 또는 타임스탬프 표현식 사이의 기간을 계산하고, 데이터에 대해 다양한 집계를 실행합니다.
GitLab에서 value stream analytics(VSA)를 구성하는 방법에 대한 정보는 analytics 문서를 참조하세요.
Value Stream Analytics는 어떻게 동작하나요?#
Value Stream Analytics는 두 개의 타임스탬프 칼럼 또는 타임스탬프 표현식 사이의 기간을 계산하고, 데이터에 대해 다양한 집계를 실행합니다.
예시:
-
머지 리퀘스트 생성 시간과 머지 리퀘스트 병합 시간 사이의 기간.
-
이슈 생성 시간과 이슈 종료 시간 사이의 기간.
이 기간은 다음과 같은 방식으로 노출됩니다:
-
집계: 중앙값, 평균
-
목록: 개별 머지 리퀘스트 및 이슈 레코드에 대한 기간 목록
기간 외에도, Stage 내의 레코드 수를 노출합니다.
기능 가용성#
-
그룹 레벨 (유료): Ultimate 또는 Premium 구독이 필요합니다. 이 버전이 기능이 가장 풍부합니다.
-
프로젝트 레벨 (유료): 그룹 레벨 VSA와 동등한 수준으로 만들기 위해 프로젝트 레벨 VSA에 기능을 지속적으로 추가하고 있습니다.
-
프로젝트 레벨 (FOSS): 현재 상태를 유지합니다.
| 기능 | 그룹 레벨 (유료) | 프로젝트 레벨 (유료) | 프로젝트 레벨 (FOSS) |
|---|---|---|---|
| 커스텀 value stream 생성 | 예 | 아니오, 기본 Stage가 포함된 하나의 value stream(기본값)만 존재 | 아니오, 기본 Stage가 포함된 하나의 value stream(기본값)만 존재 |
| 커스텀 Stage 생성 | 예 | 아니오 | 아니오 |
| 필터링 (작성자, 라벨, 마일스톤 등) | 예 | 예 | 예 |
| Stage 시간 차트 | 예 | 아니오 | 아니오 |
| 전체 시간 차트 | 예 | 아니오 | 아니오 |
| 유형별 태스크 차트 | 예 | 아니오 | 아니오 |
| DORA Metrics | 예 | 예 | 아니오 |
| 사이클 타임 및 리드 타임 요약 (Lifecycle metrics) | 예 | 예 | 아니오 |
| 신규 이슈, 커밋, 배포 (Lifecycle metrics) | 예, 커밋 제외 | 예 | 예 |
| 집계 백엔드 사용 | 예 | 아니오 | 아니오 |
| 날짜 필터 동작 | 날짜 범위 내에서 완료된 항목 필터링 | 생성 날짜로 항목 필터링. | 생성 날짜로 항목 필터링. |
| 인가 | 최소 reporter 이상 | 최소 reporter 이상 | 공개 가능. |
VSA 핵심 도메인 객체#
Stage#
Stage는 이벤트 쌍(시작 이벤트와 종료 이벤트)과 Stage 이름과 같은 추가 메타데이터를 나타냅니다. Stage는 백엔드에서 정의된 페어링 규칙 내에서 사용자가 구성할 수 있습니다.
예시 Stage: 코드 리뷰
-
시작 이벤트 식별자: 머지 리퀘스트 생성 시간.
-
시작 이벤트 칼럼:
merge_requests.created_at타임스탬프 칼럼 사용. -
종료 이벤트 식별자: 머지 리퀘스트 병합 시간.
-
종료 이벤트 칼럼:
merge_request_metrics.merged_at타임스탬프 칼럼 사용. -
Stage 이벤트 해시 ID: 시작 이벤트와 종료 이벤트 식별자 쌍에 대해 계산된 해시.
두 Stage의 시작 이벤트와 종료 이벤트 구성이 동일하면, 해당 Stage 이벤트 해시 ID는 동일합니다.
- Stage 이벤트 해시 ID는 이후 파티셔닝된 데이터베이스 테이블에 집계된 데이터를 저장하는 데 사용됩니다.
과거에, value stream analytics는 구독에 관계없이 최종 사용자가 항상 사용할 수 있는 여섯 개의 Stage를 정의했습니다.
Value stream#
Value stream은 Stage의 컨테이너 객체입니다. 그룹당 여러 개의 value stream이 존재할 수 있으며, DevOps 라이프사이클의 다양한 측면에 초점을 맞춥니다.
이벤트#
이벤트는 value stream analytics 기능의 가장 작은 빌딩 블록입니다. Stage는 두 개의 이벤트로 구성됩니다:
-
시작 이벤트
-
종료 이벤트
이러한 이벤트는 기간 계산에서 핵심적인 역할을 합니다.
수식: duration = end_event_time - start_event_time
기간 계산을 유연하게 만들기 위해, 각 Event는 별도의 클래스로 구현됩니다.
이 클래스들은 계산 쿼리에서 사용되는 타임스탬프 표현식을 정의하는 역할을 합니다.
Event 클래스 구현#
StageEvent 기본 클래스에 설명된 몇 가지 메서드를 구현해야 합니다.
가장 중요한 메서드는 다음과 같습니다:
-
object_type -
timestamp_projection
object_type 메서드는 계산에 어떤 도메인 객체를 쿼리할지 정의합니다. 현재 두 가지 모델이 허용됩니다:
-
Issue -
MergeRequest
기간 계산에는 timestamp_projection 메서드가 사용됩니다.
def timestamp_projection
# your timestamp expression comes here
end
# event will use the issue creation time in the duration calculation
def timestamp_projection
Issue.arel_table[:created_at]
end
더 복잡한 표현식도 가능합니다(예: COALESCE 사용).
기존 이벤트 클래스에서 예시를 확인하세요.
경우에 따라, timestamp_projection 메서드를 정의하는 것만으로는 충분하지 않습니다. 계산 쿼리는 타임스탬프 표현식이 포함된 테이블을 알아야 합니다. 각 Event 클래스는 timestamp_projection이 동작하도록 계산 쿼리를 수정하는 역할을 합니다. 일반적으로 이는 추가 테이블을 조인하는 것을 의미합니다.
issue_metrics 테이블을 조인하고 타임스탬프 표현식으로 first_mentioned_in_commit_at 칼럼을 사용하는 예시:
def object_type
Issue
end
def timestamp_projection
IssueMetrics.arel_table[:first_mentioned_in_commit_at]
end
def apply_query_customization(query)
# in this case the query attribute will be based on the Issue model: `Issue.where(...)`
query.joins(:metrics)
end
시작 이벤트와 종료 이벤트 유효성 검사#
일부 시작/종료 이벤트 쌍은 서로 "호환"되지 않습니다. 예시:
-
"Issue created"에서 "Merge Request created"로: 이벤트 클래스가 서로 다른 도메인 모델에 정의되어 있으며,
object_type메서드가 다릅니다. -
"Issue closed"에서 "Issue created"로: 이슈는 종료되기 전에 먼저 생성되어야 합니다.
-
"Issue closed"에서 "Issue closed"로: 기간이 항상 0입니다.
StageEvents 모듈은 허용된 start_event 및 end_event 페어링(PAIRING_RULES 상수)을 설명합니다. 새 이벤트가 추가되면 이 모듈에 등록해야 합니다.
새 이벤트를 추가하려면:
-
ENUM_MAPPING에 고유한 번호로 항목을 추가합니다. 이 번호는Stage모델에서enum으로 사용됩니다. -
PAIRING_RULES해시에서 해당 이벤트와 호환되는 이벤트를 정의합니다.
지원되는 시작/종료 이벤트 페어링:
graph LR; IssueCreated --> IssueClosed; IssueCreated --> IssueFirstAddedToBoard; IssueCreated --> IssueFirstAssociatedWithMilestone; IssueCreated --> IssueFirstMentionedInCommit; IssueCreated --> IssueLastEdited; IssueCreated --> IssueLabelAdded; IssueCreated --> IssueLabelRemoved; IssueCreated --> IssueFirstAssignedAt; MergeRequestCreated --> MergeRequestMerged; MergeRequestCreated --> MergeRequestClosed; MergeRequestCreated --> MergeRequestFirstDeployedToProduction; MergeRequestCreated --> MergeRequestLastBuildStarted; MergeRequestCreated --> MergeRequestLastBuildFinished; MergeRequestCreated --> MergeRequestLastEdited; MergeRequestCreated --> MergeRequestLabelAdded; MergeRequestCreated --> MergeRequestLabelRemoved; MergeRequestCreated --> MergeRequestFirstAssignedAt; MergeRequestFirstAssignedAt --> MergeRequestClosed; MergeRequestFirstAssignedAt --> MergeRequestLastBuildStarted; MergeRequestFirstAssignedAt --> MergeRequestLastEdited; MergeRequestFirstAssignedAt --> MergeRequestMerged; MergeRequestFirstAssignedAt --> MergeRequestLabelAdded; MergeRequestFirstAssignedAt --> MergeRequestLabelRemoved; MergeRequestLastBuildStarted --> MergeRequestLastBuildFinished; MergeRequestLastBuildStarted --> MergeRequestClosed; MergeRequestLastBuildStarted --> MergeRequestFirstDeployedToProduction; MergeRequestLastBuildStarted --> MergeRequestLastEdited; MergeRequestLastBuildStarted --> MergeRequestMerged; MergeRequestLastBuildStarted --> MergeRequestLabelAdded; MergeRequestLastBuildStarted --> MergeRequestLabelRemoved; MergeRequestMerged --> MergeRequestFirstDeployedToProduction; MergeRequestMerged --> MergeRequestClosed; MergeRequestMerged --> MergeRequestFirstDeployedToProduction; MergeRequestMerged --> MergeRequestLastEdited; MergeRequestMerged --> MergeRequestLabelAdded; MergeRequestMerged --> MergeRequestLabelRemoved; IssueLabelAdded --> IssueLabelAdded; IssueLabelAdded --> IssueLabelRemoved; IssueLabelAdded --> IssueClosed; IssueLabelAdded --> IssueFirstAssignedAt; IssueLabelRemoved --> IssueClosed; IssueLabelRemoved --> IssueFirstAssignedAt; IssueFirstAddedToBoard --> IssueClosed; IssueFirstAddedToBoard --> IssueFirstAssociatedWithMilestone; IssueFirstAddedToBoard --> IssueFirstMentionedInCommit; IssueFirstAddedToBoard --> IssueLastEdited; IssueFirstAddedToBoard --> IssueLabelAdded; IssueFirstAddedToBoard --> IssueLabelRemoved; IssueFirstAddedToBoard --> IssueFirstAssignedAt; IssueFirstAssignedAt --> IssueClosed; IssueFirstAssignedAt --> IssueFirstAddedToBoard; IssueFirstAssignedAt --> IssueFirstAssociatedWithMilestone; IssueFirstAssignedAt --> IssueFirstMentionedInCommit; IssueFirstAssignedAt --> IssueLastEdited; IssueFirstAssignedAt --> IssueLabelAdded; IssueFirstAssignedAt --> IssueLabelRemoved; IssueFirstAssociatedWithMilestone --> IssueClosed; IssueFirstAssociatedWithMilestone --> IssueFirstAddedToBoard; IssueFirstAssociatedWithMilestone --> IssueFirstMentionedInCommit; IssueFirstAssociatedWithMilestone --> IssueLastEdited; IssueFirstAssociatedWithMilestone --> IssueLabelAdded; IssueFirstAssociatedWithMilestone --> IssueLabelRemoved; IssueFirstAssociatedWithMilestone --> IssueFirstAssignedAt; IssueFirstMentionedInCommit --> IssueClosed; IssueFirstMentionedInCommit --> IssueFirstAssociatedWithMilestone; IssueFirstMentionedInCommit --> IssueFirstAddedToBoard; IssueFirstMentionedInCommit --> IssueLastEdited; IssueFirstMentionedInCommit --> IssueLabelAdded; IssueFirstMentionedInCommit --> IssueLabelRemoved; IssueClosed --> IssueLastEdited; IssueClosed --> IssueLabelAdded; IssueClosed --> IssueLabelRemoved; MergeRequestClosed --> MergeRequestFirstDeployedToProduction; MergeRequestClosed --> MergeRequestLastEdited; MergeRequestClosed --> MergeRequestLabelAdded; MergeRequestClosed --> MergeRequestLabelRemoved; MergeRequestFirstDeployedToProduction --> MergeRequestLastEdited; MergeRequestFirstDeployedToProduction --> MergeRequestLabelAdded; MergeRequestFirstDeployedToProduction --> MergeRequestLabelRemoved; MergeRequestLastBuildFinished --> MergeRequestClosed; MergeRequestLastBuildFinished --> MergeRequestFirstDeployedToProduction; MergeRequestLastBuildFinished --> MergeRequestLastEdited; MergeRequestLastBuildFinished --> MergeRequestMerged; MergeRequestLastBuildFinished --> MergeRequestLabelAdded; MergeRequestLastBuildFinished --> MergeRequestLabelRemoved; MergeRequestLabelAdded --> MergeRequestLabelAdded; MergeRequestLabelAdded --> MergeRequestLabelRemoved; MergeRequestLabelAdded --> MergeRequestMerged; MergeRequestLabelAdded --> MergeRequestFirstAssignedAt; MergeRequestLabelRemoved --> MergeRequestLabelAdded; MergeRequestLabelRemoved --> MergeRequestLabelRemoved; MergeRequestLabelRemoved --> MergeRequestFirstAssignedAt;
기본 Stage#
value stream analytics의 최초 구현에서는 7개의 Stage가 정의되었습니다. 이러한 Stage는 각 부모에 대해 항상 사용할 수 있으나, 이 Stage를 변경하는 것은 불가능합니다.
효율성을 높이고 생성되는 레코드 수를 줄이기 위해, 기본 Stage는 인메모리 객체(비영속)로 표현됩니다. 사용자가 처음으로 커스텀 Stage를 생성하면, 모든 Stage가 영속됩니다. 이 동작은 value stream analytics 서비스 객체에 구현되어 있습니다.
이 이유는 나중에 Stage를 숨기고 순서를 지정하는 기능을 추가하고 싶었기 때문입니다.
Data Collector#
DataCollector는 데이터베이스에서 데이터를 쿼리하는 중심 지점입니다. 이 클래스는 항상 단일 Stage에서 동작하며 다음 구성 요소로 이루어집니다:
BaseQueryBuilder:
초기 쿼리를 구성하는 역할을 합니다.
-
Stage특정 구성 처리: 이벤트 및 쿼리 커스터마이제이션. -
UI에서 오는 파라미터: 날짜 범위.
-
Median:BaseQueryBuilder의 쿼리를 사용하여 Stage에 대한 기간의 중앙값을 계산합니다. -
RecordsFetcher:BaseQueryBuilder의 쿼리와 특정Finder클래스를 사용하여 Stage에 관련된 레코드를 로드하고 가시성 규칙을 적용합니다. -
DataForDurationChart: 스캐터플롯 차트를 위해 완료 시간(종료 이벤트 타임스탬프)과 함께 계산된 기간을 로드합니다.
새로운 계산이나 쿼리의 경우, DataCollector 클래스에서 새 메서드 호출로 구현합니다.
집계된 value stream analytics 백엔드를 지원하기 위해, 이 클래스들은 Aggregated 네임스페이스 내에서 재구현되었습니다.
데이터베이스 쿼리 백엔드#
VSA는 두 가지 백엔드를 지원합니다: 집계(aggregated)와 "라이브(live)". 라이브 쿼리 백엔드는 레거시로 간주될 수 있으며, 향후 단계적으로 폐지될 예정입니다.
-
"라이브": 표준
IssuableFinders를 사용합니다. -
집계(aggregated): 사전 집계된 데이터베이스 테이블에서 데이터를 쿼리합니다.
고수준 개요#
-
Rails Controller (
Analytics::CycleAnalytics모듈): Value stream analytics는analytics워크스페이스 내에서 구현된 JSON 엔드포인트를 통해 데이터를 노출합니다. Stage 구성도 JSON 엔드포인트(CRUD)로 구현합니다. -
Services (
Analytics::CycleAnalytics모듈): 모든Stage관련 작업은 각 서비스 객체에 위임됩니다. -
Models (
Analytics::CycleAnalytics모듈): 모델은Stage객체를 영속화하는 데 사용됩니다. -
Feature 클래스 (
Gitlab::Analytics::CycleAnalytics모듈):
쿼리를 구성하고 기능 특정 비즈니스 로직을 정의하는 역할을 합니다.
DataCollector,Event,StageEvents등.
프론트엔드#
프로젝트 VSA는 모든 사용자가 사용할 수 있으며:
-
티어에 따른 주요 메트릭과 DORA 메트릭의 혼합을 포함합니다.
-
기본 Stage 세트를 사용합니다.
그룹 VSA는 유료 사용자만 사용할 수 있으며, 프로젝트 VSA를 확장하여 다음을 포함합니다:
-
커스텀 value stream을 생성하는 기능.
그룹 및 프로젝트 레벨 VSA 프론트엔드는 모두 Vue와 Vuex로 구축되었으며 유사한 패턴을 따릅니다:
-
index.js파일은 URL 쿼리 파라미터를 추출하고, Vue 앱과 Vuex 스토어를 생성하며,initializeVuex 액션을 디스패치합니다. -
base.vue파일은 각 페이지의 메트릭, 필터, 차트, Stage 테이블을 포함한 메인 컴포넌트를 렌더링하는 데 사용됩니다.
그룹 VSA Vuex 스토어는 차트 렌더링에 사용되는 일부 상태와 로직을 분리하기 위해 Vuex 모듈을 활용합니다.
공유 컴포넌트#
Stage 테이블과 경로와 같은 UI의 일부는 프로젝트 VSA와 그룹 VSA 간에 공유됩니다. 이러한 공유 컴포넌트는 프로젝트 VSA 디렉터리 app/assets/javascripts/cycle_analytics/components에 있으며, 필요한 경우 그룹 레벨 VSA에 포함됩니다.
그룹 레벨 기능의 모든 프론트엔드 코드는 ee/app/assets/javascripts/analytics/cycle_analytics/components에 위치합니다.
테스팅#
이벤트와 가능한 페어링이 많기 때문에 각 페어링을 테스트하는 것은 불가능합니다. 규칙은 Event 클래스를 사용하는 테스트 케이스를 최소 하나 이상 갖는 것입니다.
새로운 Event를 사용하는 Stage에 대한 테스트 케이스를 작성하는 것은 두 이벤트 모두에 대해 데이터를 생성해야 하기 때문에 어려울 수 있습니다. 이를 좀 더 간단하게 만들기 위해, 각 테스트 케이스는 data_collector_spec.rb에 구현되어야 하며, 여기서 Stage는 DataCollector를 통해 테스트됩니다. 각 테스트 케이스는 다음 사례를 다루는 여러 테스트로 전환됩니다:
-
다른 부모:
Group또는Project -
다른 계산:
Median,RecordsFetcher또는DataForDurationChart
VSA 프론트엔드는 두 가지 다른 레벨(통합, 단위)에서 광범위하게 테스트됩니다:
-
Capybara와 RSpec을 통한 실제 백엔드를 사용하는 엔드 투 엔드 통합 테스트.
-
사전 생성된 데이터 픽스처를 사용하는 Jest 프론트엔드 테스트.
개발 환경 설정 및 테스팅#
Value Stream Analytics는 GDK를 통해 실행할 수 있습니다. 기본적으로 기능의 프로젝트 레벨(FOSS) 버전을 볼 수 있습니다.
GDK가 실행 중인 경우, 시드 스크립트를 실행하여 일부 데이터를 생성할 수 있습니다:
SEED_CYCLE_ANALYTICS=true SEED_VSA=true FILTER=cycle_analytics rake db:seed_fu
데이터 생성기 스크립트는 이슈 및 머지 리퀘스트 데이터가 포함된 새 그룹과 새 프로젝트를 생성합니다(스크립트 출력 참조). 기능의 그룹 레벨 버전을 보려면 GDK 인스턴스에 대한 라이선스를 요청해야 합니다.
이 단계 후, 그룹 레벨 value stream analytics 페이지에 접근하여 value stream과 Stage를 생성할 수 있습니다. 데이터 집계가 지연될 수 있으므로 Stage 생성 직후에는 데이터가 표시되지 않을 수도 있습니다. 이 과정을 빠르게 하려면 rails 콘솔(rails c)에서 다음 명령어를 실행할 수 있습니다:
Analytics::CycleAnalytics::ReaggregationWorker.new.perform
시드 데이터#
Value stream analytics#
value stream analytics를 위한 데이터 시드 방법에 대한 지침은 개발 시드 파일을 참조하세요.