UI5

아직도 Intent 없이 Fiori 연결? — 3단계 설정 #shorts #SAP #Fiori

📖 개요 — 왜 Intent Navigation인가

Fiori 앱을 하드코딩된 URL로 연결하면 시스템이 바뀔 때마다 링크가 깨지고, 권한 체크도 우회됩니다. Intent Navigation은 "무엇을(Semantic Object) 어떻게(Action)" 한다는 의미 기반 주소로 앱을 연결해 이 문제를 해결합니다. 이 글에서는 판매 오더(SalesOrder) 시나리오로 Launchpad 설정부터 파라미터 전달 코드, 실패 패턴까지 단계별로 다룹니다.

  • Intent 구조(Semantic Object + Action)와 동작 원리 이해
  • Launchpad에서 Target Mapping을 등록하는 3단계 절차
  • CrossApplicationNavigation으로 파라미터 전달 코드 작성
  • toExternal vs hrefForExternal 구분과 AppState 히스토리 관리

📚 시작 전에 알아야 할 것

SAPUI5 컨트롤러 구조(Component, Router)와 XML 뷰 기본, Fiori Launchpad(FLP)에서 타일로 앱을 실행해 본 경험이 필요합니다. OData 파라미터 개념을 알면 파라미터 매핑 부분이 쉽게 읽힙니다.

🔧 환경 및 준비물

  • SAPUI5 1.108 이상 (1.120부터 getService가 deprecated이므로 getServiceAsync 사용이 권장됩니다)
  • 온프레미스: S/4HANA 2021 이상 + Frontend Server의 Launchpad Designer(/UI2/FLPD_CUST) 또는 Launchpad App Manager
  • 클라우드: SAP BTP의 SAP Build Work Zone, standard edition(구 Launchpad Service)
  • 권한: 카탈로그/그룹 편집 권한, 테스트용 비즈니스 롤(PFCG 또는 Role Collection)

온프레미스와 BTP는 등록 UI가 다를 뿐, Intent 개념과 클라이언트 코드는 동일하게 동작합니다.

💡 핵심 개념 — Intent는 "전화번호부"다

직접 URL 연결이 "친구 집 주소를 외워서 찾아가는 것"이라면, Intent Navigation은 "전화번호부에서 이름으로 찾는 것"입니다. 앱의 물리적 위치(컴포넌트 URL)가 바뀌어도 이름(Intent)만 유지되면 연결이 깨지지 않습니다.

Intent의 문법은 다음과 같습니다.

#SemanticObject-action?param1=value1&param2=value2
// 예: #SalesOrder-display?SalesOrderID=4711
구성 요소역할예시
Semantic Object비즈니스 엔터티(명사)SalesOrder, Customer
Action수행할 동작(동사)display, manage, create
Parameters대상 앱에 전달할 컨텍스트SalesOrderID=4711

사용자가 Intent를 실행하면 FLP 셸이 Target Mapping(Intent → 실제 앱 매핑 테이블)을 조회하고, 사용자 롤에 할당된 카탈로그 안에서만 매칭을 허용합니다. 즉 내비게이션 자체가 권한 체크를 겸합니다. 이것이 window.open이나 라우터 직접 호출로는 얻을 수 없는 핵심 이점입니다. 흐름을 도식화하면 이렇습니다.

소스 앱 → CrossApplicationNavigation 서비스 → 셸 해시 변경 → Target Mapping 조회(롤 필터링) → 대상 앱 Component 기동 → startupParameters 전달

💻 실전 예제 — 3단계로 완성하기

1단계: Launchpad에 Target Mapping 등록 (기본)

Launchpad Designer 기준 절차입니다. (BTP Work Zone은 Content Manager에서 App 항목 생성 시 동일 필드를 입력합니다.)

  1. 카탈로그(예: ZC_SD_SALES)를 열고 Target Mappings → Create Target Mapping 선택
  2. Semantic Object SalesOrder, Action display 입력, Application Type은 SAPUI5 Fiori App, Component ID(예: zsd.salesorder.display)와 ICF 경로 지정
  3. Parameters 영역에 SalesOrderID를 추가하고 Required 체크 → 저장 후 카탈로그를 테스트 롤에 할당

대상 앱에서는 startupParameters로 값을 수신합니다.

// 대상 앱 Component.js — 파라미터 수신
onInit: function () {
  const oCompData = this.getOwnerComponent().getComponentData();
  const aOrderId = oCompData?.startupParameters?.SalesOrderID;
  if (aOrderId?.length) {
    this._bindOrder(aOrderId[0]); // 값은 항상 배열로 전달됨
  }
}

2단계: 소스 앱에서 파라미터 전달 이동 (검증·로깅 포함)

실무에서는 이동 전에 해당 Intent가 현재 사용자에게 지원되는지 확인해야 합니다. 권한이 없는 사용자에게 죽은 버튼을 보여주지 않기 위해서입니다.

// 소스 앱 컨트롤러 — 오더 상세로 이동
onOrderPress: async function (oEvent) {
  const sOrderId = oEvent.getSource().getBindingContext()
                         .getProperty("SalesOrderID");
  try {
    const oCrossNav = await sap.ushell.Container
      .getServiceAsync("CrossApplicationNavigation");

    const aSupported = await oCrossNav.isNavigationSupported([
      { target: { semanticObject: "SalesOrder", action: "display" } }
    ]);
    if (!aSupported[0].supported) {
      sap.m.MessageBox.warning("판매 오더 조회 권한이 없습니다.");
      return;
    }
    oCrossNav.toExternal({
      target: { semanticObject: "SalesOrder", action: "display" },
      params: { SalesOrderID: sOrderId, DisplayMode: "readonly" }
    });
  } catch (oError) {
    sap.base.Log.error("Intent 이동 실패", oError, "zsd.salesorder.list");
    sap.m.MessageToast.show("이동 중 오류가 발생했습니다.");
  }
}

toExternal은 즉시 이동을 수행하고, hrefForExternal은 이동하지 않고 해시 문자열만 반환합니다. 링크를 새 탭으로 열게 하거나 이메일에 삽입할 URL을 만들 때는 후자를 사용합니다.

// 새 탭용 링크 생성 — hrefForExternal
const sHash = await oCrossNav.hrefForExternal({
  target: { semanticObject: "SalesOrder", action: "display" },
  params: { SalesOrderID: sOrderId }
});
const sUrl = window.location.origin + window.location.pathname + sHash;

3단계: 프로덕션 — AppState로 상태 보존과 뒤로가기

URL 파라미터는 길이 제한이 있고 민감 데이터 노출 위험이 있습니다. 필터 조건 같은 큰 상태는 AppState(서버 저장 + 키만 URL에 노출)로 전달하는 것이 일반적으로 권장됩니다.

// 필터 상태를 AppState로 넘기기
const oAppState = this.getOwnerComponent()
  .getService? null : sap.ushell.Container; // 컨테이너 확보
const oCrossNav = await sap.ushell.Container
  .getServiceAsync("CrossApplicationNavigation");
const oState = oCrossNav.createEmptyAppState(this.getOwnerComponent());
oState.setData({
  selectionFields: { SoldToParty: "10001", Status: ["OPEN", "BLOCKED"] }
});
await oState.save(); // 키가 발급됨

oCrossNav.toExternal({
  target: { semanticObject: "SalesOrder", action: "manage" },
  params: { "sap-xapp-state": oState.getKey() } // 키만 URL에 노출
});

뒤로가기는 브라우저 히스토리를 직접 조작하지 말고 셸 서비스를 사용해야 크로스 앱 히스토리가 꼬이지 않습니다.

const oShellNav = await sap.ushell.Container
  .getServiceAsync("ShellNavigation");
// 히스토리를 남기지 않는 이동(대체 이동)이 필요하면:
oCrossNav.toExternal({ target: {...}, writeHistory: false });
// 표준 뒤로가기:
history.back(); // FLP 셸이 해시 기반으로 복원 처리

테스트 관점에서는 QUnit에서 sap.ushell.Container를 스텁 처리해 toExternal 호출 인자를 검증하고, 보안 관점에서는 파라미터에 개인정보를 넣지 않는 것(대신 sap-xapp-state 사용)이 핵심입니다.

⚠️ 흔한 실수와 트러블슈팅

Q1. "Could not open app. Try again later" 오류가 납니다.
대부분 Target Mapping 미등록이거나, 등록은 됐지만 해당 카탈로그가 사용자 롤에 없는 경우입니다. /n/UI2/FLIA(Intent Analysis) 또는 FLP의 지원 정보 팝업으로 매칭 결과를 확인하세요.

Q2. 이동은 되는데 대상 앱에 파라미터가 비어 있습니다.
Target Mapping의 파라미터 이름과 코드의 params 키가 대소문자까지 일치해야 합니다. SalesOrderId vs SalesOrderID 오타가 가장 흔한 원인입니다. 또 startupParameters 값은 배열이므로 [0] 접근을 잊지 마세요.

Q3. 개발기에서는 되는데 운영기에서 실패합니다.
카탈로그 트랜스포트 누락, 또는 운영기 롤에 카탈로그 미할당이 원인인 경우가 많습니다. 대상 앱 ICF 서비스 비활성화 여부도 확인 대상입니다.

Q4. WebIDE/BAS 로컬 실행에서 sap.ushell is undefined 오류가 납니다.
셸 컨테이너는 FLP 안에서만 존재합니다. 로컬은 FLP Sandbox(test/flpSandbox.html)로 실행하고, 코드에서는 sap.ushell?.Container 존재 여부를 방어적으로 체크하세요.

🚀 더 나아가기

여기까지 익혔다면 다음 주제로 확장해 보세요. (1) Smart Table/Smart Link의 Semantic Object 어노테이션으로 코드 없이 내비게이션 링크 자동 생성, (2) RAP/Fiori Elements 앱의 manifest.json crossNavigation 인바운드 선언, (3) BTP Work Zone의 Common Data Model 기반 콘텐츠 연합, (4) sap-ushell-config를 통한 스마트 링크 화이트리스트 제어가 자연스러운 다음 코스입니다.

배포 전 실무 체크리스트 7가지

  1. Semantic Object/Action 네이밍이 표준 오브젝트와 충돌하지 않는가
  2. Target Mapping 필수 파라미터가 코드의 params 키와 대소문자까지 일치하는가
  3. 운영 롤(카탈로그)에 소스·대상 앱이 모두 할당되어 있는가
  4. isNavigationSupported로 권한 없는 사용자 UI를 숨겼는가
  5. 민감 데이터가 URL이 아닌 sap-xapp-state로 전달되는가
  6. 뒤로가기 시 소스 앱의 필터/스크롤 상태가 복원되는가(iAppState)
  7. 트랜스포트에 카탈로그·그룹·Target Mapping이 모두 포함됐는가

📚 참고 링크

댓글 0

아직 댓글이 없습니다.