Skip to content

Commit 2e8ffbd

Browse files
committed
Add ticket code search across client-side and server-side search
- Client-side: searchFilterCards now matches card ticket codes (e.g. typing "PROJ-42" in view header search finds the card) - Server-side: board search (both searchBoardsForUser and searchBoardsForUserInTeam) now includes card_prefix in OR conditions, so searching "PROJ" finds boards with that prefix - New BoardSearchFieldCardPrefix for dedicated prefix search - Updated search placeholder to hint at ticket code search https://claude.ai/code/session_017e9AHfd7FTjFDHSTaEZYvW
1 parent e25c452 commit 2e8ffbd

4 files changed

Lines changed: 32 additions & 13 deletions

File tree

server/model/board.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const (
3232
BoardSearchFieldNone BoardSearchField = ""
3333
BoardSearchFieldTitle BoardSearchField = "title"
3434
BoardSearchFieldPropertyName BoardSearchField = "property_name"
35+
BoardSearchFieldCardPrefix BoardSearchField = "card_prefix"
3536
)
3637

3738
// Board groups a set of blocks and its layout
@@ -467,6 +468,8 @@ func BoardSearchFieldFromString(field string) (BoardSearchField, error) {
467468
return BoardSearchFieldTitle, nil
468469
case string(BoardSearchFieldPropertyName):
469470
return BoardSearchFieldPropertyName, nil
471+
case string(BoardSearchFieldCardPrefix):
472+
return BoardSearchFieldCardPrefix, nil
470473
}
471474
return BoardSearchFieldNone, ErrInvalidBoardSearchField
472475
}

server/services/store/sqlstore/board.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,16 +1016,16 @@ func (s *SQLStore) searchBoardsForUserInTeam(db sq.BaseRunner, teamID, term, use
10161016
})
10171017

10181018
if term != "" {
1019-
// break search query into space separated words
1020-
// and search for all words.
1021-
// This should later be upgraded to industrial-strength
1022-
// word tokenizer, that uses much more than space
1023-
// to break words.
1024-
1019+
// Search both title and card_prefix (OR logic) so users can find
1020+
// boards by their ticket code prefix (e.g. searching "PROJ" finds
1021+
// the board with card_prefix=PROJ).
10251022
conditions := sq.And{}
1026-
10271023
for _, word := range strings.Split(strings.TrimSpace(term), " ") {
1028-
conditions = append(conditions, sq.Like{"lower(b.title)": "%" + strings.ToLower(word) + "%"})
1024+
lowerWord := "%" + strings.ToLower(word) + "%"
1025+
conditions = append(conditions, sq.Or{
1026+
sq.Like{"lower(b.title)": lowerWord},
1027+
sq.Like{"lower(b.card_prefix)": lowerWord},
1028+
})
10291029
}
10301030

10311031
openBoardsQ = openBoardsQ.Where(conditions)
@@ -1126,15 +1126,23 @@ func (s *SQLStore) searchBoardsForUser(db sq.BaseRunner, term string, searchFiel
11261126
boardMembersQ = boardMembersQ.Where(where, whereTerm)
11271127
teamMembersQ = teamMembersQ.Where(where, whereTerm)
11281128
channelMembersQ = channelMembersQ.Where(where, whereTerm)
1129+
} else if searchField == model.BoardSearchFieldCardPrefix {
1130+
conditions := sq.Like{"lower(b.card_prefix)": "%" + strings.ToLower(term) + "%"}
1131+
boardMembersQ = boardMembersQ.Where(conditions)
1132+
teamMembersQ = teamMembersQ.Where(conditions)
1133+
channelMembersQ = channelMembersQ.Where(conditions)
11291134
} else { // model.BoardSearchFieldTitle
11301135
// break search query into space separated words
11311136
// and search for all words.
1132-
// This should later be upgraded to industrial-strength
1133-
// word tokenizer, that uses much more than space
1134-
// to break words.
1137+
// Also match card_prefix so users can find boards
1138+
// by typing a ticket code prefix.
11351139
conditions := sq.And{}
11361140
for _, word := range strings.Split(strings.TrimSpace(term), " ") {
1137-
conditions = append(conditions, sq.Like{"lower(b.title)": "%" + strings.ToLower(word) + "%"})
1141+
lowerWord := "%" + strings.ToLower(word) + "%"
1142+
conditions = append(conditions, sq.Or{
1143+
sq.Like{"lower(b.title)": lowerWord},
1144+
sq.Like{"lower(b.card_prefix)": lowerWord},
1145+
})
11381146
}
11391147

11401148
boardMembersQ = boardMembersQ.Where(conditions)

webapp/src/components/viewHeader/viewHeaderSearch.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const ViewHeaderSearch = (): JSX.Element => {
6262
<Editable
6363
ref={searchFieldRef}
6464
value={searchValue}
65-
placeholderText={intl.formatMessage({id: 'ViewHeader.search-text', defaultMessage: 'Search cards'})}
65+
placeholderText={intl.formatMessage({id: 'ViewHeader.search-text', defaultMessage: 'Search cards or ticket codes'})}
6666
onChange={(value) => {
6767
setSearchValue(value)
6868
debouncedDispatchSearchText(value)

webapp/src/store/cards.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,14 @@ function searchFilterCards(cards: Card[], board: Board, searchTextRaw: string):
343343
return true
344344
}
345345

346+
// Match against ticket code (e.g. "PROJ-42")
347+
if (board.cardPrefix && card.fields.cardNumber) {
348+
const ticketCode = `${board.cardPrefix}-${card.fields.cardNumber}`.toLowerCase()
349+
if (ticketCode.includes(searchText)) {
350+
return true
351+
}
352+
}
353+
346354
for (const [propertyId, propertyValue] of Object.entries(card.fields.properties)) {
347355
// TODO: Refactor to a shared function that returns the display value of a property
348356
const propertyTemplate = board.cardProperties.find((o) => o.id === propertyId)

0 commit comments

Comments
 (0)