-
Notifications
You must be signed in to change notification settings - Fork 3
CON-1896 jobs progress #24
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package job | ||
|
|
||
| // GetJobResponse defines get job response | ||
| type GetJobResponse struct { | ||
| TranslationJobUID string | ||
| JobName string | ||
| } | ||
|
|
||
| // FindFirstJobByName finds the first job by name from the list of jobs | ||
| func FindFirstJobByName(jobs []GetJobResponse, name string) (GetJobResponse, bool) { | ||
| for _, job := range jobs { | ||
| if job.JobName == name { | ||
| return job, true | ||
| } | ||
| } | ||
| return GetJobResponse{}, false | ||
| } | ||
|
|
||
| type getJobResponse struct { | ||
| Response struct { | ||
| Code string `json:"code"` | ||
| Data struct { | ||
| JobName string `json:"jobName"` | ||
| TranslationJobUID string `json:"translationJobUid"` | ||
| } `json:"data"` | ||
| } `json:"response"` | ||
| } | ||
| type getJobsResponse struct { | ||
| Response struct { | ||
| Code string `json:"code"` | ||
| Data struct { | ||
| Items []struct { | ||
| JobName string `json:"jobName"` | ||
| TranslationJobUID string `json:"translationJobUid"` | ||
| } `json:"items"` | ||
| } `json:"data"` | ||
| } `json:"response"` | ||
| } | ||
|
|
||
| func toGetJobResponse(r getJobResponse) GetJobResponse { | ||
| return GetJobResponse{ | ||
| TranslationJobUID: r.Response.Data.TranslationJobUID, | ||
| JobName: r.Response.Data.JobName, | ||
| } | ||
| } | ||
|
|
||
| func toGetJobsResponse(r getJobsResponse) []GetJobResponse { | ||
| res := make([]GetJobResponse, len(r.Response.Data.Items)) | ||
| for i, job := range r.Response.Data.Items { | ||
| res[i] = GetJobResponse{ | ||
| TranslationJobUID: job.TranslationJobUID, | ||
| JobName: job.JobName, | ||
| } | ||
| } | ||
| return res | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package job | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| ) | ||
|
|
||
| // GetJobProgressResponse defines get job progress response | ||
| type GetJobProgressResponse struct { | ||
| TranslationJobUID string | ||
| TotalWordCount uint32 | ||
| PercentComplete uint32 | ||
| Json []byte | ||
| } | ||
| type getJobProgressResponse struct { | ||
| Response struct { | ||
| Code string `json:"code"` | ||
| Data struct { | ||
| ContentProgressReport []struct { | ||
| Progress struct { | ||
| PercentComplete int `json:"percentComplete"` | ||
| TotalWordCount int `json:"totalWordCount"` | ||
| } `json:"progress"` | ||
| TargetLocaleDescription string `json:"targetLocaleDescription"` | ||
| TargetLocaleId string `json:"targetLocaleId"` | ||
| UnauthorizedProgressReport struct { | ||
| StringCount int `json:"stringCount"` | ||
| WordCount int `json:"wordCount"` | ||
| } `json:"unauthorizedProgressReport"` | ||
| WorkflowProgressReportList []struct { | ||
| WorkflowName string `json:"workflowName"` | ||
| WorkflowStepSummaryReportItemList []struct { | ||
| StringCount int `json:"stringCount"` | ||
| WordCount int `json:"wordCount"` | ||
| WorkflowStepName string `json:"workflowStepName"` | ||
| WorkflowStepType string `json:"workflowStepType"` | ||
| WorkflowStepUid string `json:"workflowStepUid"` | ||
| } `json:"workflowStepSummaryReportItemList"` | ||
| WorkflowUid string `json:"workflowUid"` | ||
| } `json:"workflowProgressReportList"` | ||
| } `json:"contentProgressReport"` | ||
| Progress struct { | ||
| PercentComplete int `json:"percentComplete"` | ||
| TotalWordCount int `json:"totalWordCount"` | ||
| } `json:"progress"` | ||
| SummaryReport []struct { | ||
| StringCount int `json:"stringCount"` | ||
| WordCount int `json:"wordCount"` | ||
| WorkflowStepName string `json:"workflowStepName"` | ||
| } `json:"summaryReport"` | ||
| } `json:"data"` | ||
| } `json:"response"` | ||
| } | ||
|
|
||
| func toGetJobProgressResponse(r getJobProgressResponse, translationJobUID string) (GetJobProgressResponse, error) { | ||
| data, err := json.Marshal(r.Response.Data) | ||
| if err != nil { | ||
| return GetJobProgressResponse{}, fmt.Errorf("failed to marshal job progress response: %w", err) | ||
| } | ||
| return GetJobProgressResponse{ | ||
| TranslationJobUID: translationJobUID, | ||
| TotalWordCount: uint32(r.Response.Data.Progress.TotalWordCount), | ||
| PercentComplete: uint32(r.Response.Data.Progress.PercentComplete), | ||
| Json: data, | ||
| }, nil | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,15 +4,18 @@ import ( | |
| "encoding/json" | ||
| "fmt" | ||
| "io" | ||
| "net/url" | ||
|
|
||
| smclient "github.com/Smartling/api-sdk-go/helpers/sm_client" | ||
| ) | ||
|
|
||
| const jobBasePath = "/job-batches-api/v2/projects/" | ||
| const jobBasePath = "/jobs-api/v3/projects/" | ||
|
|
||
| // Job defines the job behaviour | ||
| type Job interface { | ||
| GetJob(projectID string, translationJobUID string) (GetJobResponse, error) | ||
| Get(projectID string, translationJobUID string) (GetJobResponse, error) | ||
| GetAllByName(projectID, name string) (jobs []GetJobResponse, err error) | ||
| Progress(projectID string, translationJobUID string) (GetJobProgressResponse, error) | ||
| } | ||
|
|
||
| // NewJob returns new Job implementation | ||
|
|
@@ -29,27 +32,89 @@ func newHttpJob(client *smclient.Client) httpJob { | |
| return httpJob{client: client} | ||
| } | ||
|
|
||
| // GetJob gets a job related info | ||
| func (h httpJob) GetJob(projectID string, translationJobUID string) (GetJobResponse, error) { | ||
| // Get gets a job related info | ||
| func (h httpJob) Get(projectID string, translationJobUID string) (GetJobResponse, error) { | ||
| url := jobBasePath + projectID + "/jobs/" + translationJobUID | ||
| var response getJobResponse | ||
| rawMessage, code, err := h.client.Get(url, nil) | ||
| if err != nil { | ||
| return GetJobResponse{}, err | ||
| } | ||
| defer func() { | ||
| if err := rawMessage.Close(); err != nil { | ||
| h.client.Logger.Debugf("failed to close response body: %v", err) | ||
| } | ||
| }() | ||
| body, err := io.ReadAll(rawMessage) | ||
| if err != nil { | ||
| return GetJobResponse{}, fmt.Errorf("failed to read response body: %w", err) | ||
| } | ||
| if code != 200 { | ||
| body, _ := io.ReadAll(rawMessage) | ||
| h.client.Logger.Debugf("response body: %s\n", body) | ||
| return GetJobResponse{}, fmt.Errorf("unexpected response code: %d", code) | ||
| } | ||
| if err := json.Unmarshal(body, &response); err != nil { | ||
| return GetJobResponse{}, fmt.Errorf("failed to unmarshal response: %w", err) | ||
| } | ||
| return toGetJobResponse(response), nil | ||
| } | ||
|
|
||
| // GetAllByName gets all jobs of a project by name | ||
| func (h httpJob) GetAllByName(projectID, name string) ([]GetJobResponse, error) { | ||
| reqURL := jobBasePath + projectID + "/jobs" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Security training reminded me of our bad habits. It would be ideal if you could find a special library that can generate a valid URL path rather than concatenating strings. Let's do it once, and it will make future development easier because we can easily copy and paste best practices. |
||
|
|
||
| params := url.Values{} | ||
| params.Set("jobName", name) | ||
|
|
||
| rawMessage, code, err := h.client.Get(reqURL, params) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer func() { | ||
| if err := rawMessage.Close(); err != nil { | ||
| h.client.Logger.Debugf("failed to close response body: %v", err) | ||
| } | ||
| }() | ||
| body, err := io.ReadAll(rawMessage) | ||
| if err != nil { | ||
| body, _ := io.ReadAll(rawMessage) | ||
| return nil, fmt.Errorf("failed to read response body: %w", err) | ||
| } | ||
| if code != 200 { | ||
| h.client.Logger.Debugf("response body: %s\n", body) | ||
| return GetJobResponse{}, err | ||
| return nil, fmt.Errorf("unexpected response code: %d", code) | ||
| } | ||
| var res getJobsResponse | ||
| if err := json.Unmarshal(body, &res); err != nil { | ||
| return nil, fmt.Errorf("failed to unmarshal response: %w", err) | ||
| } | ||
| jobs := toGetJobsResponse(res) | ||
|
|
||
| return jobs, nil | ||
| } | ||
|
|
||
| // Progress returns a job related progress | ||
| func (h httpJob) Progress(projectID string, translationJobUID string) (GetJobProgressResponse, error) { | ||
| url := jobBasePath + projectID + "/jobs/" + translationJobUID + "/progress" | ||
| var response getJobProgressResponse | ||
| rawMessage, code, err := h.client.Get(url, nil) | ||
| if err != nil { | ||
| return GetJobProgressResponse{}, err | ||
| } | ||
| defer func() { | ||
| if err := rawMessage.Close(); err != nil { | ||
| h.client.Logger.Debugf("failed to close response body: %v", err) | ||
| } | ||
| }() | ||
| body, err := io.ReadAll(rawMessage) | ||
| if err != nil { | ||
| return GetJobProgressResponse{}, fmt.Errorf("failed to read response body: %w", err) | ||
| } | ||
| if code != 200 { | ||
| h.client.Logger.Debugf("response body: %s\n", body) | ||
| return GetJobProgressResponse{}, fmt.Errorf("unexpected response code: %d", code) | ||
| } | ||
| if err := json.Unmarshal(body, &response); err != nil { | ||
| return GetJobResponse{}, fmt.Errorf("failed to unmarshal response: %w", err) | ||
| return GetJobProgressResponse{}, fmt.Errorf("failed to unmarshal response: %w", err) | ||
| } | ||
| return toGetJobResponse(response), nil | ||
| return toGetJobProgressResponse(response, translationJobUID) | ||
| } | ||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please research how to add validation for input parameters? An example for this specific case: we must check that parameters are not empty and
projectIDfollows specific patterns.P.S. Well, I do not want to broaden the scope of this ticket/PR. Let's make a Jira ticket for research on how to use a declarative approach for validation (rather than imerative).