Skip to content

Commit f43688f

Browse files
Adds filter button to graph search box (#3686)
* Adds filter button to graph search box * Remembers filter setting globally
1 parent 4fee8f4 commit f43688f

File tree

9 files changed

+77
-3
lines changed

9 files changed

+77
-3
lines changed

src/constants.search.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export const searchOperationHelpRegex =
4242

4343
export interface SearchQuery {
4444
query: string;
45+
filter?: boolean;
4546
matchAll?: boolean;
4647
matchCase?: boolean;
4748
matchRegex?: boolean;

src/constants.storage.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export type GlobalStorage = {
7171
'launchpad:groups:collapsed': StoredLaunchpadGroup[];
7272
'launchpad:indicator:hasLoaded': boolean;
7373
'launchpad:indicator:hasInteracted': string;
74+
'graph:searchMode': StoredGraphSearchMode;
7475
} & { [key in `confirm:ai:tos:${AIProviders}`]: boolean } & {
7576
[key in `provider:authentication:skip:${string}`]: boolean;
7677
} & { [key in `gk:${string}:checkin`]: Stored<StoredGKCheckInResponse> } & {
@@ -231,6 +232,8 @@ export interface StoredGraphFilters {
231232

232233
export type StoredGraphRefType = 'head' | 'remote' | 'tag';
233234

235+
export type StoredGraphSearchMode = 'normal' | 'filter';
236+
234237
export interface StoredGraphExcludedRef {
235238
id: string;
236239
type: StoredGraphRefType;

src/plus/webviews/graph/graphWebview.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ import type {
175175
UpdateColumnsParams,
176176
UpdateExcludeTypesParams,
177177
UpdateGraphConfigurationParams,
178+
UpdateGraphSearchModeParams,
178179
UpdateIncludedRefsParams,
179180
UpdateRefsVisibilityParams,
180181
UpdateSelectionParams,
@@ -212,6 +213,7 @@ import {
212213
UpdateColumnsCommand,
213214
UpdateExcludeTypesCommand,
214215
UpdateGraphConfigurationCommand,
216+
UpdateGraphSearchModeCommand,
215217
UpdateIncludedRefsCommand,
216218
UpdateRefsVisibilityCommand,
217219
UpdateSelectionCommand,
@@ -758,6 +760,9 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
758760
case UpdateGraphConfigurationCommand.is(e):
759761
this.updateGraphConfig(e.params);
760762
break;
763+
case UpdateGraphSearchModeCommand.is(e):
764+
this.updateGraphSearchMode(e.params);
765+
break;
761766
case UpdateExcludeTypesCommand.is(e):
762767
this.updateExcludedTypes(this._graph?.repoPath, e.params);
763768
break;
@@ -837,6 +842,10 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
837842
}
838843
}
839844

845+
updateGraphSearchMode(params: UpdateGraphSearchModeParams) {
846+
void this.container.storage.store('graph:searchMode', params.searchMode);
847+
}
848+
840849
private _showActiveSelectionDetailsDebounced:
841850
| Deferrable<GraphWebviewProvider['showActiveSelectionDetails']>
842851
| undefined = undefined;
@@ -2497,6 +2506,8 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
24972506
});
24982507
}
24992508

2509+
const defaultSearchMode = this.container.storage.get('graph:searchMode') ?? 'normal';
2510+
25002511
return {
25012512
...this.host.baseWebviewState,
25022513
windowFocused: this.isWindowFocused,
@@ -2543,6 +2554,7 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
25432554
includeOnlyRefs: refsVisibility.includeOnlyRefs,
25442555
nonce: this.host.cspNonce,
25452556
workingTreeStats: getSettledValue(workingStatsResult) ?? { added: 0, deleted: 0, modified: 0 },
2557+
defaultSearchMode: defaultSearchMode,
25462558
};
25472559
}
25482560

src/plus/webviews/graph/protocol.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export type GraphMissingRefsMetadata = Record</*id*/ string, /*missingType*/ Gra
6464
export type GraphPullRequestMetadata = PullRequestMetadata;
6565

6666
export type GraphRefMetadataTypes = 'upstream' | 'pullRequest' | 'issue';
67+
export type GraphSearchMode = 'normal' | 'filter';
6768

6869
export type GraphScrollMarkerTypes =
6970
| 'selection'
@@ -115,6 +116,7 @@ export interface State extends WebviewState {
115116
nonce?: string;
116117
workingTreeStats?: GraphWorkingTreeStats;
117118
searchResults?: DidSearchParams['results'];
119+
defaultSearchMode?: GraphSearchMode;
118120
excludeRefs?: GraphExcludeRefs;
119121
excludeTypes?: GraphExcludeTypes;
120122
includeOnlyRefs?: GraphIncludeOnlyRefs;
@@ -299,6 +301,11 @@ export const UpdateGraphConfigurationCommand = new IpcCommand<UpdateGraphConfigu
299301
'configuration/update',
300302
);
301303

304+
export interface UpdateGraphSearchModeParams {
305+
searchMode: GraphSearchMode;
306+
}
307+
export const UpdateGraphSearchModeCommand = new IpcCommand<UpdateGraphSearchModeParams>(scope, 'search/update/mode');
308+
302309
export interface UpdateIncludedRefsParams {
303310
branchesVisibility?: GraphBranchesVisibility;
304311
refs?: GraphIncludeOnlyRef[];

src/webviews/apps/plus/graph/GraphWrapper.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import type {
4141
GraphMissingRefsMetadata,
4242
GraphRefMetadataItem,
4343
GraphRepository,
44+
GraphSearchMode,
4445
GraphSearchResults,
4546
GraphSearchResultsError,
4647
InternalNotificationType,
@@ -83,7 +84,10 @@ import { GlFeatureBadge } from '../../shared/components/react/feature-badge';
8384
import { GlFeatureGate } from '../../shared/components/react/feature-gate';
8485
import { GlIssuePullRequest } from '../../shared/components/react/issue-pull-request';
8586
import { GlSearchBox } from '../../shared/components/search/react';
86-
import type { SearchNavigationEventDetail } from '../../shared/components/search/search-box';
87+
import type {
88+
SearchModeChangeEventDetail,
89+
SearchNavigationEventDetail,
90+
} from '../../shared/components/search/search-box';
8791
import type { DateTimeFormat } from '../../shared/date';
8892
import { formatDate, fromNow } from '../../shared/date';
8993
import { emitTelemetrySentEvent } from '../../shared/telemetry';
@@ -100,6 +104,7 @@ export interface GraphWrapperProps {
100104
onChangeColumns?: (colsSettings: GraphColumnsConfig) => void;
101105
onChangeExcludeTypes?: (key: keyof GraphExcludeTypes, value: boolean) => void;
102106
onChangeGraphConfiguration?: (changes: UpdateGraphConfigurationParams['changes']) => void;
107+
onChangeGraphSearchMode?: (searchMode: GraphSearchMode) => void;
103108
onChangeRefIncludes?: (branchesVisibility: GraphBranchesVisibility, refs?: GraphRefOptData[]) => void;
104109
onChangeRefsVisibility?: (refs: GraphExcludedRef[], visible: boolean) => void;
105110
onChangeSelection?: (rows: GraphRow[]) => void;
@@ -226,6 +231,7 @@ export function GraphWrapper({
226231
onChangeColumns,
227232
onChangeExcludeTypes,
228233
onChangeGraphConfiguration,
234+
onChangeGraphSearchMode,
229235
onChangeRefIncludes,
230236
onChangeRefsVisibility,
231237
onChangeSelection,
@@ -629,6 +635,11 @@ export function GraphWrapper({
629635
onSearchOpenInView?.(searchQuery);
630636
};
631637

638+
const handleSearchModeChange = (e: CustomEvent<SearchModeChangeEventDetail>) => {
639+
const { searchMode } = e.detail;
640+
onChangeGraphSearchMode?.(searchMode);
641+
};
642+
632643
const ensureSearchResultRow = async (id: string): Promise<string | undefined> => {
633644
if (onEnsureRowPromise == null) return id;
634645
if (ensuredIds.current.has(id)) return id;
@@ -1379,13 +1390,15 @@ export function GraphWrapper({
13791390
valid={Boolean(searchQuery?.query && searchQuery.query.length > 2)}
13801391
more={searchResults?.paging?.hasMore ?? false}
13811392
searching={searching}
1393+
filter={state.defaultSearchMode === 'filter'}
13821394
value={searchQuery?.query ?? ''}
13831395
errorMessage={searchResultsError?.error ?? ''}
13841396
resultsHidden={searchResultsHidden}
13851397
resultsLoaded={searchResults != null}
13861398
onChange={e => handleSearchInput(e)}
13871399
onNavigate={e => handleSearchNavigation(e)}
13881400
onOpenInView={() => handleSearchOpenInView()}
1401+
onSearchModeChange={e => handleSearchModeChange(e)}
13891402
/>
13901403
<span>
13911404
<span className="action-divider"></span>
@@ -1609,6 +1622,7 @@ export function GraphWrapper({
16091622
refMetadataById={refsMetadata}
16101623
rowsStats={rowsStats}
16111624
rowsStatsLoading={rowsStatsLoading}
1625+
searchMode={searchQuery?.filter ? 'filter' : 'normal'}
16121626
shaLength={graphConfig?.idLength}
16131627
shiftSelectMode="simple"
16141628
suppressNonRefRowTooltips

src/webviews/apps/plus/graph/graph.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
GraphExcludeTypes,
1414
GraphMissingRefsMetadata,
1515
GraphRefMetadataItem,
16+
GraphSearchMode,
1617
InternalNotificationType,
1718
State,
1819
UpdateGraphConfigurationParams,
@@ -49,6 +50,7 @@ import {
4950
UpdateColumnsCommand,
5051
UpdateExcludeTypesCommand,
5152
UpdateGraphConfigurationCommand,
53+
UpdateGraphSearchModeCommand,
5254
UpdateIncludedRefsCommand,
5355
UpdateRefsVisibilityCommand,
5456
UpdateSelectionCommand,
@@ -104,6 +106,7 @@ export class GraphApp extends App<State> {
104106
)}
105107
onChangeExcludeTypes={this.onExcludeTypesChanged.bind(this)}
106108
onChangeGraphConfiguration={this.onGraphConfigurationChanged.bind(this)}
109+
onChangeGraphSearchMode={this.onGraphSearchModeChanged.bind(this)}
107110
onChangeRefIncludes={this.onRefIncludesChanged.bind(this)}
108111
onChangeRefsVisibility={(refs: GraphExcludedRef[], visible: boolean) =>
109112
this.onRefsVisibilityChanged(refs, visible)
@@ -654,6 +657,10 @@ export class GraphApp extends App<State> {
654657
this.sendCommand(UpdateGraphConfigurationCommand, { changes: changes });
655658
}
656659

660+
private onGraphSearchModeChanged(searchMode: GraphSearchMode) {
661+
this.sendCommand(UpdateGraphSearchModeCommand, { searchMode: searchMode });
662+
}
663+
657664
private onSelectionChanged(rows: GraphRow[]) {
658665
const selection = rows.filter(r => r != null).map(r => ({ id: r.sha, type: r.type as GitGraphRowType }));
659666
this._telemetry.sendEvent({ name: 'graph/row/selected', data: { rows: selection.length } });

src/webviews/apps/shared/components/search/react.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ export const GlSearchBox = reactWrapper(GlSearchBoxWC, {
1010
onChange: 'gl-search-inputchange' as EventName<CustomEventType<'gl-search-inputchange'>>,
1111
onNavigate: 'gl-search-navigate' as EventName<CustomEventType<'gl-search-navigate'>>,
1212
onOpenInView: 'gl-search-openinview' as EventName<CustomEventType<'gl-search-openinview'>>,
13+
onSearchModeChange: 'gl-search-modechange' as EventName<CustomEventType<'gl-search-modechange'>>,
1314
},
1415
});

src/webviews/apps/shared/components/search/search-box.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import type { SearchQuery } from '../../../../../constants.search';
77
import { pluralize } from '../../../../../system/string';
88
import { DOM } from '../../dom';
99
import { GlElement } from '../element';
10-
import type { GlSearchInput, SearchNavigationEventDetail } from './search-input';
10+
import type { GlSearchInput, SearchModeChangeEventDetail, SearchNavigationEventDetail } from './search-input';
1111
import '../code-icon';
1212
import '../overlays/tooltip';
1313
import '../progress';
1414
import './search-input';
1515

16-
export { SearchNavigationEventDetail };
16+
export { SearchModeChangeEventDetail, SearchNavigationEventDetail };
1717

1818
declare global {
1919
interface HTMLElementTagNameMap {
@@ -131,6 +131,9 @@ export class GlSearchBox extends GlElement {
131131
@property({ type: Boolean })
132132
matchRegex = true;
133133

134+
@property({ type: Boolean })
135+
filter = false;
136+
134137
@property({ type: Number })
135138
total = 0;
136139

@@ -266,6 +269,7 @@ export class GlSearchBox extends GlElement {
266269
id="search-input"
267270
exportparts="search: search"
268271
.errorMessage="${this.errorMessage}"
272+
.filter=${this.filter}
269273
.label="${this.label}"
270274
.placeholder="${this.placeholder}"
271275
.matchAll="${this.matchAll}"

src/webviews/apps/shared/components/search/search-input.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ export interface SearchNavigationEventDetail {
1515
direction: 'first' | 'previous' | 'next' | 'last';
1616
}
1717

18+
export interface SearchModeChangeEventDetail {
19+
searchMode: 'normal' | 'filter';
20+
}
21+
1822
declare global {
1923
interface HTMLElementTagNameMap {
2024
'gl-search-input': GlSearchInput;
@@ -23,6 +27,7 @@ declare global {
2327
interface GlobalEventHandlersEventMap {
2428
'gl-search-inputchange': CustomEvent<SearchQuery>;
2529
'gl-search-navigate': CustomEvent<SearchNavigationEventDetail>;
30+
'gl-search-modechange': CustomEvent<SearchModeChangeEventDetail>;
2631
}
2732
}
2833

@@ -302,6 +307,7 @@ export class GlSearchInput extends GlElement {
302307
@property({ type: String }) label = 'Search';
303308
@property({ type: String }) placeholder = 'Search...';
304309
@property({ type: String }) value = '';
310+
@property({ type: Boolean }) filter = false;
305311
@property({ type: Boolean }) matchAll = false;
306312
@property({ type: Boolean }) matchCase = false;
307313
@property({ type: Boolean }) matchRegex = true;
@@ -380,6 +386,12 @@ export class GlSearchInput extends GlElement {
380386
this.debouncedOnSearchChanged();
381387
}
382388

389+
handleFilter(_e: Event) {
390+
this.filter = !this.filter;
391+
this.emit('gl-search-modechange', { searchMode: this.filter ? 'filter' : 'normal' });
392+
this.debouncedOnSearchChanged();
393+
}
394+
383395
handleKeyup(_e: KeyboardEvent) {
384396
this.updateHelpText();
385397
}
@@ -423,6 +435,7 @@ export class GlSearchInput extends GlElement {
423435
private onSearchChanged() {
424436
const search: SearchQuery = {
425437
query: this.value,
438+
filter: this.filter,
426439
matchAll: this.matchAll,
427440
matchCase: this.matchCase,
428441
matchRegex: this.matchRegex,
@@ -508,6 +521,18 @@ export class GlSearchInput extends GlElement {
508521
</menu-item>
509522
</div>
510523
</gl-popover>
524+
<gl-tooltip hoist placement="top" content="Filter Commits">
525+
<button
526+
class="action-button"
527+
type="button"
528+
role="checkbox"
529+
aria-label="Filter Commits"
530+
aria-checked="${this.filter}"
531+
@click="${this.handleFilter}"
532+
>
533+
<code-icon icon="list-filter"></code-icon>
534+
</button>
535+
</gl-tooltip>
511536
<div class="field">
512537
<input
513538
id="search"

0 commit comments

Comments
 (0)