-
Notifications
You must be signed in to change notification settings - Fork 68
Expand file tree
/
Copy pathsearch_jobs.go
More file actions
292 lines (252 loc) · 7.6 KB
/
search_jobs.go
File metadata and controls
292 lines (252 loc) · 7.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
package main
import (
"encoding/json"
"flag"
"fmt"
"strings"
"github.com/sourcegraph/src-cli/internal/api"
"github.com/sourcegraph/src-cli/internal/cmderrors"
)
// searchJobFragment is a GraphQL fragment that defines the fields to be queried
// for a SearchJob. It includes the job's ID, query, state, creator information,
// timestamps, URLs, and repository statistics.
const searchJobFragment = `
fragment SearchJobFields on SearchJob {
id
query
state
creator {
username
}
createdAt
startedAt
finishedAt
URL
logURL
repoStats {
total
completed
failed
inProgress
}
}`
// SearchJob represents a search job with its metadata, including the search query,
// execution state, creator information, timestamps, URLs, and repository statistics.
type SearchJob struct {
ID string
Query string
State string
Creator struct {
Username string
}
CreatedAt string
StartedAt string
FinishedAt string
URL string
LogURL string
RepoStats struct {
Total int
Completed int
Failed int
InProgress int
}
}
// availableColumns defines the available column names for output
var availableColumns = map[string]bool{
"id": true,
"query": true,
"state": true,
"username": true,
"createdat": true,
"startedat": true,
"finishedat": true,
"url": true,
"logurl": true,
"total": true,
"completed": true,
"failed": true,
"inprogress": true,
}
// defaultColumns defines the default columns to display
var defaultColumns = []string{"id", "username", "state", "query"}
// SearchJobCommandBuilder helps build search job commands with common flags and options
type SearchJobCommandBuilder struct {
Name string
Usage string
Flags *flag.FlagSet
ApiFlags *api.Flags
}
// Global variables
var searchJobsCommands commander
// newSearchJobCommand creates a new search job command builder
func newSearchJobCommand(name string, usage string) *SearchJobCommandBuilder {
flagSet := flag.NewFlagSet(name, flag.ExitOnError)
return &SearchJobCommandBuilder{
Name: name,
Usage: usage,
Flags: flagSet,
ApiFlags: api.NewFlags(flagSet),
}
}
// build creates and registers the command
func (b *SearchJobCommandBuilder) build(handlerFunc func(*flag.FlagSet, *api.Flags, []string, bool, api.Client) error) {
columnsFlag := b.Flags.String("c", strings.Join(defaultColumns, ","),
"Comma-separated list of columns to display. Available: id,query,state,username,createdat,startedat,finishedat,url,logurl,total,completed,failed,inprogress")
jsonFlag := b.Flags.Bool("json", false, "Output results as JSON for programmatic access")
usageFunc := func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage of 'src search-jobs %s':\n", b.Name)
b.Flags.PrintDefaults()
fmt.Println(b.Usage)
}
handler := func(args []string) error {
if err := parseSearchJobsArgs(b.Flags, args); err != nil {
return err
}
// Parse columns
columns := parseColumns(*columnsFlag)
client := createSearchJobsClient(b.Flags, b.ApiFlags)
return handlerFunc(b.Flags, b.ApiFlags, columns, *jsonFlag, client)
}
searchJobsCommands = append(searchJobsCommands, &command{
flagSet: b.Flags,
handler: handler,
usageFunc: usageFunc,
})
}
// parseColumns parses and validates the columns flag
func parseColumns(columnsFlag string) []string {
if columnsFlag == "" {
return defaultColumns
}
columns := strings.Split(columnsFlag, ",")
var validColumns []string
for _, col := range columns {
col = strings.ToLower(strings.TrimSpace(col))
if availableColumns[col] {
validColumns = append(validColumns, col)
}
}
if len(validColumns) == 0 {
return defaultColumns
}
return validColumns
}
// createSearchJobsClient creates a reusable API client for search jobs commands
func createSearchJobsClient(out *flag.FlagSet, apiFlags *api.Flags) api.Client {
return cfg.apiClient(apiFlags, out.Output())
}
// parseSearchJobsArgs parses command arguments with the provided flag set
// and returns an error if parsing fails
func parseSearchJobsArgs(flagSet *flag.FlagSet, args []string) error {
if err := flagSet.Parse(args); err != nil {
return err
}
return nil
}
// validateJobID validates that a job ID was provided
func validateJobID(args []string) (string, error) {
if len(args) != 1 {
return "", cmderrors.Usage("must provide a search job ID")
}
return args[0], nil
}
// displaySearchJob formats and outputs a search job based on selected columns or JSON
func displaySearchJob(job *SearchJob, columns []string, asJSON bool) error {
if asJSON {
return outputAsJSON(job)
}
return outputAsColumns(job, columns)
}
// displaySearchJobs formats and outputs multiple search jobs
func displaySearchJobs(jobs []SearchJob, columns []string, asJSON bool) error {
if asJSON {
return outputAsJSON(jobs)
}
for _, job := range jobs {
if err := outputAsColumns(&job, columns); err != nil {
return err
}
}
return nil
}
// outputAsJSON outputs data as JSON
func outputAsJSON(data any) error {
jsonBytes, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
fmt.Println(string(jsonBytes))
return nil
}
// outputAsColumns outputs a search job as tab-delimited columns
func outputAsColumns(job *SearchJob, columns []string) error {
values := make([]string, 0, len(columns))
for _, col := range columns {
switch col {
case "id":
values = append(values, job.ID)
case "query":
values = append(values, job.Query)
case "state":
values = append(values, job.State)
case "username":
values = append(values, job.Creator.Username)
case "createdat":
values = append(values, job.CreatedAt)
case "startedat":
values = append(values, job.StartedAt)
case "finishedat":
values = append(values, job.FinishedAt)
case "url":
values = append(values, job.URL)
case "logurl":
values = append(values, job.LogURL)
case "total":
values = append(values, fmt.Sprintf("%d", job.RepoStats.Total))
case "completed":
values = append(values, fmt.Sprintf("%d", job.RepoStats.Completed))
case "failed":
values = append(values, fmt.Sprintf("%d", job.RepoStats.Failed))
case "inprogress":
values = append(values, fmt.Sprintf("%d", job.RepoStats.InProgress))
}
}
fmt.Println(strings.Join(values, "\t"))
return nil
}
// init registers the 'src search-jobs' command with the CLI. It provides subcommands
// for managing search jobs, including creating, listing, getting, canceling and deleting
// jobs. The command uses a flagset for parsing options and displays usage information
// when help is requested.
func init() {
usage := `'src search-jobs' is a tool that manages search jobs on a Sourcegraph instance.
Usage:
src search-jobs command [command options]
The commands are:
cancel cancels a search job by ID
create creates a search job
delete deletes a search job by ID
get gets a search job by ID
list lists search jobs
logs fetches logs for a search job by ID
restart restarts a search job by ID
results fetches results for a search job by ID
Common options for all commands:
-c Select columns to display (e.g., -c id,query,state,username)
-json Output results in JSON format
Use "src search-jobs [command] -h" for more information about a command.
`
flagSet := flag.NewFlagSet("search-jobs", flag.ExitOnError)
handler := func(args []string) error {
searchJobsCommands.run(flagSet, "src search-jobs", usage, args)
return nil
}
commands = append(commands, &command{
flagSet: flagSet,
aliases: []string{"search-job"},
handler: handler,
usageFunc: func() {
fmt.Println(usage)
},
})
}