Work items 위젯

프론트엔드 아키텍처

작업 항목을 위한 위젯은 프론트엔드 위젯에서 큰 영감을 받았습니다. 작업 항목은 이슈와 아키텍처적으로 다르기 때문에 몇 가지 차이가 예상됩니다.

GraphQL (Vue Apollo)은 작업 항목 위젯 스택의 핵심을 이룹니다.

작업 항목을 위한 위젯 정보 검색

작업 항목을 표시하려면 프론트엔드는 표시하려는 작업 항목에서 사용 가능한 위젯을 알아야 합니다. 이를 위해 다음과 같은 쿼리를 사용하여 위젯 목록을 가져와야 합니다.

query workItem($workItemId: WorkItemID!) {
  workItem(id: $workItemId) {
    id
    widgets {
      ... on WorkItemWidgetAssignees {
        type
        assignees {
          nodes {
            name
          }
        }
      }
    }
  }
}

GraphQL 쿼리와 뮤테이션

GraphQL 쿼리와 뮤테이션은 작업 항목에 독립적입니다. 작업 항목 쿼리와 뮤테이션은 위젯 레벨에서 발생해야 하므로 위젯은 독립적으로 재사용 가능한 구성 요소여야 합니다. 작업 항목 쿼리와 뮤테이션은 모든 작업 항목 유형을 지원해야 하며 동적이어야 합니다. 위젯 식별자를 지정하여 어떤 작업 항목 속성도 쿼리하고 변이시켜야 합니다.

다음 쿼리 예에서 설명 위젯은 어떤 작업 항목의 설명을 표시하고 업데이트하는 데 쿼리와 뮤테이션을 사용합니다.

query workItem($fullPath: ID!, $iid: String!) {
  workspace: namespace(fullPath: $fullPath) {
    id
    workItem(iid: $iid) {
      id
      iid
      widgets {
        ... on WorkItemWidgetDescription {
          description
          descriptionHtml
        }
      }
    }
  }
}

뮤테이션 예시:

mutation {
  workItemUpdate(input: {
    id: "gid://gitlab/AnyWorkItem/499"
    descriptionWidget: {
      description: "새 설명"
    }
  }) {
    errors
    workItem {
      description
    }
  }
}

위젯 책임과 구조

위젯은 제목, 설명 또는 레이블과 같은 단일 속성을 표시하고 업데이트하는 데 책임이 있습니다. 위젯은 모든 유형의 작업 항목을 지원해야 합니다. 구성 요소 재사용성을 극대화하려면 위젯은 책임을 지닌 필드 래퍼여야 합니다.

필드 컴포넌트는 일반적이고 간단한 구성 요소입니다. 입력 필드, 날짜 선택기 또는 드롭다운 목록과 같은 속성이나 작업 항목의 세부 정보를 알지 못합니다.

위젯은 작업 항목에 따라 여러 사용 사례를 지원할 수 있도록 구성할 수 있어야 합니다. 위젯을 구축할 때 속성을 제공하고 props와 주입된 속성의 사용을 최소화하면서 추가 컨텍스트를 제공하기 위해 슬롯을 사용해야합니다.

예시

현재 우리는 폴더에서 찾을 수 있는 많은 편집 가능한 위젯을 보유하고 있습니다.

또한 재사용 가능한 기본 드롭다운 위젯 래퍼를 보유하고 있으며, 이 위젯은 드롭다운을 가진 새 위젯에 사용할 수 있도록 지원합니다. 이 위젯은 다중 선택 및 단일 선택을 모두 지원합니다.

프론트엔드의 상세 보기에서 새 작업 항목 위젯을 구현하는 단계

새 위젯 작업을 시작하기 전에

  1. 새 위젯의 범위를 알고 있고 새 위젯을 위한 디자인이 준비되어 있는지 확인하십시오.
  2. 새 위젯이 백엔드에서 이미 구현되어 있고 유효한 작업 항목 유형에 의해 반환되고 있는지 확인하십시오. 멀티버전 호환성을 위해 우리는 ~백엔드와 ~프론트엔드를 별개의 마일스톤으로 가져야 합니다.
  3. 위젯 업데이트가 workItemUpdate에서 지원되는지 확인하십시오.
  4. 각 위젯이 다른 요구 사항을 가지고 있으므로 사전에 질문을 하고 PM/UX와 논의한 후 MVC를 만드는 것이 좋은 아이디어입니다.

새 위젯에 대한 작업을 시작할 때

  1. 입력 필드에 따라 드롭다운, 입력 텍스트 또는 기타 사용자 정의 디자인을 사용하는지 확인하고 기존 래퍼를 사용하거나 완전히 새 컴포넌트를 사용해야 합니다.
  2. 이상적으로 모든 새 위젯은 우선 테스트할 공간을 확보하기 위해 FF 뒤에 있어야 하지만 위젯에 대한 우선순위가 있을 경우를 제외하고는 모든 새 위젯이 FF 뒤에 있어야 합니다.
  3. 폴더에 새 위젯을 만들어야 합니다.
  4. 사이드바에서 편집 가능한 위젯인 경우 work_item_attributes_wrapper에 포함해야 합니다.

단계

새 작업 항목 위젯을 추가하는 프로세스 예시를 보려면 병합 요청 #159720를 참조하세요.

  1. app/assets/javascripts/work_items/constants.jsI18N_WORK_ITEM_ERROR_FETCHING_<widget_name>을 정의하십시오.
  2. 컴포넌트 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을 참조하십시오.
  3. 컴포넌트를 보기/편집 작업 항목 화면에 추가하십시오 app/assets/javascripts/work_items/components/work_item_attributes_wrapper.vue.
  4. 위젯이 새 작업 항목 생성 시에 사용 가능한 경우:
    1. 컴포넌트를 app/assets/javascripts/work_items/components/create_work_item.vue에 추가하십시오.
    2. app/assets/javascripts/work_items/graphql/typedefs.graphql에서 로컬 입력 유형을 정의하십시오.
    3. app/assets/javascripts/work_items/graphql/cache_utils.js에서 위젯을 위한 새 작업 항목 상태 GraphQL 데이터를 스텁합니다.
    4. app/assets/javascripts/work_items/graphql/resolvers.js에서 GraphQL이 GraphQL 데이터를 업데이트하는 방법을 정의하십시오.
      • 단일 값 위젯에 대해 특별한 CLEAR_VALUE 상수가 필요합니다. 왜냐하면 값이 null인 경우에는 값이 null이지만 깨끗하게 지운 것인지, 기본값이 null인지를 구분할 수 없기 때문입니다. 이 예는 ee/app/assets/javascripts/work_items/components/work_item_health_status.vue를 참조하십시오. 대부분의 다중 값을 지원하는 대부분의 위젯에는 []null을 구분할 수 있기 때문에 필요하지 않습니다.
    5. 위젯에 대한 GraphQL 쿼리를 추가하십시오:
      • CE 위젯의 경우 app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphqlee/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql에,
      • EE 위젯의 경우 ee/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql에 추가하십시오.
  5. 번역을 업데이트하십시오: tooling/bin/gettext_extractor locale/gitlab.pot.

이 시점에서 프론트엔드에서 위젯을 사용할 수 있어야 합니다.

이제 기존 파일에 대한 테스트를 업데이트하고 새 파일에 대한 테스트를 작성할 수 있습니다.

  1. spec/frontend/work_items/components/create_work_item_spec.js 또는 ee/spec/frontend/work_items/components/create_work_item_spec.js.
  2. spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js 또는 ee/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js.
  3. spec/frontend/work_items/components/work_item_<widget_name>_spec.js 또는 ee/spec/frontend/work_items/components/work_item_<widget_name>_spec.js.
  4. spec/frontend/work_items/graphql/resolvers_spec.js 또는 ee/spec/frontend/work_items/graphql/resolvers_spec.js.
  5. spec/features/projects/work_items/work_item_spec.rb 또는 ee/spec/features/projects/work_items/work_item_spec.rb.

참고: 과도한 SQL 쿼리로 인해 일부 기능 사양이 실패할 수 있습니다. 이를 해결하기 위해 spec/support/shared_examples/features/work_items/rolledup_dates_shared_examples.rb에서 모의 Gitlab::QueryLimiting::Transaction.threshold를 업데이트하십시오.

프론트엔드에서 새 작업 항목 위젯을 구현하는 단계

  1. 새 위젯에 대한 범위를 파악하고 디자인이 준비되었는지 확인하세요.
  2. 새 위젯이 이미 백엔드에서 구현되어 작업 항목 쿼리에 의해 반환되고 있는지 확인하고, 다중 버전 호환성으로 인해~백엔드와~프론트엔드가 별도의 마일스톤에 있는지 확인하세요.
  3. 위젯이 workItemCreate 뮤테이션에서 지원되는지 확인하세요.
  4. 새로운 프론트엔드 위젯이 디자인을 기반으로 만들어진 후, 작업 항목 보기 작성에 포함되어 있는지 확인하세요.

작성 보기에서 값을 저장하는 Apollo 캐시

작성 뷰는 상세 보기와 거의 동일하기 때문에 각 위젯의 드래프트 데이터를 저장하고 싶었고, 특정 유형의 각 새로운 작업 항목에는 새로운 캐시 항목 apollo가 있습니다.

예를 들어, 작성 뷰를 초기화할 때, work items cache utils에 있는 setNewWorkItemCache 함수가 호출되며, 이는 작업 항목 모달 작성 보기작업 항목 구성 요소 작성 보기 모두에서 호출됩니다.

사용에 따라 작성 보기를 어떤 Vue 파일이든 포함시킬 수 있습니다. 작성 보기의 workItemType를 전달하면 작업 항목 유형 쿼리에서 가져온 적용 가능한 작업 항목 위젯만 포함되며, 위젯 정의에서만 보여집니다.

작성 보기에서 작성한 드래프트의 값을 가져오는 로컬 뮤테이션을 지원합니다.

Apollo 캐시에서 작성 양식에 새 위젯 지원

  1. 각 위젯은 별도로 사용될 수 있으므로, 각 위젯은 updateWorkItem 뮤테이션을 사용합니다.
  2. 이제 드래프트 데이터를 업데이트하려면 데이터와 함께 캐시를 업데이트해야 합니다.
  3. 작업 항목을 업데이트하기 전에 새 작업 항목인지 또는 작업 항목 id/iid가 있는지 확인하세요. 예시.
if (this.workItemId === newWorkItemId(this.workItemType)) {
  this.$apollo.mutate({
    mutation: updateNewWorkItemMutation,
    variables: {
      input: {
        workItemType: this.workItemType,
        fullPath: this.fullPath,
        assignees: this.localAssignees,
      },
    },
});

로컬 뮤테이션에서 새 작업 항목 위젯 지원

  1. 작업 항목 로컬 뮤테이션 유형 정보에 입력 유형을 추가하세요. 사용자 지정 객체 또는 기본값 등 무엇이든 상관없습니다.

작업 항목의 parent에 워크 아이템의 부모의 이름과 ID가 포함된 경우 예시

input LocalParentWidgetInput {
  id: String
  name: String
}

input LocalUpdateNewWorkItemInput {
  fullPath: String!
  workItemType: String!
  healthStatus: String
  color: String
  title: String
  description: String
  confidential: Boolean
  parent: [LocalParentWidgetInput]
}
  1. 위젯에서 드래프트 저장을 지원하려면 위젯에서 새 매개변수를 전달하세요.
this.$apollo.mutate({
    mutation: updateNewWorkItemMutation,
    variables: {
      input: {
        workItemType: this.workItemType,
        fullPath: this.fullPath,
        parent: {
          id: 'gid:://gitlab/WorkItem/1',
          name: '워크 아이템의 부모'
        }
      },
    },
})
  1. graphql 리졸버에서 업데이트를 지원하고 새 작업 항목 캐시를 업데이트하는 로직을 추가하세요.
  const { parent } = input;

  if (parent) {
      const parentWidget = findWidget(WIDGET_TYPE_PARENT, draftData?.workspace?.workItem);
      parentWidget.parent = parent;

      const parentWidgetIndex = draftData.workspace.workItem.widgets.findIndex(
        (widget) => widget.type === WIDGET_TYPE_PARENT,
      );
      draftData.workspace.workItem.widgets[parentWidgetIndex] = parentWidget;
  }

  1. 작업 항목 보기 작성에서 드래프트의 값을 가져옵니다.

if (this.isWidgetSupported(WIDGET_TYPE_PARENT)) {
    workItemCreateInput.parentWidget = {
      id: this.workItemParentId
    };
}

await this.$apollo.mutate({
  mutation: createWorkItemMutation,
  variables: {
    input: {
      ...workItemCreateInput,
    },
});

위젯을 작업 항목 유형에 매핑

모든 작업 항목 유형은 미리 정의된 위젯 풀을 공유하며, 특정 유형에 활성화된 위젯에 따라 사용자 정의됩니다. 사용자가 새로운 작업 항목 유형을 생성하고 그에 대한 위젯 집합을 정의 할 수 있도록 하기로 했기 때문에 각 작업 항목 유형의 위젯 매핑이 데이터베이스에 저장됩니다. 위젯 매핑은 widget_definitions 테이블에 저장되며, 기본 작업 항목 유형 및 향후 사용자 정의 유형에 대한 위젯 정의에 사용될 수 있습니다. 예상된 데이터베이스 테이블 구조에 대한 자세한 내용은 이 이슈 설명서에서 찾을 수 있습니다.

작업 항목 유형에 새 위젯 추가

각 작업 항목 유형에 할당된 위젯 정보는 데이터베이스에 저장되기 때문에, 작업 항목 유형에 새 위젯을 추가하려면 데이터베이스 마이그레이션을 통해 진행해야 합니다. 또한 위젯 가져오기(lib/gitlab/database_importers/work_items/widgets_importer.rb)를 업데이트해야 합니다.

위젯 정의 표 구조

테이블의 각 레코드는 위젯을 작업 항목 유형에 매핑하는 것을 정의합니다. 현재 “전역” 정의만 사용됩니다(NULL namespace_id와 함께 정의). 차후 반복에서 이러한 매핑을 사용자 정의할 수 있도록 할 계획입니다. 예를 들어 아래 테이블은 다음을 정의합니다:

  • 가중치 위젯은 작업 항목 유형 0과 1에 대해 활성화되어 있습니다.
  • 가중치 위젯은 작업 항목 유형 1에 대해 편집할 수 없으며 rollup 값만 포함하며, 작업 항목 유형 0은 편집 가능한 값만 포함합니다.
  • 네임스페이스 1에서 가중치 위젯은 MyWeight로 이름이 변경됩니다. 사용자가 위젯의 이름을 변경하면, 이 네임스페이스의 모든 위젯 매핑의 이름을 변경하는 것이 합리적입니다 - 왜냐하면 ‘name’ 속성이 비정규화되기 때문에, 이 위젯 유형의 모든 작업 항목 유형에 대해 네임스페이스가 있는 매핑을 만들어야 합니다.
  • 특정 작업 항목 유형에 대해 가중치 위젯을 비활성화할 수 있습니다 (네임스페이스 3에서는 작업 항목 유형 0에 대해 비활성화되지만, 작업 항목 유형 1에 대해서는 여전히 활성화되어 있습니다)
ID namespace_id work_item_type_id widget_type widget_options Name Disabled
1   0 1 {‘editable’ => true, ‘rollup’ => false } Weight false
2   1 1 {‘editable’ => false, ‘rollup’ => true } Weight false
3 1 0 1 {‘editable’ => true, ‘rollup’ => false } MyWeight false
4 1 1 1 {‘editable’ => false, ‘rollup’ => true } MyWeight false
5 2 0 1 {‘editable’ => true, ‘rollup’ => false } Other Weight false
6 3 0 1 {‘editable’ => true, ‘rollup’ => false } Weight true

백엔드 아키텍처

사용자 정의된 상세한 뮤테이션(예: WorkItemCreateFromTask) 또는 workItemCreate 또는 workItemUpdate 뮤테이션의 일부로 위젯을 업데이트할 수 있습니다.

위젯 콜백

작업 항목의 뮤테이션과 함께 위젯을 업데이트할 때, 백엔드 코드는 WorkItems::Callbacks::Base를 상속받은 콜백 클래스를 구현해야 합니다. 이러한 클래스에는 ActiveRecord 콜백과 유사한 이름의 콜백 메서드가 있으며 유사하게 작동합니다.

위젯과 동일한 이름의 콜백 클래스는 자동으로 사용됩니다. 예를 들어, 작업 항목에 AwardEmoji 위젯이있는 경우 WorkItems::Callbacks::AwardEmoji가 호출됩니다. 다른 클래스를 사용하려면 callback_class 클래스 메서드를 오버라이드할 수 있습니다.

콜백 클래스가 병합 요청 또는 epic과 같은 다른 이슈에도 사용되는 경우, 클래스를 Issuable::Callbacks에 정의하고 IssuableBaseService#available_callbacks 목록에 클래스를 추가하세요. 이들은 작업 항목 업데이트와 레거시 이슈, 병합 요청 또는 epic 업데이트에 대해 실행됩니다.

excluded_in_new_type?를 사용하여 작업 항목 유형이 변경되고 위젯을 더 이상 사용할 수 없을 때를 확인하세요. 이는 일반적으로 관련이 없어진 레코드를 제거하는 트리거입니다.

사용 가능한 콜백

  • after_initialize은 작업 항목이 BuildService에 의해 초기화된 후에 호출되며,CreateServiceUpdateService에 의해 저장되기 전에 실행됩니다. 이 콜백은 생성 또는 업데이트 데이터베이스 트랜잭션 외부에서 실행됩니다.
  • before_create은 작업 항목이 CreateService에 의해 저장되기 전에 호출됩니다. 이 콜백은 생성 데이터베이스 트랜잭션 내에서 실행됩니다.
  • before_update은 작업 항목이 UpdateService에 의해 저장되기 전에 호출됩니다. 이 콜백은 업데이트 데이터베이스 트랜잭션 내에서 실행됩니다.
  • after_create는 작업 항목이 CreateService에 의해 저장된 후에 호출됩니다. 이 콜백은 생성 데이터베이스 트랜잭션 내에서 실행됩니다.
  • after_update는 작업 항목이 UpdateService에 의해 저장된 후에 호출됩니다. 이 콜백은 업데이트 데이터베이스 트랜잭션 내에서 실행됩니다.
  • after_save는 작업 항목이 CreateService 또는 UpdateService에 의해 생성 또는 DB 업데이트 트랜잭션을 커밋하기 전에 호출됩니다.
  • after_update_commit은 작업 항목이 UpdateService에 의해 DB 업데이트 트랜잭션을 커밋한 후에 호출됩니다.
  • after_save_commit은 작업 항목이 CreateService 또는 UpdateService에 의해 생성 또는 DB 업데이트 트랜잭션을 커밋한 후에 호출됩니다.

새로운 백엔드 위젯 만들기

새로운 작업 항목 위젯을 추가하는 과정에 대한 예제는 merge request #158688를 참조하세요.

  1. 작업 항목 뮤테이션에 위젯 인수를 추가하세요:
    • 위젯이 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.
  2. 위젯 인수를 정의하세요. 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를 사용하세요.
  3. 위젯 필드를 정의할 것입니다. app/graphql/types/work_items/widgets/<widget_name>_type.rb 또는 ee/app/graphql/types/work_items/widgets/<widget_name>_type.rb에 위젯 유형을 추가하세요.
  4. app/assets/javascripts/graphql_shared/possible_types.jsonWorkItemWidget 배열에 위젯을 추가하세요.
  5. app/graphql/types/work_items/widget_interface.rbTYPE_MAPPINGS에 위젯 유형 매핑을 추가하세요. 또는 ee/app/graphql/ee/types/work_items/widget_interface.rbEE_TYPE_MAPPINGS에 추가하세요.
  6. app/models/work_items/widget_definition.rbwidget_type enum에 위젯 유형을 추가하세요.
  7. app/models/work_items/widgets/<widget_name>.rb에 위젯의 일부로 사용 가능한 빠른 액션을 정의하세요.
  8. app/services/work_items/callbacks/<widget_name>.rb에 뮤테이션들이 작업 항목을 생성/업데이트하는 방법을 정의하세요.
    • excluded_in_new_type?을 처리해야 하는지 여부를 고려하세요.
    • 오류를 처리하려면 raise_error를 사용하세요.
  9. lib/gitlab/database_importers/work_items/base_type_importer.rbWIDGET_NAMES 해시에 위젯을 정의하세요.
  10. 해당 작업 항목 유형에 위젯을 할당하세요. - lib/gitlab/database_importers/work_items/base_type_importer.rbWIDGETS_FOR_TYPE 해시에 추가하세요. - db/migrate/<version>_add_<widget_name>_widget_to_work_item_types.rb에 마이그레이션을 생성하세요. 최신 모범 사례에 대한 자세한 내용은 최신 discussion on merge request 148119를 참조하세요. 포스트 마이그레이션을 사용할 필요는 없습니다.
  11. GraphQL 문서를 업데이트하세요: bundle exec rake gitlab:graphql:compile_docs.
  12. 번역을 업데이트하세요: tooling/bin/gettext_extractor locale/gitlab.pot.

여기까지 하면 GraphQL 쿼리 및 뮤테이션을 사용할 수 있어야 합니다.

이제 기존 파일의 테스트를 업데이트하고 새 파일에 대해 테스트를 작성할 수 있습니다:

  1. spec/graphql/types/work_items/widget_interface_spec.rb 또는 ee/spec/graphql/types/work_items/widget_interface_spec.rb.
  2. spec/models/work_items/widget_definition_spec.rb 또는 ee/spec/models/ee/work_items/widget_definition_spec.rb.
  3. spec/models/work_items/widgets/<widget_name>_spec.rb 또는 ee/spec/models/work_items/widgets/<widget_name>_spec.rb.
  4. 요청:
    • 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.
  5. Callback: spec/services/work_items/callbacks/<widget_name>_spec.rb 또는 ee/spec/services/work_items/callbacks/<widget_name>_spec.rb.
  6. GraphQL 유형: spec/graphql/types/work_items/widgets/<widget_name>_type_spec.rb 또는 ee/spec/graphql/types/work_items/widgets/<widget_name>_type_spec.rb.
  7. GraphQL 입력 유형(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.rbspec/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.rbee/spec/graphql/types/work_items/widgets/<widget_name>_update_input_type_spec.rb.
  8. 마이그레이션: spec/migrations/<version>_add_<widget_name>_widget_to_work_item_types_spec.rb.