Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion bun_client/frontend/src/components/PluginOutput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { useState, useEffect } from 'react';
import { rpcCall } from '../api';
import { Button, Badge, Card, mapStatusToVariant, Theme } from '../design';
import { resolvePluginBody, sortedAnnotations, type PluginResult } from '../plugin_utils';
import {
canRerunPlugin,
resolvePluginBody,
sortedAnnotations,
type PluginResult,
} from '../plugin_utils';
import PluginAnnotations from './PluginAnnotations';
import PluginBodyView from './PluginBodyView';

Expand Down Expand Up @@ -159,6 +164,17 @@ export default function PluginOutput({
gap: '10px',
}}
>
{canRerunPlugin(plugin) && (
<Button
onClick={() => executePlugin(pluginName)}
loading={executingPlugins.has(pluginName)}
variant="secondary"
size="sm"
title={`Re-run ${pluginName}`}
>
Re-run
</Button>
)}
<Badge variant={mapStatusToVariant(plugin.status)}>
{plugin.status}
</Badge>
Expand Down
79 changes: 49 additions & 30 deletions bun_client/frontend/src/components/review/PluginsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Button, colors, shadows } from '../../design';
import { resolvePluginBody, sortedAnnotations } from '../../plugin_utils';
import { canRerunPlugin, resolvePluginBody, sortedAnnotations } from '../../plugin_utils';
import PluginAnnotations from '../PluginAnnotations';
import PluginBodyView from '../PluginBodyView';
import type { PluginResult } from './types';
Expand Down Expand Up @@ -115,39 +115,58 @@ export default function PluginsPanel({
<span style={{ fontWeight: 600, fontSize: '14px' }}>
{name}
</span>
<span
<div
style={{
fontSize: '11px',
padding: '2px 10px',
borderRadius: '12px',
fontWeight: 600,
background:
data.status === 'success'
? colors.bgSuccessDim
: data.status === 'pending'
? colors.bgWarningDim
: colors.bgDangerDim,
color:
data.status === 'success'
? colors.textSuccess
: data.status === 'pending'
? colors.textWarning
: colors.textDanger,
border: `1px solid ${data.status === 'success' ? colors.borderSuccessDim : data.status === 'pending' ? colors.borderWarningDim : colors.borderDangerDim}`,
display: 'flex',
alignItems: 'center',
gap: '8px',
}}
>
{data.status.toUpperCase()}
</span>
{data.status.toLowerCase() === 'deferred' && (
<Button
onClick={() => onExecutePlugin(name)}
loading={executingPlugins.has(name)}
variant="secondary"
size="sm"
{canRerunPlugin(data) && (
<Button
onClick={() => onExecutePlugin(name)}
loading={executingPlugins.has(name)}
variant="secondary"
size="sm"
title={`Re-run ${name}`}
>
Re-run
</Button>
)}
<span
style={{
fontSize: '11px',
padding: '2px 10px',
borderRadius: '12px',
fontWeight: 600,
background:
data.status === 'success'
? colors.bgSuccessDim
: data.status === 'pending'
? colors.bgWarningDim
: colors.bgDangerDim,
color:
data.status === 'success'
? colors.textSuccess
: data.status === 'pending'
? colors.textWarning
: colors.textDanger,
border: `1px solid ${data.status === 'success' ? colors.borderSuccessDim : data.status === 'pending' ? colors.borderWarningDim : colors.borderDangerDim}`,
}}
>
Execute
</Button>
)}
{data.status.toUpperCase()}
</span>
{data.status.toLowerCase() === 'deferred' && (
<Button
onClick={() => onExecutePlugin(name)}
loading={executingPlugins.has(name)}
variant="secondary"
size="sm"
>
Execute
</Button>
)}
</div>
</div>
<div
className="plugin-output markdown-content"
Expand Down
21 changes: 21 additions & 0 deletions bun_client/frontend/src/plugin_utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect, test, describe } from 'bun:test';
import {
annotationSeverityVariant,
canRerunPlugin,
resolvePluginBody,
sortedAnnotations,
totalAnnotationCount,
Expand Down Expand Up @@ -129,6 +130,26 @@ describe('annotationSeverityVariant', () => {
});
});

describe('canRerunPlugin', () => {
test('offers a re-run for plugins that finished executing', () => {
expect(canRerunPlugin({ result: 'summary', status: 'success' })).toBe(true);
expect(canRerunPlugin({ result: 'Error: boom', status: 'error' })).toBe(true);
});

test('does not offer a re-run for a deferred plugin that never ran', () => {
expect(canRerunPlugin({ result: '', status: 'deferred' })).toBe(false);
expect(canRerunPlugin({ result: '', status: 'DEFERRED' })).toBe(false);
});

test('does not offer a re-run while a plugin is still in flight', () => {
expect(canRerunPlugin({ result: '', status: 'pending' })).toBe(false);
});

test('treats a missing status as not re-runnable', () => {
expect(canRerunPlugin({ result: '', status: '' })).toBe(false);
});
});

describe('totalAnnotationCount', () => {
test('sums annotations across plugins and ignores plugins without any', () => {
const plugins: Record<string, PluginResult> = {
Expand Down
13 changes: 13 additions & 0 deletions bun_client/frontend/src/plugin_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ export function annotationSeverityVariant(severity: string): StatusVariant {
}
}

/**
* Whether a plugin can be re-run from the UI.
*
* Only plugins that have finished executing offer a re-run: a `deferred`
* plugin has never run (it gets an Execute button instead) and a `pending`
* one is still in flight. Everything else — success, error, or a status a
* newer server invents — has a result on screen worth refreshing.
*/
export function canRerunPlugin(plugin: PluginResult): boolean {
const status = (plugin.status || '').toLowerCase();
return status !== '' && status !== 'deferred' && status !== 'pending';
}

/** Total number of annotations across every plugin, for summary counts. */
export function totalAnnotationCount(plugins: Record<string, PluginResult>): number {
return Object.values(plugins).reduce((sum, p) => sum + (p.annotations?.length ?? 0), 0);
Expand Down
Loading