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

UiPath / uipathcli / 12390549287

18 Dec 2024 09:56AM UTC coverage: 86.121% (+0.05%) from 86.074%
12390549287

push

github

thschmitt
Add support to analyze studio projects

56 of 272 new or added lines in 8 files covered. (20.59%)

70 existing lines in 4 files now uncovered.

4660 of 5411 relevant lines covered (86.12%)

0.97 hits per line

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

85.0
/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✔
UNCOV
56
                time.Sleep(1 * time.Second)
×
57
        }
UNCOV
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✔
UNCOV
79
                return "", fmt.Errorf("Error reading response: %w", err)
×
UNCOV
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✔
UNCOV
88
                return "", fmt.Errorf("Error parsing json response: %w", err)
×
UNCOV
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✔
UNCOV
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)
×
UNCOV
104
        }
×
105
        defer response.Body.Close()
1✔
106
        body, err := io.ReadAll(response.Body)
1✔
107
        if err != nil {
1✔
UNCOV
108
                return true, fmt.Errorf("Error reading response: %w", err)
×
UNCOV
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✔
UNCOV
117
                return true, fmt.Errorf("Error parsing json response: %w", err)
×
UNCOV
118
        }
×
119
        if result.Status == "NotStarted" || result.Status == "Running" {
1✔
UNCOV
120
                return false, nil
×
UNCOV
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✔
UNCOV
134
                        return nil, err
×
UNCOV
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✔
UNCOV
149
                return nil, err
×
UNCOV
150
        }
×
151
        request.Header.Add("Content-Type", contentType)
1✔
152
        for key, value := range context.Auth.Header {
1✔
UNCOV
153
                request.Header.Add(key, value)
×
UNCOV
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✔
UNCOV
189
                return nil, err
×
UNCOV
190
        }
×
191
        for key, value := range context.Auth.Header {
1✔
192
                request.Header.Add(key, value)
×
UNCOV
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✔
UNCOV
208
                return fmt.Errorf("Error creating form field 'file': %w", err)
×
UNCOV
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✔
UNCOV
217
                return fmt.Errorf("Error writing form field 'file': %w", err)
×
UNCOV
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✔
UNCOV
242
                        errorChan <- err
×
UNCOV
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
        }
UNCOV
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✔
307

308
func NewDigitizeCommand() *DigitizeCommand {
1✔
309
        return &DigitizeCommand{}
1✔
310
}
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

© 2025 Coveralls, Inc