Last updated 2 years ago by nddery .
ISC · Repository · Bugs · Original npm · Tarball · package.json
$ cnpm install glossier-dot-com 
SYNC missed versions from official npm registry.

Staging: (Tracks the staging branch)

Production: (Tracks the master branch)

Barney: (Tracks the master branch)

Build Status


Style guides


Environment variables

create an .env file and copy in the contents from the Glos V2 Env shared secure note that is in 1Password.


git clone
cd glossier-v2

How to run the project

You can run the server with these commands:


And in a separate console:

bundle exec sidekiq

Clearing the cache

When you get unexpected behavior, for example an alert when loading the page that reads: Stripe Payment is not configured in Spree you will have to clear the cache like this: bundle exec rails runner Rails.cache.clear and restart the server.

Running the Test Suite

To run the entire test suite, or to prepare your local environment for working with the tests, simply run:


To run specs individually:

bin/rspec spec/path/to/file_spec.rb


How to configure your Ruby editor for Rubocop

How to configure your JavaScript editor for StandardJS

How to run JS tests from the terminal

To run a single instance of front-end unit tests: | yarn test

To run the tests whenever a change is detected:

| yarn test:watch

Note: Chrome 58+ is needed to run your tests headlessly.

Stripe tests

The following credit card number will generate errors after entering a new card and going to the next step:

  • 4000000000000127: Your card's security code is incorrect
  • 4000000000000069: Your card has expired
  • 4000000000000119: An error occurred while processing your card. Try again in a little bit.
  • 4000000000000002: Your card was declined - when submitting card to use for payment
  • 4000000000000341: Your card was declined - when placing order

See all possible tests here:

Stripe Webhooks

When a card is updated by Stripe (expiration date for instance), we can subscribe to that event. The Stripe account must be configured with a URL similar to this one: Replace the domain name with the proper one.

To receive those events from Stripe, you need a tunnel. If you do not have localtunnel installed, do this: npm install -g localtunnel. Then run localtunnel on port 3000: lt --port 3000.

Localtunnel then displays the public URL to use. Copy this URL and use it in your webhook configuration screen in Stripe.

To test the webhook, click on the button Test Webhooks in the save window where you configure the webhooks in Stripe. This will send a dummy request to you application.

You can also test with a real card. In Stripe, go in the Customers list, edit one of them and change the expiration date of the card. You should then receive a request for it.

About the compiled assets


The work done to reach a better Sass compile time means we are compiling the Sass stylesheets using Libsass. This has an unfortunate outcome of not working with the usual Sprocket compilation when deploying. The main reason for this is because Libsass does not support asset-url type functions like Ruby Sass does and will probably never will.

This means that a shim function was introduce to fake the support of asset-url for the development environment:

$image-url-path: "../../../" !default

@function asset-url($url)
  @return url("#{$image-url-path}#{$url}")

@function image-url($url)
  @return asset-url($url)

For deployments we defer to Sprockets in order to leverage fingerprinting and CDN support, the shim isn't necessary and we generate an scss file which is then picked up by Ruby Sass for an aditional compilation. We do this because the first sass to scss compilation leaves asset-url type functions in place which in turn Ruby Sass can interpret.

The structure of the generated assets is as follow:

↳ assets
  ↳ stylesheets
    ↳ spree
      ↳ frontend
        ↳ source
          hot.sass             <- @import `all.sass` and `configs/_funtions.sass`
          all.sass             <- @import everything else, used for `assets:precompile`
        ↳ build
          hot.css          <- Generated by `npm script styles`
          static.css      <- Generated by `npm script styles`


Where is the .babelrc you ask ? There are none. The babel options are passed directly to the Webpack babel-loader in config/webpack/environment.js. This is done so that we can overwrite the options for the test build. See these files for background.

Front-end app structure

The app is found at javascripts/spree/source/frontend and is structured as follows:

+ –– featureName
+ –– shared
+ –– services
+ –– factories
+ –– configs.js (everything that needs to be configurated in `.config`)
+ –– init.js (module creation and dependencies)
+ –– run.js (everything that needs to be ran on bootstrap via `.run`)

build (output folder)

+ –– redirect.js (safe redirection after migrating away from Angular #! routes)
+ –– init.js (app creation and init)

The result is compiled into build.js and templates.js to be added into Rails's application.js manifest.

The third party scripts are compiled independently into libs.js.

Angular specifics

HTML5 Mode

Since we are using HTML5 mode, we require to add a <base> meta element in the header of the site on every page in order to tell after which path element in the url Angular starts taking over. This is done in the _head.html.slim file.

One caveat is that it would be causing issues on the homepage (since it has a base of /), we need to insert a target="_self" on every link on the page to force that link to not go through Angular's routing. This is done in the run block, which is called on every page:

Paperclip images

To reprocess the thumbnails:

RAILS_ENV=staging bundle exec rake paperclip:refresh:thumbnails CLASS=Spree::Image

Generating front-end assests

Raster images and svg files in the project are optimized and handle through yarn scripts (see the package.json file here).

The images folder is devided in 3 sections:

  • logo (untouched)
  • source: this is where we add the source unoptimized files and assets
  • build: this is the destination of the output of yarn scripts (this will get replaced when running scripts so we should not add files directly in here)

Here are the steps:

  1. To optimize the raster images, run the yarn images
  2. To create the various svg stores, run the yarn svgs
  3. To create the templates.js, run yarn templates
  4. You can all of them by entering yarn build

Svg folder structure, store splitting and lazyloading

When generated from the yarn svgs script, the svg stores will be split according to this collection, the last array item rest being the catch-all key for all the folders that have not been specified before. For example, if the array contains ['foo', 'bar', 'rest'], it will generate three stores: foo.svg, bar.svg and rest.svg.

You can then use the svg fetcher helper to lazy-load (or not) those stores on the site, like it is currently done here.

Please note that only base.svg is immediately loaded, so that store should stay as small and crucial as possible, while the other stores are lazy-loaded on DOMContentLoaded.

Responsive images & lazy-loading

If possible (and where relevant) we use lazy-loading and responsive images to make the loading time of the site as tiny as possible. This is achieved via a plugin called lazysizes that takes care of those 2 things.

To use it, simply replace a normal img tag by this bit of markup:

  img.lazyload data-sizes="auto" data-srcset="#{image[:portrait_small_url]} 480w, #{image[:portrait_normal_url]} 1200w" data-src="#{image[:portrait_small_url]}"

Use the data-src fallback in order load images on browsers that don't support srcset.

Webpack bundle analysis

To see an analysis of the current production bundle, you can run yarn js:analyse.

It will

  • open a broswer tab and load an html output,
  • create such html file on the root of the app,
  • also generate a json representation of that bundle

You can also choose to run bundle-buddy on the generated sourcemaps. This is not part of the yarn js:analyze command yet. You'll need to have bundle-buddy installed (npm i -g bundle-buddy).

$ rm -rf public/packs
$ yarn js:analyze
$ bundle-buddy public/packs/*.map

Delighted integration

Delighted is a SAAS for sending surveys to customer. Using their API, we send then the order & customer data. This is done in 2 steps:

  1. The Survey instance is created when the order transitions to "complete".
  2. Rake task delighted:process_surveys then processes unprocessed surveys, and sends them to Delighted.

You can manually process the survey for an order this way:


Things to be aware of:

  • On Delighted, you can only see the properties we send after the customer has responded to the survey.
  • Delighted does not have a concept of "test mode". So you have to apply for a free trial and change the API key.
  • Delighted will not send more then one survey to an email address within 1 month.
  • We can ask Delighted to "delay" the survey. The Survey class already does that: it will make sure the survey is delayed so that the customer receives it 1 month after the purchase.

For testing on staging:

  • You need to use different addresses (because of the 1-month limitation). One thing you can do is to go in the Admin and change the email address of a user (if you want to test the data sent for a returning customer)
  • The send delay logic is effective on staging. But we have a hack for forcing of a delay of 0: put the Balm Dotcom in your cart.
  • The rake task on the staging server will run every 30 minutes.

Klayvio integration

We use Klayvio for mailing list subscribers. More notes to follow..

Static Pages with Contentful

How to create a new static page with Contentful:

  1. In Contentful go to the Entries tab and click create a new entry and in the dropdown select Page.
  2. Give the page a title and a slug; the slug will be the url location of the page.
  3. Add blocks to the page; existing block templates can be used or a new template can be created.
    1. In order to create a new block template, go to the Content Types tab and click the settings link on the template field.
    2. In the Validations tab enter the name of the template under Predefinded Values.
    3. In the project create a new partial under app/view/pages/blocks with the name that was entered on Contentful in lowercase, preceded by an underscore.
  4. In preview mode, the page will show up at its intended location. If in production mode, the page and the blocks that it's built from must be Published in order for it to be visible.
  5. Run the task to pull from staging: rake glossier:cms:sync

Note: A webhook is setup in Contentful to make a POST request to api/contentful/entries that will expire the cache based on the content type id.

Emails with Contentful

Email content is hosted in Contentful under the Content Model Email and rendered via the Render Email Service. An admin can adjust copy in both the subject and the body of the email with HTML, inline CSS and Mustache templating (to pass in dynamic variables).

Email templates may be previewed locally via /rails/mailers in the browser.

A/B Testing with Contentful and Split Gem

A/B testing functions are located inside the SplitExperiment service:

If you're working on a PR introducing a new A/B test or modifying an existing one, please assign it the label Introduces AB Testing to make sure we don't lose track of it:✓&q=label%3A"Introduces AB Testing" .

Start by creating an Experiment: An Experiment contains, at least, version1 and version2 but can have as many versions as needed. In most cases, our tests are using version1 as the already existing version of the test. version1 will then be called control, and version2 will be called variant (different from the original behaviour).

To add a new experiment, you need to list it in config/experiments.yml

    - control:version1
    - variant:version2
  resettable: false

(You can find more on the Split README)

Once the experiment is set up, you can call it in SplitExperiment:

def experiment_name
    @experiment_name ||= ab_test(:experiment_name)

Then the necessary HTML structure, layouting and styles are created in the Rails template to handle and show the content you created from your experiment.

The Split gem will handle switching between version 1 and version 2 of your experiment for the users. The A/B Testing result can be accessed by going to once identified as an administrator through your email address.

Once your test is set inside SplitExperiment, you can call it through controllers by using @experiments.name_of_you_experiment. This will return you the value of the variant decided by Split.

To then track the experiment, we are using a function located in the resource file for Analytics.'experiment_name' ,experiment_variant)

Unless the tracking is general, and can accept multiple experiments, it is preferable to hardcode the experiment_name to make sure we keep track of it in the future.

You may call the Experiment Viewed tracking event with the gloSegmentExperiment directive:

glo-segment-experiment experiment-name='name_of_the_experiment' experiment-variant='value_of_the_variant'

You can also request the tracking to be triggered only on click by using the attribute track-on-click="" on the glo-segment-experiment element.

An Experiment shouldn't start without a tracking set-up

Before deploying an experiment, make sure to follow these sanity checks:


Tax is automatically calculated via Avatax by Avalara. At times, their API has proven to be unstable. To disable tax calculation site-wide, without require a deployment, add a new config with the following:

SpreeAvatax::Config.create!(enabled: false, timeout: SpreeAvatax::Config::DEFAULT_TIMEOUT)

To re-enable tax calculation, add the following config (via the console):

SpreeAvatax::Config.create!(enabled: true, timeout: SpreeAvatax::Config::DEFAULT_TIMEOUT)

Rep Program

Users that are promoted to reps (via the Spree::Role or rep) will receive credit for orders placed by user's that shop with them.

Steps to create a Glossier Rep:

  1. Add the rep role tho their account in Solidus Admin
  2. Create the rep in Contentful
  3. Add the rep's Contentful slug to their account in the Solidus Admin
  4. Invite the rep to connect with Stripe. The link to do so is available from their account page once the rep role is assigned.

Creating new Contentful spaces based off the Production one

The entire Production space content (or any other space) can be duplicated in another space via the Contentful Export and Import in the command line.

To export a space:

  1. npm install -g contentful-export
  2. contentful-export --space-id spaceID --management-token managementToken, and replace spaceId and managementToken with their proper values.

To import a space:

  1. npm install -g contentful-import
  2. contentful-import --space-id spaceID --management-token managementToken --content-file exported-file.json, and replace the spaceId, managementToken and exported-file.json with their proper values.

API Rate Limit warnings are expected, but if the exporter/importer are unable to progress, contact a PM about requesting an increase from Contentful.

Accessing the Glossier (Staging) space in Contentful

This can be done by replacing the values for CONTENTFUL_ACCESS_TOKEN and CONTENTFUL_SPACE_ID with the ones found in the glossier-boxen repo

Testing on Staging

Rep account / cocococo12 This account can be used to access the Stripe Connect dashboard as well as a test account on glossier

Test URL

Retail Implementation Detail

Required configuration

In order to make the Shopify order import work the following has to be done:

  1. Create a new PaymentMethod with the Spree::Gateway::ShopifyGateway
  2. Create a new Tax Category for Shopify
  3. Create a new Tax Rate for Shopify with the Shopify Tax Category
    • Make sure the tax rate is set to 0 and has the proper country
  4. For the Shipping Method, select the newly created Shopify Tax Category

Sync with server

We are importing Shopify orders to Solidus via webhooks event. The server you which to sync at must be configured in the Shopify settings under Notification and at the bottom Webhooks.

EVENT : Order fulfillment
URL : https://DOMAIN/retail/shopify/hooks/order

If you which test locally, I suggest you use ngrok

Export products

In order to do the first wave of exporting the products from Solidus to Shopify, you need to run the following task: bundle exec rake shopify:export_products. After that, all the update/create/destroy are managed from in-within Glossier.

Update the Variant stocks

By default, we set the variant stock to 1. In order to copy the Solidus stock items to Shopify, the following task has to be run: bundle exec rake shopify:update_variants_stock

Backfill existing objects

If we have multiple server running on the same Shopify instance and that all the product are already exported, the following task has to be run: bundle exec rake shopify:backfill_objects.

That will link the Shopify object to the Solidus object in your database.

NOTE: If you were to re-export them, you would lose the association on original server so the Solidus object wouldn't know what Shopify object it's linked with

Set dummy stock count

Since the implementation relies heavily on the stock-location POPUP and that by default the quantity is set to 0, you may want to run that task bundle exec rake stock:update_stocks_for_popup_location in order to ensure that you have proper quantity before updating the variant stocks to Shopify.

Digital Products

Digital products are supported (essentially the gift card for now). In order to mark a product as digital, just assign the digital product property to it. Gift Cards are also currently assigned the Digital shipping category, and other digital products should replicate this pattern.

Algolia Search

Algolia is used to drive product search.

In order to feed Algolia as of now, a json file is created, and uploaded to Algolia via the dashboard.

There are 2 ways to generate the file:

  • through the Algolia Search link under the Products tab in the admin
  • through a rake task bundle exec rake algolia_search:data_dump (this will be hard to use on heroku due to ephemeral file system)

Namespace structure

  • AlgoliaSearch::Data::Export should be used in the future when we implement the API
  • AlgoliaSearch::Data::Indexshould be used in the future when we implement the indexing

Other objects relating to Algolia can live in the AlgoliaSearch namespace.

Fields currently loaded from Contentful:

name in template field name in Contenful
keywords keywords
body description
tagline tagline
subtitle subtitle
description description
how_to_use how_to_use
benefits_html benefits_html
key_ingredients key_ingredients
all_ingredients all_ingredients
claims claims

Fields coming from Rails / Spree

name in template field name in model
objectID id
title name
image_url slider_first_image
url coming from product_url helper

Currently missing fields:

Gift Cards

Glossier offers gift cards, coming in two types:

  • physical gift cards (phased out for the moment)
  • virtual gift cards

Virtual gift cards can be delivered via two vehicles:

  • printable
  • emailable

Printable Gift Cards

The card is generated as a PDF and sent along the confirmation order. The virtual_delivery_method field on the Spree::VirtualGiftCard model must be equal to print

Emailable Gift Cards

The card is delivered by email on the date chosen by the user. The virtual_delivery_method field on the Spree::VirtualGiftCard model must be equal to email or be nil. This is the default behavior.

Heroku Review apps

When a PR is opened, a dedicated staging environment is created for it on Heroku. The URL to access the site will be added to the PR as soon as the application is provisioned. The application contains the product catalog and a default admin user: / beauty123

Access Restriction on Staging & Review apps

Access to any application running in a staging environment is restricted by IP address. The Montreal and New York offices have been whitelisted and additional IPs can be appended to the WHITELISTED_IPS env variable (on Heroku or C66)

How to update the database on Barney (, which tracks master)

  1. Start with the most recent production snapshot:

rake bootstrap:restore_from_snapshot

  1. Remove sensitive information from the snapshot:

rake bootstrap:sanitize_production_db

  1. Send the sanitized DB to Barney (

heroku pg:reset --app glossier --confirm glossier DATABASE_URL

heroku pg:push glossier-v2_development DATABASE_URL --app glossier


We need to ensure the website is Level A compatible.

  1. Every inputs needs to have a Label associated. If in the design we don't want that label to be visible, we can add the class .visibility-hidden to it. A label needs a for value associated with a matching id value. Ex:
<label for="firstnameLabel" class="visibility-hidden">First Name</label>
<input type="text" id="firstnameLabel" name="firstname" />

Current Tags

  • 2.0.0                                ...           latest (2 years ago)

1 Versions

  • 2.0.0                                ...           2 years ago
Maintainers (1)
Today 0
This Week 0
This Month 0
Last Day 0
Last Week 0
Last Month 5
Dependencies (72)
Dev Dependencies (0)
Dependents (0)

Copyright 2014 - 2017 © |