Archives

Blog index

Blog

  • How to Leave Your Job


    So you have a job offer from another company and are ready to leave your existing role. You want to leave without burning the social capital you’ve built up over time. What do you do?

    First, relax. Having that first conversation with your manager is usually nerve wracking, but everything will be okay. Switching jobs is expected, and chances are that your manager has been on both sides of the table before. Follow these guidelines below and you’ll be on your way to your new gig in no time.

    http://discoverpraxis.com/how-to-leave-your-job/

  • Celebrating 10 Years


    I started this blog ten years ago today. I was in a room at the Doubletree in Tarrytown, NY. It was a Sunday and it was the weekend in-between my first FEE seminars. I ordered a pizza and some ziti from Capri Pizza and decided to tear down my old HTML site and give blogging a try. I figured out how to install WordPress 2.5.1 and went to town. Here is what the site looked like:

    original blog

    Here is the post that started it all.

    Since then, I went on to work at FEE and move to NY, passing that Doubletree regularly. I blogged for a year straight. I’ve lived in 5 different cities and posted from every single one. I’ve written 763 posts on this blog since then and I average about 20,000 unique visitors a month according to my server logs.

    The blogging has paid off. Here are things that have come from my blogging and showing my work:

    • Photography gigs
    • Selling my photos
    • Photos published in dozens of newspapers, magazines, and websites because they were licensed as Creative Commons
    • The Light Graffiti Guide Sean and I wrote has been used by teachers all across the world.
    • Photos being shown at the 2008 Nuit des musées at Le CompaArchived Link, Conservatoire de l’agriculture in Chartres, France.
    • More contract gigs than I can count
    • My second full-time job (eResources)
    • My third full-time job (Praxis). Here is the post that put Isaac over the edge.
    • Weekly emails from people who found my tutorials helpful.
    • Meeting authors whose books I wrote about
    • Invitations to meetups and discussion groups
    • People using my code for their own projects
    • I’ve served up appliance repair manuals to hundreds of thousands of people. I let my Dad upload the ones he had back in 2008 to so that people could find them for free. Just doing our part to keep the Right to Repair/Fix It movement strong.

    Also, not only is today my 10 year blogging anniversary, it is also my 5 year wedding anniversary! Amanda and I had just started dating when I put this blog up, so she’s been here since the beginning. Little did I know that we’d get married exactly five years later.

    It’s been a fun ten years and I’m not done yet. cagrimmett.com is here to stay. Check out my archives and stay tuned for more.

  • Automated Accountability Check-ins


    The Need

    During the Praxis bootcamp, participants are expected to make every single day a non-zero day. Most participants ask us to hold them accountable, so they email one of our staff each day and that person emails them the next day if they miss a check-in. This takes an enormous number of emails, staff overhead to keep track of, and is difficult to search. Isaac asked me, “What can we do to automate this?”

    It needs this functionality:

    1. The ability to submit what you’ve worked on that day.
    2. The ability to see what you’ve submitted.
    3. The ability for staff to see what you’ve submitted.
    4. The ability to send an email to people who didn’t submit a check-in the previous day.
    5. The ability for logging who missed a check-in.

    The Restrictions

    There are existing systems that do this sort of thing, so why make our own? During the bootcamp, our customers use what we call The Portal, which is a curriculum platform I built on top of WordPress with the help of Restrict Content Pro and wpcomplete. I didn’t want to add another destination into the mix. I wanted a reason for customers to go to the Portal every day and keep their work moving forward. Plus, they already have logins there. No need to add yet another login for them to keep track of.

    The Solution Sketch

    1. USP Pro for front-end post submission
    2. Custom page template to display the content box but hide the tedious stuff: User ID, today’s date, user email
    3. New WordPress user meta field for keeping track of accountability opt-ins
    4. Custom script to check if opt-ins have posted within the last 28 hours and fire off a webhook to Zapier to send the email and log missed check-ins to a spreadsheet
    5. Zapier time trigger with a GET hook to run the script at a specific time to kick off the accountability check
    6. Custom template to display all opted-in participants and their check-ins to make it easy for staff to see everything in one place
    7. Hide the check-ins from search results

    The Solution Details

    Front-end post submission and showing posts

    USP Pro handles all sorts of complexity when it comes to submitting posts from the front-end of a WordPress site: The form, the custom post type, assigning the current logged-in user to be the author, and displaying success and error messages. They also have hooks and filters to make using the posts in templates easier.

    I used a custom template to hide some of the things I’d need to make the whole process work: User ID to assign as the post author, name and today’s date for the title of the post.

    After the form, we also want to show all the posts someone has already submitted. I do that with a WP_Query_  Note: I added a Delete button here in case some makes an accidental post. If you want someone to be able to delete their check-in post, they need to have the role Author.

    Here is the code I used for the template and comments about what it does:

    // Check if user is logged in. Display the check-in form if so, login form if not. if ( is_user_logged_in() ) { //Get the logged in user, today's date, and the PHP session ID 	$current_user = wp_get_current_user(); 	$today = date("F j, Y"); 	$ses_id = session_id(); ?>    class="usp-pro-form">  	 	 id="usp-pro" class="usp-pro usp-form-4102"> 	 id="usp-form-4102" class="usp-form" method="post" enctype="multipart/form-data" action="" data-parsley-validate="" data-persist="garlic">  		 class="usp-fieldset usp-fieldset-default"> 			 for="usp-content" class="usp-label usp-label-content">

    What did you do to make today a non-zero day?

    name="usp-content" id="usp-content" rows="5" cols="30" maxlength="999999" data-required="true" required="required" placeholder="Today I..." class="usp-input usp-textarea usp-input-content"> type="hidden" name="usp-content-required" value="1"> class="usp-hidden"> // Get first and last name, today's date, set that as the title. echo '. $current_user->user_firstname . ' ' . $current_user->user_lastname . ' ' . $today . '" type="hidden" />'; // Get user ID for author echo '. $current_user->ID . '">'; // Get PHP session ID. Plugin thing. echo '. $ses_id . '">'; ?> type="text" name="usp-verify" id="verify" value="" style="display:none;" class="exclude"> type="hidden" name="usp-form-id" value="4102">
    echo do_shortcode('[usp_form id="submit"]'); ?>
    class="submitted-checkins">

    Your submitted check-ins:

    // Show posts from this user ID with the type usp_post $post_query = new WP_Query( array( 'post_type' => 'usp_post', 'author' => $current_user->ID, 'orderby' => 'date', 'order' => 'DESC', ) ); if ( $post_query->have_posts() ) : while( $post_query->have_posts() ) : $post_query->the_post(); ?> post_class(); ?>>

    the_time('F j, Y \a\t g:i a') ?>

    the_content(); ?> href=" echo get_delete_post_link(); ?>" style="font-size: .8em;">Delete this check-in
    endwhile; else: echo "

    You have no check-in posts. Submit one above!

    "
    ; endif; ?>
    } else { ?> style="padding:50px;"> echo do_shortcode('[login_form]'); ?>
} ?>

The page (with a little bit of CSS added, which I’ll leave as an exercise for the reader) looks like this: Automated Accountability Check-in Home Page

Adding custom user meta field for accountability opt-in

This goes in functions.php:

add_action( 'show_user_profile', 'accountability_opt_in' ); add_action( 'edit_user_profile', 'accountability_opt_in' );  function accountability_opt_in( $user ) { ?>  	

Daily Accountability

class="form-table"> for="checkin">Opt in to daily check-in accountability? type="checkbox" name="checkin" id="checkin" if (get_the_author_meta( 'checkin', $user->ID) == 'True' ) { ?>checked="checked" }?> value="True" /> /> class="description">Yes, opt me in. } add_action( 'personal_options_update', 'save_accountability_opt_in' ); add_action( 'edit_user_profile_update', 'save_accountability_opt_in' ); function save_accountability_opt_in( $user_id ) { if ( !current_user_can( 'edit_user', $user_id ) ) return false; update_usermeta( $user_id, 'checkin', $_POST['checkin'] ); }

So, at the bottom of the user profile, it now shows this: Automated Accountability Check-in User Meta Field

Checking to see if opted in user has submitted a post in the last 28 hours

Outline:

  1. I trigger this by hitting the URL once a night. So no one else can trigger it, hit it with a long random key as a query string, which I first get and check.
  2. Load WordPress so I can use pre-built functions.
  3. Get list of opted-in users.
  4. Check to see if they have posts. If so, get the most recent one. If not, send a webhook to Zapier.
  5. If they have posts, check the date of the most recent one. If it is within the last 28 hours, do nothing. If not, send a webhook to Zapier. 28 hours gives a little wiggle room for early/late posts.

I like to leave the echos on for debugging if something goes wrong. Like, for example, if you use the_date instead of get_the_date and occasionally get NULL instead of a date and can’t figure out why. (Hint: the_date only fires once in a loop. Thanks for the help debugging, Eric Davis!)

Also, I know I’m using two different types for formatting for if statements. It helps me keep them separate.

 $key = $_GET['key'];  if ($key == 'RANDOM_KEY' ) {  	require_once($_SERVER['DOCUMENT_ROOT'] . '/wp-load.php');  	function get_user_by_meta_data( $meta_key, $meta_value ) {  		// Query for users based on the meta data 		$user_query = new WP_User_Query( 			array( 				'meta_key'	  =>	$meta_key, 				'meta_value'	=>	$meta_value 			) 		);  		// Get the results from the query, returning the users 		$users = $user_query->get_results();  		return $users; 	}  	$opted_in_users = get_user_by_meta_data( 'checkin', 'True');  	foreach ($opted_in_users as $user) { 		echo $user->ID . ' ' . $user->user_email . '
'
; $post_query = new WP_Query( array( 'post_type' => 'usp_post', 'author' => $user->ID, 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', 'showposts' => '1', ) ); // The Loop if ( $post_query->have_posts() ) : while ( $post_query->have_posts() ) : $post_query->the_post(); $date = get_the_date('U'); echo 'last post date: ' . $date . '
'
; //echo 'now: ' . time() . '
';
if( $date > (time() - 100800)) { echo 'Within 28 hours

'
; continue; } else { echo 'Not within 28 hours

'
; // Initialize curl $curl = curl_init(); $data = array( 'user_id' => $user->ID, 'email' => $user->user_email, 'first_name' => $user->first_name, 'last_name' => $user->last_name, 'last_post' => $date, ); $jsonEncodedData = json_encode($data); $opts = array( CURLOPT_URL => 'https://hooks.zapier.com/hooks/catch/1503890/aqztv5/', CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POST => 1, CURLOPT_POSTFIELDS => $jsonEncodedData, CURLOPT_HTTPHEADER => array('Content-Type: application/json','Content-Length: ' . strlen($jsonEncodedData)) ); // Set curl options curl_setopt_array($curl, $opts); // Get the results $result = curl_exec($curl); // Close resource curl_close($curl); echo $result; } endwhile; else : echo "No posts

"
; // Initialize curl $curl = curl_init(); $data = array( 'user_id' => $user->ID, 'email' => $user->user_email, 'first_name' => $user->first_name, 'last_name' => $user->last_name, ); $jsonEncodedData = json_encode($data); $opts = array( CURLOPT_URL => 'https://hooks.zapier.com/hooks/catch/1503890/aqztv5/', CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POST => 1, CURLOPT_POSTFIELDS => $jsonEncodedData, CURLOPT_HTTPHEADER => array('Content-Type: application/json','Content-Length: ' . strlen($jsonEncodedData)) ); // Set curl options curl_setopt_array($curl, $opts); // Get the results $result = curl_exec($curl); // Close resource curl_close($curl); echo $result; endif; } } else { exit("You are not authorized. Go away."); } ?>

Template for viewing posts by user

Viewing posts in the wp-admin area is less than ideal. They are grouped by date instead of author and the full contents doesn’t show unless you click. No want to view updates at a glance. So I made a template for that.

// First, check if user is an admin. if ( !is_user_logged_in() || !current_user_can('administrator') ) { 	   wp_redirect( site_url() );  exit; } else { // User IDs are passed via query string. If no string, show all names 	$user = $_GET['id']; if ( $user == null ) {?> 	

Participants who have submitted check-ins:

$args1 = array( //'role' => 'author', 'orderby' => 'first_name', 'order' => 'ASC' ); $authors = get_users($args1); foreach ($authors as $user) { $user_post_count = count_user_posts( $user->ID , 'usp_post' ); if ( $user_post_count > 0) { echo '. $user->id . '">' . $user->first_name . ' ' . $user->last_name . '
'
; } } } else { $user = get_user_by('id', $user); $post_query = new WP_Query( array( 'post_type' => 'usp_post', 'author' => $user->ID, 'orderby' => 'date', 'order' => 'DESC', ) ); ?> />

href="/check-ins/"> Back to all participants with check-ins

echo $user->first_name . ' ' . $user->last_name .'\'s check-in posts';?>

if ( $post_query->have_posts() ) : while( $post_query->have_posts() ) : $post_query->the_post(); ?> post_class(); ?>>

the_time('F j, Y \a\t g:i a') ?>

the_content(); ?>
endwhile; else: echo "

You have no check-in posts. Submit one above!

"
; endif; } }

Here is what the template outputs:

check-in post user list

user posts

The best way to do this is to hook in to when the post type is initiated and toggle the ‘exclude_from_search option so ‘true’. I added this to functions.php:

add_action( 'init', 'usp_post_hide_search', 99 );  function usp_post_hide_search() { 	global $wp_post_types;  	if ( post_type_exists( 'usp_post' ) ) {  		// exclude from search results 		$wp_post_types['usp_post']->exclude_from_search = true; 	} } 

Using Zapier

I opted to use Zapier for two things:

  1. Triggering the nightly post checking script via GET. wp_cron isn’t super reliable if you need something to run at a given time, and since I’m hosting on WPengine, I don’t have access to the server cron. We already use Zapier for a ton of stuff, so I fire off a GET to my script with the query string random key.
  2. Sending the email and logging emails sent. I could have done this all in the script, but I wanted staff to be able to easily change the email and possibly add more post-email items like sending a message to Slack and Salesforce, or sending a text message to the user.

Going Forward

Here are a few ways I want to improve this when I have time:

  1. Extend the template that staff uses to see check-in posts to include filter criteria.
  2. Let users opt-in and opt-out of automated accountability instead of admins opting them in or out.
  3. I might want to expire or hide posts
  4. USP by default doesn’t preserve line breaks. I’ll need to update that.
  • 12 Tips for New Managers


    Praxis alumni Nate Baker and Nick Rundlett both became new managers this month. Congrats, guys! Nate asked the community for tips on how to be a good supervisor. Here is what our advisors and alumni had to say.

    https://discoverpraxis.com/12-tips-for-new-managers/Archived Link

  • 16 Books That Shaped How I Interact with the World


    Yesterday my friend Derek Magill asked the Praxis community, “What books have had the most impact on your career/education?”

    I love this question. Notice the use of the word impact. He isn’t asking for your favorite books, the books you recommend on a specific subject, or the books you agree with the most. He is asking for the books that have had a significant impact on your education. Books that influenced and changed the way you think and interact with the world.

    Here is my list, in order of when I read them. I explain what impact they had on my education and how that shifted the way I interact with the world.

    https://discoverpraxis.com/16-books-shaped-interact-world/Archived Link

  • How to send custom conversion events back to Facebook advertising with ConvertFlow


    I made a video that shows you how to send custom conversion events to Facebook whenever a visitor, lead or customer engages with one of your website’s calls-to-action – all without having to touch your website’s code. It was featured on the ConvertFlow blog!

    Check it out here: https://convertflow.com/blog/how-to-use-convertflow-with-your-facebook-ad-campaigns/Archived Link

  • New book notes: Reamde by Neal Stephenson


    Reamde is a wild ride that traverses half the globe, has multiple storylines intertwined, and jumps back and forth from the virtual and physical world. Stephenson is a captivating writer who pulls you into the story. He makes 1000+ pages feel like 300.

    Check it out here: http://cagrimmett.com/book-notes/reamde_neal_stephenson/

  • Improving Jekyll Build Time


    When I migrated all of my old posts to Jekyll, my build time skyrocketed. The average time was 39 seconds for 853 posts. Abysmal. This is especially annoying when I’m actively working on the site with jekyll serve and waiting 40 seconds for changes to reflect.

    The first step to figuring out what takes so long is running the Liquid profiler: $ jekyll build --profile

    This is what it output for me:

    Filename                                                | Count |    Bytes |  Time --------------------------------------------------------+-------+----------+------ _layouts/default.html                                   |   853 | 9652.42K | 7.462 search/feed.json                                        |     1 | 1394.16K | 4.148 _includes/header.html                                   |   853 | 1230.35K | 3.485 _layouts/post.html                                      |   755 | 5119.00K | 2.301 _includes/opengraph.html                                |   853 |  870.67K | 1.694 _includes/icons.html                                    |   810 | 1752.89K | 1.454 sitemap.xml                                             |     1 |  203.34K | 1.321 _includes/footer.html                                   |   853 |  109.12K | 0.273 til/posts.yml                                           |     1 |   14.27K | 0.147 about.md                                                |     1 |   18.02K | 0.085 _posts/2016-07-04-posts-heatmap-calendar.md             |     1 |   16.78K | 0.080 isotope.html                                            |     1 |  389.52K | 0.048 _layouts/book.html                                      |    34 |  198.47K | 0.039 til.md                                                  |     1 |   58.76K | 0.023 feed.xml                                                |     1 |  139.11K | 0.022 ... with a dozen more tiny blog index pages from paginator  done in 39.359 seconds. 

    I tried a bunch of random stuff that made tiny improvements before I started doing research: Removing extraneous files, cleaning up if blocks that I no longer used, hardcoding things in the header that relied on variables from _config.yml, etc. None of this made significant improvements. I had already moved extraneous plugins long ago and I keep Jekyll up to date, so there was nothing more to do there.

    I eventually came across this post by Mike Neumegen, which mentioned jekyll-include-cache, which caches the files you tell it to with the first instance and then serves the cached versions up for subsequent requests. For includes that don’t change based on the page content, you can replace {% include example.html %} with {% include_cached example.html %}. When I did this with my sidebar, nav bar, and footer, it cut my average first build time to 28 seconds, with subsequent regeneration times around 19 seconds. Awesome! (Neumegen’s post, which mentioned jekyll-include-cache contains a lot of other helpful advice for Jekyll build optimization. Check it out!)

    My next step was to look at other includes to see if I could shave more time off. Some files had a mix of static and dynamic assets, so I split out all of the static assets into more include_cached includes. This cut another 5-6 seconds off of my build time. Boom.

    Now that my layouts and my includes were as optimized as I could get them without major rewrites, I turned to the next major time hog: search/feed.json. 4 seconds for a single file! This file loops through everything on the site and dumps it into a single file so that I can crawl it with Javascript to power search on my site.

    I used Mat Hayward’s search project with a few tweaks. As I dug deeper on the specific functions in the feed.json generator, I noticed that everything was being converted from Markdown to HTML first, then HTML stripped out, then turned into JSON. I wondered if skipping the markdown conversion step would lead to inaccurate search, so I tried it. Not only did my search function the same, but it dropped feed.json’s build time down to around a sixth of a second.

    My build time now looks like this:

    Filename                                                | Count |    Bytes |  Time --------------------------------------------------------+-------+----------+------ _layouts/default.html                                   |   853 | 9564.71K | 2.922 _includes/head.html                                     |   853 | 1571.79K | 2.623 _includes/opengraph.html                                |   853 |  870.67K | 2.171 sitemap.xml                                             |     1 |  203.34K | 1.177 _layouts/post.html                                      |   755 | 5195.80K | 0.509 search/feed.json                                        |     1 | 1394.65K | 0.176 _posts/2016-07-04-posts-heatmap-calendar.md             |     1 |   16.78K | 0.091 about.md                                                |     1 |   18.02K | 0.076 isotope.html                                            |     1 |  389.52K | 0.050 _layouts/book.html                                      |    34 |  198.47K | 0.030 _includes/header.html                                   |     1 |    1.43K | 0.021 til.md                                                  |     1 |   58.76K | 0.019 feed.xml                                                |     1 |  139.24K | 0.018 ... with a dozen more tiny blog index pages from paginator  done in 19.594 seconds. 

    Regeneration times when running jekyll serve are hovering around 13 seconds now, which is a significant quality of life improvement for me when managing this blog.

    There is probably more work I can do with moving the highlighter to the front end with JS and simplifying the Liquid in head.html and opengraph.html, but I’m stopping here for now.

  • Troubleshooting Problems Installing Craft CMS on Laravel Homestead


    Today I installed Craft CMS in for a new Praxis project. I use Laravel Homestead for my local development environment. It installs like pretty much every other PHP app on Homestead (use this guide by Matt Collins if you aren’t very familiar with Homestead), but I ran into a few annoying errors along the way:

    Mcrypt is required

    If Mcrypt is required shows when you first go to http://yoursite.local/admin to finish the install, it is probably because you are a good citizen and your Homestead box is actually up to date, running PHP 7.2 as the default. Here’s the issue: mcrypt was removed from PHP 7.2. Craft 2 still needs it. There are three solutions:

    • If you need PHP 7.2, you’ll have to install Craft 3. It is still under development as of this writing, so I didn’t take that path. (Update April 2, 2018 – Apparently Craft 3 is launching on April 4, so you won’t have this issue for long!)
    • You can install the mcrypt extension for PHP 7.2.
    • You can use a different PHP version. I took this route. Homestead makes this super simple by allowing multiple PHP versions. In your Homestead.yaml, set the php variable to php: "7.0" under your site in the Sites block. Here is what mine looks like:
    - map: craft-praxis.local      to: /home/vagrant/craft-praxis/public      php: "7.0"

    GROUP BY incompatible with sql_mode=only_full_group_by

    If you see this message, chances are that you are using MySQL 5.7.5+, which changed the way it deals with GROUP BY. See this StackOverflow post for the two solutions: Removing the ONLY_FULL_GROUP_BY option from sql_mode or adding the initSQLs database config settings in craft/config/db.php.

  • A letter to David Hogg: You are better than college

    Photo courtesy of ABC News

    David,

    First, I want to extend my sympathy for you and your friends at Marjory Stoneman Douglas HS and Parkland. No one should have to go through something like that, let alone be thrown into the public spotlight and questioned and mocked by people who disagree with you. You are a brave, impressive dude.

    That’s why I don’t think you need college.

    A college degree is a signal. The thing is, this signal is not as valuable as you think. It’s a signal to the world of the value you can create in the market. It conveys information about your ability, skill, and intelligence. There is a lot of noise in the world of work, and it’s hard to figure out who’s worth working with. A degree cuts through some of that noise and puts you in a smaller pool of competitors.

    College persists for the ambitious — and thus the self-reinforcing data about successful people having degrees — because of a religious-like belief in it’s necessity. But it’s not necessary at all.

    Not long ago a degree may have been the best signal most people could get. There weren’t many ways to demonstrate your value to the market, so a degree was one of the better bets.

    Things have changed dramatically. Technology has opened up the world. The tools available to you now have lowered search and information costs, and you can create signals of your own that are far more powerful than a degree. Now, a college degree is one of the weakest, least common denominator efforts to doing this and is easy to surpass.

    You told Axios that you are thinking about taking a gap year. That is a great idea.

    I encourage you to continue throwing yourself into activism, organizing, and speaking. Learn how big events are planned and operated behind the scenes. Learn what different kinds of messaging works best for certain audiences. Continue refining your speeches. Make personal connections. Build email lists, communities, and followings. All of this will help you gain the experience and skills that future employers will value. Don’t spend the next four years in a classroom, wasting the valuable opportunity you have now.

    You have forward tilt. You have natural sales skills. You have organizing skills. You have some marketing experience. You know how to work in fast-paced environments. You have a drive and focus that most people your age will never come close to.

    At Praxis, we work with hundreds of bright, ambitious young people and help them launch careers without degrees. They’re better than college. You are too!

    Sincerely,

    Chuck Grimmett
    CTO
    Praxis

  • How to Get Your GitHub Profile Ready For Job Applications


    If you are going out for a development job or any sort of technical job, especially if you don’t have a lot of professional experience in the field, the best thing you can do is put some of your work on display. GitHub is the most popular place to do that.

    Here are the guidelines we give our technically skilled participants at Praxis for getting their GitHub accounts ready for their apprenticeship applications and interviews.

    https://discoverpraxis.com/get-github-profile-ready-job-applications/Archived Link

  • How to Get Work Done: A Primer


    Here are the three pillars of getting work done. If you are having trouble with staying on top of your work, chances are that it comes back to one of these three main areas.

    This is common advice for our participants as they complete their challenging apprenticeships at growing startups.

    https://discoverpraxis.com/get-work-done-primer/Archived Link

  • Three Recent Participant Projects I Love


    We have a big focus on projects (learning by doing!) at Praxis. Here are three recent participant projects I love and what the projects signaled. https://discoverpraxis.com/three-recent-participant-projects-love/Archived Link

  • The Sheep at Sawkill Farm


    Amanda and I spent the weekend exploring the Hudson Valley. On our way back today, we drove through Red Hook and saw that the Sawkill Farm farm store was open, so we stopped to pick up some meat. I had my camera gear in tow and they graciously allowed me to take some photos of their sheep. It was a drizzly day, but I had a wonderful time taking photos. The sheep have so much character and personality!

    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
    Sawkill Farm Sheep
  • Compared to What?


    People often compare actual things with imagined, perfect, idealized alternatives. Asking Compared to what? helps you frame your decisions in relation to the relevant alternatives and ground them in reality. https://discoverpraxis.com/compared-to-what/Archived Link

  • Changing the CSV Delimiter and Enclosure Characters on a Mac


    TIL that you can’t change the CSV delimiter or enclosure characters on Excel for Mac or Apple Numbers. You have to use Open Office, and even there it is tricky. You need to go to File > Save As, select Text CSV, then make sure you check the “Edit Filter Settings” checkbox:

    CSV Open Office Filter Settings

    Then you get the field option AFTER you hit Save, only if the Edit Filter settings box is checked:

    CSV Open Office Field Settings

  • Building JSON for WPComplete in Excel with Concatenate


    I’m using the ImportWP Pro plugin to quickly create pages in the Praxis curriculum portal. One of the custom fields I need to set is for the WPComplete course system we use. It takes JSON for which course the page belongs to and which page to redirect the to once the current page is marked complete. Here is an example:

    {"course":"New Module 4 - Thinking","redirect":{"title":"You Have No Idea How Wrong You Are","url":"https://portal.discoverpraxis.com/bootcamp/new-module-4/no-idea-how-wrong"}}

    I needed to build this JSON in Excel with the rest of the page info. The pages I’m setting the redirect title and URL for are being created in the spreadsheet, too. So I wanted to build this JSON cell from the contents of other cells. The commas and quotes were a problem for my typical way of building text from other cells: =B5&" "&C5

    Here is the new solution I found: Break the JSON down into constituent parts, put each in its own cell, then build the JSON with the concatenate() function.

    Here are the 5 cells I needed:

    1. {"course":"New Module 4 - Thinking","redirect":{"title":"
    2. The title from a spreadsheet column
    3. ","url":"https://portal.discoverpraxis.com/bootcamp/new-module-4/
    4. The page slug from a spreadsheet column
    5. "}}

    I put 1 in A23, 3 in A24, and 5 in A25. Then pulled 2 and 4 from various cells in columns A and B. Then my JSON cells used this formula: =CONCATENATE(A23,A4,A24,B4,A25)

    Concatenated together, it looks like this:

    {"course":"New Module 4 - Thinking","redirect":{"title":"PAGETITLE","url":"https://portal.discoverpraxis.com/bootcamp/new-module-4/PAGESLUG"}}

    Excel Concatenate

  • macOS Excel Fill Down Keyboard Shortcut


    I never remember the keyboard shortcut for Fill Down in Excel on macOS, so I’m posting it here where I’ll have it.

    Control + D

  • Three More Book Notes


    I’m catching up on my book notes. Today I posted three more before work:

    1. Show Your Work by Austin Kleon
    2. Shoe Dog by Phil Knight
    3. The E-Myth Revisited by Michael Gerber

    More to come, so stay tuned!

  • Catching up on Book Notes


    I’m catching up on my book notes. There are a lot of books I read this past year that I neglected to post notes for, so I’m getting caught up on that. I posted five new books to my Book Notes section today:

    1. Kindred by Octavia Butler
    2. Stories of Your Life and Others by Ted Chiang
    3. A Burglar’s Guide to the City by Geoff Manaugh
    4. Boon Island by Kenneth Roberts
    5. The Story of Sushi by Trevor Corson

    Lots more to come, so stay tuned!

  • How to Back Up Your Laravel Homestead Databases


    Today I upgraded from Homestead 4.0.0 to 5.0.1 so I could test my sites in PHP 7. That was a major upgrade and a number of things changed, so I decided that I needed to back up my databases before I did the upgrade. I’d only ever dumped specific databases before, but TIL how to dump them all at once. Here is how to do that in Laravel Homestead:

    1. $ cd ~/Homestead
    2. $ vagrant ssh
    3. $ mysqldump -u homestead -psecret --all-databases > Code/homestead-YYYY-MM-DD-hh-mm.sql
    4. Test the sql file. I had a syntax error the first time and it only wrote out an error message. You’ve been warned.
    5. Move the SQL file somewhere safe. IE outside of any folder you might delete or change during the upgrade.
    6. After the upgrade, here is how to reload your databases in Homestead: $ mysql -u homestead -psecret < Code/homestead-YYYY-MM-DD-hh-mm.sql

    I had some major issues with the upgrade. When I first tried it, vagrant up hung at the SSH auth step and never moved. I went down a deep rabbit hole with my friend Eric Davis trying to debug the SSH issues, to no avail. My last trial was to roll back to Homestead 4.0.0, check my vagrant ssh-config settings, then try the upgrade with those settings. Then, miraculously, when I tried the upgrade again, vagrant up worked with no SSH problems! No difference in settings. I’m baffled, but relieved.

    FYI, there are some Homestead.yaml differences between Homestead 4 and 5, so make sure you have some time set aside to read the docs, update your yaml file, and rerun vagrant up --provision a bunch of times to get everything working again.

  • Free Charting Tools for Your Next Article

    A Praxis participant emailed me last week asking for recommendations on free embeddable charting tools for an article he is writing. I thought that the information would be useful for everyone, so I wrote an article with examples! https://discoverpraxis.com/free-charting-tools-next-article/Archived Link

  • Rebuilding cagrimmett.com and Moving Old WordPress Posts to Jekyll


    Earlier this week I did a major revamp of cagrimmett.com. It started with redesigning my page templates to include a sidebar, then it morphed into making a long-standing goal of mine reality: Reviving the posts from my old 2008-2012 WordPress blog and getting them into Jekyll.

    The Redesign

    The redesign started as a seed in my mind when I visited http://datavisualizi.ngArchived Link a few months ago. I loved the look of the sidebar with the social links. What I didn’t love about it was the way the menu works and how the sidebar is sticky. I noticed the site is also built on Jekyll, so I briefly considered installing the theme, but I decided against it after about 30 seconds. I wrote most of my current theme myself and don’t want to mess up my TIL and Book Notes templates. So I took an afternoon and rewrote some of my templates to include a sidebar.

    The trickiest part was figuring out what I wanted to do with the sidebar on smaller screen sizes. I decided to push it up to the top on pages, show the social buttons on the Home and About pages, and then hide it altogether on posts.

    The other main issue was what to do with featured images. My templates were built to include featured images on posts before, but this time I decided to make them optional. The home page and archive pages don’t display featured images, but the category pages do.

    Migrating my Microblog to Jekyll

    Last year I started a Microblog, built a custom theme for it, and posted there 116 times over a few months.

    I found it confusing to have multiple places to store content, so I decided to consolidate. I’m keeping cooklikechuck.com separate, but I’m killed cagrimmett.com and moved all the posts here. This was originally on WordPress, so it a small test case for whether or not it would be possible to resurrect my old 2008-2012 blog and migrate the posts to Jekyll.

    There is an official jekyll-import gem that supports WordPress, so I figured that this was going to be a piece of cake. That is, until I tried to install the gem. An hour later and 5 dependencies deep down the debugging rabbit hole, I finally gave up. Cryptic error messages somewhere around the mysql2 dependency finally made me throw in the towel and go to bed.

    I woke up the next morning with a renewed sense of confidence and started searching. I came across this WordPress to Jekyll exporter, which worked phenomenally well. It dumped all of my posts out as .md files with the WordPress metadata translated into YAML front matter.

    I was a little worried about what to do with all of the image links, but then I realized that all uploaded images in WordPress are stored at /wp-content/uploads/year/month/filename.jpg. If I add that /wp-content/uploads/ directory to Jekyll wholesale and replace all instances of cagrimmett.com with www.cagrimmett.com, all images in posts will work! So I pulled the directory down off my server, dusted off my grep skills, and everything worked as intended. Success.

    The only other hiccup was that about half of my posts on the microblog didn’t have titles. That is part of the whole microblog philosophy. Those posts showed up in my main feed with just ID numbers, which looks strange. I solved that by adding hidden: true to the posts to hide them from the Jekyll paginator. Win number 2 for the old grep skills.

    Resurrecting CAGblog

    After my success with copying over my microblog posts, I decided to kick the difficulty up a notch and see if I could move over 500 posts from my old 2008-2012 site, which I affectionately called CAGblog. I was eager to get this stuff back online because 2008 is when I first started blogging, and I wanted my internet cred back. I know some of my stuff from when I was 18 is pretty cringeworthy, but having my old stuff up for posterity really matters to me.

    Here is one of the few screenshots I have of it, from 2008:

    CAGblog

    Before I shut the blog down in 2013 and put up a landing page in its place, I made a backup and promptly forgot where I stored it. After hours of searching old hard drives, I finally found it on dropbox.com. It hadn’t synced to my local machine because I had selective sync turned on. I almost panicked earlier because I thought I had lost all my old stuff. Thank goodness for Dropbox!

    The first step in the revival was to restore my old database to a working WordPress site so that I could install the Jekyll exporter plugin. I only really needed the posts, not a complete working site, so I ignored all my old post templates and stuff. The database was from an early version 3 of WordPress, so I was concerned that it wouldn’t work on my Homestead dev environment and I’d have to go hunt down an old WordPress install somewhere. Thankfully WordPress has a bunch of update routines in place to handle just this kind of thing, so after going through a few “You need to update this database” prompts, I was able to log in to the wp-admin area and access all my old posts.

    Just like on cagrimmett.com, the WordPress to Jekyll exporter worked exactly as intended. Now the fun began: I had a ton of old categories and tags I didn’t want to use, old images I needed to find and get linked up, and old links that needed to be rewritten.

    Categories and Tags

    For categories and tags, I simply edited the yaml metadata on the old posts and changed the keys to oldcategories and oldtags so that Jekyll wouldn’t parse them as normal. Grep win number 3.

    For old images, I used the same method as on my microblog: I moved the wp-content/uploads/ folder to the root of my Jekyll site. That took care of about 50% of the broken images. The rest were scattered around other folders, so I searched for broken images, looked at where they were, and moved over every folder I could find. There are a few casualties, namely images that I didn’t host myself, but the vast majority are back up and running. I was a n00b 10 years ago and just threw stuff in my main directory, so now I have a bunch of old stuff cluttering up my previously pristine main Jekyll folder. Perhaps one day I will move all that to more logical folder structure and write redirects, but for now it is all staying where it is.

    The links aren’t easy to preserve on a static site because I used the default post ID permalinks on my WordPress site. Those are the ones that look like website.com/?p=2347. I used the permalink key in Jekyll to set the permalink to /blog/[oldid].html. (Another win for grep. Man, regex is useful.) Then I followed this post to do some apache magic, fetch the ID from its query string, and redirect it to its new home. Now all of my old post links redirect to their new home on my Jekyll site! I clicked a few old ones from Facebook just to make sure.

    When I moved to Cloudflare, my site search broke. With all of this old content back on my site, I knew I needed to get search working so the posts are findable, so I took a look at it again. The issue was that I had been submitting the form action to the same page. The query string didn’t get preserved with all the redirects/rewrites I’m doing/Cloudfront does, so I edited the submit function to just kick off the JS search function with the query directly instead of submitting/refreshing the page. Go run some searches and dig through my archives!

  • SVG Viewbox


    Today I wrote an article for the Praxis blog outlining four free charting/visualization tools and showing examples of visualizations created with those tools. One outputs SVGs, but they are a fixed size. But since SVGs are just code, I figured I could make it responsive.

    First I tried the old responsive image trick: width: 100%; height: auto; – This resulted in the SVG scaling, but only the top 10% was visible.

    After some Googling, I came across this article: How to Scale SVG on CSS Tricks. Here I learned about the viewbox attribute. The viewBox does many things:

    • It defines the aspect ratio of the image.
    • It defines how all the lengths and coordinates used inside the SVG should be scaled to fit the total space available.
    • It defines the origin of the SVG coordinate system, the point where x=0 and y=0.

    My original SVG was 848px by 500px. So, I set viewbox="0 0 848 500" and width=100% and removed the height attribute.

    This led to a nice scalable SVG:

    2005200620072008200920102011201220132014201520162017East GateNorth GateNortheast GateSouth GateWest GateHighway 191

  • Altcoin Research: What is Sia?


    This month I’m spending some time researching altcoins. I’m posting my notes on Yours.org. First up: Sia. https://www.yours.org/content/altcoin-research–what-is-sia–c72e723a56a0