pairer 1.0.0 → 1.2.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: e8774463f2c0328260af7daf81b8c36eb0f019c841777e163fcb59ca7e799ba0
4
- data.tar.gz: fdbffea2f68177d3b6d74ae71a6fd32e1a7a967118a624296091d9f1a105f0ca
3
+ metadata.gz: 03422d4cc7e2e8f77e732ab5d8c961a12ac1eca81f239ff1686225a03b044b96
4
+ data.tar.gz: 7093748edb2faae592a62dc81dbde13d123914cfb2537b78afe1149dd31c4b16
5
5
  SHA512:
6
- metadata.gz: 3f39555a2253f14613bee72894a8b96ed255d9d482daeb5e713099c0fa13bdb6038780c206235ae93d819b987fa8eaa8b86258346c90789aaf8f4a9448771704
7
- data.tar.gz: cb2d36c125a60124159a8ed48835b92e52554fa2e253817b5a60edb19535b4e2a164616c7f42454174a884d3feb0bbf51810c6aa7d2c909597d076fa7f90c4f0
6
+ metadata.gz: a6f6cfea6e12f5efd07b1d7a118e13f14196406bc3ef25341fef7974fff6d6f650dca5fc80b216222fcd754eaa3a89f3495c8b4942403c5480bcb5161fc0c23f
7
+ data.tar.gz: ab9182ccfc3c737f39247c1a10fac7a47d12db3f71749e9d337ad374359a99e583937d8cd0ada65d237e8f898799a3e59cb10d16b291e4760e6248bae776050a
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Pairer
2
2
 
3
- <a href='https://github.com/westonganger/pairer/actions' target='_blank'><img src="https://github.com/westonganger/pairer/workflows/Tests/badge.svg" style="max-width:100%;" height='21' style='border:0px;height:21px;' border='0' alt="CI Status"></a>
3
+ <a href="https://badge.fury.io/rb/pairer" target="_blank"><img height="21" style='border:0px;height:21px;' border='0' src="https://badge.fury.io/rb/pairer.svg" alt="Gem Version"></a>
4
+ <a href='https://github.com/westonganger/pairer/actions' target='_blank'><img src="https://github.com/westonganger/pairer/actions/workflows/test.yml/badge.svg?branch=master" style="max-width:100%;" height='21' style='border:0px;height:21px;' border='0' alt="CI Status"></a>
5
+ <a href='https://rubygems.org/gems/pairer' target='_blank'><img height='21' style='border:0px;height:21px;' src='https://img.shields.io/gem/dt/pairer?color=brightgreen&label=Rubygems%20Downloads' border='0' alt='RubyGems Downloads' /></a>
4
6
 
5
7
  Pairer is a Rails app/engine to help you to easily generate and rotate pairs within a larger group. For example its great for pair programming teams where you want to work with someone new everyday.
6
8
 
@@ -12,11 +14,20 @@ Each organization has many boards. Within each board you can create people and r
12
14
 
13
15
  Developed as a Rails engine. So you can add to any existing app or create a brand new app with the functionality.
14
16
 
17
+ First add the gem to your Gemfile
18
+
15
19
  ```ruby
16
20
  ### Gemfile
17
21
  gem 'pairer'
18
22
  ```
19
23
 
24
+ Then install and run the database migrations
25
+
26
+ ```sh
27
+ bundle install
28
+ bundle exec rake pairer:install:migrations
29
+ bundle exec rake db:migrate
30
+ ```
20
31
 
21
32
  #### Option A: Mount to a path
22
33
 
@@ -120,11 +131,10 @@ Run server using: `bin/dev` or `cd test/dummy/; rails s`
120
131
  bundle exec rspec
121
132
  ```
122
133
 
123
- We can locally test different versions of Rails using `ENV['RAILS_VERSION']` and different database gems using `ENV['DB_GEM']`
134
+ We can locally test different versions of Rails using `ENV['RAILS_VERSION']`
124
135
 
125
136
  ```
126
137
  export RAILS_VERSION=7.0
127
- export DB_GEM=sqlite3
128
138
  bundle install
129
139
  bundle exec rspec
130
140
  ```
data/Rakefile CHANGED
@@ -12,7 +12,6 @@ load 'rails/tasks/statistics.rake'
12
12
  require 'bundler/gem_tasks'
13
13
 
14
14
  require 'rspec/core/rake_task'
15
-
16
15
  RSpec::Core::RakeTask.new(:spec)
17
16
 
18
17
  task test: [:spec]
@@ -1,6 +1,3 @@
1
- //= require rails-ujs
2
- //= require bootstrap-sprockets
3
-
4
1
  window.init = function(){
5
2
  $('form').attr('autocomplete', 'off');
6
3
 
@@ -0,0 +1,65 @@
1
+ :root{
2
+ --navbar-height: 3.5em;
3
+ }
4
+
5
+ .navbar{
6
+ font-size: 1.1em;
7
+ min-height: var(--navbar-height);
8
+ }
9
+
10
+ .navbar .navbar-brand{
11
+ font-size: 1.5em;
12
+ font-family: monospace;
13
+ padding: 8px 15px;
14
+ }
15
+
16
+ .navbar .navbar-brand:hover{
17
+ color: initial;
18
+ }
19
+
20
+ .navbar .navbar-nav li a, .navbar-brand{
21
+ line-height: var(--my-navbar-height)/2;
22
+ }
23
+
24
+ .alert .close{
25
+ opacity: 0.6;
26
+ }
27
+
28
+ .alert .close:hover{
29
+ opacity: 0.8;
30
+ }
31
+
32
+ h1, h2, h3, h4, h5{
33
+ color: #424242;
34
+ }
35
+
36
+ body{
37
+ background-color: #F5F6F6;
38
+ padding-top: 80px;
39
+ padding-bottom: 20px;
40
+ }
41
+
42
+ label{
43
+ margin-right: 10px;
44
+ }
45
+
46
+ footer{
47
+ position: fixed;
48
+ text-align: center;
49
+ bottom: 10px;
50
+ right: 0;
51
+ left: 0;
52
+ }
53
+
54
+ .btn-default{
55
+ border-color: gray;
56
+ background-color: gray;
57
+ background-image: none;
58
+ }
59
+
60
+ @media(max-width: 768px){
61
+ .form-inline .form-control{
62
+ display: inline-block;
63
+ width: initial;
64
+ }
65
+ }
@@ -0,0 +1,79 @@
1
+ .no-gutter{
2
+ padding:0 !important;
3
+ }
4
+ .bold{
5
+ font-weight:bold !important;
6
+ }
7
+ .no-bold{
8
+ font-weight:400 !important;
9
+ }
10
+ .italic{
11
+ font-style: italic !important;
12
+ }
13
+ .underline{
14
+ text-decoration:underline !important;
15
+ }
16
+ .space-left{
17
+ margin-left:5px !important;
18
+ }
19
+ .space-left2{
20
+ margin-left:10px !important;
21
+ }
22
+ .space-left3{
23
+ margin-left:15px !important;
24
+ }
25
+ .space-left4{
26
+ margin-left:20px !important;
27
+ }
28
+ .space-left5{
29
+ margin-left:30px !important;
30
+ }
31
+ .space-right{
32
+ margin-right:5px !important;
33
+ }
34
+ .space-right2{
35
+ margin-right:10px !important;
36
+ }
37
+ .space-right3{
38
+ margin-right:15px !important;
39
+ }
40
+ .space-right4{
41
+ margin-right:20px !important;
42
+ }
43
+ .space-right5{
44
+ margin-right:30px !important;
45
+ }
46
+ .space-above{
47
+ margin-top:5px !important;
48
+ }
49
+ .space-above2{
50
+ margin-top:10px !important;
51
+ }
52
+ .space-above3{
53
+ margin-top:20px !important;
54
+ }
55
+ .space-above4{
56
+ margin-top:30px !important;
57
+ }
58
+ .space-above5{
59
+ margin-top:40px !important;
60
+ }
61
+ .space-below{
62
+ margin-bottom:5px !important;
63
+ }
64
+ .space-below2{
65
+ margin-bottom:10px !important;
66
+ }
67
+ .space-below3{
68
+ margin-bottom:15px !important;
69
+ }
70
+ .space-below4{
71
+ margin-bottom:20px !important;
72
+ }
73
+ .space-below5{
74
+ margin-bottom:30px !important;
75
+ }
76
+ .table-text-center th,
77
+ .table-text-center td{
78
+ text-align:center !important;
79
+ }
@@ -0,0 +1,9 @@
1
+ module Pairer
2
+ module ApplicationCable
3
+ class Channel < ActionCable::Channel::Base
4
+ end
5
+
6
+ class Connection < ActionCable::Connection::Base
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Pairer
2
+ class BoardChannel < ApplicationCable::Channel
3
+ def subscribed
4
+ stream_from "board_#{params[:id]}"
5
+ end
6
+ end
7
+ end
@@ -74,13 +74,15 @@ module Pairer
74
74
  else
75
75
  saved = @board.update(params.require(:board).permit(:name, :password, :group_size, :num_iterations_to_track, roles: []))
76
76
  end
77
-
77
+
78
78
  if saved
79
79
  if params.dig(:board, :password)
80
80
  flash.notice = "Password updated."
81
81
  else
82
82
  flash.notice = "Board updated."
83
83
  end
84
+
85
+ broadcast_changes
84
86
  else
85
87
  if @board.errors[:num_iterations_to_track].present?
86
88
  flash.alert = "Number of Iterations to Track #{@board.errors[:num_iterations_to_track].first}"
@@ -100,14 +102,19 @@ module Pairer
100
102
 
101
103
  def shuffle
102
104
  @board.shuffle!
105
+ broadcast_changes
103
106
  redirect_to(action: :show)
104
107
  end
105
108
 
106
109
  def create_person
107
110
  @person = @board.people.create(name: params[:name])
108
111
 
112
+ if @person.persisted?
113
+ broadcast_changes
114
+ end
115
+
109
116
  if request.format.js?
110
- render
117
+ render_js_page_replace
111
118
  else
112
119
  redirect_to(action: :show)
113
120
  end
@@ -117,9 +124,10 @@ module Pairer
117
124
  @person = @board.people.find_by!(public_id: params.require(:person_id))
118
125
 
119
126
  @person.toggle!(:locked)
127
+ broadcast_changes
120
128
 
121
129
  if request.format.js?
122
- render
130
+ render_js_page_replace
123
131
  else
124
132
  redirect_to(action: :show)
125
133
  end
@@ -129,56 +137,104 @@ module Pairer
129
137
  @person = @board.people.find_by!(public_id: params.require(:person_id))
130
138
 
131
139
  @person.destroy!
140
+ broadcast_changes
132
141
 
133
142
  redirect_to(action: :show)
134
143
  end
135
144
 
136
145
  def create_group
137
146
  @group = @board.groups.create!(board_iteration_number: @board.current_iteration_number)
147
+ broadcast_changes
138
148
 
139
149
  if request.format.js?
140
- render
150
+ render_js_page_replace
141
151
  else
142
152
  redirect_to(action: :show)
143
153
  end
144
154
  end
145
155
 
146
156
  def lock_group
147
- @group = @board.groups.find_by!(public_id: params.require(:group_id))
157
+ @group = @board.current_groups.find_by!(public_id: params.require(:group_id))
148
158
 
149
159
  @group.toggle!(:locked)
160
+ broadcast_changes
150
161
 
151
162
  if request.format.js?
152
- render
163
+ render_js_page_replace
153
164
  else
154
165
  redirect_to(action: :show)
155
166
  end
156
167
  end
157
168
 
158
169
  def delete_group
159
- @group = @board.groups.find_by!(public_id: params.require(:group_id))
170
+ @group = @board.current_groups.find_by!(public_id: params.require(:group_id))
160
171
 
161
172
  @group.destroy!
173
+ broadcast_changes
162
174
 
163
175
  if request.format.js?
164
- render
176
+ render_js_page_replace
165
177
  else
166
178
  redirect_to(action: :show)
167
179
  end
168
180
  end
169
181
 
170
182
  def update_group
171
- @group = @board.groups.find_by!(public_id: params.require(:group_id))
183
+ @group = @board.current_groups.find_by!(public_id: params.require(:group_id))
184
+
185
+ person_ids_was = @group.person_ids_array
186
+ roles_was = @group.roles_array
172
187
 
173
188
  attrs = {
174
189
  person_ids: (params[:person_ids] if params[:person_ids].present?),
175
190
  roles: (params[:roles] if params[:roles].present?),
176
191
  }.compact
177
192
 
178
- @group.update!(attrs)
193
+ @group.assign_attributes(attrs)
194
+
195
+ new_person_ids = @group.person_ids_array - person_ids_was
196
+ new_roles = @group.roles_array - roles_was
197
+
198
+ if @group.changed?
199
+ changed = true
200
+
201
+ if params[:removal] == "true"
202
+ @group.save!
203
+ else
204
+ ActiveRecord::Base.transaction do
205
+ @group.save!
206
+
207
+ if new_person_ids.any?
208
+ @board.current_groups.detect do |g|
209
+ next if g.id == @group.id
210
+
211
+ if g.person_ids_array.intersection(new_person_ids).any?
212
+ g.person_ids = g.person_ids_array - @group.person_ids_array
213
+ g.save!
214
+ true
215
+ end
216
+ end
217
+ end
218
+
219
+ if new_roles.any?
220
+ @board.current_groups.detect do |g|
221
+ next if g.id == @group.id
222
+
223
+ if g.roles_array.intersection(new_roles).any?
224
+ g.roles = g.roles_array - @group.roles_array
225
+ g.save!
226
+ true
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
232
+
233
+ broadcast_changes
234
+ end
179
235
 
180
236
  if request.format.js?
181
- render
237
+ render_js_page_replace
182
238
  else
183
239
  redirect_to(action: :show)
184
240
  end
@@ -191,12 +247,37 @@ module Pairer
191
247
  redirect_to(action: :index)
192
248
  end
193
249
 
194
- @board = Pairer::Board.find_by!(org_id: session[:pairer_current_org_id], public_id: params[:id])
250
+ @board = Pairer::Board
251
+ .includes(:people, :groups)
252
+ .find_by!(org_id: session[:pairer_current_org_id], public_id: params[:id])
195
253
  end
196
254
 
197
255
  def people_by_id
198
256
  @people_by_id ||= @board.people.map{|x| [x.to_param, x] }.to_h
199
257
  end
200
258
 
259
+ def broadcast_changes
260
+ ActionCable.server.broadcast(
261
+ "board_#{@board.public_id}",
262
+ {
263
+ action: "reload",
264
+ identifier: session[:pairer_user_id],
265
+ }
266
+ )
267
+ end
268
+
269
+ include ActionView::Helpers::JavaScriptHelper
270
+ def render_js_page_replace
271
+ get_board
272
+
273
+ js_page_content = escape_javascript(render_to_string "pairer/boards/show", layout: false)
274
+
275
+ javascript = <<~STR
276
+ $("#page-content").html("#{js_page_content}");
277
+ STR
278
+
279
+ render(js: javascript)
280
+ end
281
+
201
282
  end
202
283
  end
@@ -12,6 +12,7 @@ module Pairer
12
12
  elsif request.method == "POST"
13
13
  if Pairer.config.allowed_org_ids.include?(params[:org_id]&.downcase)
14
14
  session[:pairer_current_org_id] = params[:org_id].downcase
15
+ session[:pairer_user_id] = "#{session[:pairer_current_org_id]}_#{SecureRandom.uuid}"
15
16
  redirect_to boards_path
16
17
  end
17
18
  end
@@ -23,6 +24,7 @@ module Pairer
23
24
  else
24
25
  session.delete(:pairer_current_org_id)
25
26
  session.delete(:pairer_current_board_id)
27
+ session.delete(:pairer_user_id)
26
28
  flash.notice = "Signed out"
27
29
  redirect_to sign_in_path
28
30
  end
@@ -15,5 +15,18 @@ module Pairer
15
15
  try(:public_id) || id
16
16
  end
17
17
 
18
+ if Rails::VERSION::STRING.to_f < 7
19
+ def in_order_of(column, order_array)
20
+ Arel.sql(
21
+ <<~SQL
22
+ case #{table_name}.#{column}
23
+ #{order_array.map_with_index{|x, i| "when '#{x}' then #{i+1}"}.join(" ")}
24
+ else #{order_array.size+1}
25
+ end
26
+ SQL
27
+ )
28
+ end
29
+ end
30
+
18
31
  end
19
32
  end
@@ -55,7 +55,7 @@ module Pairer
55
55
  groups.where(board_iteration_number: prev_iteration_number).each do |g|
56
56
  if g.locked?
57
57
  ### Clone Locked Groups
58
-
58
+
59
59
  new_group = g.dup
60
60
 
61
61
  new_group.assign_attributes(
@@ -70,7 +70,7 @@ module Pairer
70
70
  available_roles = (available_roles - new_group.roles_array)
71
71
  else
72
72
  ### Retain Position of Locked People within Existing Groups
73
-
73
+
74
74
  group_locked_person_ids = (g.person_ids_array - available_person_ids)
75
75
 
76
76
  if group_locked_person_ids.any?
@@ -102,7 +102,7 @@ module Pairer
102
102
 
103
103
  if available_person_ids.size < num_to_add
104
104
  ### Add to group whatever is left
105
-
105
+
106
106
  g.person_ids = g.person_ids_array + available_person_ids
107
107
 
108
108
  available_person_ids = []
@@ -118,13 +118,15 @@ module Pairer
118
118
  end
119
119
 
120
120
  g.person_ids = (g.person_ids_array | chosen_person_ids).sort
121
+
122
+ available_person_ids = (available_person_ids - chosen_person_ids)
121
123
  end
122
124
 
123
125
  ### Assign People to New Groups
124
126
  while available_person_ids.any? do
125
127
  if available_person_ids.size <= self.group_size
126
128
  ### Create group using whats left
127
-
129
+
128
130
  new_groups << groups.new(
129
131
  board_iteration_number: next_iteration_number,
130
132
  person_ids: available_person_ids,
@@ -183,7 +185,7 @@ module Pairer
183
185
 
184
186
  ### Reload groups to fix any issues with caching after creations and deletions
185
187
  groups.reload
186
-
188
+
187
189
  return true
188
190
  end
189
191
 
@@ -198,7 +200,7 @@ module Pairer
198
200
  end
199
201
 
200
202
  private
201
-
203
+
202
204
  def stats_hash_for_two_pairs
203
205
  h = {}
204
206
 
@@ -25,7 +25,7 @@ module Pairer
25
25
 
26
26
  def roles=(val)
27
27
  if val.is_a?(Array)
28
- sanitized_array = self[:roles] = val.map{|x| x.presence&.strip }.uniq(&:downcase).compact
28
+ sanitized_array = val.map{|x| x.presence&.strip }.uniq(&:downcase).compact
29
29
 
30
30
  if !new_record?
31
31
  sanitized_array = sanitized_array.intersection(board.roles_array) ### This may slow the query down
@@ -11,8 +11,11 @@ html
11
11
 
12
12
  = favicon_link_tag "pairer/favicon.ico"
13
13
 
14
+ link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.4.0/united/bootstrap.min.css" integrity="sha512-JsK+6bBl5wmtKe9PnCXclLS1fwG7GvLZ9IkBg/ACzuSJJUKTedxyXRFujJf+KpcNFFhSX+I05YPAm99r1ivmog==" crossorigin="anonymous" referrerpolicy="no-referrer"
15
+
16
+ = stylesheet_link_tag 'pairer/utility'
14
17
  = stylesheet_link_tag 'pairer/application', media: 'all'
15
-
18
+
16
19
  script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" referrerpolicy="no-referrer"
17
20
 
18
21
  link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/3.2.1/css/font-awesome.min.css" integrity="sha512-IJ+BZHGlT4K43sqBGUzJ90pcxfkREDVZPZxeexRigVL8rzdw/gyJIflDahMdNzBww4k0WxpyaWpC2PLQUWmMUQ==" crossorigin="anonymous" referrerpolicy="no-referrer"
@@ -22,8 +25,13 @@ html
22
25
  link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/themes/base/jquery-ui.min.css" integrity="sha512-ELV+xyi8IhEApPS/pSj66+Jiw+sOT1Mqkzlh8ExXihe4zfqbWkxPRi8wptXIO9g73FSlhmquFlUOuMSoXz5IRw==" crossorigin="anonymous" referrerpolicy="no-referrer"
23
26
  script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js" integrity="sha512-57oZ/vW8ANMjR/KQ6Be9v/+/h6bq9/l3f0Oc7vn6qMqyhvPd1cvKBRWWpzu0QoneImqr2SkmO4MSqU+RpHom3Q==" crossorigin="anonymous" referrerpolicy="no-referrer"
24
27
 
28
+ script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js" integrity="sha512-0bEtK0USNd96MnO4XhH8jhv3nyRF0eK87pJke6pkYf3cM0uDIhNJy9ltuzqgypoIFXw3JSuiy04tVk4AjpZdZw==" crossorigin="anonymous" referrerpolicy="no-referrer"
29
+
30
+ script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha512-oBTprMeNEKCnqfuqKd6sbvFzmFQtlXS3e0C/RGFV0hD6QzhHV+ODfaQbAlmY6/q0ubbwlAM/nCJjkrgA3waLzg==" crossorigin="anonymous" referrerpolicy="no-referrer"
31
+
32
+ = javascript_include_tag 'rails-ujs'
25
33
  = javascript_include_tag 'pairer/application'
26
-
34
+
27
35
  body
28
36
  nav.navbar.navbar-inverse.navbar-fixed-top
29
37
  .container-fluid
@@ -50,4 +58,5 @@ html
50
58
  .container-fluid
51
59
  = render "pairer/shared/flash"
52
60
 
53
- = yield
61
+ #page-content
62
+ = yield
@@ -0,0 +1,17 @@
1
+ <script type="module">
2
+ import * as ActionCable from "https://cdn.jsdelivr.net/npm/@rails/actioncable@<%= Rails::VERSION::STRING.split(".")[0..2].join(".") %>/+esm";
3
+
4
+ var consumer = ActionCable.createConsumer();
5
+
6
+ var warned = false;
7
+
8
+ consumer.subscriptions.create({channel: "Pairer::BoardChannel", id: "<%= @board.public_id %>"}, {
9
+ received: function(data){
10
+ if(data.action == "reload" && data.identifier != "<%= session[:pairer_user_id] %>" && !warned){
11
+ warned = true;
12
+ alert("The data has been modified since this page was loaded.\n\nPress OK to reload the page.");
13
+ window.location.reload();
14
+ }
15
+ },
16
+ });
17
+ </script>
@@ -160,7 +160,7 @@ javascript:
160
160
  connectWith: ".person-list",
161
161
  items: "> .person",
162
162
  revert: true,
163
- update: function(){
163
+ receive: function(event, ui){
164
164
  var group_id = $(this).data('group-id');
165
165
  var prev_person_ids = $(this).data('prev-person-ids');
166
166
 
@@ -168,6 +168,26 @@ javascript:
168
168
 
169
169
  if(!group_id){
170
170
  // When on the unassigned list
171
+
172
+ if(ui.sender){
173
+ var group_id = ui.sender.data("group-id");
174
+ var person_ids = [];
175
+
176
+ ui.sender.find('.person').each(function(i, item){
177
+ person_ids.push($(item).data('person-id'));
178
+ });
179
+
180
+ if(person_ids.length === 0){
181
+ person_ids.push(""); // to ensure empty array value make the params
182
+ }
183
+
184
+ $.ajax({
185
+ url: "#{update_group_board_path(@board, format: :js)}",
186
+ method: "POST",
187
+ data: {group_id: group_id, person_ids: person_ids, removal: "true"},
188
+ });
189
+ }
190
+
171
191
  return true; // true only skips ajax update, false cancels the entire item drag/move
172
192
  }
173
193
 
@@ -177,7 +197,7 @@ javascript:
177
197
  person_ids.push($(item).data('person-id'));
178
198
  });
179
199
 
180
- if(equals(prev_person_ids.sort(), person_ids.sort())){
200
+ if(equals(prev_person_ids.sort(), Array(person_ids).sort())){
181
201
  return true; // true only skips ajax update, false cancels the entire item drag/move
182
202
  }
183
203
 
@@ -189,8 +209,6 @@ javascript:
189
209
  url: "#{update_group_board_path(@board, format: :js)}",
190
210
  method: "POST",
191
211
  data: {group_id: group_id, person_ids: person_ids},
192
- }).done(function(){
193
- $el.data('prev-person-ids', person_ids);
194
212
  });
195
213
  },
196
214
  });
@@ -199,7 +217,7 @@ javascript:
199
217
  connectWith: ".roles-list",
200
218
  items: "> .role",
201
219
  revert: true,
202
- update: function(){
220
+ receive: function(event, ui){
203
221
  var group_id = $(this).data('group-id');
204
222
  var prev_roles = $(this).data('prev-roles');
205
223
 
@@ -207,6 +225,26 @@ javascript:
207
225
 
208
226
  if(!group_id){
209
227
  // When on the unassigned list
228
+
229
+ if(ui.sender){
230
+ var group_id = ui.sender.data("group-id");
231
+ var roles = [];
232
+
233
+ ui.sender.find('.role').each(function(i, item){
234
+ roles.push($(item).data('role-name'));
235
+ });
236
+
237
+ if(roles.length === 0){
238
+ roles.push(""); // to ensure empty array value make the params
239
+ }
240
+
241
+ $.ajax({
242
+ url: "#{update_group_board_path(@board, format: :js)}",
243
+ method: "POST",
244
+ data: {group_id: group_id, roles: roles},
245
+ });
246
+ }
247
+
210
248
  return true; // true only skips ajax update, false cancels the entire item drag/move
211
249
  }
212
250
 
@@ -216,7 +254,7 @@ javascript:
216
254
  roles.push($(item).data('role-name'));
217
255
  });
218
256
 
219
- if(equals(prev_roles.sort(), roles.sort())){
257
+ if(equals(prev_roles.sort(), Array(roles).sort())){
220
258
  return true; // true only skips ajax update, false cancels the entire item drag/move
221
259
  }
222
260
 
@@ -228,8 +266,6 @@ javascript:
228
266
  url: "#{update_group_board_path(@board, format: :js)}",
229
267
  method: "POST",
230
268
  data: {group_id: group_id, roles: roles},
231
- }).done(function(){
232
- $el.data('prev-roles', roles);
233
269
  });
234
270
  },
235
271
  });
@@ -238,3 +274,5 @@ javascript:
238
274
  window.init_sortable_lists();
239
275
 
240
276
  });
277
+
278
+ = render "action_cable_script"
data/lib/pairer/engine.rb CHANGED
@@ -1,7 +1,4 @@
1
1
  require 'slim'
2
- require 'sassc-rails'
3
- require 'bootstrap-sass'
4
- require 'bootswatch-rails'
5
2
  require 'hashids'
6
3
 
7
4
  module Pairer
@@ -9,8 +6,11 @@ module Pairer
9
6
  isolate_namespace Pairer
10
7
 
11
8
  initializer "pairer.assets.precompile" do |app|
9
+ # this initializer is only called when sprockets is in use
10
+
12
11
  app.config.assets.precompile << "pairer_manifest.js" ### manifest file required
13
12
  app.config.assets.precompile << "pairer/favicon.ico"
13
+ app.config.assets.precompile << "rails-ujs" # provided by activesupport
14
14
 
15
15
  ### Automatically precompile assets in specified folders
16
16
  ["app/assets/images/"].each do |folder|
@@ -28,16 +28,6 @@ module Pairer
28
28
  end
29
29
  end
30
30
 
31
- initializer "pairer.append_migrations" do |app|
32
- ### Automatically load all migrations into main rails app
33
-
34
- if !app.root.to_s.match?(root.to_s)
35
- config.paths["db/migrate"].expanded.each do |expanded_path|
36
- app.config.paths["db/migrate"] << expanded_path
37
- end
38
- end
39
- end
40
-
41
31
  initializer "pairer.load_static_assets" do |app|
42
32
  ### Expose static assets
43
33
  app.middleware.use ::ActionDispatch::Static, "#{root}/public"
@@ -1,3 +1,3 @@
1
1
  module Pairer
2
- VERSION = '1.0.0'
2
+ VERSION = "1.2.0".freeze
3
3
  end
data/lib/pairer.rb CHANGED
@@ -14,11 +14,3 @@ module Pairer
14
14
  end
15
15
 
16
16
  end
17
-
18
- if RUBY_VERSION.to_f <= 2.6 && !Array.new.respond_to?(:intersection)
19
- Array.class_eval do
20
- def intersection(other)
21
- self & other
22
- end
23
- end
24
- end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pairer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Weston Ganger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-03 00:00:00.000000000 Z
11
+ date: 2024-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,16 +16,16 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '5.0'
19
+ version: '6.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '5.0'
26
+ version: '6.1'
27
27
  - !ruby/object:Gem::Dependency
28
- name: slim
28
+ name: actioncable
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: sassc-rails
42
+ name: slim
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
@@ -52,34 +52,6 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: bootstrap-sass
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '3.0'
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '3.0'
69
- - !ruby/object:Gem::Dependency
70
- name: bootswatch-rails
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '3.0'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '3.0'
83
55
  - !ruby/object:Gem::Dependency
84
56
  name: hashids
85
57
  requirement: !ruby/object:Gem::Requirement
@@ -122,20 +94,6 @@ dependencies:
122
94
  - - ">="
123
95
  - !ruby/object:Gem::Version
124
96
  version: '0'
125
- - !ruby/object:Gem::Dependency
126
- name: factory_bot
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: '0'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: '0'
139
97
  - !ruby/object:Gem::Dependency
140
98
  name: database_cleaner
141
99
  requirement: !ruby/object:Gem::Requirement
@@ -192,8 +150,10 @@ files:
192
150
  - app/assets/images/pairer/favicon.ico
193
151
  - app/assets/images/pairer/sweep.png
194
152
  - app/assets/javascripts/pairer/application.js
195
- - app/assets/stylesheets/pairer/application.scss
196
- - app/assets/stylesheets/pairer/utility.scss
153
+ - app/assets/stylesheets/pairer/application.css
154
+ - app/assets/stylesheets/pairer/utility.css
155
+ - app/channels/pairer/application_cable.rb
156
+ - app/channels/pairer/board_channel.rb
197
157
  - app/controllers/pairer/application_controller.rb
198
158
  - app/controllers/pairer/boards_controller.rb
199
159
  - app/controllers/pairer/sessions_controller.rb
@@ -207,6 +167,7 @@ files:
207
167
  - app/models/pairer/person.rb
208
168
  - app/views/layouts/pairer/application.html.slim
209
169
  - app/views/layouts/pairer/application.js.erb
170
+ - app/views/pairer/boards/_action_cable_script.html.erb
210
171
  - app/views/pairer/boards/_current_groups.html.slim
211
172
  - app/views/pairer/boards/_group.html.slim
212
173
  - app/views/pairer/boards/_group_lock_button.html.slim
@@ -214,14 +175,8 @@ files:
214
175
  - app/views/pairer/boards/_recently_accessed_boards.html.slim
215
176
  - app/views/pairer/boards/_role.html.slim
216
177
  - app/views/pairer/boards/_stats.html.slim
217
- - app/views/pairer/boards/create_group.js.erb
218
- - app/views/pairer/boards/create_person.js.erb
219
- - app/views/pairer/boards/delete_group.js.erb
220
178
  - app/views/pairer/boards/index.html.slim
221
- - app/views/pairer/boards/lock_group.js.erb
222
- - app/views/pairer/boards/lock_person.js.erb
223
179
  - app/views/pairer/boards/show.html.slim
224
- - app/views/pairer/boards/update_group.js.erb
225
180
  - app/views/pairer/exceptions/show.html.slim
226
181
  - app/views/pairer/sessions/sign_in.html.slim
227
182
  - app/views/pairer/shared/_flash.html.slim
@@ -247,14 +202,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
247
202
  requirements:
248
203
  - - ">="
249
204
  - !ruby/object:Gem::Version
250
- version: '0'
205
+ version: '2.7'
251
206
  required_rubygems_version: !ruby/object:Gem::Requirement
252
207
  requirements:
253
208
  - - ">="
254
209
  - !ruby/object:Gem::Version
255
210
  version: '0'
256
211
  requirements: []
257
- rubygems_version: 3.4.6
212
+ rubygems_version: 3.4.22
258
213
  signing_key:
259
214
  specification_version: 4
260
215
  summary: Rails app/engine to Easily rotate and keep track of working pairs
@@ -1,82 +0,0 @@
1
- /*
2
- *= require ./utility
3
- *= require_self
4
- */
5
-
6
- $my-navbar-height: 3.5em;
7
-
8
- @import "bootswatch/united/variables";
9
-
10
- $grid-float-breakpoint: 992px; // collapse for xs and sm devices */
11
-
12
- @import "bootstrap-sprockets";
13
- @import "bootstrap";
14
- @import "bootswatch/united/bootswatch";
15
-
16
- .badge-danger{
17
- background-color: $brand-danger !important;
18
- color: white !important;
19
- }
20
-
21
- .navbar{
22
- font-size: 1.1em;
23
- min-height: $my-navbar-height;
24
-
25
- .navbar-brand{
26
- font-size: 1.5em;
27
- font-family: monospace;
28
- padding: 8px 15px;
29
- &:hover{
30
- color: initial;
31
- }
32
- }
33
-
34
- .navbar-nav li a, .navbar-brand{
35
- line-height: $my-navbar-height/2;
36
- }
37
- }
38
-
39
-
40
- .alert{
41
- .close{
42
- opacity: 0.6;
43
- &:hover{
44
- opacity: 0.8;
45
- }
46
- }
47
- }
48
-
49
- h1, h2, h3, h4, h5{
50
- color: #424242;
51
- }
52
-
53
- body{
54
- background-color: #F5F6F6;
55
- padding-top: 80px;
56
- padding-bottom: 20px;
57
- }
58
-
59
- label{
60
- margin-right: 10px;
61
- }
62
-
63
- footer{
64
- position: fixed;
65
- text-align: center;
66
- bottom: 10px;
67
- right: 0;
68
- left: 0;
69
- }
70
-
71
- .btn-default{
72
- border-color: gray;
73
- background-color: gray;
74
- background-image: none;
75
- }
76
-
77
- @media(max-width: 768px){
78
- .form-inline .form-control{
79
- display: inline-block;
80
- width: initial;
81
- }
82
- }
@@ -1,221 +0,0 @@
1
- .no-gutter{
2
- padding:0 !important;
3
- }
4
- .bold{
5
- font-weight:bold !important;
6
- }
7
- .no-bold{
8
- font-weight:400 !important;
9
- }
10
- .italic{
11
- font-style: italic !important;
12
- }
13
- .underline{
14
- text-decoration:underline !important;
15
- }
16
- .space-left{
17
- margin-left:5px !important;
18
- }
19
- .space-left2{
20
- margin-left:10px !important;
21
- }
22
- .space-left3{
23
- margin-left:15px !important;
24
- }
25
- .space-left4{
26
- margin-left:20px !important;
27
- }
28
- .space-left5{
29
- margin-left:30px !important;
30
- }
31
- .space-right{
32
- margin-right:5px !important;
33
- }
34
- .space-right2{
35
- margin-right:10px !important;
36
- }
37
- .space-right3{
38
- margin-right:15px !important;
39
- }
40
- .space-right4{
41
- margin-right:20px !important;
42
- }
43
- .space-right5{
44
- margin-right:30px !important;
45
- }
46
- .space-above{
47
- margin-top:5px !important;
48
- }
49
- .space-above2{
50
- margin-top:10px !important;
51
- }
52
- .space-above3{
53
- margin-top:20px !important;
54
- }
55
- .space-above4{
56
- margin-top:30px !important;
57
- }
58
- .space-above5{
59
- margin-top:40px !important;
60
- }
61
- .space-below{
62
- margin-bottom:5px !important;
63
- }
64
- .space-below2{
65
- margin-bottom:10px !important;
66
- }
67
- .space-below3{
68
- margin-bottom:15px !important;
69
- }
70
- .space-below4{
71
- margin-bottom:20px !important;
72
- }
73
- .space-below5{
74
- margin-bottom:30px !important;
75
- }
76
- .width-100{
77
- width:100%;
78
- }
79
- .width-90{
80
- width: 90%;
81
- }
82
- .table-text-center{
83
- th td{
84
- text-align:center !important;
85
- }
86
- }
87
- .display-table{
88
- height:130px;
89
- display:table;
90
-
91
- .vertical-align{
92
- display:table-cell;
93
- vertical-align:middle;
94
- }
95
- }
96
- /*
97
- .visible-xs,
98
- .visible-sm,
99
- .visible-md,
100
- .visible-lg {
101
- display: none !important;
102
- }
103
-
104
- .visible-xs {
105
- @media (max-width: 767px) {
106
- display: block !important;
107
- table{
108
- display: table !important;
109
- }
110
- tr{
111
- display: table-row !important;
112
- }
113
- th, td{
114
- display: table-cell !important;
115
- }
116
- i, abbr, cite, code, strong, a, br, img, span, sub, button, input, label, select, textarea{
117
- display: inline !important;
118
- }
119
- }
120
- }
121
-
122
- .visible-sm {
123
- @media (min-width: 768px) and (max-width: 991px) {
124
- display: block !important;
125
- table{
126
- display: table !important;
127
- }
128
- tr{
129
- display: table-row !important;
130
- }
131
- th, td{
132
- display: table-cell !important;
133
- }
134
- i, abbr, cite, code, strong, a, br, img, span, sub, button, input, label, select, textarea{
135
- display: inline !important;
136
- }
137
- }
138
- }
139
-
140
- .visible-md {
141
- @media (min-width: 992px) and (max-width: 1199px) {
142
- display: block !important;
143
- table{
144
- display: table !important;
145
- }
146
- tr{
147
- display: table-row !important;
148
- }
149
- th, td{
150
- display: table-cell !important;
151
- }
152
- i, abbr, cite, code, strong, a, br, img, span, sub, button, input, label, select, textarea{
153
- display: inline !important;
154
- }
155
- }
156
- }
157
-
158
- .visible-lg {
159
- @media (min-width: 1200px) {
160
- display: block !important;
161
- table{
162
- display: table !important;
163
- }
164
- tr{
165
- display: table-row !important;
166
- }
167
- th, td{
168
- display: table-cell !important;
169
- }
170
- i, abbr, cite, code, strong, a, br, img, span, sub, button, input, label, select, textarea{
171
- display: inline !important;
172
- }
173
- }
174
- }
175
-
176
- .hidden-xs {
177
- @media (max-width: 767px) {
178
- display: none !important;
179
- }
180
- }
181
- .hidden-sm {
182
- @media (min-width: 768px) and (max-width: 991px) {
183
- display: none !important;
184
- }
185
- }
186
- .hidden-md {
187
- @media (min-width: 992px) and (max-width: 1199px) {
188
- display: none !important;
189
- }
190
- }
191
- .hidden-lg {
192
- @media (min-width: 1200px) {
193
- display: none !important;
194
- }
195
- }
196
- .visible-print {
197
- display: none !important;
198
-
199
- @media print {
200
- display: block !important;
201
- table{
202
- display: table !important;
203
- }
204
- tr{
205
- display: table-row !important;
206
- }
207
- th, td{
208
- display: table-cell !important;
209
- }
210
- i, abbr, cite, code, strong, a, br, img, span, sub, button, input, label, select, textarea{
211
- display: inline !important;
212
- }
213
- }
214
- }
215
-
216
- .hidden-print {
217
- @media print {
218
- display: none !important;
219
- }
220
- }
221
- */
@@ -1,3 +0,0 @@
1
- $(".groups-container tbody").append("<%= j render 'pairer/boards/group', group: @group %>");
2
-
3
- window.init_sortable_lists();
@@ -1,7 +0,0 @@
1
- if(<%= raw @person.persisted? %>){
2
- $(".unassigned-lists-container .person-list").append("<%= j render 'pairer/boards/person', person: @person %>")
3
-
4
- $("form.new-person-form").find("input","select","textarea").val('')
5
- }else if(<%= raw @person.errors[:name].present? %>){
6
- alert("Name <%= @person.errors[:name].first %>");
7
- }
@@ -1,7 +0,0 @@
1
- var group_row = $(".group-row[data-group-id=<%= @group.public_id %>]");
2
-
3
- $('.unassigned-lists-container .person-list').append(group_row.find(".person"));
4
-
5
- $('.unassigned-lists-container .roles-list').append(group_row.find(".role"));
6
-
7
- group_row.remove();
@@ -1 +0,0 @@
1
- $(".group-row[data-group-id=<%= @group.public_id %>] .group-lock-btn").replaceWith("<%= j render "group_lock_button", group: @group %>");
@@ -1 +0,0 @@
1
- $(".person[data-person-id=<%= @person.public_id %>]").replaceWith("<%= j render "person", person: @person %>")
File without changes