Skip to content

Instantly share code, notes, and snippets.

@gjohnson
Created November 21, 2014 22:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gjohnson/f52b8607c0c8f9b23098 to your computer and use it in GitHub Desktop.
Save gjohnson/f52b8607c0c8f9b23098 to your computer and use it in GitHub Desktop.

Contributing

We're huge fans of open-source, and absolutely we love getting good contributions to segmentio/integrations! These docs will tell you everything you need to know about how to add your own integration to the library with a pull request, so we can merge it in for everyone else to use.

Getting Setup

To start, you need a couple of tools that will help you integrate as fast as possible:

Once you have those tools installed, cd into your fork and run:

$ khaos create integration lib/<slug>

Khaos will ask you a couple of question and create the integration skeleton for you! See our tracking API to check what each method does.

  • identify - does your integration support the identify method ?
  • track - does your integration support the track method ?
  • page - does your integration support the page method ?
  • screen - does your integration support the screen method ?
  • group - does your integration support the group method ?
  • alias - does your integration support the alias method ?
  • mapper - the mapper helps you map raw msg to something the integration API will accept.
  • docs - docs link
  • endpoint - api endpoint

khaos

Channels

We currently have 3 channels, add all channels you want your integration to support. Note that if your integration is bundled in analytics.js you should omit the client channel.

channels(['server', 'mobile', 'client'])

Settings

When a Segment user first turns on the integration, they'll be asked to enter settings specific to the integration.

These are typically fields like:

  • API Keys
  • whether to track pageviews
  • mappings from Segment events to integration specific events

As a good rule of thumb, any option in the integration should be a separate key in the settings object. These will be stored as this.settings on the integration itself.

Segment teammates: settings should match up with those found in the integration-metadata repo.

Validation

You can ensure the settings has key, or the message has path easily using ensure().

.ensure('settings.apiKey')
.ensure('message.userId')
.ensure('message.context.ip')

The above example will ensure that the user has an apiKey and the message has an userId and ip, otherwise the integration will reject the call automatically.

Dynamic validation.

You can dymanically decide to reject a message by providing a function to .ensure().

/**
 * Mixpanel requires an `.apiKey` on `track`
 * if the message is older than 5 days.
 */

Mixpanel.ensure(function(msg, settings){
  if (settings.apiKey) return;
  if ('track' != msg.type()) return;
  if (!shouldImport(msg)) return;
  return this.invalid('.apiKey is required if "track" message is older than 5 days.');
});

Note that for invalid settings you must use .invalid(msg), for invalid messages you must use .reject(msg).

Validation tests

The generated integration will contain some tests.

it('should have the correct settings', function(){
  test
    .name('My Integration')
    .channels(['server', 'mobile', 'client'])
    .ensure('settings.apiKey')
    .ensure('message.userId')
    .ensure('message.context.ip')
    .retries(2);
});

describe('.validate()', function() {
  var msg;

  beforeEach(function(){
    msg = {
      userId: 'user-id',
      type: 'identify',
      context: { ip: '0.0.0.0' }
    };
  });

  it('should not be valid without an api key', function(){
    delete settings.apiKey;
    test.invalid(msg, settings);
  });

  it('should not be valid without a userId', function(){
    delete msg.userId;
    test.invalid(msg, settings);
  });

  it('should not be valid without a context.ip', function(){
    delete msg.context.ip;
    test.invalid(msg, settings);
  });

  it('should be valid with complete message and settings', function(){
    test.valid(msg, settings);
  });
});

Mapper

To see what methods are available on msg, check out facade.

A mapper should receive a raw msg of Facade and return a payload, the payload is then passed to the method in order to send it out:

Suppose the API only accepts $user_id and $name You can add a mapper.identify that does the mappings for you.

// my-integration/mapper.js
exports.identify = function(msg, settings){
  return {
    $user_id: msg.userId(),
    $name: msg.name(),
  }
};

Then identify will receive { $user_id: '811a5bdd', $name: 'john doe' } to send.

// my-integration/index.js
MyIntegration.prototype.identify = function(payload, fn){
  return this
    .post()
    .auth(this.settings.apiKey)
    .type('json')
    .send(payload)
    .end(this.handle(fn));
};

Dynamic Mapper

It's useful to dynamically call the mapper sometimes and not have it do it's magic behind the scenes.

To call the mapper directly make sure you remove .mapper(mapper) from the integration generator, that way identify() will receive the message and can just call / not call the mapper.

var MyIntegration = module.exports = integration('My Integration')
  .endpoint('https://api.my-integration.com/v1')
  .ensure('settings.apiKey')
  .ensure('message.userId')
  .mapper(mapper)
  .retries(2);
var MyIntegration = module.exports = integration('My Integration')
  .endpoint('https://api.my-integration.com/v1')
  .ensure('settings.apiKey')
  .ensure('message.userId')
  .retries(2);
MyIntegration.prototype.identify = function(msg, fn){
  var json = mapper.identify(msg);
  return this;
};

Mapper tests

The generated integration will contain test/fixtures/*.json, Each json file contains input and output, where input is just the raw message you expect to receive, and output is the raw payload you expect to be sent.

to test them out, simply call test.maps('my-fixture-name'):

  describe('identify', function(){
    it('should map basic identify', function(){
      test.maps('identify-basic');
    });
  });

Request tests

You can reuse the generated fixtures to test requests as well:

  it('should send basic identify', function(done){
    var json = test.fixture('identify-basic');
    test
      .identify(json.input)
      .sends(json.output)
      .expects(200)
      .end(done);
  });

Running tests

You can run your integration tests just by passing it's title to GREP env variable.

$ GREP=MyIntegration make test

Checklist

  • When making a fix, first write a breaking test for it
  • Make sure your tests are complete and passing
  • Make sure you follow the style of the rest of our integrations
  • Run make lint and make sure that it doesn't complain
  • Make sure new intgration tests have fixtures
  • Send demo account credentials to integrations@segment.io (teammates: put them in Meldium)

That's it!

You can now submit the PR for review :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment