From 0378ecb6ba7abea58043a2dd67f35d5bc4c9f363 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Fri, 22 May 2026 15:48:07 +0800 Subject: [PATCH] update --- .../__tests__/developer-api-section.spec.tsx | 77 ++++++++++++ .../access/developer-api-section.tsx | 113 +++++++++++++++--- 2 files changed, 171 insertions(+), 19 deletions(-) create mode 100644 web/features/deployments/detail/settings-tab/access/__tests__/developer-api-section.spec.tsx diff --git a/web/features/deployments/detail/settings-tab/access/__tests__/developer-api-section.spec.tsx b/web/features/deployments/detail/settings-tab/access/__tests__/developer-api-section.spec.tsx new file mode 100644 index 0000000000..564a822509 --- /dev/null +++ b/web/features/deployments/detail/settings-tab/access/__tests__/developer-api-section.spec.tsx @@ -0,0 +1,77 @@ +import { render, screen } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { DeveloperApiSection } from '../developer-api-section' + +type QueryOptions = { + queryKey?: string[] +} + +type QueryResult = { + data?: unknown + isLoading: boolean + isError: boolean +} + +const mockUseQuery = vi.fn<(options: QueryOptions) => QueryResult>() + +vi.mock('@tanstack/react-query', () => ({ + useMutation: () => ({ mutate: vi.fn() }), + useQuery: (options: QueryOptions) => mockUseQuery(options), +})) + +vi.mock('@/service/client', () => ({ + consoleQuery: { + enterprise: { + appDeployAccessService: { + createDeveloperApiKey: { + mutationOptions: () => ({ mutationFn: vi.fn() }), + }, + deleteDeveloperApiKey: { + mutationOptions: () => ({ mutationFn: vi.fn() }), + }, + getAppInstanceAccess: { + queryOptions: () => ({ queryKey: ['app-instance-access'] }), + }, + updateDeveloperApi: { + mutationOptions: () => ({ mutationFn: vi.fn() }), + }, + }, + }, + }, +})) + +function queryResult(overrides: Partial = {}): QueryResult { + return { + data: undefined, + isLoading: false, + isError: false, + ...overrides, + } +} + +describe('DeveloperApiSection', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + // Loading should reserve the same shape as the enabled API page: endpoint copy row plus API key table. + describe('Loading state', () => { + it('should render the updated API tab skeleton while access config is loading', () => { + // Arrange + mockUseQuery.mockReturnValue(queryResult({ isLoading: true })) + + // Act + const { container } = render() + + // Assert + expect(screen.getByText('deployments.access.api.endpoint')).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: 'deployments.access.api.table.name' })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: 'deployments.access.api.table.environment' })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: 'deployments.access.api.table.key' })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: 'deployments.access.api.table.action' })).toBeInTheDocument() + expect(container.querySelectorAll('[data-slot="deployment-developer-api-skeleton"]')).toHaveLength(1) + expect(container.querySelectorAll('[data-slot="deployment-developer-api-desktop-row-skeleton"]')).toHaveLength(2) + expect(container.querySelectorAll('[data-slot="deployment-developer-api-mobile-row-skeleton"]')).toHaveLength(2) + }) + }) +}) diff --git a/web/features/deployments/detail/settings-tab/access/developer-api-section.tsx b/web/features/deployments/detail/settings-tab/access/developer-api-section.tsx index 957ac908e2..8dbb2b9906 100644 --- a/web/features/deployments/detail/settings-tab/access/developer-api-section.tsx +++ b/web/features/deployments/detail/settings-tab/access/developer-api-section.tsx @@ -8,9 +8,20 @@ import { Switch, SwitchSkeleton } from '@langgenius/dify-ui/switch' import { useMutation, useQuery } from '@tanstack/react-query' import { atom, useAtom, useSetAtom } from 'jotai' import { useTranslation } from 'react-i18next' -import { SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton' +import { SkeletonRectangle } from '@/app/components/base/skeleton' import { consoleQuery } from '@/service/client' import { SectionState } from '../../common' +import { + DetailTable, + DetailTableBody, + DetailTableCard, + DetailTableCardList, + DetailTableCell, + DetailTableHead, + DetailTableHeader, + DetailTableRow, +} from '../../table' +import { API_KEY_DETAIL_TABLE_COLUMN_CLASS_NAMES } from '../../table-styles' import { ApiKeyGenerateMenu, ApiKeyList } from './api-keys' import { CopyPill } from './common' @@ -127,31 +138,95 @@ function CreatedApiTokenCard({ token, onDismiss }: { } function DeveloperApiSkeleton() { + const { t } = useTranslation('deployments') + return ( -
- - -
- - +
+
+
+ {t('access.api.endpoint')}
- - -
- {DEVELOPER_API_KEY_SKELETON_KEYS.map(key => ( - -
- - -
- -
- ))} + +
+
+
) } +function ApiKeyTableSkeleton() { + return ( + <> + + {DEVELOPER_API_KEY_SKELETON_KEYS.map(key => ( + +
+
+
+ + +
+ +
+
+ + +
+
+
+ ))} +
+
+ + + + {DEVELOPER_API_KEY_SKELETON_KEYS.map(key => ( + + ))} + + +
+ + ) +} + +function ApiKeyTableHeaderSkeleton() { + const { t } = useTranslation('deployments') + + return ( + + + {t('access.api.table.name')} + {t('access.api.table.environment')} + {t('access.api.table.key')} + {t('access.api.table.action')} + + + ) +} + +function ApiKeyDesktopRowSkeleton() { + return ( + + + + + + + + + + + +
+ +
+
+
+ ) +} + export function DeveloperApiSection({ appInstanceId, }: {