InfoGrab DocsInfoGrab Docs

Value Stream Analytics 개발 가이드라인

요약

GitLab에서 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_eventend_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를 확장하여 다음을 포함합니다:

  • 개요 Stage.

  • 커스텀 value stream을 생성하는 기능.

그룹 및 프로젝트 레벨 VSA 프론트엔드는 모두 Vue와 Vuex로 구축되었으며 유사한 패턴을 따릅니다:

  • index.js 파일은 URL 쿼리 파라미터를 추출하고, Vue 앱과 Vuex 스토어를 생성하며, initialize Vuex 액션을 디스패치합니다.

  • 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를 위한 데이터 시드 방법에 대한 지침은 개발 시드 파일을 참조하세요.

Value Stream Analytics 개발 가이드라인

GitLab v19.1
원문 보기
요약

GitLab에서 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_eventend_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를 확장하여 다음을 포함합니다:

  • 개요 Stage.

  • 커스텀 value stream을 생성하는 기능.

그룹 및 프로젝트 레벨 VSA 프론트엔드는 모두 Vue와 Vuex로 구축되었으며 유사한 패턴을 따릅니다:

  • index.js 파일은 URL 쿼리 파라미터를 추출하고, Vue 앱과 Vuex 스토어를 생성하며, initialize Vuex 액션을 디스패치합니다.

  • 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를 위한 데이터 시드 방법에 대한 지침은 개발 시드 파일을 참조하세요.