diff --git a/ui/src/plugin/PluginList.jsx b/ui/src/plugin/PluginList.jsx
index 67af85b81..346e1ceae 100644
--- a/ui/src/plugin/PluginList.jsx
+++ b/ui/src/plugin/PluginList.jsx
@@ -2,6 +2,7 @@ import React, { useMemo, useState, useCallback } from 'react'
import {
Button,
Datagrid,
+ Empty,
TextField,
TopToolbar,
useNotify,
@@ -10,7 +11,13 @@ import {
useTranslate,
} from 'react-admin'
import { makeStyles } from '@material-ui/core/styles'
-import { useMediaQuery, Tooltip, Chip, Typography } from '@material-ui/core'
+import {
+ useMediaQuery,
+ Tooltip,
+ Chip,
+ Typography,
+ Box,
+} from '@material-ui/core'
import { MdError, MdRefresh } from 'react-icons/md'
import { List, DateField, SimpleList, useResourceRefresh } from '../common'
import { httpClient } from '../dataProvider'
@@ -72,8 +79,7 @@ const ManifestField = ({ source }) => {
return {manifest[source] || '-'}
}
-const PluginListActions = () => {
- const translate = useTranslate()
+const RescanButton = () => {
const notify = useNotify()
const refresh = useRefresh()
const [loading, setLoading] = useState(false)
@@ -92,20 +98,37 @@ const PluginListActions = () => {
})
}, [notify, refresh])
+ return (
+
+ )
+}
+
+const PluginListActions = () => {
return (
-
+
)
}
+const PluginEmpty = () => {
+ return (
+ <>
+
+
+
+
+ >
+ )
+}
+
const PluginList = (props) => {
const isXsmall = useMediaQuery((theme) => theme.breakpoints.down('xs'))
const translate = useTranslate()
@@ -118,6 +141,7 @@ const PluginList = (props) => {
exporter={false}
bulkActionButtons={false}
actions={}
+ empty={}
>
{isXsmall ? (
{
TopToolbar: ({ children }) => (
{children}
),
+ Empty: () => No resources
,
Datagrid: ({ children }) => {children}
,
TextField: ({ source }) => ,
}
@@ -42,9 +49,10 @@ vi.mock('react-admin', async () => {
// Mock common components
vi.mock('../common', async () => {
return {
- List: ({ children, actions, ...props }) => (
+ List: ({ children, actions, empty, ...props }) => (
{actions}
+ {empty &&
{empty}
}
{children}
),
@@ -94,14 +102,16 @@ describe('PluginList', () => {
expect(screen.getByTestId('datagrid')).toBeInTheDocument()
})
- it('renders the rescan button', () => {
+ it('renders the rescan button in the toolbar', () => {
render()
- expect(screen.getByTestId('rescan-button')).toBeInTheDocument()
+ const toolbar = screen.getByTestId('top-toolbar')
+ expect(within(toolbar).getByTestId('rescan-button')).toBeInTheDocument()
})
it('calls rescan endpoint when rescan button is clicked', async () => {
render()
- const rescanButton = screen.getByTestId('rescan-button')
+ const toolbar = screen.getByTestId('top-toolbar')
+ const rescanButton = within(toolbar).getByTestId('rescan-button')
fireEvent.click(rescanButton)
@@ -114,7 +124,8 @@ describe('PluginList', () => {
it('calls refresh after successful rescan', async () => {
render()
- const rescanButton = screen.getByTestId('rescan-button')
+ const toolbar = screen.getByTestId('top-toolbar')
+ const rescanButton = within(toolbar).getByTestId('rescan-button')
fireEvent.click(rescanButton)
@@ -127,7 +138,8 @@ describe('PluginList', () => {
mockHttpClient.mockRejectedValue(new Error('Network error'))
render()
- const rescanButton = screen.getByTestId('rescan-button')
+ const toolbar = screen.getByTestId('top-toolbar')
+ const rescanButton = within(toolbar).getByTestId('rescan-button')
fireEvent.click(rescanButton)
@@ -137,4 +149,25 @@ describe('PluginList', () => {
})
})
})
+
+ it('renders a rescan button in the empty state', () => {
+ render()
+ const emptyState = screen.getByTestId('empty-state')
+ expect(emptyState).toBeInTheDocument()
+ expect(within(emptyState).getByTestId('rescan-button')).toBeInTheDocument()
+ })
+
+ it('empty state rescan button triggers rescan', async () => {
+ render()
+ const emptyState = screen.getByTestId('empty-state')
+ const rescanButton = within(emptyState).getByTestId('rescan-button')
+
+ fireEvent.click(rescanButton)
+
+ await waitFor(() => {
+ expect(mockHttpClient).toHaveBeenCalledWith('/api/plugin/rescan', {
+ method: 'POST',
+ })
+ })
+ })
})