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.
To start, you need a couple of tools that will help you integrate as fast as possible:
- Khaos (
npm i -g khaos
) - kahos-segmentio-integration (
khaos install segmentio/khaos-segmentio-integration integration
)
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
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'])
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.
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.
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)
.
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);
});
});
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));
};
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;
};
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');
});
});
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);
});
You can run your integration tests just by passing it's title to GREP
env variable.
$ GREP=MyIntegration make test
- 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 [email protected] (teammates: put them in Meldium)
You can now submit the PR for review :D