Work items 위젯
GitLab v19.1Work items 위젯은 프론트엔드 위젯에서 많은 영감을 받았습니다. GraphQL(Vue Apollo)은 work items 위젯 스택의 핵심을 구성합니다. work item 페이지를 표시하려면 프론트엔드가 표시하려는 work item에서 어떤 위젯을 사용할 수 있는지 알아야 합니다.
프론트엔드 아키텍처#
Work items 위젯은 프론트엔드 위젯에서 많은 영감을 받았습니다. Work items는 issuable과 아키텍처적으로 다르기 때문에 일부 차이점이 있을 수 있습니다.
GraphQL(Vue Apollo)은 work items 위젯 스택의 핵심을 구성합니다.
work items의 위젯 정보 조회#
work item 페이지를 표시하려면 프론트엔드가 표시하려는 work item에서 어떤 위젯을 사용할 수 있는지 알아야 합니다. 이를 위해 다음과 같은 쿼리를 사용하여 위젯 목록을 가져와야 합니다:
query workItem($workItemId: WorkItemID!) {
workItem(id: $workItemId) {
id
widgets {
... on WorkItemWidgetAssignees {
type
assignees {
nodes {
name
}
}
}
}
}
}
GraphQL 쿼리 및 뮤테이션#
GraphQL 쿼리와 뮤테이션은 work item에 독립적입니다. Work item 쿼리와 뮤테이션은 위젯 수준에서 수행되어야 하며, 위젯이 독립적으로 재사용 가능한 컴포넌트가 되도록 합니다. Work item 쿼리와 뮤테이션은 모든 work item 타입을 지원하고 동적이어야 합니다. 위젯 식별자를 지정하여 모든 work item 속성을 쿼리하고 수정할 수 있어야 합니다.
이 쿼리 예시에서 description 위젯은 쿼리와 뮤테이션을 사용하여 모든 work item의 description을 표시하고 업데이트합니다:
query workItem($fullPath: ID!, $iid: String!) {
namespace(fullPath: $fullPath) {
id
workItem(iid: $iid) {
id
iid
widgets {
... on WorkItemWidgetDescription {
description
descriptionHtml
}
}
}
}
}
뮤테이션 예시:
mutation {
workItemUpdate(input: {
id: "gid://gitlab/AnyWorkItem/499"
descriptionWidget: {
description: "New description"
}
}) {
errors
workItem {
description
}
}
}
위젯의 책임과 구조#
위젯은 제목, description, 라벨 등 단일 속성을 표시하고 업데이트하는 역할을 담당합니다. 위젯은 모든 타입의 work item을 지원해야 합니다. 컴포넌트 재사용성을 극대화하기 위해 위젯은 담당하는 속성의 work item 쿼리와 뮤테이션을 소유하는 필드 래퍼여야 합니다.
필드 컴포넌트는 범용적이고 단순한 컴포넌트입니다. 입력 필드, 날짜 선택기, 드롭다운 목록 등과 같이 속성이나 work item 세부 사항에 대한 지식이 없습니다.
위젯은 work items에 따라 다양한 사용 사례를 지원하도록 구성 가능해야 합니다. 위젯을 구축할 때는 props와 주입된 속성의 사용을 최소화하면서 추가 컨텍스트를 제공하기 위해 슬롯을 사용하세요.
예시#
현재 폴더에서 찾을 수 있는 편집 가능한 위젯이 많이 있습니다:
또한 드롭다운이 있는 새 위젯에 사용할 수 있는 재사용 가능한 기본 드롭다운 위젯 래퍼도 있습니다. 다중 선택과 단일 선택 모두 지원합니다.
상세 뷰에서 새 work item 위젯을 프론트엔드에 구현하는 단계#
새 위젯 작업을 시작하기 전에#
-
새 위젯의 범위를 파악하고 디자인이 준비되어 있는지 확인하세요.
-
새 위젯이 이미 백엔드에 구현되어 있고 유효한 work item 타입에 대한 work item 쿼리에서 반환되고 있는지 확인하세요. 멀티버전 호환성으로 인해 ~backend와 ~frontend는 별도의 마일스톤에 있어야 합니다.
-
workItemUpdate에서 위젯 업데이트가 지원되는지 확인하세요. -
위젯마다 다른 요구사항이 있으므로, 사전에 질문하고 PM/UX와 논의한 후 MVC를 만들어 이터레이션을 진행하는 것이 좋습니다.
새 위젯 작업을 시작할 때#
-
드롭다운, 텍스트 입력 또는 기타 커스텀 디자인 등 입력 필드에 따라 기존 래퍼를 사용하거나 완전히 새로운 컴포넌트를 만들어야 합니다.
-
위젯에 우선순위가 없다면 테스트 여지를 확보하기 위해 모든 새 위젯은 FF(Feature Flag) 뒤에 두는 것이 이상적입니다.
-
폴더에 새 위젯을 만드세요.
-
사이드바에서 편집 가능한 위젯이라면 work_item_attributes_wrapper에 포함해야 합니다.
단계#
새 work item 위젯 추가 프로세스의 예시로 머지 리퀘스트 #159720을 참조하세요.
-
app/assets/javascripts/work_items/constants.js에서I18N_WORK_ITEM_ERROR_FETCHING_<widget_name>을 정의하세요. -
컴포넌트
app/assets/javascripts/work_items/components/work_item_<widget_name>.vue또는ee/app/assets/javascripts/work_items/components/work_item_<widget_name>.vue를 만드세요.
컴포넌트는 workItemByIidQuery에서 사용 가능한 props를 받아서는 안 됩니다 - 이슈 #461761을 참조하세요.
-
app/assets/javascripts/work_items/components/work_item_attributes_wrapper.vue의 work item 보기/편집 화면에 컴포넌트를 추가하세요. -
새 work item을 생성할 때 위젯을 사용할 수 있는 경우:
app/assets/javascripts/work_items/components/create_work_item.vue의 work item 생성 화면에 컴포넌트를 추가하세요.
-
app/assets/javascripts/work_items/graphql/typedefs.graphql에서 로컬 입력 타입을 정의하세요. -
app/assets/javascripts/work_items/graphql/cache_utils.js에서 위젯에 대한 새 work item 상태 GraphQL 데이터를 스텁 처리하세요. -
app/assets/javascripts/work_items/graphql/resolvers.js에서 GraphQL이 GraphQL 데이터를 업데이트하는 방법을 정의하세요.
단일 값 위젯에는 특별한 CLEAR_VALUE 상수가 필요합니다. 값이 지워서 null인지, 설정하지 않아서 null인지를 구분할 수 없기 때문입니다.
예시: ee/app/assets/javascripts/work_items/components/work_item_health_status.vue.
[]와 null을 구분할 수 있는 여러 값을 지원하는 대부분의 위젯에는 이것이 필요하지 않습니다.
-
생성 뷰에서 값을 저장하는 데 Apollo 캐시가 어떻게 사용되는지에 대해 더 읽어보세요.
-
위젯에 대한 GraphQL 쿼리를 추가하세요:
CE 위젯의 경우 app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql과 ee/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql에 추가하세요.
-
EE 위젯의 경우
ee/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql에 추가하세요. -
번역 업데이트:
tooling/bin/gettext_extractor locale/gitlab.pot.
이 시점에서 프론트엔드에서 위젯을 사용할 수 있어야 합니다.
이제 기존 파일의 테스트를 업데이트하고 새 파일에 대한 테스트를 작성할 수 있습니다:
-
spec/frontend/work_items/components/create_work_item_spec.js또는ee/spec/frontend/work_items/components/create_work_item_spec.js. -
spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js또는ee/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js. -
spec/frontend/work_items/components/work_item_<widget_name>_spec.js또는ee/spec/frontend/work_items/components/work_item_<widget_name>_spec.js. -
spec/frontend/work_items/graphql/resolvers_spec.js또는ee/spec/frontend/work_items/graphql/resolvers_spec.js. -
spec/features/work_items/detail/work_item_detail_spec.rb또는ee/spec/features/work_items/detail/work_item_detail_spec.rb.
과도한 SQL 쿼리로 인해 일부 피처 스펙이 실패할 수 있습니다.
이를 해결하려면 spec/support/shared_examples/features/work_items/rolledup_dates_shared_examples.rb에서 모킹된 Gitlab::QueryLimiting::Transaction.threshold를 업데이트하세요.
생성 뷰에서 새 work item 위젯을 프론트엔드에 구현하는 단계#
-
새 위젯의 범위를 파악하고 디자인이 준비되어 있는지 확인하세요.
-
새 위젯이 이미 백엔드에 구현되어 있고 유효한 work item 타입에 대한 work item 쿼리에서 반환되고 있는지 확인하세요. 멀티버전 호환성으로 인해 ~backend와 ~frontend는 별도의 마일스톤에 있어야 합니다.
-
workItemCreate뮤테이션에서 위젯이 지원되는지 확인하세요. -
디자인을 기반으로 새 프론트엔드 위젯을 만든 후 work item 생성 뷰에 포함해야 합니다.
생성 뷰에서 값을 저장하는 데 Apollo 캐시 사용#
생성 뷰는 상세 뷰와 거의 동일하고, 각 위젯의 초안 데이터를 저장하고 싶었기 때문에 특정 타입의 각 새 work item에는 새 apollo 캐시 항목이 있습니다.
예를 들어 생성 뷰를 초기화할 때 work items 캐시 유틸리티의 setNewWorkItemCache 함수가 있으며, 이 함수는 work item 생성 모달과 work item 생성 컴포넌트 양쪽에서 호출됩니다.
사용 방식에 따라 모든 Vue 파일에 work item 생성 뷰를 포함할 수 있습니다. 생성 뷰의 workItemType을 전달하면 work item 타입 쿼리에서 가져온 적용 가능한 work item 위젯만 포함되며 위젯 정의에 있는 것만 표시됩니다.
생성 뷰에서 work item 초안 데이터를 업데이트하는 로컬 뮤테이션이 있습니다.
Apollo 캐시의 생성 폼에서 새 위젯 지원#
-
각 위젯은 별도로 사용할 수 있으므로, 각 위젯은
updateWorkItem뮤테이션을 사용합니다. -
이제 초안 데이터를 업데이트하려면 캐시를 데이터로 업데이트해야 합니다.
-
work item을 업데이트하기 직전에 새 work item인지 또는 work item
id/iid가 존재하는지 확인합니다. 예시:
if (this.workItemId === newWorkItemId(this.workItemType)) {
this.$apollo.mutate({
mutation: updateNewWorkItemMutation,
variables: {
input: {
workItemType: this.workItemType,
fullPath: this.fullPath,
assignees: this.localAssignees,
},
},
});
로컬 뮤테이션에서 새 work item 위젯 지원#
- work item 로컬 뮤테이션 typedefs에 입력 타입을 추가하세요. 커스텀 객체 또는 기본 값이 될 수 있습니다.
work item의 부모의 이름과 ID를 가진 parent를 추가하려는 경우 예시:
input LocalParentWidgetInput {
id: String
name: String
}
input LocalUpdateNewWorkItemInput {
fullPath: String!
workItemType: String!
healthStatus: String
color: String
title: String
description: String
confidential: Boolean
parent: [LocalParentWidgetInput]
}
- 생성 뷰에서 초안 저장을 지원하기 위해 위젯에서 새 파라미터를 전달하세요.
this.$apollo.mutate({
mutation: updateNewWorkItemMutation,
variables: {
input: {
workItemType: this.workItemType,
fullPath: this.fullPath,
parent: {
id: 'gid:://gitlab/WorkItem/1',
name: 'Parent of work item'
}
},
},
})
- graphql 리졸버에서 업데이트를 지원하고 새 work item 캐시를 업데이트하는 로직을 추가하세요.
const { parent } = input;
if (parent) {
const parentWidget = findWidget(WIDGET_TYPE_PARENT, draftData?.namespace?.workItem);
parentWidget.parent = parent;
const parentWidgetIndex = draftData.namespace.workItem.widgets.findIndex(
(widget) => widget.type === WIDGET_TYPE_PARENT,
);
draftData.namespace.workItem.widgets[parentWidgetIndex] = parentWidget;
}
- work item 생성 뷰에서 초안 값을 가져오세요.
if (this.isWidgetSupported(WIDGET_TYPE_PARENT)) {
workItemCreateInput.parentWidget = {
id: this.workItemParentId
};
}
await this.$apollo.mutate({
mutation: createWorkItemMutation,
variables: {
input: {
...workItemCreateInput,
},
});
위젯을 work item 타입에 매핑#
모든 work item 타입은 사전 정의된 동일한 위젯 풀을 공유하며, 특정 타입에서 어떤 위젯이 활성화되어 있는지에 따라 맞춤화됩니다. 위젯 매핑은 데이터베이스가 아닌 Ruby 정의 클래스를 통해 메모리 내에서 정의됩니다.
각 work item 타입은 app/models/work_items/types_framework/system_defined/definitions/ 아래에 정의 클래스가 있습니다.
각 클래스의 widgets 메서드는 해당 타입에서 사용 가능한 위젯을 결정하는 위젯 타입 문자열 배열을 반환합니다:
# app/models/work_items/types_framework/system_defined/definitions/issue.rb
def self.widgets
%w[assignees description labels milestone hierarchy weight ...]
end
인메모리 모델 WorkItems::TypesFramework::SystemDefined::WidgetDefinition은
애플리케이션 시작 시 이러한 정의 클래스에서 레코드를 빌드합니다.
데이터베이스 쿼리 없이 ActiveRecord와 유사한 쿼리 인터페이스(find_by, where, all)를 제공하기 위해
ActiveRecord::FixedItemsModel을 사용합니다.
work item 타입에 새 위젯 추가#
work item 타입에 위젯을 추가하려면 관련 정의 클래스의 widgets 메서드에 위젯 타입 문자열을 추가하세요.
예를 들어 Ticket 타입에 designs 위젯을 추가하려면 Definitions::Ticket.widgets가 반환하는 배열에 'designs'를 추가하세요.
데이터베이스 마이그레이션은 필요하지 않습니다.
백엔드 아키텍처#
커스텀 세분화된 뮤테이션(예: WorkItemCreateFromTask)을 사용하거나 workItemCreate 또는 workItemUpdate 뮤테이션의 일부로 위젯을 업데이트할 수 있습니다.
위젯 콜백#
work item의 뮤테이션과 함께 위젯을 업데이트할 때 백엔드 코드는 WorkItems::Callbacks::Base를 상속받는 콜백 클래스를 사용하여 구현해야 합니다.
이 클래스들은 ActiveRecord 콜백과 유사하게 명명되고 비슷하게 동작하는 콜백 메서드를 가집니다.
위젯과 동일한 이름의 콜백 클래스가 자동으로 사용됩니다.
예를 들어 WorkItems::Callbacks::AwardEmoji는 work item에 AwardEmoji 위젯이 있을 때 호출됩니다.
다른 클래스를 사용하려면 callback_class 클래스 메서드를 재정의할 수 있습니다.
콜백 클래스가 머지 리퀘스트나 에픽 같은 다른 issuable에도 사용될 때는 Issuable::Callbacks 아래에 클래스를 정의하고 IssuableBaseService#available_callbacks의 목록에 클래스를 추가하세요.
이는 work item 업데이트와 레거시 이슈, 머지 리퀘스트, 에픽 업데이트 모두에 대해 실행됩니다.
위젯이 더 이상 사용 가능하지 않도록 work item 타입이 변경되는지 확인하려면 excluded_in_new_type?을 사용하세요.
이는 일반적으로 더 이상 관련 없는 연결된 레코드를 제거하는 트리거입니다.
사용 가능한 콜백#
-
after_initialize는BuildService에 의해 work item이 초기화된 후,CreateService와UpdateService에 의해 work item이 저장되기 전에 호출됩니다. 이 콜백은 생성 또는 업데이트 데이터베이스 트랜잭션 외부에서 실행됩니다. -
before_create는CreateService에 의해 work item이 저장되기 전에 호출됩니다. 이 콜백은 생성 데이터베이스 트랜잭션 내에서 실행됩니다. -
before_update는UpdateService에 의해 work item이 저장되기 전에 호출됩니다. 이 콜백은 업데이트 데이터베이스 트랜잭션 내에서 실행됩니다. -
after_create는CreateService에 의해 work item이 저장된 후에 호출됩니다. 이 콜백은 생성 데이터베이스 트랜잭션 내에서 실행됩니다. -
after_update는UpdateService에 의해 work item이 저장된 후에 호출됩니다. 이 콜백은 업데이트 데이터베이스 트랜잭션 내에서 실행됩니다. -
after_save는CreateService또는UpdateService에 의해 생성 또는 DB 업데이트 트랜잭션이 커밋되기 전에 호출됩니다. -
after_update_commit는UpdateService에 의해 DB 업데이트 트랜잭션이 커밋된 후에 호출됩니다. -
after_save_commit는CreateService또는UpdateService에 의해 생성 또는 DB 업데이트 트랜잭션이 커밋된 후에 호출됩니다.
새 백엔드 위젯 만들기#
머지 리퀘스트 !158688은 역사적 예시로 참조되지만 위젯 정의에 대한 레거시 데이터베이스 기반 접근 방식을 사용합니다. 현재 인메모리 시스템을 반영하는 아래 단계를 대신 따르세요.
work item 뮤테이션에 위젯 인자를 추가하세요:
work item 생성 및 업데이트에 동일한 인자를 가진 위젯을 사용할 수 있는 CE 기능의 경우: app/graphql/mutations/concerns/mutations/work_items/shared_arguments.rb.
- 두 뮤테이션 중 하나에만 위젯을 사용할 수 있거나 인자가 다른 EE 기능의 경우:
생성: app/graphql/mutations/concerns/mutations/work_items/create_arguments.rb 또는 ee/app/graphql/ee/mutations/work_items/create.rb.
-
업데이트:
app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb또는ee/app/graphql/ee/mutations/work_items/update.rb.
app/graphql/types/work_items/widgets/<widget_name>_input_type.rb 또는 ee/app/graphql/types/work_items/widgets/<widget_name>_input_type.rb에 위젯 입력 타입을 추가하여 위젯 인자를 정의하세요.
생성 및 업데이트 뮤테이션에 대한 입력 타입이 다른 경우 <widget_name>_create_input_type.rb 및/또는 <widget_name>_update_input_type.rb를 사용하세요.
app/graphql/types/work_items/widgets/<widget_name>_type.rb 또는 ee/app/graphql/types/work_items/widgets/<widget_name>_type.rb에 위젯 타입을 추가하여 위젯 필드를 정의하세요.
app/assets/javascripts/graphql_shared/possible_types.json의 WorkItemWidget 배열에 위젯을 추가하세요.
app/graphql/types/work_items/widget_interface.rb의 TYPE_MAPPINGS 또는 ee/app/graphql/ee/types/work_items/widget_interface.rb의 EE_TYPE_MAPPINGS에 위젯 타입 매핑을 추가하세요.
app/models/work_items/types_framework/system_defined/widget_definition.rb의 widget_types 메서드에 위젯 타입 문자열을 추가하세요.
app/models/work_items/widgets/<widget_name>.rb에서 위젯의 일부로 사용 가능한 quick action을 정의하세요.
app/services/work_items/callbacks/<widget_name>.rb에 콜백을 추가하여 뮤테이션이 work item을 생성/업데이트하는 방법을 정의하세요.
if excluded_in_new_type?을 처리해야 하는지 고려하세요.
-
오류 처리에는
raise_error를 사용하세요.
app/models/work_items/types_framework/system_defined/definitions/ 아래의 각 관련 정의 클래스의 widgets 메서드에 위젯 타입 문자열을 추가하여 적절한 work item 타입에 위젯을 할당하세요.
예를 들어 Issue 타입에 위젯을 추가하려면 Definitions::Issue.widgets에 문자열을 추가하세요.
데이터베이스 마이그레이션은 필요하지 않습니다.
GraphQL 문서를 업데이트하세요: bundle exec rake gitlab:graphql:compile_docs.
번역을 업데이트하세요: tooling/bin/gettext_extractor locale/gitlab.pot.
이 시점에서 GraphQL 쿼리와 뮤테이션을 사용할 수 있어야 합니다.
이제 기존 파일의 테스트를 업데이트하고 새 파일에 대한 테스트를 작성할 수 있습니다:
-
spec/graphql/types/work_items/widget_interface_spec.rb또는ee/spec/graphql/ee/types/work_items/widget_interface_spec.rb. -
spec/models/work_items/widgets/<widget_name>_spec.rb또는ee/spec/models/work_items/widgets/<widget_name>_spec.rb. -
Request:
CE: spec/requests/api/graphql/mutations/work_items/update_spec.rb 및/또는 spec/requests/api/graphql/mutations/work_items/create_spec.rb.
-
EE:
ee/spec/requests/api/graphql/mutations/work_items/update_spec.rb및/또는ee/spec/requests/api/graphql/mutations/work_items/create_spec.rb. -
Callback:
spec/services/work_items/callbacks/<widget_name>_spec.rb또는ee/spec/services/work_items/callbacks/<widget_name>_spec.rb. -
GraphQL type:
spec/graphql/types/work_items/widgets/<widget_name>_type_spec.rb또는ee/spec/graphql/types/work_items/widgets/<widget_name>_type_spec.rb. -
GraphQL input type(s):
CE: spec/graphql/types/work_items/widgets/<widget_name>_input_type_spec.rb 또는 spec/graphql/types/work_items/widgets/<widget_name>_create_input_type_spec.rb와 spec/graphql/types/work_items/widgets/<widget_name>_update_input_type_spec.rb.
- EE:
ee/spec/graphql/types/work_items/widgets/<widget_name>_input_type_spec.rb또는ee/spec/graphql/types/work_items/widgets/<widget_name>_create_input_type_spec.rb와ee/spec/graphql/types/work_items/widgets/<widget_name>_update_input_type_spec.rb.