SlideShare ist ein Scribd-Unternehmen logo
1 von 28
Downloaden Sie, um offline zu lesen
Virtual Madness @ Etsy 
Nishan Subedi 
nishan@etsy.com 
@subedinishan
@subedinishan 
~275 engineers
@subedinishan 
#devtools 
• Infrastructure team 
• <iframe>JS</iframe> 
• Tooling
Virtual Madness - Pre Marionette 
@subedinishan
@subedinishan 
jQuery days 
(function (window, $) { 
$(function() { 
$("input.delete-host").change(function() { 
var hostname = $(this).attr('data-hostname'); 
if ($(this).is(':checked')) { 
$('#div-vm-' + hostname).addClass('selected'); 
$('input#delete_dns-' + hostname).prop('checked', true); 
$('input#remove_disk-' + hostname).prop('checked', true); 
$('input#remove_autostart-' + hostname).prop('checked', true); 
} else { 
$('#div-vm-' + hostname).removeClass('selected'); 
if (!$('input#delete_dns-' + hostname).attr('disabled')) { 
$('input#delete_dns-' + hostname).prop('checked', false); 
$('input#remove_disk-' + hostname).prop('checked', false); 
$('input#remove_autostart-' + hostname).prop('checked', false); 
} 
} 
}); 
}); 
! 
})(window, jQuery);
@subedinishan 
PHP for templating 
<?php 
include('templates/filters.php'); 
$extra_head = <<<HEAD 
<script type="text/javascript" src="https://www.google.com/jsapi"></script> 
<script type="text/javascript"> 
google.load('visualization', '1.0', {'packages':['corechart']}); 
function drawVisualization() { 
var guest_data = google.visualization.arrayToDataTable([ 
$format_virt_counts 
]); 
google.setOnLoadCallback(drawVisualization); 
</script> 
HEAD; 
?> 
<h2>Subnet Capacity</h2> 
<?php foreach ($graph_data AS $host_name => $host_data) : ?> 
<h3><a href="<?php echo $host_data['page_url']; ?>" target="blank"><?php echo $host_name ?></a></h3> 
<?php if (!empty($host_data['graphs'])) : ?> 
<ul class="host_graphs"> 
<?php foreach ($host_data['graphs'] AS $host_graph) : ?> 
<li><?php echo $host_graph; ?></li> 
<?php endforeach ?> 
</ul> 
<?php endif ?> 
<script> 
get_guest_graphs('<?php echo $host_name; ?>');
@subedinishan 
PHP for templating 
<?php 
include('templates/filters.php'); 
$extra_head = <<<HEAD 
<script type="text/javascript" src="https://www.google.com/jsapi"></script> 
<script type="text/javascript"> 
google.load('visualization', '1.0', {'packages':['corechart']}); 
function drawVisualization() { 
var guest_data = google.visualization.arrayToDataTable([ 
$format_virt_counts 
]); 
google.setOnLoadCallback(drawVisualization); 
</script> 
HEAD; 
?> 
<h2>Subnet Capacity</h2> 
<?php foreach ($graph_data AS $host_name => $host_data) : ?> 
<h3><a href="<?php echo $host_data['page_url']; ?>" target="blank"><?php echo $host_name ?></a></h3> 
<?php if (!empty($host_data['graphs'])) : ?> 
<ul class="host_graphs"> 
<?php foreach ($host_data['graphs'] AS $host_graph) : ?> 
<li><?php echo $host_graph; ?></li> 
<?php endforeach ?> 
</ul>
@subedinishan 
Concerns 
• We “understand” vanilla Javascript 
• Method chain hell 
• Yet another framework
If we can build it, so can 
@subedinishan 
you!
@subedinishan 
Technologies
@subedinishan
@subedinishan 
Example - streamer 
var VmsManager = new Marionette.Application({ 
onStart: function() { 
// Start websocket connection 
this.socket = io.connect('//'+ document.location.hostname +':8008', { 
transports: ['websocket', 'xhr-polling'], secure: true 
}); 
this.socket.on('logdata', _.bind(this.onServerLog, this)); 
}, 
onServerLog: function(data) { 
this.logs.push(new this.Entities.Log(data)); 
}, 
onServerLogSubscribe: function(channel) { 
this.socket.emit('subscribe', channel); 
} navigate: function(route, options) { 
options || (options = {}); 
Backbone.history.navigate(route, options); 
}, 
hideStreamer: function() { 
this.streamRegion.currentView.$el.hide(); 
}, 
showStreamer: function() { 
this.streamRegion.currentView.$el.show(); 
} 
});
@subedinishan 
Example - FilteredCollection 
this.filteredVms = VmsManager.Entities.FilteredCollection({ 
collection: options.collection, 
filterFunction: function(filterCriterion) { 
var criterion = filterCriterion.query.toLowerCase(); 
return function(vms) { 
if( 
vms.get("hostname").toLowerCase().indexOf(criterion) !== -1 && 
(vms.get("type") == filterCriterion.type || 'all' == filterCriterion.type) 
) { 
return vms; 
} 
}; 
} 
});
@subedinishan 
Example - message queue 
var VmsManager = new Marionette.Application({ 
onStart: function() { 
// Start a message queue 
this.messageQueue = new this.Entities.Messages(); 
this.messageRegion.show(new this.VmsApp.Message.Messages({ 
collection: this.messageQueue 
})); 
onMessage: function(options) { 
this.messageQueue.push(new this.Entities.Message(options)); 
}, 
}); 
!! 
Entities.Message = Backbone.Model.extend({ 
defaults: { 
type: 'info', 
timeout: 2000, 
isDismissible: true, 
message: 'Nothing to see here' 
} 
});
@subedinishan 
Message - view 
Message.Message = Marionette.ItemView.extend({ 
template: 'message', 
className: function() { 
var val = ' alert col-md-12'; 
val += ' alert-' + this.model.get('type'); 
if (this.model.get('isDismissible')) { 
val += ' alert-dismissible '; 
} 
return val; 
}, 
onShow: function() { 
if (this.model.get('timeout') !== 0) { 
// Model not used after flash message display 
// so, set up a timeout for cleanup 
_.delay(_.bind(this.cleanup, this), this.model.get('timeout')); 
} 
}, 
cleanup: function() { 
var timeout = this.model.get('timeout'); 
this.$el.fadeOut(timeout/2); 
// We rely on the fadeOut successfully completing in timeout*2 time 
_.delay(_.bind(this.model.destroy, this.model), timeout*2); 
_.delay(_.bind(this.destroy, this), timeout*2); 
} 
}); 
Message.Messages = Marionette.CollectionView.extend({ 
childView: Message.Message 
});
@subedinishan 
Examples - DSL 
Add.Vm = Marionette.ItemView.extend({ 
template: 'create', 
className: 'panel panel-default alert col-md-12', 
ui: { 
create: ".js-create-vm", 
size: "#size", 
search: "#search", 
base_role: "#base_role", 
role_add: ".js-role-add", 
primary: "#primary", 
ldap: "#ldap", 
}, 
bindings: { 
'#ldap': 'ldap', 
'#base_role': 'base_role', 
'#version': 'version', 
'#hostname': 'hostname', 
'#search': 'search', 
'#size': 'size', 
'#virt': 'virt', 
'#primary': 'primary', 
}, 
events: { 
"click @ui.create": "submitClicked", 
"change @ui.size": "sizeChanged", 
"click @ui.search": "searchClicked", 
"change @ui.base_role": "baseRoleChanged", 
"click @ui.role_add": "roleAdd", 
"change @ui.ldap": "ldapChanged", 
}, 
initialize: function() { 
this.model.fetch({success: this.render}); 
this.stickit(); 
var fetchingVms = VmsManager.request("vms:entities"); 
$.when(fetchingVms).done(_.bind(this.entitiesFetched, this));
@subedinishan 
Template loading 
// Override Marionette template loading and compiling to use our template loader. 
_.extend(Backbone.Marionette.TemplateCache.prototype, { 
compileTemplate: function(template) { 
return template; 
}, 
! 
loadTemplate: function(templateId) { 
var contents = raw_template_cache[templateId]; 
return function(data) { 
return Mustache.render(contents, data, function(name) { 
return raw_template_cache[name]; 
}); 
}; 
} 
}); 
!
@subedinishan 
Template loading - PHP 
class TemplateHelper extends HelperBase { 
const DIRECTORY = "templates/"; 
const EXTENSION = ".mustache"; 
! 
protected function get_template_names() { 
return glob(self::DIRECTORY . '*' . self::EXTENSION); 
} 
! 
protected function to_array() { 
$return = []; 
foreach ($this->get_template_names() as $template_name) { 
$key = str_replace(self::EXTENSION, '', $template_name); 
$key = str_replace(self::DIRECTORY, '', $key); 
$return[$key] = file_get_contents($template_name); 
} 
return $return; 
} 
! 
public function generate_template_javascript_variable() { 
return '<script language="javascript" type="text/javascript"> var raw_template_cache = ' 
. json_encode($this->to_array()) . ' </script>'; 
} 
} 
!
@subedinishan 
Template Loading - PHP 
! 
! 
! 
! 
$app->get('/v2/.*?', function() use ($app) { 
echo $app->helper('Template')->generate_template_javascript_variable(); 
$page_object = new WebBase(); 
echo $page_object->render('base'); 
});
@subedinishan 
Modals
@subedinishan 
Modal - inside ItemView 
this.confirmationModal = new BootstrapDialog({ 
title: 'Are you sure?', 
message: 'Do you really want to delete ' + this.model.get('hostname') + '?', 
type: BootstrapDialog.TYPE_DANGER, 
buttons: [{ 
label: 'DELETE', 
action: function(dialog){ 
parentScope.model.destroy({ 
success: function(model, response) { 
VmsManager.Common.Notifier.success(response, { 
fail: "VM deletion failed with response: " + response.message, 
success: "Delete VM successful", 
undef:"Delete VM came back with response" + JSON.stringify(response) 
}); 
}, 
error: function(model, response) { 
VmsManager.Common.Notifier.error(response, { 
error: "VM Deletion failed with response: " + JSON.stringify(response) 
}); 
} 
}); 
dialog.close(); 
} 
}], 
onhidden: function() { 
VmsManager.trigger('streamer:show'); 
VmsManager.trigger("vm:destroy"); 
VmsManager.trigger("log:subscribe", Etsy.data.username + "-delete"); 
}
Marionette JS 
• active and helpful community 
• great upgrade support 
• adopted in the company 
• plug and play - able to glue things together 
• CollectionView 
@subedinishan
@subedinishan 
Future of Marionette @ Etsy 
• Behaviors 
• Skeletor - build once, use everywhere 
• Migration 
• Adoption
@subedinishan 
What worked? 
• Simplify implementation 
• Simplify abstractions 
• Leverage existing resources 
• Create additional resources
@subedinishan 
Get your learning on
@subedinishan 
Questions? 
! 
We’re Hiring! etsy.com/careers
@subedinishan 
References 
https://avatars3.githubusercontent.com/u/1522517?v=3&s=460

Weitere ähnliche Inhalte

Was ist angesagt?

Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form componentSamuel ROZE
 
Doctrine 2
Doctrine 2Doctrine 2
Doctrine 2zfconfua
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDAleix Vergés
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteLeonardo Proietti
 
Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)Remy Sharp
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patternsSamuel ROZE
 
Yearning jQuery
Yearning jQueryYearning jQuery
Yearning jQueryRemy Sharp
 
Анатолий Поляков - Drupal.ajax framework from a to z
Анатолий Поляков - Drupal.ajax framework from a to zАнатолий Поляков - Drupal.ajax framework from a to z
Анатолий Поляков - Drupal.ajax framework from a to zLEDC 2016
 
Remy Sharp The DOM scripting toolkit jQuery
Remy Sharp The DOM scripting toolkit jQueryRemy Sharp The DOM scripting toolkit jQuery
Remy Sharp The DOM scripting toolkit jQuerydeimos
 
jQuery UI Widgets, Drag and Drop, Drupal 7 Javascript
jQuery UI Widgets, Drag and Drop, Drupal 7 JavascriptjQuery UI Widgets, Drag and Drop, Drupal 7 Javascript
jQuery UI Widgets, Drag and Drop, Drupal 7 JavascriptDarren Mothersele
 
Drupal & javascript
Drupal & javascriptDrupal & javascript
Drupal & javascriptAlmog Baku
 
Joe Walker Interactivewebsites Cometand Dwr
Joe Walker Interactivewebsites Cometand DwrJoe Walker Interactivewebsites Cometand Dwr
Joe Walker Interactivewebsites Cometand Dwrdeimos
 
Love and Loss: A Symfony Security Play
Love and Loss: A Symfony Security PlayLove and Loss: A Symfony Security Play
Love and Loss: A Symfony Security PlayKris Wallsmith
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony AppsKris Wallsmith
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 MinutesAzim Kurt
 
Drupal csu-open atriumname
Drupal csu-open atriumnameDrupal csu-open atriumname
Drupal csu-open atriumnameEmanuele Quinto
 

Was ist angesagt? (20)

Your Entity, Your Code
Your Entity, Your CodeYour Entity, Your Code
Your Entity, Your Code
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form component
 
Doctrine 2
Doctrine 2Doctrine 2
Doctrine 2
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
 
Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
Yearning jQuery
Yearning jQueryYearning jQuery
Yearning jQuery
 
Separation of concerns - DPC12
Separation of concerns - DPC12Separation of concerns - DPC12
Separation of concerns - DPC12
 
Анатолий Поляков - Drupal.ajax framework from a to z
Анатолий Поляков - Drupal.ajax framework from a to zАнатолий Поляков - Drupal.ajax framework from a to z
Анатолий Поляков - Drupal.ajax framework from a to z
 
Remy Sharp The DOM scripting toolkit jQuery
Remy Sharp The DOM scripting toolkit jQueryRemy Sharp The DOM scripting toolkit jQuery
Remy Sharp The DOM scripting toolkit jQuery
 
jQuery UI Widgets, Drag and Drop, Drupal 7 Javascript
jQuery UI Widgets, Drag and Drop, Drupal 7 JavascriptjQuery UI Widgets, Drag and Drop, Drupal 7 Javascript
jQuery UI Widgets, Drag and Drop, Drupal 7 Javascript
 
Drupal & javascript
Drupal & javascriptDrupal & javascript
Drupal & javascript
 
Dojo Confessions
Dojo ConfessionsDojo Confessions
Dojo Confessions
 
Joe Walker Interactivewebsites Cometand Dwr
Joe Walker Interactivewebsites Cometand DwrJoe Walker Interactivewebsites Cometand Dwr
Joe Walker Interactivewebsites Cometand Dwr
 
Love and Loss: A Symfony Security Play
Love and Loss: A Symfony Security PlayLove and Loss: A Symfony Security Play
Love and Loss: A Symfony Security Play
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes
 
Drupal csu-open atriumname
Drupal csu-open atriumnameDrupal csu-open atriumname
Drupal csu-open atriumname
 
Matters of State
Matters of StateMatters of State
Matters of State
 

Andere mochten auch

Integrated server
Integrated serverIntegrated server
Integrated serverfebru
 
NT320-Final White Paper
NT320-Final White PaperNT320-Final White Paper
NT320-Final White PaperRyan Ellingson
 
NYAI #5 - Fun With Neural Nets by Jason Yosinski
NYAI #5 - Fun With Neural Nets by Jason YosinskiNYAI #5 - Fun With Neural Nets by Jason Yosinski
NYAI #5 - Fun With Neural Nets by Jason YosinskiRizwan Habib
 
NYAI #7 - Using Data Science to Operationalize Machine Learning by Matthew Ru...
NYAI #7 - Using Data Science to Operationalize Machine Learning by Matthew Ru...NYAI #7 - Using Data Science to Operationalize Machine Learning by Matthew Ru...
NYAI #7 - Using Data Science to Operationalize Machine Learning by Matthew Ru...Rizwan Habib
 
Building Tooling And Culture Together
Building Tooling And Culture TogetherBuilding Tooling And Culture Together
Building Tooling And Culture TogetherNishan Subedi
 
NYAI #7 - Top-down vs. Bottom-up Computational Creativity by Dr. Cole D. Ingr...
NYAI #7 - Top-down vs. Bottom-up Computational Creativity by Dr. Cole D. Ingr...NYAI #7 - Top-down vs. Bottom-up Computational Creativity by Dr. Cole D. Ingr...
NYAI #7 - Top-down vs. Bottom-up Computational Creativity by Dr. Cole D. Ingr...Rizwan Habib
 
Why Twitter Is All The Rage: A Data Miner's Perspective (PyTN 2014)
Why Twitter Is All The Rage: A Data Miner's Perspective (PyTN 2014)Why Twitter Is All The Rage: A Data Miner's Perspective (PyTN 2014)
Why Twitter Is All The Rage: A Data Miner's Perspective (PyTN 2014)Matthew Russell
 
NYAI #8 - HOLIDAY PARTY + NYC AI OVERVIEW with NYC's Chief Digital Officer Sr...
NYAI #8 - HOLIDAY PARTY + NYC AI OVERVIEW with NYC's Chief Digital Officer Sr...NYAI #8 - HOLIDAY PARTY + NYC AI OVERVIEW with NYC's Chief Digital Officer Sr...
NYAI #8 - HOLIDAY PARTY + NYC AI OVERVIEW with NYC's Chief Digital Officer Sr...Rizwan Habib
 
NYAI #9: Concepts and Questions As Programs by Brenden Lake
NYAI #9: Concepts and Questions As Programs by Brenden LakeNYAI #9: Concepts and Questions As Programs by Brenden Lake
NYAI #9: Concepts and Questions As Programs by Brenden LakeRizwan Habib
 
NYAI - Understanding Music Through Machine Learning by Brian McFee
NYAI - Understanding Music Through Machine Learning by Brian McFeeNYAI - Understanding Music Through Machine Learning by Brian McFee
NYAI - Understanding Music Through Machine Learning by Brian McFeeRizwan Habib
 
NYAI - Commodity Machine Learning & Beyond by Andreas Mueller
NYAI - Commodity Machine Learning & Beyond by Andreas MuellerNYAI - Commodity Machine Learning & Beyond by Andreas Mueller
NYAI - Commodity Machine Learning & Beyond by Andreas MuellerRizwan Habib
 
Machine Learning with scikit-learn
Machine Learning with scikit-learnMachine Learning with scikit-learn
Machine Learning with scikit-learnodsc
 
Mining the Social Web for Fun and Profit: A Getting Started Guide
Mining the Social Web for Fun and Profit: A Getting Started GuideMining the Social Web for Fun and Profit: A Getting Started Guide
Mining the Social Web for Fun and Profit: A Getting Started GuideMatthew Russell
 
Privacy, Ethics, and Future Uses of the Social Web
Privacy, Ethics, and Future Uses of the Social WebPrivacy, Ethics, and Future Uses of the Social Web
Privacy, Ethics, and Future Uses of the Social WebMatthew Russell
 
Lessons Learned from Running Hundreds of Kaggle Competitions
Lessons Learned from Running Hundreds of Kaggle CompetitionsLessons Learned from Running Hundreds of Kaggle Competitions
Lessons Learned from Running Hundreds of Kaggle CompetitionsBen Hamner
 
What convnets look at when they look at nudity
What convnets look at when they look at nudityWhat convnets look at when they look at nudity
What convnets look at when they look at nudityRyan Compton
 
Mining Social Web APIs with IPython Notebook (PyCon 2014)
Mining Social Web APIs with IPython Notebook (PyCon 2014)Mining Social Web APIs with IPython Notebook (PyCon 2014)
Mining Social Web APIs with IPython Notebook (PyCon 2014)Matthew Russell
 
NYAI - Intersection of neuroscience and deep learning by Russell Hanson
NYAI - Intersection of neuroscience and deep learning by Russell HansonNYAI - Intersection of neuroscience and deep learning by Russell Hanson
NYAI - Intersection of neuroscience and deep learning by Russell HansonRizwan Habib
 
Mining Social Web APIs with IPython Notebook (Data Day Texas 2015)
Mining Social Web APIs with IPython Notebook (Data Day Texas 2015)Mining Social Web APIs with IPython Notebook (Data Day Texas 2015)
Mining Social Web APIs with IPython Notebook (Data Day Texas 2015)Matthew Russell
 
NYAI - Scaling Machine Learning Applications by Braxton McKee
NYAI - Scaling Machine Learning Applications by Braxton McKeeNYAI - Scaling Machine Learning Applications by Braxton McKee
NYAI - Scaling Machine Learning Applications by Braxton McKeeRizwan Habib
 

Andere mochten auch (20)

Integrated server
Integrated serverIntegrated server
Integrated server
 
NT320-Final White Paper
NT320-Final White PaperNT320-Final White Paper
NT320-Final White Paper
 
NYAI #5 - Fun With Neural Nets by Jason Yosinski
NYAI #5 - Fun With Neural Nets by Jason YosinskiNYAI #5 - Fun With Neural Nets by Jason Yosinski
NYAI #5 - Fun With Neural Nets by Jason Yosinski
 
NYAI #7 - Using Data Science to Operationalize Machine Learning by Matthew Ru...
NYAI #7 - Using Data Science to Operationalize Machine Learning by Matthew Ru...NYAI #7 - Using Data Science to Operationalize Machine Learning by Matthew Ru...
NYAI #7 - Using Data Science to Operationalize Machine Learning by Matthew Ru...
 
Building Tooling And Culture Together
Building Tooling And Culture TogetherBuilding Tooling And Culture Together
Building Tooling And Culture Together
 
NYAI #7 - Top-down vs. Bottom-up Computational Creativity by Dr. Cole D. Ingr...
NYAI #7 - Top-down vs. Bottom-up Computational Creativity by Dr. Cole D. Ingr...NYAI #7 - Top-down vs. Bottom-up Computational Creativity by Dr. Cole D. Ingr...
NYAI #7 - Top-down vs. Bottom-up Computational Creativity by Dr. Cole D. Ingr...
 
Why Twitter Is All The Rage: A Data Miner's Perspective (PyTN 2014)
Why Twitter Is All The Rage: A Data Miner's Perspective (PyTN 2014)Why Twitter Is All The Rage: A Data Miner's Perspective (PyTN 2014)
Why Twitter Is All The Rage: A Data Miner's Perspective (PyTN 2014)
 
NYAI #8 - HOLIDAY PARTY + NYC AI OVERVIEW with NYC's Chief Digital Officer Sr...
NYAI #8 - HOLIDAY PARTY + NYC AI OVERVIEW with NYC's Chief Digital Officer Sr...NYAI #8 - HOLIDAY PARTY + NYC AI OVERVIEW with NYC's Chief Digital Officer Sr...
NYAI #8 - HOLIDAY PARTY + NYC AI OVERVIEW with NYC's Chief Digital Officer Sr...
 
NYAI #9: Concepts and Questions As Programs by Brenden Lake
NYAI #9: Concepts and Questions As Programs by Brenden LakeNYAI #9: Concepts and Questions As Programs by Brenden Lake
NYAI #9: Concepts and Questions As Programs by Brenden Lake
 
NYAI - Understanding Music Through Machine Learning by Brian McFee
NYAI - Understanding Music Through Machine Learning by Brian McFeeNYAI - Understanding Music Through Machine Learning by Brian McFee
NYAI - Understanding Music Through Machine Learning by Brian McFee
 
NYAI - Commodity Machine Learning & Beyond by Andreas Mueller
NYAI - Commodity Machine Learning & Beyond by Andreas MuellerNYAI - Commodity Machine Learning & Beyond by Andreas Mueller
NYAI - Commodity Machine Learning & Beyond by Andreas Mueller
 
Machine Learning with scikit-learn
Machine Learning with scikit-learnMachine Learning with scikit-learn
Machine Learning with scikit-learn
 
Mining the Social Web for Fun and Profit: A Getting Started Guide
Mining the Social Web for Fun and Profit: A Getting Started GuideMining the Social Web for Fun and Profit: A Getting Started Guide
Mining the Social Web for Fun and Profit: A Getting Started Guide
 
Privacy, Ethics, and Future Uses of the Social Web
Privacy, Ethics, and Future Uses of the Social WebPrivacy, Ethics, and Future Uses of the Social Web
Privacy, Ethics, and Future Uses of the Social Web
 
Lessons Learned from Running Hundreds of Kaggle Competitions
Lessons Learned from Running Hundreds of Kaggle CompetitionsLessons Learned from Running Hundreds of Kaggle Competitions
Lessons Learned from Running Hundreds of Kaggle Competitions
 
What convnets look at when they look at nudity
What convnets look at when they look at nudityWhat convnets look at when they look at nudity
What convnets look at when they look at nudity
 
Mining Social Web APIs with IPython Notebook (PyCon 2014)
Mining Social Web APIs with IPython Notebook (PyCon 2014)Mining Social Web APIs with IPython Notebook (PyCon 2014)
Mining Social Web APIs with IPython Notebook (PyCon 2014)
 
NYAI - Intersection of neuroscience and deep learning by Russell Hanson
NYAI - Intersection of neuroscience and deep learning by Russell HansonNYAI - Intersection of neuroscience and deep learning by Russell Hanson
NYAI - Intersection of neuroscience and deep learning by Russell Hanson
 
Mining Social Web APIs with IPython Notebook (Data Day Texas 2015)
Mining Social Web APIs with IPython Notebook (Data Day Texas 2015)Mining Social Web APIs with IPython Notebook (Data Day Texas 2015)
Mining Social Web APIs with IPython Notebook (Data Day Texas 2015)
 
NYAI - Scaling Machine Learning Applications by Braxton McKee
NYAI - Scaling Machine Learning Applications by Braxton McKeeNYAI - Scaling Machine Learning Applications by Braxton McKee
NYAI - Scaling Machine Learning Applications by Braxton McKee
 

Ähnlich wie Virtual Madness @ Etsy

Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxMichelangelo van Dam
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11Michelangelo van Dam
 
international PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secretsinternational PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secretssmueller_sandsmedia
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of LithiumNate Abele
 
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)arcware
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologyDaniel Knell
 
Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From IusethisMarcus Ramberg
 
WordPress as an application framework
WordPress as an application frameworkWordPress as an application framework
WordPress as an application frameworkDustin Filippini
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web servicesMichelangelo van Dam
 
Mashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web AppsMashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web AppsBastian Hofmann
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Michelangelo van Dam
 
[Coscup 2012] JavascriptMVC
[Coscup 2012] JavascriptMVC[Coscup 2012] JavascriptMVC
[Coscup 2012] JavascriptMVCAlive Kuo
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Fabien Potencier
 

Ähnlich wie Virtual Madness @ Etsy (20)

jQuery secrets
jQuery secretsjQuery secrets
jQuery secrets
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBenelux
 
Clean Javascript
Clean JavascriptClean Javascript
Clean Javascript
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11
 
international PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secretsinternational PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secrets
 
Unit testing zend framework apps
Unit testing zend framework appsUnit testing zend framework apps
Unit testing zend framework apps
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of Lithium
 
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
 
Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From Iusethis
 
WordPress as an application framework
WordPress as an application frameworkWordPress as an application framework
WordPress as an application framework
 
jQuery secrets
jQuery secretsjQuery secrets
jQuery secrets
 
Unittests für Dummies
Unittests für DummiesUnittests für Dummies
Unittests für Dummies
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web services
 
Mashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web AppsMashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web Apps
 
Mashing up JavaScript
Mashing up JavaScriptMashing up JavaScript
Mashing up JavaScript
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
 
[Coscup 2012] JavascriptMVC
[Coscup 2012] JavascriptMVC[Coscup 2012] JavascriptMVC
[Coscup 2012] JavascriptMVC
 
Codigo taller-plugins
Codigo taller-pluginsCodigo taller-plugins
Codigo taller-plugins
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)
 

Kürzlich hochgeladen

办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书zdzoqco
 
Film cover research (1).pptxsdasdasdasdasdasa
Film cover research (1).pptxsdasdasdasdasdasaFilm cover research (1).pptxsdasdasdasdasdasa
Film cover research (1).pptxsdasdasdasdasdasa494f574xmv
 
TRENDS Enabling and inhibiting dimensions.pptx
TRENDS Enabling and inhibiting dimensions.pptxTRENDS Enabling and inhibiting dimensions.pptx
TRENDS Enabling and inhibiting dimensions.pptxAndrieCagasanAkio
 
IP addressing and IPv6, presented by Paul Wilson at IETF 119
IP addressing and IPv6, presented by Paul Wilson at IETF 119IP addressing and IPv6, presented by Paul Wilson at IETF 119
IP addressing and IPv6, presented by Paul Wilson at IETF 119APNIC
 
SCM Symposium PPT Format Customer loyalty is predi
SCM Symposium PPT Format Customer loyalty is prediSCM Symposium PPT Format Customer loyalty is predi
SCM Symposium PPT Format Customer loyalty is predieusebiomeyer
 
Unidad 4 – Redes de ordenadores (en inglés).pptx
Unidad 4 – Redes de ordenadores (en inglés).pptxUnidad 4 – Redes de ordenadores (en inglés).pptx
Unidad 4 – Redes de ordenadores (en inglés).pptxmibuzondetrabajo
 
ETHICAL HACKING dddddddddddddddfnandni.pptx
ETHICAL HACKING dddddddddddddddfnandni.pptxETHICAL HACKING dddddddddddddddfnandni.pptx
ETHICAL HACKING dddddddddddddddfnandni.pptxNIMMANAGANTI RAMAKRISHNA
 
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书rnrncn29
 
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书rnrncn29
 
Top 10 Interactive Website Design Trends in 2024.pptx
Top 10 Interactive Website Design Trends in 2024.pptxTop 10 Interactive Website Design Trends in 2024.pptx
Top 10 Interactive Website Design Trends in 2024.pptxDyna Gilbert
 
Company Snapshot Theme for Business by Slidesgo.pptx
Company Snapshot Theme for Business by Slidesgo.pptxCompany Snapshot Theme for Business by Slidesgo.pptx
Company Snapshot Theme for Business by Slidesgo.pptxMario
 

Kürzlich hochgeladen (11)

办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
 
Film cover research (1).pptxsdasdasdasdasdasa
Film cover research (1).pptxsdasdasdasdasdasaFilm cover research (1).pptxsdasdasdasdasdasa
Film cover research (1).pptxsdasdasdasdasdasa
 
TRENDS Enabling and inhibiting dimensions.pptx
TRENDS Enabling and inhibiting dimensions.pptxTRENDS Enabling and inhibiting dimensions.pptx
TRENDS Enabling and inhibiting dimensions.pptx
 
IP addressing and IPv6, presented by Paul Wilson at IETF 119
IP addressing and IPv6, presented by Paul Wilson at IETF 119IP addressing and IPv6, presented by Paul Wilson at IETF 119
IP addressing and IPv6, presented by Paul Wilson at IETF 119
 
SCM Symposium PPT Format Customer loyalty is predi
SCM Symposium PPT Format Customer loyalty is prediSCM Symposium PPT Format Customer loyalty is predi
SCM Symposium PPT Format Customer loyalty is predi
 
Unidad 4 – Redes de ordenadores (en inglés).pptx
Unidad 4 – Redes de ordenadores (en inglés).pptxUnidad 4 – Redes de ordenadores (en inglés).pptx
Unidad 4 – Redes de ordenadores (en inglés).pptx
 
ETHICAL HACKING dddddddddddddddfnandni.pptx
ETHICAL HACKING dddddddddddddddfnandni.pptxETHICAL HACKING dddddddddddddddfnandni.pptx
ETHICAL HACKING dddddddddddddddfnandni.pptx
 
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
 
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
 
Top 10 Interactive Website Design Trends in 2024.pptx
Top 10 Interactive Website Design Trends in 2024.pptxTop 10 Interactive Website Design Trends in 2024.pptx
Top 10 Interactive Website Design Trends in 2024.pptx
 
Company Snapshot Theme for Business by Slidesgo.pptx
Company Snapshot Theme for Business by Slidesgo.pptxCompany Snapshot Theme for Business by Slidesgo.pptx
Company Snapshot Theme for Business by Slidesgo.pptx
 

Virtual Madness @ Etsy

  • 1. Virtual Madness @ Etsy Nishan Subedi nishan@etsy.com @subedinishan
  • 2.
  • 4. @subedinishan #devtools • Infrastructure team • <iframe>JS</iframe> • Tooling
  • 5. Virtual Madness - Pre Marionette @subedinishan
  • 6. @subedinishan jQuery days (function (window, $) { $(function() { $("input.delete-host").change(function() { var hostname = $(this).attr('data-hostname'); if ($(this).is(':checked')) { $('#div-vm-' + hostname).addClass('selected'); $('input#delete_dns-' + hostname).prop('checked', true); $('input#remove_disk-' + hostname).prop('checked', true); $('input#remove_autostart-' + hostname).prop('checked', true); } else { $('#div-vm-' + hostname).removeClass('selected'); if (!$('input#delete_dns-' + hostname).attr('disabled')) { $('input#delete_dns-' + hostname).prop('checked', false); $('input#remove_disk-' + hostname).prop('checked', false); $('input#remove_autostart-' + hostname).prop('checked', false); } } }); }); ! })(window, jQuery);
  • 7. @subedinishan PHP for templating <?php include('templates/filters.php'); $extra_head = <<<HEAD <script type="text/javascript" src="https://www.google.com/jsapi"></script> <script type="text/javascript"> google.load('visualization', '1.0', {'packages':['corechart']}); function drawVisualization() { var guest_data = google.visualization.arrayToDataTable([ $format_virt_counts ]); google.setOnLoadCallback(drawVisualization); </script> HEAD; ?> <h2>Subnet Capacity</h2> <?php foreach ($graph_data AS $host_name => $host_data) : ?> <h3><a href="<?php echo $host_data['page_url']; ?>" target="blank"><?php echo $host_name ?></a></h3> <?php if (!empty($host_data['graphs'])) : ?> <ul class="host_graphs"> <?php foreach ($host_data['graphs'] AS $host_graph) : ?> <li><?php echo $host_graph; ?></li> <?php endforeach ?> </ul> <?php endif ?> <script> get_guest_graphs('<?php echo $host_name; ?>');
  • 8. @subedinishan PHP for templating <?php include('templates/filters.php'); $extra_head = <<<HEAD <script type="text/javascript" src="https://www.google.com/jsapi"></script> <script type="text/javascript"> google.load('visualization', '1.0', {'packages':['corechart']}); function drawVisualization() { var guest_data = google.visualization.arrayToDataTable([ $format_virt_counts ]); google.setOnLoadCallback(drawVisualization); </script> HEAD; ?> <h2>Subnet Capacity</h2> <?php foreach ($graph_data AS $host_name => $host_data) : ?> <h3><a href="<?php echo $host_data['page_url']; ?>" target="blank"><?php echo $host_name ?></a></h3> <?php if (!empty($host_data['graphs'])) : ?> <ul class="host_graphs"> <?php foreach ($host_data['graphs'] AS $host_graph) : ?> <li><?php echo $host_graph; ?></li> <?php endforeach ?> </ul>
  • 9. @subedinishan Concerns • We “understand” vanilla Javascript • Method chain hell • Yet another framework
  • 10. If we can build it, so can @subedinishan you!
  • 13. @subedinishan Example - streamer var VmsManager = new Marionette.Application({ onStart: function() { // Start websocket connection this.socket = io.connect('//'+ document.location.hostname +':8008', { transports: ['websocket', 'xhr-polling'], secure: true }); this.socket.on('logdata', _.bind(this.onServerLog, this)); }, onServerLog: function(data) { this.logs.push(new this.Entities.Log(data)); }, onServerLogSubscribe: function(channel) { this.socket.emit('subscribe', channel); } navigate: function(route, options) { options || (options = {}); Backbone.history.navigate(route, options); }, hideStreamer: function() { this.streamRegion.currentView.$el.hide(); }, showStreamer: function() { this.streamRegion.currentView.$el.show(); } });
  • 14. @subedinishan Example - FilteredCollection this.filteredVms = VmsManager.Entities.FilteredCollection({ collection: options.collection, filterFunction: function(filterCriterion) { var criterion = filterCriterion.query.toLowerCase(); return function(vms) { if( vms.get("hostname").toLowerCase().indexOf(criterion) !== -1 && (vms.get("type") == filterCriterion.type || 'all' == filterCriterion.type) ) { return vms; } }; } });
  • 15. @subedinishan Example - message queue var VmsManager = new Marionette.Application({ onStart: function() { // Start a message queue this.messageQueue = new this.Entities.Messages(); this.messageRegion.show(new this.VmsApp.Message.Messages({ collection: this.messageQueue })); onMessage: function(options) { this.messageQueue.push(new this.Entities.Message(options)); }, }); !! Entities.Message = Backbone.Model.extend({ defaults: { type: 'info', timeout: 2000, isDismissible: true, message: 'Nothing to see here' } });
  • 16. @subedinishan Message - view Message.Message = Marionette.ItemView.extend({ template: 'message', className: function() { var val = ' alert col-md-12'; val += ' alert-' + this.model.get('type'); if (this.model.get('isDismissible')) { val += ' alert-dismissible '; } return val; }, onShow: function() { if (this.model.get('timeout') !== 0) { // Model not used after flash message display // so, set up a timeout for cleanup _.delay(_.bind(this.cleanup, this), this.model.get('timeout')); } }, cleanup: function() { var timeout = this.model.get('timeout'); this.$el.fadeOut(timeout/2); // We rely on the fadeOut successfully completing in timeout*2 time _.delay(_.bind(this.model.destroy, this.model), timeout*2); _.delay(_.bind(this.destroy, this), timeout*2); } }); Message.Messages = Marionette.CollectionView.extend({ childView: Message.Message });
  • 17. @subedinishan Examples - DSL Add.Vm = Marionette.ItemView.extend({ template: 'create', className: 'panel panel-default alert col-md-12', ui: { create: ".js-create-vm", size: "#size", search: "#search", base_role: "#base_role", role_add: ".js-role-add", primary: "#primary", ldap: "#ldap", }, bindings: { '#ldap': 'ldap', '#base_role': 'base_role', '#version': 'version', '#hostname': 'hostname', '#search': 'search', '#size': 'size', '#virt': 'virt', '#primary': 'primary', }, events: { "click @ui.create": "submitClicked", "change @ui.size": "sizeChanged", "click @ui.search": "searchClicked", "change @ui.base_role": "baseRoleChanged", "click @ui.role_add": "roleAdd", "change @ui.ldap": "ldapChanged", }, initialize: function() { this.model.fetch({success: this.render}); this.stickit(); var fetchingVms = VmsManager.request("vms:entities"); $.when(fetchingVms).done(_.bind(this.entitiesFetched, this));
  • 18. @subedinishan Template loading // Override Marionette template loading and compiling to use our template loader. _.extend(Backbone.Marionette.TemplateCache.prototype, { compileTemplate: function(template) { return template; }, ! loadTemplate: function(templateId) { var contents = raw_template_cache[templateId]; return function(data) { return Mustache.render(contents, data, function(name) { return raw_template_cache[name]; }); }; } }); !
  • 19. @subedinishan Template loading - PHP class TemplateHelper extends HelperBase { const DIRECTORY = "templates/"; const EXTENSION = ".mustache"; ! protected function get_template_names() { return glob(self::DIRECTORY . '*' . self::EXTENSION); } ! protected function to_array() { $return = []; foreach ($this->get_template_names() as $template_name) { $key = str_replace(self::EXTENSION, '', $template_name); $key = str_replace(self::DIRECTORY, '', $key); $return[$key] = file_get_contents($template_name); } return $return; } ! public function generate_template_javascript_variable() { return '<script language="javascript" type="text/javascript"> var raw_template_cache = ' . json_encode($this->to_array()) . ' </script>'; } } !
  • 20. @subedinishan Template Loading - PHP ! ! ! ! $app->get('/v2/.*?', function() use ($app) { echo $app->helper('Template')->generate_template_javascript_variable(); $page_object = new WebBase(); echo $page_object->render('base'); });
  • 22. @subedinishan Modal - inside ItemView this.confirmationModal = new BootstrapDialog({ title: 'Are you sure?', message: 'Do you really want to delete ' + this.model.get('hostname') + '?', type: BootstrapDialog.TYPE_DANGER, buttons: [{ label: 'DELETE', action: function(dialog){ parentScope.model.destroy({ success: function(model, response) { VmsManager.Common.Notifier.success(response, { fail: "VM deletion failed with response: " + response.message, success: "Delete VM successful", undef:"Delete VM came back with response" + JSON.stringify(response) }); }, error: function(model, response) { VmsManager.Common.Notifier.error(response, { error: "VM Deletion failed with response: " + JSON.stringify(response) }); } }); dialog.close(); } }], onhidden: function() { VmsManager.trigger('streamer:show'); VmsManager.trigger("vm:destroy"); VmsManager.trigger("log:subscribe", Etsy.data.username + "-delete"); }
  • 23. Marionette JS • active and helpful community • great upgrade support • adopted in the company • plug and play - able to glue things together • CollectionView @subedinishan
  • 24. @subedinishan Future of Marionette @ Etsy • Behaviors • Skeletor - build once, use everywhere • Migration • Adoption
  • 25. @subedinishan What worked? • Simplify implementation • Simplify abstractions • Leverage existing resources • Create additional resources
  • 26. @subedinishan Get your learning on
  • 27. @subedinishan Questions? ! We’re Hiring! etsy.com/careers