플러그인 분석
플러그인 분석 (Plugin Analytics)
Backstage 인스턴스를 설정하고 유지하며 반복 개선하는 것은 큰 투자가 될 수 있어요. 이 투자에 대한 수익을 측정하기 위해 Backstage는 이벤트 기반 Analytics API를 제공해요. 이 API는 앱 통합자가 선택한 분석 도구에서 Backstage 사용을 수집하고 분석할 수 있는 유연성을 제공하면서, 플러그인 개발자에게 핵심 사용자 상호작용을 계측하기 위한 표준 인터페이스를 제공해요.
출처: 문서
본문
Backstage 인스턴스를 설정하고 유지하며 반복 개선하는 것은 큰 투자가 될 수 있어요. 이 투자에 대한 수익을 측정하기 위해 Backstage는 이벤트 기반 Analytics API를 제공해요. 이 API는 앱 통합자가 선택한 분석 도구에서 Backstage 사용을 수집하고 분석할 수 있는 유연성을 제공하면서, 플러그인 개발자에게 핵심 사용자 상호작용을 계측하기 위한 표준 인터페이스를 제공해요.
개념 (Concepts)
-
이벤트는 최소한
action(예:click)과subject(예: '클릭한 대상')로 구성돼요. -
속성(Attributes)은 이벤트별로 제공될 수 있는 추가 차원 데이터(키/값 쌍 형태)를 나타내요. 위 예시를 이어 가면, 사용자가 클릭해 이동한 URL이
{ "to": "/a/page" }처럼 보일 수 있어요. -
컨텍스트(Context)는 이벤트가 발생한 더 넓은 맥락을 나타내요. 기본적으로
pluginId와extensionId를 포함해요.
이 이벤트 구성은 다양한 세부 수준에서 분석을 가능하게 하는 것을 목표로 해요. 매우 세밀한 질문(예: '특정 라우트에서 가장 많이 클릭된 대상은 무엇인가')부터 매우 높은 수준의 질문(예: '내 Backstage 인스턴스에서 가장 많이 사용된 플러그인은 무엇인가')까지 답할 수 있게 해줘요.
지원되는 분석 도구 (Supported Analytics Tools)
이러한 이벤트를 소비하고 분석 도구로 전달하는 데 필요한 것은 AnalyticsApi의 구체적인 구현뿐이지만, 일반적인 통합은 패키징되어 플러그인으로 제공돼요. 아래에서 원하는 분석 도구를 찾아보세요.
| Analytics Tool | 지원 상태 |
|---|---|
| Google Analytics | 예 ✅ |
| Google Analytics 4 | 예 ✅ |
| New Relic Browser | 커뮤니티 ✅ |
| Matomo | 커뮤니티 ✅ |
| Quantum Metric | 커뮤니티 ✅ |
| Generic HTTP | 커뮤니티 ✅ |
통합을 제안하려면 조직이 사용하는 분석 도구에 대한 이슈를 열어 주세요. 또는 통합 작성 하기로 이동해 직접 통합에 기여하는 방법을 배우세요!
핵심 이벤트 (Key Events)
다음 표는 설치한 플러그인에 따라 캡처될 수 있는 이벤트를 요약해요.
| Action | Subject | 기타 참고 사항 |
|---|---|---|
navigate |
이동된 페이지의 URL. | 라우트 위치가 변경될 때 즉시 발생(연결된 플러그인/라우트 데이터가 모호한 경우 플러그인/라우트 데이터가 알려진 뒤, 다음 이벤트 또는 문서 언로드 직전에 발생). 현재 라우트의 매개변수가 속성으로 포함됨. |
click |
클릭된 링크의 텍스트. | to 속성은 클릭된 URL을 나타냄. |
create |
생성되는 소프트웨어의 name. 주어진 Software 템플릿이 name 속성을 요청하지 않으면 new {templateName} 문자열이 대신 사용됨. |
컨텍스트는 템플릿의 참조(예: template:default/template-name)로 설정된 entityRef를 담음. value는 템플릿을 실행해 절약한 분 수(템플릿의 backstage.io/time-saved 어노테이션을 기반으로, 사용 가능한 경우)를 나타냄. |
search |
모든 검색 바 컴포넌트에 입력된 검색어. | 컨텍스트는 검색을 제한하는 types를 나타내는 searchTypes를 담음. value는 쿼리에 대한 총 검색 결과 수를 나타냄. 권한 프레임워크를 사용 중이면 보이지 않을 수 있음. |
discover |
클릭된 검색 결과의 제목. | value는 결과 순위임. to 속성도 제공됨. |
not-found |
not found 페이지를 초래한 리소스의 경로. | 최소한 TechDocs가 발생시킴. |
캡처되었으면 하는 이벤트가 있다면, 보고 싶은 이벤트와 그것이 답하는 데 도움이 되는 질문을 설명하는 이슈를 열어 주세요. 또는 이벤트 캡처하기로 이동해 계측에 직접 기여하는 방법을 배우세요!
OSS 플러그인 관리자: 위 표에 이벤트를 문서화해도 좋아요.
통합 작성하기 (Writing Integrations)
분석 이벤트 전달은 Backstage Utility API로 구현돼요. 제공되는 API는 AnalyticsEvent 객체를 받는 단일 메서드 captureEvent만 제공하면 돼요.
AnalyticsImplementationBlueprint를 사용한 간단한 구현:
import { AnalyticsImplementationBlueprint } from '@backstage/plugin-app-react';export const acmeAnalyticsImplementation = AnalyticsImplementationBlueprint.make({ name: 'acme', params: define => define({ deps: {}, factory() { return { captureEvent: event => { window._AcmeAnalyticsQ.push(event); }, }; }, }), });
실제로는 인스턴스화 로직을 캡슐화하고 구성에서 일부 세부 사항을 가져오고 싶을 거예요. 더 완전한 예시는 다음과 같을 수 있어요.
import { AnalyticsApi, AnalyticsEvent, configApiRef,} from '@backstage/frontend-plugin-api';import { AnalyticsImplementationBlueprint } from '@backstage/plugin-app-react';import { AcmeAnalytics } from 'acme-analytics';class AcmeAnalyticsImpl implements AnalyticsApi { private constructor(accountId: number) { AcmeAnalytics.init(accountId); } static fromConfig(config) { const accountId = config.getString('app.analytics.acme.id'); return new AcmeAnalyticsImpl(accountId); } captureEvent(event: AnalyticsEvent) { const { action, ...rest } = event; AcmeAnalytics.send(action, rest); }}export const acmeAnalyticsImplementation = AnalyticsImplementationBlueprint.make({ name: 'acme', params: define => define({ deps: { configApi: configApiRef }, factory: ({ configApi }) => AcmeAnalyticsImpl.fromConfig(configApi), }), });
내부 도구가 아닌 분석 서비스와 통합하는 경우, API 구현을 플러그인으로 기여하는 것을 고려해 보세요!
관례적으로 그러한 패키지는 @backstage/analytics-module-[name]으로 이름을 지어야 하고, 모든 구성은 app.analytics.[name] 아래에 키를 지정해야 해요.
사용자 신원 처리 (Handling User Identity)
통합하는 분석 플랫폼에 사용자 신원에 대한 일급 개념이 있다면, 다음 관례를 따라 이를(선택적으로) 지원하도록 선택할 수 있어요.
-
구현이 의존성 중 하나로
identityApi를 사용해 인스턴스화되도록 허용하세요. -
identityApi의getBackstageIdentity()메서드가 해석하는userEntityRef를 분석 플랫폼으로 보내는 사용자 ID의 기준으로 사용하세요.
이벤트 캡처하기 (Capturing Events)
컴포넌트에서 이벤트를 계측하려면 먼저 @backstage/frontend-plugin-api가 제공하는 useAnalytics() 훅을 사용해 분석 추적기를 검색하세요. 추적기에는 action과 subject를 인자로 받는 captureEvent 메서드가 포함돼요.
import { useAnalytics } from '@backstage/frontend-plugin-api';const analytics = useAnalytics();analytics.captureEvent('deploy', serviceName);
캡처하는 이벤트는 일반적인 클릭이나 UI 생명주기 이벤트가 아니라, 플러그인이 고유하게 책임지는 사용자 의도와 도메인 작업을 반영해야 해요. @backstage/ui의 많은 컴포넌트(Link, ButtonLink, Tab, MenuItem, Tag, Table 행 등)는 종종 click 이벤트를 자동으로 캡처하므로, 탐색 스타일의 클릭을 수동으로 계측할 필요가 거의 없어요. 그러한 컴포넌트 중 하나가 올바른 UI 원시 요소이지만 기본 이벤트가 캡처하려는 것이 아니라면, noTrack prop을 전달해 억제하고 자체 클릭 핸들러에서 captureEvent를 호출하세요.
추가 속성 제공하기 (Providing Extra Attributes)
추가 차원 attributes와 숫자 value는 이벤트에 관련될 때 세 번째 options 인자에 제공할 수 있어요.
analytics.captureEvent('merge', pullRequestName, { value: pullRequestAgeInMinutes, attributes: { org, repo, },});
위 예시에서 다음 객체와 유사한 이벤트가 캡처돼요.
{ "action": "merge", "subject": "Name of Pull Request", "value": 60, "attributes": { "org": "some-org", "repo": "some-repo" }}
이벤트용 컨텍스트 제공하기 (Providing Context for Events)
attributes 옵션은 계측하는 컴포넌트 내에서 사용할 수 있는 세부 정보를 캡처하는 데 좋아요. React 트리 위쪽에서만 사용할 수 있는 메타데이터를 캡처하거나, 앱 통합자가 공통 값으로 별개의 이벤트를 집계하는 데 도움이 되도록 <AnalyticsContext>를 사용하세요.
import { AnalyticsContext, useAnalytics } from '@backstage/frontend-plugin-api';const MyComponent = ({ value }) => { const analytics = useAnalytics(); const handleClick = () => analytics.captureEvent('check', value); return <SomeThing value={value} onClick={handleClick} />;};const MyWrapper = () => { return ( <AnalyticsContext attributes={{ segment: 'xyz' }}> <MyComponent value={'Some Value'} /> </AnalyticsContext> );};
위 예시에서 <SomeThing />을 클릭하면 다음과 유사한 분석 이벤트가 발생해요.
{ "action": "check", "subject": "Some Value", "context": { "segment": "xyz" }}
위 예시에서는 간결함을 위해 Backstage 핵심이 제공하는 컨텍스트 키(pluginId와 extensionId)가 생략된 점에 유의하세요. 실제로는 그 세부 사항들이 사용자가 제공하는 추가 컨텍스트와 함께 포함될 거예요.
분석 컨텍스트는 중첩될 수 있으며, 그 값은 React 트리 아래로 병합되어 키가 덮어쓰여질 수 있어요.
이벤트 명명 고려 사항 (Event Naming Considerations)
이벤트는 다양한 세부 수준에서 분석을 가능하게 하기 위해 구성 요소로 분할돼요. 분석 시점에 이 유연성을 유지하려면 각 세부 수준을 분해된 상태로 유지하는 것이 중요해요.
-
지나치게 구체적인
action을 제공하지 마세요. 예를 들어filterEntityTable대신 action으로 그냥filter를 사용하고,EntityTable은 이벤트의context의 일부로(가장 흔하게는filter이벤트가 캡처된extensionId의 일부로 자동적으로) 지정되도록 허용하세요. -
반대로 이벤트에
attributes를 추가하거나context를 감쌀 때는 기존 이벤트를 보고 캡처하는 데이터가 그들의attributes나context의 의도, 유형 또는 콘텐츠와 일치하는지 확인하세요. 예를 들어 Catalog를 포함하는 이벤트에는entityRef컨텍스트 키를 포함하는 것이 일반적이에요. 이벤트에서 동일한 키와 값을 사용하면 플러그인 간에 계측된 이벤트를 쉽게 집계할 수 있게 돼요.
이벤트 캡처 단위 테스트 (Unit Testing Event Capture)
@backstage/frontend-test-utils 패키지에는 캡처된 분석 이벤트를 스파이하고 검증하기 위해 단위 테스트에서 사용할 수 있는 MockAnalyticsApi 구현이 포함돼 있어요.
다음과 같이 사용하세요.
import { render, fireEvent, waitFor } from '@testing-library/react';import { analyticsApiRef } from '@backstage/frontend-plugin-api';import { MockAnalyticsApi, TestApiProvider, wrapInTestApp,} from '@backstage/frontend-test-utils';describe('SomeComponent', () => { it('should capture event on click', () => { const apiSpy = new MockAnalyticsApi(); const { getByText } = render( wrapInTestApp( <TestApiProvider apis={[[analyticsApiRef, apiSpy]]}> <SomeComponentUnderTest /> </TestApiProvider>, ), ); fireEvent.click(getByText('some component text')); await waitFor(() => { expect(apiSpy.getEvents()[0]).toMatchObject({ action: 'expected action', subject: 'expected subject', attributes: { foo: 'bar', }, }); }); });});