Omnibug wouldn't be here today without community contributions. We plan to keep Omnibug open sourced for the foreseeable future, and depend on volunteer's time to keep Omnibug running.
Find an issue? That's not good! Here's a list of information that we needed when opening a Github issue:
- The browser name & version
- OS name & version
- Version of Omnibug you're running (clicking on "More" next to Omnibug in Firefox will show this)
- The issue you're having
- A way to replicate it (ideally a page we can access)
To do any work, fork the original repository, and clone it locally. Then, install via yarn:
$ yarn install
The installation process will download and install all of the requirements needed to build and develop Omnibug, as well as run an initial build so you can immediately start using the development version.
Once installed on your machine, you can install as a developer / local version in Chrome and Firefox. You only need to
install them once within the respective browser. If you make any updates to the codebase (e.g. yarn run build
), you will
need to click the refresh button within the browser's extension page. This prevents you from seeing the omnibug.io/installed
page multiple times.
To install the local version in Chrome, go to the chrome://extensions/ settings, then be sure "Developer Mode"
is checked. Once developer mode is enabled, select "Load unpacked extension..." and point to the /platform/chromium/manifest.json
file. Learn more at Google's docs.
To install the local version in Firefox, go to the [about:addons](Add-ons Manager), then click the settings/gear icon.
From there, select "Debug Add-ons", and then select "Load Temporary Add-on" and point to the /platform/firefox/manifest.json
file. Learn more at Mozilla's docs.
To install the local version in Edge, go to the edge://extensions/ settings, then be sure "Developer Mode"
is checked. Once developer mode is enabled, select "Load unpacked extension..." and point to the /platform/chromium/manifest.json
file. Learn more at Microsoft's docs.
We use ESLint to ensure a consistent code style. Here are some basic guidelines though:
- Since we only support the latest few versions of Chrome and Firefox, we can safely use most of the ES6 features. Please consider using these instead of outdated methods, as the ES6 version will provider a cleaner and faster code base.
- Use 4 spaces for 1 tab
- Any function, method, or class should have be declared on a it's own line
- A JSDoc comment should be included for each function, method, or class
- Use double quotes or template syntax (backticks) where possible
- if / else statements should (always) have opening & closing brackets inline
- Run
yarn run lint
to view issues, andyarn run lint:fix
to fix them. When you build, ESLint will run in the background and fail the build if there are errors
A provider is simply a marketing technology that exists in Omnibug. This could be a tool for analytics, re-marketing, UX-testing, etc. You can find a full list of providers in our source code. It is recommended to review a few existing providers to understand how providers can be setup.
Every provider extends the BaseProvider
class.
The bare minimum requirement is to set the constructor()
method, all other properties do not need to
override the BaseProvider
implementation. However, it is recommended to extend several properties and methods to allow
for full customization and create a better user experience within Omnibug.
When working with providers, it is important to understand how we define a parameter. A parameter can come from multiple locations:
- A URL parameter, e.g.
foo
inhttps://omnibug.io/?foo=abc123
- A parameter passed in the POST request (see: parsePostData())
- A custom value you've passed via handleQueryParam() or handleCustom()
No matter where the parameter value comes from, the syntax remains the same:
{
"key": "Parameter key",
"name": "Friendly Parameter Name",
"group": "Group ID",
"hidden": false,
"value": "the value passed to the provider for this parameter"
}
key
should be the unique identifier to the parameter. If from a URL or POST data, it should be the parameter's key. If the parameter is dynamically created, please be sure to create a unique identifier.field
should be the friendly name of the parameter, which is shown in the interface to the usergroup
should be thekey
of the group you want this parameter to be grouped into, (see: get groups())value
should be the value of the parameter, as passed to the providerhidden
can be set totrue
to hide this parameter from the interface. This is useful for times when want to further manipulate the parameter in thehandleQueryParam
orhandleCustom
methods, and can be omitted or set tofalse
to be shown.
These methods are set within BaseProvider
and can be extended as needed.
The constructor()
method sets 4 required fields, as noted below. You should always have a constructor()
method in
your provider.
this._key
should be set to a unique identifier for the provider. This is usually the provider's name, but with only alphanumeric characters, forced to uppercase.this._pattern
should be a RegExp pattern that matches the URL request of the provider. This should be as specific as possible to prevent false matches, however it should be flexible enough for all of the various types of requests the provider sends. This RegExp matches against the full URL, but does not match against any POST data.this._name
should be the provider's official name.this._type
should be one of the keys found inBaseProvider
'stype()
method, e.g.analytics
ormarketing
. Should your provider not fall into any of these categories, please suggest a new type within your pull request, which will be added to theBaseProvider
class.
get columnMapping()
returns an object used for mapping values to the columns on the Omnibug overview screen. Right now,
there are 2 types of columns that can be mapped:
account
, which is the account / client ID that is used by the provider.requestType
, which is the type of request that is sent to the provider. This is what shows in the badges in the Omnibug overview screen. Page view requests should match the words "page view"
get groups()
should return an array of objects that describe the groups shown within the Omnibug interface for this provider.
[
{
"key": "products",
"name": "Products"
},
{
"key": "page",
"name": "Page Data"
}
]
Any parameter that does not have a group set will be displayed under the Other
group.
get keys()
returns an object used for to map raw parameter keys to friendly names and groupings. The name
is the
friendly version of the parameter's name (used for the field
property), and the group should be the ID for the group
you want the parameter to be in (see: get groups()).
{
"foo": {
"name": "The foobar value",
"group": "page"
},
"pids": {
"name": "Products Displayed",
"group": "products"
}
}
parseUrl(rawUrl, postData = "")
will parse the URL (and POST data, if applicable) and return a friendly JSON object that
is then used to display the request in the Omnibug interface. For a majority of providers, you will not need to override
this method and it can be omitted from your provider's class.
parsePostData(postData = "")
will take any POST data that is sent to the provider and parse it out into an array of
key / value pairs to be parsed. If your provider is not sending any POST data, or the POST data is either a flat JSON object
or sent as a form-data style (key=value&key2=value2
), you do not need to override this method.
However, if your provider is sending another format or you want better control over the parameters that are returned, you can override this method. Your method should return an array of key/value arrays, e.g.:
[
["key1", "value1"],
["key2", "value2"],
["key3", "value3"]
]
handleQueryParam(name, value)
parses each parameter key and value (from either the URL or parsed POST data), and
returns the human-readable object. By default, handleQueryParam
will check if name
is within the object returned
by the get keys()
method. If it is, then the value is assigned and the parameter object is returned. If it is not,
then it will show up under the Other
group with the raw parameter key shown as the name.
There are times that you may want to override this method. You might have a set of parameters that match a
certain naming schema but there are a lot of them and it doesn't make sense to list them all out. For example, your
provider has custom dimensions that are passed in the cd1
, cd2
, cd3
, etc. format. Instead of listing each of those
possibilities out in the get keys()
method, you could add logic here to see if the name matches your format and then
return an object with a dynamically generated name.
It is recommended that you still call the inherited method for when the parameters do not match your logic.
Using the custom dimension example above, here is a code sample of what you might have:
handleQueryParam(name, value)
{
let result = {};
// Check if the parameter name/key matches our format (cd1, cd2, etc.)
if(/^cd(\d+)$/.test(name))
{
// The parameter matches our format, so let's dynamically create the field name
result = {
"key": name,
"field": "Custom Dimension " + RegExp.$1,
"value": value,
"group": "dimensions"
};
}
else
{
// It does not match, so be sure to call the parent method!
result = super.handleQueryParam(name, value);
}
return result;
}
It is important to note that you should only return one parameter at a time from this.
handleCustom(url, params)
allows you to add additional parameters based off the URL or previously identified parameters.
For example, the Account ID for a provider might exist as the sub-domain, but is not populated in any other place. You
should return an array of parameter objects.
Note that the url
argument is a URL object and the params
argument is a URLSearchParams object that contains all
of the URL parameters, plus the parameters from any parsed POST data.
Below is an example of grabbing the account ID from the sub-domain and returning it via the handleCustom
method:
handleCustom(url, params)
{
let hostname = url.hostname.split('.'),
result = {
"key": "accountID",
"field": "Account ID",
"group": "general",
"value": hostname[0]
};
return [result];
}
All providers should have a test suite included with their pull request. The following items to test are recommended:
- The provider's
key
,type
,name
, andpattern
are properly set - Verify that the
pattern
works- Check a handful of URLs to ensure that they do match
- Use bogus URLs to verify that it is not greedy
- Ensure that OmnibugProvider returns your provider for one or more of the test URLs
- Check to verify your provider returns any data
- Check for properly parsed POST data
- Check for any dynamic parameters
- As set in the
handleQueryParam
method - And as set in the
handleCustom
method
- As set in the
Testing is done via AVA and can be executed by running the following command:
$ yarn run test
This will run the build process to build the providers and core Omnibug classes, and then show the result of the test suite. Any pull requests or pushes to the repository will trigger Travis CI and Coveralls to verify that all tests are passing and Omnibug has good test coverage across the providers and core classes.
Since each browser requires slightly different manifests & some need polyfills, Grunt takes care of building browser-specific code from the generic code base. To build for all browsers:
$ yarn run build
This will output to the /platform/$BROWSER
folders, as well as output a .zip
for distribution in the /build/$BROWSER
folders.
If you get stuck or this documentation isn't clear, there are multiple ways to get help:
- The Omnibug Discord group
- Github issues with a "question" tag
- Tweeting @Omnibug with your question(s)