• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

UiPath / uipathcli / 12390458216

18 Dec 2024 09:49AM UTC coverage: 90.453% (+0.008%) from 90.445%
12390458216

Pull #131

github

thschmitt
Add support to digitize using the default project

Making the project id optional and setting it to the default
project if not provided. This makes it easier to digitize files.
Pull Request #131: Add support to digitize using the default project

16 of 16 new or added lines in 1 file covered. (100.0%)

2 existing lines in 1 file now uncovered.

4292 of 4745 relevant lines covered (90.45%)

1.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.81
/plugin/digitizer/digitize_command.go
1
package digitzer
2

3
import (
4
        "bytes"
5
        "crypto/tls"
6
        "encoding/json"
7
        "errors"
8
        "fmt"
9
        "io"
10
        "mime/multipart"
11
        "net/http"
12
        "net/textproto"
13
        "net/url"
14
        "strings"
15
        "time"
16

17
        "github.com/UiPath/uipathcli/log"
18
        "github.com/UiPath/uipathcli/output"
19
        "github.com/UiPath/uipathcli/plugin"
20
        "github.com/UiPath/uipathcli/utils"
21
)
22

23
// The DigitizeCommand is a convenient wrapper over the async digitizer API
24
// to make it seem like it is a single sync call.
25
type DigitizeCommand struct{}
26

27
func (c DigitizeCommand) Command() plugin.Command {
1✔
28
        return *plugin.NewCommand("du").
1✔
29
                WithCategory("digitization", "Document Digitization", "Digitizes a document, extracting its Document Object Model (DOM) and text.").
1✔
30
                WithOperation("digitize", "Digitize file", "Digitize the given file").
1✔
31
                WithParameter("project-id", plugin.ParameterTypeString, "The project id", false).
1✔
32
                WithParameter("file", plugin.ParameterTypeBinary, "The file to digitize", true).
1✔
33
                WithParameter("content-type", plugin.ParameterTypeString, "The content type", false)
1✔
34
}
1✔
35

36
func (c DigitizeCommand) Execute(context plugin.ExecutionContext, writer output.OutputWriter, logger log.Logger) error {
1✔
37
        if context.Organization == "" {
2✔
38
                return errors.New("Organization is not set")
1✔
39
        }
1✔
40
        if context.Tenant == "" {
2✔
41
                return errors.New("Tenant is not set")
1✔
42
        }
1✔
43
        documentId, err := c.startDigitization(context, logger)
1✔
44
        if err != nil {
2✔
45
                return err
1✔
46
        }
1✔
47

48
        for i := 1; i <= 60; i++ {
2✔
49
                finished, err := c.waitForDigitization(documentId, context, writer, logger)
1✔
50
                if err != nil {
2✔
51
                        return err
1✔
52
                }
1✔
53
                if finished {
2✔
54
                        return nil
1✔
55
                }
1✔
56
                time.Sleep(1 * time.Second)
×
57
        }
58
        return fmt.Errorf("Digitization with documentId '%s' did not finish in time", documentId)
×
59
}
60

61
func (c DigitizeCommand) startDigitization(context plugin.ExecutionContext, logger log.Logger) (string, error) {
1✔
62
        uploadBar := utils.NewProgressBar(logger)
1✔
63
        defer uploadBar.Remove()
1✔
64
        requestError := make(chan error)
1✔
65
        request, err := c.createDigitizeRequest(context, uploadBar, requestError)
1✔
66
        if err != nil {
1✔
UNCOV
67
                return "", err
×
UNCOV
68
        }
×
69
        if context.Debug {
2✔
70
                c.logRequest(logger, request)
1✔
71
        }
1✔
72
        response, err := c.send(request, context.Insecure, requestError)
1✔
73
        if err != nil {
2✔
74
                return "", fmt.Errorf("Error sending request: %w", err)
1✔
75
        }
1✔
76
        defer response.Body.Close()
1✔
77
        body, err := io.ReadAll(response.Body)
1✔
78
        if err != nil {
1✔
79
                return "", fmt.Errorf("Error reading response: %w", err)
×
80
        }
×
81
        c.logResponse(logger, response, body)
1✔
82
        if response.StatusCode != http.StatusAccepted {
2✔
83
                return "", fmt.Errorf("Digitizer returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
84
        }
1✔
85
        var result digitizeResponse
1✔
86
        err = json.Unmarshal(body, &result)
1✔
87
        if err != nil {
1✔
88
                return "", fmt.Errorf("Error parsing json response: %w", err)
×
89
        }
×
90
        return result.DocumentId, nil
1✔
91
}
92

93
func (c DigitizeCommand) waitForDigitization(documentId string, context plugin.ExecutionContext, writer output.OutputWriter, logger log.Logger) (bool, error) {
1✔
94
        request, err := c.createDigitizeStatusRequest(documentId, context)
1✔
95
        if err != nil {
1✔
96
                return true, err
×
97
        }
×
98
        if context.Debug {
2✔
99
                c.logRequest(logger, request)
1✔
100
        }
1✔
101
        response, err := c.sendRequest(request, context.Insecure)
1✔
102
        if err != nil {
1✔
103
                return true, fmt.Errorf("Error sending request: %w", err)
×
104
        }
×
105
        defer response.Body.Close()
1✔
106
        body, err := io.ReadAll(response.Body)
1✔
107
        if err != nil {
1✔
108
                return true, fmt.Errorf("Error reading response: %w", err)
×
109
        }
×
110
        c.logResponse(logger, response, body)
1✔
111
        if response.StatusCode != http.StatusOK {
2✔
112
                return true, fmt.Errorf("Digitizer returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
113
        }
1✔
114
        var result digitizeResultResponse
1✔
115
        err = json.Unmarshal(body, &result)
1✔
116
        if err != nil {
1✔
117
                return true, fmt.Errorf("Error parsing json response: %w", err)
×
118
        }
×
119
        if result.Status == "NotStarted" || result.Status == "Running" {
1✔
120
                return false, nil
×
121
        }
×
122
        err = writer.WriteResponse(*output.NewResponseInfo(response.StatusCode, response.Status, response.Proto, response.Header, bytes.NewReader(body)))
1✔
123
        return true, err
1✔
124
}
125

126
func (c DigitizeCommand) createDigitizeRequest(context plugin.ExecutionContext, uploadBar *utils.ProgressBar, requestError chan error) (*http.Request, error) {
1✔
127
        projectId := c.getProjectId(context.Parameters)
1✔
128

1✔
129
        var err error
1✔
130
        file := context.Input
1✔
131
        if file == nil {
2✔
132
                file, err = c.getFileParameter(context.Parameters)
1✔
133
                if err != nil {
1✔
134
                        return nil, err
×
135
                }
×
136
        }
137
        contentType, _ := c.getParameter("content-type", context.Parameters)
1✔
138
        if contentType == "" {
2✔
139
                contentType = "application/octet-stream"
1✔
140
        }
1✔
141

142
        bodyReader, bodyWriter := io.Pipe()
1✔
143
        contentType, contentLength := c.writeMultipartBody(bodyWriter, file, contentType, requestError)
1✔
144
        uploadReader := c.progressReader("uploading...", "completing  ", bodyReader, contentLength, uploadBar)
1✔
145

1✔
146
        uri := c.formatUri(context.BaseUri, context.Organization, context.Tenant, projectId) + "/digitization/start?api-version=1"
1✔
147
        request, err := http.NewRequest("POST", uri, uploadReader)
1✔
148
        if err != nil {
1✔
149
                return nil, err
×
150
        }
×
151
        request.Header.Add("Content-Type", contentType)
1✔
152
        for key, value := range context.Auth.Header {
1✔
153
                request.Header.Add(key, value)
×
154
        }
×
155
        return request, nil
1✔
156
}
157

158
func (c DigitizeCommand) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *utils.ProgressBar) io.Reader {
1✔
159
        if length < 10*1024*1024 {
2✔
160
                return reader
1✔
161
        }
1✔
162
        progressReader := utils.NewProgressReader(reader, func(progress utils.Progress) {
2✔
163
                displayText := text
1✔
164
                if progress.Completed {
2✔
165
                        displayText = completedText
1✔
166
                }
1✔
167
                progressBar.Update(displayText, progress.BytesRead, length, progress.BytesPerSecond)
1✔
168
        })
169
        return progressReader
1✔
170
}
171

172
func (c DigitizeCommand) formatUri(baseUri url.URL, org string, tenant string, projectId string) string {
1✔
173
        path := baseUri.Path
1✔
174
        if baseUri.Path == "" {
2✔
175
                path = "/{organization}/{tenant}/du_/api/framework/projects/{projectId}"
1✔
176
        }
1✔
177
        path = strings.ReplaceAll(path, "{organization}", org)
1✔
178
        path = strings.ReplaceAll(path, "{tenant}", tenant)
1✔
179
        path = strings.ReplaceAll(path, "{projectId}", projectId)
1✔
180
        path = strings.TrimSuffix(path, "/")
1✔
181
        return fmt.Sprintf("%s://%s%s", baseUri.Scheme, baseUri.Host, path)
1✔
182
}
183

184
func (c DigitizeCommand) createDigitizeStatusRequest(documentId string, context plugin.ExecutionContext) (*http.Request, error) {
1✔
185
        projectId := c.getProjectId(context.Parameters)
1✔
186
        uri := c.formatUri(context.BaseUri, context.Organization, context.Tenant, projectId) + fmt.Sprintf("/digitization/result/%s?api-version=1", documentId)
1✔
187
        request, err := http.NewRequest("GET", uri, &bytes.Buffer{})
1✔
188
        if err != nil {
1✔
189
                return nil, err
×
190
        }
×
191
        for key, value := range context.Auth.Header {
1✔
192
                request.Header.Add(key, value)
×
193
        }
×
194
        return request, nil
1✔
195
}
196

197
func (c DigitizeCommand) calculateMultipartSize(stream utils.Stream) int64 {
1✔
198
        size, _ := stream.Size()
1✔
199
        return size
1✔
200
}
1✔
201

202
func (c DigitizeCommand) writeMultipartForm(writer *multipart.Writer, stream utils.Stream, contentType string) error {
1✔
203
        filePart := textproto.MIMEHeader{}
1✔
204
        filePart.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, stream.Name()))
1✔
205
        filePart.Set("Content-Type", contentType)
1✔
206
        w, err := writer.CreatePart(filePart)
1✔
207
        if err != nil {
1✔
208
                return fmt.Errorf("Error creating form field 'file': %w", err)
×
209
        }
×
210
        data, err := stream.Data()
1✔
211
        if err != nil {
2✔
212
                return err
1✔
213
        }
1✔
214
        defer data.Close()
1✔
215
        _, err = io.Copy(w, data)
1✔
216
        if err != nil {
1✔
217
                return fmt.Errorf("Error writing form field 'file': %w", err)
×
218
        }
×
219
        return nil
1✔
220
}
221

222
func (c DigitizeCommand) writeMultipartBody(bodyWriter *io.PipeWriter, stream utils.Stream, contentType string, errorChan chan error) (string, int64) {
1✔
223
        contentLength := c.calculateMultipartSize(stream)
1✔
224
        formWriter := multipart.NewWriter(bodyWriter)
1✔
225
        go func() {
2✔
226
                defer bodyWriter.Close()
1✔
227
                defer formWriter.Close()
1✔
228
                err := c.writeMultipartForm(formWriter, stream, contentType)
1✔
229
                if err != nil {
2✔
230
                        errorChan <- err
1✔
231
                        return
1✔
232
                }
1✔
233
        }()
234
        return formWriter.FormDataContentType(), contentLength
1✔
235
}
236

237
func (c DigitizeCommand) send(request *http.Request, insecure bool, errorChan chan error) (*http.Response, error) {
1✔
238
        responseChan := make(chan *http.Response)
1✔
239
        go func(request *http.Request) {
2✔
240
                response, err := c.sendRequest(request, insecure)
1✔
241
                if err != nil {
1✔
242
                        errorChan <- err
×
243
                        return
×
244
                }
×
245
                responseChan <- response
1✔
246
        }(request)
247

248
        select {
1✔
249
        case err := <-errorChan:
1✔
250
                return nil, err
1✔
251
        case response := <-responseChan:
1✔
252
                return response, nil
1✔
253
        }
254
}
255

256
func (c DigitizeCommand) sendRequest(request *http.Request, insecure bool) (*http.Response, error) {
1✔
257
        transport := &http.Transport{
1✔
258
                TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, //nolint // This is user configurable and disabled by default
1✔
259
        }
1✔
260
        client := &http.Client{Transport: transport}
1✔
261
        return client.Do(request)
1✔
262
}
1✔
263

264
func (c DigitizeCommand) getProjectId(parameters []plugin.ExecutionParameter) string {
1✔
265
        projectId, _ := c.getParameter("project-id", parameters)
1✔
266
        if projectId == "" {
2✔
267
                projectId = "00000000-0000-0000-0000-000000000000"
1✔
268
        }
1✔
269
        return projectId
1✔
270
}
271

272
func (c DigitizeCommand) getParameter(name string, parameters []plugin.ExecutionParameter) (string, error) {
1✔
273
        for _, p := range parameters {
2✔
274
                if p.Name == name {
2✔
275
                        if data, ok := p.Value.(string); ok {
2✔
276
                                return data, nil
1✔
277
                        }
1✔
278
                }
279
        }
280
        return "", fmt.Errorf("Could not find '%s' parameter", name)
1✔
281
}
282

283
func (c DigitizeCommand) getFileParameter(parameters []plugin.ExecutionParameter) (utils.Stream, error) {
1✔
284
        for _, p := range parameters {
2✔
285
                if p.Name == "file" {
2✔
286
                        if stream, ok := p.Value.(utils.Stream); ok {
2✔
287
                                return stream, nil
1✔
288
                        }
1✔
289
                }
290
        }
291
        return nil, fmt.Errorf("Could not find 'file' parameter")
×
292
}
293

294
func (c DigitizeCommand) logRequest(logger log.Logger, request *http.Request) {
1✔
295
        buffer := &bytes.Buffer{}
1✔
296
        _, _ = buffer.ReadFrom(request.Body)
1✔
297
        body := buffer.Bytes()
1✔
298
        request.Body = io.NopCloser(bytes.NewReader(body))
1✔
299
        requestInfo := log.NewRequestInfo(request.Method, request.URL.String(), request.Proto, request.Header, bytes.NewReader(body))
1✔
300
        logger.LogRequest(*requestInfo)
1✔
301
}
1✔
302

303
func (c DigitizeCommand) logResponse(logger log.Logger, response *http.Response, body []byte) {
1✔
304
        responseInfo := log.NewResponseInfo(response.StatusCode, response.Status, response.Proto, response.Header, bytes.NewReader(body))
1✔
305
        logger.LogResponse(*responseInfo)
1✔
306
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc