-
Notifications
You must be signed in to change notification settings - Fork 70
feat: Add search-jobs CLI commands for managing search jobs #1145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
ee7a504
feat: Add search-jobs CLI commands for managing search jobs
trly 4f57cbc
feat: implement search-jobs logs command
trly 45339c2
feat: Add search jobs results retrieval command
trly bcf88c4
cleanup trailing whitespace
trly f46b05a
remove tests as they are not functionally testing production code
trly dae3f65
Fix linter issues. Add support for job id numbers (#1151)
peterguy bdbe4b8
feat: Add search jobs restart command
trly 50165f4
(lint) removed unused testutil
trly 38248af
refactor: Redesign search-jobs command structure with builder pattern
trly 6c66488
refactor: make search-jobs command builder methods private
trly 049e554
(fix) do not display command success output when using -get-curl
trly b87df9b
(fix) clean up inconsistencies in usage text
trly File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,297 @@ | ||
| 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 api.NewClient(api.ClientOpts{ | ||
| Endpoint: cfg.Endpoint, | ||
| AccessToken: cfg.AccessToken, | ||
| Out: out.Output(), | ||
| Flags: apiFlags, | ||
| }) | ||
| } | ||
|
|
||
| // 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 interface{}) 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) | ||
| }, | ||
| }) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "context" | ||
| "flag" | ||
| "fmt" | ||
|
|
||
| "github.com/sourcegraph/src-cli/internal/api" | ||
| ) | ||
|
|
||
| // GraphQL mutation constants | ||
| const cancelSearchJobMutation = `mutation CancelSearchJob($id: ID!) { | ||
| cancelSearchJob(id: $id) { | ||
| alwaysNil | ||
| } | ||
| }` | ||
|
|
||
| // cancelSearchJob cancels a search job with the given ID | ||
| func cancelSearchJob(client api.Client, jobID string) error { | ||
| var result struct { | ||
| CancelSearchJob struct { | ||
| AlwaysNil bool | ||
| } | ||
| } | ||
|
|
||
| if ok, err := client.NewRequest(cancelSearchJobMutation, map[string]interface{}{ | ||
| "id": jobID, | ||
| }).Do(context.Background(), &result); err != nil || !ok { | ||
| return err | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // displayCancelSuccessMessage outputs a success message for the canceled job | ||
| func displayCancelSuccessMessage(out *flag.FlagSet, jobID string) { | ||
| fmt.Fprintf(out.Output(), "Search job %s canceled successfully\n", jobID) | ||
| } | ||
|
|
||
| // init registers the 'cancel' subcommand for search jobs, which allows users to cancel | ||
| // a running search job by its ID. It sets up the command's flag parsing, usage information, | ||
| // and handles the GraphQL mutation to cancel the specified search job. | ||
| func init() { | ||
| usage := ` | ||
| Examples: | ||
|
|
||
| Cancel a search job by ID: | ||
|
|
||
| $ src search-jobs cancel U2VhcmNoSm9iOjY5 | ||
|
|
||
| Arguments: | ||
| The ID of the search job to cancel. | ||
|
|
||
| The cancel command stops a running search job and outputs a confirmation message. | ||
| ` | ||
|
|
||
| cmd := newSearchJobCommand("cancel", usage) | ||
|
|
||
| cmd.build(func(flagSet *flag.FlagSet, apiFlags *api.Flags, columns []string, asJSON bool, client api.Client) error { | ||
|
|
||
| jobID, err := validateJobID(flagSet.Args()) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if err := cancelSearchJob(client, jobID); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if apiFlags.GetCurl() { | ||
| return nil | ||
| } | ||
|
|
||
| displayCancelSuccessMessage(flagSet, jobID) | ||
|
|
||
| return nil | ||
| }) | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.