Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/simulation/workbench/components/nesExternalModeToolbar.tsx
13399 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Button, Checkbox, Input, Select, Text, Tooltip } from '@fluentui/react-components';
7
import { Play16Regular, Stop16Regular } from '@fluentui/react-icons';
8
import * as mobx from 'mobx';
9
import * as mobxlite from 'mobx-react-lite';
10
import * as React from 'react';
11
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
12
import { NesExternalOptions } from '../stores/nesExternalOptions';
13
import { CacheMode, RunnerOptions } from '../stores/runnerOptions';
14
import { SimulationRunsProvider } from '../stores/simulationBaseline';
15
import { SimulationRunner, StateKind } from '../stores/simulationRunner';
16
import { useLocalStorageState } from '../stores/storage';
17
import { CompareAgainstRunPicker } from './compareAgainstRunPicker';
18
import { CurrentRunPicker } from './currentRunPicker';
19
import {
20
createCacheMissesFilter,
21
createFailuresFilter,
22
createFilterer,
23
createGrepFilter,
24
createRanTestsFilter,
25
} from './filterUtils';
26
import { TestFilterer } from './testFilterer';
27
28
interface FilterParams {
29
grep: string;
30
showFailedOnly: boolean;
31
showWithCacheMissesOnly: boolean;
32
showOnlyRanTests: boolean;
33
}
34
35
/** Adapted from localModeToolbar's useAsyncFilter */
36
const useAsyncFilter = (
37
filterParams: FilterParams,
38
debounceMs: number,
39
createFilter: (params: FilterParams) => TestFilterer,
40
onFilterChange: (filter: TestFilterer | undefined) => void
41
) => {
42
const isMounted = useRef(true);
43
const latestFilterParams = useRef(filterParams);
44
const isFilteringRef = useRef(false);
45
const hasPendingFilterRef = useRef(false);
46
const [isFiltering, setIsFiltering] = useState(false);
47
48
useEffect(() => {
49
latestFilterParams.current = filterParams;
50
}, [filterParams]);
51
52
useEffect(() => {
53
return () => { isMounted.current = false; };
54
}, []);
55
56
const applyFilter = useCallback(() => {
57
if (!isMounted.current) { return; }
58
if (isFilteringRef.current) {
59
hasPendingFilterRef.current = true;
60
return;
61
}
62
isFilteringRef.current = true;
63
setIsFiltering(true);
64
65
setTimeout(() => {
66
const params = latestFilterParams.current;
67
requestAnimationFrame(() => {
68
try {
69
const newFilter = createFilter(params);
70
requestAnimationFrame(() => {
71
if (isMounted.current) {
72
onFilterChange(newFilter);
73
isFilteringRef.current = false;
74
setIsFiltering(false);
75
if (hasPendingFilterRef.current) {
76
hasPendingFilterRef.current = false;
77
applyFilter();
78
}
79
}
80
});
81
} catch (error) {
82
console.error('Error applying filter:', error);
83
if (isMounted.current) {
84
isFilteringRef.current = false;
85
setIsFiltering(false);
86
if (hasPendingFilterRef.current) {
87
hasPendingFilterRef.current = false;
88
applyFilter();
89
}
90
}
91
}
92
});
93
}, 0);
94
}, [createFilter, onFilterChange]);
95
96
useEffect(() => {
97
const debounceTimer = setTimeout(applyFilter, debounceMs);
98
return () => clearTimeout(debounceTimer);
99
}, [filterParams, applyFilter, debounceMs]);
100
101
return { isFiltering };
102
};
103
104
type SelectOptionEvent = { value: string };
105
106
type Props = {
107
runner: SimulationRunner;
108
runnerOptions: RunnerOptions;
109
nesExternalOptions: NesExternalOptions;
110
simulationRunsProvider: SimulationRunsProvider;
111
onFiltererChange: (filter: TestFilterer | undefined) => void;
112
};
113
114
export const NesExternalModeToolbar = mobxlite.observer(
115
({
116
runner,
117
runnerOptions,
118
nesExternalOptions,
119
simulationRunsProvider,
120
onFiltererChange,
121
}: Props) => {
122
123
const [showFailedOnly, setShowFailedOnly] = useLocalStorageState('nesShowFailedOnly', undefined, false);
124
const [showOnlyRanTests, setShowOnlyRanTests] = useLocalStorageState('nesShowOnlyRanTests', undefined, false);
125
const [showWithCacheMissesOnly, setShowWithCacheMissesOnly] = useState(false);
126
127
const filterParams = useMemo(() => ({
128
grep: runnerOptions.grep.value,
129
showFailedOnly,
130
showWithCacheMissesOnly,
131
showOnlyRanTests,
132
}), [
133
runnerOptions.grep.value,
134
showFailedOnly,
135
showWithCacheMissesOnly,
136
showOnlyRanTests,
137
]);
138
139
const createFilter = useCallback((params: FilterParams): TestFilterer => {
140
const predicates = [];
141
predicates.push(createGrepFilter(params.grep));
142
if (params.showFailedOnly) {
143
predicates.push(createFailuresFilter());
144
}
145
if (params.showWithCacheMissesOnly) {
146
predicates.push(createCacheMissesFilter());
147
}
148
if (params.showOnlyRanTests) {
149
predicates.push(createRanTestsFilter());
150
}
151
return createFilterer(predicates);
152
}, []);
153
154
const handleFilterChange = useCallback((filter: TestFilterer | undefined) => {
155
onFiltererChange(filter);
156
}, [onFiltererChange]);
157
158
const { isFiltering } = useAsyncFilter(filterParams, 500, createFilter, handleFilterChange);
159
160
const isSimulationRunning = runner.state.kind === StateKind.Running;
161
162
const handleRunStopButtonClick = useCallback(() => {
163
mobx.runInAction(() => {
164
if (isSimulationRunning) {
165
runner.stopRunning();
166
} else {
167
runner.startRunning({
168
grep: runnerOptions.grep.value,
169
cacheMode: runnerOptions.cacheMode.value,
170
n: parseInt(runnerOptions.n.value),
171
noFetch: runnerOptions.noFetch.value,
172
additionalArgs: runnerOptions.additionalArgs.value,
173
nesExternalScenariosPath: nesExternalOptions.externalScenariosPath.value,
174
});
175
}
176
});
177
}, [isSimulationRunning, runner, runnerOptions, nesExternalOptions]);
178
179
const handleScenariosPathChange = useCallback((e: React.FormEvent<HTMLInputElement>) => {
180
mobx.runInAction(() => {
181
nesExternalOptions.externalScenariosPath.value = (e.target as HTMLInputElement).value;
182
});
183
}, [nesExternalOptions]);
184
185
const handleGrepChange = useCallback((e: React.FormEvent<HTMLInputElement>) => {
186
mobx.runInAction(() => {
187
runnerOptions.grep.value = (e.target as HTMLInputElement).value;
188
});
189
}, [runnerOptions.grep]);
190
191
const handleNRunsChange = useCallback((e: React.FormEvent<HTMLInputElement>) => {
192
runnerOptions.n.value = (e.target as HTMLInputElement).value;
193
}, [runnerOptions.n]);
194
195
const handleNoFetchChange = useCallback(() => {
196
mobx.runInAction(() => {
197
runnerOptions.noFetch.value = !runnerOptions.noFetch.value;
198
});
199
}, [runnerOptions.noFetch]);
200
201
const handleExtraArgsChange = useCallback((e: React.FormEvent<HTMLInputElement>) => {
202
mobx.runInAction(() => {
203
runnerOptions.additionalArgs.value = (e.target as HTMLInputElement).value;
204
});
205
}, [runnerOptions.additionalArgs]);
206
207
const handleRunFromDiskChange = useCallback((selected: string | undefined) => {
208
const name = selected ?? '';
209
runner.setSelectedRunFromDisk(name);
210
}, [runner]);
211
212
const handleCacheModeChange = useCallback((_e: React.FormEvent<HTMLSelectElement>, option: SelectOptionEvent) => {
213
runnerOptions.cacheMode.value = option.value as CacheMode;
214
}, [runnerOptions.cacheMode]);
215
216
const hasValidScenariosPath = nesExternalOptions.externalScenariosPath.value.length > 0;
217
218
return (
219
<div
220
className='toolbar'
221
style={{
222
display: 'flex',
223
flexDirection: 'column',
224
justifyContent: 'space-between',
225
}}
226
>
227
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
228
<div style={{ display: 'flex', alignItems: 'center' }}>
229
<CurrentRunPicker
230
simulationRunsProvider={simulationRunsProvider}
231
disabled={isSimulationRunning}
232
onChange={handleRunFromDiskChange}
233
outputFolderName={runner.selectedRun}
234
/>
235
<CompareAgainstRunPicker
236
simulationRunsProvider={simulationRunsProvider}
237
/>
238
</div>
239
</div>
240
<div style={{ height: '8px' }} />
241
<div>
242
<div>NES External Scenarios</div>
243
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
244
<Input
245
id='nesExternalScenariosPath'
246
size='small'
247
placeholder='Path to external scenarios, e.g., ../eval/simulation/nes'
248
title='Path to directory containing NES external scenario recordings'
249
value={nesExternalOptions.externalScenariosPath.value}
250
onChange={handleScenariosPathChange}
251
style={{ width: '400px', maxWidth: '30vw' }}
252
/>
253
</div>
254
</div>
255
<div style={{ height: '8px' }} />
256
<div>
257
<div>Configure new run</div>
258
<div style={{ display: 'flex', gap: '10px' }}>
259
<Input
260
id='grep'
261
size='small'
262
placeholder='grep'
263
title='Filter by test name'
264
value={runnerOptions.grep.value}
265
onChange={handleGrepChange}
266
style={{ width: '300px', maxWidth: '25vw' }}
267
/>
268
<Input
269
id='nRuns'
270
size='small'
271
style={{ width: '40px' }}
272
placeholder='N'
273
title='Specify number of runs per each test'
274
value={runnerOptions.n.value}
275
onChange={handleNRunsChange}
276
/>
277
<Select onChange={handleCacheModeChange} defaultValue={runnerOptions.cacheMode.value}>
278
<option value={CacheMode.Default} title='Use cache if available'>Use Cache</option>
279
<option value={CacheMode.Disable} title='Do not use cache'>No Cache</option>
280
<option value={CacheMode.Require} title='Use cache, fail if not available'>Require Cache</option>
281
</Select>
282
<Tooltip relationship='label' content={'Do not send requests to the model endpoint (uses cache but doesn\'t write to it)'}>
283
<Checkbox
284
label='No fetch'
285
defaultChecked={runnerOptions.noFetch.value}
286
onChange={handleNoFetchChange}
287
/>
288
</Tooltip>
289
<Input
290
id='extraArgs'
291
size='small'
292
style={{ width: '300px' }}
293
placeholder='Extra args, e.g., --parallelism=10 --require-cache'
294
value={runnerOptions.additionalArgs.value}
295
onInput={handleExtraArgsChange}
296
/>
297
<Tooltip relationship='label' content={!hasValidScenariosPath ? 'Set the external scenarios path first' : ''}>
298
<Button
299
size='small' appearance={isSimulationRunning ? 'secondary' : 'primary'}
300
icon={isSimulationRunning ? <Stop16Regular /> : <Play16Regular />}
301
iconPosition='before'
302
onClick={handleRunStopButtonClick}
303
disabled={!hasValidScenariosPath && !isSimulationRunning}
304
style={{ width: '69px' }}
305
>
306
{isSimulationRunning ? 'Stop' : 'Run'}
307
</Button>
308
</Tooltip>
309
</div>
310
</div>
311
<div style={{ height: '8px' }} />
312
<div>
313
<Tooltip content={'Only modify tests displayed. Tests will be run even if not in list below'} relationship={'label'}>
314
<Text>View Filters {isFiltering && '(Filtering...)'}</Text>
315
</Tooltip>
316
<div style={{ display: 'flex', alignItems: 'center' }}>
317
<Checkbox
318
className='showFailedOnly'
319
label='Show with failures only'
320
checked={showFailedOnly}
321
onChange={() => setShowFailedOnly(!showFailedOnly)}
322
/>
323
<Checkbox
324
label='Show with cache misses only'
325
checked={showWithCacheMissesOnly}
326
onChange={() => setShowWithCacheMissesOnly(!showWithCacheMissesOnly)}
327
/>
328
<Checkbox
329
label='Show ran tests only'
330
checked={showOnlyRanTests}
331
onChange={() => setShowOnlyRanTests(!showOnlyRanTests)}
332
/>
333
</div>
334
</div>
335
</div>
336
);
337
}
338
);
339
340