1. List Report에서 정렬 기본값이 왜 중요한가
RAP(ABAP RESTful Application Programming Model)으로 List Report 앱을 처음 만들어 실행하면, 데이터가 키 필드 순서 그대로(사실상 무작위처럼 보이는 순서로) 표시됩니다. 판매 오더 목록이라면 사용자는 당연히 "최신 오더가 맨 위에" 나오길 기대하지만, UUID 키 기반의 RAP 앱에서는 아무 설정 없이 그런 화면이 나오지 않습니다.
실무에서 이 문제는 생각보다 자주 발생합니다. 예를 들어 구매팀이 쓰는 구매요청 목록 앱에서 정렬 기본값이 없으면, 사용자는 앱을 열 때마다 매번 "생성일" 컬럼을 클릭해 내림차순으로 바꿔야 합니다. 이런 반복 작업은 사용자 불만으로 직결되고, "앱이 미완성 같다"는 인상을 줍니다. 해결책은 단 하나의 어노테이션, @UI.presentationVariant입니다. 이 글에서는 SalesOrder 시나리오를 통해 초기 정렬, 다중 컬럼 정렬, 그룹화, 필터 기본값까지 단계별로 설정하는 방법을 다룹니다.
이 글을 끝까지 읽으면 다음을 할 수 있게 됩니다.
- List Report 초기 표시 시 원하는 컬럼으로 자동 정렬
- 오름차순/내림차순을 섞은 다중 컬럼 정렬 우선순위 지정
- 그룹화(GroupBy) 기본값과 필터 기본값의 조합 구성
2. @UI.presentationVariant 어노테이션 개요
사전에 알아두면 좋은 것: CDS 뷰 엔티티 기본 문법, RAP의 3계층 구조(기본 뷰 → 프로젝션 뷰 → 서비스 정의/바인딩), 그리고 ADT(Eclipse 기반 ABAP Development Tools) 사용 경험입니다. 환경은 SAP BTP ABAP Environment 또는 SAP S/4HANA 2021 이상(on-premise), S/4HANA Cloud에서 일반적으로 동일하게 동작하며, UI는 Fiori elements List Report(OData V4 기준)를 전제로 합니다.
@UI.presentationVariant는 "데이터를 화면에 어떻게 보여줄지"를 정의하는 엔티티 레벨 어노테이션입니다. 식당에 비유하면, 데이터 자체는 주방의 재료이고 presentationVariant는 "플레이팅 지시서"입니다. 어떤 순서로 접시에 올릴지(sortOrder), 어떤 기준으로 묶어 담을지(groupBy), 어떤 형태의 그릇을 쓸지(visualizations)를 지시합니다. 반면 뒤에서 다룰 @UI.selectionVariant는 "어떤 재료를 주방에서 꺼내올지", 즉 데이터 선택 조건을 담당합니다.
주요 구성 요소는 다음과 같습니다.
| 속성 | 역할 |
|---|---|
sortOrder | 정렬 대상 요소와 방향(#ASC/#DESC) 목록. 배열 순서가 곧 우선순위 |
groupBy | 테이블 행을 묶는 그룹화 기준 요소 목록 |
visualizations | 적용 대상 시각화. List Report 테이블이면 #AS_LINEITEM |
qualifier | 여러 변형을 구분하는 이름표(생략 시 기본 변형으로 적용) |
배치 위치는 CDS 프로젝션 뷰의 헤더(define 문 위) 또는 메타데이터 확장(Metadata Extension)의 헤더입니다. 실무에서는 UI 어노테이션을 데이터 모델과 분리하기 위해 메타데이터 확장에 두는 방식이 권장됩니다. 이때 프로젝션 뷰에 @Metadata.allowExtensions: true가 선언되어 있어야 합니다.
3. 기본 정렬 설정 실전 예제 (1단계)
판매 오더 프로젝션 뷰 ZC_SalesOrderTP에 "오더 일자 내림차순" 기본 정렬을 걸어 보겠습니다. 메타데이터 확장 파일의 헤더에 다음과 같이 작성합니다.
@Metadata.layer: #CORE
@UI: {
headerInfo: {
typeName: '판매 오더',
typeNamePlural: '판매 오더 목록'
},
presentationVariant: [{
sortOrder: [{ by: 'OrderDate', direction: #DESC }],
visualizations: [{ type: #AS_LINEITEM }]
}]
}
annotate view ZC_SalesOrderTP with
{
@UI.lineItem: [{ position: 10 }]
SalesOrderId;
@UI.lineItem: [{ position: 20 }]
OrderDate;
@UI.lineItem: [{ position: 30 }]
NetAmount;
}
포인트를 짚어 보면 이렇습니다.
presentationVariant는 배열([{ }]) 형태입니다. 대괄호를 빠뜨리면 활성화 오류가 납니다.by에는 CDS 뷰에서 정의한 요소 이름 그대로(alias 포함) 문자열로 적습니다.visualizations: [{ type: #AS_LINEITEM }]은 이 변형이 라인아이템 테이블에 적용됨을 명시합니다. 일부 릴리스/시나리오에서 이 항목이 없으면 정렬이 반영되지 않는 경우가 있어 항상 함께 쓰는 것이 안전합니다.
활성화 후 앱을 새로고침하고 "Go"를 누르면, 최신 오더가 맨 위에 표시됩니다. 서버로 전송되는 OData 요청에 $orderby=OrderDate desc가 자동으로 붙는 것을 브라우저 개발자 도구 네트워크 탭에서 확인할 수 있습니다. 즉 이 정렬은 화면단 정렬이 아니라 서버 사이드 정렬이므로 페이징과도 자연스럽게 맞물립니다.
4. 다중 컬럼 정렬 설정 (2단계)
실무 요건은 보통 한 컬럼으로 끝나지 않습니다. "영업조직별로 오름차순 묶고, 같은 조직 안에서는 금액이 큰 오더부터"라는 요건이라면 sortOrder 배열에 순서대로 나열합니다. 배열의 앞쪽이 상위 우선순위입니다.
@UI.presentationVariant: [{
sortOrder: [
{ by: 'SalesOrganization', direction: #ASC },
{ by: 'NetAmount', direction: #DESC },
{ by: 'OrderDate', direction: #DESC }
],
visualizations: [{ type: #AS_LINEITEM }]
}]
위 설정은 OData 요청에서 $orderby=SalesOrganization asc,NetAmount desc,OrderDate desc로 변환됩니다. 마지막에 OrderDate를 넣은 이유는 타이브레이커(동점 처리) 때문입니다. 금액까지 같은 오더가 여러 건일 때 정렬 결과가 실행할 때마다 흔들리지 않도록, 마지막에 유일성이 높은 필드를 하나 더 두는 것이 실무 관례입니다. 개발 중에는 어노테이션 오타를 빨리 잡기 위해 네트워크 탭의 $orderby 파라미터를 로그처럼 확인하는 습관을 들이면 트러블슈팅 시간이 크게 줄어듭니다.
5. 그룹화(GroupBy) 기본값 설정 (3단계)
정렬에 더해 "오더 상태별로 묶어서 보여달라"는 요건은 groupBy로 처리합니다. 프로덕션 수준에서 안정적으로 동작시키려면 groupBy 대상 요소를 sortOrder 맨 앞에도 함께 넣는 것이 핵심입니다. 그룹화는 정렬된 결과를 묶는 방식으로 렌더링되기 때문에, 정렬 기준과 그룹 기준이 어긋나면 같은 그룹이 화면에 쪼개져 나타날 수 있습니다.
@UI.presentationVariant: [{
sortOrder: [
{ by: 'OverallStatus', direction: #ASC },
{ by: 'OrderDate', direction: #DESC }
],
groupBy: [ 'OverallStatus' ],
visualizations: [{ type: #AS_LINEITEM }]
}]
이렇게 하면 List Report 테이블이 "상태" 값별 그룹 헤더로 묶이고, 각 그룹 안에서는 최신 오더부터 표시됩니다. 상태 코드가 O, C 같은 내부 값 그대로 그룹 헤더에 노출되지 않도록, 해당 필드에 @ObjectModel.text.element로 텍스트 요소를 연결해 두면 사용자 친화적인 그룹 헤더를 얻을 수 있습니다. 또한 성능 관점에서 그룹화·정렬 대상 필드는 DB에서 정렬 가능한 단순 필드(계산 부하가 큰 가상 요소 지양)로 두는 것이 일반적으로 권장됩니다.
6. 필터 기본값과의 조합 — @UI.selectionVariant 연계
"처음 열었을 때 진행 중 오더만, 최신순으로"처럼 표시 방식과 데이터 선택을 함께 제어하려면 @UI.selectionVariant를 조합합니다.
@UI: {
presentationVariant: [{
sortOrder: [{ by: 'OrderDate', direction: #DESC }],
visualizations: [{ type: #AS_LINEITEM }]
}],
selectionVariant: [{
qualifier: 'OpenOrders',
text: '진행 중 오더',
filter: 'OverallStatus eq ''O'''
}]
}
selectionVariant는 필터바 상단의 변형(variant) 선택지로 나타나며, 사용자가 클릭 한 번으로 미리 정의된 필터 조합을 적용할 수 있게 합니다. 반면 특정 필터 필드에 초기값 자체를 채워 두고 싶다면 필드 레벨의 @Consumption.filter.defaultValue를 사용합니다.
@Consumption.filter.defaultValue: 'O'
@UI.selectionField: [{ position: 10 }]
OverallStatus;
정리하면 역할 분담은 이렇습니다. 표시 방식(정렬/그룹)은 presentationVariant, 이름 붙은 필터 세트는 selectionVariant, 개별 필터 초기값은 Consumption.filter.defaultValue. 두 변형을 하나로 묶어 기본 적용하는 @UI.selectionPresentationVariant도 있으나, 입문 단계에서는 위 세 가지 조합만으로 대부분의 요건을 충족할 수 있습니다.
7. 흔한 실수 3가지와 트러블슈팅
실수 1 — 어노테이션 위치 오류. presentationVariant는 엔티티(헤더) 레벨 어노테이션인데, 이를 필드 위에 붙이면 활성화 오류가 나거나 조용히 무시됩니다. 반드시 annotate view 또는 define view entity 문 위에 배치하세요.
실수 2 — 요소 이름 오타. by: 'orderdate'처럼 CDS 요소 이름과 다르게 쓰면 정렬이 적용되지 않습니다. 프로젝션 뷰에서 alias를 지정했다면 alias 이름을 써야 합니다. 네트워크 탭에서 $orderby가 아예 없거나 이상한 필드로 나가는지 확인하는 것이 가장 빠른 진단법입니다.
실수 3 — 전파(레이어) 혼동. 기본 뷰, 프로젝션 뷰, 메타데이터 확장 여러 곳에 같은 어노테이션이 있으면 @Metadata.layer 우선순위(#CUSTOMER > #PARTNER > ... > #CORE)에 따라 상위 레이어가 이깁니다. "분명 고쳤는데 반영이 안 된다"면 다른 레이어의 확장이 덮어쓰고 있는지 확인하세요.
자주 묻는 질문:
- Q1. 어노테이션을 넣었는데 정렬이 전혀 안 됩니다. →
visualizations: [{ type: #AS_LINEITEM }]누락 여부, 서비스 메타데이터 캐시(브라우저 강력 새로고침, 필요 시 앱 재시작)를 순서대로 점검하세요. - Q2. 어떤 사용자에게만 기본 정렬이 무시됩니다. → 해당 사용자가 저장한 개인 변형(My Views)이 기본값으로 지정되어 있으면 개발자 정의보다 우선 적용됩니다. 표준 변형으로 되돌리면 해결됩니다.
- Q3. Association 너머 필드로 정렬할 수 있나요? →
sortOrder는 해당 엔티티에 노출된 요소만 대상입니다. 연관 뷰의 필드는 프로젝션 뷰에서_Assoc.Field as MyField처럼 자체 요소로 노출한 뒤 사용하세요.
8. 실무 적용 패턴 요약
새 List Report를 만들 때 아래 체크리스트를 습관처럼 적용하면 "미완성 같은 첫 화면" 문제를 예방할 수 있습니다.
- [ ] 사용자 관점의 기본 정렬 기준 1개 확정 (보통 생성일/변경일 내림차순)
- [ ] 동점 처리를 위한 타이브레이커 필드를 sortOrder 마지막에 추가
- [ ] groupBy 사용 시 그룹 필드를 sortOrder 맨 앞에 중복 배치
- [ ] visualizations에 #AS_LINEITEM 명시
- [ ] UI 어노테이션은 메타데이터 확장으로 분리, 레이어(#CORE 등) 명확화
- [ ] 필터 초기값은 @Consumption.filter.defaultValue, 필터 세트는 @UI.selectionVariant로 역할 구분
- [ ] 배포 전 네트워크 탭에서 $orderby/$filter 파라미터 최종 확인
여기까지 익혔다면 다음으로는 qualifier를 활용한 복수 presentationVariant 정의, @UI.selectionPresentationVariant로 필터+표시 변형을 하나로 묶는 패턴, 그리고 Analytical List Page에서의 차트 시각화(#AS_CHART) 적용으로 확장해 보시길 권합니다. 더 깊이 보고 싶다면 아래 문서들이 도움이 됩니다.
댓글 0
아직 댓글이 없습니다.