This is an automated email from the ASF dual-hosted git repository.
baoyuan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git
The following commit(s) were added to refs/heads/master by this push:
new 6fdcbb482 feat: improve empty state UX for service list (#3275)
6fdcbb482 is described below
commit 6fdcbb4820c8242cfe3a301d1866c1b4bfe5c158
Author: Baluduvamsi2006 <[email protected]>
AuthorDate: Fri Feb 27 13:43:14 2026 +0530
feat: improve empty state UX for service list (#3275)
---
e2e/tests/services.empty.spec.ts | 60 ++++++++++++++++++++++++++++++++++++++++
src/locales/en/common.json | 4 ++-
src/routes/services/index.tsx | 11 +++++++-
3 files changed, 73 insertions(+), 2 deletions(-)
diff --git a/e2e/tests/services.empty.spec.ts b/e2e/tests/services.empty.spec.ts
new file mode 100644
index 000000000..a616b18d6
--- /dev/null
+++ b/e2e/tests/services.empty.spec.ts
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { servicesPom } from '@e2e/pom/services';
+import { e2eReq } from '@e2e/utils/req';
+import { test } from '@e2e/utils/test';
+import { expect } from '@playwright/test';
+
+import { deleteAllServices } from '@/apis/services';
+
+test.describe('Services Empty State', () => {
+ test.describe.configure({ mode: 'serial' });
+
+ test.beforeAll(async () => {
+ await deleteAllServices(e2eReq);
+ });
+
+ test('should display custom empty state when no services exist', async ({
page }) => {
+ await test.step('navigate to services page', async () => {
+ await servicesPom.getServiceNavBtn(page).click();
+ await servicesPom.isIndexPage(page);
+ });
+
+ await test.step('verify empty state is displayed', async () => {
+ const table = page.getByRole('table');
+ await expect(table).toBeVisible();
+
+ const emptyDescription = page.getByText('No services found. Click
Add Service to create your first one.');
+ await expect(emptyDescription).toBeVisible();
+
+ const emptyImage = page.locator('.ant-empty-image');
+ await expect(emptyImage).toBeVisible();
+ });
+
+ await test.step('verify no service rows are displayed', async () => {
+ const serviceRows = page.getByRole('cell', { name: /service_name_/
});
+ await expect(serviceRows).toHaveCount(0);
+ });
+
+ await test.step('verify add button is still accessible', async () => {
+ const addButton = servicesPom.getAddServiceBtn(page);
+ await expect(addButton).toBeVisible();
+ await expect(addButton).toBeEnabled();
+ });
+ });
+});
diff --git a/src/locales/en/common.json b/src/locales/en/common.json
index 8195d80c7..600379810 100644
--- a/src/locales/en/common.json
+++ b/src/locales/en/common.json
@@ -329,7 +329,9 @@
"singular": "Secret"
},
"services": {
- "singular": "Service"
+ "singular": "Service",
+ "empty": "No services found. Click Add Service to create your first one."
+
},
"settings": {
"adminKey": "Admin Key",
diff --git a/src/routes/services/index.tsx b/src/routes/services/index.tsx
index 10fa740b2..ea5c5011f 100644
--- a/src/routes/services/index.tsx
+++ b/src/routes/services/index.tsx
@@ -17,13 +17,14 @@
import type { ProColumns } from '@ant-design/pro-components';
import { ProTable } from '@ant-design/pro-components';
import { createFileRoute } from '@tanstack/react-router';
+import { Empty } from 'antd';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { getServiceListQueryOptions, useServiceList } from '@/apis/hooks';
import { DeleteResourceBtn } from '@/components/page/DeleteResourceBtn';
import PageHeader from '@/components/page/PageHeader';
-import { ToAddPageBtn,ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
+import { ToAddPageBtn, ToDetailPageBtn } from '@/components/page/ToAddPageBtn';
import { AntdConfigProvider } from '@/config/antdConfigProvider';
import { API_SERVICES } from '@/config/constant';
import { queryClient } from '@/config/global';
@@ -91,6 +92,14 @@ const ServiceList = () => {
return (
<AntdConfigProvider>
<ProTable
+ locale={{
+ emptyText: (
+ <Empty
+ description={t('services.empty')}
+ image={Empty.PRESENTED_IMAGE_SIMPLE}
+ />
+ ),
+ }}
columns={columns}
dataSource={data.list}
rowKey="id"