timeEntries.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. /*
  2. * Copyright 2023 Girish M
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package main
  17. import (
  18. "encoding/json"
  19. "fmt"
  20. "io"
  21. "regexp"
  22. "strings"
  23. )
  24. type ActivityLinks struct {
  25. Self Self `json:"self"`
  26. Projects []Self `json:"projects"`
  27. }
  28. type AllowedValues struct {
  29. Type string `json:"type"`
  30. ID int `json:"id"`
  31. Name string `json:"name"`
  32. Position int `json:"position"`
  33. Default bool `json:"default"`
  34. Links ActivityLinks `json:"_links"`
  35. }
  36. type Activity struct {
  37. Type string `json:"type"`
  38. Name string `json:"name"`
  39. Required bool `json:"required"`
  40. HasDefault bool `json:"hasDefault"`
  41. Writable bool `json:"writable"`
  42. Location string `json:"location"`
  43. Embedded struct {
  44. AllowedValues []AllowedValues `json:"allowedValues"`
  45. } `json:"_embedded"`
  46. }
  47. type Dependency struct {
  48. }
  49. type Rtl struct {
  50. }
  51. type TimeEntryOption struct {
  52. Rtl Rtl `json:"rtl"`
  53. }
  54. type AllowedValue struct {
  55. Href string `json:"href"`
  56. }
  57. type LinksTimeEntryWP struct {
  58. AllowedValues AllowedValue `json:"allowedValues"`
  59. }
  60. type TimeEntryWP struct {
  61. Type string `json:"type"`
  62. Name string `json:"name"`
  63. Required bool `json:"required"`
  64. HasDefault bool `json:"hasDefault"`
  65. Writable bool `json:"writable"`
  66. Location string `json:"location"`
  67. Links LinksTimeEntryWP `json:"_links"`
  68. }
  69. type ID struct {
  70. Type string `json:"type"`
  71. Name string `json:"name"`
  72. Required bool `json:"required"`
  73. HasDefault bool `json:"hasDefault"`
  74. Writable bool `json:"writable"`
  75. Options TimeEntryOption `json:"options"`
  76. }
  77. type SchemaLinks struct {
  78. }
  79. type Schema struct {
  80. Type string `json:"_type"`
  81. Dependencies []Dependency `json:"_dependencies"`
  82. ID ID `json:"id"`
  83. CreatedAt ID `json:"createdAt"`
  84. UpdatedAt ID `json:"updatedAt"`
  85. SpentOn ID `json:"spentOn"`
  86. Hours ID `json:"hours"`
  87. User ID `json:"user"`
  88. WorkPackage TimeEntryWP `json:"workPackage"`
  89. Project TimeEntryWP `json:"project"`
  90. Activity Activity `json:"activity"`
  91. CustomField string `json:"-"`
  92. Links SchemaLinks `json:"_links"`
  93. }
  94. type Link struct {
  95. Href string `json:"href"`
  96. Title string `json:"title"`
  97. }
  98. type PayloadLinks struct {
  99. Project Link `json:"project"`
  100. Activity Link `json:"activity"`
  101. WorkPackage Link `json:"workPackage"`
  102. }
  103. type Comment struct {
  104. Format string `json:"format"`
  105. Raw string `json:"raw"`
  106. HTML string `json:"html"`
  107. }
  108. type Payload struct {
  109. Links PayloadLinks `json:"_links"`
  110. Hours string `json:"hours"`
  111. Comment Comment `json:"comment"`
  112. SpentOn string `json:"spentOn"`
  113. CustomField string `json:"-"`
  114. }
  115. type ValidationError struct {
  116. }
  117. type EmbeddedTimeEntry struct {
  118. Payload Payload `json:"payload"`
  119. Schema Schema `json:"schema"`
  120. ValidationErrors ValidationError `json:"validationErrors"`
  121. }
  122. type LinksTimeEntry struct {
  123. Self Self `json:"self"`
  124. Validate Self `json:"validate"`
  125. Commit Self `json:"commit"`
  126. Project Self `json:"project"`
  127. User Self `json:"user"`
  128. WorkPackage Self `json:"workPackage"`
  129. }
  130. type TimeEntries struct {
  131. Type string `json:"_type"`
  132. Embedded EmbeddedTimeEntry `json:"_embedded"`
  133. Links LinksTimeEntry `json:"_links"`
  134. }
  135. type TimeEntriesBody struct {
  136. Links struct {
  137. WorkPackage struct {
  138. Href string `json:"href"`
  139. } `json:"workPackage"`
  140. } `json:"_links"`
  141. }
  142. type TimeEntryPostBody struct {
  143. Links struct {
  144. WorkPackage struct {
  145. Href string `json:"href"`
  146. } `json:"workPackage"`
  147. Activity struct {
  148. Href string `json:"href"`
  149. } `json:"activity"`
  150. Project struct {
  151. Href string `json:"href"`
  152. } `json:"project"`
  153. } `json:"_links"`
  154. Hours string `json:"hours"`
  155. Comment Comment `json:"comment"`
  156. SpentOn string `json:"spentOn"`
  157. CustomField string `json:"-"`
  158. }
  159. type UpdateImmediately struct {
  160. Href string `json:"href"`
  161. Method string `json:"method"`
  162. }
  163. type Delete struct {
  164. Href string `json:"href"`
  165. Method string `json:"method"`
  166. }
  167. type TimeLinks struct {
  168. Self Self `json:"self"`
  169. UpdateImmediately UpdateImmediately `json:"updateImmediately"`
  170. Delete Delete `json:"delete"`
  171. Project Link `json:"project"`
  172. WorkPackage Link `json:"workPackage"`
  173. User Link `json:"user"`
  174. Activity Link `json:"activity"`
  175. CustomField string `json:"-"`
  176. }
  177. type TimeElement struct {
  178. Type string `json:"_type"`
  179. ID int `json:"id"`
  180. Comment Comment `json:"comment"`
  181. SpentOn string `json:"spentOn"`
  182. Hours string `json:"hours"`
  183. CreatedAt string `json:"createdAt"`
  184. UpdatedAt string `json:"updatedAt"`
  185. Links TimeLinks `json:"_links"`
  186. CustomField float64 `json:"-"`
  187. }
  188. type TimeEntryList struct {
  189. Type string `json:"_type"`
  190. Total int `json:"total"`
  191. Count int `json:"count"`
  192. PageSize int `json:"pageSize"`
  193. Offset int `json:"offset"`
  194. Embedded struct {
  195. Elements []TimeElement `json:"elements"`
  196. } `json:"_embedded"`
  197. }
  198. func (tel *TimeEntryList) UnmarshalJSON(data []byte) error {
  199. var jsonData map[string]interface{}
  200. if err := json.Unmarshal(data, &jsonData); err != nil {
  201. return err
  202. }
  203. tel.Type, _ = jsonData["_type"].(string)
  204. tel.Total, _ = jsonData["total"].(int)
  205. tel.Count, _ = jsonData["count"].(int)
  206. tel.PageSize, _ = jsonData["pageSize"].(int)
  207. tel.Offset, _ = jsonData["offset"].(int)
  208. embeddedData, ok := jsonData["_embedded"].(map[string]interface{})
  209. if !ok {
  210. return fmt.Errorf("missing _embedded field or it's not an object")
  211. }
  212. elementsData, ok := embeddedData["elements"].([]interface{})
  213. if !ok {
  214. return fmt.Errorf("missing elements field or it's not an array")
  215. }
  216. for _, element := range elementsData {
  217. elementBytes, err := json.Marshal(element)
  218. if err != nil {
  219. return err
  220. }
  221. var timeElement TimeElement
  222. if err := json.Unmarshal(elementBytes, &timeElement); err != nil {
  223. return err
  224. }
  225. tel.Embedded.Elements = append(tel.Embedded.Elements, timeElement)
  226. }
  227. return nil
  228. }
  229. func (sl Schema) MarshalJSON() ([]byte, error) {
  230. data := make(map[string]interface{})
  231. data["_type"] = sl.Type
  232. data["_dependencies"] = sl.Dependencies
  233. data["id"] = sl.ID
  234. data["createdAt"] = sl.CreatedAt
  235. data["updatedAt"] = sl.UpdatedAt
  236. data["spentOn"] = sl.SpentOn
  237. data["hours"] = sl.Hours
  238. data["user"] = sl.User
  239. data["workPackage"] = sl.WorkPackage
  240. data["project"] = sl.Project
  241. data["activity"] = sl.Activity
  242. data["_links"] = sl.Links
  243. fieldName := getCustomFieldName()
  244. data[fieldName] = sl.CustomField
  245. return json.Marshal(data)
  246. }
  247. func (pl Payload) MarshalJSON() ([]byte, error) {
  248. data := make(map[string]interface{})
  249. data["_links"] = pl.Links
  250. data["hours"] = pl.Hours
  251. data["comment"] = pl.Comment
  252. data["spentOn"] = pl.SpentOn
  253. fieldName := getCustomFieldName()
  254. data[fieldName] = pl.CustomField
  255. return json.Marshal(data)
  256. }
  257. func (tl TimeLinks) MarshalJSON() ([]byte, error) {
  258. data := make(map[string]interface{})
  259. data["self"] = tl.Self
  260. data["updateImmediately"] = tl.UpdateImmediately
  261. data["delete"] = tl.Delete
  262. data["project"] = tl.Project
  263. data["workPackage"] = tl.WorkPackage
  264. data["user"] = tl.User
  265. data["activity"] = tl.Activity
  266. fieldName := getCustomFieldName()
  267. data[fieldName] = tl.CustomField
  268. return json.Marshal(data)
  269. }
  270. func (te *TimeElement) MarshalJSON() ([]byte, error) {
  271. data := make(map[string]interface{})
  272. data["_type"] = te.Type
  273. data["id"] = te.ID
  274. data["comment"] = te.Comment
  275. data["spentOn"] = te.SpentOn
  276. data["hours"] = te.Hours
  277. data["createdAt"] = te.CreatedAt
  278. data["updatedAt"] = te.UpdatedAt
  279. data["_links"] = te.Links
  280. fieldName := getCustomFieldName()
  281. data[fieldName] = te.CustomField
  282. return json.Marshal(data)
  283. }
  284. func (te *TimeElement) UnmarshalJSON(data []byte) error {
  285. var jsonData map[string]interface{}
  286. if err := json.Unmarshal(data, &jsonData); err != nil {
  287. return err
  288. }
  289. te.Type, _ = jsonData["_type"].(string)
  290. te.ID = int(jsonData["id"].(float64))
  291. commentBytes, _ := json.Marshal(jsonData["comment"])
  292. if err := json.Unmarshal(commentBytes, &te.Comment); err != nil {
  293. return err
  294. }
  295. te.SpentOn, _ = jsonData["spentOn"].(string)
  296. te.Hours, _ = jsonData["hours"].(string)
  297. te.CreatedAt, _ = jsonData["createdAt"].(string)
  298. te.UpdatedAt, _ = jsonData["updatedAt"].(string)
  299. linksBytes, ok := jsonData["_links"].(map[string]interface{})
  300. if !ok {
  301. return fmt.Errorf("missing _links field or it's not an object")
  302. }
  303. linksData, _ := json.Marshal(linksBytes)
  304. if err := json.Unmarshal(linksData, &te.Links); err != nil {
  305. return err
  306. }
  307. fieldName := getCustomFieldName()
  308. te.CustomField, _ = jsonData[fieldName].(float64)
  309. return nil
  310. }
  311. func (tb TimeEntryPostBody) MarshalJSON() ([]byte, error) {
  312. data := make(map[string]interface{})
  313. data["_links"] = tb.Links
  314. data["hours"] = tb.Hours
  315. data["spentOn"] = tb.SpentOn
  316. data["comment"] = tb.Comment
  317. fieldName := getCustomFieldName()
  318. data[fieldName] = tb.CustomField
  319. return json.Marshal(data)
  320. }
  321. func getCustomFieldName() string {
  322. if customFieldForBillableHours == "" {
  323. resp, err := GetTimeEntriesSchema(OpURLStr, APIKeyStr)
  324. if err == nil {
  325. body, _ := io.ReadAll(resp.Body)
  326. defer resp.Body.Close()
  327. _ = json.Unmarshal(body, &timeEntriesSchema)
  328. customFieldForBillableHours = findFirstKVContainingBillable(timeEntriesSchema)
  329. return customFieldForBillableHours
  330. }
  331. }
  332. return customFieldForBillableHours
  333. }
  334. func findFirstKVContainingBillable(jsonObj map[string]interface{}) string {
  335. customFieldRegex := regexp.MustCompile(`customField\d+`)
  336. for key, v := range jsonObj {
  337. if customFieldRegex.MatchString(key) {
  338. var strValue, ok = v.(map[string]interface{})
  339. if ok && strings.Contains(strings.ToLower(strValue["name"].(string)), "billable") {
  340. return key
  341. }
  342. }
  343. }
  344. return "customField1"
  345. }