fix(datasets): update empty state handling and permissions messaging (#39360)

This commit is contained in:
Wu Tianwei 2026-07-22 11:57:35 +08:00 committed by GitHub
parent 04f9026724
commit 84f3c410c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 77 additions and 53 deletions

View File

@ -342,7 +342,7 @@ describe('List', () => {
).toHaveAttribute('href', '/datasets/create-from-pipeline')
})
it('should not render first empty state for legacy editors without dataset creation permissions', async () => {
it('should render a permission empty state without dataset creation permissions', async () => {
mockConsoleState = {
isCurrentWorkspaceEditor: true,
isCurrentWorkspaceManager: true,
@ -361,7 +361,8 @@ describe('List', () => {
render(<List />)
expect(screen.queryByText('dataset.firstEmpty.title')).not.toBeInTheDocument()
expect(screen.getByTestId('datasets-component')).toBeInTheDocument()
expect(screen.getByText('dataset.firstEmpty.noCreatePermission')).toBeInTheDocument()
expect(screen.queryByTestId('datasets-component')).not.toBeInTheDocument()
})
it('should not render first empty state before the first dataset page resolves', async () => {

View File

@ -82,11 +82,10 @@ describe('DatasetFirstEmptyState', () => {
)
})
it('renders nothing when no empty-state action is available', () => {
const { container } = render(
<DatasetFirstEmptyState canConnectExternalDataset={false} canCreateDataset={false} />,
)
it('shows a permission message when no empty-state action is available', () => {
render(<DatasetFirstEmptyState canConnectExternalDataset={false} canCreateDataset={false} />)
expect(container).toBeEmptyDOMElement()
expect(screen.getByText('dataset.firstEmpty.noCreatePermission')).toBeInTheDocument()
expect(screen.queryByRole('link')).not.toBeInTheDocument()
})
})

View File

@ -67,8 +67,6 @@ function DatasetFirstEmptyState({
: undefined
const hasActions = createActions.length > 0 || !!connectAction
if (!hasActions) return null
return (
<div className="flex grow flex-col overflow-hidden">
<div className="relative min-h-[520px] flex-1 overflow-hidden">
@ -89,47 +87,54 @@ function DatasetFirstEmptyState({
<span aria-hidden className="i-ri-book-2-line size-6 text-text-tertiary" />
</div>
</div>
<h2 id="datasets-first-empty-title" className="system-sm-regular text-text-tertiary">
{t(($) => $['firstEmpty.title'], { ns: 'dataset' })}
<h2
id="datasets-first-empty-title"
className="max-w-[520px] text-center system-sm-regular whitespace-pre-line text-text-tertiary"
>
{t(($) => $[hasActions ? 'firstEmpty.title' : 'firstEmpty.noCreatePermission'], {
ns: 'dataset',
})}
</h2>
</div>
<div className="flex w-full flex-col gap-2 pb-8">
{createActions.length > 0 && (
<div className="flex flex-col gap-2">
{createActions.map((action) => (
<FirstEmptyActionCard
key={action.id}
badge={action.badge}
description={action.description}
href={action.href}
icon={action.icon}
stepByStepTourTarget={action.target}
title={action.title}
visualStyle="list"
/>
))}
</div>
)}
{createActions.length > 0 && connectAction && (
<div className="flex items-center gap-2 text-text-tertiary">
<div className="h-px min-w-0 flex-1 bg-linear-to-r from-background-body/0 to-divider-regular" />
<span className="system-xs-medium-uppercase uppercase">
{t(($) => $['firstEmpty.or'], { ns: 'dataset' })}
</span>
<div className="h-px min-w-0 flex-1 bg-linear-to-r from-divider-regular to-background-body/0" />
</div>
)}
{connectAction && (
<FirstEmptyActionCard
description={connectAction.description}
href={connectAction.href}
icon={connectAction.icon}
stepByStepTourTarget={connectAction.target}
title={connectAction.title}
visualStyle="list"
/>
)}
</div>
{hasActions && (
<div className="flex w-full flex-col gap-2 pb-8">
{createActions.length > 0 && (
<div className="flex flex-col gap-2">
{createActions.map((action) => (
<FirstEmptyActionCard
key={action.id}
badge={action.badge}
description={action.description}
href={action.href}
icon={action.icon}
stepByStepTourTarget={action.target}
title={action.title}
visualStyle="list"
/>
))}
</div>
)}
{createActions.length > 0 && connectAction && (
<div className="flex items-center gap-2 text-text-tertiary">
<div className="h-px min-w-0 flex-1 bg-linear-to-r from-background-body/0 to-divider-regular" />
<span className="system-xs-medium-uppercase uppercase">
{t(($) => $['firstEmpty.or'], { ns: 'dataset' })}
</span>
<div className="h-px min-w-0 flex-1 bg-linear-to-r from-divider-regular to-background-body/0" />
</div>
)}
{connectAction && (
<FirstEmptyActionCard
description={connectAction.description}
href={connectAction.href}
icon={connectAction.icon}
stepByStepTourTarget={connectAction.target}
title={connectAction.title}
visualStyle="list"
/>
)}
</div>
)}
</div>
</section>
</div>

View File

@ -91,11 +91,7 @@ const List = () => {
keywords.trim().length > 0 ||
searchKeywords.trim().length > 0 ||
includeAll
const showEmptyDataList =
!hasAnyDataset &&
(canCreateDataset || canConnectExternalDataset) &&
hasResolvedFirstPage &&
!hasActiveFilters
const showEmptyDataList = !hasAnyDataset && hasResolvedFirstPage && !hasActiveFilters
const showFilteredEmptyState = !hasAnyDataset && hasResolvedFirstPage && hasActiveFilters
const activeStepByStepTourTaskId = useAtomValue(activeStepByStepTourTaskIdAtom)
const activeStepByStepTourGuideIndex = useAtomValue(activeStepByStepTourGuideIndexAtom)

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "هل لديك قاعدة معرفة بالفعل؟ اربطها عبر API دون ترحيل البيانات.",
"firstEmpty.createDescription": "أسرع طريقة للبدء. يمكنك التبديل إلى التخصيص في أي وقت.",
"firstEmpty.createTitle": "إنشاء قاعدة معرفة جاهزة للاستخدام",
"firstEmpty.noCreatePermission": "لا توجد قواعد معرفة يمكنك الوصول إليها.\nتواصل مع مشرف صيانة المورد أو مالك مساحة العمل لطلب الوصول.",
"firstEmpty.or": "أو",
"firstEmpty.pipelineDescription": "عرّف تدفق التقسيم والتنظيف والفهرسة الخاص بك للبيانات المتخصصة.",
"firstEmpty.pipelineTitle": "إنشاء قاعدة معرفة مخصصة",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Hast du bereits eine Wissensdatenbank? Verbinde sie per API, ohne Daten zu migrieren.",
"firstEmpty.createDescription": "Der schnellste Einstieg. Du kannst jederzeit zu benutzerdefiniert wechseln.",
"firstEmpty.createTitle": "Einsatzbereite Wissensdatenbank erstellen",
"firstEmpty.noCreatePermission": "Keine zugänglichen Wissensdatenbanken.\nWende dich an den Ressourcenbetreuer oder Workspace-Inhaber, um Zugriff anzufordern.",
"firstEmpty.or": "Oder",
"firstEmpty.pipelineDescription": "Definiere eigene Chunking-, Bereinigungs- und Indexierungsabläufe für spezialisierte Daten.",
"firstEmpty.pipelineTitle": "Benutzerdefinierte Wissensdatenbank erstellen",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Already have a knowledge base? Connect it via API without migrating data.",
"firstEmpty.createDescription": "Upload documents and let Dify handle the rest. The fastest way to get started.",
"firstEmpty.createTitle": "Create a ready-to-use knowledge base",
"firstEmpty.noCreatePermission": "No accessible knowledge bases.\nContact the resource maintainer or your Workspace Owner to request access.",
"firstEmpty.or": "Or",
"firstEmpty.pipelineDescription": "Build a custom data processing workflow with flexible nodes and steps.",
"firstEmpty.pipelineTitle": "Build a custom knowledge base",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "¿Ya tienes una base de conocimiento? Conéctala mediante API sin migrar datos.",
"firstEmpty.createDescription": "La forma más rápida de empezar. Puedes cambiar a personalizado en cualquier momento.",
"firstEmpty.createTitle": "Crear una base de conocimiento lista para usar",
"firstEmpty.noCreatePermission": "No hay bases de conocimiento accesibles.\nContacta con el mantenedor del recurso o el propietario del espacio de trabajo para solicitar acceso.",
"firstEmpty.or": "O",
"firstEmpty.pipelineDescription": "Define tu propio flujo de fragmentación, limpieza e indexación para datos especializados.",
"firstEmpty.pipelineTitle": "Crear una base de conocimiento personalizada",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Already have a knowledge base? Connect it via API without migrating data.",
"firstEmpty.createDescription": "The fastest way to start. Switch to custom anytime.",
"firstEmpty.createTitle": "ایجاد پایگاه دانش آماده استفاده",
"firstEmpty.noCreatePermission": "هیچ پایگاه دانشی در دسترس نیست.\nبرای درخواست دسترسی با نگهدارنده منبع یا مالک فضای کاری تماس بگیرید.",
"firstEmpty.or": "Or",
"firstEmpty.pipelineDescription": "جریان قطعه‌بندی، پاک‌سازی و نمایه‌سازی خود را برای داده‌های تخصصی تعریف کنید.",
"firstEmpty.pipelineTitle": "ساخت پایگاه دانش سفارشی",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Vous avez déjà une base de connaissances ? Connectez-la via API sans migrer les données.",
"firstEmpty.createDescription": "La façon la plus rapide de commencer. Vous pouvez passer au personnalisé à tout moment.",
"firstEmpty.createTitle": "Créer une base de connaissances prête à lemploi",
"firstEmpty.noCreatePermission": "Aucune base de connaissances accessible.\nContactez le mainteneur de la ressource ou le propriétaire de lespace de travail pour demander laccès.",
"firstEmpty.or": "Ou",
"firstEmpty.pipelineDescription": "Définissez votre propre flux de découpage, nettoyage et indexation pour des données spécialisées.",
"firstEmpty.pipelineTitle": "Créer une base de connaissances personnalisée",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Already have a knowledge base? Connect it via API without migrating data.",
"firstEmpty.createDescription": "The fastest way to start. Switch to custom anytime.",
"firstEmpty.createTitle": "उपयोग के लिए तैयार नॉलेज बेस बनाएं",
"firstEmpty.noCreatePermission": "कोई सुलभ नॉलेज बेस नहीं है।\nऐक्सेस का अनुरोध करने के लिए संसाधन के रखरखावकर्ता या वर्कस्पेस के मालिक से संपर्क करें।",
"firstEmpty.or": "Or",
"firstEmpty.pipelineDescription": "विशेष डेटा के लिए अपना चंकिंग, क्लीनअप और इंडेक्सिंग फ़्लो परिभाषित करें.",
"firstEmpty.pipelineTitle": "कस्टम नॉलेज बेस बनाएं",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Already have a knowledge base? Connect it via API without migrating data.",
"firstEmpty.createDescription": "The fastest way to start. Switch to custom anytime.",
"firstEmpty.createTitle": "Buat basis pengetahuan siap pakai",
"firstEmpty.noCreatePermission": "Tidak ada basis pengetahuan yang dapat diakses.\nHubungi pengelola sumber daya atau Pemilik Workspace untuk meminta akses.",
"firstEmpty.or": "Or",
"firstEmpty.pipelineDescription": "Tentukan alur chunking, pembersihan, dan pengindeksan Anda sendiri untuk data khusus.",
"firstEmpty.pipelineTitle": "Bangun basis pengetahuan kustom",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Hai già una knowledge base? Collegala via API senza migrare i dati.",
"firstEmpty.createDescription": "Il modo più rapido per iniziare. Puoi passare al personalizzato in qualsiasi momento.",
"firstEmpty.createTitle": "Crea una knowledge base pronta alluso",
"firstEmpty.noCreatePermission": "Nessuna knowledge base accessibile.\nContatta il manutentore della risorsa o il proprietario del workspace per richiedere laccesso.",
"firstEmpty.or": "Oppure",
"firstEmpty.pipelineDescription": "Definisci il tuo flusso di suddivisione, pulizia e indicizzazione per dati specializzati.",
"firstEmpty.pipelineTitle": "Crea una knowledge base personalizzata",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "既にナレッジベースをお持ちですか?データを移行せずに API 経由で接続できます。",
"firstEmpty.createDescription": "ドキュメントをアップロードするだけで、あとは Dify にお任せ。最も手軽な始め方です。",
"firstEmpty.createTitle": "すぐに使えるナレッジベースを作成",
"firstEmpty.noCreatePermission": "アクセス可能なナレッジベースがありません。\nアクセスを申請するには、リソースのメンテナーまたはワークスペースのオーナーにお問い合わせください。",
"firstEmpty.or": "または",
"firstEmpty.pipelineDescription": "柔軟なノードとステップで、カスタムのデータ処理ワークフローを構築できます。",
"firstEmpty.pipelineTitle": "カスタムナレッジベースを構築",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "이미 지식 베이스가 있나요? 데이터를 이전하지 않고 API로 연결하세요.",
"firstEmpty.createDescription": "가장 빠르게 시작하는 방법입니다. 언제든 사용자 지정으로 전환할 수 있습니다.",
"firstEmpty.createTitle": "바로 사용할 수 있는 지식 베이스 만들기",
"firstEmpty.noCreatePermission": "액세스할 수 있는 지식 베이스가 없습니다.\n액세스를 요청하려면 리소스 관리자 또는 워크스페이스 소유자에게 문의하세요.",
"firstEmpty.or": "또는",
"firstEmpty.pipelineDescription": "전문 데이터에 맞게 청킹, 정리, 인덱싱 흐름을 직접 정의하세요.",
"firstEmpty.pipelineTitle": "사용자 지정 지식 베이스 만들기",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Already have a knowledge base? Connect it via API without migrating data.",
"firstEmpty.createDescription": "The fastest way to start. Switch to custom anytime.",
"firstEmpty.createTitle": "Een gebruiksklare kennisbank maken",
"firstEmpty.noCreatePermission": "Er zijn geen toegankelijke kennisbanken.\nNeem contact op met de beheerder van de resource of de eigenaar van de workspace om toegang aan te vragen.",
"firstEmpty.or": "Or",
"firstEmpty.pipelineDescription": "Definieer je eigen flow voor chunking, opschoning en indexering voor gespecialiseerde data.",
"firstEmpty.pipelineTitle": "Een aangepaste kennisbank bouwen",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Already have a knowledge base? Connect it via API without migrating data.",
"firstEmpty.createDescription": "The fastest way to start. Switch to custom anytime.",
"firstEmpty.createTitle": "Utwórz gotową do użycia bazę wiedzy",
"firstEmpty.noCreatePermission": "Brak dostępnych baz wiedzy.\nSkontaktuj się z opiekunem zasobu lub właścicielem obszaru roboczego, aby poprosić o dostęp.",
"firstEmpty.or": "Or",
"firstEmpty.pipelineDescription": "Zdefiniuj własny przepływ dzielenia na fragmenty, czyszczenia i indeksowania dla specjalistycznych danych.",
"firstEmpty.pipelineTitle": "Zbuduj niestandardową bazę wiedzy",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Já tem uma base de conhecimento? Conecte-a via API sem migrar dados.",
"firstEmpty.createDescription": "A forma mais rápida de começar. Você pode mudar para personalizado a qualquer momento.",
"firstEmpty.createTitle": "Criar uma base de conhecimento pronta para uso",
"firstEmpty.noCreatePermission": "Nenhuma base de conhecimento acessível.\nEntre em contato com o mantenedor do recurso ou o proprietário do workspace para solicitar acesso.",
"firstEmpty.or": "Ou",
"firstEmpty.pipelineDescription": "Defina seu próprio fluxo de divisão, limpeza e indexação para dados especializados.",
"firstEmpty.pipelineTitle": "Criar uma base de conhecimento personalizada",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Already have a knowledge base? Connect it via API without migrating data.",
"firstEmpty.createDescription": "The fastest way to start. Switch to custom anytime.",
"firstEmpty.createTitle": "Creează o bază de cunoștințe gata de utilizare",
"firstEmpty.noCreatePermission": "Nu există baze de cunoștințe accesibile.\nContactează întreținătorul resursei sau proprietarul spațiului de lucru pentru a solicita acces.",
"firstEmpty.or": "Or",
"firstEmpty.pipelineDescription": "Definește propriul flux de fragmentare, curățare și indexare pentru date specializate.",
"firstEmpty.pipelineTitle": "Construiește o bază de cunoștințe personalizată",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "У вас уже есть база знаний? Подключите ее через API без миграции данных.",
"firstEmpty.createDescription": "Самый быстрый способ начать. В любой момент можно перейти к настройке вручную.",
"firstEmpty.createTitle": "Создать готовую к использованию базу знаний",
"firstEmpty.noCreatePermission": "Нет доступных баз знаний.\nОбратитесь к сопровождающему ресурса или владельцу рабочего пространства, чтобы запросить доступ.",
"firstEmpty.or": "Или",
"firstEmpty.pipelineDescription": "Настройте собственный поток разбиения, очистки и индексации для специализированных данных.",
"firstEmpty.pipelineTitle": "Создать пользовательскую базу знаний",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Already have a knowledge base? Connect it via API without migrating data.",
"firstEmpty.createDescription": "The fastest way to start. Switch to custom anytime.",
"firstEmpty.createTitle": "Ustvari bazo znanja, pripravljeno za uporabo",
"firstEmpty.noCreatePermission": "Ni dostopnih baz znanja.\nZa dostop se obrnite na vzdrževalca vira ali lastnika delovnega prostora.",
"firstEmpty.or": "Or",
"firstEmpty.pipelineDescription": "Določite svoj potek razčlenjevanja, čiščenja in indeksiranja za specializirane podatke.",
"firstEmpty.pipelineTitle": "Zgradi prilagojeno bazo znanja",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Already have a knowledge base? Connect it via API without migrating data.",
"firstEmpty.createDescription": "The fastest way to start. Switch to custom anytime.",
"firstEmpty.createTitle": "สร้างฐานความรู้ที่พร้อมใช้งาน",
"firstEmpty.noCreatePermission": "ไม่มีฐานความรู้ที่เข้าถึงได้\nโปรดติดต่อผู้ดูแลทรัพยากรหรือเจ้าของเวิร์กสเปซเพื่อขอสิทธิ์เข้าถึง",
"firstEmpty.or": "Or",
"firstEmpty.pipelineDescription": "กำหนดโฟลว์การแบ่งส่วน การล้างข้อมูล และการจัดทำดัชนีของคุณเองสำหรับข้อมูลเฉพาะทาง",
"firstEmpty.pipelineTitle": "สร้างฐานความรู้แบบกำหนดเอง",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Zaten bir bilgi tabanınız var mı? Verileri taşımadan API ile bağlayın.",
"firstEmpty.createDescription": "Başlamanın en hızlı yolu. İstediğiniz zaman özele geçebilirsiniz.",
"firstEmpty.createTitle": "Kullanıma hazır bilgi tabanı oluştur",
"firstEmpty.noCreatePermission": "Erişilebilir bilgi tabanı yok.\nErişim istemek için kaynak bakımcısı veya çalışma alanı sahibiyle iletişime geçin.",
"firstEmpty.or": "Veya",
"firstEmpty.pipelineDescription": "Özel veriler için kendi parçalama, temizleme ve indeksleme akışınızı tanımlayın.",
"firstEmpty.pipelineTitle": "Özel bilgi tabanı oluştur",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Уже маєте базу знань? Підключіть її через API без міграції даних.",
"firstEmpty.createDescription": "Найшвидший спосіб почати. У будь-який момент можна перейти до власного налаштування.",
"firstEmpty.createTitle": "Створити готову до використання базу знань",
"firstEmpty.noCreatePermission": "Немає доступних баз знань.\nЗверніться до супроводжувача ресурсу або власника робочого простору, щоб отримати доступ.",
"firstEmpty.or": "Або",
"firstEmpty.pipelineDescription": "Визначте власний процес поділу, очищення та індексації для спеціалізованих даних.",
"firstEmpty.pipelineTitle": "Створити власну базу знань",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "Đã có cơ sở tri thức? Kết nối qua API mà không cần di chuyển dữ liệu.",
"firstEmpty.createDescription": "Cách nhanh nhất để bắt đầu. Bạn có thể chuyển sang tùy chỉnh bất cứ lúc nào.",
"firstEmpty.createTitle": "Tạo cơ sở tri thức dùng ngay",
"firstEmpty.noCreatePermission": "Không có cơ sở tri thức nào có thể truy cập.\nHãy liên hệ người bảo trì tài nguyên hoặc Chủ sở hữu workspace để yêu cầu quyền truy cập.",
"firstEmpty.or": "Hoặc",
"firstEmpty.pipelineDescription": "Tự định nghĩa luồng chia đoạn, làm sạch và lập chỉ mục cho dữ liệu chuyên biệt.",
"firstEmpty.pipelineTitle": "Xây dựng cơ sở tri thức tùy chỉnh",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "已有知识库?通过 API 直接接入,无需迁移数据。",
"firstEmpty.createDescription": "上传文档,剩下的交给 Dify。最快的上手方式。",
"firstEmpty.createTitle": "创建即用型知识库",
"firstEmpty.noCreatePermission": "暂无可访问的知识库。\n请联系资源维护者或工作区所有者获取权限。",
"firstEmpty.or": "或",
"firstEmpty.pipelineDescription": "用灵活的节点和步骤搭建自定义的数据处理工作流。",
"firstEmpty.pipelineTitle": "构建自定义知识库",

View File

@ -75,6 +75,7 @@
"firstEmpty.connectDescription": "已有知識庫?透過 API 直接連接,無需遷移資料。",
"firstEmpty.createDescription": "最快的開始方式。之後可隨時切換為自訂。",
"firstEmpty.createTitle": "建立即用型知識庫",
"firstEmpty.noCreatePermission": "暫無可存取的知識庫。\n請聯絡資源維護者或工作空間擁有者以取得權限。",
"firstEmpty.or": "或",
"firstEmpty.pipelineDescription": "為專用資料定義自己的分段、清理與索引流程。",
"firstEmpty.pipelineTitle": "建立自訂知識庫",