FR-052 — 雲端整合 Tab 顯示 Gate(Cloud Integration Tab Gate)

狀態:設計定稿待 user review(Phase 2 產出) 提出:2026-07-21 來源 Notion case3a4346da-4cd0-80fd「雲端整合的Tab要有跟雲端空間整合設定做連接的才看得到」(功能=專案規劃) arc 關聯:relates to FR-016(Google Drive 整合)、FR-042(框架儲存脫鉤 / storage scope)


1. 需求白話(Phase 0 已與 user 對齊)

專案規劃頁的「雲端整合」Tab(TabView tab index 5),只有在該租戶(tenant)的雲端空間整合狀態為 CONNECTED 時才顯示;未連接就直接隱藏該 tab(不渲染)。

1.1 設計決策(user 拍板)

# 決策 選定
1 Gate 門檻 只認 CONNECTED(EXPIRED / REVOKED / DISCONNECTED 皆隱藏)
2 未連接時 tab 直接隱藏(不渲染,非 disabled)
3 FE 信號來源 新增輕量 status endpoint(user 選)—— 但見 §2.2 ⚠️ 已有現成非 admin status endpoint,實作前請 user 二選一

2. 現況盤點(實地 grep)

2.1 落點

  • FEcompliance-manager-fe/src/views/project/ProjectPlanningView.vue:2110
    <TabPanel :header="t('lang.project_planning.tab_cloud_integrations') ?? '雲端整合'">
        <ProjectCloudIntegrationsPanel :project-id="projectId" :can-edit="isProjectManager" />
    </TabPanel>
    TabView tab index:0 控制項實作 / 1 SSP / 2 專案基本資訊 / 3 參與人員 / 4 程序書管理 / 5 雲端整合:283 註解)。雲端整合是最後一個 tab(index 5)
  • BE 狀態信號app/cloud_integration/service/google_drive_integration_service.py::get_status(tenant_id)GoogleDriveIntegrationStatusDTOstatus{CONNECTED / EXPIRED / REVOKED / DISCONNECTED}(tenant 層級,DISCONNECTED = 從無整合)。

2.2 ⚠️ 已有現成 status endpoint(避免重複造輪子,實作前須拍板)

api/cloud_integration/routes/google_drive_integration_route.py:22

class GoogleDriveIntegrationRoute(MethodResource):
    # GET /integrations/google-drive — 取當前 tenant Drive 狀態,jwt_required()(非 admin gated)
    def get(self):
        dto = service.get_status(user.tenant_id)
        return return_response(True, GoogleDriveStatusResponse().dump(asdict(dto)))
    # DELETE 才是 super_admin gated

GET 已對所有登入者開放、回 status。故 FR-052 有兩條路(CLAUDE.md「先找既有能力、禁止重複造輪子」,本設計建議走 A):

選項 做法 BE 改動 取捨
A(建議)reuse 既有 FE 直接打 GET /integrations/google-drive,判 status === 'CONNECTED' 回傳多帶 email/folder(已對全 JWT 開放,非新暴露);最省
B(user 原選)新輕量端點 新增 GET /integrations/google-drive/connected{connected: bool} 一支 route + service 一行 語意最窄、不外洩 email/folder;但多一支端點與既有重疊

user Phase 0 選了 B(新輕量端點)——但當時我尚未 grep 出既有 GET 已非 admin gated。實作前一句話讓 user 二選一(見 implementation-plan §Step 0)。以下設計兩案並存,預設 A。


3. User Mental Walkthrough

情境一:tenant 已 CONNECTED
1. Manager 進專案規劃頁 → onMounted 取 drive 狀態 → connected=true。
2. TabView 顯示 6 個 tab,最後一個「雲端整合」可見、可點。→ 行為同今。

情境二:tenant 未連接(DISCONNECTED / EXPIRED / REVOKED)
1. Manager 進專案規劃頁 → 取狀態 → connected=false。
2. TabView 只顯示前 5 個 tab(控制項/SSP/基本資訊/參與人員/程序書),
   「雲端整合」tab 根本不出現。
3. Manager 若要用雲端整合 → 需先由 super_admin 於系統層「雲端空間整合設定」接好 Drive,
   之後重進本頁才看得到(本案不做頁內引導文案——直接隱藏,決策 2)。

邊界:狀態查詢失敗(API error)
- connected 預設 false → tab 隱藏(fail-closed:拿不到狀態時不顯示,比誤顯示一個連不上的 tab 好)。

4. FE 設計(本案 FE 為主,BE 視 §2.2 選項)

ProjectPlanningView.vue

<!-- 只有 driveConnected 才渲染整個 TabPanel -->
<TabPanel v-if="driveConnected" :header="t('lang.project_planning.tab_cloud_integrations') ?? '雲端整合'">
    <ProjectCloudIntegrationsPanel :project-id="projectId" :can-edit="isProjectManager" />
</TabPanel>
const driveConnected = ref(false)
async function loadDriveStatus() {
    try {
        // 選項 A:既有 endpoint
        const s = await CloudIntegrationService.getDriveStatus()  // GET /integrations/google-drive
        driveConnected.value = s?.status === 'CONNECTED'
        // 選項 B:新端點 → driveConnected.value = s?.connected === true
    } catch (e) {
        driveConnected.value = false   // fail-closed
    }
}
onMounted(loadDriveStatus)

4.1 PrimeVue TabView v-if 動態 tab 雷區(FR-047 frontend-overview §3.6)

  • TabView 的 :active-index 依「渲染出的 TabPanel 順序」計 index。雲端整合是最後一個 tab,用 v-if 移除末尾 tab 不影響前 5 個 index(若移的是中間 tab 才會 off-by-one)→ 本案安全。
  • 若專案已有把 activeTab 記在 query/store,需確認「隱藏末 tab 後」不會殘留 index=5 指向不存在 tab(保險:loadDriveStatus resolve 前 activeTab 若 >4 且 !connected → clamp 到 0)。plan 列為 FE 驗證項。

5. BE 設計(僅選項 B 時)

若 user 確認走 B:

檔案 動作
Route api/cloud_integration/routes/google_drive_integration_route.py 新增 GoogleDriveConnectedRouteGET /integrations/google-drive/connectedjwt_required()
App google_drive_integration_service.py is_connected(tenant_id) -> boolget_status(tenant_id).status == "CONNECTED",reuse)
註冊 api/cloud_integration/__init__.py add_resource(..., "/integrations/google-drive/connected")
  • 走選項 A:BE 零改動,只 FE + 可能補一支 CloudIntegrationService.getDriveStatus()(若 FE 尚無)。

6. 權限 / Error handling

  • 狀態查詢:jwt_required(),回自己 tenant 狀態(既有 get_status 已 tenant-scoped,RLS 不涉)。
  • 不新增 error code(純唯讀 gate)。
  • fail-closed:查詢失敗 → tab 隱藏。

7. 不在範圍(YAGNI)

  • 不做「未連接時的引導文案 / 導去系統設定連結」(決策 2 = 直接隱藏)。
  • 不改雲端空間整合設定頁本身(super_admin 系統層,非本案)。
  • 不 gate 其他頁面的雲端相關入口(只本頁 tab;若有其他入口列 follow-up)。
  • 不做 project-level(vs tenant-level)drive 連接判斷 —— case 語意是 tenant 層「雲端空間整合設定」。

8. 風險 / 待查證(plan Step 0)

  1. §2.2 A/B 二選一:先讓 user 拍板(建議 A,零 BE 改動)。
  2. FE CloudIntegrationService 是否已有取 tenant drive status 的方法(grep FE)——有就直接用,避免 FE 也重造。
  3. TabView activeTab 持久化是否會殘留 index=5(§4.1)——FE 驗。