Keep discovery manual refresh visible

This commit is contained in:
rcourtman
2026-05-15 23:55:22 +01:00
parent b0c4faa4a0
commit f81490ca4f
7 changed files with 62 additions and 26 deletions
@@ -122,27 +122,30 @@ export const AIRuntimeControlsSection: Component<AIRuntimeControlsSectionProps>
: 'Workload discovery will automatically re-scan resources at this interval'}
</p>
</div>
<div class="flex items-center justify-between gap-3 rounded border border-border bg-surface-alt px-3 py-2">
<p class="text-[10px] text-muted">
Runs the new, changed, and stale workload sweep used by the schedule.
</p>
<button
type="button"
class="inline-flex min-h-10 sm:min-h-9 items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-base-content hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-50"
onClick={() => void state.handleRunDiscoveryRefresh()}
disabled={state.saving() || state.discoveryRunRunning()}
>
<Show
when={state.discoveryRunRunning()}
fallback={<RefreshCwIcon class="h-3.5 w-3.5" />}
>
<RefreshCwIcon class="h-3.5 w-3.5 animate-spin" />
</Show>
{state.discoveryRunRunning() ? 'Running...' : 'Run discovery now'}
</button>
</div>
</Show>
<div class="flex flex-col gap-2 rounded border border-border bg-surface-alt px-3 py-2 sm:flex-row sm:items-center sm:justify-between sm:gap-3">
<p class="text-[10px] text-muted sm:flex-1">
{state.form.discoveryEnabled
? 'Runs the new, changed, and stale workload sweep used by the schedule.'
: 'Runs a one-time workload discovery refresh without changing the schedule.'}
</p>
<button
type="button"
class="inline-flex min-h-10 items-center gap-1.5 self-start rounded-md border border-border px-3 py-1.5 text-xs font-medium text-base-content hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-9 sm:self-auto sm:flex-shrink-0"
onClick={() => void state.handleRunDiscoveryRefresh()}
disabled={state.saving() || state.discoveryRunRunning()}
>
<Show
when={state.discoveryRunRunning()}
fallback={<RefreshCwIcon class="h-3.5 w-3.5" />}
>
<RefreshCwIcon class="h-3.5 w-3.5 animate-spin" />
</Show>
{state.discoveryRunRunning() ? 'Running...' : 'Run discovery now'}
</button>
</div>
<p class="text-[10px] text-muted">{getAISettingsWorkloadDiscoverySummary().text}</p>
</div>
</Show>
@@ -506,6 +506,35 @@ describe('AISettings workload discovery persistence', () => {
});
});
it('keeps the manual discovery refresh visible when recurring discovery is off', async () => {
getSettingsMock.mockResolvedValue({
...baseSettings(),
discovery_enabled: false,
discovery_interval_hours: 0,
});
renderComponent();
await waitFor(() => {
expect(screen.getByRole('button', { name: /Workload Discovery Off/i })).toBeInTheDocument();
});
fireEvent.click(screen.getByRole('button', { name: /Workload Discovery Off/i }));
expect(screen.queryByLabelText('Scan Interval')).not.toBeInTheDocument();
expect(
screen.getByText('Runs a one-time workload discovery refresh without changing the schedule.'),
).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /Run discovery now/i }));
await waitFor(() => {
expect(runDiscoveryRefreshMock).toHaveBeenCalledTimes(1);
expect(notificationSuccessMock).toHaveBeenCalledWith(
'Discovery refresh finished: 1 workload refreshed.',
);
});
});
it('reports when a manual discovery refresh has no pending work', async () => {
getSettingsMock.mockResolvedValue({
...baseSettings(),
@@ -268,6 +268,9 @@ describe('settings architecture guardrails', () => {
expect(aiSettingsStateSource).toContain('discoveryRunRunning');
expect(aiRuntimeControlsSectionSource).toContain('state.handleRunDiscoveryRefresh()');
expect(aiRuntimeControlsSectionSource).toContain('Run discovery now');
expect(aiRuntimeControlsSectionSource).toContain(
'Runs a one-time workload discovery refresh without changing the schedule.',
);
expect(aiRuntimeControlsSectionSource).not.toContain("fetch('/api/discovery/run");
});