ticket-replicator 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7606fa759b9d684b5f7ba6f51ae8492d0d75aa12f35aecf18dcacad00128d3ed
4
- data.tar.gz: 31c447e651e6f1a3ee29f99d3ff5d82716f17ea2f26c0bcf99e120f73164695f
3
+ metadata.gz: c470d25146cd29253a36275e23181f072d7131333f271f748ebe3560d9849825
4
+ data.tar.gz: 72393510381f5d4abdd4aa3bccafbec872fc30f585ddee5fdf7a8ae4e0f28f2e
5
5
  SHA512:
6
- metadata.gz: 83564cefebd76b731d4bf201bf85aa076f11ad69344d8d6bc81ff43677946bc7050318f57fea6528ecf222a203a815e28dd8120b5fd4d9a2feb610c8e00a0a66
7
- data.tar.gz: 9c7e90104e65a3ad37316714715c18ef83280b0d27feb272d73f110aeeda7d5cb47d22aaeff8a4c901ebd714115c9ae38078bdd9a1696c66eb46652466d1120b
6
+ metadata.gz: d1460f599c98ef9bdd4c0852cbf9c0b3734a7cba3453c96c4ab25e7dbac7a6e3303cf3b051b2d54ac80f7296113e383fafd1236eb62b8c4b57fe1d1cf190dc12
7
+ data.tar.gz: d641aa5a31b2efab1f6028383d534caf55336eeb49f43ceae446044df1cd0073873d6ca5f0b835d0581de049a402813a50a03478fdd81b9914259af079fbdadc
data/.rubocop.yml CHANGED
@@ -16,6 +16,11 @@ Style/Documentation:
16
16
  Style/HashSyntax:
17
17
  Enabled: false
18
18
 
19
+ Style/SafeNavigation:
20
+ Enabled: true
21
+ AllowedMethods:
22
+ - '[]'
23
+
19
24
  Style/StringLiterals:
20
25
  EnforcedStyle: double_quotes
21
26
 
data/README.md CHANGED
@@ -5,9 +5,9 @@
5
5
  ## Purpose
6
6
 
7
7
  In order to:
8
- * have an overall transparency about all the activities pertaining to a project/product,
9
- * track progress
10
- * act accordingly
8
+ * Have an overall transparency about all the activities pertaining to a project/product,
9
+ * Track progress
10
+ * Act accordingly
11
11
 
12
12
  As a stakeholder
13
13
 
@@ -20,17 +20,15 @@ I need to replicate defect information from one system to a single reference sys
20
20
  * ... and add to the application's Gemfile by executing:
21
21
 
22
22
  ```bash
23
- bundle add TODO: GEM_NAME
23
+ bundle add ticket-replicator
24
24
  ```
25
25
 
26
26
  * ... if bundler is not being used to manage dependencies, by executing:
27
27
 
28
28
  ```bash
29
- gem install TODO: GEM_NAME
29
+ gem install ticket-replicator
30
30
  ```
31
31
 
32
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
33
-
34
32
  ## Setup
35
33
 
36
34
  ### Set environment variables
@@ -38,6 +36,7 @@ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_O
38
36
  Define the following environment variables
39
37
  - `TICKET_REPLICATOR_JIRA_PROJECT_KEY`
40
38
  - `TICKET_REPLICATOR_JIRA_TICKET_TYPE_NAME`
39
+ - `TICKET_REPLICATOR_SOURCE_TICKET_URL_ERB`
41
40
 
42
41
  For your JIRA access the following variables have to be defined:
43
42
 
@@ -61,7 +60,7 @@ Optional environment variables:
61
60
  ```
62
61
  1. Edit the field mapping configuration file to fit your context.
63
62
 
64
- ### Target Jira Ticket Type Workflow Expectation - Any to Any
63
+ ### Jira Ticket Type Workflow Expectation - Any to Any
65
64
 
66
65
  The target issue type in Jira must have a workflow that allows direct transitions between any states. This avoids
67
66
  the need for administrative rights to calculate complex transition paths or discover workflow states through trial
@@ -8,122 +8,118 @@ Feature: Load tickets into Jira
8
8
  | name |
9
9
  | TICKET_REPLICATOR_JIRA_PROJECT_KEY |
10
10
  | TICKET_REPLICATOR_JIRA_TICKET_TYPE_NAME |
11
+ # And the following environment variables have been set:
12
+ # | name | value |
13
+ # | TICKET_REPLICATOR_SOURCE_TICKET_URL | http://url/to/source/ticket/<%= source_ticket_id %> |
11
14
  And the project has no tickets
12
15
 
13
16
  Scenario: Load tickets in Jira
14
17
  Given a file named "queue/20.transformed/sap_solution_manager_defects.csv" with:
15
18
  """
16
- "ID","Status","Resolution","Priority","Summary"
17
- "10001","Open","","Highest","SMAN-10001 | Login page randomly fails to load CSS assets (10001)"
18
- "10002","In Process","","Highest","SMAN-10002 | Database deadlock during order processing (10002)"
19
- "10003","Confirmed","Done","Medium","SMAN-10003 | Invalid date format in SOAP response (10003)"
20
- "10004","Closed","Done","Low","SMAN-10004 | App crashes when offline on Android 12 (10004)"
21
- "10005","Open","","High","SMAN-10005 | Session tokens not properly invalidated (10005)"
22
- "10006","Solution Proposal","","Medium","SMAN-10006 | Jenkins pipeline timeout on large builds (10006)"
23
- "10007","Withdrawn","Won't Do","Low","SMAN-10007 | Test data missing edge case scenarios (10007)"
24
- "10008","Open","","Highest","SMAN-10008 | Memory leak in caching implementation (10008)"
25
- "10009","Wait on External","","Medium","SMAN-10009 | Inconsistent button styles across modules (10009)"
19
+ "ID","Status","Resolution","Priority","Summary","Source Ticket URL"
20
+ "10001","Open","","Highest","SMAN-10001 | Login page randomly fails to load CSS assets (10001)","http://url/to/source/ticket/10001"
21
+ "10002","In Process","","Highest","SMAN-10002 | Database deadlock during order processing (10002)","http://url/to/source/ticket/10002"
22
+ "10003","Confirmed","Done","Medium","SMAN-10003 | Invalid date format in SOAP response (10003)","http://url/to/source/ticket/10003"
23
+ "10004","Closed","Done","Low","SMAN-10004 | App crashes when offline on Android 12 (10004)","http://url/to/source/ticket/10004"
24
+ "10005","Open","","High","SMAN-10005 | Session tokens not properly invalidated (10005)","http://url/to/source/ticket/10005"
25
+ "10006","Solution Proposal","","Medium","SMAN-10006 | Jenkins pipeline timeout on large builds (10006)","http://url/to/source/ticket/10006"
26
+ "10007","Withdrawn","Won't Do","Low","SMAN-10007 | Test data missing edge case scenarios (10007)","http://url/to/source/ticket/10007"
27
+ "10008","Open","","Highest","SMAN-10008 | Memory leak in caching implementation (10008)","http://url/to/source/ticket/10008"
28
+ "10009","Wait on External","","Medium","SMAN-10009 | Inconsistent button styles across modules (10009)","http://url/to/source/ticket/10009"
26
29
  """
27
30
  When I successfully run `ticket-replicator --load`
28
31
  Then the Jira project should only have the following tickets:
29
- | status | resolution | priority | summary | source ticket url |
30
- | Open | | Highest | SMAN-10001 \| Login page randomly fails to load CSS assets (10001) | https://sap.example/id/10001 |
31
- | In Process | | Highest | SMAN-10002 \| Database deadlock during order processing (10002) | https://sap.example/id/10002 |
32
- | Confirmed | Done | Medium | SMAN-10003 \| Invalid date format in SOAP response (10003) | https://sap.example/id/10003 |
33
- | Closed | Done | Low | SMAN-10004 \| App crashes when offline on Android 12 (10004) | https://sap.example/id/10004 |
34
- | Open | | High | SMAN-10005 \| Session tokens not properly invalidated (10005) | https://sap.example/id/10005 |
35
- | Solution Proposal | | Medium | SMAN-10006 \| Jenkins pipeline timeout on large builds (10006) | https://sap.example/id/10006 |
36
- | Withdrawn | Won't Do | Low | SMAN-10007 \| Test data missing edge case scenarios (10007) | https://sap.example/id/10007 |
37
- | Open | | Highest | SMAN-10008 \| Memory leak in caching implementation (10008) | https://sap.example/id/10008 |
38
- | Wait on External | | Medium | SMAN-10009 \| Inconsistent button styles across modules (10009) | https://sap.example/id/10009 |
39
- And the source ticket URL is found in the ticket descriptions of those tickets
32
+ | status | resolution | priority | summary | source_ticket_url |
33
+ | Open | | Highest | SMAN-10001 \| Login page randomly fails to load CSS assets (10001) | http://url/to/source/ticket/10001 |
34
+ | In Process | | Highest | SMAN-10002 \| Database deadlock during order processing (10002) | http://url/to/source/ticket/10002 |
35
+ | Confirmed | Done | Medium | SMAN-10003 \| Invalid date format in SOAP response (10003) | http://url/to/source/ticket/10003 |
36
+ | Closed | Done | Low | SMAN-10004 \| App crashes when offline on Android 12 (10004) | http://url/to/source/ticket/10004 |
37
+ | Open | | High | SMAN-10005 \| Session tokens not properly invalidated (10005) | http://url/to/source/ticket/10005 |
38
+ | Solution Proposal | | Medium | SMAN-10006 \| Jenkins pipeline timeout on large builds (10006) | http://url/to/source/ticket/10006 |
39
+ | Withdrawn | Won't Do | Low | SMAN-10007 \| Test data missing edge case scenarios (10007) | http://url/to/source/ticket/10007 |
40
+ | Open | | Highest | SMAN-10008 \| Memory leak in caching implementation (10008) | http://url/to/source/ticket/10008 |
41
+ | Wait on External | | Medium | SMAN-10009 \| Inconsistent button styles across modules (10009) | http://url/to/source/ticket/10009 |
40
42
 
41
43
  Scenario: Loading the ticket information twice in Jira does not create additional tickets
42
44
  Given a file named "queue/20.transformed/sap_solution_manager_defects.csv" with:
43
45
  """
44
- "ID","Status","Resolution","Priority","Summary"
45
- "10001","Open","","Highest","SMAN-10001 | Login page randomly fails to load CSS assets (10001)"
46
- "10002","In Process","","Highest","SMAN-10002 | Database deadlock during order processing (10002)"
46
+ "ID","Status","Resolution","Priority","Summary","Source Ticket URL"
47
+ "10001","Open","","Highest","SMAN-10001 | Login page randomly fails to load CSS assets (10001)","http://url/to/source/ticket/10001"
48
+ "10002","In Process","","Highest","SMAN-10002 | Database deadlock during order processing (10002)","http://url/to/source/ticket/10002"
47
49
  """
48
50
  And a file named "queue/20.transformed/sap_solution_manager_defects.SAME_CONTENT.csv" with:
49
51
  """
50
- "ID","Status","Resolution","Priority","Summary"
51
- "10001","Open","","Highest","SMAN-10001 | Login page randomly fails to load CSS assets (10001)"
52
- "10002","In Process","","Highest","SMAN-10002 | Database deadlock during order processing (10002)"
52
+ "ID","Status","Resolution","Priority","Summary","Source Ticket URL"
53
+ "10001","Open","","Highest","SMAN-10001 | Login page randomly fails to load CSS assets (10001)","http://url/to/source/ticket/10001"
54
+ "10002","In Process","","Highest","SMAN-10002 | Database deadlock during order processing (10002)","http://url/to/source/ticket/10002"
53
55
  """
54
56
  When I successfully run `ticket-replicator --load`
55
57
  Then the Jira project should only have the following tickets:
56
- | status | resolution | priority | summary | source ticket url |
57
- | Open | | Highest | SMAN-10001 \| Login page randomly fails to load CSS assets (10001) | https://sap.example/id/10001 |
58
- | In Process | | Highest | SMAN-10002 \| Database deadlock during order processing (10002) | https://sap.example/id/10002 |
59
- And the source ticket URL is found in the ticket descriptions of those tickets
58
+ | status | resolution | priority | summary | source_ticket_url |
59
+ | Open | | Highest | SMAN-10001 \| Login page randomly fails to load CSS assets (10001) | http://url/to/source/ticket/10001 |
60
+ | In Process | | Highest | SMAN-10002 \| Database deadlock during order processing (10002) | http://url/to/source/ticket/10002 |
60
61
 
61
62
  Scenario: Tickets are updated only if changes happened or they were not replicated before
62
63
  Given a file named "queue/20.transformed/2025-03-29.16h16.sap_solution_manager_defects.csv" with:
63
64
  """
64
- "ID","Status","Resolution","Priority","Summary"
65
- "10001","Open","","Highest","SMAN-10001 | Login page randomly fails to load CSS assets (10001)"
66
- "10002","In Process","Cannot Reproduce","Medium","SMAN-10002 | Database deadlock during order processing (10002)"
67
- "10016","Confirmed","Done","High","SMAN-10016 | Slow response time on product search API (10016)"
65
+ "ID","Status","Resolution","Priority","Summary","Source Ticket URL"
66
+ "10001","Open","","Highest","SMAN-10001 | Login page randomly fails to load CSS assets (10001)","http://url/to/source/ticket/10001"
67
+ "10002","In Process","Cannot Reproduce","Medium","SMAN-10002 | Database deadlock during order processing (10002)","http://url/to/source/ticket/10002"
68
+ "10016","Confirmed","Done","High","SMAN-10016 | Slow response time on product search API (10016)","http://url/to/source/ticket/10016"
68
69
  """
69
70
  And I successfully run `ticket-replicator --load`
70
71
  When a file named "queue/20.transformed/2025-03-29.19h56.update_tickets.csv" with:
71
72
  """
72
- "ID","Status","Resolution","Priority","Summary"
73
- "10001","Closed","","Highest","SMAN-10001 | Login page randomly fails to load CSS assets (10001)"
74
- "10002","In Process","","High","SMAN-10002 | *Recurring* database deadlock during order processing (10002)"
75
- "10008","Open","","Highest","SMAN-10008 | Memory leak in caching implementation (10008)"
76
- "10016","Confirmed","Done","High","SMAN-10016 | Slow response time on product search API (10016)"
73
+ "ID","Status","Resolution","Priority","Summary","Source Ticket URL"
74
+ "10001","Closed","","Highest","SMAN-10001 | Login page randomly fails to load CSS assets (10001)","http://url/to/source/ticket/10001"
75
+ "10002","In Process","","High","SMAN-10002 | *Recurring* database deadlock during order processing (10002)","http://url/to/source/ticket/10002"
76
+ "10008","Open","","Highest","SMAN-10008 | Memory leak in caching implementation (10008)","http://url/to/source/ticket/10008"
77
+ "10016","Confirmed","Done","High","SMAN-10016 | Slow response time on product search API (10016)","http://url/to/source/ticket/10016"
77
78
  """
78
79
  And I successfully run `ticket-replicator --jira-http-debug --load`
79
80
  Then the Jira project should only have the following tickets:
80
- | example purpose | status | resolution | priority | summary | source ticket url |
81
- | one update due to status change | Closed | | Highest | SMAN-10001 \| Login page randomly fails to load CSS assets (10001) | https://sap.example/id/10001 |
82
- | one update due to priority, resolution and summary updates | In Process | | High | SMAN-10002 \| *Recurring* database deadlock during order processing (10002) | https://sap.example/id/10002 |
83
- | two updates due to new ticket created (fields and status changes) | Open | | Highest | SMAN-10008 \| Memory leak in caching implementation (10008) | https://sap.example/id/10008 |
84
- | no update since no status or field change | Confirmed | Done | High | SMAN-10016 \| Slow response time on product search API (10016) | https://sap.example/id/10016 |
85
- And only 4 Jira update requests were emitted
81
+ | example purpose | status | resolution | priority | summary | source_ticket_url |
82
+ | one update due to status change | Closed | | Highest | SMAN-10001 \| Login page randomly fails to load CSS assets (10001) | http://url/to/source/ticket/10001 |
83
+ | one update due to priority, resolution and summary updates | In Process | | High | SMAN-10002 \| *Recurring* database deadlock during order processing (10002) | http://url/to/source/ticket/10002 |
84
+ | three updates due to new ticket created (fields, source ticket link and status) | Open | | Highest | SMAN-10008 \| Memory leak in caching implementation (10008) | http://url/to/source/ticket/10008 |
85
+ | no update since no status or field change | Confirmed | Done | High | SMAN-10016 \| Slow response time on product search API (10016) | http://url/to/source/ticket/10016 |
86
+ And only 5 Jira update requests were emitted
86
87
 
87
88
  Scenario: Attempting to set an unexpected status generates information about the current row being loaded
88
89
  Given a file named "queue/20.transformed/sap_solution_manager_defects.csv" with:
89
90
  """
90
- "ID","Status","Resolution","Priority","Summary"
91
- "10001","Open","","Highest","SMAN-10001 | Login page randomly fails to load CSS assets (10001)"
92
- "10002","In Process","","Highest","SMAN-10002 | Database deadlock during order processing (10002)"
93
- "10003","Confirmed","Done","Medium","SMAN-10003 | Invalid date format in SOAP response (10003)"
94
- "10004","____ INEXISTING STATUS ____","Done","Low","SMAN-10004 | App crashes when offline on Android 12 (10004)"
95
- "10005","Open","","High","SMAN-10005 | Session tokens not properly invalidated (10005)"
96
- "10006","Solution Proposal","","Medium","SMAN-10006 | Jenkins pipeline timeout on large builds (10006)"
97
- "10007","Withdrawn","Won't Do","Low","SMAN-10007 | Test data missing edge case scenarios (10007)"
98
- "10008","Open","","Highest","SMAN-10008 | Memory leak in caching implementation (10008)"
99
- "10009","Wait on External","","Medium","SMAN-10009 | Inconsistent button styles across modules (10009)"
91
+ "ID","Status","Resolution","Priority","Summary","Source Ticket URL"
92
+ "10001","Open","","Highest","SMAN-10001 | Login page randomly fails to load CSS assets (10001)","http://url/to/source/ticket/10001"
93
+ "10002","In Process","","Highest","SMAN-10002 | Database deadlock during order processing (10002)","http://url/to/source/ticket/10002"
94
+ "10003","Confirmed","Done","Medium","SMAN-10003 | Invalid date format in SOAP response (10003)","http://url/to/source/ticket/10003"
95
+ "10004","____ INEXISTING STATUS ____","Done","Low","SMAN-10004 | App crashes when offline on Android 12 (10004)","http://url/to/source/ticket/10004"
96
+ "10005","Open","","High","SMAN-10005 | Session tokens not properly invalidated (10005)","http://url/to/source/ticket/10005"
97
+ "10006","Solution Proposal","","Medium","SMAN-10006 | Jenkins pipeline timeout on large builds (10006)","http://url/to/source/ticket/10006"
98
+ "10007","Withdrawn","Won't Do","Low","SMAN-10007 | Test data missing edge case scenarios (10007)","http://url/to/source/ticket/10007"
99
+ "10008","Open","","Highest","SMAN-10008 | Memory leak in caching implementation (10008)","http://url/to/source/ticket/10008"
100
+ "10009","Wait on External","","Medium","SMAN-10009 | Inconsistent button styles across modules (10009)","http://url/to/source/ticket/10009"
100
101
  """
101
102
  When I run `ticket-replicator --load`
102
103
  Then it should fail with:
103
104
  """
104
105
  ERROR Object : Ticket::Replicator::FileLoader::LoadError: queue/20.transformed/sap_solution_manager_defects.csv:5: error while loading row:
105
106
  No transition found for "____ INEXISTING STATUS ____" in ["No Error -> No Error", "Tester Action -> Tester Action", "Close Bug -> Closed", "Confirmed -> Confirmed", "Defect Correction in Process -> Defect Correction in Process", "Deferred -> Deferred", "Forwarded -> Forwarded", "Information Required -> Information Required", "New -> New", "Open -> Open", "Solution Proposal -> Solution Proposal", "Wait for Defect Correction -> Wait for Defect Correction", "Wait on External -> Wait on External", "Withdrawn -> Withdrawn", "In Process -> In Process"].:
106
- #<CSV::Row id:"10004" status:"____ INEXISTING STATUS ____" resolution:"Done" priority:"Low" summary:"SMAN-10004 | App crashes when offline on Android 12 (10004)">
107
+ #<CSV::Row id:"10004" status:"____ INEXISTING STATUS ____" resolution:"Done" priority:"Low" summary:"SMAN-10004 | App crashes when offline on Android 12 (10004)" source_ticket_url:"http://url/to/source/ticket/10004">
107
108
  """
108
109
 
109
110
  Scenario: Attempting to set an unexpected priority generates information about the current row being loaded
110
111
  Given a file named "queue/20.transformed/sap_solution_manager_defects.csv" with:
111
112
  """
112
- "ID","Status","Resolution","Priority","Summary"
113
- "10001","Open","","Highest","SMAN-10001 | Login page randomly fails to load CSS assets (10001)"
114
- "10002","In Process","","Highest","SMAN-10002 | Database deadlock during order processing (10002)"
115
- "10003","Confirmed","Done","Medium","SMAN-10003 | Invalid date format in SOAP response (10003)"
116
- "10004","Closed","Done","Low","SMAN-10004 | App crashes when offline on Android 12 (10004)"
117
- "10005","Open","","High","SMAN-10005 | Session tokens not properly invalidated (10005)"
118
- "10006","Solution Proposal","","Medium","SMAN-10006 | Jenkins pipeline timeout on large builds (10006)"
119
- "10007","Withdrawn","Won't Do","Low","SMAN-10007 | Test data missing edge case scenarios (10007)"
120
- "10008","Open","","____ UNEXPECTED PRIORITY ____","SMAN-10008 | Memory leak in caching implementation (10008)"
121
- "10009","Wait on External","","Medium","SMAN-10009 | Inconsistent button styles across modules (10009)"
113
+ "ID","Status","Resolution","Priority","Summary","Source Ticket URL"
114
+ "10008","Open","","____ UNEXPECTED PRIORITY ____","SMAN-10008 | Memory leak in caching implementation (10008)","http://url/to/source/ticket/10008"
115
+ "10009","Wait on External","","Medium","SMAN-10009 | Inconsistent button styles across modules (10009)","http://url/to/source/ticket/10009"
122
116
  """
123
117
  When I run `ticket-replicator --load`
124
118
  Then it should fail with:
125
119
  """
126
- ERROR Object : Ticket::Replicator::FileLoader::LoadError: queue/20.transformed/sap_solution_manager_defects.csv:9: error while loading row:
120
+ ERROR Object : Ticket::Replicator::FileLoader::LoadError: queue/20.transformed/sap_solution_manager_defects.csv:2: error while loading row:
127
121
  Bad Request:
128
- #<CSV::Row id:"10008" status:"Open" resolution:"" priority:"____ UNEXPECTED PRIORITY ____" summary:"SMAN-10008 | Memory leak in caching implementation (10008)">
122
+ #<CSV::Row id:"10008" status:"Open" resolution:"" priority:"____ UNEXPECTED PRIORITY ____" summary:"SMAN-10008 | Memory leak in caching implementation (10008)" source_ticket_url:"http://url/to/source/ticket/10008">
129
123
  """
124
+
125
+
@@ -5,9 +5,8 @@ Feature: Transform SAP tickets to Jira format
5
5
 
6
6
  Background:
7
7
  Given the following environment variables have been set:
8
- | name | value |
9
- | TICKET_REPLICATOR_SOURCE_TICKET_BASE_URL_ERB | http://url/to/source/ticket/<%= source_id %> |
10
-
8
+ | name | value |
9
+ | TICKET_REPLICATOR_SOURCE_TICKET_URL | http://url/to/source/ticket/<%= source_ticket_id %> |
11
10
 
12
11
  Scenario: Transform valid ticket CSV
13
12
  Given a file named "config/ticket-replicator.mappings.yml" with:
@@ -62,17 +61,17 @@ Feature: Transform SAP tickets to Jira format
62
61
  When I successfully run `ticket-replicator --transform`
63
62
  Then a file named "queue/20.transformed/sap_solution_manager_defects.csv" should contain exactly:
64
63
  """
65
- "ID","Status","Resolution","Priority","Summary"
66
- "10001","Open","","High","SMAN-10001 | Login page randomly fails to load CSS assets (10001)"
67
- "10002","In Progress","","Highest","SMAN-10002 | Database deadlock during order processing (10002)"
68
- "10003","Resolved","Done","Medium","SMAN-10003 | Invalid date format in SOAP response (10003)"
69
- "10004","Closed","Done","Low","SMAN-10004 | App crashes when offline on Android 12 (10004)"
70
- "10005","Open","","High","SMAN-10005 | Session tokens not properly invalidated (10005)"
71
- "10006","In Review","","Medium","SMAN-10006 | Jenkins pipeline timeout on large builds (10006)"
72
- "10007","Closed","Won't Do","Low","SMAN-10007 | Test data missing edge case scenarios (10007)"
73
- "10008","Open","","Highest","SMAN-10008 | Memory leak in caching implementation (10008)"
74
- "10009","Blocked","","Medium","SMAN-10009 | Inconsistent button styles across modules (10009)"
75
- "10010","Resolved","Done","High","SMAN-10010 | Slow response time on product search API (10010)"
64
+ "ID","Status","Resolution","Priority","Summary","Source Ticket URL"
65
+ "10001","Open","","High","SMAN-10001 | Login page randomly fails to load CSS assets (10001)","http://url/to/source/ticket/10001"
66
+ "10002","In Progress","","Highest","SMAN-10002 | Database deadlock during order processing (10002)","http://url/to/source/ticket/10002"
67
+ "10003","Resolved","Done","Medium","SMAN-10003 | Invalid date format in SOAP response (10003)","http://url/to/source/ticket/10003"
68
+ "10004","Closed","Done","Low","SMAN-10004 | App crashes when offline on Android 12 (10004)","http://url/to/source/ticket/10004"
69
+ "10005","Open","","High","SMAN-10005 | Session tokens not properly invalidated (10005)","http://url/to/source/ticket/10005"
70
+ "10006","In Review","","Medium","SMAN-10006 | Jenkins pipeline timeout on large builds (10006)","http://url/to/source/ticket/10006"
71
+ "10007","Closed","Won't Do","Low","SMAN-10007 | Test data missing edge case scenarios (10007)","http://url/to/source/ticket/10007"
72
+ "10008","Open","","Highest","SMAN-10008 | Memory leak in caching implementation (10008)","http://url/to/source/ticket/10008"
73
+ "10009","Blocked","","Medium","SMAN-10009 | Inconsistent button styles across modules (10009)","http://url/to/source/ticket/10009"
74
+ "10010","Resolved","Done","High","SMAN-10010 | Slow response time on product search API (10010)","http://url/to/source/ticket/10010"
76
75
  """
77
76
 
78
77
  Scenario: Transform valid ticket Excel
@@ -127,24 +126,24 @@ Feature: Transform SAP tickets to Jira format
127
126
  When I successfully run `ticket-replicator --transform`
128
127
  Then a file named "queue/20.transformed/sap_solution_manager_defects.csv" should contain exactly:
129
128
  """
130
- "ID","Status","Resolution","Priority","Summary"
131
- "3000017049","Closed","Done","Medium","SMAN-3000017049 | Summary"
132
- "9400011377","Confirmed","","Low","SMAN-9400011377 | Summary"
133
- "3000016618","Defect Correction in Process","","Medium","SMAN-3000016618 | Summary"
134
- "9400013805","Deferred","","Medium","SMAN-9400013805 | Summary"
135
- "9400013816","Forwarded","","High","SMAN-9400013816 | Summary"
136
- "9400011382","In Process","","Medium","SMAN-9400011382 | Summary"
137
- "9400011393","Information Required","","Medium","SMAN-9400011393 | Summary"
138
- "9400011381","New","","Medium","SMAN-9400011381 | Summary"
139
- "3000016617","No Error","","Medium","SMAN-3000016617 | Summary"
140
- "9400011372","Open","","Highest","SMAN-9400011372 | Summary"
141
- "9400011403","Solution Proposal","","Medium","SMAN-9400011403 | Summary"
142
- "9400011380","Tester Action","","Medium","SMAN-9400011380 | Summary"
143
- "9400011705","Wait for Defect Correction","","Medium","SMAN-9400011705 | Summary"
144
- "9400011437","Wait on External","","Medium","SMAN-9400011437 | Summary"
145
- "9400011466","Withdrawn","Cannot Reproduce","Medium","SMAN-9400011466 | Summary"
146
- "3000017667","Closed","Done","Medium","SMAN-3000017667 | Summary"
147
- "3000018423","No Error","","Medium","SMAN-3000018423 | Summary"
129
+ "ID","Status","Resolution","Priority","Summary","Source Ticket URL"
130
+ "3000017049","Closed","Done","Medium","SMAN-3000017049 | Summary","http://url/to/source/ticket/3000017049"
131
+ "9400011377","Confirmed","","Low","SMAN-9400011377 | Summary","http://url/to/source/ticket/9400011377"
132
+ "3000016618","Defect Correction in Process","","Medium","SMAN-3000016618 | Summary","http://url/to/source/ticket/3000016618"
133
+ "9400013805","Deferred","","Medium","SMAN-9400013805 | Summary","http://url/to/source/ticket/9400013805"
134
+ "9400013816","Forwarded","","High","SMAN-9400013816 | Summary","http://url/to/source/ticket/9400013816"
135
+ "9400011382","In Process","","Medium","SMAN-9400011382 | Summary","http://url/to/source/ticket/9400011382"
136
+ "9400011393","Information Required","","Medium","SMAN-9400011393 | Summary","http://url/to/source/ticket/9400011393"
137
+ "9400011381","New","","Medium","SMAN-9400011381 | Summary","http://url/to/source/ticket/9400011381"
138
+ "3000016617","No Error","","Medium","SMAN-3000016617 | Summary","http://url/to/source/ticket/3000016617"
139
+ "9400011372","Open","","Highest","SMAN-9400011372 | Summary","http://url/to/source/ticket/9400011372"
140
+ "9400011403","Solution Proposal","","Medium","SMAN-9400011403 | Summary","http://url/to/source/ticket/9400011403"
141
+ "9400011380","Tester Action","","Medium","SMAN-9400011380 | Summary","http://url/to/source/ticket/9400011380"
142
+ "9400011705","Wait for Defect Correction","","Medium","SMAN-9400011705 | Summary","http://url/to/source/ticket/9400011705"
143
+ "9400011437","Wait on External","","Medium","SMAN-9400011437 | Summary","http://url/to/source/ticket/9400011437"
144
+ "9400011466","Withdrawn","Cannot Reproduce","Medium","SMAN-9400011466 | Summary","http://url/to/source/ticket/9400011466"
145
+ "3000017667","Closed","Done","Medium","SMAN-3000017667 | Summary","http://url/to/source/ticket/3000017667"
146
+ "3000018423","No Error","","Medium","SMAN-3000018423 | Summary","http://url/to/source/ticket/3000018423"
148
147
  """
149
148
 
150
149
  Scenario: Processing an invalid Excel file generates an error with relevant information
@@ -8,6 +8,10 @@ Feature: Transform and load extracted ticket queue
8
8
  | name |
9
9
  | TICKET_REPLICATOR_JIRA_PROJECT_KEY |
10
10
  | TICKET_REPLICATOR_JIRA_TICKET_TYPE_NAME |
11
+ And the following environment variables have been set:
12
+ | name | value |
13
+ | TICKET_REPLICATOR_SOURCE_TICKET_URL | http://url/to/source/ticket/<%= source_ticket_id %> |
14
+
11
15
  And a file named "config/ticket-replicator.mappings.yml" with:
12
16
  """
13
17
  field_mapping:
@@ -57,20 +61,20 @@ Feature: Transform and load extracted ticket queue
57
61
  And the current date time is "2025-05-10 07:52:00 UTC"
58
62
  When I successfully run `ticket-replicator --ticket-queue-transform-and-load`
59
63
  Then the Jira project should only have the following tickets:
60
- | status | resolution | priority | summary | source ticket url |
61
- | Closed | Done | Medium | SMAN-3000017049 \| Summary | https://example.com/defect/3000017049 |
62
- | Confirmed | | Low | SMAN-9400011377 \| Summary | https://example.com/defect/9400011377 |
63
- | Defect Correction in Process | | Medium | SMAN-3000016618 \| Summary | https://example.com/defect/3000016618 |
64
- | Deferred | | Medium | SMAN-9400013805 \| Summary | https://example.com/defect/9400013805 |
65
- | Forwarded | | High | SMAN-9400013816 \| Summary | https://example.com/defect/9400013816 |
66
- | In Process | | Medium | SMAN-9400011382 \| Summary | https://example.com/defect/9400011382 |
67
- | Information Required | | Medium | SMAN-9400011393 \| Summary | https://example.com/defect/9400011393 |
68
- | Withdrawn | Won't Do | Medium | SMAN-9400011381 \| Summary | https://example.com/defect/9400011381 |
69
- | No Error | | Medium | SMAN-3000016617 \| Summary | https://example.com/defect/3000016617 |
70
- | Open | | Highest | SMAN-9400011372 \| Summary | https://example.com/defect/9400011372 |
71
- | Withdrawn | Won't Do | Medium | SMAN-9400011466 \| Summary | https://example.com/defect/9400011466 |
72
- | Closed | Done | Medium | SMAN-3000017667 \| Summary | https://example.com/defect/3000017667 |
73
- | No Error | | Medium | SMAN-3000018423 \| Summary | https://example.com/defect/3000018423 |
64
+ | status | resolution | priority | summary | source_ticket_url |
65
+ | Closed | Done | Medium | SMAN-3000017049 \| Summary | http://url/to/source/ticket/3000017049 |
66
+ | Confirmed | | Low | SMAN-9400011377 \| Summary | http://url/to/source/ticket/9400011377 |
67
+ | Defect Correction in Process | | Medium | SMAN-3000016618 \| Summary | http://url/to/source/ticket/3000016618 |
68
+ | Deferred | | Medium | SMAN-9400013805 \| Summary | http://url/to/source/ticket/9400013805 |
69
+ | Forwarded | | High | SMAN-9400013816 \| Summary | http://url/to/source/ticket/9400013816 |
70
+ | In Process | | Medium | SMAN-9400011382 \| Summary | http://url/to/source/ticket/9400011382 |
71
+ | Information Required | | Medium | SMAN-9400011393 \| Summary | http://url/to/source/ticket/9400011393 |
72
+ | Withdrawn | Won't Do | Medium | SMAN-9400011381 \| Summary | http://url/to/source/ticket/9400011381 |
73
+ | No Error | | Medium | SMAN-3000016617 \| Summary | http://url/to/source/ticket/3000016617 |
74
+ | Open | | Highest | SMAN-9400011372 \| Summary | http://url/to/source/ticket/9400011372 |
75
+ | Withdrawn | Won't Do | Medium | SMAN-9400011466 \| Summary | http://url/to/source/ticket/9400011466 |
76
+ | Closed | Done | Medium | SMAN-3000017667 \| Summary | http://url/to/source/ticket/3000017667 |
77
+ | No Error | | Medium | SMAN-3000018423 \| Summary | http://url/to/source/ticket/3000018423 |
74
78
  # TODO: And the source ticket URL is found in the ticket descriptions of those tickets
75
79
  And a file named "queue/30.archived/2025-05-10.07h52m00.sap_solution_manager_defects.xlsx" should exist
76
80
  And the file named "queue/10.extracted/sap_solution_manager_defects.xlsx" should not exist anymore
@@ -20,6 +20,7 @@ module Ticket
20
20
 
21
21
  def run
22
22
  save_ticket
23
+ update_source_ticket_remote_link
23
24
  transition_ticket_to_the_expected_status
24
25
  end
25
26
 
@@ -70,6 +71,53 @@ module Ticket
70
71
  %i[summary priority]
71
72
  end
72
73
 
74
+ def update_source_ticket_remote_link
75
+ return unless source_ticket_link_needs_update?
76
+
77
+ ticket.source_ticket_link&.delete
78
+
79
+ ticket.jira_ticket.remotelink.build.save!(ticket_link_attributes)
80
+ end
81
+
82
+ # rubocop:disable Metrics/MethodLength
83
+ def source_ticket_link_needs_update?
84
+ existing_ticket_link_attributes = ticket.source_ticket_link&.attrs
85
+
86
+ object = existing_ticket_link_attributes&.[]("object")
87
+ existing_url = object&.[]("url")
88
+ existing_title = object&.[]("title")
89
+
90
+ filtered_existing_ticket_link_attributes = ticket_link_attributes(existing_url, existing_title)
91
+
92
+ log.debug do
93
+ <<~EOLOG
94
+
95
+ existing_ticket_link_attributes = #{existing_ticket_link_attributes.inspect}
96
+ filtered_existing_ticket_link_attributes = #{filtered_existing_ticket_link_attributes}
97
+ ticket_link_attributes: #{ticket_link_attributes.inspect}
98
+ EOLOG
99
+ end
100
+
101
+ filtered_existing_ticket_link_attributes != ticket_link_attributes
102
+ end
103
+
104
+ # rubocop:enable Metrics/MethodLength
105
+
106
+ def source_ticket_link_title
107
+ "Source Ticket #{id}"
108
+ end
109
+
110
+ def ticket_link_attributes(url = source_ticket_url, title = source_ticket_link_title)
111
+ self.class.build_ticket_link_attributes(url, title)
112
+ end
113
+
114
+ def self.build_ticket_link_attributes(
115
+ url, title, application_name = Ticket::SOURCE_TICKET_REMOTE_LINK_APPLICATION
116
+ )
117
+ { "object" => { "url" => url, "title" => title } }
118
+ .merge(application_name ? { "application" => { "name" => application_name } } : {})
119
+ end
120
+
73
121
  def transition_ticket_to_the_expected_status
74
122
  ticket.transition_to(status)
75
123
  end
@@ -115,7 +163,8 @@ module Ticket
115
163
 
116
164
  class << self
117
165
  def fields_to_load
118
- @fields_to_load ||= RowTransformer.fields_to_transform.collect { |field| field.to_s.downcase.to_sym }
166
+ @fields_to_load ||=
167
+ RowTransformer.fields_to_transform.collect { |field| field.to_s.parameterize.underscore.to_sym }
119
168
  end
120
169
  end
121
170
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "ticket/replicator/replicated_summary"
4
+ require "erb"
4
5
 
5
6
  module Ticket
6
7
  class Replicator
@@ -18,13 +19,25 @@ module Ticket
18
19
  end
19
20
 
20
21
  def run
21
- fields_to_transform.collect { |field| send("transformed_#{field.to_s.downcase}") }
22
+ fields_to_transform.collect { |field| send("transformed_#{field.to_s.parameterize.underscore}") }
22
23
  end
23
24
 
24
25
  def transformed_id
25
26
  remapped_field_extracted_value_for :id
26
27
  end
27
28
 
29
+ def transformed_source_ticket_url
30
+ source_ticket_url_builder(transformed_id)
31
+ end
32
+
33
+ def source_ticket_url_builder(source_ticket_id)
34
+ ERB.new(ticket_replicator_source_ticket_url).result(binding)
35
+ end
36
+
37
+ def ticket_replicator_source_ticket_url
38
+ ENV.fetch("TICKET_REPLICATOR_SOURCE_TICKET_URL") { |name| raise "#{name}: not set in environment!" }
39
+ end
40
+
28
41
  def transformed_status
29
42
  mapped_value_for :status
30
43
  end
@@ -50,7 +63,15 @@ module Ticket
50
63
  self.class.fields_to_transform
51
64
  end
52
65
 
66
+ def fields_to_remap
67
+ self.class.fields_to_remap
68
+ end
69
+
53
70
  def self.fields_to_transform
71
+ fields_to_remap + ["Source Ticket URL"]
72
+ end
73
+
74
+ def self.fields_to_remap
54
75
  %i[ID Status Resolution Priority Summary]
55
76
  end
56
77
 
@@ -77,7 +98,7 @@ module Ticket
77
98
  end
78
99
 
79
100
  def remapped_field_extracted_row
80
- @remapped_field_extracted_row ||= fields_to_transform.to_h do |field|
101
+ @remapped_field_extracted_row ||= fields_to_remap.to_h do |field|
81
102
  mapped_field_key = remapped_field_key(field)
82
103
  [mapped_field_key, extracted_row_value_for(mapped_field_key)]
83
104
  end
@@ -36,8 +36,25 @@ module Ticket
36
36
  ReplicatedSummary.parse(summary).source_id
37
37
  end
38
38
 
39
+ SOURCE_TICKET_REMOTE_LINK_APPLICATION = "Ticket Source"
40
+
41
+ def source_ticket_link
42
+ @source_ticket_link ||=
43
+ jira_ticket.remotelink.all.find do |link|
44
+ source_ticket_link_attributes(link)&.[]("application")&.[]("name") == SOURCE_TICKET_REMOTE_LINK_APPLICATION
45
+ end
46
+ end
47
+
48
+ def source_ticket_url
49
+ source_ticket_link_attributes(source_ticket_link)&.[]("object")&.[]("url")
50
+ end
51
+
39
52
  private
40
53
 
54
+ def source_ticket_link_attributes(link)
55
+ link&.attrs
56
+ end
57
+
41
58
  def cmp_values(object)
42
59
  object&.source_id
43
60
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ticket
4
4
  class Replicator
5
- VERSION = "1.0.0"
5
+ VERSION = "1.1.0"
6
6
  end
7
7
  end
@@ -43,7 +43,8 @@ module Ticket
43
43
 
44
44
  describe "#transformed_headers" do
45
45
  it "returns the headers" do
46
- expect(transformer.transformed_headers).to eq(%w[ID Status Resolution Priority Summary])
46
+ expect(transformer.transformed_headers)
47
+ .to eq(%w[ID Status Resolution Priority Summary] + ["Source Ticket URL"])
47
48
  end
48
49
  end
49
50
 
@@ -20,12 +20,13 @@ module Ticket
20
20
  end
21
21
 
22
22
  describe ".fields_to_load" do
23
- it { expect(described_class.fields_to_load).to eq(%i[id status resolution priority summary]) }
23
+ it { expect(described_class.fields_to_load).to eq(%i[id status resolution priority summary source_ticket_url]) }
24
24
  end
25
25
 
26
26
  describe "#run" do
27
27
  it do
28
28
  expect(loader).to receive(:save_ticket)
29
+ expect(loader).to receive(:update_source_ticket_remote_link)
29
30
  expect(loader).to receive(:transition_ticket_to_the_expected_status)
30
31
 
31
32
  loader.run
@@ -187,6 +188,30 @@ module Ticket
187
188
  end
188
189
  end
189
190
 
191
+ describe "#build_ticket_link_attributes" do
192
+ it "builds the link attributes with a default application" do
193
+ expect(described_class.send(:build_ticket_link_attributes,
194
+ "https://url/to/source/ticket/123", "Source Ticket 123"))
195
+ .to eq({ "object" => { "url" => "https://url/to/source/ticket/123", "title" => "Source Ticket 123" },
196
+ "application" => { "name" => "Ticket Source" } })
197
+ end
198
+
199
+ it "builds the link attributes with a custom application" do
200
+ expect(described_class.send(:build_ticket_link_attributes,
201
+ "https://url/to/source/ticket/123", "Source Ticket 123",
202
+ "A custom application"))
203
+ .to eq({ "object" => { "url" => "https://url/to/source/ticket/123", "title" => "Source Ticket 123" },
204
+ "application" => { "name" => "A custom application" } })
205
+ end
206
+
207
+ it "builds the link attributes with no application" do
208
+ expect(described_class.send(:build_ticket_link_attributes,
209
+ "https://url/to/source/ticket/123", "Source Ticket 123",
210
+ nil))
211
+ .to eq({ "object" => { "url" => "https://url/to/source/ticket/123", "title" => "Source Ticket 123" } })
212
+ end
213
+ end
214
+
190
215
  describe "#ticket_fields_need_to_be_updated?" do
191
216
  before do
192
217
  allow(loader).to receive_messages(ticket_previously_replicated?: ticket_previously_replicated?,
@@ -275,21 +300,139 @@ module Ticket
275
300
  end
276
301
  end
277
302
 
278
- describe "#transition_ticket_to_the_expected_status" do
303
+ # rubocop:disable Metrics/BlockLength
304
+ context "when caring for the source ticket link" do
305
+ def build_link(url, title, application_name = "Ticket Source")
306
+ RowLoader.build_ticket_link_attributes(url, title, application_name)
307
+ end
308
+
279
309
  let(:loader) { described_class.send(:new, jira_project, row) }
280
- let(:row) { { id: "123", status: "Testing" } }
310
+ let(:row) { { id: "123", source_ticket_url: "https://url/to/source/ticket/123" } }
311
+ let(:ticket) { instance_double(Ticket, jira_ticket: jira_ticket) }
312
+ let(:jira_ticket) { double(JIRA::Resource::Issue, remotelink: double(build: remotelink)) }
313
+ let(:remotelink) { instance_double(JIRA::Resource::Remotelink) }
281
314
 
282
- let(:ticket) { double(Ticket) }
315
+ before { allow(loader).to receive_messages(ticket: ticket) }
283
316
 
284
- it do
285
- expect(loader).to receive(:ticket).at_least(:once).and_return(ticket)
286
- expect(ticket).to receive(:transition_to).with("Testing")
317
+ let(:expected_link) { build_link("https://url/to/source/ticket/123", "Source Ticket 123") }
318
+
319
+ describe "#source_ticket_link_title" do
320
+ it { expect(loader.source_ticket_link_title).to eq("Source Ticket 123") }
321
+ end
322
+
323
+ describe "#update_source_ticket_remote_link" do
324
+ before do
325
+ allow(loader).to receive_messages(source_ticket_link_needs_update?: needs_update)
326
+ end
327
+
328
+ context "when the link does not exist" do
329
+ let(:needs_update) { true }
330
+
331
+ before { allow(ticket).to receive_messages(source_ticket_link: nil) }
332
+
333
+ it "setups the link" do
334
+ expect(remotelink).to receive(:save!).with(expected_link)
335
+
336
+ loader.update_source_ticket_remote_link
337
+ end
338
+ end
339
+
340
+ context "when the link needs to be updated" do
341
+ let(:needs_update) { true }
342
+ let(:source_ticket_link) { instance_double(JIRA::Resource::Remotelink) }
343
+
344
+ before { allow(ticket).to receive_messages(source_ticket_link: source_ticket_link) }
345
+
346
+ it "deletes the previous link and creates a new one" do
347
+ expect(source_ticket_link).to receive(:delete)
348
+ expect(remotelink).to receive(:save!).with(expected_link)
349
+
350
+ loader.update_source_ticket_remote_link
351
+ end
352
+ end
353
+
354
+ context "when the link does not need to be updated" do
355
+ let(:needs_update) { false }
287
356
 
288
- loader.transition_ticket_to_the_expected_status
357
+ it "no link creation happens" do
358
+ expect(remotelink).not_to receive(:save!)
359
+
360
+ loader.update_source_ticket_remote_link
361
+ end
362
+ end
363
+ end
364
+
365
+ describe "#source_ticket_link_needs_update?" do
366
+ before do
367
+ allow(ticket).to receive_messages(source_ticket_link: remote_link)
368
+ end
369
+
370
+ context "when link has yet to be created" do
371
+ let(:remote_link) { nil }
372
+
373
+ it { expect(loader.source_ticket_link_needs_update?).to be_truthy }
374
+ end
375
+
376
+ context "when link already exists" do
377
+ let(:remote_link) do
378
+ instance_double(JIRA::Resource::Remotelink,
379
+ attrs: source_ticket_link,
380
+ inspect: "<JIRA::Resource::Remotelink attrs: #{source_ticket_link}>")
381
+ end
382
+
383
+ context "when the link is not up to date" do
384
+ let(:source_ticket_link) { build_link(existing_url, existing_title) }
385
+
386
+ context "when url has changed" do
387
+ let(:existing_url) { "https://__OLD_URL__/to/source/ticket/1234" }
388
+ let(:existing_title) { "Source Ticket 123" }
389
+
390
+ it { expect(loader.source_ticket_link_needs_update?).to be_truthy }
391
+ end
392
+
393
+ context "when title has changed" do
394
+ let(:existing_url) { "https://url/to/source/ticket/123" }
395
+ let(:existing_title) { "__OLD TITLE__Source Ticket 1234" }
396
+
397
+ it { expect(loader.source_ticket_link_needs_update?).to be_truthy }
398
+ end
399
+ end
400
+
401
+ context "when the link is up to date" do
402
+ let(:existing_url) { "https://url/to/source/ticket/123" }
403
+ let(:existing_title) { "Source Ticket 123" }
404
+ let(:source_ticket_link) do
405
+ { "object" => {
406
+ "url" => "https://url/to/source/ticket/123",
407
+ "title" => "Source Ticket 123",
408
+ "icon" => { "title" => "link", "url16x16" => "https://url/to/source/ticket/123/icon" }
409
+ },
410
+ "application" => { "name" => "Ticket Source" },
411
+ "extra_attributes" =>
412
+ { "id" => 10_443, "self" => "https://url/to/source/ticket/123/remotelink/10443" } }
413
+ end
414
+
415
+ it("only checks the needed attributes") { expect(loader.source_ticket_link_needs_update?).to be_falsey }
416
+ end
417
+ end
418
+ end
419
+ # rubocop:enable Metrics/BlockLength
420
+
421
+ describe "#transition_ticket_to_the_expected_status" do
422
+ let(:loader) { described_class.send(:new, jira_project, row) }
423
+ let(:row) { { id: "123", status: "Testing" } }
424
+
425
+ let(:ticket) { double(Ticket) }
426
+
427
+ it do
428
+ expect(loader).to receive(:ticket).at_least(:once).and_return(ticket)
429
+ expect(ticket).to receive(:transition_to).with("Testing")
430
+
431
+ loader.transition_ticket_to_the_expected_status
432
+ end
289
433
  end
290
434
  end
291
435
  end
436
+ # rubocop:enable Metrics/ClassLength
292
437
  end
293
-
294
- # rubocop:enable Metrics/ClassLength
295
438
  end
@@ -33,11 +33,11 @@ RSpec.describe Ticket::Replicator::RowTransformer do
33
33
  allow(transformer).to receive(:transformed_status).and_return("Open")
34
34
  allow(transformer).to receive(:transformed_resolution).and_return("")
35
35
  allow(transformer).to receive(:transformed_priority).and_return("Low")
36
- allow(transformer).to receive(:transformed_team).and_return("Transformed Team")
37
36
  allow(transformer).to receive(:transformed_summary).and_return("transformed summary (123)")
37
+ allow(transformer).to receive(:transformed_source_ticket_url).and_return("url/to/source/ticket/123")
38
38
 
39
39
  expect(transformer.run)
40
- .to eq(["123", "Open", "", "Low", "transformed summary (123)"])
40
+ .to eq(["123", "Open", "", "Low", "transformed summary (123)", "url/to/source/ticket/123"])
41
41
  end
42
42
  end
43
43
 
@@ -84,6 +84,46 @@ RSpec.describe Ticket::Replicator::RowTransformer do
84
84
  end
85
85
  end
86
86
 
87
+ describe "#ticket_replicator_source_ticket_url" do
88
+ let(:extracted_row) { {} }
89
+
90
+ context "when the source ticket url is set in the environment" do
91
+ before do
92
+ allow(ENV)
93
+ .to receive(:fetch).with("TICKET_REPLICATOR_SOURCE_TICKET_URL").and_return("url/to/source/ticket")
94
+ end
95
+
96
+ it { expect(transformer.ticket_replicator_source_ticket_url).to eq("url/to/source/ticket") }
97
+ end
98
+
99
+ context "when the source ticket url is not set in the environment" do
100
+ it do
101
+ expect { transformer.ticket_replicator_source_ticket_url }
102
+ .to raise_error("TICKET_REPLICATOR_SOURCE_TICKET_URL: not set in environment!")
103
+ end
104
+ end
105
+ end
106
+
107
+ describe "#source_ticket_url_builder" do
108
+ let(:extracted_row) { {} }
109
+
110
+ before do
111
+ allow(transformer).to receive_messages(ticket_replicator_source_ticket_url: ticket_replicator_source_ticket_url)
112
+ end
113
+
114
+ context "when the URL has no syntax error" do
115
+ let(:ticket_replicator_source_ticket_url) { "http://url/to/source/ticket/<%= source_ticket_id %>" }
116
+
117
+ it { expect(transformer.source_ticket_url_builder("123")).to eq("http://url/to/source/ticket/123") }
118
+ end
119
+
120
+ context "when the URL has a syntax error" do
121
+ let(:ticket_replicator_source_ticket_url) { "http://url/to/source/ticket/<%= missing_variable %>" }
122
+
123
+ it { expect { transformer.source_ticket_url_builder("123") }.to raise_error(StandardError) }
124
+ end
125
+ end
126
+
87
127
  describe "using mappings" do
88
128
  before { allow(transformer).to receive_messages(mappings: mappings) }
89
129
 
@@ -126,6 +166,21 @@ RSpec.describe Ticket::Replicator::RowTransformer do
126
166
  end
127
167
  end
128
168
 
169
+ describe "#transformed_source_ticket_url" do
170
+ let(:remapped_field_extracted_row) { { "id" => "123" } }
171
+ let(:mappings) { {} }
172
+
173
+ before do
174
+ allow(transformer)
175
+ .to receive_messages(transformed_id: "123",
176
+ ticket_replicator_source_ticket_url: "url/to/source/ticket/<%= source_ticket_id %>")
177
+ end
178
+
179
+ it "returns the source ticket url" do
180
+ expect(transformer.transformed_source_ticket_url).to eq("url/to/source/ticket/123")
181
+ end
182
+ end
183
+
129
184
  describe "#transformed_summary" do
130
185
  let(:remapped_field_extracted_row) { { "id" => "426", "summary" => "a summary" } }
131
186
  let(:mappings) { {} }
@@ -136,7 +136,6 @@ module Ticket
136
136
  let(:jira_auto_tool) { instance_double(Jira::Auto::Tool) }
137
137
  let(:jira_client) { instance_double(JIRA::Client, Field: field_navigator) }
138
138
 
139
- # let(:jira_ticket) { OpenStruct.new(jira_ticket_properties_hash["fields"]) }
140
139
  let(:jira_ticket) { JIRA::Resource::Issue.build(jira_client, jira_ticket_properties_hash) }
141
140
 
142
141
  let(:ticket) { described_class.new(jira_auto_tool, jira_ticket) }
@@ -149,6 +148,56 @@ module Ticket
149
148
  it { expect(ticket.summary).to eq("SMAN-16384 | Login page randomly fails to load CSS assets (10001)") }
150
149
  end
151
150
 
151
+ def build_ticket_link(url, title, application = Ticket::SOURCE_TICKET_REMOTE_LINK_APPLICATION)
152
+ double(JIRA::Resource::Remotelink, attrs: RowLoader.build_ticket_link_attributes(url, title, application))
153
+ end
154
+
155
+ describe "#source_ticket_link" do
156
+ let(:ticket_links) { [double(JIRA::Resource::Remotelink, attrs: {}), source_ticket_jira_link].compact }
157
+
158
+ before { allow(jira_ticket).to receive_messages(remotelink: double(all: ticket_links)) }
159
+
160
+ let(:source_ticket_jira_link) { build_ticket_link("https://ur/to/source/ticket/16384", "Source Ticket 16384") }
161
+
162
+ it { expect(ticket.source_ticket_link).to eq(source_ticket_jira_link) }
163
+
164
+ context "when no source ticket is linked" do
165
+ let(:source_ticket_jira_link) { nil }
166
+ it { expect(ticket.source_ticket_link).to be_nil }
167
+ end
168
+
169
+ context "when the existing remote link is not a ticket source link (no application specified)" do
170
+ let(:source_ticket_jira_link) do
171
+ build_ticket_link("https://ur/to/source/ticket/16384", "Source Ticket 16384", nil)
172
+ end
173
+
174
+ it { expect(ticket.source_ticket_link).to be_nil }
175
+ end
176
+
177
+ context "when the existing remote link is not a ticket source link (another application specified)" do
178
+ let(:source_ticket_jira_link) do
179
+ build_ticket_link("https://ur/to/source/ticket/16384", "Source Ticket 16384", "Another application")
180
+ end
181
+
182
+ it { expect(ticket.source_ticket_link).to be_nil }
183
+ end
184
+ end
185
+
186
+ describe "#source_ticket_url" do
187
+ before do
188
+ allow(ticket).to receive_messages(source_ticket_link: source_ticket_jira_link)
189
+ end
190
+
191
+ let(:source_ticket_jira_link) { build_ticket_link("https://ur/to/source/ticket/16384", "Source Ticket 16384") }
192
+
193
+ it { expect(ticket.source_ticket_url).to eq("https://ur/to/source/ticket/16384") }
194
+
195
+ context "when no source ticket is linked" do
196
+ let(:source_ticket_jira_link) { nil }
197
+ it { expect(ticket.source_ticket_url).to be_nil }
198
+ end
199
+ end
200
+
152
201
  describe "replicated?" do
153
202
  let(:ticket) { described_class.new(jira_auto_tool, jira_ticket) }
154
203
  let(:jira_ticket) { double(JIRA::Resource::Issue, summary: actual_summary) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ticket-replicator
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christophe Broult