Skip to content

Commit d04bc70

Browse files
committed
feat: jql app mode work with dynamically typed jql queries
1 parent 4739006 commit d04bc70

File tree

6 files changed

+207
-483
lines changed

6 files changed

+207
-483
lines changed

demo_custom_jql.png

235 Bytes
Loading

internal/app/fuzzy_find.go

+66-46
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,24 @@ import (
1212
)
1313

1414
type FuzzyFind struct {
15-
MarginTop int
16-
MarginBottom int
17-
Complete chan FuzzyFindResult
18-
records []string
19-
recordsProvider func(query string) []string
20-
query string
21-
fuzzyStatus string
22-
title string
23-
matches fuzzy.Matches
24-
matchesAll fuzzy.Matches
25-
buffer bytes.Buffer
26-
dirty bool
27-
selected int
28-
screenX int
29-
screenY int
30-
supplierDebounce func(f func())
31-
debounceDisabled bool
15+
MarginTop int
16+
MarginBottom int
17+
Complete chan FuzzyFindResult
18+
records []string
19+
recordsProvider func(query string) []string
20+
query string
21+
fuzzyStatus string
22+
title string
23+
matches fuzzy.Matches
24+
matchesAll fuzzy.Matches
25+
buffer bytes.Buffer
26+
dirty bool
27+
selected int
28+
screenX int
29+
screenY int
30+
supplierDebounce func(f func())
31+
debounceDisabled bool
32+
disableFuzzyMatch bool
3233
}
3334

3435
type FuzzyFindResult struct {
@@ -40,7 +41,7 @@ const (
4041
ResultsMarginBottom = 3
4142
WriteIndicator = "> "
4243
MaxResults = 4096
43-
DynamicSupplierDebounce = 50 * time.Millisecond
44+
DefaultSupplierDebounce = 50 * time.Millisecond
4445
SearchResultsPivot = 6
4546
)
4647

@@ -63,38 +64,40 @@ func NewFuzzyFind(title string, records []string) *FuzzyFind {
6364
})
6465
}
6566
return &FuzzyFind{
66-
Complete: make(chan FuzzyFindResult),
67-
records: records,
68-
query: EmptyLine,
69-
fuzzyStatus: "0/0",
70-
matches: nil,
71-
selected: 0,
72-
dirty: true,
73-
matchesAll: matchesAll,
74-
recordsProvider: nil,
75-
title: title,
76-
MarginTop: 0,
77-
MarginBottom: 1,
78-
debounceDisabled: false,
67+
Complete: make(chan FuzzyFindResult),
68+
records: records,
69+
query: EmptyLine,
70+
fuzzyStatus: "0/0",
71+
matches: nil,
72+
selected: 0,
73+
dirty: true,
74+
matchesAll: matchesAll,
75+
recordsProvider: nil,
76+
title: title,
77+
MarginTop: 0,
78+
MarginBottom: 1,
79+
debounceDisabled: false,
80+
disableFuzzyMatch: false,
7981
}
8082
}
8183

8284
func NewFuzzyFindWithProvider(title string, recordsProvider func(query string) []string) *FuzzyFind {
8385
return &FuzzyFind{
84-
Complete: make(chan FuzzyFindResult),
85-
records: nil,
86-
query: "init",
87-
fuzzyStatus: "0/0",
88-
matches: nil,
89-
selected: 0,
90-
dirty: true,
91-
matchesAll: make(fuzzy.Matches, 0, MaxResults),
92-
recordsProvider: recordsProvider,
93-
supplierDebounce: debounce.New(DynamicSupplierDebounce),
94-
title: title,
95-
MarginTop: 0,
96-
MarginBottom: 1,
97-
debounceDisabled: false,
86+
Complete: make(chan FuzzyFindResult),
87+
records: nil,
88+
query: "init",
89+
fuzzyStatus: "0/0",
90+
matches: nil,
91+
selected: 0,
92+
dirty: true,
93+
matchesAll: make(fuzzy.Matches, 0, MaxResults),
94+
recordsProvider: recordsProvider,
95+
supplierDebounce: debounce.New(DefaultSupplierDebounce),
96+
title: title,
97+
MarginTop: 0,
98+
MarginBottom: 1,
99+
debounceDisabled: false,
100+
disableFuzzyMatch: false,
98101
}
99102
}
100103

@@ -130,7 +133,7 @@ func (f *FuzzyFind) Update() {
130133
}
131134
}
132135
f.query = buff
133-
if len(f.query) == 0 {
136+
if len(f.query) == 0 || f.disableFuzzyMatch {
134137
f.matches = f.matchesAll
135138
} else {
136139
f.matches = fuzzy.Find(f.query, f.records)
@@ -189,6 +192,19 @@ func (f *FuzzyFind) GetQuery() string {
189192
return f.query
190193
}
191194

195+
func (f *FuzzyFind) SetQuery(q string) {
196+
f.buffer.WriteString(q)
197+
f.dirty = true
198+
}
199+
200+
func (f *FuzzyFind) AlwaysShowAllResults() {
201+
// it's a bit weird feature ... to disable fuzzy match in fuzzy finder
202+
// maybe part of the logic should be extracted from that fuzzy finder
203+
// and then simple "records table" could be displayed without fuzzy-find functionality, and
204+
// without such a weird stuff
205+
f.disableFuzzyMatch = true
206+
}
207+
192208
func (f *FuzzyFind) GetSelectedItem() string {
193209
if len(f.records) == 0 {
194210
return ""
@@ -200,6 +216,10 @@ func (f *FuzzyFind) SetDebounceDisabled(b bool) {
200216
f.debounceDisabled = b
201217
}
202218

219+
func (f *FuzzyFind) SetDebounceMs(d time.Duration) {
220+
f.supplierDebounce = debounce.New(d)
221+
}
222+
203223
func (f *FuzzyFind) drawRecords(screen tcell.Screen) {
204224
matchesLen := f.matches.Len()
205225
if matchesLen == 0 {

internal/issues/jql_search.go

+34-96
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,49 @@
11
package issues
22

33
import (
4-
"fmt"
54
"github.com/gdamore/tcell/v2"
65
"github.com/mk-5/fjira/internal/app"
76
"github.com/mk-5/fjira/internal/jira"
87
"github.com/mk-5/fjira/internal/ui"
98
"strings"
9+
"time"
1010
)
1111

1212
const (
13-
MaxJqlLines = 1000
1413
DefaultJqlQuery = "created >= -30d order by created DESC"
15-
MaxJqlLength = 2000
14+
BadRequest = "Bad Request"
1615
)
1716

1817
type jqlSearchView struct {
1918
app.View
20-
api jira.Api
21-
bottomBar *app.ActionBar
22-
fuzzyFind *app.FuzzyFind
23-
jqlStorage *jqlStorage
19+
api jira.Api
20+
fuzzyFind *app.FuzzyFind
21+
issues []jira.Issue
22+
jql string
2423
}
2524

2625
func NewJqlSearchView(api jira.Api) app.View {
27-
bottomBar := ui.CreateBottomActionBarWithItems([]ui.NavItemConfig{
28-
ui.NavItemConfig{Action: ui.ActionNew, Text1: ui.MessageNew, Text2: "[F1]", Key: tcell.KeyF1},
29-
})
30-
bottomBar.AddItem(ui.NewDeleteItem())
31-
bottomBar.AddItem(ui.NewCancelBarItem())
3226
return &jqlSearchView{
33-
api: api,
34-
bottomBar: bottomBar,
35-
jqlStorage: &jqlStorage{},
27+
api: api,
28+
jql: DefaultJqlQuery,
3629
}
3730
}
3831

3932
func (view *jqlSearchView) Init() {
4033
go view.startJqlFuzzyFind()
41-
go view.handleBottomBarActions()
4234
}
4335

4436
func (view *jqlSearchView) Destroy() {
45-
view.bottomBar.Destroy()
37+
// do nothing
4638
}
4739

4840
func (view *jqlSearchView) Draw(screen tcell.Screen) {
4941
if view.fuzzyFind != nil {
5042
view.fuzzyFind.Draw(screen)
5143
}
52-
view.bottomBar.Draw(screen)
5344
}
5445

5546
func (view *jqlSearchView) Update() {
56-
view.bottomBar.Update()
5747
if view.fuzzyFind != nil {
5848
view.fuzzyFind.Update()
5949
}
@@ -63,11 +53,9 @@ func (view *jqlSearchView) Resize(screenX, screenY int) {
6353
if view.fuzzyFind != nil {
6454
view.fuzzyFind.Resize(screenX, screenY)
6555
}
66-
view.bottomBar.Resize(screenX, screenY)
6756
}
6857

6958
func (view *jqlSearchView) HandleKeyEvent(ev *tcell.EventKey) {
70-
view.bottomBar.HandleKeyEvent(ev)
7159
if view.fuzzyFind != nil {
7260
view.fuzzyFind.HandleKeyEvent(ev)
7361
}
@@ -76,94 +64,44 @@ func (view *jqlSearchView) HandleKeyEvent(ev *tcell.EventKey) {
7664
func (view *jqlSearchView) startJqlFuzzyFind() {
7765
app.GetApp().ClearNow()
7866
app.GetApp().Loading(true)
79-
jqls, err := view.jqlStorage.readAll()
80-
if err != nil {
81-
app.Error(err.Error())
82-
return
83-
}
84-
jqls = append(jqls, DefaultJqlQuery)
85-
view.fuzzyFind = app.NewFuzzyFind(ui.MessageJqlFuzzyFind, jqls)
86-
view.fuzzyFind.MarginBottom = 1
67+
view.fuzzyFind = app.NewFuzzyFindWithProvider(ui.MessageJqlFuzzyFind, view.findIssues)
68+
view.fuzzyFind.MarginBottom = 0
69+
view.fuzzyFind.SetQuery(DefaultJqlQuery)
70+
view.fuzzyFind.AlwaysShowAllResults()
71+
// higher debounce in order to give more time to change jql
72+
view.fuzzyFind.SetDebounceMs(500 * time.Millisecond)
8773
app.GetApp().Loading(false)
88-
if jql := <-view.fuzzyFind.Complete; true {
74+
if chosen := <-view.fuzzyFind.Complete; true {
8975
app.GetApp().ClearNow()
9076
query := view.fuzzyFind.GetQuery()
91-
if jql.Index < 0 && strings.TrimSpace(query) == "" {
77+
if chosen.Index < 0 && strings.TrimSpace(query) == "" {
9278
// do nothing
9379
return
9480
}
95-
if jql.Index < 0 {
96-
// do nothing, restart view
97-
app.GetApp().SetView(NewJqlSearchView(view.api))
81+
if chosen.Index >= 0 {
82+
chosenIssue := view.issues[chosen.Index]
83+
app.GoTo("issue", chosenIssue.Key, view.reopen, view.api)
9884
return
9985
}
100-
view.fuzzyFind = nil
101-
app.GoTo("issues-search-jql", jqls[jql.Index], view.api)
102-
//GoIntoIssuesSearchForJql(jqls[jql.Index], view.api)
10386
}
10487
}
10588

106-
func (view *jqlSearchView) handleBottomBarActions() {
107-
for {
108-
action := <-view.bottomBar.Action
109-
switch action {
110-
case ui.ActionCancel:
111-
view.cancel()
112-
return
113-
case ui.ActionNew:
114-
view.newJql()
115-
return
116-
case ui.ActionDelete:
117-
go view.confirmJqlDelete(view.fuzzyFind.GetSelectedItem())
118-
}
119-
}
89+
func (view *jqlSearchView) reopen() {
90+
app.GoTo("jql", view.api)
12091
}
12192

122-
func (view *jqlSearchView) cancel() {
123-
if view.fuzzyFind != nil {
124-
app.GetApp().Quit()
125-
}
126-
if view.fuzzyFind == nil {
127-
app.GetApp().SetView(NewJqlSearchView(view.api))
93+
func (view *jqlSearchView) findIssues(query string) []string {
94+
app.GetApp().LoadingWithText(true, ui.MessageSearchIssuesLoading)
95+
issues, err := view.api.SearchJql(query)
96+
app.GetApp().Loading(false)
97+
if err != nil && strings.Contains(err.Error(), BadRequest) {
98+
// do nothing, invalid JQL query
99+
return FormatJiraIssues(view.issues)
128100
}
129-
}
130-
131-
func (view *jqlSearchView) newJql() {
132-
app.GetApp().SetView(ui.NewTextWriterView(&ui.TextWriterArgs{
133-
Header: ui.MessageTypeJqlAndSave,
134-
GoBack: func() {
135-
app.GetApp().SetView(NewJqlSearchView(view.api))
136-
},
137-
TextConsumer: func(s string) {
138-
err := view.jqlStorage.addNew(s)
139-
if err != nil {
140-
app.Error(err.Error())
141-
return
142-
}
143-
app.Success(ui.MessageJqlAddSuccess)
144-
},
145-
MaxLength: MaxJqlLength,
146-
}))
147-
}
148-
149-
func (view *jqlSearchView) confirmJqlDelete(jql string) {
150-
message := fmt.Sprintf(ui.MessageJqlRemoveConfirm, jql)
151-
view.fuzzyFind = nil
152-
app.GetApp().ClearNow()
153-
view.bottomBar.Clear()
154-
view.bottomBar.AddItem(ui.NewYesBarItem())
155-
view.bottomBar.AddItem(ui.NewCancelBarItem())
156-
createNewJql := app.Confirm(app.GetApp(), message)
157-
switch createNewJql {
158-
case true:
159-
err := view.jqlStorage.remove(jql)
160-
if err != nil {
161-
app.Error(fmt.Sprintf(ui.MessageCannotAddNewJql, err))
162-
return
163-
}
164-
app.Success(ui.MessageJqlRemoveSuccess)
165-
app.GetApp().SetView(NewJqlSearchView(view.api))
166-
case false:
167-
app.GetApp().SetView(NewJqlSearchView(view.api))
101+
if err != nil {
102+
app.Error(err.Error())
103+
return FormatJiraIssues(view.issues)
168104
}
105+
view.issues = issues
106+
return FormatJiraIssues(view.issues)
169107
}

0 commit comments

Comments
 (0)