pact_broker 2.84.0 → 2.85.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/db/migrations/20210913_add_pending_to_verifications.rb +7 -0
  4. data/lib/pact/doc/markdown/consumer_contract_renderer.rb +3 -3
  5. data/lib/pact_broker/api/decorators/reason_decorator.rb +2 -2
  6. data/lib/pact_broker/api/decorators/verification_summary_decorator.rb +1 -2
  7. data/lib/pact_broker/api/middleware/configuration.rb +33 -0
  8. data/lib/pact_broker/api/pact_broker_urls.rb +5 -1
  9. data/lib/pact_broker/api/renderers/html_pact_renderer.rb +15 -11
  10. data/lib/pact_broker/api/resources/pact_versions_for_branch.rb +40 -0
  11. data/lib/pact_broker/api/resources/verifications.rb +5 -1
  12. data/lib/pact_broker/api.rb +1 -0
  13. data/lib/pact_broker/app.rb +2 -0
  14. data/lib/pact_broker/badges/service.rb +3 -1
  15. data/lib/pact_broker/configuration.rb +33 -29
  16. data/lib/pact_broker/domain/verification.rb +9 -0
  17. data/lib/pact_broker/domain/webhook.rb +22 -10
  18. data/lib/pact_broker/domain/webhook_request.rb +2 -2
  19. data/lib/pact_broker/index/service.rb +73 -22
  20. data/lib/pact_broker/locale/en.yml +2 -0
  21. data/lib/pact_broker/pacts/metadata.rb +4 -2
  22. data/lib/pact_broker/pacts/pact_publication.rb +37 -0
  23. data/lib/pact_broker/pacts/pact_publication_dataset_module.rb +39 -0
  24. data/lib/pact_broker/pacts/repository.rb +2 -1
  25. data/lib/pact_broker/test/http_test_data_builder.rb +1 -1
  26. data/lib/pact_broker/test/test_data_builder.rb +5 -1
  27. data/lib/pact_broker/ui/app.rb +7 -1
  28. data/lib/pact_broker/ui/controllers/dashboard.rb +80 -0
  29. data/lib/pact_broker/ui/controllers/groups.rb +23 -8
  30. data/lib/pact_broker/ui/helpers/url_helper.rb +5 -1
  31. data/lib/pact_broker/ui/view_models/index_item.rb +49 -7
  32. data/lib/pact_broker/ui/view_models/matrix_branch.rb +1 -1
  33. data/lib/pact_broker/ui/view_models/matrix_tag.rb +0 -1
  34. data/lib/pact_broker/ui/views/dashboard/show.haml +195 -0
  35. data/lib/pact_broker/ui/views/groups/show.html.erb +60 -14
  36. data/lib/pact_broker/ui/views/index/_dashboard_navbar.haml +7 -0
  37. data/lib/pact_broker/ui/views/index/_navbar.haml +0 -7
  38. data/lib/pact_broker/ui/views/index/show-with-tags.haml +3 -1
  39. data/lib/pact_broker/ui/views/index/show.haml +35 -13
  40. data/lib/pact_broker/ui/views/matrix/show.haml +7 -3
  41. data/lib/pact_broker/verifications/pseudo_branch_status.rb +2 -0
  42. data/lib/pact_broker/verifications/service.rb +1 -0
  43. data/lib/pact_broker/version.rb +1 -1
  44. data/lib/pact_broker/webhooks/event_listener.rb +3 -3
  45. data/lib/pact_broker/webhooks/execution_configuration.rb +16 -0
  46. data/lib/pact_broker/webhooks/execution_configuration_creator.rb +3 -0
  47. data/lib/pact_broker/webhooks/job.rb +1 -1
  48. data/lib/pact_broker/webhooks/trigger_service.rb +2 -2
  49. data/lib/pact_broker/webhooks/webhook_execution_result.rb +3 -7
  50. data/lib/pact_broker/webhooks/webhook_request_logger.rb +4 -12
  51. data/lib/pact_broker/webhooks/webhook_request_template.rb +6 -8
  52. data/pact_broker.gemspec +4 -0
  53. data/public/javascripts/index.js +75 -68
  54. data/public/javascripts/pact.js +14 -14
  55. data/public/stylesheets/index.css +1 -2
  56. data/public/stylesheets/pact.css +11 -0
  57. data/script/data/auto-create-things-for-tags.rb +2 -0
  58. data/script/data/branches.rb +2 -2
  59. data/script/data/contract-published-requiring-verification.rb +1 -1
  60. data/script/data/environments.rb +0 -0
  61. data/script/data/pending.rb +26 -0
  62. data/script/data/tags.rb +35 -0
  63. data/script/data/webhook.rb +22 -0
  64. data/script/seed.rb +50 -89
  65. data/spec/features/delete_pact_versions_for_branch_spec.rb +34 -0
  66. data/spec/fixtures/approvals/modifiable_resources.approved.json +4 -0
  67. data/spec/integration/app_spec.rb +6 -6
  68. data/spec/lib/pact/doc/markdown/consumer_contract_renderer_spec.rb +2 -2
  69. data/spec/lib/pact_broker/api/decorators/reason_decorator_spec.rb +2 -2
  70. data/spec/lib/pact_broker/api/decorators/verification_summary_decorator_spec.rb +2 -0
  71. data/spec/lib/pact_broker/api/decorators/webhook_execution_result_decorator_spec.rb +1 -1
  72. data/spec/lib/pact_broker/api/middleware/configuration_spec.rb +43 -0
  73. data/spec/lib/pact_broker/api/resources/verifications_spec.rb +2 -3
  74. data/spec/lib/pact_broker/api/resources/webhook_execution_spec.rb +1 -1
  75. data/spec/lib/pact_broker/badges/service_spec.rb +22 -0
  76. data/spec/lib/pact_broker/domain/webhook_request_spec.rb +2 -1
  77. data/spec/lib/pact_broker/domain/webhook_spec.rb +15 -5
  78. data/spec/lib/pact_broker/index/service_spec.rb +1 -5
  79. data/spec/lib/pact_broker/index/service_view_spec.rb +144 -0
  80. data/spec/lib/pact_broker/pacts/metadata_spec.rb +11 -2
  81. data/spec/lib/pact_broker/pacts/pact_publication_latest_verification_spec.rb +29 -0
  82. data/spec/lib/pact_broker/pacts/repository_spec.rb +15 -2
  83. data/spec/lib/pact_broker/ui/view_models/index_item_spec.rb +11 -8
  84. data/spec/lib/pact_broker/verifications/pseudo_branch_status_spec.rb +9 -1
  85. data/spec/lib/pact_broker/verifications/service_spec.rb +4 -2
  86. data/spec/lib/pact_broker/webhooks/job_spec.rb +4 -4
  87. data/spec/lib/pact_broker/webhooks/trigger_service_spec.rb +9 -5
  88. data/spec/lib/pact_broker/webhooks/webhook_request_logger_spec.rb +6 -12
  89. data/spec/lib/pact_broker/webhooks/webhook_request_template_spec.rb +3 -2
  90. data/spec/support/generated_markdown.md +3 -3
  91. metadata +47 -4
  92. data/spec/lib/pact_broker/api/resources/webhook_execution_result_spec.rb +0 -56
@@ -7,8 +7,12 @@ module PactBroker
7
7
 
8
8
  extend self
9
9
 
10
+ def dashboard_url consumer_name, provider_name, base_url = ""
11
+ "#{base_url}/dashboard/provider/#{provider_name}/consumer/#{consumer_name}"
12
+ end
13
+
10
14
  def group_url pacticipant_name, base_url = ""
11
- "#{base_url}/groups/#{ERB::Util.url_encode(pacticipant_name)}"
15
+ "#{base_url}/pacticipants/#{ERB::Util.url_encode(pacticipant_name)}"
12
16
  end
13
17
 
14
18
  def matrix_url consumer_name, provider_name, base_url = ""
@@ -18,7 +18,8 @@ module PactBroker
18
18
  :provider_version_branches,
19
19
  :latest_for_branch?,
20
20
  :consumer_version_environment_names,
21
- :provider_version_environment_names
21
+ :provider_version_environment_names,
22
+ :latest_verification
22
23
  ] => :relationship
23
24
 
24
25
 
@@ -57,6 +58,10 @@ module PactBroker
57
58
  PactBroker::Versions::AbbreviateNumber.call(provider_version_number)
58
59
  end
59
60
 
61
+ def display_latest_label?
62
+ consumer_version_latest_tag_names.empty? && @relationship.tag_names.empty?
63
+ end
64
+
60
65
  def latest?
61
66
  @relationship.latest?
62
67
  end
@@ -81,6 +86,10 @@ module PactBroker
81
86
  "#{pactigration_base_url(base_url, @relationship)}/latest"
82
87
  end
83
88
 
89
+ def dashboard_url
90
+ Helpers::URLHelper.dashboard_url(consumer_name, provider_name, base_url)
91
+ end
92
+
84
93
  def pact_url
85
94
  PactBroker::Api::PactBrokerUrls.pact_url(base_url, @relationship)
86
95
  end
@@ -145,7 +154,7 @@ module PactBroker
145
154
 
146
155
  def last_verified_date
147
156
  if @relationship.ever_verified?
148
- date = @relationship.latest_verification.execution_date
157
+ date = latest_verification.execution_date
149
158
  PactBroker::DateHelper.distance_of_time_in_words(date, DateTime.now) + " ago"
150
159
  else
151
160
  ""
@@ -166,10 +175,15 @@ module PactBroker
166
175
  when :success then "success"
167
176
  when :stale then "warning"
168
177
  when :failed then "danger"
178
+ when :failed_pending then "danger"
169
179
  else ""
170
180
  end
171
181
  end
172
182
 
183
+ def failed_and_pact_pending?
184
+ latest_verification&.failed_and_pact_pending?
185
+ end
186
+
173
187
  def warning?
174
188
  pseudo_branch_verification_status == "warning"
175
189
  end
@@ -179,7 +193,18 @@ module PactBroker
179
193
  when :success
180
194
  "Successfully verified by #{provider_name} (#{short_version_number(@relationship.latest_verification_provider_version_number)})"
181
195
  when :stale
182
- "Pact has changed since last successful verification by #{provider_name} (#{short_version_number(@relationship.latest_verification_provider_version_number)})"
196
+ # TODO when there are multiple tags/branches, the tag/branch shown may not be the relevant one, but
197
+ # it shouldn't happen very often. Can change this to "tag a or b"
198
+ desc = if @relationship.consumer_version_branches.any?
199
+ "from branch #{@relationship.consumer_version_branches.first} "
200
+ elsif @relationship.tag_names.any?
201
+ "with tag #{@relationship.tag_names.first} "
202
+ else
203
+ ""
204
+ end
205
+ "Pact #{desc}has changed since last successful verification by #{provider_name} (#{short_version_number(@relationship.latest_verification_provider_version_number)})"
206
+ when :failed_pending
207
+ "Verification by #{provider_name} (#{short_version_number(@relationship.latest_verification_provider_version_number)}) failed, but did not fail the build as the pact content was in pending state for that provider branch"
183
208
  when :failed
184
209
  "Verification by #{provider_name} (#{short_version_number(@relationship.latest_verification_provider_version_number)}) failed"
185
210
  else
@@ -204,17 +229,34 @@ module PactBroker
204
229
  end
205
230
  end
206
231
 
232
+ def show_menu?
233
+ !view_by_environment? && (@relationship.tag_names.any? || consumer_version_branches.any?)
234
+ end
235
+
207
236
  def base_url
208
237
  @options[:base_url]
209
238
  end
210
239
 
211
- def tagged_pacts
240
+ def pact_tags
212
241
  @relationship.tag_names.map do |tag|
213
242
  {
214
- tag: tag,
243
+ name: tag,
215
244
  deletionUrl: PactBroker::Api::PactBrokerUrls.tagged_pact_versions_url(consumer_name, provider_name, tag, base_url)
216
- }.to_json
217
- end
245
+ }
246
+ end.to_json
247
+ end
248
+
249
+ def pact_branches
250
+ consumer_version_branches.map do | branch_name |
251
+ {
252
+ name: branch_name,
253
+ deletionUrl: PactBroker::Api::PactBrokerUrls.pact_versions_for_branch_url(consumer_name, provider_name, branch_name, base_url)
254
+ }
255
+ end.to_json
256
+ end
257
+
258
+ def view_by_environment?
259
+ @options[:view] == "environment"
218
260
  end
219
261
 
220
262
  private
@@ -22,7 +22,7 @@ module PactBroker
22
22
  if branch_version.latest?
23
23
  "This is the latest version of #{pacticipant_name} from branch \"#{branch_version.branch_name}\"."
24
24
  else
25
- "A more recent version of #{pacticipant_name} from branch \"#{branch_version.branch_name}\" exists."
25
+ "This version of #{pacticipant_name} is from branch \"#{branch_version.branch_name}\". A more recent version from this branch exists."
26
26
  end
27
27
  end
28
28
 
@@ -6,7 +6,6 @@ module PactBroker
6
6
  module UI
7
7
  module ViewDomain
8
8
  class MatrixTag
9
-
10
9
  include PactBroker::Api::PactBrokerUrls
11
10
 
12
11
  attr_reader :name, :pacticipant_name, :version_number
@@ -0,0 +1,195 @@
1
+ %body
2
+ != render :haml, :'index/_css_and_js', :layout => false
3
+ .container
4
+ != render :haml, :'index/_dashboard_navbar', :layout => false, locals: {tag_toggle: nil, base_url: base_url}
5
+
6
+ %div.mt-4
7
+ .row
8
+ .col
9
+ .row.my-0
10
+ .col.my-0{ style: "text-transform: uppercase; font-size: 70%"}
11
+ Consumer
12
+ .row.my-0
13
+ .col.my-0
14
+ %h2.page-header{ style: "margin-top: 0" }
15
+ = consumer_name
16
+ .col
17
+ .row
18
+ .col.my-0{ style: "text-transform: uppercase; font-size: 70%"}
19
+ Provider
20
+ .row.my-0
21
+ .col.my-0
22
+ %h2.page-header{ style: "margin-top: 0" }
23
+ = provider_name
24
+
25
+
26
+ - unless errors.blank?
27
+ - errors.each do | error |
28
+ %div.alert.alert-danger
29
+ = error
30
+
31
+ - if consumer && provider
32
+ %form
33
+ %div.mt-4.mb-2
34
+ %label.mr-2
35
+ View pacts by:
36
+ %div.form-check.form-check-inline
37
+ %input.form-check-input{ type: "radio", name: "view", id: "by_branch", value: "branch", checked: view == "branch" }
38
+ %label.form-check-label{ for:"by_branch"} branches
39
+ %div.form-check.form-check-inline
40
+ %input.form-check-input{ type: "radio", name: "view", id: "by_tag", value: "tag", checked: view == "tag"}
41
+ %label.form-check-label{ for:"by_tag"} tags
42
+ %div.form-check.form-check-inline
43
+ %input.form-check-input{ type: "radio", name: "view", id: "by_environment", value: "environment", checked: view == "environment"}
44
+ %label.form-check-label{ for:"by_environment"} environments
45
+ %div.form-check.form-check-inline
46
+ %input.form-check-input{ type: "radio", name: "view", id: "by_all", value: "all", checked: view == "all"}
47
+ %label.form-check-label{ for:"by_all"} no filter
48
+ %input{ type: "hidden", name: "page", value: page_number }
49
+ %input{ type: "hidden", name: "pageSize", value: page_size }
50
+
51
+ %table.table.table-bordered.table-striped{ id: 'relationships' }
52
+ %thead
53
+ %tr
54
+ %th.consumer-version-number
55
+ Consumer<br>Version
56
+ %span.sort-icon.relationships-sort
57
+ %th.provider-version-number
58
+ Provider<br>Version
59
+ %span.sort-icon.relationships-sort
60
+ %th.pact{ style: 'width: 40px' }
61
+ %th
62
+ Published
63
+ %span.sort-icon.relationships-sort
64
+ %th
65
+ Webhook<br>status
66
+ %th
67
+ Last<br>verified
68
+ %span.sort-icon.relationships-sort
69
+ %th
70
+ %tbody
71
+
72
+ - index_items.each do | index_item |
73
+ %tr{'data-pact-versions-url': index_item.pact_versions_url,
74
+ 'data-consumer-name': index_item.consumer_name,
75
+ 'data-provider-name': index_item.provider_name,
76
+ 'data-integration-url': index_item.integration_url,
77
+ 'data-pact-tags': index_item.pact_tags,
78
+ 'data-pact-branches': index_item.pact_branches,
79
+ 'data-view': view
80
+ }
81
+ %td.consumer-version-number{"data-text": index_item.consumer_version_order}
82
+ %div.clippable{"data-clippable": index_item.consumer_version_number}
83
+ = index_item.display_consumer_version_number
84
+ - if index_item.display_consumer_version_number
85
+ %button.clippy.invisible{ title: "Copy to clipboard" }
86
+ %span.copy-icon
87
+ - if view == "branch" || view == "all"
88
+ - index_item.consumer_version_branches.each do | branch_name |
89
+ %div{"class": "tag badge badge-dark"}
90
+ = "branch: " + branch_name
91
+ - if view == "tag" || view == "all"
92
+ - index_item.consumer_version_latest_tag_names.each do | tag_name |
93
+ .tag.badge.badge-primary
94
+ = "tag: " + tag_name
95
+ - if view == "environment" || view == "all"
96
+ - index_item.consumer_version_environment_names.each do | environment_name |
97
+ .tag.badge.badge-success
98
+ = "env: " + environment_name
99
+ - if view == "all" && index_item.display_latest_label? && index_item.latest?
100
+ .tag.badge.bg-light
101
+ latest
102
+ %td.provider-version-number
103
+ %div.clippable{"data-clippable": index_item.provider_version_number}
104
+ = index_item.display_provider_version_number
105
+ - if index_item.display_provider_version_number
106
+ %button.clippy.invisible{ title: "Copy to clipboard" }
107
+ %span.copy-icon
108
+ - if view == "branch" || view == "all"
109
+ - index_item.provider_version_branches.each do | branch_name |
110
+ %div{"class": "tag badge badge-dark"}
111
+ = "branch: " + branch_name
112
+ - if view == "tag" || view == "all"
113
+ - index_item.provider_version_latest_tag_names.each do | tag_name |
114
+ .tag.badge.badge-primary
115
+ = "tag: " + tag_name
116
+ - if view == "environment" || view == "all"
117
+ - index_item.provider_version_environment_names.each do | environment_name |
118
+ .tag.badge.badge-success
119
+ = "env: " + environment_name
120
+ %td.pact
121
+ %span.pact
122
+ %a{ href: index_item.pact_url, title: "View pact" }
123
+ %span.pact-matrix
124
+ %a{ href: index_item.pact_matrix_url, title: "View pact matrix" }
125
+ %td{"data-text": index_item.publication_date_of_latest_pact_order}
126
+ = index_item.publication_date_of_latest_pact.gsub("about ", "")
127
+ %td{ class: "table-#{index_item.webhook_status}" }
128
+ - if index_item.show_webhook_status?
129
+ %a{ href: index_item.webhook_url }
130
+ = index_item.webhook_label
131
+
132
+ %td{ class: "table-#{index_item.pseudo_branch_verification_status}", title: index_item.verification_tooltip, "data-toggle": "tooltip", "data-placement": "left" }
133
+ %div
134
+ = index_item.last_verified_date.gsub("about ", "")
135
+ - if index_item.warning?
136
+ %span.warning-icon{ 'aria-hidden': true }
137
+ - if index_item.failed_and_pact_pending?
138
+ %div
139
+ (pact pending)
140
+ %td
141
+ - if index_item.show_menu?
142
+ %span.integration-settings.kebab-horizontal{ 'aria-hidden': true }
143
+
144
+ %div.pagination.text-center
145
+
146
+ - pagination_locals = { page_number: page_number, page_size: page_size, pagination_record_count: pagination_record_count, current_page_size: current_page_size }
147
+ != render :haml, :'index/_pagination', :layout => false, locals: pagination_locals
148
+
149
+ :javascript
150
+ $(function(){
151
+ $("#relationships").tablesorter();
152
+ });
153
+
154
+ $(document).ready(function(){
155
+ initializeClipper(".clippable");
156
+
157
+ $("span.pact a").load("#{base_url}/images/doc-text.svg");
158
+ $("span.pact-matrix a").load("#{base_url}/images/doc-matrix.svg");
159
+ $('td[data-toggle="tooltip"]').each(function(index, td){
160
+ //appended tooltip div screws up table if it's appended after a
161
+ //td, so need to append it to a div
162
+ $(td).tooltip({container: $(td).first()});
163
+ });
164
+ });
165
+
166
+ $(".reset-search").on("click", function() {
167
+ const url = new URL(window.location)
168
+ url.searchParams.delete('search')
169
+ window.location = url.toString();
170
+ })
171
+
172
+ $(".submit-search").on("click", function() {
173
+ search = $("#search").val();
174
+ const url = new URL(window.location)
175
+ url.searchParams.set('search', search)
176
+ window.location = url.toString();
177
+ })
178
+
179
+ $(".search").keypress(function(event) {
180
+ const enterKeyCode = 13;
181
+
182
+ const key = event.which;
183
+ if (key === enterKeyCode) {
184
+ event.preventDefault();
185
+ search = $("#search").val();
186
+ const url = new URL(window.location)
187
+ url.searchParams.set('search', search)
188
+ window.location = url.toString();
189
+ }
190
+ })
191
+
192
+ $("[name*='view']").change(function(event){
193
+ $("[name='page']").attr('disabled','disabled');
194
+ $(this.form).submit()
195
+ })
@@ -1,17 +1,25 @@
1
1
  <% require 'sanitize' %>
2
+ <% require 'pact_broker/version' %>
3
+ <% version = PactBroker::VERSION %>
2
4
 
3
5
  <!DOCTYPE html>
4
6
  <html lang="en">
5
7
  <head>
6
8
  <meta charset="utf-8">
7
- <title>Network Graph</title>
9
+ <title><%= pacticipant_name %></title>
8
10
  <link rel='shortcut icon' href='<%= base_url %>/favicon.ico' type='image/x-icon'/>
9
11
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
10
12
  <meta name="description" content="">
11
13
  <meta name="author" content="Duncan Alexander">
12
14
  <script type="text/javascript" src="<%= base_url %>/javascripts/d3.v3.js.pagespeed.ce.dFNRrGTALe.js"></script>
15
+ <link href="<%= base_url %>/css/bootstrap.min.css?v=<%= version %>" rel="stylesheet">
16
+ <script src="<%= base_url %>/javascripts/jquery-3.5.1.min.js?v=<%= version %>" type='text/javascript'></script>
17
+ <script src="<%= base_url %>/js/bootstrap.bundle.min.js?v=<%= version %>" type='text/javascript'></script>
13
18
  <style>
14
- body{
19
+ body {
20
+ padding-top: 10px;
21
+ }
22
+ svg{
15
23
  font-family:"Helvetica Neue",Arial,sans-serif;
16
24
  font-size:.95em;
17
25
  font-weight:bold;
@@ -23,6 +31,11 @@ body{
23
31
  .relationship {
24
32
  cursor: pointer;
25
33
  }
34
+ .page-header {
35
+ padding-bottom: 9px;
36
+ margin: 40px 0 20px;
37
+ border-bottom: 1px solid #eee;
38
+ }
26
39
  </style>
27
40
  <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
28
41
  <!--[if lt IE 9]>
@@ -31,20 +44,52 @@ body{
31
44
  <!-- developed by Duncan Alexander - hypothete.com -->
32
45
  </head>
33
46
  <body>
34
- <h1>Network graph of <%= escape_html(pacticipant_name) %> relationships</h1>
35
-
36
- <% if repository_url %>
37
- <p>Repository URL:
38
-
39
- <%
40
- repository_link = "<a href=\"#{repository_url}\">#{repository_url}</a>"
41
- %>
42
-
43
- <%= Sanitize.fragment(repository_link, Sanitize::Config::BASIC) %>
44
-
47
+ <div class="container">
48
+ <nav aria-label="breadcrumb">
49
+ <ol class="breadcrumb">
50
+ <li class="breadcrumb-item" style="margin:0;"><a href="<%= base_url %>/">Home</a></li>
51
+ <li class="breadcrumb-item active" aria-current="page"><%= escape_html(pacticipant_name) %></li>
52
+ </ol>
53
+ </nav>
54
+ <h1 class="page-header"><%= escape_html(pacticipant_name) %></h1>
55
+
56
+ <ul class="nav nav-tabs">
57
+ <li class="nav-item">
58
+ <a class="nav-link <%= tab == "details" ? "active" : "" %>" href="<%= details_url %>">Details</a>
59
+ </li>
60
+ <li class="nav-item">
61
+ <a class="nav-link <%= tab == "network" ? "active" : "" %>" href="<%= network_url %>">Network Graph</a>
62
+ </li>
63
+ </ul>
64
+
65
+ <% if tab == "details" %>
66
+ <div class="container mt-5">
67
+ <div class="row">
68
+ <div class="col col-md-2">
69
+ Main branch:
70
+ </div>
71
+ <div class="col col-md-6">
72
+ <%= pacticipant.main_branch %>
73
+ </div>
74
+ </div>
75
+ <div class="row">
76
+ <div class="col col-md-2">
77
+ Repository URL:
78
+ </div>
79
+ <div class="col col-md-6">
80
+ <% if repository_url %>
81
+ <%
82
+ repository_link = "<a href=\"#{repository_url}\">#{repository_url}</a>"
83
+ %>
84
+
85
+ <%= Sanitize.fragment(repository_link, Sanitize::Config::BASIC) %>
86
+ <% end %>
87
+ </div>
88
+ </div>
89
+ </div>
45
90
  <% end %>
46
91
 
47
-
92
+ <% if tab == "network" %>
48
93
  <script type="text/javascript">
49
94
  var windowWidth, windowHeight, svg;
50
95
 
@@ -421,5 +466,6 @@ window.onload = function() {
421
466
 
422
467
  });
423
468
  }</script>
469
+ <% end %>
424
470
  </body>
425
471
  </html>