Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

beta waitlist #3

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ PATH
samson_datadog (0.0.0)
dogapi (~> 1.9)

PATH
remote: plugins/deploy_waitlist
specs:
samson_deploy_waitlist (0.0.0)

PATH
remote: plugins/docker_binary_builder
specs:
Expand Down Expand Up @@ -568,6 +573,7 @@ DEPENDENCIES
samson_assertible!
samson_aws_ecr!
samson_datadog!
samson_deploy_waitlist!
samson_docker_binary_builder!
samson_env!
samson_flowdock!
Expand Down
22 changes: 22 additions & 0 deletions app/views/projects/deploy_groups.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<%= page_title do %>
<%= icon_tag 'star' %> Projects
<% end %>

<%= search_form do %>
<%= render 'shared/search_query' %>
<% end %>

<div class="row dashboard">
<%= render @projects %>
</div>

<div class="admin-actions">
<% if current_user.admin? %>
<div class="pull-right">
<%= render "locks/button", lock: global_lock, resource: nil, prefix: "Global " %>
<%= link_to "New", new_project_path, class: "btn btn-default" %>
</div>
<% end %>

<%= paginate @projects %>
</div>
13 changes: 13 additions & 0 deletions clock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require 'clockwork'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have we considered using periodical, which is already built in?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, but only because not familiar with it.

embarassed--google search coming up dry. can you add link?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require './config/boot'
require './config/environment'

include Clockwork

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please ensure we're logging to a place that gets rotated (such as log/production.log)


require './plugins/deploy_waitlist/lib/samson_deploy_waitlist/waitlist_monitor.rb'

handler do |job|
puts "Running #{job}"
end

every(30.seconds, 'deploy_waitlist_monitor') { SamsonDeployWaitlist::WaitlistMonitor.check_your_head }
2 changes: 1 addition & 1 deletion config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class Application < Rails::Application
config.samson.export_job.max_age = Integer(ENV['EXPORT_JOB_MAX_AGE'] || 1.day)
config.samson.start_time = Time.now

# flowdock uses routes: run after the routes are loaded which happens after after_initialize
# flowdock and deploy_waitlist use routes: run after the routes are loaded which happens after after_initialize
# config.ru sets SERVER_MODE after application.rb is loaded when using `rails s`
initializer :execute_job, after: :set_routes_reloader_hook do
if !Rails.env.test? && ENV['SERVER_MODE'] && !ENV['PRECOMPILE']
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class DeployWaitlistController < ApplicationController

def show
respond_to do |format|
format.html
format.json { render json: current_waitlist.to_json }
end
end

def add

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to consider RESTful routes? I think add and remove make sense, but i'm just asking if we have strong opinions either way

Rails.logger.warn("current_waitlist: #{current_waitlist.list}")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is good for our debugging, but in a PR to zendesk, i would advise making this debug

current_waitlist.add({ email: deployer, added: now })
render json: current_waitlist.to_json
end

def remove
current_waitlist.remove(deployer.to_i)
render json: current_waitlist.to_json
end

private

def now
Time.now
end

def current_waitlist
@waitlist ||= Waitlist.new(project.id, stage.id)
end

def deployer
params[:deployer]
end

def stage
Stage.find params[:stage]
end

def project
Project.find params[:project]
end
end
72 changes: 72 additions & 0 deletions plugins/deploy_waitlist/app/models/waitlist.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
class Waitlist
attr_reader :project_id, :stage_id, :list, :metadata

WAITLIST_KEY = 'deploy_waitlist'.freeze
METADATA_KEY = '.metadata'.freeze

def initialize(project_id, stage_id)
@project_id = project_id
@stage_id = stage_id
@list = Rails.cache.read(key) || []
@metadata = Rails.cache.read(metadata_key) || {}
end

def add(deployer_hash = {})
@list << deployer_hash
@metadata[:last_updated] = Time.now
@metadata[:head_updated_at] = Time.now if @list.size == 1
set
end

def remove(index)
@list.delete_at(index)
@metadata[:last_updated] = Time.now
@metadata[:head_updated_at] = Time.now if (index == 0)
set
end

# accessors for the view
def created_at
@metadata[:created_at]
end

def head_updated_at
@metadata[:head_updated_at]
end

def head_locked?
return false if @list.blank?
stage = Stage.find @stage_id
return false unless stage.lock.present?
stage.lock.user.email == list[0][:email]
end

def to_json
{
created_at: created_at,
head_updated_at: head_updated_at,
head_is_locked: head_locked?,
list: list
}
end

private

def set
Rails.cache.write(key, @list)
@metadata[:created_at] = Time.now unless @metadata[:created_at]
Rails.cache.write(metadata_key, @metadata)
end

def key
WAITLIST_KEY + stage_key
end

def metadata_key
"#{WAITLIST_KEY}.{METADATA_KEY}#{stage_key}"
end

def stage_key
".project-#{project_id}.stage-#{stage_id}"
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<%
waitlist = Waitlist.new(@project.id, @stage.id)
%>
<div id="waitlist" style="margin-bottom: 20px; margin-top: 20px;">
<h2 style="margin-bottom: 0px;">Deployment Waitlist <span style="font-size:10px; font-style: italic;" id="createdAt"/></h2>
<div style="margin-bottom: 10px;">
<span class="glyphicon glyphicon-time"></span> Note: Once you're head of the waitlist, you have <%= SamsonDeployWaitlist::WaitlistMonitor::TIME_TO_LOCK_QUEUE %>
minutes to lock the deployment before you're automatically kicked to the back of the line.</div>
<ol style="width: 60%" id="list"></ol>
<div style="display: none; margin: 25px;" id="empty">Such empty! ¯\_(ツ)_/¯</div>
<button onclick="addUser();"><span class="glyphicon glyphicon-plus"></span> Add Me</button>
</div>

<script>
var stageId = <%= @stage.id %>;
var projectId = <%= @project.id %>;
var deployer = '<%= current_user.email %>';
var authenticity_token = '<%= form_authenticity_token %>';
stage: stageId,

$( document ).ready(function(){
$.getScript( "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.min.js", function( data, textStatus, jqxhr ) {
$.getScript("https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.14/moment-timezone-with-data-2012-2022.min.js", function( data, textStatus, jqxhr ) {
drawList();
});
});
});

function drawList() {
$.ajax({
url: "/deploy_waitlist.json?stage=" + stageId + "&project=" + projectId,
}).done(function(wishlist) {
console.log(wishlist);
var createdAt = moment(wishlist.created_at).tz('America/Los_Angeles').format("MMM Do, YYYY h:mm a");
var headUpdatedAt = moment(wishlist.head_updated_at).fromNow();
var deployers = wishlist.list;
if (createdAt) {
$("#createdAt").html("created " + createdAt);
}
var list = $(document.createElement('ol')).attr('id','list')
.css({ 'width' : '60%',
'list-style' : 'none',
'padding-left' : '0'});
if (deployers.length) {
$.each(deployers, function( index, value ) {
$(list).append(listItem(value, index));
});
var headSince = $(document.createElement('span')).css({'margin-left' : '10px',
'font-size' : '10px',
'font-weight' : 600});
$(headSince).append("Head since " + headUpdatedAt)
if (wishlist.head_is_locked) {
$(headSince).prepend('<span class="glyphicon glyphicon-lock"></span> ');
} else {
$(headSince).css('color', 'red');
}
$(list).find('li').first().append(headSince);

$("#empty").hide();
} else {
$("#empty").show();
}
$("#list").replaceWith(list);
setTimeout(drawList, 3000);
});
}

function listItem(deployer, index) {
var item = $(document.createElement('li'));
var added = $(document.createElement('span')).css('margin-left', '10px').css('font-size', '10px');
$(added).append("Enqueued " + moment(deployer.added).fromNow());
$(item).append(deployer.email).append(added);
var remove = $(document.createElement('a'));
$(remove).attr('href', '#');
$(remove).click(function() {
removeUser(index);
})
$(remove).css({
'color' : 'red',
'margin-right' : '5px'
});
$(remove).append('<span class="glyphicon glyphicon-remove"></span>');
$(item).prepend(remove);
return item;
}

function addUser() {
$.post( "/deploy_waitlist/add", $.extend({}, postParams(), { deployer: deployer}), function( data ) {
drawList(data);
});
}
function removeUser(index) {
$.post( "/deploy_waitlist/remove", $.extend({}, postParams(), { deployer: index}), function( data ) {
drawList(data);
});
}

function postParams() {
return {
authenticity_token: authenticity_token,
stage: stageId,
project: projectId
}
}
</script>
5 changes: 5 additions & 0 deletions plugins/deploy_waitlist/config/routes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Samson::Application.routes.draw do
post '/deploy_waitlist/add', to: 'deploy_waitlist#add'
post '/deploy_waitlist/remove', to: 'deploy_waitlist#remove'
get '/deploy_waitlist', to: 'deploy_waitlist#show'
end
Loading